diff --git a/ConversationsClassic/AppData/Store/ClientsStore.swift b/ConversationsClassic/AppData/Store/ClientsStore.swift index 1820e29..178663b 100644 --- a/ConversationsClassic/AppData/Store/ClientsStore.swift +++ b/ConversationsClassic/AppData/Store/ClientsStore.swift @@ -139,3 +139,17 @@ extension ClientsStore { } } } + +extension ClientsStore { + func conversationStore(for roster: Roster) async throws -> ConversationStore { + while !ready { + await Task.yield() + } + + guard let client = clients.first(where: { $0.credentials.bareJid == roster.bareJid }) else { + throw ClientStoreError.clientNotFound + } + + return ConversationStore(roster: roster, client: client) + } +} diff --git a/ConversationsClassic/AppData/Store/ConversationStore.swift b/ConversationsClassic/AppData/Store/ConversationStore.swift new file mode 100644 index 0000000..5b2a461 --- /dev/null +++ b/ConversationsClassic/AppData/Store/ConversationStore.swift @@ -0,0 +1,15 @@ +import Foundation + +@MainActor +final class ConversationStore: ObservableObject { + @Published private(set) var roster: Roster + @Published private(set) var messages: [String] = [] + @Published private(set) var test = true + + private let client: Client + + init(roster: Roster, client: Client) { + self.client = client + self.roster = roster + } +} diff --git a/ConversationsClassic/Resources/Strings/Localizable.strings b/ConversationsClassic/Resources/Strings/Localizable.strings index 65097e2..14a3c1b 100644 --- a/ConversationsClassic/Resources/Strings/Localizable.strings +++ b/ConversationsClassic/Resources/Strings/Localizable.strings @@ -44,7 +44,9 @@ // MARK: Chats list screen "ChatsList.title" = "Chats"; - +// MARK: Conversation +"Conversation.title" = "Conversation"; +"Conversation.startError" = "Error occurs in conversation starting"; diff --git a/ConversationsClassic/View/Main/ChatList/ChatsListScreen.swift b/ConversationsClassic/View/Main/ChatList/ChatsListScreen.swift index ee3391a..9f936b2 100644 --- a/ConversationsClassic/View/Main/ChatList/ChatsListScreen.swift +++ b/ConversationsClassic/View/Main/ChatList/ChatsListScreen.swift @@ -43,14 +43,37 @@ struct ChatsListScreen: View { } private struct ChatsRow: View { - // @EnvironmentObject var store: AppStore + @Environment(\.router) var router + @EnvironmentObject var clientsStore: ClientsStore var chat: Chat var body: some View { SharedListRow(iconType: .charCircle(chat.participant), text: chat.participant) .onTapGesture { - // store.dispatch(.chatsAction(.startChat(accountJid: chat.account, participantJid: chat.participant))) + // Task { + // router.showModal { + // LoadingScreen() + // } + // defer { + // router.dismissModal() + // } + // + // do { + // let conversation = try await clientsStore.conversationStore(for: roster) + // router.showScreen(.push) { _ in + // ConversationScreen(conversation: conversation) + // } + // } catch { + // router.showAlert( + // .alert, + // title: L10n.Global.Error.title, + // subtitle: L10n.Conversation.startError + // ) { + // Button(L10n.Global.ok, role: .cancel) {} + // } + // } + // } } } } diff --git a/ConversationsClassic/View/Main/Contacts/ContactsScreen.swift b/ConversationsClassic/View/Main/Contacts/ContactsScreen.swift index 6436a5b..aafaf22 100644 --- a/ConversationsClassic/View/Main/Contacts/ContactsScreen.swift +++ b/ConversationsClassic/View/Main/Contacts/ContactsScreen.swift @@ -56,7 +56,7 @@ private struct ContactsScreenRow: View { text: roster.contactBareJid ) .onTapGesture { - // store.dispatch(.chatsAction(.startChat(accountJid: roster.bareJid, participantJid: roster.contactBareJid))) + startChat() } .swipeActions(edge: .trailing, allowsFullSwipe: false) { Button { @@ -70,7 +70,7 @@ private struct ContactsScreenRow: View { } .contextMenu { Button(L10n.Contacts.sendMessage, systemImage: "message") { - // store.dispatch(.chatsAction(.startChat(accountJid: roster.bareJid, participantJid: roster.contactBareJid))) + startChat() } Divider() @@ -147,4 +147,31 @@ private struct ContactsScreenRow: View { } } } + + private func startChat() { + Task { + router.showModal { + LoadingScreen() + } + defer { + router.dismissModal() + } + + do { + let conversation = try await clientsStore.conversationStore(for: roster) + router.showScreen(.push) { _ in + ConversationScreen(conversation: conversation) + .navigationBarHidden(true) + } + } catch { + router.showAlert( + .alert, + title: L10n.Global.Error.title, + subtitle: L10n.Conversation.startError + ) { + Button(L10n.Global.ok, role: .cancel) {} + } + } + } + } } diff --git a/ConversationsClassic/View/Main/Conversation/ConversationScreen.swift b/ConversationsClassic/View/Main/Conversation/ConversationScreen.swift new file mode 100644 index 0000000..a4eb1e3 --- /dev/null +++ b/ConversationsClassic/View/Main/Conversation/ConversationScreen.swift @@ -0,0 +1,196 @@ +import Combine +import Foundation +import Martin +import SwiftUI + +struct ConversationScreen: View { + @Environment(\.router) var router + @State var conversation: ConversationStore + + @State private var autoScroll = true + @State private var firstIsVisible = true + + var body: some View { + ZStack { + // Background color + Color.Material.Background.light + .ignoresSafeArea() + + // Content + VStack(spacing: 0) { + // Header + SharedNavigationBar( + leftButton: .init( + image: Image(systemName: "chevron.left"), + action: { + router.dismissScreen() + } + ), + centerText: .init(text: L10n.Conversation.title) + ) + + // Msg list + let messages = conversation.messages + if !messages.isEmpty { + ScrollViewReader { _ in + List { + Text("Test") + // ForEach(messages) { message in + // ConversationMessageRow(message: message) + // .id(message.id) + // .onAppear { + // if message.id == messages.first?.id { + // firstIsVisible = true + // autoScroll = true + // } + // } + // .onDisappear { + // if message.id == messages.first?.id { + // firstIsVisible = false + // autoScroll = false + // } + // } + // } + // .rotationEffect(.degrees(180)) + } + .rotationEffect(.degrees(180)) + .listStyle(.plain) + .background(Color.Material.Background.light) + .scrollDismissesKeyboard(.immediately) + .scrollIndicators(.hidden) + // .onChange(of: autoScroll) { new in + // if new, !firstIsVisible { + // withAnimation { + // proxy.scrollTo(messages.first?.id, anchor: .top) + // } + // } + // } + } + } else { + Spacer() + } + } + .onTapGesture { + UIApplication.shared.sendAction(#selector(UIResponder.resignFirstResponder), to: nil, from: nil, for: nil) + } + + // Jump to last button + if !autoScroll { + VStack { + Spacer() + HStack { + Spacer() + Button { + autoScroll = true + } label: { + ZStack { + Circle() + .fill(Color.Material.Shape.white) + Image(systemName: "arrow.down") + .foregroundColor(.Material.Elements.active) + } + .frame(width: 40, height: 40) + .shadow(color: .black.opacity(0.2), radius: 4) + .padding(.trailing, 8) + .padding(.bottom, 8) + } + } + } + } + } + // .safeAreaInset(edge: .bottom, spacing: 0) { + // ConversationTextInput(autoScroll: $autoScroll) + // } + } +} + +// Preview +// #if DEBUG +// struct ConversationScreen_Previews: PreviewProvider { +// static var previews: some View { +// ConversationScreen() +// .environmentObject(pStore) +// } +// +// static var pStore: AppStore { +// let state = pState +// return AppStore(initialState: state, reducer: AppState.reducer, middlewares: []) +// } +// +// static var pState: AppState { +// var state = AppState() +// +// let acc = "user@test.com" +// let contact = "some@test.com" +// +// state.conversationsState.currentChat = Chat(id: "1", account: acc, participant: contact, type: .chat) +// state.conversationsState.currentMessages = [ +// Message( +// id: "1", +// type: .chat, +// contentType: .text, +// from: contact, +// to: acc, +// body: "this is for test sdgdsfg dsfg dsfgdg dsfgdfgsdgsdfgdfg sdfgdsfgdfsg dsfgdsfgsdfg dsfgdfgsdg fgf fgfg sdfsdf sdfsdf sdf sdfsdf sdf sdfsdf sdfsdfsdf sdfsdf ", +// subject: nil, +// thread: nil, +// oobUrl: nil, +// date: Date(), +// pending: true, sentError: false +// ), +// Message( +// id: "2", +// type: .chat, +// contentType: .text, +// from: contact, +// to: acc, +// body: "this is for testsdfsdf sdfsdf sdfs sdf sdffsdf sdf sdf sdf sdf sdf sdff sdfffwwe ", +// subject: nil, +// thread: nil, +// oobUrl: nil, +// date: Date(), +// pending: false, +// sentError: false +// ), +// Message(id: "3", type: .chat, contentType: .text, from: contact, to: acc, body: "this is for test", subject: nil, thread: nil, oobUrl: nil, date: Date(), pending: false, sentError: true), +// Message( +// id: "4", +// type: .chat, +// contentType: .text, +// from: acc, +// to: contact, +// body: "this is for test sdfkjwek jwkjfh jwerf jdfhskjdhf jsdhfjhwefh sjdhfh fsdjhfh sd ", +// subject: nil, +// thread: nil, +// oobUrl: nil, +// date: Date(), +// pending: false, +// sentError: false +// ), +// Message(id: "5", type: .chat, contentType: .text, from: contact, to: acc, body: "this is for test sdfjkkeke kekkddjw;; w;edkdjfj l kjwekrjfk wef", subject: nil, thread: nil, oobUrl: nil, date: Date(), pending: false, sentError: false), +// Message(id: "6", type: .chat, contentType: .text, from: acc, to: contact, body: "this is for testsdf dsdkkekkddn wejkjfj ", subject: nil, thread: nil, oobUrl: nil, date: Date(), pending: false, sentError: false), +// Message( +// id: "7", +// type: .chat, +// contentType: .text, +// from: acc, +// to: contact, +// +// body: "this is for test sdgdsfg dsfg dsfgdg dsfgdfgsdgsdfgdfg sdfgdsfgdfsg dsfgdsfgsdfg dsfgdfgsdg fgf fgfg sdfsdf sdfsdf sdf sdfsdf sdf sdfsdf sdfsdfsdf sdfsdf ", +// subject: nil, +// thread: nil, +// oobUrl: nil, +// date: Date(), pending: false, sentError: false +// ), +// Message(id: "8", type: .chat, contentType: .text, from: acc, to: contact, body: "so test", subject: nil, thread: nil, oobUrl: nil, date: Date(), pending: false, sentError: false), +// Message(id: "9", type: .chat, contentType: .text, from: contact, to: acc, body: "so test", subject: nil, thread: nil, oobUrl: nil, date: Date(), pending: false, sentError: false), +// Message(id: "10", type: .chat, contentType: .text, from: acc, to: contact, body: "so test so test so test", subject: nil, thread: nil, oobUrl: nil, date: Date(), pending: false, sentError: false), +// Message(id: "11", type: .chat, contentType: .text, from: contact, to: acc, body: "xD", subject: nil, thread: nil, oobUrl: nil, date: Date(), pending: false, sentError: false) +// ] +// +// state.conversationsState.replyText = "> Some Text here! And if it a long and very long text sdfsadfsadfsafsadfsadfsadfsadfassadfsadfsafsafdsadfsafdsadfsadfas sdf sdf asdf sdfasdfsd sdfasdf sdfsdfdsasdfsdfa dsafsaf" +// +// return state +// } +// } +// #endif