diff --git a/.gitignore b/.gitignore index 53904978..3b48da8d 100644 --- a/.gitignore +++ b/.gitignore @@ -6,7 +6,7 @@ xcuserdata/ ## DS_Store -.DS_Store* +.DS_Store ## compatibility with Xcode 8 and earlier (ignoring not required starting Xcode 9) *.xcscmblueprint diff --git a/.swiftlint.yml b/.swiftlint.yml new file mode 100644 index 00000000..a83a01ec --- /dev/null +++ b/.swiftlint.yml @@ -0,0 +1,137 @@ +reporter: "xcode" # reporter type (xcode, json, csv, checkstyle, junit, html, emoji) + +excluded: # paths to ignore during linting. Takes precedence over `included`. + - Carthage + - Pods + - SwiftLint/Common/3rdPartyLib +disabled_rules: # rule identifiers to exclude from running + - trailing_whitespace +# - force_cast +# - force_unwrapping +# - force_try + - empty_enum_arguments +# - overridden_super_call +# - sorted_imports +# - vertical_whitespace + - inclusive_language + - trailing_closure + - file_name + +opt_in_rules: # some rules are only opt-in +# - missing_docs + - yoda_condition # Default configuration: warning + - empty_count # Default configuration: error, only_after_dot: false + - empty_string # Default configuration: warning + - closure_end_indentation # Default configuration: warning + - closure_spacing # Default configuration: warning + - explicit_init # Default configuration: warning + - first_where # Default configuration: warning + - number_separator # Default configuration: warning, minimum_length: 0 + - explicit_failure_calls + - fatal_error_message +# - extension_access_modifier +# - implicitly_unwrapped_optional +# - operator_usage_whitespace + - vertical_parameter_alignment_on_call + - multiline_parameters +# - multiple_empty_lines # Defined into custom roles + - nesting + - file_name + - convenience_type + - modifier_order + + - private_outlet + - prohibited_super_call + - protocol_property_accessors_order + - redundant_nil_coalescing + - syntactic_sugar + - comments_space + - conditional_returns_on_newline + +conditional_returns_on_newline: + if_only: true + +line_length: + warning: 200 + error: 250 + ignores_function_declarations: true + ignores_comments: true + ignores_urls: true + +function_body_length: + warning: 300 + error: 500 + +function_parameter_count: + warning: 6 + error: 8 + +type_name: # class name + min_length: 3 + max_length: + warning: 60 + error: 80 + +type_body_length: + warning: 300 + error: 500 + +file_length: + warning: 1000 + error: 1500 + ignore_comment_only_lines: true + +identifier_name: # Variable name + allowed_symbols: "_" + min_length: 1 + max_length: + warning: 60 + error: 80 + excluded: + - id + - URL + - GlobalAPIKey + +large_tuple: + warning: 4 + error: 5 + +private_outlet: + allow_private_set: true + +#nesting: +# type_level: +# warning: 3 +# error: 6 +# statement_level: +# warning: 5 +# error: 10 + +number_separator: + minimum_length: 8 + +#cyclomatic complexity below 4 is considered good; +#cyclomatic complexity between 5 and 7 is considered medium complexity, +#between 8 and 10 is high complexity, +#and above that is extreme complexity. +cyclomatic_complexity: + ignores_case_statements: true + warning: 7 + error: 11 + +custom_rules: + comments_space: # From https://github.com/brandenr/swiftlintconfig + name: "Space After Comment" + regex: '(^ *//\w+)' + message: "There should be a space after //" + severity: warning + explicit_failure_calls: + name: “Avoid asserting ‘false’” + regex: ‘((assert|precondition)\(false)’ + message: “Use assertionFailure() or preconditionFailure() instead.” + severity: warning + multiple_empty_lines: + name: "Multiple Empty Lines" + regex: '((?:\s*\n){3,})' + message: "There are too many line breaks" + severity: error diff --git a/Docs/93-style-info-card.png b/Docs/93-style-info-card.png new file mode 100644 index 00000000..d7fddde5 Binary files /dev/null and b/Docs/93-style-info-card.png differ diff --git a/Docs/94-style-buttonlog.png b/Docs/94-style-buttonlog.png new file mode 100644 index 00000000..84398d8c Binary files /dev/null and b/Docs/94-style-buttonlog.png differ diff --git a/Docs/95-style-checklist.png b/Docs/95-style-checklist.png new file mode 100644 index 00000000..b5ca4261 Binary files /dev/null and b/Docs/95-style-checklist.png differ diff --git a/Docs/96-style-numeric.png b/Docs/96-style-numeric.png new file mode 100644 index 00000000..5daa5fbb Binary files /dev/null and b/Docs/96-style-numeric.png differ diff --git a/Docs/97-style-env-example.png b/Docs/97-style-env-example.png new file mode 100644 index 00000000..77bc9627 Binary files /dev/null and b/Docs/97-style-env-example.png differ diff --git a/Docs/show-api-key.png b/Docs/api-key.png similarity index 100% rename from Docs/show-api-key.png rename to Docs/api-key.png diff --git a/Docs/biometric_authentication.png b/Docs/biometric_authenticaion.png similarity index 100% rename from Docs/biometric_authentication.png rename to Docs/biometric_authenticaion.png diff --git a/Docs/show-api-key-details.png b/Docs/client-admin.png similarity index 100% rename from Docs/show-api-key-details.png rename to Docs/client-admin.png diff --git a/Docs/password_less_01.png b/Docs/passowrd_less_01.png similarity index 100% rename from Docs/password_less_01.png rename to Docs/passowrd_less_01.png diff --git a/Docs/password_less_02.png b/Docs/passowrd_less_02.png similarity index 100% rename from Docs/password_less_02.png rename to Docs/passowrd_less_02.png diff --git a/OTFMagicBox Watch Watch App/CareKitListView.swift b/OTFMagicBox Watch Watch App/CareKitListView.swift deleted file mode 100644 index 45ec2c16..00000000 --- a/OTFMagicBox Watch Watch App/CareKitListView.swift +++ /dev/null @@ -1,157 +0,0 @@ -// -// CareKitListView.swift -// OTFMagicBox Watch Watch App -// -// Created by Tomas Martins on 13/09/23. -// - -import SwiftUI -import Contacts -import OTFCareKit -import OTFCareKitUI -import OTFCareKitStore - -struct CareKitListView: View { - @ObservedObject var storeManager: OCKStoreManager = .shared - @State var tasks: [OCKTask] = [] - - var body: some View { - List { - if tasks.isEmpty { - emptyState - } else { - ForEach(tasks, id: \.id) { task in - SimpleTaskView(isComplete: false) { - Text(task.title ?? "No title for task") - } - } - } - } - .task { - await fetchTasksAsync() - } - .refreshable { - await fetchTasksAsync() - } - } - - func fetchTasksAsync() async { - await withCheckedContinuation { continuation in - Task { - storeManager.coreDataStore.populateSampleData() - storeManager.coreDataStore.fetchTasks { result in - switch result { - case .success(let success): - self.tasks = success - continuation.resume() - case .failure(let error): - dump(error) - self.tasks = [] - continuation.resume() - } - } - } - } - } - - var emptyState: some View { - SimpleTaskView(title: Text(Constants.CustomiseStrings.noTasks), - detail: Text(Constants.CustomiseStrings.noTasksForThisDate), - isComplete: false) - } -} - -struct CareKitListView_Previews: PreviewProvider { - static var previews: some View { - CareKitListView() - } -} - -internal extension OCKStore { - - enum Tasks: String, CaseIterable { - case doxylamine - case nausea - case kegels - } - - enum Contacts: String, CaseIterable { - case jane - case matthew - } - - // Adds tasks and contacts into the store - func populateSampleData() { - - let thisMorning = Calendar.current.startOfDay(for: Date()) - let aFewDaysAgo = Calendar.current.date(byAdding: .day, value: -4, to: thisMorning)! - let beforeBreakfast = Calendar.current.date(byAdding: .hour, value: 8, to: aFewDaysAgo)! - let afterLunch = Calendar.current.date(byAdding: .hour, value: 14, to: aFewDaysAgo)! - - let schedule = OCKSchedule(composing: [ - OCKScheduleElement(start: beforeBreakfast, end: nil, - interval: DateComponents(day: 1)), - - OCKScheduleElement(start: afterLunch, end: nil, - interval: DateComponents(day: 2)) - ]) - - var doxylamine = OCKTask(id: Tasks.doxylamine.rawValue, title: "Take Doxylamine", - carePlanUUID: nil, schedule: schedule) - doxylamine.instructions = "Take 25mg of doxylamine when you experience nausea." - doxylamine.asset = "pills" - let nauseaSchedule = OCKSchedule(composing: [ - OCKScheduleElement(start: beforeBreakfast, end: nil, interval: DateComponents(day: 1), - text: "Anytime throughout the day", targetValues: [], duration: .allDay) - ]) - - var nausea = OCKTask(id: Tasks.nausea.rawValue, title: "Track your nausea", - carePlanUUID: nil, schedule: nauseaSchedule) - nausea.impactsAdherence = false - nausea.instructions = "Tap the button below anytime you experience nausea." - - let kegelElement = OCKScheduleElement(start: beforeBreakfast, end: nil, interval: DateComponents(day: 2)) - let kegelSchedule = OCKSchedule(composing: [kegelElement]) - var kegels = OCKTask(id: Tasks.kegels.rawValue, title: "Kegel Exercises", carePlanUUID: nil, schedule: kegelSchedule) - kegels.impactsAdherence = true - kegels.instructions = "Perform kegel exercies" - - addTasks([nausea, doxylamine, kegels], callbackQueue: .main, completion: nil) - - var contact1 = OCKContact(id: Contacts.jane.rawValue, givenName: "Jane", - familyName: "Daniels", carePlanUUID: nil) - contact1.asset = "JaneDaniels" - contact1.title = "Family Practice Doctor" - contact1.role = "Dr. Daniels is a family practice doctor with 8 years of experience." - contact1.emailAddresses = [OCKLabeledValue(label: CNLabelEmailiCloud, value: "janedaniels@icloud.com")] - contact1.phoneNumbers = [OCKLabeledValue(label: CNLabelWork, value: "(324) 555-7415")] - contact1.messagingNumbers = [OCKLabeledValue(label: CNLabelWork, value: "(324) 555-7415")] - - contact1.address = { - let address = OCKPostalAddress() - address.street = "2598 Reposa Way" - address.city = "San Francisco" - address.state = "CA" - address.postalCode = "94127" - return address - }() - - var contact2 = OCKContact(id: Contacts.matthew.rawValue, givenName: "Matthew", - familyName: "Reiff", carePlanUUID: nil) - contact2.asset = "MatthewReiff" - contact2.title = "OBGYN" - contact2.role = "Dr. Reiff is an OBGYN with 13 years of experience." - contact2.phoneNumbers = [OCKLabeledValue(label: CNLabelWork, value: "(324) 555-7415")] - contact2.messagingNumbers = [OCKLabeledValue(label: CNLabelWork, value: "(324) 555-7415")] - contact2.address = { - let address = OCKPostalAddress() - address.street = "396 El Verano Way" - address.city = "San Francisco" - address.state = "CA" - address.postalCode = "94127" - return address - }() - - addContacts([contact1, contact2]) - } -} diff --git a/OTFMagicBox Watch Watch App/ContentView.swift b/OTFMagicBox Watch Watch App/ContentView.swift deleted file mode 100644 index 67586957..00000000 --- a/OTFMagicBox Watch Watch App/ContentView.swift +++ /dev/null @@ -1,20 +0,0 @@ -// -// ContentView.swift -// OTFMagicBox Watch Watch App -// -// Created by Tomas Martins on 14/08/23. -// - -import SwiftUI - -struct ContentView: View { - var body: some View { - CareKitListView() - } -} - -struct ContentView_Previews: PreviewProvider { - static var previews: some View { - ContentView() - } -} diff --git a/OTFMagicBox Watch Watch App/OTFMagicBox_WatchApp.swift b/OTFMagicBox Watch Watch App/OTFMagicBox_WatchApp.swift deleted file mode 100644 index 3414d1a2..00000000 --- a/OTFMagicBox Watch Watch App/OTFMagicBox_WatchApp.swift +++ /dev/null @@ -1,17 +0,0 @@ -// -// OTFMagicBox_WatchApp.swift -// OTFMagicBox Watch Watch App -// -// Created by Tomas Martins on 14/08/23. -// - -import SwiftUI - -@main -struct OTFMagicBox_Watch_Watch_AppApp: App { - var body: some Scene { - WindowGroup { - ContentView() - } - } -} diff --git a/OTFMagicBox Watch Watch AppTests/OTFMagicBox_Watch_Watch_AppTests.swift b/OTFMagicBox Watch Watch AppTests/OTFMagicBox_Watch_Watch_AppTests.swift deleted file mode 100644 index 5c4d594c..00000000 --- a/OTFMagicBox Watch Watch AppTests/OTFMagicBox_Watch_Watch_AppTests.swift +++ /dev/null @@ -1,36 +0,0 @@ -// -// OTFMagicBox_Watch_Watch_AppTests.swift -// OTFMagicBox Watch Watch AppTests -// -// Created by Tomas Martins on 14/08/23. -// - -import XCTest -@testable import OTFMagicBox_Watch_Watch_App - -final class OTFMagicBox_Watch_Watch_AppTests: XCTestCase { - - override func setUpWithError() throws { - // Put setup code here. This method is called before the invocation of each test method in the class. - } - - override func tearDownWithError() throws { - // Put teardown code here. This method is called after the invocation of each test method in the class. - } - - func testExample() throws { - // This is an example of a functional test case. - // Use XCTAssert and related functions to verify your tests produce the correct results. - // Any test you write for XCTest can be annotated as throws and async. - // Mark your test throws to produce an unexpected failure when your test encounters an uncaught error. - // Tests marked async will run the test method on an arbitrary thread managed by the Swift runtime. - } - - func testPerformanceExample() throws { - // This is an example of a performance test case. - self.measure { - // Put the code you want to measure the time of here. - } - } - -} diff --git a/OTFMagicBox Watch Watch AppUITests/OTFMagicBox_Watch_Watch_AppUITests.swift b/OTFMagicBox Watch Watch AppUITests/OTFMagicBox_Watch_Watch_AppUITests.swift deleted file mode 100644 index e9a496f5..00000000 --- a/OTFMagicBox Watch Watch AppUITests/OTFMagicBox_Watch_Watch_AppUITests.swift +++ /dev/null @@ -1,41 +0,0 @@ -// -// OTFMagicBox_Watch_Watch_AppUITests.swift -// OTFMagicBox Watch Watch AppUITests -// -// Created by Tomas Martins on 14/08/23. -// - -import XCTest - -final class OTFMagicBox_Watch_Watch_AppUITests: XCTestCase { - - override func setUpWithError() throws { - // Put setup code here. This method is called before the invocation of each test method in the class. - - // In UI tests it is usually best to stop immediately when a failure occurs. - continueAfterFailure = false - - // In UI tests it’s important to set the initial state - such as interface orientation - required for your tests before they run. The setUp method is a good place to do this. - } - - override func tearDownWithError() throws { - // Put teardown code here. This method is called after the invocation of each test method in the class. - } - - func testExample() throws { - // UI tests must launch the application that they test. - let app = XCUIApplication() - app.launch() - - // Use XCTAssert and related functions to verify your tests produce the correct results. - } - - func testLaunchPerformance() throws { - if #available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 7.0, *) { - // This measures how long it takes to launch your application. - measure(metrics: [XCTApplicationLaunchMetric()]) { - XCUIApplication().launch() - } - } - } -} diff --git a/OTFMagicBox Watch Watch AppUITests/OTFMagicBox_Watch_Watch_AppUITestsLaunchTests.swift b/OTFMagicBox Watch Watch AppUITests/OTFMagicBox_Watch_Watch_AppUITestsLaunchTests.swift deleted file mode 100644 index 7abb99a2..00000000 --- a/OTFMagicBox Watch Watch AppUITests/OTFMagicBox_Watch_Watch_AppUITestsLaunchTests.swift +++ /dev/null @@ -1,32 +0,0 @@ -// -// OTFMagicBox_Watch_Watch_AppUITestsLaunchTests.swift -// OTFMagicBox Watch Watch AppUITests -// -// Created by Tomas Martins on 14/08/23. -// - -import XCTest - -final class OTFMagicBox_Watch_Watch_AppUITestsLaunchTests: XCTestCase { - - override class var runsForEachTargetApplicationUIConfiguration: Bool { - true - } - - override func setUpWithError() throws { - continueAfterFailure = false - } - - func testLaunch() throws { - let app = XCUIApplication() - app.launch() - - // Insert steps here to perform after app launch but before taking a screenshot, - // such as logging into a test account or navigating somewhere in the app - - let attachment = XCTAttachment(screenshot: app.screenshot()) - attachment.name = "Launch Screen" - attachment.lifetime = .keepAlways - add(attachment) - } -} diff --git a/OTFMagicBox.xcodeproj/project.pbxproj b/OTFMagicBox.xcodeproj/project.pbxproj index 0d8aebaa..aff49b74 100644 --- a/OTFMagicBox.xcodeproj/project.pbxproj +++ b/OTFMagicBox.xcodeproj/project.pbxproj @@ -7,8 +7,6 @@ objects = { /* Begin PBXBuildFile section */ - 04CDFE9A4C5C79E8EACA8709 /* Pods_OTFMagicBox_Watch_Watch_App.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 44835E788FC7C51DB58219C2 /* Pods_OTFMagicBox_Watch_Watch_App.framework */; }; - 06540FA342BE7267771D8AF4 /* Pods_OTFMagicBox.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 55AC9A6EAEB0412B5C7F5D6C /* Pods_OTFMagicBox.framework */; }; 326086F7283E48A300888678 /* DeleteAccountView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 326086F6283E48A300888678 /* DeleteAccountView.swift */; }; 32AAA4A5283396CF00DA0DA8 /* ModuleAppSysParameter.yml in Resources */ = {isa = PBXBuildFile; fileRef = 32AAA4A4283396CF00DA0DA8 /* ModuleAppSysParameter.yml */; }; 32AAA4A728339E7F00DA0DA8 /* ModuleAppYmlReader+DataModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 32AAA4A628339E7F00DA0DA8 /* ModuleAppYmlReader+DataModel.swift */; }; @@ -16,6 +14,7 @@ 32C6DD9828D8A04700E453A4 /* LogoutViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 32C6DD9728D8A04700E453A4 /* LogoutViewModel.swift */; }; 32C6DD9A28D8A06F00E453A4 /* UpdateUserViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 32C6DD9928D8A06F00E453A4 /* UpdateUserViewModel.swift */; }; 32C6DD9C28D8A08700E453A4 /* ChangePasswordViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 32C6DD9B28D8A08700E453A4 /* ChangePasswordViewModel.swift */; }; + 4CF07F71CAF10A608704FCFF /* Pods_OTFMagicBox.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = E86A26F2DE6C8AB4A0B19344 /* Pods_OTFMagicBox.framework */; }; 5A41D023266A9106007CCEB4 /* MainView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5A41D022266A9106007CCEB4 /* MainView.swift */; }; 5A41D034266A91CB007CCEB4 /* TasksUIView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5A41D033266A91CB007CCEB4 /* TasksUIView.swift */; }; 5A41D037266A955C007CCEB4 /* TaskItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5A41D036266A955C007CCEB4 /* TaskItem.swift */; }; @@ -71,18 +70,7 @@ 5AB1808F26BC58DE007AF766 /* HealthRecordStep.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5AB1808E26BC58DE007AF766 /* HealthRecordStep.swift */; }; 7C7EC6792A65912C003B0FFB /* OTFNetworkObserver.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C7EC6782A65912C003B0FFB /* OTFNetworkObserver.swift */; }; 7C979A602A5731C200EBCE2F /* OTFColor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C979A5F2A5731C200EBCE2F /* OTFColor.swift */; }; - 7CA3D2F32A8A43C300FCB048 /* OTFMagicBox_WatchApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7CA3D2F22A8A43C300FCB048 /* OTFMagicBox_WatchApp.swift */; }; - 7CA3D2F52A8A43C300FCB048 /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7CA3D2F42A8A43C300FCB048 /* ContentView.swift */; }; - 7CA3D2F72A8A43C400FCB048 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 7CA3D2F62A8A43C400FCB048 /* Assets.xcassets */; }; - 7CA3D2FA2A8A43C400FCB048 /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 7CA3D2F92A8A43C400FCB048 /* Preview Assets.xcassets */; }; - 7CA3D3042A8A43C400FCB048 /* OTFMagicBox_Watch_Watch_AppTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7CA3D3032A8A43C400FCB048 /* OTFMagicBox_Watch_Watch_AppTests.swift */; }; - 7CA3D30E2A8A43C400FCB048 /* OTFMagicBox_Watch_Watch_AppUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7CA3D30D2A8A43C400FCB048 /* OTFMagicBox_Watch_Watch_AppUITests.swift */; }; - 7CA3D3102A8A43C400FCB048 /* OTFMagicBox_Watch_Watch_AppUITestsLaunchTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7CA3D30F2A8A43C400FCB048 /* OTFMagicBox_Watch_Watch_AppUITestsLaunchTests.swift */; }; - 7CA3D3132A8A43C400FCB048 /* OTFMagicBox Watch Watch App.app in Embed Watch Content */ = {isa = PBXBuildFile; fileRef = 7CA3D2F02A8A43C300FCB048 /* OTFMagicBox Watch Watch App.app */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; - 7CA403032AB20C0F00A3D022 /* CareKitListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7CA403022AB20C0F00A3D022 /* CareKitListView.swift */; }; - 7CD965022AB6AA5E003E9394 /* OCKStoreManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8467619A27DF8A1F00A3207E /* OCKStoreManager.swift */; }; - 7CD965032AB6B784003E9394 /* Constants.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5AADFD4F264E9A880069FEF7 /* Constants.swift */; }; - 7CEB1FC52A8A5A83002918FE /* (null) in Embed Frameworks */ = {isa = PBXBuildFile; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; + 84146ED82BD14F4B007D768C /* TaskListRow+Extras.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84146ED72BD14F4B007D768C /* TaskListRow+Extras.swift */; }; 841CA60A275F719100C6861D /* ViewDidLoadModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 841CA609275F719000C6861D /* ViewDidLoadModifier.swift */; }; 841CA60C275F827300C6861D /* CareKitStoreManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 841CA60B275F827300C6861D /* CareKitStoreManager.swift */; }; 841FEFBB274E5CE100698892 /* SSEAndSyncManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 841FEFBA274E5CE000698892 /* SSEAndSyncManager.swift */; }; @@ -126,10 +114,26 @@ 849989DE2AD7CB1C009CBA41 /* PDFViewer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 849989DD2AD7CB1B009CBA41 /* PDFViewer.swift */; }; 849989E02AD7CB25009CBA41 /* PDFKitRepresentedView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 849989DF2AD7CB25009CBA41 /* PDFKitRepresentedView.swift */; }; 849989E22AD7CC67009CBA41 /* UploadDocumentManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 849989E12AD7CC67009CBA41 /* UploadDocumentManager.swift */; }; - 849989E42AD7CD53009CBA41 /* ActivityLoader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 849989E32AD7CD53009CBA41 /* ActivityLoader.swift */; }; + 849989E42AD7CD53009CBA41 /* LoaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 849989E32AD7CD53009CBA41 /* LoaderView.swift */; }; 849989E62AD7D182009CBA41 /* Extension+Array.swift in Sources */ = {isa = PBXBuildFile; fileRef = 849989E52AD7D182009CBA41 /* Extension+Array.swift */; }; 84A1758B2750A035001587BD /* Extention+Notification.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84A1758A2750A035001587BD /* Extention+Notification.swift */; }; + 84EEAD042BBCA6D100414955 /* OTFStyleColorStyler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84EEAD012BBCA6D100414955 /* OTFStyleColorStyler.swift */; }; + 84EEAD052BBCA6D100414955 /* OTFStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84EEAD022BBCA6D100414955 /* OTFStyle.swift */; }; + 84EEAD062BBCA6D100414955 /* OTFYamlStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84EEAD032BBCA6D100414955 /* OTFYamlStyle.swift */; }; + 84F43F502B4D6A4A009DFFF8 /* OTFMagicBoxWatch.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84F43F4F2B4D6A4A009DFFF8 /* OTFMagicBoxWatch.swift */; }; + 84F43F542B4D6A4C009DFFF8 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 84F43F532B4D6A4C009DFFF8 /* Assets.xcassets */; }; + 84F43F572B4D6A4C009DFFF8 /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 84F43F562B4D6A4C009DFFF8 /* Preview Assets.xcassets */; }; + 84F43F5A2B4D6A4C009DFFF8 /* OTFMagicBoxWatch.app in Embed Watch Content */ = {isa = PBXBuildFile; fileRef = 84F43F4D2B4D6A4A009DFFF8 /* OTFMagicBoxWatch.app */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; 9004A9222AD7DD390084622A /* HelpView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9004A9212AD7DD390084622A /* HelpView.swift */; }; + 901700F42B63B856006C611C /* ActivityIndicatorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 901700F32B63B856006C611C /* ActivityIndicatorView.swift */; }; + 902E18A62AE8F0A000F694F2 /* OTFFont.swift in Sources */ = {isa = PBXBuildFile; fileRef = 902E18A52AE8F0A000F694F2 /* OTFFont.swift */; }; + 90C79E452B4DCE120020D20F /* OTFCareKitStoreManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 90C79E3F2B4DCE110020D20F /* OTFCareKitStoreManager.swift */; }; + 90C79E462B4DCE120020D20F /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 90C79E402B4DCE110020D20F /* ContentView.swift */; }; + 90C79E472B4DCE120020D20F /* CareKitListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 90C79E412B4DCE120020D20F /* CareKitListView.swift */; }; + 90C79E482B4DCE120020D20F /* Extension+Notification.swift in Sources */ = {isa = PBXBuildFile; fileRef = 90C79E422B4DCE120020D20F /* Extension+Notification.swift */; }; + 90C79E492B4DCE120020D20F /* Extension+OCKTask.swift in Sources */ = {isa = PBXBuildFile; fileRef = 90C79E432B4DCE120020D20F /* Extension+OCKTask.swift */; }; + 90C79E4A2B4DCE120020D20F /* SessionManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 90C79E442B4DCE120020D20F /* SessionManager.swift */; }; + 90C79E4C2B4E862A0020D20F /* WatchStoreService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 90C79E4B2B4E862A0020D20F /* WatchStoreService.swift */; }; A8C0556B296D61770098D1FB /* Extention+ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = A8C0556A296D61770098D1FB /* Extention+ViewController.swift */; }; A8FCD2AA2A4C6EF7006E0B6E /* KeychainCloudManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = A8FCD2A92A4C6EF7006E0B6E /* KeychainCloudManager.swift */; }; A8FCD2AC2A4C6F0D006E0B6E /* LocalAuthentication.swift in Sources */ = {isa = PBXBuildFile; fileRef = A8FCD2AB2A4C6F0D006E0B6E /* LocalAuthentication.swift */; }; @@ -139,6 +143,7 @@ CE482CFB262C954A00C0A2D5 /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE482CFA262C954A00C0A2D5 /* ContentView.swift */; }; CE482CFD262C954C00C0A2D5 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = CE482CFC262C954C00C0A2D5 /* Assets.xcassets */; }; CE482D03262C954C00C0A2D5 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = CE482D01262C954C00C0A2D5 /* LaunchScreen.storyboard */; }; + FFB3790929612305437E0E08 /* Pods_OTFMagicBoxWatch.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 2DC70EC922D0E3C888B7C474 /* Pods_OTFMagicBoxWatch.framework */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -149,26 +154,12 @@ remoteGlobalIDString = CE482CF2262C954A00C0A2D5; remoteInfo = OTFMagicBox; }; - 7CA3D3002A8A43C400FCB048 /* PBXContainerItemProxy */ = { + 84F43F582B4D6A4C009DFFF8 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = CE482CEB262C954A00C0A2D5 /* Project object */; proxyType = 1; - remoteGlobalIDString = 7CA3D2EF2A8A43C300FCB048; - remoteInfo = "OTFMagicBox Watch Watch App"; - }; - 7CA3D30A2A8A43C400FCB048 /* PBXContainerItemProxy */ = { - isa = PBXContainerItemProxy; - containerPortal = CE482CEB262C954A00C0A2D5 /* Project object */; - proxyType = 1; - remoteGlobalIDString = 7CA3D2EF2A8A43C300FCB048; - remoteInfo = "OTFMagicBox Watch Watch App"; - }; - 7CA3D3112A8A43C400FCB048 /* PBXContainerItemProxy */ = { - isa = PBXContainerItemProxy; - containerPortal = CE482CEB262C954A00C0A2D5 /* Project object */; - proxyType = 1; - remoteGlobalIDString = 7CA3D2EF2A8A43C300FCB048; - remoteInfo = "OTFMagicBox Watch Watch App"; + remoteGlobalIDString = 84F43F4C2B4D6A4A009DFFF8; + remoteInfo = "OTFMagicBoxWatch Watch App"; }; /* End PBXContainerItemProxy section */ @@ -179,26 +170,15 @@ dstPath = "$(CONTENTS_FOLDER_PATH)/Watch"; dstSubfolderSpec = 16; files = ( - 7CA3D3132A8A43C400FCB048 /* OTFMagicBox Watch Watch App.app in Embed Watch Content */, + 84F43F5A2B4D6A4C009DFFF8 /* OTFMagicBoxWatch.app in Embed Watch Content */, ); name = "Embed Watch Content"; runOnlyForDeploymentPostprocessing = 0; }; - 7CEB1FC62A8A5A83002918FE /* Embed Frameworks */ = { - isa = PBXCopyFilesBuildPhase; - buildActionMask = 2147483647; - dstPath = ""; - dstSubfolderSpec = 10; - files = ( - 7CEB1FC52A8A5A83002918FE /* (null) in Embed Frameworks */, - ); - name = "Embed Frameworks"; - runOnlyForDeploymentPostprocessing = 0; - }; /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ - 23661129E1AA02D9A3410FC7 /* Pods-OTFMagicBox Watch Watch App.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-OTFMagicBox Watch Watch App.debug.xcconfig"; path = "Target Support Files/Pods-OTFMagicBox Watch Watch App/Pods-OTFMagicBox Watch Watch App.debug.xcconfig"; sourceTree = ""; }; + 2DC70EC922D0E3C888B7C474 /* Pods_OTFMagicBoxWatch.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_OTFMagicBoxWatch.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 32100A2B28AE5BB500035F5E /* .github */ = {isa = PBXFileReference; lastKnownFileType = folder; path = .github; sourceTree = ""; }; 326086F6283E48A300888678 /* DeleteAccountView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeleteAccountView.swift; sourceTree = ""; }; 32AAA4A4283396CF00DA0DA8 /* ModuleAppSysParameter.yml */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.yaml; path = ModuleAppSysParameter.yml; sourceTree = ""; }; @@ -208,8 +188,7 @@ 32C6DD9928D8A06F00E453A4 /* UpdateUserViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UpdateUserViewModel.swift; sourceTree = ""; }; 32C6DD9B28D8A08700E453A4 /* ChangePasswordViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChangePasswordViewModel.swift; sourceTree = ""; }; 32E5138C28EC68F100A16FA5 /* Extention+ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Extention+ViewController.swift"; sourceTree = ""; }; - 44835E788FC7C51DB58219C2 /* Pods_OTFMagicBox_Watch_Watch_App.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_OTFMagicBox_Watch_Watch_App.framework; sourceTree = BUILT_PRODUCTS_DIR; }; - 55AC9A6EAEB0412B5C7F5D6C /* Pods_OTFMagicBox.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_OTFMagicBox.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 58ADE8FC0AF041D6DF78CE64 /* Pods-OTFMagicBoxWatch.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-OTFMagicBoxWatch.release.xcconfig"; path = "Target Support Files/Pods-OTFMagicBoxWatch/Pods-OTFMagicBoxWatch.release.xcconfig"; sourceTree = ""; }; 5A41D022266A9106007CCEB4 /* MainView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainView.swift; sourceTree = ""; }; 5A41D033266A91CB007CCEB4 /* TasksUIView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TasksUIView.swift; sourceTree = ""; }; 5A41D036266A955C007CCEB4 /* TaskItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TaskItem.swift; sourceTree = ""; }; @@ -267,20 +246,10 @@ 5AB1808926B9C76D007AF766 /* SurveyItemViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SurveyItemViewController.swift; sourceTree = ""; }; 5AB1808C26BC4ED6007AF766 /* LoginViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LoginViewController.swift; sourceTree = ""; }; 5AB1808E26BC58DE007AF766 /* HealthRecordStep.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HealthRecordStep.swift; sourceTree = ""; }; + 62EB2116D692E7C5E54E80E1 /* Pods-OTFMagicBoxWatch.care.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-OTFMagicBoxWatch.care.xcconfig"; path = "Target Support Files/Pods-OTFMagicBoxWatch/Pods-OTFMagicBoxWatch.care.xcconfig"; sourceTree = ""; }; 7C7EC6782A65912C003B0FFB /* OTFNetworkObserver.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OTFNetworkObserver.swift; sourceTree = ""; }; 7C979A5F2A5731C200EBCE2F /* OTFColor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OTFColor.swift; sourceTree = ""; }; - 7CA3D2F02A8A43C300FCB048 /* OTFMagicBox Watch Watch App.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "OTFMagicBox Watch Watch App.app"; sourceTree = BUILT_PRODUCTS_DIR; }; - 7CA3D2F22A8A43C300FCB048 /* OTFMagicBox_WatchApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OTFMagicBox_WatchApp.swift; sourceTree = ""; }; - 7CA3D2F42A8A43C300FCB048 /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = ""; }; - 7CA3D2F62A8A43C400FCB048 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; - 7CA3D2F92A8A43C400FCB048 /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = ""; }; - 7CA3D2FF2A8A43C400FCB048 /* OTFMagicBox Watch Watch AppTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "OTFMagicBox Watch Watch AppTests.xctest"; sourceTree = BUILT_PRODUCTS_DIR; }; - 7CA3D3032A8A43C400FCB048 /* OTFMagicBox_Watch_Watch_AppTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OTFMagicBox_Watch_Watch_AppTests.swift; sourceTree = ""; }; - 7CA3D3092A8A43C400FCB048 /* OTFMagicBox Watch Watch AppUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "OTFMagicBox Watch Watch AppUITests.xctest"; sourceTree = BUILT_PRODUCTS_DIR; }; - 7CA3D30D2A8A43C400FCB048 /* OTFMagicBox_Watch_Watch_AppUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OTFMagicBox_Watch_Watch_AppUITests.swift; sourceTree = ""; }; - 7CA3D30F2A8A43C400FCB048 /* OTFMagicBox_Watch_Watch_AppUITestsLaunchTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OTFMagicBox_Watch_Watch_AppUITestsLaunchTests.swift; sourceTree = ""; }; - 7CA403022AB20C0F00A3D022 /* CareKitListView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CareKitListView.swift; sourceTree = ""; }; - 7CD965012AB660E8003E9394 /* OTFMagicBox Watch Watch App.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = "OTFMagicBox Watch Watch App.entitlements"; sourceTree = ""; }; + 84146ED72BD14F4B007D768C /* TaskListRow+Extras.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "TaskListRow+Extras.swift"; sourceTree = ""; }; 841CA609275F719000C6861D /* ViewDidLoadModifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewDidLoadModifier.swift; sourceTree = ""; }; 841CA60B275F827300C6861D /* CareKitStoreManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CareKitStoreManager.swift; sourceTree = ""; }; 841FEFBA274E5CE000698892 /* SSEAndSyncManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SSEAndSyncManager.swift; sourceTree = ""; }; @@ -323,15 +292,34 @@ 849989DD2AD7CB1B009CBA41 /* PDFViewer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PDFViewer.swift; sourceTree = ""; }; 849989DF2AD7CB25009CBA41 /* PDFKitRepresentedView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PDFKitRepresentedView.swift; sourceTree = ""; }; 849989E12AD7CC67009CBA41 /* UploadDocumentManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UploadDocumentManager.swift; sourceTree = ""; }; - 849989E32AD7CD53009CBA41 /* ActivityLoader.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ActivityLoader.swift; sourceTree = ""; }; + 849989E32AD7CD53009CBA41 /* LoaderView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LoaderView.swift; sourceTree = ""; }; 849989E52AD7D182009CBA41 /* Extension+Array.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Extension+Array.swift"; sourceTree = ""; }; 84A1758A2750A035001587BD /* Extention+Notification.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Extention+Notification.swift"; sourceTree = ""; }; + 84EEAD012BBCA6D100414955 /* OTFStyleColorStyler.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OTFStyleColorStyler.swift; sourceTree = ""; }; + 84EEAD022BBCA6D100414955 /* OTFStyle.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OTFStyle.swift; sourceTree = ""; }; + 84EEAD032BBCA6D100414955 /* OTFYamlStyle.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OTFYamlStyle.swift; sourceTree = ""; }; + 84F43F4D2B4D6A4A009DFFF8 /* OTFMagicBoxWatch.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = OTFMagicBoxWatch.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 84F43F4F2B4D6A4A009DFFF8 /* OTFMagicBoxWatch.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OTFMagicBoxWatch.swift; sourceTree = ""; }; + 84F43F532B4D6A4C009DFFF8 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; + 84F43F562B4D6A4C009DFFF8 /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = ""; }; 9004A9212AD7DD390084622A /* HelpView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HelpView.swift; sourceTree = ""; }; - A5576C00FEC63C6798D1DB53 /* Pods-OTFMagicBox.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-OTFMagicBox.release.xcconfig"; path = "Target Support Files/Pods-OTFMagicBox/Pods-OTFMagicBox.release.xcconfig"; sourceTree = ""; }; + 901700F32B63B856006C611C /* ActivityIndicatorView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActivityIndicatorView.swift; sourceTree = ""; }; + 902E18A52AE8F0A000F694F2 /* OTFFont.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OTFFont.swift; sourceTree = ""; }; + 90C79E3E2B4DCD9D0020D20F /* OTFMagicBoxWatch.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = OTFMagicBoxWatch.entitlements; sourceTree = ""; }; + 90C79E3F2B4DCE110020D20F /* OTFCareKitStoreManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OTFCareKitStoreManager.swift; sourceTree = ""; }; + 90C79E402B4DCE110020D20F /* ContentView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = ""; }; + 90C79E412B4DCE120020D20F /* CareKitListView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CareKitListView.swift; sourceTree = ""; }; + 90C79E422B4DCE120020D20F /* Extension+Notification.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Extension+Notification.swift"; sourceTree = ""; }; + 90C79E432B4DCE120020D20F /* Extension+OCKTask.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Extension+OCKTask.swift"; sourceTree = ""; }; + 90C79E442B4DCE120020D20F /* SessionManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SessionManager.swift; sourceTree = ""; }; + 90C79E4B2B4E862A0020D20F /* WatchStoreService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WatchStoreService.swift; sourceTree = ""; }; + 9BC94C5B492FDF7370667F8A /* Pods-OTFMagicBox.care.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-OTFMagicBox.care.xcconfig"; path = "Target Support Files/Pods-OTFMagicBox/Pods-OTFMagicBox.care.xcconfig"; sourceTree = ""; }; + A49F6F1563A78034A9EBE56C /* Pods-OTFMagicBoxWatch.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-OTFMagicBoxWatch.debug.xcconfig"; path = "Target Support Files/Pods-OTFMagicBoxWatch/Pods-OTFMagicBoxWatch.debug.xcconfig"; sourceTree = ""; }; A8C0556A296D61770098D1FB /* Extention+ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Extention+ViewController.swift"; sourceTree = ""; }; A8FCD2A92A4C6EF7006E0B6E /* KeychainCloudManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeychainCloudManager.swift; sourceTree = ""; }; A8FCD2AB2A4C6F0D006E0B6E /* LocalAuthentication.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocalAuthentication.swift; sourceTree = ""; }; - C68B1F7051EF9581BEEB32BF /* Pods-OTFMagicBox.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-OTFMagicBox.debug.xcconfig"; path = "Target Support Files/Pods-OTFMagicBox/Pods-OTFMagicBox.debug.xcconfig"; sourceTree = ""; }; + BE0B0929EFF0E25E87CF3B71 /* Pods-OTFMagicBox.carehealth.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-OTFMagicBox.carehealth.xcconfig"; path = "Target Support Files/Pods-OTFMagicBox/Pods-OTFMagicBox.carehealth.xcconfig"; sourceTree = ""; }; + C786F095D5BBC2634543074F /* Pods-OTFMagicBox.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-OTFMagicBox.debug.xcconfig"; path = "Target Support Files/Pods-OTFMagicBox/Pods-OTFMagicBox.debug.xcconfig"; sourceTree = ""; }; CE2DB9C6263B1EC400E2FB88 /* AppSysParameters.yml */ = {isa = PBXFileReference; lastKnownFileType = text.yaml; path = AppSysParameters.yml; sourceTree = ""; }; CE482CF3262C954A00C0A2D5 /* OTFMagicBox.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = OTFMagicBox.app; sourceTree = BUILT_PRODUCTS_DIR; }; CE482CF6262C954A00C0A2D5 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; @@ -340,7 +328,9 @@ CE482CFC262C954C00C0A2D5 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; CE482D02262C954C00C0A2D5 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; CE482D04262C954C00C0A2D5 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; - E5CF7B6203990ECA79E1B177 /* Pods-OTFMagicBox Watch Watch App.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-OTFMagicBox Watch Watch App.release.xcconfig"; path = "Target Support Files/Pods-OTFMagicBox Watch Watch App/Pods-OTFMagicBox Watch Watch App.release.xcconfig"; sourceTree = ""; }; + E86A26F2DE6C8AB4A0B19344 /* Pods_OTFMagicBox.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_OTFMagicBox.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + F07B7C39F5B74DE968EFEDC4 /* Pods-OTFMagicBox.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-OTFMagicBox.release.xcconfig"; path = "Target Support Files/Pods-OTFMagicBox/Pods-OTFMagicBox.release.xcconfig"; sourceTree = ""; }; + F2F96CA14BB557FB216784C9 /* Pods-OTFMagicBoxWatch.carehealth.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-OTFMagicBoxWatch.carehealth.xcconfig"; path = "Target Support Files/Pods-OTFMagicBoxWatch/Pods-OTFMagicBoxWatch.carehealth.xcconfig"; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -351,25 +341,11 @@ ); runOnlyForDeploymentPostprocessing = 0; }; - 7CA3D2ED2A8A43C300FCB048 /* Frameworks */ = { - isa = PBXFrameworksBuildPhase; - buildActionMask = 2147483647; - files = ( - 04CDFE9A4C5C79E8EACA8709 /* Pods_OTFMagicBox_Watch_Watch_App.framework in Frameworks */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; - 7CA3D2FC2A8A43C400FCB048 /* Frameworks */ = { - isa = PBXFrameworksBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - runOnlyForDeploymentPostprocessing = 0; - }; - 7CA3D3062A8A43C400FCB048 /* Frameworks */ = { + 84F43F4A2B4D6A4A009DFFF8 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + FFB3790929612305437E0E08 /* Pods_OTFMagicBoxWatch.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -378,7 +354,7 @@ buildActionMask = 2147483647; files = ( 5A49628526DF73B700A4B4DF /* HealthKit.framework in Frameworks */, - 06540FA342BE7267771D8AF4 /* Pods_OTFMagicBox.framework in Frameworks */, + 4CF07F71CAF10A608704FCFF /* Pods_OTFMagicBox.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -507,7 +483,7 @@ 5A49632926EB5E2E00A4B4DF /* Extension */, 5A49626D26D4A2EA00A4B4DF /* Yaml */, 5AADFD4F264E9A880069FEF7 /* Constants.swift */, - 849989E32AD7CD53009CBA41 /* ActivityLoader.swift */, + 849989E32AD7CD53009CBA41 /* LoaderView.swift */, 5AB1808126B88A2B007AF766 /* Metrics.swift */, 5A49627D26DEA59F00A4B4DF /* HealthKitManager.swift */, 5A49627926DE69AE00A4B4DF /* OTFHealthKitManager.swift */, @@ -519,6 +495,7 @@ A8FCD2A92A4C6EF7006E0B6E /* KeychainCloudManager.swift */, 849989E12AD7CC67009CBA41 /* UploadDocumentManager.swift */, A8FCD2AB2A4C6F0D006E0B6E /* LocalAuthentication.swift */, + 902E18A52AE8F0A000F694F2 /* OTFFont.swift */, ); path = Library; sourceTree = ""; @@ -595,44 +572,6 @@ path = Contacts; sourceTree = ""; }; - 7CA3D2F12A8A43C300FCB048 /* OTFMagicBox Watch Watch App */ = { - isa = PBXGroup; - children = ( - 7CD965012AB660E8003E9394 /* OTFMagicBox Watch Watch App.entitlements */, - 7CA3D2F22A8A43C300FCB048 /* OTFMagicBox_WatchApp.swift */, - 7CA3D2F42A8A43C300FCB048 /* ContentView.swift */, - 7CA403022AB20C0F00A3D022 /* CareKitListView.swift */, - 7CA3D2F62A8A43C400FCB048 /* Assets.xcassets */, - 7CA3D2F82A8A43C400FCB048 /* Preview Content */, - ); - path = "OTFMagicBox Watch Watch App"; - sourceTree = ""; - }; - 7CA3D2F82A8A43C400FCB048 /* Preview Content */ = { - isa = PBXGroup; - children = ( - 7CA3D2F92A8A43C400FCB048 /* Preview Assets.xcassets */, - ); - path = "Preview Content"; - sourceTree = ""; - }; - 7CA3D3022A8A43C400FCB048 /* OTFMagicBox Watch Watch AppTests */ = { - isa = PBXGroup; - children = ( - 7CA3D3032A8A43C400FCB048 /* OTFMagicBox_Watch_Watch_AppTests.swift */, - ); - path = "OTFMagicBox Watch Watch AppTests"; - sourceTree = ""; - }; - 7CA3D30C2A8A43C400FCB048 /* OTFMagicBox Watch Watch AppUITests */ = { - isa = PBXGroup; - children = ( - 7CA3D30D2A8A43C400FCB048 /* OTFMagicBox_Watch_Watch_AppUITests.swift */, - 7CA3D30F2A8A43C400FCB048 /* OTFMagicBox_Watch_Watch_AppUITestsLaunchTests.swift */, - ); - path = "OTFMagicBox Watch Watch AppUITests"; - sourceTree = ""; - }; 841FEFBC274E5D8200698892 /* Datastore */ = { isa = PBXGroup; children = ( @@ -674,11 +613,11 @@ 846761A927DF8BC300A3207E /* StaticViews */ = { isa = PBXGroup; children = ( - 846761AA27DF8BC300A3207E /* PlatformPicker.swift */, - 846761AB27DF8BC300A3207E /* StaticUI.swift */, + 846761B427DF8BC300A3207E /* CareKit */, 846761AC27DF8BC300A3207E /* RK UI */, 846761B327DF8BC300A3207E /* CardBackground.swift */, - 846761B427DF8BC300A3207E /* CareKit */, + 846761AA27DF8BC300A3207E /* PlatformPicker.swift */, + 846761AB27DF8BC300A3207E /* StaticUI.swift */, ); path = StaticViews; sourceTree = ""; @@ -691,6 +630,7 @@ 846761AF27DF8BC300A3207E /* TaskViewControllerRepresentable.swift */, 846761B027DF8BC300A3207E /* RKTasks.swift */, 846761B127DF8BC300A3207E /* TaskListRow.swift */, + 84146ED72BD14F4B007D768C /* TaskListRow+Extras.swift */, 846761B227DF8BC300A3207E /* TaskListViewController.swift */, ); path = "RK UI"; @@ -707,17 +647,90 @@ path = CareKit; sourceTree = ""; }; + 84847E4A2BA1C42300B2B016 /* Views */ = { + isa = PBXGroup; + children = ( + 901700F32B63B856006C611C /* ActivityIndicatorView.swift */, + 90C79E412B4DCE120020D20F /* CareKitListView.swift */, + ); + path = Views; + sourceTree = ""; + }; + 84847E4B2BA1C51600B2B016 /* DataStore */ = { + isa = PBXGroup; + children = ( + 90C79E4B2B4E862A0020D20F /* WatchStoreService.swift */, + 90C79E3F2B4DCE110020D20F /* OTFCareKitStoreManager.swift */, + ); + path = DataStore; + sourceTree = ""; + }; + 84847E4C2BA1C58100B2B016 /* Managers */ = { + isa = PBXGroup; + children = ( + 90C79E442B4DCE120020D20F /* SessionManager.swift */, + ); + path = Managers; + sourceTree = ""; + }; + 84EEAD002BBCA6D100414955 /* Styling */ = { + isa = PBXGroup; + children = ( + 84EEAD012BBCA6D100414955 /* OTFStyleColorStyler.swift */, + 84EEAD022BBCA6D100414955 /* OTFStyle.swift */, + 84EEAD032BBCA6D100414955 /* OTFYamlStyle.swift */, + ); + path = Styling; + sourceTree = ""; + }; + 84F43F4E2B4D6A4A009DFFF8 /* OTFMagicBoxWatch */ = { + isa = PBXGroup; + children = ( + 84F43F4F2B4D6A4A009DFFF8 /* OTFMagicBoxWatch.swift */, + 90C79E402B4DCE110020D20F /* ContentView.swift */, + 84847E4C2BA1C58100B2B016 /* Managers */, + 84847E4B2BA1C51600B2B016 /* DataStore */, + 901700EF2B5A701F006C611C /* Extensions */, + 84847E4A2BA1C42300B2B016 /* Views */, + 84F43F552B4D6A4C009DFFF8 /* Preview Content */, + 84F43F532B4D6A4C009DFFF8 /* Assets.xcassets */, + 90C79E3E2B4DCD9D0020D20F /* OTFMagicBoxWatch.entitlements */, + ); + path = OTFMagicBoxWatch; + sourceTree = ""; + }; + 84F43F552B4D6A4C009DFFF8 /* Preview Content */ = { + isa = PBXGroup; + children = ( + 84F43F562B4D6A4C009DFFF8 /* Preview Assets.xcassets */, + ); + path = "Preview Content"; + sourceTree = ""; + }; 8C979D2203D122CC68BA93F2 /* Pods */ = { isa = PBXGroup; children = ( - C68B1F7051EF9581BEEB32BF /* Pods-OTFMagicBox.debug.xcconfig */, - A5576C00FEC63C6798D1DB53 /* Pods-OTFMagicBox.release.xcconfig */, - 23661129E1AA02D9A3410FC7 /* Pods-OTFMagicBox Watch Watch App.debug.xcconfig */, - E5CF7B6203990ECA79E1B177 /* Pods-OTFMagicBox Watch Watch App.release.xcconfig */, + C786F095D5BBC2634543074F /* Pods-OTFMagicBox.debug.xcconfig */, + BE0B0929EFF0E25E87CF3B71 /* Pods-OTFMagicBox.carehealth.xcconfig */, + 9BC94C5B492FDF7370667F8A /* Pods-OTFMagicBox.care.xcconfig */, + F07B7C39F5B74DE968EFEDC4 /* Pods-OTFMagicBox.release.xcconfig */, + A49F6F1563A78034A9EBE56C /* Pods-OTFMagicBoxWatch.debug.xcconfig */, + F2F96CA14BB557FB216784C9 /* Pods-OTFMagicBoxWatch.carehealth.xcconfig */, + 62EB2116D692E7C5E54E80E1 /* Pods-OTFMagicBoxWatch.care.xcconfig */, + 58ADE8FC0AF041D6DF78CE64 /* Pods-OTFMagicBoxWatch.release.xcconfig */, ); path = Pods; sourceTree = ""; }; + 901700EF2B5A701F006C611C /* Extensions */ = { + isa = PBXGroup; + children = ( + 90C79E432B4DCE120020D20F /* Extension+OCKTask.swift */, + 90C79E422B4DCE120020D20F /* Extension+Notification.swift */, + ); + path = Extensions; + sourceTree = ""; + }; A8705D29296E8E85003AFC0B /* Recovered References */ = { isa = PBXGroup; children = ( @@ -730,8 +743,8 @@ isa = PBXGroup; children = ( 5A49628426DF73B700A4B4DF /* HealthKit.framework */, - 55AC9A6EAEB0412B5C7F5D6C /* Pods_OTFMagicBox.framework */, - 44835E788FC7C51DB58219C2 /* Pods_OTFMagicBox_Watch_Watch_App.framework */, + E86A26F2DE6C8AB4A0B19344 /* Pods_OTFMagicBox.framework */, + 2DC70EC922D0E3C888B7C474 /* Pods_OTFMagicBoxWatch.framework */, ); name = Frameworks; sourceTree = ""; @@ -742,9 +755,7 @@ 32100A2B28AE5BB500035F5E /* .github */, CE482CF5262C954A00C0A2D5 /* OTFMagicBox */, 5A7B6DAA26FD282C00F872A1 /* OTFMagicBoxTests */, - 7CA3D2F12A8A43C300FCB048 /* OTFMagicBox Watch Watch App */, - 7CA3D3022A8A43C400FCB048 /* OTFMagicBox Watch Watch AppTests */, - 7CA3D30C2A8A43C400FCB048 /* OTFMagicBox Watch Watch AppUITests */, + 84F43F4E2B4D6A4A009DFFF8 /* OTFMagicBoxWatch */, CE482CF4262C954A00C0A2D5 /* Products */, 8C979D2203D122CC68BA93F2 /* Pods */, C9D92AAC7DE27C25427A4DB9 /* Frameworks */, @@ -757,9 +768,7 @@ children = ( CE482CF3262C954A00C0A2D5 /* OTFMagicBox.app */, 5A7B6DA926FD282C00F872A1 /* OTFMagicBoxTests.xctest */, - 7CA3D2F02A8A43C300FCB048 /* OTFMagicBox Watch Watch App.app */, - 7CA3D2FF2A8A43C400FCB048 /* OTFMagicBox Watch Watch AppTests.xctest */, - 7CA3D3092A8A43C400FCB048 /* OTFMagicBox Watch Watch AppUITests.xctest */, + 84F43F4D2B4D6A4A009DFFF8 /* OTFMagicBoxWatch.app */, ); name = Products; sourceTree = ""; @@ -783,6 +792,7 @@ 5AADFD43264E86B00069FEF7 /* Login */, 5AADFD3D264E86A20069FEF7 /* Library */, 5AADFD35264E86950069FEF7 /* Onboarding */, + 84EEAD002BBCA6D100414955 /* Styling */, CE482CF8262C954A00C0A2D5 /* SceneDelegate.swift */, CE482CF6262C954A00C0A2D5 /* AppDelegate.swift */, 5AB1807F26B8877C007AF766 /* OCKStore + SampleData.swift */, @@ -819,77 +829,42 @@ productReference = 5A7B6DA926FD282C00F872A1 /* OTFMagicBoxTests.xctest */; productType = "com.apple.product-type.bundle.unit-test"; }; - 7CA3D2EF2A8A43C300FCB048 /* OTFMagicBox Watch Watch App */ = { + 84F43F4C2B4D6A4A009DFFF8 /* OTFMagicBoxWatch */ = { isa = PBXNativeTarget; - buildConfigurationList = 7CA3D31B2A8A43C400FCB048 /* Build configuration list for PBXNativeTarget "OTFMagicBox Watch Watch App" */; + buildConfigurationList = 84F43F5D2B4D6A4C009DFFF8 /* Build configuration list for PBXNativeTarget "OTFMagicBoxWatch" */; buildPhases = ( - 05EB063AD94BCC1191F92DD8 /* [CP] Check Pods Manifest.lock */, - 7CA3D2EC2A8A43C300FCB048 /* Sources */, - 7CA3D2ED2A8A43C300FCB048 /* Frameworks */, - 7CA3D2EE2A8A43C300FCB048 /* Resources */, - 7CEB1FC62A8A5A83002918FE /* Embed Frameworks */, - 9333C6CAA54ADF6700EA8400 /* [CP] Embed Pods Frameworks */, + FFD91FC98245B864E873D8DF /* [CP] Check Pods Manifest.lock */, + 84F43F492B4D6A4A009DFFF8 /* Sources */, + 84F43F4A2B4D6A4A009DFFF8 /* Frameworks */, + 84F43F4B2B4D6A4A009DFFF8 /* Resources */, + 901EEAFD2BDA2A10004EC8B5 /* ShellScript */, + CA4E7853B35A745B8BED7B95 /* [CP] Embed Pods Frameworks */, ); buildRules = ( ); dependencies = ( ); - name = "OTFMagicBox Watch Watch App"; - productName = "OTFMagicBox Watch Watch App"; - productReference = 7CA3D2F02A8A43C300FCB048 /* OTFMagicBox Watch Watch App.app */; + name = OTFMagicBoxWatch; + productName = "OTFMagicBoxWatch Watch App"; + productReference = 84F43F4D2B4D6A4A009DFFF8 /* OTFMagicBoxWatch.app */; productType = "com.apple.product-type.application"; }; - 7CA3D2FE2A8A43C400FCB048 /* OTFMagicBox Watch Watch AppTests */ = { - isa = PBXNativeTarget; - buildConfigurationList = 7CA3D31C2A8A43C400FCB048 /* Build configuration list for PBXNativeTarget "OTFMagicBox Watch Watch AppTests" */; - buildPhases = ( - 7CA3D2FB2A8A43C400FCB048 /* Sources */, - 7CA3D2FC2A8A43C400FCB048 /* Frameworks */, - 7CA3D2FD2A8A43C400FCB048 /* Resources */, - ); - buildRules = ( - ); - dependencies = ( - 7CA3D3012A8A43C400FCB048 /* PBXTargetDependency */, - ); - name = "OTFMagicBox Watch Watch AppTests"; - productName = "OTFMagicBox Watch Watch AppTests"; - productReference = 7CA3D2FF2A8A43C400FCB048 /* OTFMagicBox Watch Watch AppTests.xctest */; - productType = "com.apple.product-type.bundle.unit-test"; - }; - 7CA3D3082A8A43C400FCB048 /* OTFMagicBox Watch Watch AppUITests */ = { - isa = PBXNativeTarget; - buildConfigurationList = 7CA3D31D2A8A43C400FCB048 /* Build configuration list for PBXNativeTarget "OTFMagicBox Watch Watch AppUITests" */; - buildPhases = ( - 7CA3D3052A8A43C400FCB048 /* Sources */, - 7CA3D3062A8A43C400FCB048 /* Frameworks */, - 7CA3D3072A8A43C400FCB048 /* Resources */, - ); - buildRules = ( - ); - dependencies = ( - 7CA3D30B2A8A43C400FCB048 /* PBXTargetDependency */, - ); - name = "OTFMagicBox Watch Watch AppUITests"; - productName = "OTFMagicBox Watch Watch AppUITests"; - productReference = 7CA3D3092A8A43C400FCB048 /* OTFMagicBox Watch Watch AppUITests.xctest */; - productType = "com.apple.product-type.bundle.ui-testing"; - }; CE482CF2262C954A00C0A2D5 /* OTFMagicBox */ = { isa = PBXNativeTarget; buildConfigurationList = CE482D07262C954C00C0A2D5 /* Build configuration list for PBXNativeTarget "OTFMagicBox" */; buildPhases = ( - DC173994E8AF983CB6A1D9DA /* [CP] Check Pods Manifest.lock */, + C6C3FF89D47613EA5AA1A900 /* [CP] Check Pods Manifest.lock */, CE482CEF262C954A00C0A2D5 /* Sources */, CE482CF0262C954A00C0A2D5 /* Frameworks */, CE482CF1262C954A00C0A2D5 /* Resources */, 7CA3D3142A8A43C400FCB048 /* Embed Watch Content */, - A2C2909005B20CD8C57AFCCC /* [CP] Embed Pods Frameworks */, + 902E18A92AEA869B00F694F2 /* ShellScript */, + 93B66E92271BF6A28C38EA90 /* [CP] Embed Pods Frameworks */, ); buildRules = ( ); dependencies = ( - 7CA3D3122A8A43C400FCB048 /* PBXTargetDependency */, + 84F43F592B4D6A4C009DFFF8 /* PBXTargetDependency */, ); name = OTFMagicBox; productName = CompletelyNewApp; @@ -903,23 +878,15 @@ isa = PBXProject; attributes = { DefaultBuildSystemTypeForWorkspace = Original; - LastSwiftUpdateCheck = 1430; + LastSwiftUpdateCheck = 1500; LastUpgradeCheck = 1240; TargetAttributes = { 5A7B6DA826FD282C00F872A1 = { CreatedOnToolsVersion = 12.5.1; TestTargetID = CE482CF2262C954A00C0A2D5; }; - 7CA3D2EF2A8A43C300FCB048 = { - CreatedOnToolsVersion = 14.3.1; - }; - 7CA3D2FE2A8A43C400FCB048 = { - CreatedOnToolsVersion = 14.3.1; - TestTargetID = 7CA3D2EF2A8A43C300FCB048; - }; - 7CA3D3082A8A43C400FCB048 = { - CreatedOnToolsVersion = 14.3.1; - TestTargetID = 7CA3D2EF2A8A43C300FCB048; + 84F43F4C2B4D6A4A009DFFF8 = { + CreatedOnToolsVersion = 15.0.1; }; CE482CF2262C954A00C0A2D5 = { CreatedOnToolsVersion = 12.4; @@ -941,9 +908,7 @@ targets = ( CE482CF2262C954A00C0A2D5 /* OTFMagicBox */, 5A7B6DA826FD282C00F872A1 /* OTFMagicBoxTests */, - 7CA3D2EF2A8A43C300FCB048 /* OTFMagicBox Watch Watch App */, - 7CA3D2FE2A8A43C400FCB048 /* OTFMagicBox Watch Watch AppTests */, - 7CA3D3082A8A43C400FCB048 /* OTFMagicBox Watch Watch AppUITests */, + 84F43F4C2B4D6A4A009DFFF8 /* OTFMagicBoxWatch */, ); }; /* End PBXProject section */ @@ -956,26 +921,12 @@ ); runOnlyForDeploymentPostprocessing = 0; }; - 7CA3D2EE2A8A43C300FCB048 /* Resources */ = { - isa = PBXResourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - 7CA3D2FA2A8A43C400FCB048 /* Preview Assets.xcassets in Resources */, - 7CA3D2F72A8A43C400FCB048 /* Assets.xcassets in Resources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; - 7CA3D2FD2A8A43C400FCB048 /* Resources */ = { - isa = PBXResourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - runOnlyForDeploymentPostprocessing = 0; - }; - 7CA3D3072A8A43C400FCB048 /* Resources */ = { + 84F43F4B2B4D6A4A009DFFF8 /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( + 84F43F572B4D6A4C009DFFF8 /* Preview Assets.xcassets in Resources */, + 84F43F542B4D6A4C009DFFF8 /* Assets.xcassets in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -996,7 +947,7 @@ /* End PBXResourcesBuildPhase section */ /* Begin PBXShellScriptBuildPhase section */ - 05EB063AD94BCC1191F92DD8 /* [CP] Check Pods Manifest.lock */ = { + 901EEAFD2BDA2A10004EC8B5 /* ShellScript */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( @@ -1004,38 +955,34 @@ inputFileListPaths = ( ); inputPaths = ( - "${PODS_PODFILE_DIR_PATH}/Podfile.lock", - "${PODS_ROOT}/Manifest.lock", ); - name = "[CP] Check Pods Manifest.lock"; outputFileListPaths = ( ); outputPaths = ( - "$(DERIVED_FILE_DIR)/Pods-OTFMagicBox Watch Watch App-checkManifestLockResult.txt", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; - showEnvVarsInLog = 0; + shellScript = "# Type a script or drag a script file from your workspace to insert its path.\nif which swiftlint >/dev/null; then\n swiftlint\nelse\n echo \"warning: SwiftLint not installed, download from https://github.com/realm/SwiftLint\"\nfi\n"; }; - 9333C6CAA54ADF6700EA8400 /* [CP] Embed Pods Frameworks */ = { + 902E18A92AEA869B00F694F2 /* ShellScript */ = { isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; buildActionMask = 2147483647; files = ( ); inputFileListPaths = ( - "${PODS_ROOT}/Target Support Files/Pods-OTFMagicBox Watch Watch App/Pods-OTFMagicBox Watch Watch App-frameworks-${CONFIGURATION}-input-files.xcfilelist", ); - name = "[CP] Embed Pods Frameworks"; + inputPaths = ( + ); outputFileListPaths = ( - "${PODS_ROOT}/Target Support Files/Pods-OTFMagicBox Watch Watch App/Pods-OTFMagicBox Watch Watch App-frameworks-${CONFIGURATION}-output-files.xcfilelist", + ); + outputPaths = ( ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-OTFMagicBox Watch Watch App/Pods-OTFMagicBox Watch Watch App-frameworks.sh\"\n"; - showEnvVarsInLog = 0; + shellScript = "# Type a script or drag a script file from your workspace to insert its path.\nif which swiftlint >/dev/null; then\n swiftlint\nelse\n echo \"warning: SwiftLint not installed, download from https://github.com/realm/SwiftLint\"\nfi\n"; }; - A2C2909005B20CD8C57AFCCC /* [CP] Embed Pods Frameworks */ = { + 93B66E92271BF6A28C38EA90 /* [CP] Embed Pods Frameworks */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( @@ -1052,7 +999,7 @@ shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-OTFMagicBox/Pods-OTFMagicBox-frameworks.sh\"\n"; showEnvVarsInLog = 0; }; - DC173994E8AF983CB6A1D9DA /* [CP] Check Pods Manifest.lock */ = { + C6C3FF89D47613EA5AA1A900 /* [CP] Check Pods Manifest.lock */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( @@ -1074,44 +1021,70 @@ shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; showEnvVarsInLog = 0; }; -/* End PBXShellScriptBuildPhase section */ - -/* Begin PBXSourcesBuildPhase section */ - 5A7B6DA526FD282C00F872A1 /* Sources */ = { - isa = PBXSourcesBuildPhase; + CA4E7853B35A745B8BED7B95 /* [CP] Embed Pods Frameworks */ = { + isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( - 5A7B6DB426FD285000F872A1 /* OTFMagicBoxYamlTests.swift in Sources */, - 5A7B6DAC26FD282C00F872A1 /* OTFMagicBoxTests.swift in Sources */, + ); + inputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-OTFMagicBoxWatch/Pods-OTFMagicBoxWatch-frameworks-${CONFIGURATION}-input-files.xcfilelist", + ); + name = "[CP] Embed Pods Frameworks"; + outputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-OTFMagicBoxWatch/Pods-OTFMagicBoxWatch-frameworks-${CONFIGURATION}-output-files.xcfilelist", ); runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-OTFMagicBoxWatch/Pods-OTFMagicBoxWatch-frameworks.sh\"\n"; + showEnvVarsInLog = 0; }; - 7CA3D2EC2A8A43C300FCB048 /* Sources */ = { - isa = PBXSourcesBuildPhase; + FFD91FC98245B864E873D8DF /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( - 7CD965032AB6B784003E9394 /* Constants.swift in Sources */, - 7CA403032AB20C0F00A3D022 /* CareKitListView.swift in Sources */, - 7CD965022AB6AA5E003E9394 /* OCKStoreManager.swift in Sources */, - 7CA3D2F52A8A43C300FCB048 /* ContentView.swift in Sources */, - 7CA3D2F32A8A43C300FCB048 /* OTFMagicBox_WatchApp.swift in Sources */, + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-OTFMagicBoxWatch-checkManifestLockResult.txt", ); runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; }; - 7CA3D2FB2A8A43C400FCB048 /* Sources */ = { +/* End PBXShellScriptBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 5A7B6DA526FD282C00F872A1 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - 7CA3D3042A8A43C400FCB048 /* OTFMagicBox_Watch_Watch_AppTests.swift in Sources */, + 5A7B6DB426FD285000F872A1 /* OTFMagicBoxYamlTests.swift in Sources */, + 5A7B6DAC26FD282C00F872A1 /* OTFMagicBoxTests.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; - 7CA3D3052A8A43C400FCB048 /* Sources */ = { + 84F43F492B4D6A4A009DFFF8 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - 7CA3D3102A8A43C400FCB048 /* OTFMagicBox_Watch_Watch_AppUITestsLaunchTests.swift in Sources */, - 7CA3D30E2A8A43C400FCB048 /* OTFMagicBox_Watch_Watch_AppUITests.swift in Sources */, + 84F43F502B4D6A4A009DFFF8 /* OTFMagicBoxWatch.swift in Sources */, + 90C79E492B4DCE120020D20F /* Extension+OCKTask.swift in Sources */, + 90C79E452B4DCE120020D20F /* OTFCareKitStoreManager.swift in Sources */, + 90C79E472B4DCE120020D20F /* CareKitListView.swift in Sources */, + 90C79E482B4DCE120020D20F /* Extension+Notification.swift in Sources */, + 901700F42B63B856006C611C /* ActivityIndicatorView.swift in Sources */, + 90C79E462B4DCE120020D20F /* ContentView.swift in Sources */, + 90C79E4A2B4DCE120020D20F /* SessionManager.swift in Sources */, + 90C79E4C2B4E862A0020D20F /* WatchStoreService.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -1129,8 +1102,10 @@ 5AB1808826B9C489007AF766 /* ScheduleViewController.swift in Sources */, 84268457274791E5007C5D50 /* SignInWithAppleStepViewController.swift in Sources */, 841FEFBF274E5D8200698892 /* CloudantSyncManager.swift in Sources */, + 84EEAD062BBCA6D100414955 /* OTFYamlStyle.swift in Sources */, 5A49632426EB257200A4B4DF /* Extension+UIImage.swift in Sources */, 846761C327DF8BC300A3207E /* ContactsSection.swift in Sources */, + 84EEAD042BBCA6D100414955 /* OTFStyleColorStyler.swift in Sources */, 849989E62AD7D182009CBA41 /* Extension+Array.swift in Sources */, 5A7B6D8F26FA353B00F872A1 /* OTFTheraforgeNetwork.swift in Sources */, 5AADFD47264E89510069FEF7 /* ConsentDocument.swift in Sources */, @@ -1149,6 +1124,7 @@ 32AAA4A728339E7F00DA0DA8 /* ModuleAppYmlReader+DataModel.swift in Sources */, 846761C527DF8BC300A3207E /* CareKitTaskViews.swift in Sources */, 5AB1808A26B9C76D007AF766 /* SurveyItemViewController.swift in Sources */, + 84146ED82BD14F4B007D768C /* TaskListRow+Extras.swift in Sources */, 84A1758B2750A035001587BD /* Extention+Notification.swift in Sources */, A8C0556B296D61770098D1FB /* Extention+ViewController.swift in Sources */, 849989E22AD7CC67009CBA41 /* UploadDocumentManager.swift in Sources */, @@ -1206,10 +1182,11 @@ 5A4304A0272AE85200926584 /* DocumentPreviewViewController.swift in Sources */, 846761BD27DF8BC300A3207E /* TaskViewControllerRepresentable.swift in Sources */, 841CA60C275F827300C6861D /* CareKitStoreManager.swift in Sources */, - 849989E42AD7CD53009CBA41 /* ActivityLoader.swift in Sources */, + 849989E42AD7CD53009CBA41 /* LoaderView.swift in Sources */, 5AB1808D26BC4ED6007AF766 /* LoginViewController.swift in Sources */, 841FEFC0274E5D8200698892 /* TheraForgeHTTPInterceptor.swift in Sources */, 846761A127DF8A6900A3207E /* OCKHealthKitStore+Extension.swift in Sources */, + 84EEAD052BBCA6D100414955 /* OTFStyle.swift in Sources */, 5A49628D26E1BD5D00A4B4DF /* UIColor.swift in Sources */, 5AB1807726B8849A007AF766 /* ChangePasscodeView.swift in Sources */, 846761A027DF8A6900A3207E /* Extension+OCKAnyTask.swift in Sources */, @@ -1223,6 +1200,7 @@ 846761BF27DF8BC300A3207E /* TaskListRow.swift in Sources */, 7C7EC6792A65912C003B0FFB /* OTFNetworkObserver.swift in Sources */, 8467619927DF89DE00A3207E /* CheckUpView.swift in Sources */, + 902E18A62AE8F0A000F694F2 /* OTFFont.swift in Sources */, 849989E02AD7CB25009CBA41 /* PDFKitRepresentedView.swift in Sources */, 5A49627C26DE6A2E00A4B4DF /* ActivityManager.swift in Sources */, 5AADFD50264E9A880069FEF7 /* Constants.swift in Sources */, @@ -1238,20 +1216,10 @@ target = CE482CF2262C954A00C0A2D5 /* OTFMagicBox */; targetProxy = 5A7B6DAE26FD282C00F872A1 /* PBXContainerItemProxy */; }; - 7CA3D3012A8A43C400FCB048 /* PBXTargetDependency */ = { - isa = PBXTargetDependency; - target = 7CA3D2EF2A8A43C300FCB048 /* OTFMagicBox Watch Watch App */; - targetProxy = 7CA3D3002A8A43C400FCB048 /* PBXContainerItemProxy */; - }; - 7CA3D30B2A8A43C400FCB048 /* PBXTargetDependency */ = { + 84F43F592B4D6A4C009DFFF8 /* PBXTargetDependency */ = { isa = PBXTargetDependency; - target = 7CA3D2EF2A8A43C300FCB048 /* OTFMagicBox Watch Watch App */; - targetProxy = 7CA3D30A2A8A43C400FCB048 /* PBXContainerItemProxy */; - }; - 7CA3D3122A8A43C400FCB048 /* PBXTargetDependency */ = { - isa = PBXTargetDependency; - target = 7CA3D2EF2A8A43C300FCB048 /* OTFMagicBox Watch Watch App */; - targetProxy = 7CA3D3112A8A43C400FCB048 /* PBXContainerItemProxy */; + target = 84F43F4C2B4D6A4A009DFFF8 /* OTFMagicBoxWatch */; + targetProxy = 84F43F582B4D6A4C009DFFF8 /* PBXContainerItemProxy */; }; /* End PBXTargetDependency section */ @@ -1309,34 +1277,38 @@ }; name = Release; }; - 7CA3D3152A8A43C400FCB048 /* Debug */ = { + 84F43F5B2B4D6A4C009DFFF8 /* Debug */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 23661129E1AA02D9A3410FC7 /* Pods-OTFMagicBox Watch Watch App.debug.xcconfig */; + baseConfigurationReference = A49F6F1563A78034A9EBE56C /* Pods-OTFMagicBoxWatch.debug.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; - CODE_SIGN_ENTITLEMENTS = "OTFMagicBox Watch Watch App/OTFMagicBox Watch Watch App.entitlements"; + CODE_SIGN_ENTITLEMENTS = OTFMagicBoxWatch/OTFMagicBoxWatch.entitlements; CODE_SIGN_IDENTITY = "iPhone Developer"; - CODE_SIGN_STYLE = Manual; + CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; - DEVELOPMENT_ASSET_PATHS = "\"OTFMagicBox Watch Watch App/Preview Content\""; - DEVELOPMENT_TEAM = ""; + DEVELOPMENT_ASSET_PATHS = "\"OTFMagicBoxWatch/Preview Content\""; + DEVELOPMENT_TEAM = 53UK9NNVG5; ENABLE_PREVIEWS = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; + GCC_C_LANGUAGE_STANDARD = gnu17; GENERATE_INFOPLIST_FILE = YES; - INFOPLIST_KEY_CFBundleDisplayName = "OTFMagicBox Watch"; + INFOPLIST_KEY_CFBundleDisplayName = OTFMagicBoxWatch; INFOPLIST_KEY_UISupportedInterfaceOrientations = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown"; INFOPLIST_KEY_WKCompanionAppBundleIdentifier = org.theraforge.magicbox.ios; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", ); + LOCALIZATION_PREFERS_STRING_CATALOGS = YES; MARKETING_VERSION = 1.0; PRODUCT_BUNDLE_IDENTIFIER = org.theraforge.magicbox.ios.watchkitapp; PRODUCT_NAME = "$(TARGET_NAME)"; - PROVISIONING_PROFILE_SPECIFIER = ""; SDKROOT = watchos; SKIP_INSTALL = YES; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)"; SWIFT_EMIT_LOC_STRINGS = YES; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = 4; @@ -1344,32 +1316,35 @@ }; name = Debug; }; - 7CA3D3162A8A43C400FCB048 /* Release */ = { + 84F43F5C2B4D6A4C009DFFF8 /* Release */ = { isa = XCBuildConfiguration; - baseConfigurationReference = E5CF7B6203990ECA79E1B177 /* Pods-OTFMagicBox Watch Watch App.release.xcconfig */; + baseConfigurationReference = 58ADE8FC0AF041D6DF78CE64 /* Pods-OTFMagicBoxWatch.release.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; - CODE_SIGN_ENTITLEMENTS = "OTFMagicBox Watch Watch App/OTFMagicBox Watch Watch App.entitlements"; + CODE_SIGN_ENTITLEMENTS = OTFMagicBoxWatch/OTFMagicBoxWatch.entitlements; CODE_SIGN_IDENTITY = "iPhone Distribution"; - CODE_SIGN_STYLE = Manual; + CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; - DEVELOPMENT_ASSET_PATHS = "\"OTFMagicBox Watch Watch App/Preview Content\""; - DEVELOPMENT_TEAM = ""; + DEVELOPMENT_ASSET_PATHS = "\"OTFMagicBoxWatch/Preview Content\""; + DEVELOPMENT_TEAM = 53UK9NNVG5; ENABLE_PREVIEWS = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; + GCC_C_LANGUAGE_STANDARD = gnu17; GENERATE_INFOPLIST_FILE = YES; - INFOPLIST_KEY_CFBundleDisplayName = "OTFMagicBox Watch"; + INFOPLIST_KEY_CFBundleDisplayName = OTFMagicBoxWatch; INFOPLIST_KEY_UISupportedInterfaceOrientations = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown"; INFOPLIST_KEY_WKCompanionAppBundleIdentifier = org.theraforge.magicbox.ios; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", ); + LOCALIZATION_PREFERS_STRING_CATALOGS = YES; MARKETING_VERSION = 1.0; PRODUCT_BUNDLE_IDENTIFIER = org.theraforge.magicbox.ios.watchkitapp; PRODUCT_NAME = "$(TARGET_NAME)"; - PROVISIONING_PROFILE_SPECIFIER = ""; SDKROOT = watchos; SKIP_INSTALL = YES; SWIFT_EMIT_LOC_STRINGS = YES; @@ -1379,91 +1354,307 @@ }; name = Release; }; - 7CA3D3172A8A43C400FCB048 /* Debug */ = { + 90C79E4D2B4E9ED00020D20F /* Care */ = { isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 14.0; + MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; + MTL_FAST_MATH = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + }; + name = Care; + }; + 90C79E4E2B4E9ED00020D20F /* Care */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 9BC94C5B492FDF7370667F8A /* Pods-OTFMagicBox.care.xcconfig */; buildSettings = { ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + CODE_SIGN_ENTITLEMENTS = OTFMagicBox/OTFMagicBox.entitlements; + CODE_SIGN_IDENTITY = "iPhone Developer"; + CODE_SIGN_STYLE = Manual; + CURRENT_PROJECT_VERSION = 2; + DEVELOPMENT_ASSET_PATHS = ""; + DEVELOPMENT_TEAM = 53UK9NNVG5; + ENABLE_PREVIEWS = YES; + EXCLUDED_ARCHS = ""; + INFOPLIST_FILE = OTFMagicBox/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 14.5; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + MARKETING_VERSION = 1.1.1; + PRODUCT_BUNDLE_IDENTIFIER = org.theraforge.magicbox.ios; + PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = OTFMagicBox_debug; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Care; + }; + 90C79E4F2B4E9ED00020D20F /* Care */ = { + isa = XCBuildConfiguration; + buildSettings = { BUNDLE_LOADER = "$(TEST_HOST)"; - CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 1; - DEVELOPMENT_TEAM = ZTGHLM6H83; - GENERATE_INFOPLIST_FILE = YES; - MARKETING_VERSION = 1.0; - PRODUCT_BUNDLE_IDENTIFIER = "tfmartins.OTFMagicBox-Watch-Watch-AppTests"; + DEVELOPMENT_TEAM = D25537G8CD; + INFOPLIST_FILE = OTFMagicBoxTests/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 14.5; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = MyCompany.OTFMagicBoxTests; PRODUCT_NAME = "$(TARGET_NAME)"; - SDKROOT = watchos; - SWIFT_EMIT_LOC_STRINGS = NO; SWIFT_VERSION = 5.0; - TARGETED_DEVICE_FAMILY = 4; - TEST_HOST = "$(BUILT_PRODUCTS_DIR)/OTFMagicBox Watch Watch App.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/OTFMagicBox Watch Watch App"; - WATCHOS_DEPLOYMENT_TARGET = 9.4; + TARGETED_DEVICE_FAMILY = "1,2"; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/OTFMagicBox.app/OTFMagicBox"; }; - name = Debug; + name = Care; }; - 7CA3D3182A8A43C400FCB048 /* Release */ = { + 90C79E502B4E9ED00020D20F /* Care */ = { isa = XCBuildConfiguration; + baseConfigurationReference = 62EB2116D692E7C5E54E80E1 /* Pods-OTFMagicBoxWatch.care.xcconfig */; buildSettings = { - ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; - BUNDLE_LOADER = "$(TEST_HOST)"; + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CODE_SIGN_ENTITLEMENTS = OTFMagicBoxWatch/OTFMagicBoxWatch.entitlements; + CODE_SIGN_IDENTITY = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; - DEVELOPMENT_TEAM = ZTGHLM6H83; + DEVELOPMENT_ASSET_PATHS = "\"OTFMagicBoxWatch/Preview Content\""; + DEVELOPMENT_TEAM = 53UK9NNVG5; + ENABLE_PREVIEWS = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; + GCC_C_LANGUAGE_STANDARD = gnu17; GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_KEY_CFBundleDisplayName = OTFMagicBoxWatch; + INFOPLIST_KEY_UISupportedInterfaceOrientations = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown"; + INFOPLIST_KEY_WKCompanionAppBundleIdentifier = org.theraforge.magicbox.ios; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + LOCALIZATION_PREFERS_STRING_CATALOGS = YES; MARKETING_VERSION = 1.0; - PRODUCT_BUNDLE_IDENTIFIER = "tfmartins.OTFMagicBox-Watch-Watch-AppTests"; + PRODUCT_BUNDLE_IDENTIFIER = org.theraforge.magicbox.ios.watchkitapp; PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = watchos; - SWIFT_EMIT_LOC_STRINGS = NO; + SKIP_INSTALL = YES; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)"; + SWIFT_EMIT_LOC_STRINGS = YES; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = 4; - TEST_HOST = "$(BUILT_PRODUCTS_DIR)/OTFMagicBox Watch Watch App.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/OTFMagicBox Watch Watch App"; - WATCHOS_DEPLOYMENT_TARGET = 9.4; + WATCHOS_DEPLOYMENT_TARGET = 8.0; }; - name = Release; + name = Care; + }; + 90C79E512B4E9F070020D20F /* CareHealth */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 14.0; + MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; + MTL_FAST_MATH = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + }; + name = CareHealth; }; - 7CA3D3192A8A43C400FCB048 /* Debug */ = { + 90C79E522B4E9F070020D20F /* CareHealth */ = { isa = XCBuildConfiguration; + baseConfigurationReference = BE0B0929EFF0E25E87CF3B71 /* Pods-OTFMagicBox.carehealth.xcconfig */; buildSettings = { ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; - CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + CODE_SIGN_ENTITLEMENTS = OTFMagicBox/OTFMagicBox.entitlements; + CODE_SIGN_IDENTITY = "iPhone Developer"; + CODE_SIGN_STYLE = Manual; + CURRENT_PROJECT_VERSION = 2; + DEVELOPMENT_ASSET_PATHS = ""; + DEVELOPMENT_TEAM = 53UK9NNVG5; + ENABLE_PREVIEWS = YES; + EXCLUDED_ARCHS = ""; + INFOPLIST_FILE = OTFMagicBox/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 14.5; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + MARKETING_VERSION = 1.1.1; + PRODUCT_BUNDLE_IDENTIFIER = org.theraforge.magicbox.ios; + PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = OTFMagicBox_debug; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = CareHealth; + }; + 90C79E532B4E9F070020D20F /* CareHealth */ = { + isa = XCBuildConfiguration; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 1; - DEVELOPMENT_TEAM = ZTGHLM6H83; - GENERATE_INFOPLIST_FILE = YES; - MARKETING_VERSION = 1.0; - PRODUCT_BUNDLE_IDENTIFIER = "tfmartins.OTFMagicBox-Watch-Watch-AppUITests"; + DEVELOPMENT_TEAM = D25537G8CD; + INFOPLIST_FILE = OTFMagicBoxTests/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 14.5; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = MyCompany.OTFMagicBoxTests; PRODUCT_NAME = "$(TARGET_NAME)"; - SDKROOT = watchos; - SWIFT_EMIT_LOC_STRINGS = NO; SWIFT_VERSION = 5.0; - TARGETED_DEVICE_FAMILY = 4; - TEST_TARGET_NAME = "OTFMagicBox Watch Watch App"; - WATCHOS_DEPLOYMENT_TARGET = 9.4; + TARGETED_DEVICE_FAMILY = "1,2"; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/OTFMagicBox.app/OTFMagicBox"; }; - name = Debug; + name = CareHealth; }; - 7CA3D31A2A8A43C400FCB048 /* Release */ = { + 90C79E542B4E9F070020D20F /* CareHealth */ = { isa = XCBuildConfiguration; + baseConfigurationReference = F2F96CA14BB557FB216784C9 /* Pods-OTFMagicBoxWatch.carehealth.xcconfig */; buildSettings = { - ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CODE_SIGN_ENTITLEMENTS = OTFMagicBoxWatch/OTFMagicBoxWatch.entitlements; + CODE_SIGN_IDENTITY = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; - DEVELOPMENT_TEAM = ZTGHLM6H83; + DEVELOPMENT_ASSET_PATHS = "\"OTFMagicBoxWatch/Preview Content\""; + DEVELOPMENT_TEAM = 53UK9NNVG5; + ENABLE_PREVIEWS = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; + GCC_C_LANGUAGE_STANDARD = gnu17; GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_KEY_CFBundleDisplayName = OTFMagicBoxWatch; + INFOPLIST_KEY_UISupportedInterfaceOrientations = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown"; + INFOPLIST_KEY_WKCompanionAppBundleIdentifier = org.theraforge.magicbox.ios; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + LOCALIZATION_PREFERS_STRING_CATALOGS = YES; MARKETING_VERSION = 1.0; - PRODUCT_BUNDLE_IDENTIFIER = "tfmartins.OTFMagicBox-Watch-Watch-AppUITests"; + PRODUCT_BUNDLE_IDENTIFIER = org.theraforge.magicbox.ios.watchkitapp; PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = watchos; - SWIFT_EMIT_LOC_STRINGS = NO; + SKIP_INSTALL = YES; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)"; + SWIFT_EMIT_LOC_STRINGS = YES; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = 4; - TEST_TARGET_NAME = "OTFMagicBox Watch Watch App"; - WATCHOS_DEPLOYMENT_TARGET = 9.4; + WATCHOS_DEPLOYMENT_TARGET = 8.0; }; - name = Release; + name = CareHealth; }; CE482D05262C954C00C0A2D5 /* Debug */ = { isa = XCBuildConfiguration; @@ -1583,7 +1774,7 @@ }; CE482D08262C954C00C0A2D5 /* Debug */ = { isa = XCBuildConfiguration; - baseConfigurationReference = C68B1F7051EF9581BEEB32BF /* Pods-OTFMagicBox.debug.xcconfig */; + baseConfigurationReference = C786F095D5BBC2634543074F /* Pods-OTFMagicBox.debug.xcconfig */; buildSettings = { ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; @@ -1595,6 +1786,7 @@ DEVELOPMENT_ASSET_PATHS = ""; DEVELOPMENT_TEAM = 53UK9NNVG5; ENABLE_PREVIEWS = YES; + EXCLUDED_ARCHS = ""; INFOPLIST_FILE = OTFMagicBox/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 14.5; LD_RUNPATH_SEARCH_PATHS = ( @@ -1612,7 +1804,7 @@ }; CE482D09262C954C00C0A2D5 /* Release */ = { isa = XCBuildConfiguration; - baseConfigurationReference = A5576C00FEC63C6798D1DB53 /* Pods-OTFMagicBox.release.xcconfig */; + baseConfigurationReference = F07B7C39F5B74DE968EFEDC4 /* Pods-OTFMagicBox.release.xcconfig */; buildSettings = { ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; @@ -1646,34 +1838,20 @@ isa = XCConfigurationList; buildConfigurations = ( 5A7B6DB026FD282C00F872A1 /* Debug */, + 90C79E532B4E9F070020D20F /* CareHealth */, + 90C79E4F2B4E9ED00020D20F /* Care */, 5A7B6DB126FD282C00F872A1 /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; - 7CA3D31B2A8A43C400FCB048 /* Build configuration list for PBXNativeTarget "OTFMagicBox Watch Watch App" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - 7CA3D3152A8A43C400FCB048 /* Debug */, - 7CA3D3162A8A43C400FCB048 /* Release */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; - 7CA3D31C2A8A43C400FCB048 /* Build configuration list for PBXNativeTarget "OTFMagicBox Watch Watch AppTests" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - 7CA3D3172A8A43C400FCB048 /* Debug */, - 7CA3D3182A8A43C400FCB048 /* Release */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; - 7CA3D31D2A8A43C400FCB048 /* Build configuration list for PBXNativeTarget "OTFMagicBox Watch Watch AppUITests" */ = { + 84F43F5D2B4D6A4C009DFFF8 /* Build configuration list for PBXNativeTarget "OTFMagicBoxWatch" */ = { isa = XCConfigurationList; buildConfigurations = ( - 7CA3D3192A8A43C400FCB048 /* Debug */, - 7CA3D31A2A8A43C400FCB048 /* Release */, + 84F43F5B2B4D6A4C009DFFF8 /* Debug */, + 90C79E542B4E9F070020D20F /* CareHealth */, + 90C79E502B4E9ED00020D20F /* Care */, + 84F43F5C2B4D6A4C009DFFF8 /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; @@ -1682,6 +1860,8 @@ isa = XCConfigurationList; buildConfigurations = ( CE482D05262C954C00C0A2D5 /* Debug */, + 90C79E512B4E9F070020D20F /* CareHealth */, + 90C79E4D2B4E9ED00020D20F /* Care */, CE482D06262C954C00C0A2D5 /* Release */, ); defaultConfigurationIsVisible = 0; @@ -1691,6 +1871,8 @@ isa = XCConfigurationList; buildConfigurations = ( CE482D08262C954C00C0A2D5 /* Debug */, + 90C79E522B4E9F070020D20F /* CareHealth */, + 90C79E4E2B4E9ED00020D20F /* Care */, CE482D09262C954C00C0A2D5 /* Release */, ); defaultConfigurationIsVisible = 0; diff --git a/OTFMagicBox/API/OTFNetworkObserver.swift b/OTFMagicBox/API/OTFNetworkObserver.swift index 2e7d005e..caee8b36 100644 --- a/OTFMagicBox/API/OTFNetworkObserver.swift +++ b/OTFMagicBox/API/OTFNetworkObserver.swift @@ -11,29 +11,29 @@ import Foundation class OTFNetworkObserver: ObservableObject { @Published private(set) var status: OTFNetworkStatus - + private let pathMonitor = NWPathMonitor() private let pathMonitorQueue = DispatchQueue(label: "NWPathMonitor") - + init(status: OTFNetworkStatus = .unsatisfied, active: Bool = true) { self.status = status if active { enablePathMonitor() } } - + private func enablePathMonitor() { pathMonitor.pathUpdateHandler = { path in guard path.status == .satisfied else { self.status = .offline return } - + self.pingEndpoint(isExpensive: path.isExpensive) } pathMonitor.start(queue: pathMonitorQueue) } - + func pingEndpoint(isExpensive: Bool) { guard let url = URL(string: "https://theraforge.org/api/v1/") else { self.status = .offline diff --git a/OTFMagicBox/API/OTFTheraforgeNetwork.swift b/OTFMagicBox/API/OTFTheraforgeNetwork.swift index 253412b3..531a7634 100644 --- a/OTFMagicBox/API/OTFTheraforgeNetwork.swift +++ b/OTFMagicBox/API/OTFTheraforgeNetwork.swift @@ -1,35 +1,35 @@ /* -Copyright (c) 2021, Hippocrates Technologies S.r.l.. All rights reserved. + Copyright (c) 2021, Hippocrates Technologies S.r.l.. All rights reserved. -Redistribution and use in source and binary forms, with or without modification, -are permitted provided that the following conditions are met: + Redistribution and use in source and binary forms, with or without modification, + are permitted provided that the following conditions are met: -1. Redistributions of source code must retain the above copyright notice, -this list of conditions and the following disclaimer. + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. -2. Redistributions in binary form must reproduce the above copyright notice, -this list of conditions and the following disclaimer in the documentation and/or -other materials provided with the distribution. + 2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation and/or + other materials provided with the distribution. -3. Neither the name of the copyright holder(s) nor the names of any contributor(s) may -be used to endorse or promote products derived from this software without specific -prior written permission. No license is granted to the trademarks of the copyright -holders even if such marks are included in this software. + 3. Neither the name of the copyright holder(s) nor the names of any contributor(s) may + be used to endorse or promote products derived from this software without specific + prior written permission. No license is granted to the trademarks of the copyright + holders even if such marks are included in this software. -4. Commercial redistribution in any form requires an explicit license agreement with the -copyright holder(s). Please contact support@hippocratestech.com for further information -regarding licensing. + 4. Commercial redistribution in any form requires an explicit license agreement with the + copyright holder(s). Please contact support@hippocratestech.com for further information + regarding licensing. -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND -ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. -IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, -INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT -NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, -WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) -ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY -OF SUCH DAMAGE. + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, + INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, + OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY + OF SUCH DAMAGE. */ import Foundation @@ -41,45 +41,45 @@ typealias AuthType = Request.SocialLogin.AuthType typealias SocialType = Request.SocialLogin.SocialType class OTFTheraforgeNetwork { - + static let shared = OTFTheraforgeNetwork() - + var otfNetworkService: TheraForgeNetwork! - + private init() { - + configureNetwork() - + } - + // Configure the API with required URL and API key. public func configureNetwork() { guard let url = URL(string: Constants.API.developmentUrl) else { OTFLog("Error: cannot create URL") return } - + let configurations = NetworkingLayer.Configurations(APIBaseURL: url, apiKey: YmlReader().apiKey) TheraForgeNetwork.configureNetwork(configurations) otfNetworkService = TheraForgeNetwork.shared } - + // Login request public func loginRequest(email: String, password: String) -> AnyPublisher { return Future { promise in self.otfNetworkService.login(request: OTFCloudClientAPI.Request.Login(email: email, - password: password)) { [weak self] result in + password: password)) { [weak self] result in self?.handleResponse(result, completion: promise) } } .receive(on: RunLoop.main) .eraseToAnyPublisher() } - + public func socialLoginRequest(userType: UserType, socialType: SocialType, authType: AuthType, - idToken: String) -> AnyPublisher{ + idToken: String) -> AnyPublisher { return Future { promise in let socialRequest = OTFCloudClientAPI.Request.SocialLogin(userType: userType, socialType: socialType, @@ -92,23 +92,19 @@ class OTFTheraforgeNetwork { .receive(on: RunLoop.main) .eraseToAnyPublisher() } - - // Registration request - // swiftlint:disable all - public func signUpRequest(firstName: String, lastName: String, type: String, email: String, - password: String, dob: String, gender: String, encryptedMasterKey: String, publicKey: String, encryptedDefaultStorageKey: String, encryptedConfidentialStorageKey: String) -> AnyPublisher { - + + public func signUpRequest(signupRequest: OTFCloudClientAPI.Request.SignUp) -> AnyPublisher { + return Future { promise in - self.otfNetworkService.signup(request: OTFCloudClientAPI.Request.SignUp(email: email, password: password, first_name: firstName, last_name: lastName, type: .patient, dob: dob, gender: gender, phoneNo: "", encryptedMasterKey: encryptedMasterKey, publicKey: publicKey, encryptedDefaultStorageKey: encryptedDefaultStorageKey, encryptedConfidentialStorageKey: encryptedConfidentialStorageKey)) { [weak self] result in + self.otfNetworkService.signup( + request: signupRequest) { [weak self] result in self?.handleResponse(result, completion: promise) } } .receive(on: RunLoop.main) .eraseToAnyPublisher() } - - // delete user account public func deleteUser(userId: String) -> AnyPublisher { return Future { promise in @@ -124,10 +120,8 @@ class OTFTheraforgeNetwork { .receive(on: RunLoop.main) .eraseToAnyPublisher() } - - // Forgot password request - public func forgotPassword(email: String) -> AnyPublisher{ + public func forgotPassword(email: String) -> AnyPublisher { return Future { promise in self.otfNetworkService.forgotPassword(request: OTFCloudClientAPI.Request.ForgotPassword(email: email)) { [weak self] result in self?.handleResponse(result, completion: promise) @@ -136,36 +130,31 @@ class OTFTheraforgeNetwork { .receive(on: RunLoop.main) .eraseToAnyPublisher() } - + // Reset password request public func resetPassword(email: String, code: String, newPassword: String) -> AnyPublisher { return Future { promise in self.otfNetworkService.resetPassword(request: OTFCloudClientAPI.Request.ResetPassword(email: email, - code: code, - newPassword: newPassword)) { [weak self] result in + code: code, + newPassword: newPassword)) { [weak self] result in self?.handleResponse(result, completion: promise) } } .receive(on: RunLoop.main) .eraseToAnyPublisher() } - + // Signout request. public func signOut() -> AnyPublisher { return Future { promise in self.otfNetworkService.signOut { [weak self] result in - switch result { - case .failure(_): self?.handleResponse(result, promise: promise) - case .success(_): - self?.moveToOnboardingView() - } } } .receive(on: RunLoop.main) .eraseToAnyPublisher() } - + // Change password request. public func changePassword(email: String, oldPassword: String, newPassword: String) -> AnyPublisher { return Future { promise in @@ -176,18 +165,25 @@ class OTFTheraforgeNetwork { .receive(on: RunLoop.main) .eraseToAnyPublisher() } - + // Upload file request. - public func uploadFile(data: Data, fileName: String, type: Request.AttachmentLocation,encryptedFileKey: String? = nil, hashFileKey: String ) -> AnyPublisher { + public func uploadFile(data: Data, fileName: String, type: Request.AttachmentLocation, encryptedFileKey: String? = nil, hashFileKey: String ) -> AnyPublisher { return Future { promise in - self.otfNetworkService.uploadFile(request: OTFCloudClientAPI.Request.UploadFiles(data: data, fileName: fileName, type: type, meta: "true", encryptedFileKey: encryptedFileKey, hashFileKey: hashFileKey)) { [weak self] result in + self.otfNetworkService.uploadFile( + request: OTFCloudClientAPI.Request.UploadFiles( + data: data, + fileName: fileName, + type: type, + meta: "true", + encryptedFileKey: encryptedFileKey, + hashFileKey: hashFileKey)) { [weak self] result in self?.handleResponse(result, promise: promise) } } .receive(on: RunLoop.main) .eraseToAnyPublisher() } - + // Download file request. public func downloadFile(attachmentID: String, type: Request.AttachmentLocation) -> AnyPublisher { return Future { promise in @@ -198,7 +194,7 @@ class OTFTheraforgeNetwork { .receive(on: RunLoop.main) .eraseToAnyPublisher() } - + // Delete file request. public func deleteFile(attachmentID: String) -> AnyPublisher { return Future { promise in @@ -209,22 +205,23 @@ class OTFTheraforgeNetwork { .receive(on: RunLoop.main) .eraseToAnyPublisher() } - + func refreshToken(_ completionHandler: @escaping (Result) -> Void) { - guard (TheraForgeKeychainService.shared.loadAuth() != nil) else { + + if TheraForgeKeychainService.shared.loadAuth() == nil { completionHandler(.failure(.missingCredential)) return } - + otfNetworkService.refreshToken { [weak self] response in self?.handleResponse(response, promise: completionHandler) } } - + func disconnectFromSSE() { NetworkingLayer.shared.eventSource?.disconnect() } - + func handleResponse(_ response: Result, completion: ((Result) -> Void)?) { switch response { case .success(_): @@ -237,11 +234,9 @@ class OTFTheraforgeNetwork { return } } - + completion?(response) } - - func handleResponse(_ response: Result, promise: (Result) -> Void) { switch response { case .success(_): @@ -254,13 +249,13 @@ class OTFTheraforgeNetwork { return } } - return promise(response) + return promise(response) } - + public func moveToOnboardingView() { DispatchQueue.main.async { UserDefaultsManager.setOnboardingCompleted(false) - try? CareKitManager.shared.wipe() + try? CareKitStoreManager.shared.wipe() self.disconnectFromSSE() NotificationCenter.default.post(name: .onboardingDidComplete, object: false) } diff --git a/OTFMagicBox/API/SSEAndSyncManager.swift b/OTFMagicBox/API/SSEAndSyncManager.swift index f5b00f1f..f1383b57 100644 --- a/OTFMagicBox/API/SSEAndSyncManager.swift +++ b/OTFMagicBox/API/SSEAndSyncManager.swift @@ -1,25 +1,25 @@ /* Copyright (c) 2021, Hippocrates Technologies S.r.l.. All rights reserved. - + Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: - + 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. - + 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. - + 3. Neither the name of the copyright holder(s) nor the names of any contributor(s) may be used to endorse or promote products derived from this software without specific prior written permission. No license is granted to the trademarks of the copyright holders even if such marks are included in this software. - + 4. Commercial redistribution in any form requires an explicit license agreement with the copyright holder(s). Please contact support@hippocratestech.com for further information regarding licensing. - + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. @@ -38,35 +38,42 @@ import OTFUtilities class SSEAndSyncManager { static let shared = SSEAndSyncManager() - + // Subscribe to SSE public func subscribeToSSEWith(auth: Auth) { OTFTheraforgeNetwork.shared.otfNetworkService.eventSourceOnOpen = { [unowned self] in syncDatabase(postNotification: true) } - + OTFTheraforgeNetwork.shared.otfNetworkService.onReceivedMessage = { [unowned self] event in OTFLog("event type %{public}@.", event.type.rawValue) if event.type.rawValue == EventType.dbUpdate.rawValue { syncDatabase(postNotification: true) } else if event.type.rawValue == EventType.userDeleted.rawValue { - NotificationCenter.default.post(name: .deleteUserAccount, object: nil) - } } - - OTFTheraforgeNetwork.shared.otfNetworkService.eventSourceOnComplete = { code, reconnect, error in + + OTFTheraforgeNetwork.shared.otfNetworkService.eventSourceOnComplete = { _, reconnect, error in OTFError("error on receiving event in SSEAndSyncManager %{public}@.", error?.localizedDescription ?? "") if reconnect == true { TheraForgeNetwork.shared.observeOnServerSentEvents(auth: auth) } } - + TheraForgeNetwork.shared.observeOnServerSentEvents(auth: auth) } - + private func syncDatabase(postNotification: Bool = false) { + // Sync local sore witth watchOS + CloudantSyncManager.shared.cloudantStore?.synchronize(target: .mobile, completion: { error in + if let error = error { + print(error) + } else { + OTFLog("Synced successfully!") + } + }) + CloudantSyncManager.shared.syncCloudantStore(notifyWhenDone: postNotification) { _ in } diff --git a/OTFMagicBox/API/TheraForgeHTTPInterceptor.swift b/OTFMagicBox/API/TheraForgeHTTPInterceptor.swift index af853b79..d14ce03b 100644 --- a/OTFMagicBox/API/TheraForgeHTTPInterceptor.swift +++ b/OTFMagicBox/API/TheraForgeHTTPInterceptor.swift @@ -1,35 +1,35 @@ /* -Copyright (c) 2021, Hippocrates Technologies S.r.l.. All rights reserved. + Copyright (c) 2021, Hippocrates Technologies S.r.l.. All rights reserved. -Redistribution and use in source and binary forms, with or without modification, -are permitted provided that the following conditions are met: + Redistribution and use in source and binary forms, with or without modification, + are permitted provided that the following conditions are met: -1. Redistributions of source code must retain the above copyright notice, -this list of conditions and the following disclaimer. + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. -2. Redistributions in binary form must reproduce the above copyright notice, -this list of conditions and the following disclaimer in the documentation and/or -other materials provided with the distribution. + 2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation and/or + other materials provided with the distribution. -3. Neither the name of the copyright holder(s) nor the names of any contributor(s) may -be used to endorse or promote products derived from this software without specific -prior written permission. No license is granted to the trademarks of the copyright -holders even if such marks are included in this software. + 3. Neither the name of the copyright holder(s) nor the names of any contributor(s) may + be used to endorse or promote products derived from this software without specific + prior written permission. No license is granted to the trademarks of the copyright + holders even if such marks are included in this software. -4. Commercial redistribution in any form requires an explicit license agreement with the -copyright holder(s). Please contact support@hippocratestech.com for further information -regarding licensing. + 4. Commercial redistribution in any form requires an explicit license agreement with the + copyright holder(s). Please contact support@hippocratestech.com for further information + regarding licensing. -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND -ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. -IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, -INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT -NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, -WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) -ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY -OF SUCH DAMAGE. + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, + INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, + OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY + OF SUCH DAMAGE. */ import OTFCDTDatastore @@ -42,7 +42,7 @@ class TheraForgeHTTPInterceptor: NSObject, CDTHTTPInterceptor { forHTTPHeaderField: "Client") context.request.addValue("\(TheraForgeNetwork.configurations!.apiKey)", forHTTPHeaderField: "API-KEY") - + if let currentAuth = TheraForgeNetwork.shared.currentAuth { context.request.setValue("Bearer \(currentAuth.token)", forHTTPHeaderField: "Authorization") } else if let auth = TheraForgeKeychainService.shared.loadAuth() { @@ -57,12 +57,12 @@ class TheraForgeHTTPInterceptor: NSObject, CDTHTTPInterceptor { NSLog("TheraForgeHTTPInterceptor: Response is nil") return context } - + guard let responseData = context.responseData else { NSLog("TheraForgeHTTPInterceptor: Response data is nil") return context } - + NSLog("TheraForgeHTTPInterceptor: \n\n\(context.request)\n\n\(String(describing: response))\n\ndata: \(String(data: responseData, encoding: .utf8)!)") return context } diff --git a/OTFMagicBox/AppDelegate.swift b/OTFMagicBox/AppDelegate.swift index 1718db81..27ca8e90 100644 --- a/OTFMagicBox/AppDelegate.swift +++ b/OTFMagicBox/AppDelegate.swift @@ -1,59 +1,69 @@ /* -Copyright (c) 2021, Hippocrates Technologies S.r.l.. All rights reserved. - -Redistribution and use in source and binary forms, with or without modification, -are permitted provided that the following conditions are met: - -1. Redistributions of source code must retain the above copyright notice, -this list of conditions and the following disclaimer. - -2. Redistributions in binary form must reproduce the above copyright notice, -this list of conditions and the following disclaimer in the documentation and/or -other materials provided with the distribution. - -3. Neither the name of the copyright holder(s) nor the names of any contributor(s) may -be used to endorse or promote products derived from this software without specific -prior written permission. No license is granted to the trademarks of the copyright -holders even if such marks are included in this software. - -4. Commercial redistribution in any form requires an explicit license agreement with the -copyright holder(s). Please contact support@hippocratestech.com for further information -regarding licensing. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND -ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. -IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, -INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT -NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, -WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) -ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY -OF SUCH DAMAGE. + Copyright (c) 2021, Hippocrates Technologies S.r.l.. All rights reserved. + + Redistribution and use in source and binary forms, with or without modification, + are permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation and/or + other materials provided with the distribution. + + 3. Neither the name of the copyright holder(s) nor the names of any contributor(s) may + be used to endorse or promote products derived from this software without specific + prior written permission. No license is granted to the trademarks of the copyright + holders even if such marks are included in this software. + + 4. Commercial redistribution in any form requires an explicit license agreement with the + copyright holder(s). Please contact support@hippocratestech.com for further information + regarding licensing. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, + INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, + OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY + OF SUCH DAMAGE. */ import UIKit +import OTFUtilities +import WatchConnectivity +import OTFCareKitStore +import OTFCloudantStore import OTFTemplateBox import OTFResearchKit -import OTFUtilities @main class AppDelegate: UIResponder, UIApplicationDelegate { var window: UIWindow? - + let careKitManager = CareKitStoreManager.shared + private(set) lazy var sessionManager: SessionManager = { + let sessionManager = SessionManager() + sessionManager.peer = self.careKitManager.cloudantSyncManager.peer + sessionManager.store = self.careKitManager.cloudantStore + return sessionManager + }() + func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { // Override point for customization after application launch. - + do { try OTFConfigManager.shared.loadDataFromFile(nil) } catch { OTFLog("error while loading data from file %{public}@", error.localizedDescription) } - let tintColor = YmlReader().tintColor + let tintColor = YmlReader().appStyle.buttonTextColor.color let defaultProtection = OTFConfigManager.shared.defaultOTFProtectionLevel() - + switch defaultProtection { case .runToCompletionWithIn10Seconds: OTFLog("Default protection is set runToCompletionWithIn10Seconds") @@ -67,25 +77,71 @@ class AppDelegate: UIResponder, UIApplicationDelegate { OCKStoreManager.shared.coreDataStore.populateSampleData() + WCSession.default.delegate = sessionManager + WCSession.default.activate() + UIView.appearance(whenContainedInInstancesOf: [ORKTaskViewController.self]).tintColor = tintColor + if #available(iOS 15, *) { + let appearance = UINavigationBarAppearance() + appearance.configureWithOpaqueBackground() + UINavigationBar.appearance().standardAppearance = appearance + UINavigationBar.appearance().scrollEdgeAppearance = appearance + } + return true } // MARK: UISceneSession Lifecycle - + func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration { // Called when a new scene session is being created. // Use this method to select a configuration to create the new scene with. return UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role) } - + func application(_ application: UIApplication, didDiscardSceneSessions sceneSessions: Set) { // Called when the user discards a scene session. // If any sessions were discarded while the application was not running, this will be called shortly after application:didFinishLaunchingWithOptions. // Use this method to release any resources that were specific to the discarded scenes, as they will not return. } - - } +class SessionManager: NSObject, WCSessionDelegate { + + fileprivate var peer: OTFWatchConnectivityPeer! + fileprivate var store: OTFCloudantStore! + + func session(_ session: WCSession, + activationDidCompleteWith activationState: WCSessionActivationState, + error: Error?) { + print("WCSession activation did complete: \(activationState)") + } + + func sessionDidBecomeInactive(_ session: WCSession) { + print("WCSession did become inactive") + } + + func sessionDidDeactivate(_ session: WCSession) { + print("WCSession did deactivate") + } + + func session(_ session: WCSession, + didReceiveMessage message: [String: Any], + replyHandler: @escaping ([String: Any]) -> Void) { + print("Did receive message from WATCHAPP! - \(message)") + if message[databaseSyncedKey] as? String != nil { + self.store.synchronize { error in + print(error?.localizedDescription ?? "Successful sync!") + DispatchQueue.main.async { + NotificationCenter.default.post(name: .databaseSuccessfllySynchronized, object: nil) + CloudantSyncManager.shared.syncCloudantStore(notifyWhenDone: false) { _ in } + } + } + } else { + peer.reply(to: message, store: store) { reply in + replyHandler(reply) + } + } + } +} diff --git a/OTFMagicBox/AppSysParameters.yml b/OTFMagicBox/AppSysParameters.yml index 1b8de5ff..7d0a6372 100644 --- a/OTFMagicBox/AppSysParameters.yml +++ b/OTFMagicBox/AppSysParameters.yml @@ -106,12 +106,13 @@ DataModel: useCareKit: "true" ############################################################################## -# TheraForge system # +# TheraForge Custom Style # ############################################################################## #Fixed colors -# Usually don't use fixed colors because they don't adapt to dark mode and accessibility mode. +# Avoid using fixed UIKit colors because they don't adapt to dark mode and accessibility modes. +# See: https://developer.apple.com/documentation/uikit/uicolor/standard_colors#3174519 # black # blue @@ -130,9 +131,10 @@ DataModel: # yellow -# Its recommened to use Adaptable colors because they adapt to dark mode and accessibility mode. -#Adaptable gray colors +# It's recommended to use adaptable UIKit grey colors because they adapt to dark mode and accessibility mode. +# See: https://developer.apple.com/documentation/uikit/uicolor/standard_colors#3281252 +# Adaptable gray colors # systemGray # systemGray1 # systemGray2 @@ -141,8 +143,11 @@ DataModel: # systemGray5 # systemGray6 -#Adaptable colors +# It's recommended to use adaptable UIKit colors because they adapt to dark mode and accessibility mode. +# See: https://developer.apple.com/documentation/uikit/uicolor/standard_colors#3174530 +# And see: https://developer.apple.com/documentation/uikit/uicolor/ui_element_colors +# Adaptable colors # systemBlue # systemBrown # systemOrange @@ -170,76 +175,80 @@ DataModel: # secondarySystemBackground # tertiarySystemBackground - appTheme: - backgroundColor: "systemBackground" - textColor: "label" - separatorColor: "separator" - cellbackgroundColor: "secondarySystemGroupedBackground" - buttonTextColor: "systemBlue" - borderColor: "Black" - headerColor: "label" - screenTitleFont: "Header" - screenTitleWeight: "" - headerTitleFont: "HeaderInherited" - headerTitleWeight: "Bold" - textWeight: "" - textFont: "Inherited" - appTitleSize: "Large Title" + # Select the active style + selectedStyle: "customStyle" + + # Define available styles with their respective configurations + styles: + # A clean and customizable look, providing a starting point for your unique app style. + - name: "customStyle" + # Background color of the app + backgroundColor: "systemBackground" + # Text color used throughout the app + textColor: "label" + # Color of separators between UI elements + separatorColor: "separator" + # Background color of table cells + cellbackgroundColor: "secondarySystemGroupedBackground" + # Text color for buttons with a red theme + buttonTextColor: "systemBlue" + # Color for borders + borderColor: "Black" + # Text color for headers and labels + headerColor: "label" + # Font for screen titles + screenTitleFont: "Header" + # Font weight for screen titles (if applicable) + screenTitleWeight: "" + # Font for header titles + headerTitleFont: "HeaderInherited" + # Font weight for header titles + headerTitleWeight: "Bold" + # Default font weight for text + textWeight: "" + # Default font for text + textFont: "Inherited" + # Font size for app titles + appTitleSize: "Large Title" + # Embraces the aesthetics of Apple's Health application, maintaining a familiar and health-centric appearance. + - name: "healthStyle" + backgroundColor: "systemBackground" + textColor: "label" + separatorColor: "separator" + cellbackgroundColor: "secondarySystemGroupedBackground" + buttonTextColor: "systemBlue" + borderColor: "Black" + headerColor: "label" + screenTitleFont: "Header" + screenTitleWeight: "" + headerTitleFont: "HeaderInherited" + headerTitleWeight: "Bold" + textWeight: "" + textFont: "Inherited" + appTitleSize: "Large Title" + # Provides a standard and consistent appearance, suitable for CareKit components. + - name: "careKitStyle" + backgroundColor: "systemBackground" + textColor: "label" + separatorColor: "separator" + cellbackgroundColor: "secondarySystemGroupedBackground" + buttonTextColor: "Teal" + borderColor: "Black" + headerColor: "label" + screenTitleFont: "Header" + screenTitleWeight: "" + headerTitleFont: "HeaderInherited" + headerTitleWeight: "Bold" + textWeight: "" + textFont: "Inherited" + appTitleSize: "Large Title" designConfig: # Offset value. - name: "offset" textValue: "20" - # Color codes - # Colors will be used in the design of the application. - # Please choose the colors according to Human Interface Guidelines from Apple. - # Refer here https://developer.apple.com/design/human-interface-guidelines/ios/visual-design/color - - name: "tintColor" - textValue: "Blue" - - name: "label" - textValue: "Teal" - - name: "secondaryLabel" - textValue: "Brown" - - name: "tertiaryLabel" - textValue: "Cyan" - - name: "customBackground" - textValue: "Brown" - - name: "secondaryCustomBackground" - textValue: "Black" - - name: "customGroupedBackground" - textValue: "Brown" - - name: "secondaryCustomGroupedBackground" - textValue: "Brown" - - name: "tertiaryCustomGroupedBackground" - textValue: "Brown" - - name: "separator" - textValue: "Gray" - - name: "customFill" - textValue: "Blue" - - name: "secondaryCustomFill" - textValue: "Mint" - - name: "tertiaryCustomFill" - textValue: "Teal" - - name: "quaternaryCustomFill" - textValue: "Blue" - - name: "customBlue" - textValue: "blue" - - name: "customGray" - textValue: "Gray" - - name: "customGray2" - textValue: "Gray2" - - name: "customGray3" - textValue: "Gray3" - - name: "customGray4" - textValue: "Gray4" - - name: "customGray5" - textValue: "Gray5" - - name: "black" - textValue: "black" - - name: "white" - textValue: "white" # Fonts # Fonts will be used in the design of the application. diff --git a/OTFMagicBox/CheckUp/CheckUpView.swift b/OTFMagicBox/CheckUp/CheckUpView.swift index d3065f31..0d5f3e22 100644 --- a/OTFMagicBox/CheckUp/CheckUpView.swift +++ b/OTFMagicBox/CheckUp/CheckUpView.swift @@ -1,25 +1,25 @@ /* Copyright (c) 2021, Hippocrates Technologies S.r.l.. All rights reserved. - + Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: - + 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. - + 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. - + 3. Neither the name of the copyright holder(s) nor the names of any contributor(s) may be used to endorse or promote products derived from this software without specific prior written permission. No license is granted to the trademarks of the copyright holders even if such marks are included in this software. - + 4. Commercial redistribution in any form requires an explicit license agreement with the copyright holder(s). Please contact support@hippocratestech.com for further information regarding licensing. - + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. @@ -37,28 +37,28 @@ import SwiftUI struct CheckUpView: View { @StateObject var viewmodel = CheckUpViewModel() @State private var isPresenting = false - + var body: some View { VStack { Text(Constants.CustomiseStrings.checkUp).font(.headerFontStyle) .foregroundColor(.otfTextColor) - .font(YmlReader().appTheme?.screenTitleFont.appFont ?? Font.system(size: 17.0)) - .fontWeight(YmlReader().appTheme?.headerTitleWeight.fontWeight) + .font(Font.otfscreenTitleFont) + .fontWeight(Font.otfheaderTitleWeight) List { Section(header: Text(Constants.CustomiseStrings.checkupListHeader) - .foregroundColor(.otfHeaderColor)) { - CountProgressRow(title: Constants.CustomiseStrings.progressRowTitle1, + .foregroundColor(.otfHeaderColor)) { + CountProgressRow(title: Constants.CustomiseStrings.progressRowTitle1, completed: viewmodel.activityTasksAndEvents.completedTasks.count, total: viewmodel.activityTasksAndEvents.eventsOfTasks.count, color: .blue, lineWidth: 4.0) .padding(.vertical, Metrics.PADDING_VERTICAL_ROW) - PercentProgressRow(title:Constants.CustomiseStrings.progressRowTitle2, + PercentProgressRow(title: Constants.CustomiseStrings.progressRowTitle2, progress: Float(viewmodel.medicationTasksAndEvents.progress), color: .green, lineWidth: 4.0) .padding(.vertical, Metrics.PADDING_VERTICAL_ROW) - PercentProgressRow(title:Constants.CustomiseStrings.progressRowTitle3, + PercentProgressRow(title: Constants.CustomiseStrings.progressRowTitle3, progress: Float(viewmodel.checkupTasksAndEvents.progress), color: .green, lineWidth: 4.0) @@ -70,16 +70,16 @@ struct CheckUpView: View { lineWidth: 4.0) .padding(.vertical, Metrics.PADDING_VERTICAL_ROW) } - .listRowBackground(Color.otfCellBackground) + .listRowBackground(Color.otfCellBackground) } - .onReceive(NotificationCenter.default.publisher(for: .deleteUserAccount)) { notification in + .onReceive(NotificationCenter.default.publisher(for: .deleteUserAccount)) { _ in isPresenting = true }.alert(isPresented: $isPresenting) { - + Alert( title: Text(Constants.CustomiseStrings.accountDeleted) - .font(YmlReader().appTheme?.textFont.appFont ?? Font.system(size: 17.0)) - .fontWeight(YmlReader().appTheme?.textWeight.fontWeight), + .font(Font.otfAppFont) + .fontWeight(Font.otfFontWeight), message: Text(Constants.deleteAccount), dismissButton: .default(Text(Constants.CustomiseStrings.okay), action: { OTFTheraforgeNetwork.shared.moveToOnboardingView() @@ -88,8 +88,8 @@ struct CheckUpView: View { } .listStyle(GroupedListStyle()) .onAppear { - UITableView.appearance().separatorColor = YmlReader().appTheme?.separatorColor.color - UITableView.appearance().backgroundColor = YmlReader().appTheme?.backgroundColor.color + UITableView.appearance().separatorColor = YmlReader().appStyle.separatorColor.color + UITableView.appearance().backgroundColor = YmlReader().appStyle.backgroundColor.color DispatchQueue.main.asyncAfter(deadline: .now() + 0.2) { viewmodel.fetchTasks() } @@ -115,7 +115,7 @@ struct ViewProvider: LibraryContentProvider { lineWidth: 4.0), title: Constants.CustomiseStrings.rowTitle2, category: .control) - + LibraryItem(CountProgressRow(title: Constants.CustomiseStrings.rowTitle3, completed: 3, total: 5, @@ -123,25 +123,25 @@ struct ViewProvider: LibraryContentProvider { lineWidth: 4.0), title: Constants.CustomiseStrings.rowTitle4, category: .control) - + LibraryItem(InstructionTaskView(task: dummyTask, date: Date(), storeManager: storeManager), title: Constants.CustomiseStrings.rowTitle5, category: .control) - + LibraryItem(GridTaskView(task: dummyTask, date: Date(), storeManager: storeManager), title: Constants.CustomiseStrings.rowTitle6, category: .control) - + LibraryItem(SimpleTaskView(task: dummyTask, date: Date(), storeManager: storeManager), title: Constants.CustomiseStrings.rowTitle7, category: .control) - + LibraryItem(ChecklistTaskView(task: dummyTask, date: Date(), storeManager: storeManager), title: Constants.CustomiseStrings.rowTitle8, category: .control) - + LibraryItem(ButtonLogTaskView(task: dummyTask, date: Date(), storeManager: storeManager), title: Constants.CustomiseStrings.rowTitle9, diff --git a/OTFMagicBox/CheckUp/CheckUpViewModel.swift b/OTFMagicBox/CheckUp/CheckUpViewModel.swift index fb11b5cb..dae3401b 100644 --- a/OTFMagicBox/CheckUp/CheckUpViewModel.swift +++ b/OTFMagicBox/CheckUp/CheckUpViewModel.swift @@ -1,25 +1,25 @@ /* Copyright (c) 2021, Hippocrates Technologies S.r.l.. All rights reserved. - + Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: - + 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. - + 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. - + 3. Neither the name of the copyright holder(s) nor the names of any contributor(s) may be used to endorse or promote products derived from this software without specific prior written permission. No license is granted to the trademarks of the copyright holders even if such marks are included in this software. - + 4. Commercial redistribution in any form requires an explicit license agreement with the copyright holder(s). Please contact support@hippocratestech.com for further information regarding licensing. - + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. @@ -39,7 +39,6 @@ import OTFCareKitUI struct TaskEvents { let interval: DateInterval let events: [OCKEvent]? - var progress: Double { guard let events = events, !events.isEmpty else { return 0 } let performed = events.filter({ $0.outcome != nil }) @@ -50,14 +49,12 @@ struct TaskEvents { struct CategoryTasksAndEvents { let category: TaskCategory let eventsOfTasks: [TaskEvents] - var completedTasks: [OCKTask] { let completed = eventsOfTasks.filter({ $0.progress == 1 }) return completed.compactMap({ $0.events?.first?.task }) } - var progress: Double { - guard eventsOfTasks.count > 0 else { + guard !eventsOfTasks.isEmpty else { return 0 } return Double(completedTasks.count) / Double(eventsOfTasks.count) @@ -69,25 +66,25 @@ final class CheckUpViewModel: ObservableObject { @Published private(set) var activityTasksAndEvents = CategoryTasksAndEvents(category: .activity, eventsOfTasks: []) @Published private(set) var checkupTasksAndEvents = CategoryTasksAndEvents(category: .checkup, eventsOfTasks: []) @Published private(set) var appointmentTasksAndEvents = CategoryTasksAndEvents(category: .appointment, eventsOfTasks: []) - + func fetchTasks() { let todayStart = Calendar.current.startOfDay(for: Date()) let todayEnd = Calendar.current.date(byAdding: .day, value: 1, to: todayStart)!.addingTimeInterval(-1) let todayInterval = DateInterval(start: todayStart, end: todayEnd) let query = OCKTaskQuery(dateInterval: DateInterval(start: todayStart, end: todayEnd)) - - CareKitManager.shared.cloudantStore?.fetchTasks(query: query, - callbackQueue: DispatchQueue.global(qos: .userInitiated)) { result in + + CareKitStoreManager.shared.cloudantStore?.fetchTasks(query: query, + callbackQueue: DispatchQueue.global(qos: .userInitiated)) { result in if case let .success(tasks) = result { let group = DispatchGroup() var error: Error? var events: [OCKEvent] = [] - + for task in tasks { group.enter() let query = OCKEventQuery(dateInterval: todayInterval) - - CareKitManager.shared.cloudantStore?.fetchEvents(task: task, query: query, previousEvents: []) { + + CareKitStoreManager.shared.cloudantStore?.fetchEvents(task: task, query: query, previousEvents: []) { switch $0 { case .failure(let fetchError): error = fetchError @@ -97,21 +94,21 @@ final class CheckUpViewModel: ObservableObject { group.leave() } } - + group.notify(queue: .main) { if error != nil { return } - + let medTasksAndEvents = events.getTasksAndEventsOf(category: .medication, for: todayInterval) self.medicationTasksAndEvents = CategoryTasksAndEvents(category: .medication, eventsOfTasks: medTasksAndEvents) - + let actTasksAndEvents = events.getTasksAndEventsOf(category: .activity, for: todayInterval) self.activityTasksAndEvents = CategoryTasksAndEvents(category: .activity, eventsOfTasks: actTasksAndEvents) - + let checkupTasksAndEvents = events.getTasksAndEventsOf(category: .checkup, for: todayInterval) self.checkupTasksAndEvents = CategoryTasksAndEvents(category: .checkup, eventsOfTasks: checkupTasksAndEvents) - + let appointTasksAndEvents = events.getTasksAndEventsOf(category: .appointment, for: todayInterval) self.appointmentTasksAndEvents = CategoryTasksAndEvents(category: .appointment, eventsOfTasks: appointTasksAndEvents) } diff --git a/OTFMagicBox/CheckUp/CountProgressRow.swift b/OTFMagicBox/CheckUp/CountProgressRow.swift index 288fd89c..295dfbcb 100644 --- a/OTFMagicBox/CheckUp/CountProgressRow.swift +++ b/OTFMagicBox/CheckUp/CountProgressRow.swift @@ -1,25 +1,25 @@ /* Copyright (c) 2021, Hippocrates Technologies S.r.l.. All rights reserved. - + Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: - + 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. - + 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. - + 3. Neither the name of the copyright holder(s) nor the names of any contributor(s) may be used to endorse or promote products derived from this software without specific prior written permission. No license is granted to the trademarks of the copyright holders even if such marks are included in this software. - + 4. Commercial redistribution in any form requires an explicit license agreement with the copyright holder(s). Please contact support@hippocratestech.com for further information regarding licensing. - + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. @@ -35,41 +35,41 @@ import SwiftUI struct CountProgressRow: View { - + static var lessonsCompleted: CountProgressRow { CountProgressRow(title: "Lessons Completed", completed: 3, total: 5, color: .green, lineWidth: 4.0) } - + static var appointmentsScheduled: CountProgressRow { CountProgressRow(title: "Appointments", completed: 1, total: 3, color: .green, lineWidth: 4.0) } - + var title: String var completed: Int var total: Int var color: Color var lineWidth: CGFloat var opacity: Double = 0.3 - + var body: some View { HStack { Text(title) .foregroundColor(.otfTextColor) - .fontWeight(YmlReader().appTheme?.textWeight.fontWeight) - .font(YmlReader().appTheme?.textFont.appFont ?? Font.system(size: 17.0)) + .fontWeight(Font.otfFontWeight) + .font(Font.otfAppFont) Spacer() Text("\(completed) of \(total)") - .font(YmlReader().appTheme?.textFont.appFont ?? Font.system(size: 17.0)) - .fontWeight(YmlReader().appTheme?.textWeight.fontWeight) + .font(Font.otfAppFont) + .fontWeight(Font.otfFontWeight) .foregroundColor(color) RingProgressView(progress: total < 1 ? 0 : Float(completed) / Float(total), - color: color, - lineWidth: lineWidth) - .frame(width: Metrics.FRAME_ROW_WIDTH, height: Metrics.FRAME_ROW_HEIGHT) + color: color, + lineWidth: lineWidth) + .frame(width: Metrics.FRAME_ROW_WIDTH, height: Metrics.FRAME_ROW_HEIGHT) } } } diff --git a/OTFMagicBox/CheckUp/PercentProgressRow.swift b/OTFMagicBox/CheckUp/PercentProgressRow.swift index 94153305..bd699eac 100644 --- a/OTFMagicBox/CheckUp/PercentProgressRow.swift +++ b/OTFMagicBox/CheckUp/PercentProgressRow.swift @@ -1,25 +1,25 @@ /* Copyright (c) 2021, Hippocrates Technologies S.r.l.. All rights reserved. - + Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: - + 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. - + 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. - + 3. Neither the name of the copyright holder(s) nor the names of any contributor(s) may be used to endorse or promote products derived from this software without specific prior written permission. No license is granted to the trademarks of the copyright holders even if such marks are included in this software. - + 4. Commercial redistribution in any form requires an explicit license agreement with the copyright holder(s). Please contact support@hippocratestech.com for further information regarding licensing. - + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. @@ -41,35 +41,35 @@ struct PercentProgressRow: View { color: .green, lineWidth: 4.0) } - + static var vitalsMeasured: PercentProgressRow { PercentProgressRow(title: "Checkups", progress: 0, color: .green, lineWidth: 4.0) } - + var title: String var progress: Float var color: Color var lineWidth: CGFloat var opacity: Double = 0.3 - + var body: some View { HStack { Text(title) .foregroundColor(.otfTextColor) - .font(YmlReader().appTheme?.textFont.appFont ?? Font.system(size: 17.0)) - .fontWeight(YmlReader().appTheme?.textWeight.fontWeight) + .font(Font.otfAppFont) + .fontWeight(Font.otfFontWeight) Spacer() Text(String(format: "%.1f", progress*100) + "%") .foregroundColor(color) - .font(YmlReader().appTheme?.textFont.appFont ?? Font.system(size: 17.0)) - .fontWeight(YmlReader().appTheme?.textWeight.fontWeight) + .font(Font.otfAppFont) + .fontWeight(Font.otfFontWeight) RingProgressView(progress: progress, - color: color, - lineWidth: lineWidth) - .frame(width: Metrics.FRAME_ROW_WIDTH, height: Metrics.FRAME_ROW_HEIGHT) + color: color, + lineWidth: lineWidth) + .frame(width: Metrics.FRAME_ROW_WIDTH, height: Metrics.FRAME_ROW_HEIGHT) } } } diff --git a/OTFMagicBox/Consent/ConsentDocument.swift b/OTFMagicBox/Consent/ConsentDocument.swift index 9666ba53..f330bbe8 100644 --- a/OTFMagicBox/Consent/ConsentDocument.swift +++ b/OTFMagicBox/Consent/ConsentDocument.swift @@ -1,35 +1,35 @@ /* -Copyright (c) 2021, Hippocrates Technologies S.r.l.. All rights reserved. + Copyright (c) 2021, Hippocrates Technologies S.r.l.. All rights reserved. -Redistribution and use in source and binary forms, with or without modification, -are permitted provided that the following conditions are met: + Redistribution and use in source and binary forms, with or without modification, + are permitted provided that the following conditions are met: -1. Redistributions of source code must retain the above copyright notice, -this list of conditions and the following disclaimer. + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. -2. Redistributions in binary form must reproduce the above copyright notice, -this list of conditions and the following disclaimer in the documentation and/or -other materials provided with the distribution. + 2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation and/or + other materials provided with the distribution. -3. Neither the name of the copyright holder(s) nor the names of any contributor(s) may -be used to endorse or promote products derived from this software without specific -prior written permission. No license is granted to the trademarks of the copyright -holders even if such marks are included in this software. + 3. Neither the name of the copyright holder(s) nor the names of any contributor(s) may + be used to endorse or promote products derived from this software without specific + prior written permission. No license is granted to the trademarks of the copyright + holders even if such marks are included in this software. -4. Commercial redistribution in any form requires an explicit license agreement with the -copyright holder(s). Please contact support@hippocratestech.com for further information -regarding licensing. + 4. Commercial redistribution in any form requires an explicit license agreement with the + copyright holder(s). Please contact support@hippocratestech.com for further information + regarding licensing. -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND -ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. -IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, -INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT -NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, -WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) -ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY -OF SUCH DAMAGE. + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, + INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, + OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY + OF SUCH DAMAGE. */ import OTFResearchKit @@ -38,17 +38,12 @@ import OTFResearchKit The Consent document of the patient. */ class ConsentDocument: ORKConsentDocument { - // MARK: Properties - override init() { super.init() - let consentTitle = ModuleAppYmlReader().consentTitle - title = consentTitle ?? Constants.YamlDefaults.ConsentTitle sections = [] - let sectionTypes: [ORKConsentSectionType] = [ .overview, .dataGathering, @@ -60,68 +55,59 @@ class ConsentDocument: ORKConsentDocument { .withdrawing, .custom ] - - let consentData = (ModuleAppYmlReader().consent?.data ?? [ConsentDescription(show: Constants.YamlDefaults.ConsentShow ? Constants.true : Constants.false, summary: Constants.YamlDefaults.ConsentSummary, content: Constants.YamlDefaults.ConsentContent, title: Constants.YamlDefaults.defaultTitleForRK, image: Constants.Images.ConsentCustomImg)]) - + let consentData = (ModuleAppYmlReader().consent?.data ?? [ConsentDescription( + show: Constants.YamlDefaults.ConsentShow ? Constants.true : Constants.false, + summary: Constants.YamlDefaults.ConsentSummary, + content: Constants.YamlDefaults.ConsentContent, + title: Constants.YamlDefaults.defaultTitleForRK, + image: Constants.Images.ConsentCustomImg)]) for (sectionType, consentData) in zip(sectionTypes, consentData) where consentData.show == Constants.true { let section = ORKConsentSection(type: sectionType) - if sectionType == .custom { section.customImage = UIImage(named: consentData.image)?.crop(to: Constants.sizeToCropImage) section.title = consentData.title } else { section.title = consentData.title } - section.summary = consentData.summary - section.content = consentData.content - sections?.append(section) + section.summary = consentData.summary + section.content = consentData.content + sections?.append(section) } - - let signature = ORKConsentSignature(forPersonWithTitle: nil, dateFormatString: nil, identifier: Constants.UserDefaults.ConsentDocumentSignature) + let signature = ORKConsentSignature(forPersonWithTitle: nil, + dateFormatString: nil, + identifier: Constants.UserDefaults.ConsentDocumentSignature) signature.title = title signaturePageTitle = title addSignature(signature) } - required init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } } extension ORKConsentSectionType: CustomStringConvertible { - public var description: String { switch self { case .overview: return "Welcome" - case .privacy: return "Protecting your Data" - case .dataUse: return "Data Use" - case .timeCommitment: return "Time Commitment" - case .studySurvey: return "Surveys" - case .studyTasks: return "Study Tasks" - case .withdrawing: return "Withdrawing" - case .custom: return "Custom consent section" - case .onlyInDocument: return "Only In Document" - case .dataGathering: return "Data Processing" - @unknown default: return "" } diff --git a/OTFMagicBox/Contacts/ContactsViewController.swift b/OTFMagicBox/Contacts/ContactsViewController.swift index eee15a2b..61ba2916 100644 --- a/OTFMagicBox/Contacts/ContactsViewController.swift +++ b/OTFMagicBox/Contacts/ContactsViewController.swift @@ -1,25 +1,25 @@ /* Copyright (c) 2021, Hippocrates Technologies S.r.l.. All rights reserved. - + Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: - + 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. - + 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. - + 3. Neither the name of the copyright holder(s) nor the names of any contributor(s) may be used to endorse or promote products derived from this software without specific prior written permission. No license is granted to the trademarks of the copyright holders even if such marks are included in this software. - + 4. Commercial redistribution in any form requires an explicit license agreement with the copyright holder(s). Please contact support@hippocratestech.com for further information regarding licensing. - + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. @@ -43,28 +43,28 @@ struct ContactsViewController: UIViewControllerRepresentable { @State private var contactsListViewController: OCKContactsListViewController var syncStoreManager: OCKSynchronizedStoreManager let queue = OperationQueue() - + init(storeManager: OCKSynchronizedStoreManager) { self.syncStoreManager = storeManager let viewController = OCKContactsListViewController(storeManager: storeManager) contactsListViewController = viewController viewController.title = Constants.CustomiseStrings.careTeam - + NotificationCenter.default.addObserver(forName: .deleteUserAccount, object: nil, queue: queue) { _ in DispatchQueue.main.async { - viewController.alertWithAction(title: Constants.CustomiseStrings.accountDeleted, message: Constants.deleteAccount) { action in + viewController.alertWithAction(title: Constants.CustomiseStrings.accountDeleted, message: Constants.deleteAccount) { _ in OTFTheraforgeNetwork.shared.moveToOnboardingView() } } } - + NotificationCenter.default.addObserver(forName: .databaseSuccessfllySynchronized, object: nil, queue: queue) { _ in viewController.fetchContacts() } } - + func updateUIViewController(_ taskViewController: OCKContactsListViewController, context: Context) {} - + func makeUIViewController(context: Context) -> OCKContactsListViewController { return contactsListViewController } @@ -73,7 +73,7 @@ struct ContactsViewController: UIViewControllerRepresentable { struct ContactsNavigationView: View { let syncStoreManager: OCKSynchronizedStoreManager @State private var isPresenting = false - + var body: some View { NavigationView { ContactsViewController(storeManager: syncStoreManager) diff --git a/OTFMagicBox/ContentView.swift b/OTFMagicBox/ContentView.swift index 19578309..fdcf7ea2 100644 --- a/OTFMagicBox/ContentView.swift +++ b/OTFMagicBox/ContentView.swift @@ -1,35 +1,35 @@ /* -Copyright (c) 2021, Hippocrates Technologies S.r.l.. All rights reserved. - -Redistribution and use in source and binary forms, with or without modification, -are permitted provided that the following conditions are met: - -1. Redistributions of source code must retain the above copyright notice, -this list of conditions and the following disclaimer. - -2. Redistributions in binary form must reproduce the above copyright notice, -this list of conditions and the following disclaimer in the documentation and/or -other materials provided with the distribution. - -3. Neither the name of the copyright holder(s) nor the names of any contributor(s) may -be used to endorse or promote products derived from this software without specific -prior written permission. No license is granted to the trademarks of the copyright -holders even if such marks are included in this software. - -4. Commercial redistribution in any form requires an explicit license agreement with the -copyright holder(s). Please contact support@hippocratestech.com for further information -regarding licensing. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND -ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. -IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, -INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT -NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, -WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) -ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY -OF SUCH DAMAGE. + Copyright (c) 2021, Hippocrates Technologies S.r.l.. All rights reserved. + + Redistribution and use in source and binary forms, with or without modification, + are permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation and/or + other materials provided with the distribution. + + 3. Neither the name of the copyright holder(s) nor the names of any contributor(s) may + be used to endorse or promote products derived from this software without specific + prior written permission. No license is granted to the trademarks of the copyright + holders even if such marks are included in this software. + + 4. Commercial redistribution in any form requires an explicit license agreement with the + copyright holder(s). Please contact support@hippocratestech.com for further information + regarding licensing. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, + INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, + OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY + OF SUCH DAMAGE. */ import SwiftUI @@ -38,8 +38,8 @@ import OTFResearchKit struct ContentView: View { var body: some View { Text("Hello, world!") - .font(YmlReader().appTheme?.textFont.appFont ?? Font.system(size: 17.0)) - .fontWeight(YmlReader().appTheme?.textWeight.fontWeight) + .font(Font.otfAppFont) + .fontWeight(Font.otfFontWeight) .padding() } } diff --git a/OTFMagicBox/Datastore/CareKitStoreManager.swift b/OTFMagicBox/Datastore/CareKitStoreManager.swift index d9fdeae8..1c5b0ea0 100644 --- a/OTFMagicBox/Datastore/CareKitStoreManager.swift +++ b/OTFMagicBox/Datastore/CareKitStoreManager.swift @@ -1,35 +1,35 @@ /* -Copyright (c) 2021, Hippocrates Technologies S.r.l.. All rights reserved. - -Redistribution and use in source and binary forms, with or without modification, -are permitted provided that the following conditions are met: - -1. Redistributions of source code must retain the above copyright notice, -this list of conditions and the following disclaimer. - -2. Redistributions in binary form must reproduce the above copyright notice, -this list of conditions and the following disclaimer in the documentation and/or -other materials provided with the distribution. - -3. Neither the name of the copyright holder(s) nor the names of any contributor(s) may -be used to endorse or promote products derived from this software without specific -prior written permission. No license is granted to the trademarks of the copyright -holders even if such marks are included in this software. - -4. Commercial redistribution in any form requires an explicit license agreement with the -copyright holder(s). Please contact support@hippocratestech.com for further information -regarding licensing. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND -ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. -IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, -INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT -NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, -WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) -ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY -OF SUCH DAMAGE. + Copyright (c) 2021, Hippocrates Technologies S.r.l.. All rights reserved. + + Redistribution and use in source and binary forms, with or without modification, + are permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation and/or + other materials provided with the distribution. + + 3. Neither the name of the copyright holder(s) nor the names of any contributor(s) may + be used to endorse or promote products derived from this software without specific + prior written permission. No license is granted to the trademarks of the copyright + holders even if such marks are included in this software. + + 4. Commercial redistribution in any form requires an explicit license agreement with the + copyright holder(s). Please contact support@hippocratestech.com for further information + regarding licensing. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, + INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, + OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY + OF SUCH DAMAGE. */ import Combine @@ -38,56 +38,59 @@ import OTFCareKitStore import OTFCloudantStore import OTFUtilities -class CareKitManager: NSObject { - - #if HEALTH - let healthKitStore = OCKHealthKitPassthroughStore(name: "CareKitHealthKitStore", - type: .onDisk(protection: .none)) - #endif +class CareKitStoreManager: NSObject { + lazy var peer = OTFWatchConnectivityPeer() +// private(set) lazy var cdStore = OCKStore(name: "local_db", type: .inMemory, remote: peer) +// private(set) lazy var hkStore = OCKHealthKitPassthroughStore(store: cdStore) +// #if HEALTH +// let healthKitStore = OCKHealthKitPassthroughStore(name: "CareKitHealthKitStore", +// type: .onDisk(protection: .none), remote: peer) +// #endif private(set) var cloudantStore: OTFCloudantStore? private(set) var synchronizedStoreManager: OCKSynchronizedStoreManager! + private(set) var cloudantSyncManager = CloudantSyncManager.shared private(set) lazy var coordinator: OCKStoreCoordinator = { let coordinator = OCKStoreCoordinator() return coordinator }() - - static let shared = CareKitManager() - + + static let shared = CareKitStoreManager() + override init() { super.init() - - initStore() - - #if HEALTH - coordinator.attach(store: healthKitStore) - #endif - - synchronizedStoreManager = OCKSynchronizedStoreManager(wrapping: coordinator) - - subscribeToNotifications() - - guard let cloudantStore = CloudantSyncManager.shared.cloudantStore else { return } + + guard let cloudantStore = cloudantSyncManager.cloudantStore else { return } self.cloudantStore = cloudantStore coordinator.attach(store: cloudantStore) + synchronizedStoreManager = OCKSynchronizedStoreManager(wrapping: coordinator) + + subscribeToNotifications() } - + func wipe() throws { try CloudantSyncManager.shared.cloudantStore?.datastoreManager.deleteDatastoreNamed("local_db") } - + fileprivate func initStore(forceUpdate: Bool = false) { #if HEALTH healthKitStore.populateSampleData() #endif UserDefaults.standard.set(Date(), forKey: Constants.prefCareKitDataInitDate) } - + private func subscribeToNotifications() { - let subscriber = Subscribers.Sink { completion in - } receiveValue: { storeNotification in + let subscriber = Subscribers.Sink { _ in + } receiveValue: { _ in + CloudantSyncManager.shared.cloudantStore?.synchronize(target: .mobile, completion: { error in + if let error = error { + print(error) + } else { + OTFLog("Synced successfully!") + } + }) CloudantSyncManager.shared.syncCloudantStore(notifyWhenDone: true, completion: nil) } - + synchronizedStoreManager.notificationPublisher.receive(subscriber: subscriber) } } diff --git a/OTFMagicBox/Datastore/CloudantSyncManager.swift b/OTFMagicBox/Datastore/CloudantSyncManager.swift index c4b69998..3657acb0 100644 --- a/OTFMagicBox/Datastore/CloudantSyncManager.swift +++ b/OTFMagicBox/Datastore/CloudantSyncManager.swift @@ -1,25 +1,25 @@ /* Copyright (c) 2021, Hippocrates Technologies S.r.l.. All rights reserved. - + Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: - + 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. - + 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. - + 3. Neither the name of the copyright holder(s) nor the names of any contributor(s) may be used to endorse or promote products derived from this software without specific prior written permission. No license is granted to the trademarks of the copyright holders even if such marks are included in this software. - + 4. Commercial redistribution in any form requires an explicit license agreement with the copyright holder(s). Please contact support@hippocratestech.com for further information regarding licensing. - + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. @@ -44,7 +44,7 @@ struct Configuration { let targetURL: URL let username: String let password: String - + static var `default`: Configuration { let remoteURLString = Constants.API.dbProxyURL let remoteURL = URL(string: remoteURLString)! @@ -59,35 +59,35 @@ enum ReplicationDirection: String { class CloudantSyncManager { static let shared = CloudantSyncManager() var cloudantStore: OTFCloudantStore? - + let peer = OTFWatchConnectivityPeer() + var storeManager: OCKSynchronizedStoreManager { - CareKitManager.shared.synchronizedStoreManager + CareKitStoreManager.shared.synchronizedStoreManager } - + private var lastSynced: Date private var shouldSyncAgain: Bool { let secondsDiff = Date().timeIntervalSince(lastSynced) return secondsDiff >= 300 } - + private init() { - cloudantStore = try? StoreService.shared.currentStore() + cloudantStore = try? StoreService.shared.currentStore(peer: peer) guard let lastDate = Calendar.current.date(byAdding: .minute, value: -10, to: Date()) else { lastSynced = Date() return } lastSynced = lastDate } - + func syncCloudantStore(notifyWhenDone: Bool, completion: ((Error?) -> Void)?) { guard let auth = TheraForgeKeychainService.shared.loadAuth() else { completion?(ForgeError.missingCredential) return } - - // TODO: - Implement a check to avoid synchronisation for every little change. + // Perhaps use a timer and sync only if there are changes, perhaps. - + if auth.isValid() { startSync(notifyWhenDone: notifyWhenDone, completion: completion) } else { @@ -95,14 +95,14 @@ class CloudantSyncManager { switch result { case .success(_): startSync(notifyWhenDone: notifyWhenDone, completion: completion) - + case .failure(let error): completion?(error) } } } } - + private func startSync(notifyWhenDone: Bool, completion: ((Error?) -> Void)?) { do { try replicate(direction: .push, completionBlock: { [unowned self] error in @@ -110,22 +110,23 @@ class CloudantSyncManager { didFinishSyncWith(error: error, completion: completion) return } - + do { try replicate(direction: .pull, completionBlock: { [unowned self] error in if let error = error { OTFError("error in cloudent manager %{public}@", error.localizedDescription) - } - else { -#if DEBUG + } else { + #if DEBUG OTFLog("Synced successfully!") -#endif + #endif lastSynced = Date() + if notifyWhenDone { DispatchQueue.main.async { NotificationCenter.default.post(name: .databaseSuccessfllySynchronized, object: nil) } } + } didFinishSyncWith(error: error, completion: completion) }) @@ -137,20 +138,20 @@ class CloudantSyncManager { didFinishSyncWith(error: error, completion: completion) } } - + private func didFinishSyncWith(error: Error?, completion: ((Error?) -> Void)?) { DispatchQueue.main.async { completion?(error) } } - + private func replicate(direction: ReplicationDirection, completionBlock: @escaping ((Error?) -> Void)) throws { - let store = try StoreService.shared.currentStore() + let store = try StoreService.shared.currentStore(peer: peer) let datastoreManager = store.datastoreManager let factory = CDTReplicatorFactory(datastoreManager: datastoreManager) - + let configuration = Configuration.default - + let replication: CDTAbstractReplication switch direction { case .push: @@ -164,15 +165,15 @@ class CloudantSyncManager { username: configuration.username, password: configuration.password) } - + replication.add(TheraForgeHTTPInterceptor()) - + let replicator = try factory.oneWay(replication) let dataStore = try datastoreManager.datastoreNamed("local_db") - + replicator.sessionConfigDelegate = TheraForgeNetwork.shared dataStore.sessionConfigDelegate = TheraForgeNetwork.shared - + switch direction { case .push: dataStore.push(to: configuration.targetURL, replicator: replicator, username: configuration.username, password: configuration.password) { (error: Error?) in @@ -184,7 +185,7 @@ class CloudantSyncManager { completionBlock(nil) } } - + case .pull: dataStore.pull(from: configuration.targetURL, replicator: replicator, username: configuration.username, password: configuration.password) { error in completionBlock(error) diff --git a/OTFMagicBox/Datastore/OCKStoreManager.swift b/OTFMagicBox/Datastore/OCKStoreManager.swift index 657780a3..b57e2249 100644 --- a/OTFMagicBox/Datastore/OCKStoreManager.swift +++ b/OTFMagicBox/Datastore/OCKStoreManager.swift @@ -1,25 +1,25 @@ /* Copyright (c) 2021, Hippocrates Technologies S.r.l.. All rights reserved. - + Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: - + 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. - + 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. - + 3. Neither the name of the copyright holder(s) nor the names of any contributor(s) may be used to endorse or promote products derived from this software without specific prior written permission. No license is granted to the trademarks of the copyright holders even if such marks are included in this software. - + 4. Commercial redistribution in any form requires an explicit license agreement with the copyright holder(s). Please contact support@hippocratestech.com for further information regarding licensing. - + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. @@ -31,15 +31,17 @@ ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ + import OTFCareKit import OTFCareKitStore +import WatchConnectivity class OCKStoreManager: ObservableObject { static let shared = OCKStoreManager() - - lazy private(set) var coreDataStore = OCKStore(name: "SampleAppStore", type: .inMemory) - - lazy private(set) var synchronizedStoreManager: OCKSynchronizedStoreManager = { + + private(set) lazy var coreDataStore = OCKStore(name: "SampleAppStore", type: .inMemory) + + private(set) lazy var synchronizedStoreManager: OCKSynchronizedStoreManager = { let coordinator = OCKStoreCoordinator() coordinator.attach(store: coreDataStore) return OCKSynchronizedStoreManager(wrapping: coordinator) diff --git a/OTFMagicBox/HealthData/HealthDataStep.swift b/OTFMagicBox/HealthData/HealthDataStep.swift index 152b4165..2eaf7bcf 100644 --- a/OTFMagicBox/HealthData/HealthDataStep.swift +++ b/OTFMagicBox/HealthData/HealthDataStep.swift @@ -1,25 +1,25 @@ /* Copyright (c) 2021, Hippocrates Technologies S.r.l.. All rights reserved. - + Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: - + 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. - + 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. - + 3. Neither the name of the copyright holder(s) nor the names of any contributor(s) may be used to endorse or promote products derived from this software without specific prior written permission. No license is granted to the trademarks of the copyright holders even if such marks are included in this software. - + 4. Commercial redistribution in any form requires an explicit license agreement with the copyright holder(s). Please contact support@hippocratestech.com for further information regarding licensing. - + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. @@ -33,34 +33,32 @@ */ import OTFResearchKit - - /** The Health data step of the patient. */ class HealthDataStep: ORKInstructionStep { - + override init(identifier: String) { super.init(identifier: identifier) - + title = ModuleAppYmlReader().healthPermissionsTitle text = ModuleAppYmlReader().healthPermissionsText } - + required init(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } - + } /** This class was created to override the `goForward` functionality. */ class HealthDataStepViewController: ORKInstructionStepViewController { - + override func goForward() { let manager = OTFHealthKitManager.shared - manager.getHealthAuthorization() { _,_ in + manager.getHealthAuthorization { _, _ in OperationQueue.main.addOperation { super.goForward() } diff --git a/OTFMagicBox/HealthData/HealthRecordStep.swift b/OTFMagicBox/HealthData/HealthRecordStep.swift index 03b9e20d..5ce3ad7b 100644 --- a/OTFMagicBox/HealthData/HealthRecordStep.swift +++ b/OTFMagicBox/HealthData/HealthRecordStep.swift @@ -1,25 +1,25 @@ /* Copyright (c) 2021, Hippocrates Technologies S.r.l.. All rights reserved. - + Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: - + 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. - + 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. - + 3. Neither the name of the copyright holder(s) nor the names of any contributor(s) may be used to endorse or promote products derived from this software without specific prior written permission. No license is granted to the trademarks of the copyright holders even if such marks are included in this software. - + 4. Commercial redistribution in any form requires an explicit license agreement with the copyright holder(s). Please contact support@hippocratestech.com for further information regarding licensing. - + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. @@ -39,28 +39,28 @@ import OTFResearchKit The Health Records Step will ask for a permission to collect HealthKit health records data of a patient. */ class HealthRecordsStep: ORKInstructionStep { - + override init(identifier: String) { super.init(identifier: identifier) - + let recordsConfig = ModuleAppYmlReader().healthRecords - + title = recordsConfig?.permissionsTitle ?? Constants.YamlDefaults.HealthRecordsPermissionsTitle - + text = recordsConfig?.permissionsText ?? Constants.YamlDefaults.HealthRecordsPermissionsText } - + required init(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } - + } class HealthRecordsStepViewController: ORKInstructionStepViewController { - + /** When this step is being dismissed, get `HealthKit` authorization in the process. - + Relies on a `CKHealthDataStep` instance as `self.step`. */ override func goForward() { @@ -69,7 +69,7 @@ class HealthRecordsStepViewController: ORKInstructionStepViewController { if succeeded { manager.upload() } - + OperationQueue.main.addOperation { super.goForward() } diff --git a/OTFMagicBox/Library/ActivityLoader.swift b/OTFMagicBox/Library/ActivityLoader.swift deleted file mode 100644 index 237b7aeb..00000000 --- a/OTFMagicBox/Library/ActivityLoader.swift +++ /dev/null @@ -1,55 +0,0 @@ -/* -Copyright (c) 2021, Hippocrates Technologies S.r.l.. All rights reserved. - -Redistribution and use in source and binary forms, with or without modification, -are permitted provided that the following conditions are met: - -1. Redistributions of source code must retain the above copyright notice, -this list of conditions and the following disclaimer. - -2. Redistributions in binary form must reproduce the above copyright notice, -this list of conditions and the following disclaimer in the documentation and/or -other materials provided with the distribution. - -3. Neither the name of the copyright holder(s) nor the names of any contributor(s) may -be used to endorse or promote products derived from this software without specific -prior written permission. No license is granted to the trademarks of the copyright -holders even if such marks are included in this software. - -4. Commercial redistribution in any form requires an explicit license agreement with the -copyright holder(s). Please contact support@hippocratestech.com for further information -regarding licensing. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND -ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. -IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, -INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT -NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, -WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) -ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY -OF SUCH DAMAGE. - */ - -import SwiftUI - -struct LoaderView: View { - var tintColor: Color = .black - var scaleSize: CGFloat = 1.0 - - var body: some View { - ProgressView() - .scaleEffect(scaleSize, anchor: .center) - .progressViewStyle(CircularProgressViewStyle(tint: tintColor)) - } -} - -extension View { - @ViewBuilder func hidden(_ shouldHide: Bool) -> some View { - switch shouldHide { - case true: self.hidden() - case false: self - } - } -} diff --git a/OTFMagicBox/Library/ActivityManager.swift b/OTFMagicBox/Library/ActivityManager.swift index e537966e..12b445ae 100644 --- a/OTFMagicBox/Library/ActivityManager.swift +++ b/OTFMagicBox/Library/ActivityManager.swift @@ -1,104 +1,104 @@ /* -Copyright (c) 2021, Hippocrates Technologies S.r.l.. All rights reserved. - -Redistribution and use in source and binary forms, with or without modification, -are permitted provided that the following conditions are met: - -1. Redistributions of source code must retain the above copyright notice, -this list of conditions and the following disclaimer. - -2. Redistributions in binary form must reproduce the above copyright notice, -this list of conditions and the following disclaimer in the documentation and/or -other materials provided with the distribution. - -3. Neither the name of the copyright holder(s) nor the names of any contributor(s) may -be used to endorse or promote products derived from this software without specific -prior written permission. No license is granted to the trademarks of the copyright -holders even if such marks are included in this software. - -4. Commercial redistribution in any form requires an explicit license agreement with the -copyright holder(s). Please contact support@hippocratestech.com for further information -regarding licensing. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND -ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. -IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, -INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT -NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, -WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) -ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY -OF SUCH DAMAGE. + Copyright (c) 2021, Hippocrates Technologies S.r.l.. All rights reserved. + + Redistribution and use in source and binary forms, with or without modification, + are permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation and/or + other materials provided with the distribution. + + 3. Neither the name of the copyright holder(s) nor the names of any contributor(s) may + be used to endorse or promote products derived from this software without specific + prior written permission. No license is granted to the trademarks of the copyright + holders even if such marks are included in this software. + + 4. Commercial redistribution in any form requires an explicit license agreement with the + copyright holder(s). Please contact support@hippocratestech.com for further information + regarding licensing. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, + INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, + OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY + OF SUCH DAMAGE. */ import Foundation import HealthKit -public class ActivityManager : NSObject { - +public class ActivityManager: NSObject { + public static let shared = ActivityManager() - - public override init() { + + override public init() { super.init() - + _ = HealthKitManager.shared } - + public func load() { guard hasGrantedAuth && !typesToCollect.isEmpty else { return } - - getHealthAuthorizaton(forTypes: self.typesToCollect) { [weak self] (success, error) in - if (success) { - self?.startHealthKitCollectionInBackground(withFrequency: .hourly) // TODO: get last freq + + getHealthAuthorizaton(forTypes: self.typesToCollect) { [weak self] (success, _) in + if success { + self?.startHealthKitCollectionInBackground(withFrequency: .hourly) } } } - - public func getHealthAuthorizaton(forTypes typesToCollect:Set, _ completion: @escaping (_ success: Bool, _ error: Error?) -> Void) { + + public func getHealthAuthorizaton(forTypes typesToCollect: Set, _ completion: @escaping (_ success: Bool, _ error: Error?) -> Void) { self.typesToCollect = typesToCollect HealthKitManager.shared.getHealthKitAuth(forTypes: self.typesToCollect) { [weak self] (success, error) in self?.hasGrantedAuth = success completion(success, error) } } - + public func startHealthKitCollectionInBackground(fromStartDate startDate: Date? = nil, withFrequency frequency: HKUpdateFrequency, _ completion: ((_ success: Bool, _ error: Error?) -> Void)? = nil) { - - //check for auth + + // check for auth guard hasGrantedAuth else { let error = NSError(domain: Constants.app, code: 2, userInfo: [NSLocalizedDescriptionKey: "Cannot startHealthKitCollection without getting auth permissions first."]) completion?(false, error) return } - - //record beginning of data collection + + // record beginning of data collection if let startDate = startDate { UserDefaults.standard.set(startDate, forKey: Constants.UserDefaults.HKStartDate) } - - //and get health authorization + + // and get health authorization HealthKitManager.shared.startBackgroundDelivery(forTypes: typesToCollect, withFrequency: frequency) { [weak self] (success, error) in self?.hasStartedCollection = success completion?(success, error) } } - + public func stopHealthKitCollection() { - HealthKitManager.shared.disableHealthKit() { [weak self] (success, error) in - if (success) { //disable successfully - self?.hasStartedCollection = false //we have disabled + HealthKitManager.shared.disableHealthKit { [weak self] (success, _) in + if success { // disable successfully + self?.hasStartedCollection = false // we have disabled } } } - + fileprivate let keyHasStartedCollection = "hasStartedCollection" fileprivate let keyHasGrantedAuth = "hasGrantedAuth" fileprivate let keyTypesToCollect = "typesToCollect" - - fileprivate var hasStartedCollection : Bool { + + fileprivate var hasStartedCollection: Bool { get { return UserDefaults.standard.bool(forKey: keyHasStartedCollection) } @@ -106,8 +106,8 @@ public class ActivityManager : NSObject { UserDefaults.standard.set(newValue, forKey: keyHasStartedCollection) } } - - fileprivate var hasGrantedAuth : Bool { + + fileprivate var hasGrantedAuth: Bool { get { return UserDefaults.standard.bool(forKey: keyHasGrantedAuth) } @@ -115,18 +115,18 @@ public class ActivityManager : NSObject { UserDefaults.standard.set(newValue, forKey: keyHasGrantedAuth) } } - + fileprivate var _typesToCollect = Set() fileprivate var typesToCollect: Set { get { - if (!_typesToCollect.isEmpty) { + if !_typesToCollect.isEmpty { return _typesToCollect } - + guard let typeIds = UserDefaults.standard.array(forKey: keyTypesToCollect) as? [String] else { return Set() // no types to process } - + var types = Set() for type in typeIds { let type = HKQuantityTypeIdentifier(rawValue: type) @@ -134,8 +134,8 @@ public class ActivityManager : NSObject { types.insert(parsedType) } } - - if (!types.isEmpty) { + + if !types.isEmpty { _typesToCollect = types } return types @@ -149,5 +149,5 @@ public class ActivityManager : NSObject { _typesToCollect = newValue } } - + } diff --git a/OTFMagicBox/Library/Constants.swift b/OTFMagicBox/Library/Constants.swift index 9afe6918..22523640 100644 --- a/OTFMagicBox/Library/Constants.swift +++ b/OTFMagicBox/Library/Constants.swift @@ -1,35 +1,35 @@ /* -Copyright (c) 2021, Hippocrates Technologies S.r.l.. All rights reserved. + Copyright (c) 2021, Hippocrates Technologies S.r.l.. All rights reserved. -Redistribution and use in source and binary forms, with or without modification, -are permitted provided that the following conditions are met: + Redistribution and use in source and binary forms, with or without modification, + are permitted provided that the following conditions are met: -1. Redistributions of source code must retain the above copyright notice, -this list of conditions and the following disclaimer. + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. -2. Redistributions in binary form must reproduce the above copyright notice, -this list of conditions and the following disclaimer in the documentation and/or -other materials provided with the distribution. + 2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation and/or + other materials provided with the distribution. -3. Neither the name of the copyright holder(s) nor the names of any contributor(s) may -be used to endorse or promote products derived from this software without specific -prior written permission. No license is granted to the trademarks of the copyright -holders even if such marks are included in this software. + 3. Neither the name of the copyright holder(s) nor the names of any contributor(s) may + be used to endorse or promote products derived from this software without specific + prior written permission. No license is granted to the trademarks of the copyright + holders even if such marks are included in this software. -4. Commercial redistribution in any form requires an explicit license agreement with the -copyright holder(s). Please contact support@hippocratestech.com for further information -regarding licensing. + 4. Commercial redistribution in any form requires an explicit license agreement with the + copyright holder(s). Please contact support@hippocratestech.com for further information + regarding licensing. -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND -ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. -IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, -INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT -NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, -WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) -ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY -OF SUCH DAMAGE. + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, + INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, + OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY + OF SUCH DAMAGE. */ import Foundation @@ -40,76 +40,61 @@ enum Constants { static let userType = "patient" static let patientFirstName = "patientFirstName" static let patientLastName = "patientLastName" - static let yamlFile = "AppSysParameters.yml" static let moduleAppFileName = "ModuleAppSysParameter.yml" - static let prefConfirmedLogin = "PREF_CONFIRMED_LOGIN" static let prefFirstRunWasMarked = "PREF_FIRST_RUN" static let prefUserEmail = "PREF_USER_EMAIL" - static let prefCareKitDataInitDate = "PREF_DATA_INIT_DATE" static let prefHealthRecordsLastUploaded = "PREF_HEALTH_LAST_UPLOAD" - static let notificationUserLogin = "NOTIFICATION_USER_LOGIN" - static let dataBucketUserDetails = "userDetails" static let dataBucketSurveys = "surveys" static let dataBucketHealthKit = "healthKit" static let dataBucketStorage = "storage" - static let onboardingDidComplete = "didCompleteOnboarding" static let isConsentDocumentViewed = "isConsentDocumentViewed" - static let sizeToCropImage = CGSize(width: 1920.0, height: 1500.0) - // String representation of Bool to compare with yaml file's booleans static let `true` = "true" static let `false` = "false" - static let deleteAccount = "Your account is deleted from one of your device" - struct UserDefaults { - //Patient data + enum UserDefaults { + // Patient data static let patientEmail = "patientEmail" static let patientFirstName = "patientFirstName" static let patientLastName = "patientLastName" static let patientGender = "female" static let patientDob = "10/10/1990" - - - //Consent + // Consent static let ConsentDocumentSignature = "ConsentDocumentParticipantSignature" static let ConsentDocumentURL = "consentFormURL" - - //Misc + // Misc static let FirstRun = "firstRun" static let FirstLogin = "firstLogin" static let CompletedMarketingSurvey = "completedMarketingSurvey" static let HKDataShare = "healthKitShare" static let HKStartDate = "healthKitDate" - - //Session + // Session static let DeviceToken = "deviceToken" static let UserId = "userId" static let ValidSession = "validSession" - // Surveys static let MedicalSurvey = "medicalSurvey" static let SF12Survey = "sf12Survey" static let SurgicalSurvey = "surgicalSurvey" static let PhysicalSurvey = "physicalSurvey" - // Watch static let WatchReceivedFiles = "watch.receivedFiles" static let WatchTransferFailedFiles = "watch.failedFiles" } - struct Notification { + enum Notification { static let MessageArrivedNotification = "MessageArrivedNotification" static let DidRegisterNotifications = "DidRegisterUserNotificationSettings" static let DidRegisterNotificationsWithToken = "didRegisterForRemoteNotificationsWithDeviceToken" - + static let WalkTestRequest = "WalkTestRequest" static let APIUserErrorNotification = "APIUserErrorNotification" static let DataSyncRequest = "DataSyncRequest" @@ -119,20 +104,17 @@ enum Constants { static let deleteUserAccount = "DeleteUserAccount" static let deleteProfile = "DeleteProfile" static let fetchImage = "FetchImage" - - //Reset tab navigation badges + // Reset tab navigation badges static let BadgeReset = "BadgeReset" - - //Session + // Session static let SessionExpired = "UserSessionExpired" static let SessionReset = "SessionReset" - - //Watch + // Watch static let SessionWatchReachabilityDidChange = "SessionWatchReachabilityDidChange" static let SessionWatchStateDidChange = "sessionWatchStateDidChange" } - struct YamlDefaults { + enum YamlDefaults { static let APIKey = "this_is_a_dummy_key_to_be_replaced_by_a_valid_one" static let FileName = "AppSysParameters.yml" static let moduleAppFileName = "ModuleAppSysParameter.yml" @@ -171,13 +153,17 @@ enum Constants { static let learnMoreTitle = "Learn more title" static let birthdayText = "When is your birthday?" static let favorite = "Which is your favorite apple?" - static let participantsText = "Please use this space to provide instructions for participants. Please make sure to provide enough information so that users can progress through the survey and complete with ease." + static let participantsText = """ + Please use this space to provide instructions for participants. \ + Please make sure to provide enough information so that users can \ + progress through the survey and complete with ease. + """ static let YourQuestion = "Your question goes here" static let defaultTitleForRK = "Default Title" static let AppTitleSize = "Title" } - - struct CustomiseStrings { + + enum CustomiseStrings { static let signUp = "Sign Up" static let signinWithApple = "Sign in with Apple" static let resetPassword = "Reset Password" @@ -260,7 +246,12 @@ Your password does not meet the following criteria: minimum 8 characters with at static let apiKeyMissing = "API Key Missing" static let intendedDescription = "Tests ability to walk" static let instructionStepTitle = "Patient Questionnaire" - static let instructionStepText = "This information will help your doctors keep track of how you feel and how well you are able to do your usual activities. If you are unsure about how to answer a question, please give the best answer you can and make a written comment beside your answer." + static let instructionStepText = + """ + This information will help your doctors keep track of how you feel and how well you are able to do your usual activities. + If you are unsure about how to answer a question, please give the best answer you can and make a written comment beside your answer. + """ + static let healthScaleTitle = "Question #1" static let healthScaleQuestion = "In general, would you say your health is:" static let rowTitle1 = "Row Title" @@ -282,17 +273,17 @@ Your password does not meet the following criteria: minimum 8 characters with at static let faceIdAlertMessage = "To use faceID authentication please login with your credientials first." static let touchIdAlertMessage = "To use touchID authentication please login with your credientials first" } - - struct Images { - static let ConsentCustomImg = "online-agreement1" + + enum Images { + static let ConsentCustomImg = "online-agreement" } - - struct Passcode { + + enum Passcode { static let lengthSix = "6" static let lengthFour = "4" } - - struct Identifier { + + enum Identifier { static let ConsentStep = "VisualConsentStep" static let ConsentReviewStep = "ConsentReviewStep" static let HealthKitDataStep = "Healthkit" @@ -301,23 +292,23 @@ Your password does not meet the following criteria: minimum 8 characters with at static let CompletionStep = "CompletionStep" static let StudyOnboardingTask = "StudyOnboardingTask" } - - struct Registration { + + enum Registration { static let Identifier = "RegistrationStep" static let Title = "Registration" static let Text = "Sign up for this study." static let PasscodeInvalidMessage = "Password must be at least 10 characters in length." } - - struct Login { + + enum Login { static let Identifier = "LoginStep" static let Title = "Login" static let Text = "Log into this study." } - - struct API { - static let developmentUrl = "https://theraforge.org/api" + + enum API { + static let developmentUrl = "https://stg.theraforge.org/api" static let dbProxyURL = API.developmentUrl + "/v1/db/" } - + } diff --git a/OTFMagicBox/Library/Extension/Environment+StoreManager.swift b/OTFMagicBox/Library/Extension/Environment+StoreManager.swift index 637e3882..af88eebd 100644 --- a/OTFMagicBox/Library/Extension/Environment+StoreManager.swift +++ b/OTFMagicBox/Library/Extension/Environment+StoreManager.swift @@ -1,25 +1,25 @@ /* Copyright (c) 2021, Hippocrates Technologies S.r.l.. All rights reserved. - + Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: - + 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. - + 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. - + 3. Neither the name of the copyright holder(s) nor the names of any contributor(s) may be used to endorse or promote products derived from this software without specific prior written permission. No license is granted to the trademarks of the copyright holders even if such marks are included in this software. - + 4. Commercial redistribution in any form requires an explicit license agreement with the copyright holder(s). Please contact support@hippocratestech.com for further information regarding licensing. - + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. @@ -35,18 +35,17 @@ import Foundation import OTFCareKit -import Foundation import SwiftUI private struct StoreManagerEnvironmentKey: EnvironmentKey { - + static var defaultValue: OCKSynchronizedStoreManager { return OCKStoreManager.shared.synchronizedStoreManager } } public extension EnvironmentValues { - + var storeManager: OCKSynchronizedStoreManager { get { self[StoreManagerEnvironmentKey.self] } set { self[StoreManagerEnvironmentKey.self] = newValue } diff --git a/OTFMagicBox/Library/Extension/Extension+Array.swift b/OTFMagicBox/Library/Extension/Extension+Array.swift index fe8d33ad..4e9160eb 100644 --- a/OTFMagicBox/Library/Extension/Extension+Array.swift +++ b/OTFMagicBox/Library/Extension/Extension+Array.swift @@ -9,11 +9,11 @@ import Sodium extension Array { func splitFile() -> (left: [Element], right: [Element]) { - let size = self.count - let splitIndex = SecretStream.XChaCha20Poly1305.HeaderBytes - let leftSplit = self[0 ..< splitIndex] - let rightSplit = self[splitIndex ..< size] - - return (left: Array(leftSplit), right: Array(rightSplit)) - } + let size = self.count + let splitIndex = SecretStream.XChaCha20Poly1305.HeaderBytes + let leftSplit = self[0 ..< splitIndex] + let rightSplit = self[splitIndex ..< size] + + return (left: Array(leftSplit), right: Array(rightSplit)) + } } diff --git a/OTFMagicBox/Library/Extension/Extension+Character.swift b/OTFMagicBox/Library/Extension/Extension+Character.swift index 06cca258..ef6e58e8 100644 --- a/OTFMagicBox/Library/Extension/Extension+Character.swift +++ b/OTFMagicBox/Library/Extension/Extension+Character.swift @@ -1,44 +1,44 @@ /* -Copyright (c) 2021, Hippocrates Technologies S.r.l.. All rights reserved. - -Redistribution and use in source and binary forms, with or without modification, -are permitted provided that the following conditions are met: - -1. Redistributions of source code must retain the above copyright notice, -this list of conditions and the following disclaimer. - -2. Redistributions in binary form must reproduce the above copyright notice, -this list of conditions and the following disclaimer in the documentation and/or -other materials provided with the distribution. - -3. Neither the name of the copyright holder(s) nor the names of any contributor(s) may -be used to endorse or promote products derived from this software without specific -prior written permission. No license is granted to the trademarks of the copyright -holders even if such marks are included in this software. - -4. Commercial redistribution in any form requires an explicit license agreement with the -copyright holder(s). Please contact support@hippocratestech.com for further information -regarding licensing. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND -ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. -IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, -INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT -NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, -WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) -ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY -OF SUCH DAMAGE. + Copyright (c) 2021, Hippocrates Technologies S.r.l.. All rights reserved. + + Redistribution and use in source and binary forms, with or without modification, + are permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation and/or + other materials provided with the distribution. + + 3. Neither the name of the copyright holder(s) nor the names of any contributor(s) may + be used to endorse or promote products derived from this software without specific + prior written permission. No license is granted to the trademarks of the copyright + holders even if such marks are included in this software. + + 4. Commercial redistribution in any form requires an explicit license agreement with the + copyright holder(s). Please contact support@hippocratestech.com for further information + regarding licensing. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, + INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, + OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY + OF SUCH DAMAGE. */ import Foundation /** - Extension of Character to check the existance of the Emoji character. + Extension of Character to check the existance of the Emoji character. */ extension Character { - + // An emoji can either be a 2 byte unicode character or a normal UTF8 character with an emoji modifier // appended as is the case with 3️⃣. 0x238C is the first instance of UTF16 emoji that requires no modifier. // `isEmoji` will evaluate to true for any character that can be turned into an emoji by adding a modifier @@ -47,5 +47,5 @@ extension Character { guard let scalar = unicodeScalars.first else { return false } return scalar.properties.isEmoji && (scalar.value > 0x238C || unicodeScalars.count > 1) } - + } diff --git a/OTFMagicBox/Library/Extension/Extension+Date.swift b/OTFMagicBox/Library/Extension/Extension+Date.swift index 3bb83b55..390a1134 100644 --- a/OTFMagicBox/Library/Extension/Extension+Date.swift +++ b/OTFMagicBox/Library/Extension/Extension+Date.swift @@ -1,41 +1,41 @@ /* -Copyright (c) 2021, Hippocrates Technologies S.r.l.. All rights reserved. - -Redistribution and use in source and binary forms, with or without modification, -are permitted provided that the following conditions are met: - -1. Redistributions of source code must retain the above copyright notice, -this list of conditions and the following disclaimer. - -2. Redistributions in binary form must reproduce the above copyright notice, -this list of conditions and the following disclaimer in the documentation and/or -other materials provided with the distribution. - -3. Neither the name of the copyright holder(s) nor the names of any contributor(s) may -be used to endorse or promote products derived from this software without specific -prior written permission. No license is granted to the trademarks of the copyright -holders even if such marks are included in this software. - -4. Commercial redistribution in any form requires an explicit license agreement with the -copyright holder(s). Please contact support@hippocratestech.com for further information -regarding licensing. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND -ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. -IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, -INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT -NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, -WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) -ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY -OF SUCH DAMAGE. + Copyright (c) 2021, Hippocrates Technologies S.r.l.. All rights reserved. + + Redistribution and use in source and binary forms, with or without modification, + are permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation and/or + other materials provided with the distribution. + + 3. Neither the name of the copyright holder(s) nor the names of any contributor(s) may + be used to endorse or promote products derived from this software without specific + prior written permission. No license is granted to the trademarks of the copyright + holders even if such marks are included in this software. + + 4. Commercial redistribution in any form requires an explicit license agreement with the + copyright holder(s). Please contact support@hippocratestech.com for further information + regarding licensing. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, + INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, + OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY + OF SUCH DAMAGE. */ import Foundation extension Date { - + var toString: String { // Create Date Formatter let dateFormatter = DateFormatter() diff --git a/OTFMagicBox/Library/Extension/Extension+Font.swift b/OTFMagicBox/Library/Extension/Extension+Font.swift index 1e219b8d..fd8f300d 100644 --- a/OTFMagicBox/Library/Extension/Extension+Font.swift +++ b/OTFMagicBox/Library/Extension/Extension+Font.swift @@ -1,35 +1,35 @@ /* -Copyright (c) 2021, Hippocrates Technologies S.r.l.. All rights reserved. + Copyright (c) 2021, Hippocrates Technologies S.r.l.. All rights reserved. -Redistribution and use in source and binary forms, with or without modification, -are permitted provided that the following conditions are met: + Redistribution and use in source and binary forms, with or without modification, + are permitted provided that the following conditions are met: -1. Redistributions of source code must retain the above copyright notice, -this list of conditions and the following disclaimer. + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. -2. Redistributions in binary form must reproduce the above copyright notice, -this list of conditions and the following disclaimer in the documentation and/or -other materials provided with the distribution. + 2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation and/or + other materials provided with the distribution. -3. Neither the name of the copyright holder(s) nor the names of any contributor(s) may -be used to endorse or promote products derived from this software without specific -prior written permission. No license is granted to the trademarks of the copyright -holders even if such marks are included in this software. + 3. Neither the name of the copyright holder(s) nor the names of any contributor(s) may + be used to endorse or promote products derived from this software without specific + prior written permission. No license is granted to the trademarks of the copyright + holders even if such marks are included in this software. -4. Commercial redistribution in any form requires an explicit license agreement with the -copyright holder(s). Please contact support@hippocratestech.com for further information -regarding licensing. + 4. Commercial redistribution in any form requires an explicit license agreement with the + copyright holder(s). Please contact support@hippocratestech.com for further information + regarding licensing. -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND -ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. -IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, -INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT -NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, -WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) -ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY -OF SUCH DAMAGE. + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, + INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, + OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY + OF SUCH DAMAGE. */ import SwiftUI @@ -42,39 +42,38 @@ extension Font { static var basicFontStyle: Font { let fontSize: CGFloat = 20.0 - guard let fontName = YmlReader().appTheme?.screenTitleFont, - fontExists(fontName, size: fontSize) else { + let fontName = YmlReader().appStyle.screenTitleFont + guard fontExists(YmlReader().appStyle.screenTitleFont, size: fontSize) else { return (.system(size: fontSize)) } return Font.custom(fontName, size: fontSize) } - + static var subHeaderFontStyle: Font { let fontSize: CGFloat = 20.0 - guard let fontName = YmlReader().appTheme?.screenTitleFont, - fontExists(fontName, size: fontSize) else { + let fontName = YmlReader().appStyle.screenTitleFont + guard fontExists(fontName, size: fontSize) else { return (.system(size: fontSize)) } return Font.custom(fontName, size: fontSize) } - + static var headerFontStyle: Font { let fontSize: CGFloat = 24.0 - guard let fontName = YmlReader().appTheme?.screenTitleFont, - fontExists(fontName, size: fontSize) else { + let fontName = YmlReader().appStyle.screenTitleFont + guard fontExists(fontName, size: fontSize) else { return (.system(size: fontSize)) } return Font.custom(fontName, size: fontSize) } - + static var titleFontStyle: Font { let fontSize: CGFloat = 35.0 - guard let fontName = YmlReader().appTheme?.screenTitleFont, - fontExists(fontName, size: fontSize) else { + let fontName = YmlReader().appStyle.screenTitleFont + guard fontExists(fontName, size: fontSize) else { return (.system(size: fontSize)) } return Font.custom(fontName, size: fontSize) } } - diff --git a/OTFMagicBox/Library/Extension/Extension+Image.swift b/OTFMagicBox/Library/Extension/Extension+Image.swift index 1e76d65a..3c5a9dcd 100644 --- a/OTFMagicBox/Library/Extension/Extension+Image.swift +++ b/OTFMagicBox/Library/Extension/Extension+Image.swift @@ -1,35 +1,35 @@ /* -Copyright (c) 2021, Hippocrates Technologies S.r.l.. All rights reserved. - -Redistribution and use in source and binary forms, with or without modification, -are permitted provided that the following conditions are met: - -1. Redistributions of source code must retain the above copyright notice, -this list of conditions and the following disclaimer. - -2. Redistributions in binary form must reproduce the above copyright notice, -this list of conditions and the following disclaimer in the documentation and/or -other materials provided with the distribution. - -3. Neither the name of the copyright holder(s) nor the names of any contributor(s) may -be used to endorse or promote products derived from this software without specific -prior written permission. No license is granted to the trademarks of the copyright -holders even if such marks are included in this software. - -4. Commercial redistribution in any form requires an explicit license agreement with the -copyright holder(s). Please contact support@hippocratestech.com for further information -regarding licensing. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND -ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. -IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, -INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT -NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, -WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) -ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY -OF SUCH DAMAGE. + Copyright (c) 2021, Hippocrates Technologies S.r.l.. All rights reserved. + + Redistribution and use in source and binary forms, with or without modification, + are permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation and/or + other materials provided with the distribution. + + 3. Neither the name of the copyright holder(s) nor the names of any contributor(s) may + be used to endorse or promote products derived from this software without specific + prior written permission. No license is granted to the trademarks of the copyright + holders even if such marks are included in this software. + + 4. Commercial redistribution in any form requires an explicit license agreement with the + copyright holder(s). Please contact support@hippocratestech.com for further information + regarding licensing. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, + INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, + OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY + OF SUCH DAMAGE. */ import SwiftUI @@ -38,7 +38,7 @@ extension Image { static var theraforgeLogo: Image { UIImage.loadImage(named: "TheraforgeLogo") } - + static var avatar: Image { UIImage.loadImage(named: "user_profile") } diff --git a/OTFMagicBox/Library/Extension/Extension+OCKAnyTask.swift b/OTFMagicBox/Library/Extension/Extension+OCKAnyTask.swift index 26f12d38..3c7baab3 100644 --- a/OTFMagicBox/Library/Extension/Extension+OCKAnyTask.swift +++ b/OTFMagicBox/Library/Extension/Extension+OCKAnyTask.swift @@ -1,25 +1,25 @@ /* Copyright (c) 2021, Hippocrates Technologies S.r.l.. All rights reserved. - + Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: - + 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. - + 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. - + 3. Neither the name of the copyright holder(s) nor the names of any contributor(s) may be used to endorse or promote products derived from this software without specific prior written permission. No license is granted to the trademarks of the copyright holders even if such marks are included in this software. - + 4. Commercial redistribution in any form requires an explicit license agreement with the copyright holder(s). Please contact support@hippocratestech.com for further information regarding licensing. - + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. @@ -46,11 +46,11 @@ extension OCKAnyTask { let keys = try? JSONDecoder().decode(GroupIdentifierKeys.self, from: data) return keys ?? GroupIdentifierKeys() } - + var category: TaskCategory { return groupIdentifierKeys.category } - + var viewType: TaskStyle { return groupIdentifierKeys.viewType } @@ -60,12 +60,12 @@ extension Collection where Element == OCKEvent { func getTasksAndEventsOf(category: TaskCategory, for interval: DateInterval) -> [TaskEvents] { let categoryTaskEvents = filter({ $0.task.category == category }) let categoryTasks = categoryTaskEvents.map({ $0.task }) - + let tasksAndEvents = categoryTasks.map { task -> TaskEvents in let eventsOfTask = categoryTaskEvents.filter({ $0.task.id == task.id }) return TaskEvents(interval: interval, events: eventsOfTask) } - + return tasksAndEvents } } diff --git a/OTFMagicBox/Library/Extension/Extension+String.swift b/OTFMagicBox/Library/Extension/Extension+String.swift index d2146cca..e7b26e85 100644 --- a/OTFMagicBox/Library/Extension/Extension+String.swift +++ b/OTFMagicBox/Library/Extension/Extension+String.swift @@ -1,25 +1,25 @@ /* Copyright (c) 2021, Hippocrates Technologies S.r.l.. All rights reserved. - + Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: - + 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. - + 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. - + 3. Neither the name of the copyright holder(s) nor the names of any contributor(s) may be used to endorse or promote products derived from this software without specific prior written permission. No license is granted to the trademarks of the copyright holders even if such marks are included in this software. - + 4. Commercial redistribution in any form requires an explicit license agreement with the copyright holder(s). Please contact support@hippocratestech.com for further information regarding licensing. - + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. @@ -40,10 +40,10 @@ import SwiftUI Extension of String to check the Emoji string. */ extension String { - + // Returns true if the given string contains Emoji. var containsEmojis: Bool { - if count == 0 { + if isEmpty { return false } for character in self where character.isEmoji { @@ -51,8 +51,7 @@ extension String { } return false } - - // swiftlint:disable all + var color: UIColor? { switch self { case "Red": @@ -159,9 +158,9 @@ extension String { return nil } } - + var fontWeight: Font.Weight? { - + switch self { case "Thin": return Font.Weight.thin @@ -185,10 +184,8 @@ extension String { return nil } } - - var appFont: Font? { - + switch self { case "Basic": return Font.basicFontStyle @@ -228,7 +225,7 @@ extension String { return nil } } - + var isValidEmail: Bool { NSPredicate(format: "SELF MATCHES %@", "[A-Z0-9a-z._%+-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,}").evaluate(with: self) } diff --git a/OTFMagicBox/Library/Extension/Extension+UIImage.swift b/OTFMagicBox/Library/Extension/Extension+UIImage.swift index 1802d003..bedbef34 100644 --- a/OTFMagicBox/Library/Extension/Extension+UIImage.swift +++ b/OTFMagicBox/Library/Extension/Extension+UIImage.swift @@ -1,35 +1,35 @@ /* -Copyright (c) 2021, Hippocrates Technologies S.r.l.. All rights reserved. - -Redistribution and use in source and binary forms, with or without modification, -are permitted provided that the following conditions are met: - -1. Redistributions of source code must retain the above copyright notice, -this list of conditions and the following disclaimer. - -2. Redistributions in binary form must reproduce the above copyright notice, -this list of conditions and the following disclaimer in the documentation and/or -other materials provided with the distribution. - -3. Neither the name of the copyright holder(s) nor the names of any contributor(s) may -be used to endorse or promote products derived from this software without specific -prior written permission. No license is granted to the trademarks of the copyright -holders even if such marks are included in this software. - -4. Commercial redistribution in any form requires an explicit license agreement with the -copyright holder(s). Please contact support@hippocratestech.com for further information -regarding licensing. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND -ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. -IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, -INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT -NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, -WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) -ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY -OF SUCH DAMAGE. + Copyright (c) 2021, Hippocrates Technologies S.r.l.. All rights reserved. + + Redistribution and use in source and binary forms, with or without modification, + are permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation and/or + other materials provided with the distribution. + + 3. Neither the name of the copyright holder(s) nor the names of any contributor(s) may + be used to endorse or promote products derived from this software without specific + prior written permission. No license is granted to the trademarks of the copyright + holders even if such marks are included in this software. + + 4. Commercial redistribution in any form requires an explicit license agreement with the + copyright holder(s). Please contact support@hippocratestech.com for further information + regarding licensing. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, + INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, + OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY + OF SUCH DAMAGE. */ import Foundation @@ -37,45 +37,45 @@ import UIKit import SwiftUI /** - Extension of UIImage to load the Image depending on its image type. + Extension of UIImage to load the Image depending on its image type. */ extension UIImage { // Checks whether the given image is a SF symbol, if not returns it as a normal image. /** Checks whether the given image is a SF symbol, if not returns it as a normal image. - - Remark:- Use this method to display any of the images in your application. This method supports all kind of images like SF Symbols or any images saved from Assets. + + Remark:- Use this method to display any of the images in your application. This method supports all kind of images like SF Symbols or any images saved from Assets. */ static func loadImage(named: String) -> Image { - if let sfImage = UIImage(systemName: named){ + if let sfImage = UIImage(systemName: named) { return Image(uiImage: sfImage) } return Image(named) } - + func crop(to size: CGSize) -> UIImage? { guard let cgImage = self.cgImage else { return nil } - + let widthRatio = size.width / CGFloat(cgImage.width) let heightRatio = size.height / CGFloat(cgImage.height) let scale = max(widthRatio, heightRatio) - + let scaledWidth = CGFloat(cgImage.width) * scale let scaledHeight = CGFloat(cgImage.height) * scale - + let xOffset = (scaledWidth - size.width) / 2 let yOffset = (scaledHeight - size.height) / 2 - + let targetRect = CGRect(x: -xOffset, y: -yOffset, width: scaledWidth, height: scaledHeight) - + UIGraphicsBeginImageContextWithOptions(size, false, self.scale) draw(in: targetRect) let croppedAndFilledImage = UIGraphicsGetImageFromCurrentImageContext() UIGraphicsEndImageContext() - + return croppedAndFilledImage } } diff --git a/OTFMagicBox/Library/Extension/Extension+URLRequest.swift b/OTFMagicBox/Library/Extension/Extension+URLRequest.swift index 22dfeb61..8cff40d5 100644 --- a/OTFMagicBox/Library/Extension/Extension+URLRequest.swift +++ b/OTFMagicBox/Library/Extension/Extension+URLRequest.swift @@ -1,35 +1,35 @@ /* -Copyright (c) 2021, Hippocrates Technologies S.r.l.. All rights reserved. - -Redistribution and use in source and binary forms, with or without modification, -are permitted provided that the following conditions are met: - -1. Redistributions of source code must retain the above copyright notice, -this list of conditions and the following disclaimer. - -2. Redistributions in binary form must reproduce the above copyright notice, -this list of conditions and the following disclaimer in the documentation and/or -other materials provided with the distribution. - -3. Neither the name of the copyright holder(s) nor the names of any contributor(s) may -be used to endorse or promote products derived from this software without specific -prior written permission. No license is granted to the trademarks of the copyright -holders even if such marks are included in this software. - -4. Commercial redistribution in any form requires an explicit license agreement with the -copyright holder(s). Please contact support@hippocratestech.com for further information -regarding licensing. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND -ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. -IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, -INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT -NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, -WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) -ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY -OF SUCH DAMAGE. + Copyright (c) 2021, Hippocrates Technologies S.r.l.. All rights reserved. + + Redistribution and use in source and binary forms, with or without modification, + are permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation and/or + other materials provided with the distribution. + + 3. Neither the name of the copyright holder(s) nor the names of any contributor(s) may + be used to endorse or promote products derived from this software without specific + prior written permission. No license is granted to the trademarks of the copyright + holders even if such marks are included in this software. + + 4. Commercial redistribution in any form requires an explicit license agreement with the + copyright holder(s). Please contact support@hippocratestech.com for further information + regarding licensing. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, + INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, + OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY + OF SUCH DAMAGE. */ import Foundation @@ -43,9 +43,9 @@ public extension URLRequest { guard let url = url, let httpMethod = httpMethod, - url.absoluteString.utf8.count > 0 + !url.absoluteString.utf8.isEmpty else { - return "" + return "" } var curlCommand = "curl --verbose \\\n" @@ -54,7 +54,7 @@ public extension URLRequest { curlCommand = curlCommand.appendingFormat(" '%@' \\\n", url.absoluteString) // Method if different from GET - if "GET" != httpMethod { + if httpMethod != "GET" { curlCommand = curlCommand.appendingFormat(" -X %@ \\\n", httpMethod) } @@ -67,7 +67,7 @@ public extension URLRequest { } // HTTP body - if let httpBody = httpBody, httpBody.count > 0 { + if let httpBody = httpBody, !httpBody.isEmpty { let httpBodyString = String(data: httpBody, encoding: String.Encoding.utf8)! let escapedHttpBody = URLRequest.escapeAllSingleQuotes(httpBodyString) curlCommand = curlCommand.appendingFormat(" --data '%@' \\\n", escapedHttpBody) diff --git a/OTFMagicBox/Library/Extension/Extention+Notification.swift b/OTFMagicBox/Library/Extension/Extention+Notification.swift index 48a34f8b..e979a4a3 100644 --- a/OTFMagicBox/Library/Extension/Extention+Notification.swift +++ b/OTFMagicBox/Library/Extension/Extention+Notification.swift @@ -1,35 +1,35 @@ /* -Copyright (c) 2021, Hippocrates Technologies S.r.l.. All rights reserved. - -Redistribution and use in source and binary forms, with or without modification, -are permitted provided that the following conditions are met: - -1. Redistributions of source code must retain the above copyright notice, -this list of conditions and the following disclaimer. - -2. Redistributions in binary form must reproduce the above copyright notice, -this list of conditions and the following disclaimer in the documentation and/or -other materials provided with the distribution. - -3. Neither the name of the copyright holder(s) nor the names of any contributor(s) may -be used to endorse or promote products derived from this software without specific -prior written permission. No license is granted to the trademarks of the copyright -holders even if such marks are included in this software. - -4. Commercial redistribution in any form requires an explicit license agreement with the -copyright holder(s). Please contact support@hippocratestech.com for further information -regarding licensing. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND -ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. -IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, -INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT -NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, -WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) -ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY -OF SUCH DAMAGE. + Copyright (c) 2021, Hippocrates Technologies S.r.l.. All rights reserved. + + Redistribution and use in source and binary forms, with or without modification, + are permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation and/or + other materials provided with the distribution. + + 3. Neither the name of the copyright holder(s) nor the names of any contributor(s) may + be used to endorse or promote products derived from this software without specific + prior written permission. No license is granted to the trademarks of the copyright + holders even if such marks are included in this software. + + 4. Commercial redistribution in any form requires an explicit license agreement with the + copyright holder(s). Please contact support@hippocratestech.com for further information + regarding licensing. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, + INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, + OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY + OF SUCH DAMAGE. */ import Foundation @@ -38,6 +38,7 @@ extension NSNotification.Name { static let onboardingDidComplete = NSNotification.Name(Constants.onboardingDidComplete) static let dataSyncRequest = NSNotification.Name(rawValue: Constants.Notification.DataSyncRequest) static let databaseSuccessfllySynchronized = NSNotification.Name(rawValue: Constants.Notification.DatabaseSynchronizedSuccessfully) + static let databaseSync = NSNotification.Name("databaseSync") static let imageUploaded = NSNotification.Name(rawValue: Constants.Notification.ImageUploadedSuccessfully) static let imageDownloaded = NSNotification.Name(rawValue: Constants.Notification.ImageDownloadedSuccessfully) static let deleteUserAccount = Notification.Name(rawValue: Constants.Notification.deleteUserAccount) diff --git a/OTFMagicBox/Library/Extension/Extention+ViewController.swift b/OTFMagicBox/Library/Extension/Extention+ViewController.swift index 5fc81d4c..0ddc10ed 100644 --- a/OTFMagicBox/Library/Extension/Extention+ViewController.swift +++ b/OTFMagicBox/Library/Extension/Extention+ViewController.swift @@ -15,12 +15,12 @@ extension UIViewController { self.present(alert, animated: true, completion: nil) } } - - func alertWithAction(title: String , message: String, completionYes: @escaping ((UIAlertAction) -> Void)) { + + func alertWithAction(title: String, message: String, completionYes: @escaping ((UIAlertAction) -> Void)) { let alertController = UIAlertController(title: title, message: message, preferredStyle: .alert) let okayAction = UIAlertAction(title: "Okay", style: .default, handler: completionYes) alertController.addAction(okayAction) self.present(alertController, animated: true, completion: nil) } - -} \ No newline at end of file + +} diff --git a/OTFMagicBox/Library/Extension/OCKHealthKitStore+Extension.swift b/OTFMagicBox/Library/Extension/OCKHealthKitStore+Extension.swift index 1b5ae4a3..ad8c9add 100644 --- a/OTFMagicBox/Library/Extension/OCKHealthKitStore+Extension.swift +++ b/OTFMagicBox/Library/Extension/OCKHealthKitStore+Extension.swift @@ -1,25 +1,25 @@ /* Copyright (c) 2021, Hippocrates Technologies S.r.l.. All rights reserved. - + Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: - + 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. - + 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. - + 3. Neither the name of the copyright holder(s) nor the names of any contributor(s) may be used to endorse or promote products derived from this software without specific prior written permission. No license is granted to the trademarks of the copyright holders even if such marks are included in this software. - + 4. Commercial redistribution in any form requires an explicit license agreement with the copyright holder(s). Please contact support@hippocratestech.com for further information regarding licensing. - + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. @@ -38,11 +38,11 @@ import Foundation import OTFUtilities extension OCKHealthKitPassthroughStore { - + enum Tasks: String, CaseIterable { case steps } - + func fillWithDummyData() { // Note: If the tasks and contacts already exist in the store, these methods will fail. If you have modified the data and would like the // changes to be reflected in the app, delete and reinstall the catalog app. @@ -55,7 +55,7 @@ extension OCKHealthKitPassthroughStore { } } } - + private func makeTasks(on start: Date) -> [OCKAnyTask] { // Steps task let stepsScheduleElement = OCKScheduleElement(start: start, end: nil, interval: .init(day: 1), @@ -64,7 +64,7 @@ extension OCKHealthKitPassthroughStore { var stepsTask = OCKHealthKitTask(id: Tasks.steps.rawValue, title: "Steps", carePlanUUID: nil, schedule: .init(composing: [stepsScheduleElement]), healthKitLinkage: hkLinkage) stepsTask.instructions = "A walk a day keeps the doctor away." - + return [stepsTask] } } diff --git a/OTFMagicBox/Library/HealthKitManager.swift b/OTFMagicBox/Library/HealthKitManager.swift index 5d278c29..e7086216 100644 --- a/OTFMagicBox/Library/HealthKitManager.swift +++ b/OTFMagicBox/Library/HealthKitManager.swift @@ -1,41 +1,41 @@ /* -Copyright (c) 2021, Hippocrates Technologies S.r.l.. All rights reserved. - -Redistribution and use in source and binary forms, with or without modification, -are permitted provided that the following conditions are met: - -1. Redistributions of source code must retain the above copyright notice, -this list of conditions and the following disclaimer. - -2. Redistributions in binary form must reproduce the above copyright notice, -this list of conditions and the following disclaimer in the documentation and/or -other materials provided with the distribution. - -3. Neither the name of the copyright holder(s) nor the names of any contributor(s) may -be used to endorse or promote products derived from this software without specific -prior written permission. No license is granted to the trademarks of the copyright -holders even if such marks are included in this software. - -4. Commercial redistribution in any form requires an explicit license agreement with the -copyright holder(s). Please contact support@hippocratestech.com for further information -regarding licensing. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND -ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. -IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, -INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT -NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, -WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) -ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY -OF SUCH DAMAGE. + Copyright (c) 2021, Hippocrates Technologies S.r.l.. All rights reserved. + + Redistribution and use in source and binary forms, with or without modification, + are permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation and/or + other materials provided with the distribution. + + 3. Neither the name of the copyright holder(s) nor the names of any contributor(s) may + be used to endorse or promote products derived from this software without specific + prior written permission. No license is granted to the trademarks of the copyright + holders even if such marks are included in this software. + + 4. Commercial redistribution in any form requires an explicit license agreement with the + copyright holder(s). Please contact support@hippocratestech.com for further information + regarding licensing. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, + INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, + OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY + OF SUCH DAMAGE. */ import HealthKit import OTFUtilities -@objc protocol SyncDelegate : AnyObject { +@objc protocol SyncDelegate: AnyObject { @objc optional func didSyncWalkTests() @objc optional func didSyncSurveys() @objc optional func didSyncEvents() @@ -43,48 +43,47 @@ import OTFUtilities } class HealthKitManager: SyncDelegate { - + static let shared = HealthKitManager() - + lazy var healthStore: HKHealthStore = HKHealthStore() - - fileprivate var queryLog = [String:Date]() + + fileprivate var queryLog = [String: Date]() fileprivate let queryLogMutex = NSLock() - fileprivate let timeBetweenQueries: TimeInterval = 60 //in seconds - - var userAuthorizedHKOnDevice : Bool? { + fileprivate let timeBetweenQueries: TimeInterval = 60 // in seconds + + var userAuthorizedHKOnDevice: Bool? { get { return UserDefaults.standard.value(forKey: Constants.UserDefaults.HKDataShare) as? Bool } set(newValue) { UserDefaults.standard.set(newValue, forKey: Constants.UserDefaults.HKDataShare) - // CKSession.putSecure(value: String(newValue ?? false), forKey: Constants.UserDefaults.HKDataShare) + // CKSession.putSecure(value: String(newValue ?? false), forKey: Constants.UserDefaults.HKDataShare) } } - + init() { NotificationCenter.default.addObserver(self, selector: #selector(HealthKitManager.syncData), name: .dataSyncRequest, object: nil) } - + deinit { NotificationCenter.default.removeObserver(self, name: .dataSyncRequest, object: nil) } - + public func getHealthKitAuth(forTypes types: Set, _ completion: @escaping (_ success: Bool, _ error: NSError?) -> Void) { - healthStore.requestAuthorization(toShare: nil, read: types) { - [weak self] success, error in - + healthStore.requestAuthorization(toShare: nil, read: types) { [weak self] success, error in + guard let strongSelf = self else { return } strongSelf.userAuthorizedHKOnDevice = success - + completion(success, error as NSError?) } } - + public func startBackgroundDelivery(forTypes types: Set, withFrequency frequency: HKUpdateFrequency, _ completion: ((_ success: Bool, _ error: Error?) -> Void)? = nil) { self.setUpBackgroundDeliveryForDataTypes(types: types, frequency: frequency, completion) } - + public func disableHealthKit(_ completion: ((_ success: Bool, _ error: Error?) -> Void)? = nil) { healthStore.disableAllBackgroundDelivery { (success, error) in if let error = error { @@ -93,31 +92,31 @@ class HealthKitManager: SyncDelegate { completion?(success, error) } } - + } extension HealthKitManager { - + fileprivate func setUpBackgroundDeliveryForDataTypes(types: Set, frequency: HKUpdateFrequency, _ completion: ((_ success: Bool, _ error: Error?) -> Void)? = nil) { let dispatchGroup = DispatchGroup() - + for type in types { - let query = HKObserverQuery(sampleType: type, predicate: nil, updateHandler: { [weak self] (query, completionHandler, error) in - + let query = HKObserverQuery(sampleType: type, predicate: nil, updateHandler: { [weak self] (_, completionHandler, _) in + guard let strongSelf = self else { completionHandler() return } - + dispatchGroup.enter() strongSelf.backgroundQuery(forType: type, completionHandler: { completionHandler() dispatchGroup.leave() }) - + }) - + healthStore.execute(query) healthStore.enableBackgroundDelivery(for: type, frequency: frequency, withCompletion: { (success, error) in if let error = error { @@ -125,76 +124,74 @@ extension HealthKitManager { } completion?(success, error) }) - + } - + dispatchGroup.notify(queue: .main) { OTFLog("Task finished.") } } - - //TODO: (delete) running the old data collection solution as a baseline to compare new values + @available(*, deprecated) - fileprivate func cumulativeBackgroundQuery(forType type: HKQuantityType, completionHandler: @escaping ()->Void) { - + fileprivate func cumulativeBackgroundQuery(forType type: HKQuantityType, completionHandler: @escaping () -> Void) { + let supportedTypes = [HKQuantityTypeIdentifier.stepCount.rawValue, HKQuantityTypeIdentifier.flightsClimbed.rawValue, HKQuantityTypeIdentifier.distanceWalkingRunning.rawValue] - if (!supportedTypes.contains(type.identifier)) { + if !supportedTypes.contains(type.identifier) { OTFLog("No cumulative query will run for type %@", type.identifier) completionHandler() return } - + guard canQuery(forType: type) else { OTFLog("Cannot yet query for %@, please try again in a minute.", type.identifier) completionHandler() return } - DispatchQueue.main.async { //run on main queue, which exists even if the app is 100% in the background. - + DispatchQueue.main.async { // run on main queue, which exists even if the app is 100% in the background. + OTFLog("[DEPRECATED] cumulative querying for type %@", type.identifier) - + } - + } - - fileprivate func backgroundQuery(forType type: HKQuantityType, completionHandler: @escaping ()->Void) { - + + fileprivate func backgroundQuery(forType type: HKQuantityType, completionHandler: @escaping () -> Void) { + guard canQuery(forType: type) else { OTFLog("Cannot yet query for %{public}@, please try again in a minute.", type.identifier) completionHandler() return } - - DispatchQueue.main.async { //run on main queue, which exists even if the app is 100% in the background. - + + DispatchQueue.main.async { // run on main queue, which exists even if the app is 100% in the background. + OTFLog("Querying for type %{public}@", type.identifier) - + } - + } fileprivate func canQuery(forType type: HKQuantityType) -> Bool { queryLogMutex.lock() defer { queryLogMutex.unlock() } - + let currentDate = Date() guard let lastQueryDate = queryLog[type.identifier] else { queryLog[type.identifier] = currentDate return true } - + if currentDate.addingTimeInterval(-timeBetweenQueries) >= lastQueryDate { queryLog[type.identifier] = currentDate return true } - + return false } - + @objc fileprivate func syncData(forHkTypes hkTypes: Set) { - + } - -} +} diff --git a/OTFMagicBox/Library/HealthRecordManager.swift b/OTFMagicBox/Library/HealthRecordManager.swift index ef4a6e12..b6df28b7 100644 --- a/OTFMagicBox/Library/HealthRecordManager.swift +++ b/OTFMagicBox/Library/HealthRecordManager.swift @@ -1,35 +1,35 @@ /* -Copyright (c) 2021, Hippocrates Technologies S.r.l.. All rights reserved. - -Redistribution and use in source and binary forms, with or without modification, -are permitted provided that the following conditions are met: - -1. Redistributions of source code must retain the above copyright notice, -this list of conditions and the following disclaimer. - -2. Redistributions in binary form must reproduce the above copyright notice, -this list of conditions and the following disclaimer in the documentation and/or -other materials provided with the distribution. - -3. Neither the name of the copyright holder(s) nor the names of any contributor(s) may -be used to endorse or promote products derived from this software without specific -prior written permission. No license is granted to the trademarks of the copyright -holders even if such marks are included in this software. - -4. Commercial redistribution in any form requires an explicit license agreement with the -copyright holder(s). Please contact support@hippocratestech.com for further information -regarding licensing. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND -ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. -IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, -INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT -NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, -WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) -ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY -OF SUCH DAMAGE. + Copyright (c) 2021, Hippocrates Technologies S.r.l.. All rights reserved. + + Redistribution and use in source and binary forms, with or without modification, + are permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation and/or + other materials provided with the distribution. + + 3. Neither the name of the copyright holder(s) nor the names of any contributor(s) may + be used to endorse or promote products derived from this software without specific + prior written permission. No license is granted to the trademarks of the copyright + holders even if such marks are included in this software. + + 4. Commercial redistribution in any form requires an explicit license agreement with the + copyright holder(s). Please contact support@hippocratestech.com for further information + regarding licensing. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, + INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, + OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY + OF SUCH DAMAGE. */ import Foundation @@ -39,11 +39,11 @@ import OTFCareKitStore import OTFUtilities class HealthRecordsManager: NSObject { - + static let shared = HealthRecordsManager() - + lazy var healthStore = HKHealthStore() - + fileprivate let typesById: [HKClinicalTypeIdentifier] = [ .allergyRecord, // HKClinicalTypeIdentifierAllergyRecord .conditionRecord, // HKClinicalTypeIdentifierConditionRecord @@ -53,9 +53,9 @@ class HealthRecordsManager: NSObject { .procedureRecord, // HKClinicalTypeIdentifierProcedureRecord .vitalSignRecord // HKClinicalTypeIdentifierVitalSignRecord ] - + fileprivate var types = Set() - + override init() { super.init() for id in typesById { @@ -63,17 +63,17 @@ class HealthRecordsManager: NSObject { types.insert(record) } } - + func getAuth(_ completion: @escaping (_ success: Bool, _ error: Error?) -> Void) { healthStore.requestAuthorization(toShare: nil, read: types) { (success, error) in completion(success, error) } } - + func upload(_ onCompletion: ((Bool, Error?) -> Void)? = nil) { for type in types { - let query = HKSampleQuery(sampleType: type, predicate: nil, limit: HKObjectQueryNoLimit, sortDescriptors: nil) { (query, samples, error) in - + let query = HKSampleQuery(sampleType: type, predicate: nil, limit: HKObjectQueryNoLimit, sortDescriptors: nil) { (_, samples, error) in + guard let samples = samples as? [HKClinicalRecord] else { OTFError("An error occurred in uploading health record: %{public}@", error?.localizedDescription ?? "") onCompletion?(false, error) @@ -82,16 +82,15 @@ class HealthRecordsManager: NSObject { OTFLog("[HealthRecordsManager] upload() - sending %{public}@ sample(s)", samples.count) for sample in samples { guard let resource = sample.fhirResource else { continue } - _ = resource.data - _ = resource.resourceType.rawValue + "-" + resource.identifier + _ = resource.data + _ = resource.resourceType.rawValue + "-" + resource.identifier } - + UserDefaults.standard.set(Date(), forKey: Constants.prefHealthRecordsLastUploaded) onCompletion?(true, nil) } healthStore.execute(query) } } - -} +} diff --git a/OTFMagicBox/Library/KeychainCloudManager.swift b/OTFMagicBox/Library/KeychainCloudManager.swift index 444f6a27..fa9dcd8e 100644 --- a/OTFMagicBox/Library/KeychainCloudManager.swift +++ b/OTFMagicBox/Library/KeychainCloudManager.swift @@ -1,74 +1,83 @@ /* -Copyright (c) 2021, Hippocrates Technologies S.r.l.. All rights reserved. + Copyright (c) 2021, Hippocrates Technologies S.r.l.. All rights reserved. -Redistribution and use in source and binary forms, with or without modification, -are permitted provided that the following conditions are met: + Redistribution and use in source and binary forms, with or without modification, + are permitted provided that the following conditions are met: -1. Redistributions of source code must retain the above copyright notice, -this list of conditions and the following disclaimer. + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. -2. Redistributions in binary form must reproduce the above copyright notice, -this list of conditions and the following disclaimer in the documentation and/or -other materials provided with the distribution. + 2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation and/or + other materials provided with the distribution. -3. Neither the name of the copyright holder(s) nor the names of any contributor(s) may -be used to endorse or promote products derived from this software without specific -prior written permission. No license is granted to the trademarks of the copyright -holders even if such marks are included in this software. + 3. Neither the name of the copyright holder(s) nor the names of any contributor(s) may + be used to endorse or promote products derived from this software without specific + prior written permission. No license is granted to the trademarks of the copyright + holders even if such marks are included in this software. -4. Commercial redistribution in any form requires an explicit license agreement with the -copyright holder(s). Please contact support@hippocratestech.com for further information -regarding licensing. + 4. Commercial redistribution in any form requires an explicit license agreement with the + copyright holder(s). Please contact support@hippocratestech.com for further information + regarding licensing. -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND -ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. -IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, -INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT -NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, -WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) -ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY -OF SUCH DAMAGE. + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, + INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, + OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY + OF SUCH DAMAGE. */ import Foundation import OTFUtilities import Sodium -class KeychainCloudManager { +enum KeychainCloudManager { static var getEmailAddress: String { let email = SwiftSodium().loadKey(keychainKey: KeychainKeys.emailKey) return String(decoding: email, as: UTF8.self) } - + static var getPassword: String { let password = SwiftSodium().loadKey(keychainKey: KeychainKeys.passwordKey) return String(decoding: password, as: UTF8.self) } - + static var getMasterKey: Bytes { let swiftSodium = SwiftSodium() let masterKey = swiftSodium.loadKey(keychainKey: KeychainKeys.masterKey) - return swiftSodium.getArrayOfBytesFromData(FileData: masterKey as NSData) + return swiftSodium.getArrayOfBytesFromData(fileData: masterKey as NSData) } - + static var getDefaultStorageKey: Bytes { let swiftSodium = SwiftSodium() let defaultStorageKey = swiftSodium.loadKey(keychainKey: KeychainKeys.defaultStorageKey) - return swiftSodium.getArrayOfBytesFromData(FileData: defaultStorageKey as NSData) + return swiftSodium.getArrayOfBytesFromData(fileData: defaultStorageKey as NSData) } - + static var getConfidentialStorageKey: Bytes { let swiftSodium = SwiftSodium() let confidentialStorageKey = swiftSodium.loadKey(keychainKey: KeychainKeys.confidentialStorageKey) - return swiftSodium.getArrayOfBytesFromData(FileData: confidentialStorageKey as NSData) + return swiftSodium.getArrayOfBytesFromData(fileData: confidentialStorageKey as NSData) } - - static func saveValuesInKeychain(email: String, password: String, masterKey: Bytes, publicKey: Bytes, secretKey: Bytes, defaultStorageKey: Bytes, confidentialStorageKey: Bytes) { + + static func saveUserCredentialsInKeychain(email: String, password: String) { let swiftSodium = SwiftSodium() swiftSodium.saveStringValue(key: email, keychainKey: KeychainKeys.emailKey) swiftSodium.saveStringValue(key: password, keychainKey: KeychainKeys.passwordKey) + } + + static func isKeyStored(key: String) -> Bool { + let swiftSodium = SwiftSodium() + return swiftSodium.isKeyStored(keychainKey: key) + } + + static func saveUserKeys(masterKey: Bytes, publicKey: Bytes, secretKey: Bytes, defaultStorageKey: Bytes, confidentialStorageKey: Bytes) { + let swiftSodium = SwiftSodium() swiftSodium.saveKey(key: publicKey, keychainKey: KeychainKeys.publicKey) swiftSodium.saveKey(key: secretKey, keychainKey: KeychainKeys.secretKey) swiftSodium.saveKey(key: masterKey, keychainKey: KeychainKeys.masterKey) @@ -77,7 +86,7 @@ class KeychainCloudManager { } } -struct KeychainKeys { +enum KeychainKeys { static let secretKey = "secretKey" static let publicKey = "publicKey" static let passwordKey = "passwordKey" diff --git a/OTFMagicBox/Library/LoaderView.swift b/OTFMagicBox/Library/LoaderView.swift new file mode 100644 index 00000000..b40bd149 --- /dev/null +++ b/OTFMagicBox/Library/LoaderView.swift @@ -0,0 +1,55 @@ +/* + Copyright (c) 2021, Hippocrates Technologies S.r.l.. All rights reserved. + + Redistribution and use in source and binary forms, with or without modification, + are permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation and/or + other materials provided with the distribution. + + 3. Neither the name of the copyright holder(s) nor the names of any contributor(s) may + be used to endorse or promote products derived from this software without specific + prior written permission. No license is granted to the trademarks of the copyright + holders even if such marks are included in this software. + + 4. Commercial redistribution in any form requires an explicit license agreement with the + copyright holder(s). Please contact support@hippocratestech.com for further information + regarding licensing. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, + INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, + OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY + OF SUCH DAMAGE. + */ + +import SwiftUI + +struct LoaderView: View { + var tintColor: Color = .black + var scaleSize: CGFloat = 1.0 + + var body: some View { + ProgressView() + .scaleEffect(scaleSize, anchor: .center) + .progressViewStyle(CircularProgressViewStyle(tint: tintColor)) + } +} + +extension View { + @ViewBuilder func hidden(_ shouldHide: Bool) -> some View { + switch shouldHide { + case true: self.hidden() + case false: self + } + } +} diff --git a/OTFMagicBox/Library/LocalAuthentication.swift b/OTFMagicBox/Library/LocalAuthentication.swift index 5268545f..b7a4d8ab 100644 --- a/OTFMagicBox/Library/LocalAuthentication.swift +++ b/OTFMagicBox/Library/LocalAuthentication.swift @@ -1,79 +1,77 @@ /* -Copyright (c) 2021, Hippocrates Technologies S.r.l.. All rights reserved. - -Redistribution and use in source and binary forms, with or without modification, -are permitted provided that the following conditions are met: - -1. Redistributions of source code must retain the above copyright notice, -this list of conditions and the following disclaimer. - -2. Redistributions in binary form must reproduce the above copyright notice, -this list of conditions and the following disclaimer in the documentation and/or -other materials provided with the distribution. - -3. Neither the name of the copyright holder(s) nor the names of any contributor(s) may -be used to endorse or promote products derived from this software without specific -prior written permission. No license is granted to the trademarks of the copyright -holders even if such marks are included in this software. - -4. Commercial redistribution in any form requires an explicit license agreement with the -copyright holder(s). Please contact support@hippocratestech.com for further information -regarding licensing. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND -ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. -IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, -INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT -NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, -WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) -ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY -OF SUCH DAMAGE. + Copyright (c) 2021, Hippocrates Technologies S.r.l.. All rights reserved. + + Redistribution and use in source and binary forms, with or without modification, + are permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation and/or + other materials provided with the distribution. + + 3. Neither the name of the copyright holder(s) nor the names of any contributor(s) may + be used to endorse or promote products derived from this software without specific + prior written permission. No license is granted to the trademarks of the copyright + holders even if such marks are included in this software. + + 4. Commercial redistribution in any form requires an explicit license agreement with the + copyright holder(s). Please contact support@hippocratestech.com for further information + regarding licensing. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, + INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, + OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY + OF SUCH DAMAGE. */ import Foundation import LocalAuthentication - - open class LocalAuthentication: NSObject { - + public static let shared = LocalAuthentication() - - private override init() {} - + + override private init() {} + var laContext = LAContext() - + func canAuthenticate() -> Bool { var error: NSError? let hasTouchId = laContext.canEvaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, error: &error) return hasTouchId } - + func hasTouchId() -> Bool { if canAuthenticate() && laContext.biometryType == .touchID { return true } return false } - + func hasFaceId() -> Bool { if canAuthenticate() && laContext.biometryType == .faceID { return true } return false } - - typealias AuthCompletion = (Bool) -> () + + typealias AuthCompletion = (Bool) -> Void func authenticationWithTouchID(completion: @escaping AuthCompletion) { let localAuthenticationContext = LAContext() localAuthenticationContext.localizedFallbackTitle = "Please use your Passcode" var authorizationError: NSError? - + let reason = "Authentication required to access the secure data" - + if localAuthenticationContext.canEvaluatePolicy(.deviceOwnerAuthentication, error: &authorizationError) { - + localAuthenticationContext.evaluatePolicy(.deviceOwnerAuthentication, localizedReason: reason) { success, evaluateError in if success { completion(success) diff --git a/OTFMagicBox/Library/Metrics.swift b/OTFMagicBox/Library/Metrics.swift index 481c68b1..44571253 100644 --- a/OTFMagicBox/Library/Metrics.swift +++ b/OTFMagicBox/Library/Metrics.swift @@ -1,94 +1,94 @@ /* -Copyright (c) 2021, Hippocrates Technologies S.r.l.. All rights reserved. - -Redistribution and use in source and binary forms, with or without modification, -are permitted provided that the following conditions are met: - -1. Redistributions of source code must retain the above copyright notice, -this list of conditions and the following disclaimer. - -2. Redistributions in binary form must reproduce the above copyright notice, -this list of conditions and the following disclaimer in the documentation and/or -other materials provided with the distribution. - -3. Neither the name of the copyright holder(s) nor the names of any contributor(s) may -be used to endorse or promote products derived from this software without specific -prior written permission. No license is granted to the trademarks of the copyright -holders even if such marks are included in this software. - -4. Commercial redistribution in any form requires an explicit license agreement with the -copyright holder(s). Please contact support@hippocratestech.com for further information -regarding licensing. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND -ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. -IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, -INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT -NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, -WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) -ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY -OF SUCH DAMAGE. + Copyright (c) 2021, Hippocrates Technologies S.r.l.. All rights reserved. + + Redistribution and use in source and binary forms, with or without modification, + are permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation and/or + other materials provided with the distribution. + + 3. Neither the name of the copyright holder(s) nor the names of any contributor(s) may + be used to endorse or promote products derived from this software without specific + prior written permission. No license is granted to the trademarks of the copyright + holders even if such marks are included in this software. + + 4. Commercial redistribution in any form requires an explicit license agreement with the + copyright holder(s). Please contact support@hippocratestech.com for further information + regarding licensing. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, + INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, + OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY + OF SUCH DAMAGE. */ import UIKit -class Metrics { - -/// used for button padding +enum Metrics { + + /// used for button padding static let PADDING_HORIZONTAL_BUTTON: CGFloat = 15 static let PADDING_VERTICAL_BUTTON: CGFloat = 15 - -/// used for button text padding + + /// used for button text padding static let PADDING_BUTTON_LABEL: CGFloat = 15 - -/// used for button corner radius + + /// used for button corner radius static let RADIUS_CORNER_BUTTON: CGFloat = 4 - -/// used for row padding + + /// used for row padding static let PADDING_VERTICAL_ROW: CGFloat = 10 - -/// used for row width + + /// used for row width static let FRAME_ROW_WIDTH: CGFloat = 30 - -/// used for row height + + /// used for row height static let FRAME_ROW_HEIGHT: CGFloat = 30 - -/// used for HelpView and Passcode view height + + /// used for HelpView and Passcode view height static let MAIN_VIEW_HEIGHT: CGFloat = 70 - -/// used for title height + + /// used for title height static let TITLE_VIEW_HEIGHT: CGFloat = 60 - -/// used for image view height + + /// used for image view height static let IMAGE_VIEW_HEIGHT: CGFloat = 72 - -/// used for image view width + + /// used for image view width static let IMAGE_VIEW_WIDTH: CGFloat = 72 - -/// used for profile main view height + + /// used for profile main view height static let PROFILE_MAIN_VIEW_HEIGHT: CGFloat = 80 - -/// used for profile image view view height + + /// used for profile image view view height static let PROFILE_IMAGE_HEIGHT: CGFloat = 80 - -/// used for profile image view width + + /// used for profile image view width static let PROFILE_IMAGE_WIDTH: CGFloat = 80 - -/// used for task item height + + /// used for task item height static let TASK_ITEM_HEIGHT: CGFloat = 32 - -/// used for task item width + + /// used for task item width static let TASK_ITEM_WIDTH: CGFloat = 32 - -/// used for main task view height + + /// used for main task view height static let TASK_VIEW_HEIGHT: CGFloat = 65 - + static let BOTTOM_SPACER: CGFloat = 20 - + static let NETWORK_INDICATOR_WIDTH: CGFloat = 30 - + static let NETWORK_INDICATOR_ACCESSORY_WIDTH: CGFloat = 15 - + } diff --git a/OTFMagicBox/Library/OTFColor.swift b/OTFMagicBox/Library/OTFColor.swift index 9e283958..45beb8b0 100644 --- a/OTFMagicBox/Library/OTFColor.swift +++ b/OTFMagicBox/Library/OTFColor.swift @@ -9,31 +9,47 @@ import SwiftUI extension Color { static var otfTextColor: Color { - if let uiColor = YmlReader().appTheme?.textColor.color { + if let uiColor = YmlReader().appStyle.textColor.color { return Color(uiColor) } else { return Color(UIColor.label) } } - + static var otfHeaderColor: Color { - if let uiColor = YmlReader().appTheme?.headerColor.color { + if let uiColor = YmlReader().appStyle.headerColor.color { return Color(uiColor) } else { return Color(UIColor.secondaryLabel) } } - + static var otfCellBackground: Color { - if let uiColor = YmlReader().appTheme?.cellbackgroundColor.color { + if let uiColor = YmlReader().appStyle.cellbackgroundColor.color { return Color(uiColor) } else { return Color(UIColor.systemBackground) } } - + static var otfButtonColor: Color { - if let uiColor = YmlReader().appTheme?.buttonTextColor.color { + if let uiColor = YmlReader().appStyle.buttonTextColor.color { + return Color(uiColor) + } else { + return Color(UIColor.black) + } + } + + static var otfborderColor : Color { + if let uiColor = YmlReader().appStyle.borderColor.color { + return Color(uiColor) + } else { + return Color(UIColor.black) + } + } + + static var otfseparatorColor : Color { + if let uiColor = YmlReader().appStyle.separatorColor.color { return Color(uiColor) } else { return Color(UIColor.black) diff --git a/OTFMagicBox/Library/OTFFont.swift b/OTFMagicBox/Library/OTFFont.swift new file mode 100644 index 00000000..55763632 --- /dev/null +++ b/OTFMagicBox/Library/OTFFont.swift @@ -0,0 +1,46 @@ +// +// OTFFont.swift +// OTFMagicBox +// +// Created by Waqas Khadim on 25/10/2023. +// + +import SwiftUI + +extension Font { + static var otfAppFont : Font { + if let font = YmlReader().appStyle.textFont.appFont { + return font + } else { + return Font.system(size: 17.0) + } + } + + static var otfFontWeight : Font.Weight? { + return YmlReader().appStyle.textWeight.fontWeight + } + + static var otfscreenTitleFont : Font { + if let font = YmlReader().appStyle.screenTitleFont.appFont { + return font + } else { + return Font.system(size: 17.0) + } + } + + static var otfscreenTitleFontWeight : Font.Weight? { + return YmlReader().appStyle.screenTitleFont.fontWeight + } + + static var otfheaderTitleFont : Font { + if let font = YmlReader().appStyle.headerTitleFont.appFont { + return font + } else { + return Font.system(size: 17.0) + } + } + + static var otfheaderTitleWeight : Font.Weight? { + return YmlReader().appStyle.headerTitleWeight.fontWeight + } +} diff --git a/OTFMagicBox/Library/OTFHealthKitManager.swift b/OTFMagicBox/Library/OTFHealthKitManager.swift index d19aafad..9d378b4d 100644 --- a/OTFMagicBox/Library/OTFHealthKitManager.swift +++ b/OTFMagicBox/Library/OTFHealthKitManager.swift @@ -1,47 +1,47 @@ /* -Copyright (c) 2021, Hippocrates Technologies S.r.l.. All rights reserved. - -Redistribution and use in source and binary forms, with or without modification, -are permitted provided that the following conditions are met: - -1. Redistributions of source code must retain the above copyright notice, -this list of conditions and the following disclaimer. - -2. Redistributions in binary form must reproduce the above copyright notice, -this list of conditions and the following disclaimer in the documentation and/or -other materials provided with the distribution. - -3. Neither the name of the copyright holder(s) nor the names of any contributor(s) may -be used to endorse or promote products derived from this software without specific -prior written permission. No license is granted to the trademarks of the copyright -holders even if such marks are included in this software. - -4. Commercial redistribution in any form requires an explicit license agreement with the -copyright holder(s). Please contact support@hippocratestech.com for further information -regarding licensing. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND -ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. -IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, -INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT -NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, -WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) -ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY -OF SUCH DAMAGE. + Copyright (c) 2021, Hippocrates Technologies S.r.l.. All rights reserved. + + Redistribution and use in source and binary forms, with or without modification, + are permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation and/or + other materials provided with the distribution. + + 3. Neither the name of the copyright holder(s) nor the names of any contributor(s) may + be used to endorse or promote products derived from this software without specific + prior written permission. No license is granted to the trademarks of the copyright + holders even if such marks are included in this software. + + 4. Commercial redistribution in any form requires an explicit license agreement with the + copyright holder(s). Please contact support@hippocratestech.com for further information + regarding licensing. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, + INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, + OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY + OF SUCH DAMAGE. */ import Foundation import HealthKit -class OTFHealthKitManager : NSObject { - +class OTFHealthKitManager: NSObject { + public static let shared = OTFHealthKitManager() - + fileprivate var hkTypesToReadInBackground: Set = [] - - private override init() { + + override private init() { let healthKitData = ModuleAppYmlReader().healthKitDataToRead as Array for requestedHKType in healthKitData { let id = HKQuantityTypeIdentifier(rawValue: "HKQuantityTypeIdentifier" + requestedHKType.type) @@ -49,7 +49,7 @@ class OTFHealthKitManager : NSObject { hkTypesToReadInBackground.insert(hkType!) } } - + /// Query for HealthKit Authorization /// - Parameter completion: (success, error) func getHealthAuthorization(_ completion: @escaping (_ success: Bool, _ error: Error?) -> Void) { @@ -58,10 +58,10 @@ class OTFHealthKitManager : NSObject { * in the background. Choose from any HKQuantityType: * https://developer.apple.com/documentation/healthkit/hkquantitytypeidentifier * *************************************************************/ - + // handle authorization from the OS ActivityManager.shared.getHealthAuthorizaton(forTypes: hkTypesToReadInBackground) { (success, error) in - if (success) { + if success { let frequency = ModuleAppYmlReader().backgroundReadFrequency if frequency == HKUpdateFrequency.daily.stringValue { @@ -78,20 +78,18 @@ class OTFHealthKitManager : NSObject { } } } - - extension HKUpdateFrequency { var stringValue: String { switch self { case .hourly: return "hourly" - + case .daily: return "daily" - + case .weekly: return "weekly" - + default: return "immediate" } diff --git a/OTFMagicBox/Library/UIColor.swift b/OTFMagicBox/Library/UIColor.swift index c08a77a2..81cb3e6d 100644 --- a/OTFMagicBox/Library/UIColor.swift +++ b/OTFMagicBox/Library/UIColor.swift @@ -1,35 +1,35 @@ /* -Copyright (c) 2021, Hippocrates Technologies S.r.l.. All rights reserved. - -Redistribution and use in source and binary forms, with or without modification, -are permitted provided that the following conditions are met: - -1. Redistributions of source code must retain the above copyright notice, -this list of conditions and the following disclaimer. - -2. Redistributions in binary form must reproduce the above copyright notice, -this list of conditions and the following disclaimer in the documentation and/or -other materials provided with the distribution. - -3. Neither the name of the copyright holder(s) nor the names of any contributor(s) may -be used to endorse or promote products derived from this software without specific -prior written permission. No license is granted to the trademarks of the copyright -holders even if such marks are included in this software. - -4. Commercial redistribution in any form requires an explicit license agreement with the -copyright holder(s). Please contact support@hippocratestech.com for further information -regarding licensing. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND -ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. -IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, -INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT -NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, -WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) -ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY -OF SUCH DAMAGE. + Copyright (c) 2021, Hippocrates Technologies S.r.l.. All rights reserved. + + Redistribution and use in source and binary forms, with or without modification, + are permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation and/or + other materials provided with the distribution. + + 3. Neither the name of the copyright holder(s) nor the names of any contributor(s) may + be used to endorse or promote products derived from this software without specific + prior written permission. No license is granted to the trademarks of the copyright + holders even if such marks are included in this software. + + 4. Commercial redistribution in any form requires an explicit license agreement with the + copyright holder(s). Please contact support@hippocratestech.com for further information + regarding licensing. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, + INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, + OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY + OF SUCH DAMAGE. */ import Foundation @@ -37,33 +37,29 @@ import UIKit import SwiftUI extension UIColor { - var rgba: (red: CGFloat, green: CGFloat, blue: CGFloat, alpha: CGFloat) { var red: CGFloat = 0 var green: CGFloat = 0 var blue: CGFloat = 0 var alpha: CGFloat = 0 getRed(&red, green: &green, blue: &blue, alpha: &alpha) - return (red, green, blue, alpha) } - /// Returns UIcolor for a given type of hex color. func getColor(colorValue: String) -> UIColor { let hex = colorValue - var cString:String = hex.trimmingCharacters(in: .whitespacesAndNewlines).uppercased() - - if (cString.hasPrefix("#")) { + var cString: String = hex.trimmingCharacters(in: .whitespacesAndNewlines).uppercased() + + if cString.hasPrefix("#") { cString.remove(at: cString.startIndex) } - - if ((cString.count) != 6) { + if (cString.count) != 6 { return UIColor.gray } - - var rgbValue:UInt64 = 0 + + var rgbValue: UInt64 = 0 Scanner(string: cString).scanHexInt64(&rgbValue) - + return UIColor( red: CGFloat((rgbValue & 0xFF0000) >> 16) / 255.0, green: CGFloat((rgbValue & 0x00FF00) >> 8) / 255.0, @@ -72,12 +68,10 @@ extension UIColor { ) } } - - public extension Color { - + private typealias PlatformColor = UIColor - + static var systemBlue: Color { Color(PlatformColor.systemBlue) } static var systemBrown: Color { Color(PlatformColor.systemBrown) } @available(iOS 15.0, macOS 12.0, tvOS 15.0, *) @@ -92,27 +86,27 @@ public extension Color { static var systemRed: Color { Color(PlatformColor.systemRed) } static var systemTeal: Color { Color(PlatformColor.systemTeal) } static var systemYellow: Color { Color(PlatformColor.systemYellow) } - + static var systemGray2: Color { Color(PlatformColor.systemGray2) } static var systemGray3: Color { Color(PlatformColor.systemGray3) } static var systemGray4: Color { Color(PlatformColor.systemGray4) } static var systemGray5: Color { Color(PlatformColor.systemGray5) } static var systemGray6: Color { Color(PlatformColor.systemGray6) } - + static var darkGray: Color { Color(PlatformColor.darkGray) } static var lightGray: Color { Color(PlatformColor.lightGray) } static var magenta: Color { Color(PlatformColor.magenta) } - + static var systemFill: Color { Color(PlatformColor.systemFill) } static var secondarySystemFill: Color { Color(PlatformColor.secondarySystemFill) } static var tertiarySystemFill: Color { Color(PlatformColor.tertiarySystemFill) } static var quaternarySystemFill: Color { Color(PlatformColor.quaternarySystemFill) } - + static var placeholderText: Color { Color(PlatformColor.placeholderText) } static var systemBackground: Color { Color(PlatformColor.systemBackground) } static var secondarySystemBackground: Color { Color(PlatformColor.secondarySystemBackground) } static var tertiarySystemBackground: Color { Color(PlatformColor.tertiarySystemBackground) } - + static var systemGroupedBackground: Color { Color(PlatformColor.systemGroupedBackground) } static var secondarySystemGroupedBackground: Color { Color(PlatformColor.secondarySystemGroupedBackground) } static var tertiarySystemGroupedBackground: Color { Color(PlatformColor.tertiarySystemGroupedBackground) } diff --git a/OTFMagicBox/Library/UploadDocumentManager.swift b/OTFMagicBox/Library/UploadDocumentManager.swift index 4555d21c..869f4d34 100644 --- a/OTFMagicBox/Library/UploadDocumentManager.swift +++ b/OTFMagicBox/Library/UploadDocumentManager.swift @@ -1,25 +1,25 @@ /* Copyright (c) 2021, Hippocrates Technologies S.r.l.. All rights reserved. - + Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: - + 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. - + 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. - + 3. Neither the name of the copyright holder(s) nor the names of any contributor(s) may be used to endorse or promote products derived from this software without specific prior written permission. No license is granted to the trademarks of the copyright holders even if such marks are included in this software. - + 4. Commercial redistribution in any form requires an explicit license agreement with the copyright holder(s). Please contact support@hippocratestech.com for further information regarding licensing. - + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. @@ -38,14 +38,14 @@ import OTFUtilities import Sodium final class UploadDocumentManager { - + private var disposables = Set() let swiftSodium = SwiftSodium() - + func encryptDocument(document: Data, fileName: String) { - let bytesImage = swiftSodium.getArrayOfBytesFromData(FileData: document as NSData) + let bytesImage = swiftSodium.getArrayOfBytesFromData(fileData: document as NSData) let defaultStorageKey = KeychainCloudManager.getDefaultStorageKey - + let fileKeyPushStream = swiftSodium.getPushStream(secretKey: defaultStorageKey)! let fileKey = swiftSodium.generateDeriveKey(key: defaultStorageKey) let eFileKey = swiftSodium.encryptFile(pushStream: fileKeyPushStream, fileBytes: fileKey) @@ -53,42 +53,38 @@ final class UploadDocumentManager { return element }) let encryptedFileKeyHex = encryptedFileKey.bytesToHex(spacing: "").lowercased() - + let documentPushStream = swiftSodium.getPushStream(secretKey: fileKey)! let fileencryption = swiftSodium.encryptFile(pushStream: documentPushStream, fileBytes: bytesImage) let newFile = [documentPushStream.header(), fileencryption].flatMap({ (element: [UInt8]) -> [UInt8] in return element }) - + let hashKeyFile = swiftSodium.generateGenericHashWithKey(message: newFile, fileKey: fileKey) let hashKeyFileHex = hashKeyFile.bytesToHex(spacing: "").lowercased() let encryptedFileData = Data(newFile) - + uploadFile(data: encryptedFileData, fileName: fileName, encryptedFileKey: encryptedFileKeyHex, hashFileKey: hashKeyFileHex) } - func decryptedFile(file: Data, hashFileKey: String) -> Data { - let dataToBytes = swiftSodium.getArrayOfBytesFromData(FileData: file as NSData) + let dataToBytes = swiftSodium.getArrayOfBytesFromData(fileData: file as NSData) let defaultStorageKey = KeychainCloudManager.getDefaultStorageKey let fileKey = swiftSodium.generateDeriveKey(key: defaultStorageKey) - + let hashKey = swiftSodium.generateGenericHashWithKey(message: dataToBytes, fileKey: fileKey) let hashKeyHex = hashKey.bytesToHex(spacing: "").lowercased() - + if hashKeyHex.contains(hashFileKey) { let (header, encryptedFile) = dataToBytes.splitFile() let encryption = swiftSodium.decryptFile(secretKey: fileKey, header: header, encryptedFile: encryptedFile) guard let (file, _) = encryption else { return Data() } let decryptedFile = Data(file) return decryptedFile - } else { - - } + } return Data() - } - + func uploadFile(data: Data, fileName: String, encryptedFileKey: String, hashFileKey: String) { OTFTheraforgeNetwork.shared.uploadFile(data: data, fileName: fileName, type: .consentForm, encryptedFileKey: encryptedFileKey, hashFileKey: hashFileKey) .receive(on: DispatchQueue.main) diff --git a/OTFMagicBox/Library/UserDefaultsManager.swift b/OTFMagicBox/Library/UserDefaultsManager.swift index cf7210e9..bd9970bb 100644 --- a/OTFMagicBox/Library/UserDefaultsManager.swift +++ b/OTFMagicBox/Library/UserDefaultsManager.swift @@ -1,54 +1,53 @@ /* -Copyright (c) 2021, Hippocrates Technologies S.r.l.. All rights reserved. - -Redistribution and use in source and binary forms, with or without modification, -are permitted provided that the following conditions are met: - -1. Redistributions of source code must retain the above copyright notice, -this list of conditions and the following disclaimer. - -2. Redistributions in binary form must reproduce the above copyright notice, -this list of conditions and the following disclaimer in the documentation and/or -other materials provided with the distribution. - -3. Neither the name of the copyright holder(s) nor the names of any contributor(s) may -be used to endorse or promote products derived from this software without specific -prior written permission. No license is granted to the trademarks of the copyright -holders even if such marks are included in this software. - -4. Commercial redistribution in any form requires an explicit license agreement with the -copyright holder(s). Please contact support@hippocratestech.com for further information -regarding licensing. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND -ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. -IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, -INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT -NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, -WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) -ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY -OF SUCH DAMAGE. + Copyright (c) 2021, Hippocrates Technologies S.r.l.. All rights reserved. + + Redistribution and use in source and binary forms, with or without modification, + are permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation and/or + other materials provided with the distribution. + + 3. Neither the name of the copyright holder(s) nor the names of any contributor(s) may + be used to endorse or promote products derived from this software without specific + prior written permission. No license is granted to the trademarks of the copyright + holders even if such marks are included in this software. + + 4. Commercial redistribution in any form requires an explicit license agreement with the + copyright holder(s). Please contact support@hippocratestech.com for further information + regarding licensing. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, + INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, + OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY + OF SUCH DAMAGE. */ import Foundation -class UserDefaultsManager { +enum UserDefaultsManager { static var onboardingDidComplete: Bool { return UserDefaults.standard.bool(forKey: Constants.onboardingDidComplete) } - + static func setOnboardingCompleted(_ completed: Bool) { UserDefaults.standard.setValue(completed, forKey: Constants.onboardingDidComplete) } - + static func setIsConsentDocumentViewed(_ completed: Bool) { UserDefaults.standard.setValue(completed, forKey: Constants.isConsentDocumentViewed) } - + static var isConsentDocumentViewed: Bool { return UserDefaults.standard.bool(forKey: Constants.isConsentDocumentViewed) } - } diff --git a/OTFMagicBox/Library/Yaml/DataModel.swift b/OTFMagicBox/Library/Yaml/DataModel.swift index 3b9e3887..e05eb53a 100644 --- a/OTFMagicBox/Library/Yaml/DataModel.swift +++ b/OTFMagicBox/Library/Yaml/DataModel.swift @@ -1,35 +1,35 @@ /* -Copyright (c) 2021, Hippocrates Technologies S.r.l.. All rights reserved. + Copyright (c) 2021, Hippocrates Technologies S.r.l.. All rights reserved. -Redistribution and use in source and binary forms, with or without modification, -are permitted provided that the following conditions are met: + Redistribution and use in source and binary forms, with or without modification, + are permitted provided that the following conditions are met: -1. Redistributions of source code must retain the above copyright notice, -this list of conditions and the following disclaimer. + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. -2. Redistributions in binary form must reproduce the above copyright notice, -this list of conditions and the following disclaimer in the documentation and/or -other materials provided with the distribution. + 2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation and/or + other materials provided with the distribution. -3. Neither the name of the copyright holder(s) nor the names of any contributor(s) may -be used to endorse or promote products derived from this software without specific -prior written permission. No license is granted to the trademarks of the copyright -holders even if such marks are included in this software. + 3. Neither the name of the copyright holder(s) nor the names of any contributor(s) may + be used to endorse or promote products derived from this software without specific + prior written permission. No license is granted to the trademarks of the copyright + holders even if such marks are included in this software. -4. Commercial redistribution in any form requires an explicit license agreement with the -copyright holder(s). Please contact support@hippocratestech.com for further information -regarding licensing. + 4. Commercial redistribution in any form requires an explicit license agreement with the + copyright holder(s). Please contact support@hippocratestech.com for further information + regarding licensing. -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND -ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. -IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, -INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT -NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, -WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) -ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY -OF SUCH DAMAGE. + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, + INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, + OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY + OF SUCH DAMAGE. */ import Foundation @@ -41,7 +41,7 @@ struct Login: Codable { let failedLoginTitle: String let failedLoginText: String } - + struct Passcode: Codable { let enable: String let passcodeOnReturnText: String @@ -54,7 +54,7 @@ struct DesignConfig: Codable { let textValue: String } -struct DefaultConfig: Codable{ +struct DefaultConfig: Codable { let apiKey: String let en: DataModel let fr: DataModel @@ -67,7 +67,8 @@ struct DefaultConfig: Codable{ let showCheckupScreen: String let showStaticUIScreen: String let useCareKit: String - let appTheme: ThemeCustomization + var selectedStyle: String + var styles: [ThemeCustomization] } struct DataModel: Codable { @@ -76,7 +77,10 @@ struct DataModel: Codable { let copyright: String } -struct ThemeCustomization: Codable{ +struct ThemeCustomization: Codable { + let name: String + + // Colors let backgroundColor: String let textColor: String let separatorColor: String @@ -84,6 +88,8 @@ struct ThemeCustomization: Codable{ let buttonTextColor: String let borderColor: String let headerColor: String + + // Font let textWeight: String let textFont: String let screenTitleFont: String diff --git a/OTFMagicBox/Library/Yaml/ModuleAppYmlReader+DataModel/ModuleAppYmlReader+DataModel.swift b/OTFMagicBox/Library/Yaml/ModuleAppYmlReader+DataModel/ModuleAppYmlReader+DataModel.swift index d130d34d..f346a467 100644 --- a/OTFMagicBox/Library/Yaml/ModuleAppYmlReader+DataModel/ModuleAppYmlReader+DataModel.swift +++ b/OTFMagicBox/Library/Yaml/ModuleAppYmlReader+DataModel/ModuleAppYmlReader+DataModel.swift @@ -1,9 +1,36 @@ -// -// ModuleAppYmlReader+DataModel.swift -// OTFMagicBox -// -// Created by Arsalan Raza on 17/05/2022. -// +/* + Copyright (c) 2021, Hippocrates Technologies S.r.l.. All rights reserved. + + Redistribution and use in source and binary forms, with or without modification, + are permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation and/or + other materials provided with the distribution. + + 3. Neither the name of the copyright holder(s) nor the names of any contributor(s) may + be used to endorse or promote products derived from this software without specific + prior written permission. No license is granted to the trademarks of the copyright + holders even if such marks are included in this software. + + 4. Commercial redistribution in any form requires an explicit license agreement with the + copyright holder(s). Please contact support@hippocratestech.com for further information + regarding licensing. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, + INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, + OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY + OF SUCH DAMAGE. + */ import Foundation import UIKit @@ -12,36 +39,28 @@ import Yams import OTFUtilities public class ModuleAppYmlReader { - /// Yaml file name. private let fileName = Constants.YamlDefaults.moduleAppFileName - - var onBoardingDataModel : OnBoardingScreen? - + var onBoardingDataModel: OnBoardingScreen? init() { let fileUrlString = Bundle.main.path(forResource: fileName, ofType: nil)! let fileUrl = URL(fileURLWithPath: fileUrlString) do { - if let dataSet = try? Data(contentsOf: fileUrl) { - guard let data = try? YAMLDecoder().decode([String: OnBoardingScreen].self, from: dataSet) else { - OTFLog("Yaml decode error") - return - } - if data["DataModel"] != nil { - onBoardingDataModel = data["DataModel"] - } - } + let dataSet = try Data(contentsOf: fileUrl) + let data = try YAMLDecoder().decode(ConfigDataModel.self, from: dataSet) + onBoardingDataModel = data.dataModel + } catch { + OTFLog("Error $public{%@}", error.localizedDescription) } } - func getPreferredLocale() -> Locale { guard let preferredIdentifier = Locale.preferredLanguages.first else { return Locale.current } return Locale(identifier: preferredIdentifier) } - + var onboardingData: [Onboarding]? { switch getPreferredLocale().languageCode { case "fr": @@ -51,314 +70,314 @@ public class ModuleAppYmlReader { } } - var primaryColor: UIColor { + var registration: Registration? { switch getPreferredLocale().languageCode { case "fr": - let valueSet = (onBoardingDataModel?.fr.onboarding ?? []) - for item in valueSet{ - return item.color.color ?? UIColor.black - } + return onBoardingDataModel?.fr.registration default: - let valueSet = (onBoardingDataModel?.en.onboarding ?? []) - for item in valueSet{ - return item.color.color ?? UIColor.black - } + return onBoardingDataModel?.en.registration } - return UIColor.black } - - var reasonForConsentText: String { + var showGender: Bool { switch getPreferredLocale().languageCode { case "fr": - return onBoardingDataModel?.fr.consent.reasonForConsentText ?? Constants.YamlDefaults.TeamWebsite + guard let showGender = onBoardingDataModel?.fr.registration.showGender else { return false } + return showGender == Constants.true default: - return onBoardingDataModel?.en.consent.reasonForConsentText ?? Constants.YamlDefaults.TeamWebsite + guard let showGender = onBoardingDataModel?.en.registration.showGender else { return false } + return showGender == Constants.true } } - - var consentFileName: String { + var showDateOfBirth: Bool { switch getPreferredLocale().languageCode { case "fr": - return onBoardingDataModel?.fr.consent.fileName ?? Constants.YamlDefaults.ConsentFileName + guard let showDOB = onBoardingDataModel?.fr.registration.showDateOfBirth else { return false } + return showDOB == Constants.true default: - return onBoardingDataModel?.en.consent.fileName ?? Constants.YamlDefaults.ConsentFileName + guard let showDOB = onBoardingDataModel?.en.registration.showDateOfBirth else { return false } + return showDOB == Constants.true } } - - var consentTitle: String? { + var completionStepTitle: String? { switch getPreferredLocale().languageCode { case "fr": - return onBoardingDataModel?.fr.consent.title ?? Constants.YamlDefaults.ConsentTitle + return onBoardingDataModel?.fr.completionStep.title ?? Constants.YamlDefaults.CompletionStepTitle default: - return onBoardingDataModel?.en.consent.title ?? Constants.YamlDefaults.ConsentTitle + return onBoardingDataModel?.en.completionStep.title ?? Constants.YamlDefaults.CompletionStepTitle } } - - var consent: Consent? { + var completionStepText: String? { switch getPreferredLocale().languageCode { case "fr": - return onBoardingDataModel?.fr.consent + return onBoardingDataModel?.fr.completionStep.text ?? Constants.YamlDefaults.CompletionStepText default: - return onBoardingDataModel?.en.consent + return onBoardingDataModel?.en.completionStep.text ?? Constants.YamlDefaults.CompletionStepText } } - var registration: Registration? { + var profileData: ProfileModel? { switch getPreferredLocale().languageCode { case "fr": - return onBoardingDataModel?.fr.registration + return onBoardingDataModel?.fr.profileDataModel default: - return onBoardingDataModel?.en.registration + return onBoardingDataModel?.en.profileDataModel } } - - var showGender: Bool { + var researchKitModel: ResearchKitModel? { switch getPreferredLocale().languageCode { case "fr": - guard let showGender = onBoardingDataModel?.fr.registration.showGender else { return false } - return showGender == Constants.true + return onBoardingDataModel?.fr.researchKitView default: - guard let showGender = onBoardingDataModel?.en.registration.showGender else { return false } - return showGender == Constants.true + return onBoardingDataModel?.en.researchKitView } } - - var showDateOfBirth: Bool { + var surverysTaskModel: SurverysTask? { switch getPreferredLocale().languageCode { case "fr": - guard let showDOB = onBoardingDataModel?.fr.registration.showDateOfBirth else { return false } - return showDOB == Constants.true + return onBoardingDataModel?.fr.surverysTask default: - guard let showDOB = onBoardingDataModel?.en.registration.showDateOfBirth else { return false } - return showDOB == Constants.true + return onBoardingDataModel?.en.surverysTask } } - - var completionStepTitle: String? { + var careKitModel: CarekitModel? { switch getPreferredLocale().languageCode { case "fr": - return onBoardingDataModel?.fr.completionStep.title ?? Constants.YamlDefaults.CompletionStepTitle + return onBoardingDataModel?.fr.carekitView default: - return onBoardingDataModel?.en.completionStep.title ?? Constants.YamlDefaults.CompletionStepTitle + return onBoardingDataModel?.en.carekitView } } - - var completionStepText: String? { + var healthRecords: HealthRecords? { switch getPreferredLocale().languageCode { case "fr": - return onBoardingDataModel?.fr.completionStep.text ?? Constants.YamlDefaults.CompletionStepText + return onBoardingDataModel?.fr.healthRecords default: - return onBoardingDataModel?.en.completionStep.text ?? Constants.YamlDefaults.CompletionStepText + return onBoardingDataModel?.en.healthRecords } } - - var isPasscodeEnabled: Bool { + var healthPermissionsTitle: String? { switch getPreferredLocale().languageCode { case "fr": - guard let passcode = onBoardingDataModel?.fr.passcode.enable else { return true } - return passcode != Constants.false + return onBoardingDataModel?.fr.healthKitData.healthPermissionsTitle ?? Constants.YamlDefaults.HealthPermissionsTitle default: - guard let passcode = onBoardingDataModel?.en.passcode.enable else { return true } - return passcode != Constants.false + return onBoardingDataModel?.en.healthKitData.healthPermissionsTitle ?? Constants.YamlDefaults.HealthPermissionsTitle } } - - var failedLoginText: String? { + var healthPermissionsText: String? { switch getPreferredLocale().languageCode { case "fr": - return onBoardingDataModel?.fr.login.failedLoginText ?? Constants.YamlDefaults.FailedLoginText + return onBoardingDataModel?.fr.healthKitData.healthPermissionsText ?? Constants.YamlDefaults.HealthPermissionsText default: - return onBoardingDataModel?.en.login.failedLoginText ?? Constants.YamlDefaults.FailedLoginText + return onBoardingDataModel?.en.healthKitData.healthPermissionsText ?? Constants.YamlDefaults.HealthPermissionsText } } - - var failedLoginTitle: String? { + var backgroundReadFrequency: String? { switch getPreferredLocale().languageCode { case "fr": - return onBoardingDataModel?.fr.login.failedLoginTitle ?? Constants.YamlDefaults.FailedLoginTitle + return onBoardingDataModel?.fr.healthKitData.backgroundReadFrequency ?? "immediate" default: - return onBoardingDataModel?.en.login.failedLoginTitle ?? Constants.YamlDefaults.FailedLoginTitle + return onBoardingDataModel?.en.healthKitData.backgroundReadFrequency ?? "immediate" } } - - - var passcodeOnReturnText: String { + var healthKitDataToRead: [HealthKitTypes] { switch getPreferredLocale().languageCode { case "fr": - return onBoardingDataModel?.fr.passcode.passcodeOnReturnText ?? Constants.YamlDefaults.PasscodeOnReturnText + return onBoardingDataModel?.fr.healthKitData.healthKitTypes ?? [HealthKitTypes(type: "stepCount"), HealthKitTypes(type: "distanceSwimming")] default: - return onBoardingDataModel?.en.passcode.passcodeOnReturnText ?? Constants.YamlDefaults.PasscodeOnReturnText + return onBoardingDataModel?.en.healthKitData.healthKitTypes ?? [HealthKitTypes(type: "stepCount"), HealthKitTypes(type: "distanceSwimming")] } } - - var passcodeType: String { + var withdrawl: Withdrawal? { switch getPreferredLocale().languageCode { case "fr": - return onBoardingDataModel?.fr.passcode.passcodeType ?? Constants.Passcode.lengthFour + return onBoardingDataModel?.fr.withdrawal default: - return onBoardingDataModel?.en.passcode.passcodeType ?? Constants.Passcode.lengthFour + return onBoardingDataModel?.en.withdrawal } } - - - var loginPasswordless: Bool { +} + +// MARK: - Consent +extension ModuleAppYmlReader { + var reasonForConsentText: String { switch getPreferredLocale().languageCode { case "fr": - guard let passwordless = onBoardingDataModel?.fr.login.loginPasswordless else { return false } - return passwordless == Constants.true + return onBoardingDataModel?.fr.consent.reasonForConsentText ?? Constants.YamlDefaults.TeamWebsite default: - guard let passwordless = onBoardingDataModel?.en.login.loginPasswordless else { return false } - return passwordless == Constants.true + return onBoardingDataModel?.en.consent.reasonForConsentText ?? Constants.YamlDefaults.TeamWebsite } } - var loginStepTitle: String { + var consentFileName: String { switch getPreferredLocale().languageCode { case "fr": - return onBoardingDataModel?.fr.login.loginStepTitle ?? Constants.YamlDefaults.LoginStepTitle + return onBoardingDataModel?.fr.consent.fileName ?? Constants.YamlDefaults.ConsentFileName default: - return onBoardingDataModel?.en.login.loginStepTitle ?? Constants.YamlDefaults.LoginStepTitle + return onBoardingDataModel?.en.consent.fileName ?? Constants.YamlDefaults.ConsentFileName } } - var loginStepText: String { + var consentTitle: String? { switch getPreferredLocale().languageCode { case "fr": - return onBoardingDataModel?.fr.login.loginStepText ?? Constants.YamlDefaults.LoginStepText + return onBoardingDataModel?.fr.consent.title ?? Constants.YamlDefaults.ConsentTitle default: - return onBoardingDataModel?.en.login.loginStepText ?? Constants.YamlDefaults.LoginStepText + return onBoardingDataModel?.en.consent.title ?? Constants.YamlDefaults.ConsentTitle } } - var passcodeText: String { + var consent: Consent? { switch getPreferredLocale().languageCode { case "fr": - return onBoardingDataModel?.fr.passcode.passcodeText ?? Constants.YamlDefaults.PasscodeText + return onBoardingDataModel?.fr.consent default: - return onBoardingDataModel?.en.passcode.passcodeText ?? Constants.YamlDefaults.PasscodeText + return onBoardingDataModel?.en.consent } } - var loginOptionsText: String { +} + +// MARK: - Theme +extension ModuleAppYmlReader { + var primaryColor: UIColor { switch getPreferredLocale().languageCode { case "fr": - return onBoardingDataModel?.fr.loginOptionsInfo.text ?? Constants.YamlDefaults.LoginOptionsText + let valueSet = (onBoardingDataModel?.fr.onboarding ?? []) + for item in valueSet { + return item.color.color ?? UIColor.black + } default: - return onBoardingDataModel?.en.loginOptionsInfo.text ?? Constants.YamlDefaults.LoginOptionsText + let valueSet = (onBoardingDataModel?.en.onboarding ?? []) + for item in valueSet { + return item.color.color ?? UIColor.black + } } + return UIColor.black } - var loginOptionsIcon: String { - switch getPreferredLocale().languageCode { + var backgroundColor: UIColor { + guard let langStr = Locale.current.languageCode else { fatalError("language not found") } + + switch langStr { case "fr": - return onBoardingDataModel?.fr.loginOptionsInfo.icon ?? Constants.YamlDefaults.LoginOptionsIcon + return onBoardingDataModel?.fr.profileDataModel.backgroundColor.color ?? UIColor.black default: - return onBoardingDataModel?.en.loginOptionsInfo.icon ?? Constants.YamlDefaults.LoginOptionsIcon + return onBoardingDataModel?.en.profileDataModel.backgroundColor.color ?? UIColor.black } } - - var profileData: ProfileModel? { +} + +// MARK: - Login +extension ModuleAppYmlReader { + var failedLoginText: String? { switch getPreferredLocale().languageCode { case "fr": - return onBoardingDataModel?.fr.profileDataModel + return onBoardingDataModel?.fr.login.failedLoginText ?? Constants.YamlDefaults.FailedLoginText default: - return onBoardingDataModel?.en.profileDataModel + return onBoardingDataModel?.en.login.failedLoginText ?? Constants.YamlDefaults.FailedLoginText } } - var backgroundColor: UIColor { - guard let langStr = Locale.current.languageCode else { fatalError("language not found") } - - switch langStr { + var failedLoginTitle: String? { + switch getPreferredLocale().languageCode { case "fr": - return onBoardingDataModel?.fr.profileDataModel.backgroundColor.color ?? UIColor.black + return onBoardingDataModel?.fr.login.failedLoginTitle ?? Constants.YamlDefaults.FailedLoginTitle default: - return onBoardingDataModel?.en.profileDataModel.backgroundColor.color ?? UIColor.black + return onBoardingDataModel?.en.login.failedLoginTitle ?? Constants.YamlDefaults.FailedLoginTitle } } - var researchKitModel: ResearchKitModel? { + var loginStepTitle: String { switch getPreferredLocale().languageCode { case "fr": - return onBoardingDataModel?.fr.researchKitView + return onBoardingDataModel?.fr.login.loginStepTitle ?? Constants.YamlDefaults.LoginStepTitle default: - return onBoardingDataModel?.en.researchKitView + return onBoardingDataModel?.en.login.loginStepTitle ?? Constants.YamlDefaults.LoginStepTitle } } - var surverysTaskModel: SurverysTask? { + var loginOptionsText: String { switch getPreferredLocale().languageCode { case "fr": - return onBoardingDataModel?.fr.surverysTask + return onBoardingDataModel?.fr.loginOptionsInfo.text ?? Constants.YamlDefaults.LoginOptionsText default: - return onBoardingDataModel?.en.surverysTask + return onBoardingDataModel?.en.loginOptionsInfo.text ?? Constants.YamlDefaults.LoginOptionsText } } - var careKitModel: CarekitModel? { + var loginOptionsIcon: String { switch getPreferredLocale().languageCode { case "fr": - return onBoardingDataModel?.fr.carekitView + return onBoardingDataModel?.fr.loginOptionsInfo.icon ?? Constants.YamlDefaults.LoginOptionsIcon default: - return onBoardingDataModel?.en.carekitView + return onBoardingDataModel?.en.loginOptionsInfo.icon ?? Constants.YamlDefaults.LoginOptionsIcon } } - var healthRecords: HealthRecords? { + var loginStepText: String { switch getPreferredLocale().languageCode { case "fr": - return onBoardingDataModel?.fr.healthRecords + return onBoardingDataModel?.fr.login.loginStepText ?? Constants.YamlDefaults.LoginStepText default: - return onBoardingDataModel?.en.healthRecords + return onBoardingDataModel?.en.login.loginStepText ?? Constants.YamlDefaults.LoginStepText } } - - var healthPermissionsTitle: String? { +} + +// MARK: - Passcode +extension ModuleAppYmlReader { + var isPasscodeEnabled: Bool { switch getPreferredLocale().languageCode { case "fr": - return onBoardingDataModel?.fr.healthKitData.healthPermissionsTitle ?? Constants.YamlDefaults.HealthPermissionsTitle + guard let passcode = onBoardingDataModel?.fr.passcode.enable else { + return true + } + return passcode != Constants.false default: - return onBoardingDataModel?.en.healthKitData.healthPermissionsTitle ?? Constants.YamlDefaults.HealthPermissionsTitle + guard let passcode = onBoardingDataModel?.en.passcode.enable else { + return true + } + return passcode != Constants.false } } - var healthPermissionsText: String? { + var passcodeOnReturnText: String { switch getPreferredLocale().languageCode { case "fr": - return onBoardingDataModel?.fr.healthKitData.healthPermissionsText ?? Constants.YamlDefaults.HealthPermissionsText + return onBoardingDataModel?.fr.passcode.passcodeOnReturnText ?? Constants.YamlDefaults.PasscodeOnReturnText default: - return onBoardingDataModel?.en.healthKitData.healthPermissionsText ?? Constants.YamlDefaults.HealthPermissionsText + return onBoardingDataModel?.en.passcode.passcodeOnReturnText ?? Constants.YamlDefaults.PasscodeOnReturnText } } - - - var backgroundReadFrequency: String? { + + var passcodeType: String { switch getPreferredLocale().languageCode { case "fr": - return onBoardingDataModel?.fr.healthKitData.backgroundReadFrequency ?? "immediate" + return onBoardingDataModel?.fr.passcode.passcodeType ?? Constants.Passcode.lengthFour default: - return onBoardingDataModel?.en.healthKitData.backgroundReadFrequency ?? "immediate" + return onBoardingDataModel?.en.passcode.passcodeType ?? Constants.Passcode.lengthFour } } - var healthKitDataToRead: [HealthKitTypes] { + var loginPasswordless: Bool { switch getPreferredLocale().languageCode { case "fr": - return onBoardingDataModel?.fr.healthKitData.healthKitTypes ?? [HealthKitTypes(type: "stepCount"), HealthKitTypes(type: "distanceSwimming")] + guard let passwordless = onBoardingDataModel?.fr.login.loginPasswordless else { return false } + return passwordless == Constants.true default: - return onBoardingDataModel?.en.healthKitData.healthKitTypes ?? [HealthKitTypes(type: "stepCount"), HealthKitTypes(type: "distanceSwimming")] + guard let passwordless = onBoardingDataModel?.en.login.loginPasswordless else { return false } + return passwordless == Constants.true } } - var withdrawl: Withdrawal? { + var passcodeText: String { switch getPreferredLocale().languageCode { case "fr": - return onBoardingDataModel?.fr.withdrawal + return onBoardingDataModel?.fr.passcode.passcodeText ?? Constants.YamlDefaults.PasscodeText default: - return onBoardingDataModel?.en.withdrawal + return onBoardingDataModel?.en.passcode.passcodeText ?? Constants.YamlDefaults.PasscodeText } } - } struct Consent: Codable { @@ -395,7 +414,15 @@ struct CompletionStep: Codable { let text: String } -struct OnBoardingScreen: Codable{ +struct ConfigDataModel: Codable { + let dataModel: OnBoardingScreen + + enum CodingKeys: String, CodingKey { + case dataModel = "DataModel" + } +} + +struct OnBoardingScreen: Codable { let en: OnBoardingDataModel let fr: OnBoardingDataModel } @@ -405,7 +432,7 @@ struct LoginOptionsInfo: Codable { let icon: String } -struct OnBoardingDataModel: Codable{ +struct OnBoardingDataModel: Codable { let onboarding: [Onboarding] let consent: Consent let registration: Registration @@ -442,11 +469,11 @@ struct HealthKitData: Codable { let healthKitTypes: [HealthKitTypes] } -struct HealthKitTypes: Codable, Equatable { +struct HealthKitTypes: Codable, Equatable { let type: String } -struct ProfileModel: Codable{ +struct ProfileModel: Codable { let title: String let profileImage: String let help: String @@ -454,7 +481,7 @@ struct ProfileModel: Codable{ let reportProblemText: String let supportText: String let consentText: String - let WithdrawStudyText: String + let withdrawStudyText: String let profileInfoHeader: String let firstName: String let lastName: String @@ -466,7 +493,7 @@ struct ProfileModel: Codable{ let backgroundColor: String } -struct ResearchKitModel: Codable{ +struct ResearchKitModel: Codable { let surveysHeaderTitle: String let formSurveyExample: String let groupedFormSurveyExample: String @@ -529,11 +556,9 @@ struct ResearchKitModel: Codable{ let miscellaneousHeaderTitle: String let webView: String let researchKit: String - } - -struct SurverysTask: Codable{ +struct SurverysTask: Codable { let title: String let additionalText: String let itemQuestion: String @@ -553,10 +578,10 @@ struct SurverysTask: Codable{ let learnMoreTitle: String let learnMoreText: String let birthdayText: String - + } -struct CarekitModel: Codable{ +struct CarekitModel: Codable { let simple: String let instruction: String let buttonLog: String @@ -569,4 +594,3 @@ struct CarekitModel: Codable{ let detailed: String let careKit: String } - diff --git a/OTFMagicBox/Library/Yaml/YmlReader.swift b/OTFMagicBox/Library/Yaml/YmlReader.swift index be198765..681772ee 100644 --- a/OTFMagicBox/Library/Yaml/YmlReader.swift +++ b/OTFMagicBox/Library/Yaml/YmlReader.swift @@ -1,25 +1,25 @@ /* Copyright (c) 2021, Hippocrates Technologies S.r.l.. All rights reserved. - + Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: - + 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. - + 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. - + 3. Neither the name of the copyright holder(s) nor the names of any contributor(s) may be used to endorse or promote products derived from this software without specific prior written permission. No license is granted to the trademarks of the copyright holders even if such marks are included in this software. - + 4. Commercial redistribution in any form requires an explicit license agreement with the copyright holder(s). Please contact support@hippocratestech.com for further information regarding licensing. - + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. @@ -42,12 +42,12 @@ import OTFUtilities YmlReader decodes the Yaml values from the given file. */ public class YmlReader { - + /// Yaml file name. private let fileName = Constants.YamlDefaults.FileName - - var dataModel : DefaultConfig? - + + var dataModel: DefaultConfig? + init() { let fileUrlString = Bundle.main.path(forResource: fileName, ofType: nil)! let fileUrl = URL(fileURLWithPath: fileUrlString) @@ -63,45 +63,35 @@ public class YmlReader { } } } - func getPreferredLocale() -> Locale { guard let preferredIdentifier = Locale.preferredLanguages.first else { return Locale.current } return Locale(identifier: preferredIdentifier) } - + // Returns primary color. var primaryColor: UIColor { - let valueSet = (dataModel?.designConfig ?? []) - - for value in valueSet where value.name == "label" { - return value.textValue.color ?? UIColor.black + guard let labelColor = appStyle.textColor.color else { + return .label } - return .black + return labelColor } - - // Returns tint color. var tintColor: UIColor { - let valueSet = (dataModel?.designConfig ?? []) - - for value in valueSet where value.name == "tintColor" { - return value.textValue.color ?? UIColor.black + guard let buttonTextColor = appStyle.buttonTextColor.color else { + return .black } - return .black + return buttonTextColor } - var apiKey: String { guard let apiKey = dataModel?.apiKey else { return Constants.YamlDefaults.APIKey } return apiKey } - - var appTitle: String { - switch getPreferredLocale().languageCode { + switch getPreferredLocale().languageCode { case "fr": if let title = dataModel?.fr.appTitle { return title @@ -113,7 +103,6 @@ public class YmlReader { } return Constants.YamlDefaults.TeamName } - var teamName: String { switch getPreferredLocale().languageCode { case "fr": @@ -122,17 +111,17 @@ public class YmlReader { return dataModel?.en.teamName ?? Constants.YamlDefaults.TeamName } } - + var teamEmail: String { return dataModel?.teamEmail ?? Constants.YamlDefaults.TeamEmail } - + var teamPhone: String { return dataModel?.teamPhone ?? Constants.YamlDefaults.TeamPhone } - + var teamCopyright: String { - + switch getPreferredLocale().languageCode { case "fr": return dataModel?.fr.copyright ?? Constants.YamlDefaults.TeamCopyright @@ -140,49 +129,49 @@ public class YmlReader { return dataModel?.en.copyright ?? Constants.YamlDefaults.TeamCopyright } } - + var teamWebsite: String { return dataModel?.teamWebsite ?? Constants.YamlDefaults.TeamWebsite } - + var showAppleLogin: Bool { guard let showSocialLogin = dataModel?.showAppleSignin else { return false } return showSocialLogin == Constants.true } - + var showGoogleLogin: Bool { guard let showSocialLogin = dataModel?.showGoogleSignin else { return false } return showSocialLogin == Constants.true } - -// var healthPermissionsTitle: String? { -// switch getPreferredLocale().languageCode { -// case "fr": -// return dataModel?.fr.healthKitData.healthPermissionsTitle ?? Constants.YamlDefaults.HealthPermissionsTitle -// default: -// return dataModel?.en.healthKitData.healthPermissionsTitle ?? Constants.YamlDefaults.HealthPermissionsTitle -// } -// } -// -// var healthPermissionsText: String? { -// switch getPreferredLocale().languageCode { -// case "fr": -// return dataModel?.fr.healthKitData.healthPermissionsText ?? Constants.YamlDefaults.HealthPermissionsText -// default: -// return dataModel?.en.healthKitData.healthPermissionsText ?? Constants.YamlDefaults.HealthPermissionsText -// } -// } - + + // var healthPermissionsTitle: String? { + // switch getPreferredLocale().languageCode { + // case "fr": + // return dataModel?.fr.healthKitData.healthPermissionsTitle ?? Constants.YamlDefaults.HealthPermissionsTitle + // default: + // return dataModel?.en.healthKitData.healthPermissionsTitle ?? Constants.YamlDefaults.HealthPermissionsTitle + // } + // } + // + // var healthPermissionsText: String? { + // switch getPreferredLocale().languageCode { + // case "fr": + // return dataModel?.fr.healthKitData.healthPermissionsText ?? Constants.YamlDefaults.HealthPermissionsText + // default: + // return dataModel?.en.healthKitData.healthPermissionsText ?? Constants.YamlDefaults.HealthPermissionsText + // } + // } + var useCareKit: Bool { guard let useCareKit = dataModel?.useCareKit else { return false } return useCareKit == Constants.true } - + var showCheckupScreen: Bool { guard let showCheckupScreen = dataModel?.showCheckupScreen else { return false } return showCheckupScreen == Constants.true } - + var showStaticUIScreen: Bool { guard let showStaticUIScreen = dataModel?.showStaticUIScreen else { return false } return showStaticUIScreen == Constants.true @@ -206,10 +195,6 @@ public class YmlReader { // } // } - var appTheme: ThemeCustomization? { - return dataModel?.appTheme - } - // var withdrawl: Withdrawal? { // switch getPreferredLocale().languageCode { // case "fr": @@ -228,3 +213,33 @@ public class YmlReader { // } // } } + +extension YmlReader { + internal var defaultStyle: ThemeCustomization { + return ThemeCustomization( + name: "defaultStyle", + backgroundColor: "systemBackground", + textColor: "label", + separatorColor: "separator", + cellbackgroundColor: "secondarySystemGroupedBackground", + buttonTextColor: "Teal", + borderColor: "Black", + headerColor: "label", + textWeight: "", + textFont: "Inherited", + screenTitleFont: "Header", + screenTitleWeight: "", + headerTitleFont: "HeaderInherited", + headerTitleWeight: "Bold", + appTitleSize: "Large Title" + ) + } + + var appStyle: ThemeCustomization { + guard let styleName = dataModel?.selectedStyle, + let style = dataModel?.styles.first(where: { $0.name == styleName }) else { + return defaultStyle + } + return style + } +} diff --git a/OTFMagicBox/Login/LoginCustomWaitStep.swift b/OTFMagicBox/Login/LoginCustomWaitStep.swift index 08a68879..3cb28d9e 100644 --- a/OTFMagicBox/Login/LoginCustomWaitStep.swift +++ b/OTFMagicBox/Login/LoginCustomWaitStep.swift @@ -1,50 +1,50 @@ /* -Copyright (c) 2021, Hippocrates Technologies S.r.l.. All rights reserved. - -Redistribution and use in source and binary forms, with or without modification, -are permitted provided that the following conditions are met: - -1. Redistributions of source code must retain the above copyright notice, -this list of conditions and the following disclaimer. - -2. Redistributions in binary form must reproduce the above copyright notice, -this list of conditions and the following disclaimer in the documentation and/or -other materials provided with the distribution. - -3. Neither the name of the copyright holder(s) nor the names of any contributor(s) may -be used to endorse or promote products derived from this software without specific -prior written permission. No license is granted to the trademarks of the copyright -holders even if such marks are included in this software. - -4. Commercial redistribution in any form requires an explicit license agreement with the -copyright holder(s). Please contact support@hippocratestech.com for further information -regarding licensing. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND -ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. -IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, -INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT -NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, -WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) -ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY -OF SUCH DAMAGE. + Copyright (c) 2021, Hippocrates Technologies S.r.l.. All rights reserved. + + Redistribution and use in source and binary forms, with or without modification, + are permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation and/or + other materials provided with the distribution. + + 3. Neither the name of the copyright holder(s) nor the names of any contributor(s) may + be used to endorse or promote products derived from this software without specific + prior written permission. No license is granted to the trademarks of the copyright + holders even if such marks are included in this software. + + 4. Commercial redistribution in any form requires an explicit license agreement with the + copyright holder(s). Please contact support@hippocratestech.com for further information + regarding licensing. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, + INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, + OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY + OF SUCH DAMAGE. */ import Foundation import OTFResearchKit class LoginCustomWaitStep: ORKStep { - + static let identifier = "LoginCustomWaitStep" - + override init(identifier: String) { super.init(identifier: identifier) } - + required init(coder aDecoder: NSCoder) { super.init(coder: aDecoder) } - + } diff --git a/OTFMagicBox/Login/LoginExistingUserViewController.swift b/OTFMagicBox/Login/LoginExistingUserViewController.swift index 57fd07ad..8ab78484 100644 --- a/OTFMagicBox/Login/LoginExistingUserViewController.swift +++ b/OTFMagicBox/Login/LoginExistingUserViewController.swift @@ -1,35 +1,35 @@ /* -Copyright (c) 2021, Hippocrates Technologies S.r.l.. All rights reserved. - -Redistribution and use in source and binary forms, with or without modification, -are permitted provided that the following conditions are met: - -1. Redistributions of source code must retain the above copyright notice, -this list of conditions and the following disclaimer. - -2. Redistributions in binary form must reproduce the above copyright notice, -this list of conditions and the following disclaimer in the documentation and/or -other materials provided with the distribution. - -3. Neither the name of the copyright holder(s) nor the names of any contributor(s) may -be used to endorse or promote products derived from this software without specific -prior written permission. No license is granted to the trademarks of the copyright -holders even if such marks are included in this software. - -4. Commercial redistribution in any form requires an explicit license agreement with the -copyright holder(s). Please contact support@hippocratestech.com for further information -regarding licensing. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND -ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. -IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, -INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT -NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, -WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) -ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY -OF SUCH DAMAGE. + Copyright (c) 2021, Hippocrates Technologies S.r.l.. All rights reserved. + + Redistribution and use in source and binary forms, with or without modification, + are permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation and/or + other materials provided with the distribution. + + 3. Neither the name of the copyright holder(s) nor the names of any contributor(s) may + be used to endorse or promote products derived from this software without specific + prior written permission. No license is granted to the trademarks of the copyright + holders even if such marks are included in this software. + + 4. Commercial redistribution in any form requires an explicit license agreement with the + copyright holder(s). Please contact support@hippocratestech.com for further information + regarding licensing. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, + INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, + OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY + OF SUCH DAMAGE. */ import Foundation @@ -39,22 +39,22 @@ import OTFResearchKit enum AuthMethod: String, CaseIterable, Codable { case email, apple - + var signinTitle: String { switch self { case .email: return Constants.CustomiseStrings.signinWithEmail - + case .apple: return Constants.CustomiseStrings.signinWithApple } } - + var signupTitle: String { switch self { case .email: return Constants.CustomiseStrings.signUpWithEmail - + case .apple: return Constants.CustomiseStrings.signUpWithApple } @@ -62,40 +62,38 @@ enum AuthMethod: String, CaseIterable, Codable { } struct LoginExistingUserViewController: UIViewControllerRepresentable { - + func makeCoordinator() -> OnboardingTaskCoordinator { OnboardingTaskCoordinator(authType: .login) } - + typealias UIViewControllerType = ORKTaskViewController - + func updateUIViewController(_ taskViewController: ORKTaskViewController, context: Context) {} - + func makeUIViewController(context: Context) -> ORKTaskViewController { - + var loginSteps: [ORKStep] let signInButtons = OnboardingOptionsStep(identifier: "SignInButtons") let loginUserPassword = ORKLoginStep(identifier: "LoginExistingStep", title: Constants.CustomiseStrings.login, text: Constants.Login.Text, loginViewControllerClass: LoginViewController.self) loginSteps = [signInButtons, loginUserPassword] - - //add consent if user dont have consent in cloud + + // add consent if user dont have consent in cloud let config = ModuleAppYmlReader() let consentDocument = ConsentDocument() /* ************************************************************** - **************************************************************/ + **************************************************************/ // use the `ORKConsentReviewStep` from ResearchKit let signature = consentDocument.signatures?.first let reviewConsentStep = ORKConsentReviewStep(identifier: "ConsentReviewStep", signature: signature, in: consentDocument) reviewConsentStep.text = YmlReader().teamWebsite reviewConsentStep.reasonForConsent = config.reasonForConsentText - + // create a task with each step if !UserDefaultsManager.isConsentDocumentViewed { UserDefaultsManager.setIsConsentDocumentViewed(true) loginSteps += [reviewConsentStep] } - - // use the `ORKPasscodeStep` from ResearchKit. if config.isPasscodeEnabled { let passcodeStep = ORKPasscodeStep(identifier: "Passcode") @@ -107,16 +105,16 @@ struct LoginExistingUserViewController: UIViewControllerRepresentable { } else { passcodeStep.passcodeType = .type4Digit } - + loginSteps += [passcodeStep] } - + // set completion step let completionStep = ORKCompletionStep(identifier: Constants.Identifier.CompletionStep) completionStep.title = ModuleAppYmlReader().completionStepTitle completionStep.text = ModuleAppYmlReader().completionStepText loginSteps += [completionStep] - + let navigableTask = ORKNavigableOrderedTask(identifier: "StudyLoginTask", steps: loginSteps) let resultSelector = ORKResultSelector(resultIdentifier: "SignInButtons") let booleanAnswerType = ORKResultPredicate.predicateForBooleanQuestionResult(with: resultSelector, expectedAnswer: true) @@ -125,24 +123,23 @@ struct LoginExistingUserViewController: UIViewControllerRepresentable { defaultStepIdentifier: "ConsentReviewStep", validateArrays: true) navigableTask.setNavigationRule(predicateRule, forTriggerStepIdentifier: "SignInButtons") - + // ADD New navigation Rule (if has or not consentDocument) // Consent Rule let resultConsent = ORKResultSelector(resultIdentifier: "ConsentReview") let booleanAnswerConsent = ORKResultPredicate.predicateForBooleanQuestionResult(with: resultConsent, expectedAnswer: true) let predicateRuleConsent = ORKPredicateStepNavigationRule(resultPredicates: [booleanAnswerConsent], - destinationStepIdentifiers: ["HealthKit"], - defaultStepIdentifier: "ConsentReviewStep", - validateArrays: true) + destinationStepIdentifiers: ["HealthKit"], + defaultStepIdentifier: "ConsentReviewStep", + validateArrays: true) navigableTask.setNavigationRule(predicateRuleConsent, forTriggerStepIdentifier: "ConsentReview") - + // wrap that task on a view controller let taskViewController = ORKTaskViewController(task: navigableTask, taskRun: nil) taskViewController.delegate = context.coordinator // enables `ORKTaskViewControllerDelegate` below - + // & present the VC! return taskViewController } - -} +} diff --git a/OTFMagicBox/Login/LoginSteps.swift b/OTFMagicBox/Login/LoginSteps.swift index b2b3db6b..082fd647 100644 --- a/OTFMagicBox/Login/LoginSteps.swift +++ b/OTFMagicBox/Login/LoginSteps.swift @@ -1,25 +1,25 @@ /* Copyright (c) 2021, Hippocrates Technologies S.r.l.. All rights reserved. - + Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: - + 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. - + 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. - + 3. Neither the name of the copyright holder(s) nor the names of any contributor(s) may be used to endorse or promote products derived from this software without specific prior written permission. No license is granted to the trademarks of the copyright holders even if such marks are included in this software. - + 4. Commercial redistribution in any form requires an explicit license agreement with the copyright holder(s). Please contact support@hippocratestech.com for further information regarding licensing. - + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. @@ -41,63 +41,63 @@ protocol LoginSteps { struct EmailLoginSteps: LoginSteps { let steps: [ORKStep] - + public init() { var loginSteps: [ORKStep] - + let loginStep = ORKLoginStep(identifier: Constants.Login.Identifier, title: Constants.Login.Title, text: Constants.Login.Text, loginViewControllerClass: LoginViewController.self) - + loginSteps = [loginStep] - + // use the `ORKPasscodeStep` from ResearchKit. if ModuleAppYmlReader().isPasscodeEnabled { let passcodeStep = ORKPasscodeStep(identifier: "Passcode") - + let type = ModuleAppYmlReader().passcodeType - + if type == Constants.Passcode.lengthSix { passcodeStep.passcodeType = .type6Digit } else { passcodeStep.passcodeType = .type4Digit } - + passcodeStep.text = Constants.CustomiseStrings.enterPasscode - + loginSteps += [passcodeStep] } - + self.steps = loginSteps } } struct AppleLoginSteps: LoginSteps { let steps: [ORKStep] - + public init() { var loginSteps: [ORKStep] - + let appleLoginStep = SignInWithAppleStep(identifier: "SignInWithApple") - + loginSteps = [appleLoginStep] - + // use the `ORKPasscodeStep` from ResearchKit. if ModuleAppYmlReader().isPasscodeEnabled { let passcodeStep = ORKPasscodeStep(identifier: "Passcode") - + let type = ModuleAppYmlReader().passcodeType - + if type == Constants.Passcode.lengthSix { passcodeStep.passcodeType = .type6Digit } else { passcodeStep.passcodeType = .type4Digit } - + passcodeStep.text = Constants.CustomiseStrings.enterPasscode - + loginSteps += [passcodeStep] } - + self.steps = loginSteps } } diff --git a/OTFMagicBox/Login/LoginViewController.swift b/OTFMagicBox/Login/LoginViewController.swift index da2494cf..efa54b2a 100644 --- a/OTFMagicBox/Login/LoginViewController.swift +++ b/OTFMagicBox/Login/LoginViewController.swift @@ -1,50 +1,51 @@ /* -Copyright (c) 2021, Hippocrates Technologies S.r.l.. All rights reserved. - -Redistribution and use in source and binary forms, with or without modification, -are permitted provided that the following conditions are met: - -1. Redistributions of source code must retain the above copyright notice, -this list of conditions and the following disclaimer. - -2. Redistributions in binary form must reproduce the above copyright notice, -this list of conditions and the following disclaimer in the documentation and/or -other materials provided with the distribution. - -3. Neither the name of the copyright holder(s) nor the names of any contributor(s) may -be used to endorse or promote products derived from this software without specific -prior written permission. No license is granted to the trademarks of the copyright -holders even if such marks are included in this software. - -4. Commercial redistribution in any form requires an explicit license agreement with the -copyright holder(s). Please contact support@hippocratestech.com for further information -regarding licensing. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND -ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. -IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, -INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT -NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, -WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) -ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY -OF SUCH DAMAGE. + Copyright (c) 2021, Hippocrates Technologies S.r.l.. All rights reserved. + + Redistribution and use in source and binary forms, with or without modification, + are permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation and/or + other materials provided with the distribution. + + 3. Neither the name of the copyright holder(s) nor the names of any contributor(s) may + be used to endorse or promote products derived from this software without specific + prior written permission. No license is granted to the trademarks of the copyright + holders even if such marks are included in this software. + + 4. Commercial redistribution in any form requires an explicit license agreement with the + copyright holder(s). Please contact support@hippocratestech.com for further information + regarding licensing. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, + INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, + OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY + OF SUCH DAMAGE. */ import Foundation import OTFResearchKit import OTFUtilities import Combine +import WatchConnectivity /** The LoginViewController provides the default login view from ResearchKit. */ class LoginViewController: ORKLoginStepViewController { - + var subscriptions = Set() var disposables: AnyCancellable? - + lazy var authButton: UIButton = { let button = UIButton() button.translatesAutoresizingMaskIntoConstraints = false @@ -58,30 +59,30 @@ class LoginViewController: ORKLoginStepViewController { } return button }() - + override func viewDidLoad() { super.viewDidLoad() authButton.addTarget(self, action: #selector(customButtonTapped), for: .touchUpInside) view.addSubview(authButton) addAuthButtonConstraints() - NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillShow(sender:)), name: UIResponder.keyboardWillShowNotification, object: nil); - - NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillHide(sender:)), name: UIResponder.keyboardWillHideNotification, object: nil); + NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillShow(sender:)), name: UIResponder.keyboardWillShowNotification, object: nil) + + NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillHide(sender:)), name: UIResponder.keyboardWillHideNotification, object: nil) } - + @objc func keyboardWillShow(sender: NSNotification) { authButton.removeFromSuperview() } - + @objc func keyboardWillHide(sender: NSNotification) { view.addSubview(authButton) addAuthButtonConstraints() } - + func getSubviewsOfView(view: UIView) -> [T] { var subviewArray = [T]() - if view.subviews.count == 0 { + if view.subviews.isEmpty { return subviewArray } for subview in view.subviews { @@ -92,7 +93,7 @@ class LoginViewController: ORKLoginStepViewController { } return subviewArray } - + @objc func customButtonTapped() { LocalAuthentication.shared.authenticationWithTouchID { success in if success { @@ -108,13 +109,13 @@ class LoginViewController: ORKLoginStepViewController { item.text = emailFromKeychain } } - + let button: [UIButton] = self.getSubviewsOfView(view: self.view) - for item in button { - if item.currentTitle == "Login" { + for item in button where item.currentTitle == "Login" { +// if item.currentTitle == "Login" { item.isEnabled = true item.isUserInteractionEnabled = true - } +// } } } } else { @@ -129,9 +130,9 @@ class LoginViewController: ORKLoginStepViewController { } } } - + override func goForward() { - + var emailAddress = String() var password = String() let textFields: [UITextField] = self.getSubviewsOfView(view: self.view) @@ -142,20 +143,20 @@ class LoginViewController: ORKLoginStepViewController { emailAddress = item.text ?? "" } } - + loginRequest(email: emailAddress, password: password) } - + func loginRequest(email: String, password: String) { - + let alert = UIAlertController(title: nil, message: Constants.CustomiseStrings.loginingIn, preferredStyle: .alert) - + let loadingIndicator = UIActivityIndicatorView(frame: CGRect(x: 10, y: 5, width: 50, height: 50)) loadingIndicator.hidesWhenStopped = true loadingIndicator.style = UIActivityIndicatorView.Style.medium loadingIndicator.startAnimating() alert.view.addSubview(loadingIndicator) - + taskViewController?.present(alert, animated: true, completion: nil) disposables = OTFTheraforgeNetwork.shared.loginRequest(email: email, password: password) .receive(on: DispatchQueue.main) @@ -166,59 +167,71 @@ class LoginViewController: ORKLoginStepViewController { alert.dismiss(animated: true) { self.showAlert(title: Constants.CustomiseStrings.loginInError, message: error.error.message ) } - default: break; + default: break } } receiveValue: { result in - self.saveValuesToLocal(email: email, password: password, encryptedDefaultStorageKeyHex: result.data.encryptedDefaultStorageKey, encryptedconfidentialStorageKeyHex: result.data.encryptedConfidentialStorageKey) + self.saveUserCrredentials(email: email, password: password) + if let encryptedDefaultStorageKeyHex = result.data.encryptedDefaultStorageKey, let encryptedconfidentialStorageKeyHex = result.data.encryptedConfidentialStorageKey { + self.saveUserKeysToLocal(email: email, + password: password, + encryptedDefaultStorageKeyHex: encryptedDefaultStorageKeyHex, + encryptedconfidentialStorageKeyHex: encryptedconfidentialStorageKeyHex) + } + self.synchronizedDatabase { _ in - alert.dismiss(animated: false, completion: { + WCSession.default.sendMessage(["databaseSynced": "true"]) { _ in } + alert.dismiss(animated: false, completion: { super.goForward() - }) + }) } - + } } - - func synchronizedDatabase(completion: ((Error?) -> Void)?){ + + func synchronizedDatabase(completion: ((Error?) -> Void)?) { DispatchQueue.main.async { - CloudantSyncManager.shared.syncCloudantStore(notifyWhenDone: true) { - result in + CloudantSyncManager.shared.syncCloudantStore(notifyWhenDone: true) { result in completion?(result) } } - - } - func saveValuesToLocal(email: String, password: String, encryptedDefaultStorageKeyHex: String, encryptedconfidentialStorageKeyHex: String){ + func saveUserCrredentials(email: String, password: String) { + KeychainCloudManager.saveUserCredentialsInKeychain(email: email, password: password) + } + + func saveUserKeysToLocal(email: String, password: String, encryptedDefaultStorageKeyHex: String, encryptedconfidentialStorageKeyHex: String) { let swiftSodium = SwiftSodium() let masterKey = swiftSodium.generateMasterKey(password: password, email: email) let keyPair = swiftSodium.sodium.box.keyPair(seed: masterKey) - - - if let keyPair = keyPair { - let encryptedDefaultStorageKey = swiftSodium.getArrayOfBytesFromData(FileData: swiftSodium.hexStringToData(string: encryptedDefaultStorageKeyHex) as NSData) - let defaultStorageKey = swiftSodium.decryptKey(bytes: encryptedDefaultStorageKey, publicKey: keyPair.publicKey, secretKey: keyPair.secretKey) - let encryptedconfidentialStorageKey = swiftSodium.getArrayOfBytesFromData(FileData: swiftSodium.hexStringToData(string: encryptedconfidentialStorageKeyHex) as NSData) + let encryptedDefaultStorageKey = swiftSodium.getArrayOfBytesFromData(fileData: swiftSodium.hexStringToData(string: encryptedDefaultStorageKeyHex) as NSData) + let defaultStorageKey = swiftSodium.decryptKey(bytes: encryptedDefaultStorageKey, publicKey: keyPair.publicKey, secretKey: keyPair.secretKey) + + let encryptedconfidentialStorageKey = swiftSodium.getArrayOfBytesFromData(fileData: swiftSodium.hexStringToData(string: encryptedconfidentialStorageKeyHex) as NSData) let confidentialStorageKey = swiftSodium.decryptKey(bytes: encryptedconfidentialStorageKey, publicKey: keyPair.publicKey, secretKey: keyPair.secretKey) - - KeychainCloudManager.saveValuesInKeychain(email: email, password: password, masterKey: masterKey, publicKey: keyPair.publicKey, secretKey: keyPair.secretKey, defaultStorageKey: defaultStorageKey, confidentialStorageKey: confidentialStorageKey) + + KeychainCloudManager.saveUserKeys( + masterKey: masterKey, + publicKey: keyPair.publicKey, + secretKey: keyPair.secretKey, + defaultStorageKey: defaultStorageKey, + confidentialStorageKey: confidentialStorageKey) } } // Forgot password. override func forgotPasswordButtonTapped() { let alert = UIAlertController(title: Constants.CustomiseStrings.resetPassword, message: Constants.CustomiseStrings.enterYourEmailToGetLink, preferredStyle: .alert) - + alert.addTextField { (textField) in textField.placeholder = Constants.CustomiseStrings.enterYourEmail } - + alert.addAction(UIAlertAction(title: Constants.CustomiseStrings.submit, style: .default) { (_) in - guard let email = alert.textFields![0].text else{ return } + guard let email = alert.textFields![0].text else { return } if email.isValidEmail { - + self.disposables = OTFTheraforgeNetwork.shared.forgotPassword(email: email) .receive(on: DispatchQueue.main) .sink(receiveCompletion: { response in @@ -228,36 +241,36 @@ class LoginViewController: ORKLoginStepViewController { DispatchQueue.main.async { self.showAlert(title: Constants.CustomiseStrings.forgotPassword, message: error.error.message) } - default: break; + default: break } - }, receiveValue: { result in + }, receiveValue: { _ in DispatchQueue.main.async { self.resetPassword(email: email) } }) - - }else { + + } else { self.showAlert(title: Constants.CustomiseStrings.resetPassword, message: Constants.CustomiseStrings.enterValidEmail) } }) - + alert.addAction(UIAlertAction(title: Constants.CustomiseStrings.cancel, style: .cancel, handler: nil)) - + self.present(alert, animated: true) } - + // Reset password for the givem email. func resetPassword(email: String) { let alert = UIAlertController(title: Constants.CustomiseStrings.resetPassword, message: Constants.CustomiseStrings.enterTheCode, preferredStyle: .alert) - + alert.addTextField { (textField) in textField.placeholder = "Code " } - + alert.addTextField { (textField) in textField.placeholder = Constants.CustomiseStrings.newPassword textField.isSecureTextEntry = true - + } alert.addAction(UIAlertAction(title: Constants.CustomiseStrings.submit, style: .default) { _ in guard let code = alert.textFields![0].text else { @@ -266,7 +279,7 @@ class LoginViewController: ORKLoginStepViewController { guard let newPassword = alert.textFields![1].text else { fatalError("Invalid password") } - + self.disposables = OTFTheraforgeNetwork.shared.resetPassword(email: email, code: code, newPassword: newPassword) .receive(on: DispatchQueue.main) .sink(receiveCompletion: { response in @@ -276,21 +289,21 @@ class LoginViewController: ORKLoginStepViewController { DispatchQueue.main.async { self.showAlert(title: Constants.CustomiseStrings.passwordResetError, message: error.error.message) } - default: break; + default: break } - }, receiveValue: { result in + }, receiveValue: { _ in DispatchQueue.main.async { self.showAlert(title: Constants.CustomiseStrings.passwordUpdated, message: "") } }) }) alert.addAction(UIAlertAction(title: Constants.CustomiseStrings.cancel, style: .cancel, handler: nil)) - + self.present(alert, animated: true) } - + func addAuthButtonConstraints() { - + NSLayoutConstraint.activate([ authButton.centerXAnchor.constraint(equalTo: view.centerXAnchor), authButton.bottomAnchor.constraint(equalTo: view.bottomAnchor, constant: -150), @@ -300,4 +313,3 @@ class LoginViewController: ORKLoginStepViewController { ]) } } - diff --git a/OTFMagicBox/Login/SignInWithAppleStepViewController.swift b/OTFMagicBox/Login/SignInWithAppleStepViewController.swift index cd3207ab..ed084240 100644 --- a/OTFMagicBox/Login/SignInWithAppleStepViewController.swift +++ b/OTFMagicBox/Login/SignInWithAppleStepViewController.swift @@ -1,35 +1,35 @@ /* -Copyright (c) 2021, Hippocrates Technologies S.r.l.. All rights reserved. - -Redistribution and use in source and binary forms, with or without modification, -are permitted provided that the following conditions are met: - -1. Redistributions of source code must retain the above copyright notice, -this list of conditions and the following disclaimer. - -2. Redistributions in binary form must reproduce the above copyright notice, -this list of conditions and the following disclaimer in the documentation and/or -other materials provided with the distribution. - -3. Neither the name of the copyright holder(s) nor the names of any contributor(s) may -be used to endorse or promote products derived from this software without specific -prior written permission. No license is granted to the trademarks of the copyright -holders even if such marks are included in this software. - -4. Commercial redistribution in any form requires an explicit license agreement with the -copyright holder(s). Please contact support@hippocratestech.com for further information -regarding licensing. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND -ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. -IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, -INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT -NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, -WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) -ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY -OF SUCH DAMAGE. + Copyright (c) 2021, Hippocrates Technologies S.r.l.. All rights reserved. + + Redistribution and use in source and binary forms, with or without modification, + are permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation and/or + other materials provided with the distribution. + + 3. Neither the name of the copyright holder(s) nor the names of any contributor(s) may + be used to endorse or promote products derived from this software without specific + prior written permission. No license is granted to the trademarks of the copyright + holders even if such marks are included in this software. + + 4. Commercial redistribution in any form requires an explicit license agreement with the + copyright holder(s). Please contact support@hippocratestech.com for further information + regarding licensing. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, + INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, + OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY + OF SUCH DAMAGE. */ import AuthenticationServices @@ -43,10 +43,10 @@ public class SignInWithAppleStep: ORKInstructionStep { public var requestedScopes: [ASAuthorization.Scope] public init(identifier: String, - title: String! = nil, - text: String! = nil, - requestedScopes: [ASAuthorization.Scope] = [.email]) { - + title: String! = nil, + text: String! = nil, + requestedScopes: [ASAuthorization.Scope] = [.email]) { + self.requestedScopes = requestedScopes super.init(identifier: identifier) self.title = Constants.CustomiseStrings.signinWithApple @@ -61,36 +61,36 @@ public class SignInWithAppleStep: ORKInstructionStep { public class SignInWithAppleStepViewController: ORKInstructionStepViewController, ASAuthorizationControllerDelegate { - + let authType: AuthType var disposables: AnyCancellable? - + /// The step presented by the step view controller. public var signInWithAppleStep: SignInWithAppleStep! { return step as? SignInWithAppleStep } - + init(authType: AuthType, step: ORKStep?) { self.authType = authType - + super.init(step: step) } - + required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } - - public override func viewDidLoad() { + + override public func viewDidLoad() { super.viewDidLoad() - + continueButtonTitle = NSLocalizedString( Constants.CustomiseStrings.signinWithApple, comment: "Please use Apple's official translations" ) - + } - public override func goForward() { + override public func goForward() { let appleIDProvider = ASAuthorizationAppleIDProvider() let request = appleIDProvider.createRequest() request.requestedScopes = signInWithAppleStep?.requestedScopes ?? [.email] @@ -110,7 +110,7 @@ public class SignInWithAppleStepViewController: ORKInstructionStepViewController OTFLog("Unable to fetch identity token") return } - + // We are using this identity token to get other required fields e.g. email of the user. // The JWT token's payload is decided by Apple itself. We should be cautious that Apple // may change the format/composition of the token in the future. @@ -122,15 +122,15 @@ public class SignInWithAppleStepViewController: ORKInstructionStepViewController let alert = UIAlertController(title: nil, message: authType == .login ? Constants.CustomiseStrings.signingIn : Constants.CustomiseStrings.signingUp, preferredStyle: .alert) - + let loadingIndicator = UIActivityIndicatorView(frame: CGRect(x: 10, y: 5, width: 50, height: 50)) loadingIndicator.hidesWhenStopped = true loadingIndicator.style = UIActivityIndicatorView.Style.medium loadingIndicator.startAnimating() alert.view.addSubview(loadingIndicator) - + taskViewController?.present(alert, animated: true) - + disposables = OTFTheraforgeNetwork.shared.socialLoginRequest(userType: .patient, socialType: .apple, authType: authType, idToken: idTokenString) .receive(on: DispatchQueue.main) .sink(receiveCompletion: { response in @@ -142,9 +142,9 @@ public class SignInWithAppleStepViewController: ORKInstructionStepViewController self.taskViewController?.present(alert, animated: true) self.showError(error) } - default: break; + default: break } - }, receiveValue: { result in + }, receiveValue: { _ in alert.dismiss(animated: true, completion: nil) super.goForward() }) @@ -163,4 +163,3 @@ public class SignInWithAppleStepViewController: ORKInstructionStepViewController ) } } - diff --git a/OTFMagicBox/ModuleAppSysParameter.yml b/OTFMagicBox/ModuleAppSysParameter.yml index a190cb08..a49940bd 100644 --- a/OTFMagicBox/ModuleAppSysParameter.yml +++ b/OTFMagicBox/ModuleAppSysParameter.yml @@ -53,7 +53,7 @@ # Key and Value: # Text written on left side of the punctuation (:) is called as Key and text written on right side is called as Value. -# Example: teamEmail: "", here "teamEmail" is the Key and whatever is displayed after punctuation (:) within the quotes is the value assigned for that Key. +# Example: teamEmail: "", here "teamEmail" is the Key and whatever is displayed after punctuation (:) within the colon "" is the value assigned for that Key. # Default Values:- @@ -192,7 +192,7 @@ DataModel: summary: "This is custom section." content: "Custom consent section. Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aenean commodo ligula eget dolor. Aenean massa. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Donec quam felis, ultricies nec, pellentesque eu, pretium quis, sem." title: "Custom consent section" - image: "online-agreement1" + image: "online-agreement" registration: showDateOfBirth: "true" @@ -283,7 +283,7 @@ DataModel: reportProblemHeader: "Report a Problem" supportText: "Support" consentText: "Consent Documents" - WithdrawStudyText: "Withdraw from Study" + withdrawStudyText: "Withdraw from Study" profileInfoHeader: "Basic Information" firstName: "First Name" lastName: "Last Name" @@ -619,7 +619,7 @@ DataModel: reportProblemHeader: "Signaler un problème" supportText: "Soutien" consentText: "Documents de consentement" - WithdrawStudyText: "Se retirer de l'étude" + withdrawStudyText: "Se retirer de l'étude" profileInfoHeader: "Informations De Base" firstName: "Prénom" lastName: "nom de famille" @@ -729,11 +729,3 @@ DataModel: taskHeader: "Tâche" detailed: "détaillée" careKit: "Kit de soins" - - - #ProfileScreenSetup - - - - - diff --git a/OTFMagicBox/OCKStore + SampleData.swift b/OTFMagicBox/OCKStore + SampleData.swift index 603ac1b7..e60ae86d 100644 --- a/OTFMagicBox/OCKStore + SampleData.swift +++ b/OTFMagicBox/OCKStore + SampleData.swift @@ -1,25 +1,25 @@ /* Copyright (c) 2021, Hippocrates Technologies S.r.l.. All rights reserved. - + Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: - + 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. - + 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. - + 3. Neither the name of the copyright holder(s) nor the names of any contributor(s) may be used to endorse or promote products derived from this software without specific prior written permission. No license is granted to the trademarks of the copyright holders even if such marks are included in this software. - + 4. Commercial redistribution in any form requires an explicit license agreement with the copyright holder(s). Please contact support@hippocratestech.com for further information regarding licensing. - + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. @@ -41,33 +41,33 @@ import HealthKit #endif internal extension OCKStore { - + enum Tasks: String, CaseIterable { case doxylamine case nausea case kegels } - + enum Contacts: String, CaseIterable { case jane case matthew } - + // Adds tasks and contacts into the store func populateSampleData() { - + let thisMorning = Calendar.current.startOfDay(for: Date()) let beforeBreakfast = Calendar.current.date(byAdding: .hour, value: 8, to: thisMorning)! let afterLunch = Calendar.current.date(byAdding: .hour, value: 14, to: thisMorning)! - + let schedule = OCKSchedule(composing: [ OCKScheduleElement(start: beforeBreakfast, end: nil, interval: DateComponents(day: 1)), - + OCKScheduleElement(start: afterLunch, end: nil, interval: DateComponents(day: 2)) ]) - + var doxylamine = OCKTask(id: Tasks.doxylamine.rawValue, title: "Take Doxylamine", carePlanUUID: nil, schedule: schedule) doxylamine.instructions = "Take 25mg of doxylamine when you experience nausea." @@ -76,20 +76,20 @@ internal extension OCKStore { OCKScheduleElement(start: beforeBreakfast, end: nil, interval: DateComponents(day: 1), text: "Anytime throughout the day", targetValues: [], duration: .allDay) ]) - + var nausea = OCKTask(id: Tasks.nausea.rawValue, title: "Track your nausea", carePlanUUID: nil, schedule: nauseaSchedule) nausea.impactsAdherence = false nausea.instructions = "Tap the button below anytime you experience nausea." - + let kegelElement = OCKScheduleElement(start: beforeBreakfast, end: nil, interval: DateComponents(day: 2)) let kegelSchedule = OCKSchedule(composing: [kegelElement]) var kegels = OCKTask(id: Tasks.kegels.rawValue, title: "Kegel Exercises", carePlanUUID: nil, schedule: kegelSchedule) kegels.impactsAdherence = true kegels.instructions = "Perform kegel exercies" - + addTasks([nausea, doxylamine, kegels], callbackQueue: .main, completion: nil) - + var contact1 = OCKContact(id: Contacts.jane.rawValue, givenName: "Jane", familyName: "Daniels", carePlanUUID: nil) contact1.asset = "JaneDaniels" @@ -98,7 +98,7 @@ internal extension OCKStore { contact1.emailAddresses = [OCKLabeledValue(label: CNLabelEmailiCloud, value: "janedaniels@icloud.com")] contact1.phoneNumbers = [OCKLabeledValue(label: CNLabelWork, value: "(324) 555-7415")] contact1.messagingNumbers = [OCKLabeledValue(label: CNLabelWork, value: "(324) 555-7415")] - + contact1.address = { let address = OCKPostalAddress() address.street = "2598 Reposa Way" @@ -107,7 +107,7 @@ internal extension OCKStore { address.postalCode = "94127" return address }() - + var contact2 = OCKContact(id: Contacts.matthew.rawValue, givenName: "Matthew", familyName: "Reiff", carePlanUUID: nil) contact2.asset = "MatthewReiff" @@ -123,19 +123,19 @@ internal extension OCKStore { address.postalCode = "94127" return address }() - + addContacts([contact1, contact2]) } } extension OCKHealthKitPassthroughStore { -#if HEALTH + #if HEALTH func populateSampleData() { - + let schedule = OCKSchedule.dailyAtTime( hour: 8, minutes: 0, start: Date(), end: nil, text: nil, duration: .hours(12), targetValues: [OCKOutcomeValue(2000.0, units: "Steps")]) - + let steps = OCKHealthKitTask( id: "steps", title: "Steps", @@ -145,7 +145,7 @@ extension OCKHealthKitPassthroughStore { quantityIdentifier: .stepCount, quantityType: .cumulative, unit: .count())) - + addTasks([steps]) { result in switch result { case .success: @@ -155,28 +155,26 @@ extension OCKHealthKitPassthroughStore { } } } -#endif + #endif } - - #if DEBUG extension OTFCloudantStore { // Adds tasks and contacts into the store func populateSampleData() { - + let thisMorning = Calendar.current.startOfDay(for: Date()) let aFewDaysAgo = Calendar.current.date(byAdding: .day, value: -4, to: thisMorning)! let beforeBreakfast = Calendar.current.date(byAdding: .hour, value: 8, to: aFewDaysAgo)! let afterLunch = Calendar.current.date(byAdding: .hour, value: 14, to: aFewDaysAgo)! - + let schedule = OCKSchedule(composing: [ OCKScheduleElement(start: beforeBreakfast, end: nil, interval: DateComponents(day: 1)), - + OCKScheduleElement(start: afterLunch, end: nil, interval: DateComponents(day: 2)) ]) - + var doxylamine = OCKTask(id: "doxylamine", title: "Take Doxylamine", carePlanUUID: nil, schedule: schedule) doxylamine.instructions = "Take 25mg of doxylamine when you experience nausea." @@ -185,43 +183,43 @@ extension OTFCloudantStore { OCKScheduleElement(start: beforeBreakfast, end: nil, interval: DateComponents(day: 1), text: "Anytime throughout the day", targetValues: [], duration: .allDay) ]) - + var nausea = OCKTask(id: "nausea", title: "Track your nausea", carePlanUUID: nil, schedule: nauseaSchedule) nausea.impactsAdherence = false nausea.instructions = "Tap the button below anytime you experience nausea." - + let kegelElement = OCKScheduleElement(start: beforeBreakfast, end: nil, interval: DateComponents(day: 2)) let kegelSchedule = OCKSchedule(composing: [kegelElement]) var kegels = OCKTask(id: "kegels", title: "Kegel Exercises", carePlanUUID: nil, schedule: kegelSchedule) kegels.impactsAdherence = true kegels.instructions = "Perform kegel exercies" - + let checkInSchedule = OCKSchedule.dailyAtTime( hour: 8, minutes: 0, start: Date(), end: nil, text: nil ) - + let checkInTask = OCKTask( id: "checkIn", title: "Check In", carePlanUUID: nil, schedule: checkInSchedule ) - + let nextWeek = Calendar.current.date( byAdding: .weekOfYear, value: 1, to: Date() )! - + let nextMonth = Calendar.current.date( byAdding: .month, value: 1, to: thisMorning ) - + let dailyElement = OCKScheduleElement( start: thisMorning, end: nextWeek, @@ -230,7 +228,7 @@ extension OTFCloudantStore { targetValues: [], duration: .allDay ) - + let weeklyElement = OCKScheduleElement( start: nextWeek, end: nextMonth, @@ -239,21 +237,21 @@ extension OTFCloudantStore { targetValues: [], duration: .allDay ) - + let rangeOfMotionCheckSchedule = OCKSchedule( composing: [dailyElement, weeklyElement] ) - + let rangeOfMotionCheckTask = OCKTask( id: "rangeOfMotionCheck", title: "Range Of Motion", carePlanUUID: nil, schedule: rangeOfMotionCheckSchedule ) - + addAnyTasks([nausea, doxylamine, kegels, checkInTask, rangeOfMotionCheckTask], callbackQueue: .main, completion: nil) - + var contact1 = OCKContact(id: "jane", givenName: "Jane", familyName: "Daniels", carePlanUUID: nil) contact1.asset = "JaneDaniels" @@ -262,7 +260,7 @@ extension OTFCloudantStore { contact1.emailAddresses = [OCKLabeledValue(label: CNLabelEmailiCloud, value: "janedaniels@icloud.com")] contact1.phoneNumbers = [OCKLabeledValue(label: CNLabelWork, value: "(324) 555-7415")] contact1.messagingNumbers = [OCKLabeledValue(label: CNLabelWork, value: "(324) 555-7415")] - + contact1.address = { let address = OCKPostalAddress() address.street = "2598 Reposa Way" @@ -271,7 +269,7 @@ extension OTFCloudantStore { address.postalCode = "94127" return address }() - + var contact2 = OCKContact(id: "matthew", givenName: "Matthew", familyName: "Reiff", carePlanUUID: nil) contact2.asset = "MatthewReiff" @@ -287,10 +285,10 @@ extension OTFCloudantStore { address.postalCode = "94127" return address }() - + addContacts([contact1, contact2]) } - + @discardableResult func convertUserToPatient(user: Response.User) -> OCKPatient? { var patient = OCKPatient(id: user.id, givenName: user.firstName ?? "", familyName: user.lastName ?? "") patient.uuid = UUID() @@ -302,7 +300,7 @@ extension OTFCloudantStore { } } -extension CareKitManager { +extension CareKitStoreManager { func populateSampleData() { OCKStoreManager.shared.coreDataStore.fetchTasks { result in switch result { @@ -324,7 +322,7 @@ extension OTFCloudantStore { completion(.failure(.fetchFailed(reason: "User not logged in."))) return } - CareKitManager.shared.cloudantStore?.fetchPatient(withID: user.id, completion: { result in + CareKitStoreManager.shared.cloudantStore?.fetchPatient(withID: user.id, completion: { result in completion(result) }) } diff --git a/OTFMagicBox/Onboarding/OnboardingItemView.swift b/OTFMagicBox/Onboarding/OnboardingItemView.swift index be033e5a..7296fcb7 100644 --- a/OTFMagicBox/Onboarding/OnboardingItemView.swift +++ b/OTFMagicBox/Onboarding/OnboardingItemView.swift @@ -1,48 +1,48 @@ /* -Copyright (c) 2021, Hippocrates Technologies S.r.l.. All rights reserved. - -Redistribution and use in source and binary forms, with or without modification, -are permitted provided that the following conditions are met: - -1. Redistributions of source code must retain the above copyright notice, -this list of conditions and the following disclaimer. - -2. Redistributions in binary form must reproduce the above copyright notice, -this list of conditions and the following disclaimer in the documentation and/or -other materials provided with the distribution. - -3. Neither the name of the copyright holder(s) nor the names of any contributor(s) may -be used to endorse or promote products derived from this software without specific -prior written permission. No license is granted to the trademarks of the copyright -holders even if such marks are included in this software. - -4. Commercial redistribution in any form requires an explicit license agreement with the -copyright holder(s). Please contact support@hippocratestech.com for further information -regarding licensing. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND -ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. -IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, -INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT -NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, -WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) -ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY -OF SUCH DAMAGE. + Copyright (c) 2021, Hippocrates Technologies S.r.l.. All rights reserved. + + Redistribution and use in source and binary forms, with or without modification, + are permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation and/or + other materials provided with the distribution. + + 3. Neither the name of the copyright holder(s) nor the names of any contributor(s) may + be used to endorse or promote products derived from this software without specific + prior written permission. No license is granted to the trademarks of the copyright + holders even if such marks are included in this software. + + 4. Commercial redistribution in any form requires an explicit license agreement with the + copyright holder(s). Please contact support@hippocratestech.com for further information + regarding licensing. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, + INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, + OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY + OF SUCH DAMAGE. */ import SwiftUI /// The onboarding items view elements. struct OnboardingItemView: View { - + /// The onboarding items view controller. var viewControllers: [UIHostingController] - + /// The current onboarding item. @State var currentOnboardingItem = 0 - + /// Creates the onboarding item view. init(_ views: [OnboardingItem]) { self.viewControllers = views.map { @@ -61,20 +61,20 @@ struct OnboardingItemView: View { OnboardingItemControl(numberOfOnboardingItems: viewControllers.count, currentOnboardingItem: $currentOnboardingItem) .background(Color.clear) .offset(x: 0, y: -80) - + } } .ignoresSafeArea() } - + } /// The onboarding items view controller. struct OnboardingItemViewController: UIViewControllerRepresentable { - + /// The controllers for the onboarding items. var controllers: [UIViewController] - + /// The current page. @Binding var currentPage: Int @@ -100,7 +100,7 @@ struct OnboardingItemViewController: UIViewControllerRepresentable { /// Documentation is in UIViewControllerRepresentable. func updateUIViewController(_ onboardingItemViewController: UIPageViewController, context: Context) { onboardingItemViewController.setViewControllers( - [self.controllers[self.currentPage]], direction: .forward, animated: true) + [self.controllers[self.currentPage]], direction: .forward, animated: true) } class Coordinator: NSObject, UIPageViewControllerDataSource, UIPageViewControllerDelegate { @@ -109,10 +109,9 @@ struct OnboardingItemViewController: UIViewControllerRepresentable { var parent: OnboardingItemViewController /// Creates onboarding items view controller. - init(_ OnboardingItemViewController: OnboardingItemViewController) { - self.parent = OnboardingItemViewController + init(_ onboardingItemViewController: OnboardingItemViewController) { + self.parent = onboardingItemViewController } - /// Documentation is in UIPageViewControllerDataSource. func pageViewController( _ pageViewController: UIPageViewController, @@ -125,7 +124,7 @@ struct OnboardingItemViewController: UIViewControllerRepresentable { } return parent.controllers[index - 1] } - + /// Documentation is in UIPageViewControllerDataSource. func pageViewController( _ pageViewController: UIPageViewController, @@ -142,8 +141,8 @@ struct OnboardingItemViewController: UIViewControllerRepresentable { /// Documentation is in UIPageViewControllerDelegate. func pageViewController(_ pageViewController: UIPageViewController, didFinishAnimating finished: Bool, previousViewControllers: [UIViewController], transitionCompleted completed: Bool) { if completed, - let visibleViewController = pageViewController.viewControllers?.first, - let index = parent.controllers.firstIndex(of: visibleViewController) { + let visibleViewController = pageViewController.viewControllers?.first, + let index = parent.controllers.firstIndex(of: visibleViewController) { parent.currentPage = index } } @@ -152,13 +151,13 @@ struct OnboardingItemViewController: UIViewControllerRepresentable { /// The onboarding item control. struct OnboardingItemControl: UIViewRepresentable { - + /// The number of onboarding items. var numberOfOnboardingItems: Int - + /// The current onboarding item. @Binding var currentOnboardingItem: Int - + /// Documentation is in UIViewRepresentable. func makeCoordinator() -> Coordinator { Coordinator(self) @@ -185,7 +184,7 @@ struct OnboardingItemControl: UIViewRepresentable { } class Coordinator: NSObject { - + /// Control of the onboarding item controller. var control: OnboardingItemControl @@ -193,7 +192,7 @@ struct OnboardingItemControl: UIViewRepresentable { init(_ control: OnboardingItemControl) { self.control = control } - + /// Updates the on boarding item. @objc func updateCurrentOnboardingItem(sender: UIPageControl) { control.currentOnboardingItem = sender.currentPage diff --git a/OTFMagicBox/Onboarding/OnboardingOptionsViewController.swift b/OTFMagicBox/Onboarding/OnboardingOptionsViewController.swift index 80af8984..69593e94 100644 --- a/OTFMagicBox/Onboarding/OnboardingOptionsViewController.swift +++ b/OTFMagicBox/Onboarding/OnboardingOptionsViewController.swift @@ -1,25 +1,25 @@ /* Copyright (c) 2021, Hippocrates Technologies S.r.l.. All rights reserved. - + Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: - + 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. - + 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. - + 3. Neither the name of the copyright holder(s) nor the names of any contributor(s) may be used to endorse or promote products derived from this software without specific prior written permission. No license is granted to the trademarks of the copyright holders even if such marks are included in this software. - + 4. Commercial redistribution in any form requires an explicit license agreement with the copyright holder(s). Please contact support@hippocratestech.com for further information regarding licensing. - + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. @@ -40,43 +40,43 @@ import OTFUtilities import Combine public class OnboardingOptionsStep: ORKQuestionStep { - public override init( + override public init( identifier: String ) { super.init(identifier: identifier) self.answerFormat = ORKAnswerFormat.booleanAnswerFormat() } - + @available(*, unavailable) public required init(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } - + } public class OnboardingOptionsViewController: ORKQuestionStepViewController, ASAuthorizationControllerDelegate { let authType: AuthType var disposables: AnyCancellable? - - public var CKMultipleSignInStep: OnboardingOptionsStep!{ + + public var CKMultipleSignInStep: OnboardingOptionsStep! { return step as? OnboardingOptionsStep } - + init(authType: AuthType, step: ORKStep?) { self.authType = authType - + super.init(step: step) } - + required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } - - public override func viewDidLoad() { - + + override public func viewDidLoad() { + setupViews() } - + public func customButton( title: String, backGroundColor: UIColor, @@ -89,27 +89,27 @@ public class OnboardingOptionsViewController: ORKQuestionStepViewController, ASA ) -> UIButton { let button = UIButton(frame: CGRect(x: 200, y: 200, width: 350, height: 50)) button.setTitle(title, for: .normal) - - button.setTitleColor(textColor,for: .normal) - button.addTarget(self,action: action,for: .touchUpInside) + + button.setTitleColor(textColor, for: .normal) + button.addTarget(self, action: action, for: .touchUpInside) button.layer.cornerRadius = 10 button.backgroundColor = backGroundColor - + if let borderColor=borderColor { button.layer.borderWidth = 2 button.layer.borderColor = borderColor.cgColor } - - if (image != "") { + + if !image.isEmpty { button.setImage(UIImage(named: image), for: .normal) button.imageEdgeInsets.left = -imageOffset } - + return button } - + private func setupViews() { - + let verticalStack = UIStackView() verticalStack.translatesAutoresizingMaskIntoConstraints = false verticalStack.spacing = 10 @@ -117,11 +117,11 @@ public class OnboardingOptionsViewController: ORKQuestionStepViewController, ASA verticalStack.distribution = .fillEqually verticalStack.backgroundColor = .clear view.addSubview(verticalStack) - + var stackViewHeight = CGFloat(0) - + let config = ModuleAppYmlReader() - + if YmlReader().showAppleLogin { stackViewHeight += 60 let buttonApple = customButton(title: Constants.CustomiseStrings.signinWithApple, backGroundColor: .black, textColor: .white, borderColor: nil, @@ -129,7 +129,7 @@ public class OnboardingOptionsViewController: ORKQuestionStepViewController, ASA buttonApple.translatesAutoresizingMaskIntoConstraints = false verticalStack.addArrangedSubview(buttonApple) } - + if YmlReader().showGoogleLogin { stackViewHeight += 60 let buttonGoogle = customButton(title: Constants.CustomiseStrings.signinWithGoogle, backGroundColor: .white, textColor: .black, @@ -138,14 +138,14 @@ public class OnboardingOptionsViewController: ORKQuestionStepViewController, ASA buttonGoogle.translatesAutoresizingMaskIntoConstraints = false verticalStack.addArrangedSubview(buttonGoogle) } - + stackViewHeight += 50 let buttonUserPassWord = customButton(title: Constants.CustomiseStrings.signinWithEmail, backGroundColor: .white, textColor: .black, borderColor: .black, reference: nil, action: #selector(loginEmailAndPaswwordAction)) buttonUserPassWord.translatesAutoresizingMaskIntoConstraints = false verticalStack.addArrangedSubview(buttonUserPassWord) - - ///Sign in label + + /// Sign in label let signInLabel = UILabel(frame: CGRect(x: 0, y: 320, width: 450, height: 50 )) signInLabel.translatesAutoresizingMaskIntoConstraints = false signInLabel.center.x = view.center.x @@ -156,13 +156,12 @@ public class OnboardingOptionsViewController: ORKQuestionStepViewController, ASA signInLabel.textAlignment = NSTextAlignment.center signInLabel.numberOfLines = 4 self.view.addSubview(signInLabel) - + var optionsIcon: UIImage? if let image = UIImage(systemName: config.loginOptionsIcon) { optionsIcon = image - } - else { + } else { optionsIcon = UIImage(systemName: Constants.YamlDefaults.LoginOptionsIcon) } @@ -173,35 +172,35 @@ public class OnboardingOptionsViewController: ORKQuestionStepViewController, ASA iconImage.contentMode = .scaleAspectFit iconImage.center.x = view.center.x view.addSubview(iconImage) - + NSLayoutConstraint.activate([ iconImage.centerXAnchor.constraint(equalTo: view.centerXAnchor), iconImage.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 20), iconImage.heightAnchor.constraint(equalToConstant: 120), iconImage.widthAnchor.constraint(equalToConstant: 120), - + signInLabel.centerXAnchor.constraint(equalTo: view.centerXAnchor), signInLabel.topAnchor.constraint(equalTo: iconImage.bottomAnchor), signInLabel.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 20), signInLabel.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -20), - + verticalStack.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 20), verticalStack.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -20), verticalStack.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor, constant: -20), verticalStack.heightAnchor.constraint(equalToConstant: stackViewHeight) ]) - + self.view.backgroundColor = .white } - + @objc func loginEmailAndPaswwordAction() { self.setAnswer(true) super.goForward() } - + private var currentNonce: String! - + @objc func loginAppleAction() { currentNonce = .makeRandomNonce() @@ -209,12 +208,12 @@ public class OnboardingOptionsViewController: ORKQuestionStepViewController, ASA let request = appleIDProvider.createRequest() request.requestedScopes = [.email] request.nonce = currentNonce.sha256 - + let authorizationController = ASAuthorizationController(authorizationRequests: [request]) authorizationController.delegate = self authorizationController.performRequests() } - + public func authorizationController(controller: ASAuthorizationController, didCompleteWithAuthorization authorization: ASAuthorization) { guard let appleIDCredential = authorization.credential as? ASAuthorizationAppleIDCredential else { @@ -225,7 +224,7 @@ public class OnboardingOptionsViewController: ORKQuestionStepViewController, ASA OTFLog("Unable to fetch identity token") return } - + // We are using this identity token to get other required fields e.g. email of the user. // The JWT token's payload is decided by Apple itself. We should be cautious that Apple // may change the format/composition of the token in the future. @@ -233,20 +232,18 @@ public class OnboardingOptionsViewController: ORKQuestionStepViewController, ASA OTFLog("Unable to serialize token string from data:", appleIDToken.debugDescription) return } - + let alert = UIAlertController(title: nil, message: authType == .login ? Constants.CustomiseStrings.signingIn : Constants.CustomiseStrings.signingUp, preferredStyle: .alert) - + let loadingIndicator = UIActivityIndicatorView(frame: CGRect(x: 10, y: 5, width: 50, height: 50)) loadingIndicator.hidesWhenStopped = true loadingIndicator.style = UIActivityIndicatorView.Style.medium loadingIndicator.startAnimating() alert.view.addSubview(loadingIndicator) - + taskViewController?.present(alert, animated: true) - - disposables = OTFTheraforgeNetwork.shared.socialLoginRequest(userType: .patient, socialType: .apple, authType: authType, idToken: idTokenString) .receive(on: DispatchQueue.main) .sink(receiveCompletion: { response in @@ -257,51 +254,48 @@ public class OnboardingOptionsViewController: ORKQuestionStepViewController, ASA self.showAlert(title: "Ok", message: error.error.message) self.showError(error) } - default: break; + default: break } - }, receiveValue: { result in + }, receiveValue: { _ in alert.dismiss(animated: true, completion: nil) self.setAnswer(false) super.goForward() }) } - + @objc func loginGoogleAction() { - + GIDSignIn.sharedInstance.signIn(withPresenting: self) { user, error in - + if let error = error { self.showError(error) return } - + // If sign in succeeded, display the app's main content View. - + guard let user = user?.user, let idToken = user.idToken?.tokenString else { self.showError(ForgeError.empty) return } - + self.signInToTheraForgeWith(idToken: idToken) } } - + func signInToTheraForgeWith(idToken: String) { let alert = UIAlertController(title: nil, message: authType == .login ? Constants.CustomiseStrings.signingIn : Constants.CustomiseStrings.signingUp, preferredStyle: .alert) - + let loadingIndicator = UIActivityIndicatorView(frame: CGRect(x: 10, y: 5, width: 50, height: 50)) loadingIndicator.hidesWhenStopped = true loadingIndicator.style = UIActivityIndicatorView.Style.medium loadingIndicator.startAnimating() alert.view.addSubview(loadingIndicator) - + taskViewController?.present(alert, animated: true) - - - disposables = OTFTheraforgeNetwork.shared.socialLoginRequest(userType: .patient, socialType: .gmail, authType: authType, idToken: idToken) .receive(on: DispatchQueue.main) .sink(receiveCompletion: { response in @@ -312,15 +306,15 @@ public class OnboardingOptionsViewController: ORKQuestionStepViewController, ASA self.showAlert(title: Constants.CustomiseStrings.okay, message: error.error.message) self.showError(error) } - default: break; + default: break } - }, receiveValue: { result in + }, receiveValue: { _ in alert.dismiss(animated: true, completion: nil) self.setAnswer(false) super.goForward() }) } - + private func showError(_ error: Error) { // with your request to Google. Alerts.showInfo( @@ -338,14 +332,14 @@ fileprivate extension String { .compactMap { String(format: "%02x", $0) } .joined() } - + /// Adapted from https://auth0.com/docs/api-auth/tutorials/nonce#generate-a-cryptographically-random-nonce static func makeRandomNonce(ofLength length: Int = 32) -> String { precondition(length > 0) let charset = Array("0123456789ABCDEFGHIJKLMNOPQRSTUVXYZabcdefghijklmnopqrstuvwxyz-._") var result = "" var remainingLength = length - + while remainingLength > 0 { let randoms: [UInt8] = (0 ..< 16).map { _ in var random: UInt8 = 0 @@ -355,12 +349,12 @@ fileprivate extension String { } return random } - + for random in randoms { if remainingLength == 0 { break } - + if random < charset.count { result.append(charset[Int(random)]) remainingLength -= 1 diff --git a/OTFMagicBox/Onboarding/OnboardingTaskCoordinator.swift b/OTFMagicBox/Onboarding/OnboardingTaskCoordinator.swift index 72e2eb8e..393e97f7 100644 --- a/OTFMagicBox/Onboarding/OnboardingTaskCoordinator.swift +++ b/OTFMagicBox/Onboarding/OnboardingTaskCoordinator.swift @@ -1,25 +1,25 @@ /* Copyright (c) 2021, Hippocrates Technologies S.r.l.. All rights reserved. - + Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: - + 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. - + 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. - + 3. Neither the name of the copyright holder(s) nor the names of any contributor(s) may be used to endorse or promote products derived from this software without specific prior written permission. No license is granted to the trademarks of the copyright holders even if such marks are included in this software. - + 4. Commercial redistribution in any form requires an explicit license agreement with the copyright holder(s). Please contact support@hippocratestech.com for further information regarding licensing. - + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. @@ -36,18 +36,19 @@ import OTFResearchKit import OTFUtilities import Combine import Sodium +import OTFCloudClientAPI final class OnboardingTaskCoordinator: NSObject { - + let authType: AuthType var disposeables: AnyCancellable? let documentManager = UploadDocumentManager() let swiftSodium = SwiftSodium() - + /// Change this value to true when registration step completes successfully private var registrationCompleted = false private var navigationDirection = ORKStepViewControllerNavigationDirection.forward - + init(authType: AuthType) { self.authType = authType } @@ -58,76 +59,88 @@ extension OnboardingTaskCoordinator: ORKTaskViewControllerDelegate { guard authType == .signup else { return true } - + let config = ModuleAppYmlReader() - + var checkRegistrationStatusFirst = false if config.isPasscodeEnabled, step.identifier == Constants.Identifier.PasscodeStep { checkRegistrationStatusFirst = true - } - else if step.identifier == Constants.Identifier.CompletionStep { + } else if step.identifier == Constants.Identifier.CompletionStep { checkRegistrationStatusFirst = true } - + if checkRegistrationStatusFirst, navigationDirection == .forward, !registrationCompleted { let stepResult = taskViewController.result.stepResult(forStepIdentifier: Constants.Registration.Identifier) - + let emailRes = stepResult?.results?.first as? ORKTextQuestionResult guard let email = emailRes?.textAnswer else { return false } - + let passwordRes = stepResult?.results?[1] as? ORKTextQuestionResult guard let pass = passwordRes?.textAnswer else { return false } let givenName = stepResult?.results?[3] as? ORKTextQuestionResult let familyName = stepResult?.results?[4] as? ORKTextQuestionResult - + let genderResult = stepResult?.results?[5] as? ORKChoiceQuestionResult let dobResult = stepResult?.results?[6] as? ORKDateQuestionResult - + guard let gender = genderResult?.choiceAnswers?.first as? String else { return false } - + guard let dob = dobResult?.dateAnswer?.toString else { return false } - + let masterKey = swiftSodium.generateMasterKey(password: pass, email: email) - + guard let keyPair = swiftSodium.sodium.box.keyPair(seed: masterKey) else { return false } - - let encryptkey : Bytes? = swiftSodium.encryptKey(bytes: masterKey, publicKey: keyPair.publicKey) - + + let encryptkey: Bytes? = swiftSodium.encryptKey(bytes: masterKey, publicKey: keyPair.publicKey) + guard let encryptedMasterKey = encryptkey else { return false } - + let defaultStorageKey = swiftSodium.generateDefaultStorageKey(masterKey: masterKey) let confidentialStorageKey = swiftSodium.generateConfidentialStorageKey(masterKey: masterKey) - + let encryptedDefaultStorageKey = swiftSodium.encryptKey(bytes: defaultStorageKey, publicKey: keyPair.publicKey) let encryptedconfidentialStorageKey = swiftSodium.encryptKey(bytes: confidentialStorageKey, publicKey: keyPair.publicKey) - + let encryptedDefaultStorageKeyHex = encryptedDefaultStorageKey.bytesToHex(spacing: "").lowercased() let encryptedconfidentialStorageKeyHex = encryptedconfidentialStorageKey.bytesToHex(spacing: "").lowercased() let encryptedMasterKeyHex = encryptedMasterKey.bytesToHex(spacing: "").lowercased() let publicKeyHex = keyPair.publicKey.bytesToHex(spacing: "").lowercased() - + let alert = UIAlertController(title: nil, message: Constants.CustomiseStrings.creatingAccount, preferredStyle: .alert) let loadingIndicator = UIActivityIndicatorView(frame: CGRect(x: 10, y: 5, width: 50, height: 50)) loadingIndicator.hidesWhenStopped = true loadingIndicator.style = UIActivityIndicatorView.Style.medium loadingIndicator.startAnimating() - + alert.view.addSubview(loadingIndicator) taskViewController.present(alert, animated: false, completion: nil) - - disposeables = OTFTheraforgeNetwork.shared.signUpRequest(firstName: givenName?.textAnswer ?? Constants.patientFirstName, lastName: familyName?.textAnswer ?? Constants.patientLastName, type: Constants.userType, email: email, password: pass, dob: dob, gender: gender, encryptedMasterKey: encryptedMasterKeyHex, publicKey: publicKeyHex, encryptedDefaultStorageKey: encryptedDefaultStorageKeyHex, encryptedConfidentialStorageKey: encryptedconfidentialStorageKeyHex) + let signup = OTFCloudClientAPI.Request.SignUp( + email: email, + password: pass, + first_name: givenName?.textAnswer ?? Constants.patientFirstName, + last_name: familyName?.textAnswer ?? Constants.patientLastName, + type: .patient, + dob: dob, + gender: gender, + phoneNo: "", + encryptedMasterKey: encryptedMasterKeyHex, + publicKey: publicKeyHex, + encryptedDefaultStorageKey: encryptedDefaultStorageKeyHex, + encryptedConfidentialStorageKey: encryptedconfidentialStorageKeyHex) + disposeables = OTFTheraforgeNetwork.shared.signUpRequest( + signupRequest: signup) .receive(on: DispatchQueue.main) .sink(receiveCompletion: { response in switch response { @@ -138,70 +151,81 @@ extension OnboardingTaskCoordinator: ORKTaskViewControllerDelegate { alert.addAction(UIAlertAction(title: Constants.CustomiseStrings.okay, style: .cancel)) taskViewController.present(alert, animated: false) } - default: break; + default: break } }, receiveValue: { result in OTFLog("Successfully revrived", result.message ?? "") alert.dismiss(animated: false) { - KeychainCloudManager.saveValuesInKeychain(email: email, password: pass, masterKey: masterKey, publicKey: keyPair.publicKey, secretKey: keyPair.secretKey, defaultStorageKey: defaultStorageKey, confidentialStorageKey: confidentialStorageKey) + KeychainCloudManager.saveUserKeys( + masterKey: masterKey, + publicKey: keyPair.publicKey, + secretKey: keyPair.secretKey, + defaultStorageKey: defaultStorageKey, + confidentialStorageKey: confidentialStorageKey) self.registrationCompleted = true taskViewController.goForward() } }) - + return false } - + return true } - - func generateMasterKey(email: String, password: String) -> Array { + + func generateMasterKey(email: String, password: String) -> [UInt8] { let masterKey = swiftSodium.generateMasterKey(password: KeychainCloudManager.getPassword, email: KeychainCloudManager.getEmailAddress) return masterKey } - + func taskViewController(_ taskViewController: ORKTaskViewController, stepViewControllerWillDisappear stepViewController: ORKStepViewController, navigationDirection direction: ORKStepViewControllerNavigationDirection) { navigationDirection = direction } - - public func taskViewController(_ taskViewController: ORKTaskViewController, - didFinishWith reason: ORKTaskViewControllerFinishReason, error: Error?) { + + public func taskViewController( + _ taskViewController: ORKTaskViewController, + didFinishWith reason: ORKTaskViewControllerFinishReason, + error: Error?) { switch reason { case .completed: - + DispatchQueue.main.async { UserDefaultsManager.setOnboardingCompleted(true) NotificationCenter.default.post(name: .onboardingDidComplete, object: true) } - + if let signatureResult = taskViewController.result.stepResult(forStepIdentifier: "ConsentReviewStep")?.results?.first as? ORKConsentSignatureResult { - + let consentDocument = ConsentDocument() signatureResult.apply(to: consentDocument) - + consentDocument.makePDF { (data, error) -> Void in - + var docURL = (FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)).last as NSURL? docURL = docURL?.appendingPathComponent("\(ModuleAppYmlReader().consentFileName).pdf") as NSURL? - + do { let url = docURL! as URL - - self.documentManager.encryptDocument(document: data!, fileName: "\(ModuleAppYmlReader().consentFileName).pdf") - + if KeychainCloudManager.isKeyStored(key: KeychainKeys.defaultStorageKey) && + KeychainCloudManager.isKeyStored(key: KeychainKeys.defaultStorageKey) { + self.documentManager.encryptDocument(document: data!, fileName: "\(ModuleAppYmlReader().consentFileName).pdf") + } else { + self.documentManager.uploadFile(data: data!, fileName: "\(ModuleAppYmlReader().consentFileName).pdf", encryptedFileKey: "", hashFileKey: "") + } + try data?.write(to: url) - + UserDefaults.standard.set(url.path, forKey: Constants.UserDefaults.ConsentDocumentURL) - + } catch let error { OTFError("error in writting data in pdf %{public}@", error.localizedDescription) } } } fallthrough - + default: // Dismiss onboarding without proceeding. DispatchQueue.main.async { @@ -209,7 +233,7 @@ extension OnboardingTaskCoordinator: ORKTaskViewControllerDelegate { } } } - + func taskViewController(_ taskViewController: ORKTaskViewController, viewControllerFor step: ORKStep) -> ORKStepViewController? { switch step { @@ -223,5 +247,5 @@ extension OnboardingTaskCoordinator: ORKTaskViewControllerDelegate { return nil } } - + } diff --git a/OTFMagicBox/Onboarding/OnboardingView.swift b/OTFMagicBox/Onboarding/OnboardingView.swift index 1e1ac4a2..a734ebb1 100644 --- a/OTFMagicBox/Onboarding/OnboardingView.swift +++ b/OTFMagicBox/Onboarding/OnboardingView.swift @@ -1,25 +1,25 @@ /* Copyright (c) 2021, Hippocrates Technologies S.r.l.. All rights reserved. - + Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: - + 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. - + 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. - + 3. Neither the name of the copyright holder(s) nor the names of any contributor(s) may be used to endorse or promote products derived from this software without specific prior written permission. No license is granted to the trademarks of the copyright holders even if such marks are included in this software. - + 4. Commercial redistribution in any form requires an explicit license agreement with the copyright holder(s). Please contact support@hippocratestech.com for further information regarding licensing. - + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. @@ -38,33 +38,36 @@ import OTFUtilities /// The onboarding view. struct OnboardingView: View { - + /// The list of the onboarding view elements. let color: Color var onboardingElements: [Onboarding] = [] - var onComplete: (() -> Void)? = nil - + var onComplete: (() -> Void)? + @State var showingOnboard = false @State var showingLogin = false - + @State private var selectedAuthMethod = AuthMethod.email - + /// Creates the on boarding view. init(onComplete: (() -> Void)? = nil) { - - // TODO: Add the actual default image, if the user doesnt enter any image. let config = ModuleAppYmlReader() let onboardingdata: [Onboarding] = { - config.onboardingData ?? [Onboarding(image: Constants.CustomiseStrings.splashImage, icon: Constants.CustomiseStrings.splashIcon, title: Constants.CustomiseStrings.welcome, color: "black", description: Constants.CustomiseStrings.defaultDescription)] + config.onboardingData ?? [Onboarding( + image: Constants.CustomiseStrings.splashImage, + icon: Constants.CustomiseStrings.splashIcon, + title: Constants.CustomiseStrings.welcome, + color: "black", + description: Constants.CustomiseStrings.defaultDescription)] }() - + onboardingElements = onboardingdata - + self.color = Color(config.primaryColor) - + self.onComplete = onComplete } - + /// Onboarding view. var body: some View { ZStack { @@ -74,11 +77,11 @@ struct OnboardingView: View { description: $0.description, color: Color($0.color.color ?? .black)) }) - + GeometryReader { geometry in VStack { Spacer() - + HStack { Button(action: { self.showingOnboard = true @@ -89,17 +92,17 @@ struct OnboardingView: View { .foregroundColor(.white) .background(self.color) .cornerRadius(Metrics.RADIUS_CORNER_BUTTON) - .font(YmlReader().appTheme?.textFont.appFont ?? Font.system(size: 17.0)) + .font(Font.otfAppFont) }) .padding(.leading, Metrics.PADDING_HORIZONTAL_BUTTON) .padding(.trailing, Metrics.PADDING_HORIZONTAL_BUTTON / 2) - + Button(action: { self.showingLogin = true }, label: { Text(Constants.CustomiseStrings.signIn) - .font(YmlReader().appTheme?.textFont.appFont ?? Font.system(size: 17.0)) - .fontWeight(YmlReader().appTheme?.textWeight.fontWeight) + .font(Font.otfAppFont) + .fontWeight(Font.otfFontWeight) .frame(maxWidth: .infinity) .padding(.vertical, Metrics.PADDING_VERTICAL_ROW) .foregroundColor(.white) @@ -109,7 +112,7 @@ struct OnboardingView: View { .padding(.trailing, Metrics.PADDING_HORIZONTAL_BUTTON) .padding(.leading, Metrics.PADDING_HORIZONTAL_BUTTON / 2) } - + Spacer() .frame(height: geometry.safeAreaInsets.bottom > 0 ? 0 : Metrics.BOTTOM_SPACER) } @@ -134,22 +137,22 @@ struct OnboardingView: View { /// The onboarding detailed view. struct OnboardingDetailsView: View { - + /// Image of the onboarding item. let icon: String - + /// Image of the onboarding item. let image: String - + /// Title of the onboarding item. let title: String - + /// Description of the onboarding item. let description: String - + /// Color of the onboarding item. let color: Color - + /// Onboarding detailed view. var body: some View { ZStack { @@ -157,7 +160,7 @@ struct OnboardingDetailsView: View { .resizable() .ignoresSafeArea() .accessibilityHidden(true) - + VStack { Image(systemName: icon) .imageScale(.large) @@ -166,15 +169,15 @@ struct OnboardingDetailsView: View { .padding(.top, Metrics.PADDING_VERTICAL_BUTTON * 2) .padding(.bottom, Metrics.PADDING_VERTICAL_BUTTON) .accessibilityHidden(true) - + Text(title) .multilineTextAlignment(.center) - .font(YmlReader().appTheme?.appTitleSize.appFont ?? Constants.YamlDefaults.AppTitleSize.appFont) + .font(YmlReader().appStyle.appTitleSize.appFont ?? Constants.YamlDefaults.AppTitleSize.appFont) .foregroundColor(color) .shadow(color: .black, radius: 15) .padding([.horizontal, .bottom], Metrics.PADDING_VERTICAL_BUTTON) - + Text(description) .multilineTextAlignment(.center) .font(.body) @@ -182,13 +185,13 @@ struct OnboardingDetailsView: View { .shadow(radius: 5) .padding(.horizontal, Metrics.PADDING_HORIZONTAL_BUTTON) - + Spacer() } .frame(maxWidth: .infinity) } } - + } struct OnboardingView_Previews: PreviewProvider { @@ -196,4 +199,3 @@ struct OnboardingView_Previews: PreviewProvider { OnboardingView() } } - diff --git a/OTFMagicBox/Onboarding/OnboardingViewController.swift b/OTFMagicBox/Onboarding/OnboardingViewController.swift index 3e8304b8..39da5c89 100644 --- a/OTFMagicBox/Onboarding/OnboardingViewController.swift +++ b/OTFMagicBox/Onboarding/OnboardingViewController.swift @@ -1,25 +1,25 @@ /* Copyright (c) 2021, Hippocrates Technologies S.r.l.. All rights reserved. - + Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: - + 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. - + 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. - + 3. Neither the name of the copyright holder(s) nor the names of any contributor(s) may be used to endorse or promote products derived from this software without specific prior written permission. No license is granted to the trademarks of the copyright holders even if such marks are included in this software. - + 4. Commercial redistribution in any form requires an explicit license agreement with the copyright holder(s). Please contact support@hippocratestech.com for further information regarding licensing. - + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. @@ -42,56 +42,61 @@ import OTFCareKitStore User activities to create a Digital Health Application. */ struct OnboardingViewController: UIViewControllerRepresentable { - + func makeCoordinator() -> OnboardingTaskCoordinator { OnboardingTaskCoordinator(authType: .signup) } - + typealias UIViewControllerType = ORKTaskViewController - + func updateUIViewController(_ taskViewController: ORKTaskViewController, context: Context) {} - + func makeUIViewController(context: Context) -> ORKTaskViewController { let config = ModuleAppYmlReader() - + // * STEP (1): get user consent // use the `ORKVisualConsentStep` from ResearchKit let consentDocument = ConsentDocument() let consentStep = ORKVisualConsentStep(identifier: "VisualConsentStep", document: consentDocument) - + // * STEP (2): ask user to review and sign consent document // use the `ORKConsentReviewStep` from ResearchKit let signature = consentDocument.signatures?.first let reviewConsentStep = ORKConsentReviewStep(identifier: "ConsentReviewStep", signature: signature, in: consentDocument) reviewConsentStep.text = YmlReader().teamWebsite reviewConsentStep.reasonForConsent = config.reasonForConsentText - + // * STEP (3): ask user to enter their email address for login // the `LoginStep` collects and email address, and // the `LoginCustomWaitStep` waits for email verification. var loginSteps: [ORKStep] let signInButtons = OnboardingOptionsStep(identifier: "SignInButtons") - + var regOption = ORKRegistrationStepOption() - + if config.showDateOfBirth { regOption.insert(.includeDOB) } - + if config.showGender { regOption.insert(.includeGender) } regOption.insert( .includeGivenName) regOption.insert( .includeFamilyName) - let regexp = try! NSRegularExpression(pattern: "^.{10,}$") - + let regexp: NSRegularExpression + do { + regexp = try NSRegularExpression(pattern: "^.{10,}$") + } catch { + fatalError("Unable to create regular expression") + } + let registerStep = ORKRegistrationStep(identifier: Constants.Registration.Identifier, title: Constants.CustomiseStrings.registration, text: Constants.CustomiseStrings.studySignup, passcodeValidationRegularExpression: regexp, passcodeInvalidMessage: Constants.CustomiseStrings.notMeetCriteria, options: regOption) loginSteps = [signInButtons, registerStep] - + // * STEP (4): ask the user to create a security passcode // * that will be required to use this app! // use the `ORKPasscodeStep` from ResearchKit. @@ -105,21 +110,21 @@ struct OnboardingViewController: UIViewControllerRepresentable { } else { passcodeStep.passcodeType = .type4Digit } - + loginSteps += [passcodeStep] } - + // * STEP (5.1): get permission to collect HealthKit data // see `HealthDataStep` to configure! -// #if HEALTH || CAREHEALTH + // #if HEALTH || CAREHEALTH let healthDataStep = HealthDataStep(identifier: "Healthkit") - + // * STEP (5.2): get permission to collect HealthKit health records data let healthRecordsStep = HealthRecordsStep(identifier: "HealthRecords") - + loginSteps += [healthDataStep, healthRecordsStep] -// #endif - + // #endif + // * STEP (6): inform the user that they are done with sign-up! // use the `ORKCompletionStep` from ResearchKit // to set completion step @@ -127,11 +132,11 @@ struct OnboardingViewController: UIViewControllerRepresentable { completionStep.title = config.completionStepTitle completionStep.text = config.completionStepText loginSteps += [completionStep] - + // * finally, CREATE an array with the steps to show the user // given intro steps that the user should review and consent to let introSteps: [ORKStep] = [consentStep, reviewConsentStep] - + // guide the user through ALL steps let fullSteps: [ORKStep] if UserDefaultsManager.isConsentDocumentViewed { @@ -140,11 +145,11 @@ struct OnboardingViewController: UIViewControllerRepresentable { UserDefaultsManager.setIsConsentDocumentViewed(true) fullSteps = introSteps + loginSteps } - + // * and SHOW the user these steps! // create a task with each step let navigableTask = ORKNavigableOrderedTask(identifier: "StudyOnboardingTask", steps: fullSteps) - + let resultSelector = ORKResultSelector(resultIdentifier: "SignInButtons") let booleanAnswerType = ORKResultPredicate.predicateForBooleanQuestionResult(with: resultSelector, expectedAnswer: true) let predicateRule = ORKPredicateStepNavigationRule(resultPredicates: [booleanAnswerType], @@ -152,13 +157,13 @@ struct OnboardingViewController: UIViewControllerRepresentable { defaultStepIdentifier: "Passcode", validateArrays: true) navigableTask.setNavigationRule(predicateRule, forTriggerStepIdentifier: "SignInButtons") - + // wrap that task on a view controller let taskViewController = ORKTaskViewController(task: navigableTask, taskRun: nil) taskViewController.delegate = context.coordinator - + // & present the VC! return taskViewController } - + } diff --git a/OTFMagicBox/Onboarding/PasscodeViewController.swift b/OTFMagicBox/Onboarding/PasscodeViewController.swift index af68aa4c..d85432c2 100644 --- a/OTFMagicBox/Onboarding/PasscodeViewController.swift +++ b/OTFMagicBox/Onboarding/PasscodeViewController.swift @@ -1,35 +1,35 @@ /* -Copyright (c) 2021, Hippocrates Technologies S.r.l.. All rights reserved. - -Redistribution and use in source and binary forms, with or without modification, -are permitted provided that the following conditions are met: - -1. Redistributions of source code must retain the above copyright notice, -this list of conditions and the following disclaimer. - -2. Redistributions in binary form must reproduce the above copyright notice, -this list of conditions and the following disclaimer in the documentation and/or -other materials provided with the distribution. - -3. Neither the name of the copyright holder(s) nor the names of any contributor(s) may -be used to endorse or promote products derived from this software without specific -prior written permission. No license is granted to the trademarks of the copyright -holders even if such marks are included in this software. - -4. Commercial redistribution in any form requires an explicit license agreement with the -copyright holder(s). Please contact support@hippocratestech.com for further information -regarding licensing. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND -ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. -IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, -INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT -NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, -WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) -ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY -OF SUCH DAMAGE. + Copyright (c) 2021, Hippocrates Technologies S.r.l.. All rights reserved. + + Redistribution and use in source and binary forms, with or without modification, + are permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation and/or + other materials provided with the distribution. + + 3. Neither the name of the copyright holder(s) nor the names of any contributor(s) may + be used to endorse or promote products derived from this software without specific + prior written permission. No license is granted to the trademarks of the copyright + holders even if such marks are included in this software. + + 4. Commercial redistribution in any form requires an explicit license agreement with the + copyright holder(s). Please contact support@hippocratestech.com for further information + regarding licensing. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, + INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, + OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY + OF SUCH DAMAGE. */ import UIKit @@ -37,53 +37,46 @@ import SwiftUI import OTFResearchKit struct PasscodeViewController: UIViewControllerRepresentable { - + func makeCoordinator() -> Coordinator { Coordinator() - } - + } + typealias UIViewControllerType = ORKPasscodeViewController - + func updateUIViewController(_ uiViewController: ORKPasscodeViewController, context: Context) {} func updateUIViewController(_ taskViewController: ORKTaskViewController, context: Context) {} func makeUIViewController(context: Context) -> ORKPasscodeViewController { - - - let editPasscodeViewController = ORKPasscodeViewController.passcodeEditingViewController(withText: "", delegate: context.coordinator, passcodeType:.type4Digit) - + let editPasscodeViewController = ORKPasscodeViewController.passcodeEditingViewController(withText: "", delegate: context.coordinator, passcodeType: .type4Digit) return editPasscodeViewController } - + class Coordinator: NSObject, ORKPasscodeDelegate { func passcodeViewControllerDidCancel(_ viewController: UIViewController) { - + } - + func passcodeViewControllerDidFinish(withSuccess viewController: UIViewController) { viewController.dismiss(animated: true, completion: nil) } - + func passcodeViewControllerDidFailAuthentication(_ viewController: UIViewController) { viewController.dismiss(animated: true, completion: nil) - + Alerts.showInfo(title: Constants.CustomiseStrings.wrongPasscode, message: Constants.CustomiseStrings.okay) } } - } +public enum Alerts { -import UIKit - -public class Alerts { - - public class func showInfo(_ vc: UIViewController? = .none, title: String, message: String) { + public static func showInfo(_ vc: UIViewController? = .none, title: String, message: String) { let alert = UIAlertController(title: title, message: message, preferredStyle: .alert) - + let cancelAction = UIAlertAction(title: Constants.CustomiseStrings.okay, style: .default, handler: nil) alert.addAction(cancelAction) - + vc?.present(alert, animated: true, completion: nil) } - + } diff --git a/OTFMagicBox/Onboarding/PasswordlessLoginStep.swift b/OTFMagicBox/Onboarding/PasswordlessLoginStep.swift index a26330b6..59287b74 100644 --- a/OTFMagicBox/Onboarding/PasswordlessLoginStep.swift +++ b/OTFMagicBox/Onboarding/PasswordlessLoginStep.swift @@ -1,73 +1,73 @@ /* -Copyright (c) 2021, Hippocrates Technologies S.r.l.. All rights reserved. - -Redistribution and use in source and binary forms, with or without modification, -are permitted provided that the following conditions are met: - -1. Redistributions of source code must retain the above copyright notice, -this list of conditions and the following disclaimer. - -2. Redistributions in binary form must reproduce the above copyright notice, -this list of conditions and the following disclaimer in the documentation and/or -other materials provided with the distribution. - -3. Neither the name of the copyright holder(s) nor the names of any contributor(s) may -be used to endorse or promote products derived from this software without specific -prior written permission. No license is granted to the trademarks of the copyright -holders even if such marks are included in this software. - -4. Commercial redistribution in any form requires an explicit license agreement with the -copyright holder(s). Please contact support@hippocratestech.com for further information -regarding licensing. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND -ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. -IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, -INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT -NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, -WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) -ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY -OF SUCH DAMAGE. + Copyright (c) 2021, Hippocrates Technologies S.r.l.. All rights reserved. + + Redistribution and use in source and binary forms, with or without modification, + are permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation and/or + other materials provided with the distribution. + + 3. Neither the name of the copyright holder(s) nor the names of any contributor(s) may + be used to endorse or promote products derived from this software without specific + prior written permission. No license is granted to the trademarks of the copyright + holders even if such marks are included in this software. + + 4. Commercial redistribution in any form requires an explicit license agreement with the + copyright holder(s). Please contact support@hippocratestech.com for further information + regarding licensing. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, + INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, + OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY + OF SUCH DAMAGE. */ import OTFResearchKit class PasswordlessLoginStep: ORKFormStep { - + static let identifier = "Login" - + static let idStepIdentifier = "IdStep" static let idConfirmStepIdentifier = "ConfirmIdStep" - + override init(identifier: String) { super.init(identifier: identifier) - + title = ModuleAppYmlReader().loginStepTitle text = ModuleAppYmlReader().loginStepText - + formItems = createFormItems() isOptional = false } - + required init(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } - + /** This function creates a form with the exact email question to ask. - + - Returns a `ORKFormItem` array with questions to show. - */ + */ fileprivate func createFormItems() -> [ORKFormItem] { let idStepTitle = "Email:" - + let titleStep = ORKFormItem(sectionTitle: "✉️ 🌎") - + let idQuestionStep = ORKFormItem(identifier: PasswordlessLoginStep.idStepIdentifier, text: idStepTitle, answerFormat: ORKEmailAnswerFormat(), optional: false) - + return [titleStep, idQuestionStep] } - + } diff --git a/OTFMagicBox/Profile/ProfileUIView.swift b/OTFMagicBox/Profile/ProfileUIView.swift index efc169e0..d5cfca70 100644 --- a/OTFMagicBox/Profile/ProfileUIView.swift +++ b/OTFMagicBox/Profile/ProfileUIView.swift @@ -1,25 +1,25 @@ /* Copyright (c) 2021, Hippocrates Technologies S.r.l.. All rights reserved. - + Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: - + 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. - + 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. - + 3. Neither the name of the copyright holder(s) nor the names of any contributor(s) may be used to endorse or promote products derived from this software without specific prior written permission. No license is granted to the trademarks of the copyright holders even if such marks are included in this software. - + 4. Commercial redistribution in any form requires an explicit license agreement with the copyright holder(s). Please contact support@hippocratestech.com for further information regarding licensing. - + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. @@ -37,7 +37,7 @@ import OTFCareKitStore import Sodium struct ProfileUIView: View { - + @State private(set) var user: OCKPatient? @State var isLoading = true @State private var isPresenting = false @@ -45,7 +45,7 @@ struct ProfileUIView: View { let manager = UploadDocumentManager() @StateObject private var viewModel = UpdateUserViewModel() @State private var isPresentingEditUser: Bool = false - var hint : String? + var hint: String? var userName: String { guard let givenName = user?.name.givenName, @@ -54,136 +54,149 @@ struct ProfileUIView: View { } return "\(givenName) \(familyName)" } - + var body: some View { - + NavigationView { VStack { Text(ModuleAppYmlReader().profileData?.title ?? Constants.CustomiseStrings.profile) - .foregroundColor(Color(YmlReader().appTheme?.textColor.color ?? UIColor.black)) - .font(YmlReader().appTheme?.screenTitleFont.appFont ?? Font.system(size: 17.0)) - .fontWeight(YmlReader().appTheme?.textWeight.fontWeight) - - - List { - Section { - if let user = user { - UpdateUserProfileView(user: user, backgroudColor: YmlReader().appTheme?.cellbackgroundColor.color ?? .black, tColor: YmlReader().appTheme?.textColor.color ?? .black, cellBackgroundColor: YmlReader().appTheme?.cellbackgroundColor.color ?? .black, headerColor: YmlReader().appTheme?.headerColor.color ?? .black, buttonColor: YmlReader().appTheme?.buttonTextColor.color ?? .black, borderColor: YmlReader().appTheme?.borderColor.color ?? .black, sepratorColor: YmlReader().appTheme?.separatorColor.color ?? .black) - } else { - LoadingView(username: "") - } + .foregroundColor(.otfTextColor) + .font(Font.otfscreenTitleFont) + .fontWeight(Font.otfFontWeight) + + List { + Section { + if let user = user { + UpdateUserProfileView(user: user) + } else { + LoadingView(username: "") } - .listRowBackground(Color.otfCellBackground) - + } + .listRowBackground(Color.otfCellBackground) + + Section { + if ModuleAppYmlReader().isPasscodeEnabled { + ChangePasscodeView() + } + HelpView(site: YmlReader().teamWebsite, title: ModuleAppYmlReader().profileData?.help ?? Constants.CustomiseStrings.help) + } + .listRowBackground(Color.otfCellBackground) + + if let email = user?.remoteID { Section { - if ModuleAppYmlReader().isPasscodeEnabled { - ChangePasscodeView() - } - HelpView(site: YmlReader().teamWebsite, title: ModuleAppYmlReader().profileData?.help ?? Constants.CustomiseStrings.help, textColor: Color(YmlReader().appTheme?.textColor.color ?? .black)) + ChangePasswordView(email: email, resetPassword: ModuleAppYmlReader().profileData?.resetPasswordText ?? Constants.CustomiseStrings.resetPassword) } .listRowBackground(Color.otfCellBackground) - - if let email = user?.remoteID { - Section { - ChangePasswordView(email: email, resetPassword: ModuleAppYmlReader().profileData?.resetPasswordText ?? Constants.CustomiseStrings.resetPassword) - } - .listRowBackground(Color.otfCellBackground) - } - - Section(header: Text(ModuleAppYmlReader().profileData?.reportProblemHeader ?? Constants.CustomiseStrings.reportProblem) - .font(YmlReader().appTheme?.headerTitleFont.appFont ?? Font.system(size: 17.0)) - .fontWeight(YmlReader().appTheme?.headerTitleWeight.fontWeight) - .foregroundColor(.otfHeaderColor)) { - ReportView(email: YmlReader().teamEmail, title: ModuleAppYmlReader().profileData?.reportProblemText ?? Constants.CustomiseStrings.reportProblem) - SupportView(phone: YmlReader().teamPhone, title: ModuleAppYmlReader().profileData?.supportText ?? Constants.CustomiseStrings.support) - } - .listRowBackground(Color.otfCellBackground) - - - if let user = user { - if let attachmentID = user.attachments?.ConsentForm?.attachmentID, let hashFileKey = user.attachments?.ConsentForm?.hashFileKey { - let doc = document.retriveFile(fileName: attachmentID) - if doc != nil { - Section { - NavigationLink(destination: PDFViewer(pdfData: manager.decryptedFile(file: doc!, hashFileKey: hashFileKey)) - ,label: { - ConsentDocumentView(title: ModuleAppYmlReader().profileData?.consentText ?? Constants.CustomiseStrings.consetDocument) - }) - .buttonStyle(PlainButtonStyle()).foregroundColor(Color.clear) - }.listRowBackground(Color.otfCellBackground) - + } + + Section(header: Text(ModuleAppYmlReader().profileData?.reportProblemHeader ?? Constants.CustomiseStrings.reportProblem) + .font(.otfheaderTitleFont) + .fontWeight(Font.otfheaderTitleWeight) + .foregroundColor(.otfHeaderColor)) { + ReportView(email: YmlReader().teamEmail, title: ModuleAppYmlReader().profileData?.reportProblemText ?? Constants.CustomiseStrings.reportProblem) + SupportView(phone: YmlReader().teamPhone, title: ModuleAppYmlReader().profileData?.supportText ?? Constants.CustomiseStrings.support) + } + .listRowBackground(Color.otfCellBackground) + + if let user = user { + if let attachmentID = user.attachments?.consentForm?.attachmentID, let hashFileKey = user.attachments?.consentForm?.hashFileKey { + let doc = document.retriveFile(fileName: attachmentID) + if doc != nil { + Section { + ZStack { + NavigationLink(destination: PDFViewer( + pdfData: + KeychainCloudManager.isKeyStored(key: KeychainKeys.defaultStorageKey) ? manager.decryptedFile(file: doc!, hashFileKey: hashFileKey) : doc!), label: { + EmptyView() + }).opacity(0.0) + ConsentDocumentView(title: ModuleAppYmlReader().profileData?.consentText ?? Constants.CustomiseStrings.consetDocument) } - } + }.listRowBackground(Color.otfCellBackground) } - - - Section { - WithdrawView(title: ModuleAppYmlReader().profileData?.WithdrawStudyText ?? Constants.CustomiseStrings.withdrawFromStudy, textColor: Color(YmlReader().appTheme?.textColor.color ?? UIColor.black)) - } - .listRowBackground(Color.otfCellBackground) - Section { - Text(YmlReader().teamCopyright) - .foregroundColor(Color(YmlReader().appTheme?.textColor.color ?? UIColor.black)) - .font(YmlReader().appTheme?.textFont.appFont ?? Font.system(size: 17.0)) - .fontWeight(YmlReader().appTheme?.textWeight.fontWeight) - } - .listRowBackground(Color.otfCellBackground) - Section { - LogoutView() } - .listRowBackground(Color.otfCellBackground) + } - Section { - if let user = user{ - DeleteAccountView(user: user, textColor: Color(YmlReader().appTheme?.buttonTextColor.color ?? UIColor.black)) - } - } - .listRowBackground(Color.otfCellBackground) + Section { + WithdrawView(title: ModuleAppYmlReader().profileData?.withdrawStudyText ?? Constants.CustomiseStrings.withdrawFromStudy) + } + .listRowBackground(Color.otfCellBackground) + Section { + Text(YmlReader().teamCopyright) + .foregroundColor(.otfTextColor) + .font(Font.otfAppFont) + .fontWeight(Font.otfFontWeight) } - .listStyle(.insetGrouped) - .onLoad { - DispatchQueue.main.asyncAfter(deadline: .now() + 0.3) { - fetchUserFromDB() + .listRowBackground(Color.otfCellBackground) + + Section { + LogoutView() + }.listRowBackground(Color.otfCellBackground) + + Section { + if let user = user { + DeleteAccountView(user: user) } - UITableView.appearance().backgroundColor = YmlReader().appTheme?.backgroundColor.color - UITableViewCell.appearance().backgroundColor = YmlReader().appTheme?.backgroundColor.color - UITableView.appearance().separatorColor = YmlReader().appTheme?.separatorColor.color } - .onReceive(NotificationCenter.default.publisher(for: .databaseSuccessfllySynchronized)) { notification in + .listRowBackground(Color.otfCellBackground) + } + .listStyle(.insetGrouped) + .onLoad { + DispatchQueue.main.asyncAfter(deadline: .now() + 0.3) { fetchUserFromDB() } - .onReceive(NotificationCenter.default.publisher(for: .deleteUserAccount)) { notification in - isPresenting = true - } - + UITableView.appearance().backgroundColor = YmlReader().appStyle.backgroundColor.color + UITableViewCell.appearance().backgroundColor = YmlReader().appStyle.backgroundColor.color + UITableView.appearance().separatorColor = YmlReader().appStyle.separatorColor.color + } + .onReceive(NotificationCenter.default.publisher(for: .databaseSuccessfllySynchronized)) { _ in + fetchUserFromDB() + } + .onReceive(NotificationCenter.default.publisher(for: .deleteUserAccount)) { _ in + isPresenting = true + } } - .background(Color(YmlReader().appTheme?.backgroundColor.color ?? UIColor.black)) + .background(Color(YmlReader().appStyle.backgroundColor.color ?? UIColor.black)) .alert(isPresented: $isPresenting) { Alert( title: Text(Constants.CustomiseStrings.accountDeleted) - .font(YmlReader().appTheme?.textFont.appFont ?? Font.system(size: 17.0)) - .fontWeight(YmlReader().appTheme?.textWeight.fontWeight), + .font(Font.otfAppFont) + .fontWeight(Font.otfFontWeight), message: Text(Constants.deleteAccount), dismissButton: .default(Text(Constants.CustomiseStrings.okay), action: { OTFTheraforgeNetwork.shared.moveToOnboardingView() }) ) } + .onAppear { + +// let careKitManager = CareKitManager.shared +// careKitManager.cloudantStore?.populateSampleData() +// +// careKitManager.cloudantStore?.fetchTasks(completion: { result in +// switch result { +// case .success(let tasks): +// print(tasks) +// +// case .failure(_): +// print("Error ==> ") +// } +// }) + } .onDisappear { NotificationCenter.default.removeObserver(self, name: .deleteUserAccount, object: nil) } - .background(Color.otfCellBackground) + .background(Color.otfCellBackground) } } - + func fetchUserFromDB() { - CareKitManager.shared.cloudantStore?.getThisPatient({ result in + CareKitStoreManager.shared.cloudantStore?.getThisPatient({ result in if case .success(let patient) = result { self.user = patient - if let attachmentID = self.user?.attachments?.Profile?.attachmentID { + if let attachmentID = self.user?.attachments?.profile?.attachmentID { viewModel.downloadFile(attachmentID: attachmentID, isProfile: true) } - if let consentFormId = self.user?.attachments?.ConsentForm?.attachmentID { + if let consentFormId = self.user?.attachments?.consentForm?.attachmentID { viewModel.downloadFile(attachmentID: consentFormId, isProfile: false) } } diff --git a/OTFMagicBox/Profile/ViewModel/ChangePasswordViewModel.swift b/OTFMagicBox/Profile/ViewModel/ChangePasswordViewModel.swift index dca8b589..f23aa342 100644 --- a/OTFMagicBox/Profile/ViewModel/ChangePasswordViewModel.swift +++ b/OTFMagicBox/Profile/ViewModel/ChangePasswordViewModel.swift @@ -9,20 +9,15 @@ import Foundation import OTFCloudClientAPI import OTFUtilities import Combine - - final class ChangePasswordViewModel: ObservableObject { - @Published var showFailureAlert = false @Published var errorMessage = String() @Published var oldPassword: String = "" @Published var newPassword: String = "" @Published var email: String - init(email: String) { self.email = email } - var error: ForgeError? var viewDismissModePublisher = PassthroughSubject() private var disposables = Set() @@ -31,8 +26,7 @@ final class ChangePasswordViewModel: ObservableObject { viewDismissModePublisher.send(shouldDismissView) } } - -// MARK: chnage password request + // MARK: chnage password request func changePassword() { OTFTheraforgeNetwork.shared.changePassword(email: email, oldPassword: oldPassword, newPassword: newPassword) .receive(on: DispatchQueue.main) @@ -51,4 +45,3 @@ final class ChangePasswordViewModel: ObservableObject { .store(in: &disposables) } } - diff --git a/OTFMagicBox/Profile/ViewModel/DeleteAccountViewModel.swift b/OTFMagicBox/Profile/ViewModel/DeleteAccountViewModel.swift index 53f5245d..0c12f91e 100644 --- a/OTFMagicBox/Profile/ViewModel/DeleteAccountViewModel.swift +++ b/OTFMagicBox/Profile/ViewModel/DeleteAccountViewModel.swift @@ -11,13 +11,13 @@ import OTFUtilities import Combine final class DeleteAccountViewModel: ObservableObject { - + @Published var showingAlert = false @Published var showingOptions = false - + private var disposables = Set() - -// MARK: delete account request + + // MARK: delete account request func deleteUserAccount(userId: String) { OTFTheraforgeNetwork.shared.deleteUser(userId: userId) .receive(on: DispatchQueue.main) diff --git a/OTFMagicBox/Profile/ViewModel/LogoutViewModel.swift b/OTFMagicBox/Profile/ViewModel/LogoutViewModel.swift index c635b542..9083b9f3 100644 --- a/OTFMagicBox/Profile/ViewModel/LogoutViewModel.swift +++ b/OTFMagicBox/Profile/ViewModel/LogoutViewModel.swift @@ -9,17 +9,17 @@ import Foundation import OTFCloudClientAPI import OTFUtilities import Combine +import WatchConnectivity final class LogoutViewModel: ObservableObject { - - // MARK: - PROPERTY - + + // MARK: - PROPERTY @Published var showingAlert = false @Published var showingOptions = false - + private var disposables = Set() - -// MARK: signout request + + // MARK: signout request func signout() { OTFTheraforgeNetwork.shared.signOut() .receive(on: DispatchQueue.main) @@ -28,12 +28,13 @@ final class LogoutViewModel: ObservableObject { case .failure(let error): self.showingAlert = true OTFError("error in signout request -> %{public}s.", error.error.message) + default: break } - } receiveValue: { data in - OTFLog("data retrieved -> %{public}s.", data.message) + } receiveValue: { _ in + WCSession.default.sendMessage(["userNotLoggedIn": "true"]) { _ in } + OTFTheraforgeNetwork.shared.moveToOnboardingView() } .store(in: &disposables) } } - diff --git a/OTFMagicBox/Profile/ViewModel/UpdateUserViewModel.swift b/OTFMagicBox/Profile/ViewModel/UpdateUserViewModel.swift index 73d29b83..b2b82b36 100644 --- a/OTFMagicBox/Profile/ViewModel/UpdateUserViewModel.swift +++ b/OTFMagicBox/Profile/ViewModel/UpdateUserViewModel.swift @@ -16,68 +16,66 @@ struct ProfileDetaiDataModel { var showGenderPicker = false var showDatePicker = false } - - final class UpdateUserViewModel: ObservableObject { - + @Published var profileDetaiDataModel: ProfileDetaiDataModel = ProfileDetaiDataModel() private var disposables = Set() var patientPublisher = PassthroughSubject() var profileImageData = PassthroughSubject() var hideLoader = PassthroughSubject() let swiftSodium = SwiftSodium() - + private var shouldDismissView = false { didSet { hideLoader.send(shouldDismissView) } } - + private var patient: OCKPatient? { didSet { patientPublisher.send(patient!) } } - + private var profileData: Data? { didSet { profileImageData.send(profileData!) } } - -// MARK: Fetch OCKPatient + + // MARK: Fetch OCKPatient func fetchPatient(userId: String) { - - CareKitManager.shared.cloudantStore?.fetchPatient(withID: userId, completion: { result in + + CareKitStoreManager.shared.cloudantStore?.fetchPatient(withID: userId, completion: { _ in self.profileDataPublisher(userId: userId) .receive(on: DispatchQueue.main) - .sink(receiveCompletion: {print ("Received completion: \($0).")}, + .sink(receiveCompletion: { print("Received completion: \($0).") }, receiveValue: {patient in - self.patient = patient - }) + self.patient = patient + }) .store(in: &self.disposables) }) } - + func profileDataPublisher(userId: String) -> AnyPublisher { - return Future { promise in - CareKitManager.shared.cloudantStore?.fetchPatient(withID: userId, completion: { result in - if case .success(let patient) = result { - return promise(.success(patient)) - } - }) - } - .receive(on: RunLoop.main) - .eraseToAnyPublisher() - } - -// MARK: update OCKPatient + return Future { promise in + CareKitStoreManager.shared.cloudantStore?.fetchPatient(withID: userId, completion: { result in + if case .success(let patient) = result { + return promise(.success(patient)) + } + }) + } + .receive(on: RunLoop.main) + .eraseToAnyPublisher() + } + + // MARK: update OCKPatient func updatePatient(user: OCKPatient) { - CareKitManager.shared.cloudantStore?.updatePatient(user) + CareKitStoreManager.shared.cloudantStore?.updatePatient(user) } - -// MARK: upload file request - func uploadFile(data: Data, fileName: String, encryptedFileKey: String? = nil , hashFileKey: String) { + + // MARK: upload file request + func uploadFile(data: Data, fileName: String, encryptedFileKey: String? = nil, hashFileKey: String) { OTFTheraforgeNetwork.shared.uploadFile(data: data, fileName: fileName, type: .profile, encryptedFileKey: encryptedFileKey, hashFileKey: hashFileKey) .receive(on: DispatchQueue.main) .sink { res in @@ -94,20 +92,20 @@ final class UpdateUserViewModel: ObservableObject { } .store(in: &disposables) } - - func showProfileImage(user : OCKPatient, imageData: Data) -> UIImage { - - if let encryptedFileKey = user.attachments?.Profile?.encryptedFileKey, let hashFileKey = user.attachments?.Profile?.hashFileKey, !encryptedFileKey.isEmpty { + + func showProfileImage(user: OCKPatient, imageData: Data) -> UIImage { + + if let encryptedFileKey = user.attachments?.profile?.encryptedFileKey, let hashFileKey = user.attachments?.profile?.hashFileKey, !encryptedFileKey.isEmpty { let image = dataToImage(data: imageData, hashFileKey: hashFileKey) - return image + return image } else { - let image = dataToImageWithoutDecryption(data: imageData, key: user.attachments?.Profile?.hashFileKey) + let image = dataToImageWithoutDecryption(data: imageData, key: user.attachments?.profile?.hashFileKey) return image } } - -//MARK: downlaod file request - func downloadFile(attachmentID: String, isProfile : Bool = false) { + + // MARK: downlaod file request + func downloadFile(attachmentID: String, isProfile: Bool = false) { OTFTheraforgeNetwork.shared.downloadFile(attachmentID: attachmentID, type: .profile) .receive(on: DispatchQueue.main) .sink { res in @@ -120,21 +118,21 @@ final class UpdateUserViewModel: ObservableObject { let fileData = data.data fileData.saveFileToDocument(data: fileData, filename: data.metadata.attachmentID) if isProfile { - let dataDict:[String: Data] = ["imageData": fileData] + let dataDict: [String: Data] = ["imageData": fileData] NotificationCenter.default.post(name: .imageDownloaded, object: dataDict) } } .store(in: &disposables) } - - //MARK: decrypt encrypted data + + // MARK: decrypt encrypted data func decryptedFile(file: Data, hashFileKey: String) -> Data { - let dataToBytes = swiftSodium.getArrayOfBytesFromData(FileData: file as NSData) + let dataToBytes = swiftSodium.getArrayOfBytesFromData(fileData: file as NSData) let fileKey = swiftSodium.generateDeriveKey(key: KeychainCloudManager.getDefaultStorageKey) - + let hashKey = swiftSodium.generateGenericHashWithKey(message: dataToBytes, fileKey: fileKey) let hashKeyHex = hashKey.bytesToHex(spacing: "").lowercased() - + if hashKeyHex.contains(hashFileKey) { let (header, encryptedFile) = dataToBytes.splitFile() let encryption = swiftSodium.decryptFile(secretKey: fileKey, header: header, encryptedFile: encryptedFile) @@ -145,11 +143,9 @@ final class UpdateUserViewModel: ObservableObject { } return Data() - + } - - -//MARK: decrypt data and convert data to UIImage + // MARK: decrypt data and convert data to UIImage func dataToImage(data: Data, hashFileKey: String) -> UIImage { let decryptedData = decryptedFile(file: data, hashFileKey: hashFileKey) if let image = UIImage(data: decryptedData) { @@ -157,46 +153,42 @@ final class UpdateUserViewModel: ObservableObject { } return UIImage() } - + func dataToImageWithoutDecryption(data: Data, key: String?) -> UIImage { - let imageToBytes = swiftSodium.getArrayOfBytesFromData(FileData: data as NSData) + let imageToBytes = swiftSodium.getArrayOfBytesFromData(fileData: data as NSData) let hashFileKey = swiftSodium.generateGenericHashWithoutKey(message: imageToBytes) - + let hashFileKeyHex = hashFileKey.bytesToHex(spacing: "").lowercased() if let keyhex = key, hashFileKeyHex.contains(keyhex), - let image = UIImage(data: data) { + let image = UIImage(data: data) { return image } return UIImage() } - -//MARK: Delete file request + + // MARK: Delete file request func deleteAttachment(attachmentID: String) { OTFTheraforgeNetwork.shared.deleteFile(attachmentID: attachmentID) .receive(on: DispatchQueue.main) .sink { res in switch res { case .failure: - self.shouldDismissView = true + self.shouldDismissView = true default: break } - } receiveValue: { data in + } receiveValue: { _ in self.synchronizeDatabase() self.shouldDismissView = true } .store(in: &disposables) } - -//MARK: delete file from document directory + + // MARK: delete file from document directory func deleteFileFromDocument(fileName: String) { - deleteFile(filename: fileName) + try? FileManager.deleteFile(filename: fileName) } - + public func synchronizeDatabase() { NotificationCenter.default.post(name: .databaseSuccessfllySynchronized, object: nil) } } - - - - diff --git a/OTFMagicBox/Profile/Views/ChangePasscodeView.swift b/OTFMagicBox/Profile/Views/ChangePasscodeView.swift index a6ef7967..1bd59fb0 100644 --- a/OTFMagicBox/Profile/Views/ChangePasscodeView.swift +++ b/OTFMagicBox/Profile/Views/ChangePasscodeView.swift @@ -1,35 +1,35 @@ /* -Copyright (c) 2021, Hippocrates Technologies S.r.l.. All rights reserved. + Copyright (c) 2021, Hippocrates Technologies S.r.l.. All rights reserved. -Redistribution and use in source and binary forms, with or without modification, -are permitted provided that the following conditions are met: + Redistribution and use in source and binary forms, with or without modification, + are permitted provided that the following conditions are met: -1. Redistributions of source code must retain the above copyright notice, -this list of conditions and the following disclaimer. + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. -2. Redistributions in binary form must reproduce the above copyright notice, -this list of conditions and the following disclaimer in the documentation and/or -other materials provided with the distribution. + 2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation and/or + other materials provided with the distribution. -3. Neither the name of the copyright holder(s) nor the names of any contributor(s) may -be used to endorse or promote products derived from this software without specific -prior written permission. No license is granted to the trademarks of the copyright -holders even if such marks are included in this software. + 3. Neither the name of the copyright holder(s) nor the names of any contributor(s) may + be used to endorse or promote products derived from this software without specific + prior written permission. No license is granted to the trademarks of the copyright + holders even if such marks are included in this software. -4. Commercial redistribution in any form requires an explicit license agreement with the -copyright holder(s). Please contact support@hippocratestech.com for further information -regarding licensing. + 4. Commercial redistribution in any form requires an explicit license agreement with the + copyright holder(s). Please contact support@hippocratestech.com for further information + regarding licensing. -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND -ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. -IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, -INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT -NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, -WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) -ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY -OF SUCH DAMAGE. + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, + INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, + OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY + OF SUCH DAMAGE. */ import SwiftUI @@ -37,23 +37,23 @@ import OTFResearchKit struct ChangePasscodeView: View { @State var showPasscode = false - + var body: some View { HStack { Text(Constants.CustomiseStrings.changePasscode) - .font(YmlReader().appTheme?.textFont.appFont ?? Font.system(size: 17.0)) - .fontWeight(YmlReader().appTheme?.textWeight.fontWeight) + .font(Font.otfAppFont) + .fontWeight(Font.otfFontWeight) Spacer() Image(systemName: "chevron.right") - .foregroundColor(Color(UIColor.tertiaryLabel)) - .font(.footnote.weight(.semibold)) + .foregroundColor(Color(UIColor.tertiaryLabel)) + .font(.footnote.weight(.semibold)) }.frame(height: Metrics.MAIN_VIEW_HEIGHT).contentShape(Rectangle()) - .gesture(TapGesture().onEnded({ - if ORKPasscodeViewController.isPasscodeStoredInKeychain() { - self.showPasscode.toggle() - } + .gesture(TapGesture().onEnded({ + if ORKPasscodeViewController.isPasscodeStoredInKeychain() { + self.showPasscode.toggle() + } })).sheet(isPresented: $showPasscode, onDismiss: { - + }, content: { PasscodeViewController() }) diff --git a/OTFMagicBox/Profile/Views/ChangePasswordView.swift b/OTFMagicBox/Profile/Views/ChangePasswordView.swift index ddd4770a..98a099a2 100644 --- a/OTFMagicBox/Profile/Views/ChangePasswordView.swift +++ b/OTFMagicBox/Profile/Views/ChangePasswordView.swift @@ -1,35 +1,35 @@ /* -Copyright (c) 2021, Hippocrates Technologies S.r.l.. All rights reserved. + Copyright (c) 2021, Hippocrates Technologies S.r.l.. All rights reserved. -Redistribution and use in source and binary forms, with or without modification, -are permitted provided that the following conditions are met: + Redistribution and use in source and binary forms, with or without modification, + are permitted provided that the following conditions are met: -1. Redistributions of source code must retain the above copyright notice, -this list of conditions and the following disclaimer. + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. -2. Redistributions in binary form must reproduce the above copyright notice, -this list of conditions and the following disclaimer in the documentation and/or -other materials provided with the distribution. + 2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation and/or + other materials provided with the distribution. -3. Neither the name of the copyright holder(s) nor the names of any contributor(s) may -be used to endorse or promote products derived from this software without specific -prior written permission. No license is granted to the trademarks of the copyright -holders even if such marks are included in this software. + 3. Neither the name of the copyright holder(s) nor the names of any contributor(s) may + be used to endorse or promote products derived from this software without specific + prior written permission. No license is granted to the trademarks of the copyright + holders even if such marks are included in this software. -4. Commercial redistribution in any form requires an explicit license agreement with the -copyright holder(s). Please contact support@hippocratestech.com for further information -regarding licensing. + 4. Commercial redistribution in any form requires an explicit license agreement with the + copyright holder(s). Please contact support@hippocratestech.com for further information + regarding licensing. -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND -ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. -IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, -INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT -NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, -WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) -ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY -OF SUCH DAMAGE. + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, + INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, + OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY + OF SUCH DAMAGE. */ import Foundation @@ -38,21 +38,21 @@ import OTFUtilities // This view creates the section in the Profile view, which navigates to the another page where we can reset the password. struct ChangePasswordView: View { - + let email: String let resetPassword: String @State var showResetPassword = false - + var body: some View { HStack { Text(resetPassword) .foregroundColor(.otfTextColor) - .font(YmlReader().appTheme?.textFont.appFont ?? Font.system(size: 17.0)) - .fontWeight(YmlReader().appTheme?.textWeight.fontWeight) + .font(Font.otfAppFont) + .fontWeight(Font.otfFontWeight) Spacer() Image(systemName: "chevron.right") - .foregroundColor(Color(UIColor.tertiaryLabel)) - .font(.footnote.weight(.semibold)) + .foregroundColor(Color(UIColor.tertiaryLabel)) + .font(.footnote.weight(.semibold)) }.frame(height: Metrics.TITLE_VIEW_HEIGHT) .contentShape(Rectangle()) .gesture(TapGesture().onEnded { @@ -67,37 +67,26 @@ struct ChangePasswordView: View { // View where we can reset the password. struct ChangePasswordDeatilsView: View { - @Environment(\.presentationMode) var presentationMode: Binding @StateObject var viewModel: ChangePasswordViewModel - - var body: some View { VStack { - + Spacer() - Image.theraforgeLogo.logoStyle() - Spacer() - TextField(Constants.CustomiseStrings.email, text: $viewModel.email) .style(.emailField) .foregroundColor(.otfTextColor) .disabled(true) - .font(YmlReader().appTheme?.textFont.appFont ?? Font.system(size: 17.0)) - - + .font(Font.otfAppFont) SecureField(ModuleAppYmlReader().profileData?.oldPassword ?? Constants.CustomiseStrings.oldPassword, text: $viewModel.oldPassword) .style(.secureField) .foregroundColor(.otfTextColor) - SecureField(ModuleAppYmlReader().profileData?.newPassword ?? Constants.CustomiseStrings.newPassword, text: $viewModel.newPassword) .style(.secureField) .foregroundColor(.otfTextColor) - Spacer() - Button(action: { viewModel.changePassword() }, label: { @@ -112,15 +101,17 @@ struct ChangePasswordDeatilsView: View { }) .padding() .alert(isPresented: $viewModel.showFailureAlert, content: ({ - Alert(title: Text(Constants.CustomiseStrings.passwordResetError).font(YmlReader().appTheme?.textFont.appFont ?? Font.system(size: 17.0)) - .fontWeight(YmlReader().appTheme?.textWeight.fontWeight), message: Text(viewModel.errorMessage), dismissButton: .default(Text(Constants.CustomiseStrings.okay))) + Alert(title: Text(Constants.CustomiseStrings.passwordResetError).font(Font.otfAppFont) + .fontWeight(Font.otfFontWeight), + message: Text(viewModel.errorMessage), + dismissButton: .default(Text(Constants.CustomiseStrings.okay))) })) - + Spacer() } .background(Color.otfCellBackground) .onReceive(viewModel.viewDismissModePublisher) { shouldDismiss in - if shouldDismiss{ + if shouldDismiss { self.presentationMode.wrappedValue.dismiss() } } diff --git a/OTFMagicBox/Profile/Views/ConsentDocumentView.swift b/OTFMagicBox/Profile/Views/ConsentDocumentView.swift index 79d106cb..8f48a484 100644 --- a/OTFMagicBox/Profile/Views/ConsentDocumentView.swift +++ b/OTFMagicBox/Profile/Views/ConsentDocumentView.swift @@ -1,61 +1,60 @@ /* -Copyright (c) 2021, Hippocrates Technologies S.r.l.. All rights reserved. + Copyright (c) 2021, Hippocrates Technologies S.r.l.. All rights reserved. -Redistribution and use in source and binary forms, with or without modification, -are permitted provided that the following conditions are met: + Redistribution and use in source and binary forms, with or without modification, + are permitted provided that the following conditions are met: -1. Redistributions of source code must retain the above copyright notice, -this list of conditions and the following disclaimer. + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. -2. Redistributions in binary form must reproduce the above copyright notice, -this list of conditions and the following disclaimer in the documentation and/or -other materials provided with the distribution. + 2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation and/or + other materials provided with the distribution. -3. Neither the name of the copyright holder(s) nor the names of any contributor(s) may -be used to endorse or promote products derived from this software without specific -prior written permission. No license is granted to the trademarks of the copyright -holders even if such marks are included in this software. + 3. Neither the name of the copyright holder(s) nor the names of any contributor(s) may + be used to endorse or promote products derived from this software without specific + prior written permission. No license is granted to the trademarks of the copyright + holders even if such marks are included in this software. -4. Commercial redistribution in any form requires an explicit license agreement with the -copyright holder(s). Please contact support@hippocratestech.com for further information -regarding licensing. + 4. Commercial redistribution in any form requires an explicit license agreement with the + copyright holder(s). Please contact support@hippocratestech.com for further information + regarding licensing. -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND -ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. -IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, -INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT -NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, -WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) -ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY -OF SUCH DAMAGE. + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, + INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, + OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY + OF SUCH DAMAGE. */ import SwiftUI import OTFUtilities struct ConsentDocumentView: View { - + let title: String init(title: String) { self.title = title } - + var body: some View { HStack { - Text(title) - .foregroundColor(.otfTextColor) - .font(YmlReader().appTheme?.textFont.appFont ?? Font.system(size: 17.0)) - .fontWeight(YmlReader().appTheme?.textWeight.fontWeight) - Spacer() - Image(systemName: "chevron.right") - .foregroundColor(Color(UIColor.tertiaryLabel)) - .font(.footnote.weight(.semibold)) - } - .frame(height: Metrics.TITLE_VIEW_HEIGHT) - .contentShape(Rectangle()) - + Text(title) + .foregroundColor(.otfTextColor) + .font(Font.otfAppFont) + .fontWeight(Font.otfFontWeight) + Spacer() + Image(systemName: "chevron.right") + .foregroundColor(Color(UIColor.tertiaryLabel)) + .font(.footnote.weight(.semibold)) + } + .frame(maxWidth: .infinity, minHeight: Metrics.TITLE_VIEW_HEIGHT) + .contentShape(Rectangle()) } } diff --git a/OTFMagicBox/Profile/Views/DeleteAccountView.swift b/OTFMagicBox/Profile/Views/DeleteAccountView.swift index 7ebdf9fd..42a44e52 100644 --- a/OTFMagicBox/Profile/Views/DeleteAccountView.swift +++ b/OTFMagicBox/Profile/Views/DeleteAccountView.swift @@ -13,8 +13,7 @@ import OTFUtilities struct DeleteAccountView: View { @StateObject private var viewModel = DeleteAccountViewModel() @State private(set) var user: OCKPatient? - let textColor: Color - + var body: some View { HStack { Spacer() @@ -24,38 +23,38 @@ struct DeleteAccountView: View { Text(Constants.CustomiseStrings.deleteAccount) .font(.basicFontStyle) .foregroundColor(Color.red) - .font(YmlReader().appTheme?.textFont.appFont ?? Font.system(size: 17.0)) - .fontWeight(YmlReader().appTheme?.textWeight.fontWeight) + .font(Font.otfAppFont) + .fontWeight(Font.otfFontWeight) }) .actionSheet(isPresented: $viewModel.showingOptions) { ActionSheet( title: Text(Constants.CustomiseStrings.removeInformation) - .font(YmlReader().appTheme?.textFont.appFont ?? Font.system(size: 17.0)) - .fontWeight(YmlReader().appTheme?.textWeight.fontWeight), + .font(Font.otfAppFont) + .fontWeight(Font.otfFontWeight), buttons: [ .destructive(Text(Constants.CustomiseStrings.deleteAccount), action: { viewModel.deleteUserAccount(userId: user?.id ?? "") }), .cancel(Text(Constants.CustomiseStrings.cancel) - .fontWeight(YmlReader().appTheme?.textWeight.fontWeight) - .font(YmlReader().appTheme?.textFont.appFont ?? Font.system(size: 17.0))) + .fontWeight(Font.otfFontWeight) + .font(Font.otfAppFont)) ] ) } .alert(isPresented: $viewModel.showingAlert) { Alert(title: Text(Constants.CustomiseStrings.faliedToDeleteAccount) - .font(YmlReader().appTheme?.textFont.appFont ?? Font.system(size: 17.0)) - .fontWeight(YmlReader().appTheme?.textWeight.fontWeight), message: nil, dismissButton: .default(Text(Constants.CustomiseStrings.okay))) + .font(Font.otfAppFont) + .fontWeight(Font.otfFontWeight), message: nil, dismissButton: .default(Text(Constants.CustomiseStrings.okay))) } - + Spacer() } - + } } struct DeleteAccountView_Previews: PreviewProvider { static var previews: some View { - DeleteAccountView(user: nil, textColor: Color.red) + DeleteAccountView(user: nil) } } diff --git a/OTFMagicBox/Profile/Views/DocumentPreviewViewController.swift b/OTFMagicBox/Profile/Views/DocumentPreviewViewController.swift index e7d177d2..b204f742 100644 --- a/OTFMagicBox/Profile/Views/DocumentPreviewViewController.swift +++ b/OTFMagicBox/Profile/Views/DocumentPreviewViewController.swift @@ -1,35 +1,35 @@ /* -Copyright (c) 2021, Hippocrates Technologies S.r.l.. All rights reserved. + Copyright (c) 2021, Hippocrates Technologies S.r.l.. All rights reserved. -Redistribution and use in source and binary forms, with or without modification, -are permitted provided that the following conditions are met: + Redistribution and use in source and binary forms, with or without modification, + are permitted provided that the following conditions are met: -1. Redistributions of source code must retain the above copyright notice, -this list of conditions and the following disclaimer. + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. -2. Redistributions in binary form must reproduce the above copyright notice, -this list of conditions and the following disclaimer in the documentation and/or -other materials provided with the distribution. + 2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation and/or + other materials provided with the distribution. -3. Neither the name of the copyright holder(s) nor the names of any contributor(s) may -be used to endorse or promote products derived from this software without specific -prior written permission. No license is granted to the trademarks of the copyright -holders even if such marks are included in this software. + 3. Neither the name of the copyright holder(s) nor the names of any contributor(s) may + be used to endorse or promote products derived from this software without specific + prior written permission. No license is granted to the trademarks of the copyright + holders even if such marks are included in this software. -4. Commercial redistribution in any form requires an explicit license agreement with the -copyright holder(s). Please contact support@hippocratestech.com for further information -regarding licensing. + 4. Commercial redistribution in any form requires an explicit license agreement with the + copyright holder(s). Please contact support@hippocratestech.com for further information + regarding licensing. -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND -ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. -IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, -INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT -NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, -WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) -ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY -OF SUCH DAMAGE. + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, + INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, + OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY + OF SUCH DAMAGE. */ import UIKit @@ -38,7 +38,7 @@ import SwiftUI struct DocumentPreviewViewController: UIViewControllerRepresentable { private var isActive: Binding private let viewController = UIViewController() - private var docController: UIDocumentInteractionController? = nil + private var docController: UIDocumentInteractionController? init(_ isActive: Binding, url: URL?) { self.isActive = isActive @@ -77,4 +77,3 @@ struct DocumentPreviewViewController: UIViewControllerRepresentable { } } } - diff --git a/OTFMagicBox/Profile/Views/HelpView.swift b/OTFMagicBox/Profile/Views/HelpView.swift index c765d13c..32bed57d 100644 --- a/OTFMagicBox/Profile/Views/HelpView.swift +++ b/OTFMagicBox/Profile/Views/HelpView.swift @@ -1,35 +1,35 @@ /* -Copyright (c) 2021, Hippocrates Technologies S.r.l.. All rights reserved. + Copyright (c) 2021, Hippocrates Technologies S.r.l.. All rights reserved. -Redistribution and use in source and binary forms, with or without modification, -are permitted provided that the following conditions are met: + Redistribution and use in source and binary forms, with or without modification, + are permitted provided that the following conditions are met: -1. Redistributions of source code must retain the above copyright notice, -this list of conditions and the following disclaimer. + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. -2. Redistributions in binary form must reproduce the above copyright notice, -this list of conditions and the following disclaimer in the documentation and/or -other materials provided with the distribution. + 2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation and/or + other materials provided with the distribution. -3. Neither the name of the copyright holder(s) nor the names of any contributor(s) may -be used to endorse or promote products derived from this software without specific -prior written permission. No license is granted to the trademarks of the copyright -holders even if such marks are included in this software. + 3. Neither the name of the copyright holder(s) nor the names of any contributor(s) may + be used to endorse or promote products derived from this software without specific + prior written permission. No license is granted to the trademarks of the copyright + holders even if such marks are included in this software. -4. Commercial redistribution in any form requires an explicit license agreement with the -copyright holder(s). Please contact support@hippocratestech.com for further information -regarding licensing. + 4. Commercial redistribution in any form requires an explicit license agreement with the + copyright holder(s). Please contact support@hippocratestech.com for further information + regarding licensing. -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND -ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. -IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, -INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT -NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, -WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) -ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY -OF SUCH DAMAGE. + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, + INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, + OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY + OF SUCH DAMAGE. */ import SwiftUI @@ -37,26 +37,24 @@ import SwiftUI struct HelpView: View { var site = "" var title = "" - var textColor: Color - init(site: String, title: String, textColor: Color) { + init(site: String, title: String) { self.site = site self.title = title - self.textColor = textColor } - + var body: some View { HStack { Text(title) - .foregroundColor(textColor) - .font(YmlReader().appTheme?.textFont.appFont ?? Font.system(size: 17.0)) - .fontWeight(YmlReader().appTheme?.textWeight.fontWeight) + .foregroundColor(.otfTextColor) + .font(Font.otfAppFont) + .fontWeight(Font.otfFontWeight) Spacer() Image(systemName: "chevron.right") - .foregroundColor(Color(UIColor.tertiaryLabel)) - .font(.footnote.weight(.semibold)) + .foregroundColor(Color(UIColor.tertiaryLabel)) + .font(.footnote.weight(.semibold)) }.frame(height: Metrics.MAIN_VIEW_HEIGHT).contentShape(Rectangle()) - .gesture(TapGesture().onEnded({ - if let url = URL(string: self.site) { + .gesture(TapGesture().onEnded({ + if let url = URL(string: self.site) { UIApplication.shared.open(url) } })) @@ -65,6 +63,6 @@ struct HelpView: View { struct HelpView_Previews: PreviewProvider { static var previews: some View { - HelpView(site: "", title: "Help", textColor: Color(YmlReader().appTheme?.textColor.color ?? UIColor.black)) + HelpView(site: "", title: "Help") } } diff --git a/OTFMagicBox/Profile/Views/LogoutView.swift b/OTFMagicBox/Profile/Views/LogoutView.swift index d17589dd..bc225221 100644 --- a/OTFMagicBox/Profile/Views/LogoutView.swift +++ b/OTFMagicBox/Profile/Views/LogoutView.swift @@ -1,47 +1,48 @@ /* -Copyright (c) 2021, Hippocrates Technologies S.r.l.. All rights reserved. + Copyright (c) 2021, Hippocrates Technologies S.r.l.. All rights reserved. -Redistribution and use in source and binary forms, with or without modification, -are permitted provided that the following conditions are met: + Redistribution and use in source and binary forms, with or without modification, + are permitted provided that the following conditions are met: -1. Redistributions of source code must retain the above copyright notice, -this list of conditions and the following disclaimer. + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. -2. Redistributions in binary form must reproduce the above copyright notice, -this list of conditions and the following disclaimer in the documentation and/or -other materials provided with the distribution. + 2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation and/or + other materials provided with the distribution. -3. Neither the name of the copyright holder(s) nor the names of any contributor(s) may -be used to endorse or promote products derived from this software without specific -prior written permission. No license is granted to the trademarks of the copyright -holders even if such marks are included in this software. + 3. Neither the name of the copyright holder(s) nor the names of any contributor(s) may + be used to endorse or promote products derived from this software without specific + prior written permission. No license is granted to the trademarks of the copyright + holders even if such marks are included in this software. -4. Commercial redistribution in any form requires an explicit license agreement with the -copyright holder(s). Please contact support@hippocratestech.com for further information -regarding licensing. + 4. Commercial redistribution in any form requires an explicit license agreement with the + copyright holder(s). Please contact support@hippocratestech.com for further information + regarding licensing. -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND -ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. -IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, -INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT -NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, -WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) -ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY -OF SUCH DAMAGE. + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, + INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, + OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY + OF SUCH DAMAGE. */ import Foundation import SwiftUI import OTFUtilities +import WatchConnectivity struct LogoutView: View { - - // MARK: - PROPERTY - @StateObject private var viewModel = LogoutViewModel() - - // MARK: - BODY + + // MARK: - PROPERTY + @StateObject private var viewModel = LogoutViewModel() + + // MARK: - BODY var body: some View { HStack { Spacer() @@ -51,28 +52,28 @@ struct LogoutView: View { Text(Constants.CustomiseStrings.logout) .font(.basicFontStyle) .foregroundColor(.otfButtonColor) - .font(YmlReader().appTheme?.textFont.appFont ?? Font.system(size: 17.0)) - .fontWeight(YmlReader().appTheme?.textWeight.fontWeight) + .font(Font.otfAppFont) + .fontWeight(Font.otfFontWeight) }) .actionSheet(isPresented: $viewModel.showingOptions) { ActionSheet( title: Text(Constants.CustomiseStrings.areYouSure) - .font(YmlReader().appTheme?.textFont.appFont ?? Font.system(size: 17.0)) - .fontWeight(YmlReader().appTheme?.textWeight.fontWeight), + .font(Font.otfAppFont) + .fontWeight(Font.otfFontWeight), buttons: [ .destructive(Text(Constants.CustomiseStrings.logout), action: { viewModel.signout() }), .cancel(Text(Constants.CustomiseStrings.cancel) - .fontWeight(YmlReader().appTheme?.textWeight.fontWeight) - .font(YmlReader().appTheme?.textFont.appFont ?? Font.system(size: 17.0))) + .fontWeight(Font.otfFontWeight) + .font(Font.otfAppFont)) ] ) } .alert(isPresented: $viewModel.showingAlert) { Alert(title: Text(Constants.CustomiseStrings.failedToLogout) - .font(YmlReader().appTheme?.textFont.appFont ?? Font.system(size: 17.0)) - .fontWeight(YmlReader().appTheme?.textWeight.fontWeight), message: nil, dismissButton: .default(Text(Constants.CustomiseStrings.okay))) + .font(Font.otfAppFont) + .fontWeight(Font.otfFontWeight), message: nil, dismissButton: .default(Text(Constants.CustomiseStrings.okay))) } Spacer() } diff --git a/OTFMagicBox/Profile/Views/PDFKitRepresentedView.swift b/OTFMagicBox/Profile/Views/PDFKitRepresentedView.swift index 23cc51cc..361942cb 100644 --- a/OTFMagicBox/Profile/Views/PDFKitRepresentedView.swift +++ b/OTFMagicBox/Profile/Views/PDFKitRepresentedView.swift @@ -1,25 +1,25 @@ /* Copyright (c) 2021, Hippocrates Technologies S.r.l.. All rights reserved. - + Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: - + 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. - + 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. - + 3. Neither the name of the copyright holder(s) nor the names of any contributor(s) may be used to endorse or promote products derived from this software without specific prior written permission. No license is granted to the trademarks of the copyright holders even if such marks are included in this software. - + 4. Commercial redistribution in any form requires an explicit license agreement with the copyright holder(s). Please contact support@hippocratestech.com for further information regarding licensing. - + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. @@ -55,4 +55,3 @@ struct PDFKitRepresentedView: UIViewRepresentable { pdfView.document = PDFDocument(data: data) } } - diff --git a/OTFMagicBox/Profile/Views/PDFViewer.swift b/OTFMagicBox/Profile/Views/PDFViewer.swift index f66739f7..eda22b76 100644 --- a/OTFMagicBox/Profile/Views/PDFViewer.swift +++ b/OTFMagicBox/Profile/Views/PDFViewer.swift @@ -1,25 +1,25 @@ /* Copyright (c) 2021, Hippocrates Technologies S.r.l.. All rights reserved. - + Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: - + 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. - + 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. - + 3. Neither the name of the copyright holder(s) nor the names of any contributor(s) may be used to endorse or promote products derived from this software without specific prior written permission. No license is granted to the trademarks of the copyright holders even if such marks are included in this software. - + 4. Commercial redistribution in any form requires an explicit license agreement with the copyright holder(s). Please contact support@hippocratestech.com for further information regarding licensing. - + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. @@ -38,10 +38,10 @@ import UniformTypeIdentifiers import MobileCoreServices struct PDFViewer: View { - + let pdfData: Data @State private var isActionSheetPresented = false - + var body: some View { PDFKitRepresentedView(pdfData) .navigationBarTitle("TheraForge Consent", displayMode: .inline) @@ -49,19 +49,19 @@ struct PDFViewer: View { isActionSheetPresented = true }) .sheet(isPresented: $isActionSheetPresented) { - ShareSheet(activityItems: [pdfData]) + ShareSheet(activityItems: [pdfData]) } } } struct ShareSheet: UIViewControllerRepresentable { typealias Callback = (_ activityType: UIActivity.ActivityType?, _ completed: Bool, _ returnedItems: [Any]?, _ error: Error?) -> Void - + let activityItems: [Any] let applicationActivities: [UIActivity]? = nil let excludedActivityTypes: [UIActivity.ActivityType]? = nil let callback: Callback? = nil - + func makeUIViewController(context: Context) -> UIActivityViewController { let controller = UIActivityViewController( activityItems: activityItems, @@ -70,6 +70,6 @@ struct ShareSheet: UIViewControllerRepresentable { controller.completionWithItemsHandler = callback return controller } - + func updateUIViewController(_ uiViewController: UIActivityViewController, context: Context) {} } diff --git a/OTFMagicBox/Profile/Views/ReportView.swift b/OTFMagicBox/Profile/Views/ReportView.swift index 3e3433e3..e6b48bd0 100644 --- a/OTFMagicBox/Profile/Views/ReportView.swift +++ b/OTFMagicBox/Profile/Views/ReportView.swift @@ -1,35 +1,35 @@ /* -Copyright (c) 2021, Hippocrates Technologies S.r.l.. All rights reserved. - -Redistribution and use in source and binary forms, with or without modification, -are permitted provided that the following conditions are met: - -1. Redistributions of source code must retain the above copyright notice, -this list of conditions and the following disclaimer. - -2. Redistributions in binary form must reproduce the above copyright notice, -this list of conditions and the following disclaimer in the documentation and/or -other materials provided with the distribution. - -3. Neither the name of the copyright holder(s) nor the names of any contributor(s) may -be used to endorse or promote products derived from this software without specific -prior written permission. No license is granted to the trademarks of the copyright -holders even if such marks are included in this software. - -4. Commercial redistribution in any form requires an explicit license agreement with the -copyright holder(s). Please contact support@hippocratestech.com for further information -regarding licensing. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND -ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. -IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, -INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT -NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, -WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) -ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY -OF SUCH DAMAGE. + Copyright (c) 2021, Hippocrates Technologies S.r.l.. All rights reserved. + + Redistribution and use in source and binary forms, with or without modification, + are permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation and/or + other materials provided with the distribution. + + 3. Neither the name of the copyright holder(s) nor the names of any contributor(s) may + be used to endorse or promote products derived from this software without specific + prior written permission. No license is granted to the trademarks of the copyright + holders even if such marks are included in this software. + + 4. Commercial redistribution in any form requires an explicit license agreement with the + copyright holder(s). Please contact support@hippocratestech.com for further information + regarding licensing. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, + INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, + OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY + OF SUCH DAMAGE. */ import SwiftUI @@ -37,23 +37,23 @@ import SwiftUI struct ReportView: View { var email = "" var title = "" - + init(email: String, title: String) { self.email = email self.title = title } - + var body: some View { HStack { Text(title) - .fontWeight(YmlReader().appTheme?.textWeight.fontWeight) + .fontWeight(Font.otfFontWeight) .foregroundColor(.otfTextColor) - .font(YmlReader().appTheme?.textFont.appFont ?? Font.system(size: 17.0)) + .font(Font.otfAppFont) Spacer() Text(self.email) .foregroundColor(.otfTextColor) - .font(YmlReader().appTheme?.textFont.appFont ?? Font.system(size: 17.0)) - .fontWeight(YmlReader().appTheme?.textWeight.fontWeight) + .font(Font.otfAppFont) + .fontWeight(Font.otfFontWeight) .lineLimit(1) } .frame(maxWidth: .infinity, minHeight: Metrics.TITLE_VIEW_HEIGHT) @@ -65,7 +65,7 @@ struct ReportView: View { .accessibilityAddTraits(.isButton) .accessibilityLabel("Report a problem to \(email)") .accessibilityInputLabels([ModuleAppYmlReader().profileData?.reportProblemText ?? Constants.CustomiseStrings.reportProblem]) - + } } @@ -83,27 +83,26 @@ import MessageUI class EmailHelper: NSObject, MFMailComposeViewControllerDelegate { public static let shared = EmailHelper() - func sendEmail(subject:String, body:String, to:String){ + func sendEmail(subject: String, body: String, to: String) { if !MFMailComposeViewController.canSendMail() { return } - + let picker = MFMailComposeViewController() - + picker.setSubject(subject) picker.setMessageBody(body, isHTML: true) picker.setToRecipients([to]) picker.mailComposeDelegate = self - + EmailHelper.getRootViewController()?.present(picker, animated: true, completion: nil) } - + func mailComposeController(_ controller: MFMailComposeViewController, didFinishWith result: MFMailComposeResult, error: Error?) { EmailHelper.getRootViewController()?.dismiss(animated: true, completion: nil) } - + static func getRootViewController() -> UIViewController? { (UIApplication.shared.connectedScenes.first?.delegate as? SceneDelegate)?.window?.rootViewController } } - diff --git a/OTFMagicBox/Profile/Views/SupportView.swift b/OTFMagicBox/Profile/Views/SupportView.swift index fbf51437..f8bff37e 100644 --- a/OTFMagicBox/Profile/Views/SupportView.swift +++ b/OTFMagicBox/Profile/Views/SupportView.swift @@ -1,35 +1,35 @@ /* -Copyright (c) 2021, Hippocrates Technologies S.r.l.. All rights reserved. + Copyright (c) 2021, Hippocrates Technologies S.r.l.. All rights reserved. -Redistribution and use in source and binary forms, with or without modification, -are permitted provided that the following conditions are met: + Redistribution and use in source and binary forms, with or without modification, + are permitted provided that the following conditions are met: -1. Redistributions of source code must retain the above copyright notice, -this list of conditions and the following disclaimer. + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. -2. Redistributions in binary form must reproduce the above copyright notice, -this list of conditions and the following disclaimer in the documentation and/or -other materials provided with the distribution. + 2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation and/or + other materials provided with the distribution. -3. Neither the name of the copyright holder(s) nor the names of any contributor(s) may -be used to endorse or promote products derived from this software without specific -prior written permission. No license is granted to the trademarks of the copyright -holders even if such marks are included in this software. + 3. Neither the name of the copyright holder(s) nor the names of any contributor(s) may + be used to endorse or promote products derived from this software without specific + prior written permission. No license is granted to the trademarks of the copyright + holders even if such marks are included in this software. -4. Commercial redistribution in any form requires an explicit license agreement with the -copyright holder(s). Please contact support@hippocratestech.com for further information -regarding licensing. + 4. Commercial redistribution in any form requires an explicit license agreement with the + copyright holder(s). Please contact support@hippocratestech.com for further information + regarding licensing. -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND -ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. -IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, -INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT -NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, -WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) -ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY -OF SUCH DAMAGE. + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, + INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, + OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY + OF SUCH DAMAGE. */ import SwiftUI @@ -41,23 +41,23 @@ struct SupportView: View { self.phone = phone self.title = title } - + var body: some View { HStack { Text(title) .foregroundColor(.otfTextColor) - .font(YmlReader().appTheme?.textFont.appFont ?? Font.system(size: 17.0)) - .fontWeight(YmlReader().appTheme?.textWeight.fontWeight) + .font(Font.otfAppFont) + .fontWeight(Font.otfFontWeight) Spacer() Text(self.phone).foregroundColor(.otfTextColor) - .font(YmlReader().appTheme?.textFont.appFont ?? Font.system(size: 17.0)) - .fontWeight(YmlReader().appTheme?.textWeight.fontWeight) + .font(Font.otfAppFont) + .fontWeight(Font.otfFontWeight) } .frame(maxWidth: .infinity, minHeight: Metrics.TITLE_VIEW_HEIGHT) .contentShape(Rectangle()) .gesture(TapGesture().onEnded({ let telephone = "tel://" - let formattedString = telephone + self.phone + let formattedString = telephone + self.phone guard let url = URL(string: formattedString) else { return } UIApplication.shared.open(url) })) diff --git a/OTFMagicBox/Profile/Views/UpdateUserProfileDetailView.swift b/OTFMagicBox/Profile/Views/UpdateUserProfileDetailView.swift index 956d0a29..4ca256e9 100644 --- a/OTFMagicBox/Profile/Views/UpdateUserProfileDetailView.swift +++ b/OTFMagicBox/Profile/Views/UpdateUserProfileDetailView.swift @@ -1,25 +1,25 @@ /* Copyright (c) 2021, Hippocrates Technologies S.r.l.. All rights reserved. - + Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: - + 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. - + 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. - + 3. Neither the name of the copyright holder(s) nor the names of any contributor(s) may be used to endorse or promote products derived from this software without specific prior written permission. No license is granted to the trademarks of the copyright holders even if such marks are included in this software. - + 4. Commercial redistribution in any form requires an explicit license agreement with the copyright holder(s). Please contact support@hippocratestech.com for further information regarding licensing. - + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. @@ -39,21 +39,14 @@ import OTFCloudClientAPI import OTFUtilities struct UpdateUserProfileDetailView: View { - let backgroudColor: UIColor - let textColor: UIColor - let cellBackgroundColor: UIColor - let headerColor: UIColor - let buttonColor: UIColor - let borderColor: UIColor - let sepratorColor: UIColor let genderValues = GenderType.allCases @StateObject private var viewModel = UpdateUserViewModel() @Environment(\.colorScheme) var colorScheme private var selectedDate: Binding { Binding( - get: { self.birthday}, - set : { + get: { self.birthday }, + set: { self.birthday = $0 self.setDateString() }) @@ -61,42 +54,31 @@ struct UpdateUserProfileDetailView: View { @State private(set) var user: OCKPatient @State var firstName: String - @State var lastName:String + @State var lastName: String @State var dob: String @State private var image: UIImage? @State var profileImageData = Data() @State var isHideLoader: Bool = true @Environment(\.presentationMode) var presentationMode: Binding - @State var birthday: Date @State var gender: GenderType - - init(user: OCKPatient, backgroudColor: UIColor, textColor: UIColor, cellBackgroundColor: UIColor, headerColor: UIColor, buttonColor: UIColor, borderColor: UIColor, sepratorColor: UIColor) { - _user = State(initialValue: user) - _firstName = State(initialValue: user.name.givenName ?? "") - _lastName = State(initialValue: user.name.familyName ?? "") - _dob = State(initialValue: user.birthday?.toString ?? "") - self.backgroudColor = backgroudColor - self.buttonColor = buttonColor - self.borderColor = borderColor - self.textColor = textColor - self.cellBackgroundColor = cellBackgroundColor - self.headerColor = headerColor - self.sepratorColor = sepratorColor - + init(user: OCKPatient) { + self._user = State(initialValue: user) + self._firstName = State(initialValue: user.name.givenName ?? "") + self._lastName = State(initialValue: user.name.familyName ?? "") + self._dob = State(initialValue: user.birthday?.toString ?? "") self._birthday = State(initialValue: user.birthday ?? Date()) self._gender = State(initialValue: user.sex?.genderType ?? .other) - let navBarAppearance = UINavigationBar.appearance() - navBarAppearance.largeTitleTextAttributes = [.foregroundColor: YmlReader().appTheme?.textColor.color ?? UIColor.black] + navBarAppearance.largeTitleTextAttributes = [.foregroundColor: YmlReader().appStyle.textColor.color ?? UIColor.black] } var body: some View { NavigationView { Form { VStack { - IconView(image: $image, hashFileKey: user.attachments?.Profile?.hashFileKey ?? "", fileName: user.attachments?.Profile?.attachmentID ?? "", viewModel: viewModel) + IconView(image: $image, hashFileKey: user.attachments?.profile?.hashFileKey ?? "", fileName: user.attachments?.profile?.attachmentID ?? "", viewModel: viewModel) .frame(width: Metrics.PROFILE_IMAGE_WIDTH, height: Metrics.PROFILE_IMAGE_HEIGHT ) Text(name) .font(.title.weight(.bold)) @@ -105,128 +87,124 @@ struct UpdateUserProfileDetailView: View { .listRowInsets(EdgeInsets()) .listRowBackground(Color.clear) .accessibilityElement(children: .ignore) - Section { HStack { Text(firstNameTitle) - .foregroundColor(.otfTextColor) - .font(YmlReader().appTheme?.textFont.appFont ?? Font.system(size: 17.0)) - .fontWeight(YmlReader().appTheme?.textWeight.fontWeight) + .foregroundColor(Color.otfTextColor) + .font(Font.otfAppFont) + .fontWeight(Font.otfFontWeight) .accessibilityHidden(true) TextField(firstNameTitle, text: $firstName) .style(.textField) - .foregroundColor(.otfTextColor) - .font(YmlReader().appTheme?.textFont.appFont ?? Font.system(size: 17.0)) + .foregroundColor(Color.otfTextColor) + .font(Font.otfAppFont) .accessibilityLabel("First Name") } HStack { Text(ModuleAppYmlReader().profileData?.lastName ?? Constants.CustomiseStrings.lastName) - .fontWeight(YmlReader().appTheme?.textWeight.fontWeight) - .font(YmlReader().appTheme?.textFont.appFont ?? Font.system(size: 17.0)) - .foregroundColor(.otfTextColor) + .fontWeight(Font.otfFontWeight) + .font(Font.otfAppFont) + .foregroundColor(Color.otfTextColor) .accessibilityHidden(true) TextField(lastNameTitle, text: $lastName) .style(.textField) - .foregroundColor(.otfTextColor) - .font(YmlReader().appTheme?.textFont.appFont ?? Font.system(size: 17.0)) + .foregroundColor(Color.otfTextColor) + .font(Font.otfAppFont) .accessibilityLabel("Last Name") } } header: { Text(infoHeader) - .font(YmlReader().appTheme?.headerTitleFont.appFont ?? Font.system(size: 17.0)) - .fontWeight(YmlReader().appTheme?.headerTitleWeight.fontWeight) - .foregroundColor(.otfHeaderColor) + .font(Font.otfAppFont) + .fontWeight(Font.otfFontWeight) + .foregroundColor(Color.otfHeaderColor) .textCase(nil) } - + Section { Picker(Constants.CustomiseStrings.selectGender, selection: $gender) { ForEach(GenderType.allCases, id: \.self) { Text($0.rawValue) - .font(YmlReader().appTheme?.textFont.appFont ?? Font.system(size: 17.0)) - .fontWeight(YmlReader().appTheme?.textWeight.fontWeight) + .font(Font.otfAppFont) + .fontWeight(Font.otfFontWeight) } .accessibilityLabel("Edit the gender set on your profile") .accessibilityInputLabels(["Edit Gender"]) } - + DatePicker("Birthdate", selection: $birthday, displayedComponents: .date) .accessibilityLabel("Edit the birthdate set on your profile") .accessibilityInputLabels(["Edit Birthdate"]) } header: { Text(otherInfoHeader) - .fontWeight(YmlReader().appTheme?.headerTitleWeight.fontWeight) - .foregroundColor(.otfHeaderColor) - .font(YmlReader().appTheme?.headerTitleFont.appFont ?? Font.system(size: 17.0)) + .fontWeight(Font.otfheaderTitleWeight) + .foregroundColor(Color.otfHeaderColor) + .font(.otfheaderTitleFont) .textCase(nil) } - Button(action: { updatePatient() if let image = image { isHideLoader = false if let imageData = image.pngData() { - let bytesImage = viewModel.swiftSodium.getArrayOfBytesFromData(FileData: imageData as NSData) + let bytesImage = viewModel.swiftSodium.getArrayOfBytesFromData(fileData: imageData as NSData) let hashKeyFile = viewModel.swiftSodium.generateGenericHashWithoutKey(message: bytesImage) let hashKeyFileHex = hashKeyFile.bytesToHex(spacing: "").lowercased() let uuid = UUID().uuidString + ".png" - viewModel.uploadFile(data: imageData, fileName: uuid , hashFileKey: hashKeyFileHex) - + viewModel.uploadFile(data: imageData, fileName: uuid, hashFileKey: hashKeyFileHex) + } } }, label: { Text(Constants.CustomiseStrings.save) .padding(Metrics.PADDING_BUTTON_LABEL) .frame(maxWidth: .infinity) - .foregroundColor(.otfButtonColor) + .foregroundColor(Color.otfButtonColor) .font(.system(size: 20, weight: .bold, design: .default)) .overlay( RoundedRectangle(cornerRadius: Metrics.RADIUS_CORNER_BUTTON) .stroke(Color.otfButtonColor, lineWidth: 2) ) }) - + } } - .listRowBackground(Color.otfCellBackground) - .navigationBarTitleDisplayMode(.inline) - .navigationBarTitle(Text(ModuleAppYmlReader().profileData?.title ?? "Profile")) - .overlay(LoaderView(tintColor: .black, scaleSize: 2.0).padding(.bottom,50).hidden(isHideLoader)) - - .onReceive(NotificationCenter.default.publisher(for: .databaseSuccessfllySynchronized)) { notification in - viewModel.fetchPatient(userId: user.id) - } - .onReceive(viewModel.patientPublisher) { patient in - patientData(patient: patient) - } - .onReceive(viewModel.profileImageData) { data in - let dataDict:[String: Data] = ["imageData": data] - NotificationCenter.default.post(name: .imageUploaded, object: dataDict) - presentationMode.wrappedValue.dismiss() - } - - .onReceive(NotificationCenter.default.publisher(for: .deleteProfile)) { notification in - if let fileName = user.attachments?.Profile?.attachmentID { - isHideLoader = false - viewModel.deleteAttachment(attachmentID: fileName) - viewModel.deleteFileFromDocument(fileName: fileName) - } - } - .onReceive(viewModel.hideLoader) { value in - isHideLoader = true - NotificationCenter.default.post(name: .imageUploaded, object: nil) - presentationMode.wrappedValue.dismiss() + .listRowBackground(Color.otfCellBackground) + .navigationBarTitleDisplayMode(.inline) + .navigationBarTitle(Text(ModuleAppYmlReader().profileData?.title ?? "Profile")) + .overlay(LoaderView(tintColor: .black, scaleSize: 2.0).padding(.bottom, 50).hidden(isHideLoader)) + + .onReceive(NotificationCenter.default.publisher(for: .databaseSuccessfllySynchronized)) { _ in + viewModel.fetchPatient(userId: user.id) + } + .onReceive(viewModel.patientPublisher) { patient in + patientData(patient: patient) + } + .onReceive(viewModel.profileImageData) { data in + let dataDict: [String: Data] = ["imageData": data] + NotificationCenter.default.post(name: .imageUploaded, object: dataDict) + presentationMode.wrappedValue.dismiss() + } + + .onReceive(NotificationCenter.default.publisher(for: .deleteProfile)) { _ in + if let fileName = user.attachments?.profile?.attachmentID { + isHideLoader = false + viewModel.deleteAttachment(attachmentID: fileName) + viewModel.deleteFileFromDocument(fileName: fileName) } - .onAppear { - if let attachmentID = user.attachments?.Profile?.attachmentID { - if profileImageData.retriveFile(fileName: attachmentID) != nil {} else { - viewModel.downloadFile(attachmentID: attachmentID) - } + } + .onReceive(viewModel.hideLoader) { _ in + isHideLoader = true + NotificationCenter.default.post(name: .imageUploaded, object: nil) + presentationMode.wrappedValue.dismiss() + } + .onAppear { + if let attachmentID = user.attachments?.profile?.attachmentID { + if profileImageData.retriveFile(fileName: attachmentID) != nil {} else { + viewModel.downloadFile(attachmentID: attachmentID) } } } - - + } func patientData(patient: OCKPatient) { self.user = patient @@ -238,7 +216,6 @@ struct UpdateUserProfileDetailView: View { } func updatePatient() { - // TODO: - Update user's profile here var name = PersonNameComponents() name.givenName = firstName name.familyName = lastName @@ -247,15 +224,14 @@ struct UpdateUserProfileDetailView: View { user.sex = gender.carekitGender viewModel.updatePatient(user: user) } - + private func setDateString() { - let formatter = DateFormatter() - formatter.dateFormat = "MM-dd-yyyy" - dob = formatter.string(from: self.birthday) - } + let formatter = DateFormatter() + formatter.dateFormat = "MM-dd-yyyy" + dob = formatter.string(from: self.birthday) + } } - // MARK: - Labels extension UpdateUserProfileDetailView { var name: String { @@ -265,19 +241,19 @@ extension UpdateUserProfileDetailView { } return "\(givenName) \(familyName)" } - + var infoHeader: String { ModuleAppYmlReader().profileData?.profileInfoHeader ?? Constants.CustomiseStrings.basicInformation } - + var firstNameTitle: String { ModuleAppYmlReader().profileData?.firstName ?? Constants.CustomiseStrings.firstName } - + var lastNameTitle: String { ModuleAppYmlReader().profileData?.lastName ?? Constants.CustomiseStrings.lastName } - + var otherInfoHeader: String { ModuleAppYmlReader().profileData?.otherInfo ?? Constants.CustomiseStrings.otherInformation } @@ -288,10 +264,10 @@ extension GenderType { switch self { case .male: return .male - + case .female: return .female - + case .other: return .other("") } @@ -303,10 +279,10 @@ extension OCKBiologicalSex { switch self { case .male: return .male - + case .female: return .female - + default: return .other } @@ -315,17 +291,17 @@ extension OCKBiologicalSex { struct IconView: View { @Binding var image: UIImage? - @State var hashFileKey : String + @State var hashFileKey: String @State var fileName: String @State var imageViews = Data() @State private var shouldPresentImagePicker = false @State private var shouldPresentActionScheet = false @State private var sourceType = UIImagePickerController.SourceType.photoLibrary @StateObject var viewModel: UpdateUserViewModel - @State var imageUI : UIImage? = nil - + @State var imageUI: UIImage? + var imageView: Image { - + if let image = image { return Image(uiImage: image) } else if let imageUI = imageUI { @@ -333,9 +309,9 @@ struct IconView: View { } else { return Image.avatar } - + } - + var body: some View { imageView .iconStyle() @@ -346,26 +322,26 @@ struct IconView: View { } .onLoad { DispatchQueue.main.async { - if let retriveImageData = imageViews.retriveFile(fileName: fileName) { + if let retriveImageData = imageViews.retriveFile(fileName: fileName) { imageUI = viewModel.dataToImageWithoutDecryption(data: retriveImageData, key: hashFileKey) } } } .actionSheet(isPresented: $shouldPresentActionScheet) { () -> ActionSheet in ActionSheet(title: Text(Constants.CustomiseStrings.chooseMode) - .font(YmlReader().appTheme?.textFont.appFont ?? Font.system(size: 17.0)) - .fontWeight(YmlReader().appTheme?.textWeight.fontWeight), + .font(Font.otfAppFont) + .fontWeight(Font.otfFontWeight), message: Text(Constants.CustomiseStrings.chooseProfileImage) - .fontWeight(YmlReader().appTheme?.textWeight.fontWeight) - .font(YmlReader().appTheme?.textFont.appFont ?? Font.system(size: 17.0)), buttons: [ActionSheet.Button.default(Text(Constants.CustomiseStrings.camera), action: { - self.shouldPresentImagePicker = true - self.sourceType = .camera - }), ActionSheet.Button.default(Text(Constants.CustomiseStrings.photoLibrary), action: { - self.shouldPresentImagePicker = true - self.sourceType = .photoLibrary - }), ActionSheet.Button.destructive(Text(Constants.CustomiseStrings.deleteProfile), action: { - NotificationCenter.default.post(name: .deleteProfile, object: nil) - }), ActionSheet.Button.cancel()]) + .fontWeight(Font.otfFontWeight) + .font(Font.otfAppFont), buttons: [ActionSheet.Button.default(Text(Constants.CustomiseStrings.camera), action: { + self.shouldPresentImagePicker = true + self.sourceType = .camera + }), ActionSheet.Button.default(Text(Constants.CustomiseStrings.photoLibrary), action: { + self.shouldPresentImagePicker = true + self.sourceType = .photoLibrary + }), ActionSheet.Button.destructive(Text(Constants.CustomiseStrings.deleteProfile), action: { + NotificationCenter.default.post(name: .deleteProfile, object: nil) + }), ActionSheet.Button.cancel()]) } .accessibilityLabel("Profile image") .accessibilityAddTraits(.isButton) @@ -374,24 +350,22 @@ struct IconView: View { } } - struct SUImagePickerView: UIViewControllerRepresentable { - var sourceType: UIImagePickerController.SourceType = .photoLibrary @Binding var image: UIImage? @Binding var isPresented: Bool - + func makeCoordinator() -> ImagePickerViewCoordinator { return ImagePickerViewCoordinator(image: $image, isPresented: $isPresented) } - + func makeUIViewController(context: Context) -> UIImagePickerController { let pickerController = UIImagePickerController() pickerController.sourceType = sourceType pickerController.delegate = context.coordinator return pickerController } - + func updateUIViewController(_ uiViewController: UIImagePickerController, context: Context) { // Nothing to update here } @@ -399,22 +373,21 @@ struct SUImagePickerView: UIViewControllerRepresentable { class ImagePickerViewCoordinator: NSObject, UINavigationControllerDelegate, UIImagePickerControllerDelegate { - @Binding var image: UIImage? @Binding var isPresented: Bool - + init(image: Binding, isPresented: Binding) { self._image = image self._isPresented = isPresented } - - func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) { + + func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey: Any]) { if let image = info[UIImagePickerController.InfoKey.originalImage] as? UIImage { self.image = image } self.isPresented = false } - + func imagePickerControllerDidCancel(_ picker: UIImagePickerController) { self.isPresented = false } @@ -424,12 +397,10 @@ extension Array where Element == UInt8 { func bytesToHex(spacing: String) -> String { var hexString: String = "" var count = self.count - for byte in self - { - hexString.append(String(format:"%02X", byte)) - count = count - 1 - if count > 0 - { + for byte in self { + hexString.append(String(format: "%02X", byte)) + count -= 1 + if !isEmpty { hexString.append(spacing) } } diff --git a/OTFMagicBox/Profile/Views/UpdateUserProfileView.swift b/OTFMagicBox/Profile/Views/UpdateUserProfileView.swift index b6a36d99..aaf1341a 100644 --- a/OTFMagicBox/Profile/Views/UpdateUserProfileView.swift +++ b/OTFMagicBox/Profile/Views/UpdateUserProfileView.swift @@ -1,35 +1,35 @@ /* -Copyright (c) 2021, Hippocrates Technologies S.r.l.. All rights reserved. - -Redistribution and use in source and binary forms, with or without modification, -are permitted provided that the following conditions are met: - -1. Redistributions of source code must retain the above copyright notice, -this list of conditions and the following disclaimer. - -2. Redistributions in binary form must reproduce the above copyright notice, -this list of conditions and the following disclaimer in the documentation and/or -other materials provided with the distribution. - -3. Neither the name of the copyright holder(s) nor the names of any contributor(s) may -be used to endorse or promote products derived from this software without specific -prior written permission. No license is granted to the trademarks of the copyright -holders even if such marks are included in this software. - -4. Commercial redistribution in any form requires an explicit license agreement with the -copyright holder(s). Please contact support@hippocratestech.com for further information -regarding licensing. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND -ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. -IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, -INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT -NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, -WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) -ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY -OF SUCH DAMAGE. + Copyright (c) 2021, Hippocrates Technologies S.r.l.. All rights reserved. + + Redistribution and use in source and binary forms, with or without modification, + are permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation and/or + other materials provided with the distribution. + + 3. Neither the name of the copyright holder(s) nor the names of any contributor(s) may + be used to endorse or promote products derived from this software without specific + prior written permission. No license is granted to the trademarks of the copyright + holders even if such marks are included in this software. + + 4. Commercial redistribution in any form requires an explicit license agreement with the + copyright holder(s). Please contact support@hippocratestech.com for further information + regarding licensing. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, + INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, + OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY + OF SUCH DAMAGE. */ import SwiftUI @@ -38,115 +38,96 @@ import OTFUtilities struct UpdateUserProfileView: View { @StateObject private var networkManager = OTFNetworkObserver() - -// var user: String + + // var user: String var imageName: String? - + var image: UIImage { guard let imageName, let image = UIImage(named: imageName) else { return UIImage(named: "user_profile")! } return image } - - var textColor: Color { - guard let uiColor = YmlReader().appTheme?.textColor.color else { - return Color(UIColor.label) - } - return Color(uiColor) - } - + @State var showUserProfile = false let user: OCKPatient - let backgroudColor: UIColor - let tColor: UIColor - let cellBackgroundColor: UIColor @State var fetchFile = Data() - let headerColor: UIColor - let buttonColor: UIColor - let borderColor: UIColor - let sepratorColor: UIColor - - @StateObject private var viewModel = UpdateUserViewModel() - + var body: some View { HStack { - ZStack(alignment: .bottomTrailing) { - if let retriveImageData = fetchFile.retriveFile(fileName: user.attachments?.Profile?.attachmentID ?? "") { - let image = viewModel.showProfileImage(user: user, imageData: retriveImageData) - ProfileIcon(image: image) - - } else { - if !fetchFile.isEmpty { - - let image = viewModel.showProfileImage(user: user, imageData: fetchFile) - ProfileIcon(image: image) - } else { - ProfileIcon(image: UIImage(named: ModuleAppYmlReader().profileData?.profileImage ?? "user_profile")!) - } - } - NetworkIndicator(status: networkManager.status) - }.padding(.trailing) - VStack(alignment: .leading) { - Text(user.remoteID ?? "") - .fontWeight(YmlReader().appTheme?.textWeight.fontWeight) - .font(YmlReader().appTheme?.textFont.appFont ?? Font.system(size: 17.0)) - .fontWeight(YmlReader().appTheme?.textWeight.fontWeight) - .foregroundColor(textColor) - - Text("Edit your profile") - .font(.footnote) - .foregroundColor(textColor) - } - Spacer() - Image(systemName: "chevron.right") - .foregroundColor(Color(UIColor.tertiaryLabel)) - .font(.footnote.weight(.semibold)) - } - - .gesture(TapGesture().onEnded({ - self.showUserProfile.toggle() - })).sheet(isPresented: $showUserProfile, onDismiss: { - - }, content: { - UpdateUserProfileDetailView(user: user, backgroudColor: backgroudColor, textColor: tColor, cellBackgroundColor: cellBackgroundColor, headerColor: headerColor, buttonColor: buttonColor, borderColor: borderColor, sepratorColor: sepratorColor) - }) - .background(Color(backgroudColor)) - .onReceive(NotificationCenter.default.publisher(for: .imageUploaded)) { notification in - if let value = notification.object as? [String: Data] { - if let data = value.first?.value{ - fetchFile = data + ZStack(alignment: .bottomTrailing) { + if let retriveImageData = fetchFile.retriveFile(fileName: user.attachments?.profile?.attachmentID ?? "") { + let image = viewModel.showProfileImage(user: user, imageData: retriveImageData) + ProfileIcon(image: image) + + } else { + if !fetchFile.isEmpty { + + let image = viewModel.showProfileImage(user: user, imageData: fetchFile) + ProfileIcon(image: image) + } else { + ProfileIcon(image: UIImage(named: ModuleAppYmlReader().profileData?.profileImage ?? "user_profile")!) } } + NetworkIndicator(status: networkManager.status) + }.padding(.trailing) + VStack(alignment: .leading) { + Text(user.remoteID ?? "") + .font(Font.otfAppFont) + .fontWeight(Font.otfFontWeight) + .foregroundColor(.otfTextColor) + + Text("Edit your profile") + .font(.footnote) + .foregroundColor(.otfTextColor) } - .onReceive(NotificationCenter.default.publisher(for: .imageDownloaded)) { notification in - if let value = notification.object as? [String: Data] { - if let data = value.first?.value{ - fetchFile = data - } + Spacer() + Image(systemName: "chevron.right") + .foregroundColor(Color(UIColor.tertiaryLabel)) + .font(.footnote.weight(.semibold)) + } + + .gesture(TapGesture().onEnded({ + self.showUserProfile.toggle() + })).sheet(isPresented: $showUserProfile, onDismiss: { + + }, content: { + UpdateUserProfileDetailView(user: user) + }) + .background(Color.otfCellBackground) + .onReceive(NotificationCenter.default.publisher(for: .imageUploaded)) { notification in + if let value = notification.object as? [String: Data] { + if let data = value.first?.value { + fetchFile = data } } - .onReceive(NotificationCenter.default.publisher(for: .deleteProfile)) { notification in - fetchFile = Data() - } - .onReceive(viewModel.profileImageData) { data in - fetchFile = data + } + .onReceive(NotificationCenter.default.publisher(for: .imageDownloaded)) { notification in + if let value = notification.object as? [String: Data] { + if let data = value.first?.value { + fetchFile = data + } } - .onAppear() { - if let attachmentID = user.attachments?.Profile?.attachmentID { - if fetchFile.retriveFile(fileName: attachmentID) != nil {} else { - viewModel.downloadFile(attachmentID: attachmentID) - } + } + .onReceive(NotificationCenter.default.publisher(for: .deleteProfile)) { _ in + fetchFile = Data() + } + .onReceive(viewModel.profileImageData) { data in + fetchFile = data + } + .onAppear { + if let attachmentID = user.attachments?.profile?.attachmentID { + if fetchFile.retriveFile(fileName: attachmentID) != nil {} else { + viewModel.downloadFile(attachmentID: attachmentID) } } + } } } - - struct NetworkIndicator: View { var status: OTFNetworkStatus - + var body: some View { ZStack { Image(systemName: "cloud.fill") @@ -154,7 +135,7 @@ struct NetworkIndicator: View { .scaledToFit() .foregroundColor(backgroundColor.opacity(0.85)) .frame(width: Metrics.NETWORK_INDICATOR_WIDTH) - + Image(systemName: accessory) .resizable() .scaledToFit() @@ -163,7 +144,7 @@ struct NetworkIndicator: View { .padding(.top, 3) } } - + var backgroundColor: Color { switch status { case .offline: @@ -174,7 +155,7 @@ struct NetworkIndicator: View { return .gray } } - + var accessory: String { switch status { case .offline: diff --git a/OTFMagicBox/Profile/Views/WithdrawView.swift b/OTFMagicBox/Profile/Views/WithdrawView.swift index 04292747..974f4b80 100644 --- a/OTFMagicBox/Profile/Views/WithdrawView.swift +++ b/OTFMagicBox/Profile/Views/WithdrawView.swift @@ -1,35 +1,35 @@ /* -Copyright (c) 2021, Hippocrates Technologies S.r.l.. All rights reserved. + Copyright (c) 2021, Hippocrates Technologies S.r.l.. All rights reserved. -Redistribution and use in source and binary forms, with or without modification, -are permitted provided that the following conditions are met: + Redistribution and use in source and binary forms, with or without modification, + are permitted provided that the following conditions are met: -1. Redistributions of source code must retain the above copyright notice, -this list of conditions and the following disclaimer. + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. -2. Redistributions in binary form must reproduce the above copyright notice, -this list of conditions and the following disclaimer in the documentation and/or -other materials provided with the distribution. + 2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation and/or + other materials provided with the distribution. -3. Neither the name of the copyright holder(s) nor the names of any contributor(s) may -be used to endorse or promote products derived from this software without specific -prior written permission. No license is granted to the trademarks of the copyright -holders even if such marks are included in this software. + 3. Neither the name of the copyright holder(s) nor the names of any contributor(s) may + be used to endorse or promote products derived from this software without specific + prior written permission. No license is granted to the trademarks of the copyright + holders even if such marks are included in this software. -4. Commercial redistribution in any form requires an explicit license agreement with the -copyright holder(s). Please contact support@hippocratestech.com for further information -regarding licensing. + 4. Commercial redistribution in any form requires an explicit license agreement with the + copyright holder(s). Please contact support@hippocratestech.com for further information + regarding licensing. -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND -ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. -IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, -INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT -NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, -WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) -ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY -OF SUCH DAMAGE. + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, + INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, + OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY + OF SUCH DAMAGE. */ import SwiftUI @@ -37,29 +37,27 @@ import SwiftUI struct WithdrawView: View { @State var showWithdraw = false let title: String - let textColor: Color - - init(title: String, textColor: Color) { + + init(title: String) { self.title = title - self.textColor = textColor } - + var body: some View { HStack { Text(title) - .foregroundColor(textColor) - .font(YmlReader().appTheme?.textFont.appFont ?? Font.system(size: 17.0)) - .fontWeight(YmlReader().appTheme?.textWeight.fontWeight) + .foregroundColor(.otfTextColor) + .font(Font.otfAppFont) + .fontWeight(Font.otfFontWeight) Spacer() Image(systemName: "chevron.right") - .foregroundColor(Color(UIColor.tertiaryLabel)) - .font(.footnote.weight(.semibold)) + .foregroundColor(Color(UIColor.tertiaryLabel)) + .font(.footnote.weight(.semibold)) }.frame(maxWidth: .infinity, minHeight: Metrics.TITLE_VIEW_HEIGHT) .contentShape(Rectangle()) .gesture(TapGesture().onEnded({ self.showWithdraw.toggle() })).sheet(isPresented: $showWithdraw, onDismiss: { - + }, content: { WithdrawalViewController() }) @@ -70,6 +68,6 @@ struct WithdrawView: View { struct WithdrawView_Previews: PreviewProvider { static var previews: some View { - WithdrawView(title: "Withdraw from Study", textColor: Color(YmlReader().appTheme?.textColor.color ?? UIColor.black)) + WithdrawView(title: "Withdraw from Study") } } diff --git a/OTFMagicBox/Profile/Views/WithdrawalViewController.swift b/OTFMagicBox/Profile/Views/WithdrawalViewController.swift index c3f592f2..1da2b01e 100644 --- a/OTFMagicBox/Profile/Views/WithdrawalViewController.swift +++ b/OTFMagicBox/Profile/Views/WithdrawalViewController.swift @@ -1,35 +1,35 @@ /* -Copyright (c) 2021, Hippocrates Technologies S.r.l.. All rights reserved. - -Redistribution and use in source and binary forms, with or without modification, -are permitted provided that the following conditions are met: - -1. Redistributions of source code must retain the above copyright notice, -this list of conditions and the following disclaimer. - -2. Redistributions in binary form must reproduce the above copyright notice, -this list of conditions and the following disclaimer in the documentation and/or -other materials provided with the distribution. - -3. Neither the name of the copyright holder(s) nor the names of any contributor(s) may -be used to endorse or promote products derived from this software without specific -prior written permission. No license is granted to the trademarks of the copyright -holders even if such marks are included in this software. - -4. Commercial redistribution in any form requires an explicit license agreement with the -copyright holder(s). Please contact support@hippocratestech.com for further information -regarding licensing. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND -ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. -IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, -INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT -NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, -WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) -ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY -OF SUCH DAMAGE. + Copyright (c) 2021, Hippocrates Technologies S.r.l.. All rights reserved. + + Redistribution and use in source and binary forms, with or without modification, + are permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation and/or + other materials provided with the distribution. + + 3. Neither the name of the copyright holder(s) nor the names of any contributor(s) may + be used to endorse or promote products derived from this software without specific + prior written permission. No license is granted to the trademarks of the copyright + holders even if such marks are included in this software. + + 4. Commercial redistribution in any form requires an explicit license agreement with the + copyright holder(s). Please contact support@hippocratestech.com for further information + regarding licensing. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, + INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, + OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY + OF SUCH DAMAGE. */ import UIKit @@ -37,31 +37,31 @@ import SwiftUI import OTFResearchKit struct WithdrawalViewController: UIViewControllerRepresentable { - + func makeCoordinator() -> Coordinator { Coordinator() } typealias UIViewControllerType = ORKTaskViewController - + func updateUIViewController(_ taskViewController: ORKTaskViewController, context: Context) {} func makeUIViewController(context: Context) -> ORKTaskViewController { - + let instructionStep = ORKInstructionStep(identifier: "WithdrawlInstruction") instructionStep.title = ModuleAppYmlReader().withdrawl?.withdrawalInstructionTitle instructionStep.text = ModuleAppYmlReader().withdrawl?.withdrawalInstructionText - + let completionStep = ORKCompletionStep(identifier: "Withdraw") completionStep.title = ModuleAppYmlReader().withdrawl?.withdrawTitle completionStep.text = ModuleAppYmlReader().withdrawl?.withdrawText - + let withdrawTask = ORKOrderedTask(identifier: "Withdraw", steps: [instructionStep, completionStep]) - + // wrap that task on a view controller let taskViewController = ORKTaskViewController(task: withdrawTask, taskRun: nil) - + taskViewController.delegate = context.coordinator // enables `ORKTaskViewControllerDelegate` below - + // & present the VC! return taskViewController @@ -70,26 +70,23 @@ struct WithdrawalViewController: UIViewControllerRepresentable { class Coordinator: NSObject, ORKTaskViewControllerDelegate { public func taskViewController(_ taskViewController: ORKTaskViewController, didFinishWith reason: ORKTaskViewControllerFinishReason, error: Error?) { switch reason { - case .completed: - if (ORKPasscodeViewController.isPasscodeStoredInKeychain()) { - ORKPasscodeViewController.removePasscodeFromKeychain() - } - UserDefaultsManager.setOnboardingCompleted(false) - UserDefaults.standard.set(nil, forKey: Constants.prefCareKitDataInitDate) - UserDefaults.standard.set(nil, forKey: Constants.prefHealthRecordsLastUploaded) - - NotificationCenter.default.post(name: .onboardingDidComplete, object: false) - - try? CareKitManager.shared.wipe() - - fallthrough - default: - // otherwise dismiss onboarding without proceeding. - taskViewController.dismiss(animated: true, completion: nil) - + case .completed: + if ORKPasscodeViewController.isPasscodeStoredInKeychain() { + ORKPasscodeViewController.removePasscodeFromKeychain() + } + UserDefaultsManager.setOnboardingCompleted(false) + UserDefaults.standard.set(nil, forKey: Constants.prefCareKitDataInitDate) + UserDefaults.standard.set(nil, forKey: Constants.prefHealthRecordsLastUploaded) + + NotificationCenter.default.post(name: .onboardingDidComplete, object: false) + + try? CareKitStoreManager.shared.wipe() + + fallthrough + default: + // otherwise dismiss onboarding without proceeding. + taskViewController.dismiss(animated: true, completion: nil) } } } - } - diff --git a/OTFMagicBox/SceneDelegate.swift b/OTFMagicBox/SceneDelegate.swift index 782b16f2..f558b1f0 100644 --- a/OTFMagicBox/SceneDelegate.swift +++ b/OTFMagicBox/SceneDelegate.swift @@ -1,35 +1,35 @@ /* -Copyright (c) 2021, Hippocrates Technologies S.r.l.. All rights reserved. - -Redistribution and use in source and binary forms, with or without modification, -are permitted provided that the following conditions are met: - -1. Redistributions of source code must retain the above copyright notice, -this list of conditions and the following disclaimer. - -2. Redistributions in binary form must reproduce the above copyright notice, -this list of conditions and the following disclaimer in the documentation and/or -other materials provided with the distribution. - -3. Neither the name of the copyright holder(s) nor the names of any contributor(s) may -be used to endorse or promote products derived from this software without specific -prior written permission. No license is granted to the trademarks of the copyright -holders even if such marks are included in this software. - -4. Commercial redistribution in any form requires an explicit license agreement with the -copyright holder(s). Please contact support@hippocratestech.com for further information -regarding licensing. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND -ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. -IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, -INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT -NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, -WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) -ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY -OF SUCH DAMAGE. + Copyright (c) 2021, Hippocrates Technologies S.r.l.. All rights reserved. + + Redistribution and use in source and binary forms, with or without modification, + are permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation and/or + other materials provided with the distribution. + + 3. Neither the name of the copyright holder(s) nor the names of any contributor(s) may + be used to endorse or promote products derived from this software without specific + prior written permission. No license is granted to the trademarks of the copyright + holders even if such marks are included in this software. + + 4. Commercial redistribution in any form requires an explicit license agreement with the + copyright holder(s). Please contact support@hippocratestech.com for further information + regarding licensing. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, + INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, + OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY + OF SUCH DAMAGE. */ import UIKit @@ -38,7 +38,7 @@ import SwiftUI class SceneDelegate: UIResponder, UIWindowSceneDelegate { var window: UIWindow? - + private var isLaunched = true func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) { @@ -49,6 +49,7 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate { // Create the SwiftUI view that provides the window contents. OTFTheraforgeNetwork.shared.configureNetwork() let contentView = LaunchView() + .appStyle(.init(from: OTFYamlStyle(style: YmlReader().appStyle))) // Use a UIHostingController as window root view controller. if let windowScene = scene as? UIWindowScene { @@ -69,16 +70,16 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate { func sceneDidBecomeActive(_ scene: UIScene) { // Called when the scene has moved from an inactive state to an active state. // Use this method to restart any tasks that were paused (or not yet started) when the scene was inactive. - + // We sync the DB when the user is already logged in and we show the `MainView`. // This is to avoid multiple sync operations at launch time. guard !isLaunched else { isLaunched = false return } - + CloudantSyncManager.shared.syncCloudantStore(notifyWhenDone: true) { _ in - // + } } @@ -97,7 +98,4 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate { // Use this method to save data, release shared resources, and store enough scene-specific state information // to restore the scene back to its current state. } - - } - diff --git a/OTFMagicBox/Schedule/ScheduleViewController.swift b/OTFMagicBox/Schedule/ScheduleViewController.swift index a9e26304..6873d4a1 100644 --- a/OTFMagicBox/Schedule/ScheduleViewController.swift +++ b/OTFMagicBox/Schedule/ScheduleViewController.swift @@ -1,105 +1,112 @@ /* -Copyright (c) 2021, Hippocrates Technologies S.r.l.. All rights reserved. - -Redistribution and use in source and binary forms, with or without modification, -are permitted provided that the following conditions are met: - -1. Redistributions of source code must retain the above copyright notice, -this list of conditions and the following disclaimer. - -2. Redistributions in binary form must reproduce the above copyright notice, -this list of conditions and the following disclaimer in the documentation and/or -other materials provided with the distribution. - -3. Neither the name of the copyright holder(s) nor the names of any contributor(s) may -be used to endorse or promote products derived from this software without specific -prior written permission. No license is granted to the trademarks of the copyright -holders even if such marks are included in this software. - -4. Commercial redistribution in any form requires an explicit license agreement with the -copyright holder(s). Please contact support@hippocratestech.com for further information -regarding licensing. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND -ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. -IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, -INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT -NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, -WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) -ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY -OF SUCH DAMAGE. + Copyright (c) 2021, Hippocrates Technologies S.r.l.. All rights reserved. + + Redistribution and use in source and binary forms, with or without modification, + are permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation and/or + other materials provided with the distribution. + + 3. Neither the name of the copyright holder(s) nor the names of any contributor(s) may + be used to endorse or promote products derived from this software without specific + prior written permission. No license is granted to the trademarks of the copyright + holders even if such marks are included in this software. + + 4. Commercial redistribution in any form requires an explicit license agreement with the + copyright holder(s). Please contact support@hippocratestech.com for further information + regarding licensing. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, + INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, + OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY + OF SUCH DAMAGE. */ import OTFCareKit +import OTFCareKitUI import OTFCareKitStore import UIKit import OTFUtilities import SwiftUI class ScheduleViewController: OCKDailyPageViewController { - + override func viewDidLoad() { super.viewDidLoad() title = Constants.CustomiseStrings.schedule - + NotificationCenter.default.addObserver(self, selector: #selector(didReceiveStoreChangeNotification(_:)), name: .databaseSuccessfllySynchronized, object: nil) - + NotificationCenter.default.addObserver(self, selector: #selector(deleteProfileEventNotification(_:)), name: .deleteUserAccount, object: nil) } - + override func viewDidDisappear(_ animated: Bool) { NotificationCenter.default.removeObserver(self, name: .deleteUserAccount, object: nil) } - + @objc private func didReceiveStoreChangeNotification(_ notification: Notification) { reload() } - + @objc private func deleteProfileEventNotification(_ notification: Notification) { - - self.alertWithAction(title: Constants.CustomiseStrings.accountDeleted, message: Constants.deleteAccount) { action in + + self.alertWithAction(title: Constants.CustomiseStrings.accountDeleted, message: Constants.deleteAccount) { _ in OTFTheraforgeNetwork.shared.moveToOnboardingView() } - + } - - override func dailyPageViewController(_ dailyPageViewController: OCKDailyPageViewController, - prepare listViewController: OCKListViewController, for date: Date) { - + + override func dailyPageViewController( + _ dailyPageViewController: OCKDailyPageViewController, + prepare listViewController: OCKListViewController, + for date: Date) { + var query = OCKTaskQuery(for: date) query.excludesTasksWithNoEvents = true + + if let ockView = listViewController.view as? OCKView { + ockView.customStyle = OTFStyle(from: OTFYamlStyle(style: YmlReader().appStyle)) + } DispatchQueue.global(qos: .default).async { [unowned self] in storeManager.store.fetchAnyTasks(query: query, callbackQueue: .main) { result in switch result { case .failure(let error): OTFError("error in fetching fetchAnyTasks %{public}@", error.localizedDescription) - + case .success(let tasks): // Add a non-CareKit view into the list /* - let tipTitle = "Customize your app!" - let tipText = "" + let tipTitle = "Customize your app!" + let tipText = "" - // Only show the tip view on the current date - if Calendar.current.isDate(date, inSameDayAs: Date()) { - let tipView = TipView() - tipView.headerView.titleLabel.text = tipTitle - tipView.headerView.detailLabel.text = tipText - tipView.imageView.image = UIImage(named: "GraphicOperatingSystem") - listViewController.appendView(tipView, animated: false) - } + // Only show the tip view on the current date + if Calendar.current.isDate(date, inSameDayAs: Date()) { + let tipView = TipView() + tipView.headerView.titleLabel.text = tipTitle + tipView.headerView.detailLabel.text = tipText + tipView.imageView.image = UIImage(named: "GraphicOperatingSystem") + listViewController.appendView(tipView, animated: false) + } */ // Filter the tasks that exist on the given date let todayTasks = tasks.filter({ $0.schedule.exists(onDay: date) }) - + // If there's no task on the given date then show no tasks card guard !todayTasks.isEmpty else { let tipTitle = Constants.CustomiseStrings.noTasks @@ -110,7 +117,7 @@ class ScheduleViewController: OCKDailyPageViewController { listViewController.appendView(tipView, animated: false) return } - + todayTasks.forEach { task in guard task.schedule.exists(onDay: date) else { return } if task.viewType == .instruction { diff --git a/OTFMagicBox/Schedule/ScheduleViewControllerRepresentable.swift b/OTFMagicBox/Schedule/ScheduleViewControllerRepresentable.swift index 19dc9c54..67b141fc 100644 --- a/OTFMagicBox/Schedule/ScheduleViewControllerRepresentable.swift +++ b/OTFMagicBox/Schedule/ScheduleViewControllerRepresentable.swift @@ -1,35 +1,35 @@ /* -Copyright (c) 2021, Hippocrates Technologies S.r.l.. All rights reserved. - -Redistribution and use in source and binary forms, with or without modification, -are permitted provided that the following conditions are met: - -1. Redistributions of source code must retain the above copyright notice, -this list of conditions and the following disclaimer. - -2. Redistributions in binary form must reproduce the above copyright notice, -this list of conditions and the following disclaimer in the documentation and/or -other materials provided with the distribution. - -3. Neither the name of the copyright holder(s) nor the names of any contributor(s) may -be used to endorse or promote products derived from this software without specific -prior written permission. No license is granted to the trademarks of the copyright -holders even if such marks are included in this software. - -4. Commercial redistribution in any form requires an explicit license agreement with the -copyright holder(s). Please contact support@hippocratestech.com for further information -regarding licensing. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND -ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. -IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, -INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT -NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, -WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) -ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY -OF SUCH DAMAGE. + Copyright (c) 2021, Hippocrates Technologies S.r.l.. All rights reserved. + + Redistribution and use in source and binary forms, with or without modification, + are permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation and/or + other materials provided with the distribution. + + 3. Neither the name of the copyright holder(s) nor the names of any contributor(s) may + be used to endorse or promote products derived from this software without specific + prior written permission. No license is granted to the trademarks of the copyright + holders even if such marks are included in this software. + + 4. Commercial redistribution in any form requires an explicit license agreement with the + copyright holder(s). Please contact support@hippocratestech.com for further information + regarding licensing. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, + INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, + OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY + OF SUCH DAMAGE. */ import OTFCareKitStore @@ -38,16 +38,17 @@ import Foundation import UIKit import SwiftUI import OTFResearchKit +import OTFCareKitUI struct ScheduleViewControllerRepresentable: UIViewControllerRepresentable { - + typealias UIViewControllerType = UIViewController - + func updateUIViewController(_ taskViewController: UIViewController, context: Context) {} + func makeUIViewController(context: Context) -> UIViewController { - let manager = CareKitManager.shared + let manager = CareKitStoreManager.shared let vc = ScheduleViewController(storeManager: manager.synchronizedStoreManager) return UINavigationController(rootViewController: vc) } - } diff --git a/OTFMagicBox/Schedule/SurveyItemViewController.swift b/OTFMagicBox/Schedule/SurveyItemViewController.swift index bb4c1f61..80e18c40 100644 --- a/OTFMagicBox/Schedule/SurveyItemViewController.swift +++ b/OTFMagicBox/Schedule/SurveyItemViewController.swift @@ -1,35 +1,35 @@ /* -Copyright (c) 2021, Hippocrates Technologies S.r.l.. All rights reserved. - -Redistribution and use in source and binary forms, with or without modification, -are permitted provided that the following conditions are met: - -1. Redistributions of source code must retain the above copyright notice, -this list of conditions and the following disclaimer. - -2. Redistributions in binary form must reproduce the above copyright notice, -this list of conditions and the following disclaimer in the documentation and/or -other materials provided with the distribution. - -3. Neither the name of the copyright holder(s) nor the names of any contributor(s) may -be used to endorse or promote products derived from this software without specific -prior written permission. No license is granted to the trademarks of the copyright -holders even if such marks are included in this software. - -4. Commercial redistribution in any form requires an explicit license agreement with the -copyright holder(s). Please contact support@hippocratestech.com for further information -regarding licensing. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND -ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. -IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, -INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT -NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, -WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) -ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY -OF SUCH DAMAGE. + Copyright (c) 2021, Hippocrates Technologies S.r.l.. All rights reserved. + + Redistribution and use in source and binary forms, with or without modification, + are permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation and/or + other materials provided with the distribution. + + 3. Neither the name of the copyright holder(s) nor the names of any contributor(s) may + be used to endorse or promote products derived from this software without specific + prior written permission. No license is granted to the trademarks of the copyright + holders even if such marks are included in this software. + + 4. Commercial redistribution in any form requires an explicit license agreement with the + copyright holder(s). Please contact support@hippocratestech.com for further information + regarding licensing. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, + INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, + OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY + OF SUCH DAMAGE. */ import Foundation @@ -51,7 +51,14 @@ class SurveyItemViewController: OCKInstructionsTaskViewController, ORKTaskViewCo } // 2b. If the user attempted to mark the task complete, display a ResearchKit survey. - let answerFormat = ORKAnswerFormat.scale(withMaximumValue: 5, minimumValue: 1, defaultValue: 5, step: 1, vertical: false, maximumValueDescription: "A LOT!", minimumValueDescription: "a little") + let answerFormat = ORKAnswerFormat.scale( + withMaximumValue: 5, + minimumValue: 1, + defaultValue: 5, + step: 1, + vertical: false, + maximumValueDescription: "A LOT!", + minimumValueDescription: "a little") let feedbackStep = ORKQuestionStep(identifier: "feedback", title: "Feedback", question: "How are you liking SampleApp?", answer: answerFormat) let surveyTask = ORKOrderedTask(identifier: "feedback", steps: [feedbackStep]) let surveyViewController = ORKTaskViewController(task: surveyTask, taskRun: nil) @@ -70,14 +77,19 @@ class SurveyItemViewController: OCKInstructionsTaskViewController, ORKTaskViewCo } // 4a. Retrieve the result from the ResearchKit survey - let survey = taskViewController.result.results!.first(where: { $0.identifier == "feedback" }) as! ORKStepResult - let feedbackResult = survey.results!.first as! ORKScaleQuestionResult + guard let survey = taskViewController.result.results!.first(where: { $0.identifier == "feedback" }) as? ORKStepResult else { + fatalError("ORKStepResult failed while casting") + } + guard let feedbackResult = survey.results!.first as? ORKScaleQuestionResult else { + fatalError("ORKScaleQuestionResult failed while casting") + } +// let feedbackResult = survey.results!.first as! ORKScaleQuestionResult let answer = Int(truncating: feedbackResult.scaleAnswer!) // 4b. Save the result into CareKit's store controller.appendOutcomeValue(value: answer, at: IndexPath(item: 0, section: 0), completion: nil) - - } + + } } class SurveyItemViewSynchronizer: OCKInstructionsTaskViewSynchronizer { @@ -88,14 +100,14 @@ class SurveyItemViewSynchronizer: OCKInstructionsTaskViewSynchronizer { instructionsView.completionButton.label.text = "Start" return instructionsView } - + override func updateView(_ view: OCKInstructionsTaskView, context: OCKSynchronizationContext) { super.updateView(view, context: context) // Check if an answer exists or not and set the detail label accordingly let element: [OCKAnyEvent]? = context.viewModel.first let firstEvent = element?.first - + if let answer = firstEvent?.outcome?.values.first?.integerValue { view.headerView.detailLabel.text = "Theraforge Rating: \(answer)" } else { diff --git a/OTFMagicBox/Schedule/TipView.swift b/OTFMagicBox/Schedule/TipView.swift index ba4e94ab..a837f18f 100644 --- a/OTFMagicBox/Schedule/TipView.swift +++ b/OTFMagicBox/Schedule/TipView.swift @@ -1,35 +1,35 @@ /* -Copyright (c) 2021, Hippocrates Technologies S.r.l.. All rights reserved. - -Redistribution and use in source and binary forms, with or without modification, -are permitted provided that the following conditions are met: - -1. Redistributions of source code must retain the above copyright notice, -this list of conditions and the following disclaimer. - -2. Redistributions in binary form must reproduce the above copyright notice, -this list of conditions and the following disclaimer in the documentation and/or -other materials provided with the distribution. - -3. Neither the name of the copyright holder(s) nor the names of any contributor(s) may -be used to endorse or promote products derived from this software without specific -prior written permission. No license is granted to the trademarks of the copyright -holders even if such marks are included in this software. - -4. Commercial redistribution in any form requires an explicit license agreement with the -copyright holder(s). Please contact support@hippocratestech.com for further information -regarding licensing. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND -ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. -IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, -INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT -NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, -WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) -ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY -OF SUCH DAMAGE. + Copyright (c) 2021, Hippocrates Technologies S.r.l.. All rights reserved. + + Redistribution and use in source and binary forms, with or without modification, + are permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation and/or + other materials provided with the distribution. + + 3. Neither the name of the copyright holder(s) nor the names of any contributor(s) may + be used to endorse or promote products derived from this software without specific + prior written permission. No license is granted to the trademarks of the copyright + holders even if such marks are included in this software. + + 4. Commercial redistribution in any form requires an explicit license agreement with the + copyright holder(s). Please contact support@hippocratestech.com for further information + regarding licensing. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, + INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, + OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY + OF SUCH DAMAGE. */ import UIKit diff --git a/OTFMagicBox/StaticViews/CardBackground.swift b/OTFMagicBox/StaticViews/CardBackground.swift index 8fbf1062..34e1c7c3 100644 --- a/OTFMagicBox/StaticViews/CardBackground.swift +++ b/OTFMagicBox/StaticViews/CardBackground.swift @@ -1,25 +1,25 @@ /* Copyright (c) 2021, Hippocrates Technologies S.r.l.. All rights reserved. - + Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: - + 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. - + 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. - + 3. Neither the name of the copyright holder(s) nor the names of any contributor(s) may be used to endorse or promote products derived from this software without specific prior written permission. No license is granted to the trademarks of the copyright holders even if such marks are included in this software. - + 4. Commercial redistribution in any form requires an explicit license agreement with the copyright holder(s). Please contact support@hippocratestech.com for further information regarding licensing. - + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. @@ -46,9 +46,8 @@ struct CardBackground: View { var body: some View { ScrollView { VStack { content } - .padding() + .padding() } .background(Color(UIColor.systemGroupedBackground)) } } - diff --git a/OTFMagicBox/StaticViews/CareKit/CareKitTaskViews.swift b/OTFMagicBox/StaticViews/CareKit/CareKitTaskViews.swift index fed6eadf..79b5efb7 100644 --- a/OTFMagicBox/StaticViews/CareKit/CareKitTaskViews.swift +++ b/OTFMagicBox/StaticViews/CareKit/CareKitTaskViews.swift @@ -1,25 +1,25 @@ /* Copyright (c) 2021, Hippocrates Technologies S.r.l.. All rights reserved. - + Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: - + 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. - + 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. - + 3. Neither the name of the copyright holder(s) nor the names of any contributor(s) may be used to endorse or promote products derived from this software without specific prior written permission. No license is granted to the trademarks of the copyright holders even if such marks are included in this software. - + 4. Commercial redistribution in any form requires an explicit license agreement with the copyright holder(s). Please contact support@hippocratestech.com for further information regarding licensing. - + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. @@ -39,16 +39,16 @@ import OTFCareKitStore // MARK: - Instruction Task View struct InstructionTaskView: UIViewControllerRepresentable { typealias UIViewControllerType = OCKInstructionsTaskViewController - + let task: OCKAnyTask let date: Date let storeManager: OCKSynchronizedStoreManager - + func makeUIViewController(context: Context) -> OCKInstructionsTaskViewController { let instructionCard = OCKInstructionsTaskViewController(task: task, eventQuery: .init(for: date), storeManager: storeManager) return instructionCard } - + func updateUIViewController(_ uiViewController: OCKInstructionsTaskViewController, context: Context) {} } @@ -66,14 +66,14 @@ struct GridTaskView: UIViewRepresentable { let task: OCKAnyTask let date: Date let storeManager: OCKSynchronizedStoreManager - + func makeUIView(context: Context) -> some UIView { let gridCard = OCKGridTaskViewController(task: task, eventQuery: .init(for: date), storeManager: storeManager) return gridCard.view } - + func updateUIView(_ uiView: UIViewType, context: Context) { - + } } @@ -90,14 +90,14 @@ struct SimpleTaskView: UIViewRepresentable { let task: OCKAnyTask let date: Date let storeManager: OCKSynchronizedStoreManager - + func makeUIView(context: Context) -> some UIView { let simpleCard = OCKSimpleTaskViewController(task: task, eventQuery: .init(for: date), storeManager: storeManager) return simpleCard.view } - + func updateUIView(_ uiView: UIViewType, context: Context) { - + } } @@ -115,14 +115,14 @@ struct ChecklistTaskView: UIViewRepresentable { let task: OCKAnyTask let date: Date let storeManager: OCKSynchronizedStoreManager - + func makeUIView(context: Context) -> some UIView { let checklistCard = OCKChecklistTaskViewController(task: task, eventQuery: .init(for: date), storeManager: storeManager) return checklistCard.view } - + func updateUIView(_ uiView: UIViewType, context: Context) { - + } } @@ -140,14 +140,14 @@ struct ButtonLogTaskView: UIViewRepresentable { let task: OCKAnyTask let date: Date let storeManager: OCKSynchronizedStoreManager - + func makeUIView(context: Context) -> some UIView { let buttonLogCard = OCKButtonLogTaskViewController(task: task, eventQuery: .init(for: date), storeManager: storeManager) return buttonLogCard.view } - + func updateUIView(_ uiView: UIViewType, context: Context) { - + } } @@ -179,7 +179,7 @@ var dummyTask: OCKAnyTask { let task = OCKTask(id: OCKStore.Tasks.doxylamine.rawValue, title: OCKStore.Tasks.doxylamine.rawValue, carePlanUUID: nil, schedule: schedule) - + return task } diff --git a/OTFMagicBox/StaticViews/CareKit/ContactsSection.swift b/OTFMagicBox/StaticViews/CareKit/ContactsSection.swift index 02ea94c3..001e901b 100644 --- a/OTFMagicBox/StaticViews/CareKit/ContactsSection.swift +++ b/OTFMagicBox/StaticViews/CareKit/ContactsSection.swift @@ -1,25 +1,25 @@ /* Copyright (c) 2021, Hippocrates Technologies S.r.l.. All rights reserved. - + Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: - + 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. - + 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. - + 3. Neither the name of the copyright holder(s) nor the names of any contributor(s) may be used to endorse or promote products derived from this software without specific prior written permission. No license is granted to the trademarks of the copyright holders even if such marks are included in this software. - + 4. Commercial redistribution in any form requires an explicit license agreement with the copyright holder(s). Please contact support@hippocratestech.com for further information regarding licensing. - + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. @@ -35,42 +35,39 @@ import SwiftUI struct ContactsSection: View { - let cellbackgroundColor: UIColor - let headerColor: UIColor - let textColor: UIColor var body: some View { Section(header: Text(ModuleAppYmlReader().careKitModel?.contactHeader ?? "Contact") - .font(YmlReader().appTheme?.headerTitleFont.appFont ?? Font.system(size: 17.0)) - .fontWeight(YmlReader().appTheme?.headerTitleWeight.fontWeight) - .foregroundColor(Color(headerColor))) { + .font(Font.otfheaderTitleFont) + .fontWeight(Font.otfheaderTitleWeight) + .foregroundColor(Color.otfHeaderColor)) { ForEach(ContactStyle.allCases, id: \.rawValue) { row in - + NavigationLink(destination: ContactDestination(style: row)) { Text(String(row.rawValue.capitalized)) - .fontWeight(YmlReader().appTheme?.textWeight.fontWeight) + .fontWeight(Font.otfFontWeight) } - .font(YmlReader().appTheme?.textFont.appFont ?? Font.system(size: 17.0)) + .font(Font.otfAppFont) .foregroundColor(.otfTextColor) .listRowBackground(Color.otfCellBackground) } - .listRowBackground(Color(cellbackgroundColor)) - .foregroundColor(Color(textColor)) + .listRowBackground(Color.otfCellBackground) + .foregroundColor(.otfTextColor) } } } struct ContactsView_Previews: PreviewProvider { static var previews: some View { - ContactsSection(cellbackgroundColor: UIColor(), headerColor: UIColor(), textColor: UIColor()) + ContactsSection() } } private struct ContactDestination: View { - + @Environment(\.storeManager) private var storeManager - + let style: ContactStyle - + var body: some View { ZStack { Color(UIColor.systemGroupedBackground) @@ -82,34 +79,33 @@ private struct ContactDestination: View { } private enum ContactStyle: String, CaseIterable { - case simple , detailed - + case simple, detailed + var rawValue: String { - get { - switch self { - case .simple: - return ModuleAppYmlReader().careKitModel?.simple ?? "" - case .detailed: - return ModuleAppYmlReader().careKitModel?.detailed ?? "" - } - } - } + switch self { + case .simple: + return ModuleAppYmlReader().careKitModel?.simple ?? "" + case .detailed: + return ModuleAppYmlReader().careKitModel?.detailed ?? "" + } + } } import OTFCareKit +import OTFCareKitUI import OTFCareKitStore private struct AdaptedContactView: UIViewControllerRepresentable { - + let style: ContactStyle let storeManager: OCKSynchronizedStoreManager - + func makeUIViewController(context: Context) -> UIViewController { let listViewController = OCKListViewController() - + let spacer = UIView(frame: .init(origin: .zero, size: .init(width: 0, height: 32))) listViewController.appendView(spacer, animated: false) - + let viewController: UIViewController? switch style { case .simple: @@ -117,10 +113,13 @@ private struct AdaptedContactView: UIViewControllerRepresentable { case .detailed: viewController = OCKDetailedContactViewController(contactID: OCKStore.Contacts.matthew.rawValue, storeManager: storeManager) } - + viewController.map { listViewController.appendViewController($0, animated: false) } + if let ockView = listViewController.view as? OCKView { + ockView.customStyle = OTFStyle(from: OTFYamlStyle(style: YmlReader().appStyle)) + } return listViewController } - + func updateUIViewController(_ uiViewController: UIViewController, context: Context) {} } diff --git a/OTFMagicBox/StaticViews/CareKit/TasksSection.swift b/OTFMagicBox/StaticViews/CareKit/TasksSection.swift index a147e53f..6f9b8e75 100644 --- a/OTFMagicBox/StaticViews/CareKit/TasksSection.swift +++ b/OTFMagicBox/StaticViews/CareKit/TasksSection.swift @@ -1,25 +1,25 @@ /* Copyright (c) 2021, Hippocrates Technologies S.r.l.. All rights reserved. - + Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: - + 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. - + 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. - + 3. Neither the name of the copyright holder(s) nor the names of any contributor(s) may be used to endorse or promote products derived from this software without specific prior written permission. No license is granted to the trademarks of the copyright holders even if such marks are included in this software. - + 4. Commercial redistribution in any form requires an explicit license agreement with the copyright holder(s). Please contact support@hippocratestech.com for further information regarding licensing. - + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. @@ -38,42 +38,39 @@ import OTFCareKitUI import OTFCareKitStore struct TaskSection: View { - let cellbackgroundColor: UIColor - let headerColor: UIColor - let textColor: UIColor var body: some View { - Section(header: Text(ModuleAppYmlReader().careKitModel?.taskHeader ?? "Task").font(YmlReader().appTheme?.headerTitleFont.appFont ?? Font.system(size: 17.0)) - .fontWeight(YmlReader().appTheme?.headerTitleWeight.fontWeight) - .foregroundColor(Color(headerColor))) { + Section(header: Text(ModuleAppYmlReader().careKitModel?.taskHeader ?? "Task").font(.otfheaderTitleFont) + .fontWeight(Font.otfheaderTitleWeight) + .foregroundColor(.otfHeaderColor)) { ForEach(TaskStyle.allCases, id: \.rawValue) { style in if style.supportsSwiftUI || style.supportsUIKit { - + NavigationLink(destination: TaskDestination(style: style)) { Text(style.rawValue.capitalized) - .fontWeight(YmlReader().appTheme?.textWeight.fontWeight) + .fontWeight(Font.otfFontWeight) } .foregroundColor(.otfTextColor) .listRowBackground(Color.otfCellBackground) - .font(YmlReader().appTheme?.textFont.appFont ?? Font.system(size: 17.0)) + .font(Font.otfAppFont) } } - .listRowBackground(Color(cellbackgroundColor)) - .foregroundColor(Color(textColor)) + .listRowBackground(Color.otfCellBackground) + .foregroundColor(.otfTextColor) } } } struct TaskDestination: View { - + @Environment(\.storeManager) private var storeManager - + let style: TaskStyle - + var body: some View { ZStack { Color(UIColor.systemGroupedBackground) .edgesIgnoringSafeArea(.all) - + if style.supportsSwiftUI && style.supportsUIKit { PlatformPicker { AdaptedTaskView(style: style, storeManager: storeManager) @@ -90,8 +87,8 @@ struct TaskDestination: View { } } } - .navigationBarTitle(Text(style.rawValue.capitalized).font(YmlReader().appTheme?.textFont.appFont ?? Font.system(size: 17.0)) - .fontWeight(YmlReader().appTheme?.textWeight.fontWeight), displayMode: .inline) + .navigationBarTitle(Text(style.rawValue.capitalized).font(Font.otfAppFont) + .fontWeight(Font.otfFontWeight), displayMode: .inline) } } @@ -100,42 +97,38 @@ enum TaskCategory: String, Codable { } enum TaskStyle: String, CaseIterable, Codable { - + case simple, instruction, buttonLog case grid, checklist case labeledValue = "labeled value", numericProgress = "Numeric Progress" - - var rawValue: String { - get { - switch self { - case .simple: - return ModuleAppYmlReader().careKitModel?.simple ?? "" - case .instruction: - return ModuleAppYmlReader().careKitModel?.instruction ?? "" - case .buttonLog: - return ModuleAppYmlReader().careKitModel?.buttonLog ?? "" - case .grid: - return ModuleAppYmlReader().careKitModel?.grid ?? "" - case .checklist: - return ModuleAppYmlReader().careKitModel?.checklist ?? "" - case .labeledValue: - return ModuleAppYmlReader().careKitModel?.labeledValue ?? "" - case .numericProgress: - return ModuleAppYmlReader().careKitModel?.numericProgress ?? "" - } - } - } - + switch self { + case .simple: + return ModuleAppYmlReader().careKitModel?.simple ?? "" + case .instruction: + return ModuleAppYmlReader().careKitModel?.instruction ?? "" + case .buttonLog: + return ModuleAppYmlReader().careKitModel?.buttonLog ?? "" + case .grid: + return ModuleAppYmlReader().careKitModel?.grid ?? "" + case .checklist: + return ModuleAppYmlReader().careKitModel?.checklist ?? "" + case .labeledValue: + return ModuleAppYmlReader().careKitModel?.labeledValue ?? "" + case .numericProgress: + return ModuleAppYmlReader().careKitModel?.numericProgress ?? "" + } + } + var supportsSwiftUI: Bool { guard #available(iOS 14, *) else { return false } - + switch self { case .simple, .instruction, .labeledValue, .numericProgress: return true case .grid, .checklist, .buttonLog: return false } } - + var supportsUIKit: Bool { switch self { case .simple, .instruction, .grid, .checklist, .buttonLog: return true @@ -146,11 +139,11 @@ enum TaskStyle: String, CaseIterable, Codable { @available(iOS 14.0, *) struct TaskView: View { - + @Environment(\.storeManager) private var storeManager - + let style: TaskStyle - + var body: some View { CardBackground { switch style { @@ -172,24 +165,25 @@ struct TaskView: View { instructions: controller.viewModel?.instructions.map(Text.init), isComplete: controller.viewModel?.isComplete ?? false) } - + // Static view - OTFCareKitUI.NumericProgressTaskView(title: Text("Steps (Static)").font(YmlReader().appTheme?.textFont.appFont ?? Font.system(size: 17.0)) - .fontWeight(YmlReader().appTheme?.textWeight.fontWeight), + OTFCareKitUI.NumericProgressTaskView(title: Text("Steps (Static)").font(Font.otfAppFont) + .fontWeight(Font.otfFontWeight), progress: Text("0"), goal: Text("100"), isComplete: false) - + // Static view - OTFCareKitUI.NumericProgressTaskView(title: Text("Steps (Static)").font(YmlReader().appTheme?.textFont.appFont ?? Font.system(size: 17.0)) - .fontWeight(YmlReader().appTheme?.textWeight.fontWeight), + OTFCareKitUI.NumericProgressTaskView(title: Text("Steps (Static)") + .font(Font.otfAppFont) + .fontWeight(Font.otfFontWeight), progress: Text("0"), goal: Text("100"), isComplete: true) } case .labeledValue: VStack(spacing: 16) { - + // HealthKit linked view OTFCareKit.LabeledValueTaskView(taskID: OCKHealthKitPassthroughStore.Tasks.steps.rawValue, eventQuery: .init(for: Date()), storeManager: storeManager) { controller in @@ -197,21 +191,22 @@ struct TaskView: View { detail: controller.viewModel?.detail.map(Text.init), state: .fromViewModel(state: controller.viewModel?.state)) } - + // Static view - LabeledValueTaskView(title: Text("Heart Rate (Static)").font(YmlReader().appTheme?.textFont.appFont ?? Font.system(size: 17.0)), - detail: Text("Anytime").font(YmlReader().appTheme?.textFont.appFont ?? Font.system(size: 17.0)) - .fontWeight(YmlReader().appTheme?.textWeight.fontWeight), + LabeledValueTaskView(title: Text("Heart Rate (Static)").font(Font.otfAppFont), + detail: Text("Anytime").font(Font.otfAppFont) + .fontWeight(YmlReader().appStyle.textWeight.fontWeight), state: .complete(Text("62"), Text("BPM"))) - + // Static view - LabeledValueTaskView(title: Text("Heart Rate (Static)").font(YmlReader().appTheme?.textFont.appFont ?? Font.system(size: 17.0)) - .fontWeight(YmlReader().appTheme?.textWeight.fontWeight), - detail: Text("Anytime").font(YmlReader().appTheme?.textFont.appFont ?? Font.system(size: 17.0)) - .fontWeight(YmlReader().appTheme?.textWeight.fontWeight), + LabeledValueTaskView(title: Text("Heart Rate (Static)") + .font(Font.otfAppFont) + .fontWeight(Font.otfFontWeight), + detail: Text("Anytime").font(Font.otfAppFont) + .fontWeight(Font.otfFontWeight), state: .incomplete(Text("NO DATA") - .fontWeight(YmlReader().appTheme?.textWeight.fontWeight) - .font(YmlReader().appTheme?.textFont.appFont ?? Font.system(size: 17.0)))) + .fontWeight(Font.otfFontWeight) + .font(Font.otfAppFont))) } default: EmptyView() @@ -221,16 +216,16 @@ struct TaskView: View { } struct AdaptedTaskView: UIViewControllerRepresentable { - + let style: TaskStyle let storeManager: OCKSynchronizedStoreManager - + func makeUIViewController(context: Context) -> UIViewController { let listViewController = OCKListViewController() - + let spacer = UIView(frame: .init(origin: .zero, size: .init(width: 0, height: 32))) listViewController.appendView(spacer, animated: false) - + let taskViewController: UIViewController? switch style { case .simple: @@ -251,21 +246,24 @@ struct AdaptedTaskView: UIViewControllerRepresentable { case .labeledValue, .numericProgress: taskViewController = nil } - + taskViewController.map { listViewController.appendViewController($0, animated: false) } + if let ockView = listViewController.view as? OCKView { + ockView.customStyle = OTFStyle(from: OTFYamlStyle(style: YmlReader().appStyle)) + } return listViewController } - + func updateUIViewController(_ uiViewController: UIViewController, context: Context) {} } private extension LabeledValueTaskViewState { - + static func fromViewModel(state: LabeledValueTaskViewModel.State?) -> Self { guard let state = state else { return .incomplete(Text("")) } - + switch state { case let .complete(value, label): return .complete(Text(value), label.map(Text.init)) @@ -274,52 +272,50 @@ private extension LabeledValueTaskViewState { } } } - - struct TasksSection: View { @StateObject var viewModel: TasksViewModel - + var body: some View { List { Section(header: Text("Simple Task") - .fontWeight(YmlReader().appTheme?.textWeight.fontWeight) - .font(YmlReader().appTheme?.textFont.appFont ?? Font.system(size: 17.0))) { + .fontWeight(Font.otfFontWeight) + .font(Font.otfAppFont)) { if let simpleTask = self.viewModel.simpleTask { SimpleTaskView(task: simpleTask, date: Date(), storeManager: viewModel.syncStoreManager) } } - + Section(header: Text("Instruction Task") - .fontWeight(YmlReader().appTheme?.textWeight.fontWeight) - .font(YmlReader().appTheme?.textFont.appFont ?? Font.system(size: 17.0))) { + .fontWeight(Font.otfFontWeight) + .font(Font.otfAppFont)) { if let instructionTask = self.viewModel.simpleTask { InstructionTaskView(task: instructionTask, date: Date(), storeManager: viewModel.syncStoreManager) } } - + Section(header: Text("Button Log Task") - .fontWeight(YmlReader().appTheme?.textWeight.fontWeight) - .font(YmlReader().appTheme?.textFont.appFont ?? Font.system(size: 17.0))) { + .fontWeight(Font.otfFontWeight) + .font(Font.otfAppFont)) { if let buttonLogTask = self.viewModel.buttonLogTask { ButtonLogTaskView(task: buttonLogTask, date: Date(), storeManager: viewModel.syncStoreManager) } } - + Section(header: Text("Grid Task") - .fontWeight(YmlReader().appTheme?.textWeight.fontWeight) - .font(YmlReader().appTheme?.textFont.appFont ?? Font.system(size: 17.0))) { + .fontWeight(Font.otfFontWeight) + .font(Font.otfAppFont)) { if let gridTask = self.viewModel.checklistTask { GridTaskView(task: gridTask, date: Date(), storeManager: viewModel.syncStoreManager) } } - + Section(header: Text("Checklist Task") - .fontWeight(YmlReader().appTheme?.textWeight.fontWeight) - .font(YmlReader().appTheme?.textFont.appFont ?? Font.system(size: 17.0))) { + .fontWeight(Font.otfFontWeight) + .font(Font.otfAppFont)) { if let checklistTask = self.viewModel.checklistTask { ChecklistTaskView(task: checklistTask, date: Date(), storeManager: viewModel.syncStoreManager) @@ -327,15 +323,10 @@ struct TasksSection: View { } } .listStyle(GroupedListStyle()) -// .onLoad { -// UITableView.appearance().backgroundColor = YmlReader().appTheme?.backgroundColor.color -// UITableViewCell.appearance().backgroundColor = YmlReader().appTheme?.backgroundColor.color -// UITableView.appearance().separatorColor = YmlReader().appTheme?.separatorColor.color -// } } } -struct TasksSection_Preview: PreviewProvider { +struct TasksSectionPreview: PreviewProvider { static var previews: some View { let viewModel = TasksViewModel(OCKStoreManager.shared.synchronizedStoreManager) let tasksSection = TasksSection(viewModel: viewModel) diff --git a/OTFMagicBox/StaticViews/CareKit/TasksViewModel.swift b/OTFMagicBox/StaticViews/CareKit/TasksViewModel.swift index c5b5ac7c..77809f36 100644 --- a/OTFMagicBox/StaticViews/CareKit/TasksViewModel.swift +++ b/OTFMagicBox/StaticViews/CareKit/TasksViewModel.swift @@ -1,25 +1,25 @@ /* Copyright (c) 2021, Hippocrates Technologies S.r.l.. All rights reserved. - + Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: - + 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. - + 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. - + 3. Neither the name of the copyright holder(s) nor the names of any contributor(s) may be used to endorse or promote products derived from this software without specific prior written permission. No license is granted to the trademarks of the copyright holders even if such marks are included in this software. - + 4. Commercial redistribution in any form requires an explicit license agreement with the copyright holder(s). Please contact support@hippocratestech.com for further information regarding licensing. - + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. @@ -39,17 +39,17 @@ import OTFUtilities class TasksViewModel: ObservableObject { let syncStoreManager: OCKSynchronizedStoreManager - - @Published var simpleTask: OCKAnyTask? = nil - @Published var instructionTask: OCKAnyTask? = nil - @Published var buttonLogTask: OCKAnyTask? = nil - @Published var gridTask: OCKAnyTask? = nil - @Published var checklistTask: OCKAnyTask? = nil - + + @Published var simpleTask: OCKAnyTask? + @Published var instructionTask: OCKAnyTask? + @Published var buttonLogTask: OCKAnyTask? + @Published var gridTask: OCKAnyTask? + @Published var checklistTask: OCKAnyTask? + init(_ storeManager: OCKSynchronizedStoreManager) { syncStoreManager = storeManager } - + func fetchTasks() { let identifiers = ["doxylamine", "nausea", "kegels", "steps", "heartRate"] var query = OCKTaskQuery(for: Date()) @@ -58,17 +58,17 @@ class TasksViewModel: ObservableObject { switch result { case .failure(let error): OTFError("error while fetching tasks %{public}@", error.localizedDescription) - + case .success(let tasks): if let kegelsTask = tasks.first(where: { $0.id == "kegels" }) { self.simpleTask = kegelsTask } - + // Create a card for the doxylamine task if there are events for it on this day. if let doxylamineTask = tasks.first(where: { $0.id == "doxylamine" }) { self.checklistTask = doxylamineTask } - + if let nauseaTask = tasks.first(where: { $0.id == "nausea" }) { self.buttonLogTask = nauseaTask } diff --git a/OTFMagicBox/StaticViews/PlatformPicker.swift b/OTFMagicBox/StaticViews/PlatformPicker.swift index 96568171..a41cc780 100644 --- a/OTFMagicBox/StaticViews/PlatformPicker.swift +++ b/OTFMagicBox/StaticViews/PlatformPicker.swift @@ -1,25 +1,25 @@ /* Copyright (c) 2021, Hippocrates Technologies S.r.l.. All rights reserved. - + Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: - + 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. - + 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. - + 3. Neither the name of the copyright holder(s) nor the names of any contributor(s) may be used to endorse or promote products derived from this software without specific prior written permission. No license is granted to the trademarks of the copyright holders even if such marks are included in this software. - + 4. Commercial redistribution in any form requires an explicit license agreement with the copyright holder(s). Please contact support@hippocratestech.com for further information regarding licensing. - + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. @@ -52,10 +52,9 @@ struct PlatformPicker: View { VStack(spacing: 0) { VStack { - Picker(selection: $selectedPlatform, label: Text("Platform") - .fontWeight(YmlReader().appTheme?.textWeight.fontWeight) - .font(YmlReader().appTheme?.textFont.appFont ?? Font.system(size: 17.0))) { + .fontWeight(Font.otfFontWeight) + .font(Font.otfAppFont)) { ForEach(0..: View { } } } - diff --git a/OTFMagicBox/StaticViews/RK UI/RKTasks.swift b/OTFMagicBox/StaticViews/RK UI/RKTasks.swift index 5860a5b1..f5cb9d47 100644 --- a/OTFMagicBox/StaticViews/RK UI/RKTasks.swift +++ b/OTFMagicBox/StaticViews/RK UI/RKTasks.swift @@ -10,12 +10,12 @@ import SwiftUI struct RKTasks: UIViewControllerRepresentable { typealias UIViewControllerType = TaskListViewController - + func makeUIViewController(context: Context) -> TaskListViewController { return TaskListViewController() } - + func updateUIViewController(_ uiViewController: TaskListViewController, context: Context) { - + } } diff --git a/OTFMagicBox/StaticViews/RK UI/SurveysList.swift b/OTFMagicBox/StaticViews/RK UI/SurveysList.swift index 03260135..3ddf912c 100644 --- a/OTFMagicBox/StaticViews/RK UI/SurveysList.swift +++ b/OTFMagicBox/StaticViews/RK UI/SurveysList.swift @@ -8,119 +8,104 @@ import SwiftUI struct SurveysList: View { - let cellbackgroundColor: UIColor - let headerColor: UIColor - let textColor: UIColor var body: some View { Section(header: Text(ModuleAppYmlReader().researchKitModel?.surveysHeaderTitle ?? Constants.dataBucketSurveys) - .font(YmlReader().appTheme?.headerTitleFont.appFont ?? Font.system(size: 17.0)) - .fontWeight(YmlReader().appTheme?.headerTitleWeight.fontWeight) - .foregroundColor(Color(headerColor))) { + .font(.otfheaderTitleFont) + .fontWeight(Font.otfheaderTitleWeight) + .foregroundColor(.otfHeaderColor)) { ForEach(TaskListRow.sections[0].rows, id: \.rawValue) { row in NavigationLink(destination: TaskViewControllerRepresentable(task: row.representedTask).navigationBarHidden(true).ignoresSafeArea()) { Text(String(describing: row)) - .fontWeight(YmlReader().appTheme?.textWeight.fontWeight) + .fontWeight(Font.otfFontWeight) } - .font(YmlReader().appTheme?.textFont.appFont ?? Font.system(size: 17.0)) + .font(Font.otfAppFont) } - .listRowBackground(Color(cellbackgroundColor)) - .foregroundColor(Color(textColor)) + .listRowBackground(Color.otfCellBackground) + .foregroundColor(.otfTextColor) } } } struct RKList_Previews: PreviewProvider { static var previews: some View { - SurveysList(cellbackgroundColor: UIColor(), headerColor: UIColor(), textColor: UIColor()) + SurveysList() } } struct SurveyQuestionsList: View { - let cellbackgroundColor: UIColor - let headerColor: UIColor - let textColor: UIColor var body: some View { Section(header: Text(ModuleAppYmlReader().researchKitModel?.surveyQuestionHeaderTitle ?? "Survey Questions") - .font(YmlReader().appTheme?.headerTitleFont.appFont ?? Font.system(size: 13.0)) - .fontWeight(YmlReader().appTheme?.headerTitleWeight.fontWeight) - .foregroundColor(Color(headerColor))) { - ForEach(TaskListRow.sections[1].rows, id: \.rawValue) { row in - - NavigationLink(destination: (TaskViewControllerRepresentable(task: row.representedTask).navigationBarHidden(true).ignoresSafeArea())) { - Text(String(describing: row)) - .fontWeight(YmlReader().appTheme?.textWeight.fontWeight) - } - .font(YmlReader().appTheme?.textFont.appFont ?? Font.system(size: 17.0)) + .font(.otfheaderTitleFont) + .fontWeight(Font.otfheaderTitleWeight) + .foregroundColor(.otfHeaderColor)) { + ForEach(TaskListRow.sections[1].rows, id: \.rawValue) { row in + + NavigationLink(destination: (TaskViewControllerRepresentable(task: row.representedTask).navigationBarHidden(true).ignoresSafeArea())) { + Text(String(describing: row)) + .fontWeight(Font.otfFontWeight) } - .listRowBackground(Color(cellbackgroundColor)) - .foregroundColor(Color(textColor)) + .font(Font.otfAppFont) } + .listRowBackground(Color.otfCellBackground) + .foregroundColor(.otfTextColor) + } } } struct OnboardingList: View { - let cellbackgroundColor: UIColor - let headerColor: UIColor - let textColor: UIColor var body: some View { Section(header: Text(ModuleAppYmlReader().researchKitModel?.onBoardingHeaderTitle ?? "Onboarding") - .font(YmlReader().appTheme?.headerTitleFont.appFont ?? Font.system(size: 17.0)) - .fontWeight(YmlReader().appTheme?.headerTitleWeight.fontWeight) - .foregroundColor(Color(headerColor))) { - ForEach(TaskListRow.sections[2].rows, id: \.rawValue) { row in - NavigationLink(destination: TaskViewControllerRepresentable(task: row.representedTask).navigationBarHidden(true).ignoresSafeArea()) { - Text(String(describing: row)) - .fontWeight(YmlReader().appTheme?.textWeight.fontWeight) - } - .font(YmlReader().appTheme?.textFont.appFont ?? Font.system(size: 17.0)) + .font(.otfheaderTitleFont) + .fontWeight(Font.otfheaderTitleWeight) + .foregroundColor(.otfHeaderColor)) { + ForEach(TaskListRow.sections[2].rows, id: \.rawValue) { row in + NavigationLink(destination: TaskViewControllerRepresentable(task: row.representedTask).navigationBarHidden(true).ignoresSafeArea()) { + Text(String(describing: row)) + .fontWeight(Font.otfFontWeight) } - .listRowBackground(Color(cellbackgroundColor)) - .foregroundColor(Color(textColor)) + .font(Font.otfAppFont) } + .listRowBackground(Color.otfCellBackground) + .foregroundColor(.otfTextColor) + } } } struct ActiveTasksList: View { - let cellbackgroundColor: UIColor - let headerColor: UIColor - let textColor: UIColor var body: some View { Section(header: Text(ModuleAppYmlReader().researchKitModel?.activeTasksHeaderTitle ?? "Active Tasks") - .font(YmlReader().appTheme?.headerTitleFont.appFont ?? Font.system(size: 17.0)) - .fontWeight(YmlReader().appTheme?.headerTitleWeight.fontWeight) - .foregroundColor(Color(headerColor))) { - ForEach(TaskListRow.sections[3].rows, id: \.rawValue) { row in - NavigationLink(destination: TaskViewControllerRepresentable(task: row.representedTask).navigationBarHidden(true).ignoresSafeArea()) { - Text(String(describing: row)) - .fontWeight(YmlReader().appTheme?.textWeight.fontWeight) - } + .font(.otfheaderTitleFont) + .fontWeight(Font.otfheaderTitleWeight) + .foregroundColor(.otfHeaderColor)) { + ForEach(TaskListRow.sections[3].rows, id: \.rawValue) { row in + NavigationLink(destination: TaskViewControllerRepresentable(task: row.representedTask).navigationBarHidden(true).ignoresSafeArea()) { + Text(String(describing: row)) + .fontWeight(Font.otfFontWeight) } - .font(YmlReader().appTheme?.textFont.appFont ?? Font.system(size: 17.0)) - - .listRowBackground(Color(cellbackgroundColor)) - .foregroundColor(Color(textColor)) } + .font(Font.otfAppFont) + + .listRowBackground(Color.otfCellBackground) + .foregroundColor(.otfTextColor) + } } } struct MiscellaneousList: View { - let cellbackgroundColor: UIColor - let headerColor: UIColor - let textColor: UIColor var body: some View { Section(header: Text(ModuleAppYmlReader().researchKitModel?.miscellaneousHeaderTitle ?? "Miscellaneous") - .font(YmlReader().appTheme?.headerTitleFont.appFont ?? Font.system(size: 17.0)) - .fontWeight(YmlReader().appTheme?.headerTitleWeight.fontWeight) - .foregroundColor(Color(headerColor))) { - ForEach(TaskListRow.sections[4].rows, id: \.rawValue) { row in - NavigationLink(destination: TaskViewControllerRepresentable(task: row.representedTask).navigationBarHidden(true).ignoresSafeArea()) { - Text(String(describing: row)) - .fontWeight(YmlReader().appTheme?.textWeight.fontWeight) - } - .font(YmlReader().appTheme?.textFont.appFont ?? Font.system(size: 17.0)) + .font(.otfheaderTitleFont) + .fontWeight(Font.otfheaderTitleWeight) + .foregroundColor(.otfHeaderColor)) { + ForEach(TaskListRow.sections[4].rows, id: \.rawValue) { row in + NavigationLink(destination: TaskViewControllerRepresentable(task: row.representedTask).navigationBarHidden(true).ignoresSafeArea()) { + Text(String(describing: row)) + .fontWeight(Font.otfFontWeight) } - .listRowBackground(Color(cellbackgroundColor)) - .foregroundColor(Color(textColor)) + .font(Font.otfAppFont) } + .listRowBackground(Color.otfCellBackground) + .foregroundColor(.otfTextColor) + } } } diff --git a/OTFMagicBox/StaticViews/RK UI/TaskListRow+Extras.swift b/OTFMagicBox/StaticViews/RK UI/TaskListRow+Extras.swift new file mode 100644 index 00000000..2aafec9d --- /dev/null +++ b/OTFMagicBox/StaticViews/RK UI/TaskListRow+Extras.swift @@ -0,0 +1,369 @@ +/* + Copyright (c) 2021, Hippocrates Technologies S.r.l.. All rights reserved. + + Redistribution and use in source and binary forms, with or without modification, + are permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation and/or + other materials provided with the distribution. + + 3. Neither the name of the copyright holder(s) nor the names of any contributor(s) may + be used to endorse or promote products derived from this software without specific + prior written permission. No license is granted to the trademarks of the copyright + holders even if such marks are included in this software. + + 4. Commercial redistribution in any form requires an explicit license agreement with the + copyright holder(s). Please contact support@hippocratestech.com for further information + regarding licensing. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, + INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, + OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY + OF SUCH DAMAGE. + */ + +import OTFResearchKit + +extension TaskListRow { + class TaskListRowSection { + var title: String + var rows: [TaskListRow] + init(title: String, rows: [TaskListRow]) { + self.title = title + self.rows = rows + } + } + + /// Returns an array of all the task list row enum cases. + static var sections: [ TaskListRowSection ] { + return [ + TaskListRowSection(title: "Surveys", rows: [.form, .groupedForm, .survey]), + TaskListRowSection(title: "Survey Questions", + rows: [ .booleanQuestion, .customBooleanQuestion, + .dateQuestion, .dateTimeQuestion, + // .heightQuestion, + // .weightQuestion, + .imageChoiceQuestion, .locationQuestion, + .numericQuestion, .scaleQuestion, + .textQuestion, .textChoiceQuestion, + .timeIntervalQuestion, .timeOfDayQuestion, + .valuePickerChoiceQuestion, .validatedTextQuestion, + .imageCapture, .videoCapture, .frontFacingCamera, + .wait, .PDFViewer + // .requestPermissions + ]), + TaskListRowSection( + title: "Onboarding", + rows: [.eligibilityTask, .consent, .accountCreation, .login, .passcode] + ), + TaskListRowSection(title: "Active Tasks", rows: + [ .audio, .amslerGrid, .fitness, .holePegTest, .psat, + .reactionTime, .shortWalk, .spatialSpanMemory, + .speechRecognition, .speechInNoise, .stroop, .swiftStroop, + .timedWalkWithTurnAround, .toneAudiometry, .dBHLToneAudiometry, + .splMeter, .towerOfHanoi, .tremorTest, .twoFingerTappingInterval, + .walkBackAndForth, .kneeRangeOfMotion, .shoulderRangeOfMotion, + .trailMaking, .visualAcuityLandoltC, .contrastSensitivityPeakLandoltC + ]), + TaskListRowSection(title: "Miscellaneous", rows: + [ .videoInstruction, .webView ]) + ] + } +} + +extension TaskListRow { + // MARK: Types + /** + Every step and task in the ResearchKit framework has to have an identifier. + Within a task, the step identifiers should be unique. + + Here we use an enum to ensure that the identifiers are kept unique. Since + the enum has a raw underlying type of a `String`, the compiler can determine + the uniqueness of the case values at compile time. + + In a real application, the identifiers for your tasks and steps might + come from a database, or in a smaller application, might have some + human-readable meaning. + */ + enum Identifier { + // Task with a form, where multiple items appear on one page. + case formTask + case groupedFormTask + case formStep + case groupedFormStep + case formItem01 + case formItem02 + case formItem03 + case formItem04 + // Survey task specific identifiers. + case surveyTask + case introStep + case questionStep + case birthdayQuestion + case summaryStep + // Task with a Boolean question. + case booleanQuestionTask + case booleanQuestionStep + // Task with an example of date entry. + case dateQuestionTask + case dateQuestionStep + // Task with an example of date and time entry. + case dateTimeQuestionTask + case dateTimeQuestionStep + // Task with an example of height entry. + // case heightQuestionTask + case heightQuestionStep1 + case heightQuestionStep2 + case heightQuestionStep3 + case heightQuestionStep4 + // Task with an example of weight entry. + // case weightQuestionTask + case weightQuestionStep1 + case weightQuestionStep2 + case weightQuestionStep3 + case weightQuestionStep4 + case weightQuestionStep5 + case weightQuestionStep6 + case weightQuestionStep7 + // Task with an image choice question. + case imageChoiceQuestionTask + case imageChoiceQuestionStep1 + case imageChoiceQuestionStep2 + // Task with a location entry. + case locationQuestionTask + case locationQuestionStep + // Task with examples of numeric questions. + case numericQuestionTask + case numericQuestionStep + case numericNoUnitQuestionStep + // Task with examples of questions with sliding scales. + case scaleQuestionTask + case discreteScaleQuestionStep + case continuousScaleQuestionStep + case discreteVerticalScaleQuestionStep + case continuousVerticalScaleQuestionStep + case textScaleQuestionStep + case textVerticalScaleQuestionStep + // Task with an example of free text entry. + case textQuestionTask + case textQuestionStep + // Task with an example of a multiple choice question. + case textChoiceQuestionTask + case textChoiceQuestionStep + // Task with an example of time of day entry. + case timeOfDayQuestionTask + case timeOfDayQuestionStep + // Task with an example of time interval entry. + case timeIntervalQuestionTask + case timeIntervalQuestionStep + // Task with a value picker. + case valuePickerChoiceQuestionTask + case valuePickerChoiceQuestionStep + // Task with an example of validated text entry. + case validatedTextQuestionTask + case validatedTextQuestionStepEmail + case validatedTextQuestionStepDomain + // Image capture task specific identifiers. + case imageCaptureTask + case imageCaptureStep + // Video capture task specific identifiers. + case videoCaptureTask + case videoCaptureStep + case frontFacingCameraStep + // Task with an example of waiting. + case waitTask + case waitStepDeterminate + case waitStepIndeterminate + case pdfViewerStep + case pdfViewerTask + // case requestPermissionsStep + // Eligibility task specific indentifiers. + case eligibilityTask + case eligibilityIntroStep + case eligibilityFormStep + case eligibilityFormItem01 + case eligibilityFormItem02 + case eligibilityFormItem03 + case eligibilityIneligibleStep + case eligibilityEligibleStep + // Consent task specific identifiers. + case consentTask + case visualConsentStep + case consentSharingStep + case consentReviewStep + case consentDocumentParticipantSignature + case consentDocumentInvestigatorSignature + // Account creation task specific identifiers. + case accountCreationTask + case registrationStep + case waitStep + case verificationStep + // Login task specific identifiers. + case loginTask + case loginStep + case loginWaitStep + // Passcode task specific identifiers. + case passcodeTask + case passcodeStep + // Active tasks. + case audioTask + case amslerGridTask + case fitnessTask + case holePegTestTask + case psatTask + case reactionTime + case shortWalkTask + case spatialSpanMemoryTask + case speechRecognitionTask + case speechInNoiseTask + case stroopTask + case swiftStroopTask + case timedWalkWithTurnAroundTask + case toneAudiometryTask + case dBHLToneAudiometryTask + case splMeterTask + case splMeterStep + case towerOfHanoi + case tremorTestTask + case twoFingerTappingIntervalTask + case walkBackAndForthTask + case kneeRangeOfMotion + case shoulderRangeOfMotion + case trailMaking + case visualAcuityLandoltC + case contrastSensitivityPeakLandoltC + // Video instruction tasks. + case videoInstructionTask + case videoInstructionStep + // Web view tasks. + case webViewTask + case webViewStep + } +} + +extension TaskListRow { + // MARK: CustomStringConvertible + var description: String { + switch self { + case .form: return NSLocalizedString(ModuleAppYmlReader().researchKitModel?.formSurveyExample ?? "Form Survey Example", comment: "") + case .groupedForm: return NSLocalizedString(ModuleAppYmlReader().researchKitModel?.groupedFormSurveyExample ?? "Grouped Form Survey Example", comment: "") + case .survey: + return NSLocalizedString(ModuleAppYmlReader().researchKitModel?.simpleSurveyExample ?? "Simple Survey Example", comment: "") + case .booleanQuestion: + return NSLocalizedString(ModuleAppYmlReader().researchKitModel?.booleanQuestion ?? "Boolean Question", comment: "") + case .customBooleanQuestion: + return NSLocalizedString(ModuleAppYmlReader().researchKitModel?.customBooleanQuestion ?? "Custom Boolean Question", comment: "") + case .dateQuestion: + return NSLocalizedString(ModuleAppYmlReader().researchKitModel?.dateQuestion ?? "Date Question", comment: "") + case .dateTimeQuestion: + return NSLocalizedString(ModuleAppYmlReader().researchKitModel?.dateAndTimeQuestion ?? "Date and Time Question", comment: "") + case .imageChoiceQuestion: + return NSLocalizedString(ModuleAppYmlReader().researchKitModel?.imageChoiceQuestion ?? "Image Choice Question", comment: "") + case .locationQuestion: + return NSLocalizedString(ModuleAppYmlReader().researchKitModel?.locationQuestion ?? "Location Question", comment: "") + case .numericQuestion: + return NSLocalizedString(ModuleAppYmlReader().researchKitModel?.numericQuestion ?? "Numeric Question", comment: "") + case .scaleQuestion: + return NSLocalizedString(ModuleAppYmlReader().researchKitModel?.scaleQuestion ?? "Scale Question", comment: "") + case .textQuestion: + return NSLocalizedString(ModuleAppYmlReader().researchKitModel?.textQuestion ?? "Text Question", comment: "") + case .textChoiceQuestion: + return NSLocalizedString(ModuleAppYmlReader().researchKitModel?.textChoiceQuestion ?? "Text Choice Question", comment: "") + case .timeIntervalQuestion: + return NSLocalizedString(ModuleAppYmlReader().researchKitModel?.timeIntervalQuestion ?? "Time Interval Question", comment: "") + case .timeOfDayQuestion: + return NSLocalizedString(ModuleAppYmlReader().researchKitModel?.timeOfDayQuestion ?? "Time of Day Question", comment: "") + case .valuePickerChoiceQuestion: + return NSLocalizedString(ModuleAppYmlReader().researchKitModel?.valuePickerChoiceQuestion ?? "Value Picker Choice Question", comment: "") + case .validatedTextQuestion: + return NSLocalizedString(ModuleAppYmlReader().researchKitModel?.validatedTextQuestion ?? "Validated Text Question", comment: "") + case .imageCapture: + return NSLocalizedString(ModuleAppYmlReader().researchKitModel?.imageCaptureStep ?? "Image Capture Step", comment: "") + case .videoCapture: + return NSLocalizedString(ModuleAppYmlReader().researchKitModel?.videoCaptureStep ?? "Video Capture Step", comment: "") + case .frontFacingCamera: + return NSLocalizedString(ModuleAppYmlReader().researchKitModel?.frontFacingCameraStep ?? "Front Facing Camera Step", comment: "") + case .wait: + return NSLocalizedString(ModuleAppYmlReader().researchKitModel?.waitStep ?? "Wait Step", comment: "") + case .PDFViewer: + return NSLocalizedString(ModuleAppYmlReader().researchKitModel?.pdfViewerStep ?? "PDF Viewer Step", comment: "") + case .eligibilityTask: + return NSLocalizedString(ModuleAppYmlReader().researchKitModel?.eligibilityTaskExample ?? "Eligibility Task Example", comment: "") + case .consent: + return NSLocalizedString(ModuleAppYmlReader().researchKitModel?.consentObtainingExample ?? "Consent-Obtaining Example", comment: "") + case .accountCreation: + return NSLocalizedString(ModuleAppYmlReader().researchKitModel?.accountCreation ?? "Account Creation", comment: "") + case .login: + return NSLocalizedString(ModuleAppYmlReader().researchKitModel?.login ?? "Login", comment: "") + case .passcode: + return NSLocalizedString(ModuleAppYmlReader().researchKitModel?.passcodeCreation ?? "Passcode Creation", comment: "") + case .audio: + return NSLocalizedString(ModuleAppYmlReader().researchKitModel?.audio ?? "Audio", comment: "") + + case .amslerGrid: + return NSLocalizedString(ModuleAppYmlReader().researchKitModel?.amslerGrid ?? "Amsler Grid", comment: "") + case .fitness: + return NSLocalizedString(ModuleAppYmlReader().researchKitModel?.fitnessCheck ?? "Fitness Check", comment: "") + + case .holePegTest: + return NSLocalizedString(ModuleAppYmlReader().researchKitModel?.holePegTest ?? "Hole Peg Test", comment: "") + case .psat: + return NSLocalizedString(ModuleAppYmlReader().researchKitModel?.psat ?? "PSAT", comment: "") + case .reactionTime: + return NSLocalizedString(ModuleAppYmlReader().researchKitModel?.reactionTime ?? "Reaction Time", comment: "") + case .shortWalk: + return NSLocalizedString(ModuleAppYmlReader().researchKitModel?.shortWalk ?? "Short Walk", comment: "") + case .spatialSpanMemory: + return NSLocalizedString(ModuleAppYmlReader().researchKitModel?.spatialSpanMemory ?? "Spatial Span Memory", comment: "") + case .speechRecognition: + return NSLocalizedString(ModuleAppYmlReader().researchKitModel?.speechRecognition ?? "Speech Recognition", comment: "") + + case .speechInNoise: + return NSLocalizedString(ModuleAppYmlReader().researchKitModel?.speechInNoise ?? "Speech in Noise", comment: "") + case .stroop: + return NSLocalizedString(ModuleAppYmlReader().researchKitModel?.stroop ?? "Stroop", comment: "") + case .swiftStroop: + return NSLocalizedString(ModuleAppYmlReader().researchKitModel?.swiftStroop ?? "Swift Stroop", comment: "") + case .timedWalkWithTurnAround: + return NSLocalizedString(ModuleAppYmlReader().researchKitModel?.timedWalkWithTurnAround ?? "Timed Walk with Turn Around", comment: "") + case .toneAudiometry: + return NSLocalizedString(ModuleAppYmlReader().researchKitModel?.toneAudiometry ?? "Tone Audiometry", comment: "") + case .dBHLToneAudiometry: + return NSLocalizedString(ModuleAppYmlReader().researchKitModel?.dBHLToneAudiometry ?? "dBHL Tone Audiometry", comment: "") + case .splMeter: + return NSLocalizedString(ModuleAppYmlReader().researchKitModel?.environmentSPLMeter ?? "Environment SPL Meter", comment: "") + case .towerOfHanoi: + return NSLocalizedString(ModuleAppYmlReader().researchKitModel?.towerOfHanoi ?? "Tower of Hanoi", comment: "") + case .twoFingerTappingInterval: + return NSLocalizedString(ModuleAppYmlReader().researchKitModel?.twoFingerTappingInterval ?? "Two Finger Tapping Interval", comment: "") + case .walkBackAndForth: + return NSLocalizedString(ModuleAppYmlReader().researchKitModel?.walkBackAndForth ?? "Walk Back and Forth", comment: "") + case .tremorTest: + return NSLocalizedString(ModuleAppYmlReader().researchKitModel?.tremorTest ?? "Tremor Test", comment: "") + case .videoInstruction: + return NSLocalizedString(ModuleAppYmlReader().researchKitModel?.videoInstructionTask ?? "Video Instruction Task", comment: "") + case .kneeRangeOfMotion: + return NSLocalizedString(ModuleAppYmlReader().researchKitModel?.kneeRangeOfMotion ?? "Knee Range of Motion", comment: "") + case .shoulderRangeOfMotion: + return NSLocalizedString(ModuleAppYmlReader().researchKitModel?.shoulderRangeOfMotion ?? "Shoulder Range of Motion", comment: "") + case .trailMaking: + return NSLocalizedString(ModuleAppYmlReader().researchKitModel?.trailMakingTest ?? "Trail Making Test", comment: "") + case .visualAcuityLandoltC: + return NSLocalizedString(ModuleAppYmlReader().researchKitModel?.visualAcuityLandoltC ?? "Visual Acuity Landolt C", comment: "") + case .contrastSensitivityPeakLandoltC: + return NSLocalizedString(ModuleAppYmlReader().researchKitModel?.contrastSensitivityPeak ?? "Contrast Sensitivity Peak", comment: "") + case .webView: + return NSLocalizedString(ModuleAppYmlReader().researchKitModel?.webView ?? "Web View", comment: "") + } + } +} diff --git a/OTFMagicBox/StaticViews/RK UI/TaskListRow.swift b/OTFMagicBox/StaticViews/RK UI/TaskListRow.swift index feac513b..7e82f3b7 100755 --- a/OTFMagicBox/StaticViews/RK UI/TaskListRow.swift +++ b/OTFMagicBox/StaticViews/RK UI/TaskListRow.swift @@ -5,19 +5,19 @@ Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: - + 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. - + 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. - + 3. Neither the name of the copyright holder(s) nor the names of any contributors may be used to endorse or promote products derived from this software without specific prior written permission. No license is granted to the trademarks of the copyright holders even if such marks are included in this software. - + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE @@ -28,7 +28,7 @@ CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ + */ import OTFResearchKit import AudioToolbox @@ -36,31 +36,31 @@ import HealthKit import UIKit /** - Wraps a SystemSoundID. + Wraps a SystemSoundID. - A class is used in order to provide appropriate cleanup when the sound is - no longer needed. -*/ + A class is used in order to provide appropriate cleanup when the sound is + no longer needed. + */ class SystemSound { var soundID: SystemSoundID = 0 - + init?(soundURL: URL) { if AudioServicesCreateSystemSoundID(soundURL as CFURL, &soundID) != noErr { - return nil + return nil } } - + deinit { AudioServicesDisposeSystemSoundID(soundID) } } /** - An enum that corresponds to a row displayed in a `TaskListViewController`. + An enum that corresponds to a row displayed in a `TaskListViewController`. - Each of the tasks is composed of one or more steps giving examples of the - types of functionality supported by the ResearchKit framework. -*/ + Each of the tasks is composed of one or more steps giving examples of the + types of functionality supported by the ResearchKit framework. + */ enum TaskListRow: Int, CustomStringConvertible { case form = 0 case groupedForm @@ -84,7 +84,7 @@ enum TaskListRow: Int, CustomStringConvertible { case frontFacingCamera case wait case PDFViewer - //case requestPermissions + // case requestPermissions case eligibilityTask case consent case accountCreation @@ -110,8 +110,6 @@ enum TaskListRow: Int, CustomStringConvertible { case tremorTest case twoFingerTappingInterval case walkBackAndForth - //case heightQuestion - //case weightQuestion case kneeRangeOfMotion case shoulderRangeOfMotion case trailMaking @@ -120,1022 +118,116 @@ enum TaskListRow: Int, CustomStringConvertible { case videoInstruction case webView - class TaskListRowSection { - var title: String - var rows: [TaskListRow] - - init(title: String, rows: [TaskListRow]) { - self.title = title - self.rows = rows - } - } - - /// Returns an array of all the task list row enum cases. - static var sections: [ TaskListRowSection ] { - - return [ - TaskListRowSection(title: "Surveys", rows: - [ - .form, - .groupedForm, - .survey - ]), - TaskListRowSection(title: "Survey Questions", rows: - [ - .booleanQuestion, - .customBooleanQuestion, - .dateQuestion, - .dateTimeQuestion, - //.heightQuestion, - //.weightQuestion, - .imageChoiceQuestion, - .locationQuestion, - .numericQuestion, - .scaleQuestion, - .textQuestion, - .textChoiceQuestion, - .timeIntervalQuestion, - .timeOfDayQuestion, - .valuePickerChoiceQuestion, - .validatedTextQuestion, - .imageCapture, - .videoCapture, - .frontFacingCamera, - .wait, - .PDFViewer, - //.requestPermissions - ]), - TaskListRowSection(title: "Onboarding", rows: - [ - .eligibilityTask, - .consent, - .accountCreation, - .login, - .passcode - ]), - TaskListRowSection(title: "Active Tasks", rows: - [ - .audio, - .amslerGrid, - .fitness, - .holePegTest, - .psat, - .reactionTime, - .shortWalk, - .spatialSpanMemory, - .speechRecognition, - .speechInNoise, - .stroop, - .swiftStroop, - .timedWalkWithTurnAround, - .toneAudiometry, - .dBHLToneAudiometry, - .splMeter, - .towerOfHanoi, - .tremorTest, - .twoFingerTappingInterval, - .walkBackAndForth, - .kneeRangeOfMotion, - .shoulderRangeOfMotion, - .trailMaking, - .visualAcuityLandoltC, - .contrastSensitivityPeakLandoltC - ]), - TaskListRowSection(title: "Miscellaneous", rows: - [ - .videoInstruction, - .webView - ]) - ]} - - // MARK: CustomStringConvertible - - var description: String { - switch self { - case .form: - return NSLocalizedString(ModuleAppYmlReader().researchKitModel?.formSurveyExample ?? "Form Survey Example", comment: "") - - case .groupedForm: - return NSLocalizedString(ModuleAppYmlReader().researchKitModel?.groupedFormSurveyExample ?? "Grouped Form Survey Example", comment: "") - - case .survey: - return NSLocalizedString(ModuleAppYmlReader().researchKitModel?.simpleSurveyExample ?? "Simple Survey Example", comment: "") - - case .booleanQuestion: - return NSLocalizedString(ModuleAppYmlReader().researchKitModel?.booleanQuestion ?? "Boolean Question", comment: "") - - case .customBooleanQuestion: - return NSLocalizedString(ModuleAppYmlReader().researchKitModel?.customBooleanQuestion ?? "Custom Boolean Question", comment: "") - - case .dateQuestion: - return NSLocalizedString(ModuleAppYmlReader().researchKitModel?.dateQuestion ?? "Date Question", comment: "") - - case .dateTimeQuestion: - return NSLocalizedString(ModuleAppYmlReader().researchKitModel?.dateAndTimeQuestion ?? "Date and Time Question", comment: "") - -// case .heightQuestion: -// return NSLocalizedString("Height Question", comment: "") - -// case .weightQuestion: -// return NSLocalizedString("Weight Question", comment: "") - - case .imageChoiceQuestion: - return NSLocalizedString(ModuleAppYmlReader().researchKitModel?.imageChoiceQuestion ?? "Image Choice Question", comment: "") - - case .locationQuestion: - return NSLocalizedString(ModuleAppYmlReader().researchKitModel?.locationQuestion ?? "Location Question", comment: "") - - case .numericQuestion: - return NSLocalizedString(ModuleAppYmlReader().researchKitModel?.numericQuestion ?? "Numeric Question", comment: "") - - case .scaleQuestion: - return NSLocalizedString(ModuleAppYmlReader().researchKitModel?.scaleQuestion ?? "Scale Question", comment: "") - - case .textQuestion: - return NSLocalizedString(ModuleAppYmlReader().researchKitModel?.textQuestion ?? "Text Question", comment: "") - - case .textChoiceQuestion: - return NSLocalizedString(ModuleAppYmlReader().researchKitModel?.textChoiceQuestion ?? "Text Choice Question", comment: "") - - case .timeIntervalQuestion: - return NSLocalizedString(ModuleAppYmlReader().researchKitModel?.timeIntervalQuestion ?? "Time Interval Question", comment: "") - - case .timeOfDayQuestion: - return NSLocalizedString(ModuleAppYmlReader().researchKitModel?.timeOfDayQuestion ?? "Time of Day Question", comment: "") - - case .valuePickerChoiceQuestion: - return NSLocalizedString(ModuleAppYmlReader().researchKitModel?.valuePickerChoiceQuestion ?? "Value Picker Choice Question", comment: "") - - case .validatedTextQuestion: - return NSLocalizedString(ModuleAppYmlReader().researchKitModel?.validatedTextQuestion ?? "Validated Text Question", comment: "") - - case .imageCapture: - return NSLocalizedString(ModuleAppYmlReader().researchKitModel?.imageCaptureStep ?? "Image Capture Step", comment: "") - - case .videoCapture: - return NSLocalizedString(ModuleAppYmlReader().researchKitModel?.videoCaptureStep ?? "Video Capture Step", comment: "") - - case .frontFacingCamera: - return NSLocalizedString(ModuleAppYmlReader().researchKitModel?.frontFacingCameraStep ?? "Front Facing Camera Step", comment: "") - - case .wait: - return NSLocalizedString(ModuleAppYmlReader().researchKitModel?.waitStep ?? "Wait Step", comment: "") - - case .PDFViewer: - return NSLocalizedString(ModuleAppYmlReader().researchKitModel?.pdfViewerStep ?? "PDF Viewer Step", comment: "") - -// case .requestPermissions: -// return NSLocalizedString("Request Permissions Step", comment: "") - - case .eligibilityTask: - return NSLocalizedString(ModuleAppYmlReader().researchKitModel?.eligibilityTaskExample ?? "Eligibility Task Example", comment: "") - - case .consent: - return NSLocalizedString(ModuleAppYmlReader().researchKitModel?.consentObtainingExample ?? "Consent-Obtaining Example", comment: "") - - case .accountCreation: - return NSLocalizedString(ModuleAppYmlReader().researchKitModel?.accountCreation ?? "Account Creation", comment: "") - - case .login: - return NSLocalizedString(ModuleAppYmlReader().researchKitModel?.login ?? "Login", comment: "") - - case .passcode: - return NSLocalizedString(ModuleAppYmlReader().researchKitModel?.passcodeCreation ?? "Passcode Creation", comment: "") - - case .audio: - return NSLocalizedString(ModuleAppYmlReader().researchKitModel?.audio ?? "Audio", comment: "") - - case .amslerGrid: - return NSLocalizedString(ModuleAppYmlReader().researchKitModel?.amslerGrid ?? "Amsler Grid", comment: "") - - case .fitness: - return NSLocalizedString(ModuleAppYmlReader().researchKitModel?.fitnessCheck ?? "Fitness Check", comment: "") - - case .holePegTest: - return NSLocalizedString(ModuleAppYmlReader().researchKitModel?.holePegTest ?? "Hole Peg Test", comment: "") - - case .psat: - return NSLocalizedString(ModuleAppYmlReader().researchKitModel?.psat ?? "PSAT", comment: "") - - case .reactionTime: - return NSLocalizedString(ModuleAppYmlReader().researchKitModel?.reactionTime ?? "Reaction Time", comment: "") - - case .shortWalk: - return NSLocalizedString(ModuleAppYmlReader().researchKitModel?.shortWalk ?? "Short Walk", comment: "") - - case .spatialSpanMemory: - return NSLocalizedString(ModuleAppYmlReader().researchKitModel?.spatialSpanMemory ?? "Spatial Span Memory", comment: "") - - case .speechRecognition: - return NSLocalizedString(ModuleAppYmlReader().researchKitModel?.speechRecognition ?? "Speech Recognition", comment: "") - - case .speechInNoise: - return NSLocalizedString(ModuleAppYmlReader().researchKitModel?.speechInNoise ?? "Speech in Noise", comment: "") - - case .stroop: - return NSLocalizedString(ModuleAppYmlReader().researchKitModel?.stroop ?? "Stroop", comment: "") - - case .swiftStroop: - return NSLocalizedString(ModuleAppYmlReader().researchKitModel?.swiftStroop ?? "Swift Stroop", comment: "") - - case .timedWalkWithTurnAround: - return NSLocalizedString(ModuleAppYmlReader().researchKitModel?.timedWalkWithTurnAround ?? "Timed Walk with Turn Around", comment: "") - - case .toneAudiometry: - return NSLocalizedString(ModuleAppYmlReader().researchKitModel?.toneAudiometry ?? "Tone Audiometry", comment: "") - - case .dBHLToneAudiometry: - return NSLocalizedString(ModuleAppYmlReader().researchKitModel?.dBHLToneAudiometry ?? "dBHL Tone Audiometry", comment: "") - - case .splMeter: - return NSLocalizedString(ModuleAppYmlReader().researchKitModel?.environmentSPLMeter ?? "Environment SPL Meter", comment: "") - - case .towerOfHanoi: - return NSLocalizedString(ModuleAppYmlReader().researchKitModel?.towerOfHanoi ?? "Tower of Hanoi", comment: "") - - case .twoFingerTappingInterval: - return NSLocalizedString(ModuleAppYmlReader().researchKitModel?.twoFingerTappingInterval ?? "Two Finger Tapping Interval", comment: "") - - case .walkBackAndForth: - return NSLocalizedString(ModuleAppYmlReader().researchKitModel?.walkBackAndForth ?? "Walk Back and Forth", comment: "") - - case .tremorTest: - return NSLocalizedString(ModuleAppYmlReader().researchKitModel?.tremorTest ?? "Tremor Test", comment: "") - - case .videoInstruction: - return NSLocalizedString(ModuleAppYmlReader().researchKitModel?.videoInstructionTask ?? "Video Instruction Task", comment: "") - - case .kneeRangeOfMotion: - return NSLocalizedString(ModuleAppYmlReader().researchKitModel?.kneeRangeOfMotion ?? "Knee Range of Motion", comment: "") - - case .shoulderRangeOfMotion: - return NSLocalizedString(ModuleAppYmlReader().researchKitModel?.shoulderRangeOfMotion ?? "Shoulder Range of Motion", comment: "") - - case .trailMaking: - return NSLocalizedString(ModuleAppYmlReader().researchKitModel?.trailMakingTest ?? "Trail Making Test", comment: "") - - case .visualAcuityLandoltC: - return NSLocalizedString(ModuleAppYmlReader().researchKitModel?.visualAcuityLandoltC ?? "Visual Acuity Landolt C", comment: "") - - case .contrastSensitivityPeakLandoltC: - return NSLocalizedString(ModuleAppYmlReader().researchKitModel?.contrastSensitivityPeak ?? "Contrast Sensitivity Peak", comment: "") - - case .webView: - return NSLocalizedString(ModuleAppYmlReader().researchKitModel?.webView ?? "Web View", comment: "") - } - } - - // MARK: Types - - /** - Every step and task in the ResearchKit framework has to have an identifier. - Within a task, the step identifiers should be unique. - - Here we use an enum to ensure that the identifiers are kept unique. Since - the enum has a raw underlying type of a `String`, the compiler can determine - the uniqueness of the case values at compile time. - - In a real application, the identifiers for your tasks and steps might - come from a database, or in a smaller application, might have some - human-readable meaning. - */ - enum Identifier { - // Task with a form, where multiple items appear on one page. - case formTask - case groupedFormTask - case formStep - case groupedFormStep - case formItem01 - case formItem02 - case formItem03 - case formItem04 - - // Survey task specific identifiers. - case surveyTask - case introStep - case questionStep - case birthdayQuestion - case summaryStep - - // Task with a Boolean question. - case booleanQuestionTask - case booleanQuestionStep - - // Task with an example of date entry. - case dateQuestionTask - case dateQuestionStep - - // Task with an example of date and time entry. - case dateTimeQuestionTask - case dateTimeQuestionStep - - // Task with an example of height entry. - //case heightQuestionTask - case heightQuestionStep1 - case heightQuestionStep2 - case heightQuestionStep3 - case heightQuestionStep4 - - // Task with an example of weight entry. - //case weightQuestionTask - case weightQuestionStep1 - case weightQuestionStep2 - case weightQuestionStep3 - case weightQuestionStep4 - case weightQuestionStep5 - case weightQuestionStep6 - case weightQuestionStep7 - - // Task with an image choice question. - case imageChoiceQuestionTask - case imageChoiceQuestionStep1 - case imageChoiceQuestionStep2 - - // Task with a location entry. - case locationQuestionTask - case locationQuestionStep - - // Task with examples of numeric questions. - case numericQuestionTask - case numericQuestionStep - case numericNoUnitQuestionStep - - // Task with examples of questions with sliding scales. - case scaleQuestionTask - case discreteScaleQuestionStep - case continuousScaleQuestionStep - case discreteVerticalScaleQuestionStep - case continuousVerticalScaleQuestionStep - case textScaleQuestionStep - case textVerticalScaleQuestionStep - - // Task with an example of free text entry. - case textQuestionTask - case textQuestionStep - - // Task with an example of a multiple choice question. - case textChoiceQuestionTask - case textChoiceQuestionStep - - // Task with an example of time of day entry. - case timeOfDayQuestionTask - case timeOfDayQuestionStep - - // Task with an example of time interval entry. - case timeIntervalQuestionTask - case timeIntervalQuestionStep - - // Task with a value picker. - case valuePickerChoiceQuestionTask - case valuePickerChoiceQuestionStep - - // Task with an example of validated text entry. - case validatedTextQuestionTask - case validatedTextQuestionStepEmail - case validatedTextQuestionStepDomain - - // Image capture task specific identifiers. - case imageCaptureTask - case imageCaptureStep - - // Video capture task specific identifiers. - case videoCaptureTask - case videoCaptureStep - - case frontFacingCameraStep - - // Task with an example of waiting. - case waitTask - case waitStepDeterminate - case waitStepIndeterminate - - case pdfViewerStep - case pdfViewerTask - - //case requestPermissionsStep - - // Eligibility task specific indentifiers. - case eligibilityTask - case eligibilityIntroStep - case eligibilityFormStep - case eligibilityFormItem01 - case eligibilityFormItem02 - case eligibilityFormItem03 - case eligibilityIneligibleStep - case eligibilityEligibleStep - - // Consent task specific identifiers. - case consentTask - case visualConsentStep - case consentSharingStep - case consentReviewStep - case consentDocumentParticipantSignature - case consentDocumentInvestigatorSignature - - // Account creation task specific identifiers. - case accountCreationTask - case registrationStep - case waitStep - case verificationStep - - // Login task specific identifiers. - case loginTask - case loginStep - case loginWaitStep - - // Passcode task specific identifiers. - case passcodeTask - case passcodeStep - - // Active tasks. - case audioTask - case amslerGridTask - case fitnessTask - case holePegTestTask - case psatTask - case reactionTime - case shortWalkTask - case spatialSpanMemoryTask - case speechRecognitionTask - case speechInNoiseTask - case stroopTask - case swiftStroopTask - case timedWalkWithTurnAroundTask - case toneAudiometryTask - case dBHLToneAudiometryTask - case splMeterTask - case splMeterStep - case towerOfHanoi - case tremorTestTask - case twoFingerTappingIntervalTask - case walkBackAndForthTask - case kneeRangeOfMotion - case shoulderRangeOfMotion - case trailMaking - case visualAcuityLandoltC - case contrastSensitivityPeakLandoltC - - // Video instruction tasks. - case videoInstructionTask - case videoInstructionStep - - // Web view tasks. - case webViewTask - case webViewStep - } - // MARK: Properties - - /// Returns a new `ORKTask` that the `TaskListRow` enumeration represents. - var representedTask: ORKTask { - switch self { - case .form: - return formTask - - case .groupedForm: - return groupedFormTask - - case .survey: - return surveyTask - - case .booleanQuestion: - return booleanQuestionTask - - case .customBooleanQuestion: - return customBooleanQuestionTask - - case .dateQuestion: - return dateQuestionTask - - case .dateTimeQuestion: - return dateTimeQuestionTask - -// case .heightQuestion: -// return heightQuestionTask - -// case .weightQuestion: -// return weightQuestionTask - - case .imageChoiceQuestion: - return imageChoiceQuestionTask - - case .locationQuestion: - return locationQuestionTask - - case .numericQuestion: - return numericQuestionTask - - case .scaleQuestion: - return scaleQuestionTask - - case .textQuestion: - return textQuestionTask - - case .textChoiceQuestion: - return textChoiceQuestionTask - - case .timeIntervalQuestion: - return timeIntervalQuestionTask - - case .timeOfDayQuestion: - return timeOfDayQuestionTask - - case .valuePickerChoiceQuestion: - return valuePickerChoiceQuestionTask - - case .validatedTextQuestion: - return validatedTextQuestionTask - - case .imageCapture: - return imageCaptureTask - - case .videoCapture: - return videoCaptureTask - - case .frontFacingCamera: - return frontFacingCameraStep - - case .wait: - return waitTask - - case .PDFViewer: - return PDFViewerTask - -// case .requestPermissions: -// return requestPermissionsTask - - case .eligibilityTask: - return eligibilityTask - - case .consent: - return consentTask - - case .accountCreation: - return accountCreationTask - - case .login: - return loginTask - - case .passcode: - return passcodeTask - - case .audio: - return audioTask - - case .amslerGrid: - return amslerGridTask - - case .fitness: - return fitnessTask - - case .holePegTest: - return holePegTestTask - - case .psat: - return PSATTask - - case .reactionTime: - return reactionTimeTask - - case .shortWalk: - return shortWalkTask - - case .spatialSpanMemory: - return spatialSpanMemoryTask - - case .speechRecognition: - return speechRecognitionTask - - case .speechInNoise: - return speechInNoiseTask - - case .stroop: - return stroopTask - - case .swiftStroop: - return swiftStroopTask - - case .timedWalkWithTurnAround: - return timedWalkWithTurnAroundTask - - case .toneAudiometry: - return toneAudiometryTask - - case .dBHLToneAudiometry: - return dBHLToneAudiometryTask - - case .splMeter: - return splMeterTask - - case .towerOfHanoi: - return towerOfHanoiTask - - case .twoFingerTappingInterval: - return twoFingerTappingIntervalTask - - case .walkBackAndForth: - return walkBackAndForthTask - - case .tremorTest: - return tremorTestTask - - case .kneeRangeOfMotion: - return kneeRangeOfMotion - - case .shoulderRangeOfMotion: - return shoulderRangeOfMotion - - case .trailMaking: - return trailMaking - - case .visualAcuityLandoltC: - return visualAcuityLandoltC - - case .contrastSensitivityPeakLandoltC: - return contrastSensitivityPeakLandoltC - - case .videoInstruction: - return videoInstruction - - case .webView: - return webView - } - } - - // MARK: Task Creation Convenience - - /** - This task demonstrates a form step, in which multiple items are presented - in a single scrollable form. This might be used for entering multi-value - data, like taking a blood pressure reading with separate systolic and - diastolic values. - */ - private var formTask: ORKTask { - let step = ORKFormStep(identifier: String(describing: Identifier.formStep), title: NSLocalizedString(ModuleAppYmlReader().surverysTaskModel?.title ?? "Form Step", comment: ""), text: ModuleAppYmlReader().surverysTaskModel?.additionalText ?? "Additional text can go here.") - - // A first field, for entering an integer. - let formItem01Text = NSLocalizedString("Field01", comment: "") - let formItem01 = ORKFormItem(identifier: String(describing: Identifier.formItem01), text: formItem01Text, answerFormat: ORKAnswerFormat.integerAnswerFormat(withUnit: nil)) - formItem01.placeholder = NSLocalizedString(ModuleAppYmlReader().surverysTaskModel?.placeholder ?? Constants.YamlDefaults.placeholder, comment: "") - - // A second field, for entering a time interval. - let formItem02Text = NSLocalizedString("Field02", comment: "") - let formItem02 = ORKFormItem(identifier: String(describing: Identifier.formItem02), text: formItem02Text, answerFormat: ORKTimeIntervalAnswerFormat()) - formItem02.placeholder = NSLocalizedString(ModuleAppYmlReader().surverysTaskModel?.placeholder ?? Constants.YamlDefaults.placeholder, comment: "") - - let formItem03Text = NSLocalizedString(ModuleAppYmlReader().surverysTaskModel?.questionText ?? Constants.YamlDefaults.questionText, comment: "") - let scaleAnswerFormat = ORKScaleAnswerFormat(maximumValue: 10, minimumValue: 0, defaultValue: 0, step: 1)//ORKScaleAnswerFormat(maximumValue: 10, minimumValue: 0, defaultValue: 0, step: 1) - scaleAnswerFormat.shouldHideRanges = true - let formItem03 = ORKFormItem(identifier: String(describing: Identifier.formItem03), text: formItem03Text, answerFormat: scaleAnswerFormat) - - let textChoices: [ORKTextChoice] = [ - ORKTextChoice(text: "choice 1", detailText: "detail 1", value: 1 as NSCoding & NSCopying & NSObjectProtocol, exclusive: false), - ORKTextChoice(text: "choice 2", detailText: "detail 2", value: 2 as NSCoding & NSCopying & NSObjectProtocol, exclusive: false), - ORKTextChoice(text: "choice 3", detailText: "detail 3", value: 3 as NSCoding & NSCopying & NSObjectProtocol, exclusive: false), - ORKTextChoice(text: "choice 4", detailText: "detail 4", value: 4 as NSCoding & NSCopying & NSObjectProtocol, exclusive: false), - ORKTextChoice(text: "choice 5", detailText: "detail 5", value: 5 as NSCoding & NSCopying & NSObjectProtocol, exclusive: false), - ORKTextChoice(text: "choice 6", detailText: "detail 6", value: 6 as NSCoding & NSCopying & NSObjectProtocol, exclusive: false) - ] - - let textScaleAnswerFormat = ORKTextScaleAnswerFormat(textChoices: textChoices, defaultIndex: 10) - textScaleAnswerFormat.shouldHideLabels = true - textScaleAnswerFormat.shouldShowDontKnowButton = true - let formItem04 = ORKFormItem(identifier: String(describing: Identifier.formItem04), text: ModuleAppYmlReader().surverysTaskModel?.questionText ?? Constants.YamlDefaults.questionText, answerFormat: textScaleAnswerFormat) - - let appleChoices: [ORKTextChoice] = [ORKTextChoice(text:ModuleAppYmlReader().surverysTaskModel?.grannySmith ?? "Granny Smith", value: 1 as NSCoding & NSCopying & NSObjectProtocol), ORKTextChoice(text:ModuleAppYmlReader().surverysTaskModel?.honeycrisp ?? "Honeycrisp", value: 2 as NSCoding & NSCopying & NSObjectProtocol), ORKTextChoice(text:ModuleAppYmlReader().surverysTaskModel?.fuji ?? "Fuji", value: 3 as NSCoding & NSCopying & NSObjectProtocol), ORKTextChoice(text:ModuleAppYmlReader().surverysTaskModel?.mcIntosh ?? "McIntosh", value: 10 as NSCoding & NSCopying & NSObjectProtocol), ORKTextChoice(text:ModuleAppYmlReader().surverysTaskModel?.kanzi ?? "Kanzi", value: 5 as NSCoding & NSCopying & NSObjectProtocol)] - - let appleAnswerFormat = ORKTextChoiceAnswerFormat(style: .singleChoice, textChoices: appleChoices) - - let appleFormItem = ORKFormItem(identifier: "appleFormItemIdentifier", text:ModuleAppYmlReader().surverysTaskModel?.itemQuestion ?? Constants.YamlDefaults.favorite, answerFormat: appleAnswerFormat) - - - step.formItems = [ - appleFormItem, - formItem03, - formItem04, - formItem01, - formItem02 - ] - let completionStep = ORKCompletionStep(identifier: "CompletionStep") - completionStep.title = NSLocalizedString("All Done!", comment: "") - completionStep.detailText = NSLocalizedString("You have completed the questionnaire.", comment: "") - return ORKOrderedTask(identifier: String(describing: Identifier.formTask), steps: [step, completionStep]) - } - - private var groupedFormTask: ORKTask { - let step = ORKFormStep(identifier: String(describing: Identifier.groupedFormStep), title: NSLocalizedString(ModuleAppYmlReader().surverysTaskModel?.groupServeyTitle ?? "Form Step", comment: ""), text: ModuleAppYmlReader().surverysTaskModel?.questionText ?? Constants.YamlDefaults.YourQuestion) - - //Start of first section - let learnMoreInstructionStep01 = ORKLearnMoreInstructionStep(identifier: "LearnMoreInstructionStep01") - learnMoreInstructionStep01.title = NSLocalizedString(ModuleAppYmlReader().surverysTaskModel?.learnMoreTitle ?? Constants.YamlDefaults.learnMoreTitle, comment: "") - learnMoreInstructionStep01.text = NSLocalizedString(ModuleAppYmlReader().surverysTaskModel?.learnMoreText ?? Constants.YamlDefaults.learnMoreText, comment: "") - let learnMoreItem01 = ORKLearnMoreItem(text: nil, learnMoreInstructionStep: learnMoreInstructionStep01) - let section01 = ORKFormItem(sectionTitle: NSLocalizedString(ModuleAppYmlReader().surverysTaskModel?.sectionTitle ?? "Section title", comment: ""), detailText: NSLocalizedString(ModuleAppYmlReader().surverysTaskModel?.sectionDetailText ?? "Section detail text", comment: ""), learnMoreItem: learnMoreItem01, showsProgress: true) - - // A first field, for entering an integer. - let formItem01Text = NSLocalizedString("Field01", comment: "") - let formItem01 = ORKFormItem(identifier: String(describing: Identifier.formItem01), text: formItem01Text, answerFormat: ORKAnswerFormat.integerAnswerFormat(withUnit: nil)) - formItem01.placeholder = NSLocalizedString(ModuleAppYmlReader().surverysTaskModel?.placeholder ?? Constants.YamlDefaults.placeholder, comment: "") - - // A second field, for entering a time interval. - let formItem02Text = NSLocalizedString("Field02", comment: "") - let formItem02 = ORKFormItem(identifier: String(describing: Identifier.formItem02), text: formItem02Text, answerFormat: ORKTimeIntervalAnswerFormat()) - formItem02.placeholder = NSLocalizedString(ModuleAppYmlReader().surverysTaskModel?.placeholder ?? Constants.YamlDefaults.placeholder, comment: "") - - - let sesAnswerFormat = ORKSESAnswerFormat(topRungText: "Best Off", bottomRungText: "Worst Off") - let sesFormItem = ORKFormItem(identifier: "sesIdentifier", text:ModuleAppYmlReader().surverysTaskModel?.socioeconomicLadder ?? "Select where you are on the socioeconomic ladder.", answerFormat: sesAnswerFormat) - - - //Start of section for scale question - let formItem03Text = NSLocalizedString(ModuleAppYmlReader().surverysTaskModel?.questionText ?? Constants.YamlDefaults.questionText, comment: "") - let scaleAnswerFormat = ORKContinuousScaleAnswerFormat(maximumValue: 10, minimumValue: 0, defaultValue: 0.0, maximumFractionDigits: 1)//ORKScaleAnswerFormat(maximumValue: 10, minimumValue: 0, defaultValue: 0, step: 1) - let formItem03 = ORKFormItem(identifier: String(describing: Identifier.formItem03), text: formItem03Text, detailText: nil, learnMoreItem: nil, showsProgress: true, answerFormat: scaleAnswerFormat, tagText: nil, optional: true) - - step.formItems = [ - section01, - formItem01, - formItem02, - formItem03, - sesFormItem - ] - - // Add a question step. - let question1StepAnswerFormat = ORKBooleanAnswerFormat() - - let question1 = NSLocalizedString(ModuleAppYmlReader().surverysTaskModel?.newsletter ?? Constants.YamlDefaults.newsletter, comment: "") - - let learnMoreInstructionStep = ORKLearnMoreInstructionStep(identifier: "LearnMoreInstructionStep01") - learnMoreInstructionStep.title = NSLocalizedString(ModuleAppYmlReader().surverysTaskModel?.learnMoreTitle ?? Constants.YamlDefaults.learnMoreTitle, comment: "") - learnMoreInstructionStep.text = NSLocalizedString(ModuleAppYmlReader().surverysTaskModel?.learnMoreText ?? Constants.YamlDefaults.learnMoreText, comment: "") - let learnMoreItem = ORKLearnMoreItem(text: nil, learnMoreInstructionStep: learnMoreInstructionStep) - - let question1Step = ORKQuestionStep(identifier: String(describing: Identifier.questionStep), title: ModuleAppYmlReader().surverysTaskModel?.questionnaire ?? Constants.YamlDefaults.Questionnaire, question: question1, answer: question1StepAnswerFormat, learnMoreItem: learnMoreItem) - question1Step.text = exampleDetailText - - //Add a question step with different layout format. - let question2StepAnswerFormat = ORKAnswerFormat.dateAnswerFormat(withDefaultDate: nil, minimumDate: nil, maximumDate: Date(), calendar: nil) - - let question2 = NSLocalizedString(ModuleAppYmlReader().surverysTaskModel?.birthdayText ?? Constants.YamlDefaults.birthdayText, comment: "") - let question2Step = ORKQuestionStep(identifier: String(describing: Identifier.birthdayQuestion), title: ModuleAppYmlReader().surverysTaskModel?.questionnaire ?? Constants.YamlDefaults.Questionnaire, question: question2, answer: question2StepAnswerFormat) - question2Step.text = exampleDetailText - - - let appleChoices: [ORKTextChoice] = [ORKTextChoice(text: "Granny Smith", value: 1 as NSCoding & NSCopying & NSObjectProtocol), ORKTextChoice(text: "Honeycrisp", value: 2 as NSCoding & NSCopying & NSObjectProtocol), ORKTextChoice(text: "Fuji", value: 3 as NSCoding & NSCopying & NSObjectProtocol), ORKTextChoice(text: "McIntosh", value: 10 as NSCoding & NSCopying & NSObjectProtocol), ORKTextChoice(text: "Kanzi", value: 5 as NSCoding & NSCopying & NSObjectProtocol)] - - let appleAnswerFormat = ORKTextChoiceAnswerFormat(style: .singleChoice, textChoices: appleChoices) - - let appleFormItem = ORKFormItem(identifier: "appleFormItemIdentifier", text: ModuleAppYmlReader().surverysTaskModel?.itemQuestion ?? Constants.YamlDefaults.favorite, answerFormat: appleAnswerFormat) - - let appleFormStep = ORKFormStep(identifier: "appleFormStepIdentifier", title: "Fruit!", text: "Select the fruit you like.") - - appleFormStep.formItems = [appleFormItem] - - return ORKOrderedTask(identifier: String(describing: Identifier.groupedFormTask), steps: [step, question1Step, question2Step, appleFormStep]) - } - /** - A task demonstrating how the ResearchKit framework can be used to present a simple - survey with an introduction, a question, and a conclusion. - */ + A task demonstrating how the ResearchKit framework can be used to present a simple + survey with an introduction, a question, and a conclusion. + */ private var surveyTask: ORKTask { // Create the intro step. let instructionStep = ORKInstructionStep(identifier: String(describing: Identifier.introStep)) instructionStep.title = NSLocalizedString("Simple Survey", comment: "") instructionStep.text = exampleDescription - instructionStep.detailText = NSLocalizedString("Please use this space to provide instructions for participants. Please make sure to provide enough information so that users can progress through the survey and complete with ease.", comment: "") - + instructionStep.detailText = NSLocalizedString( + "Please use this space to provide instructions for participants. Please make sure to provide enough information so that users can progress through the survey and complete with ease.", + comment: "") // Add a question step. let question1StepAnswerFormat = ORKBooleanAnswerFormat() - let question1 = NSLocalizedString(ModuleAppYmlReader().surverysTaskModel?.newsletter ?? Constants.YamlDefaults.newsletter, comment: "") - let learnMoreInstructionStep = ORKLearnMoreInstructionStep(identifier: "LearnMoreInstructionStep01") learnMoreInstructionStep.title = NSLocalizedString("Learn more title", comment: "") learnMoreInstructionStep.text = NSLocalizedString("Learn more text", comment: "") let learnMoreItem = ORKLearnMoreItem(text: nil, learnMoreInstructionStep: learnMoreInstructionStep) - - let question1Step = ORKQuestionStep(identifier: String(describing: Identifier.questionStep), title: ModuleAppYmlReader().surverysTaskModel?.questionnaire ?? Constants.YamlDefaults.Questionnaire, question: question1, answer: question1StepAnswerFormat, learnMoreItem: learnMoreItem) + let question1Step = ORKQuestionStep( + identifier: String(describing: Identifier.questionStep), + title: ModuleAppYmlReader().surverysTaskModel?.questionnaire ?? Constants.YamlDefaults.Questionnaire, + question: question1, + answer: question1StepAnswerFormat, + learnMoreItem: learnMoreItem) question1Step.text = exampleDetailText - - //Add a question step with different layout format. + // Add a question step with different layout format. let question2StepAnswerFormat = ORKAnswerFormat.dateAnswerFormat(withDefaultDate: nil, minimumDate: nil, maximumDate: Date(), calendar: nil) - let question2 = NSLocalizedString(ModuleAppYmlReader().surverysTaskModel?.birthdayText ?? Constants.YamlDefaults.birthdayText, comment: "") - let question2Step = ORKQuestionStep(identifier: String(describing: Identifier.birthdayQuestion), title: ModuleAppYmlReader().surverysTaskModel?.questionnaire ?? Constants.YamlDefaults.Questionnaire, question: question2, answer: question2StepAnswerFormat) + let question2Step = ORKQuestionStep( + identifier: String(describing: Identifier.birthdayQuestion), + title: ModuleAppYmlReader().surverysTaskModel?.questionnaire ?? Constants.YamlDefaults.Questionnaire, + question: question2, answer: question2StepAnswerFormat) question2Step.text = exampleDetailText - // Add a summary step. let summaryStep = ORKInstructionStep(identifier: String(describing: Identifier.summaryStep)) summaryStep.title = NSLocalizedString("Thanks", comment: "") summaryStep.text = NSLocalizedString("Thank you for participating in this sample survey.", comment: "") - - return ORKOrderedTask(identifier: String(describing: Identifier.surveyTask), steps: [ - instructionStep, - question1Step, - question2Step, - summaryStep - ]) + return ORKOrderedTask(identifier: String(describing: Identifier.surveyTask), + steps: [ instructionStep, question1Step, question2Step, summaryStep ]) } - + /// This task presents just a single "Yes" / "No" question. private var booleanQuestionTask: ORKTask { let answerFormat = ORKBooleanAnswerFormat() - // We attach an answer format to a question step to specify what controls the user sees. - let questionStep = ORKQuestionStep(identifier: String(describing: Identifier.booleanQuestionStep), title: NSLocalizedString("Boolean", comment: ""), question: ModuleAppYmlReader().surverysTaskModel?.questionText ?? Constants.YamlDefaults.questionText, answer: answerFormat) - + let questionStep = ORKQuestionStep( + identifier: String(describing: Identifier.booleanQuestionStep), + title: NSLocalizedString("Boolean", comment: ""), + question: ModuleAppYmlReader().surverysTaskModel?.questionText ?? Constants.YamlDefaults.questionText, + answer: answerFormat) // The detail text is shown in a small font below the title. questionStep.text = exampleDetailText - return ORKOrderedTask(identifier: String(describing: Identifier.booleanQuestionTask), steps: [questionStep]) } /// This task presents a customized "Yes" / "No" question. private var customBooleanQuestionTask: ORKTask { let answerFormat = ORKBooleanAnswerFormat(yesString: "Agree", noString: "Disagree") - // We attach an answer format to a question step to specify what controls the user sees. - let questionStep = ORKQuestionStep(identifier: String(describing: Identifier.booleanQuestionStep), title: NSLocalizedString("Custom Boolean", comment: ""), question: ModuleAppYmlReader().surverysTaskModel?.questionText ?? Constants.YamlDefaults.questionText, answer: answerFormat) - + let questionStep = ORKQuestionStep( + identifier: String(describing: Identifier.booleanQuestionStep), + title: NSLocalizedString("Custom Boolean", comment: ""), + question: ModuleAppYmlReader().surverysTaskModel?.questionText ?? Constants.YamlDefaults.questionText, + answer: answerFormat) // The detail text is shown in a small font below the title. questionStep.text = exampleDetailText - return ORKOrderedTask(identifier: String(describing: Identifier.booleanQuestionTask), steps: [questionStep]) } - /// This task demonstrates a question which asks for a date. - private var dateQuestionTask: ORKTask { - /* - The date answer format can also support minimum and maximum limits, - a specific default value, and overriding the calendar to use. - */ - let answerFormat = ORKAnswerFormat.dateAnswerFormat() - - let step = ORKQuestionStep(identifier: String(describing: Identifier.dateQuestionStep), title: NSLocalizedString("Date", comment: ""), question: ModuleAppYmlReader().surverysTaskModel?.questionText ?? Constants.YamlDefaults.questionText, answer: answerFormat) - - step.text = exampleDetailText - - return ORKOrderedTask(identifier: String(describing: Identifier.dateQuestionTask), steps: [step]) - } - - /// This task demonstrates a question asking for a date and time of an event. - private var dateTimeQuestionTask: ORKTask { - /* - This uses the default calendar. Use a more detailed constructor to - set minimum / maximum limits. - */ - let answerFormat = ORKAnswerFormat.dateTime() - - let step = ORKQuestionStep(identifier: String(describing: Identifier.dateTimeQuestionStep), title: NSLocalizedString("Date and Time", comment: ""), question: ModuleAppYmlReader().surverysTaskModel?.questionText ?? Constants.YamlDefaults.questionText, answer: answerFormat) - - step.text = exampleDetailText - - return ORKOrderedTask(identifier: String(describing: Identifier.dateTimeQuestionTask), steps: [step]) - } - - /// This task demonstrates a question asking for the user height. - /*private var heightQuestionTask: ORKTask { - let answerFormat1 = ORKAnswerFormat.heightAnswerFormat() - - let step1 = ORKQuestionStep(identifier: String(describing: Identifier.heightQuestionStep1), title: NSLocalizedString("Height", comment: ""), question: exampleQuestionText, answer: answerFormat1) - - step1.text = "Local system" - - let answerFormat2 = ORKAnswerFormat.heightAnswerFormat(with: ORKMeasurementSystem.metric) - - let step2 = ORKQuestionStep(identifier: String(describing: Identifier.heightQuestionStep2), title: NSLocalizedString("Height", comment: ""), question: exampleQuestionText, answer: answerFormat2) - - step2.text = "Metric system" - - let answerFormat3 = ORKAnswerFormat.heightAnswerFormat(with: ORKMeasurementSystem.USC) - - let step3 = ORKQuestionStep(identifier: String(describing: Identifier.heightQuestionStep3), title: NSLocalizedString("Height", comment: ""), question: exampleQuestionText, answer: answerFormat3) - - step3.text = "USC system" - - let answerFormat4 = ORKHealthKitQuantityTypeAnswerFormat(quantityType: HKQuantityType.quantityType(forIdentifier: HKQuantityTypeIdentifier.height)!, unit: HKUnit.meterUnit(with: .centi), style: .decimal) - - let step4 = ORKQuestionStep(identifier: String(describing: Identifier.heightQuestionStep4), title: NSLocalizedString("Height", comment: ""), question: exampleQuestionText, answer: answerFormat4) - - step4.text = "HealthKit, height" - - return ORKOrderedTask(identifier: String(describing: Identifier.heightQuestionTask), steps: [step1, step2, step3, step4]) - }*/ - - /// This task demonstrates a question asking for the user weight. - /*private var weightQuestionTask: ORKTask { - let answerFormat1 = ORKAnswerFormat.weightAnswerFormat() - - let step1 = ORKQuestionStep(identifier: String(describing: Identifier.weightQuestionStep1), title: NSLocalizedString("Weight", comment: ""), question: exampleQuestionText, answer: answerFormat1) - - step1.text = "Local system, default precision" - - let answerFormat2 = ORKAnswerFormat.weightAnswerFormat(with: ORKMeasurementSystem.metric) - - let step2 = ORKQuestionStep(identifier: String(describing: Identifier.weightQuestionStep2), title: NSLocalizedString("Weight", comment: ""), question: exampleQuestionText, answer: answerFormat2) - - step2.text = "Metric system, default precision" - - let answerFormat3 = ORKAnswerFormat.weightAnswerFormat(with: ORKMeasurementSystem.metric, numericPrecision: ORKNumericPrecision.low, minimumValue: ORKDoubleDefaultValue, maximumValue: ORKDoubleDefaultValue, defaultValue: ORKDoubleDefaultValue) - - let step3 = ORKQuestionStep(identifier: String(describing: Identifier.weightQuestionStep3), title: NSLocalizedString("Weight", comment: ""), question: exampleQuestionText, answer: answerFormat3) - - step3.text = "Metric system, low precision" - - let answerFormat4 = ORKAnswerFormat.weightAnswerFormat(with: ORKMeasurementSystem.metric, numericPrecision: ORKNumericPrecision.high, minimumValue: 20.0, maximumValue: 100.0, defaultValue: 45.50) - - let step4 = ORKQuestionStep(identifier: String(describing: Identifier.weightQuestionStep4), title: NSLocalizedString("Weight", comment: ""), question: exampleQuestionText, answer: answerFormat4) - - step4.text = "Metric system, high precision" - - let answerFormat5 = ORKAnswerFormat.weightAnswerFormat(with: ORKMeasurementSystem.USC) - - let step5 = ORKQuestionStep(identifier: String(describing: Identifier.weightQuestionStep5), title: NSLocalizedString("Weight", comment: ""), question: exampleQuestionText, answer: answerFormat5) - - step5.text = "USC system, default precision" - - let answerFormat6 = ORKAnswerFormat.weightAnswerFormat(with: ORKMeasurementSystem.USC, numericPrecision: ORKNumericPrecision.high, minimumValue: 50.0, maximumValue: 150.0, defaultValue: 100.0) - - let step6 = ORKQuestionStep(identifier: String(describing: Identifier.weightQuestionStep6), title: NSLocalizedString("Weight", comment: ""), question: exampleQuestionText, answer: answerFormat6) - - step6.text = "USC system, high precision" - - let answerFormat7 = ORKHealthKitQuantityTypeAnswerFormat(quantityType: HKQuantityType.quantityType(forIdentifier: HKQuantityTypeIdentifier.bodyMass)!, unit: HKUnit.gramUnit(with: .kilo), style: .decimal) - - let step7 = ORKQuestionStep(identifier: String(describing: Identifier.weightQuestionStep7), title: NSLocalizedString("Weight", comment: ""), question: exampleQuestionText, answer: answerFormat7) - - step7.text = "HealthKit, body mass" - - return ORKOrderedTask(identifier: String(describing: Identifier.weightQuestionTask), steps: [step1, step2, step3, step4, step5, step6, step7]) - }*/ - - /** - This task demonstrates a survey question involving picking from a series of - image choices. A more realistic applciation of this type of question might be to - use a range of icons for faces ranging from happy to sad. - */ - private var imageChoiceQuestionTask: ORKTask { - let roundShapeImage = UIImage(named: "round_shape")! - let roundShapeText = NSLocalizedString("Round Shape", comment: "") - - let squareShapeImage = UIImage(named: "square_shape")! - let squareShapeText = NSLocalizedString("Square Shape", comment: "") - - let imageChoces = [ - ORKImageChoice(normalImage: roundShapeImage, selectedImage: nil, text: roundShapeText, value: roundShapeText as NSCoding & NSCopying & NSObjectProtocol), - ORKImageChoice(normalImage: squareShapeImage, selectedImage: nil, text: squareShapeText, value: squareShapeText as NSCoding & NSCopying & NSObjectProtocol) - ] - - let answerFormat1 = ORKAnswerFormat.choiceAnswerFormat(with: imageChoces) - - let questionStep1 = ORKQuestionStep(identifier: String(describing: Identifier.imageChoiceQuestionStep1), title: NSLocalizedString("Image Choice", comment: ""), question: ModuleAppYmlReader().surverysTaskModel?.questionText ?? Constants.YamlDefaults.questionText, answer: answerFormat1) - - questionStep1.text = exampleDetailText - - let answerFormat2 = ORKAnswerFormat.choiceAnswerFormat(with: imageChoces, style: .singleChoice, vertical: true) - - let questionStep2 = ORKQuestionStep(identifier: String(describing: Identifier.imageChoiceQuestionStep2), title: NSLocalizedString("Image Choice", comment: ""), question: ModuleAppYmlReader().surverysTaskModel?.questionText ?? Constants.YamlDefaults.questionText, answer: answerFormat2) - - questionStep2.text = exampleDetailText - - return ORKOrderedTask(identifier: String(describing: Identifier.imageChoiceQuestionTask), steps: [questionStep1, questionStep2]) - } - /// This task presents just a single location question. private var locationQuestionTask: ORKTask { let answerFormat = ORKLocationAnswerFormat() - // We attach an answer format to a question step to specify what controls the user sees. - let questionStep = ORKQuestionStep(identifier: String(describing: Identifier.locationQuestionStep), title: NSLocalizedString("Location", comment: ""), question: ModuleAppYmlReader().surverysTaskModel?.questionText ?? Constants.YamlDefaults.questionText, answer: answerFormat) + let questionStep = ORKQuestionStep( + identifier: String(describing: Identifier.locationQuestionStep), + title: NSLocalizedString("Location", comment: ""), + question: ModuleAppYmlReader().surverysTaskModel?.questionText ?? Constants.YamlDefaults.questionText, + answer: answerFormat) // The detail text is shown in a small font below the title. questionStep.text = exampleDetailText questionStep.placeholder = NSLocalizedString("Address", comment: "") - return ORKOrderedTask(identifier: String(describing: Identifier.locationQuestionTask), steps: [questionStep]) } /** - This task demonstrates use of numeric questions with and without units. - Note that the unit is just a string, prompting the user to enter the value - in the expected unit. The unit string propagates into the result object. - */ + This task demonstrates use of numeric questions with and without units. + Note that the unit is just a string, prompting the user to enter the value + in the expected unit. The unit string propagates into the result object. + */ private var numericQuestionTask: ORKTask { // This answer format will display a unit in-line with the numeric entry field. let localizedQuestionStep1AnswerFormatUnit = NSLocalizedString("Your unit", comment: "") let questionStep1AnswerFormat = ORKAnswerFormat.decimalAnswerFormat(withUnit: localizedQuestionStep1AnswerFormatUnit) - - let questionStep1 = ORKQuestionStep(identifier: String(describing: Identifier.numericQuestionStep), title: NSLocalizedString("Numeric", comment: ""), question: ModuleAppYmlReader().surverysTaskModel?.questionText ?? Constants.YamlDefaults.questionText, answer: questionStep1AnswerFormat) - + let questionStep1 = ORKQuestionStep( + identifier: String(describing: Identifier.numericQuestionStep), + title: NSLocalizedString("Numeric", comment: ""), + question: ModuleAppYmlReader().surverysTaskModel?.questionText ?? Constants.YamlDefaults.questionText, + answer: questionStep1AnswerFormat) questionStep1.text = exampleDetailText questionStep1.placeholder = NSLocalizedString(ModuleAppYmlReader().surverysTaskModel?.placeholder ?? Constants.YamlDefaults.placeholder, comment: "") - // This answer format is similar to the previous one, but this time without displaying a unit. - let questionStep2 = ORKQuestionStep(identifier: String(describing: Identifier.numericNoUnitQuestionStep), title: NSLocalizedString("Numeric", comment: ""), question: ModuleAppYmlReader().surverysTaskModel?.questionText ?? Constants.YamlDefaults.questionText, answer: ORKAnswerFormat.decimalAnswerFormat(withUnit: nil)) - + let questionStep2 = ORKQuestionStep( + identifier: String(describing: Identifier.numericNoUnitQuestionStep), + title: NSLocalizedString("Numeric", comment: ""), + question: ModuleAppYmlReader().surverysTaskModel?.questionText ?? Constants.YamlDefaults.questionText, + answer: ORKAnswerFormat.decimalAnswerFormat(withUnit: nil)) questionStep2.text = exampleDetailText questionStep2.placeholder = NSLocalizedString("Placeholder without unit.", comment: "") - return ORKOrderedTask(identifier: String(describing: Identifier.numericQuestionTask), steps: [ questionStep1, questionStep2 @@ -1146,51 +238,82 @@ enum TaskListRow: Int, CustomStringConvertible { private var scaleQuestionTask: ORKTask { // The first step is a scale control with 10 discrete ticks. let stepTitle = NSLocalizedString("Scale", comment: "") - - let step1AnswerFormat = ORKAnswerFormat.scale(withMaximumValue: 10, minimumValue: 1, defaultValue: NSIntegerMax, step: 1, vertical: false, maximumValueDescription: exampleHighValueText, minimumValueDescription: exampleLowValueText) - - let questionStep1 = ORKQuestionStep(identifier: String(describing: Identifier.discreteScaleQuestionStep), title: stepTitle, question: ModuleAppYmlReader().surverysTaskModel?.questionText ?? Constants.YamlDefaults.questionText, answer: step1AnswerFormat) - + let step1AnswerFormat = ORKAnswerFormat.scale( + withMaximumValue: 10, + minimumValue: 1, + defaultValue: NSIntegerMax, + step: 1, + vertical: false, + maximumValueDescription: exampleHighValueText, + minimumValueDescription: exampleLowValueText) + let questionStep1 = ORKQuestionStep( + identifier: String(describing: Identifier.discreteScaleQuestionStep), + title: stepTitle, question: ModuleAppYmlReader().surverysTaskModel?.questionText ?? Constants.YamlDefaults.questionText, + answer: step1AnswerFormat) questionStep1.text = NSLocalizedString("Discrete Scale", comment: "") - // The second step is a scale control that allows continuous movement with a percent formatter. - let step2AnswerFormat = ORKAnswerFormat.continuousScale(withMaximumValue: 1.0, minimumValue: 0.0, defaultValue: 99.0, maximumFractionDigits: 0, vertical: false, maximumValueDescription: nil, minimumValueDescription: nil) + let step2AnswerFormat = ORKAnswerFormat.continuousScale( + withMaximumValue: 1.0, + minimumValue: 0.0, + defaultValue: 99.0, + maximumFractionDigits: 0, + vertical: false, + maximumValueDescription: nil, + minimumValueDescription: nil) step2AnswerFormat.numberStyle = .percent - - let questionStep2 = ORKQuestionStep(identifier: String(describing: Identifier.continuousScaleQuestionStep), title: stepTitle, question: ModuleAppYmlReader().surverysTaskModel?.questionText ?? Constants.YamlDefaults.questionText, answer: step2AnswerFormat) - + let questionStep2 = ORKQuestionStep( + identifier: String(describing: Identifier.continuousScaleQuestionStep), + title: stepTitle, question: ModuleAppYmlReader().surverysTaskModel?.questionText ?? Constants.YamlDefaults.questionText, + answer: step2AnswerFormat) questionStep2.text = NSLocalizedString("Continuous Scale", comment: "") - // The third step is a vertical scale control with 10 discrete ticks. - let step3AnswerFormat = ORKAnswerFormat.scale(withMaximumValue: 10, minimumValue: 1, defaultValue: NSIntegerMax, step: 1, vertical: true, maximumValueDescription: nil, minimumValueDescription: nil) - - let questionStep3 = ORKQuestionStep(identifier: String(describing: Identifier.discreteVerticalScaleQuestionStep), title: stepTitle, question: ModuleAppYmlReader().surverysTaskModel?.questionText ?? Constants.YamlDefaults.questionText, answer: step3AnswerFormat) - + let step3AnswerFormat = ORKAnswerFormat.scale( + withMaximumValue: 10, + minimumValue: 1, + defaultValue: NSIntegerMax, + step: 1, + vertical: true, + maximumValueDescription: nil, + minimumValueDescription: nil) + let questionStep3 = ORKQuestionStep( + identifier: String(describing: Identifier.discreteVerticalScaleQuestionStep), + title: stepTitle, question: ModuleAppYmlReader().surverysTaskModel?.questionText ?? Constants.YamlDefaults.questionText, + answer: step3AnswerFormat) questionStep3.text = NSLocalizedString("Discrete Vertical Scale", comment: "") - // The fourth step is a vertical scale control that allows continuous movement. - let step4AnswerFormat = ORKAnswerFormat.continuousScale(withMaximumValue: 5.0, minimumValue: 1.0, defaultValue: 99.0, maximumFractionDigits: 2, vertical: true, maximumValueDescription: exampleHighValueText, minimumValueDescription: exampleLowValueText) - - let questionStep4 = ORKQuestionStep(identifier: String(describing: Identifier.continuousVerticalScaleQuestionStep), title: stepTitle, question: ModuleAppYmlReader().surverysTaskModel?.questionText ?? Constants.YamlDefaults.questionText, answer: step4AnswerFormat) - + let step4AnswerFormat = ORKAnswerFormat.continuousScale( + withMaximumValue: 5.0, + minimumValue: 1.0, + defaultValue: 99.0, + maximumFractionDigits: 2, + vertical: true, + maximumValueDescription: exampleHighValueText, + minimumValueDescription: exampleLowValueText) + let questionStep4 = ORKQuestionStep( + identifier: String(describing: Identifier.continuousVerticalScaleQuestionStep), + title: stepTitle, question: ModuleAppYmlReader().surverysTaskModel?.questionText ?? Constants.YamlDefaults.questionText, + answer: step4AnswerFormat) questionStep4.text = "Continuous Vertical Scale" - // The fifth step is a scale control that allows text choices. - let textChoices: [ORKTextChoice] = [ORKTextChoice(text: "Poor", value: 1 as NSCoding & NSCopying & NSObjectProtocol), ORKTextChoice(text: "Fair", value: 2 as NSCoding & NSCopying & NSObjectProtocol), ORKTextChoice(text: "Good", value: 3 as NSCoding & NSCopying & NSObjectProtocol), ORKTextChoice(text: "Above Average", value: 10 as NSCoding & NSCopying & NSObjectProtocol), ORKTextChoice(text: "Excellent", value: 5 as NSCoding & NSCopying & NSObjectProtocol)] - + let textChoices: [ORKTextChoice] = [ + ORKTextChoice(text: "Poor", value: 1 as NSCoding & NSCopying & NSObjectProtocol), + ORKTextChoice(text: "Fair", value: 2 as NSCoding & NSCopying & NSObjectProtocol), + ORKTextChoice(text: "Good", value: 3 as NSCoding & NSCopying & NSObjectProtocol), + ORKTextChoice(text: "Above Average", value: 10 as NSCoding & NSCopying & NSObjectProtocol), + ORKTextChoice(text: "Excellent", value: 5 as NSCoding & NSCopying & NSObjectProtocol)] let step5AnswerFormat = ORKAnswerFormat.textScale(with: textChoices, defaultIndex: NSIntegerMax, vertical: false) - - let questionStep5 = ORKQuestionStep(identifier: String(describing: Identifier.textScaleQuestionStep), title: stepTitle, question: ModuleAppYmlReader().surverysTaskModel?.questionText ?? Constants.YamlDefaults.questionText, answer: step5AnswerFormat) - + let questionStep5 = ORKQuestionStep( + identifier: String(describing: Identifier.textScaleQuestionStep), + title: stepTitle, question: ModuleAppYmlReader().surverysTaskModel?.questionText ?? Constants.YamlDefaults.questionText, + answer: step5AnswerFormat) questionStep5.text = "Text Scale" - // The sixth step is a vertical scale control that allows text choices. let step6AnswerFormat = ORKAnswerFormat.textScale(with: textChoices, defaultIndex: NSIntegerMax, vertical: true) - - let questionStep6 = ORKQuestionStep(identifier: String(describing: Identifier.textVerticalScaleQuestionStep), title: stepTitle, question: ModuleAppYmlReader().surverysTaskModel?.questionText ?? Constants.YamlDefaults.questionText, answer: step6AnswerFormat) - + let questionStep6 = ORKQuestionStep( + identifier: String(describing: Identifier.textVerticalScaleQuestionStep), + title: stepTitle, question: ModuleAppYmlReader().surverysTaskModel?.questionText ?? Constants.YamlDefaults.questionText, + answer: step6AnswerFormat) questionStep6.text = "Text Vertical Scale" - return ORKOrderedTask(identifier: String(describing: Identifier.scaleQuestionTask), steps: [ questionStep1, questionStep2, @@ -1198,497 +321,106 @@ enum TaskListRow: Int, CustomStringConvertible { questionStep4, questionStep5, questionStep6 - ]) - } - - /** - This task demonstrates asking for text entry. Both single and multi-line - text entry are supported, with appropriate parameters to the text answer - format. - */ - private var textQuestionTask: ORKTask { - let answerFormat = ORKAnswerFormat.textAnswerFormat() - answerFormat.multipleLines = true - answerFormat.maximumLength = 280 - - let step = ORKQuestionStep(identifier: String(describing: Identifier.textQuestionStep), title: NSLocalizedString("Text", comment: ""), question: ModuleAppYmlReader().surverysTaskModel?.questionText ?? Constants.YamlDefaults.questionText, answer: answerFormat) - - step.text = exampleDetailText - - return ORKOrderedTask(identifier: String(describing: Identifier.textQuestionTask), steps: [step]) + ]) } /** - This task demonstrates a survey question for picking from a list of text - choices. In this case, the text choices are presented in a table view - (compare with the `valuePickerQuestionTask`). - */ - private var textChoiceQuestionTask: ORKTask { - let textChoiceOneText = NSLocalizedString("Choice 1", comment: "") - let textChoiceTwoText = NSLocalizedString("Choice 2", comment: "") - let textChoiceThreeText = NSLocalizedString("Choice 3", comment: "") - let textChoiceFourText = NSLocalizedString("Other", comment: "") - - // The text to display can be separate from the value coded for each choice: - let textChoices = [ - ORKTextChoice(text: textChoiceOneText, value: "choice_1" as NSCoding & NSCopying & NSObjectProtocol), - ORKTextChoice(text: textChoiceTwoText, value: "choice_2" as NSCoding & NSCopying & NSObjectProtocol), - ORKTextChoice(text: textChoiceThreeText, value: "choice_3" as NSCoding & NSCopying & NSObjectProtocol), - ORKTextChoiceOther.choice(withText: textChoiceFourText, detailText: nil, value: "choice_4" as NSCoding & NSCopying & NSObjectProtocol, exclusive: true, textViewPlaceholderText: "enter additional information") - ] - - let answerFormat = ORKAnswerFormat.choiceAnswerFormat(with: .singleChoice, textChoices: textChoices) - - let questionStep = ORKQuestionStep(identifier: String(describing: Identifier.textChoiceQuestionStep), title: NSLocalizedString("Text Choice", comment: ""), question: ModuleAppYmlReader().surverysTaskModel?.questionText ?? Constants.YamlDefaults.questionText, answer: answerFormat) - - questionStep.text = exampleDetailText - - return ORKOrderedTask(identifier: String(describing: Identifier.textChoiceQuestionTask), steps: [questionStep]) - } - - /** - This task demonstrates requesting a time interval. For example, this might - be a suitable answer format for a question like "How long is your morning - commute?" - */ - private var timeIntervalQuestionTask: ORKTask { - /* - The time interval answer format is constrained to entering a time - less than 24 hours and in steps of minutes. For times that don't fit - these restrictions, use another mode of data entry. - */ - let answerFormat = ORKAnswerFormat.timeIntervalAnswerFormat() - - let step = ORKQuestionStep(identifier: String(describing: Identifier.timeIntervalQuestionStep), title: NSLocalizedString("Time Interval", comment: ""), question: ModuleAppYmlReader().surverysTaskModel?.questionText ?? Constants.YamlDefaults.questionText, answer: answerFormat) - - step.text = exampleDetailText - - return ORKOrderedTask(identifier: String(describing: Identifier.timeIntervalQuestionTask), steps: [step]) - } - - /// This task demonstrates a question asking for a time of day. - private var timeOfDayQuestionTask: ORKTask { - /* - Because we don't specify a default, the picker will default to the - time the step is presented. For questions like "What time do you have - breakfast?", it would make sense to set the default on the answer - format. - */ - let answerFormat = ORKAnswerFormat.timeOfDayAnswerFormat() - - let questionStep = ORKQuestionStep(identifier: String(describing: Identifier.timeOfDayQuestionStep), title: NSLocalizedString("Time", comment: ""), question: ModuleAppYmlReader().surverysTaskModel?.questionText ?? Constants.YamlDefaults.questionText, answer: answerFormat) - - questionStep.text = exampleDetailText - - return ORKOrderedTask(identifier: String(describing: Identifier.timeOfDayQuestionTask), steps: [questionStep]) - } - - /** - This task demonstrates a survey question using a value picker wheel. - Compare with the `textChoiceQuestionTask` and `imageChoiceQuestionTask` - which can serve a similar purpose. - */ + This task demonstrates a survey question using a value picker wheel. + Compare with the `textChoiceQuestionTask` and `imageChoiceQuestionTask` + which can serve a similar purpose. + */ private var valuePickerChoiceQuestionTask: ORKTask { let textChoiceOneText = NSLocalizedString("Choice 1", comment: "") let textChoiceTwoText = NSLocalizedString("Choice 2", comment: "") let textChoiceThreeText = NSLocalizedString("Choice 3", comment: "") - // The text to display can be separate from the value coded for each choice: let textChoices = [ ORKTextChoice(text: textChoiceOneText, value: "choice_1" as NSCoding & NSCopying & NSObjectProtocol), ORKTextChoice(text: textChoiceTwoText, value: "choice_2" as NSCoding & NSCopying & NSObjectProtocol), ORKTextChoice(text: textChoiceThreeText, value: "choice_3" as NSCoding & NSCopying & NSObjectProtocol) ] - let answerFormat = ORKAnswerFormat.valuePickerAnswerFormat(with: textChoices) - - let questionStep = ORKQuestionStep(identifier: String(describing: Identifier.valuePickerChoiceQuestionStep), title: NSLocalizedString("Value Picker", comment: ""), question: ModuleAppYmlReader().surverysTaskModel?.questionText ?? Constants.YamlDefaults.questionText, answer: answerFormat) - + let questionStep = ORKQuestionStep( + identifier: String(describing: Identifier.valuePickerChoiceQuestionStep), + title: NSLocalizedString("Value Picker", comment: ""), + question: ModuleAppYmlReader().surverysTaskModel?.questionText ?? Constants.YamlDefaults.questionText, + answer: answerFormat) questionStep.text = NSLocalizedString("Text Value picker", comment: "") - return ORKOrderedTask(identifier: String(describing: Identifier.valuePickerChoiceQuestionTask), steps: [questionStep]) } - - /** - This task demonstrates asking for text entry. Both single and multi-line - text entry are supported, with appropriate parameters to the text answer - format. - */ - private var validatedTextQuestionTask: ORKTask { - let answerFormatEmail = ORKAnswerFormat.emailAnswerFormat() - let stepEmail = ORKQuestionStep(identifier: String(describing: Identifier.validatedTextQuestionStepEmail), title: NSLocalizedString("Validated Text", comment: ""), question: NSLocalizedString("Email", comment: ""), answer: answerFormatEmail) - stepEmail.text = exampleDetailText - - let domainRegularExpressionPattern = "^(https?:\\/\\/)?([\\da-z\\.-]+)\\.([a-z\\.]{2,6})([\\/\\w \\.-]*)*\\/?$" - let domainRegularExpression = try? NSRegularExpression(pattern: domainRegularExpressionPattern) - let answerFormatDomain = ORKAnswerFormat.textAnswerFormat(withValidationRegularExpression: domainRegularExpression!, invalidMessage: "Invalid URL: %@") - answerFormatDomain.multipleLines = false - answerFormatDomain.keyboardType = .URL - answerFormatDomain.autocapitalizationType = UITextAutocapitalizationType.none - answerFormatDomain.autocorrectionType = UITextAutocorrectionType.no - answerFormatDomain.spellCheckingType = UITextSpellCheckingType.no - answerFormatDomain.textContentType = UITextContentType.URL - let stepDomain = ORKQuestionStep(identifier: String(describing: Identifier.validatedTextQuestionStepDomain), title: NSLocalizedString("Validated Text", comment: ""), question: NSLocalizedString("URL", comment: ""), answer: answerFormatDomain) - stepDomain.text = exampleDetailText - - return ORKOrderedTask(identifier: String(describing: Identifier.validatedTextQuestionTask), steps: [stepEmail, stepDomain]) - } - - /// This task presents the image capture step in an ordered task. - private var imageCaptureTask: ORKTask { - // Create the intro step. - let instructionStep = ORKInstructionStep(identifier: String(describing: Identifier.introStep)) - - instructionStep.title = NSLocalizedString("Image Capture Survey", comment: "") - - instructionStep.text = exampleDescription - - let handSolidImage = UIImage(named: "hand_solid")! - instructionStep.image = handSolidImage.withRenderingMode(.alwaysTemplate) - - let imageCaptureStep = ORKImageCaptureStep(identifier: String(describing: Identifier.imageCaptureStep)) - imageCaptureStep.title = NSLocalizedString("Image Capture", comment: "") - imageCaptureStep.isOptional = false - imageCaptureStep.accessibilityInstructions = NSLocalizedString("Your instructions for capturing the image", comment: "") - imageCaptureStep.accessibilityHint = NSLocalizedString("Captures the image visible in the preview", comment: "") - - imageCaptureStep.templateImage = UIImage(named: "hand_outline_big")! - - imageCaptureStep.templateImageInsets = UIEdgeInsets(top: 0.05, left: 0.05, bottom: 0.05, right: 0.05) - - return ORKOrderedTask(identifier: String(describing: Identifier.imageCaptureTask), steps: [ - instructionStep, - imageCaptureStep - ]) - } - - /// This task presents the video capture step in an ordered task. - private var videoCaptureTask: ORKTask { - // Create the intro step. - let instructionStep = ORKInstructionStep(identifier: String(describing: Identifier.introStep)) - - instructionStep.title = NSLocalizedString("Video Capture Survey", comment: "") - - instructionStep.text = exampleDescription - - let handSolidImage = UIImage(named: "hand_solid")! - instructionStep.image = handSolidImage.withRenderingMode(.alwaysTemplate) - - let videoCaptureStep = ORKVideoCaptureStep(identifier: String(describing: Identifier.videoCaptureStep)) - videoCaptureStep.title = NSLocalizedString("Video Capture", comment: "") - videoCaptureStep.accessibilityInstructions = NSLocalizedString("Your instructions for capturing the video", comment: "") - videoCaptureStep.accessibilityHint = NSLocalizedString("Captures the video visible in the preview", comment: "") - videoCaptureStep.templateImage = UIImage(named: "hand_outline_big")! - videoCaptureStep.templateImageInsets = UIEdgeInsets(top: 0.05, left: 0.05, bottom: 0.05, right: 0.05) - videoCaptureStep.duration = 30.0; // 30 seconds - - return ORKOrderedTask(identifier: String(describing: Identifier.videoCaptureTask), steps: [ - instructionStep, - videoCaptureStep - ]) - } - - /// This task presents a wait task. - private var waitTask: ORKTask { - let waitStepIndeterminate = ORKWaitStep(identifier: String(describing: Identifier.waitStepIndeterminate)) - waitStepIndeterminate.title = NSLocalizedString("Wait Step", comment: "") - waitStepIndeterminate.text = exampleDescription - waitStepIndeterminate.indicatorType = ORKProgressIndicatorType.indeterminate - - let waitStepDeterminate = ORKWaitStep(identifier: String(describing: Identifier.waitStepDeterminate)) - waitStepDeterminate.title = NSLocalizedString("Wait Step", comment: "") - waitStepDeterminate.text = exampleDescription - waitStepDeterminate.indicatorType = ORKProgressIndicatorType.progressBar - - return ORKOrderedTask(identifier: String(describing: Identifier.waitTask), steps: [waitStepIndeterminate, waitStepDeterminate]) + + /// This task presents a wait task. + private var waitTask: ORKTask { + let waitStepIndeterminate = ORKWaitStep(identifier: String(describing: Identifier.waitStepIndeterminate)) + waitStepIndeterminate.title = NSLocalizedString("Wait Step", comment: "") + waitStepIndeterminate.text = exampleDescription + waitStepIndeterminate.indicatorType = ORKProgressIndicatorType.indeterminate + let waitStepDeterminate = ORKWaitStep(identifier: String(describing: Identifier.waitStepDeterminate)) + waitStepDeterminate.title = NSLocalizedString("Wait Step", comment: "") + waitStepDeterminate.text = exampleDescription + waitStepDeterminate.indicatorType = ORKProgressIndicatorType.progressBar + return ORKOrderedTask(identifier: String(describing: Identifier.waitTask), steps: [waitStepIndeterminate, waitStepDeterminate]) } /// This task presents the PDF Viewer Step private var PDFViewerTask: ORKTask { - let PDFViewerStep = ORKPDFViewerStep(identifier: String(describing: Identifier.pdfViewerStep), pdfURL: Bundle.main.bundleURL.appendingPathComponent("ResearchKit.pdf")) PDFViewerStep.title = NSLocalizedString("PDF Step", comment: "") - return ORKOrderedTask(identifier: String(describing: Identifier.pdfViewerTask), steps: [PDFViewerStep]) } - /*private var requestPermissionsTask: ORKTask { - let healthKitTypesToWrite: Set = [ - HKObjectType.quantityType(forIdentifier: .bodyMassIndex)!, - HKObjectType.quantityType(forIdentifier: .activeEnergyBurned)!, - HKObjectType.workoutType()] - - let healthKitTypesToRead: Set = [ - HKObjectType.characteristicType(forIdentifier: .dateOfBirth)!, - HKObjectType.characteristicType(forIdentifier: .bloodType)!, - HKObjectType.workoutType()] - - - let healthKitPermissionType = ORKHealthKitPermissionType(sampleTypesToWrite: healthKitTypesToWrite, - objectTypesToRead: healthKitTypesToRead) - - let notificationsPermissionType = ORKNotificationPermissionType( - authorizationOptions: [.alert, .badge, .sound]) - - let motionActivityPermissionType = ORKMotionActivityPermissionType() - - let requestPermissionsStep = ORKRequestPermissionsStep( - identifier: String(describing: Identifier.requestPermissionsStep), - permissionTypes: [ - healthKitPermissionType, - notificationsPermissionType, - motionActivityPermissionType - ]) - - requestPermissionsStep.title = "Authorization Requests" - requestPermissionsStep.text = "Please review the authorizations below and enable to contribute to the study." - - return ORKOrderedTask(identifier: String(describing: Identifier.requestPermissionsStep), steps: [requestPermissionsStep]) - }*/ - - /** - A task demonstrating how the ResearchKit framework can be used to determine - eligibility using a navigable ordered task. - */ - private var eligibilityTask: ORKTask { - // Intro step - let introStep = ORKInstructionStep(identifier: String(describing: Identifier.eligibilityIntroStep)) - introStep.title = NSLocalizedString("Eligibility Task", comment: "") - introStep.text = exampleDescription - introStep.detailText = NSLocalizedString("Please use this space to provide instructions for participants. Please make sure to provide enough information so that users can progress through the survey and complete with ease.", comment: "") - - // Form step - let formStep = ORKFormStep(identifier: String(describing: Identifier.eligibilityFormStep)) - formStep.title = NSLocalizedString("Eligibility", comment: "") - formStep.isOptional = false - - // Form items - let textChoices: [ORKTextChoice] = [ORKTextChoice(text: "Yes", value: "Yes" as NSCoding & NSCopying & NSObjectProtocol), ORKTextChoice(text: "No", value: "No" as NSCoding & NSCopying & NSObjectProtocol), ORKTextChoice(text: "N/A", value: "N/A" as NSCoding & NSCopying & NSObjectProtocol)] - let answerFormat = ORKTextChoiceAnswerFormat(style: ORKChoiceAnswerStyle.singleChoice, textChoices: textChoices) - - let formItem01 = ORKFormItem(identifier: String(describing: Identifier.eligibilityFormItem01), text: ModuleAppYmlReader().surverysTaskModel?.questionText ?? Constants.YamlDefaults.questionText, answerFormat: answerFormat) - formItem01.isOptional = false - let formItem02 = ORKFormItem(identifier: String(describing: Identifier.eligibilityFormItem02), text: ModuleAppYmlReader().surverysTaskModel?.questionText ?? Constants.YamlDefaults.questionText, answerFormat: answerFormat) - formItem02.isOptional = false - let formItem03 = ORKFormItem(identifier: String(describing: Identifier.eligibilityFormItem03), text: ModuleAppYmlReader().surverysTaskModel?.questionText ?? Constants.YamlDefaults.questionText, answerFormat: answerFormat) - formItem03.isOptional = false - - formStep.formItems = [ - formItem01, - formItem02, - formItem03 - ] - - // Ineligible step - let ineligibleStep = ORKInstructionStep(identifier: String(describing: Identifier.eligibilityIneligibleStep)) - ineligibleStep.title = NSLocalizedString("Eligibility Result", comment: "") - ineligibleStep.detailText = NSLocalizedString("You are ineligible to join the study", comment: "") - - // Eligible step - let eligibleStep = ORKCompletionStep(identifier: String(describing: Identifier.eligibilityEligibleStep)) - eligibleStep.title = NSLocalizedString("Eligibility Result", comment: "") - eligibleStep.detailText = NSLocalizedString("You are eligible to join the study", comment: "") - - // Create the task - let eligibilityTask = ORKNavigableOrderedTask(identifier: String(describing: Identifier.eligibilityTask), steps: [ - introStep, - formStep, - ineligibleStep, - eligibleStep - ]) - - // Build navigation rules. - var resultSelector = ORKResultSelector(stepIdentifier: String(describing: Identifier.eligibilityFormStep), resultIdentifier: String(describing: Identifier.eligibilityFormItem01)) - let predicateFormItem01 = ORKResultPredicate.predicateForChoiceQuestionResult(with: resultSelector, expectedAnswerValue: "Yes" as NSCoding & NSCopying & NSObjectProtocol) - - resultSelector = ORKResultSelector(stepIdentifier: String(describing: Identifier.eligibilityFormStep), resultIdentifier: String(describing: Identifier.eligibilityFormItem02)) - let predicateFormItem02 = ORKResultPredicate.predicateForChoiceQuestionResult(with: resultSelector, expectedAnswerValue: "Yes" as NSCoding & NSCopying & NSObjectProtocol) - - resultSelector = ORKResultSelector(stepIdentifier: String(describing: Identifier.eligibilityFormStep), resultIdentifier: String(describing: Identifier.eligibilityFormItem03)) - let predicateFormItem03 = ORKResultPredicate.predicateForChoiceQuestionResult(with: resultSelector, expectedAnswerValue: "No" as NSCoding & NSCopying & NSObjectProtocol) - - let predicateEligible = NSCompoundPredicate(andPredicateWithSubpredicates: [predicateFormItem01, predicateFormItem02, predicateFormItem03]) - let predicateRule = ORKPredicateStepNavigationRule(resultPredicatesAndDestinationStepIdentifiers: [ (predicateEligible, String(describing: Identifier.eligibilityEligibleStep)) ]) - - eligibilityTask.setNavigationRule(predicateRule, forTriggerStepIdentifier: String(describing: Identifier.eligibilityFormStep)) - - // Add end direct rules to skip unneeded steps - let directRule = ORKDirectStepNavigationRule(destinationStepIdentifier: ORKNullStepIdentifier) - eligibilityTask.setNavigationRule(directRule, forTriggerStepIdentifier: String(describing: Identifier.eligibilityIneligibleStep)) - - return eligibilityTask - } - - /// A task demonstrating how the ResearchKit framework can be used to obtain informed consent. - private var consentTask: ORKTask { - /* - Informed consent starts by presenting an animated sequence conveying - the main points of your consent document. - */ - let visualConsentStep = ORKVisualConsentStep(identifier: String(describing: Identifier.visualConsentStep), document: consentDocument) - - let investigatorShortDescription = NSLocalizedString("Institution", comment: "") - let investigatorLongDescription = NSLocalizedString("Institution and its partners", comment: "") - let localizedLearnMoreHTMLContent = NSLocalizedString("Your sharing learn more content here.", comment: "") - - /* - If you want to share the data you collect with other researchers for - use in other studies beyond this one, it is best practice to get - explicit permission from the participant. Use the consent sharing step - for this. - */ - let sharingConsentStep = ORKConsentSharingStep(identifier: String(describing: Identifier.consentSharingStep), investigatorShortDescription: investigatorShortDescription, investigatorLongDescription: investigatorLongDescription, localizedLearnMoreHTMLContent: localizedLearnMoreHTMLContent) - - /* - After the visual presentation, the consent review step displays - your consent document and can obtain a signature from the participant. - - The first signature in the document is the participant's signature. - This effectively tells the consent review step which signatory is - reviewing the document. - */ - let signature = consentDocument.signatures!.first - - let reviewConsentStep = ORKConsentReviewStep(identifier: String(describing: Identifier.consentReviewStep), signature: signature, in: consentDocument) - reviewConsentStep.requiresScrollToBottom = true - - // In a real application, you would supply your own localized text. - reviewConsentStep.title = NSLocalizedString("Consent Document", comment: "") - reviewConsentStep.text = loremIpsumText - reviewConsentStep.reasonForConsent = loremIpsumText - - return ORKOrderedTask(identifier: String(describing: Identifier.consentTask), steps: [ - visualConsentStep, - sharingConsentStep, - reviewConsentStep - ]) - } - - /// This task presents the Account Creation process. - private var accountCreationTask: ORKTask { - /* - A registration step provides a form step that is populated with email and password fields. - If you wish to include any of the additional fields, then you can specify it through the `options` parameter. - */ - let registrationTitle = NSLocalizedString("Registration", comment: "") - let passcodeValidationRegexPattern = "^(?=.*\\d).{4,8}$" - let passcodeValidationRegularExpression = try? NSRegularExpression(pattern: passcodeValidationRegexPattern) - let passcodeInvalidMessage = NSLocalizedString("A valid password must be 4 to 8 characters long and include at least one numeric character.", comment: "") - let registrationOptions: ORKRegistrationStepOption = [.includeGivenName, .includeFamilyName, .includeGender, .includeDOB, .includePhoneNumber] - let registrationStep = ORKRegistrationStep(identifier: String(describing: Identifier.registrationStep), title: registrationTitle, text: exampleDetailText, passcodeValidationRegularExpression: passcodeValidationRegularExpression, passcodeInvalidMessage: passcodeInvalidMessage, options: registrationOptions) - registrationStep.phoneNumberValidationRegularExpression = try? NSRegularExpression(pattern: "^[+]{1,1}[1]{1,1}\\s{1,1}[(]{1,1}[1-9]{3,3}[)]{1,1}\\s{1,1}[1-9]{3,3}\\s{1,1}[1-9]{4,4}$") - registrationStep.phoneNumberInvalidMessage = "Expected format +1 (555) 555 5555" - - /* - A wait step allows you to upload the data from the user registration onto your server before presenting the verification step. - */ - let waitTitle = NSLocalizedString("Creating account", comment: "") - let waitText = NSLocalizedString("Please wait while we upload your data", comment: "") - let waitStep = ORKWaitStep(identifier: String(describing: Identifier.waitStep)) - waitStep.title = waitTitle - waitStep.text = waitText - - /* - A verification step view controller subclass is required in order to use the verification step. - The subclass provides the view controller button and UI behavior by overriding the following methods. - */ - class VerificationViewController: ORKVerificationStepViewController { - override func resendEmailButtonTapped() { - let alertTitle = NSLocalizedString("Resend Verification Email", comment: "") - let alertMessage = NSLocalizedString("Button tapped", comment: "") - let alert = UIAlertController(title: alertTitle, message: alertMessage, preferredStyle: UIAlertController.Style.alert) - alert.addAction(UIAlertAction(title: "OK", style: UIAlertAction.Style.default, handler: nil)) - self.present(alert, animated: true, completion: nil) - } - } - - let verificationStep = ORKVerificationStep(identifier: String(describing: Identifier.verificationStep), text: exampleDetailText, verificationViewControllerClass: VerificationViewController.self) - - return ORKOrderedTask(identifier: String(describing: Identifier.accountCreationTask), steps: [ - registrationStep, - waitStep, - verificationStep - ]) - } - - /// This tasks presents the login step. - private var loginTask: ORKTask { - /* - A login step view controller subclass is required in order to use the login step. - The subclass provides the behavior for the login step forgot password button. - */ - class LoginViewController: ORKLoginStepViewController { - override func forgotPasswordButtonTapped() { - let alertTitle = NSLocalizedString("Forgot password?", comment: "") - let alertMessage = NSLocalizedString("Button tapped", comment: "") - let alert = UIAlertController(title: alertTitle, message: alertMessage, preferredStyle: UIAlertController.Style.alert) - alert.addAction(UIAlertAction(title: "OK", style: UIAlertAction.Style.default, handler: nil)) - self.present(alert, animated: true, completion: nil) - } - } - - /* - A login step provides a form step that is populated with email and password fields, - and a button for `Forgot password?`. - */ - let loginTitle = NSLocalizedString("Login", comment: "") - let loginStep = ORKLoginStep(identifier: String(describing: Identifier.loginStep), title: loginTitle, text: exampleDetailText, loginViewControllerClass: LoginViewController.self) - - /* - A wait step allows you to validate the data from the user login against your server before proceeding. - */ - let waitTitle = NSLocalizedString("Logging in", comment: "") - let waitText = NSLocalizedString("Please wait while we validate your credentials", comment: "") - let waitStep = ORKWaitStep(identifier: String(describing: Identifier.loginWaitStep)) - waitStep.title = waitTitle - waitStep.text = waitText - - return ORKOrderedTask(identifier: String(describing: Identifier.loginTask), steps: [loginStep, waitStep]) - } - - /// This task demonstrates the Passcode creation process. - private var passcodeTask: ORKTask { - /* - If you want to protect the app using a passcode. It is reccomended to - ask user to create passcode as part of the consent process and use the - authentication and editing view controllers to interact with the passcode. - - The passcode is stored in the keychain. - */ - let passcodeConsentStep = ORKPasscodeStep(identifier: String(describing: Identifier.passcodeStep)) - passcodeConsentStep.title = NSLocalizedString("Passcode", comment: "") - return ORKOrderedTask(identifier: String(describing: Identifier.passcodeTask), steps: [passcodeConsentStep]) - } - /// This task presents the Audio pre-defined active task. private var audioTask: ORKTask { - return ORKOrderedTask.audioTask(withIdentifier: String(describing: Identifier.audioTask), intendedUseDescription: exampleDescription, speechInstruction: exampleSpeechInstruction, shortSpeechInstruction: exampleSpeechInstruction, duration: 20, recordingSettings: nil, checkAudioLevel: true, options: []) + return ORKOrderedTask.audioTask( + withIdentifier: String(describing: Identifier.audioTask), + intendedUseDescription: exampleDescription, + speechInstruction: exampleSpeechInstruction, + shortSpeechInstruction: exampleSpeechInstruction, + duration: 20, + recordingSettings: nil, + checkAudioLevel: true, + options: []) } /** - Amsler Grid + Amsler Grid */ private var amslerGridTask: ORKTask { return ORKOrderedTask.amslerGridTask(withIdentifier: String(describing: Identifier.amslerGridTask), intendedUseDescription: exampleDescription, options: []) } /** - This task presents the Fitness pre-defined active task. For this example, - short walking and rest durations of 20 seconds each are used, whereas more - realistic durations might be several minutes each. - */ + This task presents the Fitness pre-defined active task. For this example, + short walking and rest durations of 20 seconds each are used, whereas more + realistic durations might be several minutes each. + */ private var fitnessTask: ORKTask { return ORKOrderedTask.fitnessCheck(withIdentifier: String(describing: Identifier.fitnessTask), intendedUseDescription: exampleDescription, walkDuration: 20, restDuration: 20, options: []) } /// This task presents the Hole Peg Test pre-defined active task. private var holePegTestTask: ORKTask { - return ORKNavigableOrderedTask.holePegTest(withIdentifier: String(describing: Identifier.holePegTestTask), intendedUseDescription: exampleDescription, dominantHand: .right, numberOfPegs: 9, threshold: 0.2, rotated: false, timeLimit: 300, options: []) + return ORKNavigableOrderedTask.holePegTest( + withIdentifier: String(describing: Identifier.holePegTestTask), + intendedUseDescription: exampleDescription, + dominantHand: .right, + numberOfPegs: 9, + threshold: 0.2, + rotated: false, + timeLimit: 300, + options: []) } /// This task presents the PSAT pre-defined active task. private var PSATTask: ORKTask { - return ORKOrderedTask.psatTask(withIdentifier: String(describing: Identifier.psatTask), intendedUseDescription: exampleDescription, presentationMode: ORKPSATPresentationMode.auditory.union(.visual), interStimulusInterval: 3.0, stimulusDuration: 1.0, seriesLength: 60, options: []) + return ORKOrderedTask.psatTask( + withIdentifier: String(describing: Identifier.psatTask), + intendedUseDescription: exampleDescription, + presentationMode: ORKPSATPresentationMode.auditory.union(.visual), + interStimulusInterval: 3.0, + stimulusDuration: 1.0, + seriesLength: 60, + options: []) } /// This task presents the Reaction Time pre-defined active task. @@ -1696,22 +428,57 @@ enum TaskListRow: Int, CustomStringConvertible { /// An example of a custom sound. let successSoundURL = Bundle.main.url(forResource: "tap", withExtension: "aif")! let successSound = SystemSound(soundURL: successSoundURL)! - return ORKOrderedTask.reactionTime(withIdentifier: String(describing: Identifier.reactionTime), intendedUseDescription: exampleDescription, maximumStimulusInterval: 10, minimumStimulusInterval: 4, thresholdAcceleration: 0.5, numberOfAttempts: 3, timeout: 3, successSound: successSound.soundID, timeoutSound: 0, failureSound: UInt32(kSystemSoundID_Vibrate), options: []) + return ORKOrderedTask.reactionTime( + withIdentifier: String(describing: Identifier.reactionTime), + intendedUseDescription: exampleDescription, + maximumStimulusInterval: 10, + minimumStimulusInterval: 4, + thresholdAcceleration: 0.5, + numberOfAttempts: 3, + timeout: 3, + successSound: successSound.soundID, + timeoutSound: 0, + failureSound: UInt32(kSystemSoundID_Vibrate), options: []) } /// This task presents the Gait and Balance pre-defined active task. private var shortWalkTask: ORKTask { - return ORKOrderedTask.shortWalk(withIdentifier: String(describing: Identifier.shortWalkTask), intendedUseDescription: exampleDescription, numberOfStepsPerLeg: 20, restDuration: 20, options: []) + return ORKOrderedTask.shortWalk( + withIdentifier: String(describing: Identifier.shortWalkTask), + intendedUseDescription: exampleDescription, + numberOfStepsPerLeg: 20, + restDuration: 20, + options: []) } /// This task presents the Spatial Span Memory pre-defined active task. private var spatialSpanMemoryTask: ORKTask { - return ORKOrderedTask.spatialSpanMemoryTask(withIdentifier: String(describing: Identifier.spatialSpanMemoryTask), intendedUseDescription: exampleDescription, initialSpan: 3, minimumSpan: 2, maximumSpan: 15, playSpeed: 1.0, maximumTests: 5, maximumConsecutiveFailures: 3, customTargetImage: nil, customTargetPluralName: nil, requireReversal: false, options: []) + return ORKOrderedTask.spatialSpanMemoryTask( + withIdentifier: String(describing: Identifier.spatialSpanMemoryTask), + intendedUseDescription: exampleDescription, + initialSpan: 3, + minimumSpan: 2, + maximumSpan: 15, + playSpeed: 1.0, + maximumTests: 5, + maximumConsecutiveFailures: 3, + customTargetImage: nil, + customTargetPluralName: nil, + requireReversal: false, + options: []) } /// This task presents the Speech Recognition pre-defined active task. private var speechRecognitionTask: ORKTask { - return ORKOrderedTask.speechRecognitionTask(withIdentifier: String(describing: Identifier.speechRecognitionTask), intendedUseDescription: exampleDescription, speechRecognizerLocale: .englishUS, speechRecognitionImage: nil, speechRecognitionText: NSLocalizedString("A quick brown fox jumps over the lazy dog.", comment: ""), shouldHideTranscript: false, allowsEdittingTranscript: true, options: []) + return ORKOrderedTask.speechRecognitionTask( + withIdentifier: String(describing: Identifier.speechRecognitionTask), + intendedUseDescription: exampleDescription, + speechRecognizerLocale: .englishUS, + speechRecognitionImage: nil, + speechRecognitionText: NSLocalizedString("A quick brown fox jumps over the lazy dog.", comment: ""), + shouldHideTranscript: false, + allowsEdittingTranscript: true, + options: []) } /// This task presents the Speech in Noise pre-defined active task. @@ -1732,38 +499,48 @@ enum TaskListRow: Int, CustomStringConvertible { instructionStep.image = UIImage(named: "stroop") instructionStep.imageContentMode = .center instructionStep.detailText = "Every time a word appears, select the first letter of the name of the COLOR that is shown." - + let instructionStep2 = ORKInstructionStep(identifier: "stroopInstructionStep2") instructionStep2.title = "Stroop" instructionStep2.text = "Your description goes here." instructionStep2.detailText = "Every time a word appears, select the first letter of the name of the COLOR that is shown." instructionStep2.image = UIImage(named: "stroop") instructionStep2.imageContentMode = .center - let countdownStep = ORKCountdownStep(identifier: "stroopCountdownStep") countdownStep.title = "Stroop" - let stroopStep = ORKSwiftStroopStep(identifier: "stroopStep") stroopStep.numberOfAttempts = 10 stroopStep.title = "Stroop" stroopStep.text = "Select the first letter of the name of the COLOR that is shown." stroopStep.spokenInstruction = stroopStep.text - let completionStep = ORKCompletionStep(identifier: "stroopCompletionStep") completionStep.title = "Activity Complete" completionStep.text = "Your data will be analyzed and you will be notified when your results are ready." - + return ORKOrderedTask(identifier: "stroopTask", steps: [instructionStep, instructionStep2, countdownStep, stroopStep, completionStep]) } - + /// This task presents the Timed Walk with turn around pre-defined active task. private var timedWalkWithTurnAroundTask: ORKTask { - return ORKOrderedTask.timedWalk(withIdentifier: String(describing: Identifier.timedWalkWithTurnAroundTask), intendedUseDescription: exampleDescription, distanceInMeters: 100.0, timeLimit: 180.0, turnAroundTimeLimit: 60.0, includeAssistiveDeviceForm: true, options: []) + return ORKOrderedTask.timedWalk( + withIdentifier: String(describing: Identifier.timedWalkWithTurnAroundTask), + intendedUseDescription: exampleDescription, + distanceInMeters: 100.0, + timeLimit: 180.0, + turnAroundTimeLimit: 60.0, + includeAssistiveDeviceForm: true, + options: []) } - + /// This task presents the Tone Audiometry pre-defined active task. private var toneAudiometryTask: ORKTask { - return ORKOrderedTask.toneAudiometryTask(withIdentifier: String(describing: Identifier.toneAudiometryTask), intendedUseDescription: exampleDescription, speechInstruction: nil, shortSpeechInstruction: nil, toneDuration: 20, options: []) + return ORKOrderedTask.toneAudiometryTask( + withIdentifier: String(describing: Identifier.toneAudiometryTask), + intendedUseDescription: exampleDescription, + speechInstruction: nil, + shortSpeechInstruction: nil, + toneDuration: 20, + options: []) } /// This task presents the dBHL Tone Audiometry pre-defined active task. @@ -1780,7 +557,7 @@ enum TaskListRow: Int, CustomStringConvertible { splMeterStep.title = NSLocalizedString("SPL Meter", comment: "") return ORKOrderedTask(identifier: String(describing: Identifier.splMeterTask), steps: [splMeterStep]) } - + private var towerOfHanoiTask: ORKTask { return ORKOrderedTask.towerOfHanoiTask(withIdentifier: String(describing: Identifier.towerOfHanoi), intendedUseDescription: exampleDescription, numberOfDisks: 5, options: []) } @@ -1788,22 +565,27 @@ enum TaskListRow: Int, CustomStringConvertible { /// This task presents the Two Finger Tapping pre-defined active task. private var twoFingerTappingIntervalTask: ORKTask { return ORKOrderedTask.twoFingerTappingIntervalTask(withIdentifier: String(describing: Identifier.twoFingerTappingIntervalTask), intendedUseDescription: exampleDescription, duration: 10, - handOptions: [.both], options: []) + handOptions: [.both], options: []) } /// This task presents a walk back-and-forth task private var walkBackAndForthTask: ORKTask { - return ORKOrderedTask.walkBackAndForthTask(withIdentifier: String(describing: Identifier.walkBackAndForthTask), intendedUseDescription: exampleDescription, walkDuration: 30, restDuration: 30, options: []) + return ORKOrderedTask.walkBackAndForthTask( + withIdentifier: String(describing: Identifier.walkBackAndForthTask), + intendedUseDescription: exampleDescription, + walkDuration: 30, + restDuration: 30, + options: []) } /// This task presents the Tremor Test pre-defined active task. private var tremorTestTask: ORKTask { return ORKOrderedTask.tremorTest(withIdentifier: String(describing: Identifier.tremorTestTask), - intendedUseDescription: exampleDescription, - activeStepDuration: 10, - activeTaskOptions: [], - handOptions: [.both], - options: []) + intendedUseDescription: exampleDescription, + activeStepDuration: 10, + activeTaskOptions: [], + handOptions: [.both], + options: []) } /// This task presents a knee range of motion task @@ -1813,15 +595,24 @@ enum TaskListRow: Int, CustomStringConvertible { /// This task presents a shoulder range of motion task private var shoulderRangeOfMotion: ORKTask { - return ORKOrderedTask.shoulderRangeOfMotionTask(withIdentifier: String(describing: Identifier.shoulderRangeOfMotion), limbOption: .left, intendedUseDescription: exampleDescription, options: []) + return ORKOrderedTask.shoulderRangeOfMotionTask( + withIdentifier: String(describing: Identifier.shoulderRangeOfMotion), + limbOption: .left, + intendedUseDescription: exampleDescription, + options: []) } /// This task presents a trail making task private var trailMaking: ORKTask { let intendedUseDescription = "Tests visual attention and task switching" - return ORKOrderedTask.trailmakingTask(withIdentifier: String(describing: Identifier.trailMaking), intendedUseDescription: intendedUseDescription, trailmakingInstruction: nil, trailType: .B, options: []) + return ORKOrderedTask.trailmakingTask( + withIdentifier: String(describing: Identifier.trailMaking), + intendedUseDescription: intendedUseDescription, + trailmakingInstruction: nil, + trailType: .B, + options: []) } - + // This task presents a visual acuity landolt C task private var visualAcuityLandoltC: ORKTask { let orderedTask = ORKOrderedTask.landoltCVisualAcuityTask(withIdentifier: String(describing: Identifier.visualAcuityLandoltC), intendedUseDescription: "lorem ipsum") @@ -1843,7 +634,6 @@ enum TaskListRow: Int, CustomStringConvertible { return ORKOrderedTask(identifier: String(describing: Identifier.videoInstructionTask), steps: [videoInstructionStep]) } - /// This task presents a video instruction step private var frontFacingCameraStep: ORKTask { let frontFacingCameraStep = ORKFrontFacingCameraStep(identifier: String(describing: Identifier.frontFacingCameraStep)) @@ -1856,7 +646,6 @@ enum TaskListRow: Int, CustomStringConvertible { return ORKOrderedTask(identifier: String(describing: Identifier.videoInstructionTask), steps: [frontFacingCameraStep]) } - /// This task presents a web view step private var webView: ORKTask { let webViewStep = ORKWebViewStep(identifier: String(describing: Identifier.webViewStep), html: exampleHtml) @@ -1864,126 +653,786 @@ enum TaskListRow: Int, CustomStringConvertible { webViewStep.showSignatureAfterContent = true return ORKOrderedTask(identifier: String(describing: Identifier.webViewTask), steps: [webViewStep]) } - - // MARK: Consent Document Creation Convenience - - /** - A consent document provides the content for the visual consent and consent - review steps. This helper sets up a consent document with some dummy - content. You should populate your consent document to suit your study. - */ - private var consentDocument: ORKConsentDocument { - let consentDocument = ORKConsentDocument() - - /* - This is the title of the document, displayed both for review and in - the generated PDF. - */ - consentDocument.title = NSLocalizedString("Example Consent", comment: "") - - // This is the title of the signature page in the generated document. - consentDocument.signaturePageTitle = NSLocalizedString("Consent", comment: "") - - /* - This is the line shown on the signature page of the generated document, - just above the signatures. - */ - consentDocument.signaturePageContent = NSLocalizedString("I agree to participate in this research study.", comment: "") - - /* - Add the participant signature, which will be filled in during the - consent review process. This signature initially does not have a - signature image or a participant name; these are collected during - the consent review step. - */ - let participantSignatureTitle = NSLocalizedString("Participant", comment: "") - let participantSignature = ORKConsentSignature(forPersonWithTitle: participantSignatureTitle, dateFormatString: nil, identifier: String(describing: Identifier.consentDocumentParticipantSignature)) - - consentDocument.addSignature(participantSignature) - - /* - Add the investigator signature. This is pre-populated with the - investigator's signature image and name, and the date of their - signature. If you need to specify the date as now, you could generate - a date string with code here. - - This signature is only used for the generated PDF. - */ - let signatureImage = UIImage(named: "signature")! - let investigatorSignatureTitle = NSLocalizedString("Investigator", comment: "") - let investigatorSignatureGivenName = NSLocalizedString("Jonny", comment: "") - let investigatorSignatureFamilyName = NSLocalizedString("Appleseed", comment: "") - let investigatorSignatureDateString = "3/10/15" - - let investigatorSignature = ORKConsentSignature(forPersonWithTitle: investigatorSignatureTitle, dateFormatString: nil, identifier: String(describing: Identifier.consentDocumentInvestigatorSignature), givenName: investigatorSignatureGivenName, familyName: investigatorSignatureFamilyName, signatureImage: signatureImage, dateString: investigatorSignatureDateString) - - consentDocument.addSignature(investigatorSignature) - - /* - This is the HTML content for the "Learn More" page for each consent - section. In a real consent, this would be your content, and you would - have different content for each section. - - If your content is just text, you can use the `content` property - instead of the `htmlContent` property of `ORKConsentSection`. - */ - let htmlContentString = "
  • Lorem
  • ipsum
  • dolor

\(loremIpsumLongText)

\(loremIpsumMediumText)

" - - /* - These are all the consent section types that have pre-defined animations - and images. We use them in this specific order, so we see the available - animated transitions. - */ - let consentSectionTypes: [ORKConsentSectionType] = [ - .overview, - .dataGathering, - .privacy, - .dataUse, - .timeCommitment, - .studySurvey, - .studyTasks, - .withdrawing - ] - - /* - For each consent section type in `consentSectionTypes`, create an - `ORKConsentSection` that represents it. +} - In a real app, you would set specific content for each section. - */ - var consentSections: [ORKConsentSection] = consentSectionTypes.map { contentSectionType in - let consentSection = ORKConsentSection(type: contentSectionType) - - consentSection.summary = loremIpsumShortText - - if contentSectionType == .overview { - consentSection.htmlContent = htmlContentString - } else { - consentSection.content = loremIpsumLongText - } - - return consentSection +// MARK: - Task Creation Convenience +extension TaskListRow { + /// Returns a new `ORKTask` that the `TaskListRow` enumeration represents. + var representedTask: ORKTask { + switch self { + case .form: return formTask + case .groupedForm: return groupedFormTask + case .survey: return surveyTask + case .booleanQuestion: return booleanQuestionTask + case .customBooleanQuestion: return customBooleanQuestionTask + case .dateQuestion: return dateQuestionTask + case .dateTimeQuestion: return dateTimeQuestionTask + case .imageChoiceQuestion: return imageChoiceQuestionTask + case .locationQuestion: return locationQuestionTask + case .numericQuestion: return numericQuestionTask + case .scaleQuestion: return scaleQuestionTask + case .textQuestion: return textQuestionTask + case .textChoiceQuestion: return textChoiceQuestionTask + case .timeIntervalQuestion: return timeIntervalQuestionTask + case .timeOfDayQuestion: return timeOfDayQuestionTask + case .valuePickerChoiceQuestion: return valuePickerChoiceQuestionTask + case .validatedTextQuestion: return validatedTextQuestionTask + case .imageCapture: return imageCaptureTask + case .videoCapture: return videoCaptureTask + case .frontFacingCamera: return frontFacingCameraStep + case .wait: return waitTask + case .PDFViewer: return PDFViewerTask + case .eligibilityTask: return eligibilityTask + case .consent: return consentTask + case .accountCreation: return accountCreationTask + case .login: return loginTask + case .passcode: return passcodeTask + case .audio: return audioTask + case .amslerGrid: return amslerGridTask + case .fitness: return fitnessTask + case .holePegTest: return holePegTestTask + case .psat: return PSATTask + case .reactionTime: return reactionTimeTask + case .shortWalk: return shortWalkTask + case .spatialSpanMemory: return spatialSpanMemoryTask + case .speechRecognition: return speechRecognitionTask + case .speechInNoise: return speechInNoiseTask + case .stroop: return stroopTask + case .swiftStroop: return swiftStroopTask + case .timedWalkWithTurnAround: return timedWalkWithTurnAroundTask + case .toneAudiometry: return toneAudiometryTask + case .dBHLToneAudiometry: return dBHLToneAudiometryTask + case .splMeter: return splMeterTask + case .towerOfHanoi: return towerOfHanoiTask + case .twoFingerTappingInterval: return twoFingerTappingIntervalTask + case .walkBackAndForth: return walkBackAndForthTask + case .tremorTest: return tremorTestTask + case .kneeRangeOfMotion: return kneeRangeOfMotion + case .shoulderRangeOfMotion: return shoulderRangeOfMotion + case .trailMaking: return trailMaking + case .visualAcuityLandoltC: return visualAcuityLandoltC + case .contrastSensitivityPeakLandoltC: return contrastSensitivityPeakLandoltC + case .videoInstruction: return videoInstruction + case .webView: return webView } - - /* - This is an example of a section that is only in the review document - or only in the generated PDF, and is not displayed in `ORKVisualConsentStep`. - */ - let consentSection = ORKConsentSection(type: .onlyInDocument) - consentSection.summary = NSLocalizedString(".OnlyInDocument Scene Summary", comment: "") - consentSection.title = NSLocalizedString(".OnlyInDocument Scene", comment: "") - consentSection.content = loremIpsumLongText - - consentSections += [consentSection] - - // Set the sections on the document after they've been created. + } +} + +// MARK: - Form task +extension TaskListRow { + /** + This task demonstrates a form step, in which multiple items are presented + in a single scrollable form. This might be used for entering multi-value + data, like taking a blood pressure reading with separate systolic and + diastolic values. + */ + private var formTask: ORKTask { + let step = ORKFormStep(identifier: String(describing: Identifier.formStep), + title: NSLocalizedString(ModuleAppYmlReader().surverysTaskModel?.title ?? "Form Step", comment: ""), + text: ModuleAppYmlReader().surverysTaskModel?.additionalText ?? "Additional text can go here.") + // A first field, for entering an integer. + let formItem01Text = NSLocalizedString("Field01", comment: "") + let formItem01 = ORKFormItem(identifier: String(describing: Identifier.formItem01), text: formItem01Text, answerFormat: ORKAnswerFormat.integerAnswerFormat(withUnit: nil)) + formItem01.placeholder = NSLocalizedString(ModuleAppYmlReader().surverysTaskModel?.placeholder ?? Constants.YamlDefaults.placeholder, comment: "") + // A second field, for entering a time interval. + let formItem02Text = NSLocalizedString("Field02", comment: "") + let formItem02 = ORKFormItem(identifier: String(describing: Identifier.formItem02), text: formItem02Text, answerFormat: ORKTimeIntervalAnswerFormat()) + formItem02.placeholder = NSLocalizedString(ModuleAppYmlReader().surverysTaskModel?.placeholder ?? Constants.YamlDefaults.placeholder, comment: "") + let formItem03Text = NSLocalizedString(ModuleAppYmlReader().surverysTaskModel?.questionText ?? Constants.YamlDefaults.questionText, comment: "") + let scaleAnswerFormat = ORKScaleAnswerFormat(maximumValue: 10, minimumValue: 0, defaultValue: 0, step: 1)// ORKScaleAnswerFormat(maximumValue: 10, minimumValue: 0, defaultValue: 0, step: 1) + scaleAnswerFormat.shouldHideRanges = true + let formItem03 = ORKFormItem(identifier: String(describing: Identifier.formItem03), text: formItem03Text, answerFormat: scaleAnswerFormat) + let textChoices: [ORKTextChoice] = [ + ORKTextChoice(text: "choice 1", detailText: "detail 1", value: 1 as NSCoding & NSCopying & NSObjectProtocol, exclusive: false), + ORKTextChoice(text: "choice 2", detailText: "detail 2", value: 2 as NSCoding & NSCopying & NSObjectProtocol, exclusive: false), + ORKTextChoice(text: "choice 3", detailText: "detail 3", value: 3 as NSCoding & NSCopying & NSObjectProtocol, exclusive: false), + ORKTextChoice(text: "choice 4", detailText: "detail 4", value: 4 as NSCoding & NSCopying & NSObjectProtocol, exclusive: false), + ORKTextChoice(text: "choice 5", detailText: "detail 5", value: 5 as NSCoding & NSCopying & NSObjectProtocol, exclusive: false), + ORKTextChoice(text: "choice 6", detailText: "detail 6", value: 6 as NSCoding & NSCopying & NSObjectProtocol, exclusive: false) + ] + let textScaleAnswerFormat = ORKTextScaleAnswerFormat(textChoices: textChoices, defaultIndex: 10) + textScaleAnswerFormat.shouldHideLabels = true + textScaleAnswerFormat.shouldShowDontKnowButton = true + let formItem04 = ORKFormItem( + identifier: String(describing: Identifier.formItem04), + text: ModuleAppYmlReader().surverysTaskModel?.questionText ?? Constants.YamlDefaults.questionText, + answerFormat: textScaleAnswerFormat) + let appleChoices: [ORKTextChoice] = [ + ORKTextChoice(text: ModuleAppYmlReader().surverysTaskModel?.grannySmith ?? "Granny Smith", value: 1 as NSCoding & NSCopying & NSObjectProtocol), + ORKTextChoice(text: ModuleAppYmlReader().surverysTaskModel?.honeycrisp ?? "Honeycrisp", value: 2 as NSCoding & NSCopying & NSObjectProtocol), + ORKTextChoice(text: ModuleAppYmlReader().surverysTaskModel?.fuji ?? "Fuji", value: 3 as NSCoding & NSCopying & NSObjectProtocol), + ORKTextChoice(text: ModuleAppYmlReader().surverysTaskModel?.mcIntosh ?? "McIntosh", value: 10 as NSCoding & NSCopying & NSObjectProtocol), + ORKTextChoice(text: ModuleAppYmlReader().surverysTaskModel?.kanzi ?? "Kanzi", value: 5 as NSCoding & NSCopying & NSObjectProtocol)] + let appleAnswerFormat = ORKTextChoiceAnswerFormat(style: .singleChoice, textChoices: appleChoices) + let appleFormItem = ORKFormItem(identifier: "appleFormItemIdentifier", + text: ModuleAppYmlReader().surverysTaskModel?.itemQuestion ?? Constants.YamlDefaults.favorite, + answerFormat: appleAnswerFormat) + step.formItems = [ appleFormItem, formItem03, formItem04, formItem01, formItem02 ] + let completionStep = ORKCompletionStep(identifier: "CompletionStep") + completionStep.title = NSLocalizedString("All Done!", comment: "") + completionStep.detailText = NSLocalizedString("You have completed the questionnaire.", comment: "") + return ORKOrderedTask(identifier: String(describing: Identifier.formTask), steps: [step, completionStep]) + } +} + +// MARK: - Account creation and passcode +extension TaskListRow { + /// This task presents the Account Creation process. + private var accountCreationTask: ORKTask { + /* + A registration step provides a form step that is populated with email and password fields. + If you wish to include any of the additional fields, then you can specify it through the `options` parameter. + */ + let registrationTitle = NSLocalizedString("Registration", comment: "") + let passcodeValidationRegexPattern = "^(?=.*\\d).{4,8}$" + let passcodeValidationRegularExpression = try? NSRegularExpression(pattern: passcodeValidationRegexPattern) + let passcodeInvalidMessage = NSLocalizedString("A valid password must be 4 to 8 characters long and include at least one numeric character.", comment: "") + let registrationOptions: ORKRegistrationStepOption = [.includeGivenName, .includeFamilyName, .includeGender, .includeDOB, .includePhoneNumber] + let registrationStep = ORKRegistrationStep( + identifier: String(describing: Identifier.registrationStep), + title: registrationTitle, + text: exampleDetailText, + passcodeValidationRegularExpression: passcodeValidationRegularExpression, + passcodeInvalidMessage: passcodeInvalidMessage, + options: registrationOptions) + registrationStep.phoneNumberValidationRegularExpression = try? NSRegularExpression(pattern: "^[+]{1,1}[1]{1,1}\\s{1,1}[(]{1,1}[1-9]{3,3}[)]{1,1}\\s{1,1}[1-9]{3,3}\\s{1,1}[1-9]{4,4}$") + registrationStep.phoneNumberInvalidMessage = "Expected format +1 (555) 555 5555" + /* + A wait step allows you to upload the data from the user registration onto your server before presenting the verification step. + */ + let waitTitle = NSLocalizedString("Creating account", comment: "") + let waitText = NSLocalizedString("Please wait while we upload your data", comment: "") + let waitStep = ORKWaitStep(identifier: String(describing: Identifier.waitStep)) + waitStep.title = waitTitle + waitStep.text = waitText + /* + A verification step view controller subclass is required in order to use the verification step. + The subclass provides the view controller button and UI behavior by overriding the following methods. + */ + class VerificationViewController: ORKVerificationStepViewController { + override func resendEmailButtonTapped() { + let alertTitle = NSLocalizedString("Resend Verification Email", comment: "") + let alertMessage = NSLocalizedString("Button tapped", comment: "") + let alert = UIAlertController(title: alertTitle, message: alertMessage, preferredStyle: UIAlertController.Style.alert) + alert.addAction(UIAlertAction(title: "OK", style: UIAlertAction.Style.default, handler: nil)) + self.present(alert, animated: true, completion: nil) + } + } + let verificationStep = ORKVerificationStep( + identifier: String(describing: Identifier.verificationStep), + text: exampleDetailText, + verificationViewControllerClass: VerificationViewController.self) + return ORKOrderedTask(identifier: String(describing: Identifier.accountCreationTask), steps: [ + registrationStep, + waitStep, + verificationStep + ]) + } + + /// This tasks presents the login step. + private var loginTask: ORKTask { + /* + A login step view controller subclass is required in order to use the login step. + The subclass provides the behavior for the login step forgot password button. + */ + class LoginViewController: ORKLoginStepViewController { + override func forgotPasswordButtonTapped() { + let alertTitle = NSLocalizedString("Forgot password?", comment: "") + let alertMessage = NSLocalizedString("Button tapped", comment: "") + let alert = UIAlertController(title: alertTitle, message: alertMessage, preferredStyle: UIAlertController.Style.alert) + alert.addAction(UIAlertAction(title: "OK", style: UIAlertAction.Style.default, handler: nil)) + self.present(alert, animated: true, completion: nil) + } + } + /* + A login step provides a form step that is populated with email and password fields, + and a button for `Forgot password?`. + */ + let loginTitle = NSLocalizedString("Login", comment: "") + let loginStep = ORKLoginStep(identifier: String(describing: Identifier.loginStep), title: loginTitle, text: exampleDetailText, loginViewControllerClass: LoginViewController.self) + /* + A wait step allows you to validate the data from the user login against your server before proceeding. + */ + let waitTitle = NSLocalizedString("Logging in", comment: "") + let waitText = NSLocalizedString("Please wait while we validate your credentials", comment: "") + let waitStep = ORKWaitStep(identifier: String(describing: Identifier.loginWaitStep)) + waitStep.title = waitTitle + waitStep.text = waitText + return ORKOrderedTask(identifier: String(describing: Identifier.loginTask), steps: [loginStep, waitStep]) + } + + /// This task demonstrates the Passcode creation process. + private var passcodeTask: ORKTask { + /* + If you want to protect the app using a passcode. It is reccomended to + ask user to create passcode as part of the consent process and use the + authentication and editing view controllers to interact with the passcode. + The passcode is stored in the keychain. + */ + let passcodeConsentStep = ORKPasscodeStep(identifier: String(describing: Identifier.passcodeStep)) + passcodeConsentStep.title = NSLocalizedString("Passcode", comment: "") + return ORKOrderedTask(identifier: String(describing: Identifier.passcodeTask), steps: [passcodeConsentStep]) + } + + /// A task demonstrating how the ResearchKit framework can be used to obtain informed consent. + private var consentTask: ORKTask { + /* + Informed consent starts by presenting an animated sequence conveying + the main points of your consent document. + */ + let visualConsentStep = ORKVisualConsentStep(identifier: String(describing: Identifier.visualConsentStep), document: consentDocument) + + let investigatorShortDescription = NSLocalizedString("Institution", comment: "") + let investigatorLongDescription = NSLocalizedString("Institution and its partners", comment: "") + let localizedLearnMoreHTMLContent = NSLocalizedString("Your sharing learn more content here.", comment: "") + /* + If you want to share the data you collect with other researchers for + use in other studies beyond this one, it is best practice to get + explicit permission from the participant. Use the consent sharing step + for this. + */ + let sharingConsentStep = ORKConsentSharingStep( + identifier: String(describing: Identifier.consentSharingStep), + investigatorShortDescription: investigatorShortDescription, + investigatorLongDescription: investigatorLongDescription, + localizedLearnMoreHTMLContent: localizedLearnMoreHTMLContent) + + /* + After the visual presentation, the consent review step displays + your consent document and can obtain a signature from the participant. + + The first signature in the document is the participant's signature. + This effectively tells the consent review step which signatory is + reviewing the document. + */ + let signature = consentDocument.signatures!.first + let reviewConsentStep = ORKConsentReviewStep(identifier: String(describing: Identifier.consentReviewStep), signature: signature, in: consentDocument) + reviewConsentStep.requiresScrollToBottom = true + // In a real application, you would supply your own localized text. + reviewConsentStep.title = NSLocalizedString("Consent Document", comment: "") + reviewConsentStep.text = loremIpsumText + reviewConsentStep.reasonForConsent = loremIpsumText + return ORKOrderedTask(identifier: String(describing: Identifier.consentTask), steps: [ + visualConsentStep, + sharingConsentStep, + reviewConsentStep + ]) + } + + private var eligibilityTask: ORKTask { + // Intro step + let introStep = ORKInstructionStep(identifier: String(describing: Identifier.eligibilityIntroStep)) + introStep.title = NSLocalizedString("Eligibility Task", comment: "") + introStep.text = exampleDescription + introStep.detailText = + NSLocalizedString( + "Please use this space to provide instructions for participants. Please make sure to provide enough information so that users can progress through the survey and complete with ease.", + comment: "") + // Form step + let formStep = ORKFormStep(identifier: String(describing: Identifier.eligibilityFormStep)) + formStep.title = NSLocalizedString("Eligibility", comment: "") + formStep.isOptional = false + // Form items + let textChoices: [ORKTextChoice] = [ + ORKTextChoice(text: "Yes", value: "Yes" as NSCoding & NSCopying & NSObjectProtocol), + ORKTextChoice(text: "No", value: "No" as NSCoding & NSCopying & NSObjectProtocol), + ORKTextChoice(text: "N/A", value: "N/A" as NSCoding & NSCopying & NSObjectProtocol)] + let answerFormat = ORKTextChoiceAnswerFormat(style: ORKChoiceAnswerStyle.singleChoice, textChoices: textChoices) + let formItem01 = ORKFormItem( + identifier: String(describing: Identifier.eligibilityFormItem01), + text: ModuleAppYmlReader().surverysTaskModel?.questionText ?? Constants.YamlDefaults.questionText, + answerFormat: answerFormat) + formItem01.isOptional = false + let formItem02 = ORKFormItem( + identifier: String(describing: Identifier.eligibilityFormItem02), + text: ModuleAppYmlReader().surverysTaskModel?.questionText ?? Constants.YamlDefaults.questionText, + answerFormat: answerFormat) + formItem02.isOptional = false + let formItem03 = ORKFormItem( + identifier: String(describing: Identifier.eligibilityFormItem03), + text: ModuleAppYmlReader().surverysTaskModel?.questionText ?? Constants.YamlDefaults.questionText, + answerFormat: answerFormat) + formItem03.isOptional = false + formStep.formItems = [ formItem01, formItem02, formItem03 ] + // Ineligible step + let ineligibleStep = ORKInstructionStep(identifier: String(describing: Identifier.eligibilityIneligibleStep)) + ineligibleStep.title = NSLocalizedString("Eligibility Result", comment: "") + ineligibleStep.detailText = NSLocalizedString("You are ineligible to join the study", comment: "") + // Eligible step + let eligibleStep = ORKCompletionStep(identifier: String(describing: Identifier.eligibilityEligibleStep)) + eligibleStep.title = NSLocalizedString("Eligibility Result", comment: "") + eligibleStep.detailText = NSLocalizedString("You are eligible to join the study", comment: "") + // Create the task + let eligibilityTask = ORKNavigableOrderedTask(identifier: String(describing: Identifier.eligibilityTask), steps: [ + introStep, + formStep, + ineligibleStep, + eligibleStep + ]) + // Build navigation rules. + var resultSelector = ORKResultSelector(stepIdentifier: String(describing: Identifier.eligibilityFormStep), resultIdentifier: String(describing: Identifier.eligibilityFormItem01)) + let predicateFormItem01 = ORKResultPredicate.predicateForChoiceQuestionResult(with: resultSelector, expectedAnswerValue: "Yes" as NSCoding & NSCopying & NSObjectProtocol) + resultSelector = ORKResultSelector(stepIdentifier: String(describing: Identifier.eligibilityFormStep), resultIdentifier: String(describing: Identifier.eligibilityFormItem02)) + let predicateFormItem02 = ORKResultPredicate.predicateForChoiceQuestionResult(with: resultSelector, expectedAnswerValue: "Yes" as NSCoding & NSCopying & NSObjectProtocol) + resultSelector = ORKResultSelector(stepIdentifier: String(describing: Identifier.eligibilityFormStep), resultIdentifier: String(describing: Identifier.eligibilityFormItem03)) + let predicateFormItem03 = ORKResultPredicate.predicateForChoiceQuestionResult(with: resultSelector, expectedAnswerValue: "No" as NSCoding & NSCopying & NSObjectProtocol) + let predicateEligible = NSCompoundPredicate(andPredicateWithSubpredicates: [predicateFormItem01, predicateFormItem02, predicateFormItem03]) + let predicateRule = ORKPredicateStepNavigationRule(resultPredicatesAndDestinationStepIdentifiers: [ (predicateEligible, String(describing: Identifier.eligibilityEligibleStep)) ]) + eligibilityTask.setNavigationRule(predicateRule, forTriggerStepIdentifier: String(describing: Identifier.eligibilityFormStep)) + + // Add end direct rules to skip unneeded steps + let directRule = ORKDirectStepNavigationRule(destinationStepIdentifier: ORKNullStepIdentifier) + eligibilityTask.setNavigationRule(directRule, forTriggerStepIdentifier: String(describing: Identifier.eligibilityIneligibleStep)) + + return eligibilityTask + } +} + +// MARK: - Text questions +extension TaskListRow { + /** + This task demonstrates asking for text entry. Both single and multi-line + text entry are supported, with appropriate parameters to the text answer + format. + */ + private var textQuestionTask: ORKTask { + let answerFormat = ORKAnswerFormat.textAnswerFormat() + answerFormat.multipleLines = true + answerFormat.maximumLength = 280 + let step = ORKQuestionStep( + identifier: String(describing: Identifier.textQuestionStep), + title: NSLocalizedString("Text", comment: ""), + question: ModuleAppYmlReader().surverysTaskModel?.questionText ?? Constants.YamlDefaults.questionText, + answer: answerFormat) + step.text = exampleDetailText + return ORKOrderedTask(identifier: String(describing: Identifier.textQuestionTask), steps: [step]) + } + + /** + This task demonstrates a survey question for picking from a list of text + choices. In this case, the text choices are presented in a table view + (compare with the `valuePickerQuestionTask`). + */ + private var textChoiceQuestionTask: ORKTask { + let textChoiceOneText = NSLocalizedString("Choice 1", comment: "") + let textChoiceTwoText = NSLocalizedString("Choice 2", comment: "") + let textChoiceThreeText = NSLocalizedString("Choice 3", comment: "") + let textChoiceFourText = NSLocalizedString("Other", comment: "") + // The text to display can be separate from the value coded for each choice: + let textChoices = [ + ORKTextChoice(text: textChoiceOneText, value: "choice_1" as NSCoding & NSCopying & NSObjectProtocol), + ORKTextChoice(text: textChoiceTwoText, value: "choice_2" as NSCoding & NSCopying & NSObjectProtocol), + ORKTextChoice(text: textChoiceThreeText, value: "choice_3" as NSCoding & NSCopying & NSObjectProtocol), + ORKTextChoiceOther.choice( + withText: textChoiceFourText, + detailText: nil, + value: "choice_4" as NSCoding & NSCopying & NSObjectProtocol, + exclusive: true, + textViewPlaceholderText: "enter additional information") + ] + let answerFormat = ORKAnswerFormat.choiceAnswerFormat(with: .singleChoice, textChoices: textChoices) + let questionStep = ORKQuestionStep( + identifier: String(describing: Identifier.textChoiceQuestionStep), + title: NSLocalizedString("Text Choice", comment: ""), + question: ModuleAppYmlReader().surverysTaskModel?.questionText ?? Constants.YamlDefaults.questionText, + answer: answerFormat) + questionStep.text = exampleDetailText + return ORKOrderedTask(identifier: String(describing: Identifier.textChoiceQuestionTask), steps: [questionStep]) + } + + /** + This task demonstrates asking for text entry. Both single and multi-line + text entry are supported, with appropriate parameters to the text answer + format. + */ + private var validatedTextQuestionTask: ORKTask { + let answerFormatEmail = ORKAnswerFormat.emailAnswerFormat() + let stepEmail = ORKQuestionStep( + identifier: String(describing: Identifier.validatedTextQuestionStepEmail), + title: NSLocalizedString("Validated Text", comment: ""), + question: NSLocalizedString("Email", comment: ""), + answer: answerFormatEmail) + stepEmail.text = exampleDetailText + let domainRegularExpressionPattern = "^(https?:\\/\\/)?([\\da-z\\.-]+)\\.([a-z\\.]{2,6})([\\/\\w \\.-]*)*\\/?$" + let domainRegularExpression = try? NSRegularExpression(pattern: domainRegularExpressionPattern) + let answerFormatDomain = ORKAnswerFormat.textAnswerFormat(withValidationRegularExpression: domainRegularExpression!, invalidMessage: "Invalid URL: %@") + answerFormatDomain.multipleLines = false + answerFormatDomain.keyboardType = .URL + answerFormatDomain.autocapitalizationType = UITextAutocapitalizationType.none + answerFormatDomain.autocorrectionType = UITextAutocorrectionType.no + answerFormatDomain.spellCheckingType = UITextSpellCheckingType.no + answerFormatDomain.textContentType = UITextContentType.URL + let stepDomain = ORKQuestionStep( + identifier: String(describing: Identifier.validatedTextQuestionStepDomain), + title: NSLocalizedString("Validated Text", comment: ""), + question: NSLocalizedString("URL", comment: ""), + answer: answerFormatDomain) + stepDomain.text = exampleDetailText + return ORKOrderedTask(identifier: String(describing: Identifier.validatedTextQuestionTask), steps: [stepEmail, stepDomain]) + } +} + +// MARK: - Date and time questions +extension TaskListRow { + /// This task demonstrates a question which asks for a date. + private var dateQuestionTask: ORKTask { + /* + The date answer format can also support minimum and maximum limits, + a specific default value, and overriding the calendar to use. + */ + let answerFormat = ORKAnswerFormat.dateAnswerFormat() + + let step = ORKQuestionStep( + identifier: String(describing: Identifier.dateQuestionStep), + title: NSLocalizedString("Date", comment: ""), + question: ModuleAppYmlReader().surverysTaskModel?.questionText ?? Constants.YamlDefaults.questionText, + answer: answerFormat) + + step.text = exampleDetailText + + return ORKOrderedTask(identifier: String(describing: Identifier.dateQuestionTask), steps: [step]) + } + + /// This task demonstrates a question asking for a date and time of an event. + private var dateTimeQuestionTask: ORKTask { + /* + This uses the default calendar. Use a more detailed constructor to + set minimum / maximum limits. + */ + let answerFormat = ORKAnswerFormat.dateTime() + + let step = ORKQuestionStep( + identifier: String(describing: Identifier.dateTimeQuestionStep), + title: NSLocalizedString("Date and Time", comment: ""), + question: ModuleAppYmlReader().surverysTaskModel?.questionText ?? Constants.YamlDefaults.questionText, + answer: answerFormat) + + step.text = exampleDetailText + + return ORKOrderedTask(identifier: String(describing: Identifier.dateTimeQuestionTask), steps: [step]) + } + + /** + This task demonstrates requesting a time interval. For example, this might + be a suitable answer format for a question like "How long is your morning + commute?" + */ + private var timeIntervalQuestionTask: ORKTask { + /* + The time interval answer format is constrained to entering a time + less than 24 hours and in steps of minutes. For times that don't fit + these restrictions, use another mode of data entry. + */ + let answerFormat = ORKAnswerFormat.timeIntervalAnswerFormat() + let step = ORKQuestionStep( + identifier: String(describing: Identifier.timeIntervalQuestionStep), + title: NSLocalizedString("Time Interval", comment: ""), + question: ModuleAppYmlReader().surverysTaskModel?.questionText ?? Constants.YamlDefaults.questionText, + answer: answerFormat) + step.text = exampleDetailText + return ORKOrderedTask(identifier: String(describing: Identifier.timeIntervalQuestionTask), steps: [step]) + } + + /// This task demonstrates a question asking for a time of day. + private var timeOfDayQuestionTask: ORKTask { + /* + Because we don't specify a default, the picker will default to the + time the step is presented. For questions like "What time do you have + breakfast?", it would make sense to set the default on the answer + format. + */ + let answerFormat = ORKAnswerFormat.timeOfDayAnswerFormat() + let questionStep = ORKQuestionStep( + identifier: String(describing: Identifier.timeOfDayQuestionStep), + title: NSLocalizedString("Time", comment: ""), + question: ModuleAppYmlReader().surverysTaskModel?.questionText ?? Constants.YamlDefaults.questionText, + answer: answerFormat) + questionStep.text = exampleDetailText + return ORKOrderedTask( + identifier: String(describing: Identifier.timeOfDayQuestionTask), + steps: [questionStep]) + } +} + +// MARK: - Image and video questions +extension TaskListRow { + /** + This task demonstrates a survey question involving picking from a series of + image choices. A more realistic applciation of this type of question might be to + use a range of icons for faces ranging from happy to sad. + */ + private var imageChoiceQuestionTask: ORKTask { + let roundShapeImage = UIImage(named: "round_shape")! + let roundShapeText = NSLocalizedString("Round Shape", comment: "") + let squareShapeImage = UIImage(named: "square_shape")! + let squareShapeText = NSLocalizedString("Square Shape", comment: "") + let imageChoces = [ + ORKImageChoice(normalImage: roundShapeImage, selectedImage: nil, text: roundShapeText, value: roundShapeText as NSCoding & NSCopying & NSObjectProtocol), + ORKImageChoice(normalImage: squareShapeImage, selectedImage: nil, text: squareShapeText, value: squareShapeText as NSCoding & NSCopying & NSObjectProtocol) + ] + let answerFormat1 = ORKAnswerFormat.choiceAnswerFormat(with: imageChoces) + let questionStep1 = ORKQuestionStep( + identifier: String(describing: Identifier.imageChoiceQuestionStep1), + title: NSLocalizedString("Image Choice", comment: ""), + question: ModuleAppYmlReader().surverysTaskModel?.questionText ?? Constants.YamlDefaults.questionText, + answer: answerFormat1) + questionStep1.text = exampleDetailText + let answerFormat2 = ORKAnswerFormat.choiceAnswerFormat(with: imageChoces, style: .singleChoice, vertical: true) + let questionStep2 = ORKQuestionStep( + identifier: String(describing: Identifier.imageChoiceQuestionStep2), + title: NSLocalizedString("Image Choice", comment: ""), + question: ModuleAppYmlReader().surverysTaskModel?.questionText ?? Constants.YamlDefaults.questionText, + answer: answerFormat2) + questionStep2.text = exampleDetailText + return ORKOrderedTask( + identifier: String(describing: Identifier.imageChoiceQuestionTask), + steps: [questionStep1, questionStep2]) + } + + /// This task presents the image capture step in an ordered task. + private var imageCaptureTask: ORKTask { + // Create the intro step. + let instructionStep = ORKInstructionStep(identifier: String(describing: Identifier.introStep)) + instructionStep.title = NSLocalizedString("Image Capture Survey", comment: "") + instructionStep.text = exampleDescription + let handSolidImage = UIImage(named: "hand_solid")! + instructionStep.image = handSolidImage.withRenderingMode(.alwaysTemplate) + let imageCaptureStep = ORKImageCaptureStep(identifier: String(describing: Identifier.imageCaptureStep)) + imageCaptureStep.title = NSLocalizedString("Image Capture", comment: "") + imageCaptureStep.isOptional = false + imageCaptureStep.accessibilityInstructions = NSLocalizedString("Your instructions for capturing the image", comment: "") + imageCaptureStep.accessibilityHint = NSLocalizedString("Captures the image visible in the preview", comment: "") + imageCaptureStep.templateImage = UIImage(named: "hand_outline_big")! + imageCaptureStep.templateImageInsets = UIEdgeInsets(top: 0.05, left: 0.05, bottom: 0.05, right: 0.05) + return ORKOrderedTask(identifier: String(describing: Identifier.imageCaptureTask), steps: [ + instructionStep, + imageCaptureStep + ]) + } + + /// This task presents the video capture step in an ordered task. + private var videoCaptureTask: ORKTask { + // Create the intro step. + let instructionStep = ORKInstructionStep(identifier: String(describing: Identifier.introStep)) + instructionStep.title = NSLocalizedString("Video Capture Survey", comment: "") + instructionStep.text = exampleDescription + let handSolidImage = UIImage(named: "hand_solid")! + instructionStep.image = handSolidImage.withRenderingMode(.alwaysTemplate) + let videoCaptureStep = ORKVideoCaptureStep(identifier: String(describing: Identifier.videoCaptureStep)) + videoCaptureStep.title = NSLocalizedString("Video Capture", comment: "") + videoCaptureStep.accessibilityInstructions = NSLocalizedString("Your instructions for capturing the video", comment: "") + videoCaptureStep.accessibilityHint = NSLocalizedString("Captures the video visible in the preview", comment: "") + videoCaptureStep.templateImage = UIImage(named: "hand_outline_big")! + videoCaptureStep.templateImageInsets = UIEdgeInsets(top: 0.05, left: 0.05, bottom: 0.05, right: 0.05) + videoCaptureStep.duration = 30.0 // 30 seconds + return ORKOrderedTask(identifier: String(describing: Identifier.videoCaptureTask), steps: [ + instructionStep, + videoCaptureStep + ]) + } +} + +// MARK: - Grouped form +extension TaskListRow { + private var groupedFormTask: ORKTask { + let step = ORKFormStep( + identifier: String(describing: Identifier.groupedFormStep), + title: NSLocalizedString(ModuleAppYmlReader().surverysTaskModel?.groupServeyTitle ?? "Form Step", comment: ""), + text: ModuleAppYmlReader().surverysTaskModel?.questionText ?? Constants.YamlDefaults.YourQuestion) + // Start of first section + let learnMoreInstructionStep01 = ORKLearnMoreInstructionStep(identifier: "LearnMoreInstructionStep01") + learnMoreInstructionStep01.title = NSLocalizedString(ModuleAppYmlReader().surverysTaskModel?.learnMoreTitle ?? Constants.YamlDefaults.learnMoreTitle, comment: "") + learnMoreInstructionStep01.text = NSLocalizedString(ModuleAppYmlReader().surverysTaskModel?.learnMoreText ?? Constants.YamlDefaults.learnMoreText, comment: "") + let learnMoreItem01 = ORKLearnMoreItem(text: nil, learnMoreInstructionStep: learnMoreInstructionStep01) + let section01 = ORKFormItem( + sectionTitle: NSLocalizedString(ModuleAppYmlReader().surverysTaskModel?.sectionTitle ?? "Section title", comment: ""), + detailText: NSLocalizedString(ModuleAppYmlReader().surverysTaskModel?.sectionDetailText ?? "Section detail text", comment: ""), + learnMoreItem: learnMoreItem01, showsProgress: true) + // A first field, for entering an integer. + let formItem01Text = NSLocalizedString("Field01", comment: "") + let formItem01 = ORKFormItem(identifier: String(describing: Identifier.formItem01), text: formItem01Text, answerFormat: ORKAnswerFormat.integerAnswerFormat(withUnit: nil)) + formItem01.placeholder = NSLocalizedString(ModuleAppYmlReader().surverysTaskModel?.placeholder ?? Constants.YamlDefaults.placeholder, comment: "") + // A second field, for entering a time interval. + let formItem02Text = NSLocalizedString("Field02", comment: "") + let formItem02 = ORKFormItem(identifier: String(describing: Identifier.formItem02), text: formItem02Text, answerFormat: ORKTimeIntervalAnswerFormat()) + formItem02.placeholder = NSLocalizedString(ModuleAppYmlReader().surverysTaskModel?.placeholder ?? Constants.YamlDefaults.placeholder, comment: "") + let sesAnswerFormat = ORKSESAnswerFormat( + topRungText: "Best Off", + bottomRungText: "Worst Off") + let sesFormItem = ORKFormItem( + identifier: "sesIdentifier", text: ModuleAppYmlReader().surverysTaskModel?.socioeconomicLadder ?? "Select where you are on the socioeconomic ladder.", + answerFormat: sesAnswerFormat) + // Start of section for scale question + let formItem03Text = NSLocalizedString(ModuleAppYmlReader().surverysTaskModel?.questionText ?? Constants.YamlDefaults.questionText, comment: "") + let scaleAnswerFormat = ORKContinuousScaleAnswerFormat( + maximumValue: 10, + minimumValue: 0, + defaultValue: 0.0, + maximumFractionDigits: 1)// ORKScaleAnswerFormat(maximumValue: 10, minimumValue: 0, defaultValue: 0, step: 1) + let formItem03 = ORKFormItem( + identifier: String(describing: Identifier.formItem03), + text: formItem03Text, + detailText: nil, + learnMoreItem: nil, + showsProgress: true, + answerFormat: scaleAnswerFormat, + tagText: nil, + optional: true) + step.formItems = [ section01, formItem01, formItem02, formItem03, sesFormItem ] + // Add a question step. + let question1StepAnswerFormat = ORKBooleanAnswerFormat() + let question1 = NSLocalizedString(ModuleAppYmlReader().surverysTaskModel?.newsletter ?? Constants.YamlDefaults.newsletter, comment: "") + let learnMoreInstructionStep = ORKLearnMoreInstructionStep(identifier: "LearnMoreInstructionStep01") + learnMoreInstructionStep.title = NSLocalizedString(ModuleAppYmlReader().surverysTaskModel?.learnMoreTitle ?? Constants.YamlDefaults.learnMoreTitle, comment: "") + learnMoreInstructionStep.text = NSLocalizedString(ModuleAppYmlReader().surverysTaskModel?.learnMoreText ?? Constants.YamlDefaults.learnMoreText, comment: "") + let learnMoreItem = ORKLearnMoreItem(text: nil, learnMoreInstructionStep: learnMoreInstructionStep) + let question1Step = ORKQuestionStep( + identifier: String(describing: Identifier.questionStep), + title: ModuleAppYmlReader().surverysTaskModel?.questionnaire ?? Constants.YamlDefaults.Questionnaire, + question: question1, + answer: question1StepAnswerFormat, + learnMoreItem: learnMoreItem) + question1Step.text = exampleDetailText + // Add a question step with different layout format. + let question2StepAnswerFormat = ORKAnswerFormat.dateAnswerFormat(withDefaultDate: nil, minimumDate: nil, maximumDate: Date(), calendar: nil) + let question2 = NSLocalizedString(ModuleAppYmlReader().surverysTaskModel?.birthdayText ?? Constants.YamlDefaults.birthdayText, comment: "") + let question2Step = ORKQuestionStep( + identifier: String(describing: Identifier.birthdayQuestion), + title: ModuleAppYmlReader().surverysTaskModel?.questionnaire ?? Constants.YamlDefaults.Questionnaire, + question: question2, + answer: question2StepAnswerFormat) + question2Step.text = exampleDetailText + let appleChoices: [ORKTextChoice] = [ + ORKTextChoice(text: "Granny Smith", value: 1 as NSCoding & NSCopying & NSObjectProtocol), + ORKTextChoice(text: "Honeycrisp", value: 2 as NSCoding & NSCopying & NSObjectProtocol), + ORKTextChoice(text: "Fuji", value: 3 as NSCoding & NSCopying & NSObjectProtocol), + ORKTextChoice(text: "McIntosh", value: 10 as NSCoding & NSCopying & NSObjectProtocol), + ORKTextChoice(text: "Kanzi", value: 5 as NSCoding & NSCopying & NSObjectProtocol)] + let appleAnswerFormat = ORKTextChoiceAnswerFormat(style: .singleChoice, textChoices: appleChoices) + let appleFormItem = ORKFormItem( + identifier: "appleFormItemIdentifier", + text: ModuleAppYmlReader().surverysTaskModel?.itemQuestion ?? Constants.YamlDefaults.favorite, + answerFormat: appleAnswerFormat) + let appleFormStep = ORKFormStep(identifier: "appleFormStepIdentifier", title: "Fruit!", text: "Select the fruit you like.") + appleFormStep.formItems = [appleFormItem] + return ORKOrderedTask(identifier: String(describing: Identifier.groupedFormTask), steps: [step, question1Step, question2Step, appleFormStep]) + } +} + +// MARK: - Consent task +extension TaskListRow { + // MARK: Consent Document Creation Convenience + /** + A consent document provides the content for the visual consent and consent + review steps. This helper sets up a consent document with some dummy + content. You should populate your consent document to suit your study. + */ + private var consentDocument: ORKConsentDocument { + let consentDocument = ORKConsentDocument() + + /* + This is the title of the document, displayed both for review and in + the generated PDF. + */ + consentDocument.title = NSLocalizedString("Example Consent", comment: "") + // This is the title of the signature page in the generated document. + consentDocument.signaturePageTitle = NSLocalizedString("Consent", comment: "") + /* + This is the line shown on the signature page of the generated document, + just above the signatures. + */ + consentDocument.signaturePageContent = NSLocalizedString("I agree to participate in this research study.", comment: "") + /* + Add the participant signature, which will be filled in during the + consent review process. This signature initially does not have a + signature image or a participant name; these are collected during + the consent review step. + */ + let participantSignatureTitle = NSLocalizedString("Participant", comment: "") + let participantSignature = ORKConsentSignature( + forPersonWithTitle: participantSignatureTitle, + dateFormatString: nil, + identifier: String(describing: Identifier.consentDocumentParticipantSignature)) + consentDocument.addSignature(participantSignature) + /* + Add the investigator signature. This is pre-populated with the + investigator's signature image and name, and the date of their + signature. If you need to specify the date as now, you could generate + a date string with code here. + This signature is only used for the generated PDF. + */ + let signatureImage = UIImage(named: "signature")! + let investigatorSignatureTitle = NSLocalizedString("Investigator", comment: "") + let investigatorSignatureGivenName = NSLocalizedString("Jonny", comment: "") + let investigatorSignatureFamilyName = NSLocalizedString("Appleseed", comment: "") + let investigatorSignatureDateString = "3/10/15" + let investigatorSignature = ORKConsentSignature( + forPersonWithTitle: investigatorSignatureTitle, + dateFormatString: nil, + identifier: String(describing: Identifier.consentDocumentInvestigatorSignature), + givenName: investigatorSignatureGivenName, + familyName: investigatorSignatureFamilyName, + signatureImage: signatureImage, + dateString: investigatorSignatureDateString) + consentDocument.addSignature(investigatorSignature) + /* + This is the HTML content for the "Learn More" page for each consent + section. In a real consent, this would be your content, and you would + have different content for each section. + + If your content is just text, you can use the `content` property + instead of the `htmlContent` property of `ORKConsentSection`. + */ + let htmlContentString = "
  • Lorem
  • ipsum
  • dolor

\(loremIpsumLongText)

\(loremIpsumMediumText)

" + /* + These are all the consent section types that have pre-defined animations + and images. We use them in this specific order, so we see the available + animated transitions. + */ + let consentSectionTypes: [ORKConsentSectionType] = [ + .overview, + .dataGathering, + .privacy, + .dataUse, + .timeCommitment, + .studySurvey, + .studyTasks, + .withdrawing + ] + /* + For each consent section type in `consentSectionTypes`, create an + `ORKConsentSection` that represents it. + + In a real app, you would set specific content for each section. + */ + var consentSections: [ORKConsentSection] = consentSectionTypes.map { contentSectionType in + let consentSection = ORKConsentSection(type: contentSectionType) + consentSection.summary = loremIpsumShortText + if contentSectionType == .overview { + consentSection.htmlContent = htmlContentString + } else { + consentSection.content = loremIpsumLongText + } + return consentSection + } + /* + This is an example of a section that is only in the review document + or only in the generated PDF, and is not displayed in `ORKVisualConsentStep`. + */ + let consentSection = ORKConsentSection(type: .onlyInDocument) + consentSection.summary = NSLocalizedString(".OnlyInDocument Scene Summary", comment: "") + consentSection.title = NSLocalizedString(".OnlyInDocument Scene", comment: "") + consentSection.content = loremIpsumLongText + consentSections += [consentSection] + // Set the sections on the document after they've been created. consentDocument.sections = consentSections - return consentDocument } - +} + +// MARK: - String +extension TaskListRow { // MARK: `ORKTask` Reused Text Convenience - private var exampleDescription: String { return NSLocalizedString("Your description goes here.", comment: "") } @@ -2021,17 +1470,20 @@ enum TaskListRow: Int, CustomStringConvertible { } private var loremIpsumMediumText: String { - return "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nam adhuc, meo fortasse vitio, quid ego quaeram non perspicis. Plane idem, inquit, et maxima quidem, qua fieri nulla maior potest. Quonam, inquit, modo?" + return "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nam adhuc, meo fortasse vitio, quid ego quaeram non perspicis." } private var loremIpsumLongText: String { - return "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nam adhuc, meo fortasse vitio, quid ego quaeram non perspicis. Plane idem, inquit, et maxima quidem, qua fieri nulla maior potest. Quonam, inquit, modo? An potest, inquit ille, quicquam esse suavius quam nihil dolere? Cave putes quicquam esse verius. Quonam, inquit, modo?" + return """ + Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nam adhuc, meo fortasse vitio, quid ego quaeram non perspicis. + Plane idem, inquit, et maxima quidem, qua fieri nulla maior potest. Quonam, inquit, modo? An potest, inquit ille, + quicquam esse suavius quam nihil dolere? Cave putes quicquam esse verius. Quonam, inquit, modo? + """ } private var exampleHtml: String { return """ - diff --git a/OTFMagicBox/StaticViews/RK UI/TaskListViewController.swift b/OTFMagicBox/StaticViews/RK UI/TaskListViewController.swift index 1727c538..0e275fea 100755 --- a/OTFMagicBox/StaticViews/RK UI/TaskListViewController.swift +++ b/OTFMagicBox/StaticViews/RK UI/TaskListViewController.swift @@ -4,19 +4,19 @@ Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: - + 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. - + 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. - + 3. Neither the name of the copyright holder(s) nor the names of any contributors may be used to endorse or promote products derived from this software without specific prior written permission. No license is granted to the trademarks of the copyright holders even if such marks are included in this software. - + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE @@ -27,20 +27,20 @@ CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ + */ import UIKit import OTFResearchKit /** - This example displays a catalog of tasks, each consisting of one or two steps, - built using the ResearchKit framework. The `TaskListViewController` displays the - available tasks in this catalog. - - When you tap a task, it is presented like a participant in a study might - see it. After completing the task, you can see the results generated by - the task by switching to the results tab. -*/ + This example displays a catalog of tasks, each consisting of one or two steps, + built using the ResearchKit framework. The `TaskListViewController` displays the + available tasks in this catalog. + + When you tap a task, it is presented like a participant in a study might + see it. After completing the task, you can see the results generated by + the task by switching to the results tab. + */ class TaskListViewController: UITableViewController, ORKTaskViewControllerDelegate { var waitStepViewController: ORKWaitStepViewController? @@ -48,79 +48,79 @@ class TaskListViewController: UITableViewController, ORKTaskViewControllerDelega var waitStepProgress: CGFloat = 0.0 // MARK: Types - + enum TableViewCellIdentifier: String { case `default` = "Default" } - + // MARK: Properties - + /** - When a task is completed, the `TaskListViewController` calls this closure - with the created task. - */ + When a task is completed, the `TaskListViewController` calls this closure + with the created task. + */ var taskResultFinishedCompletionHandler: ((ORKResult) -> Void)? - + // MARK: View Life Cycle - + override func viewDidLoad() { super.viewDidLoad() - + if #available(iOS 13.0, *) { self.tableView.backgroundColor = UIColor.systemGroupedBackground } - + tableView.register(UITableViewCell.self, forCellReuseIdentifier: TableViewCellIdentifier.default.rawValue) } - + // MARK: UITableViewDataSource override func numberOfSections(in tableView: UITableView) -> Int { return TaskListRow.sections.count } - + override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { return TaskListRow.sections[section].rows.count } - + override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? { return TaskListRow.sections[section].title } - + override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let cell = tableView.dequeueReusableCell(withIdentifier: TableViewCellIdentifier.default.rawValue, for: indexPath) - + let taskListRow = TaskListRow.sections[(indexPath as NSIndexPath).section].rows[(indexPath as NSIndexPath).row] - + cell.textLabel!.text = "\(taskListRow)" - + if #available(iOS 13.0, *) { cell.textLabel?.textColor = UIColor.label } - + return cell } - + // MARK: UITableViewDelegate - + override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { tableView.deselectRow(at: indexPath, animated: true) - + // Present the task view controller that the user asked for. let taskListRow = TaskListRow.sections[(indexPath as NSIndexPath).section].rows[(indexPath as NSIndexPath).row] - + // Create a task from the `TaskListRow` to present in the `ORKTaskViewController`. let task = taskListRow.representedTask - + /* - Passing `nil` for the `taskRunUUID` lets the task view controller - generate an identifier for this run of the task. - */ + Passing `nil` for the `taskRunUUID` lets the task view controller + generate an identifier for this run of the task. + */ let taskViewController = ORKTaskViewController(task: task, taskRun: nil) // Make sure we receive events from `taskViewController`. taskViewController.delegate = self - + // Assign a directory to store `taskViewController` output. taskViewController.outputDirectory = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first! @@ -131,23 +131,23 @@ class TaskListViewController: UITableViewController, ORKTaskViewControllerDelega */ present(taskViewController, animated: true, completion: nil) } - + // MARK: ORKTaskViewControllerDelegate - + func taskViewController(_ taskViewController: ORKTaskViewController, didFinishWith reason: ORKTaskViewControllerFinishReason, error: Error?) { /* - The `reason` passed to this method indicates why the task view - controller finished: Did the user cancel, save, or actually complete - the task; or was there an error? + The `reason` passed to this method indicates why the task view + controller finished: Did the user cancel, save, or actually complete + the task; or was there an error? - The actual result of the task is on the `result` property of the task - view controller. - */ + The actual result of the task is on the `result` property of the task + view controller. + */ taskResultFinishedCompletionHandler?(taskViewController.result) taskViewController.dismiss(animated: true, completion: nil) } - + func taskViewController(_ taskViewController: ORKTaskViewController, stepViewControllerWillAppear stepViewController: ORKStepViewController) { // Example data processing for the wait step. if stepViewController.step?.identifier == "WaitStepIndeterminate" || @@ -169,20 +169,19 @@ class TaskListViewController: UITableViewController, ORKTaskViewControllerDelega }) } } - + func taskViewController(_ taskViewController: ORKTaskViewController, learnMoreButtonPressedWith learnMoreStep: ORKLearnMoreInstructionStep, for stepViewController: ORKStepViewController) { - // FIXME: Temporary fix. This method should not be called if it is only used to present the learnMoreStepViewController, the stepViewController should present the learnMoreStepViewController. stepViewController.present(UINavigationController(rootViewController: ORKLearnMoreStepViewController(step: learnMoreStep)), animated: true) { - + } } - + func delay(_ delay: Double, closure: @escaping () -> Void ) { let delayTime = DispatchTime.now() + delay let dispatchWorkItem = DispatchWorkItem(block: closure) DispatchQueue.main.asyncAfter(deadline: delayTime, execute: dispatchWorkItem) } - + @objc func updateProgressOfWaitStepViewController() { if let waitStepViewController = waitStepViewController { diff --git a/OTFMagicBox/StaticViews/RK UI/TaskViewControllerRepresentable.swift b/OTFMagicBox/StaticViews/RK UI/TaskViewControllerRepresentable.swift index 986c60ff..dcb33399 100644 --- a/OTFMagicBox/StaticViews/RK UI/TaskViewControllerRepresentable.swift +++ b/OTFMagicBox/StaticViews/RK UI/TaskViewControllerRepresentable.swift @@ -11,57 +11,55 @@ import OTFResearchKit struct TaskViewControllerRepresentable: UIViewControllerRepresentable { let task: ORKTask - + var waitStepViewController: ORKWaitStepViewController? var waitStepUpdateTimer: Timer? var waitStepProgress: CGFloat = 0.0 - + var taskResultFinishedCompletionHandler: ((ORKResult) -> Void)? - + @Environment(\.presentationMode) var presentationMode - + typealias UIViewControllerType = ORKTaskViewController - + func makeUIViewController(context: Context) -> ORKTaskViewController { let taskViewController = ORKTaskViewController(task: task, taskRun: nil) - + taskViewController.delegate = context.coordinator taskViewController.outputDirectory = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first! - + return taskViewController } - + func updateUIViewController(_ uiViewController: ORKTaskViewController, context: Context) { - + } - + func makeCoordinator() -> Coordinator { Coordinator(self) } - - class Coordinator: NSObject, ORKTaskViewControllerDelegate { var parent: TaskViewControllerRepresentable - + init(_ parent: TaskViewControllerRepresentable) { self.parent = parent } - + func taskViewController(_ taskViewController: ORKTaskViewController, didFinishWith reason: ORKTaskViewControllerFinishReason, error: Error?) { /* - The `reason` passed to this method indicates why the task view - controller finished: Did the user cancel, save, or actually complete - the task; or was there an error? + The `reason` passed to this method indicates why the task view + controller finished: Did the user cancel, save, or actually complete + the task; or was there an error? - The actual result of the task is on the `result` property of the task - view controller. - */ + The actual result of the task is on the `result` property of the task + view controller. + */ parent.taskResultFinishedCompletionHandler?(taskViewController.result) - //taskViewController.dismiss(animated: true, completion: nil) + // taskViewController.dismiss(animated: true, completion: nil) parent.presentationMode.wrappedValue.dismiss() } - + func taskViewController(_ taskViewController: ORKTaskViewController, stepViewControllerWillAppear stepViewController: ORKStepViewController) { // Example data processing for the wait step. if stepViewController.step?.identifier == "WaitStepIndeterminate" || @@ -77,27 +75,29 @@ struct TaskViewControllerRepresentable: UIViewControllerRepresentable { if let stepViewController = stepViewController as? ORKWaitStepViewController { self.parent.waitStepViewController = stepViewController self.parent.waitStepProgress = 0.0 - self.parent.waitStepUpdateTimer = Timer(timeInterval: 0.1, target: self, selector: #selector(TaskListViewController.updateProgressOfWaitStepViewController), userInfo: nil, repeats: true) + self.parent.waitStepUpdateTimer = Timer( + timeInterval: 0.1, + target: self, + selector: #selector(TaskListViewController.updateProgressOfWaitStepViewController), + userInfo: nil, + repeats: true) RunLoop.main.add(self.parent.waitStepUpdateTimer!, forMode: RunLoop.Mode.common) } }) } } - + func taskViewController(_ taskViewController: ORKTaskViewController, learnMoreButtonPressedWith learnMoreStep: ORKLearnMoreInstructionStep, for stepViewController: ORKStepViewController) { - // FIXME: Temporary fix. This method should not be called if it is only used to present the learnMoreStepViewController, the stepViewController should present the learnMoreStepViewController. stepViewController.present(UINavigationController(rootViewController: ORKLearnMoreStepViewController(step: learnMoreStep)), animated: true) { - + } } - - func delay(_ delay: Double, closure: @escaping () -> Void ) { let delayTime = DispatchTime.now() + delay let dispatchWorkItem = DispatchWorkItem(block: closure) DispatchQueue.main.asyncAfter(deadline: delayTime, execute: dispatchWorkItem) } - + @objc func updateProgressOfWaitStepViewController() { if let waitStepViewController = parent.waitStepViewController { diff --git a/OTFMagicBox/StaticViews/StaticUI.swift b/OTFMagicBox/StaticViews/StaticUI.swift index dc10e8ef..b92a17a6 100644 --- a/OTFMagicBox/StaticViews/StaticUI.swift +++ b/OTFMagicBox/StaticViews/StaticUI.swift @@ -1,25 +1,25 @@ /* Copyright (c) 2021, Hippocrates Technologies S.r.l.. All rights reserved. - + Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: - + 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. - + 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. - + 3. Neither the name of the copyright holder(s) nor the names of any contributor(s) may be used to endorse or promote products derived from this software without specific prior written permission. No license is granted to the trademarks of the copyright holders even if such marks are included in this software. - + 4. Commercial redistribution in any form requires an explicit license agreement with the copyright holder(s). Please contact support@hippocratestech.com for further information regarding licensing. - + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. @@ -40,15 +40,15 @@ struct CareKitViews: View { VStack { Text("ResearchKit Views") .foregroundColor(.otfTextColor) - .font(YmlReader().appTheme?.screenTitleFont.appFont ?? Font.system(size: 17.0)) - .fontWeight(YmlReader().appTheme?.headerTitleWeight.fontWeight) + .font(Font.otfscreenTitleFont) + .fontWeight(Font.otfheaderTitleWeight) List { - ContactsSection(cellbackgroundColor: YmlReader().appTheme?.cellbackgroundColor.color ?? .black, headerColor: YmlReader().appTheme?.headerColor.color ?? .black, textColor: YmlReader().appTheme?.textColor.color ?? .black) - TaskSection(cellbackgroundColor: YmlReader().appTheme?.cellbackgroundColor.color ?? .black, headerColor: YmlReader().appTheme?.headerColor.color ?? .black, textColor: YmlReader().appTheme?.textColor.color ?? .black) + ContactsSection() + TaskSection() } .listStyle(GroupedListStyle()) } - + } } @@ -57,62 +57,62 @@ struct ResearchKitViews: View { VStack { Text("ResearchKit Views") .foregroundColor(.otfTextColor) - .font(YmlReader().appTheme?.screenTitleFont.appFont ?? Font.system(size: 17.0)) - .fontWeight(YmlReader().appTheme?.screenTitleFont.fontWeight) + .font(Font.otfscreenTitleFont) + .fontWeight(Font.otfscreenTitleFontWeight) List { - SurveysList(cellbackgroundColor: YmlReader().appTheme?.cellbackgroundColor.color ?? .black, headerColor: YmlReader().appTheme?.headerColor.color ?? .black, textColor: YmlReader().appTheme?.textColor.color ?? .black) - SurveyQuestionsList(cellbackgroundColor: YmlReader().appTheme?.cellbackgroundColor.color ?? .black, headerColor: YmlReader().appTheme?.headerColor.color ?? .black, textColor: YmlReader().appTheme?.textColor.color ?? .black) - OnboardingList(cellbackgroundColor: YmlReader().appTheme?.cellbackgroundColor.color ?? .black, headerColor: YmlReader().appTheme?.headerColor.color ?? .black, textColor: YmlReader().appTheme?.textColor.color ?? .black) - ActiveTasksList(cellbackgroundColor: YmlReader().appTheme?.cellbackgroundColor.color ?? .black, headerColor: YmlReader().appTheme?.headerColor.color ?? .black, textColor: YmlReader().appTheme?.textColor.color ?? .black) - MiscellaneousList(cellbackgroundColor: YmlReader().appTheme?.cellbackgroundColor.color ?? .black, headerColor: YmlReader().appTheme?.headerColor.color ?? .black, textColor: YmlReader().appTheme?.textColor.color ?? .black) + SurveysList() + SurveyQuestionsList() + OnboardingList() + ActiveTasksList() + MiscellaneousList() } .listStyle(GroupedListStyle()) } - + } } struct StaticUI: View { - + @State private var isPresenting = false - + init() { - UITableView.appearance().separatorColor = YmlReader().appTheme?.separatorColor.color - UITableView.appearance().backgroundColor = YmlReader().appTheme?.backgroundColor.color + UITableView.appearance().separatorColor = YmlReader().appStyle.separatorColor.color + UITableView.appearance().backgroundColor = YmlReader().appStyle.backgroundColor.color } var body: some View { NavigationView { VStack { Text("Sample Views") .foregroundColor(.otfTextColor) - .font(YmlReader().appTheme?.screenTitleFont.appFont ?? Font.system(size: 17.0)) - .fontWeight(YmlReader().appTheme?.headerTitleWeight.fontWeight) + .font(Font.otfscreenTitleFont) + .fontWeight(Font.otfheaderTitleWeight) List { - + NavigationLink(destination: CareKitViews()) { Text(ModuleAppYmlReader().careKitModel?.careKit ?? "CareKit") - .fontWeight(YmlReader().appTheme?.textWeight.fontWeight) + .fontWeight(Font.otfFontWeight) } .foregroundColor(.otfTextColor) .listRowBackground(Color.otfCellBackground) - + NavigationLink(destination: ResearchKitViews()) { Text(ModuleAppYmlReader().researchKitModel?.researchKit ?? "ResearchKit") - .fontWeight(YmlReader().appTheme?.textWeight.fontWeight) + .fontWeight(Font.otfFontWeight) } .foregroundColor(.otfTextColor) .listRowBackground(Color.otfCellBackground) } .listStyle(GroupedListStyle()) - .onReceive(NotificationCenter.default.publisher(for: .deleteUserAccount)) { notification in + .onReceive(NotificationCenter.default.publisher(for: .deleteUserAccount)) { _ in isPresenting = true } .alert(isPresented: $isPresenting) { - + Alert( title: Text(Constants.CustomiseStrings.accountDeleted) - .font(YmlReader().appTheme?.textFont.appFont ?? Font.system(size: 17.0)) - .fontWeight(YmlReader().appTheme?.textWeight.fontWeight), + .font(Font.otfAppFont) + .fontWeight(Font.otfFontWeight), message: Text(Constants.deleteAccount), dismissButton: .default(Text(Constants.CustomiseStrings.okay), action: { OTFTheraforgeNetwork.shared.moveToOnboardingView() @@ -125,7 +125,7 @@ struct StaticUI: View { .background(Color.otfCellBackground) } } - .font(YmlReader().appTheme?.textFont.appFont ?? Font.system(size: 17.0)) + .font(Font.otfAppFont) } } diff --git a/OTFMagicBox/Styling/OTFStyle.swift b/OTFMagicBox/Styling/OTFStyle.swift new file mode 100644 index 00000000..ed30f907 --- /dev/null +++ b/OTFMagicBox/Styling/OTFStyle.swift @@ -0,0 +1,29 @@ +// +// OTFStyle.swift +// OTFMagicBox +// +// Created by Tomas Martins on 31/01/24. +// + +import Foundation +import SwiftUI +import OTFDesignSystem +import OTFCareKitUI + +class OTFStyle: OCKStyler { + let otfDesignStyle: OTFDesignStyler + let color: OCKColorStyler + + init(from otfDesignStyle: OTFDesignStyler) { + self.otfDesignStyle = otfDesignStyle + self.color = OTFThemeColorStyler(from: otfDesignStyle.color) + } +} + +public extension View { + internal func appStyle(_ style: OTFStyle) -> some View { + return self + .environment(\.otfdsStyle, style.otfDesignStyle) + .environment(\.careKitStyle, style) + } +} diff --git a/OTFMagicBox/Styling/OTFStyleColorStyler.swift b/OTFMagicBox/Styling/OTFStyleColorStyler.swift new file mode 100644 index 00000000..8abdacb0 --- /dev/null +++ b/OTFMagicBox/Styling/OTFStyleColorStyler.swift @@ -0,0 +1,107 @@ +// +// OTFStyleColorStyler.swift +// OTFMagicBox +// +// Created by Tomas Martins on 06/02/24. +// + +import Foundation +import OTFDesignSystem +import OTFCareKitUI + +class OTFThemeColorStyler: OCKColorStyler { + let otfDesignColor: OTFDesignStylerColor + + init(from otfDesignColor: OTFDesignStylerColor) { + self.otfDesignColor = otfDesignColor + } + + var label: UIColor { + return UIColor(otfDesignColor.label) + } + + var secondaryLabel: UIColor { + return UIColor(otfDesignColor.secondaryLabel) + } + + var tertiaryLabel: UIColor { + return UIColor(otfDesignColor.tertiaryLabel) + } + + var separator: UIColor { + return UIColor(otfDesignColor.separator) + } + + var customFill: UIColor { + return UIColor(otfDesignColor.customFill) + } + + var secondaryCustomFill: UIColor { + return UIColor(otfDesignColor.secondaryCustomFill) + } + + var tertiaryCustomFill: UIColor { + return UIColor(otfDesignColor.tertiaryCustomFill) + } + + var quaternaryCustomFill: UIColor { + return UIColor(otfDesignColor.quaternaryCustomFill) + } + + var customBlue: UIColor { + return UIColor(otfDesignColor.customBlue) + } + + var customGray: UIColor { + return UIColor(otfDesignColor.customGray) + } + + var customGray2: UIColor { + return UIColor(otfDesignColor.customGray2) + } + + var customGray3: UIColor { + return UIColor(otfDesignColor.customGray3) + } + + var customGray4: UIColor { + return UIColor(otfDesignColor.customGray4) + } + + var customGray5: UIColor { + return UIColor(otfDesignColor.customGray5) + } + + var white: UIColor { + return UIColor.white + } + + var black: UIColor { + return UIColor.black + } + + var clear: UIColor { + return UIColor.clear + } + + var customBackground: UIColor { + return UIColor(otfDesignColor.customBackground) + } + + var secondaryCustomBackground: UIColor { + return UIColor(otfDesignColor.secondaryCustomBackground) + } + + var customGroupedBackground: UIColor { + return UIColor(otfDesignColor.customGroupedBackground) + } + + var secondaryCustomGroupedBackground: UIColor { + return UIColor(otfDesignColor.secondaryCustomGroupedBackground) + } + + var tertiaryCustomGroupedBackground: UIColor { + return UIColor(otfDesignColor.tertiaryCustomGroupedBackground) + } +} + diff --git a/OTFMagicBox/Styling/OTFYamlStyle.swift b/OTFMagicBox/Styling/OTFYamlStyle.swift new file mode 100644 index 00000000..12411258 --- /dev/null +++ b/OTFMagicBox/Styling/OTFYamlStyle.swift @@ -0,0 +1,75 @@ +// +// OTFYamlStyle.swift +// OTFMagicBox +// +// Created by Tomas Martins on 06/02/24. +// + +import SwiftUI +import OTFDesignSystem +import OTFCareKitUI + +class OTFYamlStyle: OTFDesignStyler { + var color: OTFDesignStylerColor + + init(style: ThemeCustomization) { + self.color = StylerColor(style: style) + } +} + +extension OTFYamlStyle { + class StylerColor: OTFDesignStylerColor { + var label: Color + var secondaryLabel: Color + var separator: Color + var customFill: Color + var customGray: Color + var primaryButton: Color + var customBackground: Color + + init(style: ThemeCustomization) { + if let backgroundColor = style.backgroundColor.color { + self.customBackground = Color(backgroundColor) + } else { + self.customBackground = .systemBackground + } + + if let textColor = style.textColor.color { + self.label = Color(textColor) + } else { + self.label = Color(UIColor.label) + } + + if let separatorColor = style.separatorColor.color { + self.separator = Color(separatorColor) + } else { + self.separator = Color.separator + } + + if let cellBackgroundColor = style.cellbackgroundColor.color { + self.customFill = Color(cellBackgroundColor) + } else { + self.customFill = Color.tertiarySystemFill + } + + if let buttonTextColor = style.buttonTextColor.color { + self.primaryButton = Color(buttonTextColor) + } else { + self.primaryButton = Color.systemBlue + } + + if let borderColor = style.borderColor.color { + self.customGray = Color(borderColor) + } else { + self.customGray = Color(UIColor.systemGray) + } + + if let headerColor = style.headerColor.color { + self.secondaryLabel = Color(headerColor) + } else { + self.secondaryLabel = Color(UIColor.secondaryLabel) + } + } + + } +} diff --git a/OTFMagicBox/Tasks/TaskItem.swift b/OTFMagicBox/Tasks/TaskItem.swift index 1fff3d29..02d2e48e 100644 --- a/OTFMagicBox/Tasks/TaskItem.swift +++ b/OTFMagicBox/Tasks/TaskItem.swift @@ -1,35 +1,35 @@ /* -Copyright (c) 2021, Hippocrates Technologies S.r.l.. All rights reserved. + Copyright (c) 2021, Hippocrates Technologies S.r.l.. All rights reserved. -Redistribution and use in source and binary forms, with or without modification, -are permitted provided that the following conditions are met: + Redistribution and use in source and binary forms, with or without modification, + are permitted provided that the following conditions are met: -1. Redistributions of source code must retain the above copyright notice, -this list of conditions and the following disclaimer. + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. -2. Redistributions in binary form must reproduce the above copyright notice, -this list of conditions and the following disclaimer in the documentation and/or -other materials provided with the distribution. + 2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation and/or + other materials provided with the distribution. -3. Neither the name of the copyright holder(s) nor the names of any contributor(s) may -be used to endorse or promote products derived from this software without specific -prior written permission. No license is granted to the trademarks of the copyright -holders even if such marks are included in this software. + 3. Neither the name of the copyright holder(s) nor the names of any contributor(s) may + be used to endorse or promote products derived from this software without specific + prior written permission. No license is granted to the trademarks of the copyright + holders even if such marks are included in this software. -4. Commercial redistribution in any form requires an explicit license agreement with the -copyright holder(s). Please contact support@hippocratestech.com for further information -regarding licensing. + 4. Commercial redistribution in any form requires an explicit license agreement with the + copyright holder(s). Please contact support@hippocratestech.com for further information + regarding licensing. -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND -ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. -IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, -INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT -NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, -WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) -ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY -OF SUCH DAMAGE. + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, + INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, + OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY + OF SUCH DAMAGE. */ import Foundation @@ -37,14 +37,14 @@ import OTFResearchKit import SwiftUI /** - The list of tasks for the user. + The list of tasks for the user. */ enum TaskItem: Int, CaseIterable { // Task items. case sampleSurvey, sampleActivity - + // Task titles. var title: String { switch self { @@ -54,7 +54,7 @@ enum TaskItem: Int, CaseIterable { return "Activity" } } - + // Task sub titles. var subtitle: String { switch self { @@ -64,7 +64,7 @@ enum TaskItem: Int, CaseIterable { return "Sample sensor/data collection activities." } } - + // Icons for the tasks. var image: UIImage? { switch self { @@ -74,7 +74,7 @@ enum TaskItem: Int, CaseIterable { return getImage(named: "activityImage") } } - + // Section titles for the tasks. var sectionTitle: String { switch self { @@ -83,7 +83,7 @@ enum TaskItem: Int, CaseIterable { } } - // Action of a each task. + // Action of a each task. var action: some View { switch self { case .sampleSurvey: @@ -92,9 +92,8 @@ enum TaskItem: Int, CaseIterable { return AnyView(TaskViewController(tasks: TaskSamples.sampleWalkingTask)) } } - + fileprivate func getImage(named: String) -> UIImage? { UIImage(named: named) ?? UIImage(systemName: "questionmark.square") } } - diff --git a/OTFMagicBox/Tasks/TaskItemView.swift b/OTFMagicBox/Tasks/TaskItemView.swift index 2cf38785..3bc2d44f 100644 --- a/OTFMagicBox/Tasks/TaskItemView.swift +++ b/OTFMagicBox/Tasks/TaskItemView.swift @@ -1,51 +1,49 @@ /* -Copyright (c) 2021, Hippocrates Technologies S.r.l.. All rights reserved. + Copyright (c) 2021, Hippocrates Technologies S.r.l.. All rights reserved. -Redistribution and use in source and binary forms, with or without modification, -are permitted provided that the following conditions are met: + Redistribution and use in source and binary forms, with or without modification, + are permitted provided that the following conditions are met: -1. Redistributions of source code must retain the above copyright notice, -this list of conditions and the following disclaimer. + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. -2. Redistributions in binary form must reproduce the above copyright notice, -this list of conditions and the following disclaimer in the documentation and/or -other materials provided with the distribution. + 2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation and/or + other materials provided with the distribution. -3. Neither the name of the copyright holder(s) nor the names of any contributor(s) may -be used to endorse or promote products derived from this software without specific -prior written permission. No license is granted to the trademarks of the copyright -holders even if such marks are included in this software. + 3. Neither the name of the copyright holder(s) nor the names of any contributor(s) may + be used to endorse or promote products derived from this software without specific + prior written permission. No license is granted to the trademarks of the copyright + holders even if such marks are included in this software. -4. Commercial redistribution in any form requires an explicit license agreement with the -copyright holder(s). Please contact support@hippocratestech.com for further information -regarding licensing. + 4. Commercial redistribution in any form requires an explicit license agreement with the + copyright holder(s). Please contact support@hippocratestech.com for further information + regarding licensing. -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND -ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. -IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, -INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT -NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, -WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) -ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY -OF SUCH DAMAGE. + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, + INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, + OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY + OF SUCH DAMAGE. */ import SwiftUI import OTFResearchKit - - struct TaskItemView: View { - + let item: TaskItem @State var showingDetail = false - + init(item: TaskItem) { self.item = item } - + var body: some View { HStack { if item.image != nil { @@ -53,11 +51,11 @@ struct TaskItemView: View { } VStack(alignment: .leading) { Text(item.title) - .font(YmlReader().appTheme?.headerTitleFont.appFont ?? Font.system(size: 17.0)) - .fontWeight(YmlReader().appTheme?.textWeight.fontWeight) + .font(.otfheaderTitleFont) + .fontWeight(Font.otfFontWeight) Text(item.subtitle) - .font(YmlReader().appTheme?.textFont.appFont ?? Font.system(size: 17.0)) - .fontWeight(YmlReader().appTheme?.textWeight.fontWeight) + .font(Font.otfAppFont) + .fontWeight(Font.otfFontWeight) } Spacer() } @@ -66,7 +64,7 @@ struct TaskItemView: View { self.showingDetail.toggle() })) .sheet(isPresented: $showingDetail, onDismiss: {}, content: { - item.action + item.action }) } } diff --git a/OTFMagicBox/Tasks/TaskSamples.swift b/OTFMagicBox/Tasks/TaskSamples.swift index 426d0f44..e323d783 100644 --- a/OTFMagicBox/Tasks/TaskSamples.swift +++ b/OTFMagicBox/Tasks/TaskSamples.swift @@ -1,100 +1,126 @@ /* -Copyright (c) 2021, Hippocrates Technologies S.r.l.. All rights reserved. - -Redistribution and use in source and binary forms, with or without modification, -are permitted provided that the following conditions are met: - -1. Redistributions of source code must retain the above copyright notice, -this list of conditions and the following disclaimer. - -2. Redistributions in binary form must reproduce the above copyright notice, -this list of conditions and the following disclaimer in the documentation and/or -other materials provided with the distribution. - -3. Neither the name of the copyright holder(s) nor the names of any contributor(s) may -be used to endorse or promote products derived from this software without specific -prior written permission. No license is granted to the trademarks of the copyright -holders even if such marks are included in this software. - -4. Commercial redistribution in any form requires an explicit license agreement with the -copyright holder(s). Please contact support@hippocratestech.com for further information -regarding licensing. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND -ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. -IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, -INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT -NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, -WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) -ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY -OF SUCH DAMAGE. + Copyright (c) 2021, Hippocrates Technologies S.r.l.. All rights reserved. + + Redistribution and use in source and binary forms, with or without modification, + are permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation and/or + other materials provided with the distribution. + + 3. Neither the name of the copyright holder(s) nor the names of any contributor(s) may + be used to endorse or promote products derived from this software without specific + prior written permission. No license is granted to the trademarks of the copyright + holders even if such marks are included in this software. + + 4. Commercial redistribution in any form requires an explicit license agreement with the + copyright holder(s). Please contact support@hippocratestech.com for further information + regarding licensing. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, + INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, + OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY + OF SUCH DAMAGE. */ import OTFResearchKit /** The sample tasks for a user. -*/ -struct TaskSamples { + */ +enum TaskSamples { // Sample walking task. static let sampleWalkingTask: ORKOrderedTask = { let intendedUseDescription = Constants.CustomiseStrings.intendedDescription - + return ORKOrderedTask.shortWalk(withIdentifier: "ShortWalkTask", intendedUseDescription: intendedUseDescription, numberOfStepsPerLeg: 20, restDuration: 30, options: ORKPredefinedTaskOption()) }() - + /** Sample survey activity. - */ + */ static let sampleSurveyTask: ORKOrderedTask = { var steps = [ORKStep]() - + // Instruction step let instructionStep = ORKInstructionStep(identifier: "IntroStep") instructionStep.title = Constants.CustomiseStrings.instructionStepTitle instructionStep.text = Constants.CustomiseStrings.instructionStepText - + steps += [instructionStep] - - //In general, would you say your health is: - let healthScaleAnswerFormat = ORKAnswerFormat.scale(withMaximumValue: 5, minimumValue: 1, defaultValue: 3, step: 1, vertical: false, maximumValueDescription: "Excellent", minimumValueDescription: "Poor") - let healthScaleQuestionStep = ORKQuestionStep(identifier: "HealthScaleQuestionStep", title: Constants.CustomiseStrings.healthScaleTitle, question: Constants.CustomiseStrings.healthScaleQuestion, answer: healthScaleAnswerFormat) - + + // In general, would you say your health is: + let healthScaleAnswerFormat = ORKAnswerFormat.scale( + withMaximumValue: 5, + minimumValue: 1, + defaultValue: 3, + step: 1, + vertical: false, + maximumValueDescription: "Excellent", + minimumValueDescription: "Poor") + let healthScaleQuestionStep = ORKQuestionStep( + identifier: "HealthScaleQuestionStep", + title: Constants.CustomiseStrings.healthScaleTitle, + question: Constants.CustomiseStrings.healthScaleQuestion, + answer: healthScaleAnswerFormat) + steps += [healthScaleQuestionStep] - + let textChoices = [ ORKTextChoice(text: "Yes, Limited A lot", value: 0 as NSCoding & NSCopying & NSObjectProtocol), ORKTextChoice(text: "Yes, Limited A Little", value: 1 as NSCoding & NSCopying & NSObjectProtocol), ORKTextChoice(text: "No, Not Limited At All", value: 2 as NSCoding & NSCopying & NSObjectProtocol) ] let textChoiceAnswerFormat = ORKAnswerFormat.choiceAnswerFormat(with: .singleChoice, textChoices: textChoices) - let textStep = ORKQuestionStep(identifier: "TextStep", title: "Daily Activities", question: "MODERATE ACTIVITIES, such as moving a table, pushing a vacuum cleaner, bowling, or playing golf:", answer: textChoiceAnswerFormat) - + let textStep = ORKQuestionStep( + identifier: "TextStep", + title: "Daily Activities", + question: "MODERATE ACTIVITIES, such as moving a table, pushing a vacuum cleaner, bowling, or playing golf:", + answer: textChoiceAnswerFormat) + steps += [textStep] - - - let formItem = ORKFormItem(identifier: "FormItem1", text: "MODERATE ACTIVITIES, such as moving a table, pushing a vacuum cleaner, bowling, or playing golf:", answerFormat: textChoiceAnswerFormat) - let formItem2 = ORKFormItem(identifier: "FormItem2", text: "Climbing SEVERAL flights of stairs:", answerFormat: textChoiceAnswerFormat) - let formStep = ORKFormStep(identifier: "FormStep", title: "Daily Activities", text: "The following two questions are about activities you might do during a typical day. Does YOUR HEALTH NOW LIMIT YOU in these activities? If so, how much?") + let formItem = ORKFormItem( + identifier: "FormItem1", + text: "MODERATE ACTIVITIES, such as moving a table, pushing a vacuum cleaner, bowling, or playing golf:", + answerFormat: textChoiceAnswerFormat) + let formItem2 = ORKFormItem( + identifier: "FormItem2", + text: "Climbing SEVERAL flights of stairs:", + answerFormat: textChoiceAnswerFormat) + let formStep = ORKFormStep( + identifier: "FormStep", + title: "Daily Activities", + text: "The following two questions are about activities you might do during a typical day. Does YOUR HEALTH NOW LIMIT YOU in these activities? If so, how much?") formStep.formItems = [formItem, formItem2] - + steps += [formStep] - + let booleanAnswer = ORKBooleanAnswerFormat(yesString: "Yes", noString: "No") - let booleanQuestionStep = ORKQuestionStep(identifier: "QuestionStep", title: nil, question: "In the past four weeks, did you feel limited in the kind of work that you can accomplish?", answer: booleanAnswer) - + let booleanQuestionStep = ORKQuestionStep( + identifier: "QuestionStep", + title: nil, + question: "In the past four weeks, did you feel limited in the kind of work that you can accomplish?", + answer: booleanAnswer) + steps += [booleanQuestionStep] - - //SUMMARY + + // SUMMARY let summaryStep = ORKCompletionStep(identifier: "SummaryStep") summaryStep.title = "Thank you." summaryStep.text = "We appreciate your time." - + steps += [summaryStep] - + return ORKOrderedTask(identifier: "SurveyTask-Assessment", steps: steps) }() } diff --git a/OTFMagicBox/Tasks/TaskViewController.swift b/OTFMagicBox/Tasks/TaskViewController.swift index b4436384..4ae8b73d 100644 --- a/OTFMagicBox/Tasks/TaskViewController.swift +++ b/OTFMagicBox/Tasks/TaskViewController.swift @@ -1,35 +1,35 @@ /* -Copyright (c) 2021, Hippocrates Technologies S.r.l.. All rights reserved. - -Redistribution and use in source and binary forms, with or without modification, -are permitted provided that the following conditions are met: - -1. Redistributions of source code must retain the above copyright notice, -this list of conditions and the following disclaimer. - -2. Redistributions in binary form must reproduce the above copyright notice, -this list of conditions and the following disclaimer in the documentation and/or -other materials provided with the distribution. - -3. Neither the name of the copyright holder(s) nor the names of any contributor(s) may -be used to endorse or promote products derived from this software without specific -prior written permission. No license is granted to the trademarks of the copyright -holders even if such marks are included in this software. - -4. Commercial redistribution in any form requires an explicit license agreement with the -copyright holder(s). Please contact support@hippocratestech.com for further information -regarding licensing. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND -ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. -IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, -INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT -NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, -WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) -ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY -OF SUCH DAMAGE. + Copyright (c) 2021, Hippocrates Technologies S.r.l.. All rights reserved. + + Redistribution and use in source and binary forms, with or without modification, + are permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation and/or + other materials provided with the distribution. + + 3. Neither the name of the copyright holder(s) nor the names of any contributor(s) may + be used to endorse or promote products derived from this software without specific + prior written permission. No license is granted to the trademarks of the copyright + holders even if such marks are included in this software. + + 4. Commercial redistribution in any form requires an explicit license agreement with the + copyright holder(s). Please contact support@hippocratestech.com for further information + regarding licensing. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, + INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, + OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY + OF SUCH DAMAGE. */ import UIKit @@ -37,22 +37,20 @@ import SwiftUI import OTFResearchKit struct TaskViewController: UIViewControllerRepresentable { - + let vc: ORKTaskViewController - + init(tasks: ORKOrderedTask) { self.vc = ORKTaskViewController(task: tasks, taskRun: NSUUID() as UUID) - + } typealias UIViewControllerType = ORKTaskViewController - + func updateUIViewController(_ taskViewController: ORKTaskViewController, context: Context) { } func makeUIViewController(context: Context) -> ORKTaskViewController { - - // & present the VC! return self.vc } - + } diff --git a/OTFMagicBox/Tasks/TasksUIView.swift b/OTFMagicBox/Tasks/TasksUIView.swift index 60427318..7845175c 100644 --- a/OTFMagicBox/Tasks/TasksUIView.swift +++ b/OTFMagicBox/Tasks/TasksUIView.swift @@ -1,84 +1,84 @@ /* -Copyright (c) 2021, Hippocrates Technologies S.r.l.. All rights reserved. - -Redistribution and use in source and binary forms, with or without modification, -are permitted provided that the following conditions are met: - -1. Redistributions of source code must retain the above copyright notice, -this list of conditions and the following disclaimer. - -2. Redistributions in binary form must reproduce the above copyright notice, -this list of conditions and the following disclaimer in the documentation and/or -other materials provided with the distribution. - -3. Neither the name of the copyright holder(s) nor the names of any contributor(s) may -be used to endorse or promote products derived from this software without specific -prior written permission. No license is granted to the trademarks of the copyright -holders even if such marks are included in this software. - -4. Commercial redistribution in any form requires an explicit license agreement with the -copyright holder(s). Please contact support@hippocratestech.com for further information -regarding licensing. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND -ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. -IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, -INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT -NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, -WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) -ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY -OF SUCH DAMAGE. + Copyright (c) 2021, Hippocrates Technologies S.r.l.. All rights reserved. + + Redistribution and use in source and binary forms, with or without modification, + are permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation and/or + other materials provided with the distribution. + + 3. Neither the name of the copyright holder(s) nor the names of any contributor(s) may + be used to endorse or promote products derived from this software without specific + prior written permission. No license is granted to the trademarks of the copyright + holders even if such marks are included in this software. + + 4. Commercial redistribution in any form requires an explicit license agreement with the + copyright holder(s). Please contact support@hippocratestech.com for further information + regarding licensing. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, + INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, + OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY + OF SUCH DAMAGE. */ import SwiftUI /** - The Tasks view, where a patient performs activities. + The Tasks view, where a patient performs activities. */ struct TasksUIView: View { - + var date: String { let formatter = DateFormatter() formatter.dateFormat = "MMM. d, YYYY" let date = formatter.string(from: Date()) return date } - + let color: Color - + let listItems = TaskItem.allCases - var listItemsPerHeader = [String:[TaskItem]]() + var listItemsPerHeader = [String: [TaskItem]]() var listItemsSections = [String]() - + init(color: Color) { self.color = color - - if listItemsPerHeader.count <= 0 { // init + + if listItemsPerHeader.isEmpty { for item in listItems { if listItemsPerHeader[item.sectionTitle] == nil { listItemsPerHeader[item.sectionTitle] = [TaskItem]() listItemsSections.append(item.sectionTitle) } - + listItemsPerHeader[item.sectionTitle]?.append(item) } } } - + var body: some View { VStack { Text(YmlReader().appTitle) - .font(YmlReader().appTheme?.screenTitleFont.appFont ?? Font.system(size: 17.0)) + .font(Font.otfscreenTitleFont) .foregroundColor(self.color) .padding(.top, 20) Text(self.date) - .font(YmlReader().appTheme?.textFont.appFont ?? Font.system(size: 17.0)) + .font(Font.otfAppFont) .padding() List { ForEach(listItemsSections, id: \.self) { key in - Section(header: Text(key).font(YmlReader().appTheme?.textFont.appFont ?? Font.system(size: 17.0))) { + Section(header: Text(key).font(Font.otfAppFont)) { ForEach(listItemsPerHeader[key]!, id: \.self) { item in TaskItemView(item: item) } @@ -91,6 +91,6 @@ struct TasksUIView: View { struct TasksUIView_Previews: PreviewProvider { static var previews: some View { - TasksUIView(color: Color(YmlReader().appTheme?.textColor.color ?? UIColor.black)) + TasksUIView(color: .otfTextColor) } } diff --git a/OTFMagicBox/ViewModifiers/StyleModifiers.swift b/OTFMagicBox/ViewModifiers/StyleModifiers.swift index 5a7d94ba..4794881d 100644 --- a/OTFMagicBox/ViewModifiers/StyleModifiers.swift +++ b/OTFMagicBox/ViewModifiers/StyleModifiers.swift @@ -1,35 +1,35 @@ /* -Copyright (c) 2021, Hippocrates Technologies S.r.l.. All rights reserved. + Copyright (c) 2021, Hippocrates Technologies S.r.l.. All rights reserved. -Redistribution and use in source and binary forms, with or without modification, -are permitted provided that the following conditions are met: + Redistribution and use in source and binary forms, with or without modification, + are permitted provided that the following conditions are met: -1. Redistributions of source code must retain the above copyright notice, -this list of conditions and the following disclaimer. + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. -2. Redistributions in binary form must reproduce the above copyright notice, -this list of conditions and the following disclaimer in the documentation and/or -other materials provided with the distribution. + 2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation and/or + other materials provided with the distribution. -3. Neither the name of the copyright holder(s) nor the names of any contributor(s) may -be used to endorse or promote products derived from this software without specific -prior written permission. No license is granted to the trademarks of the copyright -holders even if such marks are included in this software. + 3. Neither the name of the copyright holder(s) nor the names of any contributor(s) may + be used to endorse or promote products derived from this software without specific + prior written permission. No license is granted to the trademarks of the copyright + holders even if such marks are included in this software. -4. Commercial redistribution in any form requires an explicit license agreement with the -copyright holder(s). Please contact support@hippocratestech.com for further information -regarding licensing. + 4. Commercial redistribution in any form requires an explicit license agreement with the + copyright holder(s). Please contact support@hippocratestech.com for further information + regarding licensing. -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND -ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. -IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, -INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT -NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, -WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) -ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY -OF SUCH DAMAGE. + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, + INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, + OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY + OF SUCH DAMAGE. */ import SwiftUI @@ -49,7 +49,7 @@ extension View { .padding() .overlay(Capsule().strokeBorder(color, style: StrokeStyle(lineWidth: 1.0))) .padding() - + case .secureField: self.padding() .overlay(Capsule().strokeBorder(color, style: StrokeStyle(lineWidth: 1.0))) @@ -57,8 +57,6 @@ extension View { } } } - - extension Image { func logoStyle() -> some View { self.resizable() @@ -66,7 +64,7 @@ extension Image { .padding(.leading, Metrics.PADDING_HORIZONTAL_BUTTON * 4) .padding(.trailing, Metrics.PADDING_HORIZONTAL_BUTTON * 4) } - + func iconStyle() -> some View { self.resizable() .clipped() @@ -74,4 +72,3 @@ extension Image { .overlay(Circle().stroke(Color(YmlReader().primaryColor), lineWidth: 2.0)) } } - diff --git a/OTFMagicBox/ViewModifiers/ViewDidLoadModifier.swift b/OTFMagicBox/ViewModifiers/ViewDidLoadModifier.swift index d2ee9ee4..f9b17171 100644 --- a/OTFMagicBox/ViewModifiers/ViewDidLoadModifier.swift +++ b/OTFMagicBox/ViewModifiers/ViewDidLoadModifier.swift @@ -1,35 +1,35 @@ /* -Copyright (c) 2021, Hippocrates Technologies S.r.l.. All rights reserved. - -Redistribution and use in source and binary forms, with or without modification, -are permitted provided that the following conditions are met: - -1. Redistributions of source code must retain the above copyright notice, -this list of conditions and the following disclaimer. - -2. Redistributions in binary form must reproduce the above copyright notice, -this list of conditions and the following disclaimer in the documentation and/or -other materials provided with the distribution. - -3. Neither the name of the copyright holder(s) nor the names of any contributor(s) may -be used to endorse or promote products derived from this software without specific -prior written permission. No license is granted to the trademarks of the copyright -holders even if such marks are included in this software. - -4. Commercial redistribution in any form requires an explicit license agreement with the -copyright holder(s). Please contact support@hippocratestech.com for further information -regarding licensing. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND -ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. -IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, -INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT -NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, -WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) -ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY -OF SUCH DAMAGE. + Copyright (c) 2021, Hippocrates Technologies S.r.l.. All rights reserved. + + Redistribution and use in source and binary forms, with or without modification, + are permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation and/or + other materials provided with the distribution. + + 3. Neither the name of the copyright holder(s) nor the names of any contributor(s) may + be used to endorse or promote products derived from this software without specific + prior written permission. No license is granted to the trademarks of the copyright + holders even if such marks are included in this software. + + 4. Commercial redistribution in any form requires an explicit license agreement with the + copyright holder(s). Please contact support@hippocratestech.com for further information + regarding licensing. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, + INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, + OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY + OF SUCH DAMAGE. */ import SwiftUI diff --git a/OTFMagicBox/Views/LaunchView.swift b/OTFMagicBox/Views/LaunchView.swift index 0687c7d3..ed47be48 100644 --- a/OTFMagicBox/Views/LaunchView.swift +++ b/OTFMagicBox/Views/LaunchView.swift @@ -1,35 +1,35 @@ /* -Copyright (c) 2021, Hippocrates Technologies S.r.l.. All rights reserved. + Copyright (c) 2021, Hippocrates Technologies S.r.l.. All rights reserved. -Redistribution and use in source and binary forms, with or without modification, -are permitted provided that the following conditions are met: + Redistribution and use in source and binary forms, with or without modification, + are permitted provided that the following conditions are met: -1. Redistributions of source code must retain the above copyright notice, -this list of conditions and the following disclaimer. + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. -2. Redistributions in binary form must reproduce the above copyright notice, -this list of conditions and the following disclaimer in the documentation and/or -other materials provided with the distribution. + 2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation and/or + other materials provided with the distribution. -3. Neither the name of the copyright holder(s) nor the names of any contributor(s) may -be used to endorse or promote products derived from this software without specific -prior written permission. No license is granted to the trademarks of the copyright -holders even if such marks are included in this software. + 3. Neither the name of the copyright holder(s) nor the names of any contributor(s) may + be used to endorse or promote products derived from this software without specific + prior written permission. No license is granted to the trademarks of the copyright + holders even if such marks are included in this software. -4. Commercial redistribution in any form requires an explicit license agreement with the -copyright holder(s). Please contact support@hippocratestech.com for further information -regarding licensing. + 4. Commercial redistribution in any form requires an explicit license agreement with the + copyright holder(s). Please contact support@hippocratestech.com for further information + regarding licensing. -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND -ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. -IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, -INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT -NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, -WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) -ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY -OF SUCH DAMAGE. + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, + INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, + OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY + OF SUCH DAMAGE. */ import SwiftUI @@ -37,13 +37,13 @@ import OTFCareKitStore import OTFCloudClientAPI struct LaunchView: View { - + @State var onboardingCompleted = UserDefaultsManager.onboardingDidComplete @State private var isDefaultAPIKey = false - + var body: some View { VStack(spacing: 10) { - if onboardingCompleted, let _ = TheraForgeKeychainService.shared.loadUser() { + if onboardingCompleted, TheraForgeKeychainService.shared.loadUser() != nil { MainView() } else { OnboardingView { @@ -55,25 +55,25 @@ struct LaunchView: View { isDefaultAPIKey = true return } - + didCompleteOnBoarding() - }).onReceive(NotificationCenter.default.publisher(for: .onboardingDidComplete)) { notification in + }).onReceive(NotificationCenter.default.publisher(for: .onboardingDidComplete)) { _ in didCompleteOnBoarding() }.alert(isPresented: $isDefaultAPIKey) { return Alert(title: Text(Constants.CustomiseStrings.apiKeyMissing) - .font(YmlReader().appTheme?.textFont.appFont ?? Font.system(size: 17.0)) - , + .font(Font.otfAppFont) + , message: Text(Constants.CustomiseStrings.setValidApiKey) - .font(YmlReader().appTheme?.textFont.appFont ?? Font.system(size: 17.0)) - .fontWeight(YmlReader().appTheme?.textWeight.fontWeight) - , + .font(Font.otfAppFont) + .fontWeight(Font.otfFontWeight) + , dismissButton: .cancel({ UserDefaultsManager.setOnboardingCompleted(false) onboardingCompleted = false })) } } - + func didCompleteOnBoarding() { self.onboardingCompleted = UserDefaultsManager.onboardingDidComplete } diff --git a/OTFMagicBox/Views/LoadingView.swift b/OTFMagicBox/Views/LoadingView.swift index 5048dc02..53625e18 100644 --- a/OTFMagicBox/Views/LoadingView.swift +++ b/OTFMagicBox/Views/LoadingView.swift @@ -1,47 +1,47 @@ /* -Copyright (c) 2021, Hippocrates Technologies S.r.l.. All rights reserved. + Copyright (c) 2021, Hippocrates Technologies S.r.l.. All rights reserved. -Redistribution and use in source and binary forms, with or without modification, -are permitted provided that the following conditions are met: + Redistribution and use in source and binary forms, with or without modification, + are permitted provided that the following conditions are met: -1. Redistributions of source code must retain the above copyright notice, -this list of conditions and the following disclaimer. + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. -2. Redistributions in binary form must reproduce the above copyright notice, -this list of conditions and the following disclaimer in the documentation and/or -other materials provided with the distribution. + 2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation and/or + other materials provided with the distribution. -3. Neither the name of the copyright holder(s) nor the names of any contributor(s) may -be used to endorse or promote products derived from this software without specific -prior written permission. No license is granted to the trademarks of the copyright -holders even if such marks are included in this software. + 3. Neither the name of the copyright holder(s) nor the names of any contributor(s) may + be used to endorse or promote products derived from this software without specific + prior written permission. No license is granted to the trademarks of the copyright + holders even if such marks are included in this software. -4. Commercial redistribution in any form requires an explicit license agreement with the -copyright holder(s). Please contact support@hippocratestech.com for further information -regarding licensing. + 4. Commercial redistribution in any form requires an explicit license agreement with the + copyright holder(s). Please contact support@hippocratestech.com for further information + regarding licensing. -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND -ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. -IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, -INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT -NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, -WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) -ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY -OF SUCH DAMAGE. + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, + INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, + OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY + OF SUCH DAMAGE. */ import SwiftUI struct LoadingView: View { - + private let username: String - + init(username: String) { self.username = username } - + var body: some View { VStack(spacing: 10) { if #available(iOS 14.0, *) { @@ -58,15 +58,18 @@ struct LoadingView: View { } struct ActivityIndicator: UIViewRepresentable { - + typealias UIView = UIActivityIndicatorView var isAnimating: Bool - fileprivate var configuration = { (indicator: UIView) in } + fileprivate var configuration = { (_: UIView) in } func makeUIView(context: UIViewRepresentableContext) -> UIView { UIView() } func updateUIView(_ uiView: UIView, context: UIViewRepresentableContext) { - isAnimating ? uiView.startAnimating() : uiView.stopAnimating() + if isAnimating { + uiView.startAnimating() + } else { + uiView.stopAnimating() + } configuration(uiView) } } - diff --git a/OTFMagicBox/Views/MainView.swift b/OTFMagicBox/Views/MainView.swift index 783ec19b..d21ab0bb 100644 --- a/OTFMagicBox/Views/MainView.swift +++ b/OTFMagicBox/Views/MainView.swift @@ -1,25 +1,25 @@ /* Copyright (c) 2021, Hippocrates Technologies S.r.l.. All rights reserved. - + Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: - + 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. - + 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. - + 3. Neither the name of the copyright holder(s) nor the names of any contributor(s) may be used to endorse or promote products derived from this software without specific prior written permission. No license is granted to the trademarks of the copyright holders even if such marks are included in this software. - + 4. Commercial redistribution in any form requires an explicit license agreement with the copyright holder(s). Please contact support@hippocratestech.com for further information regarding licensing. - + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. @@ -33,14 +33,16 @@ */ import SwiftUI +import OTFCareKitUI import OTFCareKitStore struct MainView: View { - + let color: Color - + init() { - self.color = Color(YmlReader().primaryColor) + self.color = Color(YmlReader().appStyle.buttonTextColor.color ?? + YmlReader().primaryColor) OTFTheraforgeNetwork.shared.refreshToken { response in switch response { case .success(let data): @@ -51,56 +53,57 @@ struct MainView: View { } } } + } - + var body: some View { TabView { if YmlReader().useCareKit { ScheduleViewControllerRepresentable().tabItem { UIImage.loadImage(named: "tab_schedule").renderingMode(.template) Text("Tasks") - .font(YmlReader().appTheme?.textFont.appFont ?? Font.system(size: 17.0)) - .fontWeight(YmlReader().appTheme?.textWeight.fontWeight) + .font(Font.otfAppFont) + .fontWeight(Font.otfFontWeight) } + .ignoresSafeArea(.all) NavigationView { - ContactsViewController(storeManager: CareKitManager.shared.synchronizedStoreManager) + ContactsViewController(storeManager: CareKitStoreManager.shared.synchronizedStoreManager) .navigationBarTitle(Text("Care Team"), displayMode: .inline) } .tabItem { UIImage.loadImage(named: "tab_care").renderingMode(.template) Text("Contacts") - .font(YmlReader().appTheme?.textFont.appFont ?? Font.system(size: 17.0)) - .fontWeight(YmlReader().appTheme?.textWeight.fontWeight) + .font(Font.otfAppFont) + .fontWeight(Font.otfFontWeight) } } - + if YmlReader().showCheckupScreen { CheckUpView().tabItem { UIImage.loadImage(named: "tab_tasks").renderingMode(.template) Text("Check Up") - .font(YmlReader().appTheme?.textFont.appFont ?? Font.system(size: 17.0)) - .fontWeight(YmlReader().appTheme?.textWeight.fontWeight) + .font(Font.otfAppFont) + .fontWeight(Font.otfFontWeight) } } - + if YmlReader().showStaticUIScreen { StaticUI().tabItem { Image(systemName: "uiwindow.split.2x1") Text("UI") - .font(YmlReader().appTheme?.textFont.appFont ?? Font.system(size: 17.0)) - .fontWeight(YmlReader().appTheme?.textWeight.fontWeight) + .font(Font.otfAppFont) + .fontWeight(Font.otfFontWeight) } } - + ProfileUIView().tabItem { UIImage.loadImage(named: "tab_profile").renderingMode(.template) Text(Constants.CustomiseStrings.profile) - .font(YmlReader().appTheme?.textFont.appFont ?? Font.system(size: 17.0)) - .fontWeight(YmlReader().appTheme?.textWeight.fontWeight) + .font(Font.otfAppFont) + .fontWeight(Font.otfFontWeight) } } .accentColor(self.color) - } } diff --git a/OTFMagicBox/Views/RingProgressView.swift b/OTFMagicBox/Views/RingProgressView.swift index 323a0273..082d74b1 100644 --- a/OTFMagicBox/Views/RingProgressView.swift +++ b/OTFMagicBox/Views/RingProgressView.swift @@ -1,25 +1,25 @@ /* Copyright (c) 2021, Hippocrates Technologies S.r.l.. All rights reserved. - + Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: - + 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. - + 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. - + 3. Neither the name of the copyright holder(s) nor the names of any contributor(s) may be used to endorse or promote products derived from this software without specific prior written permission. No license is granted to the trademarks of the copyright holders even if such marks are included in this software. - + 4. Commercial redistribution in any form requires an explicit license agreement with the copyright holder(s). Please contact support@hippocratestech.com for further information regarding licensing. - + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. @@ -39,14 +39,14 @@ struct RingProgressView: View { var color: Color var lineWidth: CGFloat var opacity: Double = 0.3 - + var body: some View { ZStack { Circle() .stroke(lineWidth: lineWidth) .opacity(opacity) .foregroundColor(color) - + Circle() .trim(from: 0.0, to: CGFloat(min(progress, 1.0))) .stroke(style: StrokeStyle(lineWidth: lineWidth, lineCap: .round, lineJoin: .round)) diff --git a/OTFMagicBoxTests/OTFMagicBoxTests.swift b/OTFMagicBoxTests/OTFMagicBoxTests.swift index 11d02d1a..a102bcd9 100644 --- a/OTFMagicBoxTests/OTFMagicBoxTests.swift +++ b/OTFMagicBoxTests/OTFMagicBoxTests.swift @@ -1,35 +1,35 @@ /* -Copyright (c) 2021, Hippocrates Technologies S.r.l.. All rights reserved. - -Redistribution and use in source and binary forms, with or without modification, -are permitted provided that the following conditions are met: - -1. Redistributions of source code must retain the above copyright notice, -this list of conditions and the following disclaimer. - -2. Redistributions in binary form must reproduce the above copyright notice, -this list of conditions and the following disclaimer in the documentation and/or -other materials provided with the distribution. - -3. Neither the name of the copyright holder(s) nor the names of any contributor(s) may -be used to endorse or promote products derived from this software without specific -prior written permission. No license is granted to the trademarks of the copyright -holders even if such marks are included in this software. - -4. Commercial redistribution in any form requires an explicit license agreement with the -copyright holder(s). Please contact support@hippocratestech.com for further information -regarding licensing. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND -ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. -IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, -INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT -NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, -WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) -ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY -OF SUCH DAMAGE. + Copyright (c) 2021, Hippocrates Technologies S.r.l.. All rights reserved. + + Redistribution and use in source and binary forms, with or without modification, + are permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation and/or + other materials provided with the distribution. + + 3. Neither the name of the copyright holder(s) nor the names of any contributor(s) may + be used to endorse or promote products derived from this software without specific + prior written permission. No license is granted to the trademarks of the copyright + holders even if such marks are included in this software. + + 4. Commercial redistribution in any form requires an explicit license agreement with the + copyright holder(s). Please contact support@hippocratestech.com for further information + regarding licensing. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, + INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, + OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY + OF SUCH DAMAGE. */ import XCTest diff --git a/OTFMagicBoxTests/OTFMagicBoxYamlTests.swift b/OTFMagicBoxTests/OTFMagicBoxYamlTests.swift index f7e5fc67..af280a8f 100644 --- a/OTFMagicBoxTests/OTFMagicBoxYamlTests.swift +++ b/OTFMagicBoxTests/OTFMagicBoxYamlTests.swift @@ -1,35 +1,35 @@ /* -Copyright (c) 2021, Hippocrates Technologies S.r.l.. All rights reserved. - -Redistribution and use in source and binary forms, with or without modification, -are permitted provided that the following conditions are met: - -1. Redistributions of source code must retain the above copyright notice, -this list of conditions and the following disclaimer. - -2. Redistributions in binary form must reproduce the above copyright notice, -this list of conditions and the following disclaimer in the documentation and/or -other materials provided with the distribution. - -3. Neither the name of the copyright holder(s) nor the names of any contributor(s) may -be used to endorse or promote products derived from this software without specific -prior written permission. No license is granted to the trademarks of the copyright -holders even if such marks are included in this software. - -4. Commercial redistribution in any form requires an explicit license agreement with the -copyright holder(s). Please contact support@hippocratestech.com for further information -regarding licensing. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND -ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. -IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, -INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT -NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, -WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) -ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY -OF SUCH DAMAGE. + Copyright (c) 2021, Hippocrates Technologies S.r.l.. All rights reserved. + + Redistribution and use in source and binary forms, with or without modification, + are permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation and/or + other materials provided with the distribution. + + 3. Neither the name of the copyright holder(s) nor the names of any contributor(s) may + be used to endorse or promote products derived from this software without specific + prior written permission. No license is granted to the trademarks of the copyright + holders even if such marks are included in this software. + + 4. Commercial redistribution in any form requires an explicit license agreement with the + copyright holder(s). Please contact support@hippocratestech.com for further information + regarding licensing. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, + INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, + OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY + OF SUCH DAMAGE. */ import XCTest @@ -38,40 +38,38 @@ import XCTest class OTFMagicBoxYamlTests: XCTestCase { /************************************************* THERAFORGE CONFIGURATIONS TESTS *******************************************************************************/ - + func testPrimaryColor() { let inputValue = YmlReader().primaryColor let expectedValue = UIColor.systemTeal XCTAssertEqual(inputValue, expectedValue) } - + func testTintColor() { let inputValue = YmlReader().tintColor let expectedValue = UIColor.blue XCTAssertEqual(inputValue, expectedValue) } - + func testRegistrationIsDOB() { let inputValue = ModuleAppYmlReader().registration?.showDateOfBirth let expectedValue = Constants.true XCTAssertEqual(inputValue, expectedValue) } - + func testRegistrationIsGender() { let inputValue = ModuleAppYmlReader().registration?.showGender let expectedValue = Constants.true XCTAssertEqual(inputValue, expectedValue) } - + /************************************************* CARDINALKIT CONFIGURATIONS TESTS *******************************************************************************/ - + func testStudyTitle() { let inputValue = YmlReader().studyTitle let expectedValue = "Health Study" XCTAssertEqual(inputValue, expectedValue) } - - func testLoginPasswordless() { let inputValue = ModuleAppYmlReader().loginPasswordless let expectedValue = true @@ -83,7 +81,7 @@ class OTFMagicBoxYamlTests: XCTestCase { let expectedValue = "Almost done!" XCTAssertEqual(inputValue, expectedValue) } - + func testLoginStepText() { let inputValue = ModuleAppYmlReader().loginStepText let expectedValue = "We need to confirm your email address and send you a copy of the consent you just signed." @@ -97,63 +95,60 @@ class OTFMagicBoxYamlTests: XCTestCase { title: "Welcome to MagicBox", color: "Black", description: "Your health care app") - XCTAssertEqual(inputValue, expectedValue); + XCTAssertEqual(inputValue, expectedValue) } - + func testTeamEmail() { let inputValue = YmlReader().teamEmail let expectedValue = "info@hippocratestech.com" - XCTAssertEqual(inputValue, expectedValue); + XCTAssertEqual(inputValue, expectedValue) } - + func testConsentTitle() { let inputValue = ModuleAppYmlReader().consentTitle let expectedValue = "TheraForge Consent" - XCTAssertEqual(inputValue, expectedValue); + XCTAssertEqual(inputValue, expectedValue) } - + func testPasscodeType() { let inputValue = ModuleAppYmlReader().passcodeType let expectedValue = "4" - XCTAssertEqual(inputValue, expectedValue); + XCTAssertEqual(inputValue, expectedValue) } - + func testFailedLoginTitle() { let inputValue = ModuleAppYmlReader().failedLoginTitle let expectedValue = "Unable to Login" - XCTAssertEqual(inputValue, expectedValue); + XCTAssertEqual(inputValue, expectedValue) } - + func testHealthPermissionsTitle() { let inputValue = ModuleAppYmlReader().healthPermissionsTitle let expectedValue = "Permission to read activity data 🏃🏽‍♀️" - XCTAssertEqual(inputValue, expectedValue); + XCTAssertEqual(inputValue, expectedValue) } - + func testUseCareKit() { let inputValue = YmlReader().useCareKit let expectedValue = true - XCTAssertEqual(inputValue, expectedValue); + XCTAssertEqual(inputValue, expectedValue) } - + func testHealthRecordsPermissionTitle() { let inputValue = ModuleAppYmlReader().healthRecords?.permissionsTitle let expectedValue = "Permission to read Health Records 🏥" - XCTAssertEqual(inputValue, expectedValue); + XCTAssertEqual(inputValue, expectedValue) } - + func testHealthKitTypes() { let inputValue = ModuleAppYmlReader().healthKitDataToRead.first let expectedValue = HealthKitTypes(type: "StepCount") - XCTAssertEqual(inputValue, expectedValue); + XCTAssertEqual(inputValue, expectedValue) } - + func testCompletionStepTitle() { let inputValue = ModuleAppYmlReader().completionStepTitle let expectedValue = "Welcome aboard." - XCTAssertEqual(inputValue, expectedValue); + XCTAssertEqual(inputValue, expectedValue) } - - } - diff --git a/OTFMagicBox Watch Watch App/Assets.xcassets/AccentColor.colorset/Contents.json b/OTFMagicBoxWatch/Assets.xcassets/AccentColor.colorset/Contents.json similarity index 100% rename from OTFMagicBox Watch Watch App/Assets.xcassets/AccentColor.colorset/Contents.json rename to OTFMagicBoxWatch/Assets.xcassets/AccentColor.colorset/Contents.json diff --git a/OTFMagicBox Watch Watch App/Assets.xcassets/AppIcon.appiconset/Contents.json b/OTFMagicBoxWatch/Assets.xcassets/AppIcon.appiconset/Contents.json similarity index 100% rename from OTFMagicBox Watch Watch App/Assets.xcassets/AppIcon.appiconset/Contents.json rename to OTFMagicBoxWatch/Assets.xcassets/AppIcon.appiconset/Contents.json diff --git a/OTFMagicBox Watch Watch App/Assets.xcassets/Contents.json b/OTFMagicBoxWatch/Assets.xcassets/Contents.json similarity index 100% rename from OTFMagicBox Watch Watch App/Assets.xcassets/Contents.json rename to OTFMagicBoxWatch/Assets.xcassets/Contents.json diff --git a/OTFMagicBoxWatch/ContentView.swift b/OTFMagicBoxWatch/ContentView.swift new file mode 100644 index 00000000..afb7f4c9 --- /dev/null +++ b/OTFMagicBoxWatch/ContentView.swift @@ -0,0 +1,50 @@ +/* + Copyright (c) 2021, Hippocrates Technologies S.r.l.. All rights reserved. + + Redistribution and use in source and binary forms, with or without modification, + are permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation and/or + other materials provided with the distribution. + + 3. Neither the name of the copyright holder(s) nor the names of any contributor(s) may + be used to endorse or promote products derived from this software without specific + prior written permission. No license is granted to the trademarks of the copyright + holders even if such marks are included in this software. + + 4. Commercial redistribution in any form requires an explicit license agreement with the + copyright holder(s). Please contact support@hippocratestech.com for further information + regarding licensing. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, + INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, + OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY + OF SUCH DAMAGE. + */ + +import OTFCareKit +import SwiftUI +import WatchKit +import OTFCareKitStore + +struct ContentView: View { + @ViewBuilder var body: some View { + if #available(watchOS 7, *) { + CareKitListView() + } + } +} + +#Preview { + ContentView() +} diff --git a/OTFMagicBoxWatch/DataStore/OTFCareKitStoreManager.swift b/OTFMagicBoxWatch/DataStore/OTFCareKitStoreManager.swift new file mode 100644 index 00000000..cc4d37e6 --- /dev/null +++ b/OTFMagicBoxWatch/DataStore/OTFCareKitStoreManager.swift @@ -0,0 +1,75 @@ +/* + Copyright (c) 2021, Hippocrates Technologies S.r.l.. All rights reserved. + + Redistribution and use in source and binary forms, with or without modification, + are permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation and/or + other materials provided with the distribution. + + 3. Neither the name of the copyright holder(s) nor the names of any contributor(s) may + be used to endorse or promote products derived from this software without specific + prior written permission. No license is granted to the trademarks of the copyright + holders even if such marks are included in this software. + + 4. Commercial redistribution in any form requires an explicit license agreement with the + copyright holder(s). Please contact support@hippocratestech.com for further information + regarding licensing. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, + INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, + OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY + OF SUCH DAMAGE. + */ + +import OTFCloudantStore +import WatchConnectivity +import WatchKit +import OTFCareKit +import OTFCareKitStore +import Combine + +class OTFCareKitStoreManager: NSObject { + static var shared = OTFCareKitStoreManager() + let peer = OTFWatchConnectivityPeer() + var cloudantStore: OTFCloudantStore! + var session: SessionManager! + var synchronizedStoreManager: OCKSynchronizedStoreManager! + var coordinator: OCKStoreCoordinator = { + let coordinator = OCKStoreCoordinator() + return coordinator + }() + + override init() { + let store = try? WatchStoreService.shared.currentStore(peer: self.peer) + guard let cloudantStore = store else { return } + self.cloudantStore = cloudantStore + + self.coordinator.attach(store: self.cloudantStore) + synchronizedStoreManager = OCKSynchronizedStoreManager(wrapping: coordinator) + + peer.automaticallySynchronizes = true + session = SessionManager(peer: peer, store: cloudantStore) + + let subscriber = Subscribers.Sink { _ in + } receiveValue: { _ in + store?.synchronize(target: Target.mobile, completion: { _ in }) + } + + synchronizedStoreManager.notificationPublisher.receive(subscriber: subscriber) + } + + func wipe() throws { + try cloudantStore.datastoreManager.deleteDatastoreNamed("local_db") + } +} diff --git a/OTFMagicBoxWatch/DataStore/WatchStoreService.swift b/OTFMagicBoxWatch/DataStore/WatchStoreService.swift new file mode 100644 index 00000000..f0a5abc1 --- /dev/null +++ b/OTFMagicBoxWatch/DataStore/WatchStoreService.swift @@ -0,0 +1,48 @@ +/* + Copyright (c) 2021, Hippocrates Technologies S.r.l.. All rights reserved. + + Redistribution and use in source and binary forms, with or without modification, + are permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation and/or + other materials provided with the distribution. + + 3. Neither the name of the copyright holder(s) nor the names of any contributor(s) may + be used to endorse or promote products derived from this software without specific + prior written permission. No license is granted to the trademarks of the copyright + holders even if such marks are included in this software. + + 4. Commercial redistribution in any form requires an explicit license agreement with the + copyright holder(s). Please contact support@hippocratestech.com for further information + regarding licensing. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, + INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, + OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY + OF SUCH DAMAGE. + */ + +import Foundation +import OTFCloudantStore + +public class WatchStoreService { + public static let shared = WatchStoreService() + + /** + - Description: It will return an instance of OTFCloudantStore initialized with the default db name `local_db` + - Returns: Fully initialized OTFCloudantStore object. + */ + public func currentStore(peer: OTFWatchConnectivityPeer) throws -> OTFCloudantStore { + return try OTFCloudantStore(storeName: "local_db", remote: peer) + } +} diff --git a/OTFMagicBoxWatch/Extensions/Extension+Notification.swift b/OTFMagicBoxWatch/Extensions/Extension+Notification.swift new file mode 100644 index 00000000..57191bf6 --- /dev/null +++ b/OTFMagicBoxWatch/Extensions/Extension+Notification.swift @@ -0,0 +1,41 @@ +/* + Copyright (c) 2021, Hippocrates Technologies S.r.l.. All rights reserved. + + Redistribution and use in source and binary forms, with or without modification, + are permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation and/or + other materials provided with the distribution. + + 3. Neither the name of the copyright holder(s) nor the names of any contributor(s) may + be used to endorse or promote products derived from this software without specific + prior written permission. No license is granted to the trademarks of the copyright + holders even if such marks are included in this software. + + 4. Commercial redistribution in any form requires an explicit license agreement with the + copyright holder(s). Please contact support@hippocratestech.com for further information + regarding licensing. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, + INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, + OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY + OF SUCH DAMAGE. + */ + +import Foundation + +extension NSNotification.Name { + static let synced = NSNotification.Name("synced") + static let databaseSync = NSNotification.Name("databaseSync") + static let userLoggedOut = NSNotification.Name("userLoggedOut") +} diff --git a/OTFMagicBoxWatch/Extensions/Extension+OCKTask.swift b/OTFMagicBoxWatch/Extensions/Extension+OCKTask.swift new file mode 100644 index 00000000..a7c35768 --- /dev/null +++ b/OTFMagicBoxWatch/Extensions/Extension+OCKTask.swift @@ -0,0 +1,86 @@ +/* + Copyright (c) 2021, Hippocrates Technologies S.r.l.. All rights reserved. + + Redistribution and use in source and binary forms, with or without modification, + are permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation and/or + other materials provided with the distribution. + + 3. Neither the name of the copyright holder(s) nor the names of any contributor(s) may + be used to endorse or promote products derived from this software without specific + prior written permission. No license is granted to the trademarks of the copyright + holders even if such marks are included in this software. + + 4. Commercial redistribution in any form requires an explicit license agreement with the + copyright holder(s). Please contact support@hippocratestech.com for further information + regarding licensing. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, + INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, + OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY + OF SUCH DAMAGE. + */ + +import OTFCareKitStore +import OTFCareKit + +private struct GroupIdentifierKeys: Codable { + var viewType: TaskStyle = .simple +} + +extension OCKTask { + private var groupIdentifierKeys: GroupIdentifierKeys { + guard let groupIdentifier = groupIdentifier else { return GroupIdentifierKeys() } + let data = Data(groupIdentifier.utf8) + let keys = try? JSONDecoder().decode(GroupIdentifierKeys.self, from: data) + return keys ?? GroupIdentifierKeys() + } + + var viewType: TaskStyle { + return groupIdentifierKeys.viewType + } +} + +enum TaskStyle: String, CaseIterable, Codable { + case simple, instruction, buttonLog + case grid, checklist + case labeledValue = "labeled value", numericProgress = "Numeric Progress" + var rawValue: String { + switch self { + case .simple: + return "Simple" + case .instruction: + return "instruction" + case .buttonLog: + return "buttonLog" + case .grid: + return "grid" + case .checklist: + return "checklist" + case .labeledValue: + return "labeled value" + case .numericProgress: + return "Numeric Progress" + } + } + + var supportsSwiftUI: Bool { + guard #available(iOS 14, *) else { return false } + + switch self { + case .simple, .instruction, .labeledValue, .numericProgress: return true + case .grid, .checklist, .buttonLog: return false + } + } +} diff --git a/OTFMagicBoxWatch/Managers/SessionManager.swift b/OTFMagicBoxWatch/Managers/SessionManager.swift new file mode 100644 index 00000000..b91625ab --- /dev/null +++ b/OTFMagicBoxWatch/Managers/SessionManager.swift @@ -0,0 +1,109 @@ +/* + Copyright (c) 2021, Hippocrates Technologies S.r.l.. All rights reserved. + + Redistribution and use in source and binary forms, with or without modification, + are permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation and/or + other materials provided with the distribution. + + 3. Neither the name of the copyright holder(s) nor the names of any contributor(s) may + be used to endorse or promote products derived from this software without specific + prior written permission. No license is granted to the trademarks of the copyright + holders even if such marks are included in this software. + + 4. Commercial redistribution in any form requires an explicit license agreement with the + copyright holder(s). Please contact support@hippocratestech.com for further information + regarding licensing. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, + INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, + OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY + OF SUCH DAMAGE. + */ + +import OTFCloudantStore +import WatchConnectivity +import WatchKit +import OTFCareKitStore +import OTFCareKit + +class SessionManager: NSObject, WCSessionDelegate { + + fileprivate(set) var peer: OTFWatchConnectivityPeer! + fileprivate(set) var store: OTFCloudantStore! + @Published var tasks = [OCKAnyTask]() + + init(peer: OTFWatchConnectivityPeer!, store: OTFCloudantStore!) { + super.init() + self.peer = peer + self.store = store + WCSession.default.delegate = self + WCSession.default.activate() + } + + func session(_ session: WCSession, + activationDidCompleteWith activationState: WCSessionActivationState, + error: Error?) { + + print("New session state: \(activationState)") + if let error { + print("Error is \(error.localizedDescription)") + } + + switch activationState { + case .activated: + print("WCSession activated successfully") + DispatchQueue.main.asyncAfter(deadline: .now() + 1.0) { + self.store.synchronize { error in + print(error?.localizedDescription ?? "Successful sync!") + DispatchQueue.main.async { + NotificationCenter.default.post(name: .databaseSync, object: nil) + } + } + } + + case .inactive: + print("Unable to activate the WCSession. Error: \(error?.localizedDescription ?? "--")") + case .notActivated: + print("Unexpected .notActivated state received after trying to activate the WCSession") + @unknown default: + print("Unexpected state received after trying to activate the WCSession") + } + } + + func session(_ session: WCSession, + didReceiveMessage message: [String: Any], + replyHandler: @escaping ([String: Any]) -> Void) { + print("Did receive message from MOBILE APP!") + if message[databaseSyncedKey] as? String != nil { + self.store.synchronize { error in + print(error?.localizedDescription ?? "Successful sync!") + DispatchQueue.main.async { + NotificationCenter.default.post(name: .databaseSync, object: nil) + } + } + } else if message["userNotLoggedIn"] as? String != nil { + // Wipe out data + self.store.deleteRecords { _ in + DispatchQueue.main.async { + NotificationCenter.default.post(name: .userLoggedOut, object: nil) + } + } + } else { + peer.reply(to: message, store: store) { reply in + replyHandler(reply) + } + } + } +} diff --git a/OTFMagicBox Watch Watch App/OTFMagicBox Watch Watch App.entitlements b/OTFMagicBoxWatch/OTFMagicBoxWatch.entitlements similarity index 87% rename from OTFMagicBox Watch Watch App/OTFMagicBox Watch Watch App.entitlements rename to OTFMagicBoxWatch/OTFMagicBoxWatch.entitlements index dab226cc..e3ba5ffc 100644 --- a/OTFMagicBox Watch Watch App/OTFMagicBox Watch Watch App.entitlements +++ b/OTFMagicBoxWatch/OTFMagicBoxWatch.entitlements @@ -5,7 +5,9 @@ com.apple.developer.healthkit com.apple.developer.healthkit.access - + + health-records + com.apple.developer.healthkit.background-delivery diff --git a/OTFMagicBoxWatch/OTFMagicBoxWatch.swift b/OTFMagicBoxWatch/OTFMagicBoxWatch.swift new file mode 100644 index 00000000..34bcdbdd --- /dev/null +++ b/OTFMagicBoxWatch/OTFMagicBoxWatch.swift @@ -0,0 +1,44 @@ +/* + Copyright (c) 2021, Hippocrates Technologies S.r.l.. All rights reserved. + + Redistribution and use in source and binary forms, with or without modification, + are permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation and/or + other materials provided with the distribution. + + 3. Neither the name of the copyright holder(s) nor the names of any contributor(s) may + be used to endorse or promote products derived from this software without specific + prior written permission. No license is granted to the trademarks of the copyright + holders even if such marks are included in this software. + + 4. Commercial redistribution in any form requires an explicit license agreement with the + copyright holder(s). Please contact support@hippocratestech.com for further information + regarding licensing. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, + INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, + OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY + OF SUCH DAMAGE. + */ + +import SwiftUI + +@main +struct OTFMagicBoxWatch: App { + var body: some Scene { + WindowGroup { + ContentView() + } + } +} diff --git a/OTFMagicBox Watch Watch App/Preview Content/Preview Assets.xcassets/Contents.json b/OTFMagicBoxWatch/Preview Content/Preview Assets.xcassets/Contents.json similarity index 100% rename from OTFMagicBox Watch Watch App/Preview Content/Preview Assets.xcassets/Contents.json rename to OTFMagicBoxWatch/Preview Content/Preview Assets.xcassets/Contents.json diff --git a/OTFMagicBoxWatch/Views/ActivityIndicatorView.swift b/OTFMagicBoxWatch/Views/ActivityIndicatorView.swift new file mode 100644 index 00000000..3c17c6d7 --- /dev/null +++ b/OTFMagicBoxWatch/Views/ActivityIndicatorView.swift @@ -0,0 +1,44 @@ +/* + Copyright (c) 2021, Hippocrates Technologies S.r.l.. All rights reserved. + + Redistribution and use in source and binary forms, with or without modification, + are permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation and/or + other materials provided with the distribution. + + 3. Neither the name of the copyright holder(s) nor the names of any contributor(s) may + be used to endorse or promote products derived from this software without specific + prior written permission. No license is granted to the trademarks of the copyright + holders even if such marks are included in this software. + + 4. Commercial redistribution in any form requires an explicit license agreement with the + copyright holder(s). Please contact support@hippocratestech.com for further information + regarding licensing. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, + INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, + OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY + OF SUCH DAMAGE. + */ + +import SwiftUI + +struct ActivityIndicatorView: View { + var body: some View { + ProgressView { + Text("Loading...") + .font(.system(size: 10)) + } + } +} diff --git a/OTFMagicBoxWatch/Views/CareKitListView.swift b/OTFMagicBoxWatch/Views/CareKitListView.swift new file mode 100644 index 00000000..c49e5cb0 --- /dev/null +++ b/OTFMagicBoxWatch/Views/CareKitListView.swift @@ -0,0 +1,110 @@ +/* + Copyright (c) 2021, Hippocrates Technologies S.r.l.. All rights reserved. + + Redistribution and use in source and binary forms, with or without modification, + are permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation and/or + other materials provided with the distribution. + + 3. Neither the name of the copyright holder(s) nor the names of any contributor(s) may + be used to endorse or promote products derived from this software without specific + prior written permission. No license is granted to the trademarks of the copyright + holders even if such marks are included in this software. + + 4. Commercial redistribution in any form requires an explicit license agreement with the + copyright holder(s). Please contact support@hippocratestech.com for further information + regarding licensing. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, + INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, + OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY + OF SUCH DAMAGE. + */ + +import SwiftUI +import Contacts +import OTFCareKit +import OTFCareKitUI +import OTFCareKitStore +import WatchConnectivity + +struct CareKitListView: View { + let carekitstore = OTFCareKitStoreManager.shared + @State var tasks = [OCKTask]() + @State var userLoggedOut = false + @State var isLoading = true + + var body: some View { + VStack { + if isLoading { + VStack(alignment: .center) { + ActivityIndicatorView() + } + } else { + if userLoggedOut { + VStack(alignment: .center) { + Text("Please log in on your phone app to see the tasks!") + .padding(.init(top: 5, leading: 15, bottom: 5, trailing: 15)) + } + } else { + if tasks.isEmpty { + VStack(alignment: .center) { + Text("No task for today!") + .padding(.init(top: 5, leading: 15, bottom: 5, trailing: 15)) + } + } else { + List { + ForEach(tasks, id: \.id) { task in + SimpleTaskView(taskID: task.id, eventQuery: .init(for: task.effectiveDate), storeManager: carekitstore.synchronizedStoreManager) + } + } + } + } + } + }.onAppear { + getDailyTasks() + } + .onReceive(NotificationCenter.default.publisher(for: .databaseSync)) { _ in + userLoggedOut = false + isLoading = true + getDailyTasks() + } + .onReceive(NotificationCenter.default.publisher(for: .userLoggedOut)) { _ in + userLoggedOut = true + } + } + + func getDailyTasks() { + carekitstore.cloudantStore.fetchTasks { result in + switch result { + case .failure(_): + isLoading = false + case .success(let data): + tasks = [] + let todayTasks: [OCKTask] = data.filter({ $0.schedule.exists(onDay: Date()) }) + print("message from peer! \(todayTasks)") + DispatchQueue.main.async { + self.tasks = todayTasks + isLoading = false + } + } + } + } +} + +struct CareKitListView_Previews: PreviewProvider { + static var previews: some View { + CareKitListView() + } +} diff --git a/Podfile b/Podfile index 13aa5940..27bd5cd5 100644 --- a/Podfile +++ b/Podfile @@ -1,37 +1,30 @@ # Uncomment the next line to define a global platform for your project +source 'https://cdn.cocoapods.org' +source 'https://github.com/TheraForge/OTFCocoapodSpecs' -target 'OTFMagicBox' do - use_frameworks! - - source 'https://cdn.cocoapods.org' - source 'https://github.com/TheraForge/OTFCocoapodSpecs' +use_frameworks! - pod 'OTFToolBox/CareHealth', '1.0.3-beta' - pod 'GoogleSignIn', '~> 7.0.0' +target 'OTFMagicBox' do + platform :ios, '14.6' - platform :ios, '14.0' + pod 'OTFToolBox/CareHealth', '1.0.4-beta' + pod 'GoogleSignIn', '~> 7.0.0' end -target 'OTFMagicBox Watch Watch App' do - use_frameworks! - - source 'https://cdn.cocoapods.org' - source 'https://github.com/TheraForge/OTFCocoapodSpecs' - - # Only include the necessary dependency for the watchOS target - pod 'OTFCareKit/Care', '2.0.2-beta.3' - - platform :watchos, '7.0' +target 'OTFMagicBoxWatch' do + platform :watchos, '8.0' + + pod 'OTFCloudantStore/CloudantCareHealth', '1.0.4-beta' + pod 'OTFCareKit/CareHealth', '2.0.2-beta.4' end - post_install do |installer| - installer.generated_projects.each do |project| - project.targets.each do |target| - target.build_configurations.each do |config| - config.build_settings['IPHONEOS_DEPLOYMENT_TARGET'] = '13.0' - config.build_settings['WATCHOS_DEPLOYMENT_TARGET'] = '7.0' - end - end - end + installer.generated_projects.each do |project| + project.targets.each do |target| + target.build_configurations.each do |config| + config.build_settings['IPHONEOS_DEPLOYMENT_TARGET'] = '14.0' + config.build_settings['WATCHOS_DEPLOYMENT_TARGET'] = '8.0' + end + end + end end diff --git a/README.md b/README.md index dfea1778..4d4340cd 100644 --- a/README.md +++ b/README.md @@ -8,17 +8,36 @@ For more details on the features of the SDK and on the TheraForge Cloud setup pr ## Change Log
+ Release 1.0.4-beta + + - **New Styling Structure** + - Thanks to the new OTFStyle structure, the app can now adopt a user-selected theme in the `AppSysParameter.yml` file, which is applied equally to custom components inside the app, CareKitUI components and OTFDesignSystem components. + - Added a new OTFStyle environment object and modifier, allowing developers to apply the selected custom style to all the components of the app, whether they are from the TheraForge design system or from CareKit; developers can even apply them to custom views they may want to create. + - Removed the appTheme property from AppSysParameter and replaced it with styles, an array of styles that developers can use to apply to the app or to add their own custom styles that they can create. + - Removed the color properties from the DesignConfig model, as the colors are now fetched from appStyle. + - **Updated OTFToolBox** + - Updated OTFToolbox version to 1.0.4-beta, which corresponds to an updated version of OTFToolBox that includes the new OTFDesignSystem framework. + - **Bug Fixes** + - Fixed a bug on the ScheduleViewController in which the background would not reach the bottom area of the screen outside the safe area. + - Fixed an issue in which, when scrolling a list on the ScheduleViewController list, the contents of the list would go over the navigation bar. This has been fixed by applying an opaque white background to the navigation bar. + - **WatchOS support** + - Improved watchOS support. + - **Watch Synchronisation** + - This release synchronizes daily tasks between iOS and watchOS and updates their outcomes in both local stores as well as in the cloud. +
+ +
Release 1.0.3-beta - - **End-to-End File Encryption (TheraForge CryptoBox)** - Added class-based end-to-end encryption (E2EE) and integrity verification technology to protect sensitive user data at rest or in transit with the XChaCha20Poly1305 algorithm, which is independent from and in addition to TLS and native storage crypto. This creates two independent layers of highly secure data cryptography. - - **Biometric Authentications** - The app supports biometric authentication (FaceID or TouchID), which provides a secure and user-friendly way to authenticate users. - - **Sign in Using Password AutoFill** - With just a few taps users can create and save new passwords or log in to an existing account - - **Manage Documents** - MagicBox allows you to upload, download, re-name and delete different documents. User profile pictures and consent forms are saved as documents. - - **Improved Theme Customization Using YAML File** + - **End-to-End File Encrption (TheraForge CryptoBox)** + Added end-to-end encrption feature which prevents third parties from accessing data while it's being transferred from one user to another + - **Biometric authentications** + The app supports biometric authentication which provides secure and user-friendly way to authenticate users + - **Password-less login, use auto-fill sign in** + With just a few taps, users can create and save new passwords or log in to an existing account + - **Manage documents** + MagicBox allows you to upload, donwload, re-name and delete different docuements. User's profile picture and consent form are saved as documents. + - **Improved theme customization using yml file** - Font - Font size - Font weight @@ -27,17 +46,17 @@ For more details on the features of the SDK and on the TheraForge Cloud setup pr - **Apple Watch Demo App** - Added a companion WatchOS app for MagicBox - Allow users to check and manage tasks for the current day from their Apple Watch - - **Enhanced Styling of the Profile Screen** - - **New Network Indicator** + - **Enhanced styling of the Profile screen** + - **New network indicator** - Implemented a networking indicator to provide a visual representation of the connection status to TheraForge CloudBox servers - - **New Consent Documents Layout Section in Profile Screen** + - **New Consent documents layout section in Profile screen** - **Accessibility Enhancements** - Enhanced VoiceOver support for the Bold Text and Invert Colors system options for enhanced accessibility - Added support for Bold Text and Invert Colors for enhanced accessibility options - **Design and Assets** - - Incorporated new assets, including more than 360 images and dozens of additional icons/non-Apple SF Symbols (such as social icons), ready to be used inside the iOS app + - Incorporated new assets, including more than 360 images and dozens of additional icons/SF Symbols, ready to be used inside the iOS app - **Compatibility Updates** - - Increased the iOS target version to iOS 14.5 for broader device compatibility and feature support, including all SF Symbols 2.2 + - Increased the iOS target version to iOS 14.5 for broader device compatibility and feature support
@@ -88,8 +107,8 @@ These are its primary characteristics: * Care plan management using Apple's Carekit framework. * Monitoring of health data with Apple's HealthKit framework. * Automatic data synchronization across the Cloud (a la Dropbox) using the OTFToolBox SDK. -* Support for various popular and innovative technologies out of the box: manual and biometric user authentication (Sign in with Apple in addition to standard login) with OAuth2, HIPAA- and GDPR-compliant traffic encryption at rest and in transit (uses TLS 1.3 crypto), real-time app notifications using HTTP2 Server-Sent Events (SSE), end-to-end file cryptography and integrity verification, etc. -* SF Symbols 2.2 support (available in iOS 14.5 and watchOS 7.4, and later releases) +* Support for various popular technologies out of the box: user authentication (Sign in with Apple in addition to standard login) with OAuth2, HIPAA- abd GDPR-compliant traffic encryption at rest and in transit (uses TLS 1.3 crypto), app notifications using HTTP 2 Server-Sent Events (SSE), etc. +* SF Symbols 1.1 support (available on iOS/iPadOS 13 and watchOS 6, and later releases). * CI/CD support via GitHub Actions. @@ -107,7 +126,7 @@ When a user launches an app for the first time, the onboarding process presents ## Consent -The informed consent is the process of a user granting authorization to an application to access specific resources on their behalf (for example, health sensors) and/or to perform certain actions (for example, as part of a medical study). Users will be asked for consent to allow access to their personal data. +The informed consent is the process of a user granting authorization to an application to access specific resources on their behalf (for exammple, health sensors) and/or to perform certain actions (for example, as part of a medical study). Users will be asked for consent to allow access to their personal data.

@@ -119,8 +138,8 @@ The consent form contains the description of the items included in the applicati

-## Consent Document in the Profile Section -In MagicBox users can check out their consent form in the profile screen by tapping on the Consent Documents section, as shown in the figure below. +## Add consent document page in profile section +In MagicBox user can now see their consent form in their profile screen by clicking on the Consent documents section.

@@ -140,23 +159,23 @@ User login credentials are securely stored in the device’s keychain.

-If you want to enable support for Google Sign-in, in Xcode add the GIDClientID parameter string in the info.plist file as shown in the figure. +Addition, add GIDClientID into info.plist file to enable Google login.

-## Biometric Authentication -MagicBox supports biometric authentication: a secure and user-friendly way to authenticate users in iOS by using Face ID or Touch ID, as shown in the figure below. +## Biometric authentications +The App support Biometric authentication. A secure and user-friendly way to authenticate users in iOS applications with the introduction of Face ID and Touch ID. -Users can authenticate by using Face ID or Touch ID. +User can authenticate by using their Face ID or Touch ID. -

+

-## Sign in Using Password AutoFill -MagicBox supports iOS Password AutoFill. With just a few taps, users can create and save new passwords or log in to an existing account. Users don’t need to enter their password, the system handles everything. It also encourages user to select strong passwords hence making user accounts more secure. +## Password-less Login, Autofill Sign in +MagicBox includes AutoFill feature. With just a few taps, users can create and save new passwords or log in to an existing account. Users don’t need to enter their password, the system handles everything. It also encourages user to select strong passwords hence making user account more secure. -

-

+

+

## Passcode @@ -182,15 +201,19 @@ Contacts are cards that contain doctor and family member details, such as addres

-## End-to-end File Encryption (TheraForge CryptoBox) -MagicBox supports class-based end-to-end file encryption using the secure and robust XChaCha20Poly1305 algorithm. It provides an additional layer of secure storage and additional security for communication that prevents third parties from accessing confidential data. Different classes can be used to provide selective access privileges to documents (the default class provides access to all the contacts explicitly approved by the user). Class-based encrypted files can only be decrypted by the intended receiver(s). +## End-to-end File Encrption (TheraForge CryptoBox) +MagicBox includes end-to-end encryption on document sending and receiving by the user. It provides secure storage and additional security for communication that prevents third parties from accessing confidential data. + +Encrypted files can only be decrypted by the intended receiver(s). + ## User Profile -In the profile section, users can manage their current session, edit their profile, contact support or withdraw from a study/project. + +In the profile section, the user can manage his current session, edit their profile, contact support and withdraw from a study.

-There's also a network indicator on top of the user's profile picture, indicating whether the user currently has a connection to the TheraForge CloudBox servers and it even indicates if it's connected via cellular or Wi-Fi. +There's also a network indicator on top of the user's profile picture, indicating whether the user currently has a connection to the TheraForge CloudBox servers and it even indicates if it's connected via cellular or though a wi-fi hotspot. ## TheraForge Secure Cloud with Sync Support @@ -236,9 +259,9 @@ MagicBox app is designed to be compatible with the iOS accessibility features, e |:----------:|:----------:|:----------:| | **Voice Over** | **Voice Control** | **Bold Text** | -## Apple Watch Demo App +## Apple Watch App -The MagicBox Demo Apple Watch App is designed as a companion app for the iPhone MagicBox application. This app is intended for users to quickly glance through their tasks and activities for the day conveniently on their Apple Watch. +The MagicBox Apple Watch App is designed as a companion app for the iPhone MagicBox application. This app is intended for users to quickly glance through their tasks and activities for the day conveniently on their Apple Watch. ![Apple Watch Demo App](Docs/91-apple-watch-demo.png) @@ -252,6 +275,22 @@ The app leverages [OTFCareKit](https://github.com/TheraForge/OTFCareKit) to fetc The Apple Watch app also supports Accessibility features, such as VoiceOver, Bold and Dynamic Text, ensuring that all users, regardless of their abilities, can use our app comfortably. +### Watch Synchronisation ### + - Synchronizes daily tasks from iOS to watchOS and updates their outcomes in both stores. + ```swift + // .mobile: synchronise watchOS store from IOS app + // .watchOS: fetch IOS app store from watchOS + // .watchAppUpdate: notify other device about data update + + CloudantSyncManager.shared.cloudantStore?.synchronize(target: .mobile, completion: { error in + if let error = error { + print(error) + } else { + OTFLog("Synced successfully!") + } + }) + ``` + ## Assets MagicBox includes a variety of assets, such as illustrations, icons, and glyphs, that are available for customization within the app. You can preview all the available assets on our [asset gallery](https://tfmart.github.io/OTFMagicBox/). @@ -263,7 +302,7 @@ You can also check the available assets locally on your machine by opening your ``` To use any of these assets in your project, simply follow these steps: -1. Locate the Images resource in Xcode's sidebar as shown in the figure below +1. Locate the Images resouce in Xcode's sidebar as shown in the figure below

2. Choose an image that you want to use in your application @@ -280,7 +319,7 @@ To review any of the optional assets to select and use them in the code, follow Image("doctor4") ``` -Any installed assets can also be used in the YAML customization files. For example, if we want to use this image on a custom section in the onboarding section of the app: +Any installed assets can also be used in the YAML customization files. For exameple, if we want to use this image on a custom section in the onboarding section of the app: ```yaml summary: "This is custom section." @@ -436,7 +475,7 @@ git clone https://github.com/TheraForge/OTFMagicBox.git -Then change the directory to the newly created OTFMagicBox subdirectory: +Then change the directory to the newly-created OTFMagicBox subdirectory: ``` cd OTFMagicBox @@ -526,38 +565,176 @@ Example: change $(PRODUCT_NAME) to “My Digital App”. ![Alt text](Docs/14-Bundle.png) -## Modify the Style/Design +## Modifying the App's Style + +The appearance of the app is determined by a flexible styling system defined within the AppSysParameters.yml file. This system is based on [CareKit’s custom styling](https://github.com/carekit-apple/CareKit?tab=readme-ov-file#styling). + +It allows you to personalize the app’s style (i.e., theme) according to your preferences, and you can easily switch between different predefined styles. + +### Style Configuration -You can change the tint color, label colors, font type, and size to customize the look of your application. +In the `AppSysParameters.yml` file, you'll discover a list of available styles, each representing a unique visual theme for your app. These styles are referred to as "styles," and they include preset configurations for colors, fonts, and other design elements. -### Colors +#### List of Themes -To customize the colors, please choose the appropriate color codes according to the Human Interface Guidelines from Apple. Refer to [Apple's Human Interface Guidelines](https://developer.apple.com/design/human-interface-guidelines/ios/visual-design/color) for more information. +1. Custom Style + - _Name_: `customStyle` + - _Description_: This style provides a clean and customizable look. Use it as a starting point to create your unique app style. + +2. Health App Style + - _Name_: `healthStyle` + - _Description_: This style provides colors and visuals based on Apple's Health app + +3. CareKit Style Style + - _Name_: `careKitStyle` + - _Description_: A default theme that matches the default CareKit look and feel. It is used as a fallback in case a specific style is not selected. + +#### Selected Style + +The `selectedStyle` field determines the active style. You can switch between styles by updating this field with the desired style name. ```yml # AppSysParameters.yml -designConfig: - # Offset value. - - name: "offset" - textValue: "20" - - # Color codes +# Select the active style +selectedStyle: "customStyle" +``` + +### Creating a New Style + +To create a new style, add a new entry to the styles array with the desired properties. Provide a unique name for the style. + +When picking colors for your custom style, refer to [Apple's Human Interface Guidelines](https://developer.apple.com/design/human-interface-guidelines/ios/visual-design/color) for valid UIKit color values. See: + +1. Semantic UI colors: https://developer.apple.com/documentation/uikit/uicolor/ui_element_colors +2. Adaptable system grey shades: https://developer.apple.com/documentation/uikit/uicolor/standard_colors#3281252 +3. Adaptable system colors: https://developer.apple.com/documentation/uikit/uicolor/standard_colors#3174530 +4. Fixed colors: https://developer.apple.com/documentation/uikit/uicolor/standard_colors#3174519 +5. Adaptable colors in Dark Mode: https://sarunw.com/posts/dark-color-cheat-sheet/#cheat-sheet + +```yml +# AppSysParameters.yml + +# Add a new style entry with a unique name +styles: + - name: "newCustomStyle" + # Background color of the app + backgroundColor: "customBackgroundColor" - # Please choose the colors according to Human Interface Guidelines from Apple. - # Refer here https://developer.apple.com/design/human-interface-guidelines/ios/visual-design/color + # Text color used throughout the app + textColor: "customTextColor" - - name: "tintColor" - textValue: "Blue" - - name: "label" - textValue: "Teal" - - name: "secondaryLabel" - textValue: "Brown" - # ... - + # Color of separators between UI elements + separatorColor: "customSeparatorColor" + + # ... (add other properties) +``` + +### Updating Style Properties + +Update the style values to customize its appearance. For example, to update the text color in the "customStyle" theme to green: + +```yml +# AppSysParameters.yml + +# Locate the desired style in the styles array +styles: + - name: "customStyle" + # Update the text color property to "systemGreen" + textColor: "systemGreen" + + # ... (other properties) +``` + +### Automatic Application to Components + +Updating the style values in the selected style section of the YAML file will dynamically reflect on the layout and appearance of various components within the app, including CareKit components, ensuring a consistent and unified visual experience throughout the application. + +For example, if we set the `buttonTextColor` property to `systemRed`, we'll see that the tint and button colors of the app is set to a Red color: + +```yml +# AppSysParameters.yml + +# Locate the selected style in the YAML file +selectedStyle: "customStyle" + +# Update properties within the selected style to customize the appearance +customStyle: + # ... + # Set the text color for buttons to "systemRed" + buttonTextColor: "systemRed" + # ... ``` -### Fonts +| ![Info Card](Docs/93-style-info-card.png) | ![Button Log](Docs/94-style-buttonlog.png) | ![Checklist](Docs/95-style-checklist.png) | ![Numeric Data](Docs/96-style-numeric.png) | +|:-:|:-:|:-:|:-:| + +### Apply Style to New Views + +When creating custom views, you can seamlessly integrate the colors of the current app style by fetching them from the `.otfdsStyle` environment variable. + +For example, if we have the following colors on `customStyle` + +```yml +# AppSysParameters.yml + +# Locate the selected style in the YAML file +selectedStyle: "customStyle" + +# Set specific colors within the selected style to be fetched in views +customStyle: + # Set the text color to "systemGreen" + textColor: "systemGreen" + + # Set the button text color to "systemGreen" + buttonTextColor: "systemGreen" + + # ... (add other properties)ç +``` + +And read them through a View with the `.otfdsStyle` environment variable: + +```swift +struct ContentView: View { + @Environment(\.otfdsStyle) var style: OTFDesignStyler? + + var body: some View { + VStack(spacing: 16) { + Text("Secure your account") + .font(.title) + .foregroundColor(style?.color.label) + Text("Setup two-factor authentication to protect your account and your data") + .foregroundColor(style?.color.secondaryLabel) + + Button("Continue") { + // ... + } + .buttonStyle(.otfPrimary) + .foregroundColor(style?.color.primaryButton) + } + } +} +``` + +The following view would be generated: + + + +Note that attempting to access the current style through the Environment property is not possible outside of a View. To read the style values outside of a View, you can use the `appStyle` property of `YmlReader`: + +```swift +class SecureAccountViewModel { + var appStyle = YmlReader().appStyle + + // ... +} +``` + +### Default Theme + +In case the style under selectedStyle is omitted or fails to be read, the app will gracefully default to a predefined style called careKitStyle. + +## Fonts You can customize the fonts used in your application, including support for the Bold Text accessibility feature. @@ -680,8 +857,7 @@ registration: Go to the Login section in the `ModuleAppSysParameter.yml` file and customize the title and the description. -If you want to use the *Sign up With Google* feature, then change the **showGoogleSignin** key to `true`. Then click on the `Info.plist` file. Xcode will show the contents of the `Info.plist` file as a list of settings (key-value pairs). -Go to the row with the key named “GIDClientID”. Click on the Value column of that row and change the value to the one required by your application which you get from the Google developer portal. Then find the "CFBundleURLSchemes" key in the Info.plist file and add the URLSchemes value, which you can also get from the Google developer portal. Also add the URLSchemes value in the URL Types row as shown in the figure below. +If you want to use the *Sign up With Google* feature, then change the **showGoogleSignin** key to `true`. Then click on the `Info.plist` file. Xcode will show the contents of the `Info.plist` file as a list of settings (key-value pairs). Go to the row with the key named “GIDClientID” Click on the Value column of that row and change the value to your application which you get from the google developer portal. Find the "CFBundleURLSchemes" key in Info.plist file and add the URLSchemes in value you can also get this value from google developer portal. Also add the URLSchemes in URL Types as shown in the figure below. @@ -723,6 +899,30 @@ useCareKit: "true" ``` +## UserInfo Usage + +The Framework `OTFCareKitStore` contains the property *userInfo* in `OCKPatient.swift` class, When you launch the `OTFMagicBox` application you will get a user object of type `OCKPatient` throught which you will able to access the userInfo object and its properties stored in `OTFCareKitStore`. + +You can you *userInfo* object to store your own properties on user level, as an example if you want to store the information of selected theme of application you need to create the `struct` including the property you want to store, assign the data to the property of your `sturct`. As userInfo is a dictunary of type `[String: String]` thats why you need to create a string from for `stuct`, To create string encode your model using `JSONEncoder` that will return `data`, then decode this `data` to string using `UTF8` after that create a key in userInfo and assign that your newely created string to key. + +Then by using shared instance of `CareKitManager` call the method `updatePatient` as and assign the +updated `OCKPatient` object to `updatePatient` method as shown in the example below. + +``` +struct yourStruct { +let isDarkMode: Bool +} +``` + +``` +if let data = try? encoder.encode(your-struct) { + let stingData = String(decoding: data, as: UTF8.self) + user?.userInfo?["Your-Key"] = stingData + CareKitManager.shared.cloudantStore?.updatePatient(user!) +} +``` + + # Registration on Apple Developer Portal If you need to run an application on a physical device (like your personal iPhone) and/or if you need to use TestFlight, then you need to register on the Apple Developer Portal. @@ -731,28 +931,78 @@ Register your project in your Apple developer account by following [these steps] # Register a new API key -- You can register a new API key using this [portal](https://theraforge.org/admin/register). -- You need to add valid details into the given form. After a successful submission, you will be presented with a popup window that will show your registered API key. +- You can register a new API key using this [dashboard](https://stg.theraforge.org/admin/). +- You need to add valid details into the given form. After a successful submission, you will be presented with a popup window that will show your registerd API key. - Make sure to copy that API key and keep it in safe place. -- Once you have registered your API key, our support team will contact you for further assistance. +- Once you have registered your API key, our support team will contact you for further asssistance. - A dashboard (called AdminBox) is used to help clients to access and modify their API keys -- AdminBox can be accessed using the [portal](https://theraforge.org/admin/login). -- Once you log in to the dashboard (as admin), you will be able to see the API key details. +- AdminBox can be accessed using the [Portal](https://stg.theraforge.org/admin/login). +- Once a client is logged in to the dashboard (as admin), he/she will be able to see all API key details based upon their role. - Here are the screenshots for better understanding.

-You can register a new API key using this portal: +Register API key component:

-When a new API key is registered, you can review it and copy it in this popup window: -

-Display API key details: -

+When API key is registered: +

+Showing all API key details to admins: +

# Xcode Setup Set up the Xcode application with your Apple developer account information as [described here](XCODE-SETUP.md). + +# MVVM (Model View ViewModel) Architecture + +Whenever we start building a new application, this question always comes in our mind, which architecture pattern to choose for our new project. The most used architectural pattern in iOS is MVC. Most of the developers used the MVC pattern for their projects. Small projects work well with MVC, but when your project size starts increases, it starts making your source code messy. + +I always found the architecture pattern is good to use, but we should not strictly follow an architecture pattern in our project. Not every architecture pattern is good enough to give you everything, there are cons & pros of every architecture pattern. if we have a lot of modules in our project, we can decide the architecture pattern according to the module also. Some module suits well with MVVM, but maybe your new module will not work well with MVVM, so instead use another architecture pattern like MVP, VIPER. So we should not completely rely on a single architecture pattern, instead, we can check it according to the module also. + +So, in OTFMagicBox we are using MVVM as it fullfils all our requirements. +MVVM is the industry-recognized software architecture pattern that overcomes all drawbacks of MVP and MVC design patterns. +MVVM suggests separating the data presentation logic(Views or UI) from the core business logic part of the application. + +## The separate code layers of MVVM + +### 1. Model: +This layer is responsible for the abstraction of the data sources. Model and ViewModel work together to get and save the data. + +### 2. View: +The purpose of this layer is to inform the ViewModel about the user’s action. This layer observes the ViewModel and does not contain any kind of application logic. + +### 3. ViewModel: + It exposes those data streams which are relevant to the View. Moreover, it serves as a link between the Model and the View. + + Some impotent role played by MVVM. + +* ViewModel does not hold any kind of reference to the View. +* Many to-1 relationships exist between View and ViewModel. +* No triggering methods to update the View. + +For better understanding of MVVM architecture view [This Article](https://www.toptal.com/ios/swift-tutorial-introduction-to-mvvm) + +## Ways to Implement MVVM in the Project + +Recommended ways to implement MVVM design pattern in iOS projects: + +* Using RxJava for DataBinding. +* Using combine framework for DataBinding. + +# Combine Framework + +The Combine framework provides a declarative Swift API for processing values over time. These values can represent many kinds of asynchronous events. Combine declares publishers to expose values that can change over time, and subscribers to receive those values from the publishers. + +If you are using swiftUI than it is recommended to use combine framework. + +Here are some link to understand and learn how to use combine framework. + +The [Official Documentation](https://developer.apple.com/documentation/combine/receiving-and-handling-events-with-combine) to understand the combine. + +Also view [This Article](https://medium.com/apple-developer-academy-federico-ii/combine-and-swiftui-9888f7b111bf) for basic understanding of combine framework. + + # CI/CD Setup Configure your project using a CI and CD pipeline via GitHub Actions as [described here](/.github/CICD.md).