1 /* 2 * Copyright (c) Meta Platforms, Inc. and affiliates. 3 * All rights reserved. 4 * 5 * This source code is licensed under the BSD-style license found in the 6 * LICENSE file in the root directory of this source tree. 7 */ 8 9 import SwiftUI 10 11 struct MessageListView: View { 12 @Binding var messages: [Message] 13 @State private var showScrollToBottomButton = false 14 @State private var userHasScrolled = false 15 @State private var keyboardHeight: CGFloat = 0 16 17 var body: some View { 18 ScrollViewReader { value in 19 ScrollView { 20 VStack { 21 ForEach(messages) { message in 22 MessageView(message: message) 23 .padding([.leading, .trailing], 20) 24 } 25 GeometryReader { geometry -> Color in 26 DispatchQueue.main.async { 27 let maxY = geometry.frame(in: .global).maxY 28 let screenHeight = UIScreen.main.bounds.height - keyboardHeight 29 let isBeyondBounds = maxY > screenHeight - 50 30 if showScrollToBottomButton != isBeyondBounds { 31 showScrollToBottomButton = isBeyondBounds 32 userHasScrolled = isBeyondBounds 33 } 34 } 35 return Color.clear 36 } 37 .frame(height: 0) 38 } 39 } 40 .onChange(of: messages) { 41 if !userHasScrolled, let lastMessageId = messages.last?.id { 42 withAnimation { 43 value.scrollTo(lastMessageId, anchor: .bottom) 44 } 45 } 46 } 47 .overlay( 48 Group { 49 if showScrollToBottomButton { 50 Button(action: { 51 withAnimation { 52 if let lastMessageId = messages.last?.id { 53 value.scrollTo(lastMessageId, anchor: .bottom) 54 } 55 userHasScrolled = false 56 } 57 }) { 58 ZStack { 59 Circle() 60 .fill(Color(UIColor.secondarySystemBackground).opacity(0.9)) 61 .frame(height: 28) 62 Image(systemName: "arrow.down.circle") 63 .resizable() 64 .aspectRatio(contentMode: .fit) 65 .frame(height: 28) 66 } 67 } 68 .transition(AnyTransition.opacity.animation(.easeInOut(duration: 0.2))) 69 } 70 }, 71 alignment: .bottom 72 ) 73 } 74 .onAppear { 75 NotificationCenter.default.addObserver(forName: UIResponder.keyboardWillShowNotification, object: nil, queue: .main) { notification in 76 let keyboardFrame = notification.userInfo?[UIResponder.keyboardFrameEndUserInfoKey] as? CGRect ?? .zero 77 keyboardHeight = keyboardFrame.height - 40 78 } 79 NotificationCenter.default.addObserver(forName: UIResponder.keyboardWillHideNotification, object: nil, queue: .main) { _ in 80 keyboardHeight = 0 81 } 82 } 83 .onDisappear { 84 NotificationCenter.default.removeObserver(self, name: UIResponder.keyboardWillShowNotification, object: nil) 85 NotificationCenter.default.removeObserver(self, name: UIResponder.keyboardWillHideNotification, object: nil) 86 } 87 } 88 } 89