diff --git a/hmns-app/client/app/(tabs)/index.tsx b/hmns-app/client/app/(tabs)/index.tsx index a293c0d..4b731d0 100644 --- a/hmns-app/client/app/(tabs)/index.tsx +++ b/hmns-app/client/app/(tabs)/index.tsx @@ -3,33 +3,43 @@ import { StyleSheet } from "react-native"; import EditScreenInfo from "../../components/EditScreenInfo"; import { Text, View } from "../../components/Themed"; -export default function TabOneScreen () { +export default function TabOneScreen() { return ( - Tab One - - + + Tab One + + + ); } - + const styles = StyleSheet.create({ container: { - alignItems: "center", flex: 1, - justifyContent: "center" + }, + header: { + alignItems: "center", + justifyContent: "center", + // Adjust the flex value or height as needed + flex: 0.45, }, separator: { height: 1, marginVertical: 30, - width: "80%" + width: "80%", }, title: { fontSize: 20, - fontWeight: "bold" - } + fontWeight: "bold", + }, + camera: { + flex: 1, // Take up all available space + }, }); + diff --git a/hmns-app/client/app/(tabs)/two.tsx b/hmns-app/client/app/(tabs)/two.tsx index 15d9ff8..88cb8d4 100644 --- a/hmns-app/client/app/(tabs)/two.tsx +++ b/hmns-app/client/app/(tabs)/two.tsx @@ -1,31 +1,17 @@ import { StyleSheet } from "react-native"; +import CameraComponent from "../../components/CameraComponent"; +import { View } from "../../components/Themed"; -import EditScreenInfo from "../../components/EditScreenInfo"; -import { Text, View } from "../../components/Themed"; - -export default function TabTwoScreen () { +export default function TabTwoScreen() { return ( - Tab Two - - + ); } const styles = StyleSheet.create({ container: { - alignItems: "center", flex: 1, - justifyContent: "center" - }, - separator: { - height: 1, - marginVertical: 30, - width: "80%" }, - title: { - fontSize: 20, - fontWeight: "bold" - } }); diff --git a/hmns-app/client/babel.config.js b/hmns-app/client/babel.config.js index a297226..241abcd 100644 --- a/hmns-app/client/babel.config.js +++ b/hmns-app/client/babel.config.js @@ -1,10 +1,10 @@ module.exports = function (api) { - api.cache(true); - return { - presets: ['babel-preset-expo'], - plugins: [ - // Required for expo-router - 'expo-router/babel', - ], - }; + api.cache(true); + return { + presets: ["babel-preset-expo"], + plugins: [ + // Required for expo-router + "expo-router/babel", + ], + }; }; diff --git a/hmns-app/client/components/.CameraComponent.tsx.swo b/hmns-app/client/components/.CameraComponent.tsx.swo new file mode 100644 index 0000000..36ef9b4 Binary files /dev/null and b/hmns-app/client/components/.CameraComponent.tsx.swo differ diff --git a/hmns-app/client/components/CameraComponent.tsx b/hmns-app/client/components/CameraComponent.tsx new file mode 100644 index 0000000..52a8a8a --- /dev/null +++ b/hmns-app/client/components/CameraComponent.tsx @@ -0,0 +1,241 @@ +import React, { useState, useEffect, useRef } from "react"; +import { View, Text, StyleSheet, TouchableOpacity, Image } from "react-native"; +import { Camera } from "expo-camera"; +// import { Ionicons } from "@expo/vector-icons"; +import * as MediaLibrary from "expo-media-library"; +import { StatusBar } from "react-native"; + +export default function CameraComponent() { + const [hasPermission, setHasPermission] = useState(null); + // // State to manage the type of camera (front or back) + // const [type, setType] = useState(Camera.Constants.Type.back); + // State to store the captured photo + const [photo, setPhoto] = useState(null); + // Reference to the camera component + const cameraRef = useRef(null); + // Function to show photo-taking tips + const showPhotoTips = () => { + alert("Photo-taking Tips:\n1. Keep your hands steady.\n2. Make sure there's enough light.\n3. Focus on the butterfly you want to search up before taking the photo."); + }; + const [confirmVisible, setConfirmVisible] = useState(false); + + // Request camera permissions when the component mounts + useEffect(() => { + (async () => { + // Request camera permission + const cameraStatus = await Camera.requestCameraPermissionsAsync(); + setHasPermission(cameraStatus.status === "granted"); + + // Request media library permission + const mediaLibraryStatus = await MediaLibrary.requestPermissionsAsync(); + if (mediaLibraryStatus.status !== "granted") { + alert("Sorry, we need camera roll permissions to save the image!"); + } + })(); + }, []); + + // // Capture a photo and save it to photo album + // const takePhoto = async () => { + // if (cameraRef.current) { + // const options = { quality: 0.5, base64: true, skipProcessing: true }; + // let newPhoto = await cameraRef.current.takePictureAsync(options); + // setPhoto(newPhoto); + + // // Save the photo to the gallery + // const asset = await MediaLibrary.createAssetAsync(newPhoto.uri); + // await MediaLibrary.createAlbumAsync('HMNS', asset, false); // Replace 'YourAppName' with your app's name or any other desired album name. + // } + // }; + const takePhoto = async () => { + if (cameraRef.current) { + const options = { quality: 0.5, base64: true, skipProcessing: true }; + const newPhoto = await cameraRef.current.takePictureAsync(options); + setPhoto(newPhoto); + setConfirmVisible(true); // Show the confirmation screen + } + }; + + // Retake the photo + const retakePhoto = () => { + setPhoto(null); + setConfirmVisible(false); // Hide the confirmation screen + }; + + // Accept the photo and save it + const acceptPhoto = async () => { + if (photo) { + try { + // Save the photo to the gallery here + const asset = await MediaLibrary.createAssetAsync(photo.uri); + await MediaLibrary.createAlbumAsync("YourAppName", asset, false); + + // Handle any operation after saving the photo, such as navigation or state reset + setConfirmVisible(false); // Hide confirmation screen + setPhoto(null); // Reset photo state + } catch (error) { + // Handle any errors here + console.error("Error saving photo", error); + alert("Error saving photo"); + } + } + }; + + // If confirmation screen should be visible, show the taken photo and options + if (confirmVisible && photo) { + return ( + + + + + Retake + + + Accept + + + + ); + } + + + + // If permissions are still being requested, return an empty view + if (hasPermission === null) { + return ; + } + // If camera access is denied, inform the user + if (hasPermission === false) { + return No access to camera; + } + + // Make sure to hide the status bar to provide a full-screen experience + StatusBar.setHidden(true); + + return ( + + + {/* Overlay the UI components on the camera preview */} + + + {/* You can add additional icons or information at the top */} + + + + {/* Placeholder for gallery button/icon */} + {photo && ( + + + + )} + + + + Tips + + + + + + ); +} + +const styles = StyleSheet.create({ + container: { + flex: 1, + backgroundColor: "black", + }, + camera: { + flex: 1, + justifyContent: "space-between", + }, + uiContainer: { + flex: 1, + backgroundColor: "transparent", + flexDirection: "column", + justifyContent: "space-between", + }, + topToolbar: { + flex: 0.1, + flexDirection: "row", + justifyContent: "space-between", + alignItems: "flex-start", + paddingTop: 20, + paddingHorizontal: 20, + }, + bottomToolbar: { + padding: 20, + flexDirection: "row", + justifyContent: "space-between", + alignItems: "center", + }, + captureButton: { + width: 70, + height: 70, + borderWidth: 4, + borderColor: "white", + borderRadius: 35, + backgroundColor: "white", + alignSelf: "center", + }, + flipButton: { + alignSelf: "center", + flex: 0.1, + alignItems: "center", + }, + galleryButton: { + width: 50, + height: 50, + borderRadius: 10, + alignSelf: "center", + overflow: "hidden", + }, + thumbnail: { + width: 50, + height: 50, + }, + tipsButton: { + // Style your button as needed + backgroundColor: "rgba(255, 255, 255, 0.5)", + borderRadius: 10, + padding: 10, + }, + tipsButtonText: { + color: "white", + fontSize: 18, + }, + confirmContainer: { + flex: 1, + justifyContent: "center", + alignItems: "center", + }, + confirmButtons: { + flexDirection: "row", + justifyContent: "space-around", + width: "100%", + padding: 20, + }, + confirmButton: { + flex: 1, + alignItems: "center", + justifyContent: "center", + marginHorizontal: 10, + paddingVertical: 10, + borderRadius: 5, + backgroundColor: "rgba(0,0,0,0.5)", + }, + preview: { + width: "100%", // You may need to adjust this + height: "80%", // You may need to adjust this + borderRadius: 4, + }, + confirmButtonText: { + // Style for the text inside your confirm buttons + color: "white", + fontSize: 18, + textAlign: "center", + }, +}); + diff --git a/hmns-app/client/components/EditScreenInfo.tsx b/hmns-app/client/components/EditScreenInfo.tsx index 12bebe9..6ab103a 100644 --- a/hmns-app/client/components/EditScreenInfo.tsx +++ b/hmns-app/client/components/EditScreenInfo.tsx @@ -1,77 +1,77 @@ -import React from 'react'; -import { StyleSheet } from 'react-native'; +import React from "react"; +import { StyleSheet } from "react-native"; -import Colors from '../constants/Colors'; -import { ExternalLink } from './ExternalLink'; -import { MonoText } from './StyledText'; -import { Text, View } from './Themed'; +import Colors from "../constants/Colors"; +import { ExternalLink } from "./ExternalLink"; +import { MonoText } from "./StyledText"; +import { Text, View } from "./Themed"; export default function EditScreenInfo({ path }: { path: string }) { - return ( - - - + return ( + + + Open up the code for this screen: - + - - {path} - + + {path} + - + Change any of the text, save the file, and your app will automatically update. - - + + - - - - Tap here if your app doesn't automatically update after making changes - - - - - ); + + + + Tap here if your app doesnt automatically update after making changes + + + + + ); } const styles = StyleSheet.create({ - getStartedContainer: { - alignItems: 'center', - marginHorizontal: 50, - }, - homeScreenFilename: { - marginVertical: 7, - }, - codeHighlightContainer: { - borderRadius: 3, - paddingHorizontal: 4, - }, - getStartedText: { - fontSize: 17, - lineHeight: 24, - textAlign: 'center', - }, - helpContainer: { - marginTop: 15, - marginHorizontal: 20, - alignItems: 'center', - }, - helpLink: { - paddingVertical: 15, - }, - helpLinkText: { - textAlign: 'center', - }, + getStartedContainer: { + alignItems: "center", + marginHorizontal: 50, + }, + homeScreenFilename: { + marginVertical: 7, + }, + codeHighlightContainer: { + borderRadius: 3, + paddingHorizontal: 4, + }, + getStartedText: { + fontSize: 17, + lineHeight: 24, + textAlign: "center", + }, + helpContainer: { + marginTop: 15, + marginHorizontal: 20, + alignItems: "center", + }, + helpLink: { + paddingVertical: 15, + }, + helpLinkText: { + textAlign: "center", + }, }); diff --git a/hmns-app/client/components/ExternalLink.tsx b/hmns-app/client/components/ExternalLink.tsx index c535be9..397a61e 100644 --- a/hmns-app/client/components/ExternalLink.tsx +++ b/hmns-app/client/components/ExternalLink.tsx @@ -1,28 +1,28 @@ -import { Link } from 'expo-router'; -import * as WebBrowser from 'expo-web-browser'; -import React from 'react'; -import { Platform } from 'react-native'; +import { Link } from "expo-router"; +import * as WebBrowser from "expo-web-browser"; +import React from "react"; +import { Platform } from "react-native"; export function ExternalLink( - props: Omit, 'href'> & { href: string } + props: Omit, "href"> & { href: string } ) { - return ( - { - if (Platform.OS !== 'web') { - // Prevent the default behavior of linking to the default browser on native. - e.preventDefault(); - // Open the link in an in-app browser. - WebBrowser.openBrowserAsync(props.href as string); - } - }} - /> - ); + return ( + { + if (Platform.OS !== "web") { + // Prevent the default behavior of linking to the default browser on native. + e.preventDefault(); + // Open the link in an in-app browser. + WebBrowser.openBrowserAsync(props.href as string); + } + }} + /> + ); } diff --git a/hmns-app/client/components/StyledText.tsx b/hmns-app/client/components/StyledText.tsx index aa3977c..b064091 100644 --- a/hmns-app/client/components/StyledText.tsx +++ b/hmns-app/client/components/StyledText.tsx @@ -1,5 +1,5 @@ -import { Text, TextProps } from './Themed'; +import { Text, TextProps } from "./Themed"; export function MonoText(props: TextProps) { - return ; + return ; } diff --git a/hmns-app/client/components/Themed.tsx b/hmns-app/client/components/Themed.tsx index 6399846..9b9e02a 100644 --- a/hmns-app/client/components/Themed.tsx +++ b/hmns-app/client/components/Themed.tsx @@ -3,42 +3,42 @@ * https://docs.expo.io/guides/color-schemes/ */ -import { Text as DefaultText, useColorScheme, View as DefaultView } from 'react-native'; +import { Text as DefaultText, useColorScheme, View as DefaultView } from "react-native"; -import Colors from '../constants/Colors'; +import Colors from "../constants/Colors"; type ThemeProps = { lightColor?: string; darkColor?: string; }; -export type TextProps = ThemeProps & DefaultText['props']; -export type ViewProps = ThemeProps & DefaultView['props']; +export type TextProps = ThemeProps & DefaultText["props"]; +export type ViewProps = ThemeProps & DefaultView["props"]; export function useThemeColor( - props: { light?: string; dark?: string }, - colorName: keyof typeof Colors.light & keyof typeof Colors.dark + props: { light?: string; dark?: string }, + colorName: keyof typeof Colors.light & keyof typeof Colors.dark ) { - const theme = useColorScheme() ?? 'light'; - const colorFromProps = props[theme]; - - if (colorFromProps) { - return colorFromProps; - } else { - return Colors[theme][colorName]; - } + const theme = useColorScheme() ?? "light"; + const colorFromProps = props[theme]; + + if (colorFromProps) { + return colorFromProps; + } else { + return Colors[theme][colorName]; + } } export function Text(props: TextProps) { - const { style, lightColor, darkColor, ...otherProps } = props; - const color = useThemeColor({ light: lightColor, dark: darkColor }, 'text'); + const { style, lightColor, darkColor, ...otherProps } = props; + const color = useThemeColor({ light: lightColor, dark: darkColor }, "text"); - return ; + return ; } export function View(props: ViewProps) { - const { style, lightColor, darkColor, ...otherProps } = props; - const backgroundColor = useThemeColor({ light: lightColor, dark: darkColor }, 'background'); + const { style, lightColor, darkColor, ...otherProps } = props; + const backgroundColor = useThemeColor({ light: lightColor, dark: darkColor }, "background"); - return ; + return ; } diff --git a/hmns-app/client/components/__tests__/StyledText-test.js b/hmns-app/client/components/__tests__/StyledText-test.js deleted file mode 100644 index f569ce8..0000000 --- a/hmns-app/client/components/__tests__/StyledText-test.js +++ /dev/null @@ -1,10 +0,0 @@ -import * as React from 'react'; -import renderer from 'react-test-renderer'; - -import { MonoText } from '../StyledText'; - -it(`renders correctly`, () => { - const tree = renderer.create(Snapshot test!).toJSON(); - - expect(tree).toMatchSnapshot(); -}); diff --git a/hmns-app/client/constants/Colors.ts b/hmns-app/client/constants/Colors.ts index 1c706c7..3a81a77 100644 --- a/hmns-app/client/constants/Colors.ts +++ b/hmns-app/client/constants/Colors.ts @@ -1,19 +1,19 @@ -const tintColorLight = '#2f95dc'; -const tintColorDark = '#fff'; +const tintColorLight = "#2f95dc"; +const tintColorDark = "#fff"; export default { - light: { - text: '#000', - background: '#fff', - tint: tintColorLight, - tabIconDefault: '#ccc', - tabIconSelected: tintColorLight, - }, - dark: { - text: '#fff', - background: '#000', - tint: tintColorDark, - tabIconDefault: '#ccc', - tabIconSelected: tintColorDark, - }, + light: { + text: "#000", + background: "#fff", + tint: tintColorLight, + tabIconDefault: "#ccc", + tabIconSelected: tintColorLight, + }, + dark: { + text: "#fff", + background: "#000", + tint: tintColorDark, + tabIconDefault: "#ccc", + tabIconSelected: tintColorDark, + }, }; diff --git a/hmns-app/client/metro.config.js b/hmns-app/client/metro.config.js index c5033a3..8fb7b51 100644 --- a/hmns-app/client/metro.config.js +++ b/hmns-app/client/metro.config.js @@ -1,10 +1,11 @@ +/* eslint-disable */ // Learn more https://docs.expo.io/guides/customizing-metro -const { getDefaultConfig } = require('expo/metro-config'); +const {getDefaultConfig} = require("expo/metro-config"); /** @type {import('expo/metro-config').MetroConfig} */ const config = getDefaultConfig(__dirname, { - // [Web-only]: Enables CSS support in Metro. - isCSSEnabled: true, + // [Web-only]: Enables CSS support in Metro. + isCSSEnabled: true, }); module.exports = config; diff --git a/hmns-app/client/package-lock.json b/hmns-app/client/package-lock.json index 45f9406..b5d63ef 100644 --- a/hmns-app/client/package-lock.json +++ b/hmns-app/client/package-lock.json @@ -13,8 +13,10 @@ "@react-navigation/native": "^6.0.2", "@types/react": "~18.2.14", "expo": "~49.0.13", + "expo-camera": "~13.4.4", "expo-font": "~11.4.0", "expo-linking": "~5.0.2", + "expo-media-library": "~15.4.1", "expo-router": "^2.0.0", "expo-splash-screen": "~0.20.5", "expo-status-bar": "~1.6.0", @@ -6545,6 +6547,17 @@ "url-parse": "^1.5.9" } }, + "node_modules/expo-camera": { + "version": "13.4.4", + "resolved": "https://registry.npmjs.org/expo-camera/-/expo-camera-13.4.4.tgz", + "integrity": "sha512-7k54APbpSulUDR2CrD5SrmKjCdfdg4tqKRpbBOKc2J2MIBHhunExU77435JDYSejHRY5bfRHZsEp3yKwR862uw==", + "dependencies": { + "invariant": "^2.2.4" + }, + "peerDependencies": { + "expo": "*" + } + }, "node_modules/expo-constants": { "version": "14.4.2", "resolved": "https://registry.npmjs.org/expo-constants/-/expo-constants-14.4.2.tgz", @@ -6612,6 +6625,14 @@ "url-parse": "^1.5.9" } }, + "node_modules/expo-media-library": { + "version": "15.4.1", + "resolved": "https://registry.npmjs.org/expo-media-library/-/expo-media-library-15.4.1.tgz", + "integrity": "sha512-lpWcT4pynWcE7TyNMUkLFH4YcueCTnq7UOJYRR0vewPEJeQXwRscka7zBtrhA+RSsJda013Q0615K+5lRLt14Q==", + "peerDependencies": { + "expo": "*" + } + }, "node_modules/expo-modules-autolinking": { "version": "1.5.1", "resolved": "https://registry.npmjs.org/expo-modules-autolinking/-/expo-modules-autolinking-1.5.1.tgz", diff --git a/hmns-app/client/package.json b/hmns-app/client/package.json index 10d3428..ed922ea 100644 --- a/hmns-app/client/package.json +++ b/hmns-app/client/package.json @@ -7,7 +7,8 @@ "android": "expo start --android", "ios": "expo start --ios", "web": "expo start --web", - "test": "jest --watchAll" + "test": "jest --watchAll", + "lint": "eslint --fix ." }, "dependencies": { "@apollo/client": "^3.8.5", @@ -29,6 +30,8 @@ "react-native-screens": "~3.22.0", "react-native-web": "~0.19.6", "typescript": "^5.1.3", - "@types/react": "~18.2.14" + "@types/react": "~18.2.14", + "expo-camera": "~13.4.4", + "expo-media-library": "~15.4.1" } }