diff --git a/packages/common/src/context/comments/commentsContext.tsx b/packages/common/src/context/comments/commentsContext.tsx index b1e568177ac..6dade224763 100644 --- a/packages/common/src/context/comments/commentsContext.tsx +++ b/packages/common/src/context/comments/commentsContext.tsx @@ -45,6 +45,17 @@ type CommentSectionProviderProps = { ) => void navigation?: NavigationProp closeDrawer?: () => void + /** + * Close the comment drawer AND any open now-playing drawer behind it. + * Passed from CommentDrawerProvider and threaded through to CommentBlock + * so that tapping a user link / mention inside a comment can close both + * drawers before navigating away. + * + * This must be threaded via context (rather than calling useCommentDrawer() + * directly in CommentBlock) because @gorhom/bottom-sheet portals content + * outside the CommentDrawerContext.Provider's React tree. + */ + closeAndExitNowPlaying?: (trackId: ID) => void uid?: string /** * Opaque source tag for the playback queue when the user plays the track @@ -94,6 +105,7 @@ export function CommentSectionProvider( setReplyingAndEditingState, navigation, closeDrawer, + closeAndExitNowPlaying, playbackSource = 'comments' } = props const { data: track } = useTrack(entityId) @@ -263,6 +275,7 @@ export function CommentSectionProvider( loadMorePages: handleLoadMorePages, navigation, closeDrawer: handleCloseDrawer, + closeAndExitNowPlaying, hasNewComments }} > diff --git a/packages/mobile/src/components/comments/CommentBlock.tsx b/packages/mobile/src/components/comments/CommentBlock.tsx index 2b8fad0e6ed..2ca2839fe19 100644 --- a/packages/mobile/src/components/comments/CommentBlock.tsx +++ b/packages/mobile/src/components/comments/CommentBlock.tsx @@ -25,7 +25,6 @@ import { UserLink } from '../user-link' import { ArtistPick } from './ArtistPick' import { CommentActionBar } from './CommentActionBar' import { CommentBadge } from './CommentBadge' -import { useCommentDrawer } from './CommentDrawerContext' import { CommentText } from './CommentText' import { Timestamp } from './Timestamp' import { TimestampLink } from './TimestampLink' @@ -43,15 +42,21 @@ export const CommentBlockInternal = ( } ) => { const { comment, isPreview, parentCommentId, highlightedCommentId } = props - const { artistId, track, navigation } = useCurrentCommentSection() + const { artistId, track, navigation, closeAndExitNowPlaying } = + useCurrentCommentSection() // Use this for in-drawer navigation (profile pic, user link, mentions) // so the destination screen is visible once we navigate. The section // context's `closeDrawer` would only close the comment drawer and leave // the now-playing drawer covering the destination. - const { closeAndExitNowPlaying } = useCommentDrawer() + // + // closeAndExitNowPlaying is threaded through CommentSectionContext rather + // than read directly from useCommentDrawer() because @gorhom/portal renders + // BottomSheetModal content at a PortalHost that sits *outside* the + // CommentDrawerContext.Provider in the React tree — calling useCommentDrawer() + // here would throw every time a comment renders. const trackIdForCloseAll = track.track_id const handleNavigateAway = useCallback(() => { - closeAndExitNowPlaying(trackIdForCloseAll) + closeAndExitNowPlaying?.(trackIdForCloseAll) }, [closeAndExitNowPlaying, trackIdForCloseAll]) const { id: commentId, diff --git a/packages/mobile/src/components/comments/CommentDrawer.tsx b/packages/mobile/src/components/comments/CommentDrawer.tsx index f76b4cb267e..c6e5cc3f3d3 100644 --- a/packages/mobile/src/components/comments/CommentDrawer.tsx +++ b/packages/mobile/src/components/comments/CommentDrawer.tsx @@ -36,6 +36,7 @@ import { ProfilePicture } from 'app/components/core' import { UserBadges } from 'app/components/user-badges' import { LoadingSpinner } from 'app/harmony-native/components/LoadingSpinner/LoadingSpinner' +import { useCommentDrawer } from './CommentDrawerContext' import { CommentDrawerForm } from './CommentDrawerForm' import { CommentDrawerHeader } from './CommentDrawerHeader' import { CommentSkeleton } from './CommentSkeleton' @@ -239,6 +240,16 @@ export const CommentDrawer = (props: CommentDrawerProps) => { const insets = useSafeAreaInsets() const commentListRef = useRef(null) + // `closeAndExitNowPlaying` is read here (inside CommentDrawerContext.Provider) + // and threaded into CommentSectionContext so that CommentBlock can access it + // without calling useCommentDrawer() directly. We must NOT call + // useCommentDrawer() from inside the BottomSheetModal portal content because + // @gorhom/portal renders portal children at a PortalHost that is a *sibling* + // of the CommentDrawerContext.Provider in the tree, so the context is + // unreachable from there — causing a 100%-reproducible crash the moment any + // comment renders. + const { closeAndExitNowPlaying } = useCommentDrawer() + // When the drawer is opened from a lineup tile (e.g. the feed), the full // track may not yet be in the query cache. CommentSectionProvider returns // null until the track loads, which would render an empty bottom sheet, so @@ -353,6 +364,7 @@ export const CommentDrawer = (props: CommentDrawerProps) => { setReplyingAndEditingState={setReplyingAndEditingState} navigation={navigation} closeDrawer={handleCloseDrawer} + closeAndExitNowPlaying={closeAndExitNowPlaying} playbackSource={playbackSource} > diff --git a/packages/mobile/src/screens/app-screen/AppTabScreen.tsx b/packages/mobile/src/screens/app-screen/AppTabScreen.tsx index 468b79e8d0a..51ba9ee5c77 100644 --- a/packages/mobile/src/screens/app-screen/AppTabScreen.tsx +++ b/packages/mobile/src/screens/app-screen/AppTabScreen.tsx @@ -251,7 +251,13 @@ export const AppTabScreen = ({ baseScreen, Stack }: AppTabScreenProps) => {