From 0ef5dbed648c373344fc6e522872a7bbef8a4a43 Mon Sep 17 00:00:00 2001 From: Adrit Rao <67882813+AdritRao@users.noreply.github.com> Date: Fri, 2 Aug 2024 01:04:15 -0700 Subject: [PATCH 1/3] Add checkbox functionality --- .../ConsentView/ConsentDocument+Export.swift | 9 ++- .../ConsentView/ConsentDocument.swift | 78 ++++++++++++++++++- ...boardingConsentMarkdownRenderingView.swift | 13 +++- .../OnboardingConsentMarkdownTestView.swift | 2 +- 4 files changed, 96 insertions(+), 6 deletions(-) diff --git a/Sources/SpeziOnboarding/ConsentView/ConsentDocument+Export.swift b/Sources/SpeziOnboarding/ConsentView/ConsentDocument+Export.swift index c18d198..783b99f 100644 --- a/Sources/SpeziOnboarding/ConsentView/ConsentDocument+Export.swift +++ b/Sources/SpeziOnboarding/ConsentView/ConsentDocument+Export.swift @@ -72,6 +72,10 @@ extension ConsentDocument { Text(markdown) .padding() + if checkboxSnapshot != nil { + Image(uiImage: checkboxSnapshot!) + } + Spacer() ZStack(alignment: .bottomLeading) { @@ -101,7 +105,10 @@ extension ConsentDocument { /// - Returns: The exported consent form in PDF format as a PDFKit `PDFDocument` @MainActor func export() async -> PDFDocument? { - let markdown = await asyncMarkdown() + var markdown = await asyncMarkdown() + if cleanedMarkdownData != nil { + markdown = cleanedMarkdownData! + } let markdownString = (try? AttributedString( markdown: markdown, diff --git a/Sources/SpeziOnboarding/ConsentView/ConsentDocument.swift b/Sources/SpeziOnboarding/ConsentView/ConsentDocument.swift index f40b0c0..d415cc6 100644 --- a/Sources/SpeziOnboarding/ConsentView/ConsentDocument.swift +++ b/Sources/SpeziOnboarding/ConsentView/ConsentDocument.swift @@ -57,7 +57,10 @@ public struct ConsentDocument: View { #endif @State var signatureSize: CGSize = .zero @Binding private var viewState: ConsentViewState - + @State private var checked: [String: Bool] = [:] + @State private var markdownStrings: [String] = [] + @State public var cleanedMarkdownData: Data? + @State public var checkboxSnapshot: UIImage? private var nameView: some View { VStack { @@ -91,6 +94,69 @@ public struct ConsentDocument: View { } } + private func extractMarkdownCB() async -> (cleanedMarkdown: String, checkboxes: [String]) { + let data = await asyncMarkdown() + let dataString = String(data: data, encoding: .utf8)! + + var result = [String]() + var start: String.Index? = nil + var cleanedMarkdown = "" + var isInBracket = false + + for (index, char) in dataString.enumerated() { + let currentIndex = dataString.index(dataString.startIndex, offsetBy: index) + + if char == "[" { + start = currentIndex + isInBracket = true + } else if char == "]", let start = start { + let end = currentIndex + let subInd = dataString.index(after: start) + let substring = String(dataString[subInd.. Date: Tue, 6 Aug 2024 19:30:23 -0700 Subject: [PATCH 2/3] Update ConsentDocument.swift --- .../ConsentView/ConsentDocument.swift | 219 +++++++++++------- 1 file changed, 132 insertions(+), 87 deletions(-) diff --git a/Sources/SpeziOnboarding/ConsentView/ConsentDocument.swift b/Sources/SpeziOnboarding/ConsentView/ConsentDocument.swift index d415cc6..add8973 100644 --- a/Sources/SpeziOnboarding/ConsentView/ConsentDocument.swift +++ b/Sources/SpeziOnboarding/ConsentView/ConsentDocument.swift @@ -12,7 +12,6 @@ import SpeziPersonalInfo import SpeziViews import SwiftUI - /// Display markdown-based consent documents that can be signed and exported. /// /// Allows the display markdown-based consent documents that can be signed using a family and given name and a hand drawn signature. @@ -58,9 +57,14 @@ public struct ConsentDocument: View { @State var signatureSize: CGSize = .zero @Binding private var viewState: ConsentViewState @State private var checked: [String: Bool] = [:] - @State private var markdownStrings: [String] = [] - @State public var cleanedMarkdownData: Data? @State public var checkboxSnapshot: UIImage? + @State public var allElements = [MarkdownElement]() + + public enum MarkdownElement: Hashable { + case signature(String) + case checkbox(String) + case text(String) + } private var nameView: some View { VStack { @@ -93,69 +97,84 @@ public struct ConsentDocument: View { Divider() } } - - private func extractMarkdownCB() async -> (cleanedMarkdown: String, checkboxes: [String]) { + + private func extractMarkdownCB() async -> ([MarkdownElement]) { let data = await asyncMarkdown() let dataString = String(data: data, encoding: .utf8)! - - var result = [String]() - var start: String.Index? = nil - var cleanedMarkdown = "" - var isInBracket = false - for (index, char) in dataString.enumerated() { - let currentIndex = dataString.index(dataString.startIndex, offsetBy: index) - - if char == "[" { - start = currentIndex - isInBracket = true - } else if char == "]", let start = start { - let end = currentIndex - let subInd = dataString.index(after: start) - let substring = String(dataString[subInd.. Date: Wed, 4 Sep 2024 12:48:41 -0700 Subject: [PATCH 3/3] Update ConsentDocument.swift --- .../ConsentView/ConsentDocument.swift | 150 ++++++++++++------ 1 file changed, 98 insertions(+), 52 deletions(-) diff --git a/Sources/SpeziOnboarding/ConsentView/ConsentDocument.swift b/Sources/SpeziOnboarding/ConsentView/ConsentDocument.swift index add8973..7b8f258 100644 --- a/Sources/SpeziOnboarding/ConsentView/ConsentDocument.swift +++ b/Sources/SpeziOnboarding/ConsentView/ConsentDocument.swift @@ -11,6 +11,7 @@ import PencilKit import SpeziPersonalInfo import SpeziViews import SwiftUI +import SpeziValidation /// Display markdown-based consent documents that can be signed and exported. /// @@ -56,13 +57,13 @@ public struct ConsentDocument: View { #endif @State var signatureSize: CGSize = .zero @Binding private var viewState: ConsentViewState - @State private var checked: [String: Bool] = [:] + public static var checked: [String: String] = [:] @State public var checkboxSnapshot: UIImage? @State public var allElements = [MarkdownElement]() public enum MarkdownElement: Hashable { case signature(String) - case checkbox(String) + case checkbox(String, [String]) case text(String) } @@ -98,24 +99,25 @@ public struct ConsentDocument: View { } } - private func extractMarkdownCB() async -> ([MarkdownElement]) { + private func extractMarkdownCB() async -> [MarkdownElement] { let data = await asyncMarkdown() let dataString = String(data: data, encoding: .utf8)! var elements = [MarkdownElement]() - var textBeforeCB = "" - - - var lines = dataString.split(separator: "\n", omittingEmptySubsequences: false) + var searchForOptions = false + var textCB = "" + var options: [String] = [] + + let lines = dataString.split(separator: "\n", omittingEmptySubsequences: false) print(lines) - + for line in lines { - print("plain line: "+line) - var trimmedLine = line.trimmingCharacters(in: .whitespaces) - print("trimmed line: "+trimmedLine) + print("plain line: " + line) + let trimmedLine = line.trimmingCharacters(in: .whitespaces) + print("trimmed line: " + trimmedLine) + if trimmedLine.hasPrefix("- [ ]") { - if !textBeforeCB.isEmpty { print("currentText:") print(textBeforeCB) @@ -123,56 +125,99 @@ public struct ConsentDocument: View { print(elements) textBeforeCB = "" } - - var textCB = trimmedLine.dropFirst(5).trimmingCharacters(in: .whitespaces) + + textCB = trimmedLine.dropFirst(5).trimmingCharacters(in: .whitespaces) print("task:") print(textCB) - elements.append(.checkbox(textCB)) + searchForOptions = true + + } else if searchForOptions { + print("search for options") + + if trimmedLine.hasPrefix(">") { + print("an option") + let option = trimmedLine.dropFirst(1).trimmingCharacters(in: .whitespaces) + options.append(option) + + } else { + searchForOptions = false + + if !options.isEmpty { + elements.append(.checkbox(textCB, options)) + options = [] + textCB = "" + } else { + elements.append(.checkbox(textCB, ["Yes", "No"])) + textCB = "" + } + + textBeforeCB += line + "\n" + } + } else { + searchForOptions = false textBeforeCB += line + "\n" } } + if !textBeforeCB.isEmpty { elements.append(.text(textBeforeCB)) } - + return elements } + struct CheckBoxView: View { - @Binding var isChecked: Bool let question: String - + let options: [String] + + @State private var elementSelected = "-" + @ValidationState private var validation + var body: some View { VStack { HStack { Text(question) .frame(maxWidth: .infinity, alignment: .leading) + Menu { - Button("Yes") { - withAnimation { - - isChecked = true - } - } - Button("No") { - withAnimation { - isChecked = false + ForEach(options, id: \.self) { theElement in + Button(theElement) { + withAnimation { + elementSelected = theElement + ConsentDocument.checked[question] = theElement + } } } } label: { - Text(isChecked ? "Yes" : "No") + Text(elementSelected) .font(.system(size: 17)) .padding(5) .background(Color.blue) .foregroundColor(.white) .cornerRadius(5) } - + .validate(elementSelected != "-", message: "Please select an option") + } + + if let firstValidationError = validation.allDisplayedValidationResults.first { + Text(firstValidationError.message) + .foregroundColor(.red) + .font(.caption) + .padding(.top, 4) } + Divider() .gridCellUnsizedAxes(.horizontal) } + .receiveValidation(in: $validation) + .onChange(of: elementSelected) { _, _ in + validation.validateSubviews() + } + .onAppear { + validation.validateSubviews() + } } } @@ -217,30 +262,30 @@ public struct ConsentDocument: View { } } public var body: some View { - VStack { - ScrollView { - ForEach(allElements, id: \.self) { element in - switch element { - case .text(let text): - if let data = text.data(using: .utf8) { - MarkdownView(asyncMarkdown: {data}, state: $viewState.base) - } - case .checkbox(let question): - if let isChecked = checked[question] { - CheckBoxView( - isChecked: Binding( - get: {self.checked[question,default:false]}, - - set: {self.checked[question] = $0} - ), - question: question - ) + ScrollView { + ForEach(allElements, id: \.self) { element in + switch element { + case .text(let text): + if let data = text.data(using: .utf8) { + MarkdownView(asyncMarkdown: {data}, state: $viewState.base) + } + case .checkbox(let question, let options): + CheckBoxView( + question: question, + options: options + ) + .onAppear { + if let selectedOption = ConsentDocument.checked[question] { + ConsentDocument.checked[question] = selectedOption + } else { + ConsentDocument.checked[question] = "-" } - case .signature: - signatureView } + case .signature: + signatureView } } + Spacer() Group { nameView @@ -257,7 +302,8 @@ public struct ConsentDocument: View { .animation(.easeInOut, value: viewState == .namesEntered) .onChange(of: viewState) { if case .export = viewState { - print(checked) + +// print(checked) // let renderer = ImageRenderer(content: checkboxesView) // if let uiImage = renderer.uiImage { // checkboxSnapshot = uiImage @@ -290,8 +336,8 @@ public struct ConsentDocument: View { Task { let elements = await extractMarkdownCB() allElements = elements - for case let .checkbox(question) in elements { - checked[question] = false + for case let .checkbox(question, options) in elements { + ConsentDocument.checked[question] = "-" } } }