From 3b89001e7afd3f667d9ef63a20d3b5c01da9a5da Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Musia=C5=82?= Date: Tue, 14 Oct 2025 10:05:35 +0200 Subject: [PATCH 01/21] add default avatar selection wip --- src/components/AvatarSelector.tsx | 15 +- .../API/parameters/UpdateUserAvatarParams.ts | 2 +- src/libs/UserUtils.ts | 19 ++- src/libs/actions/PersonalDetails.ts | 5 +- .../settings/Profile/Avatar/AvatarPage.tsx | 153 ++++++++++++------ src/styles/index.ts | 2 +- 6 files changed, 136 insertions(+), 60 deletions(-) diff --git a/src/components/AvatarSelector.tsx b/src/components/AvatarSelector.tsx index 1137f9f4f0e9..ea47a10ae52e 100644 --- a/src/components/AvatarSelector.tsx +++ b/src/components/AvatarSelector.tsx @@ -1,4 +1,4 @@ -import React, {useState} from 'react'; +import React, {useEffect, useState} from 'react'; import {View} from 'react-native'; import useStyleUtils from '@hooks/useStyleUtils'; import useTheme from '@hooks/useTheme'; @@ -35,6 +35,13 @@ function AvatarSelector({selectedID, onSelect, label, size = CONST.AVATAR_SIZE.M const StyleUtils = useStyleUtils(); const [selected, setSelected] = useState(selectedID); + useEffect(() => { + if (selectedID === selected) { + return; + } + setSelected(selectedID); + }, [selected, selectedID]); + const handleSelect = (id: keyof typeof ALL_CUSTOM_AVATARS) => { setSelected(id); onSelect(id); @@ -43,12 +50,12 @@ function AvatarSelector({selectedID, onSelect, label, size = CONST.AVATAR_SIZE.M return ( <> {!!label && ( - + {label} )} - {ALL_CUSTOM_AVATARS_ORDERED.map(({id, url}) => { + {ALL_CUSTOM_AVATARS_ORDERED.map(({id, local}) => { const isSelected = selected === id; return ( @@ -62,7 +69,7 @@ function AvatarSelector({selectedID, onSelect, label, size = CONST.AVATAR_SIZE.M > ) { +function updateAvatar( + file: File | CustomRNImageManipulatorResult | {uri: string; name: string}, + currentUserPersonalDetails: Pick, +) { if (!currentUserPersonalDetails.accountID) { return; } diff --git a/src/pages/settings/Profile/Avatar/AvatarPage.tsx b/src/pages/settings/Profile/Avatar/AvatarPage.tsx index 10e91792b4eb..38673d53b386 100644 --- a/src/pages/settings/Profile/Avatar/AvatarPage.tsx +++ b/src/pages/settings/Profile/Avatar/AvatarPage.tsx @@ -3,10 +3,12 @@ import {View} from 'react-native'; import AttachmentPicker from '@components/AttachmentPicker'; import Avatar from '@components/Avatar'; import AvatarCropModal from '@components/AvatarCropModal/AvatarCropModal'; +import AvatarSelector from '@components/AvatarSelector'; import Button from '@components/Button'; import ButtonWithDropdownMenu from '@components/ButtonWithDropdownMenu'; import type {DropdownOption} from '@components/ButtonWithDropdownMenu/types'; import DotIndicatorMessage from '@components/DotIndicatorMessage'; +import FixedFooter from '@components/FixedFooter'; import HeaderWithBackButton from '@components/HeaderWithBackButton'; import * as Expensicons from '@components/Icon/Expensicons'; import ScreenWrapper from '@components/ScreenWrapper'; @@ -14,12 +16,15 @@ import ScrollView from '@components/ScrollView'; import useCurrentUserPersonalDetails from '@hooks/useCurrentUserPersonalDetails'; import useLocalize from '@hooks/useLocalize'; import useThemeStyles from '@hooks/useThemeStyles'; +import {ALL_CUSTOM_AVATARS} from '@libs/Avatars/CustomAvatarCatalog'; +import type {CustomAvatarID} from '@libs/Avatars/CustomAvatarCatalog'; import {validateAvatarImage} from '@libs/AvatarUtils'; import {isSafari} from '@libs/Browser'; import type {CustomRNImageManipulatorResult} from '@libs/cropOrRotateImage/types'; import Navigation from '@libs/Navigation/Navigation'; -import {isDefaultAvatar} from '@libs/UserUtils'; +import {getDefaultAvatarName, isDefaultAvatar} from '@libs/UserUtils'; import {isValidAccountRoute} from '@libs/ValidationUtils'; +import DiscardChangesConfirmation from '@pages/iou/request/step/DiscardChangesConfirmation'; import type {FileObject} from '@pages/media/AttachmentModalScreen/types'; import {deleteAvatar, openPublicProfilePage, updateAvatar} from '@userActions/PersonalDetails'; import CONST from '@src/CONST'; @@ -47,13 +52,23 @@ function ProfileAvatar() { name: '', type: '', }); + + const [selected, setSelected] = useState(); + const avatarStyle = [styles.avatarXLarge, styles.alignSelfStart, styles.alignSelfCenter]; const currentUserPersonalDetails = useCurrentUserPersonalDetails(); const accountID = currentUserPersonalDetails?.accountID ?? CONST.DEFAULT_NUMBER_ID; - const avatarURL = currentUserPersonalDetails?.avatar ?? ''; + const avatarURL = selected ? ALL_CUSTOM_AVATARS[selected as CustomAvatarID]?.url : (currentUserPersonalDetails?.avatar ?? ''); const isUsingDefaultAvatar = isDefaultAvatar(currentUserPersonalDetails?.avatar ?? ''); + useEffect(() => { + if (!isUsingDefaultAvatar) { + return; + } + setSelected(getDefaultAvatarName(accountID)); + }, [accountID, isUsingDefaultAvatar]); + const setError = (error: TranslationPaths | null, phraseParam: Record) => { setErrorData({ validationError: error, @@ -169,59 +184,66 @@ function ProfileAvatar() { shouldShowOfflineIndicatorInWideScreen > + + + + {({openPicker}) => { + if (isUsingDefaultAvatar) { + return ( +