diff --git a/.changeset/cozy-melons-rescue.md b/.changeset/cozy-melons-rescue.md new file mode 100644 index 00000000..63e826a7 --- /dev/null +++ b/.changeset/cozy-melons-rescue.md @@ -0,0 +1,5 @@ +--- +'@theoplayer/react-native-ui': patch +--- + +Resolved an issue where button presses occasionally failed to register in fullscreen presentation mode on iOS and Android. diff --git a/src/ui/components/button/actionbutton/ActionButton.tsx b/src/ui/components/button/actionbutton/ActionButton.tsx index a5d15038..e1ed4efb 100644 --- a/src/ui/components/button/actionbutton/ActionButton.tsx +++ b/src/ui/components/button/actionbutton/ActionButton.tsx @@ -1,5 +1,5 @@ -import { Image, ImageSourcePropType, Platform, TouchableOpacity, View, ViewStyle } from 'react-native'; -import React, { ReactNode, useContext, useState } from 'react'; +import { Image, ImageSourcePropType, Platform, View, ViewStyle, PanResponder } from 'react-native'; +import React, { ReactNode, useContext, useState, useRef } from 'react'; import { SvgContext } from '../svg/SvgUtils'; import { PlayerContext } from '../../util/PlayerContext'; import type { ButtonBaseProps } from '../ButtonBaseProps'; @@ -46,26 +46,53 @@ export const DEFAULT_ACTION_BUTTON_STYLE: ViewStyle = { export const ActionButton = (props: React.PropsWithChildren) => { const { activeOpacity, children, icon, style, svg, onPress, highlighted, testID } = props; const [focused, setFocused] = useState(false); + const [pressed, setPressed] = useState(false); const context = useContext(PlayerContext); const shouldChangeTintColor = highlighted || (focused && Platform.isTV); const touchable = props.touchable != false; - if (!touchable) { - return {svg}; - } + const pressedRef = useRef(false); - const onTouch = () => { - if (context.ui.buttonsEnabled_) { - onPress?.(); - } + const handlePressIn = () => { + setPressed(true); + pressedRef.current = true; context.ui.onUserAction_(); }; + const handlePressOut = () => { + setPressed(false); + pressedRef.current = false; + }; + + /** + * Use a PanResponder instead of Touchable component to fix the issue of onPress events sometimes being filtered by + * React Native in fullscreen presentation mode on Android & iOS. + */ + const panResponder = useRef( + PanResponder.create({ + onStartShouldSetPanResponder: () => touchable, + onMoveShouldSetPanResponder: () => false, + onPanResponderGrant: () => handlePressIn(), + onPanResponderRelease: () => { + if (pressedRef.current) { + onPress?.(); + } + handlePressOut(); + }, + onPanResponderTerminate: () => handlePressOut(), + onPanResponderReject: () => handlePressOut(), + }), + ).current; + + if (!touchable) { + return {svg}; + } + return ( - { context.ui.onUserAction_(); setFocused(true); @@ -94,6 +121,6 @@ export const ActionButton = (props: React.PropsWithChildren) /> )} {children} - + ); };