Various fixes (#6268)
This commit is contained in:
parent
8ca26bde7d
commit
d8c8893c49
19 changed files with 139 additions and 30 deletions
|
|
@ -142,17 +142,17 @@ const ChannelListItem = ({
|
|||
styles.text,
|
||||
isBright && styles.highlight,
|
||||
isMuted && styles.muted,
|
||||
isActive && !isInfo ? styles.textActive : null,
|
||||
isActive && isTablet && !isInfo ? styles.textActive : null,
|
||||
isInfo ? styles.textInfo : null,
|
||||
], [isBright, styles, isMuted, isActive, isInfo]);
|
||||
], [isBright, styles, isMuted, isActive, isInfo, isTablet]);
|
||||
|
||||
const containerStyle = useMemo(() => [
|
||||
styles.container,
|
||||
isActive && !isInfo && styles.activeItem,
|
||||
isActive && isTablet && !isInfo && styles.activeItem,
|
||||
isInfo && styles.infoItem,
|
||||
{minHeight: height},
|
||||
],
|
||||
[height, isActive, isInfo, styles]);
|
||||
[height, isActive, isTablet, isInfo, styles]);
|
||||
|
||||
if (!hasMember) {
|
||||
return null;
|
||||
|
|
@ -176,7 +176,7 @@ const ChannelListItem = ({
|
|||
<View style={styles.wrapper}>
|
||||
<ChannelIcon
|
||||
hasDraft={hasDraft}
|
||||
isActive={isInfo ? false : isActive}
|
||||
isActive={isInfo ? false : isTablet && isActive}
|
||||
isInfo={isInfo}
|
||||
isUnread={isBright}
|
||||
isArchived={channel.deleteAt > 0}
|
||||
|
|
|
|||
|
|
@ -224,8 +224,8 @@ const FloatingTextInput = forwardRef<FloatingTextInputRef, FloatingTextInputProp
|
|||
}
|
||||
|
||||
return {
|
||||
top: withTiming(toValue, {duration: 250, easing: Easing.linear}),
|
||||
fontSize: withTiming(toSize, {duration: 250, easing: Easing.linear}),
|
||||
top: withTiming(toValue, {duration: 100, easing: Easing.linear}),
|
||||
fontSize: withTiming(toSize, {duration: 100, easing: Easing.linear}),
|
||||
backgroundColor: focusedLabel || inputText ? theme.centerChannelBg : 'transparent',
|
||||
paddingHorizontal: focusedLabel || inputText ? 4 : 0,
|
||||
color,
|
||||
|
|
|
|||
|
|
@ -8,7 +8,6 @@ import {StyleProp, Text, TextStyle} from 'react-native';
|
|||
import {joinChannel, switchToChannelById} from '@actions/remote/channel';
|
||||
import {useServerUrl} from '@context/server';
|
||||
import {t} from '@i18n';
|
||||
import {dismissAllModals, popToRoot} from '@screens/navigation';
|
||||
import {alertErrorWithFallback} from '@utils/draft';
|
||||
import {preventDoubleTap} from '@utils/tap';
|
||||
|
||||
|
|
@ -87,8 +86,6 @@ const ChannelMention = ({
|
|||
|
||||
if (c?.id) {
|
||||
switchToChannelById(serverUrl, c.id);
|
||||
await dismissAllModals();
|
||||
await popToRoot();
|
||||
}
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -300,6 +300,7 @@ const Post = ({
|
|||
testID={itemTestID}
|
||||
onPress={handlePress}
|
||||
onLongPress={showPostOptions}
|
||||
delayLongPress={200}
|
||||
underlayColor={changeOpacity(theme.centerChannelColor, 0.1)}
|
||||
style={styles.touchableContainer}
|
||||
>
|
||||
|
|
|
|||
|
|
@ -11,6 +11,14 @@ exports[`Server Icon Server Icon Component should match snapshot 1`] = `
|
|||
accessible={true}
|
||||
collapsable={false}
|
||||
focusable={false}
|
||||
hitSlop={
|
||||
Object {
|
||||
"bottom": 5,
|
||||
"left": 40,
|
||||
"right": 20,
|
||||
"top": 20,
|
||||
}
|
||||
}
|
||||
onClick={[Function]}
|
||||
onResponderGrant={[Function]}
|
||||
onResponderMove={[Function]}
|
||||
|
|
@ -44,6 +52,14 @@ exports[`Server Icon Server Icon Component should match snapshot with mentions 1
|
|||
accessible={true}
|
||||
collapsable={false}
|
||||
focusable={false}
|
||||
hitSlop={
|
||||
Object {
|
||||
"bottom": 5,
|
||||
"left": 40,
|
||||
"right": 20,
|
||||
"top": 20,
|
||||
}
|
||||
}
|
||||
onClick={[Function]}
|
||||
onResponderGrant={[Function]}
|
||||
onResponderMove={[Function]}
|
||||
|
|
@ -112,6 +128,14 @@ exports[`Server Icon Server Icon Component should match snapshot with unreads 1`
|
|||
accessible={true}
|
||||
collapsable={false}
|
||||
focusable={false}
|
||||
hitSlop={
|
||||
Object {
|
||||
"bottom": 5,
|
||||
"left": 40,
|
||||
"right": 20,
|
||||
"top": 20,
|
||||
}
|
||||
}
|
||||
onClick={[Function]}
|
||||
onResponderGrant={[Function]}
|
||||
onResponderMove={[Function]}
|
||||
|
|
|
|||
|
|
@ -25,6 +25,8 @@ type Props = {
|
|||
unreadStyle?: StyleProp<ViewStyle>;
|
||||
}
|
||||
|
||||
const hitSlop = {top: 20, bottom: 5, left: 40, right: 20};
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
badge: {
|
||||
left: 13,
|
||||
|
|
@ -64,6 +66,7 @@ export default function ServerIcon({
|
|||
onPress={onPress}
|
||||
type='opacity'
|
||||
testID={testID}
|
||||
hitSlop={hitSlop}
|
||||
>
|
||||
<CompassIcon
|
||||
size={size}
|
||||
|
|
|
|||
|
|
@ -44,15 +44,15 @@ const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => {
|
|||
},
|
||||
mentionsOneDigit: {
|
||||
top: 1,
|
||||
left: 28,
|
||||
left: 31,
|
||||
},
|
||||
mentionsTwoDigits: {
|
||||
top: 1,
|
||||
left: 26,
|
||||
left: 30,
|
||||
},
|
||||
mentionsThreeDigits: {
|
||||
top: 1,
|
||||
left: 23,
|
||||
left: 28,
|
||||
},
|
||||
};
|
||||
});
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ export default keyMirror({
|
|||
CHANNEL_DELETED: null,
|
||||
CLOSE_BOTTOM_SHEET: null,
|
||||
CONFIG_CHANGED: null,
|
||||
FETCHING_POSTS: null,
|
||||
FREEZE_SCREEN: null,
|
||||
GALLERY_ACTIONS: null,
|
||||
LEAVE_CHANNEL: null,
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
// See LICENSE.txt for license information.
|
||||
|
||||
import React, {useEffect, useRef, useState} from 'react';
|
||||
import {DeviceEventEmitter, StyleSheet, View} from 'react-native';
|
||||
import {BackHandler, DeviceEventEmitter, NativeEventSubscription, StyleSheet, View} from 'react-native';
|
||||
import {KeyboardTrackingViewRef} from 'react-native-keyboard-tracking-view';
|
||||
import {Edge, SafeAreaView, useSafeAreaInsets} from 'react-native-safe-area-context';
|
||||
|
||||
|
|
@ -13,6 +13,7 @@ import {ACCESSORIES_CONTAINER_NATIVE_ID} from '@constants/post_draft';
|
|||
import {useAppState, useIsTablet} from '@hooks/device';
|
||||
import {useDefaultHeaderHeight} from '@hooks/header';
|
||||
import {useTeamSwitch} from '@hooks/team_switch';
|
||||
import {popTopScreen} from '@screens/navigation';
|
||||
|
||||
import ChannelPostList from './channel_post_list';
|
||||
import ChannelHeader from './header';
|
||||
|
|
@ -52,6 +53,18 @@ const Channel = ({channelId, componentId}: ChannelProps) => {
|
|||
return () => listener.remove();
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
let back: NativeEventSubscription|undefined;
|
||||
if (!isTablet && componentId) {
|
||||
back = BackHandler.addEventListener('hardwareBackPress', () => {
|
||||
popTopScreen(componentId);
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
return () => back?.remove();
|
||||
}, [componentId, isTablet]);
|
||||
|
||||
const marginTop = defaultHeight + (isTablet ? insets.top : 0);
|
||||
useEffect(() => {
|
||||
// This is done so that the header renders
|
||||
|
|
|
|||
|
|
@ -54,7 +54,12 @@ const ChannelPostList = ({
|
|||
}
|
||||
}, 500), [channelId, posts]);
|
||||
|
||||
const intro = <Intro channelId={channelId}/>;
|
||||
const intro = (
|
||||
<Intro
|
||||
channelId={channelId}
|
||||
hasPosts={posts.length > 0}
|
||||
/>
|
||||
);
|
||||
|
||||
const postList = (
|
||||
<PostList
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ import type RoleModel from '@typings/database/models/servers/role';
|
|||
|
||||
type Props = {
|
||||
channel: ChannelModel;
|
||||
loading?: boolean;
|
||||
hasPosts: boolean;
|
||||
roles: RoleModel[];
|
||||
}
|
||||
|
||||
|
|
@ -35,8 +35,8 @@ const styles = StyleSheet.create({
|
|||
},
|
||||
});
|
||||
|
||||
const Intro = ({channel, loading = false, roles}: Props) => {
|
||||
const [fetching, setFetching] = useState(false);
|
||||
const Intro = ({channel, hasPosts, roles}: Props) => {
|
||||
const [fetching, setFetching] = useState(!hasPosts);
|
||||
const theme = useTheme();
|
||||
const element = useMemo(() => {
|
||||
if (channel.type === General.OPEN_CHANNEL && channel.name === General.DEFAULT_CHANNEL) {
|
||||
|
|
@ -78,7 +78,23 @@ const Intro = ({channel, loading = false, roles}: Props) => {
|
|||
return () => listener.remove();
|
||||
}, []);
|
||||
|
||||
if (loading || fetching) {
|
||||
// We add a timeout to remove the loading indicator
|
||||
// Even if the channel does not have any posts
|
||||
useEffect(() => {
|
||||
const time = setTimeout(() => {
|
||||
if (!hasPosts && fetching) {
|
||||
setFetching(false);
|
||||
}
|
||||
}, 1000);
|
||||
|
||||
return () => {
|
||||
if (time) {
|
||||
clearTimeout(time);
|
||||
}
|
||||
};
|
||||
}, [hasPosts, fetching]);
|
||||
|
||||
if (fetching) {
|
||||
return (
|
||||
<ActivityIndicator
|
||||
size='small'
|
||||
|
|
|
|||
|
|
@ -274,7 +274,7 @@ const FilteredList = ({
|
|||
});
|
||||
|
||||
useEffect(() => {
|
||||
bounce.current = debounce(search, 250);
|
||||
bounce.current = debounce(search, 500);
|
||||
bounce.current();
|
||||
return () => {
|
||||
if (bounce.current) {
|
||||
|
|
|
|||
|
|
@ -1,7 +1,8 @@
|
|||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import React, {useCallback, useEffect, useMemo, useState} from 'react';
|
||||
import {debounce, DebouncedFunc} from 'lodash';
|
||||
import React, {useCallback, useEffect, useMemo, useRef, useState} from 'react';
|
||||
import {DeviceEventEmitter, Keyboard, View} from 'react-native';
|
||||
import {Navigation} from 'react-native-navigation';
|
||||
|
||||
|
|
@ -44,6 +45,7 @@ const FindChannels = ({closeButtonId, componentId}: Props) => {
|
|||
const theme = useTheme();
|
||||
const [term, setTerm] = useState('');
|
||||
const [loading, setLoading] = useState(false);
|
||||
const bounce = useRef<DebouncedFunc<() => void>>();
|
||||
const styles = getStyleSheet(theme);
|
||||
const color = useMemo(() => changeOpacity(theme.centerChannelColor, 0.72), [theme]);
|
||||
const keyboardHeight = useKeyboardHeight();
|
||||
|
|
@ -66,10 +68,18 @@ const FindChannels = ({closeButtonId, componentId}: Props) => {
|
|||
}, []);
|
||||
|
||||
const onChangeText = useCallback((text) => {
|
||||
setTerm(text);
|
||||
if (!text) {
|
||||
if (text) {
|
||||
bounce.current?.cancel();
|
||||
bounce.current = debounce(() => {
|
||||
setTerm(text);
|
||||
}, 100);
|
||||
bounce.current();
|
||||
} else {
|
||||
setTerm(text);
|
||||
setLoading(false);
|
||||
}
|
||||
|
||||
return () => bounce.current?.cancel();
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
|
|
|
|||
|
|
@ -38,7 +38,7 @@ type Props = {
|
|||
}
|
||||
|
||||
const AnimatedSafeAreaView = Animated.createAnimatedComponent(SafeAreaView);
|
||||
const edges: Edge[] = ['left', 'right'];
|
||||
const edges: Edge[] = ['left', 'right', 'bottom'];
|
||||
const styles = StyleSheet.create({
|
||||
container: {
|
||||
alignItems: 'center',
|
||||
|
|
|
|||
|
|
@ -2,17 +2,20 @@
|
|||
// See LICENSE.txt for license information.
|
||||
|
||||
import {useManagedConfig} from '@mattermost/react-native-emm';
|
||||
import {useIsFocused, useRoute} from '@react-navigation/native';
|
||||
import React, {useEffect} from 'react';
|
||||
import {StyleSheet} from 'react-native';
|
||||
import {useIsFocused, useNavigation, useRoute} from '@react-navigation/native';
|
||||
import React, {useCallback, useEffect} from 'react';
|
||||
import {useIntl} from 'react-intl';
|
||||
import {BackHandler, StyleSheet, ToastAndroid} from 'react-native';
|
||||
import Animated, {useAnimatedStyle, withTiming} from 'react-native-reanimated';
|
||||
import {Edge, SafeAreaView, useSafeAreaInsets} from 'react-native-safe-area-context';
|
||||
|
||||
import FreezeScreen from '@components/freeze_screen';
|
||||
import TeamSidebar from '@components/team_sidebar';
|
||||
import {Screens} from '@constants';
|
||||
import {useTheme} from '@context/theme';
|
||||
import {useIsTablet} from '@hooks/device';
|
||||
import {resetToTeams} from '@screens/navigation';
|
||||
import EphemeralStore from '@store/ephemeral_store';
|
||||
|
||||
import AdditionalTabletView from './additional_tablet_view';
|
||||
import CategoriesList from './categories_list';
|
||||
|
|
@ -34,17 +37,43 @@ const styles = StyleSheet.create({
|
|||
},
|
||||
});
|
||||
|
||||
let backPressedCount = 0;
|
||||
let backPressTimeout: NodeJS.Timeout|undefined;
|
||||
|
||||
const ChannelListScreen = (props: ChannelProps) => {
|
||||
const theme = useTheme();
|
||||
const managedConfig = useManagedConfig<ManagedConfig>();
|
||||
const intl = useIntl();
|
||||
|
||||
const isTablet = useIsTablet();
|
||||
const route = useRoute();
|
||||
const isFocused = useIsFocused();
|
||||
const navigation = useNavigation();
|
||||
const insets = useSafeAreaInsets();
|
||||
const params = route.params as {direction: string};
|
||||
const canAddOtherServers = managedConfig?.allowOtherServers !== 'false';
|
||||
|
||||
const handleBackPress = useCallback(() => {
|
||||
const focused = navigation.isFocused() && EphemeralStore.getNavigationTopComponentId() === Screens.HOME;
|
||||
if (!backPressedCount && focused) {
|
||||
backPressedCount++;
|
||||
ToastAndroid.show(intl.formatMessage({
|
||||
id: 'mobile.android.back_handler_exit',
|
||||
defaultMessage: 'Press back again to exit',
|
||||
}), ToastAndroid.SHORT);
|
||||
|
||||
if (backPressTimeout) {
|
||||
clearTimeout(backPressTimeout);
|
||||
}
|
||||
backPressTimeout = setTimeout(() => {
|
||||
clearTimeout(backPressTimeout!);
|
||||
backPressedCount = 0;
|
||||
}, 2000);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}, [intl]);
|
||||
|
||||
const animated = useAnimatedStyle(() => {
|
||||
if (!isFocused) {
|
||||
let initial = 0;
|
||||
|
|
@ -72,6 +101,11 @@ const ChannelListScreen = (props: ChannelProps) => {
|
|||
}
|
||||
}, [Boolean(props.teamsCount)]);
|
||||
|
||||
useEffect(() => {
|
||||
const back = BackHandler.addEventListener('hardwareBackPress', handleBackPress);
|
||||
return () => back.remove();
|
||||
}, [handleBackPress]);
|
||||
|
||||
return (
|
||||
<FreezeScreen freeze={!isFocused}>
|
||||
{<Animated.View style={top}/>}
|
||||
|
|
|
|||
|
|
@ -197,9 +197,9 @@ const ServerItem = ({
|
|||
viewRef.current?.measureInWindow((x, y, w, h) => {
|
||||
const bounds: TutorialItemBounds = {
|
||||
startX: x - 20,
|
||||
startY: y - 5,
|
||||
startY: y,
|
||||
endX: x + w + 20,
|
||||
endY: y + h + 5,
|
||||
endY: y + h,
|
||||
};
|
||||
setShowTutorial(true);
|
||||
setItemBounds(bounds);
|
||||
|
|
@ -343,7 +343,7 @@ const ServerItem = ({
|
|||
useEffect(() => {
|
||||
let time: NodeJS.Timeout;
|
||||
if (highlight && !tutorialWatched) {
|
||||
time = setTimeout(startTutorial, 300);
|
||||
time = setTimeout(startTutorial, 650);
|
||||
}
|
||||
return () => clearTimeout(time);
|
||||
}, [highlight, tutorialWatched]);
|
||||
|
|
|
|||
|
|
@ -32,6 +32,7 @@ if (Platform.OS === 'ios') {
|
|||
enableFreeze(true);
|
||||
|
||||
type HomeProps = LaunchProps & {
|
||||
componentId: string;
|
||||
time?: number;
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ import {changeOpacity, makeStyleSheetFromTheme} from '@utils/theme';
|
|||
import {typography} from '@utils/typography';
|
||||
|
||||
type Props = {
|
||||
autoFocus?: boolean;
|
||||
buttonDisabled: boolean;
|
||||
connecting: boolean;
|
||||
displayName?: string;
|
||||
|
|
@ -71,6 +72,7 @@ const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => ({
|
|||
}));
|
||||
|
||||
const ServerForm = ({
|
||||
autoFocus = false,
|
||||
buttonDisabled,
|
||||
connecting,
|
||||
displayName = '',
|
||||
|
|
@ -163,6 +165,7 @@ const ServerForm = ({
|
|||
<FloatingTextInput
|
||||
autoCorrect={false}
|
||||
autoCapitalize={'none'}
|
||||
autoFocus={autoFocus}
|
||||
blurOnSubmit={false}
|
||||
containerStyle={styles.enterServer}
|
||||
enablesReturnKeyAutomatically={true}
|
||||
|
|
|
|||
|
|
@ -341,6 +341,7 @@ const Server = ({
|
|||
theme={theme}
|
||||
/>
|
||||
<ServerForm
|
||||
autoFocus={launchType === LaunchType.AddServer}
|
||||
buttonDisabled={buttonDisabled}
|
||||
connecting={connecting}
|
||||
displayName={displayName}
|
||||
|
|
|
|||
Loading…
Reference in a new issue