From 0569e024423969cc8d274b739dc7ee82d28e4271 Mon Sep 17 00:00:00 2001 From: Samuel Newman Date: Tue, 22 Oct 2024 23:03:53 +0300 Subject: [PATCH] get monologue tab working against all odds --- src/view/com/pager/PagerWithHeader.tsx | 348 ++++++++++++++----------- src/view/screens/Profile.tsx | 124 ++++++--- 2 files changed, 283 insertions(+), 189 deletions(-) diff --git a/src/view/com/pager/PagerWithHeader.tsx b/src/view/com/pager/PagerWithHeader.tsx index 6d601c2899..079da59319 100644 --- a/src/view/com/pager/PagerWithHeader.tsx +++ b/src/view/com/pager/PagerWithHeader.tsx @@ -15,6 +15,7 @@ import Animated, { useAnimatedRef, useAnimatedStyle, useSharedValue, + withTiming, } from 'react-native-reanimated' import {useNonReactiveCallback} from '#/lib/hooks/useNonReactiveCallback' @@ -44,179 +45,210 @@ export interface PagerWithHeaderProps { onCurrentPageSelected?: (index: number) => void allowHeaderOverScroll?: boolean } -export const PagerWithHeader = React.forwardRef( - function PageWithHeaderImpl( - { - children, - testID, +export const PagerWithHeader = React.forwardRef< + PagerRef, + PagerWithHeaderProps & { + headerRef: React.Ref<{ + scrollHeaderAway: () => void + scrollHeaderBack: () => void + }> + } +>(function PageWithHeaderImpl( + { + children, + testID, + items, + isHeaderReady, + renderHeader, + initialPage, + onPageSelected, + onCurrentPageSelected, + allowHeaderOverScroll, + headerRef, + }, + ref, +) { + const [currentPage, setCurrentPage] = React.useState(0) + const [tabBarHeight, setTabBarHeight] = React.useState(0) + const [headerOnlyHeight, setHeaderOnlyHeight] = React.useState(0) + const scrollY = useSharedValue(0) + const headerHeight = headerOnlyHeight + tabBarHeight + + const maybePrevScrollY = useSharedValue(null) + function scrollHeaderAway() { + 'worklet' + maybePrevScrollY.value = scrollY.value + scrollY.value = withTiming(headerHeight) + } + + function scrollHeaderBack() { + 'worklet' + if (maybePrevScrollY.value !== null) { + scrollY.value = withTiming(maybePrevScrollY.value) + maybePrevScrollY.value = null + } + } + + React.useImperativeHandle(headerRef, () => ({ + scrollHeaderAway: () => { + runOnUI(scrollHeaderAway)() + }, + scrollHeaderBack: () => { + runOnUI(scrollHeaderBack)() + }, + })) + + // capture the header bar sizing + const onTabBarLayout = useNonReactiveCallback((evt: LayoutChangeEvent) => { + const height = evt.nativeEvent.layout.height + if (height > 0) { + // The rounding is necessary to prevent jumps on iOS + setTabBarHeight(Math.round(height * 2) / 2) + } + }) + const onHeaderOnlyLayout = useNonReactiveCallback((height: number) => { + if (height > 0) { + // The rounding is necessary to prevent jumps on iOS + setHeaderOnlyHeight(Math.round(height * 2) / 2) + } + }) + + const renderTabBar = React.useCallback( + (props: RenderTabBarFnProps) => { + return ( + + + + ) + }, + [ + headerOnlyHeight, items, isHeaderReady, renderHeader, - initialPage, - onPageSelected, + currentPage, onCurrentPageSelected, + onTabBarLayout, + onHeaderOnlyLayout, + scrollY, + testID, allowHeaderOverScroll, - }: PagerWithHeaderProps, - ref, - ) { - const [currentPage, setCurrentPage] = React.useState(0) - const [tabBarHeight, setTabBarHeight] = React.useState(0) - const [headerOnlyHeight, setHeaderOnlyHeight] = React.useState(0) - const scrollY = useSharedValue(0) - const headerHeight = headerOnlyHeight + tabBarHeight + ], + ) - // capture the header bar sizing - const onTabBarLayout = useNonReactiveCallback((evt: LayoutChangeEvent) => { - const height = evt.nativeEvent.layout.height - if (height > 0) { - // The rounding is necessary to prevent jumps on iOS - setTabBarHeight(Math.round(height * 2) / 2) - } - }) - const onHeaderOnlyLayout = useNonReactiveCallback((height: number) => { - if (height > 0) { - // The rounding is necessary to prevent jumps on iOS - setHeaderOnlyHeight(Math.round(height * 2) / 2) - } - }) + const scrollRefs = useSharedValue | null>>([]) + const registerRef = React.useCallback( + (scrollRef: AnimatedRef | null, atIndex: number) => { + scrollRefs.modify(refs => { + 'worklet' + refs[atIndex] = scrollRef + return refs + }) + }, + [scrollRefs], + ) - const renderTabBar = React.useCallback( - (props: RenderTabBarFnProps) => { - return ( - - - - ) - }, - [ - headerOnlyHeight, - items, - isHeaderReady, - renderHeader, - currentPage, - onCurrentPageSelected, - onTabBarLayout, - onHeaderOnlyLayout, - scrollY, - testID, - allowHeaderOverScroll, - ], - ) - - const scrollRefs = useSharedValue | null>>([]) - const registerRef = React.useCallback( - (scrollRef: AnimatedRef | null, atIndex: number) => { - scrollRefs.modify(refs => { - 'worklet' - refs[atIndex] = scrollRef - return refs - }) - }, - [scrollRefs], - ) - - const lastForcedScrollY = useSharedValue(0) - const adjustScrollForOtherPages = () => { - 'worklet' - const currentScrollY = scrollY.value - const forcedScrollY = Math.min(currentScrollY, headerOnlyHeight) - if (lastForcedScrollY.value !== forcedScrollY) { - lastForcedScrollY.value = forcedScrollY - const refs = scrollRefs.value - for (let i = 0; i < refs.length; i++) { - const scollRef = refs[i] - if (i !== currentPage && scollRef != null) { - scrollTo(scollRef, 0, forcedScrollY, false) - } + const lastForcedScrollY = useSharedValue(0) + const adjustScrollForOtherPages = () => { + 'worklet' + const currentScrollY = scrollY.value + const forcedScrollY = Math.min(currentScrollY, headerOnlyHeight) + if (lastForcedScrollY.value !== forcedScrollY) { + lastForcedScrollY.value = forcedScrollY + const refs = scrollRefs.value + for (let i = 0; i < refs.length; i++) { + const scollRef = refs[i] + if (i !== currentPage && scollRef != null) { + scrollTo(scollRef, 0, forcedScrollY, false) } } } + } - const throttleTimeout = React.useRef | null>( - null, - ) - const queueThrottledOnScroll = useNonReactiveCallback(() => { - if (!throttleTimeout.current) { - throttleTimeout.current = setTimeout(() => { - throttleTimeout.current = null - runOnUI(adjustScrollForOtherPages)() - }, 80 /* Sync often enough you're unlikely to catch it unsynced */) + const throttleTimeout = React.useRef | null>( + null, + ) + const queueThrottledOnScroll = useNonReactiveCallback(() => { + if (!throttleTimeout.current) { + throttleTimeout.current = setTimeout(() => { + throttleTimeout.current = null + runOnUI(adjustScrollForOtherPages)() + }, 80 /* Sync often enough you're unlikely to catch it unsynced */) + } + }) + + const onScrollWorklet = React.useCallback( + (e: NativeScrollEvent) => { + 'worklet' + const nextScrollY = e.contentOffset.y + // HACK: onScroll is reporting some strange values on load (negative header height). + // Highly improbable that you'd be overscrolled by over 400px - + // in fact, I actually can't do it, so let's just ignore those. -sfn + const isPossiblyInvalid = + headerHeight > 0 && Math.round(nextScrollY * 2) / 2 === -headerHeight + if (!isPossiblyInvalid) { + scrollY.value = nextScrollY + runOnJS(queueThrottledOnScroll)() } - }) + }, + [scrollY, queueThrottledOnScroll, headerHeight], + ) - const onScrollWorklet = React.useCallback( - (e: NativeScrollEvent) => { - 'worklet' - const nextScrollY = e.contentOffset.y - // HACK: onScroll is reporting some strange values on load (negative header height). - // Highly improbable that you'd be overscrolled by over 400px - - // in fact, I actually can't do it, so let's just ignore those. -sfn - const isPossiblyInvalid = - headerHeight > 0 && Math.round(nextScrollY * 2) / 2 === -headerHeight - if (!isPossiblyInvalid) { - scrollY.value = nextScrollY - runOnJS(queueThrottledOnScroll)() - } - }, - [scrollY, queueThrottledOnScroll, headerHeight], - ) - - const onPageSelectedInner = React.useCallback( - (index: number) => { - setCurrentPage(index) - onPageSelected?.(index) - }, - [onPageSelected, setCurrentPage], - ) - - const onPageSelecting = React.useCallback((index: number) => { + const onPageSelectedInner = React.useCallback( + (index: number) => { setCurrentPage(index) - }, []) + onPageSelected?.(index) + }, + [onPageSelected, setCurrentPage], + ) - return ( - - {toArray(children) - .filter(Boolean) - .map((child, i) => { - const isReady = - isHeaderReady && headerOnlyHeight > 0 && tabBarHeight > 0 - return ( - - - - ) - })} - - ) - }, -) + const onPageSelecting = React.useCallback((index: number) => { + setCurrentPage(index) + }, []) + + return ( + + {toArray(children) + .filter(Boolean) + .map((child, i) => { + const isReady = + isHeaderReady && headerOnlyHeight > 0 && tabBarHeight > 0 + return ( + + + + ) + })} + + ) +}) let PagerTabBar = ({ currentPage, diff --git a/src/view/screens/Profile.tsx b/src/view/screens/Profile.tsx index 677fe09f47..af496681d8 100644 --- a/src/view/screens/Profile.tsx +++ b/src/view/screens/Profile.tsx @@ -25,6 +25,7 @@ import {isInvalidHandle} from '#/lib/strings/handles' import {colors, s} from '#/lib/styles' import {useProfileShadow} from '#/state/cache/profile-shadow' import {listenSoftReset} from '#/state/events' +import {ConvoProvider, isConvoActive, useConvo} from '#/state/messages/convo' import {useModerationOpts} from '#/state/preferences/moderation-opts' import {useActorStarterPacksQuery} from '#/state/queries/actor-starter-packs' import {useLabelerInfoQuery} from '#/state/queries/labeler' @@ -41,6 +42,7 @@ import {ErrorScreen} from '#/view/com/util/error/ErrorScreen' import {FAB} from '#/view/com/util/fab/FAB' import {ListRef} from '#/view/com/util/List' import {CenteredView} from '#/view/com/util/Views' +import {MessagesList} from '#/screens/Messages/components/MessagesList' import {ProfileHeader, ProfileHeaderLoading} from '#/screens/Profile/Header' import {ProfileFeedSection} from '#/screens/Profile/Sections/Feed' import {ProfileLabelsSection} from '#/screens/Profile/Sections/Labels' @@ -190,8 +192,9 @@ function ProfileScreenLoaded({ const [scrollViewTag, setScrollViewTag] = React.useState(null) const postsSectionRef = React.useRef(null) - const repliesSectionRef = React.useRef(null) - const mediaSectionRef = React.useRef(null) + // const repliesSectionRef = React.useRef(null) + // const mediaSectionRef = React.useRef(null) + const monologueSectionRef = React.useRef(null) const likesSectionRef = React.useRef(null) const feedsSectionRef = React.useRef(null) const listsSectionRef = React.useRef(null) @@ -213,8 +216,9 @@ function ProfileScreenLoaded({ const hasLabeler = !!profile.associated?.labeler const showFiltersTab = hasLabeler const showPostsTab = true - const showRepliesTab = hasSession - const showMediaTab = !hasLabeler + // const showRepliesTab = hasSession + // const showMediaTab = !hasLabeler + const showMonologueTab = !!currentAccount const showLikesTab = isMe const showFeedsTab = isMe || (profile.associated?.feedgens || 0) > 0 const showStarterPacksTab = @@ -226,8 +230,9 @@ function ProfileScreenLoaded({ showFiltersTab ? _(msg`Labels`) : undefined, showListsTab && hasLabeler ? _(msg`Lists`) : undefined, showPostsTab ? _(msg`Posts`) : undefined, - showRepliesTab ? _(msg`Replies`) : undefined, - showMediaTab ? _(msg`Media`) : undefined, + // showRepliesTab ? _(msg`Replies`) : undefined, + // showMediaTab ? _(msg`Media`) : undefined, + showMonologueTab ? _(msg`Monologue`) : undefined, showLikesTab ? _(msg`Likes`) : undefined, showFeedsTab ? _(msg`Feeds`) : undefined, showStarterPacksTab ? _(msg`Starter Packs`) : undefined, @@ -237,8 +242,9 @@ function ProfileScreenLoaded({ let nextIndex = 0 let filtersIndex: number | null = null let postsIndex: number | null = null - let repliesIndex: number | null = null - let mediaIndex: number | null = null + // let repliesIndex: number | null = null + // let mediaIndex: number | null = null + let monologueIndex: number | null = null let likesIndex: number | null = null let feedsIndex: number | null = null let starterPacksIndex: number | null = null @@ -249,11 +255,14 @@ function ProfileScreenLoaded({ if (showPostsTab) { postsIndex = nextIndex++ } - if (showRepliesTab) { - repliesIndex = nextIndex++ - } - if (showMediaTab) { - mediaIndex = nextIndex++ + // if (showRepliesTab) { + // repliesIndex = nextIndex++ + // } + // if (showMediaTab) { + // mediaIndex = nextIndex++ + // } + if (showMonologueTab) { + monologueIndex = nextIndex++ } if (showLikesTab) { likesIndex = nextIndex++ @@ -274,10 +283,12 @@ function ProfileScreenLoaded({ labelsSectionRef.current?.scrollToTop() } else if (index === postsIndex) { postsSectionRef.current?.scrollToTop() - } else if (index === repliesIndex) { - repliesSectionRef.current?.scrollToTop() - } else if (index === mediaIndex) { - mediaSectionRef.current?.scrollToTop() + // } else if (index === repliesIndex) { + // repliesSectionRef.current?.scrollToTop() + // } else if (index === mediaIndex) { + // mediaSectionRef.current?.scrollToTop() + } else if (index === monologueIndex) { + monologueSectionRef.current?.scrollToTop() } else if (index === likesIndex) { likesSectionRef.current?.scrollToTop() } else if (index === feedsIndex) { @@ -291,8 +302,9 @@ function ProfileScreenLoaded({ [ filtersIndex, postsIndex, - repliesIndex, - mediaIndex, + // repliesIndex, + // mediaIndex, + monologueIndex, likesIndex, feedsIndex, listsIndex, @@ -330,7 +342,17 @@ function ProfileScreenLoaded({ openComposer({mention}) } + const headerRef = React.useRef<{ + scrollHeaderAway: () => void + scrollHeaderBack: () => void + }>(null!) + const onPageSelected = (i: number) => { + if (showMonologueTab && i === monologueIndex) { + headerRef.current?.scrollHeaderAway() + } else { + headerRef.current?.scrollHeaderBack() + } setCurrentPage(i) } @@ -363,6 +385,7 @@ function ProfileScreenLoaded({ screenDescription={_(msg`profile`)} modui={moderation.ui('profileView')}> ) : null} - {showRepliesTab + {showMonologueTab + ? ({headerHeight, isFocused, scrollElRef}) => ( + + + + ) + : null} + {/* {showRepliesTab ? ({headerHeight, isFocused, scrollElRef}) => ( ) - : null} + : null} */} {showLikesTab ? ({headerHeight, isFocused, scrollElRef}) => ( - {hasSession && ( - } - accessibilityRole="button" - accessibilityLabel={_(msg`New post`)} - accessibilityHint="" - /> - )} + {hasSession && + (showMonologueTab ? monologueIndex !== currentPage : true) && ( + } + accessibilityRole="button" + accessibilityLabel={_(msg`New post`)} + accessibilityHint="" + /> + )} ) } +function ProfileMonologueSection({}: // isFocused, +// scrollElRef, +// headerHeight, +// setScrollViewTag, +{ + headerHeight: number + isFocused: boolean + scrollElRef: ListRef + setScrollViewTag: (tag: number | null) => void +}) { + const [hasScrolled, setHasScrolled] = React.useState(false) + const convo = useConvo() + + if (isConvoActive(convo)) { + return ( + } + /> + ) + } +} + function useRichText(text: string): [RichTextAPI, boolean] { const agent = useAgent() const [prevText, setPrevText] = React.useState(text)