From ead115364e703ca2962a8939ad59560bc544d5da Mon Sep 17 00:00:00 2001 From: Alex Israelov Date: Wed, 26 Feb 2025 18:33:23 -0800 Subject: [PATCH 01/13] start error reporting --- .../SmartGlassesRepresentative.java | 2 +- .../android/app/src/main/AndroidManifest.xml | 3 + .../augmentos_manager/MainApplication.kt | 2 + .../logcapture/LogcatCaptureModule.java | 53 ++++++ .../logcapture/LogcatCapturePackage.java | 24 +++ augmentos_manager/src/App.tsx | 7 + augmentos_manager/src/components/types.ts | 1 + augmentos_manager/src/logic/LogService.ts | 106 +++++++++++ .../src/screens/ErrorReportScreen.tsx | 170 ++++++++++++++++++ .../src/screens/SettingsPage.tsx | 9 + 10 files changed, 376 insertions(+), 1 deletion(-) create mode 100644 augmentos_manager/android/app/src/main/java/com/augmentos/augmentos_manager/logcapture/LogcatCaptureModule.java create mode 100644 augmentos_manager/android/app/src/main/java/com/augmentos/augmentos_manager/logcapture/LogcatCapturePackage.java create mode 100644 augmentos_manager/src/logic/LogService.ts create mode 100644 augmentos_manager/src/screens/ErrorReportScreen.tsx diff --git a/augmentos_core/app/src/main/java/com/augmentos/augmentos_core/smarterglassesmanager/smartglassesconnection/SmartGlassesRepresentative.java b/augmentos_core/app/src/main/java/com/augmentos/augmentos_core/smarterglassesmanager/smartglassesconnection/SmartGlassesRepresentative.java index 91db12c66..dfe549246 100644 --- a/augmentos_core/app/src/main/java/com/augmentos/augmentos_core/smarterglassesmanager/smartglassesconnection/SmartGlassesRepresentative.java +++ b/augmentos_core/app/src/main/java/com/augmentos/augmentos_core/smarterglassesmanager/smartglassesconnection/SmartGlassesRepresentative.java @@ -275,7 +275,7 @@ public void onHomeScreenEvent(HomeScreenEvent receivedEvent){ @Subscribe public void onTextWallViewEvent(TextWallViewRequestEvent receivedEvent){ if (smartGlassesCommunicator != null) { - Log.d(TAG, "SINGLE TEXT WALL BOOM"); + // Log.d(TAG, "SINGLE TEXT WALL BOOM"); smartGlassesCommunicator.displayTextWall(receivedEvent.text); } } diff --git a/augmentos_manager/android/app/src/main/AndroidManifest.xml b/augmentos_manager/android/app/src/main/AndroidManifest.xml index c87e1c58f..2b69adfa6 100644 --- a/augmentos_manager/android/app/src/main/AndroidManifest.xml +++ b/augmentos_manager/android/app/src/main/AndroidManifest.xml @@ -17,6 +17,9 @@ + + + diff --git a/augmentos_manager/android/app/src/main/java/com/augmentos/augmentos_manager/MainApplication.kt b/augmentos_manager/android/app/src/main/java/com/augmentos/augmentos_manager/MainApplication.kt index 5b60d283b..7372356f3 100644 --- a/augmentos_manager/android/app/src/main/java/com/augmentos/augmentos_manager/MainApplication.kt +++ b/augmentos_manager/android/app/src/main/java/com/augmentos/augmentos_manager/MainApplication.kt @@ -22,6 +22,7 @@ import it.innove.BleManagerPackage import kjd.reactnative.bluetooth.RNBluetoothClassicPackage import com.reactnativecommunity.slider.ReactSliderPackage import com.lugg.RNCConfig.RNCConfigPackage +import com.augmentos.augmentos_manager.logcapture.LogcatCapturePackage // import com.augmentos.augmentos_manager.NotificationServicePackage @@ -54,6 +55,7 @@ class MainApplication : Application(), ReactApplication { TpaHelpersPackage(), FetchConfigHelperPackage(), RNCConfigPackage(), + LogcatCapturePackage() ) } diff --git a/augmentos_manager/android/app/src/main/java/com/augmentos/augmentos_manager/logcapture/LogcatCaptureModule.java b/augmentos_manager/android/app/src/main/java/com/augmentos/augmentos_manager/logcapture/LogcatCaptureModule.java new file mode 100644 index 000000000..16fc216a1 --- /dev/null +++ b/augmentos_manager/android/app/src/main/java/com/augmentos/augmentos_manager/logcapture/LogcatCaptureModule.java @@ -0,0 +1,53 @@ +package com.augmentos.augmentos_manager.logcapture; + +import com.facebook.react.bridge.ReactApplicationContext; +import com.facebook.react.bridge.ReactContextBaseJavaModule; +import com.facebook.react.bridge.ReactMethod; +import com.facebook.react.bridge.Promise; + +import java.io.BufferedReader; +import java.io.InputStreamReader; +import java.util.ArrayList; + +public class LogcatCaptureModule extends ReactContextBaseJavaModule { + private final ReactApplicationContext reactContext; + + public LogcatCaptureModule(ReactApplicationContext reactContext) { + super(reactContext); + this.reactContext = reactContext; + } + + @Override + public String getName() { + return "LogcatCapture"; + } + + @ReactMethod + public void getLogs(int logSize, Promise promise) { + try { + Process process = Runtime.getRuntime().exec("logcat -d -t " + logSize); + BufferedReader bufferedReader = new BufferedReader( + new InputStreamReader(process.getInputStream())); + + StringBuilder log = new StringBuilder(); + String line; + while ((line = bufferedReader.readLine()) != null) { + log.append(line).append("\n"); + } + + promise.resolve(log.toString()); + } catch (Exception e) { + promise.reject("ERR_UNEXPECTED_EXCEPTION", e); + } + } + + @ReactMethod + public void clearLogs(Promise promise) { + try { + Runtime.getRuntime().exec("logcat -c"); + promise.resolve(true); + } catch (Exception e) { + promise.reject("ERR_UNEXPECTED_EXCEPTION", e); + } + } +} \ No newline at end of file diff --git a/augmentos_manager/android/app/src/main/java/com/augmentos/augmentos_manager/logcapture/LogcatCapturePackage.java b/augmentos_manager/android/app/src/main/java/com/augmentos/augmentos_manager/logcapture/LogcatCapturePackage.java new file mode 100644 index 000000000..20ed5f80a --- /dev/null +++ b/augmentos_manager/android/app/src/main/java/com/augmentos/augmentos_manager/logcapture/LogcatCapturePackage.java @@ -0,0 +1,24 @@ +package com.augmentos.augmentos_manager.logcapture; + +import com.facebook.react.ReactPackage; +import com.facebook.react.bridge.NativeModule; +import com.facebook.react.bridge.ReactApplicationContext; +import com.facebook.react.uimanager.ViewManager; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +public class LogcatCapturePackage implements ReactPackage { + @Override + public List createViewManagers(ReactApplicationContext reactContext) { + return Collections.emptyList(); + } + + @Override + public List createNativeModules(ReactApplicationContext reactContext) { + List modules = new ArrayList<>(); + modules.add(new LogcatCaptureModule(reactContext)); + return modules; + } +} \ No newline at end of file diff --git a/augmentos_manager/src/App.tsx b/augmentos_manager/src/App.tsx index 0d6213b77..b7521ace1 100644 --- a/augmentos_manager/src/App.tsx +++ b/augmentos_manager/src/App.tsx @@ -32,6 +32,8 @@ import VerifyEmailScreen from './screens/VerifyEmail.tsx'; import PrivacySettingsScreen from './screens/PrivacySettingsScreen.tsx'; import GrantPermissionsScreen from './screens/GrantPermissionsScreen.tsx'; import ConnectingToPuckComponent from './components/ConnectingToPuckComponent.tsx'; +import { GlassesMirrorProvider } from './providers/GlassesMirrorContext.tsx'; +import ErrorReportScreen from './screens/ErrorReportScreen.tsx'; const linking = { prefixes: ['https://augmentos.org'], @@ -261,6 +263,11 @@ const App: React.FC = () => { /> )} + diff --git a/augmentos_manager/src/components/types.ts b/augmentos_manager/src/components/types.ts index b407fcc1c..2d4ae5c67 100644 --- a/augmentos_manager/src/components/types.ts +++ b/augmentos_manager/src/components/types.ts @@ -24,6 +24,7 @@ export type RootStackParamList = { SelectGlassesBluetoothScreen: { glassesModelName: string }; GlassesPairingGuideScreen: { glassesModelName: string }; AppSettings: { packageName: string, appName: string }; + ErrorReportScreen: undefined; }; diff --git a/augmentos_manager/src/logic/LogService.ts b/augmentos_manager/src/logic/LogService.ts new file mode 100644 index 000000000..a69f62495 --- /dev/null +++ b/augmentos_manager/src/logic/LogService.ts @@ -0,0 +1,106 @@ +// LogService.ts +import { NativeModules, Platform } from 'react-native'; +import BackendServerComms from '../backend_comms/BackendServerComms'; + +const { LogcatCapture } = NativeModules; + +// This is a simple stub for iOS - actual iOS implementation will be added in the future +class LogService { + private static instance: LogService; + private TAG = 'MXT2_LogService'; + private backendComms: BackendServerComms; + + private constructor() { + this.backendComms = BackendServerComms.getInstance(); + } + + public static getInstance(): LogService { + if (!LogService.instance) { + LogService.instance = new LogService(); + } + return LogService.instance; + } + + /** + * Gets device logs + * @param lines Number of log lines to retrieve + * @returns Promise with log data + */ + public async getLogs(lines: number = 1000): Promise { + try { + if (Platform.OS === 'android' && LogcatCapture) { + return await LogcatCapture.getLogs(lines); + } else { + // Stub for iOS - to be implemented in the future + console.warn(`${this.TAG}: Log module not available on iOS yet`); + return 'Log capture is not yet available on iOS'; + } + } catch (error) { + console.error(`${this.TAG}: Error getting logs -`, error); + return `Error retrieving logs: ${error}`; + } + } + + /** + * Clears device logs + * @returns Promise + */ + public async clearLogs(): Promise { + try { + if (Platform.OS === 'android' && LogcatCapture) { + return await LogcatCapture.clearLogs(); + } else { + // Stub for iOS - to be implemented in the future + console.warn(`${this.TAG}: Log clearing not available on iOS yet`); + return false; + } + } catch (error) { + console.error(`${this.TAG}: Error clearing logs -`, error); + return false; + } + } + + /** + * Send error report to backend server + * @param description User-provided description of the issue + * @param token Authentication token + * @returns Promise Success status + */ + public async sendErrorReport(description: string, token: string): Promise { + try { + // Get logs + const logs = await this.getLogs(); + + // Prepare data for report + const reportData = { + description, + logs, + deviceInfo: { + platform: Platform.OS, + version: Platform.Version, + }, + timestamp: new Date().toISOString() + }; + + // Use your existing backend communications infrastructure + return new Promise((resolve, reject) => { + this.backendComms.restRequest( + '/error-report', + reportData, + { + onSuccess: () => resolve(true), + onFailure: (errorCode) => { + console.error(`${this.TAG}: Failed to send error report, code: ${errorCode}`); + reject(new Error(`Failed to send error report, code: ${errorCode}`)); + } + } + ); + }); + } catch (error) { + console.error(`${this.TAG}: Error sending report -`, error); + throw error; + } + } +} + +export default LogService; \ No newline at end of file diff --git a/augmentos_manager/src/screens/ErrorReportScreen.tsx b/augmentos_manager/src/screens/ErrorReportScreen.tsx new file mode 100644 index 000000000..2ff70df0b --- /dev/null +++ b/augmentos_manager/src/screens/ErrorReportScreen.tsx @@ -0,0 +1,170 @@ +import React, { useState, useEffect } from 'react'; +import { + View, + Text, + StyleSheet, + TouchableOpacity, + TextInput, + ScrollView, + Platform, + Alert, + ActivityIndicator +} from 'react-native'; +import LogService from '../logic/LogService'; +import { useStatus } from '../providers/AugmentOSStatusProvider'; + +interface ErrorReportingScreenProps { + navigation: any; +} + +const ErrorReportingScreen: React.FC = ({ navigation }) => { + const [description, setDescription] = useState(''); + const [isSending, setIsSending] = useState(false); + const { status } = useStatus(); + + const sendErrorReport = async () => { + if (description.trim().length === 0) { + Alert.alert('Error', 'Please enter a description of the issue'); + return; + } + + setIsSending(true); + + try { + // Get the log service instance + const logService = LogService.getInstance(); + + // Use the token from state or pass a placeholder if not available + const authToken = status.core_info.core_token || 'placeholder-token'; + + // Send the error report + await logService.sendErrorReport(description, authToken); + + Alert.alert( + 'Success', + 'Error report submitted successfully. Thank you for helping improve the app!', + [{ + text: 'OK', + onPress: () => { + setDescription(''); + navigation.goBack(); // Return to previous screen after successful report + } + }] + ); + } catch (error) { + console.error("Error sending report:", error); + Alert.alert( + 'Error', + 'Could not send error report. Please try again later.', + [{ text: 'OK' }] + ); + } finally { + setIsSending(false); + } + }; + + return ( + + Report an Error + + Describe the issue you encountered: + + + + This will send your description along with recent app logs to our support team. + No personal information is collected other than what you provide above. + + + + {isSending ? ( + + ) : ( + Send Report + )} + + + navigation.goBack()} + disabled={isSending} + > + Cancel + + + ); +}; + +const styles = StyleSheet.create({ + container: { + flex: 1, + padding: 16, + backgroundColor: '#f5f5f5', + }, + title: { + fontSize: 22, + fontWeight: 'bold', + marginBottom: 20, + color: '#333', + }, + label: { + fontSize: 16, + marginBottom: 8, + color: '#555', + }, + input: { + backgroundColor: '#fff', + borderWidth: 1, + borderColor: '#ddd', + borderRadius: 4, + padding: 12, + fontSize: 16, + minHeight: 120, + textAlignVertical: 'top', + marginBottom: 16, + color: '#333', + }, + note: { + fontSize: 14, + color: '#777', + marginBottom: 20, + fontStyle: 'italic', + }, + button: { + backgroundColor: '#2196F3', + padding: 15, + borderRadius: 4, + alignItems: 'center', + marginBottom: 12, + }, + buttonText: { + color: '#fff', + fontSize: 16, + fontWeight: 'bold', + }, + cancelButton: { + padding: 15, + borderRadius: 4, + alignItems: 'center', + marginBottom: 30, + borderWidth: 1, + borderColor: '#ccc', + }, + cancelButtonText: { + color: '#555', + fontSize: 16, + }, +}); + +export default ErrorReportingScreen; \ No newline at end of file diff --git a/augmentos_manager/src/screens/SettingsPage.tsx b/augmentos_manager/src/screens/SettingsPage.tsx index f1d4cbbe5..5436b6229 100644 --- a/augmentos_manager/src/screens/SettingsPage.tsx +++ b/augmentos_manager/src/screens/SettingsPage.tsx @@ -402,6 +402,15 @@ const SettingsPage: React.FC = ({ + {/* Bug Report */} + {/* { + navigation.navigate('ErrorReportScreen'); + }}> + + Report an Issue + + */} + {/* Forget Glasses */} Date: Wed, 5 Mar 2025 18:12:47 -0800 Subject: [PATCH 02/13] virtual wearable --- .../smartglassescommunicators/VirtualSGC.java | 100 ++++++++++++++++++ .../SmartGlassesRepresentative.java | 3 + .../SmartGlassesOperatingSystem.java | 3 +- .../special/VirtualWearable.java | 21 ++++ ...earable_basic.png => virtual_wearable.png} | Bin .../src/logic/getGlassesImage.tsx | 2 + .../src/screens/SelectGlassesModelScreen.tsx | 1 + 7 files changed, 129 insertions(+), 1 deletion(-) create mode 100644 augmentos_core/app/src/main/java/com/augmentos/augmentos_core/smarterglassesmanager/smartglassescommunicators/VirtualSGC.java create mode 100644 augmentos_core/app/src/main/java/com/augmentos/augmentos_core/smarterglassesmanager/supportedglasses/special/VirtualWearable.java rename augmentos_manager/src/assets/glasses/{audio_wearable_basic.png => virtual_wearable.png} (100%) diff --git a/augmentos_core/app/src/main/java/com/augmentos/augmentos_core/smarterglassesmanager/smartglassescommunicators/VirtualSGC.java b/augmentos_core/app/src/main/java/com/augmentos/augmentos_core/smarterglassesmanager/smartglassescommunicators/VirtualSGC.java new file mode 100644 index 000000000..46d1b96db --- /dev/null +++ b/augmentos_core/app/src/main/java/com/augmentos/augmentos_core/smarterglassesmanager/smartglassescommunicators/VirtualSGC.java @@ -0,0 +1,100 @@ +package com.augmentos.augmentos_core.smarterglassesmanager.smartglassescommunicators; + +import android.content.Context; +import android.graphics.Bitmap; +import android.util.Log; + +import com.augmentos.augmentos_core.smarterglassesmanager.eventbusmessages.GlassesBluetoothSearchDiscoverEvent; +import com.augmentos.augmentos_core.smarterglassesmanager.eventbusmessages.TextToSpeechEvent; +import com.augmentos.augmentos_core.smarterglassesmanager.supportedglasses.SmartGlassesDevice; +import com.augmentos.augmentos_core.smarterglassesmanager.utils.SmartGlassesConnectionState; + +import org.greenrobot.eventbus.EventBus; + +public class VirtualSGC extends SmartGlassesCommunicator { + private static final String TAG = "WearableAi_AndroidWearableSGC"; + + + Context context; + SmartGlassesDevice smartGlassesDevice; + + public VirtualSGC(Context context, SmartGlassesDevice smartGlassesDevice){ + super(); + mConnectState = SmartGlassesConnectionState.DISCONNECTED; + this.smartGlassesDevice = smartGlassesDevice; + } + + public void setFontSizes(){ + } + + public void connectToSmartGlasses(){ + connectionEvent(SmartGlassesConnectionState.CONNECTED); + } + + public void blankScreen(){ + } + + public void displayRowsCard(String[] rowStrings){ + + } + + @Override + public void destroy() { + mConnectState = SmartGlassesConnectionState.DISCONNECTED; + this.context = null; + this.smartGlassesDevice = null; + Log.d(TAG, "VirtualSGC destroyed successfully."); + } + + + public void displayReferenceCardSimple(String title, String body){} + + public void displayReferenceCardImage(String title, String body, String imgUrl){} + + public void displayBulletList(String title, String [] bullets){} + + public void displayBulletList(String title, String [] bullets, int lingerTime){} + + public void displayTextWall(String text){} + public void displayDoubleTextWall(String textTop, String textBottom){} + + public void stopScrollingTextViewMode() { + } + + public void startScrollingTextViewMode(String title){ + } + + public void scrollingTextViewIntermediateText(String text){ + } + + public void scrollingTextViewFinalText(String text){ + } + + public void showHomeScreen(){ + } + + public void displayPromptView(String prompt, String [] options){ + } + + public void displayTextLine(String text){} + + @Override + public void displayBitmap(Bitmap bmp) { + + } + + @Override + public void displayCustomContent(String json) {} + + @Override + public void findCompatibleDeviceNames() { + EventBus.getDefault().post(new GlassesBluetoothSearchDiscoverEvent(smartGlassesDevice.deviceModelName, "NOTREQUIREDSKIP")); + this.destroy(); + } + + public void showNaturalLanguageCommandScreen(String prompt, String naturalLanguageArgs){} + + public void updateNaturalLanguageCommandScreen(String naturalLanguageArgs){} + + public void setFontSize(SmartGlassesFontSize fontSize){} +} \ No newline at end of file diff --git a/augmentos_core/app/src/main/java/com/augmentos/augmentos_core/smarterglassesmanager/smartglassesconnection/SmartGlassesRepresentative.java b/augmentos_core/app/src/main/java/com/augmentos/augmentos_core/smarterglassesmanager/smartglassesconnection/SmartGlassesRepresentative.java index 9a309f9c1..dc4f70b5d 100644 --- a/augmentos_core/app/src/main/java/com/augmentos/augmentos_core/smarterglassesmanager/smartglassesconnection/SmartGlassesRepresentative.java +++ b/augmentos_core/app/src/main/java/com/augmentos/augmentos_core/smarterglassesmanager/smartglassesconnection/SmartGlassesRepresentative.java @@ -14,6 +14,7 @@ import com.augmentos.augmentos_core.smarterglassesmanager.eventbusmessages.AudioChunkNewEvent; import com.augmentos.augmentos_core.smarterglassesmanager.eventbusmessages.DisableBleScoAudioEvent; +import com.augmentos.augmentos_core.smarterglassesmanager.smartglassescommunicators.VirtualSGC; import com.augmentos.augmentos_core.smarterglassesmanager.smartglassescommunicators.special.SelfSGC; import com.augmentos.augmentos_core.smarterglassesmanager.eventbusmessages.LC3AudioChunkNewEvent; import com.augmentos.augmentoslib.events.DisplayCustomContentRequestEvent; @@ -131,6 +132,8 @@ private SmartGlassesCommunicator createCommunicator() { return new AndroidSGC(context, smartGlassesDevice, dataObservable); case AUDIO_WEARABLE_GLASSES: return new AudioWearableSGC(context, smartGlassesDevice); + case VIRTUAL_WEARABLE: + return new VirtualSGC(context, smartGlassesDevice); case ULTRALITE_MCU_OS_GLASSES: return new UltraliteSGC(context, smartGlassesDevice, lifecycleOwner); case EVEN_REALITIES_G1_MCU_OS_GLASSES: diff --git a/augmentos_core/app/src/main/java/com/augmentos/augmentos_core/smarterglassesmanager/supportedglasses/SmartGlassesOperatingSystem.java b/augmentos_core/app/src/main/java/com/augmentos/augmentos_core/smarterglassesmanager/supportedglasses/SmartGlassesOperatingSystem.java index c737a63ee..cb480c99a 100644 --- a/augmentos_core/app/src/main/java/com/augmentos/augmentos_core/smarterglassesmanager/supportedglasses/SmartGlassesOperatingSystem.java +++ b/augmentos_core/app/src/main/java/com/augmentos/augmentos_core/smarterglassesmanager/supportedglasses/SmartGlassesOperatingSystem.java @@ -7,5 +7,6 @@ public enum SmartGlassesOperatingSystem { ULTRALITE_MCU_OS_GLASSES, EVEN_REALITIES_G1_MCU_OS_GLASSES, INMO_GO_MCU_OS_GLASSES, - AUDIO_WEARABLE_GLASSES + AUDIO_WEARABLE_GLASSES, + VIRTUAL_WEARABLE } diff --git a/augmentos_core/app/src/main/java/com/augmentos/augmentos_core/smarterglassesmanager/supportedglasses/special/VirtualWearable.java b/augmentos_core/app/src/main/java/com/augmentos/augmentos_core/smarterglassesmanager/supportedglasses/special/VirtualWearable.java new file mode 100644 index 000000000..0a84f0f2d --- /dev/null +++ b/augmentos_core/app/src/main/java/com/augmentos/augmentos_core/smarterglassesmanager/supportedglasses/special/VirtualWearable.java @@ -0,0 +1,21 @@ +package com.augmentos.augmentos_core.smarterglassesmanager.supportedglasses.special; + +import com.augmentos.augmentos_core.smarterglassesmanager.supportedglasses.SmartGlassesDevice; +import com.augmentos.augmentos_core.smarterglassesmanager.supportedglasses.SmartGlassesOperatingSystem; + +public class VirtualWearable extends SmartGlassesDevice { + public VirtualWearable() { + deviceModelName = "Virtual Wearable"; + deviceIconName = "bluetooth_earpiece"; + anySupport = true; + fullSupport = true; + glassesOs = SmartGlassesOperatingSystem.VIRTUAL_WEARABLE; + hasDisplay = false; + hasSpeakers = false; //set as false because we want to do this from ASP + hasCamera = false; + hasInMic = false; //set as false because we want to do this from ASP + hasOutMic = false; + useScoMic = true; + weight = 14; + } +} \ No newline at end of file diff --git a/augmentos_manager/src/assets/glasses/audio_wearable_basic.png b/augmentos_manager/src/assets/glasses/virtual_wearable.png similarity index 100% rename from augmentos_manager/src/assets/glasses/audio_wearable_basic.png rename to augmentos_manager/src/assets/glasses/virtual_wearable.png diff --git a/augmentos_manager/src/logic/getGlassesImage.tsx b/augmentos_manager/src/logic/getGlassesImage.tsx index 4a61a9c26..fa427fa01 100644 --- a/augmentos_manager/src/logic/getGlassesImage.tsx +++ b/augmentos_manager/src/logic/getGlassesImage.tsx @@ -19,6 +19,8 @@ export const getGlassesImage = (glasses: string | null) => { case 'virtual-wearable': case 'Audio Wearable': return require('../assets/glasses/audio_wearable.png'); + case 'Virtual Wearable': + return require('../assets/glasses/virtual_wearable.png'); default: return require('../assets/glasses/unknown_wearable.png'); } diff --git a/augmentos_manager/src/screens/SelectGlassesModelScreen.tsx b/augmentos_manager/src/screens/SelectGlassesModelScreen.tsx index 42107a54f..f47e7f557 100644 --- a/augmentos_manager/src/screens/SelectGlassesModelScreen.tsx +++ b/augmentos_manager/src/screens/SelectGlassesModelScreen.tsx @@ -40,6 +40,7 @@ const SelectGlassesModelScreen: React.FC = ({ { modelName: 'Mentra Mach1', key: 'mentra_mach1' }, { modelName: 'Even Realities G1', key: 'evenrealities_g1' }, { modelName: 'Audio Wearable', key: 'Audio Wearable' }, + { modelName: 'Virtual Wearable', key: 'Virtual Wearable' }, ]; React.useEffect(() => { }, [status]); From d2e2b7ae216815017957f3b4968ca8c1e06dd50e Mon Sep 17 00:00:00 2001 From: Alex Israelov Date: Thu, 6 Mar 2025 09:27:08 -0800 Subject: [PATCH 03/13] fix virtual wearable, screen mirror fix start --- .../SmartGlassesAndroidService.java | 4 +- augmentos_manager/android/app/build.gradle | 8 ++ .../android/app/src/main/AndroidManifest.xml | 1 + augmentos_manager/android/settings.gradle | 2 + augmentos_manager/package-lock.json | 20 ++++ augmentos_manager/package.json | 2 + .../src/screens/GlassesMirror.tsx | 94 ++++++++++++++++++- 7 files changed, 127 insertions(+), 4 deletions(-) diff --git a/augmentos_core/app/src/main/java/com/augmentos/augmentos_core/smarterglassesmanager/smartglassesconnection/SmartGlassesAndroidService.java b/augmentos_core/app/src/main/java/com/augmentos/augmentos_core/smarterglassesmanager/smartglassesconnection/SmartGlassesAndroidService.java index 2d5ce9c00..71bf48556 100644 --- a/augmentos_core/app/src/main/java/com/augmentos/augmentos_core/smarterglassesmanager/smartglassesconnection/SmartGlassesAndroidService.java +++ b/augmentos_core/app/src/main/java/com/augmentos/augmentos_core/smarterglassesmanager/smartglassesconnection/SmartGlassesAndroidService.java @@ -26,6 +26,7 @@ import com.augmentos.augmentos_core.smarterglassesmanager.eventbusmessages.NewAsrLanguagesEvent; import com.augmentos.augmentos_core.smarterglassesmanager.smartglassescommunicators.SmartGlassesFontSize; import com.augmentos.augmentos_core.smarterglassesmanager.comms.MessageTypes; +import com.augmentos.augmentos_core.smarterglassesmanager.supportedglasses.special.VirtualWearable; import com.augmentos.augmentoslib.events.BulletPointListViewRequestEvent; import com.augmentos.augmentoslib.events.CenteredTextViewRequestEvent; import com.augmentos.augmentoslib.events.DoubleTextWallViewRequestEvent; @@ -420,7 +421,8 @@ public static SmartGlassesDevice getSmartGlassesDeviceFromModelName(String model new VuzixShield(), new InmoAirOne(), new TCLRayNeoXTwo(), - new AudioWearable() + new AudioWearable(), + new VirtualWearable() ) ); diff --git a/augmentos_manager/android/app/build.gradle b/augmentos_manager/android/app/build.gradle index 8c8662733..c19f7fcb4 100644 --- a/augmentos_manager/android/app/build.gradle +++ b/augmentos_manager/android/app/build.gradle @@ -89,6 +89,14 @@ dependencies { implementation project(':react-native-ble-manager') implementation project(':react-native-linear-gradient') implementation 'androidx.localbroadcastmanager:localbroadcastmanager:1.0.0' + + // Add react-native-camera + //implementation project(':react-native-camera') + implementation(project(':react-native-camera')) { + attributes { + attribute(Attribute.of('react-native-camera', String), 'general') // Or 'mlkit' + } + } // Add react-native-svg implementation project(':react-native-svg') // Add this line diff --git a/augmentos_manager/android/app/src/main/AndroidManifest.xml b/augmentos_manager/android/app/src/main/AndroidManifest.xml index c87e1c58f..b8d42d086 100644 --- a/augmentos_manager/android/app/src/main/AndroidManifest.xml +++ b/augmentos_manager/android/app/src/main/AndroidManifest.xml @@ -16,6 +16,7 @@ + diff --git a/augmentos_manager/android/settings.gradle b/augmentos_manager/android/settings.gradle index 276437448..7e56c0341 100644 --- a/augmentos_manager/android/settings.gradle +++ b/augmentos_manager/android/settings.gradle @@ -42,6 +42,8 @@ project(':SmartGlassesManager').projectDir = new File(rootProject.projectDir, '. include ':react-native-svg' project(':react-native-svg').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-svg/android') +include ':react-native-camera' +project(':react-native-camera').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-camera/android') include ':react-native-config' project(':react-native-config').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-config/android') \ No newline at end of file diff --git a/augmentos_manager/package-lock.json b/augmentos_manager/package-lock.json index 3a2f76cc5..67ddb3026 100644 --- a/augmentos_manager/package-lock.json +++ b/augmentos_manager/package-lock.json @@ -46,6 +46,8 @@ "react-native-bluetooth-classic": "^1.73.0-rc.12", + "react-native-camera": "^4.2.1", + "react-native-config": "^1.5.5", "react-native-elements": "^3.4.3", @@ -23076,6 +23078,24 @@ }, + "node_modules/react-native-camera": { + + "version": "4.2.1", + + "resolved": "https://registry.npmjs.org/react-native-camera/-/react-native-camera-4.2.1.tgz", + + "integrity": "sha512-+Vkql24PFYQfsPRznJCvPwJQfyzCnjlcww/iZ4Ej80bgivKjL9eU0IMQIXp4yi6XCrKi4voWXxIDPMupQZKeIQ==", + + "license": "MIT AND Apache-2.0 AND BSD-3-Clause", + + "dependencies": { + + "prop-types": "^15.6.2" + + } + + }, + "node_modules/react-native-config": { "version": "1.5.5", diff --git a/augmentos_manager/package.json b/augmentos_manager/package.json index 38acea0b3..980baf31c 100644 --- a/augmentos_manager/package.json +++ b/augmentos_manager/package.json @@ -54,6 +54,8 @@ "react-native-bluetooth-classic": "^1.73.0-rc.12", + "react-native-camera": "^4.2.1", + "react-native-config": "^1.5.5", "react-native-elements": "^3.4.3", diff --git a/augmentos_manager/src/screens/GlassesMirror.tsx b/augmentos_manager/src/screens/GlassesMirror.tsx index 7445a4c03..6916187ab 100644 --- a/augmentos_manager/src/screens/GlassesMirror.tsx +++ b/augmentos_manager/src/screens/GlassesMirror.tsx @@ -1,4 +1,4 @@ -import React, { useState } from 'react'; +import React, { useState, useRef, useEffect } from 'react'; import { View, Text, @@ -6,7 +6,11 @@ import { Image, TouchableOpacity, StatusBar, + Platform, + PermissionsAndroid, + BackHandler, } from 'react-native'; +import { RNCamera } from 'react-native-camera'; import NavigationBar from '../components/NavigationBar.tsx'; import { useStatus } from '../providers/AugmentOSStatusProvider.tsx'; import { useGlassesMirror } from '../providers/GlassesMirrorContext.tsx'; @@ -15,10 +19,35 @@ interface GlassesMirrorProps { isDarkTheme: boolean; } +// Request camera permission for Android SDK 33 +const requestCameraPermission = async () => { + if (Platform.OS === 'android' && Platform.Version >= 33) { + try { + const granted = await PermissionsAndroid.request( + PermissionsAndroid.PERMISSIONS.CAMERA, + { + title: 'Camera Permission', + message: 'This app needs access to your camera for the fullscreen mirror mode.', + buttonNeutral: 'Ask Me Later', + buttonNegative: 'Cancel', + buttonPositive: 'OK', + }, + ); + return granted === PermissionsAndroid.RESULTS.GRANTED; + } catch (err) { + console.warn('Camera permission error:', err); + return false; + } + } + return true; // iOS handles permissions through Info.plist +}; + const GlassesMirror: React.FC = ({isDarkTheme}) => { const { status } = useStatus(); const { events } = useGlassesMirror(); // From context const [isFullScreen, setIsFullScreen] = useState(false); + const [hasCameraPermission, setHasCameraPermission] = useState(false); + const cameraRef = useRef(null); // Helper to check if we have a glasses model name const isGlassesConnected = !!status.glasses_info?.model_name; @@ -26,6 +55,33 @@ const GlassesMirror: React.FC = ({isDarkTheme}) => { // Get only the last event const lastEvent = events.length > 0 ? events[events.length - 1] : null; + // Check camera permission when entering fullscreen + const checkCameraPermission = async () => { + const hasPermission = await requestCameraPermission(); + setHasCameraPermission(hasPermission); + return hasPermission; + }; + + // Setup back button handling + useEffect(() => { + const backHandler = BackHandler.addEventListener('hardwareBackPress', () => { + if (isFullScreen) { + toggleFullScreen(); + return true; + } + return false; + }); + + return () => backHandler.remove(); + }, [isFullScreen]); + + // Check permission when entering fullscreen + useEffect(() => { + if (isFullScreen) { + checkCameraPermission(); + } + }, [isFullScreen]); + // Function to toggle fullscreen mode const toggleFullScreen = () => { if (!isFullScreen) { @@ -78,8 +134,27 @@ const GlassesMirror: React.FC = ({isDarkTheme}) => { {/* Fullscreen mode */} {isFullScreen && isGlassesConnected && lastEvent ? ( - {/* Dark background for better contrast */} - + {/* Camera feed */} + {hasCameraPermission ? ( + + ) : ( + + + Camera permission needed for fullscreen mode + + + )} {/* Overlay the glasses display content */} @@ -229,6 +304,11 @@ const styles = StyleSheet.create({ bottom: 0, zIndex: 1000, }, + cameraBackground: { + position: 'absolute', + width: '100%', + height: '100%', + }, titleContainer: { paddingVertical: 15, paddingHorizontal: 20, @@ -347,6 +427,14 @@ const styles = StyleSheet.create({ width: '100%', height: '100%', backgroundColor: '#1a1a1a', // Dark background for contrast with green text + justifyContent: 'center', + alignItems: 'center', + }, + cameraPermissionText: { + color: 'white', + fontSize: 16, + fontFamily: 'Montserrat-Regular', + textAlign: 'center', }, fullscreenOverlay: { position: 'absolute', From ccb33c7571d636b4a039a953ad4d5b29bc9ce384 Mon Sep 17 00:00:00 2001 From: Alex Israelov Date: Thu, 6 Mar 2025 12:08:54 -0800 Subject: [PATCH 04/13] remove the java mic debouncer --- .../SmartGlassesAndroidService.java | 43 ------------------- 1 file changed, 43 deletions(-) diff --git a/augmentos_core/app/src/main/java/com/augmentos/augmentos_core/smarterglassesmanager/smartglassesconnection/SmartGlassesAndroidService.java b/augmentos_core/app/src/main/java/com/augmentos/augmentos_core/smarterglassesmanager/smartglassesconnection/SmartGlassesAndroidService.java index 71bf48556..70c3d1b95 100644 --- a/augmentos_core/app/src/main/java/com/augmentos/augmentos_core/smarterglassesmanager/smartglassesconnection/SmartGlassesAndroidService.java +++ b/augmentos_core/app/src/main/java/com/augmentos/augmentos_core/smarterglassesmanager/smartglassesconnection/SmartGlassesAndroidService.java @@ -98,11 +98,6 @@ public abstract class SmartGlassesAndroidService extends LifecycleService { public Handler connectHandler; String translationLanguage; - private Handler micDebounceHandler = new Handler(); - private Runnable micTurnOffRunnable; - private static final long MIC_DEBOUNCE_DELAY_MS = 10000; // 10 seconds - private boolean pendingMicTurnOff = false; - public SmartGlassesAndroidService(Class mainActivityClass, String myChannelId, int myNotificationId, String notificationAppName, String notificationDescription, int notificationDrawable){ this.myNotificationId = myNotificationId; this.mainActivityClass = mainActivityClass; @@ -583,44 +578,6 @@ public void sendCustomContent(String json){ public void changeMicrophoneState(boolean isMicrophoneEnabled) { Log.d(TAG, "Want to changing microphone state to " + isMicrophoneEnabled); Log.d(TAG, "Force core onboard mic: " + getForceCoreOnboardMic(this.getApplicationContext())); - - // If we're trying to turn ON the microphone - if (isMicrophoneEnabled) { - // Cancel any pending turn-off operations - if (pendingMicTurnOff) { - Log.d(TAG, "Cancelling pending microphone turn-off"); - micDebounceHandler.removeCallbacks(micTurnOffRunnable); - pendingMicTurnOff = false; - } - - // Immediately turn on the microphone - applyMicrophoneState(true); - } - // If we're trying to turn OFF the microphone - else { - // If there's already a pending turn-off, do nothing (debounce is already in progress) - if (!pendingMicTurnOff) { - Log.d(TAG, "Scheduling microphone turn-off with " + MIC_DEBOUNCE_DELAY_MS + "ms debounce"); - pendingMicTurnOff = true; - - // Define the runnable that will turn off the mic after the delay - micTurnOffRunnable = new Runnable() { - @Override - public void run() { - Log.d(TAG, "Executing debounced microphone turn-off"); - pendingMicTurnOff = false; - applyMicrophoneState(false); - } - }; - - // Schedule the delayed turn-off - micDebounceHandler.postDelayed(micTurnOffRunnable, MIC_DEBOUNCE_DELAY_MS); - } - } - } - public void applyMicrophoneState(boolean isMicrophoneEnabled) { - Log.d(TAG, "Want to changing microphone state to " + isMicrophoneEnabled); - Log.d(TAG, "Force core onboard mic: " + getForceCoreOnboardMic(this.getApplicationContext())); if (smartGlassesRepresentative.smartGlassesDevice.getHasInMic() && !getForceCoreOnboardMic(this.getApplicationContext())) { // If we should be using the glasses microphone smartGlassesRepresentative.smartGlassesCommunicator.changeSmartGlassesMicrophoneState(isMicrophoneEnabled); From 920763e651aad8a0f88acde0d4b00de1b9c8d927 Mon Sep 17 00:00:00 2001 From: Alex Israelov Date: Thu, 6 Mar 2025 14:37:23 -0800 Subject: [PATCH 05/13] * fix android 11 issues maybe * screen mirror mostly good --- augmentos_manager/android/app/build.gradle | 11 +++++++++-- .../augmentos/augmentos_manager/MainApplication.kt | 2 ++ augmentos_manager/src/logic/PermissionsUtils.tsx | 2 +- augmentos_manager/src/screens/GlassesMirror.tsx | 5 ++++- 4 files changed, 16 insertions(+), 4 deletions(-) diff --git a/augmentos_manager/android/app/build.gradle b/augmentos_manager/android/app/build.gradle index c19f7fcb4..a6cf3f585 100644 --- a/augmentos_manager/android/app/build.gradle +++ b/augmentos_manager/android/app/build.gradle @@ -72,6 +72,15 @@ android { excludes += ["META-INF/INDEX.LIST"] } } + configurations.all { + resolutionStrategy.dependencySubstitution { + substitute module('AugmentOS_Manager:react-native-camera') using project(':react-native-camera') with { + attributes { + attribute(Attribute.of("react-native-camera", String), "general") + } + } + } + } } dependencies { // React Native core dependencies @@ -90,8 +99,6 @@ dependencies { implementation project(':react-native-linear-gradient') implementation 'androidx.localbroadcastmanager:localbroadcastmanager:1.0.0' - // Add react-native-camera - //implementation project(':react-native-camera') implementation(project(':react-native-camera')) { attributes { attribute(Attribute.of('react-native-camera', String), 'general') // Or 'mlkit' diff --git a/augmentos_manager/android/app/src/main/java/com/augmentos/augmentos_manager/MainApplication.kt b/augmentos_manager/android/app/src/main/java/com/augmentos/augmentos_manager/MainApplication.kt index 5b60d283b..564426afb 100644 --- a/augmentos_manager/android/app/src/main/java/com/augmentos/augmentos_manager/MainApplication.kt +++ b/augmentos_manager/android/app/src/main/java/com/augmentos/augmentos_manager/MainApplication.kt @@ -22,6 +22,7 @@ import it.innove.BleManagerPackage import kjd.reactnative.bluetooth.RNBluetoothClassicPackage import com.reactnativecommunity.slider.ReactSliderPackage import com.lugg.RNCConfig.RNCConfigPackage +import org.reactnative.camera.RNCameraPackage // import com.augmentos.augmentos_manager.NotificationServicePackage @@ -54,6 +55,7 @@ class MainApplication : Application(), ReactApplication { TpaHelpersPackage(), FetchConfigHelperPackage(), RNCConfigPackage(), + RNCameraPackage() ) } diff --git a/augmentos_manager/src/logic/PermissionsUtils.tsx b/augmentos_manager/src/logic/PermissionsUtils.tsx index 0ec76f09e..dea16a0de 100644 --- a/augmentos_manager/src/logic/PermissionsUtils.tsx +++ b/augmentos_manager/src/logic/PermissionsUtils.tsx @@ -94,5 +94,5 @@ export const getAndroidPermissions = (): Permission[] => { list.push(PermissionsAndroid.PERMISSIONS.WRITE_CALENDAR); list.push(PermissionsAndroid.PERMISSIONS.RECORD_AUDIO); } - return list as Permission[]; + return list.filter(permission => permission != null) as Permission[]; } diff --git a/augmentos_manager/src/screens/GlassesMirror.tsx b/augmentos_manager/src/screens/GlassesMirror.tsx index 6916187ab..caad2bfd8 100644 --- a/augmentos_manager/src/screens/GlassesMirror.tsx +++ b/augmentos_manager/src/screens/GlassesMirror.tsx @@ -139,8 +139,9 @@ const GlassesMirror: React.FC = ({isDarkTheme}) => { Date: Thu, 6 Mar 2025 15:29:21 -0800 Subject: [PATCH 06/13] prevent crash when trying to change mic state when no glasses are connected --- .../smartglassesconnection/SmartGlassesAndroidService.java | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/augmentos_core/app/src/main/java/com/augmentos/augmentos_core/smarterglassesmanager/smartglassesconnection/SmartGlassesAndroidService.java b/augmentos_core/app/src/main/java/com/augmentos/augmentos_core/smarterglassesmanager/smartglassesconnection/SmartGlassesAndroidService.java index 70c3d1b95..89378366e 100644 --- a/augmentos_core/app/src/main/java/com/augmentos/augmentos_core/smarterglassesmanager/smartglassesconnection/SmartGlassesAndroidService.java +++ b/augmentos_core/app/src/main/java/com/augmentos/augmentos_core/smarterglassesmanager/smartglassesconnection/SmartGlassesAndroidService.java @@ -578,6 +578,12 @@ public void sendCustomContent(String json){ public void changeMicrophoneState(boolean isMicrophoneEnabled) { Log.d(TAG, "Want to changing microphone state to " + isMicrophoneEnabled); Log.d(TAG, "Force core onboard mic: " + getForceCoreOnboardMic(this.getApplicationContext())); + + if(smartGlassesRepresentative == null || smartGlassesRepresentative.smartGlassesDevice == null) { + Log.d(TAG, "Cannot change microphone state: smartGlassesRepresentative or smartGlassesDevice is null"); + return; + } + if (smartGlassesRepresentative.smartGlassesDevice.getHasInMic() && !getForceCoreOnboardMic(this.getApplicationContext())) { // If we should be using the glasses microphone smartGlassesRepresentative.smartGlassesCommunicator.changeSmartGlassesMicrophoneState(isMicrophoneEnabled); From d1466b4263f2c98654bf163d5f5808102a13f351 Mon Sep 17 00:00:00 2001 From: Alex Israelov Date: Thu, 6 Mar 2025 23:14:39 -0800 Subject: [PATCH 07/13] improve error reporting --- .../src/backend_comms/BackendServerComms.tsx | 39 +++- augmentos_manager/src/logic/LogService.ts | 21 +- .../src/screens/ErrorReportScreen.tsx | 187 +++++++++--------- 3 files changed, 137 insertions(+), 110 deletions(-) diff --git a/augmentos_manager/src/backend_comms/BackendServerComms.tsx b/augmentos_manager/src/backend_comms/BackendServerComms.tsx index cc2f833c5..49699dfb9 100644 --- a/augmentos_manager/src/backend_comms/BackendServerComms.tsx +++ b/augmentos_manager/src/backend_comms/BackendServerComms.tsx @@ -18,10 +18,10 @@ export default class BackendServerComms { const port = Config.AUGMENTOS_PORT; const protocol = secure ? 'https' : 'http'; const serverUrl = `${protocol}://${host}:${port}`; - console.log("\n\n\n\n Got a new server url: "); + console.log("Got a new server url: "); console.log(serverUrl); - console.log('React Native Config:', Config); - console.log("\n\n\n"); + //console.log('React Native Config:', Config); + //console.log("\n\n\n"); return serverUrl; } @@ -69,6 +69,39 @@ export default class BackendServerComms { callback.onFailure(-1); } } + + /** + * Send error report to backend server + * @param reportData The error report data + * @returns Promise resolving to the response data, or rejecting with an error + */ + public async sendErrorReport(coreToken: string, reportData: any): Promise { + + const url = `${this.serverUrl}/app/error-report`; + console.log('Sending error report to:', url); + + const config: AxiosRequestConfig = { + method: 'POST', + url, + headers: { + 'Content-Type': 'application/json', + 'Authorization': `Bearer ${coreToken}`, + }, + data: reportData, + }; + + try { + const response = await axios(config); + if (response.status === 200) { + return response.data; + } else { + throw new Error(`Error sending report: ${response.statusText}`); + } + } catch (error: any) { + console.error(`${this.TAG}: Error sending report -`, error.message || error); + throw error; + } + } public async exchangeToken(supabaseToken: string): Promise { const url = `${this.serverUrl}/auth/exchange-token`; diff --git a/augmentos_manager/src/logic/LogService.ts b/augmentos_manager/src/logic/LogService.ts index a69f62495..a13c52a38 100644 --- a/augmentos_manager/src/logic/LogService.ts +++ b/augmentos_manager/src/logic/LogService.ts @@ -66,7 +66,7 @@ class LogService { * @param token Authentication token * @returns Promise Success status */ - public async sendErrorReport(description: string, token: string): Promise { + public async sendErrorReport(coreToken: string, description: string): Promise { try { // Get logs const logs = await this.getLogs(); @@ -82,23 +82,12 @@ class LogService { timestamp: new Date().toISOString() }; - // Use your existing backend communications infrastructure - return new Promise((resolve, reject) => { - this.backendComms.restRequest( - '/error-report', - reportData, - { - onSuccess: () => resolve(true), - onFailure: (errorCode) => { - console.error(`${this.TAG}: Failed to send error report, code: ${errorCode}`); - reject(new Error(`Failed to send error report, code: ${errorCode}`)); - } - } - ); - }); + // Use the dedicated method in BackendServerComms + await this.backendComms.sendErrorReport(coreToken, reportData); + return true; } catch (error) { console.error(`${this.TAG}: Error sending report -`, error); - throw error; + return false; } } } diff --git a/augmentos_manager/src/screens/ErrorReportScreen.tsx b/augmentos_manager/src/screens/ErrorReportScreen.tsx index 2ff70df0b..44f167e66 100644 --- a/augmentos_manager/src/screens/ErrorReportScreen.tsx +++ b/augmentos_manager/src/screens/ErrorReportScreen.tsx @@ -1,17 +1,19 @@ import React, { useState, useEffect } from 'react'; -import { - View, - Text, - StyleSheet, - TouchableOpacity, - TextInput, - ScrollView, +import { + View, + Text, + StyleSheet, + TouchableOpacity, + TextInput, + ScrollView, Platform, Alert, - ActivityIndicator + ActivityIndicator, + GestureResponderEvent } from 'react-native'; import LogService from '../logic/LogService'; import { useStatus } from '../providers/AugmentOSStatusProvider'; +import Button from '../components/Button'; interface ErrorReportingScreenProps { navigation: any; @@ -20,30 +22,30 @@ interface ErrorReportingScreenProps { const ErrorReportingScreen: React.FC = ({ navigation }) => { const [description, setDescription] = useState(''); const [isSending, setIsSending] = useState(false); - const { status } = useStatus(); - + const { status } = useStatus(); + const sendErrorReport = async () => { if (description.trim().length === 0) { Alert.alert('Error', 'Please enter a description of the issue'); return; } - + setIsSending(true); - + try { // Get the log service instance const logService = LogService.getInstance(); - + // Use the token from state or pass a placeholder if not available - const authToken = status.core_info.core_token || 'placeholder-token'; - + const coreToken = status.core_info.core_token || 'placeholder-token'; + // Send the error report - await logService.sendErrorReport(description, authToken); - + await logService.sendErrorReport(description, coreToken); + Alert.alert( - 'Success', + 'Success', 'Error report submitted successfully. Thank you for helping improve the app!', - [{ + [{ text: 'OK', onPress: () => { setDescription(''); @@ -54,7 +56,7 @@ const ErrorReportingScreen: React.FC = ({ navigation } catch (error) { console.error("Error sending report:", error); Alert.alert( - 'Error', + 'Error', 'Could not send error report. Please try again later.', [{ text: 'OK' }] ); @@ -62,108 +64,111 @@ const ErrorReportingScreen: React.FC = ({ navigation setIsSending(false); } }; - + return ( - - Report an Error - - Describe the issue you encountered: - - - - This will send your description along with recent app logs to our support team. - No personal information is collected other than what you provide above. - - - - {isSending ? ( - - ) : ( - Send Report - )} - - - navigation.goBack()} - disabled={isSending} - > - Cancel - - + + + Report an Error + + Describe the issue you encountered: + + + + This will send your description along with recent app logs to our support team. + No personal information is collected other than what you provide above. + + + + + + + ); }; const styles = StyleSheet.create({ container: { flex: 1, - padding: 16, - backgroundColor: '#f5f5f5', + backgroundColor: '#f8f9fa', + justifyContent: 'space-between', + }, + scrollContent: { + flex: 1, + padding: 24, }, title: { - fontSize: 22, - fontWeight: 'bold', - marginBottom: 20, + fontFamily: 'Montserrat-Bold', + fontSize: 26, + marginBottom: 24, color: '#333', + textAlign: 'center', }, label: { - fontSize: 16, - marginBottom: 8, - color: '#555', + fontFamily: 'Montserrat-Regular', + fontSize: 18, + marginBottom: 12, + color: '#444', }, input: { backgroundColor: '#fff', borderWidth: 1, - borderColor: '#ddd', - borderRadius: 4, - padding: 12, + borderColor: '#e0e0e0', + borderRadius: 8, + padding: 16, fontSize: 16, - minHeight: 120, + minHeight: 160, textAlignVertical: 'top', - marginBottom: 16, + marginBottom: 20, color: '#333', + shadowColor: '#000', + shadowOffset: { width: 0, height: 1 }, + shadowOpacity: 0.1, + shadowRadius: 2, + elevation: 2, }, note: { + fontFamily: 'Montserrat-Regular', fontSize: 14, - color: '#777', + color: '#666', marginBottom: 20, fontStyle: 'italic', + lineHeight: 20, + }, + buttonContainer: { + padding: 24, + paddingBottom: Platform.OS === 'ios' ? 40 : 24, + backgroundColor: '#f8f9fa', + alignItems: 'center', }, button: { backgroundColor: '#2196F3', - padding: 15, - borderRadius: 4, + padding: 16, + borderRadius: 8, alignItems: 'center', - marginBottom: 12, + shadowColor: '#000', + shadowOffset: { width: 0, height: 2 }, + shadowOpacity: 0.2, + shadowRadius: 4, + elevation: 4, }, buttonText: { + fontFamily: 'Montserrat-Bold', color: '#fff', - fontSize: 16, - fontWeight: 'bold', - }, - cancelButton: { - padding: 15, - borderRadius: 4, - alignItems: 'center', - marginBottom: 30, - borderWidth: 1, - borderColor: '#ccc', - }, - cancelButtonText: { - color: '#555', - fontSize: 16, + fontSize: 18, }, }); From 7a158caea7a350f59f3152c131f7fa3555dae552 Mon Sep 17 00:00:00 2001 From: Alex Israelov Date: Fri, 7 Mar 2025 10:44:27 -0800 Subject: [PATCH 08/13] * clean up transcripts >30m old * Clean up old LC3 audio buffers * Clean upsession subscription history --- .../src/services/core/session.service.ts | 29 +++++++++++++ .../src/services/core/subscription.service.ts | 23 ++++++++++ .../processing/transcription.service.ts | 16 +++++-- .../packages/utils/src/lc3/LC3Service3.ts | 43 ++++++++++++++++--- 4 files changed, 102 insertions(+), 9 deletions(-) diff --git a/augmentos_cloud/packages/cloud/src/services/core/session.service.ts b/augmentos_cloud/packages/cloud/src/services/core/session.service.ts index 7d333ea86..efe06c641 100644 --- a/augmentos_cloud/packages/cloud/src/services/core/session.service.ts +++ b/augmentos_cloud/packages/cloud/src/services/core/session.service.ts @@ -137,7 +137,14 @@ export class SessionService { addTranscriptSegment(userSession: ExtendedUserSession, segment: TranscriptSegment): void { if (userSession) { + // Add new segment userSession.transcript.segments.push(segment); + + // Prune old segments (older than 30 minutes) + const thirtyMinutesAgo = new Date(Date.now() - 30 * 60 * 1000); + userSession.transcript.segments = userSession.transcript.segments.filter( + seg => seg.timestamp && new Date(seg.timestamp) >= thirtyMinutesAgo + ); } } @@ -187,9 +194,31 @@ export class SessionService { if (session.lc3Service) { session.lc3Service.cleanup(); } + + // Clean up subscription history for this session + const subscriptionService = require('./subscription.service').default; + if (subscriptionService && typeof subscriptionService.removeSessionSubscriptionHistory === 'function') { + subscriptionService.removeSessionSubscriptionHistory(sessionId); + } + + // Clear transcript history + if (session.transcript && session.transcript.segments) { + session.transcript.segments = []; + } + + // Clean other data structures + if (session.bufferedAudio) { + session.bufferedAudio = []; + } this.activeSessions.delete(sessionId); console.log(`[Ended session] ${sessionId}`); + + // Suggest garbage collection if available + if (global.gc) { + console.log('🧹 Running garbage collection after ending session'); + global.gc(); + } } getAllSessions(): ExtendedUserSession[] { diff --git a/augmentos_cloud/packages/cloud/src/services/core/subscription.service.ts b/augmentos_cloud/packages/cloud/src/services/core/subscription.service.ts index d811ae459..6c530fc52 100644 --- a/augmentos_cloud/packages/cloud/src/services/core/subscription.service.ts +++ b/augmentos_cloud/packages/cloud/src/services/core/subscription.service.ts @@ -203,6 +203,29 @@ import { StreamType, ExtendedStreamType, isLanguageStream, UserSession, parseLan console.log(`Removed all subscriptions for ${packageName} in session ${userSession.sessionId}`); } } + + /** + * Removes all subscription history for a session + * Used when a session is being killed to free memory + * @param sessionId - User session identifier + */ + removeSessionSubscriptionHistory(sessionId: string): void { + // Find all keys that start with this session ID + const keysToRemove: string[] = []; + + for (const key of this.history.keys()) { + if (key.startsWith(`${sessionId}:`)) { + keysToRemove.push(key); + } + } + + // Remove all history entries for this session + keysToRemove.forEach(key => { + this.history.delete(key); + }); + + console.log(`Removed subscription history for session ${sessionId} (${keysToRemove.length} entries)`); + } /** * Checks if a TPA has a specific subscription diff --git a/augmentos_cloud/packages/cloud/src/services/processing/transcription.service.ts b/augmentos_cloud/packages/cloud/src/services/processing/transcription.service.ts index 21196c857..e8b7718c2 100644 --- a/augmentos_cloud/packages/cloud/src/services/processing/transcription.service.ts +++ b/augmentos_cloud/packages/cloud/src/services/processing/transcription.service.ts @@ -311,9 +311,11 @@ export class TranscriptionService { private updateTranscriptHistory(userSession: ExtendedUserSession, event: ConversationTranscriptionEventArgs, isFinal: boolean): void { const segments = userSession.transcript.segments; const hasInterimLast = segments.length > 0 && !segments[segments.length - 1].isFinal; - // Only save engligh transcriptions. + // Only save English transcriptions. if (event.result.language !== 'en-US') return; + const currentTime = new Date(); + if (isFinal) { if (hasInterimLast) { segments.pop(); @@ -322,7 +324,7 @@ export class TranscriptionService { resultId: event.result.resultId, speakerId: event.result.speakerId, text: event.result.text, - timestamp: new Date(), + timestamp: currentTime, isFinal: true }); } else { @@ -331,7 +333,7 @@ export class TranscriptionService { resultId: event.result.resultId, speakerId: event.result.speakerId, text: event.result.text, - timestamp: new Date(), + timestamp: currentTime, isFinal: false }; } else { @@ -339,11 +341,17 @@ export class TranscriptionService { resultId: event.result.resultId, speakerId: event.result.speakerId, text: event.result.text, - timestamp: new Date(), + timestamp: currentTime, isFinal: false }); } } + + // Prune old segments (older than 30 minutes) + const thirtyMinutesAgo = new Date(currentTime.getTime() - 30 * 60 * 1000); + userSession.transcript.segments = segments.filter( + seg => seg.timestamp && new Date(seg.timestamp) >= thirtyMinutesAgo + ); } } diff --git a/augmentos_cloud/packages/utils/src/lc3/LC3Service3.ts b/augmentos_cloud/packages/utils/src/lc3/LC3Service3.ts index 3b5c32189..3281a8317 100644 --- a/augmentos_cloud/packages/utils/src/lc3/LC3Service3.ts +++ b/augmentos_cloud/packages/utils/src/lc3/LC3Service3.ts @@ -93,16 +93,20 @@ export class LC3Service { if (!this.initialized || !this.decoder) { await this.initialize(); } + + let localInputData: Uint8Array | null = null; + try { const numFrames = Math.floor(audioData.byteLength / this.frameBytes); const totalSamples = numFrames * this.frameSamples; const outputBuffer = new ArrayBuffer(totalSamples * 2); // 16-bit PCM const outputView = new DataView(outputBuffer); - const inputData = new Uint8Array(audioData); + localInputData = new Uint8Array(audioData); let outputOffset = 0; + for (let i = 0; i < numFrames; i++) { this.decoder!.frame.set( - inputData.subarray(i * this.frameBytes, (i + 1) * this.frameBytes) + localInputData.subarray(i * this.frameBytes, (i + 1) * this.frameBytes) ); this.decoder!.decode(); for (let j = 0; j < this.frameSamples; j++) { @@ -114,17 +118,46 @@ export class LC3Service { outputOffset += 2; } } + + // Update last used timestamp + if (this.decoder) { + this.decoder.lastUsed = Date.now(); + } + return outputBuffer; } catch (error) { console.error('❌ Error decoding LC3 audio:', error); return null; + } finally { + // Release references to input data to help GC + // This is safe even if there was an error + localInputData = null; } } cleanup(): void { - this.initialized = false; - this.lc3Exports = null; - this.decoder = null; + try { + if (this.decoder && this.lc3Exports) { + // Force garbage collection of the ArrayBuffer views + this.decoder.samples = new Float32Array(0); + this.decoder.frame = new Uint8Array(0); + + // If WebAssembly instances support explicit cleanup in future, add it here + + // Log memory usage before clearing references + if (global.gc) { + console.log('🧹 Running garbage collection for LC3Service cleanup'); + global.gc(); + } + } + } catch (error) { + console.error('Error during LC3Service cleanup:', error); + } finally { + // Clear all references to allow garbage collection + this.initialized = false; + this.lc3Exports = null; + this.decoder = null; + } } } From 8a51de16781bf4b81286daf5b70c30f53c75f590 Mon Sep 17 00:00:00 2001 From: Alex Israelov Date: Fri, 7 Mar 2025 16:32:19 -0800 Subject: [PATCH 09/13] app store webview pt1 --- augmentos_core/CLAUDE.md | 31 ++ augmentos_manager/android/app/build.gradle | 1 + .../augmentos_manager/MainApplication.kt | 2 + augmentos_manager/android/settings.gradle | 5 +- augmentos_manager/package-lock.json | 30 ++ augmentos_manager/package.json | 2 + augmentos_manager/src/App.tsx | 12 + .../src/components/NavigationBar.tsx | 5 +- augmentos_manager/src/components/types.ts | 2 + .../src/screens/AppStoreNative.tsx | 351 ++++++++++++++++++ .../src/screens/AppStoreWebview.tsx | 130 +++++++ 11 files changed, 567 insertions(+), 4 deletions(-) create mode 100644 augmentos_core/CLAUDE.md create mode 100644 augmentos_manager/src/screens/AppStoreNative.tsx create mode 100644 augmentos_manager/src/screens/AppStoreWebview.tsx diff --git a/augmentos_core/CLAUDE.md b/augmentos_core/CLAUDE.md new file mode 100644 index 000000000..ffbff00de --- /dev/null +++ b/augmentos_core/CLAUDE.md @@ -0,0 +1,31 @@ +# AugmentOS Core Development Guide + +## Build Commands +- `./gradlew build` - Full project build +- `./gradlew assembleDebug` - Build debug APK +- `./gradlew test` - Run unit tests +- `./gradlew androidTest` - Run instrumentation tests + +## Environment Setup +- Java SDK 17 required +- AugmentOS_Core depends on "SmartGlassesManager" repo being adjacent + +## Code Style Guidelines +- Classes: PascalCase (e.g., `WebSocketManager`) +- Methods: camelCase (e.g., `isConnected()`) +- Constants: UPPER_SNAKE_CASE (e.g., `MAX_RETRY_ATTEMPTS`) +- Member variables: camelCase with m prefix (e.g., `mService`) +- Indentation: 2 spaces +- Javadoc comments for public methods and classes + +## Error Handling +- Log errors with `Log.e(TAG, "Error message", e)` +- Use callbacks for asynchronous error propagation +- Handle all checked exceptions +- Provide user feedback for permissions issues + +## Architecture +- Service-based design with `AugmentosService` as main component +- Fragment-based UI with Navigation Component +- EventBus for component communication +- WebSocket for server communication \ No newline at end of file diff --git a/augmentos_manager/android/app/build.gradle b/augmentos_manager/android/app/build.gradle index a6cf3f585..e95b03430 100644 --- a/augmentos_manager/android/app/build.gradle +++ b/augmentos_manager/android/app/build.gradle @@ -97,6 +97,7 @@ dependencies { implementation project(':react-native-safe-area-context') implementation project(':react-native-ble-manager') implementation project(':react-native-linear-gradient') + implementation project(':react-native-webview') implementation 'androidx.localbroadcastmanager:localbroadcastmanager:1.0.0' implementation(project(':react-native-camera')) { diff --git a/augmentos_manager/android/app/src/main/java/com/augmentos/augmentos_manager/MainApplication.kt b/augmentos_manager/android/app/src/main/java/com/augmentos/augmentos_manager/MainApplication.kt index b2648e896..dfacc1ba1 100644 --- a/augmentos_manager/android/app/src/main/java/com/augmentos/augmentos_manager/MainApplication.kt +++ b/augmentos_manager/android/app/src/main/java/com/augmentos/augmentos_manager/MainApplication.kt @@ -24,6 +24,7 @@ import com.reactnativecommunity.slider.ReactSliderPackage import com.lugg.RNCConfig.RNCConfigPackage import org.reactnative.camera.RNCameraPackage import com.augmentos.augmentos_manager.logcapture.LogcatCapturePackage +import com.reactnativecommunity.webview.RNCWebViewPackage // import com.augmentos.augmentos_manager.NotificationServicePackage @@ -58,6 +59,7 @@ class MainApplication : Application(), ReactApplication { RNCConfigPackage(), RNCameraPackage(), LogcatCapturePackage(), + RNCWebViewPackage(), ) } diff --git a/augmentos_manager/android/settings.gradle b/augmentos_manager/android/settings.gradle index 7e56c0341..9d8be2538 100644 --- a/augmentos_manager/android/settings.gradle +++ b/augmentos_manager/android/settings.gradle @@ -46,4 +46,7 @@ include ':react-native-camera' project(':react-native-camera').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-camera/android') include ':react-native-config' -project(':react-native-config').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-config/android') \ No newline at end of file +project(':react-native-config').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-config/android') + +include ':react-native-webview' +project(':react-native-webview').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-webview/android') \ No newline at end of file diff --git a/augmentos_manager/package-lock.json b/augmentos_manager/package-lock.json index 67ddb3026..38bc79a73 100644 --- a/augmentos_manager/package-lock.json +++ b/augmentos_manager/package-lock.json @@ -76,6 +76,8 @@ "react-native-vector-icons": "^10.2.0", + "react-native-webview": "^13.13.2", + "text-encoding": "^0.7.0" }, @@ -23634,6 +23636,34 @@ }, + "node_modules/react-native-webview": { + + "version": "13.13.2", + + "resolved": "https://registry.npmjs.org/react-native-webview/-/react-native-webview-13.13.2.tgz", + + "integrity": "sha512-zACPDTF0WnaEnKZ9mA/r/UpcOpV2gQM06AAIrOOexnO8UJvXL8Pjso0b/wTqKFxUZZnmjKuwd8gHVUosVOdVrw==", + + "license": "MIT", + + "dependencies": { + + "escape-string-regexp": "^4.0.0", + + "invariant": "2.2.4" + + }, + + "peerDependencies": { + + "react": "*", + + "react-native": "*" + + } + + }, + "node_modules/react-native/node_modules/@jest/types": { "version": "26.6.2", diff --git a/augmentos_manager/package.json b/augmentos_manager/package.json index 980baf31c..d72ee02ba 100644 --- a/augmentos_manager/package.json +++ b/augmentos_manager/package.json @@ -84,6 +84,8 @@ "react-native-vector-icons": "^10.2.0", + "react-native-webview": "^13.13.2", + "text-encoding": "^0.7.0" }, diff --git a/augmentos_manager/src/App.tsx b/augmentos_manager/src/App.tsx index 4430868c6..9f572205d 100644 --- a/augmentos_manager/src/App.tsx +++ b/augmentos_manager/src/App.tsx @@ -11,6 +11,8 @@ import ProfileSettingsPage from './screens/ProfileSettingsPage'; import GlassesMirror from './screens/GlassesMirror'; import NotificationListener from './components/NotificationListener'; import AppStore from './screens/AppStore'; +import AppStoreNative from './screens/AppStoreNative'; +import AppStoreWeb from './screens/AppStoreWebview'; import AppDetails from './screens/AppDetails'; import Reviews from './screens/ReviewSection.tsx'; import { StyleSheet, Text, View } from 'react-native'; @@ -150,6 +152,16 @@ const App: React.FC = () => { options={{ title: 'App Store', headerShown: false }}> {props => } + + {props => } + + + {props => } + ({ diff --git a/augmentos_manager/src/components/NavigationBar.tsx b/augmentos_manager/src/components/NavigationBar.tsx index 1d0cf2636..0f3b87811 100644 --- a/augmentos_manager/src/components/NavigationBar.tsx +++ b/augmentos_manager/src/components/NavigationBar.tsx @@ -78,13 +78,12 @@ const NavigationBar: React.FC = ({ {/* App Store Icon */} navigation.navigate('AppStore')} - disabled={true} + onPress={() => navigation.navigate('AppStoreWeb')} style={styles.iconWrapper}> diff --git a/augmentos_manager/src/components/types.ts b/augmentos_manager/src/components/types.ts index 7da44ab62..34f1dc192 100644 --- a/augmentos_manager/src/components/types.ts +++ b/augmentos_manager/src/components/types.ts @@ -7,6 +7,8 @@ export type RootStackParamList = { Login: undefined; SettingsPage: undefined; AppStore: undefined; + AppStoreNative: undefined; + AppStoreWeb: undefined; PairPuckScreen: undefined; // Add this line SplashScreen: undefined; VerifyEmailScreen: undefined; diff --git a/augmentos_manager/src/screens/AppStoreNative.tsx b/augmentos_manager/src/screens/AppStoreNative.tsx new file mode 100644 index 000000000..e827007bf --- /dev/null +++ b/augmentos_manager/src/screens/AppStoreNative.tsx @@ -0,0 +1,351 @@ +import React, { useState, useEffect } from 'react'; +import { + View, + Text, + FlatList, + TouchableOpacity, + StyleSheet, + TextInput, +} from 'react-native'; +import MaterialCommunityIcons from 'react-native-vector-icons/MaterialCommunityIcons'; +import { NativeStackNavigationProp } from '@react-navigation/native-stack'; +import { useNavigation, useFocusEffect } from '@react-navigation/native'; + +import { RootStackParamList, AppStoreItem } from '../components/types'; +import NavigationBar from '../components/NavigationBar'; +import AppItem from '../components/AppStore/AppItem.tsx'; +import InternetConnectionFallbackComponent from '../components/InternetConnectionFallbackComponent.tsx'; +import LoadingComponent from '../components/LoadingComponent'; // Import the LoadingComponent + +interface AppStoreProps { + isDarkTheme: boolean; +} + +import BackendServerComms from '../backend_comms/BackendServerComms.tsx'; +import { GET_APP_STORE_DATA_ENDPOINT } from '../consts.tsx'; + +const AppStoreNative: React.FC = ({ isDarkTheme }) => { + const navigation = + useNavigation>(); + + const [searchQuery, setSearchQuery] = useState(''); + const [filteredApps, setFilteredApps] = useState([]); + const [selectedCategory] = useState(null); + + // New state for handling connection errors + const [isError, setIsError] = useState(false); + + // New state for handling loading + const [isLoading, setIsLoading] = useState(true); // Initialize as loading + + // Theme colors + const theme = { + backgroundColor: isDarkTheme ? '#1c1c1c' : '#f9f9f9', + headerBg: isDarkTheme ? '#333333' : '#fff', + textColor: isDarkTheme ? '#FFFFFF' : '#333333', + subTextColor: isDarkTheme ? '#999999' : '#666666', + cardBg: isDarkTheme ? '#333333' : '#fff', + borderColor: isDarkTheme ? '#444444' : '#e0e0e0', + searchBg: isDarkTheme ? '#2c2c2c' : '#f5f5f5', + categoryChipBg: isDarkTheme ? '#444444' : '#e9e9e9', + categoryChipText: isDarkTheme ? '#FFFFFF' : '#555555', + selectedChipBg: isDarkTheme ? '#666666' : '#333333', + selectedChipText: isDarkTheme ? '#FFFFFF' : '#FFFFFF', + }; + + useEffect(() => { + fetchAppStoreData(); + }, []); + + useFocusEffect( + React.useCallback(() => { + // Any logic that was previously in animations can be placed here if needed + }, []), + ); + + const fetchAppStoreData = async () => { + setIsLoading(true); // Start loading + const backendServerComms = BackendServerComms.getInstance(); + + const callback = { + onSuccess: (data: any) => { + const visibleApps = data.filter((app: AppStoreItem) => app.showInAppStore); + setFilteredApps(visibleApps); // Assuming the API returns a list of AppStoreItem + setIsError(false); // Reset error state on success + setIsLoading(false); // Stop loading + }, + onFailure: () => { + setIsError(true); // Set error state on failure + setIsLoading(false); // Stop loading + }, + }; + + await backendServerComms.restRequest(GET_APP_STORE_DATA_ENDPOINT, null, callback); + }; + + const handleSearch = (text: string) => { + setSearchQuery(text); + filterApps(text, selectedCategory); + }; + + const filterApps = (query: string, category: string | null) => { + let apps = filteredApps; + + if (category) { + apps = apps.filter(app => app.category === category); + } + + if (query) { + apps = apps.filter(app => + app.name.toLowerCase().includes(query.toLowerCase()), + ); + } + + setFilteredApps(apps); + }; + + // If you decide to handle categories in the future, you can uncomment and use this + // const handleCategoryPress = (category: string) => { + // if (category === 'All') { + // setSelectedCategory(null); + // filterApps(searchQuery, null); + // } else { + // const newCategory = selectedCategory === category ? null : category; + // setSelectedCategory(newCategory); + // filterApps(searchQuery, newCategory); + // } + // }; + + const renderItem = ({ item, index }: { item: AppStoreItem; index: number }) => ( + navigation.navigate('AppDetails', { app: item })} + /> + ); + + // Uncomment and implement if you reintroduce recommended items or categories + // const renderRecommendedItem = ({ item }: { item: AppStoreItem }) => ( + // navigation.navigate('AppDetails', { app: item })} + // /> + // ); + // + // const renderCategory = ({ item }: { item: string }) => ( + // handleCategoryPress(item)}> + // + // {item} + // + // + // ); + + return ( + + {/* Conditionally render Header and Search Bar when there is no error and not loading */} + {!isError && !isLoading && ( + + + AugmentOS Store + + + + + {searchQuery.length > 0 && ( + handleSearch('')} + style={styles.clearButton} + > + + + )} + + + )} + + {/* Content Section */} + + {isLoading ? ( + // Render Loading Component when data is loading + + ) : isError ? ( + // Render Error Component when there is an error + + ) : ( + // Render Main Content when there is no error and data is loaded + <> + {/* Recommended Section */} + + + Recommended for You + + {/* Uncomment and implement when ready + item.identifier_code} + contentContainerStyle={styles.recommendList} + showsHorizontalScrollIndicator={false} + /> */} + + + {/* App List Section */} + + item.identifierCode} + contentContainerStyle={styles.listContent} + showsVerticalScrollIndicator={false} + windowSize={5} + maxToRenderPerBatch={5} + removeClippedSubviews={true} + /> + + + )} + + + {/* Navigation Bar remains visible regardless of connection */} + + {}} isDarkTheme={isDarkTheme} /> + + + ); +}; + +const styles = StyleSheet.create({ + container: { + flex: 1, + backgroundColor: '#f9f9f9', + }, + headerContainer: { + paddingVertical: 15, + paddingHorizontal: 15, + borderBottomWidth: 1, + }, + header: { + fontSize: 24, + fontFamily: 'Montserrat-Bold', + fontWeight: 'bold', + textAlign: 'left', + }, + headerTextDark: { + color: '#ffffff', + }, + headerTextLight: { + color: '#000000', + }, + searchContainer: { + flexDirection: 'row', + alignItems: 'center', + marginTop: 10, + borderRadius: 8, + paddingHorizontal: 15, + }, + searchIcon: { + marginRight: 10, + }, + searchInput: { + height: 40, + flex: 1, + fontSize: 14, + fontFamily: 'Montserrat-Regular', + }, + clearButton: { + padding: 8, + marginLeft: 4, + marginRight: -4, + }, + contentContainer: { + flex: 1, + paddingHorizontal: 15, + paddingTop: 15, + }, + recommendSection: { + marginBottom: 20, + }, + recommendHeader: { + fontSize: 22, + fontWeight: '700', + fontFamily: 'Montserrat-Bold', + }, + listContainer: { + flex: 1, + }, + listContent: { + paddingBottom: 80, + }, + navigationBarContainer: { + position: 'absolute', + bottom: 0, + width: '100%', + borderTopWidth: 1, + paddingBottom: 20, + }, +}); + +export default AppStoreNative; diff --git a/augmentos_manager/src/screens/AppStoreWebview.tsx b/augmentos_manager/src/screens/AppStoreWebview.tsx new file mode 100644 index 000000000..b8aab16f4 --- /dev/null +++ b/augmentos_manager/src/screens/AppStoreWebview.tsx @@ -0,0 +1,130 @@ +import React, { useState } from 'react'; +import { View, StyleSheet, Text, ActivityIndicator } from 'react-native'; +import { WebView } from 'react-native-webview'; +import Config from 'react-native-config'; +import { useStatus } from '../providers/AugmentOSStatusProvider'; +import NavigationBar from '../components/NavigationBar'; +import LoadingComponent from '../components/LoadingComponent'; +import InternetConnectionFallbackComponent from '../components/InternetConnectionFallbackComponent'; + +interface AppStoreWebProps { + isDarkTheme: boolean; +} + +const AppStoreWeb: React.FC = ({ isDarkTheme }) => { + const { status } = useStatus(); + const coreToken = status.core_info.core_token; + const [isLoading, setIsLoading] = useState(true); + const [hasError, setHasError] = useState(false); + + // Theme colors + const theme = { + backgroundColor: isDarkTheme ? '#1c1c1c' : '#f9f9f9', + headerBg: isDarkTheme ? '#333333' : '#fff', + textColor: isDarkTheme ? '#FFFFFF' : '#333333', + secondaryTextColor: isDarkTheme ? '#aaaaaa' : '#777777', + borderColor: isDarkTheme ? '#444444' : '#e0e0e0', + buttonBg: isDarkTheme ? '#444444' : '#eeeeee', + buttonTextColor: isDarkTheme ? '#ffffff' : '#333333', + primaryColor: '#0088FF' + }; + + // Get the app store URL from environment variable or use a fallback + const appStoreUrl = Config.AUGMENTOS_APPSTORE_URL || 'https://store.augmentos.org'; + + // Handle WebView loading events + const handleLoadStart = () => setIsLoading(true); + const handleLoadEnd = () => setIsLoading(false); + const handleError = () => { + setIsLoading(false); + setHasError(true); + }; + + // Set up headers with the authentication token + const headers = { + 'Authorization': `Bearer ${coreToken}`, + }; + + return ( + + {hasError ? ( + setHasError(false)} isDarkTheme={false} /> + ) : ( + + ( + + + + Loading App Store... + + + )} + /> + + )} + + {/* Navigation Bar remains visible */} + + {}} isDarkTheme={isDarkTheme} /> + + + ); +}; + +const styles = StyleSheet.create({ + container: { + flex: 1, + }, + webViewContainer: { + flex: 1, + marginBottom: 55, // Space for the navigation bar + }, + webView: { + flex: 1, + }, + loadingOverlay: { + position: 'absolute', + top: 0, + left: 0, + right: 0, + bottom: 0, + justifyContent: 'center', + alignItems: 'center', + backgroundColor: 'rgba(0, 0, 0, 0.3)', + }, + loadingText: { + marginTop: 10, + fontSize: 16, + fontFamily: 'Montserrat-Regular', + }, + navigationBarContainer: { + position: 'absolute', + bottom: 0, + width: '100%', + borderTopWidth: 1, + paddingBottom: 20, + }, +}); + +export default AppStoreWeb; \ No newline at end of file From 2692898e9bfd13f2d8f1ef0259671838e7283127 Mon Sep 17 00:00:00 2001 From: Alex Israelov Date: Fri, 7 Mar 2025 21:55:47 -0800 Subject: [PATCH 10/13] ability to skip version check --- augmentos_manager/src/App.tsx | 7 +++++ augmentos_manager/src/screens/Homepage.tsx | 11 ++++++- .../src/screens/VersionUpdateScreen.tsx | 30 +++++++++++++++++++ 3 files changed, 47 insertions(+), 1 deletion(-) diff --git a/augmentos_manager/src/App.tsx b/augmentos_manager/src/App.tsx index 9f572205d..7e6dac5ed 100644 --- a/augmentos_manager/src/App.tsx +++ b/augmentos_manager/src/App.tsx @@ -36,6 +36,7 @@ import VersionUpdateScreen from './screens/VersionUpdateScreen.tsx'; import { GlassesMirrorProvider } from './providers/GlassesMirrorContext.tsx'; import GlassesPairingGuidePreparationScreen from './screens/GlassesPairingGuidePreparationScreen.tsx'; import ErrorReportScreen from './screens/ErrorReportScreen.tsx'; +import { saveSetting } from './logic/SettingsHelper'; const linking = { prefixes: ['https://augmentos.org'], @@ -53,6 +54,12 @@ const Stack = createNativeStackNavigator(); const App: React.FC = () => { const [isDarkTheme, setIsDarkTheme] = useState(false); + // Reset ignoreVersionCheck setting on app start + useEffect(() => { + saveSetting('ignoreVersionCheck', false); + console.log('Reset version check ignore flag on app start'); + }, []); + const toggleTheme = () => { setIsDarkTheme(prevTheme => !prevTheme); }; diff --git a/augmentos_manager/src/screens/Homepage.tsx b/augmentos_manager/src/screens/Homepage.tsx index a747b1286..491d3f89e 100644 --- a/augmentos_manager/src/screens/Homepage.tsx +++ b/augmentos_manager/src/screens/Homepage.tsx @@ -19,6 +19,7 @@ import BackendServerComms from '../backend_comms/BackendServerComms.tsx'; import semver from 'semver'; import { Config } from 'react-native-config'; import CloudConnection from '../components/CloudConnection.tsx'; +import { loadSetting, saveSetting } from '../logic/SettingsHelper'; interface HomepageProps { isDarkTheme: boolean; @@ -56,6 +57,14 @@ const Homepage: React.FC = ({ isDarkTheme, toggleTheme }) => { setIsCheckingVersion(true); try { + // Check if version checks are being ignored this session + const ignoreCheck = await loadSetting('ignoreVersionCheck', false); + if (ignoreCheck) { + console.log('Version check skipped due to user preference'); + setIsCheckingVersion(false); + return; + } + const backendComms = BackendServerComms.getInstance(); const localVer = getLocalVersion(); @@ -114,7 +123,7 @@ const Homepage: React.FC = ({ isDarkTheme, toggleTheme }) => { // Check version once on mount useEffect(() => { - //checkCloudVersion(); + checkCloudVersion(); }, []); // Simple animated wrapper so we do not duplicate logic diff --git a/augmentos_manager/src/screens/VersionUpdateScreen.tsx b/augmentos_manager/src/screens/VersionUpdateScreen.tsx index 90c6ddc0d..f06d34c82 100644 --- a/augmentos_manager/src/screens/VersionUpdateScreen.tsx +++ b/augmentos_manager/src/screens/VersionUpdateScreen.tsx @@ -14,6 +14,7 @@ import { ScrollView } from 'react-native-gesture-handler'; import Icon from 'react-native-vector-icons/MaterialCommunityIcons'; import Button from '../components/Button'; import InstallApkModule from '../bridge/InstallApkModule.tsx'; +import { saveSetting } from '../logic/SettingsHelper'; interface VersionUpdateScreenProps { route: { @@ -221,6 +222,25 @@ const VersionUpdateScreen: React.FC = ({ ? 'Retry Connection' : 'Update AugmentOS'} + + + + )} @@ -273,6 +293,16 @@ const styles = StyleSheet.create({ alignItems: 'center', marginTop: 16, }, + skipButtonContainer: { + marginTop: 16, + width: '100%', + alignItems: 'center', + }, + skipButton: { + backgroundColor: 'transparent', + borderWidth: 1, + borderColor: '#666', + }, darkBackground: { backgroundColor: '#1c1c1c', }, From fe7b3bed45157cc71e210c6f617ab177971616b9 Mon Sep 17 00:00:00 2001 From: Alex Israelov Date: Fri, 7 Mar 2025 22:01:45 -0800 Subject: [PATCH 11/13] improve styling --- .../src/screens/VersionUpdateScreen.tsx | 87 +++++++++---------- 1 file changed, 42 insertions(+), 45 deletions(-) diff --git a/augmentos_manager/src/screens/VersionUpdateScreen.tsx b/augmentos_manager/src/screens/VersionUpdateScreen.tsx index f06d34c82..8832032b3 100644 --- a/augmentos_manager/src/screens/VersionUpdateScreen.tsx +++ b/augmentos_manager/src/screens/VersionUpdateScreen.tsx @@ -153,8 +153,8 @@ const VersionUpdateScreen: React.FC = ({ isDarkTheme ? styles.darkBackground : styles.lightBackground, ]} > - - + + {connectionError ? ( = ({ isDarkTheme ? styles.lightSubtext : styles.darkSubtext, ]} > - {/*{connectionError*/} - {/* ? 'Could not connect to the server. Please check your connection and try again.'*/} - {/* : isVersionMismatch*/} - {/* ? `Your AugmentOS (${localVersion}) is outdated. The latest version is ${cloudVersion}. Please update to continue.`*/} - {/* : 'Your AugmentOS is up to date. Returning to home...'}*/} {connectionError ? 'Could not connect to the server. Please check your connection and try again.' : isVersionMismatch ? 'Your AugmentOS is outdated. Please update to continue.' : 'Your AugmentOS is up to date. Returning to home...'} + - {(connectionError || isVersionMismatch) && ( - + {(connectionError || isVersionMismatch) && ( + + + + - - - - - )} - - + + )} + ); }; @@ -261,15 +256,17 @@ const styles = StyleSheet.create({ marginTop: 16, fontSize: 16, }, - scrollViewContainer: { + mainContainer: { flex: 1, + flexDirection: 'column', + justifyContent: 'space-between', + padding: 24, }, - contentContainer: { + infoContainer: { flex: 1, - padding: 24, justifyContent: 'center', alignItems: 'center', - minHeight: '100%', + paddingTop: 60, }, iconContainer: { marginBottom: 32, @@ -291,7 +288,7 @@ const styles = StyleSheet.create({ setupContainer: { width: '100%', alignItems: 'center', - marginTop: 16, + paddingBottom: 40, }, skipButtonContainer: { marginTop: 16, From c058258a964fc448156ccfc192ff26ede8a21cb3 Mon Sep 17 00:00:00 2001 From: Alex Israelov Date: Fri, 7 Mar 2025 22:28:51 -0800 Subject: [PATCH 12/13] * ask for location permissions again * disable app store navbar * fix live translation image * default skip version check --- augmentos_manager/src/App.tsx | 3 ++- augmentos_manager/src/components/NavigationBar.tsx | 3 ++- augmentos_manager/src/logic/getAppImage.tsx | 1 + augmentos_manager/src/screens/GrantPermissionsScreen.tsx | 3 +++ 4 files changed, 8 insertions(+), 2 deletions(-) diff --git a/augmentos_manager/src/App.tsx b/augmentos_manager/src/App.tsx index 7e6dac5ed..7702d8c29 100644 --- a/augmentos_manager/src/App.tsx +++ b/augmentos_manager/src/App.tsx @@ -56,7 +56,8 @@ const App: React.FC = () => { // Reset ignoreVersionCheck setting on app start useEffect(() => { - saveSetting('ignoreVersionCheck', false); +//TODO: SET THIS TO FALSE + saveSetting('ignoreVersionCheck', true); console.log('Reset version check ignore flag on app start'); }, []); diff --git a/augmentos_manager/src/components/NavigationBar.tsx b/augmentos_manager/src/components/NavigationBar.tsx index 0f3b87811..c2384cba3 100644 --- a/augmentos_manager/src/components/NavigationBar.tsx +++ b/augmentos_manager/src/components/NavigationBar.tsx @@ -83,7 +83,8 @@ const NavigationBar: React.FC = ({ diff --git a/augmentos_manager/src/logic/getAppImage.tsx b/augmentos_manager/src/logic/getAppImage.tsx index 46e909b73..4d003a5c6 100644 --- a/augmentos_manager/src/logic/getAppImage.tsx +++ b/augmentos_manager/src/logic/getAppImage.tsx @@ -7,6 +7,7 @@ export const getAppImage = (packageName: string) => { return require('../assets/app-icons/mentra-link.png'); case 'com.mentra.adhdaid': return require('../assets/app-icons/ADHD-aid.png'); + case 'com.augmentos.live-translation': case 'com.augmentos.livetranslation': return require('../assets/app-icons/translation.png'); case 'com.example.placeholder': diff --git a/augmentos_manager/src/screens/GrantPermissionsScreen.tsx b/augmentos_manager/src/screens/GrantPermissionsScreen.tsx index 3679b0d41..a602deed3 100644 --- a/augmentos_manager/src/screens/GrantPermissionsScreen.tsx +++ b/augmentos_manager/src/screens/GrantPermissionsScreen.tsx @@ -18,6 +18,7 @@ import { displayPermissionDeniedWarning, doesHaveAllPermissions, requestGrantPer import Button from '../components/Button'; import { checkNotificationPermission } from '../logic/NotificationServiceUtils'; import { checkAndRequestNotificationAccessSpecialPermission, checkNotificationAccessSpecialPermission } from "../utils/NotificationServiceUtils"; +import { openCorePermissionsActivity } from '../bridge/CoreServiceStarter'; interface GrantPermissionsScreenProps { isDarkTheme: boolean; @@ -75,6 +76,7 @@ const GrantPermissionsScreen: React.FC = ({ console.log('App has come to foreground!'); if (await doesHaveAllPermissions()) { + openCorePermissionsActivity(); navigation.reset({ index: 0, routes: [{ name: 'SplashScreen' }], @@ -103,6 +105,7 @@ const GrantPermissionsScreen: React.FC = ({ if ((await doesHaveAllPermissions())) { console.log("WE SUPPOSEDLY HAVE ALL THE PERMSSS"); + openCorePermissionsActivity(); navigation.reset({ index: 0, routes: [{ name: 'SplashScreen' }], From 9e5c29a50be8dd45a8c7940d9970e110110e2437 Mon Sep 17 00:00:00 2001 From: Alex Israelov Date: Fri, 7 Mar 2025 22:52:08 -0800 Subject: [PATCH 13/13] readd java mic debounce --- .../smartglassesconnection/SmartGlassesAndroidService.java | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/augmentos_core/app/src/main/java/com/augmentos/augmentos_core/smarterglassesmanager/smartglassesconnection/SmartGlassesAndroidService.java b/augmentos_core/app/src/main/java/com/augmentos/augmentos_core/smarterglassesmanager/smartglassesconnection/SmartGlassesAndroidService.java index 352e1cec1..44384dcfb 100644 --- a/augmentos_core/app/src/main/java/com/augmentos/augmentos_core/smarterglassesmanager/smartglassesconnection/SmartGlassesAndroidService.java +++ b/augmentos_core/app/src/main/java/com/augmentos/augmentos_core/smarterglassesmanager/smartglassesconnection/SmartGlassesAndroidService.java @@ -98,6 +98,11 @@ public abstract class SmartGlassesAndroidService extends LifecycleService { public Handler connectHandler; String translationLanguage; + private Handler micDebounceHandler = new Handler(); + private Runnable micTurnOffRunnable; + private static final long MIC_DEBOUNCE_DELAY_MS = 10000; // 10 seconds + private boolean pendingMicTurnOff = false; + public SmartGlassesAndroidService(Class mainActivityClass, String myChannelId, int myNotificationId, String notificationAppName, String notificationDescription, int notificationDrawable){ this.myNotificationId = myNotificationId; this.mainActivityClass = mainActivityClass;