From 5cf454c6800d6bfe26e08c6b4393b82bff442b4d Mon Sep 17 00:00:00 2001 From: Maksymilian Jopek Date: Thu, 3 Mar 2022 22:57:17 +0100 Subject: Initial commit - v1.0.0 --- components/Camera.js | 237 +++++++++++++++++++++++++++++++++++++++++++ components/CameraSettings.js | 73 +++++++++++++ components/Gallery.js | 155 ++++++++++++++++++++++++++++ components/Home.js | 60 +++++++++++ components/PhotoBig.js | 104 +++++++++++++++++++ components/PhotoSmall.js | 55 ++++++++++ components/RadioButton.js | 56 ++++++++++ components/RadioGroup.js | 74 ++++++++++++++ components/Settings.js | 83 +++++++++++++++ components/Upload.js | 9 ++ 10 files changed, 906 insertions(+) create mode 100644 components/Camera.js create mode 100644 components/CameraSettings.js create mode 100644 components/Gallery.js create mode 100644 components/Home.js create mode 100644 components/PhotoBig.js create mode 100644 components/PhotoSmall.js create mode 100644 components/RadioButton.js create mode 100644 components/RadioGroup.js create mode 100644 components/Settings.js create mode 100644 components/Upload.js (limited to 'components') diff --git a/components/Camera.js b/components/Camera.js new file mode 100644 index 0000000..78d7ed0 --- /dev/null +++ b/components/Camera.js @@ -0,0 +1,237 @@ +import * as React from 'react'; +import { View, Text, StyleSheet, TouchableOpacity, Image, ToastAndroid } from 'react-native'; +import * as MediaLibrary from "expo-media-library"; +import * as ECamera from "expo-camera"; +import * as ImagePicker from 'expo-image-picker'; + +import RevPng from "../assets/reverse.png"; +import PickPng from "../assets/img-picker.png"; +import SettPng from "../assets/settings-icon.png"; +import { getIp } from "./Settings" +import CameraSettings from "./CameraSettings" + +export default class Camera extends React.Component { + constructor(props) { + super(props) + this.state = { + hasCameraPermission: null, + type: ECamera.Constants.Type.front, + camera: null, + img: <>, + show: false, + wb: null, + fm: null, + ra: null, + si: null, + whiteBalance: null, + flashMode: null, + ratio: null, + pictureSize: null, + settings: { + wb: [], + fm: [], + ra: [], + si: [], + }, + } + this.changeCamera = this.changeCamera.bind(this) + this.settChange = this.settChange.bind(this) + } + + async componentDidMount() { + let { status } = await ECamera.getCameraPermissionsAsync(); + this.setState({ hasCameraPermission: status == 'granted' }); + } + changeCamera() { + this.setState({ + type: this.state.type === ECamera.Constants.Type.back + ? ECamera.Constants.Type.front + : ECamera.Constants.Type.back, + }); + } + async cameraRdy(camera) { + if (this.state.wb) return; + const ratios = await camera.getSupportedRatiosAsync() + const sizes = {} + for (const r of ratios) { + sizes[r] = await camera.getAvailablePictureSizesAsync(r) + } + this.setState({ + wb: ECamera.Constants.WhiteBalance, + fm: ECamera.Constants.FlashMode, + ra: ratios, + si: sizes, + whiteBalance: ECamera.Constants.WhiteBalance[0], + flashMode: ECamera.Constants.FlashMode[0], + ratio: ratios[0], + pictureSize: sizes[ratios[0]][0], + settings: { + wb: Object.keys(ECamera.Constants.WhiteBalance), + fm: Object.keys(ECamera.Constants.FlashMode), + ra: ratios, + si: sizes, + }, + }) + console.log("settings", { ratios, sizes }) + } + async settChange(which, nVal) { + console.log("Camera@settChange: ", { which, nVal }) + const ns = {} + ns[which] = nVal + this.setState(ns) + } + + render() { + let camera = null; + async function takePhoto() { + if (camera) { + let foto = await camera.takePictureAsync(); + let asset = await MediaLibrary.createAssetAsync(foto.uri); // domyślnie zapisuje w folderze DCIM + this.setState({ img: }) + setTimeout(() => this.setState({ img: <> }), 4000); + ToastAndroid.showWithGravity( + 'The photo has been taken', + ToastAndroid.SHORT, + ToastAndroid.CENTER + ); + } + } + async function editPhoto() { + if (camera) { + let result = await ImagePicker.launchImageLibraryAsync({ + mediaTypes: ImagePicker.MediaTypeOptions.All, + allowsEditing: true, + aspect: [4, 3], + quality: 1, + }); + if (!result.cancelled) { + const uri = result.uri; + const body = new FormData(); + body.append('photo', { + uri: uri, + type: 'image/jpeg', + name: uri.split('/').pop(), + }); + + console.log(body) + const url = await getIp() + "/upload" + await fetch(url, { + method: "POST", + body + }).catch(e => console.log(e, url)) + Alert.alert("Alert", "Galllery - file uploaded and saved!"); + } + } + } + function toggleSettings() { + this.setState({ show: !this.state.show }) + } + const { hasCameraPermission } = this.state; // podstawienie zmiennej ze state + + if (hasCameraPermission == null) { + return ; + } else if (hasCameraPermission === false) { + return brak dostępu do kamery; + } else { + return ( + + { camera = ref }} + style={{ flex: 1 }} + type={this.state.type} + onCameraReady={() => this.cameraRdy(camera)} + ratio={this.state.ratio} + whiteBalance={this.state.whiteBalance} + pictureSize={this.state.pictureSize} + flashMode={this.state.flashMode} + key={this.state.flashMode} + > + + + + + + + {this.state.img} + + + + + ); + } + } +} + +const styles = StyleSheet.create({ + cont: { + flex: 1, + }, + btn: { + backgroundColor: "rgba(0, 0, 0, 0.6)", + borderRadius: 100, + position: "absolute", + flex: 1, + justifyContent: 'center', + alignItems: 'center', + bottom: 20, + }, + btn1: { + left: 30, + width: 70, + height: 70, + }, + btn2: { + left: "37%", + width: 120, + height: 120, + }, + btn3: { + right: 60, + width: 70, + height: 70, + }, + btn4: { + bottom: 80, + right: 20, + width: 70, + height: 70, + }, + btnIco: { + width: 50, + height: 50, + }, + btnIco2: { + width: 42, + height: 42, + }, + btnTxt: { + fontSize: 160, + marginTop: -55, + color: "#EB1F63", + }, + img: { + position: "absolute", + top: "15%", + left: "40%", + width: 90, + height: 160, + borderColor: "#EB1F63", + borderWidth: 5, + }, + nav: { + flexDirection: "row", + margin: 10, + }, + navTxt: { + fontSize: 26, + margin: 5, + }, + navBtn: { + flex: 1 + }, + list: { + margin: 9, + paddingBottom: 10, + // height: "100%", + }, +}) + diff --git a/components/CameraSettings.js b/components/CameraSettings.js new file mode 100644 index 0000000..741da28 --- /dev/null +++ b/components/CameraSettings.js @@ -0,0 +1,73 @@ +import * as React from 'react'; +import { Animated, StyleSheet, View, Text, Button } from "react-native"; + +import RadioGroup from "./RadioGroup" + +export default function CameraSettings(props) { + const [pos, _setPos] = React.useState(new Animated.Value(800)) + const [cs, setCs] = React.useState({ whiteBalance: 0, flashMode: 0, ratio: 0, pictureSize: 0 }) + const [ps, setPs] = React.useState(props.settings.si[props.settings.ra[0]] ?? "a") + React.useEffect(() => { + if (ps === "a") setPs(props.settings.si[props.settings.ra[0]] ?? "a") + }) + let toPos = 100; + console.log("props.settings", props.settings) + // console.log("props.settings.si[props.settings.ra[0]]", props.settings.si[props.settings.ra[0]]) + if (props.show) toPos = 0; else toPos = 800 + // console.log("props@CameraSettings", props) + // console.log("state@CameraSettings", { pos, cs, ps }) + + //animacja + Animated.spring(pos, { + toValue: toPos, + velocity: 1, + tension: 0, + friction: 10, + useNativeDriver: true, + }).start(); + + function onChange(n, v, i) { + if (n === "ratio") setPs(props.settings.si[v]) + const ncs = {} + ncs[n] = i + setCs({ ...cs, ...ncs }) + props.onChange(n, v) + } + + // console.log(`CameraSettings: bef_render: chosen={${cs['pictureSize']}} labels={${ps}}`) + return ( + + SETTINGS + onChange('whiteBalance', v, i)} chosen={cs['whiteBalance']} header={"WHITE BALANCE"} labels={props.settings.wb} key={Math.random() * 10e16} /> + onChange('flashMode', v, i)} chosen={cs['flashMode']} header={"FLASH MODE"} labels={props.settings.fm} key={Math.random() * 10e16} /> + onChange('ratio', v, i)} chosen={cs['ratio']} header={"RATIOS"} labels={props.settings.ra} key={Math.random() * 10e16} /> + onChange('pictureSize', v, i)} chosen={cs['pictureSize']} header={"PICTURE SIZE"} labels={ps} key={Math.random() * 10e16} /> + + ); +} + + + +const styles = StyleSheet.create({ + animatedView: { + position: "absolute", + bottom: 0, + left: 0, + right: 0, + backgroundColor: "rgba(0, 0, 0, 0.3)", + padding: 20, + height: 765, + width: 220, + overflow: "scroll", + paddingBottom: 150, + // marginBottom: 30, + }, + h1: { + fontSize: 30, + color: "white", + fontWeight: "bold", + marginBottom: 10, + textAlign: "left", + }, +}); + diff --git a/components/Gallery.js b/components/Gallery.js new file mode 100644 index 0000000..c97f8bd --- /dev/null +++ b/components/Gallery.js @@ -0,0 +1,155 @@ +import * as React from 'react'; +import { View, Text, StyleSheet, TouchableOpacity, FlatList, Dimensions, Alert } from 'react-native'; +import * as MediaLibrary from "expo-media-library"; + +import PhotoSmall from "./PhotoSmall" +import { getIp } from "./Settings" + +const w = Dimensions.get("window").width +// const h = Dimensions.get("window").height +const IMG_SQUARE_WIDTH = (w - 18) / 5; +const IMG_SQUARE_HEIGHT = IMG_SQUARE_WIDTH + 20; + +export default class Gallery extends React.Component { + constructor(props) { + super(props) + this.state = { + imgs: [], + cols: 5, + imgWidth: IMG_SQUARE_WIDTH, + imgHeight: IMG_SQUARE_HEIGHT, + flatlistkey: 0, + selectedImgs: [], + } + this.gridList = this.gridList.bind(this) + this.addToSelection = this.addToSelection.bind(this) + this.imgPress = this.imgPress.bind(this) + this.getImgs = this.getImgs.bind(this) + this.removeSelected = this.removeSelected.bind(this) + this.uploadSelected = this.uploadSelected.bind(this) + } + + async getImgs() { + let photos = await MediaLibrary.getAssetsAsync({ + first: 40, // ilość pobranych assetów + mediaType: 'photo', // typ pobieranych danych, photo jest domyślne + sortBy: "creationTime", + }) + console.log("ASSET", photos.assets[0]) + this.setState({ imgs: photos.assets, flatlistkey: this.state.flatlistkey + 1 }) + } + componentDidMount() { + this.props.navigation.addListener('focus', this.getImgs) + this.getImgs() + } + + gridList() { + this.setState({ + cols: this.state.cols === 5 ? 1 : 5, + imgWidth: this.state.imgWidth === IMG_SQUARE_WIDTH ? w - 18 : IMG_SQUARE_WIDTH, + imgHeight: this.state.imgWidth === IMG_SQUARE_WIDTH ? IMG_SQUARE_HEIGHT + 30 : IMG_SQUARE_HEIGHT, + flatlistkey: this.state.flatlistkey + 1, + }) + } + addToSelection(id) { + if (this.state.selectedImgs.includes(id)) { + this.setState({ + selectedImgs: this.state.selectedImgs.filter(i => i !== id) + }) + } else { + this.setState({ + selectedImgs: [...this.state.selectedImgs, id] + }) + } + } + imgPress(uri, id) { + if (this.state.selectedImgs.length) + this.addToSelection(id) + else + this.props.navigation.navigate("photoBig", { source: { uri }, id }) + } + async removeSelected() { + await MediaLibrary.deleteAssetsAsync(this.state.selectedImgs); + this.setState({ selectedImgs: [] }) + this.getImgs(); + } + async uploadSelected() { + if (this.state.selectedImgs.length === 0) return; + const photos = this.state.imgs.filter(i => this.state.selectedImgs.includes(i.id)); + console.log(photos) + const body = new FormData(); + for (const p of photos) { + body.append('photo', { + uri: p.uri, + type: 'image/jpeg', + name: p.uri.split('/').pop(), + }) + } + + const url = await getIp() + "/upload" + await fetch(url, { + method: "POST", + body + }) + Alert.alert("Alert", "Galllery - files uploaded and saved!"); + } + + render() { + return ( + + + GRID / LIST + this.props.navigation.navigate("camera", { albumId: this.state.imgs[0].albumId })}>OPEN{'\n'}CAMERA + REMOVE{'\n'}SELECTED + UPLOAD{'\n'}SELECTED + this.props.navigation.navigate("settings")}>SETT + + { + // console.log(item.uri) + return ( + this.imgPress(item.uri, item.id)} + onLongPress={() => this.addToSelection(item.id)} + > + + + ) + }} + keyExtractor={p => p.id} + contentContainerStyle={styles.list} + numColumns={this.state.cols} + key={this.state.flatlistkey} + /> + + ) + } +} + +const styles = StyleSheet.create({ + cont: { + }, + nav: { + flexDirection: "row", + margin: 10, + }, + navTxt: { + fontSize: w / 26, + margin: 5, + }, + navBtn: { + flex: 1 + }, + list: { + margin: 9, + paddingBottom: 110, + // height: "100%", + // marginBottom: -20, + }, +}) diff --git a/components/Home.js b/components/Home.js new file mode 100644 index 0000000..fb857c5 --- /dev/null +++ b/components/Home.js @@ -0,0 +1,60 @@ +import * as React from 'react'; +import { View, Text, StyleSheet, TouchableOpacity } from 'react-native'; +import * as MediaLibrary from "expo-media-library"; +import * as ECamera from "expo-camera"; + +export default class Home extends React.Component { + constructor(props) { + super(props) + this.state = {} + } + + async componentDidMount() { + const { status } = await MediaLibrary.requestPermissionsAsync(); + if (status !== 'granted') { + alert('brak uprawnień do czytania image-ów z galerii') + } + const status2 = (await ECamera.requestCameraPermissionsAsync()).status; + console.log(status2) + if (status2 !== 'granted') { + alert('brak uprawnień do wykonywania zdjec') + } + } + + render() { + return ( + + this.props.navigation.navigate("gallery")}>CAMERA{"\n"}SETTINGS App + Change camera white balance + Change camera flash mode + Change camera picture size + Change camera camera ratio + + ) + } +} + +const styles = StyleSheet.create({ + cont: { + flexDirection: "column", + marginTop: 10, + paddingTop: 10, + backgroundColor: "#EB1F63", + justifyContent: "center", + alignItems: "center", + height: "100%", + }, + btn: { + margin: 10, + }, + h1: { + color: "white", + fontSize: 55, + fontWeight: "bold", + textAlign: "center", + }, + span: { + color: "white", + fontSize: 22, + } +}) diff --git a/components/PhotoBig.js b/components/PhotoBig.js new file mode 100644 index 0000000..8f26b62 --- /dev/null +++ b/components/PhotoBig.js @@ -0,0 +1,104 @@ +import * as React from 'react'; +import { View, Text, StyleSheet, TouchableOpacity, Image, Dimensions, Alert } from 'react-native'; +// import * as ImageManipulator from "expo-image-manipulator"; +import * as MediaLibrary from "expo-media-library"; +import * as FileSystem from "expo-file-system"; +import { shareAsync } from 'expo-sharing'; + +import { getIp } from "./Settings" + +const w = Dimensions.get("window").width +const h = Dimensions.get("window").height + +export default class PhotoBig extends React.Component { + constructor(props) { + super(props) + this.state = {} + } + + async componentDidMount() { + } + + render() { + const src = this.props.route.params.source; + // (async () => console.log(await isAvailableAsync(), src.uri.replace(/storage\/.*?\//, '')))() + const share = async () => { + let assetUriParts = src.uri.split("/"); + let assetName = assetUriParts[assetUriParts.length - 1]; + let uri = `${FileSystem.documentDirectory}/${assetName}`; + console.log('uri', uri) + await FileSystem.copyAsync({ + from: src.uri, + to: uri, + }); + + // Share the image from the uri that you copied it to + await shareAsync(uri); + await FileSystem.deleteAsync(uri) + // shareAsync(src.uri, {}) + } + const remove = async () => { + await MediaLibrary.deleteAssetsAsync(this.props.route.params.id); + this.props.navigation.goBack(); + } + + const upload = async () => { + const uri = this.props.route.params.source.uri; + const body = new FormData(); + body.append('photo', { + uri: uri, + type: 'image/jpeg', + name: uri.split('/').pop(), + }); + + console.log(body) + const url = await getIp() + "/upload" + await fetch(url, { + method: "POST", + body + }).catch(e => console.log(e, url)) + Alert.alert("Alert", "Galllery - file uploaded and saved!"); + } + return ( + + + + {/* } style={{ flex: 1 }}>SHARE */} + SHARE + DELETE + UPLOAD + + + ) + } +} + +const styles = StyleSheet.create({ + cont: { + flexDirection: "column", + paddingTop: 20, + backgroundColor: "black", + // backgroundColor: "#EB1F63", + justifyContent: "center", + alignItems: "center", + height: "100%", + }, + img: { + width: w * 0.9, + height: h * 0.65, + borderRadius: 15, + }, + h1: { + color: "white", + fontSize: w / 15, + fontWeight: "bold", + }, + txts: { + flex: 1, + flexDirection: "row", + justifyContent: "center", + alignItems: "center", + marginLeft: w / 15, + }, +}) + diff --git a/components/PhotoSmall.js b/components/PhotoSmall.js new file mode 100644 index 0000000..52f86d1 --- /dev/null +++ b/components/PhotoSmall.js @@ -0,0 +1,55 @@ +import * as React from 'react'; +import { View, Text, StyleSheet, Image } from 'react-native'; +// import * as MediaLibrary from "expo-media-library"; + +const MARGIN = 3; + +export default class PhotoSmall extends React.Component { + constructor(props) { + super(props) + this.state = {} + } + + async componentDidMount() { + } + + render() { + return ( + + + {this.props.id} + + + + ) + } +} + +const styles = StyleSheet.create({ + cont: { + flex: 1, + flexDirection: "column", + margin: MARGIN, + justifyContent: "center", + alignItems: "center", + }, + img: { + borderRadius: 15, + flex: 1, + }, + txt: { + position: "absolute", + bottom: 10, + right: 10, + color: "white", + fontWeight: "bold", + }, + plus: { + position: "absolute", + alignSelf: "center", + fontWeight: "100", + fontSize: 100, + color: "#EB1F63", + }, +}) + + diff --git a/components/RadioButton.js b/components/RadioButton.js new file mode 100644 index 0000000..4c178e2 --- /dev/null +++ b/components/RadioButton.js @@ -0,0 +1,56 @@ +import * as React from 'react'; +import { StyleSheet, View, TouchableOpacity, Text } from "react-native"; + +export default class RadioButton extends React.Component { + constructor(props) { + super(props); + this.state = { + value: props.value ?? true, + contStyle: props.style ?? {}, + onClick: props.onClick, + label: props.label, + } + } + + render() { + return ( + + + + + {this.state.label} + + ) + } +} + +const styles = StyleSheet.create({ + contStyle: { + flexDirection: "row", + marginTop: 10, + }, + label: { + marginLeft: 15, + marginTop: 3, + fontSize: 17, + fontWeight: "bold", + color: "white", + }, + outer: { + borderColor: "#EB1F63", + borderWidth: 2, + borderRadius: 20, + width: 30, + height: 30, + }, + inner: { + borderColor: "#EB1F63", + borderWidth: 10, + borderRadius: 20, + width: 20, + height: 20, + top: 3, + left: 3, + }, +}) + diff --git a/components/RadioGroup.js b/components/RadioGroup.js new file mode 100644 index 0000000..9829fcd --- /dev/null +++ b/components/RadioGroup.js @@ -0,0 +1,74 @@ +import * as React from 'react'; +import { StyleSheet, View, Text } from "react-native"; + +import RadioButton from "./RadioButton" + +export default class RadioGroup extends React.Component { + constructor(props) { + super(props); + this.state = { + count: props.labels.length, + labels: props.labels, + default: props.default ?? 0, + header: props.header, + // radios, + values: [], + baseKey: Number.MIN_SAFE_INTEGER, + chosen: props.chosen, + } + const values = [] + for (let j = 0; j < this.state.count; j++) { + values.push(j === this.state.chosen) + } + this.state = { + ...this.state, + baseKey: this.state.baseKey + this.state.count + 1, + values, + } + } + + componentDidMount() { + // this.onClick(this.state.default) + } + + onClick(i) { + const values = [] + for (let j = 0; j < this.state.count; j++) { + values.push(j === i) + } + // console.log("RadioGroup@onClick", { values }) + this.setState({ + baseKey: this.state.baseKey + this.state.count + 1, + values, + // chosen: i, + }, () => this.props.onChange(this.state.labels[i], i) + ) + } + + render() { + // if (this.state.header === "PICTURE SIZE") console.log("Radio Group, Picture size: ", this.state) + const radios = [] + for (const [i, v] of this.state.values.entries()) { + radios.push( + this.onClick(i)} /> + ) + } + // console.log("RadioGroup@render:", { state: this.state }) + // this.onClick(i)} /> + return ( + + {this.state.header} + {radios} + + ) + } +} + +const styles = StyleSheet.create({ + h2y: { + fontSize: 20, + color: "yellow", + fontWeight: "bold", + textAlign: "right", + }, +}) diff --git a/components/Settings.js b/components/Settings.js new file mode 100644 index 0000000..813b7c5 --- /dev/null +++ b/components/Settings.js @@ -0,0 +1,83 @@ +import * as React from 'react'; +import { View, Text, StyleSheet, TouchableOpacity } from 'react-native'; +import * as SecureStore from 'expo-secure-store'; +import Dialog from "react-native-dialog"; +import * as WebBrowser from 'expo-web-browser'; + +// import * as ImageManipulator from "expo-image-manipulator"; + +export default class Settings extends React.Component { + constructor(props) { + super(props) + this.state = { ip: ":", dialogVisible: false } + + this.toggleDialog = this.toggleDialog.bind(this) + } + + async componentDidMount() { + this.setState({ ip: await getIp(true) }) + } + + toggleDialog() { + this.setState({ dialogVisible: !this.state.dialogVisible }) + } + + render() { + const [ip, port] = this.state.ip.split(":"); + let [cip, cpo] = [ip, port] + const saveIP = async () => { + const ip = cip + ":" + cpo + await setIp(ip) + this.setState({ ip }) + this.toggleDialog() + } + return ( + + Current IP: {ip}{"\n"}Current port: {port} + New IP & PORT + WebBrowser.openBrowserAsync("http://" + this.state.ip)}>Open web app + + New IP & PORT + cip = c} label="IP" defaultValue={ip} /> + cpo = c} label="PORT" defaultValue={port} /> + + + + + ) + } +} + +export async function getIp(skipProtocol = false) { + return (skipProtocol ? "" : "http://") + (await SecureStore.getItemAsync("ip") ?? "192.168.0.118:8000") +} +export async function setIp(ip) { + await SecureStore.setItemAsync("ip", ip) +} + +const styles = StyleSheet.create({ + cont: { + flexDirection: "column", + backgroundColor: "black", + justifyContent: "space-evenly", + alignItems: "center", + height: "100%", + }, + h1: { + color: "white", + fontSize: 23, + // height: 60, + }, + h1b: { + color: "white", + fontSize: 25, + fontWeight: "bold", + // height: 32, + }, + span: { + color: "white", + fontSize: 22, + }, +}) + + diff --git a/components/Upload.js b/components/Upload.js new file mode 100644 index 0000000..357d46b --- /dev/null +++ b/components/Upload.js @@ -0,0 +1,9 @@ +/** + @param {Array} files - files to be uploaded + @return {Promise} - true if upload succeded +*/ +export default async function uploadFiles(files) { + + return true; +} + -- cgit v1.3.1