diff --git a/Sources/ExyteChat/Views/MessageView/MessageMenu/MessageMenu+ReactionSelectionView.swift b/Sources/ExyteChat/Views/MessageView/MessageMenu/MessageMenu+ReactionSelectionView.swift index 1f31f98a..6eced1d2 100644 --- a/Sources/ExyteChat/Views/MessageView/MessageMenu/MessageMenu+ReactionSelectionView.swift +++ b/Sources/ExyteChat/Views/MessageView/MessageMenu/MessageMenu+ReactionSelectionView.swift @@ -28,6 +28,7 @@ struct ReactionSelectionView: View { @State private var xOffset: CGFloat = 0.0 @State private var yOffset: CGFloat = 0.0 @State private var viewState: ViewState = .initial + @Binding var chatViewFrame: CGRect @State private var bubbleDiameter: CGFloat = .zero @@ -177,7 +178,7 @@ struct ReactionSelectionView: View { Color.clear.viewWidth(max(1, leadingPadding - 8)) Spacer() } else { - let additionalPadding = max(0, UIScreen.main.bounds.width - maxSelectionRowWidth - trailingPadding) + let additionalPadding = max(0, chatViewFrame.width - maxSelectionRowWidth - trailingPadding) Color.clear.viewWidth(additionalPadding + trailingPadding * 3) } } @@ -188,7 +189,7 @@ struct ReactionSelectionView: View { Spacer() Color.clear.viewWidth(trailingPadding) } else { - let additionalPadding = max(0, UIScreen.main.bounds.width - maxSelectionRowWidth - leadingPadding) + let additionalPadding = max(0, chatViewFrame.width - maxSelectionRowWidth - leadingPadding) Color.clear.viewWidth(additionalPadding + trailingPadding * 3) } } @@ -287,16 +288,16 @@ struct ReactionSelectionView: View { /// - Note: If the messageFrame's width is equal to, or larger than, the Screens width then we skip the offset animation /// - Note: This also prevents the offset animation from occuring when the user uses a custom message builder private func getXOffset() -> CGFloat { - guard viewModel.messageFrame.width < UIScreen.main.bounds.width else { return .leastNonzeroMagnitude } + guard viewModel.messageFrame.width < chatViewFrame.width else { return .leastNonzeroMagnitude } switch viewState { case .initial, .row: return .leastNonzeroMagnitude case .search, .picked: if alignment == .left { - let additionalPadding = max(0, UIScreen.main.bounds.width - maxSelectionRowWidth - leadingPadding) - UIApplication.safeArea.leading - return -((UIScreen.main.bounds.width - (additionalPadding + trailingPadding * 3) - (bubbleDiameter * 0.8)) - viewModel.messageFrame.maxX) + let additionalPadding = max(0, chatViewFrame.width - maxSelectionRowWidth - leadingPadding) - UIApplication.safeArea.leading + return -((chatViewFrame.width - (additionalPadding + trailingPadding * 3) - (bubbleDiameter * 0.8)) - viewModel.messageFrame.maxX) } else { - let additionalPadding = max(0, UIScreen.main.bounds.width - maxSelectionRowWidth - trailingPadding) - UIApplication.safeArea.leading + let additionalPadding = max(0, chatViewFrame.width - maxSelectionRowWidth - trailingPadding) - UIApplication.safeArea.leading return viewModel.messageFrame.minX - ((additionalPadding + trailingPadding * 3) + (bubbleDiameter * 0.8)) } } @@ -307,7 +308,7 @@ struct ReactionSelectionView: View { /// - Note: If the messageFrame's width is equal to, or larger than, the Screens width then we skip the offset animation /// - Note: This also prevents the offset animation from occuring when the user uses a custom message builder private func getYOffset() -> CGFloat { - guard viewModel.messageFrame.width < UIScreen.main.bounds.width else { return .leastNonzeroMagnitude } + guard viewModel.messageFrame.width < chatViewFrame.width else { return .leastNonzeroMagnitude } switch viewState { case .initial, .row: return .leastNonzeroMagnitude @@ -396,6 +397,7 @@ internal struct InteriorRadialShadow: ViewModifier { VStack { ReactionSelectionView( viewModel: ChatViewModel(), + chatViewFrame: .constant(UIScreen.main.bounds), backgroundColor: .gray, selectedColor: .blue, animation: .linear(duration: 0.2), diff --git a/Sources/ExyteChat/Views/MessageView/MessageMenu/MessageMenu.swift b/Sources/ExyteChat/Views/MessageView/MessageMenu/MessageMenu.swift index 396f8fbf..452888da 100644 --- a/Sources/ExyteChat/Views/MessageView/MessageMenu/MessageMenu.swift +++ b/Sources/ExyteChat/Views/MessageView/MessageMenu/MessageMenu.swift @@ -30,7 +30,7 @@ struct MessageMenu: View { @Binding var isShowingMenu: Bool /// Overall ChatView Frame - let chatViewFrame: CGRect = UIScreen.main.bounds + @State private var chatViewFrame: CGRect = .zero /// The max height for the menu /// - Note: menus that exceed this value will be placed in a ScrollView @@ -152,62 +152,75 @@ struct MessageMenu: View { } public var body: some View { - ZStack(alignment: .top) { - - // Reaction Overview Rectangle - if reactionOverviewIsVisible, case .vStack = messageMenuStyle { - ReactionOverview(viewModel: viewModel, message: message, width: reactionOverviewWidth, backgroundColor: theme.colors.messageFriendBG, inScrollView: false) - .frame(width: reactionOverviewWidth) - .maxHeightGetter($reactionOverviewHeight) - .offset(y: UIApplication.safeArea.top) - .transition(defaultTransition) + GeometryReader { geometry in + ZStack(alignment: .top) { + + // Reaction Overview Rectangle + if reactionOverviewIsVisible, case .vStack = messageMenuStyle { + ReactionOverview(viewModel: viewModel, message: message, width: reactionOverviewWidth, backgroundColor: theme.colors.messageFriendBG, inScrollView: false) + .frame(width: reactionOverviewWidth) + .maxHeightGetter($reactionOverviewHeight) + .offset(y: UIApplication.safeArea.top) + .transition(defaultTransition) + .opacity(messageMenuOpacity) + } + + // Some views to help debug layout and animations + //debugViews() + + // The message and menu view + messageMenuView() + .frameGetter($messageMenuFrame) + .position(x: chatViewFrame.width / 2 + horizontalOffset, y: verticalOffset) .opacity(messageMenuOpacity) + } - - // Some views to help debug layout and animations - //debugViews() - - // The message and menu view - messageMenuView() - .frameGetter($messageMenuFrame) - .position(x: chatViewFrame.width / 2 + horizontalOffset, y: verticalOffset) - .opacity(messageMenuOpacity) - - } - .frame(maxWidth: .infinity, maxHeight: .infinity) - .edgesIgnoringSafeArea(.all) - .background( - ZStack { - Rectangle() - .foregroundStyle(.ultraThinMaterial) - Rectangle() - .fill(.primary.opacity(0.1)) - } + .frame(maxWidth: .infinity, maxHeight: .infinity) .edgesIgnoringSafeArea(.all) - .opacity(backgroundOpacity) - .onTapGesture { - if viewState == .keyboard { - keyboardState.resignFirstResponder() + .background( + ZStack { + Rectangle() + .foregroundStyle(.ultraThinMaterial) + Rectangle() + .fill(.primary.opacity(0.1)) + } + .edgesIgnoringSafeArea(.all) + .opacity(backgroundOpacity) + .onTapGesture { + if viewState == .keyboard { + keyboardState.resignFirstResponder() + transitionViewState(to: .ready) + } else { + dismissSelf() + } + } + ) + .onAppear { + chatViewFrame = geometry.frame(in: .global) + + transitionViewState(to: .prepare) + + DispatchQueue.main.asyncAfter(deadline: .now() + .milliseconds(Int(animationDuration * 333))) { + transitionViewState(to: .original) + } + + DispatchQueue.main.asyncAfter(deadline: .now() + .milliseconds(Int(animationDuration * 666))) { transitionViewState(to: .ready) - } else { - dismissSelf() } } - ) - .onAppear { - transitionViewState(to: .prepare) - - DispatchQueue.main.asyncAfter(deadline: .now() + .milliseconds(Int(animationDuration * 333))) { - transitionViewState(to: .original) - } - - DispatchQueue.main.asyncAfter(deadline: .now() + .milliseconds(Int(animationDuration * 666))) { - transitionViewState(to: .ready) + .onChange(of: keyboardState.keyboardFrame) { + if viewState == .ready, keyboardState.isShown { + transitionViewState(to: .keyboard) + } } - } - .onChange(of: keyboardState.keyboardFrame) { - if viewState == .ready, keyboardState.isShown { - transitionViewState(to: .keyboard) + .onChange(of: geometry.size) { _ , newSize in + chatViewFrame = CGRect( + origin: chatViewFrame.origin, + size: CGSize(width: newSize.width, height: newSize.height) + ) + + // Recalculate reaction overview width + reactionOverviewWidth = chatViewFrame.width - UIApplication.safeArea.leading - UIApplication.safeArea.trailing } } } @@ -231,7 +244,7 @@ struct MessageMenu: View { reactionOverviewIsVisible = shouldShowReactionOverviewView reactionSelectionIsVisible = shouldShowReactionSelectionView menuIsVisible = true - verticalOffset = UIScreen.main.bounds.height * 2 + verticalOffset = chatViewFrame.height * 2 /// Kick off the background animation withAnimation(.easeInOut(duration: animationDuration)) { @@ -250,7 +263,7 @@ struct MessageMenu: View { } /// If we're in landscape mode, adjust the `horizontalOffset` appropriately - if UIScreen.main.bounds.width > UIScreen.main.bounds.height { + if chatViewFrame.width > chatViewFrame.height { switch alignment { case .left: horizontalOffset = UIApplication.safeArea.leading @@ -456,6 +469,7 @@ struct MessageMenu: View { if reactionSelectionIsVisible { ReactionSelectionView( viewModel: viewModel, + chatViewFrame: $chatViewFrame, backgroundColor: theme.colors.messageFriendBG, selectedColor: theme.colors.messageMyBG, animation: .bouncy(duration: animationDuration),