open gallery
long press on castle image to enter editor, open fab
press rotate
press rotate again
press rotate once more
go back to gallery, all rotations are saved
tap on the intermediate rotations and press delete
long press on the vertical castle image
enter editor, press the mirror button
cattle is mirrored
back to gallery, 2 vertical castles are symmetric
//original image
Object {
"albumId": "-2075821635",
"creationTime": 1582682347060,
"duration": 0,
"filename": "fbf57b4e-9bbc-4626-a25c-c4299c5c1563.jpg",
"height": 4096,
"id": "289",
"mediaType": "photo",
"modificationTime": 1582682347000,
"uri": "file:///storage/emulated/0/DCIM/fbf57b4e-9bbc-4626-a25c-c4299c5c1563.jpg",
"width": 2304,
}
------------------------------
//rotated image
//width and height are swapped
Object {
"height": 2304,
"uri": "file:///data/user/0/host.exp.exponent/cache/ExperienceData/%2540chuanshuoge%252Fexpo-medialibrary/ImageManipulator/3da8a50b-4461-463c-b85f-0cdffd3240bc.jpg",
"width": 4096,
}
-------------------------
//saved rotated image
//saved image has no creation time, but has modification time
Object {
"albumId": "540528482",
"creationTime": 0,
"duration": 0,
"filename": "5a4c80c817a17b44b3d5529b738e563b.jpg",
"height": 710,
"id": "14",
"mediaType": "photo",
"modificationTime": 1582173281000,
"uri": "file:///storage/emulated/0/Download/5a4c80c817a17b44b3d5529b738e563b.jpg",
"width": 1136,
},
------------------------------
//editor.js
import React, { useState, useEffect } from 'react';
import { BackHandler } from 'react-native';
import * as MediaLibrary from 'expo-media-library';
import * as Sharing from 'expo-sharing';
import * as FileSystem from 'expo-file-system';
import * as ImageManipulator from 'expo-image-manipulator';
import { Image, ImageBackground, Alert, Dimensions, ScrollView } from 'react-native'
import {
Container, Header, Title, Content, Footer,
FooterTab, Button, Left, Right, Body, Icon, Text,
Accordion, Card, CardItem, Thumbnail, ListItem,
CheckBox, DatePicker, DeckSwiper, Fab, View,
Badge, Form, Item, Input, Label, Picker, Textarea,
Switch, Radio, Spinner, Tab, Tabs, TabHeading,
ScrollableTab, H1, H2, H3, Drawer, Toast
} from 'native-base';
import {
Ionicons, MaterialIcons, Foundation,
MaterialCommunityIcons, Octicons
} from '@expo/vector-icons';
import ReactNativeZoomableView from '@dudigital/react-native-zoomable-view/src/ReactNativeZoomableView';
import { Col, Row, Grid } from "react-native-easy-grid";
const picWidth = Math.round(Dimensions.get('window').width) / 2 - 20;
export default function Editor(props) {
const [gallaryPic, setgalleryPic] = useState([])
const [currentPic, setcurrentPic] = useState(0)
const [openGridView, setopenGridView] = useState(false)
const [selectedPic, setselectedPic] = useState([])
const [activeFab, setactiveFab] = useState(false)
useEffect(() => {
setcurrentPic(0)
getPictureFromGallary()
//if back key is pressed on the phone, close editor
BackHandler.addEventListener('hardwareBackPress', () => {
props.closeEditor()
});
//component will unmount
return () => { BackHandler.removeEventListener('hardwareBackPress') }
}, [])
getPictureFromGallary = async () => {
const pics = await MediaLibrary.getAssetsAsync({
sortBy: MediaLibrary.SortBy.modificationTime
})
//await setdefaultPicture(pics.assets[0].uri)
await setgalleryPic(pics.assets)
}
previousPic = () => {
const length = gallaryPic.length
if (length === 0) { return }
currentPic > 0 ? setcurrentPic(currentPic - 1) : setcurrentPic(length - 1)
}
nextPic = () => {
const length = gallaryPic.length
if (length === 0) { return }
currentPic < length - 1 ? setcurrentPic(currentPic + 1) : setcurrentPic(0)
}
confirmDelete = async (image) => {
//remove picture in android directory
await MediaLibrary.deleteAssetsAsync(image)
.then(async () => {
//remove picture in memory
await setgalleryPic(gallaryPic.filter(item => item.id !== image.id))
await setselectedPic(selectedPic.filter(id => id !== image.id))
//correct current pic index
previousPic()
nextPic()
})
.catch(error => {
alert(error)
});
}
togglePicHighlight = (id) => {
selectedPic.includes(id) ?
setselectedPic(selectedPic.filter(_id => { return _id !== id })) :
setselectedPic(selectedPic.concat(id))
}
picLongPress = (index) => {
setcurrentPic(index)
setopenGridView(false)
}
deletePics = () => {
let i = 0
selectedPic.forEach(async (id) => {
const removePic = gallaryPic.find(item => item.id === id)
//remove picture in android directory
await MediaLibrary.deleteAssetsAsync(removePic)
i++
//all pics deleted from android storage
if (i === selectedPic.length) {
//remove picture in memory
setgalleryPic(gallaryPic.filter(item => !selectedPic.includes(item.id)))
setselectedPic([])
setcurrentPic(0)
}
})
}
shareImage = async (image) => {
//copy image from memory to app cache, app cache will be automatically cleaned when storage is low
//sharing can't access 'file:///storage/emulated/0/DCIM/e3bda948-2407-47e1-b5cf-12e7705bf86e.jpg'
//have to copy to 'file:///data/user/0/host.exp.exponent/cache/ExperienceData/%2540chuanshuoge%252Fexpo-medialibrary/e3bda948-2407-47e1-b5cf-12e7705bf86e.jpg'
//console.log(FileSystem.cacheDirectory + image.filename)
await FileSystem.copyAsync({
from: image.uri,
to: FileSystem.cacheDirectory + image.filename
})
await Sharing.shareAsync(FileSystem.cacheDirectory + image.filename)
}
rotatePic = async (image) => {
//console.log(image)
const newImage = await ImageManipulator.manipulateAsync(
image.uri,
[{ rotate: 90 }],
{
compress: 1,
format: image.filename.includes('png') ? ImageManipulator.SaveFormat.PNG :
ImageManipulator.SaveFormat.JPEG
}
);
//console.log(newImage)
await MediaLibrary.saveToLibraryAsync(newImage.uri)
.then(async () => {
//display most recent modified image
await getPictureFromGallary()
await setcurrentPic(0)
})
.catch(error => {
alert(error)
});
//console.log(gallaryPic)
}
mirrorPic = async (image) => {
const newImage = await ImageManipulator.manipulateAsync(
image.uri,
[{ flip: ImageManipulator.FlipType.Horizontal }],
{
compress: 1,
format: image.filename.includes('png') ? ImageManipulator.SaveFormat.PNG :
ImageManipulator.SaveFormat.JPEG
}
);
await MediaLibrary.saveToLibraryAsync(newImage.uri)
.then(async () => {
await getPictureFromGallary()
await setcurrentPic(0)
})
.catch(error => {
alert(error)
});
}
return (
<Container>
<Header style={{ marginTop: 25 }}>
<Left>
<Button transparent onPress={() => props.closeEditor()}>
<Ionicons name='ios-arrow-round-back' size={40} color="white"></Ionicons>
</Button>
</Left>
<Body style={{ alignItems: 'center' }}>
<Title>Editor</Title>
</Body>
<Right>
<Button transparent iconRight onPress={() => setopenGridView(true)}>
<Text style={{ color: 'white' }}>Gallary{' '}</Text>
<Ionicons name='ios-arrow-round-forward' size={40} color="white"></Ionicons>
</Button>
</Right>
</Header>
<View style={{ flex: 1 }}>
{gallaryPic.length > 0 ?
<ImageBackground source={require('./assets/background.jpg')}
style={{ flex: 1, resizeMode: 'stretch' }}>
<ReactNativeZoomableView
maxZoom={1.5}
minZoom={0.5}
zoomStep={0.5}
initialZoom={1}
bindToBorders={true}>
<Image style={{ width: '100%', height: '100%', resizeMode: 'contain' }}
source={{ uri: gallaryPic[currentPic].uri }} />
</ReactNativeZoomableView>
</ImageBackground>
: null}
{openGridView ? null :
<Fab
active={activeFab}
direction="up"
containerStyle={{}}
style={{ backgroundColor: '#5067FF' }}
position="bottomRight"
onPress={() => activeFab ? setactiveFab(false) : setactiveFab(true)}>
<Foundation name='social-yelp'></Foundation>
<Button style={{ backgroundColor: '#34A34F' }}
onPress={() => shareImage(gallaryPic[currentPic])}>
<Foundation name='share' size={30} color="white"></Foundation>
</Button>
<Button style={{ backgroundColor: 'yellow' }}
onPress={() => rotatePic(gallaryPic[currentPic])}>
<MaterialCommunityIcons name='axis-x-rotate-clockwise' size={30} />
</Button>
<Button style={{ backgroundColor: 'pink' }}
onPress={() => mirrorPic(gallaryPic[currentPic])}>
<Octicons name='mirror' size={30} />
</Button>
<Button style={{ backgroundColor: 'purple' }}
onPress={() => alert('double tap on image to zoom')}>
<Foundation name='zoom-in' size={30} color="white"></Foundation>
</Button>
<Button style={{ backgroundColor: '#DD5144' }}
onPress={() =>
Alert.alert(
'Delete current picture?',
'',
[{
text: 'Cancel',
onPress: () => { },
style: 'cancel',
},
{ text: 'OK', onPress: () => confirmDelete(gallaryPic[currentPic]) },
],
{ cancelable: true },
)
}>
<MaterialIcons name='delete' size={30} color="white" />
</Button>
</Fab>
}
</View>
{openGridView ? null :
<Footer>
<FooterTab>
<Button active onPress={() => previousPic()}>
<Text>Previous</Text>
</Button>
<Button active onPress={() => nextPic()}>
<Text>Next</Text>
</Button>
</FooterTab>
</Footer>
}
{openGridView ?
<View style={{
position: 'absolute',
bottom: 0,
right: 0,
left: 0,
top: 0,
backgroundColor: 'white',
zIndex: 5,
}}>
<Header style={{ marginTop: 25 }}>
<Left>
<Button transparent onPress={() => setopenGridView(false)}>
<Ionicons name='ios-arrow-round-back' size={40} color="white"></Ionicons>
</Button>
</Left>
<Body style={{ alignItems: 'center' }}>
<Title>Gallary</Title>
</Body>
<Right>
{selectedPic.length > 0 ?
<Button transparent onPress={() =>
Alert.alert(
'Delete selected pictures?',
'',
[{
text: 'Cancel',
onPress: () => { },
style: 'cancel',
},
{ text: 'OK', onPress: () => deletePics() },
],
{ cancelable: true },
)}>
<MaterialIcons name='delete' size={40} color="white" />
</Button>
: null}
</Right>
</Header>
<ScrollView>
{gallaryPic.length > 0 ?
<Grid>
<Col>
{gallaryPic.map((item, index) => {
const { id, uri } = item
return (
index % 2 === 0 ?
<Row key={id} style={{ justifyContent: 'center', marginVertical: 5 }}>
<Button transparent
style={{
height: picWidth, width: picWidth, borderColor: 'green',
borderWidth: selectedPic.includes(id) ? 2 : 0
}}
onPress={() => togglePicHighlight(id)}
onLongPress={() => picLongPress(index)}>
<Image style={{ height: '100%', width: '100%', resizeMode: 'cover' }}
source={{ uri: uri }} />
</Button>
</Row>
: null)
})}
</Col>
<Col>
{gallaryPic.map((item, index) => {
const { id, uri } = item
return (
index % 2 === 1 ?
<Row key={id} style={{ justifyContent: 'center', marginVertical: 5 }}>
<Button transparent
style={{
height: picWidth, width: picWidth, borderColor: 'green',
borderWidth: selectedPic.includes(id) ? 2 : 0
}}
onPress={() => togglePicHighlight(id)}
onLongPress={() => picLongPress(index)}>
<Image style={{ height: '100%', width: '100%', resizeMode: 'cover' }}
source={{ uri: uri }} />
</Button>
</Row>
: null)
})}
</Col>
</Grid>
: null}
</ScrollView>
</View> : null}
</Container>
)
}
reference:
https://docs.expo.io/versions/latest/sdk/imagemanipulator/
https://docs.expo.io/versions/latest/sdk/media-library/
http://chuanshuoge2.blogspot.com/2020/02/expo-image-editor-3-share.html
No comments:
Post a Comment