some iOS bug fixes and UI edits

This commit is contained in:
ak 2024-02-10 13:52:47 -08:00
parent cd7934b59a
commit 721603cff3
14 changed files with 2391 additions and 1111 deletions

View file

@ -57,9 +57,11 @@ def fetchItem(store):
'lowername': request.args['itemName'].lower(), 'lowername': request.args['itemName'].lower(),
'store': store['key'], '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): while (fetch.last is not None):
fetch = itemsDB.fetch({ fetch = itemsDB.fetch({
'lowername': request.args['itemName'].lower(), 'lowername': request.args['itemName'].lower(),
@ -69,8 +71,6 @@ def fetchItem(store):
if fetch.items: if fetch.items:
item = fetch.items[0] item = fetch.items[0]
break
return item return item
def fetchItems(store): def fetchItems(store):

View file

@ -1,92 +1,46 @@
import { useState, useRef, useEffect } from "react"; 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 { 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 { useAssets } from "expo-asset";
import * as Location from "expo-location"; import * as Location from "expo-location";
import { useNavigation, router } from "expo-router"; import { useNavigation } from "expo-router";
import messageHandler from "./messageHandler";
export default function () { export default function () {
const [webViewKey, setWebViewKey] = useState(Math.random() * 10);
const [assets] = useAssets([require("../assets/index.html")]); const [assets] = useAssets([require("../assets/index.html")]);
const [htmlString, setHtmlString] = useState<string>(); const [htmlString, setHtmlString] = useState<string>();
const dimensions = useWindowDimensions(); const dimensions = useWindowDimensions();
const webViewRef = useRef<WebView | null>(); const webViewRef = useRef<WebView | null>();
const navigation = useNavigation(); 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 getLocation = async () => {
const { status } = await Location.requestForegroundPermissionsAsync(); const { status } = await Location.requestForegroundPermissionsAsync();
if (status !== "granted") return; if (status !== "granted") return;
const location = await Location.getCurrentPositionAsync({ const location = await Location.getCurrentPositionAsync({
distanceInterval: 0, // for IOS distanceInterval: 0,
accuracy: Location.Accuracy.High, 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()`; webViewRef.current?.injectJavaScript(
// passes string to webview - there it is handled by OpenLayers to change the current location `setTimeout(() => {window.passLocation(${location.coords.longitude}, ${location.coords.latitude})}, 50); true`
webViewRef.current?.injectJavaScript(str); );
}; };
// refresh on navigating back to this page
useEffect(() => { useEffect(() => {
// remove header
navigation.setOptions({ headerShown: false }); navigation.setOptions({ headerShown: false });
const focusHandler = navigation.addListener("focus", () => {
webViewRef.current?.reload(); return navigation.addListener("focus", () => {
setWebViewKey(Math.random() * 10);
getLocation(); getLocation();
}); });
return focusHandler;
}, [navigation]); }, [navigation]);
// loads assets
useEffect(() => { useEffect(() => {
if (assets) { if (assets) {
fetch(assets[0].localUri || "") fetch(assets[0].localUri || "")
@ -95,29 +49,23 @@ export default function () {
} }
}, [assets]); }, [assets]);
// exits if no map passed in if (!htmlString) return <></>;
if (!htmlString) {
return <></>;
}
const styles = StyleSheet.create({
container: {
flex: 1,
alignItems: "center",
justifyContent: "center",
},
});
return ( return (
<SafeAreaProvider> <SafeAreaProvider>
<SafeAreaView style={styles.container}> <SafeAreaView
style={{
flex: 1,
alignItems: "center",
justifyContent: "center",
}}
>
<WebView <WebView
ref={(currentRef) => (webViewRef.current = currentRef)} ref={webViewRef}
injectedJavascript="" injectedJavascript=""
source={{ source={{
html: htmlString, html: htmlString,
}} }}
javaScriptEnabled
style={{ style={{
width: dimensions.width, width: dimensions.width,
height: dimensions.height, height: dimensions.height,
@ -130,8 +78,13 @@ export default function () {
containerStyle={{ containerStyle={{
flex: 1, flex: 1,
}} }}
onMessage={messageHandler} onMessage={(event) => {
messageHandler(event, webViewRef.current);
}}
webviewDebuggingEnabled={true} webviewDebuggingEnabled={true}
javaScriptEnabled={true}
domStorageEnabled={true}
key={webViewKey}
/> />
</SafeAreaView> </SafeAreaView>
</SafeAreaProvider> </SafeAreaProvider>

View file

@ -83,6 +83,7 @@ export default function () {
break; break;
case 500: case 500:
console.log(res);
Alert.alert( Alert.alert(
"Error!", "Error!",
"Backend server error. Please report to ak95@riseup.net" "Backend server error. Please report to ak95@riseup.net"
@ -108,6 +109,7 @@ export default function () {
flex: 1, flex: 1,
alignItems: "center", alignItems: "center",
height: "100%", height: "100%",
paddingBottom: "5%",
}} }}
> >
<Stack.Screen <Stack.Screen

View 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;

View file

@ -8,22 +8,18 @@ import {
Alert, Alert,
} from "react-native"; } from "react-native";
import { import { useNavigation, Stack, useLocalSearchParams } from "expo-router";
useNavigation,
Stack,
useLocalSearchParams,
router,
} from "expo-router";
import * as ImagePicker from "expo-image-picker";
import * as FileSystem from "expo-file-system"; import * as FileSystem from "expo-file-system";
import { styled } from "nativewind"; import { styled } from "nativewind";
import utmObj from "utm-latlng";
import mime from "mime";
import StoreItem from "../../components/StoreItem"; import StoreItem from "../../components/StoreItem";
import handleAddItem from "./handleAddItem";
import toUTM from "./toUTM";
import uploadImage from "./uploadImage";
declare function isNaN(x: string | number): boolean; declare function isNaN(x: string | number): boolean;
interface storeItemInterface { interface storeItemInterface {
@ -43,16 +39,14 @@ export default function () {
const [image, setImage] = useState(null); const [image, setImage] = useState(null);
const navigation = useNavigation(); const navigation = useNavigation();
const utm = new utmObj();
const StyledText = styled(Text); const StyledText = styled(Text);
const coords = useLocalSearchParams().coords.toString().split("%2B"); const coords = useLocalSearchParams().coords.toString().split("%2B");
const lon = coords[0]; const lon = coords[0];
const lat = coords[1]; const lat = coords[1];
const utmCoords = utm.convertLatLngToUtm(lat, lon, 5); const utmCoords = toUTM(lat, lon);
// get store information
const getStore = async () => { const getStore = async () => {
const res = await fetch( const res = await fetch(
`${process.env.EXPO_PUBLIC_BACKEND_URL}/?` + `${process.env.EXPO_PUBLIC_BACKEND_URL}/?` +
@ -71,6 +65,7 @@ export default function () {
const json = await res.json(); const json = await res.json();
setStoreName(json.name); setStoreName(json.name);
setStoreKey(json.key.toString()); setStoreKey(json.key.toString());
const imageKey = json.key.toString();
const items = json.items.map((item: storeItemInterface, index: number) => { const items = json.items.map((item: storeItemInterface, index: number) => {
const date = new Date(item.lastUpdated.replace(" ", "T") + "Z"); const date = new Date(item.lastUpdated.replace(" ", "T") + "Z");
@ -88,71 +83,20 @@ export default function () {
}); });
setStoreItems(items); 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( await FileSystem.downloadAsync(
imageURL, imageURL,
FileSystem.documentDirectory + storeKey FileSystem.documentDirectory + imageKey
) )
.then(({ uri }) => { .then(({ uri }) => {
setImage(uri); setImage(uri);
}) })
.catch(() => {}); .catch(() => {});
console.log(image);
}; };
useEffect(() => { useEffect(() => {
getStore(); return navigation.addListener("focus", () => 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;
}, [navigation]); }, [navigation]);
return ( return (
@ -162,6 +106,7 @@ export default function () {
flex: 1, flex: 1,
alignItems: "center", alignItems: "center",
height: "100%", height: "100%",
paddingBottom: "5%",
}} }}
> >
<Stack.Screen <Stack.Screen
@ -169,7 +114,13 @@ export default function () {
title: storeName, title: storeName,
}} }}
/> />
<TouchableOpacity onPress={uploadImage}> <TouchableOpacity
onPress={() => {
uploadImage(storeKey).then((uri) => {
setImage(uri);
});
}}
>
{image ? ( {image ? (
<Image source={{ uri: image }} className="h-[33vh] w-[100vw]" /> <Image source={{ uri: image }} className="h-[33vh] w-[100vw]" />
) : ( ) : (
@ -187,7 +138,7 @@ export default function () {
<ScrollView style={{ flex: 1 }}>{storeItems}</ScrollView> <ScrollView style={{ flex: 1 }}>{storeItems}</ScrollView>
<TouchableOpacity <TouchableOpacity
className="px-8 py-4 my-4 bg-green-700 rounded" className="px-8 py-4 my-4 bg-green-700 rounded"
onPress={handleAddItem} onPress={() => handleAddItem(storeKey)}
> >
<StyledText className="text-white text-center text-2xl"> <StyledText className="text-white text-center text-2xl">
Add Add

View 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;

View file

@ -10,11 +10,12 @@ import {
Alert, Alert,
} from "react-native"; } from "react-native";
import { Stack, useLocalSearchParams, router } from "expo-router"; import { Stack, useLocalSearchParams, router } from "expo-router";
import * as ImagePicker from "expo-image-picker";
import utmObj from "utm-latlng"; import utmObj from "utm-latlng";
import { styled } from "nativewind"; import { styled } from "nativewind";
import mime from "mime"; import mime from "mime";
import uploadImage from "./uploadImage";
declare function isNaN(x: string | number): boolean; declare function isNaN(x: string | number): boolean;
export default function () { export default function () {
@ -27,20 +28,6 @@ export default function () {
let { coords } = useLocalSearchParams(); 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 () => { const handleSubmit = async () => {
coords = coords.split("%2B"); coords = coords.split("%2B");
const lon = coords[0]; const lon = coords[0];
@ -170,6 +157,7 @@ export default function () {
flex: 1, flex: 1,
alignItems: "center", alignItems: "center",
height: "100%", height: "100%",
paddingBottom: "5%",
}} }}
> >
<Stack.Screen <Stack.Screen
@ -177,7 +165,7 @@ export default function () {
title: "New Store", title: "New Store",
}} }}
/> />
<TouchableOpacity onPress={uploadImage}> <TouchableOpacity onPress={() => setImage(uploadImage())}>
{image ? ( {image ? (
<Image source={{ uri: image.uri }} className="h-[33vh] w-[100vw]" /> <Image source={{ uri: image.uri }} className="h-[33vh] w-[100vw]" />
) : ( ) : (

View 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
View 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;

View 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

File diff suppressed because it is too large Load diff

View file

@ -11,6 +11,7 @@
"dependencies": { "dependencies": {
"@andordavoti/react-native-timeago": "^0.0.14", "@andordavoti/react-native-timeago": "^0.0.14",
"@expo/vector-icons": "^13.0.0", "@expo/vector-icons": "^13.0.0",
"@react-native-picker/picker": "2.4.10",
"@react-navigation/native": "^6.0.2", "@react-navigation/native": "^6.0.2",
"expo": "~49.0.18", "expo": "~49.0.18",
"expo-asset": "^8.10.1", "expo-asset": "^8.10.1",
@ -30,20 +31,19 @@
"nativewind": "^2.0.11", "nativewind": "^2.0.11",
"react": "18.2.0", "react": "18.2.0",
"react-dom": "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-gesture-handler": "~2.12.0",
"react-native-safe-area-context": "4.6.3", "react-native-safe-area-context": "4.6.3",
"react-native-screens": "~3.22.0", "react-native-screens": "~3.22.0",
"react-native-web": "~0.19.6", "react-native-web": "~0.19.6",
"react-native-webview": "13.2.2", "react-native-webview": "13.2.2",
"utm-latlng": "^1.0.7", "utm-latlng": "^1.0.7"
"@react-native-picker/picker": "2.4.10"
}, },
"devDependencies": { "devDependencies": {
"@babel/core": "^7.20.0", "@babel/core": "^7.20.0",
"@types/react": "~18.2.14", "@types/react": "~18.2.14",
"react-test-renderer": "18.2.0", "react-test-renderer": "18.2.0",
"tailwindcss": "^3.3.2", "tailwindcss": "3.3.2",
"typescript": "^5.1.3" "typescript": "^5.1.3"
}, },
"overrides": { "overrides": {

7
react-native/src/index.d.ts vendored Normal file
View file

@ -0,0 +1,7 @@
export {};
declare global {
interface Window {
passLocation: Function;
}
}