34582edf3e
* tweaks to constants (#7478) * add did * use correct did * typo * tweak * Prevent Drawer gesture conflicting with Suggestions scroll (#7468) * Extract BlockDrawerGeesture * Block drawer when scrolling interstitials (cherry picked from commit9e3f2f4374) * yolo interstitial * yolo mode * right swipe * fix nav gesture * vibe controls * collapsible post text * rm blurview, cover for tall videos * smarter video source handling * use thumbnails, improve perf significantly * better android loading * improve aspect ratio * optimize source changes * rm spinner on ios * whoops, remove debug `false` * FIX WRONG VIDEOS SHOWING UP * don't spring on way down * release video players when leaving screen * remove jank animation * Add grid * improve contract, fix double tap * Filter out posts without videos * Only do grid on native * Pipe through feedSourceUri and link to feed * Handle passed through params * Partial revert, just filter posts to start at index * Clean up cards, remove entry interstitial * Tweak handle * Change constant name * Rename some things * Make types legit * Clean up more naming * Add placeholder for grid view * Handle web, set up new organization * Begin work on Header * Replace types * Squashed commit of the following: commit3d1be4c0f1Author: Samuel Newman <mozzius@protonmail.com> Date: Fri Jan 17 01:08:05 2025 +0000 extend animation commitc9f199413bAuthor: Samuel Newman <mozzius@protonmail.com> Date: Fri Jan 17 01:01:24 2025 +0000 fix gap commit22e520795fAuthor: Samuel Newman <mozzius@protonmail.com> Date: Fri Jan 17 00:50:16 2025 +0000 thinner bar, format time commitc32427f214Author: Samuel Newman <mozzius@protonmail.com> Date: Fri Jan 17 00:47:57 2025 +0000 fix 2 in 3 screens commitcbf84c08d6Author: Samuel Newman <mozzius@protonmail.com> Date: Fri Jan 17 00:45:46 2025 +0000 rm unneeded var commit7e0e100177Author: Samuel Newman <mozzius@protonmail.com> Date: Fri Jan 17 00:41:18 2025 +0000 scrubberrrrr * use white with opacity rather than gray * Simultaneous gesture * cleanup attempt * fix jank * link to profile on press * fix jitter fr this time * mostly fix android flicker * Maybe fix row generation * Add content hider to video card * emoji in post text * reduce update rate * fix type error * Fix grid layout trailing single item * Add Discover interstitial, settings, includes pin for now * Explore interstitial, handle dimissal, pinning, compact card * Only use grid placeholder on native * Update events * Add feature gate * android nav bar fixes + lower update speed * fix interval + decel rate on interstitials * attempt to fix broken scrub on android (not working) * follow button * Part out the interstitials for perf, add view more * Remove prod web route * Wrap interstitials with BlockDrawerGesture * Bring video cropping in line with images (#7462) * Mimic image cropping for videos on web * Same on native * Rename variables for clarity * Fix Android scrubbing * Add FeedFeedbackProvider * Remove swipe gesture * fix light status bar behaviour * bump * feedback * Copy pasta to new location * Copy pasta part deux * Filter only videos * Make whole text clickable to expand (cherry picked from commit 4cf31120779f4e06eb4c296b3d4b53814d432b07) * move scrubber to own file * end card * add icon to end card * add min view time to viewability config * play haptic on like * tweak feedback * tweak feedback again * Moderation (cherry picked from commit 6b6b471cfb363031284b3e7a1f6e0ade3ac4ae47) * remove bad check * fix feedback for new video grid * change prop name to items as well * Simplify logic * Fix mod footer * Give scrubber more space on android * Add subtle track behind scrubber, adjust opacity * wire in feed context again... * Add better a11y desc to card * Fix key issue * Update a11y copy * Fix scrubber height * improve scrubber animation * Make follow button more obvious * Make header back button more clear * Disable interactions with actual video el * keep content away from the bottom safe area * fix blur * fix moderation issue * improve contrast on mod screen * Make moderation static per item * Memoize rows * Optimizations * Take video moderation into account * Only blur titles for list blur * Change copy * Bump blur radius * animate text in both directions * Rm unused field * Filter by root early * Refactor for clarity * add compose prompt to scrubber * rm log * tweak gradient * Bump SDK, use contentMode to power video feed * Ensure ProfileFeed view also supports video feed * improve scrubber on android * rm border from footer * Update prod video feed did * Separate caches * Add lil hover to View More * Fix undefined logic, remove header for interstitial * Ungate * Fix stuckness * remove extra useless map * Fix effect cleanup * Send seen without cleanup * Simplify react stuff * Earlier early return to avoid loading flash * remove scrubber placeholder * Remove opacity hack * Render useEvent conditionally * Fix Android flash --------- Co-authored-by: dan <dan.abramov@gmail.com> Co-authored-by: Samuel Newman <mozzius@protonmail.com> Co-authored-by: Eric Bailey <git@esb.lol>
336 lines
9.5 KiB
TypeScript
336 lines
9.5 KiB
TypeScript
import React, {useImperativeHandle} from 'react'
|
|
import {
|
|
NativeScrollEvent,
|
|
NativeSyntheticEvent,
|
|
Pressable,
|
|
ScrollView,
|
|
StyleProp,
|
|
TextInput,
|
|
View,
|
|
ViewStyle,
|
|
} from 'react-native'
|
|
import {
|
|
KeyboardAwareScrollView,
|
|
useKeyboardHandler,
|
|
} from 'react-native-keyboard-controller'
|
|
import {runOnJS} from 'react-native-reanimated'
|
|
import {ReanimatedScrollEvent} from 'react-native-reanimated/lib/typescript/hook/commonTypes'
|
|
import {useSafeAreaInsets} from 'react-native-safe-area-context'
|
|
import {msg} from '@lingui/macro'
|
|
import {useLingui} from '@lingui/react'
|
|
|
|
import {useEnableKeyboardController} from '#/lib/hooks/useEnableKeyboardController'
|
|
import {ScrollProvider} from '#/lib/ScrollContext'
|
|
import {logger} from '#/logger'
|
|
import {isAndroid, isIOS} from '#/platform/detection'
|
|
import {useA11y} from '#/state/a11y'
|
|
import {useDialogStateControlContext} from '#/state/dialogs'
|
|
import {List, ListMethods, ListProps} from '#/view/com/util/List'
|
|
import {atoms as a, useTheme} from '#/alf'
|
|
import {useThemeName} from '#/alf/util/useColorModeTheme'
|
|
import {Context, useDialogContext} from '#/components/Dialog/context'
|
|
import {
|
|
DialogControlProps,
|
|
DialogInnerProps,
|
|
DialogOuterProps,
|
|
} from '#/components/Dialog/types'
|
|
import {createInput} from '#/components/forms/TextField'
|
|
import {BottomSheet, BottomSheetSnapPoint} from '../../../modules/bottom-sheet'
|
|
import {
|
|
BottomSheetSnapPointChangeEvent,
|
|
BottomSheetStateChangeEvent,
|
|
} from '../../../modules/bottom-sheet/src/BottomSheet.types'
|
|
import {BottomSheetNativeComponent} from '../../../modules/bottom-sheet/src/BottomSheetNativeComponent'
|
|
|
|
export {useDialogContext, useDialogControl} from '#/components/Dialog/context'
|
|
export * from '#/components/Dialog/shared'
|
|
export * from '#/components/Dialog/types'
|
|
export * from '#/components/Dialog/utils'
|
|
// @ts-ignore
|
|
export const Input = createInput(TextInput)
|
|
|
|
export function Outer({
|
|
children,
|
|
control,
|
|
onClose,
|
|
nativeOptions,
|
|
testID,
|
|
}: React.PropsWithChildren<DialogOuterProps>) {
|
|
const themeName = useThemeName()
|
|
const t = useTheme(themeName)
|
|
const ref = React.useRef<BottomSheetNativeComponent>(null)
|
|
const closeCallbacks = React.useRef<(() => void)[]>([])
|
|
const {setDialogIsOpen, setFullyExpandedCount} =
|
|
useDialogStateControlContext()
|
|
|
|
const prevSnapPoint = React.useRef<BottomSheetSnapPoint>(
|
|
BottomSheetSnapPoint.Hidden,
|
|
)
|
|
|
|
const [disableDrag, setDisableDrag] = React.useState(false)
|
|
const [snapPoint, setSnapPoint] = React.useState<BottomSheetSnapPoint>(
|
|
BottomSheetSnapPoint.Partial,
|
|
)
|
|
|
|
const callQueuedCallbacks = React.useCallback(() => {
|
|
for (const cb of closeCallbacks.current) {
|
|
try {
|
|
cb()
|
|
} catch (e: any) {
|
|
logger.error(e || 'Error running close callback')
|
|
}
|
|
}
|
|
|
|
closeCallbacks.current = []
|
|
}, [])
|
|
|
|
const open = React.useCallback<DialogControlProps['open']>(() => {
|
|
// Run any leftover callbacks that might have been queued up before calling `.open()`
|
|
callQueuedCallbacks()
|
|
setDialogIsOpen(control.id, true)
|
|
ref.current?.present()
|
|
}, [setDialogIsOpen, control.id, callQueuedCallbacks])
|
|
|
|
// This is the function that we call when we want to dismiss the dialog.
|
|
const close = React.useCallback<DialogControlProps['close']>(cb => {
|
|
if (typeof cb === 'function') {
|
|
closeCallbacks.current.push(cb)
|
|
}
|
|
ref.current?.dismiss()
|
|
}, [])
|
|
|
|
// This is the actual thing we are doing once we "confirm" the dialog. We want the dialog's close animation to
|
|
// happen before we run this. It is passed to the `BottomSheet` component.
|
|
const onCloseAnimationComplete = React.useCallback(() => {
|
|
// This removes the dialog from our list of stored dialogs. Not super necessary on iOS, but on Android this
|
|
// tells us that we need to toggle the accessibility overlay setting
|
|
setDialogIsOpen(control.id, false)
|
|
callQueuedCallbacks()
|
|
onClose?.()
|
|
}, [callQueuedCallbacks, control.id, onClose, setDialogIsOpen])
|
|
|
|
const onSnapPointChange = (e: BottomSheetSnapPointChangeEvent) => {
|
|
const {snapPoint} = e.nativeEvent
|
|
setSnapPoint(snapPoint)
|
|
|
|
if (
|
|
snapPoint === BottomSheetSnapPoint.Full &&
|
|
prevSnapPoint.current !== BottomSheetSnapPoint.Full
|
|
) {
|
|
setFullyExpandedCount(c => c + 1)
|
|
} else if (
|
|
snapPoint !== BottomSheetSnapPoint.Full &&
|
|
prevSnapPoint.current === BottomSheetSnapPoint.Full
|
|
) {
|
|
setFullyExpandedCount(c => c - 1)
|
|
}
|
|
prevSnapPoint.current = snapPoint
|
|
}
|
|
|
|
const onStateChange = (e: BottomSheetStateChangeEvent) => {
|
|
if (e.nativeEvent.state === 'closed') {
|
|
onCloseAnimationComplete()
|
|
|
|
if (prevSnapPoint.current === BottomSheetSnapPoint.Full) {
|
|
setFullyExpandedCount(c => c - 1)
|
|
}
|
|
prevSnapPoint.current = BottomSheetSnapPoint.Hidden
|
|
}
|
|
}
|
|
|
|
useImperativeHandle(
|
|
control.ref,
|
|
() => ({
|
|
open,
|
|
close,
|
|
}),
|
|
[open, close],
|
|
)
|
|
|
|
const context = React.useMemo(
|
|
() => ({
|
|
close,
|
|
isNativeDialog: true,
|
|
nativeSnapPoint: snapPoint,
|
|
disableDrag,
|
|
setDisableDrag,
|
|
}),
|
|
[close, snapPoint, disableDrag, setDisableDrag],
|
|
)
|
|
|
|
return (
|
|
<BottomSheet
|
|
ref={ref}
|
|
cornerRadius={20}
|
|
backgroundColor={t.atoms.bg.backgroundColor}
|
|
{...nativeOptions}
|
|
onSnapPointChange={onSnapPointChange}
|
|
onStateChange={onStateChange}
|
|
disableDrag={disableDrag}>
|
|
<Context.Provider value={context}>
|
|
<View testID={testID}>{children}</View>
|
|
</Context.Provider>
|
|
</BottomSheet>
|
|
)
|
|
}
|
|
|
|
export function Inner({children, style, header}: DialogInnerProps) {
|
|
const insets = useSafeAreaInsets()
|
|
return (
|
|
<>
|
|
{header}
|
|
<View
|
|
style={[
|
|
a.pt_2xl,
|
|
a.px_xl,
|
|
{
|
|
paddingBottom: insets.bottom + insets.top,
|
|
},
|
|
style,
|
|
]}>
|
|
{children}
|
|
</View>
|
|
</>
|
|
)
|
|
}
|
|
|
|
export const ScrollableInner = React.forwardRef<ScrollView, DialogInnerProps>(
|
|
function ScrollableInner(
|
|
{children, style, contentContainerStyle, header, ...props},
|
|
ref,
|
|
) {
|
|
const {nativeSnapPoint, disableDrag, setDisableDrag} = useDialogContext()
|
|
const insets = useSafeAreaInsets()
|
|
|
|
useEnableKeyboardController(isIOS)
|
|
|
|
const [keyboardHeight, setKeyboardHeight] = React.useState(0)
|
|
|
|
useKeyboardHandler(
|
|
{
|
|
onEnd: e => {
|
|
'worklet'
|
|
runOnJS(setKeyboardHeight)(e.height)
|
|
},
|
|
},
|
|
[],
|
|
)
|
|
|
|
const basePading =
|
|
(isIOS ? 30 : 50) + (isIOS ? keyboardHeight / 4 : keyboardHeight)
|
|
const fullPaddingBase = insets.bottom + insets.top + basePading
|
|
const fullPadding = isIOS ? fullPaddingBase : fullPaddingBase + 50
|
|
|
|
const paddingBottom =
|
|
nativeSnapPoint === BottomSheetSnapPoint.Full ? fullPadding : basePading
|
|
|
|
const onScroll = (e: NativeSyntheticEvent<NativeScrollEvent>) => {
|
|
if (!isAndroid) {
|
|
return
|
|
}
|
|
const {contentOffset} = e.nativeEvent
|
|
if (contentOffset.y > 0 && !disableDrag) {
|
|
setDisableDrag(true)
|
|
} else if (contentOffset.y <= 1 && disableDrag) {
|
|
setDisableDrag(false)
|
|
}
|
|
}
|
|
|
|
return (
|
|
<KeyboardAwareScrollView
|
|
style={[style]}
|
|
contentContainerStyle={[
|
|
a.pt_2xl,
|
|
a.px_xl,
|
|
{paddingBottom},
|
|
contentContainerStyle,
|
|
]}
|
|
ref={ref}
|
|
{...props}
|
|
bounces={nativeSnapPoint === BottomSheetSnapPoint.Full}
|
|
bottomOffset={30}
|
|
scrollEventThrottle={50}
|
|
onScroll={isAndroid ? onScroll : undefined}
|
|
keyboardShouldPersistTaps="handled"
|
|
stickyHeaderIndices={header ? [0] : undefined}>
|
|
{header}
|
|
{children}
|
|
</KeyboardAwareScrollView>
|
|
)
|
|
},
|
|
)
|
|
|
|
export const InnerFlatList = React.forwardRef<
|
|
ListMethods,
|
|
ListProps<any> & {
|
|
webInnerStyle?: StyleProp<ViewStyle>
|
|
webInnerContentContainerStyle?: StyleProp<ViewStyle>
|
|
}
|
|
>(function InnerFlatList({style, ...props}, ref) {
|
|
const insets = useSafeAreaInsets()
|
|
const {nativeSnapPoint, disableDrag, setDisableDrag} = useDialogContext()
|
|
|
|
const onScroll = (e: ReanimatedScrollEvent) => {
|
|
'worklet'
|
|
if (!isAndroid) {
|
|
return
|
|
}
|
|
const {contentOffset} = e
|
|
if (contentOffset.y > 0 && !disableDrag) {
|
|
runOnJS(setDisableDrag)(true)
|
|
} else if (contentOffset.y <= 1 && disableDrag) {
|
|
runOnJS(setDisableDrag)(false)
|
|
}
|
|
}
|
|
|
|
return (
|
|
<ScrollProvider onScroll={onScroll}>
|
|
<List
|
|
keyboardShouldPersistTaps="handled"
|
|
bounces={nativeSnapPoint === BottomSheetSnapPoint.Full}
|
|
ListFooterComponent={
|
|
<View style={{height: insets.bottom + a.pt_5xl.paddingTop}} />
|
|
}
|
|
ref={ref}
|
|
{...props}
|
|
style={[style]}
|
|
/>
|
|
</ScrollProvider>
|
|
)
|
|
})
|
|
|
|
export function Handle() {
|
|
const t = useTheme()
|
|
const {_} = useLingui()
|
|
const {screenReaderEnabled} = useA11y()
|
|
const {close} = useDialogContext()
|
|
|
|
return (
|
|
<View style={[a.absolute, a.w_full, a.align_center, a.z_10, {height: 20}]}>
|
|
<Pressable
|
|
accessible={screenReaderEnabled}
|
|
onPress={() => close()}
|
|
accessibilityLabel={_(msg`Dismiss`)}
|
|
accessibilityHint={_(msg`Double tap to close the dialog`)}>
|
|
<View
|
|
style={[
|
|
a.rounded_sm,
|
|
{
|
|
top: 10,
|
|
width: 35,
|
|
height: 5,
|
|
alignSelf: 'center',
|
|
backgroundColor: t.palette.contrast_975,
|
|
opacity: 0.5,
|
|
},
|
|
]}
|
|
/>
|
|
</Pressable>
|
|
</View>
|
|
)
|
|
}
|
|
|
|
export function Close() {
|
|
return null
|
|
}
|