Open User avatar in gallery (#6474)
This commit is contained in:
parent
f12be07df7
commit
0f0c7d5795
9 changed files with 110 additions and 29 deletions
|
|
@ -3,6 +3,7 @@
|
|||
|
||||
import React, {useMemo} from 'react';
|
||||
import FastImage, {Source} from 'react-native-fast-image';
|
||||
import Animated from 'react-native-reanimated';
|
||||
|
||||
import CompassIcon from '@components/compass_icon';
|
||||
import {ACCOUNT_OUTLINE_IMAGE} from '@constants/profile';
|
||||
|
|
@ -16,11 +17,15 @@ import type UserModel from '@typings/database/models/servers/user';
|
|||
|
||||
type Props = {
|
||||
author?: UserModel | UserProfile;
|
||||
forwardRef?: React.RefObject<any>;
|
||||
iconSize?: number;
|
||||
size: number;
|
||||
source?: Source | string;
|
||||
};
|
||||
|
||||
// @ts-expect-error FastImage does work with Animated.createAnimatedComponent
|
||||
const AnimatedFastImage = Animated.createAnimatedComponent(FastImage);
|
||||
|
||||
const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => {
|
||||
return {
|
||||
icon: {
|
||||
|
|
@ -29,7 +34,7 @@ const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => {
|
|||
};
|
||||
});
|
||||
|
||||
const Image = ({author, iconSize, size, source}: Props) => {
|
||||
const Image = ({author, forwardRef, iconSize, size, source}: Props) => {
|
||||
const theme = useTheme();
|
||||
const serverUrl = useServerUrl();
|
||||
const style = getStyleSheet(theme);
|
||||
|
|
@ -69,8 +74,9 @@ const Image = ({author, iconSize, size, source}: Props) => {
|
|||
const pictureUrl = client.getProfilePictureUrl(author.id, lastPictureUpdate);
|
||||
const imgSource = source ?? {uri: `${serverUrl}${pictureUrl}`};
|
||||
return (
|
||||
<FastImage
|
||||
<AnimatedFastImage
|
||||
key={pictureUrl}
|
||||
ref={forwardRef}
|
||||
style={fIStyle}
|
||||
source={imgSource}
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@ const STATUS_BUFFER = Platform.select({
|
|||
|
||||
type ProfilePictureProps = {
|
||||
author?: UserModel | UserProfile;
|
||||
forwardRef?: React.RefObject<any>;
|
||||
iconSize?: number;
|
||||
showStatus?: boolean;
|
||||
size: number;
|
||||
|
|
@ -60,6 +61,7 @@ const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => {
|
|||
|
||||
const ProfilePicture = ({
|
||||
author,
|
||||
forwardRef,
|
||||
iconSize,
|
||||
showStatus = true,
|
||||
size = 64,
|
||||
|
|
@ -105,6 +107,7 @@ const ProfilePicture = ({
|
|||
>
|
||||
<Image
|
||||
author={author}
|
||||
forwardRef={forwardRef}
|
||||
iconSize={iconSize}
|
||||
size={size}
|
||||
source={source}
|
||||
|
|
|
|||
|
|
@ -64,6 +64,7 @@ const Details = ({channelName, isDirectChannel, ownPost, userDisplayName}: Props
|
|||
return (
|
||||
<View style={styles.container}>
|
||||
{userElement}
|
||||
{Boolean(channelName) &&
|
||||
<FormattedText
|
||||
id='gallery.footer.channel_name'
|
||||
defaultMessage='Shared in {channelName}'
|
||||
|
|
@ -72,6 +73,7 @@ const Details = ({channelName, isDirectChannel, ownPost, userDisplayName}: Props
|
|||
style={styles.chanelText}
|
||||
values={channelDisplayName}
|
||||
/>
|
||||
}
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -77,7 +77,9 @@ const Footer = ({
|
|||
}
|
||||
|
||||
let userDisplayName;
|
||||
if (enablePostUsernameOverride && post?.props?.override_username) {
|
||||
if (item.type === 'avatar') {
|
||||
userDisplayName = item.name;
|
||||
} else if (enablePostUsernameOverride && post?.props?.override_username) {
|
||||
userDisplayName = post.props.override_username as string;
|
||||
} else {
|
||||
userDisplayName = displayUsername(author, undefined, teammateNameDisplay);
|
||||
|
|
@ -124,12 +126,14 @@ const Footer = ({
|
|||
}
|
||||
<View style={styles.container}>
|
||||
<View style={styles.details}>
|
||||
{item.type !== 'avatar' &&
|
||||
<Avatar
|
||||
authorId={author?.id}
|
||||
overrideIconUrl={overrideIconUrl}
|
||||
/>
|
||||
}
|
||||
<Details
|
||||
channelName={channelName}
|
||||
channelName={item.type === 'avatar' ? '' : channelName}
|
||||
isDirectChannel={isDirectChannel}
|
||||
ownPost={author?.id === currentUserId}
|
||||
userDisplayName={userDisplayName}
|
||||
|
|
@ -139,7 +143,7 @@ const Footer = ({
|
|||
<Actions
|
||||
disabled={action !== 'none'}
|
||||
canDownloadFiles={canDownloadFiles}
|
||||
enablePublicLinks={enablePublicLink}
|
||||
enablePublicLinks={enablePublicLink && item.type !== 'avatar'}
|
||||
fileId={item.id!}
|
||||
onCopyPublicLink={handleCopyLink}
|
||||
onDownload={handleDownload}
|
||||
|
|
|
|||
|
|
@ -316,19 +316,23 @@ const LightboxSwipeout = forwardRef<LightboxSwipeoutRef, LightboxSwipeoutProps>(
|
|||
})}
|
||||
|
||||
<Animated.View style={StyleSheet.absoluteFillObject}>
|
||||
{target.type !== 'image' && typeof renderItem === 'function' ? (
|
||||
renderItem({
|
||||
source: imageSource,
|
||||
width: targetWidth.value,
|
||||
height: targetHeight.value,
|
||||
itemStyles,
|
||||
})
|
||||
) : (
|
||||
<AnimatedImage
|
||||
source={imageSource}
|
||||
style={itemStyles}
|
||||
/>
|
||||
)}
|
||||
{
|
||||
target.type !== 'image' &&
|
||||
target.type !== 'avatar' &&
|
||||
typeof renderItem === 'function' ? (
|
||||
renderItem({
|
||||
source: imageSource,
|
||||
width: targetWidth.value,
|
||||
height: targetHeight.value,
|
||||
itemStyles,
|
||||
})
|
||||
) : (
|
||||
<AnimatedImage
|
||||
source={imageSource}
|
||||
style={itemStyles}
|
||||
/>
|
||||
)
|
||||
}
|
||||
</Animated.View>
|
||||
|
||||
<Animated.View
|
||||
|
|
|
|||
|
|
@ -109,7 +109,11 @@ const GalleryViewer = ({
|
|||
onInteraction: interaction,
|
||||
};
|
||||
|
||||
if (props.item.type !== 'image' && typeof renderPage === 'function') {
|
||||
if (
|
||||
props.item.type !== 'image' &&
|
||||
props.item.type !== 'avatar' &&
|
||||
typeof renderPage === 'function'
|
||||
) {
|
||||
return renderPage(props, index);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -6,13 +6,18 @@
|
|||
import React from 'react';
|
||||
import {StyleSheet, View} from 'react-native';
|
||||
import FastImage from 'react-native-fast-image';
|
||||
import Animated from 'react-native-reanimated';
|
||||
|
||||
import ProfilePicture from '@components/profile_picture';
|
||||
|
||||
import type UserModel from '@typings/database/models/servers/user';
|
||||
|
||||
// @ts-expect-error FastImage does work with Animated.createAnimatedComponent
|
||||
const AnimatedFastImage = Animated.createAnimatedComponent(FastImage);
|
||||
|
||||
type Props = {
|
||||
enablePostIconOverride: boolean;
|
||||
forwardRef?: React.RefObject<any>;
|
||||
user: UserModel;
|
||||
userIconOverride?: string;
|
||||
}
|
||||
|
|
@ -25,11 +30,12 @@ const styles = StyleSheet.create({
|
|||
},
|
||||
});
|
||||
|
||||
const UserProfileAvatar = ({enablePostIconOverride, user, userIconOverride}: Props) => {
|
||||
const UserProfileAvatar = ({enablePostIconOverride, forwardRef, user, userIconOverride}: Props) => {
|
||||
if (enablePostIconOverride && userIconOverride) {
|
||||
return (
|
||||
<View style={styles.avatar}>
|
||||
<FastImage
|
||||
<AnimatedFastImage
|
||||
ref={forwardRef}
|
||||
style={styles.avatar}
|
||||
source={{uri: userIconOverride}}
|
||||
/>
|
||||
|
|
@ -40,6 +46,7 @@ const UserProfileAvatar = ({enablePostIconOverride, user, userIconOverride}: Pro
|
|||
return (
|
||||
<ProfilePicture
|
||||
author={user}
|
||||
forwardRef={forwardRef}
|
||||
showStatus={true}
|
||||
size={96}
|
||||
statusSize={24}
|
||||
|
|
|
|||
|
|
@ -2,10 +2,16 @@
|
|||
// See LICENSE.txt for license information.
|
||||
import React from 'react';
|
||||
import {useIntl} from 'react-intl';
|
||||
import {Text, View} from 'react-native';
|
||||
import {Text, TouchableOpacity, View} from 'react-native';
|
||||
import Animated from 'react-native-reanimated';
|
||||
|
||||
import {useServerUrl} from '@app/context/server';
|
||||
import {useGalleryItem} from '@app/hooks/gallery';
|
||||
import {openGalleryAtIndex} from '@app/utils/gallery';
|
||||
import {GalleryInit} from '@context/gallery';
|
||||
import {useTheme} from '@context/theme';
|
||||
import {useIsTablet} from '@hooks/device';
|
||||
import NetworkManager from '@managers/network_manager';
|
||||
import {changeOpacity, makeStyleSheetFromTheme} from '@utils/theme';
|
||||
import {typography} from '@utils/typography';
|
||||
import {displayUsername} from '@utils/user';
|
||||
|
|
@ -55,29 +61,74 @@ const UserProfileTitle = ({
|
|||
isChannelAdmin, isSystemAdmin, isTeamAdmin,
|
||||
teammateDisplayName, user, userIconOverride, usernameOverride,
|
||||
}: Props) => {
|
||||
const galleryIdentifier = `${user.id}-avatarPreview`;
|
||||
const intl = useIntl();
|
||||
const isTablet = useIsTablet();
|
||||
const theme = useTheme();
|
||||
const serverUrl = useServerUrl();
|
||||
const styles = getStyleSheet(theme);
|
||||
const override = enablePostUsernameOverride && usernameOverride;
|
||||
|
||||
let displayName;
|
||||
let displayName: string;
|
||||
if (override) {
|
||||
displayName = usernameOverride;
|
||||
} else {
|
||||
displayName = displayUsername(user, intl.locale, teammateDisplayName, false);
|
||||
}
|
||||
|
||||
const onPress = () => {
|
||||
let imageUrl: string|undefined;
|
||||
if (enablePostIconOverride && userIconOverride) {
|
||||
imageUrl = userIconOverride;
|
||||
} else {
|
||||
try {
|
||||
const client = NetworkManager.getClient(serverUrl);
|
||||
const lastPictureUpdate = user.isBot ? (user.props?.bot_last_icon_update || 0) : user.lastPictureUpdate;
|
||||
const pictureUrl = client.getProfilePictureUrl(user.id, lastPictureUpdate);
|
||||
imageUrl = `${serverUrl}${pictureUrl}`;
|
||||
} catch {
|
||||
// handle below that the client is not set
|
||||
}
|
||||
}
|
||||
|
||||
if (imageUrl) {
|
||||
const item: GalleryItemType = {
|
||||
id: user.id,
|
||||
uri: imageUrl,
|
||||
width: 400,
|
||||
height: 400,
|
||||
name: displayName,
|
||||
mime_type: 'images/png',
|
||||
authorId: user.id,
|
||||
type: 'avatar',
|
||||
};
|
||||
openGalleryAtIndex(galleryIdentifier, 0, [item]);
|
||||
}
|
||||
};
|
||||
|
||||
const {ref, onGestureEvent, styles: galleryStyles} = useGalleryItem(
|
||||
galleryIdentifier,
|
||||
0,
|
||||
onPress,
|
||||
);
|
||||
|
||||
const hideUsername = override || (displayName && displayName === user.username);
|
||||
const prefix = hideUsername ? '@' : '';
|
||||
|
||||
return (
|
||||
<View style={[styles.container, isTablet && styles.tablet]}>
|
||||
<UserProfileAvatar
|
||||
enablePostIconOverride={enablePostIconOverride}
|
||||
user={user}
|
||||
userIconOverride={userIconOverride}
|
||||
/>
|
||||
<GalleryInit galleryIdentifier={galleryIdentifier}>
|
||||
<Animated.View style={galleryStyles}>
|
||||
<TouchableOpacity onPress={onGestureEvent}>
|
||||
<UserProfileAvatar
|
||||
forwardRef={ref}
|
||||
enablePostIconOverride={enablePostIconOverride}
|
||||
user={user}
|
||||
userIconOverride={userIconOverride}
|
||||
/>
|
||||
</TouchableOpacity>
|
||||
</Animated.View>
|
||||
</GalleryInit>
|
||||
<View style={styles.details}>
|
||||
<UserProfileTag
|
||||
isBot={user.isBot || Boolean(userIconOverride || usernameOverride)}
|
||||
|
|
|
|||
2
types/screens/gallery.d.ts
vendored
2
types/screens/gallery.d.ts
vendored
|
|
@ -55,7 +55,7 @@ type OnGestureEvent<T extends GestureHandlerGestureEvent> = (
|
|||
) => void;
|
||||
|
||||
type GalleryItemType = {
|
||||
type: 'image' | 'video' | 'file';
|
||||
type: 'image' | 'video' | 'file' | 'avatar';
|
||||
id: string;
|
||||
width: number;
|
||||
height: number;
|
||||
|
|
|
|||
Loading…
Reference in a new issue