mirror of
https://github.com/fluxerapp/fluxer
synced 2026-10-17 21:15:32 +09:00
fix(app): honour motion settings in the mobile meme picker (#1942)
This commit is contained in:
1 parent
986c586683
commit
2377a4c9d0
1 file changed
+20
-39
@@ -1,10 +1,10 @@
|
|||||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||||
|
|
||||||
import Accessibility from '@app/features/accessibility/state/Accessibility';
|
|
||||||
import {ConfirmModal} from '@app/features/app/components/dialogs/ConfirmModal';
|
import {ConfirmModal} from '@app/features/app/components/dialogs/ConfirmModal';
|
||||||
import {LongPressable} from '@app/features/app/components/LongPressable';
|
import {LongPressable} from '@app/features/app/components/LongPressable';
|
||||||
import {useAnimatedMediaVideoPlayback} from '@app/features/app/hooks/useAnimatedMediaPlayback';
|
import {useAnimatedMediaVideoPlayback} from '@app/features/app/hooks/useAnimatedMediaPlayback';
|
||||||
import {useSearchInputAutofocus} from '@app/features/app/hooks/useSearchInputAutofocus';
|
import {useSearchInputAutofocus} from '@app/features/app/hooks/useSearchInputAutofocus';
|
||||||
|
import {useShouldAnimate} from '@app/features/app/hooks/useShouldAnimate';
|
||||||
import styles from '@app/features/channel/components/GifPicker.module.css';
|
import styles from '@app/features/channel/components/GifPicker.module.css';
|
||||||
import memeStyles from '@app/features/channel/components/MemesPicker.module.css';
|
import memeStyles from '@app/features/channel/components/MemesPicker.module.css';
|
||||||
import {PickerEmptyState} from '@app/features/channel/components/shared/PickerEmptyState';
|
import {PickerEmptyState} from '@app/features/channel/components/shared/PickerEmptyState';
|
||||||
@@ -19,6 +19,8 @@ import type {FavoriteMeme} from '@app/features/expressions/models/FavoriteMeme';
|
|||||||
import FavoriteMemes from '@app/features/expressions/state/FavoriteMemes';
|
import FavoriteMemes from '@app/features/expressions/state/FavoriteMemes';
|
||||||
import {AUDIO_DESCRIPTOR, GIFS_DESCRIPTOR} from '@app/features/i18n/utils/CommonMessageDescriptors';
|
import {AUDIO_DESCRIPTOR, GIFS_DESCRIPTOR} from '@app/features/i18n/utils/CommonMessageDescriptors';
|
||||||
import {isKeyboardActivationKey} from '@app/features/input/utils/KeyboardUtils';
|
import {isKeyboardActivationKey} from '@app/features/input/utils/KeyboardUtils';
|
||||||
|
import {useNearViewport} from '@app/features/messaging/hooks/useNearViewport';
|
||||||
|
import {buildStaticGifPreviewURL} from '@app/features/messaging/utils/MediaProxyUtils';
|
||||||
import {ComponentBus} from '@app/features/platform/utils/ComponentBus';
|
import {ComponentBus} from '@app/features/platform/utils/ComponentBus';
|
||||||
import {DeleteIcon, EditIcon} from '@app/features/ui/action_menu/ContextMenuIcons';
|
import {DeleteIcon, EditIcon} from '@app/features/ui/action_menu/ContextMenuIcons';
|
||||||
import * as ModalCommands from '@app/features/ui/commands/ModalCommands';
|
import * as ModalCommands from '@app/features/ui/commands/ModalCommands';
|
||||||
@@ -31,7 +33,7 @@ import {msg} from '@lingui/core/macro';
|
|||||||
import {useLingui} from '@lingui/react/macro';
|
import {useLingui} from '@lingui/react/macro';
|
||||||
import {GifIcon, ImageIcon, MusicNoteIcon, SmileySadIcon, VideoCameraIcon} from '@phosphor-icons/react';
|
import {GifIcon, ImageIcon, MusicNoteIcon, SmileySadIcon, VideoCameraIcon} from '@phosphor-icons/react';
|
||||||
import {clsx} from 'clsx';
|
import {clsx} from 'clsx';
|
||||||
import {AnimatePresence, motion} from 'framer-motion';
|
import {AnimatePresence} from 'framer-motion';
|
||||||
import {matchSorter} from 'match-sorter';
|
import {matchSorter} from 'match-sorter';
|
||||||
import {observer} from 'mobx-react-lite';
|
import {observer} from 'mobx-react-lite';
|
||||||
import type React from 'react';
|
import type React from 'react';
|
||||||
@@ -93,6 +95,8 @@ const EDIT_SAVED_MEDIA_DESCRIPTOR = msg({
|
|||||||
comment: 'Button or menu action label in the channel and chat mobile memes picker. Keep it concise.',
|
comment: 'Button or menu action label in the channel and chat mobile memes picker. Keep it concise.',
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const GRID_ITEM_PRELOAD_ROOT_MARGIN = '800px 0px';
|
||||||
|
|
||||||
type ContentType = 'all' | 'image' | 'video' | 'audio' | 'gif';
|
type ContentType = 'all' | 'image' | 'video' | 'audio' | 'gif';
|
||||||
|
|
||||||
interface FilterOption {
|
interface FilterOption {
|
||||||
@@ -414,14 +418,20 @@ const MemeGridRenderer = observer(({memes}: {memes: Array<GridItemProps>}) => {
|
|||||||
const GridItem = observer(
|
const GridItem = observer(
|
||||||
({title, onClick, onLongPress, url, width, height, contentType, duration, filename, isGifv}: GridItemProps) => {
|
({title, onClick, onLongPress, url, width, height, contentType, duration, filename, isGifv}: GridItemProps) => {
|
||||||
const {i18n} = useLingui();
|
const {i18n} = useLingui();
|
||||||
const [isVisible, setIsVisible] = useState(false);
|
const {ref: mediaRef, isNearViewport: isVisible} = useNearViewport<HTMLDivElement>({
|
||||||
const mediaRef = useRef<HTMLDivElement>(null);
|
rememberKey: url,
|
||||||
|
rootMargin: GRID_ITEM_PRELOAD_ROOT_MARGIN,
|
||||||
|
});
|
||||||
const videoRef = useRef<HTMLVideoElement>(null);
|
const videoRef = useRef<HTMLVideoElement>(null);
|
||||||
const accessibleName = title || filename;
|
const accessibleName = title || filename;
|
||||||
const isVideo = contentType.startsWith('video/') || contentType.includes('gif');
|
const isVideo = contentType.startsWith('video/');
|
||||||
const isAudio = contentType.startsWith('audio/');
|
const isAudio = contentType.startsWith('audio/');
|
||||||
|
const isGifImage = !isVideo && contentType.toLowerCase().includes('gif');
|
||||||
|
const shouldAnimateGif = useShouldAnimate({kind: 'gif', isAnimated: !isAudio && (isVideo || isGifImage)});
|
||||||
|
const thumbnailSrc = isGifImage && !shouldAnimateGif ? buildStaticGifPreviewURL(url) : url;
|
||||||
const videoPlaybackAllowed = useAnimatedMediaVideoPlayback(videoRef, {
|
const videoPlaybackAllowed = useAnimatedMediaVideoPlayback(videoRef, {
|
||||||
enabled: isVisible && !isAudio && isVideo,
|
enabled: isVisible && !isAudio && isVideo,
|
||||||
|
shouldPlay: shouldAnimateGif,
|
||||||
});
|
});
|
||||||
const handleClick = () => {
|
const handleClick = () => {
|
||||||
onClick();
|
onClick();
|
||||||
@@ -431,27 +441,6 @@ const GridItem = observer(
|
|||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
handleClick();
|
handleClick();
|
||||||
};
|
};
|
||||||
useEffect(() => {
|
|
||||||
const mediaElement = mediaRef.current;
|
|
||||||
if (!mediaElement) return;
|
|
||||||
const observer = new IntersectionObserver(
|
|
||||||
(entries) => {
|
|
||||||
entries.forEach((entry) => {
|
|
||||||
if (entry.isIntersecting) {
|
|
||||||
setIsVisible(true);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
},
|
|
||||||
{
|
|
||||||
rootMargin: '800px 0px',
|
|
||||||
threshold: 0,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
observer.observe(mediaElement);
|
|
||||||
return () => {
|
|
||||||
observer.disconnect();
|
|
||||||
};
|
|
||||||
}, []);
|
|
||||||
return (
|
return (
|
||||||
<LongPressable
|
<LongPressable
|
||||||
onLongPress={onLongPress}
|
onLongPress={onLongPress}
|
||||||
@@ -464,14 +453,7 @@ const GridItem = observer(
|
|||||||
onKeyDown={handleKeyDown}
|
onKeyDown={handleKeyDown}
|
||||||
data-flx="channel.mobile-memes-picker.grid-item.grid-item.click"
|
data-flx="channel.mobile-memes-picker.grid-item.grid-item.click"
|
||||||
>
|
>
|
||||||
<motion.div
|
<div className={memeStyles.fullSize} data-flx="channel.mobile-memes-picker.grid-item.div">
|
||||||
className={memeStyles.fullSize}
|
|
||||||
initial={Accessibility.useReducedMotion ? {opacity: 1} : {opacity: 0}}
|
|
||||||
animate={{opacity: 1}}
|
|
||||||
exit={Accessibility.useReducedMotion ? {opacity: 1} : {opacity: 0}}
|
|
||||||
transition={{duration: Accessibility.useReducedMotion ? 0 : 0.2}}
|
|
||||||
data-flx="channel.mobile-memes-picker.grid-item.div"
|
|
||||||
>
|
|
||||||
{isGifv && <GifIndicator data-flx="channel.mobile-memes-picker.grid-item.gif-indicator" />}
|
{isGifv && <GifIndicator data-flx="channel.mobile-memes-picker.grid-item.gif-indicator" />}
|
||||||
<div
|
<div
|
||||||
ref={mediaRef}
|
ref={mediaRef}
|
||||||
@@ -482,13 +464,13 @@ const GridItem = observer(
|
|||||||
<video
|
<video
|
||||||
ref={videoRef}
|
ref={videoRef}
|
||||||
className={styles.gif}
|
className={styles.gif}
|
||||||
autoPlay={videoPlaybackAllowed}
|
autoPlay={shouldAnimateGif && videoPlaybackAllowed}
|
||||||
loop={true}
|
loop={true}
|
||||||
muted={true}
|
muted={true}
|
||||||
playsInline={true}
|
playsInline={true}
|
||||||
disablePictureInPicture={true}
|
disablePictureInPicture={true}
|
||||||
disableRemotePlayback={true}
|
disableRemotePlayback={true}
|
||||||
preload="auto"
|
preload={shouldAnimateGif ? 'auto' : 'metadata'}
|
||||||
src={url}
|
src={url}
|
||||||
data-flx="channel.mobile-memes-picker.grid-item.gif"
|
data-flx="channel.mobile-memes-picker.grid-item.gif"
|
||||||
/>
|
/>
|
||||||
@@ -496,9 +478,8 @@ const GridItem = observer(
|
|||||||
{isVisible && !isAudio && !isVideo && (
|
{isVisible && !isAudio && !isVideo && (
|
||||||
<img
|
<img
|
||||||
className={styles.gif}
|
className={styles.gif}
|
||||||
src={url}
|
src={thumbnailSrc}
|
||||||
alt={title}
|
alt={title}
|
||||||
loading="lazy"
|
|
||||||
data-flx="channel.mobile-memes-picker.grid-item.gif--2"
|
data-flx="channel.mobile-memes-picker.grid-item.gif--2"
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
@@ -526,7 +507,7 @@ const GridItem = observer(
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</motion.div>
|
</div>
|
||||||
</LongPressable>
|
</LongPressable>
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|||||||
Reference in new issue
Block a user