From 1bfa8da3ee85bc6039f1488216430001f7d44dce Mon Sep 17 00:00:00 2001 From: Divyanshu patil Date: Thu, 1 Jan 2026 02:26:02 +0530 Subject: [PATCH 01/18] feat: global robust pressableOpacity component --- app/containers/PressableOpacity.tsx | 167 ++++++++++++++++++++++++++++ 1 file changed, 167 insertions(+) create mode 100644 app/containers/PressableOpacity.tsx diff --git a/app/containers/PressableOpacity.tsx b/app/containers/PressableOpacity.tsx new file mode 100644 index 0000000000..72a749492e --- /dev/null +++ b/app/containers/PressableOpacity.tsx @@ -0,0 +1,167 @@ +/* eslint-disable react-hooks/immutability */ +import React, { forwardRef } from 'react'; +import { + Pressable, + type PressableProps, + type GestureResponderEvent, + type PressableAndroidRippleConfig, + StyleSheet, + type ViewStyle +} from 'react-native'; +import Animated, { useAnimatedStyle, useSharedValue, withTiming } from 'react-native-reanimated'; +import { View } from 'react-native-animatable'; + +import { isAndroid } from '../lib/methods/helpers'; +import { useTheme } from '../theme'; + +export interface IPressableOpacityProps extends PressableProps { + opacityAnimationConfig?: { + fadeInDuration?: number; + fadeOutDuration?: number; + dimOpacity?: number; + }; + disableOpacityOnAndroid?: boolean; + disableAndroidRipple?: boolean; +} + +const AnimatedPressable = Animated.createAnimatedComponent(Pressable); + +const FADE_IN_DURATION = 50; +const FADE_OUT_DURATION = 100; +const DIM_OPACITY = 0.3; + +const PressableOpacity = forwardRef, IPressableOpacityProps>( + ( + { + children, + style, + onPressIn, + onPressOut, + android_ripple, + disableAndroidRipple, + disableOpacityOnAndroid, + opacityAnimationConfig, + ...restProps + }, + ref + ) => { + const { colors } = useTheme(); + const opacity = useSharedValue(1); + + const rStyle = useAnimatedStyle(() => ({ + opacity: opacity.value + })); + + const androidRippleResolvedColor = android_ripple?.color ?? colors.buttonBackgroundSecondaryPress; + + const androidRippleConfig: PressableAndroidRippleConfig = { + ...android_ripple, + color: androidRippleResolvedColor + }; + + // without this layout style mapping on container the ripple overflows + const { containerStyle } = getProcessedStyles(style); + const pressableStyles = getCleanedStyles({ containerStyle }); + + const { + fadeInDuration = FADE_IN_DURATION, + fadeOutDuration = FADE_OUT_DURATION, + dimOpacity = DIM_OPACITY + } = opacityAnimationConfig ?? {}; + + const handlePressIn = (e: GestureResponderEvent) => { + opacity.value = withTiming(dimOpacity, { duration: fadeInDuration }); + onPressIn?.(e); + }; + + const handlePressOut = (e: GestureResponderEvent) => { + opacity.value = withTiming(1, { duration: fadeOutDuration }); + onPressOut?.(e); + }; + const shouldBlockOpacityAnimationOnAndroid = isAndroid && disableOpacityOnAndroid; + + return ( + // required as android_ripple does not clips the ripple according to borderradius + + + {children} + + + ); + } +); + +PressableOpacity.displayName = 'PressableOpacity'; + +const extractStyles = (style: PressableProps['style']) => { + if (typeof style === 'function') { + return undefined; // cannot extract + } + const flat = StyleSheet.flatten(style); + return flat; +}; + +interface IGetProccessedStylesReturnType { + containerStyle: ViewStyle | undefined; + flattenedStyle: ViewStyle | undefined; +} + +const getProcessedStyles = (style: PressableProps['style']): IGetProccessedStylesReturnType => { + const flattenedStyle = extractStyles(style); + + if (!flattenedStyle) return { containerStyle: undefined, flattenedStyle: undefined }; + const containerStyle: ViewStyle = { + borderEndEndRadius: flattenedStyle.borderEndEndRadius, + borderTopEndRadius: flattenedStyle.borderTopEndRadius, + borderTopLeftRadius: flattenedStyle.borderTopLeftRadius, + borderEndStartRadius: flattenedStyle.borderEndStartRadius, + borderStartEndRadius: flattenedStyle.borderStartEndRadius, + borderTopRightRadius: flattenedStyle.borderTopRightRadius, + borderTopStartRadius: flattenedStyle.borderTopStartRadius, + borderBottomEndRadius: flattenedStyle.borderBottomEndRadius, + borderBottomLeftRadius: flattenedStyle.borderBottomLeftRadius, + borderStartStartRadius: flattenedStyle.borderStartStartRadius, + borderBottomRightRadius: flattenedStyle.borderBottomRightRadius, + borderBottomStartRadius: flattenedStyle.borderBottomStartRadius, + // width: flattenedStyle.width, + // height: flattenedStyle.height, + margin: flattenedStyle.margin, + marginLeft: flattenedStyle.marginLeft, + marginRight: flattenedStyle.marginRight, + marginBottom: flattenedStyle.marginBottom, + marginTop: flattenedStyle.marginTop, + borderRadius: flattenedStyle.borderRadius, + flex: flattenedStyle.flex, + overflow: flattenedStyle.overflow ?? 'hidden' + }; + + return { containerStyle, flattenedStyle }; +}; + +interface IRemoveStylesParams { + containerStyle: ViewStyle | undefined; +} + +// this removes margin and other layout styles from getting applied on the pressable +const getCleanedStyles = ({ containerStyle }: IRemoveStylesParams): ViewStyle | undefined => { + if (!containerStyle) return undefined; + + const cleanedStyles = {} as Partial; + for (const key of Object.keys(containerStyle)) { + const value = containerStyle[key as keyof ViewStyle]; + if (value !== undefined && value !== null) { + // overflow isnt numeric so we skip it + if (key === 'overflow' || key === 'flex') continue; + (cleanedStyles as any)[key] = 0; + } + } + return cleanedStyles as ViewStyle; +}; + +export default PressableOpacity; From eb1b783a1cd8e0809cff9791e94a9c5542c7ffef Mon Sep 17 00:00:00 2001 From: Divyanshu patil Date: Thu, 1 Jan 2026 02:26:52 +0530 Subject: [PATCH 02/18] chore: migrate TouchableOpacity to Pressable Phase 1 --- app/containers/AudioPlayer/PlayButton.tsx | 12 ++++-- app/containers/AudioPlayer/PlaybackSpeed.tsx | 21 +++++----- app/containers/Avatar/Avatar.tsx | 10 +++-- app/containers/Button/index.tsx | 18 ++++++--- app/containers/CallHeader.tsx | 40 ++++++++++++++----- .../IncomingCallNotification/index.tsx | 35 +++++++++++----- .../InAppNotification/NotifierComponent.tsx | 19 ++++++--- app/containers/NativeButton/index.tsx | 2 + .../ReactionsList/ReactionsList.test.tsx | 11 +++-- app/containers/RoomHeader/RoomHeader.tsx | 11 +++-- app/containers/TabView/index.tsx | 6 +-- app/containers/ThreadDetails.tsx | 9 +++-- app/containers/message/User.tsx | 11 +++-- app/views/RoomView/UploadProgress.tsx | 7 ++-- app/views/RoomsListView/components/Header.tsx | 6 +-- app/views/ShareView/Thumbs.tsx | 20 +++++++--- 16 files changed, 161 insertions(+), 77 deletions(-) diff --git a/app/containers/AudioPlayer/PlayButton.tsx b/app/containers/AudioPlayer/PlayButton.tsx index 34e77ced8f..0965287f1d 100644 --- a/app/containers/AudioPlayer/PlayButton.tsx +++ b/app/containers/AudioPlayer/PlayButton.tsx @@ -6,8 +6,8 @@ import styles from './styles'; import RCActivityIndicator from '../ActivityIndicator'; import { AUDIO_BUTTON_HIT_SLOP } from './constants'; import { type TAudioState } from './types'; -import NativeButton from '../NativeButton'; import getPlayButtonAccessibilityLabel from './getPlayButtonAccessibilityLabel'; +import PressableOpacity from '../PressableOpacity'; interface IButton { disabled?: boolean; @@ -45,15 +45,19 @@ const PlayButton = ({ onPress, disabled = false, audioState }: IButton): React.R const { colors } = useTheme(); return ( - + hitSlop={AUDIO_BUTTON_HIT_SLOP} + disableOpacityOnAndroid> - + ); }; diff --git a/app/containers/AudioPlayer/PlaybackSpeed.tsx b/app/containers/AudioPlayer/PlaybackSpeed.tsx index 68fcc886bc..a06dabdbfb 100644 --- a/app/containers/AudioPlayer/PlaybackSpeed.tsx +++ b/app/containers/AudioPlayer/PlaybackSpeed.tsx @@ -1,12 +1,12 @@ import React from 'react'; -import { Text } from 'react-native'; +import { Text, View } from 'react-native'; import i18n from '../../i18n'; import styles from './styles'; import { useTheme } from '../../theme'; import { AUDIO_PLAYBACK_SPEED, AVAILABLE_SPEEDS } from './constants'; import { useUserPreferences } from '../../lib/methods/userPreferences'; -import NativeButton from '../NativeButton'; +import PressableOpacity from '../PressableOpacity'; const PlaybackSpeed = () => { const [playbackSpeed, setPlaybackSpeed] = useUserPreferences(AUDIO_PLAYBACK_SPEED, AVAILABLE_SPEEDS[1]); @@ -19,13 +19,16 @@ const PlaybackSpeed = () => { }; return ( - - {playbackSpeed}x - + + + {playbackSpeed}x + + ); }; diff --git a/app/containers/Avatar/Avatar.tsx b/app/containers/Avatar/Avatar.tsx index b203f29041..8468ab23e0 100644 --- a/app/containers/Avatar/Avatar.tsx +++ b/app/containers/Avatar/Avatar.tsx @@ -1,7 +1,6 @@ import React from 'react'; import { View } from 'react-native'; import { Image } from 'expo-image'; -import Touchable from 'react-native-platform-touchable'; import { settings as RocketChatSettings } from '@rocket.chat/sdk'; import Emoji from '../markdown/components/emoji/Emoji'; @@ -10,6 +9,7 @@ import { SubscriptionType } from '../../definitions'; import { type IAvatar } from './interfaces'; import MarkdownContext from '../markdown/contexts/MarkdownContext'; import I18n from '../../i18n'; +import PressableOpacity from '../PressableOpacity'; const Avatar = React.memo( ({ @@ -97,9 +97,13 @@ const Avatar = React.memo( if (onPress) { image = ( - + {image} - + ); } diff --git a/app/containers/Button/index.tsx b/app/containers/Button/index.tsx index 1fe25724e8..9709e9f66a 100644 --- a/app/containers/Button/index.tsx +++ b/app/containers/Button/index.tsx @@ -1,13 +1,12 @@ import React from 'react'; import { type StyleProp, StyleSheet, Text, type TextStyle, type ViewStyle } from 'react-native'; -import Touchable, { type PlatformTouchableProps } from 'react-native-platform-touchable'; import { useTheme } from '../../theme'; import sharedStyles from '../../views/Styles'; import ActivityIndicator from '../ActivityIndicator'; +import PressableOpacity, { type IPressableOpacityProps } from '../PressableOpacity'; -// @ts-ignore -interface IButtonProps extends PlatformTouchableProps { +interface IButtonProps extends IPressableOpacityProps { title: string; onPress: () => void; type?: 'primary' | 'secondary'; @@ -61,6 +60,7 @@ const Button: React.FC = ({ style, styleText, small, + android_ripple, ...otherProps }) => { const { colors } = useTheme(); @@ -68,9 +68,15 @@ const Button: React.FC = ({ const isDisabled = disabled || loading; const defaultBackgroundColor = isPrimary ? colors.buttonBackgroundPrimaryDefault : colors.buttonBackgroundSecondaryDefault; + const defaultBackgroundRippleColor = isPrimary ? colors.buttonBackgroundPrimaryPress : colors.buttonBackgroundSecondaryPress; + const disabledBackgroundColor = isPrimary ? colors.buttonBackgroundPrimaryDisabled : colors.buttonBackgroundSecondaryDisabled; const resolvedBackgroundColor = backgroundColor || defaultBackgroundColor; + const resolvedBackgroundRippleColor = android_ripple?.color || defaultBackgroundRippleColor; + + const resolvedAndroidRipple = { ...android_ripple, color: resolvedBackgroundRippleColor }; + const resolvedTextColor = color || (isPrimary ? colors.fontWhite : colors.fontDefault); const containerStyle = [ @@ -88,16 +94,18 @@ const Button: React.FC = ({ ]; return ( - {loading ? : {title}} - + ); }; diff --git a/app/containers/CallHeader.tsx b/app/containers/CallHeader.tsx index 64f937fa00..03cf153df5 100644 --- a/app/containers/CallHeader.tsx +++ b/app/containers/CallHeader.tsx @@ -1,6 +1,5 @@ import React from 'react'; import { StyleSheet, Text, View } from 'react-native'; -import Touchable from 'react-native-platform-touchable'; import { A11y } from 'react-native-a11y-order'; import { useAppSelector } from '../lib/hooks/useAppSelector'; @@ -12,6 +11,7 @@ import AvatarContainer from './Avatar'; import StatusContainer from './Status'; import DotsLoader from './DotsLoader'; import I18n from '../i18n'; +import PressableOpacity from './PressableOpacity'; type TCallHeader = { mic: boolean; @@ -32,11 +32,21 @@ export const CallHeader = ({ mic, cam, setCam, setMic, title, avatar, uid, name, const handleColors = (enabled: boolean) => { if (calling) { - if (enabled) return { button: colors.buttonBackgroundSecondaryDisabled, icon: colors.strokeExtraDark }; - return { button: 'transparent', icon: colors.strokeLight }; + if (enabled) + return { + button: colors.buttonBackgroundSecondaryDisabled, + buttonRipple: 'transparent', + icon: colors.strokeExtraDark + }; + return { button: 'transparent', buttonRipple: 'transparent', icon: colors.strokeLight }; } - if (enabled) return { button: colors.buttonBackgroundPrimaryDefault, icon: colors.surfaceLight }; - return { button: 'transparent', icon: colors.strokeExtraDark }; + if (enabled) + return { + button: colors.buttonBackgroundPrimaryDefault, + buttonRipple: colors.buttonBackgroundPrimaryPress, + icon: colors.surfaceLight + }; + return { button: 'transparent', buttonRipple: 'transparent', icon: colors.strokeExtraDark }; }; return ( @@ -49,24 +59,32 @@ export const CallHeader = ({ mic, cam, setCam, setMic, title, avatar, uid, name, - setCam(!cam)} style={[style.iconCallContainerRight, { backgroundColor: handleColors(cam).button }]} hitSlop={BUTTON_HIT_SLOP} - disabled={calling}> + disabled={calling} + disableOpacityOnAndroid + android_ripple={{ + color: handleColors(cam).buttonRipple + }}> - + - setMic(!mic)} style={[style.iconCallContainer, { backgroundColor: handleColors(mic).button }]} hitSlop={BUTTON_HIT_SLOP} - disabled={calling}> + disabled={calling} + disableOpacityOnAndroid + android_ripple={{ + color: handleColors(cam).buttonRipple + }}> - + diff --git a/app/containers/InAppNotification/IncomingCallNotification/index.tsx b/app/containers/InAppNotification/IncomingCallNotification/index.tsx index 9e7070a822..1cdedcf47a 100644 --- a/app/containers/InAppNotification/IncomingCallNotification/index.tsx +++ b/app/containers/InAppNotification/IncomingCallNotification/index.tsx @@ -1,6 +1,5 @@ import React, { useEffect, useRef, useState } from 'react'; import { AccessibilityInfo, findNodeHandle, Text, View } from 'react-native'; -import Touchable from 'react-native-platform-touchable'; import { useSafeAreaInsets } from 'react-native-safe-area-context'; import { useDispatch } from 'react-redux'; import { A11y } from 'react-native-a11y-order'; @@ -16,6 +15,8 @@ import { CallHeader } from '../../CallHeader'; import { useStyle } from './style'; import useUserData from '../../../lib/hooks/useUserData'; import Ringer, { ERingerSounds } from '../../Ringer'; +import PressableOpacity from '../../PressableOpacity'; +import { useTheme } from '../../../theme'; export interface INotifierComponent { notification: { @@ -42,7 +43,7 @@ const IncomingCallHeader = React.memo( const isMasterDetail = useAppSelector(state => state.app.isMasterDetail); const styles = useStyle(); const insets = useSafeAreaInsets(); - + const { colors } = useTheme(); useEffect(() => { const focusOnIncomingCall = setTimeout(() => { const node = findNodeHandle(componentRef.current); @@ -84,40 +85,52 @@ const IncomingCallHeader = React.memo( - { setAudio(!audio); hideNotification(); }} accessibilityLabel={i18n.t('A11y_incoming_call_dismiss')} - style={styles.closeButton}> + style={styles.closeButton} + disableOpacityOnAndroid + android_ripple={{ + color: colors.buttonBackgroundSecondaryPress + }}> - + - { setAudio(!audio); hideNotification(); dispatch(cancelCall({ callId })); }} - style={styles.cancelButton}> + style={styles.cancelButton} + disableOpacityOnAndroid + android_ripple={{ + color: colors.buttonBackgroundDangerPress + }}> {i18n.t('decline')} - + - { setAudio(!audio); hideNotification(); dispatch(acceptCall({ callId })); }} - style={styles.acceptButton}> + style={styles.acceptButton} + disableOpacityOnAndroid + android_ripple={{ + color: colors.buttonBackgroundSuccessPress + }}> {i18n.t('accept')} - + {audio ? : null} diff --git a/app/containers/InAppNotification/NotifierComponent.tsx b/app/containers/InAppNotification/NotifierComponent.tsx index 0924d94856..2ddb368ae4 100644 --- a/app/containers/InAppNotification/NotifierComponent.tsx +++ b/app/containers/InAppNotification/NotifierComponent.tsx @@ -1,6 +1,5 @@ import React from 'react'; import { StyleSheet, Text, View } from 'react-native'; -import Touchable from 'react-native-platform-touchable'; import { connect } from 'react-redux'; import { useSafeAreaInsets } from 'react-native-safe-area-context'; @@ -13,6 +12,7 @@ import { goRoom } from '../../lib/methods/helpers/goRoom'; import { type IApplicationState, type ISubscription, type SubscriptionType } from '../../definitions'; import { hideNotification } from '../../lib/methods/helpers/notifications'; import { useResponsiveLayout } from '../../lib/hooks/useResponsiveLayout/useResponsiveLayout'; +import PressableOpacity from '../PressableOpacity'; export interface INotifierComponent { notification: { @@ -109,11 +109,13 @@ const NotifierComponent = React.memo(({ notification, isMasterDetail }: INotifie height: rowHeight } ]}> - <> @@ -126,10 +128,15 @@ const NotifierComponent = React.memo(({ notification, isMasterDetail }: INotifie - - + + - + ); }); diff --git a/app/containers/NativeButton/index.tsx b/app/containers/NativeButton/index.tsx index 0be9ae2771..bfb5c24045 100644 --- a/app/containers/NativeButton/index.tsx +++ b/app/containers/NativeButton/index.tsx @@ -1,3 +1,5 @@ +// since we are using PressableOpacity in Play / Pause Button this file is safe to delete + import React from 'react'; import { TouchableNativeFeedback, TouchableOpacity, type TouchableOpacityProps, View } from 'react-native'; diff --git a/app/containers/ReactionsList/ReactionsList.test.tsx b/app/containers/ReactionsList/ReactionsList.test.tsx index 3ffa28cb1e..c36ed3779e 100644 --- a/app/containers/ReactionsList/ReactionsList.test.tsx +++ b/app/containers/ReactionsList/ReactionsList.test.tsx @@ -61,7 +61,8 @@ interface ITabViewProps { // Mock TabView to better match the actual implementation jest.mock('../TabView', () => { const React = require('react'); - const { View, TouchableOpacity } = require('react-native'); + const { View } = require('react-native'); + const PressableOpacity = require('../PressableOpacity'); return { TabView: ({ routes, renderScene, renderTabItem }: ITabViewProps) => { @@ -76,9 +77,13 @@ jest.mock('../TabView', () => { {routes.map((route, idx) => ( - jumpTo(route.key)}> + jumpTo(route.key)} + disableAndroidRipple> {renderTabItem(route, idx === index ? '#FF0000' : '#666666')} - + ))} {renderScene({ route: routes[index] })} diff --git a/app/containers/RoomHeader/RoomHeader.tsx b/app/containers/RoomHeader/RoomHeader.tsx index bd2a95d89d..494b58fd50 100644 --- a/app/containers/RoomHeader/RoomHeader.tsx +++ b/app/containers/RoomHeader/RoomHeader.tsx @@ -1,6 +1,5 @@ import React from 'react'; import { StyleSheet, Text, useWindowDimensions, View } from 'react-native'; -import { TouchableOpacity } from 'react-native-gesture-handler'; import { useResponsiveLayout } from '../../lib/hooks/useResponsiveLayout/useResponsiveLayout'; import I18n from '../../i18n'; @@ -12,6 +11,7 @@ import { useTheme } from '../../theme'; import { useAppSelector } from '../../lib/hooks/useAppSelector'; import useStatusAccessibilityLabel from '../../lib/hooks/useStatusAccessibilityLabel'; import { type IUsersTyping } from '../../reducers/usersTyping'; +import PressableOpacity from '../PressableOpacity'; const HIT_SLOP = { top: 5, @@ -201,7 +201,12 @@ const Header = React.memo( accessible accessibilityLabel={accessibilityLabel} accessibilityRole='header'> - + {tmid ? null : ( - + ); } diff --git a/app/containers/TabView/index.tsx b/app/containers/TabView/index.tsx index 6c6679ce7b..f9f6c4911d 100644 --- a/app/containers/TabView/index.tsx +++ b/app/containers/TabView/index.tsx @@ -1,10 +1,10 @@ import React, { useCallback, useState } from 'react'; import { View } from 'react-native'; import { TabView as ReanimatedTabView, type Route, type NavigationState } from 'reanimated-tab-view'; -import { TouchableOpacity } from 'react-native-gesture-handler'; import styles from './styles'; import { useTheme } from '../../theme'; +import PressableOpacity from '../PressableOpacity'; interface TabViewProps { routes: Route[]; @@ -28,9 +28,9 @@ export const TabView = ({ routes, renderTabItem, renderScene }: TabViewProps) => {routes.map((tab: Route, index: number) => ( - jumpTo(tab.key)} hitSlop={10}> + jumpTo(tab.key)} hitSlop={10} disableAndroidRipple> {renderTabItem(tab, routeIndex === index ? colors.strokeHighlight : colors.fontSecondaryInfo)} - + {badgeColor ? : null} - toggleFollowThread?.(isFollowing, item.id)}> + onPress={() => toggleFollowThread?.(isFollowing, item.id)} + disableOpacityOnAndroid> - + ); diff --git a/app/containers/message/User.tsx b/app/containers/message/User.tsx index f24a65d849..178b95e417 100644 --- a/app/containers/message/User.tsx +++ b/app/containers/message/User.tsx @@ -1,6 +1,7 @@ import React, { useContext } from 'react'; -import { StyleSheet, Text, TouchableOpacity, View } from 'react-native'; +import { StyleSheet, Text, View } from 'react-native'; +import PressableOpacity from '../PressableOpacity'; import { type MessageType, type MessageTypesValues, SubscriptionType } from '../../definitions'; import { useTheme } from '../../theme'; import { type IRoomInfoParam } from '../../views/SearchMessagesView'; @@ -116,12 +117,16 @@ const User = React.memo( return ( - + {textContent} {isLargeFontScale ? null : } - + - this.tryAgain(item)}> + this.tryAgain(item)} disableAndroidRipple> {I18n.t('Try_again')} - + diff --git a/app/views/RoomsListView/components/Header.tsx b/app/views/RoomsListView/components/Header.tsx index 6d56a9582f..a9e79b56d7 100644 --- a/app/views/RoomsListView/components/Header.tsx +++ b/app/views/RoomsListView/components/Header.tsx @@ -1,6 +1,5 @@ import React, { memo } from 'react'; import { StyleSheet, Text, View, useWindowDimensions } from 'react-native'; -import { TouchableOpacity } from 'react-native-gesture-handler'; import { showActionSheetRef } from '../../../containers/ActionSheet'; import SearchHeader from '../../../containers/SearchHeader'; @@ -9,6 +8,7 @@ import { useAppSelector } from '../../../lib/hooks/useAppSelector'; import { useTheme } from '../../../theme'; import sharedStyles from '../../Styles'; import ServersList from './ServersList'; +import PressableOpacity from '../../../containers/PressableOpacity'; const styles = StyleSheet.create({ container: { @@ -66,7 +66,7 @@ const RoomsListHeaderView = ({ search, searchEnabled }: { search: (text: string) } return ( - + {serverName} @@ -80,7 +80,7 @@ const RoomsListHeaderView = ({ search, searchEnabled }: { search: (text: string) {subtitle} ) : null} - + ); }; diff --git a/app/views/ShareView/Thumbs.tsx b/app/views/ShareView/Thumbs.tsx index d3bc25fb99..df99b234b6 100644 --- a/app/views/ShareView/Thumbs.tsx +++ b/app/views/ShareView/Thumbs.tsx @@ -1,14 +1,14 @@ import React from 'react'; import { FlatList, Image, StyleSheet, View } from 'react-native'; -import { RectButton, TouchableNativeFeedback, TouchableOpacity } from 'react-native-gesture-handler'; +import { RectButton } from 'react-native-gesture-handler'; import { BUTTON_HIT_SLOP } from '../../containers/message/utils'; import { themes } from '../../lib/constants/colors'; import { CustomIcon } from '../../containers/CustomIcon'; -import { isIOS } from '../../lib/methods/helpers'; import { THUMBS_HEIGHT } from './constants'; import { type TSupportedThemes } from '../../theme'; import { type IShareAttachment } from '../../definitions'; +import PressableOpacity from '../../containers/PressableOpacity'; const THUMB_SIZE = 64; @@ -92,10 +92,18 @@ const ThumbContent = React.memo(({ item, theme }: IThumbContent) => { ); }); -const ThumbButton = isIOS ? TouchableOpacity : TouchableNativeFeedback; - const Thumb = ({ item, theme, isShareExtension, onPress, onRemove }: IThumb) => ( - onPress(item)} activeOpacity={0.7}> + onPress(item)} + opacityAnimationConfig={{ + dimOpacity: 0.7 + }} + android_ripple={{ + color: `${themes[theme].surfaceLight}33`, // to reduce opacity + foreground: true + }} + disableOpacityOnAndroid> <> ) : null} - + ); const Thumbs = ({ attachments, theme, isShareExtension, onPress, onRemove }: IThumbs) => { From 97f08140a43e63d4f914b365c55867ccb6ba7582 Mon Sep 17 00:00:00 2001 From: Divyanshu patil Date: Thu, 1 Jan 2026 21:11:56 +0530 Subject: [PATCH 03/18] fix: serverlist primary button explicitly being treated as secondary --- app/views/RoomsListView/components/ServersList.tsx | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/app/views/RoomsListView/components/ServersList.tsx b/app/views/RoomsListView/components/ServersList.tsx index 850eea2375..3cd1359a42 100644 --- a/app/views/RoomsListView/components/ServersList.tsx +++ b/app/views/RoomsListView/components/ServersList.tsx @@ -143,12 +143,10 @@ const ServersList = () => {