diff --git a/apps/ios/Shared/Model/SimpleXAPI.swift b/apps/ios/Shared/Model/SimpleXAPI.swift index ac841c5ebf..bdd037e769 100644 --- a/apps/ios/Shared/Model/SimpleXAPI.swift +++ b/apps/ios/Shared/Model/SimpleXAPI.swift @@ -185,22 +185,22 @@ func apiGetChats() throws -> [ChatData] { throw r } -func apiGetChat(type: ChatType, id: Int64) throws -> Chat { - let r = chatSendCmdSync(.apiGetChat(type: type, id: id, pagination: .last(count: 50))) +func apiGetChat(type: ChatType, id: Int64, search: String = "") throws -> Chat { + let r = chatSendCmdSync(.apiGetChat(type: type, id: id, pagination: .last(count: 50), search: search)) if case let .apiChat(chat) = r { return Chat.init(chat) } throw r } -func apiGetChatItems(type: ChatType, id: Int64, pagination: ChatPagination) async throws -> [ChatItem] { - let r = await chatSendCmd(.apiGetChat(type: type, id: id, pagination: pagination)) +func apiGetChatItems(type: ChatType, id: Int64, pagination: ChatPagination, search: String = "") async throws -> [ChatItem] { + let r = await chatSendCmd(.apiGetChat(type: type, id: id, pagination: pagination, search: search)) if case let .apiChat(chat) = r { return chat.chatItems } throw r } -func loadChat(chat: Chat) { +func loadChat(chat: Chat, search: String = "") { do { let cInfo = chat.chatInfo - let chat = try apiGetChat(type: cInfo.chatType, id: cInfo.apiId) + let chat = try apiGetChat(type: cInfo.chatType, id: cInfo.apiId, search: search) let m = ChatModel.shared m.updateChatInfo(chat.chatInfo) m.reversedChatItems = chat.chatItems.reversed() diff --git a/apps/ios/Shared/Views/Chat/ChatView.swift b/apps/ios/Shared/Views/Chat/ChatView.swift index 324581f3fb..d09e2ebd29 100644 --- a/apps/ios/Shared/Views/Chat/ChatView.swift +++ b/apps/ios/Shared/Views/Chat/ChatView.swift @@ -28,13 +28,19 @@ struct ChatView: View { @State private var firstPage = false @State private var itemsInView: Set = [] @State private var scrollProxy: ScrollViewProxy? + @State private var searchMode = false + @State private var searchText: String = "" + @FocusState private var searchFocussed var body: some View { let cInfo = chat.chatInfo - - return VStack { + return VStack(spacing: 0) { + if searchMode { + searchToolbar() + Divider() + } ZStack(alignment: .trailing) { - chatItemsList(cInfo) + chatItemsList() if let proxy = scrollProxy { floatingButtons(proxy) } @@ -47,11 +53,12 @@ struct ChatView: View { composeState: $composeState, keyboardVisible: $keyboardVisible ) - .disabled(!chat.chatInfo.sendMsgEnabled) + .disabled(!cInfo.sendMsgEnabled) } .padding(.top, 1) .navigationTitle(cInfo.chatViewName) .navigationBarTitleDisplayMode(.inline) + .navigationBarBackButtonHidden(true) .toolbar { ToolbarItem(placement: .navigationBarLeading) { Button { @@ -62,10 +69,7 @@ struct ChatView: View { } } } label: { - HStack(spacing: 4) { - Image(systemName: "chevron.backward") - Text("Chats", comment: "back button to return to chats list") - } + Image(systemName: "chevron.backward") } } ToolbarItem(placement: .principal) { @@ -108,25 +112,75 @@ struct ChatView: View { case let .direct(contact): HStack { callButton(contact, .audio, imageName: "phone") - callButton(contact, .video, imageName: "video") + Menu { + Button { + CallController.shared.startCall(contact, .video) + } label: { + Label("Video call", systemImage: "video") + } + searchButton() + } label: { + Image(systemName: "ellipsis") + } } case let .group(groupInfo): - if groupInfo.canAddMembers { - addMembersButton() - .sheet(isPresented: $showAddMembersSheet) { - AddGroupMembersView(chat: chat, groupInfo: groupInfo) - } + HStack { + if groupInfo.canAddMembers { + addMembersButton() + .sheet(isPresented: $showAddMembersSheet) { + AddGroupMembersView(chat: chat, groupInfo: groupInfo) + } + } + Menu { + searchButton() + } label: { + Image(systemName: "ellipsis") + } } default: EmptyView() } } } - .navigationBarBackButtonHidden(true) } - private func chatItemsList(_ cInfo: ChatInfo) -> some View { - GeometryReader { g in + private func searchToolbar() -> some View { + HStack { + HStack { + Image(systemName: "magnifyingglass") + TextField("Search", text: $searchText) + .focused($searchFocussed) + .foregroundColor(.primary) + .frame(maxWidth: .infinity) + + Button { + searchText = "" + } label: { + Image(systemName: "xmark.circle.fill").opacity(searchText == "" ? 0 : 1) + } + } + .padding(EdgeInsets(top: 8, leading: 6, bottom: 8, trailing: 6)) + .foregroundColor(.secondary) + .background(Color(.secondarySystemBackground)) + .cornerRadius(10.0) + + Button ("Cancel") { + searchText = "" + searchMode = false + searchFocussed = false + DispatchQueue.main.asyncAfter(deadline: .now() + 0.35) { + chatModel.reversedChatItems = [] + loadChat(chat: chat) + } + } + } + .padding(.horizontal) + .padding(.vertical, 8) + } + + private func chatItemsList() -> some View { + let cInfo = chat.chatInfo + return GeometryReader { g in ScrollViewReader { proxy in ScrollView { let maxWidth = @@ -161,6 +215,9 @@ struct ChatView: View { scrollProxy = proxy } .onTapGesture { hideKeyboard() } + .onChange(of: searchText) { _ in + loadChat(chat: chat, search: searchText) + } } } .scaleEffect(x: 1, y: -1, anchor: .center) @@ -225,6 +282,16 @@ struct ChatView: View { } } + private func searchButton() -> some View { + Button { + searchMode = true + searchFocussed = true + searchText = "" + } label: { + Label("Search", systemImage: "magnifyingglass") + } + } + private func addMembersButton() -> some View { Button { if case let .group(gInfo) = chat.chatInfo { @@ -250,7 +317,8 @@ struct ChatView: View { let items = try await apiGetChatItems( type: cInfo.chatType, id: cInfo.apiId, - pagination: .before(chatItemId: firstItem.id, count: 50) + pagination: .before(chatItemId: firstItem.id, count: 50), + search: searchText ) await MainActor.run { if items.count == 0 { diff --git a/apps/ios/SimpleX.xcodeproj/project.pbxproj b/apps/ios/SimpleX.xcodeproj/project.pbxproj index 8659b991bf..cab6545152 100644 --- a/apps/ios/SimpleX.xcodeproj/project.pbxproj +++ b/apps/ios/SimpleX.xcodeproj/project.pbxproj @@ -12,8 +12,12 @@ 3C8C548928133C84000A3EC7 /* PasteToConnectView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3C8C548828133C84000A3EC7 /* PasteToConnectView.swift */; }; 3CDBCF4227FAE51000354CDD /* ComposeLinkView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3CDBCF4127FAE51000354CDD /* ComposeLinkView.swift */; }; 3CDBCF4827FF621E00354CDD /* CILinkView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3CDBCF4727FF621E00354CDD /* CILinkView.swift */; }; - 5C00164028A1B87B0094D739 /* Introspect in Frameworks */ = {isa = PBXBuildFile; productRef = 5C00163F28A1B87B0094D739 /* Introspect */; }; 5C00164428A26FBC0094D739 /* ContextMenu.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C00164328A26FBC0094D739 /* ContextMenu.swift */; }; + 5C00164C28ACEA380094D739 /* libHSsimplex-chat-3.1.0-iRSBBAuo4W6e6BG2FodFz-ghc8.10.7.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5C00164728ACEA380094D739 /* libHSsimplex-chat-3.1.0-iRSBBAuo4W6e6BG2FodFz-ghc8.10.7.a */; }; + 5C00164D28ACEA380094D739 /* libgmp.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5C00164828ACEA380094D739 /* libgmp.a */; }; + 5C00164E28ACEA380094D739 /* libHSsimplex-chat-3.1.0-iRSBBAuo4W6e6BG2FodFz.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5C00164928ACEA380094D739 /* libHSsimplex-chat-3.1.0-iRSBBAuo4W6e6BG2FodFz.a */; }; + 5C00164F28ACEA380094D739 /* libffi.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5C00164A28ACEA380094D739 /* libffi.a */; }; + 5C00165028ACEA380094D739 /* libgmpxx.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5C00164B28ACEA380094D739 /* libgmpxx.a */; }; 5C029EA82837DBB3004A9677 /* CICallItemView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C029EA72837DBB3004A9677 /* CICallItemView.swift */; }; 5C029EAA283942EA004A9677 /* CallController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C029EA9283942EA004A9677 /* CallController.swift */; }; 5C05DF532840AA1D00C683F9 /* CallSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C05DF522840AA1D00C683F9 /* CallSettings.swift */; }; @@ -54,11 +58,6 @@ 5C971E1D27AEBEF600C8A3CE /* ChatInfoView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C971E1C27AEBEF600C8A3CE /* ChatInfoView.swift */; }; 5C971E2127AEBF8300C8A3CE /* ChatInfoImage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C971E2027AEBF8300C8A3CE /* ChatInfoImage.swift */; }; 5C9A5BDB2871E05400A5B906 /* SetNotificationsMode.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C9A5BDA2871E05400A5B906 /* SetNotificationsMode.swift */; }; - 5C9C2D9F28929B6900CC63B1 /* libffi.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5C9C2D9A28929B6900CC63B1 /* libffi.a */; }; - 5C9C2DA028929B6900CC63B1 /* libHSsimplex-chat-3.1.0-FNUbBjLYHjnDjt6ldpTolw.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5C9C2D9B28929B6900CC63B1 /* libHSsimplex-chat-3.1.0-FNUbBjLYHjnDjt6ldpTolw.a */; }; - 5C9C2DA128929B6900CC63B1 /* libgmp.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5C9C2D9C28929B6900CC63B1 /* libgmp.a */; }; - 5C9C2DA228929B6900CC63B1 /* libHSsimplex-chat-3.1.0-FNUbBjLYHjnDjt6ldpTolw-ghc8.10.7.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5C9C2D9D28929B6900CC63B1 /* libHSsimplex-chat-3.1.0-FNUbBjLYHjnDjt6ldpTolw-ghc8.10.7.a */; }; - 5C9C2DA328929B6900CC63B1 /* libgmpxx.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5C9C2D9E28929B6900CC63B1 /* libgmpxx.a */; }; 5C9C2DA52894777E00CC63B1 /* GroupProfileView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C9C2DA42894777E00CC63B1 /* GroupProfileView.swift */; }; 5C9C2DA7289957AE00CC63B1 /* AdvancedNetworkSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C9C2DA6289957AE00CC63B1 /* AdvancedNetworkSettings.swift */; }; 5C9C2DA92899DA6F00CC63B1 /* NetworkAndServers.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C9C2DA82899DA6F00CC63B1 /* NetworkAndServers.swift */; }; @@ -197,6 +196,11 @@ 3CDBCF4127FAE51000354CDD /* ComposeLinkView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ComposeLinkView.swift; sourceTree = ""; }; 3CDBCF4727FF621E00354CDD /* CILinkView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CILinkView.swift; sourceTree = ""; }; 5C00164328A26FBC0094D739 /* ContextMenu.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContextMenu.swift; sourceTree = ""; }; + 5C00164728ACEA380094D739 /* libHSsimplex-chat-3.1.0-iRSBBAuo4W6e6BG2FodFz-ghc8.10.7.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-3.1.0-iRSBBAuo4W6e6BG2FodFz-ghc8.10.7.a"; sourceTree = ""; }; + 5C00164828ACEA380094D739 /* libgmp.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmp.a; sourceTree = ""; }; + 5C00164928ACEA380094D739 /* libHSsimplex-chat-3.1.0-iRSBBAuo4W6e6BG2FodFz.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-3.1.0-iRSBBAuo4W6e6BG2FodFz.a"; sourceTree = ""; }; + 5C00164A28ACEA380094D739 /* libffi.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libffi.a; sourceTree = ""; }; + 5C00164B28ACEA380094D739 /* libgmpxx.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmpxx.a; sourceTree = ""; }; 5C029EA72837DBB3004A9677 /* CICallItemView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CICallItemView.swift; sourceTree = ""; }; 5C029EA9283942EA004A9677 /* CallController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CallController.swift; sourceTree = ""; }; 5C05DF522840AA1D00C683F9 /* CallSettings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CallSettings.swift; sourceTree = ""; }; @@ -239,11 +243,6 @@ 5C971E1C27AEBEF600C8A3CE /* ChatInfoView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatInfoView.swift; sourceTree = ""; }; 5C971E2027AEBF8300C8A3CE /* ChatInfoImage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatInfoImage.swift; sourceTree = ""; }; 5C9A5BDA2871E05400A5B906 /* SetNotificationsMode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SetNotificationsMode.swift; sourceTree = ""; }; - 5C9C2D9A28929B6900CC63B1 /* libffi.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libffi.a; sourceTree = ""; }; - 5C9C2D9B28929B6900CC63B1 /* libHSsimplex-chat-3.1.0-FNUbBjLYHjnDjt6ldpTolw.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-3.1.0-FNUbBjLYHjnDjt6ldpTolw.a"; sourceTree = ""; }; - 5C9C2D9C28929B6900CC63B1 /* libgmp.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmp.a; sourceTree = ""; }; - 5C9C2D9D28929B6900CC63B1 /* libHSsimplex-chat-3.1.0-FNUbBjLYHjnDjt6ldpTolw-ghc8.10.7.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-3.1.0-FNUbBjLYHjnDjt6ldpTolw-ghc8.10.7.a"; sourceTree = ""; }; - 5C9C2D9E28929B6900CC63B1 /* libgmpxx.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmpxx.a; sourceTree = ""; }; 5C9C2DA42894777E00CC63B1 /* GroupProfileView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GroupProfileView.swift; sourceTree = ""; }; 5C9C2DA6289957AE00CC63B1 /* AdvancedNetworkSettings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AdvancedNetworkSettings.swift; sourceTree = ""; }; 5C9C2DA82899DA6F00CC63B1 /* NetworkAndServers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkAndServers.swift; sourceTree = ""; }; @@ -326,7 +325,6 @@ buildActionMask = 2147483647; files = ( 5CE2BA702845308900EC33A6 /* SimpleXChat.framework in Frameworks */, - 5C00164028A1B87B0094D739 /* Introspect in Frameworks */, 646BB38C283BEEB9001CE359 /* LocalAuthentication.framework in Frameworks */, 5C8F01CD27A6F0D8007D2C8D /* CodeScanner in Frameworks */, ); @@ -351,13 +349,13 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - 5C9C2DA028929B6900CC63B1 /* libHSsimplex-chat-3.1.0-FNUbBjLYHjnDjt6ldpTolw.a in Frameworks */, - 5C9C2DA128929B6900CC63B1 /* libgmp.a in Frameworks */, 5CE2BA93284534B000EC33A6 /* libiconv.tbd in Frameworks */, - 5C9C2DA228929B6900CC63B1 /* libHSsimplex-chat-3.1.0-FNUbBjLYHjnDjt6ldpTolw-ghc8.10.7.a in Frameworks */, - 5C9C2D9F28929B6900CC63B1 /* libffi.a in Frameworks */, + 5C00164F28ACEA380094D739 /* libffi.a in Frameworks */, 5CE2BA94284534BB00EC33A6 /* libz.tbd in Frameworks */, - 5C9C2DA328929B6900CC63B1 /* libgmpxx.a in Frameworks */, + 5C00164E28ACEA380094D739 /* libHSsimplex-chat-3.1.0-iRSBBAuo4W6e6BG2FodFz.a in Frameworks */, + 5C00164D28ACEA380094D739 /* libgmp.a in Frameworks */, + 5C00164C28ACEA380094D739 /* libHSsimplex-chat-3.1.0-iRSBBAuo4W6e6BG2FodFz-ghc8.10.7.a in Frameworks */, + 5C00165028ACEA380094D739 /* libgmpxx.a in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -412,11 +410,11 @@ 5C764E5C279C70B7000C6508 /* Libraries */ = { isa = PBXGroup; children = ( - 5C9C2D9A28929B6900CC63B1 /* libffi.a */, - 5C9C2D9C28929B6900CC63B1 /* libgmp.a */, - 5C9C2D9E28929B6900CC63B1 /* libgmpxx.a */, - 5C9C2D9D28929B6900CC63B1 /* libHSsimplex-chat-3.1.0-FNUbBjLYHjnDjt6ldpTolw-ghc8.10.7.a */, - 5C9C2D9B28929B6900CC63B1 /* libHSsimplex-chat-3.1.0-FNUbBjLYHjnDjt6ldpTolw.a */, + 5C00164A28ACEA380094D739 /* libffi.a */, + 5C00164828ACEA380094D739 /* libgmp.a */, + 5C00164B28ACEA380094D739 /* libgmpxx.a */, + 5C00164728ACEA380094D739 /* libHSsimplex-chat-3.1.0-iRSBBAuo4W6e6BG2FodFz-ghc8.10.7.a */, + 5C00164928ACEA380094D739 /* libHSsimplex-chat-3.1.0-iRSBBAuo4W6e6BG2FodFz.a */, ); path = Libraries; sourceTree = ""; @@ -690,7 +688,6 @@ name = "SimpleX (iOS)"; packageProductDependencies = ( 5C8F01CC27A6F0D8007D2C8D /* CodeScanner */, - 5C00163F28A1B87B0094D739 /* Introspect */, ); productName = "SimpleX (iOS)"; productReference = 5CA059CA279559F40002BEB4 /* SimpleX.app */; @@ -1502,11 +1499,6 @@ /* End XCRemoteSwiftPackageReference section */ /* Begin XCSwiftPackageProductDependency section */ - 5C00163F28A1B87B0094D739 /* Introspect */ = { - isa = XCSwiftPackageProductDependency; - package = 5C00163E28A1B87B0094D739 /* XCRemoteSwiftPackageReference "SwiftUI-Introspect" */; - productName = Introspect; - }; 5C8F01CC27A6F0D8007D2C8D /* CodeScanner */ = { isa = XCSwiftPackageProductDependency; package = 5C8F01CB27A6F0D8007D2C8D /* XCRemoteSwiftPackageReference "CodeScanner" */; diff --git a/apps/ios/SimpleXChat/APITypes.swift b/apps/ios/SimpleXChat/APITypes.swift index f34a12524e..b0bc469594 100644 --- a/apps/ios/SimpleXChat/APITypes.swift +++ b/apps/ios/SimpleXChat/APITypes.swift @@ -24,7 +24,7 @@ public enum ChatCommand { case apiImportArchive(config: ArchiveConfig) case apiDeleteStorage case apiGetChats - case apiGetChat(type: ChatType, id: Int64, pagination: ChatPagination) + case apiGetChat(type: ChatType, id: Int64, pagination: ChatPagination, search: String) case apiSendMessage(type: ChatType, id: Int64, file: String?, quotedItemId: Int64?, msg: MsgContent) case apiUpdateChatItem(type: ChatType, id: Int64, itemId: Int64, msg: MsgContent) case apiDeleteChatItem(type: ChatType, id: Int64, itemId: Int64, mode: CIDeleteMode) @@ -85,7 +85,8 @@ public enum ChatCommand { case let .apiImportArchive(cfg): return "/_db import \(encodeJSON(cfg))" case .apiDeleteStorage: return "/_db delete" case .apiGetChats: return "/_get chats pcc=on" - case let .apiGetChat(type, id, pagination): return "/_get chat \(ref(type, id)) \(pagination.cmdString)" + case let .apiGetChat(type, id, pagination, search): return "/_get chat \(ref(type, id)) \(pagination.cmdString)" + + (search == "" ? "" : " search=\(search)") case let .apiSendMessage(type, id, file, quotedItemId, mc): let msg = encodeJSON(ComposedMessage(filePath: file, quotedItemId: quotedItemId, msgContent: mc)) return "/_send \(ref(type, id)) json \(msg)"