From 05f84eb7521cab75817b4079c83a1f70f76f2d22 Mon Sep 17 00:00:00 2001 From: Augustinas Malinauskas Date: Wed, 1 May 2024 10:03:57 -0700 Subject: [PATCH] feat: select text on iOS (#97) --- Enchanted.xcodeproj/project.pbxproj | 14 ++++- .../Chat/Components/MessageListVIew.swift | 61 +++++++++++-------- .../UI/iOS/Components/SelectTextSheet.swift | 57 +++++++++++++++++ 3 files changed, 106 insertions(+), 26 deletions(-) create mode 100644 Enchanted/UI/iOS/Components/SelectTextSheet.swift diff --git a/Enchanted.xcodeproj/project.pbxproj b/Enchanted.xcodeproj/project.pbxproj index 99acedc..ae3972c 100644 --- a/Enchanted.xcodeproj/project.pbxproj +++ b/Enchanted.xcodeproj/project.pbxproj @@ -36,6 +36,7 @@ FF15EF6A2B826C0300D4A541 /* SimpleFloatingButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF15EF692B826C0300D4A541 /* SimpleFloatingButton.swift */; }; FF1BC3C52BA0753400A58043 /* Splash in Frameworks */ = {isa = PBXBuildFile; productRef = FF1BC3C42BA0753400A58043 /* Splash */; }; FF1BC3C72BA0757700A58043 /* SplashSyntaxHighlighter+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF1BC3C62BA0757700A58043 /* SplashSyntaxHighlighter+Extension.swift */; }; + FF226A652BE2A0EC00CC91F1 /* SelectTextSheet.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF226A642BE2A0EC00CC91F1 /* SelectTextSheet.swift */; }; FF24B30E2B66BE8500AB618F /* RunningBorder.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF24B30D2B66BE8500AB618F /* RunningBorder.swift */; }; FF2F03422B795E0B00349855 /* Clipboard.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF2F03412B795E0B00349855 /* Clipboard.swift */; }; FF2F03442B79631800349855 /* Button+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF2F03432B79631800349855 /* Button+Extension.swift */; }; @@ -122,6 +123,7 @@ FF1002742B278C170011A4DC /* AppStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppStore.swift; sourceTree = ""; }; FF15EF692B826C0300D4A541 /* SimpleFloatingButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SimpleFloatingButton.swift; sourceTree = ""; }; FF1BC3C62BA0757700A58043 /* SplashSyntaxHighlighter+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "SplashSyntaxHighlighter+Extension.swift"; sourceTree = ""; }; + FF226A642BE2A0EC00CC91F1 /* SelectTextSheet.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SelectTextSheet.swift; sourceTree = ""; }; FF24B30D2B66BE8500AB618F /* RunningBorder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunningBorder.swift; sourceTree = ""; }; FF2F03412B795E0B00349855 /* Clipboard.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Clipboard.swift; sourceTree = ""; }; FF2F03432B79631800349855 /* Button+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Button+Extension.swift"; sourceTree = ""; }; @@ -281,6 +283,14 @@ path = Components; sourceTree = ""; }; + FF226A632BE2A0E100CC91F1 /* Components */ = { + isa = PBXGroup; + children = ( + FF226A642BE2A0EC00CC91F1 /* SelectTextSheet.swift */, + ); + path = Components; + sourceTree = ""; + }; FF38F84D2B7A7B5300546B56 /* MenuBar */ = { isa = PBXGroup; children = ( @@ -340,6 +350,7 @@ FF66A51F2B77789300FAAC1E /* iOS */ = { isa = PBXGroup; children = ( + FF226A632BE2A0E100CC91F1 /* Components */, FFEC32A92B24797C003E5C04 /* ChatView_iOS.swift */, ); path = iOS; @@ -403,6 +414,7 @@ isa = PBXGroup; children = ( FFEC32982B24779B003E5C04 /* Enchanted.entitlements */, + FFE2C8222B9A657A00BD82F3 /* Accessibility.plist */, FFBBF4822B348345008D611C /* Info.plist */, FFEC32962B24779B003E5C04 /* Assets.xcassets */, FFEC32A12B24783B003E5C04 /* Application */, @@ -414,7 +426,6 @@ FF1002422B25BAC50011A4DC /* Stores */, FF1002412B25BAAE0011A4DC /* SwiftData */, FFEC32A52B247879003E5C04 /* UI */, - FFE2C8222B9A657A00BD82F3 /* Accessibility.plist */, ); path = Enchanted; sourceTree = ""; @@ -658,6 +669,7 @@ FF7FBE4C2B78E384000901F7 /* SamplePrompt.swift in Sources */, FF38F8582B7AB1AD00546B56 /* PanelManager.swift in Sources */, FF6D82172B9122F9001183A8 /* CompletionInstructionSD.swift in Sources */, + FF226A652BE2A0EC00CC91F1 /* SelectTextSheet.swift in Sources */, FF10025A2B2624C40011A4DC /* ConversationHistoryListView.swift in Sources */, FF10026D2B2751760011A4DC /* SettingsView.swift in Sources */, FF33066C2B83BB31007B33E5 /* SidebarButton.swift in Sources */, diff --git a/Enchanted/UI/Shared/Chat/Components/MessageListVIew.swift b/Enchanted/UI/Shared/Chat/Components/MessageListVIew.swift index bdaa039..d8dcdab 100644 --- a/Enchanted/UI/Shared/Chat/Components/MessageListVIew.swift +++ b/Enchanted/UI/Shared/Chat/Components/MessageListVIew.swift @@ -16,6 +16,7 @@ struct MessageListView: View { var conversationState: ConversationState var userInitials: String @Binding var editMessage: MessageSD? + @State private var messageSelected: MessageSD? func onEditMessageTap() -> (MessageSD) -> Void { return { message in @@ -23,34 +24,39 @@ struct MessageListView: View { } } - func createUserContextMenu(_ message: MessageSD) -> ContextMenu>, Button>?, Button>?)>> { - ContextMenu(menuItems: { - Button(action: {Clipboard.shared.setString(message.content)}) { - Label("Copy", systemImage: "doc.on.doc") - } - - if message.role == "user" { - Button(action: { - withAnimation { editMessage = message } - }) { - Label("Edit", systemImage: "pencil") - } - } - - if editMessage?.id == message.id { - Button(action: { - withAnimation { editMessage = nil } - }) { - Label("Unselect", systemImage: "pencil") - } - } - }) - } - var body: some View { ScrollViewReader { scrollViewProxy in ScrollView { ForEach(messages) { message in + + let contextMenu = ContextMenu(menuItems: { + Button(action: {Clipboard.shared.setString(message.content)}) { + Label("Copy", systemImage: "doc.on.doc") + } + +#if os(iOS) + Button(action: { messageSelected = message }) { + Label("Select Text", systemImage: "selection.pin.in.out") + } +#endif + + if message.role == "user" { + Button(action: { + withAnimation { editMessage = message } + }) { + Label("Edit", systemImage: "pencil") + } + } + + if editMessage?.id == message.id { + Button(action: { + withAnimation { editMessage = nil } + }) { + Label("Unselect", systemImage: "pencil") + } + } + }) + ChatMessageView( message: message, showLoader: conversationState == .loading && messages.last == message, @@ -60,7 +66,7 @@ struct MessageListView: View { .listRowInsets(EdgeInsets()) .listRowSeparator(.hidden) .padding(.vertical, 10) - .contextMenu(createUserContextMenu(message)) + .contextMenu(contextMenu) .padding(.horizontal, 10) .runningBorder(animated: message.id == editMessage?.id) .id(message) @@ -76,6 +82,11 @@ struct MessageListView: View { .onChange(of: messages.last?.content) { scrollViewProxy.scrollTo(messages.last, anchor: .bottom) } +#if os(iOS) + .sheet(item: $messageSelected) { message in + SelectTextSheet(message: message) + } +#endif } } } diff --git a/Enchanted/UI/iOS/Components/SelectTextSheet.swift b/Enchanted/UI/iOS/Components/SelectTextSheet.swift new file mode 100644 index 0000000..83e3cef --- /dev/null +++ b/Enchanted/UI/iOS/Components/SelectTextSheet.swift @@ -0,0 +1,57 @@ +// +// SelectTextSheet.swift +// Enchanted +// +// Created by Augustinas Malinauskas on 01/05/2024. +// + +#if os(iOS) +import SwiftUI +import UIKit + +struct SelectTextSheet: View { + @Environment(\.presentationMode) var presentationMode + @FocusState private var isTextEditorFocused: Bool + + var message: MessageSD + var body: some View { + VStack { + ZStack { + Text("Select Text") + .font(.system(size: 16)) + .fontWeight(.bold) + + HStack { + Spacer() + Button(action: {}) { + Image(systemName: "x.circle.fill") + .padding(7) + } + .buttonStyle(.plain) + } + .padding() + } + + TextEditor(text: .constant(message.content)) + .focusable() + .focused($isTextEditorFocused) + .onReceive(NotificationCenter.default.publisher(for: UITextField.textDidBeginEditingNotification)) { obj in + if let textField = obj.object as? UITextView { + textField.selectedTextRange = textField.textRange(from: textField.beginningOfDocument, to: textField.endOfDocument) + } + } + + } + .textSelection(.enabled) + .onAppear { + isTextEditorFocused = true + } + + } +} + +#Preview { + SelectTextSheet(message: MessageSD.sample[0]) +} + +#endif