aboutsummaryrefslogtreecommitdiffstats
path: root/components
diff options
context:
space:
mode:
Diffstat (limited to 'components')
-rw-r--r--components/Camera.js237
-rw-r--r--components/CameraSettings.js73
-rw-r--r--components/Gallery.js155
-rw-r--r--components/Home.js60
-rw-r--r--components/PhotoBig.js104
-rw-r--r--components/PhotoSmall.js55
-rw-r--r--components/RadioButton.js56
-rw-r--r--components/RadioGroup.js74
-rw-r--r--components/Settings.js83
-rw-r--r--components/Upload.js9
10 files changed, 906 insertions, 0 deletions
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: <Image style={styles.img} source={asset} /> })
+ 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 <View />;
+ } else if (hasCameraPermission === false) {
+ return <Text>brak dostępu do kamery</Text>;
+ } else {
+ return (
+ <View style={styles.cont}>
+ <ECamera.Camera
+ ref={ref => { 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}
+ >
+ <View style={{ flex: 1 }}>
+ <TouchableOpacity style={[styles.btn, styles.btn1]} onPress={this.changeCamera}><Image style={styles.btnIco} source={RevPng} /></TouchableOpacity>
+ <TouchableOpacity style={[styles.btn, styles.btn2]} onPress={takePhoto.bind(this)}><Text style={styles.btnTxt}>+</Text></TouchableOpacity>
+ <TouchableOpacity style={[styles.btn, styles.btn3]} onPress={editPhoto.bind(this)}><Image style={styles.btnIco2} source={PickPng} /></TouchableOpacity>
+ <TouchableOpacity style={[styles.btn, styles.btn4]} onPress={toggleSettings.bind(this)}><Image style={styles.btnIco2} source={SettPng} /></TouchableOpacity>
+ {this.state.img}
+ </View>
+ </ECamera.Camera>
+ <CameraSettings show={this.state.show} onChange={this.settChange} settings={this.state.settings} ratio={this.state.ratio} />
+ </View>
+ );
+ }
+ }
+}
+
+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 (
+ <Animated.ScrollView style={[styles.animatedView, { transform: [{ translateY: pos }] }]}>
+ <Text style={styles.h1}>SETTINGS</Text>
+ <RadioGroup onChange={(v, i) => onChange('whiteBalance', v, i)} chosen={cs['whiteBalance']} header={"WHITE BALANCE"} labels={props.settings.wb} key={Math.random() * 10e16} />
+ <RadioGroup onChange={(v, i) => onChange('flashMode', v, i)} chosen={cs['flashMode']} header={"FLASH MODE"} labels={props.settings.fm} key={Math.random() * 10e16} />
+ <RadioGroup onChange={(v, i) => onChange('ratio', v, i)} chosen={cs['ratio']} header={"RATIOS"} labels={props.settings.ra} key={Math.random() * 10e16} />
+ <RadioGroup onChange={(v, i) => onChange('pictureSize', v, i)} chosen={cs['pictureSize']} header={"PICTURE SIZE"} labels={ps} key={Math.random() * 10e16} />
+ </Animated.ScrollView>
+ );
+}
+
+
+
+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 (
+ <View style={styles.cont}>
+ <View style={styles.nav}>
+ <TouchableOpacity styles={styles.navBtn} onPress={this.gridList}><Text style={styles.navTxt}>GRID / LIST</Text></TouchableOpacity>
+ <TouchableOpacity styles={styles.navBtn} onPress={() => this.props.navigation.navigate("camera", { albumId: this.state.imgs[0].albumId })}><Text style={styles.navTxt}>OPEN{'\n'}CAMERA</Text></TouchableOpacity>
+ <TouchableOpacity styles={styles.navBtn} onPress={this.removeSelected}><Text style={styles.navTxt}>REMOVE{'\n'}SELECTED</Text></TouchableOpacity>
+ <TouchableOpacity styles={styles.navBtn} onPress={this.uploadSelected}><Text style={styles.navTxt}>UPLOAD{'\n'}SELECTED</Text></TouchableOpacity>
+ <TouchableOpacity styles={styles.navBtn} onPress={() => this.props.navigation.navigate("settings")}><Text style={styles.navTxt}>SETT</Text></TouchableOpacity>
+ </View>
+ <FlatList
+ // ListHeaderComponent={
+ // }
+ data={this.state.imgs}
+ renderItem={({ item }) => {
+ // console.log(item.uri)
+ return (
+ <TouchableOpacity
+ onPress={() => this.imgPress(item.uri, item.id)}
+ onLongPress={() => this.addToSelection(item.id)}
+ >
+ <PhotoSmall source={{ uri: item.uri }} size={{
+ width: this.state.imgWidth,
+ height: this.state.imgHeight,
+ }} style={{ opacity: this.state.selectedImgs.includes(item.id) ? 0.2 : 1 }} id={item.id} />
+ </TouchableOpacity>
+ )
+ }}
+ keyExtractor={p => p.id}
+ contentContainerStyle={styles.list}
+ numColumns={this.state.cols}
+ key={this.state.flatlistkey}
+ />
+ </View>
+ )
+ }
+}
+
+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 (
+ <View style={styles.cont}>
+ <TouchableOpacity style={styles.btn} onPress={() => this.props.navigation.navigate("gallery")}><Text style={styles.h1}>CAMERA{"\n"}SETTINGS App</Text></TouchableOpacity>
+ <Text style={styles.span}>Change camera white balance</Text>
+ <Text style={styles.span}>Change camera flash mode</Text>
+ <Text style={styles.span}>Change camera picture size</Text>
+ <Text style={styles.span}>Change camera camera ratio</Text>
+ </View>
+ )
+ }
+}
+
+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 (
+ <View style={[this.props.style, styles.cont]}>
+ <Image source={src} style={styles.img} />
+ <View style={styles.txts}>
+ {/* <TouchableOpacity onPress={() => } style={{ flex: 1 }}><Text style={styles.h1}>SHARE</Text></TouchableOpacity> */}
+ <TouchableOpacity onPress={share} style={{ flex: 1 }}><Text style={styles.h1}>SHARE</Text></TouchableOpacity>
+ <TouchableOpacity onPress={remove} style={{ flex: 1 }}><Text style={styles.h1}>DELETE</Text></TouchableOpacity>
+ <TouchableOpacity onPress={upload} style={{ flex: 1 }}><Text style={styles.h1}>UPLOAD</Text></TouchableOpacity>
+ </View>
+ </View>
+ )
+ }
+}
+
+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 (
+ <View style={styles.cont}>
+ <Image source={this.props.source} style={[this.props.style, styles.img, { width: this.props.size.width - MARGIN * 2, height: this.props.size.height }]} />
+ <Text style={[this.props.style, styles.txt]}>{this.props.id}</Text>
+ <Text style={[{ opacity: this.props.style.opacity === 1 ? 0 : 1 }, styles.plus]}>+</Text>
+ </View>
+ )
+ }
+}
+
+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 (
+ <View style={[this.state.contStyle, styles.contStyle]}>
+ <TouchableOpacity style={styles.outer} onPress={this.state.onClick}>
+ <View style={this.state.value ? styles.inner : {}}></View>
+ </TouchableOpacity>
+ <Text style={styles.label}>{this.state.label}</Text>
+ </View >
+ )
+ }
+}
+
+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(
+ <RadioButton key={Math.random() * 10e16} label={this.state.labels[i]} value={v} onClick={() => this.onClick(i)} />
+ )
+ }
+ // console.log("RadioGroup@render:", { state: this.state })
+ // <RadioButton key={this.state.baseKey + i} label={this.state.labels[i]} value={v} onClick={() => this.onClick(i)} />
+ return (
+ <View style={{ paddingBottom: this.state.header === "PICTURE SIZE" ? 30 : 10, }}>
+ <Text style={styles.h2y}>{this.state.header}</Text>
+ {radios}
+ </View>
+ )
+ }
+}
+
+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 (
+ <View style={styles.cont}>
+ <View><Text style={styles.h1}>Current IP: {ip}{"\n"}Current port: {port}</Text></View>
+ <TouchableOpacity onPress={this.toggleDialog}><Text style={styles.h1b}>New IP & PORT</Text></TouchableOpacity>
+ <TouchableOpacity onPress={() => WebBrowser.openBrowserAsync("http://" + this.state.ip)}><Text style={styles.h1b}>Open web app</Text></TouchableOpacity>
+ <Dialog.Container visible={this.state.dialogVisible}>
+ <Dialog.Title>New IP & PORT</Dialog.Title>
+ <Dialog.Input onChangeText={c => cip = c} label="IP" defaultValue={ip} />
+ <Dialog.Input onChangeText={c => cpo = c} label="PORT" defaultValue={port} />
+ <Dialog.Button label="Cancel" onPress={this.toggleDialog} />
+ <Dialog.Button label="Save" onPress={saveIP} />
+ </Dialog.Container>
+ </View >
+ )
+ }
+}
+
+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<object>} files - files to be uploaded
+ @return {Promise<boolean>} - true if upload succeded
+*/
+export default async function uploadFiles(files) {
+
+ return true;
+}
+