expo-app/App.js

650 lines
20 KiB
JavaScript

import "react-native-gesture-handler";
import { StatusBar } from "expo-status-bar";
import { Text, View } from "react-native";
import { createDrawerNavigator } from "@react-navigation/drawer";
import { DefaultTheme, NavigationContainer } from "@react-navigation/native";
import SideBar from "./src/Components/SideBar";
import {
AppContext,
AppProvider,
AppStyles,
Constants,
GetDataFromList,
GetMultipleData,
IsPlatformIos,
} from "./src/utils";
import {
Suspense,
lazy,
useCallback,
useContext,
useEffect,
useState,
} from "react";
import { SafeAreaView } from "react-native-safe-area-context";
import "./i18n";
import DeviceScreen from "./src/Screens/Device";
import { createStackNavigator } from "@react-navigation/stack";
import { TouchableOpacity } from "react-native";
import MyIcon from "./src/Components/Icon";
import { useTranslation } from "react-i18next";
import * as SplashScreen from "expo-splash-screen";
/*
This screens shouldn't be loaded lazy as it one of the first screens the user sees
*/
import OnboardingScreen from "./src/Screens/Onboarding/Onboarding";
import NoDevicesConnectedScreen from "./src/Screens/NoDevicesConnected";
/*
This screens should be loaded lazy as they are they not seen by the user on startup
*/
const SettingsScreen = lazy(() => import("./src/Screens/Settings"));
const HelpScreen = lazy(() => import("./src/Screens/Help"));
const OpenSourceLicensesModalContent = lazy(() =>
import("./src/Screens/Settings/modals/openSourceLicences")
);
const MotorEditActionModalContent = lazy(() =>
import("./src/Screens/Device/modals/EditActions/Motor")
);
const MotorEditActionMotorModeSelectionModalContent = lazy(() =>
import("./src/Screens/Device/modals/EditActions/Motor").then((module) => ({
default: module.MotorEditActionMotorModeSelectionModalContent,
}))
);
const WaitXSecondsEditActionModalContent = lazy(() =>
import("./src/Screens/Device/modals/EditActions/Wait")
);
const StopEditActionModalContent = lazy(() =>
import("./src/Screens/Device/modals/EditActions/Stop")
);
const SearchForNewDevicesModalContent = lazy(() =>
import("./src/Screens/SearchForNewDevices")
);
const HelpCenterModalContent = lazy(() =>
import("./src/Screens/Help/modals/HelpCenter")
);
const FeedbackStepsModalContent = lazy(() =>
import("./src/Screens/Help/modals/Feedback/steps")
);
const GiveFeedbackModalContent = lazy(() =>
import("./src/Screens/Help/modals/Feedback")
);
const FeedbackFinishedModalContent = lazy(() =>
import("./src/Screens/Help/modals/Feedback/finished")
);
const SettingsAppColorSchemeModalContent = lazy(() =>
import("./src/Screens/Settings/modals/appColorScheme")
);
const SettingsAppLanguageModalContent = lazy(() =>
import("./src/Screens/Settings/modals/appLanguage")
);
const ChooseSceneModalContent = lazy(() =>
import("./src/Screens/Device/modals/ChooseScene")
);
const CreateSceneModalContent = lazy(() =>
import("./src/Screens/Device/modals/ChooseScene/CreateScene")
);
const AddSceneActionModalContent = lazy(() =>
import("./src/Screens/Device/modals/AddSceneAction")
);
const LayerSelectionModalContent = lazy(() =>
import("./src/Screens/Device/modals/AddSceneAction/LayerSelection")
);
const UpdateSceneNameModalContent = lazy(() =>
import("./src/Screens/Device/modals/UpdateSceneName")
);
const SettingsChangeDeviceDisplayNameModalContent = lazy(() =>
import("./src/Screens/Device/modals/SettingsChangeDeviceDisplayName")
);
const LightsEditActionModalContent = lazy(() =>
import("./src/Screens/Device/modals/EditActions/Lights")
);
const LightsEditActionColorModeSelectionModalContent = lazy(() =>
import("./src/Screens/Device/modals/EditActions/Lights").then((module) => ({
default: module.LightsEditActionColorModeSelectionModalContent,
}))
);
const EditActionAnimationSelectionModalContent = lazy(() =>
import("./src/Screens/Device/modals/EditActions")
);
const Drawer = createDrawerNavigator();
const Stack = createStackNavigator();
// Keep the splash screen visible while we fetch resources
SplashScreen.preventAutoHideAsync();
export function MyApp() {
const appContext = useContext(AppContext);
const { t } = useTranslation();
const [appIsReady, setAppIsReady] = useState(false);
const [showOnboarding, setShowOnboarding] = useState(false);
useEffect(() => {
const loadData = async () => {
const data = await GetMultipleData([
"appLanguage",
"appColorScheme",
"userExpertMode",
"userDeveloperMode",
"userColorSwatchesFavorites",
]);
const appLanguage = GetDataFromList(data, "appLanguage");
const appColorScheme = GetDataFromList(data, "appColorScheme");
const userExpertMode = GetDataFromList(data, "userExpertMode");
const userDeveloperMode = GetDataFromList(data, "userDeveloperMode");
const userColorSwatchesFavorites = GetDataFromList(
data,
"userColorSwatchesFavorites"
);
// if the app language is not set we know that the user is using the app for the first time
if (appLanguage === null) {
setShowOnboarding(true);
}
appContext.setAppLanguage(
appLanguage === null ? Constants.defaultLanguage : appLanguage
);
appContext.setAppColorScheme(
appColorScheme === null ? "auto" : appColorScheme
);
appContext.setIsUserExpertModeEnabled(
userExpertMode == null ? false : userExpertMode
);
appContext.setUserIsDeveloperModeEnabled(
userDeveloperMode == null ? false : userDeveloperMode
);
appContext.setUserColorSwatchesFavorites(
userColorSwatchesFavorites === null
? Constants.defaultColorSwatchesFavorites
: userColorSwatchesFavorites
);
setAppIsReady(true);
};
loadData();
}, []);
const options = ({ navigation, pageTitle }) => {
return getScreenStackOptions(
navigation,
pageTitle,
appContext.appTheme.text,
appContext.appTheme.backgroundColor,
true
);
};
const navigatonTheme = {
...DefaultTheme,
colors: {
...DefaultTheme.colors,
background: appContext.appTheme.backgroundColor,
border: "transparent",
},
};
const onLayoutRootView = useCallback(async () => {
if (appIsReady) {
// This tells the splash screen to hide immediately! If we call this after
// `setAppIsReady`, then we may see a blank screen while the app is
// loading its initial state and rendering its first pixels. So instead,
// we hide the splash screen once we know the root view has already
// performed layout.
await SplashScreen.hideAsync();
}
}, [appIsReady]);
if (!appIsReady) return null;
return (
<SafeAreaView style={{ flex: 1 }} onLayout={onLayoutRootView}>
{showOnboarding ? (
<OnboardingScreen onContinue={() => setShowOnboarding(false)} />
) : (
<>
<NavigationContainer theme={navigatonTheme}>
<Stack.Navigator screenOptions={{ headerTitleAlign: "center" }}>
<Stack.Screen
name="drawer"
component={MyDrawer}
options={{ headerShown: false }}
/>
<Stack.Screen
name="modalChooseScene"
component={ChooseSceneModalContent}
options={({ navigation }) =>
options({
navigation: navigation,
pageTitle: t(
"screens.device.scenes.modalChooseScene.pageTitle"
),
})
}
/>
<Stack.Screen
name="modalCreateScene"
component={CreateSceneModalContent}
options={({ navigation }) =>
options({
navigation: navigation,
pageTitle: t(
"screens.device.scenes.modalCreateScene.pageTitle"
),
})
}
/>
<Stack.Screen
name="modalAddSceneAction"
component={AddSceneActionModalContent}
options={({ navigation }) =>
options({
navigation: navigation,
pageTitle: t(
"screens.device.scenes.modalAddSceneAction.pageTitle"
),
})
}
/>
<Stack.Screen
name="modalLayerSelection"
component={LayerSelectionModalContent}
options={({ navigation }) =>
options({
navigation: navigation,
pageTitle: t(
"screens.device.scenes.modalLayerSelection.pageTitle"
),
})
}
/>
<Stack.Screen
name="modalLightsEditAction"
component={LightsEditActionModalContent}
options={({ navigation }) =>
options({
navigation: navigation,
})
}
/>
<Stack.Screen
name="modalLightsEditActionColorModeSelection"
component={LightsEditActionColorModeSelectionModalContent}
options={({ navigation }) =>
options({
navigation: navigation,
pageTitle: t(
"screens.device.scenes.editActions.modalLightsEditActionColorModeSelection.pageTitle"
),
})
}
/>
<Stack.Screen
name="modalEditActionAnimationInOrOutSelection"
component={EditActionAnimationSelectionModalContent}
options={({ navigation }) =>
options({
navigation: navigation,
})
}
/>
<Stack.Screen
name="modalMotorEditAction"
component={MotorEditActionModalContent}
options={({ navigation }) =>
options({
navigation: navigation,
pageTitle: t(
"screens.device.scenes.editActions.modalMotorEditAction.pageTitle"
),
})
}
/>
<Stack.Screen
name="modalMotorEditActionMotorModeSelection"
component={MotorEditActionMotorModeSelectionModalContent}
options={({ navigation }) =>
options({
navigation: navigation,
pageTitle: t(
"screens.device.scenes.editActions.modalMotorEditActionMotorModeSelection.pageTitle"
),
})
}
/>
<Stack.Screen
name="modalWaitEditAction"
component={WaitXSecondsEditActionModalContent}
options={({ navigation }) =>
options({
navigation: navigation,
pageTitle: t(
"screens.device.scenes.editActions.modalWaitEditAction.pageTitle"
),
})
}
/>
<Stack.Screen
name="modalStopEditAction"
component={StopEditActionModalContent}
options={({ navigation }) =>
options({
navigation: navigation,
pageTitle: t(
"screens.device.scenes.editActions.modalStopEditAction.pageTitle"
),
})
}
/>
<Stack.Screen
name="modalUpdateSceneName"
component={UpdateSceneNameModalContent}
options={({ navigation }) =>
options({
navigation: navigation,
pageTitle: t(
"screens.device.scenes.modalUpdateSceneName.pageTitle"
),
})
}
/>
<Stack.Screen
name="modalHelpCenter"
component={HelpCenterModalContent}
options={({ navigation }) =>
options({
navigation: navigation,
pageTitle: t("screens.help.modalHelpCenter.pageTitle"),
})
}
/>
<Stack.Screen
name="modalGiveFeedback"
component={GiveFeedbackModalContent}
options={({ navigation }) =>
options({
navigation: navigation,
pageTitle: t("screens.help.modalGiveFeedback.pageTitle"),
})
}
/>
<Stack.Screen
name="modalFeedbackSteps"
component={FeedbackStepsModalContent}
options={({ navigation }) =>
options({
navigation: navigation,
pageTitle: t("screens.help.modalFeedbackSteps.pageTitle"),
})
}
/>
<Stack.Screen
name="modalFeedbackFinished"
component={FeedbackFinishedModalContent}
options={({ navigation }) =>
options({
navigation: navigation,
pageTitle: t(
"screens.help.modalFeedbackFinished.pageTitle"
),
})
}
/>
<Stack.Screen
name="modalSettingsAppColorScheme"
component={SettingsAppColorSchemeModalContent}
options={({ navigation }) =>
options({
navigation: navigation,
pageTitle: t(
"screens.settings.cardGeneral.modalAppColorSchemePicker.pageTitle"
),
})
}
/>
<Stack.Screen
name="modalSettingsAppLanguage"
component={SettingsAppLanguageModalContent}
options={({ navigation }) =>
options({
navigation: navigation,
pageTitle: t(
"screens.settings.cardGeneral.modalLanguagePageTitle"
),
})
}
/>
<Stack.Screen
name="modalOpenSourceLicences"
component={OpenSourceLicensesModalContent}
options={({ navigation }) =>
options({
navigation: navigation,
pageTitle: t(
"screens.settings.modalOpenSourceLicences.pageTitle"
),
})
}
/>
<Stack.Screen
name="modalSettingsChangeDeviceDisplayName"
component={SettingsChangeDeviceDisplayNameModalContent}
options={({ navigation }) =>
options({
navigation: navigation,
pageTitle: t(
"screens.device.settings.modalSettingsChangeDeviceDisplayName.pageTitle"
),
})
}
/>
<Stack.Screen
name="modalSearchForNewDevices"
component={SearchForNewDevicesModalContent}
options={({ navigation }) =>
options({
navigation: navigation,
pageTitle: t("screens.modalSearchForNewDevices.pageTitle"),
})
}
/>
</Stack.Navigator>
</NavigationContainer>
<StatusBar
backgroundColor={appContext.appTheme.backgroundColor}
style={appContext.appTheme._id === "dark" ? "light" : "dark"}
/>
</>
)}
</SafeAreaView>
);
}
// This is for the left sidebar navigation
function MyDrawer() {
const appContext = useContext(AppContext);
return (
<Drawer.Navigator
screenOptions={{
headerShown: false,
//headerStyle: {
// backgroundColor: appContext.appTheme.backgroundColor,
//},
//headerTintColor: appContext.appTheme.text,
drawerStyle: {
backgroundColor: appContext.appTheme.drawer.backgroundColor,
},
}}
drawerContent={(props) => <SideBar {...props} />}
>
<Drawer.Screen
name="_noDevicesConnected"
component={NoDevicesConnectedStack}
/>
{appContext.devices.map((device) => (
<Drawer.Screen
key={device.id}
name={device.displayName}
component={DeviceScreenStack}
initialParams={{ deviceDisplayName: device.displayName }}
/>
))}
<Drawer.Screen name="Help" component={HelpScreenStack} />
<Drawer.Screen name="Settings" component={SettingsScreenStack} />
</Drawer.Navigator>
);
}
const DeviceScreenStack = (props) => {
return (
<MyScreenStack
name="device"
pageTitle={props.route.params.deviceDisplayName}
component={DeviceScreen}
navigation={props.navigation}
params={props.route.params}
/>
);
};
const NoDevicesConnectedStack = (props) => {
const { t } = useTranslation();
return (
<MyScreenStack
name="noDevicesConnected"
pageTitle={t("screens.noDevicesConnected.pageTitle")}
component={NoDevicesConnectedScreen}
navigation={props.navigation}
/>
);
};
const HelpScreenStack = (props) => {
const { t } = useTranslation();
return (
<MyScreenStack
name="help"
pageTitle={t("screens.help.pageTitle")}
component={HelpScreen}
navigation={props.navigation}
/>
);
};
const SettingsScreenStack = (props) => {
const { t } = useTranslation();
return (
<MyScreenStack
name="settings"
pageTitle={t("screens.settings.pageTitle")}
component={SettingsScreen}
navigation={props.navigation}
/>
);
};
function getScreenStackOptions(
navigation,
pageTitle,
headerTintColor,
headerBackgroundColor,
isModalScreen
) {
return {
title: pageTitle,
headerLeft: () => (
<TouchableOpacity
onPress={() =>
isModalScreen ? navigation.goBack() : navigation.toggleDrawer()
}
>
<MyIcon
name={isModalScreen ? "chevron-left" : "menu"}
size={24}
style={[AppStyles.headerNavigationIcons]}
/>
</TouchableOpacity>
),
headerTintColor: headerTintColor,
headerTitleAlign: "center",
headerStyle: {
backgroundColor: headerBackgroundColor,
height: IsPlatformIos() ? 76 : 56,
},
};
}
function MyScreenStack({ navigation, name, pageTitle, component, params }) {
const appContext = useContext(AppContext);
return (
<Stack.Navigator initialRouteName={name}>
<Stack.Screen
name={name}
component={component}
initialParams={params}
options={getScreenStackOptions(
navigation,
pageTitle,
appContext.appTheme.text,
appContext.appTheme.backgroundColor
)}
/>
</Stack.Navigator>
);
}
export default function App() {
return (
<Suspense
fallback={
<View
style={{ flex: 1, justifyContent: "center", alignItems: "center" }}
>
<Text>Loading...</Text>
</View>
}
>
<AppProvider>
<MyApp />
</AppProvider>
</Suspense>
);
}