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 { proxy in List { 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) } } } } } .environmentObject(conversation) .safeAreaInset(edge: .bottom, spacing: 0) { ConversationTextInput(autoScroll: $autoScroll) } .task { await conversation.loadMoreBackward() } } } // 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