From 51a2fa8c2830319df78a9b95a7c69b113886d54c Mon Sep 17 00:00:00 2001 From: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com> Date: Mon, 29 Aug 2022 14:08:46 +0100 Subject: [PATCH] ios: programmatic navigation between list/chat (#980) * ios: programmatic navigation between list/chat * prevent chat info sheet from showing when switching conversation * add direct chat with member to model * set status to connected Co-authored-by: JRoberts <8711996+jr-simplex@users.noreply.github.com> --- apps/ios/Shared/Model/ChatModel.swift | 10 ++++++ apps/ios/Shared/Model/SimpleXAPI.swift | 3 +- apps/ios/Shared/Views/Chat/ChatInfoView.swift | 1 + apps/ios/Shared/Views/Chat/ChatView.swift | 14 ++++++++- .../Views/Chat/Group/GroupChatInfoView.swift | 1 + .../Chat/Group/GroupMemberInfoView.swift | 31 ++++++++++++------- .../Views/ChatList/ChatListNavLink.swift | 7 ----- .../Shared/Views/ChatList/ChatListView.swift | 19 ++++++++++++ .../Shared/Views/Helpers/NavLinkPlain.swift | 7 +---- 9 files changed, 66 insertions(+), 27 deletions(-) diff --git a/apps/ios/Shared/Model/ChatModel.swift b/apps/ios/Shared/Model/ChatModel.swift index 6949762925..ab1739846a 100644 --- a/apps/ios/Shared/Model/ChatModel.swift +++ b/apps/ios/Shared/Model/ChatModel.swift @@ -59,6 +59,16 @@ final class ChatModel: ObservableObject { chats.first(where: { $0.id == id }) } + func getContactChat(_ contactId: Int64) -> Chat? { + chats.first { chat in + if case let .direct(contact) = chat.chatInfo { + return contact.contactId == contactId + } else { + return false + } + } + } + private func getChatIndex(_ id: String) -> Int? { chats.firstIndex(where: { $0.id == id }) } diff --git a/apps/ios/Shared/Model/SimpleXAPI.swift b/apps/ios/Shared/Model/SimpleXAPI.swift index 9f17dc8737..6db4b01075 100644 --- a/apps/ios/Shared/Model/SimpleXAPI.swift +++ b/apps/ios/Shared/Model/SimpleXAPI.swift @@ -206,8 +206,9 @@ func apiGetChatItems(type: ChatType, id: Int64, pagination: ChatPagination, sear func loadChat(chat: Chat, search: String = "") { do { let cInfo = chat.chatInfo - let chat = try apiGetChat(type: cInfo.chatType, id: cInfo.apiId, search: search) let m = ChatModel.shared + m.reversedChatItems = [] + let chat = try apiGetChat(type: cInfo.chatType, id: cInfo.apiId, search: search) m.updateChatInfo(chat.chatInfo) m.reversedChatItems = chat.chatItems.reversed() } catch let error { diff --git a/apps/ios/Shared/Views/Chat/ChatInfoView.swift b/apps/ios/Shared/Views/Chat/ChatInfoView.swift index 62449f90a0..667ae0e597 100644 --- a/apps/ios/Shared/Views/Chat/ChatInfoView.swift +++ b/apps/ios/Shared/Views/Chat/ChatInfoView.swift @@ -216,6 +216,7 @@ struct ChatInfoView: View { try await apiDeleteChat(type: chat.chatInfo.chatType, id: chat.chatInfo.apiId) await MainActor.run { chatModel.removeChat(chat.chatInfo.id) + chatModel.chatId = nil dismiss() } } catch let error { diff --git a/apps/ios/Shared/Views/Chat/ChatView.swift b/apps/ios/Shared/Views/Chat/ChatView.swift index 5251617021..63368ecf48 100644 --- a/apps/ios/Shared/Views/Chat/ChatView.swift +++ b/apps/ios/Shared/Views/Chat/ChatView.swift @@ -72,7 +72,10 @@ struct ChatView: View { } } } label: { - Image(systemName: "chevron.backward") + HStack(spacing: 0) { + Image(systemName: "chevron.backward") + Text("Chats") + } } } ToolbarItem(placement: .principal) { @@ -235,6 +238,15 @@ struct ChatView: View { .onChange(of: searchText) { _ in loadChat(chat: chat, search: searchText) } + .onChange(of: chatModel.chatId) { _ in + if let chatId = chatModel.chatId, let chat = chatModel.getChat(chatId) { + showChatInfoSheet = false + loadChat(chat: chat) + DispatchQueue.main.async { + scrollToBottom(proxy) + } + } + } } } .scaleEffect(x: 1, y: -1, anchor: .center) diff --git a/apps/ios/Shared/Views/Chat/Group/GroupChatInfoView.swift b/apps/ios/Shared/Views/Chat/Group/GroupChatInfoView.swift index 25b9cff0e7..6cb92a10ac 100644 --- a/apps/ios/Shared/Views/Chat/Group/GroupChatInfoView.swift +++ b/apps/ios/Shared/Views/Chat/Group/GroupChatInfoView.swift @@ -221,6 +221,7 @@ struct GroupChatInfoView: View { try await apiDeleteChat(type: chat.chatInfo.chatType, id: chat.chatInfo.apiId) await MainActor.run { chatModel.removeChat(chat.chatInfo.id) + chatModel.chatId = nil dismiss() } } catch let error { diff --git a/apps/ios/Shared/Views/Chat/Group/GroupMemberInfoView.swift b/apps/ios/Shared/Views/Chat/Group/GroupMemberInfoView.swift index 3a315bb3e6..e68378f4c7 100644 --- a/apps/ios/Shared/Views/Chat/Group/GroupMemberInfoView.swift +++ b/apps/ios/Shared/Views/Chat/Group/GroupMemberInfoView.swift @@ -30,11 +30,11 @@ struct GroupMemberInfoView: View { groupMemberInfoHeader() .listRowBackground(Color.clear) -// if let contactId = member.memberContactId { -// Section { -// openDirectChatButton(contactId) -// } -// } + if let contactId = member.memberContactId { + Section { + openDirectChatButton(contactId) + } + } Section("Member") { infoRow("Group", groupInfo.displayName) @@ -80,18 +80,25 @@ struct GroupMemberInfoView: View { func openDirectChatButton(_ contactId: Int64) -> some View { Button { - if let i = chatModel.chats.firstIndex(where: { chat in - switch chat.chatInfo { - case let .direct(contact): return contact.contactId == contactId - default: return false + var chat = chatModel.getContactChat(contactId) + if chat == nil { + do { + chat = try apiGetChat(type: .direct, id: contactId) + if let chat = chat { + // TODO it's not correct to blindly set network status to connected - we should manage network status in model / backend + chat.serverInfo = Chat.ServerInfo(networkStatus: .connected) + chatModel.addChat(chat) + } + } catch let error { + logger.error("openDirectChatButton apiGetChat error: \(responseError(error))") } - }) { + } + if let chat = chat { dismissAllSheets(animated: true) - chatModel.chatId = chatModel.chats[i].chatInfo.id + chatModel.chatId = chat.id } } label: { Label("Send direct message", systemImage: "message") - .foregroundColor(.accentColor) } } diff --git a/apps/ios/Shared/Views/ChatList/ChatListNavLink.swift b/apps/ios/Shared/Views/ChatList/ChatListNavLink.swift index 6cfef1a403..562abfc746 100644 --- a/apps/ios/Shared/Views/ChatList/ChatListNavLink.swift +++ b/apps/ios/Shared/Views/ChatList/ChatListNavLink.swift @@ -28,16 +28,10 @@ struct ChatListNavLink: View { } } - private func chatView() -> some View { - ChatView(chat: chat) - .onAppear { loadChat(chat: chat) } - } - @ViewBuilder private func contactNavLink(_ contact: Contact) -> some View { let v = NavLinkPlain( tag: chat.chatInfo.id, selection: $chatModel.chatId, - destination: { chatView() }, label: { ChatPreviewView(chat: chat) }, disabled: !contact.ready ) @@ -97,7 +91,6 @@ struct ChatListNavLink: View { NavLinkPlain( tag: chat.chatInfo.id, selection: $chatModel.chatId, - destination: { chatView() }, label: { ChatPreviewView(chat: chat) }, disabled: !groupInfo.ready ) diff --git a/apps/ios/Shared/Views/ChatList/ChatListView.swift b/apps/ios/Shared/Views/ChatList/ChatListView.swift index 4a4dd4d658..4c504eda8a 100644 --- a/apps/ios/Shared/Views/ChatList/ChatListView.swift +++ b/apps/ios/Shared/Views/ChatList/ChatListView.swift @@ -14,6 +14,7 @@ struct ChatListView: View { // not really used in this view @State private var showSettings = false @State private var searchText = "" + @State private var selectedChat: ChatId? var body: some View { let v = NavigationView { @@ -25,6 +26,7 @@ struct ChatListView: View { } } .onChange(of: chatModel.chatId) { _ in + selectedChat = chatModel.chatId if chatModel.chatId == nil, let chatId = chatModel.chatToTop { chatModel.chatToTop = nil chatModel.popChat(chatId) @@ -63,6 +65,15 @@ struct ChatListView: View { } } } + .background( + NavigationLink( + destination: chatView(selectedChat), + isActive: Binding( + get: { selectedChat != nil }, + set: { _, _ in selectedChat = nil } + ) + ) { EmptyView() } + ) } .navigationViewStyle(.stack) @@ -73,6 +84,14 @@ struct ChatListView: View { } } + @ViewBuilder private func chatView(_ chatId: ChatId?) -> some View { + if let chatId = chatId, let chat = chatModel.getChat(chatId) { + ChatView(chat: chat).onAppear { + loadChat(chat: chat) + } + } + } + private func filteredChats() -> [Chat] { let s = searchText.trimmingCharacters(in: .whitespaces).localizedLowercase return s == "" diff --git a/apps/ios/Shared/Views/Helpers/NavLinkPlain.swift b/apps/ios/Shared/Views/Helpers/NavLinkPlain.swift index fb12292b6a..3dde57a427 100644 --- a/apps/ios/Shared/Views/Helpers/NavLinkPlain.swift +++ b/apps/ios/Shared/Views/Helpers/NavLinkPlain.swift @@ -8,10 +8,9 @@ import SwiftUI -struct NavLinkPlain: View { +struct NavLinkPlain: View { @State var tag: V @Binding var selection: V? - @ViewBuilder var destination: () -> Destination @ViewBuilder var label: () -> Label var disabled = false @@ -21,10 +20,6 @@ struct NavLinkPlain: View { .disabled(disabled) label() } - .background { - NavigationLink("", tag: tag, selection: $selection, destination: destination) - .hidden() - } } }