From 91daa59c3cdc13d35038ae40db35a013e12f9438 Mon Sep 17 00:00:00 2001 From: FitseTLT Date: Fri, 29 Mar 2024 22:50:15 +0300 Subject: [PATCH 01/24] implemented offline indicator for video --- src/components/VideoPlayer/BaseVideoPlayer.js | 20 ++++++++++++++++--- src/languages/en.ts | 1 + src/languages/es.ts | 1 + 3 files changed, 19 insertions(+), 3 deletions(-) diff --git a/src/components/VideoPlayer/BaseVideoPlayer.js b/src/components/VideoPlayer/BaseVideoPlayer.js index 91737ad3938a..9b247905db37 100644 --- a/src/components/VideoPlayer/BaseVideoPlayer.js +++ b/src/components/VideoPlayer/BaseVideoPlayer.js @@ -3,13 +3,17 @@ import {Video, VideoFullscreenUpdate} from 'expo-av'; import React, {useCallback, useEffect, useRef, useState} from 'react'; import {View} from 'react-native'; import _ from 'underscore'; +import BlockingView from '@components/BlockingViews/BlockingView'; import FullScreenLoadingIndicator from '@components/FullscreenLoadingIndicator'; import Hoverable from '@components/Hoverable'; +import * as Expensicons from '@components/Icon/Expensicons'; import PressableWithoutFeedback from '@components/Pressable/PressableWithoutFeedback'; import {useFullScreenContext} from '@components/VideoPlayerContexts/FullScreenContext'; import {usePlaybackContext} from '@components/VideoPlayerContexts/PlaybackContext'; import VideoPopoverMenu from '@components/VideoPopoverMenu'; +import useLocalize from '@hooks/useLocalize'; import useNetwork from '@hooks/useNetwork'; +import useTheme from '@hooks/useTheme'; import useThemeStyles from '@hooks/useThemeStyles'; import addEncryptedAuthTokenToURL from '@libs/addEncryptedAuthTokenToURL'; import * as DeviceCapabilities from '@libs/DeviceCapabilities'; @@ -73,6 +77,8 @@ function BaseVideoPlayer({ const isCurrentlyURLSet = currentlyPlayingURL === url; const isUploading = _.some(CONST.ATTACHMENT_LOCAL_URL_PREFIX, (prefix) => url.startsWith(prefix)); const videoStateRef = useRef(null); + const {translate} = useLocalize(); + const theme = useTheme(); const togglePlayCurrentVideo = useCallback(() => { videoResumeTryNumber.current = 0; @@ -275,9 +281,17 @@ function BaseVideoPlayer({ )} - - {(isLoading || isBuffering) && } - + {((isLoading && !isOffline) || isBuffering) && } + {isLoading && isOffline && ( + + + + )} {shouldShowVideoControls && !isLoading && (isPopoverVisible || isHovered || canUseTouchScreen) && ( `You've selected the maximum number (${count}) of participants.`, youAppearToBeOffline: 'You appear to be offline.', thisFeatureRequiresInternet: 'This feature requires an active internet connection to be used.', + attachementWillBeAvailableOnceBackOnline: 'Attachment will become available once back online.', areYouSure: 'Are you sure?', verify: 'Verify', yesContinue: 'Yes, continue', diff --git a/src/languages/es.ts b/src/languages/es.ts index da4a17e76fdc..fb417cf5364f 100644 --- a/src/languages/es.ts +++ b/src/languages/es.ts @@ -257,6 +257,7 @@ export default { maxParticipantsReached: ({count}: MaxParticipantsReachedParams) => `Has seleccionado el número máximo (${count}) de participantes.`, youAppearToBeOffline: 'Parece que estás desconectado.', thisFeatureRequiresInternet: 'Esta función requiere una conexión a Internet activa para ser utilizada.', + attachementWillBeAvailableOnceBackOnline: 'El archivo adjunto estará disponible cuando vuelvas a estar en línea.', areYouSure: '¿Estás seguro?', verify: 'Verifique', yesContinue: 'Sí, continuar', From 7b007e769a31f71f8c14638c822437e2d74d53a6 Mon Sep 17 00:00:00 2001 From: FitseTLT Date: Thu, 4 Apr 2024 23:39:14 +0300 Subject: [PATCH 02/24] Created attachment offline indicator component --- src/components/AttachmentOfflineIndicator.tsx | 59 +++++++++++++++++++ src/components/VideoPlayer/BaseVideoPlayer.js | 11 ++-- src/components/VideoPlayerPreview/index.tsx | 1 + 3 files changed, 64 insertions(+), 7 deletions(-) create mode 100644 src/components/AttachmentOfflineIndicator.tsx diff --git a/src/components/AttachmentOfflineIndicator.tsx b/src/components/AttachmentOfflineIndicator.tsx new file mode 100644 index 000000000000..476af1dcd31c --- /dev/null +++ b/src/components/AttachmentOfflineIndicator.tsx @@ -0,0 +1,59 @@ +import React, {useMemo} from 'react'; +import type {StyleProp, ViewStyle} from 'react-native'; +import {View} from 'react-native'; +import useNetwork from '@hooks/useNetwork'; +import useTheme from '@hooks/useTheme'; +import useThemeStyles from '@hooks/useThemeStyles'; +import useWindowDimensions from '@hooks/useWindowDimensions'; +import variables from '@styles/variables'; +import Icon from './Icon'; +import * as Expensicons from './Icon/Expensicons'; +import Text from './Text'; + +type AttachmentOfflineIndicatorProps = { + /** Optional styles for container element that will override the default styling for the offline indicator */ + containerStyles?: StyleProp; + + /** Optional styles for the container */ + style?: StyleProp; +}; + +function AttachmentOfflineIndicator({containerStyles, title, subtitle, isPreview = false}: AttachmentOfflineIndicatorProps) { + const theme = useTheme(); + const styles = useThemeStyles(); + const {isOffline} = useNetwork(); + const {isSmallScreenWidth} = useWindowDimensions(); + + const computedStyles = useMemo((): StyleProp => { + if (containerStyles) { + return containerStyles; + } + + return isSmallScreenWidth ? styles.offlineIndicatorMobile : styles.offlineIndicator; + }, [containerStyles, isSmallScreenWidth, styles.offlineIndicatorMobile, styles.offlineIndicator]); + + if (!isOffline) { + return null; + } + + return ( + + + {!isPreview && ( + + {title} + {subtitle} + + )} + + ); +} + +AttachmentOfflineIndicator.displayName = 'OfflineIndicator'; + +export default AttachmentOfflineIndicator; diff --git a/src/components/VideoPlayer/BaseVideoPlayer.js b/src/components/VideoPlayer/BaseVideoPlayer.js index d1533b2bf487..2cbd22e173d5 100644 --- a/src/components/VideoPlayer/BaseVideoPlayer.js +++ b/src/components/VideoPlayer/BaseVideoPlayer.js @@ -3,17 +3,15 @@ import {Video, VideoFullscreenUpdate} from 'expo-av'; import React, {useCallback, useEffect, useRef, useState} from 'react'; import {View} from 'react-native'; import _ from 'underscore'; -import BlockingView from '@components/BlockingViews/BlockingView'; +import AttachmentOfflineIndicator from '@components/AttachmentOfflineIndicator'; import FullScreenLoadingIndicator from '@components/FullscreenLoadingIndicator'; import Hoverable from '@components/Hoverable'; -import * as Expensicons from '@components/Icon/Expensicons'; import PressableWithoutFeedback from '@components/Pressable/PressableWithoutFeedback'; import {useFullScreenContext} from '@components/VideoPlayerContexts/FullScreenContext'; import {usePlaybackContext} from '@components/VideoPlayerContexts/PlaybackContext'; import VideoPopoverMenu from '@components/VideoPopoverMenu'; import useLocalize from '@hooks/useLocalize'; import useNetwork from '@hooks/useNetwork'; -import useTheme from '@hooks/useTheme'; import useThemeStyles from '@hooks/useThemeStyles'; import addEncryptedAuthTokenToURL from '@libs/addEncryptedAuthTokenToURL'; import * as DeviceCapabilities from '@libs/DeviceCapabilities'; @@ -44,6 +42,7 @@ function BaseVideoPlayer({ // user hovers the mouse over the carousel arrows, but this UI bug feels much less troublesome for now. // eslint-disable-next-line no-unused-vars isVideoHovered, + isPreview, }) { const styles = useThemeStyles(); const { @@ -78,7 +77,6 @@ function BaseVideoPlayer({ const isUploading = _.some(CONST.ATTACHMENT_LOCAL_URL_PREFIX, (prefix) => url.startsWith(prefix)); const videoStateRef = useRef(null); const {translate} = useLocalize(); - const theme = useTheme(); const togglePlayCurrentVideo = useCallback(() => { videoResumeTryNumber.current = 0; @@ -285,11 +283,10 @@ function BaseVideoPlayer({ {((isLoading && !isOffline) || isBuffering) && } {isLoading && isOffline && ( - )} diff --git a/src/components/VideoPlayerPreview/index.tsx b/src/components/VideoPlayerPreview/index.tsx index 37ddacb1f0db..dcbbbc900074 100644 --- a/src/components/VideoPlayerPreview/index.tsx +++ b/src/components/VideoPlayerPreview/index.tsx @@ -83,6 +83,7 @@ function VideoPlayerPreview({videoUrl, thumbnailUrl, fileName, videoDimensions, videoDuration={videoDuration} shouldUseSmallVideoControls style={[styles.w100, styles.h100]} + isPreview /> Date: Fri, 5 Apr 2024 20:51:27 +0300 Subject: [PATCH 03/24] did code cleanup --- src/components/AttachmentOfflineIndicator.tsx | 30 +++++++------------ src/components/VideoPlayer/types.ts | 1 + 2 files changed, 12 insertions(+), 19 deletions(-) diff --git a/src/components/AttachmentOfflineIndicator.tsx b/src/components/AttachmentOfflineIndicator.tsx index 476af1dcd31c..76830abb42e8 100644 --- a/src/components/AttachmentOfflineIndicator.tsx +++ b/src/components/AttachmentOfflineIndicator.tsx @@ -1,36 +1,28 @@ -import React, {useMemo} from 'react'; -import type {StyleProp, ViewStyle} from 'react-native'; +import React from 'react'; import {View} from 'react-native'; import useNetwork from '@hooks/useNetwork'; import useTheme from '@hooks/useTheme'; import useThemeStyles from '@hooks/useThemeStyles'; -import useWindowDimensions from '@hooks/useWindowDimensions'; import variables from '@styles/variables'; import Icon from './Icon'; import * as Expensicons from './Icon/Expensicons'; import Text from './Text'; type AttachmentOfflineIndicatorProps = { - /** Optional styles for container element that will override the default styling for the offline indicator */ - containerStyles?: StyleProp; + /** Whether the offline indicator is displayed for the attachment preview. */ + isPreview?: boolean; - /** Optional styles for the container */ - style?: StyleProp; + /** Title text to be displayed. */ + title: string; + + /** Subtitle text to be displayed. */ + subtitle: string; }; -function AttachmentOfflineIndicator({containerStyles, title, subtitle, isPreview = false}: AttachmentOfflineIndicatorProps) { +function AttachmentOfflineIndicator({title, subtitle, isPreview = false}: AttachmentOfflineIndicatorProps) { const theme = useTheme(); const styles = useThemeStyles(); const {isOffline} = useNetwork(); - const {isSmallScreenWidth} = useWindowDimensions(); - - const computedStyles = useMemo((): StyleProp => { - if (containerStyles) { - return containerStyles; - } - - return isSmallScreenWidth ? styles.offlineIndicatorMobile : styles.offlineIndicator; - }, [containerStyles, isSmallScreenWidth, styles.offlineIndicatorMobile, styles.offlineIndicator]); if (!isOffline) { return null; @@ -39,7 +31,7 @@ function AttachmentOfflineIndicator({containerStyles, title, subtitle, isPreview return ( ; shouldPlay?: boolean; + isPreview?: boolean; }; export type {VideoPlayerProps, VideoWithOnFullScreenUpdate}; From e6153f2fb460b2e7fc6843cea89e158d15c3c07c Mon Sep 17 00:00:00 2001 From: FitseTLT Date: Fri, 5 Apr 2024 20:59:44 +0300 Subject: [PATCH 04/24] fix lint and type --- src/components/VideoPlayer/BaseVideoPlayer.tsx | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/components/VideoPlayer/BaseVideoPlayer.tsx b/src/components/VideoPlayer/BaseVideoPlayer.tsx index 0d6635db1e77..e01f2c7a2c8b 100644 --- a/src/components/VideoPlayer/BaseVideoPlayer.tsx +++ b/src/components/VideoPlayer/BaseVideoPlayer.tsx @@ -19,7 +19,7 @@ import addEncryptedAuthTokenToURL from '@libs/addEncryptedAuthTokenToURL'; import * as DeviceCapabilities from '@libs/DeviceCapabilities'; import CONST from '@src/CONST'; import shouldReplayVideo from './shouldReplayVideo'; -import type {VideoWithOnFullScreenUpdate} from './types'; +import type {VideoPlayerProps, VideoWithOnFullScreenUpdate} from './types'; import * as VideoUtils from './utils'; import VideoPlayerControls from './VideoPlayerControls'; @@ -44,9 +44,8 @@ function BaseVideoPlayer({ // but current workaround is just not to use it here for now. This causes not displaying the video controls when // user hovers the mouse over the carousel arrows, but this UI bug feels much less troublesome for now. // eslint-disable-next-line no-unused-vars - isVideoHovered = false, isPreview, -}) { +}: VideoPlayerProps) { const styles = useThemeStyles(); const {pauseVideo, playVideo, currentlyPlayingURL, sharedElement, originalParent, shareVideoPlayerElements, currentVideoPlayerRef, updateCurrentlyPlayingURL, videoResumeTryNumber} = usePlaybackContext(); From cbf52fe51b31d4d29b88e21095e97d4df865a077 Mon Sep 17 00:00:00 2001 From: FitseTLT Date: Fri, 5 Apr 2024 21:01:30 +0300 Subject: [PATCH 05/24] revert unnecessary change --- src/components/VideoPlayer/BaseVideoPlayer.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/components/VideoPlayer/BaseVideoPlayer.tsx b/src/components/VideoPlayer/BaseVideoPlayer.tsx index e01f2c7a2c8b..b771e3fc61be 100644 --- a/src/components/VideoPlayer/BaseVideoPlayer.tsx +++ b/src/components/VideoPlayer/BaseVideoPlayer.tsx @@ -43,7 +43,8 @@ function BaseVideoPlayer({ // isVideoHovered caused a bug with unexpected video switching. We are investigating the root cause of the issue, // but current workaround is just not to use it here for now. This causes not displaying the video controls when // user hovers the mouse over the carousel arrows, but this UI bug feels much less troublesome for now. - // eslint-disable-next-line no-unused-vars + // eslint-disable-next-line @typescript-eslint/no-unused-vars + isVideoHovered = false, isPreview, }: VideoPlayerProps) { const styles = useThemeStyles(); From b3f14d666b65e008f0d207a9ab0f28d5b11d45dd Mon Sep 17 00:00:00 2001 From: FitseTLT Date: Fri, 5 Apr 2024 21:04:52 +0300 Subject: [PATCH 06/24] removed unnecessary view --- src/components/VideoPlayer/BaseVideoPlayer.tsx | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/src/components/VideoPlayer/BaseVideoPlayer.tsx b/src/components/VideoPlayer/BaseVideoPlayer.tsx index b771e3fc61be..e7eb78b12ae0 100644 --- a/src/components/VideoPlayer/BaseVideoPlayer.tsx +++ b/src/components/VideoPlayer/BaseVideoPlayer.tsx @@ -308,13 +308,11 @@ function BaseVideoPlayer({ {((isLoading && !isOffline) || isBuffering) && } {isLoading && isOffline && ( - - - + )} {controlsStatus !== CONST.VIDEO_PLAYER.CONTROLS_STATUS.HIDE && !isLoading && (isPopoverVisible || isHovered || canUseTouchScreen) && ( Date: Fri, 5 Apr 2024 22:06:54 +0300 Subject: [PATCH 07/24] implemented for image view --- src/components/AttachmentOfflineIndicator.tsx | 14 +++++--------- src/components/ImageView/index.tsx | 6 +++++- src/components/VideoPlayer/BaseVideoPlayer.tsx | 8 +------- 3 files changed, 11 insertions(+), 17 deletions(-) diff --git a/src/components/AttachmentOfflineIndicator.tsx b/src/components/AttachmentOfflineIndicator.tsx index 76830abb42e8..b22478330bff 100644 --- a/src/components/AttachmentOfflineIndicator.tsx +++ b/src/components/AttachmentOfflineIndicator.tsx @@ -1,5 +1,6 @@ import React from 'react'; import {View} from 'react-native'; +import useLocalize from '@hooks/useLocalize'; import useNetwork from '@hooks/useNetwork'; import useTheme from '@hooks/useTheme'; import useThemeStyles from '@hooks/useThemeStyles'; @@ -11,18 +12,13 @@ import Text from './Text'; type AttachmentOfflineIndicatorProps = { /** Whether the offline indicator is displayed for the attachment preview. */ isPreview?: boolean; - - /** Title text to be displayed. */ - title: string; - - /** Subtitle text to be displayed. */ - subtitle: string; }; -function AttachmentOfflineIndicator({title, subtitle, isPreview = false}: AttachmentOfflineIndicatorProps) { +function AttachmentOfflineIndicator({isPreview = false}: AttachmentOfflineIndicatorProps) { const theme = useTheme(); const styles = useThemeStyles(); const {isOffline} = useNetwork(); + const {translate} = useLocalize(); if (!isOffline) { return null; @@ -38,8 +34,8 @@ function AttachmentOfflineIndicator({title, subtitle, isPreview = false}: Attach /> {!isPreview && ( - {title} - {subtitle} + {translate('common.youAppearToBeOffline')} + {translate('common.attachementWillBeAvailableOnceBackOnline')} )} diff --git a/src/components/ImageView/index.tsx b/src/components/ImageView/index.tsx index 5d09e7abf41d..2316577158f8 100644 --- a/src/components/ImageView/index.tsx +++ b/src/components/ImageView/index.tsx @@ -2,11 +2,13 @@ import type {SyntheticEvent} from 'react'; import React, {useCallback, useEffect, useRef, useState} from 'react'; import type {GestureResponderEvent, LayoutChangeEvent} from 'react-native'; import {View} from 'react-native'; +import AttachmentOfflineIndicator from '@components/AttachmentOfflineIndicator'; import FullscreenLoadingIndicator from '@components/FullscreenLoadingIndicator'; import Image from '@components/Image'; import RESIZE_MODES from '@components/Image/resizeModes'; import type {ImageOnLoadEvent} from '@components/Image/types'; import PressableWithoutFeedback from '@components/Pressable/PressableWithoutFeedback'; +import useNetwork from '@hooks/useNetwork'; import useStyleUtils from '@hooks/useStyleUtils'; import useThemeStyles from '@hooks/useThemeStyles'; import * as DeviceCapabilities from '@libs/DeviceCapabilities'; @@ -33,6 +35,7 @@ function ImageView({isAuthTokenRequired = false, url, fileName, onError}: ImageV const [imgHeight, setImgHeight] = useState(0); const [zoomScale, setZoomScale] = useState(0); const [zoomDelta, setZoomDelta] = useState(); + const {isOffline} = useNetwork(); const scrollableRef = useRef(null); const canUseTouchScreen = DeviceCapabilities.canUseTouchScreen(); @@ -243,7 +246,8 @@ function ImageView({isAuthTokenRequired = false, url, fileName, onError}: ImageV /> - {isLoading && } + {isLoading && !isOffline && } + {isLoading && } ); } diff --git a/src/components/VideoPlayer/BaseVideoPlayer.tsx b/src/components/VideoPlayer/BaseVideoPlayer.tsx index e7eb78b12ae0..10d5051c5234 100644 --- a/src/components/VideoPlayer/BaseVideoPlayer.tsx +++ b/src/components/VideoPlayer/BaseVideoPlayer.tsx @@ -307,13 +307,7 @@ function BaseVideoPlayer({ )} {((isLoading && !isOffline) || isBuffering) && } - {isLoading && isOffline && ( - - )} + {isLoading && } {controlsStatus !== CONST.VIDEO_PLAYER.CONTROLS_STATUS.HIDE && !isLoading && (isPopoverVisible || isHovered || canUseTouchScreen) && ( Date: Fri, 5 Apr 2024 22:42:34 +0300 Subject: [PATCH 08/24] implemented for thumbnail preview --- src/components/AttachmentOfflineIndicator.tsx | 2 +- src/components/ImageWithSizeCalculation.tsx | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/components/AttachmentOfflineIndicator.tsx b/src/components/AttachmentOfflineIndicator.tsx index b22478330bff..8a831b02b407 100644 --- a/src/components/AttachmentOfflineIndicator.tsx +++ b/src/components/AttachmentOfflineIndicator.tsx @@ -25,7 +25,7 @@ function AttachmentOfflineIndicator({isPreview = false}: AttachmentOfflineIndica } return ( - + - {isLoading && !isImageCached && } + {isLoading && !isImageCached && !isOffline && } + {isLoading && !isImageCached && } ); } From da534453d52c1fb2993b2e31451503bfedd83bd3 Mon Sep 17 00:00:00 2001 From: FitseTLT Date: Fri, 5 Apr 2024 23:01:48 +0300 Subject: [PATCH 09/24] minor fix --- src/components/VideoPlayer/BaseVideoPlayer.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/src/components/VideoPlayer/BaseVideoPlayer.tsx b/src/components/VideoPlayer/BaseVideoPlayer.tsx index 10d5051c5234..08036890d35c 100644 --- a/src/components/VideoPlayer/BaseVideoPlayer.tsx +++ b/src/components/VideoPlayer/BaseVideoPlayer.tsx @@ -70,7 +70,6 @@ function BaseVideoPlayer({ const isCurrentlyURLSet = currentlyPlayingURL === url; const isUploading = CONST.ATTACHMENT_LOCAL_URL_PREFIX.some((prefix) => url.startsWith(prefix)); const videoStateRef = useRef(null); - const {translate} = useLocalize(); const togglePlayCurrentVideo = useCallback(() => { videoResumeTryNumber.current = 0; From 2a69717b6ce7afd5350777351018c84c588bbe64 Mon Sep 17 00:00:00 2001 From: FitseTLT Date: Mon, 8 Apr 2024 20:27:35 +0300 Subject: [PATCH 10/24] implemented offline indicator for native --- src/components/Lightbox/index.tsx | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/components/Lightbox/index.tsx b/src/components/Lightbox/index.tsx index 86a52c2baf6c..e5261771c5a4 100644 --- a/src/components/Lightbox/index.tsx +++ b/src/components/Lightbox/index.tsx @@ -2,12 +2,14 @@ import React, {useCallback, useContext, useEffect, useMemo, useState} from 'reac import type {LayoutChangeEvent, StyleProp, ViewStyle} from 'react-native'; import {ActivityIndicator, PixelRatio, StyleSheet, View} from 'react-native'; import {useSharedValue} from 'react-native-reanimated'; +import AttachmentOfflineIndicator from '@components/AttachmentOfflineIndicator'; import AttachmentCarouselPagerContext from '@components/Attachments/AttachmentCarousel/Pager/AttachmentCarouselPagerContext'; import Image from '@components/Image'; import type {ImageOnLoadEvent} from '@components/Image/types'; import MultiGestureCanvas, {DEFAULT_ZOOM_RANGE} from '@components/MultiGestureCanvas'; import type {CanvasSize, ContentSize, OnScaleChangedCallback, ZoomRange} from '@components/MultiGestureCanvas/types'; import {getCanvasFitScale} from '@components/MultiGestureCanvas/utils'; +import useNetwork from '@hooks/useNetwork'; import useStyleUtils from '@hooks/useStyleUtils'; import useThemeStyles from '@hooks/useThemeStyles'; import NUMBER_OF_CONCURRENT_LIGHTBOXES from './numberOfConcurrentLightboxes'; @@ -40,6 +42,7 @@ type LightboxProps = { function Lightbox({isAuthTokenRequired = false, uri, onScaleChanged: onScaleChangedProp, onError, style, zoomRange = DEFAULT_ZOOM_RANGE}: LightboxProps) { const StyleUtils = useStyleUtils(); const styles = useThemeStyles(); + const {isOffline} = useNetwork(); /** * React hooks must be used in the render function of the component at top-level and unconditionally. @@ -243,12 +246,13 @@ function Lightbox({isAuthTokenRequired = false, uri, onScaleChanged: onScaleChan )} {/* Show activity indicator while the lightbox is still loading the image. */} - {isLoading && ( + {isLoading && !isOffline && ( )} + {isLoading && } )} From bf1786e5a387a9663f3f11cfee6290bb7bc58ac6 Mon Sep 17 00:00:00 2001 From: FitseTLT Date: Mon, 8 Apr 2024 20:32:31 +0300 Subject: [PATCH 11/24] minor fix --- src/components/VideoPlayer/BaseVideoPlayer.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/src/components/VideoPlayer/BaseVideoPlayer.tsx b/src/components/VideoPlayer/BaseVideoPlayer.tsx index 08036890d35c..2ddb19716c8e 100644 --- a/src/components/VideoPlayer/BaseVideoPlayer.tsx +++ b/src/components/VideoPlayer/BaseVideoPlayer.tsx @@ -12,7 +12,6 @@ import PressableWithoutFeedback from '@components/Pressable/PressableWithoutFeed import {useFullScreenContext} from '@components/VideoPlayerContexts/FullScreenContext'; import {usePlaybackContext} from '@components/VideoPlayerContexts/PlaybackContext'; import VideoPopoverMenu from '@components/VideoPopoverMenu'; -import useLocalize from '@hooks/useLocalize'; import useNetwork from '@hooks/useNetwork'; import useThemeStyles from '@hooks/useThemeStyles'; import addEncryptedAuthTokenToURL from '@libs/addEncryptedAuthTokenToURL'; From 2a7fac59e5fde23d38751b0732de483d13c4da43 Mon Sep 17 00:00:00 2001 From: FitseTLT Date: Wed, 10 Apr 2024 14:32:00 +0300 Subject: [PATCH 12/24] revert light box change --- src/components/Lightbox/index.tsx | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/components/Lightbox/index.tsx b/src/components/Lightbox/index.tsx index e5261771c5a4..86a52c2baf6c 100644 --- a/src/components/Lightbox/index.tsx +++ b/src/components/Lightbox/index.tsx @@ -2,14 +2,12 @@ import React, {useCallback, useContext, useEffect, useMemo, useState} from 'reac import type {LayoutChangeEvent, StyleProp, ViewStyle} from 'react-native'; import {ActivityIndicator, PixelRatio, StyleSheet, View} from 'react-native'; import {useSharedValue} from 'react-native-reanimated'; -import AttachmentOfflineIndicator from '@components/AttachmentOfflineIndicator'; import AttachmentCarouselPagerContext from '@components/Attachments/AttachmentCarousel/Pager/AttachmentCarouselPagerContext'; import Image from '@components/Image'; import type {ImageOnLoadEvent} from '@components/Image/types'; import MultiGestureCanvas, {DEFAULT_ZOOM_RANGE} from '@components/MultiGestureCanvas'; import type {CanvasSize, ContentSize, OnScaleChangedCallback, ZoomRange} from '@components/MultiGestureCanvas/types'; import {getCanvasFitScale} from '@components/MultiGestureCanvas/utils'; -import useNetwork from '@hooks/useNetwork'; import useStyleUtils from '@hooks/useStyleUtils'; import useThemeStyles from '@hooks/useThemeStyles'; import NUMBER_OF_CONCURRENT_LIGHTBOXES from './numberOfConcurrentLightboxes'; @@ -42,7 +40,6 @@ type LightboxProps = { function Lightbox({isAuthTokenRequired = false, uri, onScaleChanged: onScaleChangedProp, onError, style, zoomRange = DEFAULT_ZOOM_RANGE}: LightboxProps) { const StyleUtils = useStyleUtils(); const styles = useThemeStyles(); - const {isOffline} = useNetwork(); /** * React hooks must be used in the render function of the component at top-level and unconditionally. @@ -246,13 +243,12 @@ function Lightbox({isAuthTokenRequired = false, uri, onScaleChanged: onScaleChan )} {/* Show activity indicator while the lightbox is still loading the image. */} - {isLoading && !isOffline && ( + {isLoading && ( )} - {isLoading && } )} From bbfda4d6a9292afc983458922a22a40a543fbd70 Mon Sep 17 00:00:00 2001 From: FitseTLT Date: Wed, 10 Apr 2024 14:53:25 +0300 Subject: [PATCH 13/24] fix buffering case --- src/components/VideoPlayer/BaseVideoPlayer.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/VideoPlayer/BaseVideoPlayer.tsx b/src/components/VideoPlayer/BaseVideoPlayer.tsx index 2ddb19716c8e..26a44364f807 100644 --- a/src/components/VideoPlayer/BaseVideoPlayer.tsx +++ b/src/components/VideoPlayer/BaseVideoPlayer.tsx @@ -305,7 +305,7 @@ function BaseVideoPlayer({ )} {((isLoading && !isOffline) || isBuffering) && } - {isLoading && } + {isLoading && !isBuffering && } {controlsStatus !== CONST.VIDEO_PLAYER.CONTROLS_STATUS.HIDE && !isLoading && (isPopoverVisible || isHovered || canUseTouchScreen) && ( Date: Thu, 11 Apr 2024 14:05:43 +0300 Subject: [PATCH 14/24] fix touch screen case for image view --- src/components/ImageView/index.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/components/ImageView/index.tsx b/src/components/ImageView/index.tsx index 2316577158f8..9865adb04d3d 100644 --- a/src/components/ImageView/index.tsx +++ b/src/components/ImageView/index.tsx @@ -213,7 +213,8 @@ function ImageView({isAuthTokenRequired = false, url, fileName, onError}: ImageV onLoad={imageLoad} onError={onError} /> - {(isLoading || zoomScale === 0) && } + {((isLoading && !isOffline) || zoomScale === 0) && } + {isLoading && } ); } From cbea4acce2f432b71940929f52b6d99c2daa1b22 Mon Sep 17 00:00:00 2001 From: FitseTLT Date: Thu, 11 Apr 2024 14:17:38 +0300 Subject: [PATCH 15/24] fix logic for zoom scale --- src/components/ImageView/index.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/ImageView/index.tsx b/src/components/ImageView/index.tsx index 9865adb04d3d..f08941ef7d77 100644 --- a/src/components/ImageView/index.tsx +++ b/src/components/ImageView/index.tsx @@ -213,7 +213,7 @@ function ImageView({isAuthTokenRequired = false, url, fileName, onError}: ImageV onLoad={imageLoad} onError={onError} /> - {((isLoading && !isOffline) || zoomScale === 0) && } + {((isLoading && !isOffline) || (!isLoading && zoomScale === 0)) && } {isLoading && } ); From 1142478d389bcafdbb5868e826c2c47110c4abd4 Mon Sep 17 00:00:00 2001 From: FitseTLT Date: Fri, 12 Apr 2024 17:08:49 +0300 Subject: [PATCH 16/24] fix light box offline indicator --- src/components/Lightbox/index.tsx | 29 +++++++++++++++++------------ 1 file changed, 17 insertions(+), 12 deletions(-) diff --git a/src/components/Lightbox/index.tsx b/src/components/Lightbox/index.tsx index 86a52c2baf6c..909c4d942e28 100644 --- a/src/components/Lightbox/index.tsx +++ b/src/components/Lightbox/index.tsx @@ -2,12 +2,14 @@ import React, {useCallback, useContext, useEffect, useMemo, useState} from 'reac import type {LayoutChangeEvent, StyleProp, ViewStyle} from 'react-native'; import {ActivityIndicator, PixelRatio, StyleSheet, View} from 'react-native'; import {useSharedValue} from 'react-native-reanimated'; +import AttachmentOfflineIndicator from '@components/AttachmentOfflineIndicator'; import AttachmentCarouselPagerContext from '@components/Attachments/AttachmentCarousel/Pager/AttachmentCarouselPagerContext'; import Image from '@components/Image'; import type {ImageOnLoadEvent} from '@components/Image/types'; import MultiGestureCanvas, {DEFAULT_ZOOM_RANGE} from '@components/MultiGestureCanvas'; import type {CanvasSize, ContentSize, OnScaleChangedCallback, ZoomRange} from '@components/MultiGestureCanvas/types'; import {getCanvasFitScale} from '@components/MultiGestureCanvas/utils'; +import useNetwork from '@hooks/useNetwork'; import useStyleUtils from '@hooks/useStyleUtils'; import useThemeStyles from '@hooks/useThemeStyles'; import NUMBER_OF_CONCURRENT_LIGHTBOXES from './numberOfConcurrentLightboxes'; @@ -47,6 +49,7 @@ function Lightbox({isAuthTokenRequired = false, uri, onScaleChanged: onScaleChan * we need to create a shared value that can be used in the render function. */ const isPagerScrollingFallback = useSharedValue(false); + const {isOffline} = useNetwork(); const attachmentCarouselPagerContext = useContext(AttachmentCarouselPagerContext); const { @@ -219,9 +222,9 @@ function Lightbox({isAuthTokenRequired = false, uri, onScaleChanged: onScaleChan style={[contentSize ?? styles.invisibleImage]} isAuthTokenRequired={isAuthTokenRequired} onError={onError} - onLoad={updateContentSize} - onLoadEnd={() => { + onLoad={(e) => { setLightboxImageLoaded(true); + updateContentSize(e); }} /> @@ -236,21 +239,23 @@ function Lightbox({isAuthTokenRequired = false, uri, onScaleChanged: onScaleChan resizeMode="contain" style={[fallbackSize ?? styles.invisibleImage]} isAuthTokenRequired={isAuthTokenRequired} - onLoad={updateContentSize} - onLoadEnd={() => setFallbackImageLoaded(true)} + onLoad={(e) => { + setFallbackImageLoaded(true); + updateContentSize(e); + }} /> )} - - {/* Show activity indicator while the lightbox is still loading the image. */} - {isLoading && ( - - )} )} + {/* Show activity or offline indicator (based on the connection status) while the lightbox is still loading the image. */} + {isLoading && !isOffline && ( + + )} + {isLoading && } ); } From df8edb102940b5e5c1d51437fd0c49b5cfff5c3f Mon Sep 17 00:00:00 2001 From: FitseTLT Date: Fri, 12 Apr 2024 22:39:25 +0300 Subject: [PATCH 17/24] fix cached image case offline indicator --- src/components/Lightbox/index.tsx | 64 +++++++++++++++++++++---------- 1 file changed, 43 insertions(+), 21 deletions(-) diff --git a/src/components/Lightbox/index.tsx b/src/components/Lightbox/index.tsx index 909c4d942e28..a6b30f1e6815 100644 --- a/src/components/Lightbox/index.tsx +++ b/src/components/Lightbox/index.tsx @@ -1,4 +1,4 @@ -import React, {useCallback, useContext, useEffect, useMemo, useState} from 'react'; +import React, {useCallback, useContext, useEffect, useMemo, useRef, useState} from 'react'; import type {LayoutChangeEvent, StyleProp, ViewStyle} from 'react-native'; import {ActivityIndicator, PixelRatio, StyleSheet, View} from 'react-native'; import {useSharedValue} from 'react-native-reanimated'; @@ -133,10 +133,12 @@ function Lightbox({isAuthTokenRequired = false, uri, onScaleChanged: onScaleChan const indexOutOfRange = page > activePage + indexCanvasOffset || page < activePage - indexCanvasOffset; return !indexOutOfRange; }, [activePage, hasSiblingCarouselItems, page]); - const [isLightboxImageLoaded, setLightboxImageLoaded] = useState(false); + const [isLightboxImageLoading, setLightboxImageLoading] = useState(false); + const isLightboxImageLoaded = useRef(false); const [isFallbackVisible, setFallbackVisible] = useState(!isLightboxVisible); - const [isFallbackImageLoaded, setFallbackImageLoaded] = useState(false); + const [isFallbackImageLoading, setFallbackImageLoading] = useState(false); + const isFallbackImageLoaded = useRef(false); const fallbackSize = useMemo(() => { if (!hasSiblingCarouselItems || !contentSize || isCanvasLoading) { return undefined; @@ -154,18 +156,19 @@ function Lightbox({isAuthTokenRequired = false, uri, onScaleChanged: onScaleChan // until the fallback gets hidden so that we don't see two overlapping images at the same time. // If there the Lightbox is not used within a carousel, we don't need to hide the Lightbox, // because it's only going to be rendered after the fallback image is hidden. - const shouldShowLightbox = isLightboxImageLoaded && !isFallbackVisible; + const shouldShowLightbox = isLightboxImageLoaded.current && !isFallbackVisible; - const isFallbackStillLoading = isFallbackVisible && !isFallbackImageLoaded; - const isLightboxStillLoading = isLightboxVisible && !isLightboxImageLoaded; - const isLoading = isActive && (isCanvasLoading || isFallbackStillLoading || isLightboxStillLoading); + const isFallbackStillLoading = isFallbackVisible && isFallbackImageLoading; + const isLightboxStillLoading = isLightboxVisible && isLightboxImageLoading; + const isLoading = isActive && (isFallbackStillLoading || isLightboxStillLoading); // Resets the lightbox when it becomes inactive useEffect(() => { if (isLightboxVisible) { return; } - setLightboxImageLoaded(false); + isLightboxImageLoaded.current = false; + setLightboxImageLoading(false); setContentSize(undefined); }, [isLightboxVisible, setContentSize]); @@ -177,9 +180,10 @@ function Lightbox({isAuthTokenRequired = false, uri, onScaleChanged: onScaleChan } // When the carousel item is active and the lightbox has finished loading, we want to hide the fallback image - if (isActive && isFallbackVisible && isLightboxVisible && isLightboxImageLoaded) { + if (isActive && isFallbackVisible && isLightboxVisible && isLightboxImageLoaded.current) { setFallbackVisible(false); - setFallbackImageLoaded(false); + setFallbackImageLoading(false); + isFallbackImageLoaded.current = false; return; } @@ -187,7 +191,7 @@ function Lightbox({isAuthTokenRequired = false, uri, onScaleChanged: onScaleChan if (!isActive && !isLightboxVisible) { setFallbackVisible(true); } - }, [hasSiblingCarouselItems, isActive, isFallbackVisible, isLightboxImageLoaded, isLightboxVisible]); + }, [hasSiblingCarouselItems, isActive, isFallbackVisible, isLightboxImageLoading, isLightboxVisible]); const scaleChange = useCallback( (scale: number) => { @@ -223,9 +227,18 @@ function Lightbox({isAuthTokenRequired = false, uri, onScaleChanged: onScaleChan isAuthTokenRequired={isAuthTokenRequired} onError={onError} onLoad={(e) => { - setLightboxImageLoaded(true); + isLightboxImageLoaded.current = true; + setLightboxImageLoading(false); updateContentSize(e); }} + onLoadStart={() => { + setTimeout(() => { + if (isLightboxImageLoaded.current) { + return; + } + setLightboxImageLoading(true); + }, 200); + }} /> @@ -240,22 +253,31 @@ function Lightbox({isAuthTokenRequired = false, uri, onScaleChanged: onScaleChan style={[fallbackSize ?? styles.invisibleImage]} isAuthTokenRequired={isAuthTokenRequired} onLoad={(e) => { - setFallbackImageLoaded(true); + setFallbackImageLoading(false); + isFallbackImageLoaded.current = true; updateContentSize(e); }} + onLoadStart={() => { + setTimeout(() => { + if (isFallbackImageLoaded.current) { + return; + } + setFallbackImageLoading(true); + }, 200); + }} /> )} + {/* Show activity or offline indicator (based on the connection status) while the lightbox is still loading the image. */} + {isLoading && !isOffline && ( + + )} + {isLoading && } )} - {/* Show activity or offline indicator (based on the connection status) while the lightbox is still loading the image. */} - {isLoading && !isOffline && ( - - )} - {isLoading && } ); } From f0835125dda095f8088b9c5df5d75141658e6118 Mon Sep 17 00:00:00 2001 From: FitseTLT Date: Mon, 15 Apr 2024 13:39:56 +0300 Subject: [PATCH 18/24] fix android loading indicator case --- src/components/Lightbox/index.tsx | 29 +++++++++++++---------------- 1 file changed, 13 insertions(+), 16 deletions(-) diff --git a/src/components/Lightbox/index.tsx b/src/components/Lightbox/index.tsx index a6b30f1e6815..e35dc1703b36 100644 --- a/src/components/Lightbox/index.tsx +++ b/src/components/Lightbox/index.tsx @@ -201,6 +201,19 @@ function Lightbox({isAuthTokenRequired = false, uri, onScaleChanged: onScaleChan [onScaleChangedContext, onScaleChangedProp], ); + useEffect(() => { + // To avoid showing loading or offline indicator for cached images we set loading + // states after a 200 ms delay based on whether the image is loaded or not by then. + setTimeout(() => { + if (!isFallbackImageLoaded.current) { + setFallbackImageLoading(true); + } + if (!isLightboxImageLoaded.current) { + setLightboxImageLoading(true); + } + }, 200); + }, []); + return ( { - setTimeout(() => { - if (isLightboxImageLoaded.current) { - return; - } - setLightboxImageLoading(true); - }, 200); - }} /> @@ -257,14 +262,6 @@ function Lightbox({isAuthTokenRequired = false, uri, onScaleChanged: onScaleChan isFallbackImageLoaded.current = true; updateContentSize(e); }} - onLoadStart={() => { - setTimeout(() => { - if (isFallbackImageLoaded.current) { - return; - } - setFallbackImageLoading(true); - }, 200); - }} /> )} From 36ccc936df58fd041f097c2e2fe4290ab339d0af Mon Sep 17 00:00:00 2001 From: FitseTLT Date: Mon, 15 Apr 2024 14:09:27 +0300 Subject: [PATCH 19/24] minor fix --- src/components/Lightbox/index.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/Lightbox/index.tsx b/src/components/Lightbox/index.tsx index e35dc1703b36..42467569a904 100644 --- a/src/components/Lightbox/index.tsx +++ b/src/components/Lightbox/index.tsx @@ -168,7 +168,7 @@ function Lightbox({isAuthTokenRequired = false, uri, onScaleChanged: onScaleChan return; } isLightboxImageLoaded.current = false; - setLightboxImageLoading(false); + setLightboxImageLoading(true); setContentSize(undefined); }, [isLightboxVisible, setContentSize]); From 75a772eb376f562b9ef16c3ea1b36801344785e5 Mon Sep 17 00:00:00 2001 From: FitseTLT Date: Mon, 15 Apr 2024 20:38:35 +0300 Subject: [PATCH 20/24] fix receipt offline indicator case --- src/components/AttachmentModal.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/AttachmentModal.tsx b/src/components/AttachmentModal.tsx index 7d13524b78df..3938c2de8559 100644 --- a/src/components/AttachmentModal.tsx +++ b/src/components/AttachmentModal.tsx @@ -463,7 +463,7 @@ function AttachmentModal({ } const context = useMemo( () => ({ - pagerItems: [], + pagerItems: [{source: sourceForAttachmentView, index: 0, isActive: true}], activePage: 0, pagerRef: undefined, isPagerScrolling: nope, @@ -472,7 +472,7 @@ function AttachmentModal({ onScaleChanged: () => {}, onSwipeDown: closeModal, }), - [closeModal, nope], + [closeModal, nope, sourceForAttachmentView], ); return ( From bd00d41ed94ef5ab1ffc1bb31b4a312cab204351 Mon Sep 17 00:00:00 2001 From: FitseTLT Date: Mon, 15 Apr 2024 22:40:59 +0300 Subject: [PATCH 21/24] minor fix --- src/components/Lightbox/index.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/src/components/Lightbox/index.tsx b/src/components/Lightbox/index.tsx index 42467569a904..bf5067e81bbb 100644 --- a/src/components/Lightbox/index.tsx +++ b/src/components/Lightbox/index.tsx @@ -168,7 +168,6 @@ function Lightbox({isAuthTokenRequired = false, uri, onScaleChanged: onScaleChan return; } isLightboxImageLoaded.current = false; - setLightboxImageLoading(true); setContentSize(undefined); }, [isLightboxVisible, setContentSize]); From 23ed2e37291380a5d047d3a8c24274b0e5ca9e0d Mon Sep 17 00:00:00 2001 From: FitseTLT Date: Thu, 2 May 2024 15:29:33 +0300 Subject: [PATCH 22/24] added delay for offline indicator --- src/components/AttachmentOfflineIndicator.tsx | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/src/components/AttachmentOfflineIndicator.tsx b/src/components/AttachmentOfflineIndicator.tsx index 8a831b02b407..d425e6f18e0e 100644 --- a/src/components/AttachmentOfflineIndicator.tsx +++ b/src/components/AttachmentOfflineIndicator.tsx @@ -1,4 +1,4 @@ -import React from 'react'; +import React, {useEffect, useState} from 'react'; import {View} from 'react-native'; import useLocalize from '@hooks/useLocalize'; import useNetwork from '@hooks/useNetwork'; @@ -20,7 +20,17 @@ function AttachmentOfflineIndicator({isPreview = false}: AttachmentOfflineIndica const {isOffline} = useNetwork(); const {translate} = useLocalize(); - if (!isOffline) { + // We don't want to show the offline indicator when the attachment is a cached one, so + // we delay the display by 200 ms to ensure it is not a cached one. + const [onCacheDelay, setOnCacheDelay] = useState(true); + + useEffect(() => { + const timeout = setTimeout(() => setOnCacheDelay(false), 200); + + return () => clearTimeout(timeout); + }, []); + + if (!isOffline || onCacheDelay) { return null; } From 4de0a9694fbefbd01736491c392b107e5c4b0cd7 Mon Sep 17 00:00:00 2001 From: FitseTLT Date: Thu, 2 May 2024 15:31:36 +0300 Subject: [PATCH 23/24] revert unnecessary change --- src/components/Lightbox/index.tsx | 53 ++++++++++--------------------- 1 file changed, 16 insertions(+), 37 deletions(-) diff --git a/src/components/Lightbox/index.tsx b/src/components/Lightbox/index.tsx index bf5067e81bbb..84e4b3c1832f 100644 --- a/src/components/Lightbox/index.tsx +++ b/src/components/Lightbox/index.tsx @@ -1,15 +1,13 @@ -import React, {useCallback, useContext, useEffect, useMemo, useRef, useState} from 'react'; +import React, {useCallback, useContext, useEffect, useMemo, useState} from 'react'; import type {LayoutChangeEvent, StyleProp, ViewStyle} from 'react-native'; import {ActivityIndicator, PixelRatio, StyleSheet, View} from 'react-native'; import {useSharedValue} from 'react-native-reanimated'; -import AttachmentOfflineIndicator from '@components/AttachmentOfflineIndicator'; import AttachmentCarouselPagerContext from '@components/Attachments/AttachmentCarousel/Pager/AttachmentCarouselPagerContext'; import Image from '@components/Image'; import type {ImageOnLoadEvent} from '@components/Image/types'; import MultiGestureCanvas, {DEFAULT_ZOOM_RANGE} from '@components/MultiGestureCanvas'; import type {CanvasSize, ContentSize, OnScaleChangedCallback, ZoomRange} from '@components/MultiGestureCanvas/types'; import {getCanvasFitScale} from '@components/MultiGestureCanvas/utils'; -import useNetwork from '@hooks/useNetwork'; import useStyleUtils from '@hooks/useStyleUtils'; import useThemeStyles from '@hooks/useThemeStyles'; import NUMBER_OF_CONCURRENT_LIGHTBOXES from './numberOfConcurrentLightboxes'; @@ -49,7 +47,6 @@ function Lightbox({isAuthTokenRequired = false, uri, onScaleChanged: onScaleChan * we need to create a shared value that can be used in the render function. */ const isPagerScrollingFallback = useSharedValue(false); - const {isOffline} = useNetwork(); const attachmentCarouselPagerContext = useContext(AttachmentCarouselPagerContext); const { @@ -133,12 +130,10 @@ function Lightbox({isAuthTokenRequired = false, uri, onScaleChanged: onScaleChan const indexOutOfRange = page > activePage + indexCanvasOffset || page < activePage - indexCanvasOffset; return !indexOutOfRange; }, [activePage, hasSiblingCarouselItems, page]); - const [isLightboxImageLoading, setLightboxImageLoading] = useState(false); - const isLightboxImageLoaded = useRef(false); + const [isLightboxImageLoaded, setLightboxImageLoaded] = useState(false); const [isFallbackVisible, setFallbackVisible] = useState(!isLightboxVisible); - const [isFallbackImageLoading, setFallbackImageLoading] = useState(false); - const isFallbackImageLoaded = useRef(false); + const [isFallbackImageLoaded, setFallbackImageLoaded] = useState(false); const fallbackSize = useMemo(() => { if (!hasSiblingCarouselItems || !contentSize || isCanvasLoading) { return undefined; @@ -156,18 +151,18 @@ function Lightbox({isAuthTokenRequired = false, uri, onScaleChanged: onScaleChan // until the fallback gets hidden so that we don't see two overlapping images at the same time. // If there the Lightbox is not used within a carousel, we don't need to hide the Lightbox, // because it's only going to be rendered after the fallback image is hidden. - const shouldShowLightbox = isLightboxImageLoaded.current && !isFallbackVisible; + const shouldShowLightbox = isLightboxImageLoaded && !isFallbackVisible; - const isFallbackStillLoading = isFallbackVisible && isFallbackImageLoading; - const isLightboxStillLoading = isLightboxVisible && isLightboxImageLoading; - const isLoading = isActive && (isFallbackStillLoading || isLightboxStillLoading); + const isFallbackStillLoading = isFallbackVisible && !isFallbackImageLoaded; + const isLightboxStillLoading = isLightboxVisible && !isLightboxImageLoaded; + const isLoading = isActive && (isCanvasLoading || isFallbackStillLoading || isLightboxStillLoading); // Resets the lightbox when it becomes inactive useEffect(() => { if (isLightboxVisible) { return; } - isLightboxImageLoaded.current = false; + setLightboxImageLoaded(false); setContentSize(undefined); }, [isLightboxVisible, setContentSize]); @@ -179,10 +174,9 @@ function Lightbox({isAuthTokenRequired = false, uri, onScaleChanged: onScaleChan } // When the carousel item is active and the lightbox has finished loading, we want to hide the fallback image - if (isActive && isFallbackVisible && isLightboxVisible && isLightboxImageLoaded.current) { + if (isActive && isFallbackVisible && isLightboxVisible && isLightboxImageLoaded) { setFallbackVisible(false); - setFallbackImageLoading(false); - isFallbackImageLoaded.current = false; + setFallbackImageLoaded(false); return; } @@ -190,7 +184,7 @@ function Lightbox({isAuthTokenRequired = false, uri, onScaleChanged: onScaleChan if (!isActive && !isLightboxVisible) { setFallbackVisible(true); } - }, [hasSiblingCarouselItems, isActive, isFallbackVisible, isLightboxImageLoading, isLightboxVisible]); + }, [hasSiblingCarouselItems, isActive, isFallbackVisible, isLightboxImageLoaded, isLightboxVisible]); const scaleChange = useCallback( (scale: number) => { @@ -200,19 +194,6 @@ function Lightbox({isAuthTokenRequired = false, uri, onScaleChanged: onScaleChan [onScaleChangedContext, onScaleChangedProp], ); - useEffect(() => { - // To avoid showing loading or offline indicator for cached images we set loading - // states after a 200 ms delay based on whether the image is loaded or not by then. - setTimeout(() => { - if (!isFallbackImageLoaded.current) { - setFallbackImageLoading(true); - } - if (!isLightboxImageLoaded.current) { - setLightboxImageLoading(true); - } - }, 200); - }, []); - return ( { - isLightboxImageLoaded.current = true; - setLightboxImageLoading(false); updateContentSize(e); + setLightboxImageLoaded(true); }} /> @@ -257,21 +237,20 @@ function Lightbox({isAuthTokenRequired = false, uri, onScaleChanged: onScaleChan style={[fallbackSize ?? styles.invisibleImage]} isAuthTokenRequired={isAuthTokenRequired} onLoad={(e) => { - setFallbackImageLoading(false); - isFallbackImageLoaded.current = true; updateContentSize(e); + setFallbackImageLoaded(true); }} /> )} - {/* Show activity or offline indicator (based on the connection status) while the lightbox is still loading the image. */} - {isLoading && !isOffline && ( + + {/* Show activity indicator while the lightbox is still loading the image. */} + {isLoading && ( )} - {isLoading && } )} From 1dd5ee45d53549832e55256428da46a06eeb6c1a Mon Sep 17 00:00:00 2001 From: FitseTLT Date: Tue, 7 May 2024 15:50:10 +0300 Subject: [PATCH 24/24] add offline indicator for lightbox --- src/components/Lightbox/index.tsx | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/components/Lightbox/index.tsx b/src/components/Lightbox/index.tsx index 84e4b3c1832f..0be0171eaa9a 100644 --- a/src/components/Lightbox/index.tsx +++ b/src/components/Lightbox/index.tsx @@ -2,12 +2,14 @@ import React, {useCallback, useContext, useEffect, useMemo, useState} from 'reac import type {LayoutChangeEvent, StyleProp, ViewStyle} from 'react-native'; import {ActivityIndicator, PixelRatio, StyleSheet, View} from 'react-native'; import {useSharedValue} from 'react-native-reanimated'; +import AttachmentOfflineIndicator from '@components/AttachmentOfflineIndicator'; import AttachmentCarouselPagerContext from '@components/Attachments/AttachmentCarousel/Pager/AttachmentCarouselPagerContext'; import Image from '@components/Image'; import type {ImageOnLoadEvent} from '@components/Image/types'; import MultiGestureCanvas, {DEFAULT_ZOOM_RANGE} from '@components/MultiGestureCanvas'; import type {CanvasSize, ContentSize, OnScaleChangedCallback, ZoomRange} from '@components/MultiGestureCanvas/types'; import {getCanvasFitScale} from '@components/MultiGestureCanvas/utils'; +import useNetwork from '@hooks/useNetwork'; import useStyleUtils from '@hooks/useStyleUtils'; import useThemeStyles from '@hooks/useThemeStyles'; import NUMBER_OF_CONCURRENT_LIGHTBOXES from './numberOfConcurrentLightboxes'; @@ -47,6 +49,7 @@ function Lightbox({isAuthTokenRequired = false, uri, onScaleChanged: onScaleChan * we need to create a shared value that can be used in the render function. */ const isPagerScrollingFallback = useSharedValue(false); + const {isOffline} = useNetwork(); const attachmentCarouselPagerContext = useContext(AttachmentCarouselPagerContext); const { @@ -245,12 +248,13 @@ function Lightbox({isAuthTokenRequired = false, uri, onScaleChanged: onScaleChan )} {/* Show activity indicator while the lightbox is still loading the image. */} - {isLoading && ( + {isLoading && !isOffline && ( )} + {isLoading && } )}