Break out the web/native image picking code and make some progress on the web version

This commit is contained in:
Paul Frazee 2023-01-27 15:51:24 -06:00
parent 0673129b20
commit 7916b26aad
21 changed files with 738 additions and 138 deletions

View file

@ -8,7 +8,7 @@ import {
} from 'react-native'
import LinearGradient from 'react-native-linear-gradient'
import {ScrollView, TextInput} from './util'
import {Image as PickedImage} from '../util/images/ImageCropPicker'
import {PickedMedia} from '../util/images/image-crop-picker/ImageCropPicker'
import {Text} from '../util/text/Text'
import {ErrorMessage} from '../util/error/ErrorMessage'
import {useStores} from '../../../state'
@ -48,12 +48,12 @@ export function Component({
const [userAvatar, setUserAvatar] = useState<string | undefined>(
profileView.avatar,
)
const [newUserBanner, setNewUserBanner] = useState<PickedImage | undefined>()
const [newUserAvatar, setNewUserAvatar] = useState<PickedImage | undefined>()
const [newUserBanner, setNewUserBanner] = useState<PickedMedia | undefined>()
const [newUserAvatar, setNewUserAvatar] = useState<PickedMedia | undefined>()
const onPressCancel = () => {
store.shell.closeModal()
}
const onSelectNewAvatar = async (img: PickedImage) => {
const onSelectNewAvatar = async (img: PickedMedia) => {
try {
const finalImg = await compressIfNeeded(img, 300000)
setNewUserAvatar(finalImg)
@ -62,7 +62,7 @@ export function Component({
setError(e.message || e.toString())
}
}
const onSelectNewBanner = async (img: PickedImage) => {
const onSelectNewBanner = async (img: PickedMedia) => {
try {
const finalImg = await compressIfNeeded(img, 500000)
setNewUserBanner(finalImg)

View file

@ -11,6 +11,7 @@ import * as EditProfileModal from './EditProfile'
import * as ServerInputModal from './ServerInput'
import * as ReportPostModal from './ReportPost'
import * as ReportAccountModal from './ReportAccount'
import * as CropImageModal from './crop-image/CropImage.web'
export const Modal = observer(function Modal() {
const store = useStores()
@ -50,6 +51,12 @@ export const Modal = observer(function Modal() {
element = <ReportPostModal.Component />
} else if (store.shell.activeModal?.name === 'report-account') {
element = <ReportAccountModal.Component />
} else if (store.shell.activeModal?.name === 'crop-image') {
element = (
<CropImageModal.Component
{...(store.shell.activeModal as models.CropImageModal)}
/>
)
} else {
return null
}

View file

@ -0,0 +1,11 @@
/**
* NOTE
* This modal is used only in the web build
* Native uses a third-party library
*/
export const snapPoints = ['0%']
export function Component() {
return null
}

View file

@ -0,0 +1,164 @@
import React from 'react'
import {StyleSheet, TouchableOpacity, View} from 'react-native'
import ImageEditor from 'react-avatar-editor'
import {Slider} from '@miblanchard/react-native-slider'
import LinearGradient from 'react-native-linear-gradient'
import {Text} from '../../util/text/Text'
import {PickedMedia} from '../../util/images/image-crop-picker/types'
import {s, gradients} from '../../../lib/styles'
import {useStores} from '../../../../state'
import {usePalette} from '../../../lib/hooks/usePalette'
import {SquareIcon, RectWideIcon, RectTallIcon} from '../../../lib/icons'
enum AspectRatio {
Square = 'square',
Wide = 'wide',
Tall = 'tall',
}
interface Dim {
width: number
height: number
}
const DIMS: Record<string, Dim> = {
[AspectRatio.Square]: {width: 1000, height: 1000},
[AspectRatio.Wide]: {width: 1000, height: 750},
[AspectRatio.Tall]: {width: 750, height: 1000},
}
export const snapPoints = ['0%']
export function Component({
uri,
onSelect,
}: {
uri: string
onSelect: (img?: PickedMedia) => void
}) {
const store = useStores()
const pal = usePalette('default')
const [as, setAs] = React.useState<AspectRatio>(AspectRatio.Square)
const [scale, setScale] = React.useState<number>(1)
const doSetAs = (v: AspectRatio) => () => setAs(v)
const onPressCancel = () => {
onSelect(undefined)
store.shell.closeModal()
}
const onPressDone = () => {
console.log('TODO')
onSelect(undefined) // TODO
store.shell.closeModal()
}
let cropperStyle
if (as === AspectRatio.Square) {
cropperStyle = styles.cropperSquare
} else if (as === AspectRatio.Wide) {
cropperStyle = styles.cropperWide
} else if (as === AspectRatio.Tall) {
cropperStyle = styles.cropperTall
}
return (
<View>
<View style={[styles.cropper, cropperStyle]}>
<ImageEditor
style={styles.imageEditor}
image={uri}
width={DIMS[as].width}
height={DIMS[as].height}
scale={scale}
/>
</View>
<View style={styles.ctrls}>
<Slider
value={scale}
onValueChange={(v: number | number[]) =>
setScale(Array.isArray(v) ? v[0] : v)
}
minimumValue={1}
maximumValue={3}
containerStyle={styles.slider}
/>
<TouchableOpacity onPress={doSetAs(AspectRatio.Wide)}>
<RectWideIcon
size={24}
style={as === AspectRatio.Wide ? s.blue3 : undefined}
/>
</TouchableOpacity>
<TouchableOpacity onPress={doSetAs(AspectRatio.Tall)}>
<RectTallIcon
size={24}
style={as === AspectRatio.Tall ? s.blue3 : undefined}
/>
</TouchableOpacity>
<TouchableOpacity onPress={doSetAs(AspectRatio.Square)}>
<SquareIcon
size={24}
style={as === AspectRatio.Square ? s.blue3 : undefined}
/>
</TouchableOpacity>
</View>
<View style={styles.btns}>
<TouchableOpacity onPress={onPressCancel}>
<Text type="xl" style={pal.link}>
Cancel
</Text>
</TouchableOpacity>
<View style={s.flex1} />
<TouchableOpacity onPress={onPressDone}>
<LinearGradient
colors={[gradients.blueLight.start, gradients.blueLight.end]}
start={{x: 0, y: 0}}
end={{x: 1, y: 1}}
style={[styles.btn]}>
<Text type="xl-medium" style={s.white}>
Done
</Text>
</LinearGradient>
</TouchableOpacity>
</View>
</View>
)
}
const styles = StyleSheet.create({
cropper: {
marginLeft: 'auto',
marginRight: 'auto',
},
cropperSquare: {
width: 400,
height: 400,
},
cropperWide: {
width: 400,
height: 300,
},
cropperTall: {
width: 300,
height: 400,
},
imageEditor: {
maxWidth: '100%',
},
ctrls: {
flexDirection: 'row',
alignItems: 'center',
marginTop: 10,
},
slider: {
flex: 1,
marginRight: 10,
},
btns: {
flexDirection: 'row',
alignItems: 'center',
marginTop: 10,
},
btn: {
borderRadius: 4,
paddingVertical: 8,
paddingHorizontal: 24,
},
})