From 88fde2cc5eab66bf734b33f49ddb28e8cdde2550 Mon Sep 17 00:00:00 2001 From: Elias Nahum Date: Mon, 19 Dec 2022 21:29:11 +0200 Subject: [PATCH] Restyle video playback error (#6871) * Restyle video playback error * video not shown when attached with multiple files and thumb fails * update local path when saving and sharing a file * feedback review --- app/components/files/files.tsx | 7 +- app/components/files/video_file.tsx | 12 +- .../document_renderer/document_renderer.tsx | 4 +- .../footer/download_with_action/index.tsx | 5 + app/screens/gallery/video_renderer/error.tsx | 145 ++++++++++++++++++ app/screens/gallery/video_renderer/index.tsx | 40 +++-- assets/base/i18n/en.json | 3 +- 7 files changed, 188 insertions(+), 28 deletions(-) create mode 100644 app/screens/gallery/video_renderer/error.tsx diff --git a/app/components/files/files.tsx b/app/components/files/files.tsx index 5f45f878e..b4a404b92 100644 --- a/app/components/files/files.tsx +++ b/app/components/files/files.tsx @@ -1,7 +1,7 @@ // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. // See LICENSE.txt for license information. -import React, {useEffect, useState} from 'react'; +import React, {useEffect, useMemo, useState} from 'react'; import {DeviceEventEmitter, StyleProp, StyleSheet, View, ViewStyle} from 'react-native'; import Animated, {useDerivedValue} from 'react-native-reanimated'; @@ -69,10 +69,9 @@ const Files = ({canDownloadFiles, failed, filesInfo, isReplyPost, layoutWidth, l filesForGallery.value[idx] = file; }; - const isSingleImage = () => (filesInfo.length === 1 && (isImage(filesInfo[0]) || isVideo(filesInfo[0]))); + const isSingleImage = useMemo(() => filesInfo.filter((f) => isImage(f) || isVideo(f)).length === 1, [filesInfo]); const renderItems = (items: FileInfo[], moreImagesCount = 0, includeGutter = false) => { - const singleImage = isSingleImage(); let nonVisibleImagesCount: number; let container: StyleProp = items.length > 1 ? styles.container : undefined; const containerWithGutter = [container, styles.gutter]; @@ -97,7 +96,7 @@ const Files = ({canDownloadFiles, failed, filesInfo, isReplyPost, layoutWidth, l file={file} index={attachmentIndex(file.id!)} onPress={handlePreviewPress} - isSingleImage={singleImage} + isSingleImage={isSingleImage} nonVisibleImagesCount={nonVisibleImagesCount} publicLinkEnabled={publicLinkEnabled} updateFileForGallery={updateFileForGallery} diff --git a/app/components/files/video_file.tsx b/app/components/files/video_file.tsx index 7b7ef0f86..c5b7d919e 100644 --- a/app/components/files/video_file.tsx +++ b/app/components/files/video_file.tsx @@ -76,9 +76,8 @@ const VideoFile = ({ const viewPortHeight = Math.max(dimensions.height, dimensions.width) * 0.45; return calculateDimensions(video.height || wrapperWidth, video.width || wrapperWidth, wrapperWidth, viewPortHeight); } - return undefined; - }, [dimensions.height, dimensions.width, video.height, video.width, wrapperWidth]); + }, [dimensions.height, dimensions.width, video.height, video.width, wrapperWidth, isSingleImage]); const getThumbnail = async () => { const data = {...file}; @@ -161,7 +160,12 @@ const VideoFile = ({ if (failed) { thumbnail = ( - + - {!isSingleImage && } + {!isSingleImage && !failed && } {thumbnail} diff --git a/app/screens/gallery/document_renderer/document_renderer.tsx b/app/screens/gallery/document_renderer/document_renderer.tsx index 693be2b00..cd94fd7d5 100644 --- a/app/screens/gallery/document_renderer/document_renderer.tsx +++ b/app/screens/gallery/document_renderer/document_renderer.tsx @@ -105,8 +105,8 @@ const DocumentRenderer = ({canDownloadFiles, item, onShouldHideControls}: Props) onPress={handleOpenFile} rippleColor={changeOpacity('#fff', 0.16)} > - - {optionText} + + {optionText} diff --git a/app/screens/gallery/footer/download_with_action/index.tsx b/app/screens/gallery/footer/download_with_action/index.tsx index 730516c6a..ce8b4c5d0 100644 --- a/app/screens/gallery/footer/download_with_action/index.tsx +++ b/app/screens/gallery/footer/download_with_action/index.tsx @@ -13,6 +13,7 @@ import {useAnimatedStyle, withTiming} from 'react-native-reanimated'; import {useSafeAreaInsets} from 'react-native-safe-area-context'; import Share from 'react-native-share'; +import {updateLocalFilePath} from '@actions/local/file'; import {downloadFile} from '@actions/remote/file'; import CompassIcon from '@components/compass_icon'; import ProgressBar from '@components/progress_bar'; @@ -173,6 +174,8 @@ const DownloadWithAction = ({action, item, onDownloadSuccess, setAction, gallery return; } + updateLocalFilePath(serverUrl, item.id, path); + Share.open({ url: path, saveToFiles: true, @@ -193,6 +196,7 @@ const DownloadWithAction = ({action, item, onDownloadSuccess, setAction, gallery album: applicationName, }); setSaved(true); + updateLocalFilePath(serverUrl, item.id, path); } catch { setError(intl.formatMessage({id: 'gallery.save_failed', defaultMessage: 'Unable to save the file'})); } @@ -223,6 +227,7 @@ const DownloadWithAction = ({action, item, onDownloadSuccess, setAction, gallery if (response.data?.path) { const path = response.data.path as string; onDownloadSuccess?.(path); + updateLocalFilePath(serverUrl, item.id, path); Share.open({ message: '', title: '', diff --git a/app/screens/gallery/video_renderer/error.tsx b/app/screens/gallery/video_renderer/error.tsx new file mode 100644 index 000000000..6d88f8fa8 --- /dev/null +++ b/app/screens/gallery/video_renderer/error.tsx @@ -0,0 +1,145 @@ +// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. +// See LICENSE.txt for license information. + +import React, {Dispatch, SetStateAction, useCallback, useState} from 'react'; +import {StyleSheet, Text, useWindowDimensions, View} from 'react-native'; +import FastImage from 'react-native-fast-image'; +import {RectButton, TouchableWithoutFeedback} from 'react-native-gesture-handler'; +import Animated from 'react-native-reanimated'; + +import {typography} from '@app/utils/typography'; +import CompassIcon from '@components/compass_icon'; +import FormattedText from '@components/formatted_text'; +import {Preferences} from '@constants'; +import {buttonBackgroundStyle, buttonTextStyle} from '@utils/buttonStyles'; +import {calculateDimensions} from '@utils/images'; +import {changeOpacity} from '@utils/theme'; + +const styles = StyleSheet.create({ + container: { + alignItems: 'center', + justifyContent: 'center', + flex: 1, + maxWidth: 600, + }, + filename: { + color: '#FFF', + ...typography('Body', 200, 'SemiBold'), + marginVertical: 8, + paddingHorizontal: 25, + textAlign: 'center', + }, + unsupported: { + color: '#FFF', + ...typography('Body', 100, 'SemiBold'), + marginTop: 10, + paddingHorizontal: 25, + opacity: 0.64, + textAlign: 'center', + }, + marginBottom: { + marginBottom: 16, + }, + marginTop: { + marginTop: 16, + }, +}); + +type Props = { + filename: string; + height: number; + isDownloading: boolean; + isRemote: boolean; + onShouldHideControls: () => void; + posterUri?: string; + setDownloading: Dispatch>; + width: number; +} + +const VideoError = ({filename, height, isDownloading, isRemote, onShouldHideControls, posterUri, setDownloading, width}: Props) => { + const [hasPoster, setHasPoster] = useState(false); + const [loadPosterError, setLoadPosterError] = useState(false); + + const handleDownload = useCallback(() => { + setDownloading(true); + }, []); + + const handlePosterSet = useCallback(() => { + setHasPoster(true); + }, []); + + const handlePosterError = useCallback(() => { + setLoadPosterError(true); + }, []); + + const dimensions = useWindowDimensions(); + const imageDimensions = calculateDimensions(height, width, dimensions.width); + + let poster; + if (posterUri && !loadPosterError) { + poster = ( + + ); + } else { + poster = ( + + ); + } + + return ( + + + {poster} + + {filename} + + {isRemote && + + + + + + + + + + + } + {!isRemote && + + } + + + ); +}; + +export default VideoError; diff --git a/app/screens/gallery/video_renderer/index.tsx b/app/screens/gallery/video_renderer/index.tsx index 6df79d899..0fb34f66a 100644 --- a/app/screens/gallery/video_renderer/index.tsx +++ b/app/screens/gallery/video_renderer/index.tsx @@ -2,8 +2,7 @@ // See LICENSE.txt for license information. import React, {useCallback, useEffect, useMemo, useRef, useState} from 'react'; -import {useIntl} from 'react-intl'; -import {Alert, DeviceEventEmitter, Platform, StyleSheet, useWindowDimensions} from 'react-native'; +import {DeviceEventEmitter, Platform, StyleSheet, useWindowDimensions} from 'react-native'; import Animated, {Easing, useAnimatedRef, useAnimatedStyle, useSharedValue, withTiming, WithTimingConfig} from 'react-native-reanimated'; import {useSafeAreaInsets} from 'react-native-safe-area-context'; import Video, {OnPlaybackRateData} from 'react-native-video'; @@ -17,6 +16,8 @@ import {changeOpacity} from '@utils/theme'; import DownloadWithAction from '../footer/download_with_action'; +import VideoError from './error'; + import type {ImageRendererProps} from '../image_renderer'; import type {GalleryAction} from '@typings/screens/gallery'; @@ -55,7 +56,6 @@ const VideoRenderer = ({height, index, initialIndex, item, isPageActive, onShoul const dimensions = useWindowDimensions(); const fullscreen = useSharedValue(false); const {bottom} = useSafeAreaInsets(); - const {formatMessage} = useIntl(); const serverUrl = useServerUrl(); const videoRef = useAnimatedRef