From f85e9b26a36ed6cf164a2db615b862d8cd347e4b Mon Sep 17 00:00:00 2001 From: Philipp Zagar Date: Sun, 17 Dec 2023 18:07:08 -0800 Subject: [PATCH] Improve documentation --- README.md | 3 ++- Sources/SpeziChat/ChatView+Export.swift | 7 +++---- Sources/SpeziChat/ChatView+ShareSheet.swift | 5 ++++- Sources/SpeziChat/ChatView.swift | 3 ++- .../SpeziChat/MessageStyleViewModifier.swift | 21 +++++++++++++++++-- Sources/SpeziChat/MessageView.swift | 20 ------------------ Sources/SpeziChat/SpeziChat.docc/SpeziChat.md | 3 ++- Tests/UITests/TestApp/ChatTestView.swift | 2 +- 8 files changed, 33 insertions(+), 31 deletions(-) diff --git a/README.md b/README.md index 955bc63..ec2a60a 100644 --- a/README.md +++ b/README.md @@ -57,6 +57,7 @@ These entries are mandatory for apps that utilize microphone and speech recognit ### Chat View The [`ChatView`](https://swiftpackageindex.com/stanfordspezi/spezichat/documentation/spezichat/chatview) provides a basic reusable chat view which includes a message input field. The input can be either typed out via the iOS keyboard or provided as voice input and transcribed into written text. +In addition, the [`ChatView`](https://swiftpackageindex.com/stanfordspezi/spezichat/documentation/spezichat/chatview) provides functionality to export the visualized [`Chat`](https://swiftpackageindex.com/stanfordspezi/spezichat/0.1.1/documentation/spezichat/chat) as a PDF document, JSON representation, or textual UTF-8 file (see `ChatView/ChatExportFormat`) via a Share Sheet (or Activity View). ```swift struct ChatTestView: View { @@ -66,7 +67,7 @@ struct ChatTestView: View { var body: some View { - ChatView($chat) + ChatView($chat, exportFormat: .pdf) .navigationTitle("SpeziChat") } } diff --git a/Sources/SpeziChat/ChatView+Export.swift b/Sources/SpeziChat/ChatView+Export.swift index bf21445..2698afe 100644 --- a/Sources/SpeziChat/ChatView+Export.swift +++ b/Sources/SpeziChat/ChatView+Export.swift @@ -13,7 +13,7 @@ import SwiftUI extension ChatView { - /// Output format of the exported ``Chat`` via a share button in the ``ChatView``. + /// Output format of the to-be exported ``Chat``. public enum ChatExportFormat { /// JSON representation of the ``Chat`` case json @@ -31,7 +31,7 @@ extension ChatView { var body: some View { - VStack(spacing: 8) { // Sadly, the SwiftUI `ImageRenderer` doesn't support SwiftUI `List`s + VStack(spacing: 8) { // The SwiftUI `ImageRenderer` doesn't support SwiftUI `List`s ForEach(chat, id: \.self) { chatEntity in HStack { if chatEntity.alignment == .trailing { @@ -50,7 +50,6 @@ extension ChatView { Spacer(minLength: 32) } } - } Spacer() @@ -120,7 +119,7 @@ extension ChatView { // Width from US Letter, height requested by the view // Reason: Splitting a view in multiple PDF pages is complex! let size = CGSize( - width: 72 * 8.5, + width: 72 * 8.5, // US Letter width height: proposedHeight ) diff --git a/Sources/SpeziChat/ChatView+ShareSheet.swift b/Sources/SpeziChat/ChatView+ShareSheet.swift index 18481de..64e825c 100644 --- a/Sources/SpeziChat/ChatView+ShareSheet.swift +++ b/Sources/SpeziChat/ChatView+ShareSheet.swift @@ -11,13 +11,15 @@ import SwiftUI extension ChatView { + /// Provides an iOS-typical Share Sheet (also called Activity View: https://developer.apple.com/design/human-interface-guidelines/activity-views) SwiftUI wrapper + /// for exporting the ``Chat`` content of the ``ChatView`` without the downsides of the SwiftUI `ShareLink` such as unnecessary reevaluations of the to-be shared content. struct ShareSheet: UIViewControllerRepresentable { let sharedItem: Data let sharedItemType: ChatExportFormat func makeUIViewController(context: Context) -> UIActivityViewController { - /// Note: Need to write down the data to storage as in-memory PDFs are not recognized properly + // Note: Need to write down the data to storage as in-memory shared content is not recognized properly (e.g., PDFs) var temporaryPath = FileManager.default.temporaryDirectory.appendingPathComponent("Exported Chat") switch sharedItemType { @@ -25,6 +27,7 @@ extension ChatView { case .text: temporaryPath = temporaryPath.appendingPathExtension("txt") case .pdf: temporaryPath = temporaryPath.appendingPathExtension("pdf") } + try? sharedItem.write(to: temporaryPath) let controller = UIActivityViewController( diff --git a/Sources/SpeziChat/ChatView.swift b/Sources/SpeziChat/ChatView.swift index a638966..c5e7cb5 100644 --- a/Sources/SpeziChat/ChatView.swift +++ b/Sources/SpeziChat/ChatView.swift @@ -60,7 +60,7 @@ public struct ChatView: View { let messagePlaceholder: String? @State var messageInputHeight: CGFloat = 0 - @State private var showShareSheet: Bool = false + @State private var showShareSheet = false public var body: some View { @@ -108,6 +108,7 @@ public struct ChatView: View { } else { ProgressView() .padding() + .presentationDetents([.medium]) } } } diff --git a/Sources/SpeziChat/MessageStyleViewModifier.swift b/Sources/SpeziChat/MessageStyleViewModifier.swift index 06ee0ed..3c57986 100644 --- a/Sources/SpeziChat/MessageStyleViewModifier.swift +++ b/Sources/SpeziChat/MessageStyleViewModifier.swift @@ -8,7 +8,7 @@ import SwiftUI - +/// Provides styling for the visualization of a textual ``ChatEntity`` within the ``ChatView``. struct MessageStyleModifier: ViewModifier { let chatAlignment: ChatEntity.Alignment @@ -41,7 +41,6 @@ struct MessageStyleModifier: ViewModifier { func body(content: Content) -> some View { content .multilineTextAlignment(multilineTextAlignment) - //.frame(maxWidth: .infinity) .padding(.horizontal, 10) .padding(.vertical, 8) .foregroundColor(foregroundColor) @@ -61,6 +60,24 @@ struct MessageStyleModifier: ViewModifier { extension View { + /// Attach this modifier to `Text`-based content in SwiftUI to format it as a typical chat bubble within a chat view. + /// The modifier handles text alignment, paddings, colourings, background, as well as the typical chat bubble visualization. + /// + /// ### Usage + /// + /// A minimal example can be found below. + /// See the ``MessageView`` for a more complete example. + /// + /// ```swift + /// struct ChatMessageView: View { + /// let chatEntity: ChatEntity + /// + /// var body: some View { + /// Text(chatEntity.content) + /// .chatMessageStyle(alignment: chatEntity.alignment) + /// } + /// } + /// ``` func chatMessageStyle(alignment: ChatEntity.Alignment) -> some View { self.modifier(MessageStyleModifier(chatAlignment: alignment)) } diff --git a/Sources/SpeziChat/MessageView.swift b/Sources/SpeziChat/MessageView.swift index c5bbe17..2d43e1a 100644 --- a/Sources/SpeziChat/MessageView.swift +++ b/Sources/SpeziChat/MessageView.swift @@ -36,27 +36,7 @@ public struct MessageView: View { private let chat: ChatEntity private let hideMessagesWithRoles: Set - - - private var foregroundColor: Color { - chat.alignment == .leading ? .primary : .white - } - - private var backgroundColor: Color { - chat.alignment == .leading ? Color(.secondarySystemBackground) : .accentColor - } - private var multilineTextAllignment: TextAlignment { - chat.alignment == .leading ? .leading : .trailing - } - - private var arrowRotation: Angle { - .degrees(chat.alignment == .leading ? -50 : -130) - } - - private var arrowAllignment: CGFloat { - chat.alignment == .leading ? -7 : 7 - } public var body: some View { if !hideMessagesWithRoles.contains(chat.role) { diff --git a/Sources/SpeziChat/SpeziChat.docc/SpeziChat.md b/Sources/SpeziChat/SpeziChat.docc/SpeziChat.md index fd40d59..db95e06 100644 --- a/Sources/SpeziChat/SpeziChat.docc/SpeziChat.md +++ b/Sources/SpeziChat/SpeziChat.docc/SpeziChat.md @@ -60,6 +60,7 @@ These entries are mandatory for apps that utilize microphone and speech recognit ### Chat View The ``ChatView`` provides a basic reusable chat view which includes a message input field. The input can be either typed out via the iOS keyboard or provided as voice input and transcribed into written text. +In addition, the ``ChatView`` provides functionality to export the visualized ``Chat`` as a PDF document, JSON representation, or textual UTF-8 file (see ``ChatView/ChatExportFormat``) via a Share Sheet (or Activity View). ```swift struct ChatTestView: View { @@ -69,7 +70,7 @@ struct ChatTestView: View { var body: some View { - ChatView($chat) + ChatView($chat, exportFormat: .pdf) .navigationTitle("SpeziChat") } } diff --git a/Tests/UITests/TestApp/ChatTestView.swift b/Tests/UITests/TestApp/ChatTestView.swift index 36e776c..1052e4c 100644 --- a/Tests/UITests/TestApp/ChatTestView.swift +++ b/Tests/UITests/TestApp/ChatTestView.swift @@ -17,7 +17,7 @@ struct ChatTestView: View { var body: some View { - ChatView($chat, exportFormat: .text) + ChatView($chat, exportFormat: .pdf) .navigationTitle("SpeziChat") .padding(.top, 16) .onChange(of: chat) { _, newValue in