import React, { useState, useEffect } from "react";
import { View, StyleSheet, ScrollView } from "react-native";
import * as Sentry from "@sentry/react-native";
import BackgroundGeolocation from "react-native-background-geolocation";
import {
Button,
Card,
Switch,
Text,
useTheme,
Divider,
RadioButton,
} from "react-native-paper";
import { createStyles } from "~/theme";
import env, { setStaging } from "~/env";
import { authActions } from "~/stores";
import {
getEmulatorModeState,
toggleEmulatorMode as toggleEmulatorModeService,
initEmulatorMode,
} from "~/location/emulatorService";
import { LOG_LEVELS, setMinLogLevel } from "~/lib/logger";
import { config as loggerConfig } from "~/lib/logger/config";
const reset = async () => {
await authActions.logout();
};
const Section = ({ title, children }) => {
const styles = useStyles();
return (
{children}
);
};
export default function Developer() {
const styles = useStyles();
const { colors } = useTheme();
const [isStaging, setIsStaging] = useState(env.IS_STAGING);
const [emulatorMode, setEmulatorMode] = useState(false);
const [syncStatus, setSyncStatus] = useState(null); // null, 'syncing', 'success', 'error'
const [syncResult, setSyncResult] = useState("");
const [logLevel, setLogLevel] = useState(LOG_LEVELS.DEBUG);
// Initialize emulator mode and log level when component mounts
useEffect(() => {
// Initialize the emulator service
initEmulatorMode();
// Set the initial state based on the global service
setEmulatorMode(getEmulatorModeState());
// Set the initial log level from config
setLogLevel(loggerConfig.minLevel);
}, []);
// Handle log level change
const handleLogLevelChange = (level) => {
setLogLevel(level);
setMinLogLevel(level);
};
// Handle toggling emulator mode
const handleEmulatorModeToggle = async (enabled) => {
const newState = await toggleEmulatorModeService(enabled);
setEmulatorMode(newState);
};
// Function to trigger geolocation sync
const triggerGeolocSync = async () => {
try {
setSyncStatus("syncing");
setSyncResult("");
// Get the count of pending records first
const count = await BackgroundGeolocation.getCount();
// Perform the sync
const records = await BackgroundGeolocation.sync();
const result = `Synced ${
records?.length || 0
} records (${count} pending)`;
setSyncResult(result);
setSyncStatus("success");
} catch (error) {
console.error("Geolocation sync failed:", error);
setSyncResult(`Sync failed: ${error.message}`);
setSyncStatus("error");
}
};
const triggerNullError = () => {
try {
// Wrap the null error in try-catch
const nonExistentObject = null;
nonExistentObject.someProperty.anotherProperty();
} catch (error) {
// Convert the native error to a JavaScript error that Error Boundary can handle
throw new Error(`Controlled null error: ${error.message}`);
}
};
const triggerExplicitError = () => {
throw new Error("This is an explicit error throw test");
};
const triggerPromiseError = async () => {
try {
await Promise.reject(new Error("This is a promise rejection test"));
} catch (error) {
// Re-throw to trigger error boundary
throw error;
}
};
const triggerAsyncError = async () => {
// Force this into a new call stack
await new Promise((resolve) => setTimeout(resolve, 0));
throw new Error("This is an async error test");
};
const triggerSentryError = () => {
try {
throw new Error("Manual Sentry capture test");
} catch (error) {
Sentry.captureException(error);
// Don't re-throw - this should only go to Sentry
}
};
// Add a more complex null error test that should be caught
const triggerDeepNullError = () => {
try {
const obj = {
level1: {
level2: null,
},
};
// This will cause a null error in a more realistic scenario
obj.level1.level2.level3.something();
} catch (error) {
// Explicitly throw a new Error with the stack
throw new Error(
`Deep null error: ${error.message}\nStack: ${error.stack}`,
);
}
};
return (
Staging Environment
{
setIsStaging(value); // Update UI immediately
await setStaging(value); // Persist the change
await reset(); // Reset auth state
}}
/>
Emulator Mode
Log Level
DEBUG
INFO
WARN
ERROR
GRAPHQL_URL:
{env.GRAPHQL_URL}
GRAPHQL_WS_URL:
{env.GRAPHQL_WS_URL}
GEOLOC_SYNC_URL:
{env.GEOLOC_SYNC_URL}
MINIO_URL:
{env.MINIO_URL}
OA_FILES_URL:
{env.OA_FILES_URL}
{syncStatus && syncResult && (
{syncResult}
)}
);
}
const useStyles = createStyles(({ wp, hp, scaleText, theme: { colors } }) => ({
container: {
flexGrow: 1,
padding: 16,
gap: 16,
},
section: {
width: "100%",
// backgroundColor: colors.surface,
},
button: {
marginVertical: 4,
backgroundColor: colors.primary,
},
buttonContent: {},
buttonLabel: {
color: colors.onPrimary,
},
settingRow: {
flexDirection: "row",
justifyContent: "space-between",
alignItems: "center",
marginBottom: 8,
},
statusText: {
color: colors.onSurface,
opacity: 0.7,
},
divider: {
marginVertical: 8,
},
urlRow: {
flexDirection: "row",
marginBottom: 8,
paddingRight: 8,
},
urlLabel: {
fontWeight: "bold",
minWidth: 120,
marginRight: 8,
},
urlValue: {
flex: 1,
flexWrap: "wrap",
},
radioRow: {
flexDirection: "row",
alignItems: "center",
marginVertical: 2,
},
sectionLabel: {
marginBottom: 8,
},
}));