mattermost-mobile/app/components/connection_banner/connection_banner.tsx
Elias Nahum 2f3dfbbbfa
Update dependencies and upgrade to RN 0.76.5 (#8421)
* update dev deps

* partial update dependencies

* update watermelondb

* update rn to 0.76.5, expo to 52.0.18 and others

* upgrade android firebase

* upgrade detox deps

* fix package-lock.json

* update emm and paste-input

* update turbo-log

* update network library

* fix tests

* review feedback

* fix Keyboard blocking signIn button

* Fall back to iphone 14 iOS 17.2 simulator as app crashes on iOS 17.4

* changes in deleteCredentialsForServer is causing a crash

* withOptions x2 clearly is wrong

* re-add cli-platform-apple fix

* fix: RN 0.76.5 issue with bottom sheet disappearing (#8478)

* experiment, using view vs BottomSheetView

* revert previous & try disabling enableDynamicSizing

* revert an unintended removal

---------

Co-authored-by: yasserfaraazkhan <attitude3cena.yf@gmail.com>
Co-authored-by: Mattermost Build <build@mattermost.com>
Co-authored-by: Rahim Rahman <rahim.rahman@mattermost.com>
2025-01-16 07:11:32 -07:00

203 lines
6.3 KiB
TypeScript

// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import {useNetInfo} from '@react-native-community/netinfo';
import React, {useCallback, useEffect, useRef, useState} from 'react';
import {useIntl} from 'react-intl';
import {
Text,
View,
} from 'react-native';
import Animated, {useAnimatedStyle, useSharedValue, withTiming} from 'react-native-reanimated';
import CompassIcon from '@components/compass_icon';
import {ANNOUNCEMENT_BAR_HEIGHT} from '@constants/view';
import {useTheme} from '@context/theme';
import {useAppState} from '@hooks/device';
import useDidUpdate from '@hooks/did_update';
import {toMilliseconds} from '@utils/datetime';
import {makeStyleSheetFromTheme} from '@utils/theme';
import {typography} from '@utils/typography';
type Props = {
websocketState: WebsocketConnectedState;
}
const getStyle = makeStyleSheetFromTheme((theme: Theme) => {
const bannerContainer = {
flex: 1,
paddingHorizontal: 10,
overflow: 'hidden' as const,
flexDirection: 'row' as const,
alignItems: 'center' as const,
marginHorizontal: 8,
borderRadius: 7,
};
return {
background: {
backgroundColor: theme.sidebarBg,
zIndex: 1,
},
bannerContainerNotConnected: {
...bannerContainer,
backgroundColor: theme.centerChannelColor,
},
bannerContainerConnected: {
...bannerContainer,
backgroundColor: theme.onlineIndicator,
},
wrapper: {
flexDirection: 'row',
flex: 1,
overflow: 'hidden',
},
bannerTextContainer: {
flex: 1,
flexGrow: 1,
marginRight: 5,
textAlign: 'center',
color: theme.centerChannelBg,
},
bannerText: {
...typography('Body', 100, 'SemiBold'),
},
};
});
const clearTimeoutRef = (ref: React.MutableRefObject<NodeJS.Timeout | null | undefined>) => {
if (ref.current) {
clearTimeout(ref.current);
ref.current = null;
}
};
const TIME_TO_OPEN = toMilliseconds({seconds: 3});
const TIME_TO_CLOSE = toMilliseconds({seconds: 1});
const ConnectionBanner = ({
websocketState,
}: Props) => {
const intl = useIntl();
const closeTimeout = useRef<NodeJS.Timeout | null>();
const openTimeout = useRef<NodeJS.Timeout | null>();
const height = useSharedValue(0);
const theme = useTheme();
const [visible, setVisible] = useState(false);
const style = getStyle(theme);
const appState = useAppState();
const netInfo = useNetInfo();
const isConnected = websocketState === 'connected';
const openCallback = useCallback(() => {
setVisible(true);
clearTimeoutRef(openTimeout);
}, []);
const closeCallback = useCallback(() => {
setVisible(false);
clearTimeoutRef(closeTimeout);
}, []);
useEffect(() => {
if (websocketState === 'connecting') {
openCallback();
} else if (!isConnected) {
openTimeout.current = setTimeout(openCallback, TIME_TO_OPEN);
}
return () => {
clearTimeoutRef(openTimeout);
clearTimeoutRef(closeTimeout);
};
}, []);
useDidUpdate(() => {
if (isConnected) {
if (visible) {
if (!closeTimeout.current) {
closeTimeout.current = setTimeout(closeCallback, TIME_TO_CLOSE);
}
} else {
clearTimeoutRef(openTimeout);
}
} else if (visible) {
clearTimeoutRef(closeTimeout);
} else if (appState === 'active') {
setVisible(true);
}
}, [isConnected]);
useDidUpdate(() => {
if (appState === 'active') {
if (!isConnected && !visible) {
if (!openTimeout.current) {
openTimeout.current = setTimeout(openCallback, TIME_TO_OPEN);
}
}
if (isConnected && visible) {
if (!closeTimeout.current) {
closeTimeout.current = setTimeout(closeCallback, TIME_TO_CLOSE);
}
}
} else {
setVisible(false);
clearTimeoutRef(openTimeout);
clearTimeoutRef(closeTimeout);
}
}, [appState === 'active']);
useEffect(() => {
height.value = withTiming(visible ? ANNOUNCEMENT_BAR_HEIGHT : 0, {
duration: 200,
});
}, [height, visible]);
const bannerStyle = useAnimatedStyle(() => ({
height: height.value,
}));
let text;
if (isConnected) {
text = intl.formatMessage({id: 'connection_banner.connected', defaultMessage: 'Connection restored'});
} else if (websocketState === 'connecting') {
text = intl.formatMessage({id: 'connection_banner.connecting', defaultMessage: 'Connecting...'});
} else if (netInfo.isInternetReachable) {
text = intl.formatMessage({id: 'connection_banner.not_reachable', defaultMessage: 'The server is not reachable'});
} else {
text = intl.formatMessage({id: 'connection_banner.not_connected', defaultMessage: 'No internet connection'});
}
return (
<Animated.View
style={[style.background, bannerStyle]}
>
<View
style={isConnected ? style.bannerContainerConnected : style.bannerContainerNotConnected}
>
{visible &&
<View
style={style.wrapper}
>
<Text
style={style.bannerTextContainer}
ellipsizeMode='tail'
numberOfLines={1}
>
<CompassIcon
color={theme.centerChannelBg}
name={isConnected ? 'check' : 'information-outline'}
size={18}
/>
{' '}
<Text style={style.bannerText}>
{text}
</Text>
</Text>
</View>
}
</View>
</Animated.View>
);
};
export default ConnectionBanner;