Break out the web/native image picking code and make some progress on the web version
This commit is contained in:
parent
0673129b20
commit
7916b26aad
21 changed files with 738 additions and 138 deletions
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
11
src/view/com/modals/crop-image/CropImage.tsx
Normal file
11
src/view/com/modals/crop-image/CropImage.tsx
Normal 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
|
||||
}
|
164
src/view/com/modals/crop-image/CropImage.web.tsx
Normal file
164
src/view/com/modals/crop-image/CropImage.web.tsx
Normal 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,
|
||||
},
|
||||
})
|
Loading…
Add table
Add a link
Reference in a new issue