diff --git a/app/actions/remote/team.ts b/app/actions/remote/team.ts index 40076b485..bcb901e3e 100644 --- a/app/actions/remote/team.ts +++ b/app/actions/remote/team.ts @@ -229,7 +229,7 @@ export async function fetchMyTeam(serverUrl: string, teamId: string, fetchOnly = } } -export const fetchAllTeams = async (serverUrl: string, page = 0, perPage = PER_PAGE_DEFAULT): Promise<{teams?: Team[]; error?: any}> => { +export const fetchAllTeams = async (serverUrl: string, page = 0, perPage = PER_PAGE_DEFAULT): Promise<{teams?: Team[]; error?: unknown}> => { try { const client = NetworkManager.getClient(serverUrl); const teams = await client.getTeams(page, perPage); @@ -475,3 +475,12 @@ export async function getTeamMembersByIds(serverUrl: string, teamId: string, use return {error}; } } + +export const buildTeamIconUrl = (serverUrl: string, teamId: string, timestamp = 0) => { + try { + const client = NetworkManager.getClient(serverUrl); + return client.getTeamIconUrl(teamId, timestamp); + } catch (error) { + return ''; + } +}; diff --git a/app/actions/remote/user.ts b/app/actions/remote/user.ts index 02fccc7a9..a3144f4bc 100644 --- a/app/actions/remote/user.ts +++ b/app/actions/remote/user.ts @@ -19,7 +19,7 @@ import {getCurrentUser, prepareUsers, queryAllUsers, queryUsersById, queryUsersB import {getFullErrorMessage} from '@utils/errors'; import {logDebug} from '@utils/log'; import {getDeviceTimezone} from '@utils/timezone'; -import {getUserTimezoneProps, removeUserFromList} from '@utils/user'; +import {getLastPictureUpdate, getUserTimezoneProps, removeUserFromList} from '@utils/user'; import {fetchGroupsByNames} from './groups'; import {forceLogoutIfNecessary} from './session'; @@ -796,6 +796,11 @@ export const buildProfileImageUrl = (serverUrl: string, userId: string, timestam } }; +export const buildProfileImageUrlFromUser = (serverUrl: string, user: UserModel | UserProfile) => { + const lastPictureUpdate = getLastPictureUpdate(user); + return buildProfileImageUrl(serverUrl, user.id, lastPictureUpdate); +}; + export const autoUpdateTimezone = async (serverUrl: string) => { let database; try { diff --git a/app/components/post_list/post/avatar/avatar.tsx b/app/components/post_list/post/avatar/avatar.tsx index 5a1e403f4..cc1f7b1cb 100644 --- a/app/components/post_list/post/avatar/avatar.tsx +++ b/app/components/post_list/post/avatar/avatar.tsx @@ -6,16 +6,15 @@ import {useIntl} from 'react-intl'; import {Keyboard, Platform, StyleSheet, TouchableOpacity, View} from 'react-native'; import FastImage from 'react-native-fast-image'; +import {buildAbsoluteUrl} from '@actions/remote/file'; import CompassIcon from '@components/compass_icon'; import ProfilePicture from '@components/profile_picture'; import {Screens, View as ViewConstant} from '@constants'; import {useServerUrl} from '@context/server'; import {useTheme} from '@context/theme'; -import NetworkManager from '@managers/network_manager'; import {openAsBottomSheet} from '@screens/navigation'; import {preventDoubleTap} from '@utils/tap'; -import type {Client} from '@client/rest'; import type PostModel from '@typings/database/models/servers/post'; import type UserModel from '@typings/database/models/servers/user'; @@ -37,12 +36,6 @@ const Avatar = ({author, enablePostIconOverride, isAutoReponse, location, post}: const intl = useIntl(); const theme = useTheme(); const serverUrl = useServerUrl(); - let client: Client | undefined; - try { - client = NetworkManager.getClient(serverUrl); - } catch { - // do nothing, client is not set - } const fromWebHook = post.props?.from_webhook === 'true'; const iconOverride = enablePostIconOverride && post.props?.use_user_icon !== 'true'; @@ -51,7 +44,7 @@ const Avatar = ({author, enablePostIconOverride, isAutoReponse, location, post}: const frameSize = ViewConstant.PROFILE_PICTURE_SIZE; const pictureSize = isEmoji ? ViewConstant.PROFILE_PICTURE_EMOJI_SIZE : ViewConstant.PROFILE_PICTURE_SIZE; const borderRadius = isEmoji ? 0 : ViewConstant.PROFILE_PICTURE_SIZE / 2; - const overrideIconUrl = client?.getAbsoluteUrl(post.props?.override_icon_url); + const overrideIconUrl = buildAbsoluteUrl(serverUrl, post.props?.override_icon_url); let iconComponent: ReactNode; if (overrideIconUrl) { diff --git a/app/components/profile_picture/image.tsx b/app/components/profile_picture/image.tsx index 3895843d8..d3cb77a3f 100644 --- a/app/components/profile_picture/image.tsx +++ b/app/components/profile_picture/image.tsx @@ -6,14 +6,14 @@ import {Image as RNImage} from 'react-native'; import FastImage, {type Source} from 'react-native-fast-image'; import Animated from 'react-native-reanimated'; +import {buildAbsoluteUrl} from '@actions/remote/file'; +import {buildProfileImageUrlFromUser} from '@actions/remote/user'; import CompassIcon from '@components/compass_icon'; import {ACCOUNT_OUTLINE_IMAGE} from '@constants/profile'; import {useServerUrl} from '@context/server'; import {useTheme} from '@context/theme'; -import NetworkManager from '@managers/network_manager'; import {changeOpacity, makeStyleSheetFromTheme} from '@utils/theme'; -import type {Client} from '@client/rest'; import type UserModel from '@typings/database/models/servers/user'; type Props = { @@ -49,6 +49,15 @@ const Image = ({author, forwardRef, iconSize, size, source, url}: Props) => { width: size, }), [size]); + const imgSource = useMemo(() => { + if (!author || typeof source === 'string') { + return undefined; + } + + const pictureUrl = buildProfileImageUrlFromUser(serverUrl, author); + return source ?? {uri: buildAbsoluteUrl(serverUrl, pictureUrl)}; + }, [author, serverUrl, source]); + if (typeof source === 'string') { return ( { ); } - let client: Client | undefined; - - try { - client = NetworkManager.getClient(serverUrl); - } catch { - // handle below that the client is not set + if (imgSource?.uri?.startsWith('file://')) { + return ( + + ); } - if (author && client) { - let lastPictureUpdate = 0; - const isBot = ('isBot' in author) ? author.isBot : author.is_bot; - if (isBot) { - lastPictureUpdate = ('isBot' in author) ? author.props?.bot_last_icon_update : author.bot_last_icon_update || 0; - } else { - lastPictureUpdate = ('lastPictureUpdate' in author) ? author.lastPictureUpdate : author.last_picture_update || 0; - } - - const pictureUrl = client.getProfilePictureUrl(author.id, lastPictureUpdate); - const imgSource = source ?? {uri: `${serverUrl}${pictureUrl}`}; - if (imgSource.uri?.startsWith('file://')) { - return ( - - ); - } + if (imgSource) { return ( { /> ); } + return ( ); diff --git a/app/components/user_list/__snapshots__/index.test.tsx.snap b/app/components/user_list/__snapshots__/index.test.tsx.snap index e6c36f589..7a29c95b5 100644 --- a/app/components/user_list/__snapshots__/index.test.tsx.snap +++ b/app/components/user_list/__snapshots__/index.test.tsx.snap @@ -9,43 +9,7 @@ exports[`components/channel_list_row should show no results 1`] = ` "flexGrow": 1, } } - data={ - [ - { - "data": [ - { - "auth_service": "", - "create_at": 1111, - "delete_at": 0, - "email": "john@doe.com", - "first_name": "", - "id": "1", - "last_name": "", - "locale": "", - "nickname": "", - "notify_props": { - "channel": "true", - "comments": "never", - "desktop": "mention", - "desktop_sound": "true", - "email": "true", - "first_name": "true", - "highlight_keys": "", - "mention_keys": "", - "push": "mention", - "push_status": "away", - }, - "position": "", - "roles": "", - "update_at": 1111, - "username": "johndoe", - }, - ], - "first": true, - "id": "J", - }, - ] - } + data={[]} getItem={[Function]} getItemCount={[Function]} initialNumToRender={15} @@ -54,7 +18,6 @@ exports[`components/channel_list_row should show no results 1`] = ` keyboardShouldPersistTaps="always" maxToRenderPerBatch={16} onContentSizeChange={[Function]} - onEndReached={[Function]} onLayout={[Function]} onMomentumScrollBegin={[Function]} onMomentumScrollEnd={[Function]} @@ -71,201 +34,194 @@ exports[`components/channel_list_row should show no results 1`] = ` "flex": 1, } } - testID="UserListRow.section_list" + testID="UserListRow.flat_list" + viewabilityConfigCallbackPairs={[]} > - - - J - - - - - - - - - + - - - - + - johndoe - - + - + - - - - - + } + propList={ + [ + "fill", + ] + } + /> + + + + No matches found for “some term” + + + Check the spelling or try another search. + - @@ -309,6 +265,7 @@ exports[`components/channel_list_row should show results and tutorial 1`] = ` "first_name": "", "id": "1", "last_name": "", + "last_picture_update": 123456, "locale": "", "nickname": "", "notify_props": { @@ -480,15 +437,40 @@ exports[`components/channel_list_row should show results and tutorial 1`] = ` } testID="create_direct_message.user_list.user_item.1.1.profile_picture" > - + > + + - + > + + - + > + + - + > + + { + const original = jest.requireActual('@managers/network_manager'); + return { + ...original, + getClient: () => { + return mockClient; + }, + }; +}); + describe('components/channel_list_row', () => { let database: Database; const user: UserProfile = { @@ -26,6 +39,7 @@ describe('components/channel_list_row', () => { position: '', roles: '', locale: '', + last_picture_update: 123456, notify_props: { channel: 'true', comments: 'never', @@ -54,6 +68,7 @@ describe('components/channel_list_row', () => { position: '', roles: '', locale: '', + last_picture_update: 123456, notify_props: { channel: 'true', comments: 'never', @@ -68,15 +83,26 @@ describe('components/channel_list_row', () => { }, }; + const originalResolveAssetSource = Image.resolveAssetSource; + beforeAll(async () => { const server = await TestHelper.setupServerDatabase(); database = server.database; + + // This is needed to properly populate the URLs until + // https://github.com/facebook/react-native/pull/43497 + // gets into React Native Jest code. + Image.resolveAssetSource = jest.fn().mockImplementation((source) => source); + }); + + afterAll(() => { + Image.resolveAssetSource = originalResolveAssetSource; }); it('should show no results', () => { const wrapper = renderWithEverything( { @@ -87,6 +113,7 @@ describe('components/channel_list_row', () => { }} loading={true} selectedIds={{}} + term={'some term'} showNoResults={true} tutorialWatched={true} />, diff --git a/app/screens/channel/channel_post_list/intro/direct_channel/group/group.tsx b/app/screens/channel/channel_post_list/intro/direct_channel/group/group.tsx index d4190d3ae..add3bc7a7 100644 --- a/app/screens/channel/channel_post_list/intro/direct_channel/group/group.tsx +++ b/app/screens/channel/channel_post_list/intro/direct_channel/group/group.tsx @@ -6,11 +6,11 @@ import React from 'react'; import {View} from 'react-native'; import FastImage from 'react-native-fast-image'; +import {buildAbsoluteUrl} from '@actions/remote/file'; +import {buildProfileImageUrlFromUser} from '@actions/remote/user'; import {useServerUrl} from '@context/server'; -import NetworkManager from '@managers/network_manager'; import {makeStyleSheetFromTheme} from '@utils/theme'; -import type {Client} from '@client/rest'; import type UserModel from '@typings/database/models/servers/user'; type Props = { @@ -37,23 +37,15 @@ const Group = ({theme, users}: Props) => { const serverUrl = useServerUrl(); const styles = getStyleSheet(theme); - let client: Client | undefined; - - try { - client = NetworkManager.getClient(serverUrl); - } catch { - return null; - } - const rows = chunk(users, 5); const groups = rows.map((c, k) => { const group = c.map((u, i) => { - const pictureUrl = client!.getProfilePictureUrl(u.id, u.lastPictureUpdate); + const pictureUrl = buildProfileImageUrlFromUser(serverUrl, u); return ( ); }); diff --git a/app/screens/channel_info/title/group_message/avatars/avatars.tsx b/app/screens/channel_info/title/group_message/avatars/avatars.tsx index cbf48fd98..4fcaca91a 100644 --- a/app/screens/channel_info/title/group_message/avatars/avatars.tsx +++ b/app/screens/channel_info/title/group_message/avatars/avatars.tsx @@ -5,12 +5,12 @@ import React from 'react'; import {View} from 'react-native'; import FastImage from 'react-native-fast-image'; +import {buildAbsoluteUrl} from '@actions/remote/file'; +import {buildProfileImageUrlFromUser} from '@actions/remote/user'; import {useServerUrl} from '@context/server'; import {useTheme} from '@context/theme'; -import NetworkManager from '@managers/network_manager'; import {makeStyleSheetFromTheme} from '@utils/theme'; -import type {Client} from '@client/rest'; import type UserModel from '@typings/database/models/servers/user'; type Props = { @@ -37,21 +37,13 @@ const GroupAvatars = ({users}: Props) => { const theme = useTheme(); const styles = getStyleSheet(theme); - let client: Client | undefined; - - try { - client = NetworkManager.getClient(serverUrl); - } catch { - return null; - } - const group = users.map((u, i) => { - const pictureUrl = client!.getProfilePictureUrl(u.id, u.lastPictureUpdate); + const pictureUrl = buildProfileImageUrlFromUser(serverUrl, u); return ( ); }); diff --git a/app/screens/edit_profile/components/edit_profile_picture.tsx b/app/screens/edit_profile/components/edit_profile_picture.tsx index 2f8726910..a4f602006 100644 --- a/app/screens/edit_profile/components/edit_profile_picture.tsx +++ b/app/screens/edit_profile/components/edit_profile_picture.tsx @@ -4,17 +4,16 @@ import React, {useCallback, useMemo, useState} from 'react'; import {Platform, View} from 'react-native'; +import {buildProfileImageUrlFromUser} from '@actions/remote/user'; import ProfileImage from '@components/profile_picture'; import {ACCOUNT_OUTLINE_IMAGE} from '@constants/profile'; import {useServerUrl} from '@context/server'; import {useTheme} from '@context/theme'; import useDidUpdate from '@hooks/did_update'; -import NetworkManager from '@managers/network_manager'; import {changeOpacity, makeStyleSheetFromTheme} from '@utils/theme'; import ProfileImagePicker from './profile_image_picker'; -import type {Client} from '@client/rest'; import type UserModel from '@typings/database/models/servers/user'; type ChangeProfilePictureProps = { @@ -47,24 +46,18 @@ const EditProfilePicture = ({user, onUpdateProfilePicture}: ChangeProfilePicture const serverUrl = useServerUrl(); const theme = useTheme(); - let client: Client | undefined; - - try { - client = NetworkManager.getClient(serverUrl); - } catch { - // does nothing - } - - const [pictureUrl, setPictureUrl] = useState(client?.getProfilePictureUrl(user.id, user.lastPictureUpdate)); + const [pictureUrl, setPictureUrl] = useState(() => { + return buildProfileImageUrlFromUser(serverUrl, user); + }); const styles = getStyleSheet(theme); useDidUpdate(() => { - const url = user.id && client ? client.getProfilePictureUrl(user.id, user.lastPictureUpdate) : undefined; + const url = user.id ? buildProfileImageUrlFromUser(serverUrl, user) : undefined; if (url !== pictureUrl) { setPictureUrl(url); } - }, [user.id, user.lastPictureUpdate]); + }, [user]); const handleProfileImage = useCallback((images?: FileInfo[]) => { let isRemoved = true; diff --git a/app/screens/edit_profile/components/profile_image_picker.tsx b/app/screens/edit_profile/components/profile_image_picker.tsx index 815e116d4..6c159b59e 100644 --- a/app/screens/edit_profile/components/profile_image_picker.tsx +++ b/app/screens/edit_profile/components/profile_image_picker.tsx @@ -6,13 +6,13 @@ import {useIntl} from 'react-intl'; import {TouchableOpacity} from 'react-native'; import {useSafeAreaInsets} from 'react-native-safe-area-context'; +import {buildProfileImageUrlFromUser} from '@actions/remote/user'; import CompassIcon from '@components/compass_icon'; import FormattedText from '@components/formatted_text'; import {ITEM_HEIGHT} from '@components/slide_up_panel_item'; import {useServerUrl} from '@context/server'; import {useTheme} from '@context/theme'; import {useIsTablet} from '@hooks/device'; -import NetworkManager from '@managers/network_manager'; import {TITLE_HEIGHT} from '@screens/bottom_sheet/content'; import PanelItem from '@screens/edit_profile/components/panel_item'; import {bottomSheet} from '@screens/navigation'; @@ -22,7 +22,6 @@ import {preventDoubleTap} from '@utils/tap'; import {changeOpacity, makeStyleSheetFromTheme} from '@utils/theme'; import {typography} from '@utils/typography'; -import type {Client} from '@client/rest'; import type UserModel from '@typings/database/models/servers/user'; const hitSlop = {top: 100, bottom: 20, right: 20, left: 100}; @@ -57,21 +56,9 @@ type ImagePickerProps = { }; const hasPictureUrl = (user: UserModel, serverUrl: string) => { - const {id, lastPictureUpdate} = user; - - let client: Client | undefined; - let profileImageUrl: string | undefined; - - try { - client = NetworkManager.getClient(serverUrl); - profileImageUrl = client.getProfilePictureUrl(id, lastPictureUpdate); - } catch { - return false; - } - // Check if image url includes query string for timestamp. If so, // it means the image has been updated from the default, i.e. '.../image?_=1544159746868' - return Boolean(profileImageUrl?.includes('image?_')); + return buildProfileImageUrlFromUser(serverUrl, user).includes('image?_'); }; const ProfileImagePicker = ({ diff --git a/app/screens/gallery/footer/avatar/index.tsx b/app/screens/gallery/footer/avatar/index.tsx index 90001cef4..646875216 100644 --- a/app/screens/gallery/footer/avatar/index.tsx +++ b/app/screens/gallery/footer/avatar/index.tsx @@ -1,18 +1,20 @@ // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. // See LICENSE.txt for license information. -import React, {useMemo} from 'react'; +import React from 'react'; import {StyleSheet, View} from 'react-native'; import FastImage from 'react-native-fast-image'; import {buildAbsoluteUrl} from '@actions/remote/file'; -import {buildProfileImageUrl} from '@actions/remote/user'; +import {buildProfileImageUrlFromUser} from '@actions/remote/user'; +import {useServerUrl} from '@app/context/server'; import CompassIcon from '@components/compass_icon'; -import {useServerUrl} from '@context/server'; import {changeOpacity} from '@utils/theme'; +import type UserModel from '@typings/database/models/servers/user'; + type Props = { - authorId?: string; + author?: UserModel; overrideIconUrl?: string; } @@ -32,28 +34,22 @@ const styles = StyleSheet.create({ }, }); -const Avatar = ({authorId, overrideIconUrl}: Props) => { +const Avatar = ({ + author, + overrideIconUrl, +}: Props) => { const serverUrl = useServerUrl(); - const avatarUri = useMemo(() => { - try { - if (overrideIconUrl) { - return buildAbsoluteUrl(serverUrl, overrideIconUrl); - } else if (authorId) { - const pictureUrl = buildProfileImageUrl(serverUrl, authorId); - return `${serverUrl}${pictureUrl}`; - } - return undefined; - } catch { - return undefined; - } - }, [serverUrl, authorId, overrideIconUrl]); + let uri = overrideIconUrl; + if (!uri && author) { + uri = buildProfileImageUrlFromUser(serverUrl, author); + } let picture; - if (avatarUri) { + if (uri) { picture = ( ); diff --git a/app/screens/gallery/footer/footer.tsx b/app/screens/gallery/footer/footer.tsx index 09ce90dbc..9969468a8 100644 --- a/app/screens/gallery/footer/footer.tsx +++ b/app/screens/gallery/footer/footer.tsx @@ -125,10 +125,10 @@ const Footer = ({ {item.type !== 'avatar' && - + }
{ - let client: Client | undefined; - try { - client = NetworkManager.getClient(serverUrl); - } catch { - // do nothing, client is not set - } - let icon; - if (client && fromWebhook && !useUserIcon && enablePostIconOverride) { + if (fromWebhook && !useUserIcon && enablePostIconOverride) { if (overrideIconUrl) { - const source: Source = {uri: client.getAbsoluteUrl(overrideIconUrl)}; + const source: Source = {uri: buildAbsoluteUrl(serverUrl, overrideIconUrl)}; icon = ( ); } - } else if (author && client) { - const pictureUrl = client.getProfilePictureUrl(author.id, author.lastPictureUpdate); + } else if (author) { + const pictureUrl = buildProfileImageUrlFromUser(serverUrl, author); icon = ( ); } else { diff --git a/app/screens/user_profile/title/index.tsx b/app/screens/user_profile/title/index.tsx index 41ea8f321..fd78b72b9 100644 --- a/app/screens/user_profile/title/index.tsx +++ b/app/screens/user_profile/title/index.tsx @@ -5,12 +5,13 @@ import {useIntl} from 'react-intl'; import {Text, TouchableOpacity, View} from 'react-native'; import Animated from 'react-native-reanimated'; +import {buildAbsoluteUrl} from '@actions/remote/file'; +import {buildProfileImageUrlFromUser} from '@actions/remote/user'; import {GalleryInit} from '@context/gallery'; import {useServerUrl} from '@context/server'; import {useTheme} from '@context/theme'; import {useIsTablet} from '@hooks/device'; import {useGalleryItem} from '@hooks/gallery'; -import NetworkManager from '@managers/network_manager'; import {openGalleryAtIndex} from '@utils/gallery'; import {changeOpacity, makeStyleSheetFromTheme} from '@utils/theme'; import {typography} from '@utils/typography'; @@ -93,14 +94,8 @@ const UserProfileTitle = ({ 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 - } + const pictureUrl = buildProfileImageUrlFromUser(serverUrl, user); + imageUrl = buildAbsoluteUrl(serverUrl, pictureUrl); } if (imageUrl) { diff --git a/app/utils/user/index.ts b/app/utils/user/index.ts index 958c3e0c1..a97173071 100644 --- a/app/utils/user/index.ts +++ b/app/utils/user/index.ts @@ -380,3 +380,11 @@ export const getEmailIntervalTexts = (interval: string) => { }; return intervalTexts[interval]; }; + +export const getLastPictureUpdate = (user: UserModel | UserProfile) => { + if ('isBot' in user) { + return user.isBot ? user.props?.bot_last_icon_update : user.lastPictureUpdate || 0; + } + + return user.is_bot ? user.bot_last_icon_update : user.last_picture_update || 0; +}; diff --git a/share_extension/components/channel_item/avatar/avatar.tsx b/share_extension/components/channel_item/avatar/avatar.tsx index fa3f8ec19..bb4506367 100644 --- a/share_extension/components/channel_item/avatar/avatar.tsx +++ b/share_extension/components/channel_item/avatar/avatar.tsx @@ -5,9 +5,10 @@ import React from 'react'; import {View} from 'react-native'; import FastImage from 'react-native-fast-image'; +import {buildAbsoluteUrl} from '@actions/remote/file'; +import {buildProfileImageUrlFromUser} from '@actions/remote/user'; import CompassIcon from '@components/compass_icon'; import {ACCOUNT_OUTLINE_IMAGE} from '@constants/profile'; -import NetworkManager from '@managers/network_manager'; import {useShareExtensionServerUrl} from '@share/state'; import {changeOpacity, makeStyleSheetFromTheme} from '@utils/theme'; @@ -34,7 +35,6 @@ const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => ({ const Avatar = ({author, theme}: Props) => { const serverUrl = useShareExtensionServerUrl(); const style = getStyleSheet(theme); - const isBot = author?.isBot || false; let pictureUrl = ''; if (author?.deleteAt) { @@ -48,24 +48,12 @@ const Avatar = ({author, theme}: Props) => { } if (author && serverUrl) { - try { - const client = NetworkManager.getClient(serverUrl); - let lastPictureUpdate = 0; - if (isBot) { - lastPictureUpdate = author?.props?.bot_last_icon_update || 0; - } else { - lastPictureUpdate = author?.lastPictureUpdate || 0; - } - - pictureUrl = client.getProfilePictureUrl(author.id, lastPictureUpdate); - } catch { - // handle below that the client is not set - } + pictureUrl = buildProfileImageUrlFromUser(serverUrl, author); } let icon; - if (pictureUrl) { - const imgSource = {uri: `${serverUrl}${pictureUrl}`}; + if (pictureUrl && serverUrl) { + const imgSource = {uri: buildAbsoluteUrl(serverUrl, pictureUrl)}; icon = (