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(),
'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,8 +71,6 @@ def fetchItem(store):
if fetch.items:
item = fetch.items[0]
break
return item
def fetchItems(store):

View file

@ -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>

View file

@ -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

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,
} 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

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,
} 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]" />
) : (

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": {
"@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
View file

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