import Combine import Foundation import Martin import SwiftUI struct ConversationScreen: View { @EnvironmentObject var store: AppStore @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 let name = ( store.state.conversationsState.currentRoster?.name ?? store.state.conversationsState.currentRoster?.contactBareJid ) ?? L10n.Chat.title SharedNavigationBar( leftButton: .init( image: Image(systemName: "chevron.left"), action: { store.dispatch(.changeFlow(store.state.previousFlow)) } ), centerText: .init(text: name) ) // Msg list let messages = store.state.conversationsState.currentMessages 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) } } } } } .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