some iOS bug fixes and UI edits
This commit is contained in:
parent
cd7934b59a
commit
721603cff3
14 changed files with 2391 additions and 1111 deletions
|
|
@ -57,9 +57,11 @@ def fetchItem(store):
|
|||
'lowername': request.args['itemName'].lower(),
|
||||
'store': store['key'],
|
||||
})
|
||||
item = fetch.items[0]
|
||||
|
||||
if not item:
|
||||
if fetch.items:
|
||||
item = fetch.items[0]
|
||||
return item
|
||||
else:
|
||||
while (fetch.last is not None):
|
||||
fetch = itemsDB.fetch({
|
||||
'lowername': request.args['itemName'].lower(),
|
||||
|
|
@ -69,9 +71,7 @@ def fetchItem(store):
|
|||
|
||||
if fetch.items:
|
||||
item = fetch.items[0]
|
||||
break
|
||||
|
||||
return item
|
||||
return item
|
||||
|
||||
def fetchItems(store):
|
||||
""" Fetch all items from a given store """
|
||||
|
|
|
|||
|
|
@ -1,92 +1,46 @@
|
|||
import { useState, useRef, useEffect } from "react";
|
||||
import { useWindowDimensions, Alert, StyleSheet } from "react-native";
|
||||
import { useWindowDimensions, Platform } from "react-native";
|
||||
import { SafeAreaProvider, SafeAreaView } from "react-native-safe-area-context";
|
||||
import WebView, { WebViewMessageEvent } from "react-native-webview";
|
||||
import WebView from "react-native-webview";
|
||||
|
||||
import { useAssets } from "expo-asset";
|
||||
import * as Location from "expo-location";
|
||||
import { useNavigation, router } from "expo-router";
|
||||
import { useNavigation } from "expo-router";
|
||||
|
||||
import messageHandler from "./messageHandler";
|
||||
|
||||
export default function () {
|
||||
const [webViewKey, setWebViewKey] = useState(Math.random() * 10);
|
||||
const [assets] = useAssets([require("../assets/index.html")]);
|
||||
const [htmlString, setHtmlString] = useState<string>();
|
||||
const dimensions = useWindowDimensions();
|
||||
const webViewRef = useRef<WebView | null>();
|
||||
const navigation = useNavigation();
|
||||
|
||||
const messageHandler = (event: WebViewMessageEvent) => {
|
||||
if (typeof event.nativeEvent.data == "string") {
|
||||
const message = event.nativeEvent.data;
|
||||
if (message === "new pin start") {
|
||||
Alert.alert(
|
||||
"New Pin",
|
||||
`Please select location for new pin then press "OK"`,
|
||||
[
|
||||
{
|
||||
text: "OK",
|
||||
onPress: () => {
|
||||
// makes injectable javascript string
|
||||
const str = `window.placePin(); true()`;
|
||||
// passes string to webview - there it is handled by OpenLayers to change the current location
|
||||
webViewRef.current?.injectJavaScript(str);
|
||||
},
|
||||
},
|
||||
]
|
||||
);
|
||||
} else if (message.startsWith(`create@`)) {
|
||||
const coords = message.slice(7).split(",");
|
||||
router.push({
|
||||
pathname: "./store/new/[coords]",
|
||||
params: {
|
||||
coords: `${coords[0]}+${coords[1]}`,
|
||||
},
|
||||
});
|
||||
} else if (message.startsWith(`open@`)) {
|
||||
const coords = message.slice(5).split(",");
|
||||
router.push({
|
||||
pathname: "./store/[coords]",
|
||||
params: {
|
||||
coords: `${coords[0]}+${coords[1]}`,
|
||||
},
|
||||
});
|
||||
} else if (message.startsWith(`search@`)) {
|
||||
const chunks = message.slice(7).split(":");
|
||||
const coords = chunks[0].split(",");
|
||||
router.push({
|
||||
pathname: "./search/[slug]",
|
||||
params: {
|
||||
slug: `${coords[0]}+${coords[1]}+${chunks[1]}`,
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const getLocation = async () => {
|
||||
const { status } = await Location.requestForegroundPermissionsAsync();
|
||||
if (status !== "granted") return;
|
||||
|
||||
const location = await Location.getCurrentPositionAsync({
|
||||
distanceInterval: 0, // for IOS
|
||||
distanceInterval: 0,
|
||||
accuracy: Location.Accuracy.High,
|
||||
timeInterval: 3000, // for Android
|
||||
timeInterval: 3000,
|
||||
});
|
||||
// makes injectable javascript string
|
||||
const str = `window.passLocation(${location.coords.longitude}, ${location.coords.latitude}); true()`;
|
||||
// passes string to webview - there it is handled by OpenLayers to change the current location
|
||||
webViewRef.current?.injectJavaScript(str);
|
||||
|
||||
webViewRef.current?.injectJavaScript(
|
||||
`setTimeout(() => {window.passLocation(${location.coords.longitude}, ${location.coords.latitude})}, 50); true`
|
||||
);
|
||||
};
|
||||
|
||||
// refresh on navigating back to this page
|
||||
useEffect(() => {
|
||||
// remove header
|
||||
navigation.setOptions({ headerShown: false });
|
||||
const focusHandler = navigation.addListener("focus", () => {
|
||||
webViewRef.current?.reload();
|
||||
|
||||
return navigation.addListener("focus", () => {
|
||||
setWebViewKey(Math.random() * 10);
|
||||
getLocation();
|
||||
});
|
||||
return focusHandler;
|
||||
}, [navigation]);
|
||||
|
||||
// loads assets
|
||||
useEffect(() => {
|
||||
if (assets) {
|
||||
fetch(assets[0].localUri || "")
|
||||
|
|
@ -95,29 +49,23 @@ export default function () {
|
|||
}
|
||||
}, [assets]);
|
||||
|
||||
// exits if no map passed in
|
||||
if (!htmlString) {
|
||||
return <></>;
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
container: {
|
||||
flex: 1,
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
},
|
||||
});
|
||||
if (!htmlString) return <></>;
|
||||
|
||||
return (
|
||||
<SafeAreaProvider>
|
||||
<SafeAreaView style={styles.container}>
|
||||
<SafeAreaView
|
||||
style={{
|
||||
flex: 1,
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
}}
|
||||
>
|
||||
<WebView
|
||||
ref={(currentRef) => (webViewRef.current = currentRef)}
|
||||
ref={webViewRef}
|
||||
injectedJavascript=""
|
||||
source={{
|
||||
html: htmlString,
|
||||
}}
|
||||
javaScriptEnabled
|
||||
style={{
|
||||
width: dimensions.width,
|
||||
height: dimensions.height,
|
||||
|
|
@ -130,8 +78,13 @@ export default function () {
|
|||
containerStyle={{
|
||||
flex: 1,
|
||||
}}
|
||||
onMessage={messageHandler}
|
||||
onMessage={(event) => {
|
||||
messageHandler(event, webViewRef.current);
|
||||
}}
|
||||
webviewDebuggingEnabled={true}
|
||||
javaScriptEnabled={true}
|
||||
domStorageEnabled={true}
|
||||
key={webViewKey}
|
||||
/>
|
||||
</SafeAreaView>
|
||||
</SafeAreaProvider>
|
||||
|
|
|
|||
|
|
@ -83,6 +83,7 @@ export default function () {
|
|||
break;
|
||||
|
||||
case 500:
|
||||
console.log(res);
|
||||
Alert.alert(
|
||||
"Error!",
|
||||
"Backend server error. Please report to ak95@riseup.net"
|
||||
|
|
@ -108,6 +109,7 @@ export default function () {
|
|||
flex: 1,
|
||||
alignItems: "center",
|
||||
height: "100%",
|
||||
paddingBottom: "5%",
|
||||
}}
|
||||
>
|
||||
<Stack.Screen
|
||||
|
|
|
|||
58
react-native/app/messageHandler.ts
Normal file
58
react-native/app/messageHandler.ts
Normal file
|
|
@ -0,0 +1,58 @@
|
|||
import { Alert } from "react-native";
|
||||
import { WebViewMessageEvent } from "react-native-webview";
|
||||
import { router } from "expo-router";
|
||||
import WebView from "react-native-webview";
|
||||
|
||||
const messageHandler = (event: WebViewMessageEvent, element: WebView) => {
|
||||
if (typeof event.nativeEvent.data == "string") {
|
||||
const message = event.nativeEvent.data;
|
||||
|
||||
if (message === "new pin start") {
|
||||
return Alert.alert(
|
||||
"New Pin",
|
||||
`Please select location for new pin then press "OK"`,
|
||||
[
|
||||
{
|
||||
text: "OK",
|
||||
onPress: () => {
|
||||
element.injectJavaScript(`window.placePin(); true()`);
|
||||
},
|
||||
},
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
if (message.startsWith(`create@`)) {
|
||||
const coords = message.slice(7).split(",");
|
||||
return router.push({
|
||||
pathname: "./store/new/[coords]",
|
||||
params: {
|
||||
coords: `${coords[0]}+${coords[1]}`,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
if (message.startsWith(`open@`)) {
|
||||
const coords = message.slice(5).split(",");
|
||||
return router.push({
|
||||
pathname: "./store/[coords]",
|
||||
params: {
|
||||
coords: `${coords[0]}+${coords[1]}`,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
if (message.startsWith(`search@`)) {
|
||||
const chunks = message.slice(7).split(":");
|
||||
const coords = chunks[0].split(",");
|
||||
return router.push({
|
||||
pathname: "./search/[slug]",
|
||||
params: {
|
||||
slug: `${coords[0]}+${coords[1]}+${chunks[1]}`,
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
export default messageHandler;
|
||||
|
|
@ -8,22 +8,18 @@ import {
|
|||
Alert,
|
||||
} from "react-native";
|
||||
|
||||
import {
|
||||
useNavigation,
|
||||
Stack,
|
||||
useLocalSearchParams,
|
||||
router,
|
||||
} from "expo-router";
|
||||
import * as ImagePicker from "expo-image-picker";
|
||||
import { useNavigation, Stack, useLocalSearchParams } from "expo-router";
|
||||
|
||||
import * as FileSystem from "expo-file-system";
|
||||
|
||||
import { styled } from "nativewind";
|
||||
|
||||
import utmObj from "utm-latlng";
|
||||
import mime from "mime";
|
||||
|
||||
import StoreItem from "../../components/StoreItem";
|
||||
|
||||
import handleAddItem from "./handleAddItem";
|
||||
import toUTM from "./toUTM";
|
||||
import uploadImage from "./uploadImage";
|
||||
|
||||
declare function isNaN(x: string | number): boolean;
|
||||
|
||||
interface storeItemInterface {
|
||||
|
|
@ -43,16 +39,14 @@ export default function () {
|
|||
const [image, setImage] = useState(null);
|
||||
|
||||
const navigation = useNavigation();
|
||||
const utm = new utmObj();
|
||||
|
||||
const StyledText = styled(Text);
|
||||
|
||||
const coords = useLocalSearchParams().coords.toString().split("%2B");
|
||||
const lon = coords[0];
|
||||
const lat = coords[1];
|
||||
const utmCoords = utm.convertLatLngToUtm(lat, lon, 5);
|
||||
const utmCoords = toUTM(lat, lon);
|
||||
|
||||
// get store information
|
||||
const getStore = async () => {
|
||||
const res = await fetch(
|
||||
`${process.env.EXPO_PUBLIC_BACKEND_URL}/?` +
|
||||
|
|
@ -71,6 +65,7 @@ export default function () {
|
|||
const json = await res.json();
|
||||
setStoreName(json.name);
|
||||
setStoreKey(json.key.toString());
|
||||
const imageKey = json.key.toString();
|
||||
|
||||
const items = json.items.map((item: storeItemInterface, index: number) => {
|
||||
const date = new Date(item.lastUpdated.replace(" ", "T") + "Z");
|
||||
|
|
@ -88,71 +83,20 @@ export default function () {
|
|||
});
|
||||
setStoreItems(items);
|
||||
|
||||
const imageURL = `${process.env.EXPO_PUBLIC_BACKEND_URL}/img?imageKey=${storeKey}`;
|
||||
const imageURL = `${process.env.EXPO_PUBLIC_BACKEND_URL}/img?imageKey=${imageKey}`;
|
||||
await FileSystem.downloadAsync(
|
||||
imageURL,
|
||||
FileSystem.documentDirectory + storeKey
|
||||
FileSystem.documentDirectory + imageKey
|
||||
)
|
||||
.then(({ uri }) => {
|
||||
setImage(uri);
|
||||
})
|
||||
.catch(() => {});
|
||||
console.log(image);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
getStore();
|
||||
}, []);
|
||||
|
||||
const uploadImage = async () => {
|
||||
// No permissions request is necessary for launching the image library
|
||||
let result = await ImagePicker.launchImageLibraryAsync({
|
||||
mediaTypes: ImagePicker.MediaTypeOptions.All,
|
||||
allowsEditing: true,
|
||||
aspect: [4, 3],
|
||||
quality: 1,
|
||||
});
|
||||
|
||||
if (!result.canceled) {
|
||||
setImage(result.assets[0]);
|
||||
const formData = new FormData();
|
||||
formData.append("file", {
|
||||
uri: image.uri,
|
||||
type: mime.getType(image.uri),
|
||||
name: "file",
|
||||
} as unknown as File);
|
||||
const res = await fetch(
|
||||
`${process.env.EXPO_PUBLIC_BACKEND_URL}/?` +
|
||||
new URLSearchParams({
|
||||
storeKey: storeKey,
|
||||
}),
|
||||
{
|
||||
method: "PUT",
|
||||
body: formData,
|
||||
mode: "cors",
|
||||
redirect: "follow",
|
||||
}
|
||||
);
|
||||
if (!res.ok) {
|
||||
Alert.alert("Error!", "Server error! Please report to ak95@riseup.net");
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const handleAddItem = () => {
|
||||
router.push({
|
||||
pathname: "/item/new/[storeKey]",
|
||||
params: {
|
||||
storeKey: storeKey,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
// remove header
|
||||
const focusHandler = navigation.addListener("focus", () => {
|
||||
getStore();
|
||||
});
|
||||
return focusHandler;
|
||||
return navigation.addListener("focus", () => getStore());
|
||||
}, [navigation]);
|
||||
|
||||
return (
|
||||
|
|
@ -162,6 +106,7 @@ export default function () {
|
|||
flex: 1,
|
||||
alignItems: "center",
|
||||
height: "100%",
|
||||
paddingBottom: "5%",
|
||||
}}
|
||||
>
|
||||
<Stack.Screen
|
||||
|
|
@ -169,7 +114,13 @@ export default function () {
|
|||
title: storeName,
|
||||
}}
|
||||
/>
|
||||
<TouchableOpacity onPress={uploadImage}>
|
||||
<TouchableOpacity
|
||||
onPress={() => {
|
||||
uploadImage(storeKey).then((uri) => {
|
||||
setImage(uri);
|
||||
});
|
||||
}}
|
||||
>
|
||||
{image ? (
|
||||
<Image source={{ uri: image }} className="h-[33vh] w-[100vw]" />
|
||||
) : (
|
||||
|
|
@ -187,7 +138,7 @@ export default function () {
|
|||
<ScrollView style={{ flex: 1 }}>{storeItems}</ScrollView>
|
||||
<TouchableOpacity
|
||||
className="px-8 py-4 my-4 bg-green-700 rounded"
|
||||
onPress={handleAddItem}
|
||||
onPress={() => handleAddItem(storeKey)}
|
||||
>
|
||||
<StyledText className="text-white text-center text-2xl">
|
||||
Add
|
||||
|
|
|
|||
12
react-native/app/store/handleAddItem.ts
Normal file
12
react-native/app/store/handleAddItem.ts
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
import { router } from "expo-router";
|
||||
|
||||
const handleAddItem = (storeKey: string) => {
|
||||
router.push({
|
||||
pathname: "/item/new/[storeKey]",
|
||||
params: {
|
||||
storeKey: storeKey,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
export default handleAddItem;
|
||||
|
|
@ -10,11 +10,12 @@ import {
|
|||
Alert,
|
||||
} from "react-native";
|
||||
import { Stack, useLocalSearchParams, router } from "expo-router";
|
||||
import * as ImagePicker from "expo-image-picker";
|
||||
import utmObj from "utm-latlng";
|
||||
import { styled } from "nativewind";
|
||||
import mime from "mime";
|
||||
|
||||
import uploadImage from "./uploadImage";
|
||||
|
||||
declare function isNaN(x: string | number): boolean;
|
||||
|
||||
export default function () {
|
||||
|
|
@ -27,20 +28,6 @@ export default function () {
|
|||
|
||||
let { coords } = useLocalSearchParams();
|
||||
|
||||
const uploadImage = async () => {
|
||||
// No permissions request is necessary for launching the image library
|
||||
let result = await ImagePicker.launchImageLibraryAsync({
|
||||
mediaTypes: ImagePicker.MediaTypeOptions.All,
|
||||
allowsEditing: true,
|
||||
aspect: [4, 3],
|
||||
quality: 1,
|
||||
});
|
||||
|
||||
if (!result.canceled) {
|
||||
setImage(result.assets[0]);
|
||||
}
|
||||
};
|
||||
|
||||
const handleSubmit = async () => {
|
||||
coords = coords.split("%2B");
|
||||
const lon = coords[0];
|
||||
|
|
@ -170,6 +157,7 @@ export default function () {
|
|||
flex: 1,
|
||||
alignItems: "center",
|
||||
height: "100%",
|
||||
paddingBottom: "5%",
|
||||
}}
|
||||
>
|
||||
<Stack.Screen
|
||||
|
|
@ -177,7 +165,7 @@ export default function () {
|
|||
title: "New Store",
|
||||
}}
|
||||
/>
|
||||
<TouchableOpacity onPress={uploadImage}>
|
||||
<TouchableOpacity onPress={() => setImage(uploadImage())}>
|
||||
{image ? (
|
||||
<Image source={{ uri: image.uri }} className="h-[33vh] w-[100vw]" />
|
||||
) : (
|
||||
|
|
|
|||
17
react-native/app/store/new/uploadImage.ts
Normal file
17
react-native/app/store/new/uploadImage.ts
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
import * as ImagePicker from "expo-image-picker";
|
||||
|
||||
const uploadImage = async () => {
|
||||
// No permissions request is necessary for launching the image library
|
||||
let result = await ImagePicker.launchImageLibraryAsync({
|
||||
mediaTypes: ImagePicker.MediaTypeOptions.All,
|
||||
allowsEditing: true,
|
||||
aspect: [4, 3],
|
||||
quality: 1,
|
||||
});
|
||||
|
||||
if (!result.canceled) {
|
||||
return result.assets[0];
|
||||
}
|
||||
};
|
||||
|
||||
export default uploadImage;
|
||||
8
react-native/app/store/toUTM.js
vendored
Normal file
8
react-native/app/store/toUTM.js
vendored
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
import utmObj from "utm-latlng";
|
||||
|
||||
const toUTM = (lat, lon) => {
|
||||
const utm = new utmObj();
|
||||
return utm.convertLatLngToUtm(lat, lon, 5);
|
||||
};
|
||||
|
||||
export default toUTM;
|
||||
41
react-native/app/store/uploadImage.ts
Normal file
41
react-native/app/store/uploadImage.ts
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
import { Alert } from "react-native";
|
||||
import * as ImagePicker from "expo-image-picker";
|
||||
import mime from "mime";
|
||||
|
||||
const uploadImage = async (storeKey: string) => {
|
||||
// No permissions request is necessary for launching the image library
|
||||
let result = await ImagePicker.launchImageLibraryAsync({
|
||||
mediaTypes: ImagePicker.MediaTypeOptions.All,
|
||||
allowsEditing: true,
|
||||
aspect: [4, 3],
|
||||
quality: 1,
|
||||
});
|
||||
|
||||
if (!result.canceled) {
|
||||
const image = result.assets[0];
|
||||
const formData = new FormData();
|
||||
formData.append("file", {
|
||||
uri: image.uri,
|
||||
type: mime.getType(image.uri),
|
||||
name: "file",
|
||||
} as unknown as File);
|
||||
const res = await fetch(
|
||||
`${process.env.EXPO_PUBLIC_BACKEND_URL}/?` +
|
||||
new URLSearchParams({
|
||||
storeKey: storeKey,
|
||||
}),
|
||||
{
|
||||
method: "PUT",
|
||||
body: formData,
|
||||
mode: "cors",
|
||||
redirect: "follow",
|
||||
}
|
||||
);
|
||||
if (!res.ok) {
|
||||
Alert.alert("Error!", "Server error! Please report to ak95@riseup.net");
|
||||
}
|
||||
return image.uri;
|
||||
}
|
||||
};
|
||||
|
||||
export default uploadImage;
|
||||
File diff suppressed because one or more lines are too long
3097
react-native/package-lock.json
generated
3097
react-native/package-lock.json
generated
File diff suppressed because it is too large
Load diff
|
|
@ -11,6 +11,7 @@
|
|||
"dependencies": {
|
||||
"@andordavoti/react-native-timeago": "^0.0.14",
|
||||
"@expo/vector-icons": "^13.0.0",
|
||||
"@react-native-picker/picker": "2.4.10",
|
||||
"@react-navigation/native": "^6.0.2",
|
||||
"expo": "~49.0.18",
|
||||
"expo-asset": "^8.10.1",
|
||||
|
|
@ -30,20 +31,19 @@
|
|||
"nativewind": "^2.0.11",
|
||||
"react": "18.2.0",
|
||||
"react-dom": "18.2.0",
|
||||
"react-native": "0.72.6",
|
||||
"react-native": "0.73.1",
|
||||
"react-native-gesture-handler": "~2.12.0",
|
||||
"react-native-safe-area-context": "4.6.3",
|
||||
"react-native-screens": "~3.22.0",
|
||||
"react-native-web": "~0.19.6",
|
||||
"react-native-webview": "13.2.2",
|
||||
"utm-latlng": "^1.0.7",
|
||||
"@react-native-picker/picker": "2.4.10"
|
||||
"utm-latlng": "^1.0.7"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.20.0",
|
||||
"@types/react": "~18.2.14",
|
||||
"react-test-renderer": "18.2.0",
|
||||
"tailwindcss": "^3.3.2",
|
||||
"tailwindcss": "3.3.2",
|
||||
"typescript": "^5.1.3"
|
||||
},
|
||||
"overrides": {
|
||||
|
|
|
|||
7
react-native/src/index.d.ts
vendored
Normal file
7
react-native/src/index.d.ts
vendored
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
export {};
|
||||
|
||||
declare global {
|
||||
interface Window {
|
||||
passLocation: Function;
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Reference in a new issue