From 1cfcffd79eb8298e628c9bb9b71570e1b1269c6a Mon Sep 17 00:00:00 2001 From: Samuel Newman Date: Tue, 29 Oct 2024 20:55:30 +0000 Subject: temp revert to old modal (#6005) --- .../Profile/Header/ProfileHeaderLabeler.tsx | 16 +- .../Profile/Header/ProfileHeaderStandard.tsx | 16 +- src/state/modals/index.tsx | 9 + src/view/com/modals/EditProfile.tsx | 310 +++++++++++++++++++++ src/view/com/modals/Modal.tsx | 6 +- src/view/com/modals/Modal.web.tsx | 5 +- 6 files changed, 354 insertions(+), 8 deletions(-) create mode 100644 src/view/com/modals/EditProfile.tsx (limited to 'src') diff --git a/src/screens/Profile/Header/ProfileHeaderLabeler.tsx b/src/screens/Profile/Header/ProfileHeaderLabeler.tsx index 37a5985cf..ca0cb1e62 100644 --- a/src/screens/Profile/Header/ProfileHeaderLabeler.tsx +++ b/src/screens/Profile/Header/ProfileHeaderLabeler.tsx @@ -15,9 +15,10 @@ import {MAX_LABELERS} from '#/lib/constants' import {useHaptics} from '#/lib/haptics' import {isAppLabeler} from '#/lib/moderation' import {logger} from '#/logger' -import {isIOS} from '#/platform/detection' +import {isIOS, isWeb} from '#/platform/detection' import {useProfileShadow} from '#/state/cache/profile-shadow' import {Shadow} from '#/state/cache/types' +import {useModalControls} from '#/state/modals' import {useLabelerSubscriptionMutation} from '#/state/queries/labeler' import {useLikeMutation, useUnlikeMutation} from '#/state/queries/like' import {usePreferencesQuery} from '#/state/queries/preferences' @@ -116,10 +117,19 @@ let ProfileHeaderLabeler = ({ } }, [labeler, playHaptic, likeUri, unlikeMod, likeMod, _]) + const {openModal} = useModalControls() const editProfileControl = useDialogControl() const onPressEditProfile = React.useCallback(() => { - editProfileControl.open() - }, [editProfileControl]) + if (isWeb) { + // temp, while we figure out the nested dialog bug + openModal({ + name: 'edit-profile', + profile, + }) + } else { + editProfileControl.open() + } + }, [editProfileControl, openModal, profile]) const onPressSubscribe = React.useCallback( () => diff --git a/src/screens/Profile/Header/ProfileHeaderStandard.tsx b/src/screens/Profile/Header/ProfileHeaderStandard.tsx index 81aadcc64..340621398 100644 --- a/src/screens/Profile/Header/ProfileHeaderStandard.tsx +++ b/src/screens/Profile/Header/ProfileHeaderStandard.tsx @@ -11,9 +11,10 @@ import {useLingui} from '@lingui/react' import {sanitizeDisplayName} from '#/lib/strings/display-names' import {logger} from '#/logger' -import {isIOS} from '#/platform/detection' +import {isIOS, isWeb} from '#/platform/detection' import {useProfileShadow} from '#/state/cache/profile-shadow' import {Shadow} from '#/state/cache/types' +import {useModalControls} from '#/state/modals' import { useProfileBlockMutationQueue, useProfileFollowMutationQueue, @@ -74,10 +75,19 @@ let ProfileHeaderStandard = ({ profile.viewer?.blockedBy || profile.viewer?.blockingByList + const {openModal} = useModalControls() const editProfileControl = useDialogControl() const onPressEditProfile = React.useCallback(() => { - editProfileControl.open() - }, [editProfileControl]) + if (isWeb) { + // temp, while we figure out the nested dialog bug + openModal({ + name: 'edit-profile', + profile, + }) + } else { + editProfileControl.open() + } + }, [editProfileControl, openModal, profile]) const onPressFollow = () => { requireAuth(async () => { diff --git a/src/state/modals/index.tsx b/src/state/modals/index.tsx index 05e0c53f6..78f476d52 100644 --- a/src/state/modals/index.tsx +++ b/src/state/modals/index.tsx @@ -4,6 +4,12 @@ import {AppBskyActorDefs, AppBskyGraphDefs} from '@atproto/api' import {useNonReactiveCallback} from '#/lib/hooks/useNonReactiveCallback' +export interface EditProfileModal { + name: 'edit-profile' + profile: AppBskyActorDefs.ProfileViewDetailed + onUpdate?: () => void +} + export interface CreateOrEditListModal { name: 'create-or-edit-list' purpose?: string @@ -102,6 +108,9 @@ export type Modal = | ChangeEmailModal | ChangePasswordModal + // Temp + | EditProfileModal + // Curation | ContentLanguagesSettingsModal | PostLanguagesSettingsModal diff --git a/src/view/com/modals/EditProfile.tsx b/src/view/com/modals/EditProfile.tsx new file mode 100644 index 000000000..1e94f483e --- /dev/null +++ b/src/view/com/modals/EditProfile.tsx @@ -0,0 +1,310 @@ +import React, {useCallback, useState} from 'react' +import { + ActivityIndicator, + KeyboardAvoidingView, + ScrollView, + StyleSheet, + TextInput, + TouchableOpacity, + View, +} from 'react-native' +import {Image as RNImage} from 'react-native-image-crop-picker' +import Animated, {FadeOut} from 'react-native-reanimated' +import {LinearGradient} from 'expo-linear-gradient' +import {AppBskyActorDefs} from '@atproto/api' +import {msg, Trans} from '@lingui/macro' +import {useLingui} from '@lingui/react' + +import {MAX_DESCRIPTION, MAX_DISPLAY_NAME} from '#/lib/constants' +import {usePalette} from '#/lib/hooks/usePalette' +import {compressIfNeeded} from '#/lib/media/manip' +import {cleanError} from '#/lib/strings/errors' +import {enforceLen} from '#/lib/strings/helpers' +import {colors, gradients, s} from '#/lib/styles' +import {useTheme} from '#/lib/ThemeContext' +import {logger} from '#/logger' +import {isWeb} from '#/platform/detection' +import {useModalControls} from '#/state/modals' +import {useProfileUpdateMutation} from '#/state/queries/profile' +import {Text} from '#/view/com/util/text/Text' +import * as Toast from '#/view/com/util/Toast' +import {EditableUserAvatar} from '#/view/com/util/UserAvatar' +import {UserBanner} from '#/view/com/util/UserBanner' +import {ErrorMessage} from '../util/error/ErrorMessage' + +const AnimatedTouchableOpacity = + Animated.createAnimatedComponent(TouchableOpacity) + +export const snapPoints = ['fullscreen'] + +export function Component({ + profile, + onUpdate, +}: { + profile: AppBskyActorDefs.ProfileViewDetailed + onUpdate?: () => void +}) { + const pal = usePalette('default') + const theme = useTheme() + const {_} = useLingui() + const {closeModal} = useModalControls() + const updateMutation = useProfileUpdateMutation() + const [imageError, setImageError] = useState('') + const [displayName, setDisplayName] = useState( + profile.displayName || '', + ) + const [description, setDescription] = useState( + profile.description || '', + ) + const [userBanner, setUserBanner] = useState( + profile.banner, + ) + const [userAvatar, setUserAvatar] = useState( + profile.avatar, + ) + const [newUserBanner, setNewUserBanner] = useState< + RNImage | undefined | null + >() + const [newUserAvatar, setNewUserAvatar] = useState< + RNImage | undefined | null + >() + const onPressCancel = () => { + closeModal() + } + const onSelectNewAvatar = useCallback( + async (img: RNImage | null) => { + setImageError('') + if (img === null) { + setNewUserAvatar(null) + setUserAvatar(null) + return + } + try { + const finalImg = await compressIfNeeded(img, 1000000) + setNewUserAvatar(finalImg) + setUserAvatar(finalImg.path) + } catch (e: any) { + setImageError(cleanError(e)) + } + }, + [setNewUserAvatar, setUserAvatar, setImageError], + ) + + const onSelectNewBanner = useCallback( + async (img: RNImage | null) => { + setImageError('') + if (!img) { + setNewUserBanner(null) + setUserBanner(null) + return + } + try { + const finalImg = await compressIfNeeded(img, 1000000) + setNewUserBanner(finalImg) + setUserBanner(finalImg.path) + } catch (e: any) { + setImageError(cleanError(e)) + } + }, + [setNewUserBanner, setUserBanner, setImageError], + ) + + const onPressSave = useCallback(async () => { + setImageError('') + try { + await updateMutation.mutateAsync({ + profile, + updates: { + displayName, + description, + }, + newUserAvatar, + newUserBanner, + }) + Toast.show(_(msg`Profile updated`)) + onUpdate?.() + closeModal() + } catch (e: any) { + logger.error('Failed to update user profile', {message: String(e)}) + } + }, [ + updateMutation, + profile, + onUpdate, + closeModal, + displayName, + description, + newUserAvatar, + newUserBanner, + setImageError, + _, + ]) + + return ( + + + + Edit my profile + + + + + + + + {updateMutation.isError && ( + + + + )} + {imageError !== '' && ( + + + + )} + + + + Display Name + + + setDisplayName(enforceLen(v, MAX_DISPLAY_NAME)) + } + accessible={true} + accessibilityLabel={_(msg`Display name`)} + accessibilityHint={_(msg`Edit your display name`)} + /> + + + + Description + + setDescription(enforceLen(v, MAX_DESCRIPTION))} + accessible={true} + accessibilityLabel={_(msg`Description`)} + accessibilityHint={_(msg`Edit your profile description`)} + /> + + {updateMutation.isPending ? ( + + + + ) : ( + + + + Save Changes + + + + )} + {!updateMutation.isPending && ( + + + + Cancel + + + + )} + + + + ) +} + +const styles = StyleSheet.create({ + title: { + textAlign: 'center', + fontWeight: '600', + fontSize: 24, + marginBottom: 18, + }, + label: { + fontWeight: '600', + paddingHorizontal: 4, + paddingBottom: 4, + marginTop: 20, + }, + form: { + paddingHorizontal: 14, + }, + textInput: { + borderWidth: 1, + borderRadius: 6, + paddingHorizontal: 14, + paddingVertical: 10, + fontSize: 16, + }, + textArea: { + borderWidth: 1, + borderRadius: 6, + paddingHorizontal: 12, + paddingTop: 10, + fontSize: 16, + height: 120, + textAlignVertical: 'top', + }, + btn: { + flexDirection: 'row', + alignItems: 'center', + justifyContent: 'center', + width: '100%', + borderRadius: 32, + padding: 10, + marginBottom: 10, + }, + avi: { + position: 'absolute', + top: 80, + left: 24, + width: 84, + height: 84, + borderWidth: 2, + borderRadius: 42, + }, + photos: { + marginBottom: 36, + marginHorizontal: -14, + }, + errorContainer: {marginTop: 20}, +}) diff --git a/src/view/com/modals/Modal.tsx b/src/view/com/modals/Modal.tsx index c2360742e..becb39ff3 100644 --- a/src/view/com/modals/Modal.tsx +++ b/src/view/com/modals/Modal.tsx @@ -13,6 +13,7 @@ import * as ChangeHandleModal from './ChangeHandle' import * as ChangePasswordModal from './ChangePassword' import * as CreateOrEditListModal from './CreateOrEditList' import * as DeleteAccountModal from './DeleteAccount' +import * as EditProfileModal from './EditProfile' import * as InAppBrowserConsentModal from './InAppBrowserConsent' import * as InviteCodesModal from './InviteCodes' import * as ContentLanguagesSettingsModal from './lang-settings/ContentLanguagesSettings' @@ -53,7 +54,10 @@ export function ModalsContainer() { let snapPoints: (string | number)[] = DEFAULT_SNAPPOINTS let element - if (activeModal?.name === 'create-or-edit-list') { + if (activeModal?.name === 'edit-profile') { + snapPoints = EditProfileModal.snapPoints + element = + } else if (activeModal?.name === 'create-or-edit-list') { snapPoints = CreateOrEditListModal.snapPoints element = } else if (activeModal?.name === 'user-add-remove-lists') { diff --git a/src/view/com/modals/Modal.web.tsx b/src/view/com/modals/Modal.web.tsx index 76b2811b1..46ced58d9 100644 --- a/src/view/com/modals/Modal.web.tsx +++ b/src/view/com/modals/Modal.web.tsx @@ -14,6 +14,7 @@ import * as ChangePasswordModal from './ChangePassword' import * as CreateOrEditListModal from './CreateOrEditList' import * as CropImageModal from './CropImage.web' import * as DeleteAccountModal from './DeleteAccount' +import * as EditProfileModal from './EditProfile' import * as InviteCodesModal from './InviteCodes' import * as ContentLanguagesSettingsModal from './lang-settings/ContentLanguagesSettings' import * as PostLanguagesSettingsModal from './lang-settings/PostLanguagesSettings' @@ -61,7 +62,9 @@ function Modal({modal}: {modal: ModalIface}) { } let element - if (modal.name === 'create-or-edit-list') { + if (modal.name === 'edit-profile') { + element = + } else if (modal.name === 'create-or-edit-list') { element = } else if (modal.name === 'user-add-remove-lists') { element = -- cgit 1.4.1