Convert profile edit avatar/banner dropdown menus to new menu (#3177)
* convert profile edit dropdown menu to new menu fix banner text add `showCancel` prop to menu outer banner dropdown to menu add Cancel button to menu replace user avatar dropdown with menu add StreamingLive icon add camera icon * remove export * use new camera icon * adjust icon colorzio/stable
parent
80cc1f18a2
commit
8123299192
|
@ -0,0 +1 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"><path fill="#000" fill-rule="evenodd" d="M8.371 3.89A2 2 0 0 1 10.035 3h3.93a2 2 0 0 1 1.664.89L17.035 6H20a2 2 0 0 1 2 2v11a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h2.965L8.37 3.89ZM12 9a3.5 3.5 0 1 0 0 7 3.5 3.5 0 0 0 0-7Z" clip-rule="evenodd"/></svg>
|
After Width: | Height: | Size: 330 B |
|
@ -0,0 +1 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"><path fill="#000" fill-rule="evenodd" d="M8.371 3.89A2 2 0 0 1 10.035 3h3.93a2 2 0 0 1 1.664.89L17.035 6H20a2 2 0 0 1 2 2v11a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h2.965L8.37 3.89ZM13.965 5h-3.93L8.63 7.11A2 2 0 0 1 6.965 8H4v11h16V8h-2.965a2 2 0 0 1-1.664-.89L13.965 5ZM12 11a2 2 0 1 0 0 4 2 2 0 0 0 0-4Zm-4 2a4 4 0 1 1 8 0 4 4 0 0 1-8 0Z" clip-rule="evenodd"/></svg>
|
After Width: | Height: | Size: 447 B |
|
@ -0,0 +1 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"><path fill="#000" fill-rule="evenodd" d="M4 4a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h16a2 2 0 0 0 2-2V6a2 2 0 0 0-2-2H4Zm8 12.5c1.253 0 2.197.609 2.674 1.5H9.326c.477-.891 1.42-1.5 2.674-1.5Zm0-2c2.404 0 4.235 1.475 4.822 3.5H20V6H4v12h3.178c.587-2.025 2.418-3.5 4.822-3.5Zm-1.25-3.75a1.25 1.25 0 1 1 2.5 0 1.25 1.25 0 0 1-2.5 0ZM12 7.5a3.25 3.25 0 1 0 0 6.5 3.25 3.25 0 0 0 0-6.5Zm5.75 2a1.25 1.25 0 1 0 0-2.5 1.25 1.25 0 0 0 0 2.5Z" clip-rule="evenodd"/></svg>
|
After Width: | Height: | Size: 527 B |
|
@ -16,6 +16,10 @@ import {
|
||||||
ItemTextProps,
|
ItemTextProps,
|
||||||
ItemIconProps,
|
ItemIconProps,
|
||||||
} from '#/components/Menu/types'
|
} from '#/components/Menu/types'
|
||||||
|
import {Button, ButtonText} from '#/components/Button'
|
||||||
|
import {msg} from '@lingui/macro'
|
||||||
|
import {useLingui} from '@lingui/react'
|
||||||
|
import {isNative} from 'platform/detection'
|
||||||
|
|
||||||
export {useDialogControl as useMenuControl} from '#/components/Dialog'
|
export {useDialogControl as useMenuControl} from '#/components/Dialog'
|
||||||
|
|
||||||
|
@ -68,7 +72,10 @@ export function Trigger({children, label}: TriggerProps) {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
export function Outer({children}: React.PropsWithChildren<{}>) {
|
export function Outer({
|
||||||
|
children,
|
||||||
|
showCancel,
|
||||||
|
}: React.PropsWithChildren<{showCancel?: boolean}>) {
|
||||||
const context = React.useContext(Context)
|
const context = React.useContext(Context)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -78,7 +85,10 @@ export function Outer({children}: React.PropsWithChildren<{}>) {
|
||||||
{/* Re-wrap with context since Dialogs are portal-ed to root */}
|
{/* Re-wrap with context since Dialogs are portal-ed to root */}
|
||||||
<Context.Provider value={context}>
|
<Context.Provider value={context}>
|
||||||
<Dialog.ScrollableInner label="Menu TODO">
|
<Dialog.ScrollableInner label="Menu TODO">
|
||||||
<View style={[a.gap_lg]}>{children}</View>
|
<View style={[a.gap_lg]}>
|
||||||
|
{children}
|
||||||
|
{isNative && showCancel && <Cancel />}
|
||||||
|
</View>
|
||||||
<View style={{height: a.gap_lg.gap}} />
|
<View style={{height: a.gap_lg.gap}} />
|
||||||
</Dialog.ScrollableInner>
|
</Dialog.ScrollableInner>
|
||||||
</Context.Provider>
|
</Context.Provider>
|
||||||
|
@ -185,6 +195,22 @@ export function Group({children, style}: GroupProps) {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function Cancel() {
|
||||||
|
const {_} = useLingui()
|
||||||
|
const {control} = React.useContext(Context)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Button
|
||||||
|
label={_(msg`Close this dialog`)}
|
||||||
|
size="small"
|
||||||
|
variant="ghost"
|
||||||
|
color="secondary"
|
||||||
|
onPress={() => control.close()}>
|
||||||
|
<ButtonText>Cancel</ButtonText>
|
||||||
|
</Button>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
export function Divider() {
|
export function Divider() {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,9 @@
|
||||||
|
import {createSinglePathSVG} from './TEMPLATE'
|
||||||
|
|
||||||
|
export const Camera_Stroke2_Corner0_Rounded = createSinglePathSVG({
|
||||||
|
path: 'M8.371 3.89A2 2 0 0 1 10.035 3h3.93a2 2 0 0 1 1.664.89L17.035 6H20a2 2 0 0 1 2 2v11a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h2.965L8.37 3.89ZM13.965 5h-3.93L8.63 7.11A2 2 0 0 1 6.965 8H4v11h16V8h-2.965a2 2 0 0 1-1.664-.89L13.965 5ZM12 11a2 2 0 1 0 0 4 2 2 0 0 0 0-4Zm-4 2a4 4 0 1 1 8 0 4 4 0 0 1-8 0Z',
|
||||||
|
})
|
||||||
|
|
||||||
|
export const Camera_Filled_Stroke2_Corner0_Rounded = createSinglePathSVG({
|
||||||
|
path: 'M8.371 3.89A2 2 0 0 1 10.035 3h3.93a2 2 0 0 1 1.664.89L17.035 6H20a2 2 0 0 1 2 2v11a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h2.965L8.37 3.89ZM12 9a3.5 3.5 0 1 0 0 7 3.5 3.5 0 0 0 0-7Z',
|
||||||
|
})
|
|
@ -0,0 +1,5 @@
|
||||||
|
import {createSinglePathSVG} from './TEMPLATE'
|
||||||
|
|
||||||
|
export const StreamingLive_Stroke2_Corner0_Rounded = createSinglePathSVG({
|
||||||
|
path: 'M4 4a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h16a2 2 0 0 0 2-2V6a2 2 0 0 0-2-2H4Zm8 12.5c1.253 0 2.197.609 2.674 1.5H9.326c.477-.891 1.42-1.5 2.674-1.5Zm0-2c2.404 0 4.235 1.475 4.822 3.5H20V6H4v12h3.178c.587-2.025 2.418-3.5 4.822-3.5Zm-1.25-3.75a1.25 1.25 0 1 1 2.5 0 1.25 1.25 0 0 1-2.5 0ZM12 7.5a3.25 3.25 0 1 0 0 6.5 3.25 3.25 0 0 0 0-6.5Zm5.75 2a1.25 1.25 0 1 0 0-2.5 1.25 1.25 0 0 0 0 2.5Z',
|
||||||
|
})
|
|
@ -1,9 +1,13 @@
|
||||||
import React, {memo, useMemo} from 'react'
|
import React, {memo, useMemo} from 'react'
|
||||||
import {Image, StyleSheet, View} from 'react-native'
|
import {Image, StyleSheet, TouchableOpacity, View} from 'react-native'
|
||||||
import Svg, {Circle, Rect, Path} from 'react-native-svg'
|
import Svg, {Circle, Rect, Path} from 'react-native-svg'
|
||||||
|
import {Image as RNImage} from 'react-native-image-crop-picker'
|
||||||
|
import {useLingui} from '@lingui/react'
|
||||||
|
import {msg, Trans} from '@lingui/macro'
|
||||||
import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome'
|
import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome'
|
||||||
import {HighPriorityImage} from 'view/com/util/images/Image'
|
|
||||||
import {ModerationUI} from '@atproto/api'
|
import {ModerationUI} from '@atproto/api'
|
||||||
|
|
||||||
|
import {HighPriorityImage} from 'view/com/util/images/Image'
|
||||||
import {openCamera, openCropper, openPicker} from '../../../lib/media/picker'
|
import {openCamera, openCropper, openPicker} from '../../../lib/media/picker'
|
||||||
import {
|
import {
|
||||||
usePhotoLibraryPermission,
|
usePhotoLibraryPermission,
|
||||||
|
@ -11,12 +15,16 @@ import {
|
||||||
} from 'lib/hooks/usePermissions'
|
} from 'lib/hooks/usePermissions'
|
||||||
import {colors} from 'lib/styles'
|
import {colors} from 'lib/styles'
|
||||||
import {usePalette} from 'lib/hooks/usePalette'
|
import {usePalette} from 'lib/hooks/usePalette'
|
||||||
import {isWeb, isAndroid} from 'platform/detection'
|
import {isWeb, isAndroid, isNative} from 'platform/detection'
|
||||||
import {Image as RNImage} from 'react-native-image-crop-picker'
|
|
||||||
import {UserPreviewLink} from './UserPreviewLink'
|
import {UserPreviewLink} from './UserPreviewLink'
|
||||||
import {DropdownItem, NativeDropdown} from './forms/NativeDropdown'
|
import * as Menu from '#/components/Menu'
|
||||||
import {useLingui} from '@lingui/react'
|
import {
|
||||||
import {msg} from '@lingui/macro'
|
Camera_Stroke2_Corner0_Rounded as Camera,
|
||||||
|
Camera_Filled_Stroke2_Corner0_Rounded as CameraFilled,
|
||||||
|
} from '#/components/icons/Camera'
|
||||||
|
import {StreamingLive_Stroke2_Corner0_Rounded as Library} from '#/components/icons/StreamingLive'
|
||||||
|
import {Trash_Stroke2_Corner0_Rounded as Trash} from '#/components/icons/Trash'
|
||||||
|
import {useTheme} from '#/alf'
|
||||||
|
|
||||||
export type UserAvatarType = 'user' | 'algo' | 'list'
|
export type UserAvatarType = 'user' | 'algo' | 'list'
|
||||||
|
|
||||||
|
@ -196,6 +204,7 @@ let EditableUserAvatar = ({
|
||||||
avatar,
|
avatar,
|
||||||
onSelectNewAvatar,
|
onSelectNewAvatar,
|
||||||
}: EditableUserAvatarProps): React.ReactNode => {
|
}: EditableUserAvatarProps): React.ReactNode => {
|
||||||
|
const t = useTheme()
|
||||||
const pal = usePalette('default')
|
const pal = usePalette('default')
|
||||||
const {_} = useLingui()
|
const {_} = useLingui()
|
||||||
const {requestCameraAccessIfNeeded} = useCameraPermission()
|
const {requestCameraAccessIfNeeded} = useCameraPermission()
|
||||||
|
@ -216,118 +225,115 @@ let EditableUserAvatar = ({
|
||||||
}
|
}
|
||||||
}, [type, size])
|
}, [type, size])
|
||||||
|
|
||||||
const dropdownItems = useMemo(
|
const onOpenCamera = React.useCallback(async () => {
|
||||||
() =>
|
if (!(await requestCameraAccessIfNeeded())) {
|
||||||
[
|
return
|
||||||
!isWeb && {
|
}
|
||||||
testID: 'changeAvatarCameraBtn',
|
|
||||||
label: _(msg`Camera`),
|
|
||||||
icon: {
|
|
||||||
ios: {
|
|
||||||
name: 'camera',
|
|
||||||
},
|
|
||||||
android: 'ic_menu_camera',
|
|
||||||
web: 'camera',
|
|
||||||
},
|
|
||||||
onPress: async () => {
|
|
||||||
if (!(await requestCameraAccessIfNeeded())) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
onSelectNewAvatar(
|
onSelectNewAvatar(
|
||||||
await openCamera({
|
await openCamera({
|
||||||
width: 1000,
|
width: 1000,
|
||||||
height: 1000,
|
height: 1000,
|
||||||
cropperCircleOverlay: true,
|
cropperCircleOverlay: true,
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
},
|
}, [onSelectNewAvatar, requestCameraAccessIfNeeded])
|
||||||
},
|
|
||||||
{
|
|
||||||
testID: 'changeAvatarLibraryBtn',
|
|
||||||
label: _(msg`Library`),
|
|
||||||
icon: {
|
|
||||||
ios: {
|
|
||||||
name: 'photo.on.rectangle.angled',
|
|
||||||
},
|
|
||||||
android: 'ic_menu_gallery',
|
|
||||||
web: 'gallery',
|
|
||||||
},
|
|
||||||
onPress: async () => {
|
|
||||||
if (!(await requestPhotoAccessIfNeeded())) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
const items = await openPicker({
|
const onOpenLibrary = React.useCallback(async () => {
|
||||||
aspect: [1, 1],
|
if (!(await requestPhotoAccessIfNeeded())) {
|
||||||
})
|
return
|
||||||
const item = items[0]
|
}
|
||||||
if (!item) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
const croppedImage = await openCropper({
|
const items = await openPicker({
|
||||||
mediaType: 'photo',
|
aspect: [1, 1],
|
||||||
cropperCircleOverlay: true,
|
})
|
||||||
height: item.height,
|
const item = items[0]
|
||||||
width: item.width,
|
if (!item) {
|
||||||
path: item.path,
|
return
|
||||||
})
|
}
|
||||||
|
|
||||||
onSelectNewAvatar(croppedImage)
|
const croppedImage = await openCropper({
|
||||||
},
|
mediaType: 'photo',
|
||||||
},
|
cropperCircleOverlay: true,
|
||||||
!!avatar && {
|
height: item.height,
|
||||||
label: 'separator',
|
width: item.width,
|
||||||
},
|
path: item.path,
|
||||||
!!avatar && {
|
})
|
||||||
testID: 'changeAvatarRemoveBtn',
|
|
||||||
label: _(msg`Remove`),
|
onSelectNewAvatar(croppedImage)
|
||||||
icon: {
|
}, [onSelectNewAvatar, requestPhotoAccessIfNeeded])
|
||||||
ios: {
|
|
||||||
name: 'trash',
|
const onRemoveAvatar = React.useCallback(() => {
|
||||||
},
|
onSelectNewAvatar(null)
|
||||||
android: 'ic_delete',
|
}, [onSelectNewAvatar])
|
||||||
web: ['far', 'trash-can'],
|
|
||||||
},
|
|
||||||
onPress: async () => {
|
|
||||||
onSelectNewAvatar(null)
|
|
||||||
},
|
|
||||||
},
|
|
||||||
].filter(Boolean) as DropdownItem[],
|
|
||||||
[
|
|
||||||
avatar,
|
|
||||||
onSelectNewAvatar,
|
|
||||||
requestCameraAccessIfNeeded,
|
|
||||||
requestPhotoAccessIfNeeded,
|
|
||||||
_,
|
|
||||||
],
|
|
||||||
)
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<NativeDropdown
|
<Menu.Root>
|
||||||
testID="changeAvatarBtn"
|
<Menu.Trigger label={_(msg`Edit avatar`)}>
|
||||||
items={dropdownItems}
|
{({props}) => (
|
||||||
accessibilityLabel={_(msg`Image options`)}
|
<TouchableOpacity {...props} activeOpacity={0.8}>
|
||||||
accessibilityHint="">
|
{avatar ? (
|
||||||
{avatar ? (
|
<HighPriorityImage
|
||||||
<HighPriorityImage
|
testID="userAvatarImage"
|
||||||
testID="userAvatarImage"
|
style={aviStyle}
|
||||||
style={aviStyle}
|
source={{uri: avatar}}
|
||||||
source={{uri: avatar}}
|
accessibilityRole="image"
|
||||||
accessibilityRole="image"
|
/>
|
||||||
/>
|
) : (
|
||||||
) : (
|
<DefaultAvatar type={type} size={size} />
|
||||||
<DefaultAvatar type={type} size={size} />
|
)}
|
||||||
)}
|
<View style={[styles.editButtonContainer, pal.btn]}>
|
||||||
<View style={[styles.editButtonContainer, pal.btn]}>
|
<CameraFilled height={14} width={14} style={t.atoms.text} />
|
||||||
<FontAwesomeIcon
|
</View>
|
||||||
icon="camera"
|
</TouchableOpacity>
|
||||||
size={12}
|
)}
|
||||||
color={pal.text.color as string}
|
</Menu.Trigger>
|
||||||
/>
|
<Menu.Outer showCancel>
|
||||||
</View>
|
<Menu.Group>
|
||||||
</NativeDropdown>
|
{isNative && (
|
||||||
|
<Menu.Item
|
||||||
|
testID="changeAvatarCameraBtn"
|
||||||
|
label={_(msg`Upload from Camera`)}
|
||||||
|
onPress={onOpenCamera}>
|
||||||
|
<Menu.ItemText>
|
||||||
|
<Trans>Upload from Camera</Trans>
|
||||||
|
</Menu.ItemText>
|
||||||
|
<Menu.ItemIcon icon={Camera} />
|
||||||
|
</Menu.Item>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<Menu.Item
|
||||||
|
testID="changeAvatarLibraryBtn"
|
||||||
|
label={_(msg`Upload from Library`)}
|
||||||
|
onPress={onOpenLibrary}>
|
||||||
|
<Menu.ItemText>
|
||||||
|
{isNative ? (
|
||||||
|
<Trans>Upload from Library</Trans>
|
||||||
|
) : (
|
||||||
|
<Trans>Upload from Files</Trans>
|
||||||
|
)}
|
||||||
|
</Menu.ItemText>
|
||||||
|
<Menu.ItemIcon icon={Library} />
|
||||||
|
</Menu.Item>
|
||||||
|
</Menu.Group>
|
||||||
|
{!!avatar && (
|
||||||
|
<>
|
||||||
|
<Menu.Divider />
|
||||||
|
<Menu.Group>
|
||||||
|
<Menu.Item
|
||||||
|
testID="changeAvatarRemoveBtn"
|
||||||
|
label={_(`Remove Avatar`)}
|
||||||
|
onPress={onRemoveAvatar}>
|
||||||
|
<Menu.ItemText>
|
||||||
|
<Trans>Remove Avatar</Trans>
|
||||||
|
</Menu.ItemText>
|
||||||
|
<Menu.ItemIcon icon={Trash} />
|
||||||
|
</Menu.Item>
|
||||||
|
</Menu.Group>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</Menu.Outer>
|
||||||
|
</Menu.Root>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
EditableUserAvatar = memo(EditableUserAvatar)
|
EditableUserAvatar = memo(EditableUserAvatar)
|
||||||
|
|
|
@ -1,21 +1,29 @@
|
||||||
import React, {useMemo} from 'react'
|
import React from 'react'
|
||||||
import {StyleSheet, View} from 'react-native'
|
import {StyleSheet, TouchableOpacity, View} from 'react-native'
|
||||||
import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome'
|
|
||||||
import {ModerationUI} from '@atproto/api'
|
import {ModerationUI} from '@atproto/api'
|
||||||
import {Image} from 'expo-image'
|
import {Image} from 'expo-image'
|
||||||
import {useLingui} from '@lingui/react'
|
import {useLingui} from '@lingui/react'
|
||||||
import {msg} from '@lingui/macro'
|
import {msg, Trans} from '@lingui/macro'
|
||||||
|
|
||||||
import {colors} from 'lib/styles'
|
import {colors} from 'lib/styles'
|
||||||
import {useTheme} from 'lib/ThemeContext'
|
import {useTheme} from 'lib/ThemeContext'
|
||||||
|
import {useTheme as useAlfTheme} from '#/alf'
|
||||||
import {openCamera, openCropper, openPicker} from '../../../lib/media/picker'
|
import {openCamera, openCropper, openPicker} from '../../../lib/media/picker'
|
||||||
import {
|
import {
|
||||||
usePhotoLibraryPermission,
|
usePhotoLibraryPermission,
|
||||||
useCameraPermission,
|
useCameraPermission,
|
||||||
} from 'lib/hooks/usePermissions'
|
} from 'lib/hooks/usePermissions'
|
||||||
import {usePalette} from 'lib/hooks/usePalette'
|
import {usePalette} from 'lib/hooks/usePalette'
|
||||||
import {isWeb, isAndroid} from 'platform/detection'
|
import {isAndroid, isNative} from 'platform/detection'
|
||||||
import {Image as RNImage} from 'react-native-image-crop-picker'
|
import {Image as RNImage} from 'react-native-image-crop-picker'
|
||||||
import {NativeDropdown, DropdownItem} from './forms/NativeDropdown'
|
import {EventStopper} from 'view/com/util/EventStopper'
|
||||||
|
import * as Menu from '#/components/Menu'
|
||||||
|
import {
|
||||||
|
Camera_Filled_Stroke2_Corner0_Rounded as CameraFilled,
|
||||||
|
Camera_Stroke2_Corner0_Rounded as Camera,
|
||||||
|
} from '#/components/icons/Camera'
|
||||||
|
import {StreamingLive_Stroke2_Corner0_Rounded as Library} from '#/components/icons/StreamingLive'
|
||||||
|
import {Trash_Stroke2_Corner0_Rounded as Trash} from '#/components/icons/Trash'
|
||||||
|
|
||||||
export function UserBanner({
|
export function UserBanner({
|
||||||
banner,
|
banner,
|
||||||
|
@ -28,118 +36,120 @@ export function UserBanner({
|
||||||
}) {
|
}) {
|
||||||
const pal = usePalette('default')
|
const pal = usePalette('default')
|
||||||
const theme = useTheme()
|
const theme = useTheme()
|
||||||
|
const t = useAlfTheme()
|
||||||
const {_} = useLingui()
|
const {_} = useLingui()
|
||||||
const {requestCameraAccessIfNeeded} = useCameraPermission()
|
const {requestCameraAccessIfNeeded} = useCameraPermission()
|
||||||
const {requestPhotoAccessIfNeeded} = usePhotoLibraryPermission()
|
const {requestPhotoAccessIfNeeded} = usePhotoLibraryPermission()
|
||||||
|
|
||||||
const dropdownItems: DropdownItem[] = useMemo(
|
const onOpenCamera = React.useCallback(async () => {
|
||||||
() =>
|
if (!(await requestCameraAccessIfNeeded())) {
|
||||||
[
|
return
|
||||||
!isWeb && {
|
}
|
||||||
testID: 'changeBannerCameraBtn',
|
onSelectNewBanner?.(
|
||||||
label: _(msg`Camera`),
|
await openCamera({
|
||||||
icon: {
|
width: 3000,
|
||||||
ios: {
|
height: 1000,
|
||||||
name: 'camera',
|
}),
|
||||||
},
|
)
|
||||||
android: 'ic_menu_camera',
|
}, [onSelectNewBanner, requestCameraAccessIfNeeded])
|
||||||
web: 'camera',
|
|
||||||
},
|
|
||||||
onPress: async () => {
|
|
||||||
if (!(await requestCameraAccessIfNeeded())) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
onSelectNewBanner?.(
|
|
||||||
await openCamera({
|
|
||||||
width: 3000,
|
|
||||||
height: 1000,
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
testID: 'changeBannerLibraryBtn',
|
|
||||||
label: _(msg`Library`),
|
|
||||||
icon: {
|
|
||||||
ios: {
|
|
||||||
name: 'photo.on.rectangle.angled',
|
|
||||||
},
|
|
||||||
android: 'ic_menu_gallery',
|
|
||||||
web: 'gallery',
|
|
||||||
},
|
|
||||||
onPress: async () => {
|
|
||||||
if (!(await requestPhotoAccessIfNeeded())) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
const items = await openPicker()
|
|
||||||
if (!items[0]) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
onSelectNewBanner?.(
|
const onOpenLibrary = React.useCallback(async () => {
|
||||||
await openCropper({
|
if (!(await requestPhotoAccessIfNeeded())) {
|
||||||
mediaType: 'photo',
|
return
|
||||||
path: items[0].path,
|
}
|
||||||
width: 3000,
|
const items = await openPicker()
|
||||||
height: 1000,
|
if (!items[0]) {
|
||||||
}),
|
return
|
||||||
)
|
}
|
||||||
},
|
|
||||||
},
|
onSelectNewBanner?.(
|
||||||
!!banner && {
|
await openCropper({
|
||||||
testID: 'changeBannerRemoveBtn',
|
mediaType: 'photo',
|
||||||
label: _(msg`Remove`),
|
path: items[0].path,
|
||||||
icon: {
|
width: 3000,
|
||||||
ios: {
|
height: 1000,
|
||||||
name: 'trash',
|
}),
|
||||||
},
|
)
|
||||||
android: 'ic_delete',
|
}, [onSelectNewBanner, requestPhotoAccessIfNeeded])
|
||||||
web: ['far', 'trash-can'],
|
|
||||||
},
|
const onRemoveBanner = React.useCallback(() => {
|
||||||
onPress: () => {
|
onSelectNewBanner?.(null)
|
||||||
onSelectNewBanner?.(null)
|
}, [onSelectNewBanner])
|
||||||
},
|
|
||||||
},
|
|
||||||
].filter(Boolean) as DropdownItem[],
|
|
||||||
[
|
|
||||||
banner,
|
|
||||||
onSelectNewBanner,
|
|
||||||
requestCameraAccessIfNeeded,
|
|
||||||
requestPhotoAccessIfNeeded,
|
|
||||||
_,
|
|
||||||
],
|
|
||||||
)
|
|
||||||
|
|
||||||
// setUserBanner is only passed as prop on the EditProfile component
|
// setUserBanner is only passed as prop on the EditProfile component
|
||||||
return onSelectNewBanner ? (
|
return onSelectNewBanner ? (
|
||||||
<NativeDropdown
|
<EventStopper onKeyDown={false}>
|
||||||
testID="changeBannerBtn"
|
<Menu.Root>
|
||||||
items={dropdownItems}
|
<Menu.Trigger label={_(msg`Edit avatar`)}>
|
||||||
accessibilityLabel={_(msg`Image options`)}
|
{({props}) => (
|
||||||
accessibilityHint="">
|
<TouchableOpacity {...props} activeOpacity={0.8}>
|
||||||
{banner ? (
|
{banner ? (
|
||||||
<Image
|
<Image
|
||||||
testID="userBannerImage"
|
testID="userBannerImage"
|
||||||
style={styles.bannerImage}
|
style={styles.bannerImage}
|
||||||
source={{uri: banner}}
|
source={{uri: banner}}
|
||||||
accessible={true}
|
accessible={true}
|
||||||
accessibilityIgnoresInvertColors
|
accessibilityIgnoresInvertColors
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<View
|
<View
|
||||||
testID="userBannerFallback"
|
testID="userBannerFallback"
|
||||||
style={[styles.bannerImage, styles.defaultBanner]}
|
style={[styles.bannerImage, styles.defaultBanner]}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
<View style={[styles.editButtonContainer, pal.btn]}>
|
<View style={[styles.editButtonContainer, pal.btn]}>
|
||||||
<FontAwesomeIcon
|
<CameraFilled height={14} width={14} style={t.atoms.text} />
|
||||||
icon="camera"
|
</View>
|
||||||
size={12}
|
</TouchableOpacity>
|
||||||
style={{color: colors.white}}
|
)}
|
||||||
color={pal.text.color as string}
|
</Menu.Trigger>
|
||||||
/>
|
<Menu.Outer showCancel>
|
||||||
</View>
|
<Menu.Group>
|
||||||
</NativeDropdown>
|
{isNative && (
|
||||||
|
<Menu.Item
|
||||||
|
testID="changeBannerCameraBtn"
|
||||||
|
label={_(msg`Upload from Camera`)}
|
||||||
|
onPress={onOpenCamera}>
|
||||||
|
<Menu.ItemText>
|
||||||
|
<Trans>Upload from Camera</Trans>
|
||||||
|
</Menu.ItemText>
|
||||||
|
<Menu.ItemIcon icon={Camera} />
|
||||||
|
</Menu.Item>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<Menu.Item
|
||||||
|
testID="changeBannerLibraryBtn"
|
||||||
|
label={_(msg`Upload from Library`)}
|
||||||
|
onPress={onOpenLibrary}>
|
||||||
|
<Menu.ItemText>
|
||||||
|
{isNative ? (
|
||||||
|
<Trans>Upload from Library</Trans>
|
||||||
|
) : (
|
||||||
|
<Trans>Upload from Files</Trans>
|
||||||
|
)}
|
||||||
|
</Menu.ItemText>
|
||||||
|
<Menu.ItemIcon icon={Library} />
|
||||||
|
</Menu.Item>
|
||||||
|
</Menu.Group>
|
||||||
|
{!!banner && (
|
||||||
|
<>
|
||||||
|
<Menu.Divider />
|
||||||
|
<Menu.Group>
|
||||||
|
<Menu.Item
|
||||||
|
testID="changeBannerRemoveBtn"
|
||||||
|
label={_(`Remove Banner`)}
|
||||||
|
onPress={onRemoveBanner}>
|
||||||
|
<Menu.ItemText>
|
||||||
|
<Trans>Remove Banner</Trans>
|
||||||
|
</Menu.ItemText>
|
||||||
|
<Menu.ItemIcon icon={Trash} />
|
||||||
|
</Menu.Item>
|
||||||
|
</Menu.Group>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</Menu.Outer>
|
||||||
|
</Menu.Root>
|
||||||
|
</EventStopper>
|
||||||
) : banner &&
|
) : banner &&
|
||||||
!((moderation?.blur && isAndroid) /* android crashes with blur */) ? (
|
!((moderation?.blur && isAndroid) /* android crashes with blur */) ? (
|
||||||
<Image
|
<Image
|
||||||
|
|
Loading…
Reference in New Issue