forked from Ivasoft/mattermost-mobile
[Gekidou MM-46229] Add hideAndLock, showAndUnlock callbacks for Search (#6677)
Co-authored-by: Elias Nahum <nahumhbl@gmail.com>
This commit is contained in:
@@ -4,9 +4,11 @@
|
||||
import React, {useMemo} from 'react';
|
||||
import {Platform, Text, View} from 'react-native';
|
||||
import Animated, {useAnimatedStyle, withTiming} from 'react-native-reanimated';
|
||||
import {useSafeAreaInsets} from 'react-native-safe-area-context';
|
||||
|
||||
import CompassIcon from '@components/compass_icon';
|
||||
import TouchableWithFeedback from '@components/touchable_with_feedback';
|
||||
import ViewConstants from '@constants/view';
|
||||
import {changeOpacity, makeStyleSheetFromTheme} from '@utils/theme';
|
||||
import {typography} from '@utils/typography';
|
||||
|
||||
@@ -24,18 +26,18 @@ type Props = {
|
||||
defaultHeight: number;
|
||||
hasSearch: boolean;
|
||||
isLargeTitle: boolean;
|
||||
largeHeight: number;
|
||||
heightOffset: number;
|
||||
leftComponent?: React.ReactElement;
|
||||
onBackPress?: () => void;
|
||||
onTitlePress?: () => void;
|
||||
rightButtons?: HeaderRightButton[];
|
||||
scrollValue?: Animated.SharedValue<number>;
|
||||
lockValue?: Animated.SharedValue<number | null>;
|
||||
showBackButton?: boolean;
|
||||
subtitle?: string;
|
||||
subtitleCompanion?: React.ReactElement;
|
||||
theme: Theme;
|
||||
title?: string;
|
||||
top: number;
|
||||
}
|
||||
|
||||
const hitSlop = {top: 20, bottom: 20, left: 20, right: 20};
|
||||
@@ -127,20 +129,21 @@ const Header = ({
|
||||
defaultHeight,
|
||||
hasSearch,
|
||||
isLargeTitle,
|
||||
largeHeight,
|
||||
heightOffset,
|
||||
leftComponent,
|
||||
onBackPress,
|
||||
onTitlePress,
|
||||
rightButtons,
|
||||
scrollValue,
|
||||
lockValue,
|
||||
showBackButton = true,
|
||||
subtitle,
|
||||
subtitleCompanion,
|
||||
theme,
|
||||
title,
|
||||
top,
|
||||
}: Props) => {
|
||||
const styles = getStyleSheet(theme);
|
||||
const insets = useSafeAreaInsets();
|
||||
|
||||
const opacity = useAnimatedStyle(() => {
|
||||
if (!isLargeTitle) {
|
||||
@@ -151,8 +154,7 @@ const Header = ({
|
||||
return {opacity: 0};
|
||||
}
|
||||
|
||||
const largeTitleLabelHeight = 60;
|
||||
const barHeight = (largeHeight - defaultHeight) - largeTitleLabelHeight;
|
||||
const barHeight = heightOffset - ViewConstants.LARGE_HEADER_TITLE_HEIGHT;
|
||||
const val = (scrollValue?.value ?? 0);
|
||||
const showDuration = 200;
|
||||
const hideDuration = 50;
|
||||
@@ -161,11 +163,15 @@ const Header = ({
|
||||
return {
|
||||
opacity: withTiming(opacityValue, {duration}),
|
||||
};
|
||||
}, [defaultHeight, largeHeight, isLargeTitle, hasSearch]);
|
||||
}, [heightOffset, isLargeTitle, hasSearch]);
|
||||
|
||||
const containerStyle = useMemo(() => {
|
||||
return [styles.container, {height: defaultHeight + top, paddingTop: top}];
|
||||
}, [defaultHeight, theme]);
|
||||
const containerAnimatedStyle = useAnimatedStyle(() => ({
|
||||
height: defaultHeight,
|
||||
paddingTop: insets.top,
|
||||
}), [defaultHeight, lockValue]);
|
||||
|
||||
const containerStyle = useMemo(() => (
|
||||
[styles.container, containerAnimatedStyle]), [styles, containerAnimatedStyle]);
|
||||
|
||||
const additionalTitleStyle = useMemo(() => ({
|
||||
marginLeft: Platform.select({android: showBackButton && !leftComponent ? 20 : 0}),
|
||||
|
||||
@@ -2,11 +2,12 @@
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import React from 'react';
|
||||
import Animated, {useAnimatedStyle} from 'react-native-reanimated';
|
||||
import {useSafeAreaInsets} from 'react-native-safe-area-context';
|
||||
import Animated, {useAnimatedStyle, useDerivedValue} from 'react-native-reanimated';
|
||||
|
||||
import {SEARCH_INPUT_HEIGHT, SEARCH_INPUT_MARGIN} from '@constants/view';
|
||||
import {useTheme} from '@context/theme';
|
||||
import useHeaderHeight, {MAX_OVERSCROLL} from '@hooks/header';
|
||||
import {clamp} from '@utils/gallery';
|
||||
import {makeStyleSheetFromTheme} from '@utils/theme';
|
||||
|
||||
import Header, {HeaderRightButton} from './header';
|
||||
@@ -23,6 +24,7 @@ type Props = SearchProps & {
|
||||
onTitlePress?: () => void;
|
||||
rightButtons?: HeaderRightButton[];
|
||||
scrollValue?: Animated.SharedValue<number>;
|
||||
lockValue?: Animated.SharedValue<number | null>;
|
||||
hideHeader?: () => void;
|
||||
showBackButton?: boolean;
|
||||
subtitle?: string;
|
||||
@@ -47,6 +49,7 @@ const NavigationHeader = ({
|
||||
onTitlePress,
|
||||
rightButtons,
|
||||
scrollValue,
|
||||
lockValue,
|
||||
showBackButton,
|
||||
subtitle,
|
||||
subtitleCompanion,
|
||||
@@ -55,21 +58,37 @@ const NavigationHeader = ({
|
||||
...searchProps
|
||||
}: Props) => {
|
||||
const theme = useTheme();
|
||||
const insets = useSafeAreaInsets();
|
||||
const styles = getStyleSheet(theme);
|
||||
|
||||
const {largeHeight, defaultHeight} = useHeaderHeight();
|
||||
const {largeHeight, defaultHeight, headerOffset} = useHeaderHeight();
|
||||
const containerHeight = useAnimatedStyle(() => {
|
||||
const minHeight = defaultHeight + insets.top;
|
||||
const minHeight = defaultHeight;
|
||||
const value = -(scrollValue?.value || 0);
|
||||
const height = ((isLargeTitle ? largeHeight : defaultHeight)) + value + insets.top;
|
||||
const calculatedHeight = (isLargeTitle ? largeHeight : defaultHeight) + value;
|
||||
const height = lockValue?.value ? lockValue.value : calculatedHeight;
|
||||
return {
|
||||
height: Math.max(height, minHeight),
|
||||
minHeight,
|
||||
maxHeight: largeHeight + insets.top + MAX_OVERSCROLL,
|
||||
maxHeight: largeHeight + MAX_OVERSCROLL,
|
||||
};
|
||||
});
|
||||
|
||||
const minScrollValue = useDerivedValue(() => scrollValue?.value || 0, [scrollValue]);
|
||||
|
||||
const translateY = useDerivedValue(() => (
|
||||
lockValue?.value ? -lockValue.value : Math.min(-minScrollValue.value, headerOffset)
|
||||
), [lockValue, minScrollValue, headerOffset]);
|
||||
|
||||
const searchTopStyle = useAnimatedStyle(() => {
|
||||
const margin = clamp(-minScrollValue.value, -headerOffset, headerOffset);
|
||||
const marginTop = (lockValue?.value ? -lockValue?.value : margin) - SEARCH_INPUT_HEIGHT - SEARCH_INPUT_MARGIN;
|
||||
return {marginTop};
|
||||
}, [lockValue, headerOffset, scrollValue]);
|
||||
|
||||
const heightOffset = useDerivedValue(() => (
|
||||
lockValue?.value ? lockValue.value : headerOffset
|
||||
), [lockValue, headerOffset]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Animated.View style={[styles.container, containerHeight]}>
|
||||
@@ -77,40 +96,36 @@ const NavigationHeader = ({
|
||||
defaultHeight={defaultHeight}
|
||||
hasSearch={hasSearch}
|
||||
isLargeTitle={isLargeTitle}
|
||||
largeHeight={largeHeight}
|
||||
heightOffset={heightOffset.value}
|
||||
leftComponent={leftComponent}
|
||||
onBackPress={onBackPress}
|
||||
onTitlePress={onTitlePress}
|
||||
rightButtons={rightButtons}
|
||||
lockValue={lockValue}
|
||||
scrollValue={scrollValue}
|
||||
showBackButton={showBackButton}
|
||||
subtitle={subtitle}
|
||||
subtitleCompanion={subtitleCompanion}
|
||||
theme={theme}
|
||||
title={title}
|
||||
top={insets.top}
|
||||
/>
|
||||
{isLargeTitle &&
|
||||
<NavigationHeaderLargeTitle
|
||||
defaultHeight={defaultHeight}
|
||||
heightOffset={heightOffset.value}
|
||||
hasSearch={hasSearch}
|
||||
largeHeight={largeHeight}
|
||||
scrollValue={scrollValue}
|
||||
subtitle={subtitle}
|
||||
theme={theme}
|
||||
title={title}
|
||||
translateY={translateY}
|
||||
/>
|
||||
}
|
||||
{hasSearch &&
|
||||
<NavigationSearch
|
||||
{...searchProps}
|
||||
defaultHeight={defaultHeight}
|
||||
largeHeight={largeHeight}
|
||||
scrollValue={scrollValue}
|
||||
hideHeader={hideHeader}
|
||||
theme={theme}
|
||||
top={0}
|
||||
/>
|
||||
<NavigationSearch
|
||||
{...searchProps}
|
||||
hideHeader={hideHeader}
|
||||
theme={theme}
|
||||
topStyle={searchTopStyle}
|
||||
/>
|
||||
}
|
||||
</Animated.View>
|
||||
</>
|
||||
|
||||
@@ -9,13 +9,12 @@ import {changeOpacity, makeStyleSheetFromTheme} from '@utils/theme';
|
||||
import {typography} from '@utils/typography';
|
||||
|
||||
type Props = {
|
||||
defaultHeight: number;
|
||||
heightOffset: number;
|
||||
hasSearch: boolean;
|
||||
largeHeight: number;
|
||||
scrollValue?: Animated.SharedValue<number>;
|
||||
subtitle?: string;
|
||||
theme: Theme;
|
||||
title: string;
|
||||
translateY: Animated.DerivedValue<number>;
|
||||
}
|
||||
|
||||
const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => ({
|
||||
@@ -34,33 +33,29 @@ const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => ({
|
||||
}));
|
||||
|
||||
const NavigationHeaderLargeTitle = ({
|
||||
defaultHeight,
|
||||
largeHeight,
|
||||
heightOffset,
|
||||
hasSearch,
|
||||
scrollValue,
|
||||
subtitle,
|
||||
theme,
|
||||
title,
|
||||
translateY,
|
||||
}: Props) => {
|
||||
const styles = getStyleSheet(theme);
|
||||
|
||||
const transform = useAnimatedStyle(() => {
|
||||
const value = scrollValue?.value || 0;
|
||||
return {
|
||||
transform: [{translateY: Math.min(-value, largeHeight - defaultHeight)}],
|
||||
};
|
||||
});
|
||||
const transform = useAnimatedStyle(() => (
|
||||
{transform: [{translateY: translateY.value}]}
|
||||
), [translateY?.value]);
|
||||
|
||||
const containerStyle = useMemo(() => {
|
||||
return [{height: largeHeight - defaultHeight}, styles.container];
|
||||
}, [defaultHeight, largeHeight, theme]);
|
||||
return [{height: heightOffset}, styles.container];
|
||||
}, [heightOffset, theme]);
|
||||
|
||||
return (
|
||||
<Animated.View style={[containerStyle, transform]}>
|
||||
<Text
|
||||
ellipsizeMode='tail'
|
||||
numberOfLines={1}
|
||||
style={styles.heading}
|
||||
style={[styles.heading]}
|
||||
testID='navigation.large_header.title'
|
||||
>
|
||||
{title}
|
||||
|
||||
@@ -2,35 +2,26 @@
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import React, {useCallback, useEffect, useMemo} from 'react';
|
||||
import {DeviceEventEmitter, Keyboard, NativeSyntheticEvent, Platform, TextInputFocusEventData} from 'react-native';
|
||||
import Animated, {useAnimatedStyle} from 'react-native-reanimated';
|
||||
import {DeviceEventEmitter, Keyboard, NativeSyntheticEvent, Platform, TextInputFocusEventData, ViewStyle} from 'react-native';
|
||||
import Animated, {AnimatedStyleProp} from 'react-native-reanimated';
|
||||
|
||||
import Search, {SearchProps} from '@components/search';
|
||||
import {Events} from '@constants';
|
||||
import {HEADER_SEARCH_HEIGHT} from '@constants/view';
|
||||
import {changeOpacity, makeStyleSheetFromTheme} from '@utils/theme';
|
||||
import {typography} from '@utils/typography';
|
||||
|
||||
type Props = SearchProps & {
|
||||
defaultHeight: number;
|
||||
largeHeight: number;
|
||||
scrollValue?: Animated.SharedValue<number>;
|
||||
topStyle: AnimatedStyleProp<ViewStyle>;
|
||||
hideHeader?: () => void;
|
||||
theme: Theme;
|
||||
top: number;
|
||||
}
|
||||
|
||||
const INITIAL_TOP = -45;
|
||||
|
||||
const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => ({
|
||||
container: {
|
||||
backgroundColor: theme.sidebarBg,
|
||||
height: HEADER_SEARCH_HEIGHT,
|
||||
justifyContent: 'center',
|
||||
paddingHorizontal: 20,
|
||||
width: '100%',
|
||||
zIndex: 10,
|
||||
top: INITIAL_TOP,
|
||||
},
|
||||
inputContainerStyle: {
|
||||
backgroundColor: changeOpacity(theme.sidebarText, 0.12),
|
||||
@@ -41,11 +32,9 @@ const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => ({
|
||||
}));
|
||||
|
||||
const NavigationSearch = ({
|
||||
defaultHeight,
|
||||
largeHeight,
|
||||
scrollValue,
|
||||
hideHeader,
|
||||
theme,
|
||||
topStyle,
|
||||
...searchProps
|
||||
}: Props) => {
|
||||
const styles = getStyleSheet(theme);
|
||||
@@ -58,12 +47,6 @@ const NavigationSearch = ({
|
||||
color: theme.sidebarText,
|
||||
}), [theme]);
|
||||
|
||||
const searchTop = useAnimatedStyle(() => {
|
||||
const value = scrollValue?.value || 0;
|
||||
const min = (largeHeight - defaultHeight);
|
||||
return {marginTop: Math.min(-Math.min((value), min), min)};
|
||||
}, [largeHeight, defaultHeight]);
|
||||
|
||||
const onFocus = useCallback((e: NativeSyntheticEvent<TextInputFocusEventData>) => {
|
||||
hideHeader?.();
|
||||
searchProps.onFocus?.(e);
|
||||
@@ -89,7 +72,7 @@ const NavigationSearch = ({
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<Animated.View style={[styles.container, searchTop]}>
|
||||
<Animated.View style={[styles.container, topStyle]}>
|
||||
<Search
|
||||
{...searchProps}
|
||||
cancelButtonProps={cancelButtonProps}
|
||||
|
||||
@@ -75,7 +75,7 @@ function PostDraft({
|
||||
ios: (keyboardHeight ? keyboardHeight - keyboardAdjustment : (postInputTop + insetsAdjustment)),
|
||||
default: postInputTop + insetsAdjustment,
|
||||
});
|
||||
const autocompleteAvailableSpace = containerHeight - autocompletePosition - (isChannelScreen ? headerHeight + insets.top : 0);
|
||||
const autocompleteAvailableSpace = containerHeight - autocompletePosition - (isChannelScreen ? headerHeight : 0);
|
||||
|
||||
const [animatedAutocompletePosition, animatedAutocompleteAvailableSpace] = useAutocompleteDefaultAnimatedValues(autocompletePosition, autocompleteAvailableSpace);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user