Replaces the alert with dropdown for profile image and banner (#123)
* replaces the alert with dropdown for profile image and banner * lint
This commit is contained in:
@@ -1,5 +1,5 @@
|
|||||||
import React, {useCallback} from 'react'
|
import React from 'react'
|
||||||
import {Alert, Image, StyleSheet, TouchableOpacity, View} from 'react-native'
|
import {Image, StyleSheet, View} from 'react-native'
|
||||||
import Svg, {Circle, Text, Defs, LinearGradient, Stop} from 'react-native-svg'
|
import Svg, {Circle, Text, Defs, LinearGradient, Stop} from 'react-native-svg'
|
||||||
import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome'
|
import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome'
|
||||||
import {
|
import {
|
||||||
@@ -9,6 +9,7 @@ import {
|
|||||||
Image as PickedImage,
|
Image as PickedImage,
|
||||||
} from 'react-native-image-crop-picker'
|
} from 'react-native-image-crop-picker'
|
||||||
import {colors, gradients} from '../../lib/styles'
|
import {colors, gradients} from '../../lib/styles'
|
||||||
|
import {DropdownButton} from './forms/DropdownButton'
|
||||||
|
|
||||||
export function UserAvatar({
|
export function UserAvatar({
|
||||||
size,
|
size,
|
||||||
@@ -25,43 +26,6 @@ export function UserAvatar({
|
|||||||
}) {
|
}) {
|
||||||
const initials = getInitials(displayName || handle)
|
const initials = getInitials(displayName || handle)
|
||||||
|
|
||||||
const handleEditAvatar = useCallback(() => {
|
|
||||||
Alert.alert('Select upload method', '', [
|
|
||||||
{
|
|
||||||
text: 'Take a new photo',
|
|
||||||
onPress: () => {
|
|
||||||
openCamera({
|
|
||||||
mediaType: 'photo',
|
|
||||||
cropping: true,
|
|
||||||
width: 2000,
|
|
||||||
height: 2000,
|
|
||||||
cropperCircleOverlay: true,
|
|
||||||
forceJpg: true, // ios only
|
|
||||||
compressImageQuality: 1,
|
|
||||||
}).then(onSelectNewAvatar)
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
text: 'Select from gallery',
|
|
||||||
onPress: () => {
|
|
||||||
openPicker({
|
|
||||||
mediaType: 'photo',
|
|
||||||
}).then(async item => {
|
|
||||||
await openCropper({
|
|
||||||
mediaType: 'photo',
|
|
||||||
path: item.path,
|
|
||||||
width: 2000,
|
|
||||||
height: 2000,
|
|
||||||
cropperCircleOverlay: true,
|
|
||||||
forceJpg: true, // ios only
|
|
||||||
compressImageQuality: 1,
|
|
||||||
}).then(onSelectNewAvatar)
|
|
||||||
})
|
|
||||||
},
|
|
||||||
},
|
|
||||||
])
|
|
||||||
}, [onSelectNewAvatar])
|
|
||||||
|
|
||||||
const renderSvg = (svgSize: number, svgInitials: string) => (
|
const renderSvg = (svgSize: number, svgInitials: string) => (
|
||||||
<Svg width={svgSize} height={svgSize} viewBox="0 0 100 100">
|
<Svg width={svgSize} height={svgSize} viewBox="0 0 100 100">
|
||||||
<Defs>
|
<Defs>
|
||||||
@@ -83,9 +47,60 @@ export function UserAvatar({
|
|||||||
</Svg>
|
</Svg>
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const dropdownItems = [
|
||||||
|
{
|
||||||
|
label: 'Camera',
|
||||||
|
icon: 'camera',
|
||||||
|
// TODO: dark mode icon
|
||||||
|
onPress: () => {
|
||||||
|
openCamera({
|
||||||
|
mediaType: 'photo',
|
||||||
|
cropping: true,
|
||||||
|
width: 2000,
|
||||||
|
height: 2000,
|
||||||
|
cropperCircleOverlay: true,
|
||||||
|
forceJpg: true, // ios only
|
||||||
|
compressImageQuality: 1,
|
||||||
|
}).then(onSelectNewAvatar)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Library',
|
||||||
|
icon: 'image',
|
||||||
|
onPress: () => {
|
||||||
|
openPicker({
|
||||||
|
mediaType: 'photo',
|
||||||
|
}).then(async item => {
|
||||||
|
await openCropper({
|
||||||
|
mediaType: 'photo',
|
||||||
|
path: item.path,
|
||||||
|
width: 2000,
|
||||||
|
height: 2000,
|
||||||
|
cropperCircleOverlay: true,
|
||||||
|
forceJpg: true, // ios only
|
||||||
|
compressImageQuality: 1,
|
||||||
|
}).then(onSelectNewAvatar)
|
||||||
|
})
|
||||||
|
},
|
||||||
|
},
|
||||||
|
// TODO: Remove avatar https://github.com/bluesky-social/social-app/issues/122
|
||||||
|
// {
|
||||||
|
// label: 'Remove',
|
||||||
|
// icon: ['far', 'trash-can'],
|
||||||
|
// onPress: () => {
|
||||||
|
// // Remove avatar API call
|
||||||
|
// },
|
||||||
|
// },
|
||||||
|
]
|
||||||
// onSelectNewAvatar is only passed as prop on the EditProfile component
|
// onSelectNewAvatar is only passed as prop on the EditProfile component
|
||||||
return onSelectNewAvatar ? (
|
return onSelectNewAvatar ? (
|
||||||
<TouchableOpacity onPress={handleEditAvatar}>
|
<DropdownButton
|
||||||
|
type="bare"
|
||||||
|
items={dropdownItems}
|
||||||
|
openToRight
|
||||||
|
rightOffset={-10}
|
||||||
|
bottomOffset={-10}
|
||||||
|
menuWidth={170}>
|
||||||
{avatar ? (
|
{avatar ? (
|
||||||
<Image
|
<Image
|
||||||
style={{
|
style={{
|
||||||
@@ -105,7 +120,7 @@ export function UserAvatar({
|
|||||||
style={{color: colors.white}}
|
style={{color: colors.white}}
|
||||||
/>
|
/>
|
||||||
</View>
|
</View>
|
||||||
</TouchableOpacity>
|
</DropdownButton>
|
||||||
) : avatar ? (
|
) : avatar ? (
|
||||||
<Image
|
<Image
|
||||||
style={{width: size, height: size, borderRadius: Math.floor(size / 2)}}
|
style={{width: size, height: size, borderRadius: Math.floor(size / 2)}}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import React, {useCallback} from 'react'
|
import React from 'react'
|
||||||
import {StyleSheet, View, TouchableOpacity, Alert, Image} from 'react-native'
|
import {StyleSheet, View, Image} from 'react-native'
|
||||||
import Svg, {Rect, Defs, LinearGradient, Stop} from 'react-native-svg'
|
import Svg, {Rect, Defs, LinearGradient, Stop} from 'react-native-svg'
|
||||||
import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome'
|
import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome'
|
||||||
import {Image as PickedImage} from 'react-native-image-crop-picker'
|
import {Image as PickedImage} from 'react-native-image-crop-picker'
|
||||||
@@ -9,6 +9,7 @@ import {
|
|||||||
openCropper,
|
openCropper,
|
||||||
openPicker,
|
openPicker,
|
||||||
} from 'react-native-image-crop-picker'
|
} from 'react-native-image-crop-picker'
|
||||||
|
import {DropdownButton} from './forms/DropdownButton'
|
||||||
|
|
||||||
export function UserBanner({
|
export function UserBanner({
|
||||||
banner,
|
banner,
|
||||||
@@ -17,14 +18,35 @@ export function UserBanner({
|
|||||||
banner?: string | null
|
banner?: string | null
|
||||||
onSelectNewBanner?: (img: PickedImage) => void
|
onSelectNewBanner?: (img: PickedImage) => void
|
||||||
}) {
|
}) {
|
||||||
const handleEditBanner = useCallback(() => {
|
const dropdownItems = [
|
||||||
Alert.alert('Select upload method', '', [
|
{
|
||||||
{
|
label: 'Camera',
|
||||||
text: 'Take a new photo',
|
icon: 'camera',
|
||||||
onPress: () => {
|
// TODO: Add darkmode support https://github.com/bluesky-social/social-app/issues/78
|
||||||
openCamera({
|
onPress: () => {
|
||||||
|
openCamera({
|
||||||
|
mediaType: 'photo',
|
||||||
|
cropping: true,
|
||||||
|
compressImageMaxWidth: 6000,
|
||||||
|
width: 6000,
|
||||||
|
compressImageMaxHeight: 2000,
|
||||||
|
height: 2000,
|
||||||
|
forceJpg: true, // ios only
|
||||||
|
compressImageQuality: 1,
|
||||||
|
includeExif: true,
|
||||||
|
}).then(onSelectNewBanner)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Library',
|
||||||
|
icon: 'image',
|
||||||
|
onPress: () => {
|
||||||
|
openPicker({
|
||||||
|
mediaType: 'photo',
|
||||||
|
}).then(async item => {
|
||||||
|
await openCropper({
|
||||||
mediaType: 'photo',
|
mediaType: 'photo',
|
||||||
cropping: true,
|
path: item.path,
|
||||||
compressImageMaxWidth: 6000,
|
compressImageMaxWidth: 6000,
|
||||||
width: 6000,
|
width: 6000,
|
||||||
compressImageMaxHeight: 2000,
|
compressImageMaxHeight: 2000,
|
||||||
@@ -33,30 +55,18 @@ export function UserBanner({
|
|||||||
compressImageQuality: 1,
|
compressImageQuality: 1,
|
||||||
includeExif: true,
|
includeExif: true,
|
||||||
}).then(onSelectNewBanner)
|
}).then(onSelectNewBanner)
|
||||||
},
|
})
|
||||||
},
|
},
|
||||||
{
|
},
|
||||||
text: 'Select from gallery',
|
// TODO: Remove banner https://github.com/bluesky-social/social-app/issues/122
|
||||||
onPress: () => {
|
// {
|
||||||
openPicker({
|
// label: 'Remove',
|
||||||
mediaType: 'photo',
|
// icon: ['far', 'trash-can'],
|
||||||
}).then(async item => {
|
// onPress: () => {
|
||||||
await openCropper({
|
// // Remove banner api call
|
||||||
mediaType: 'photo',
|
// },
|
||||||
path: item.path,
|
// },
|
||||||
compressImageMaxWidth: 6000,
|
]
|
||||||
width: 6000,
|
|
||||||
compressImageMaxHeight: 2000,
|
|
||||||
height: 2000,
|
|
||||||
forceJpg: true, // ios only
|
|
||||||
compressImageQuality: 1,
|
|
||||||
includeExif: true,
|
|
||||||
}).then(onSelectNewBanner)
|
|
||||||
})
|
|
||||||
},
|
|
||||||
},
|
|
||||||
])
|
|
||||||
}, [onSelectNewBanner])
|
|
||||||
|
|
||||||
const renderSvg = () => (
|
const renderSvg = () => (
|
||||||
<Svg width="100%" height="150" viewBox="50 0 200 100">
|
<Svg width="100%" height="150" viewBox="50 0 200 100">
|
||||||
@@ -77,7 +87,13 @@ export function UserBanner({
|
|||||||
|
|
||||||
// setUserBanner is only passed as prop on the EditProfile component
|
// setUserBanner is only passed as prop on the EditProfile component
|
||||||
return onSelectNewBanner ? (
|
return onSelectNewBanner ? (
|
||||||
<TouchableOpacity onPress={handleEditBanner}>
|
<DropdownButton
|
||||||
|
type="bare"
|
||||||
|
items={dropdownItems}
|
||||||
|
openToRight
|
||||||
|
rightOffset={-200}
|
||||||
|
bottomOffset={-10}
|
||||||
|
menuWidth={170}>
|
||||||
{banner ? (
|
{banner ? (
|
||||||
<Image style={styles.bannerImage} source={{uri: banner}} />
|
<Image style={styles.bannerImage} source={{uri: banner}} />
|
||||||
) : (
|
) : (
|
||||||
@@ -90,7 +106,7 @@ export function UserBanner({
|
|||||||
style={{color: colors.white}}
|
style={{color: colors.white}}
|
||||||
/>
|
/>
|
||||||
</View>
|
</View>
|
||||||
</TouchableOpacity>
|
</DropdownButton>
|
||||||
) : banner ? (
|
) : banner ? (
|
||||||
<Image
|
<Image
|
||||||
style={styles.bannerImage}
|
style={styles.bannerImage}
|
||||||
|
|||||||
@@ -36,6 +36,9 @@ export function DropdownButton({
|
|||||||
label,
|
label,
|
||||||
menuWidth,
|
menuWidth,
|
||||||
children,
|
children,
|
||||||
|
openToRight = false,
|
||||||
|
rightOffset = 0,
|
||||||
|
bottomOffset = 0,
|
||||||
}: {
|
}: {
|
||||||
type: DropdownButtonType
|
type: DropdownButtonType
|
||||||
style?: StyleProp<ViewStyle>
|
style?: StyleProp<ViewStyle>
|
||||||
@@ -43,6 +46,9 @@ export function DropdownButton({
|
|||||||
label?: string
|
label?: string
|
||||||
menuWidth?: number
|
menuWidth?: number
|
||||||
children?: React.ReactNode
|
children?: React.ReactNode
|
||||||
|
openToRight?: boolean
|
||||||
|
rightOffset?: number
|
||||||
|
bottomOffset?: number
|
||||||
}) {
|
}) {
|
||||||
const ref = useRef<TouchableOpacity>(null)
|
const ref = useRef<TouchableOpacity>(null)
|
||||||
|
|
||||||
@@ -59,12 +65,11 @@ export function DropdownButton({
|
|||||||
if (!menuWidth) {
|
if (!menuWidth) {
|
||||||
menuWidth = 200
|
menuWidth = 200
|
||||||
}
|
}
|
||||||
createDropdownMenu(
|
const newX = openToRight
|
||||||
pageX + width - menuWidth,
|
? pageX + width + rightOffset
|
||||||
pageY + height,
|
: pageX + width - menuWidth
|
||||||
menuWidth,
|
const newY = pageY + height + bottomOffset
|
||||||
items,
|
createDropdownMenu(newX, newY, menuWidth, items)
|
||||||
)
|
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -184,7 +189,7 @@ function createDropdownMenu(
|
|||||||
{items.map((item, index) => (
|
{items.map((item, index) => (
|
||||||
<TouchableOpacity
|
<TouchableOpacity
|
||||||
key={index}
|
key={index}
|
||||||
style={[styles.menuItem]}
|
style={styles.menuItem}
|
||||||
onPress={() => onPressItem(index)}>
|
onPress={() => onPressItem(index)}>
|
||||||
{item.icon && (
|
{item.icon && (
|
||||||
<FontAwesomeIcon style={styles.icon} icon={item.icon} />
|
<FontAwesomeIcon style={styles.icon} icon={item.icon} />
|
||||||
|
|||||||
Reference in New Issue
Block a user