From 23cc71a3a3c8dffb87701642f3a374aeacc6cc9e Mon Sep 17 00:00:00 2001 From: Ty Fiero Date: Thu, 2 May 2024 17:44:51 -0700 Subject: [PATCH] The Refactor --- software/source/clients/ios/README.md | 6 +- .../clients/ios/react-native/assets/qr.png | Bin 13745 -> 0 bytes .../ios/react-native/src/screens/Main.tsx | 257 +++++------------- .../react-native/src/utils/RecordButton.ts | 180 ------------ .../react-native/src/utils/RecordButton.tsx | 151 ++++++++++ .../react-native/src/utils/useSoundEffect.ts | 15 +- 6 files changed, 220 insertions(+), 389 deletions(-) delete mode 100644 software/source/clients/ios/react-native/assets/qr.png delete mode 100644 software/source/clients/ios/react-native/src/utils/RecordButton.ts create mode 100644 software/source/clients/ios/react-native/src/utils/RecordButton.tsx diff --git a/software/source/clients/ios/README.md b/software/source/clients/ios/README.md index 2f65352..64ffeaf 100644 --- a/software/source/clients/ios/README.md +++ b/software/source/clients/ios/README.md @@ -1,6 +1,6 @@ # iOS/Android Client -***WORK IN PROGRESS*** +**_WORK IN PROGRESS_** This repository contains the source code for the 01 iOS/Android app. Work in progress, we will continue to improve this application to get it working properly. @@ -9,10 +9,11 @@ Feel free to improve this and make a pull request! If you want to run it on your own, you will need to install Expo Go on your mobile device. ## Setup Instructions + Follow the **[software setup steps](https://github.com/OpenInterpreter/01?tab=readme-ov-file#software)** in the main repo's README first before you read this ```shell -cd software/source/clients/ios/react-native # cd into `react-native` +cd software/source/clients/mobile/react-native # cd into `react-native` npm install # install dependencies npx expo start # start local development server ``` @@ -20,6 +21,7 @@ npx expo start # start local development server In **Expo Go** select _Scan QR code_ to scan the QR code produced by the `npx expo start` command ## Using the App + ```shell poetry run 01 --mobile # exposes QR code for 01 Light server ``` diff --git a/software/source/clients/ios/react-native/assets/qr.png b/software/source/clients/ios/react-native/assets/qr.png deleted file mode 100644 index 33cf7e07f5b56ae93e8acc2df296da0ae76c5b96..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 13745 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 diff --git a/software/source/clients/ios/react-native/src/screens/Main.tsx b/software/source/clients/ios/react-native/src/screens/Main.tsx index ecccadd..f0136dc 100644 --- a/software/source/clients/ios/react-native/src/screens/Main.tsx +++ b/software/source/clients/ios/react-native/src/screens/Main.tsx @@ -1,15 +1,19 @@ import React, { useState, useEffect, useCallback, useRef } from "react"; -import { View, Text, TouchableOpacity, StyleSheet, BackHandler, Image } from "react-native"; +import { + View, + Text, + TouchableOpacity, + StyleSheet, + BackHandler, +} from "react-native"; import * as FileSystem from "expo-file-system"; -import { AVPlaybackStatus, AVPlaybackStatusSuccess, Audio } from "expo-av"; +import { Audio } from "expo-av"; import { polyfill as polyfillEncoding } from "react-native-polyfill-globals/src/encoding"; import { create } from "zustand"; -import useStore from "../utils/state"; import { Animated } from "react-native"; -import * as Haptics from "expo-haptics"; import useSoundEffect from "../utils/useSoundEffect"; import RecordButton from "../utils/RecordButton"; -import { useNavigation } from "@react-navigation/native"; +import { useNavigation } from "@react-navigation/core"; interface MainProps { route: { @@ -45,6 +49,8 @@ const Main: React.FC = ({ route }) => { const [connectionStatus, setConnectionStatus] = useState("Connecting..."); const [ws, setWs] = useState(null); + const [wsUrl, setWsUrl] = useState(""); + const [rescan, setRescan] = useState(false); const [isPressed, setIsPressed] = useState(false); const [recording, setRecording] = useState(null); const addToQueue = useAudioQueueStore((state) => state.addToQueue); @@ -64,13 +70,12 @@ const Main: React.FC = ({ route }) => { const navigation = useNavigation(); const backgroundColor = backgroundColorAnim.interpolate({ inputRange: [0, 1], - outputRange: ["black", "white"], // Change as needed + outputRange: ["black", "white"], }); const buttonBackgroundColor = backgroundColorAnim.interpolate({ inputRange: [0, 1], - outputRange: ["white", "black"], // Inverse of the container + outputRange: ["white", "black"], }); - const constructTempFilePath = async (buffer: string) => { try { await dirExists(); @@ -107,13 +112,8 @@ const Main: React.FC = ({ route }) => { } const playNextAudio = useCallback(async () => { - // console.log( - // `in playNextAudio audioQueue is ${audioQueue.length} and sound is ${sound}` - //); - if (audioQueue.length > 0 && sound == null) { const uri = audioQueue.shift() as string; - // console.log("load audio from", uri); try { const { sound: newSound } = await Audio.Sound.createAsync({ uri }); @@ -126,7 +126,7 @@ const Main: React.FC = ({ route }) => { playNextAudio(); } } else { - // console.log("audioQueue is empty or sound is not null"); + // audioQueue is empty or sound is not null return; } }, [audioQueue, sound, soundUriMap]); @@ -144,6 +144,21 @@ const Main: React.FC = ({ route }) => { [sound, soundUriMap, playNextAudio] ); + useEffect(() => { + const backAction = () => { + navigation.navigate("Home"); // Always navigate back to Home + return true; // Prevent default action + }; + + // Add event listener for hardware back button on Android + const backHandler = BackHandler.addEventListener( + "hardwareBackPress", + backAction + ); + + return () => backHandler.remove(); + }, [navigation]); + useEffect(() => { if (audioQueue.length > 0 && !sound) { playNextAudio(); @@ -155,14 +170,13 @@ const Main: React.FC = ({ route }) => { useEffect(() => { let websocket: WebSocket; try { - console.log("Connecting to WebSocket at " + scannedData); + // console.log("Connecting to WebSocket at " + scannedData); + setWsUrl(scannedData); websocket = new WebSocket(scannedData); websocket.binaryType = "blob"; websocket.onopen = () => { setConnectionStatus(`Connected`); - // setConnectionStatus(`Connected to ${scannedData}`); - console.log("WebSocket connected"); }; websocket.onmessage = async (e) => { @@ -170,15 +184,11 @@ const Main: React.FC = ({ route }) => { const message = JSON.parse(e.data); if (message.content && message.type == "audio") { - console.log("✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅ Audio message"); - const buffer = message.content; - // console.log(buffer.length); if (buffer && buffer.length > 0) { const filePath = await constructTempFilePath(buffer); if (filePath !== null) { addToQueue(filePath); - // console.log("audio file written to", filePath); } else { console.error("Failed to create file path"); } @@ -198,7 +208,6 @@ const Main: React.FC = ({ route }) => { websocket.onclose = () => { setConnectionStatus("Disconnected."); - console.log("WebSocket disconnected"); }; setWs(websocket); @@ -212,170 +221,41 @@ const Main: React.FC = ({ route }) => { websocket.close(); } }; - }, [scannedData]); - - useEffect(() => { - console.log("Permission Response:", permissionResponse); - if (permissionResponse?.status !== "granted") { - console.log("Requesting permission.."); - requestPermission(); - } - }, []); - - const startRecording = useCallback(async () => { - if (recording) { - console.log("A recording is already in progress."); - return; - } - - try { - console.log("🌶️🌶️🌶️🌶️🌶️🌶️🌶️🌶️🌶️🌶️"); - - console.log(permissionResponse); - - if ( - permissionResponse !== null && - permissionResponse.status !== `granted` - ) { - console.log("Requesting permission.."); - await requestPermission(); - } - - await Audio.setAudioModeAsync({ - allowsRecordingIOS: true, - playsInSilentModeIOS: true, - }); - - console.log("Starting recording.."); - const newRecording = new Audio.Recording(); - await newRecording.prepareToRecordAsync( - Audio.RecordingOptionsPresets.HIGH_QUALITY - ); - await newRecording.startAsync(); - - setRecording(newRecording); - } catch (err) { - console.error("Failed to start recording", err); - } - }, []); - - const stopRecording = useCallback(async () => { - console.log("Stopping recording.."); - - if (recording) { - await recording.stopAndUnloadAsync(); - await Audio.setAudioModeAsync({ - allowsRecordingIOS: false, - }); - const uri = recording.getURI(); - // console.log("recording uri at ", uri); - setRecording(null); - - if (ws && uri) { - const response = await fetch(uri); - // console.log("fetched audio file", response); - const blob = await response.blob(); - - const reader = new FileReader(); - reader.readAsArrayBuffer(blob); - reader.onloadend = () => { - const audioBytes = reader.result; - if (audioBytes) { - ws.send(audioBytes); - const audioArray = new Uint8Array(audioBytes as ArrayBuffer); - const decoder = new TextDecoder("utf-8"); - // console.log( - // "sent audio bytes to WebSocket", - // decoder.decode(audioArray).slice(0, 50) - // ); - } - }; - } - } - }, [recording]); - - const toggleRecording = (shouldPress: boolean) => { - Animated.timing(backgroundColorAnim, { - toValue: shouldPress ? 1 : 0, - duration: 400, - useNativeDriver: false, // 'backgroundColor' does not support native driver - }).start(); - Animated.timing(buttonBackgroundColorAnim, { - toValue: shouldPress ? 1 : 0, - duration: 400, - useNativeDriver: false, // 'backgroundColor' does not support native driver - }).start(); - }; - - useEffect(() => { - const backAction = () => { - navigation.navigate('Home'); // Always navigate back to Home - return true; // Prevent default action - }; - - // Add event listener for hardware back button on Android - const backHandler = BackHandler.addEventListener( - 'hardwareBackPress', - backAction - ); - - return () => backHandler.remove(); - }, [navigation]); - + }, [scannedData, rescan]); return ( - {/* { - console.log("hi!"); - - navigation.navigate("Camera"); - }} - > - - - - */} - {/* */} - - {connectionStatus} - + { - playPip(); - setIsPressed(true); - toggleRecording(true); // Pass true when pressed - startRecording(); - Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Heavy); - }} - onPressOut={() => { - playPop(); - setIsPressed(false); - toggleRecording(false); // Pass false when released - stopRecording(); - Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Heavy); + style={styles.statusButton} + onPress={() => { + setRescan(!rescan); }} > - - {/* - Record - */} - + {connectionStatus} + @@ -418,27 +298,14 @@ const styles = StyleSheet.create({ paddingTop: 50, }, - button: { - width: 100, - height: 100, - borderRadius: 50, - justifyContent: "center", - alignItems: "center", - }, - buttonTextDefault: { - color: "black", - fontSize: 16, - }, - buttonTextRecording: { - color: "white", - fontSize: 16, - }, statusText: { + fontSize: 12, + fontWeight: "bold", + }, + statusButton: { position: "absolute", bottom: 20, alignSelf: "center", - fontSize: 12, - fontWeight: "bold", }, }); diff --git a/software/source/clients/ios/react-native/src/utils/RecordButton.ts b/software/source/clients/ios/react-native/src/utils/RecordButton.ts deleted file mode 100644 index 30f6192..0000000 --- a/software/source/clients/ios/react-native/src/utils/RecordButton.ts +++ /dev/null @@ -1,180 +0,0 @@ -import React, { useState, useEffect, useCallback, useRef } from "react"; -import { View, Text, TouchableOpacity, StyleSheet, Image, Touchable } from "react-native"; -import * as FileSystem from "expo-file-system"; -import { AVPlaybackStatus, AVPlaybackStatusSuccess, Audio } from "expo-av"; -import { create } from "zustand"; -import useStore from "../utils/state"; -import { Animated } from "react-native"; -import * as Haptics from "expo-haptics"; -import useSoundEffect from "../utils/useSoundEffect"; - -import { useNavigation } from "@react-navigation/native"; - -interface RecordButtonProps { - playPip: () => void; - playPop: () => void; - recording: Audio.Recording | null; - setRecording: (recording: Audio.Recording | null) => void; - ws: WebSocket | null; - backgroundColorAnim: Animated.Value; - buttonBackgroundColorAnim: Animated.Value; - setIsPressed: (isPressed: boolean) => void; -} - - -const styles = StyleSheet.create({ - container: { - flex: 1, - position: "relative", - }, - middle: { - flex: 1, - justifyContent: "center", - alignItems: "center", - padding: 10, - position: "relative", - }, - circle: { - width: 100, - height: 100, - borderRadius: 50, - justifyContent: "center", - alignItems: "center", - }, - qr: { - position: "absolute", - top: 30, - left: 10, - padding: 10, - zIndex: 100, - }, - icon: { - height: 40, - width: 40, - }, - topBar: { - height: 40, - backgroundColor: "#000", - paddingTop: 50, - }, - - button: { - width: 100, - height: 100, - borderRadius: 50, - justifyContent: "center", - alignItems: "center", - }, - buttonTextDefault: { - color: "black", - fontSize: 16, - }, - buttonTextRecording: { - color: "white", - fontSize: 16, - }, - statusText: { - position: "absolute", - bottom: 20, - alignSelf: "center", - fontSize: 12, - fontWeight: "bold", - }, - }); - - -const RecordButton = ({ playPip, playPop, recording, setRecording, ws, backgroundColorAnim, buttonBackgroundColorAnim, setIsPressed}: RecordButtonProps) => { - const [permissionResponse, requestPermission] = Audio.usePermissions(); - - useEffect(() => { - console.log("Permission Response:", permissionResponse); - if (permissionResponse?.status !== "granted") { - console.log("Requesting permission.."); - requestPermission(); - } - }, []); - - const startRecording = useCallback(async () => { - if (recording) { - console.log("A recording is already in progress."); - return; - } - - try { - console.log("🌶️🌶️🌶️🌶️🌶️🌶️🌶️🌶️🌶️🌶️"); - - console.log(permissionResponse); - - if ( - permissionResponse !== null && - permissionResponse.status !== `granted` - ) { - console.log("Requesting permission.."); - await requestPermission(); - } - - await Audio.setAudioModeAsync({ - allowsRecordingIOS: true, - playsInSilentModeIOS: true, - }); - - console.log("Starting recording.."); - const newRecording = new Audio.Recording(); - await newRecording.prepareToRecordAsync( - Audio.RecordingOptionsPresets.HIGH_QUALITY - ); - await newRecording.startAsync(); - - setRecording(newRecording); - } catch (err) { - console.error("Failed to start recording", err); - } - }, []); - - const stopRecording = useCallback(async () => { - console.log("Stopping recording.."); - - if (recording) { - await recording.stopAndUnloadAsync(); - await Audio.setAudioModeAsync({ - allowsRecordingIOS: false, - }); - const uri = recording.getURI(); - // console.log("recording uri at ", uri); - setRecording(null); - - if (ws && uri) { - const response = await fetch(uri); - // console.log("fetched audio file", response); - const blob = await response.blob(); - - const reader = new FileReader(); - reader.readAsArrayBuffer(blob); - reader.onloadend = () => { - const audioBytes = reader.result; - if (audioBytes) { - ws.send(audioBytes); - } - }; - } - } - }, [recording]); - - const toggleRecording = (shouldPress: boolean) => { - Animated.timing(backgroundColorAnim, { - toValue: shouldPress ? 1 : 0, - duration: 400, - useNativeDriver: false, // 'backgroundColor' does not support native driver - }).start(); - Animated.timing(buttonBackgroundColorAnim, { - toValue: shouldPress ? 1 : 0, - duration: 400, - useNativeDriver: false, // 'backgroundColor' does not support native driver - }).start(); - }; - - return ( - ); -}; - -export default RecordButton; diff --git a/software/source/clients/ios/react-native/src/utils/RecordButton.tsx b/software/source/clients/ios/react-native/src/utils/RecordButton.tsx new file mode 100644 index 0000000..ffdaeb0 --- /dev/null +++ b/software/source/clients/ios/react-native/src/utils/RecordButton.tsx @@ -0,0 +1,151 @@ +import React, { useEffect, useCallback } from "react"; +import { TouchableOpacity, StyleSheet } from "react-native"; +import { Audio } from "expo-av"; +import { Animated } from "react-native"; +import * as Haptics from "expo-haptics"; + +interface RecordButtonProps { + playPip: () => void; + playPop: () => void; + recording: Audio.Recording | null; + setRecording: (recording: Audio.Recording | null) => void; + ws: WebSocket | null; + buttonBackgroundColorAnim: Animated.Value; + backgroundColorAnim: Animated.Value; + backgroundColor: Animated.AnimatedInterpolation; + buttonBackgroundColor: Animated.AnimatedInterpolation; + setIsPressed: (isPressed: boolean) => void; +} + +const styles = StyleSheet.create({ + circle: { + width: 100, + height: 100, + borderRadius: 50, + justifyContent: "center", + alignItems: "center", + }, + button: { + width: 100, + height: 100, + borderRadius: 50, + justifyContent: "center", + alignItems: "center", + }, +}); + +const RecordButton: React.FC = ({ + playPip, + playPop, + recording, + setRecording, + ws, + backgroundColorAnim, + buttonBackgroundColorAnim, + backgroundColor, + buttonBackgroundColor, + setIsPressed, +}: RecordButtonProps) => { + const [permissionResponse, requestPermission] = Audio.usePermissions(); + + useEffect(() => { + if (permissionResponse?.status !== "granted") { + requestPermission(); + } + }, []); + + const startRecording = useCallback(async () => { + if (recording) { + console.log("A recording is already in progress."); + return; + } + + try { + if ( + permissionResponse !== null && + permissionResponse.status !== `granted` + ) { + await requestPermission(); + } + + await Audio.setAudioModeAsync({ + allowsRecordingIOS: true, + playsInSilentModeIOS: true, + }); + + const newRecording = new Audio.Recording(); + await newRecording.prepareToRecordAsync( + Audio.RecordingOptionsPresets.HIGH_QUALITY + ); + await newRecording.startAsync(); + + setRecording(newRecording); + } catch (err) { + console.error("Failed to start recording", err); + } + }, []); + + const stopRecording = useCallback(async () => { + if (recording) { + await recording.stopAndUnloadAsync(); + await Audio.setAudioModeAsync({ + allowsRecordingIOS: false, + }); + const uri = recording.getURI(); + setRecording(null); + + if (ws && uri) { + const response = await fetch(uri); + const blob = await response.blob(); + + const reader = new FileReader(); + reader.readAsArrayBuffer(blob); + reader.onloadend = () => { + const audioBytes = reader.result; + if (audioBytes) { + ws.send(audioBytes); + } + }; + } + } + }, [recording]); + + const toggleRecording = (shouldPress: boolean) => { + Animated.timing(backgroundColorAnim, { + toValue: shouldPress ? 1 : 0, + duration: 400, + useNativeDriver: false, + }).start(); + Animated.timing(buttonBackgroundColorAnim, { + toValue: shouldPress ? 1 : 0, + duration: 400, + useNativeDriver: false, + }).start(); + }; + + return ( + { + playPip(); + setIsPressed(true); + toggleRecording(true); + startRecording(); + Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Heavy); + }} + onPressOut={() => { + playPop(); + setIsPressed(false); + toggleRecording(false); + stopRecording(); + Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Heavy); + }} + > + + + ); +}; + +export default RecordButton; diff --git a/software/source/clients/ios/react-native/src/utils/useSoundEffect.ts b/software/source/clients/ios/react-native/src/utils/useSoundEffect.ts index 250353c..5e73fec 100644 --- a/software/source/clients/ios/react-native/src/utils/useSoundEffect.ts +++ b/software/source/clients/ios/react-native/src/utils/useSoundEffect.ts @@ -1,20 +1,11 @@ import { useEffect, useState } from "react"; -import { Audio, InterruptionModeAndroid, InterruptionModeIOS } from "expo-av"; +import { Audio } from "expo-av"; -const useSoundEffect = (soundFile) => { - const [sound, setSound] = useState(null); // Explicitly set initial state to null +const useSoundEffect = (soundFile: any) => { + const [sound, setSound] = useState(null); // Explicitly set initial state to null useEffect(() => { const loadSound = async () => { - // await Audio.setAudioModeAsync({ - // staysActiveInBackground: true, - // shouldDuckAndroid: true, - // playThroughEarpieceAndroid: false, - // interruptionModeIOS: InterruptionModeIOS.DoNotMix, - // interruptionModeAndroid: InterruptionModeAndroid.DoNotMix, - // allowsRecordingIOS: false, - // playsInSilentModeIOS: true, - // }); const { sound: newSound } = await Audio.Sound.createAsync(soundFile); setSound(newSound); };