AzureCommunicationUI/sdk/AzureCommunicationUICalling/Sources/Presentation/SwiftUI/Calling/CallingViewComponent/CaptionsRtt/CaptionsRttInfoView.swift (150 lines of code) (raw):

// // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. // import SwiftUI import FluentUI struct CaptionsRttInfoView: View { @ObservedObject var viewModel: CaptionsRttInfoViewModel var avatarViewManager: AvatarViewManagerProtocol @State private var isLastItemVisible = true @State private var previousDrawerHeight: CGFloat = 0 @Environment(\.verticalSizeClass) var verticalSizeClass var body: some View { GeometryReader { geometry in if viewModel.isLoading { loadingView } else { contentView(geometry: geometry) } } } // MARK: - Subviews @ViewBuilder private func contentView(geometry: GeometryProxy) -> some View { let containerHeight: CGFloat = geometry.size.height ScrollViewReader { scrollView in ScrollView { contentListView(scrollView: scrollView, parentGeometry: geometry) .frame(minHeight: containerHeight, alignment: .bottom) .frame(maxWidth: .infinity) } .background(Color(StyleProvider.color.drawerColor)) .onChange(of: containerHeight) { newHeight in if newHeight != previousDrawerHeight { previousDrawerHeight = newHeight // Only scroll if the last item is visible if isLastItemVisible { DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) { scrollToBottom(scrollView) } } } } .onChange(of: viewModel.displayData) { _ in if isLastItemVisible { scrollToBottom(scrollView) } } } } @ViewBuilder private func contentListView(scrollView: ScrollViewProxy, parentGeometry: GeometryProxy) -> some View { VStack(spacing: 0) { ForEach(viewModel.displayData.indices, id: \.self) { index in let data = viewModel.displayData[index] if data.captionsRttType == .rttInfo { rttInfoCell() .id(data.id) } else { CaptionsRttInfoCellView( displayData: data, avatarViewManager: avatarViewManager, localizationProvider: viewModel.localizationProvider ) .id(viewModel.displayData[index].id) .background(lastItemBackgroundIfNeeded(index: index, parentFrame: parentGeometry)) } }.onAppear { if isLastItemVisible { scrollToBottom(scrollView) } } } } @ViewBuilder private func lastItemBackgroundIfNeeded(index: Int, parentFrame: GeometryProxy) -> some View { if index == viewModel.displayData.indices.last { GeometryReader { geo in Color.clear .onAppear { checkLastItemVisibility(geometry: geo, parentFrame: parentFrame) } .onChange(of: viewModel.displayData) { _ in // Force re-check visibility when display data changes checkLastItemVisibility(geometry: geo, parentFrame: parentFrame) } } } else { EmptyView() } } private var loadingView: some View { VStack { Spacer() HStack { Spacer() ActivityIndicator(size: .small) .isAnimating(true) Text(viewModel.loadingMessage) .font(.caption) .foregroundColor(Color(StyleProvider.color.textSecondary)) Spacer() } Spacer() } } private func rttInfoCell() -> some View { HStack(alignment: .top, spacing: 12) { Icon(name: CompositeIcon.rtt, size: DrawerListConstants.iconSize) .foregroundColor(Color(StyleProvider.color.drawerIconDark)) .accessibilityHidden(true) .padding([.top, .leading], 10) warningMessage .font(.body) .foregroundColor(.primary) .fixedSize(horizontal: false, vertical: true) .padding([.top, .trailing, .bottom], 10) } .background(Color(StyleProvider.color.surface)) .cornerRadius(8) .padding(.horizontal, 10) .id("RTTInfoView") } private var warningMessage: some View { Group { Text(viewModel.rttInfoMessage) + Text(" ") + Text(viewModel.localizationProvider.getLocalizedString(.rttLinkLearnMore)) .foregroundColor(Color(StyleProvider.color.primaryColor)) }.accessibilityElement(children: .combine) .accessibilityAddTraits(.isLink) .onTapGesture { if let url = URL(string: StringConstants.rttLearnMoreLink) { UIApplication.shared.open(url) } } } // MARK: - Helper Methods private func scrollToBottom(_ scrollView: ScrollViewProxy) { if let lastID = viewModel.displayData.last?.id { withAnimation { scrollView.scrollTo(lastID, anchor: .bottom) } } else if viewModel.isRttDisplayed { withAnimation { scrollView.scrollTo("RTTInfoView", anchor: .bottom) } } } private func checkLastItemVisibility(geometry: GeometryProxy, parentFrame: GeometryProxy) { let itemFrame = geometry.frame(in: .global) let parentBounds = parentFrame.frame(in: .global) let isVisible = itemFrame.maxY > parentBounds.minY && itemFrame.minY < parentBounds.maxY if isLastItemVisible != isVisible { isLastItemVisible = isVisible } } }