diff --git a/package.json b/package.json index af492ee..9c0c553 100644 --- a/package.json +++ b/package.json @@ -16,6 +16,7 @@ "ios": "react-native run-ios", "lint": "eslint .", "lint:fix": "eslint . --fix", + "typecheck": "tsc --noEmit", "start": "react-native start", "test": "jest --passWithNoTests", "postinstall": "yarn run npm-license-crawler -onlyDirectDependencies -json licenses.json && patch-package", diff --git a/src/api/ApiHandler.tsx b/src/api/ApiHandler.tsx index 2255247..f3c8677 100644 --- a/src/api/ApiHandler.tsx +++ b/src/api/ApiHandler.tsx @@ -52,9 +52,15 @@ export const ApiProvider: FC> = ({ children }) => { console.log('new Api'); useEffect(() => { - dispatch(setTriedToConnect({ triedToConnect: false })); + if (configIndex === null) { + console.log('ApiProvider - configIndex is null'); - if (currentConfiguration && configIndex !== null) { + return; + } + + dispatch(setTriedToConnect({ triedToConnect: false, index: configIndex })); + + if (currentConfiguration) { console.info( 'Initializing API Handler', currentConfiguration, @@ -62,7 +68,7 @@ export const ApiProvider: FC> = ({ children }) => { ); api.registerOnDisconnectedHandler(() => { - dispatch(clearOpenDtuState()); + dispatch(clearOpenDtuState({ index: configIndex })); }); api.disconnect(); @@ -70,24 +76,28 @@ export const ApiProvider: FC> = ({ children }) => { api.setConfig(currentConfiguration, configIndex); api.registerOnConnectedHandler(index => { - dispatch(setIsConnected({ isConnected: true })); + dispatch(setIsConnected({ isConnected: true, index: configIndex })); dispatch(setDeviceState({ deviceState: DeviceState.Connected, index })); }); api.registerOnDisconnectedHandler(() => { - dispatch(setIsConnected({ isConnected: false })); + dispatch(setIsConnected({ isConnected: false, index: configIndex })); }); api.registerLiveDataHandler((data, valid, index) => { - dispatch(setTriedToConnect({ triedToConnect: true })); - dispatch(setLiveData({ data, valid })); + dispatch( + setTriedToConnect({ triedToConnect: true, index: configIndex }), + ); + dispatch(setLiveData({ data, valid, index: configIndex })); dispatch(setDeviceState({ deviceState: DeviceState.Connected, index })); }); api.registerHttpStatusHandler( ({ systemStatus, networkStatus, ntpStatus, mqttStatus }, index) => { if (systemStatus) { - dispatch(setSystemStatus({ data: systemStatus })); + dispatch( + setSystemStatus({ data: systemStatus, index: configIndex }), + ); dispatch( updateDtuHostname({ hostname: systemStatus.hostname, @@ -103,15 +113,17 @@ export const ApiProvider: FC> = ({ children }) => { } if (networkStatus) { - dispatch(setNetworkStatus({ data: networkStatus })); + dispatch( + setNetworkStatus({ data: networkStatus, index: configIndex }), + ); } if (ntpStatus) { - dispatch(setNtpStatus({ data: ntpStatus })); + dispatch(setNtpStatus({ data: ntpStatus, index: configIndex })); } if (mqttStatus) { - dispatch(setMqttStatus({ data: mqttStatus })); + dispatch(setMqttStatus({ data: mqttStatus, index: configIndex })); } setDeviceState({ deviceState: DeviceState.Connected, index }); diff --git a/src/api/opendtuapi.ts b/src/api/opendtuapi.ts index eef038a..e1aef81 100644 --- a/src/api/opendtuapi.ts +++ b/src/api/opendtuapi.ts @@ -69,7 +69,7 @@ class OpenDtuApi { this.baseUrl = baseUrl; } - public setUserString(userString: string): void { + public setUserString(userString: string | null): void { this.userString = userString; } @@ -142,7 +142,7 @@ class OpenDtuApi { this.onDisconnectedHandler = null; } - private async getSystemStatusFromUrl( + public async getSystemStatusFromUrl( url: URL, ): Promise { // GET /api/system/status @@ -288,13 +288,15 @@ class OpenDtuApi { return; } - if (this.baseUrl && this.userString) { + if (this.baseUrl) { const urlObject = new URL(this.baseUrl); const authString = this.getAuthString(); const protocol = urlObject.protocol === 'https:' ? 'wss' : 'ws'; const host = urlObject.host; - const url = `${protocol}://${authString}${host}/livedata`; + const url = `${protocol}://${authString ?? ''}${host}/livedata`; + + console.log(`Connecting websocket to ${url}`); this.ws = new WebSocket(url); @@ -466,9 +468,13 @@ class OpenDtuApi { ); } - private getAuthString(): string { + private getAuthString(): string | null { let user = null; + if (!this.userString) { + return null; + } + try { user = JSON.parse(this.userString || ''); } catch { @@ -492,6 +498,10 @@ class OpenDtuApi { 'GET', ); + if (!res) { + return null; + } + if (res.status === 200) { return await res.json(); } @@ -506,6 +516,10 @@ class OpenDtuApi { const res = await this.makeAuthenticatedRequest('/api/ntp/status', 'GET'); + if (!res) { + return null; + } + if (res.status === 200) { return await res.json(); } @@ -520,6 +534,10 @@ class OpenDtuApi { const res = await this.makeAuthenticatedRequest('/api/mqtt/status', 'GET'); + if (!res) { + return null; + } + if (res.status === 200) { return await res.json(); } @@ -531,7 +549,7 @@ class OpenDtuApi { route: string, method: string, body: string | null = null, - ): Promise { + ): Promise { const authString = this.getAuthString(); const controller = new AbortController(); @@ -546,8 +564,8 @@ class OpenDtuApi { signal: controller.signal, headers: { 'X-Requested-With': 'XMLHttpRequest', - Authorization: 'Basic ' + authString, 'Content-Type': 'application/json', + ...(authString ? { Authorization: 'Basic ' + authString } : {}), }, }; @@ -558,12 +576,16 @@ class OpenDtuApi { const url = `${authString}${this.baseUrl}${route}`; console.log('makeAuthenticatedRequest', url, requestOptions); - const res = await fetch(url, requestOptions); - if (res.status === 200) { + + try { + const res = await fetch(url, requestOptions); clearTimeout(abortTimeout); - } - return res; + return res; + } catch (error) { + console.log('makeAuthenticatedRequest error', error); + return null; + } } } diff --git a/src/components/OpenDTUValue.tsx b/src/components/OpenDTUValue.tsx index d1ea912..a15d441 100644 --- a/src/components/OpenDTUValue.tsx +++ b/src/components/OpenDTUValue.tsx @@ -5,7 +5,7 @@ import { Text, useTheme } from 'react-native-paper'; import type { ValueObject } from '@/types/opendtu/status'; export interface OpenDTUValueProps { - statusValue?: ValueObject; + statusValue?: ValueObject | null; textWhenInvalid?: string; textProps?: Omit, 'children'>; } diff --git a/src/database/index.tsx b/src/database/index.tsx index dc68a0c..6ca1d8c 100644 --- a/src/database/index.tsx +++ b/src/database/index.tsx @@ -244,7 +244,8 @@ const DatabaseProvider: FC> = ({ children }) => { const inverters = useAppSelector( state => state.settings.selectedDtuConfig !== null - ? state.opendtu.liveData?.inverters ?? null + ? state.opendtu.dtuStates[state.settings.selectedDtuConfig]?.liveData + ?.inverters ?? null : null, (left, right) => left === null && right === null diff --git a/src/github/FetchHandler.tsx b/src/github/FetchHandler.tsx index 5f518c3..a1c002a 100644 --- a/src/github/FetchHandler.tsx +++ b/src/github/FetchHandler.tsx @@ -9,6 +9,8 @@ import { setReleases, } from '@/slices/github'; +import useDeviceIndex from '@/hooks/useDeviceIndex'; + import ago from '@/utils/ago'; import { @@ -20,8 +22,11 @@ import { useAppDispatch, useAppSelector } from '@/store'; const FetchHandler: FC = () => { const dispatch = useAppDispatch(); + const index = useDeviceIndex(); - const isConnected = useAppSelector(state => state.opendtu.isConnected); + const isConnected = useAppSelector(state => + index === null ? undefined : state.opendtu.dtuStates[index]?.isConnected, + ); const latestReleaseRefetchOk = useAppSelector(state => state.github.latestRelease.lastUpdate ? ago(state.github.latestRelease.lastUpdate) > 1000 * 60 * 10 // 10 minutes diff --git a/src/hooks/useDeviceIndex.ts b/src/hooks/useDeviceIndex.ts new file mode 100644 index 0000000..a93b99c --- /dev/null +++ b/src/hooks/useDeviceIndex.ts @@ -0,0 +1,7 @@ +import { useAppSelector } from '@/store'; + +const useDeviceIndex = (): number | null => { + return useAppSelector(state => state.settings.selectedDtuConfig); +}; + +export default useDeviceIndex; diff --git a/src/hooks/useDtuState.ts b/src/hooks/useDtuState.ts new file mode 100644 index 0000000..eed71e3 --- /dev/null +++ b/src/hooks/useDtuState.ts @@ -0,0 +1,22 @@ +import type { EqualityFn } from 'react-redux'; + +import type { OpenDTUDeviceState } from '@/types/opendtu/state'; + +import useDeviceIndex from '@/hooks/useDeviceIndex'; + +import { useAppSelector } from '@/store'; + +const useDtuState = ( + selector: (state: OpenDTUDeviceState | undefined) => T, + equalityFn?: EqualityFn, +): T | undefined => { + const index = useDeviceIndex(); + + return useAppSelector( + state => + index === null ? undefined : selector(state.opendtu.dtuStates[index]), + equalityFn, + ); +}; + +export default useDtuState; diff --git a/src/hooks/useHasLiveData.ts b/src/hooks/useHasLiveData.ts index d518e7b..095eb69 100644 --- a/src/hooks/useHasLiveData.ts +++ b/src/hooks/useHasLiveData.ts @@ -13,13 +13,20 @@ const useHasLiveData = (): boolean => { );*/ return useAppSelector(state => { - const liveData = state.opendtu.liveData; + const index = state.settings.selectedDtuConfig; + + if (index === null) { + console.log('index is null'); + return false; + } + + const liveData = state.opendtu.dtuStates[index]?.liveData ?? null; if (liveData === null) { console.log('liveData is null'); return false; } - const lastUpdate = liveData.lastUpdate; + const lastUpdate = liveData?.lastUpdate ?? null; if (lastUpdate === null) { console.log('lastUpdate is null'); return false; diff --git a/src/hooks/useIsConnected.ts b/src/hooks/useIsConnected.ts index ee7fe02..91c0b07 100644 --- a/src/hooks/useIsConnected.ts +++ b/src/hooks/useIsConnected.ts @@ -1,7 +1,15 @@ +import useDeviceIndex from '@/hooks/useDeviceIndex'; + import { useAppSelector } from '@/store'; const useIsConnected = (): boolean => { - return useAppSelector(state => state.opendtu.isConnected); + const index = useDeviceIndex(); + + return useAppSelector(state => + index === null + ? false + : state.opendtu.dtuStates[index]?.isConnected ?? false, + ); }; export default useIsConnected; diff --git a/src/hooks/useLivedata.ts b/src/hooks/useLivedata.ts index e280615..4c8b905 100644 --- a/src/hooks/useLivedata.ts +++ b/src/hooks/useLivedata.ts @@ -6,9 +6,18 @@ import { useAppSelector } from '@/store'; const useLivedata = ( selector: (state: LiveData | null) => T, - equalityFn?: EqualityFn, -): T => { - return useAppSelector(state => selector(state.opendtu.liveData), equalityFn); + equalityFn?: EqualityFn, +): T | null => { + const currentIndex = useAppSelector( + state => state.settings.selectedDtuConfig, + ); + return useAppSelector( + state => + currentIndex + ? selector(state.opendtu.dtuStates[currentIndex]?.liveData ?? null) + : null, + equalityFn, + ); }; export default useLivedata; diff --git a/src/hooks/useTriedToConnect.ts b/src/hooks/useTriedToConnect.ts index c6bedad..5cf024f 100644 --- a/src/hooks/useTriedToConnect.ts +++ b/src/hooks/useTriedToConnect.ts @@ -1,7 +1,15 @@ +import useDeviceIndex from '@/hooks/useDeviceIndex'; + import { useAppSelector } from '@/store'; const useTriedToConnect = (): boolean => { - return useAppSelector(state => state.opendtu.triedToConnect); + const index = useDeviceIndex(); + + return useAppSelector(state => + index === null + ? false + : state.opendtu.dtuStates[index]?.triedToConnect ?? false, + ); }; export default useTriedToConnect; diff --git a/src/slices/opendtu.ts b/src/slices/opendtu.ts index 98bee95..02befe5 100644 --- a/src/slices/opendtu.ts +++ b/src/slices/opendtu.ts @@ -1,7 +1,7 @@ import { createSlice } from '@reduxjs/toolkit'; import type { - OpenDTUState, + OpenDTUReduxState, SetLiveDataAction, SetSetupBaseUrlAction, SetSetupUserStringAction, @@ -12,21 +12,17 @@ import type { SetNetworkStatusAction, SetNtpStatusAction, SetMqttStatusAction, + ClearOpenDtuStateAction, + OpenDTUDeviceState, } from '@/types/opendtu/state'; -const initialState: OpenDTUState = { - liveData: null, - systemStatus: null, - networkStatus: null, - ntpStatus: null, - mqttStatus: null, +const initialState: OpenDTUReduxState = { + dtuStates: {}, setup: { userString: null, baseUrl: null, }, - isConnected: false, deviceState: {}, - triedToConnect: false, }; const opendtuSlice = createSlice({ @@ -35,14 +31,22 @@ const opendtuSlice = createSlice({ reducers: { setLiveData: (state, action: SetLiveDataAction) => { if (action.payload.valid) { - state.liveData = action.payload.data; + if (!state.dtuStates[action.payload.index]) { + state.dtuStates[action.payload.index] = {}; + } + + (state.dtuStates[action.payload.index] as OpenDTUDeviceState).liveData = + action.payload.data; } }, - clearLiveData: state => { - state.liveData = null; - }, setSystemStatus: (state, action: SetSystemStatusAction) => { - state.systemStatus = action.payload.data; + if (!state.dtuStates[action.payload.index]) { + state.dtuStates[action.payload.index] = {}; + } + + ( + state.dtuStates[action.payload.index] as OpenDTUDeviceState + ).systemStatus = action.payload.data; }, setSetupUserString: (state, action: SetSetupUserStringAction) => { if (!state.setup) { @@ -71,7 +75,13 @@ const opendtuSlice = createSlice({ }; }, setIsConnected: (state, action: SetIsConnectedAction) => { - state.isConnected = action.payload.isConnected; + if (!state.dtuStates[action.payload.index]) { + state.dtuStates[action.payload.index] = {}; + } + + ( + state.dtuStates[action.payload.index] as OpenDTUDeviceState + ).isConnected = action.payload.isConnected; }, setDeviceState: (state, action: SetDeviceStateAction) => { if (!state.deviceState) { @@ -84,30 +94,47 @@ const opendtuSlice = createSlice({ state.deviceState = {}; }, setTriedToConnect: (state, action: SetTriedToConnectAction) => { - state.triedToConnect = action.payload.triedToConnect; + if (!state.dtuStates[action.payload.index]) { + state.dtuStates[action.payload.index] = {}; + } + + ( + state.dtuStates[action.payload.index] as OpenDTUDeviceState + ).triedToConnect = action.payload.triedToConnect; }, - clearOpenDtuState: state => { - state.liveData = null; - state.systemStatus = null; - state.networkStatus = null; - state.ntpStatus = null; - state.mqttStatus = null; + clearOpenDtuState: (state, action: ClearOpenDtuStateAction) => { + state.dtuStates[action.payload.index] = {}; }, setNetworkStatus: (state, action: SetNetworkStatusAction) => { - state.networkStatus = action.payload.data; + if (!state.dtuStates[action.payload.index]) { + state.dtuStates[action.payload.index] = {}; + } + + ( + state.dtuStates[action.payload.index] as OpenDTUDeviceState + ).networkStatus = action.payload.data; }, setNtpStatus: (state, action: SetNtpStatusAction) => { - state.ntpStatus = action.payload.data; + if (!state.dtuStates[action.payload.index]) { + state.dtuStates[action.payload.index] = {}; + } + + (state.dtuStates[action.payload.index] as OpenDTUDeviceState).ntpStatus = + action.payload.data; }, setMqttStatus: (state, action: SetMqttStatusAction) => { - state.mqttStatus = action.payload.data; + if (!state.dtuStates[action.payload.index]) { + state.dtuStates[action.payload.index] = {}; + } + + (state.dtuStates[action.payload.index] as OpenDTUDeviceState).mqttStatus = + action.payload.data; }, }, }); export const { setLiveData, - clearLiveData, setSystemStatus, setSetupUserString, setSetupBaseUrl, diff --git a/src/translations/translation-files b/src/translations/translation-files index ce1477e..9c9508e 160000 --- a/src/translations/translation-files +++ b/src/translations/translation-files @@ -1 +1 @@ -Subproject commit ce1477e6c0ce2337cbfdd9b4a7b60aec99fd7935 +Subproject commit 9c9508efbca8a4f4c18aff657fbc533a27e9c829 diff --git a/src/types/opendtu/state.ts b/src/types/opendtu/state.ts index b969f66..e0ece19 100644 --- a/src/types/opendtu/state.ts +++ b/src/types/opendtu/state.ts @@ -21,16 +21,18 @@ export enum DeviceState { } export type SetLiveDataAction = PayloadAction<{ + index: Index; valid: boolean; data: LiveData; }>; export type SetSystemStatusAction = PayloadAction<{ + index: Index; data: SystemStatus; }>; export type SetSetupUserStringAction = PayloadAction<{ - userString: string; + userString: string | null; }>; export type SetSetupBaseUrlAction = PayloadAction<{ @@ -38,6 +40,7 @@ export type SetSetupBaseUrlAction = PayloadAction<{ }>; export type SetIsConnectedAction = PayloadAction<{ + index: Index; isConnected: boolean; }>; @@ -47,18 +50,26 @@ export type SetDeviceStateAction = PayloadAction<{ }>; export type SetTriedToConnectAction = PayloadAction<{ + index: Index; triedToConnect: boolean; }>; +export type ClearOpenDtuStateAction = PayloadAction<{ + index: Index; +}>; + export type SetNetworkStatusAction = PayloadAction<{ + index: Index; data: NetworkStatus; }>; export type SetNtpStatusAction = PayloadAction<{ + index: Index; data: NtpStatus; }>; export type SetMqttStatusAction = PayloadAction<{ + index: Index; data: MqttStatus; }>; @@ -67,14 +78,18 @@ export interface OpenDTUSetup { userString: string | null; } -export interface OpenDTUState { - liveData: LiveData | null; - systemStatus: SystemStatus | null; - networkStatus: NetworkStatus | null; - ntpStatus: NtpStatus | null; - mqttStatus: MqttStatus | null; +export interface OpenDTUDeviceState { + liveData?: LiveData; + systemStatus?: SystemStatus; + networkStatus?: NetworkStatus; + ntpStatus?: NtpStatus; + mqttStatus?: MqttStatus; + isConnected?: boolean; + triedToConnect?: boolean; +} + +export interface OpenDTUReduxState { + dtuStates: Record; setup: OpenDTUSetup; - isConnected: boolean; deviceState: Record; - triedToConnect: boolean; } diff --git a/src/types/settings.ts b/src/types/settings.ts index fb031d5..b19b83b 100644 --- a/src/types/settings.ts +++ b/src/types/settings.ts @@ -7,7 +7,7 @@ export type Index = number; export interface OpenDTUConfig { // Config to connect to OpenDTU. It will be possible to configure multiple baseUrl: string; - userString: string; // same as 'user' from webinterface localstorage + userString: string | null; // same as 'user' from webinterface localstorage (null means no auth, so read-only) serialNumber: string | null; // null means never connected hostname: string | null; // null means never connected customName: string | null; // null means customName is not set diff --git a/src/views/navigation/screens/AboutOpenDTUScreen.tsx b/src/views/navigation/screens/AboutOpenDTUScreen.tsx index e382ad4..aaa3b89 100644 --- a/src/views/navigation/screens/AboutOpenDTUScreen.tsx +++ b/src/views/navigation/screens/AboutOpenDTUScreen.tsx @@ -12,6 +12,8 @@ import { Appbar, List, useTheme } from 'react-native-paper'; import SettingsSurface from '@/components/styled/SettingsSurface'; +import useDtuState from '@/hooks/useDtuState'; + import formatBytes from '@/utils/formatBytes'; import percentage from '@/utils/percentage'; @@ -28,7 +30,7 @@ const AboutOpenDTUScreen: FC = () => { navigation.goBack(); }, [navigation]); - const systemStatus = useAppSelector(state => state.opendtu.systemStatus); + const systemStatus = useDtuState(state => state?.systemStatus); const latestVersion = useAppSelector( state => state.github.latestRelease.data?.tag_name, diff --git a/src/views/navigation/screens/MainScreen.tsx b/src/views/navigation/screens/MainScreen.tsx index d0900b4..19ecc1f 100644 --- a/src/views/navigation/screens/MainScreen.tsx +++ b/src/views/navigation/screens/MainScreen.tsx @@ -15,7 +15,8 @@ const MainScreen: FC = ({ navigation }) => { state.settings.selectedDtuConfig !== null ? (state.settings.dtuConfigs[state.settings.selectedDtuConfig] ?.customName || - state.opendtu.systemStatus?.hostname) ?? + state.opendtu.dtuStates[state.settings.selectedDtuConfig] + ?.systemStatus?.hostname) ?? null : null, ); diff --git a/src/views/navigation/screens/MqttInformationScreen.tsx b/src/views/navigation/screens/MqttInformationScreen.tsx index 72e053f..5ca4a44 100644 --- a/src/views/navigation/screens/MqttInformationScreen.tsx +++ b/src/views/navigation/screens/MqttInformationScreen.tsx @@ -10,7 +10,8 @@ import { Appbar, List, useTheme } from 'react-native-paper'; import SettingsSurface from '@/components/styled/SettingsSurface'; -import { useAppSelector } from '@/store'; +import useDtuState from '@/hooks/useDtuState'; + import { StyledSafeAreaView } from '@/style'; const MqttInformationScreen: FC = () => { @@ -23,7 +24,7 @@ const MqttInformationScreen: FC = () => { navigation.goBack(); }, [navigation]); - const mqttStatus = useAppSelector(state => state.opendtu.mqttStatus); + const mqttStatus = useDtuState(state => state?.mqttStatus); return ( <> diff --git a/src/views/navigation/screens/NetworkInformationScreen.tsx b/src/views/navigation/screens/NetworkInformationScreen.tsx index 2e9a9e8..fca14b0 100644 --- a/src/views/navigation/screens/NetworkInformationScreen.tsx +++ b/src/views/navigation/screens/NetworkInformationScreen.tsx @@ -10,7 +10,8 @@ import { Appbar, List, useTheme } from 'react-native-paper'; import SettingsSurface from '@/components/styled/SettingsSurface'; -import { useAppSelector } from '@/store'; +import useDtuState from '@/hooks/useDtuState'; + import { StyledSafeAreaView } from '@/style'; const NetworkInformationScreen: FC = () => { @@ -23,7 +24,7 @@ const NetworkInformationScreen: FC = () => { navigation.goBack(); }, [navigation]); - const networkStatus = useAppSelector(state => state.opendtu.networkStatus); + const networkStatus = useDtuState(state => state?.networkStatus); const wifiQuality = useMemo(() => { let quality = 0; diff --git a/src/views/navigation/screens/NtpInformationScreen.tsx b/src/views/navigation/screens/NtpInformationScreen.tsx index 6200d99..176465b 100644 --- a/src/views/navigation/screens/NtpInformationScreen.tsx +++ b/src/views/navigation/screens/NtpInformationScreen.tsx @@ -10,7 +10,8 @@ import { Appbar, List, useTheme } from 'react-native-paper'; import SettingsSurface from '@/components/styled/SettingsSurface'; -import { useAppSelector } from '@/store'; +import useDtuState from '@/hooks/useDtuState'; + import { StyledSafeAreaView } from '@/style'; const NtpInformationScreen: FC = () => { @@ -23,7 +24,7 @@ const NtpInformationScreen: FC = () => { navigation.goBack(); }, [navigation]); - const ntpStatus = useAppSelector(state => state.opendtu.ntpStatus); + const ntpStatus = useDtuState(state => state?.ntpStatus); return ( <> diff --git a/src/views/navigation/screens/SetupAddOpenDTUScreen.tsx b/src/views/navigation/screens/SetupAddOpenDTUScreen.tsx index 11d5c87..2c60769 100644 --- a/src/views/navigation/screens/SetupAddOpenDTUScreen.tsx +++ b/src/views/navigation/screens/SetupAddOpenDTUScreen.tsx @@ -59,7 +59,7 @@ const SetupAddOpenDTUScreen: FC = ({ navigation }) => { try { url = new URL(ourAddress); } catch { - setError('Invalid address'); + setError(t('setup.errors.invalidAddress')); return; } @@ -110,7 +110,7 @@ const SetupAddOpenDTUScreen: FC = ({ navigation }) => { navigation.navigate('SetupAuthenticateOpenDTUInstanceScreen'); setLoading(false); - }, [address, baseUrls, dispatch, navigation, openDtuApi]); + }, [t, address, baseUrls, dispatch, navigation, openDtuApi]); const valid = !!address; diff --git a/src/views/navigation/screens/SetupAuthenticateOpenDTUInstanceScreen.tsx b/src/views/navigation/screens/SetupAuthenticateOpenDTUInstanceScreen.tsx index 9a2eeac..696f300 100644 --- a/src/views/navigation/screens/SetupAuthenticateOpenDTUInstanceScreen.tsx +++ b/src/views/navigation/screens/SetupAuthenticateOpenDTUInstanceScreen.tsx @@ -5,6 +5,7 @@ import { Box } from 'react-native-flex-layout'; import { logger } from 'react-native-logs'; import { Button, + Divider, HelperText, TextInput, Title, @@ -13,6 +14,8 @@ import { import { setSetupUserString } from '@/slices/opendtu'; +import { DeviceState } from '@/types/opendtu/state'; + import StyledTextInput from '@/components/styled/StyledTextInput'; import { useApi } from '@/api/ApiHandler'; @@ -31,7 +34,7 @@ const SetupAuthenticateOpenDTUInstanceScreen: FC = ({ const openDtuApi = useApi(); - const address = useAppSelector(state => state.opendtu.setup.baseUrl); + const address = useAppSelector(state => state.opendtu.setup?.baseUrl); const previousStepValid = address !== null; @@ -57,7 +60,7 @@ const SetupAuthenticateOpenDTUInstanceScreen: FC = ({ ); if (result === false) { - setError('Invalid credentials'); + setError(t('setup.errors.invalidCredentials')); log.info('Invalid credentials'); setLoading(false); return; @@ -70,12 +73,40 @@ const SetupAuthenticateOpenDTUInstanceScreen: FC = ({ return; } else { - setError('Something went wrong! Please try again.'); + setError(t('setup.errors.genericError')); log.info('Something went wrong! Please try again.'); } setLoading(false); - }, [address, username, password, openDtuApi, dispatch, navigation]); + }, [t, address, username, password, openDtuApi, dispatch, navigation]); + + const handleAnonymous = useCallback(async () => { + if (!address) return; + + setLoading(true); + setError(null); + + try { + const result = await openDtuApi.getSystemStatusFromUrl(new URL(address)); + + if (result.deviceState !== DeviceState.Reachable) { + setError('Invalid credentials'); + log.info('Invalid credentials'); + setLoading(false); + return; + } + + dispatch(setSetupUserString({ userString: null })); + navigation.navigate('SetupOpenDTUCompleteScreen'); + setLoading(false); + + setLoading(false); + } catch (e) { + setError(t('setup.errors.genericError')); + log.info('Error while connecting to OpenDTU instance', e); + setLoading(false); + } + }, [address, openDtuApi, dispatch, navigation, t]); return ( @@ -131,6 +162,15 @@ const SetupAuthenticateOpenDTUInstanceScreen: FC = ({ > {t('setup.login')} + + ); diff --git a/src/views/navigation/screens/SetupOpenDTUCompleteScreen.tsx b/src/views/navigation/screens/SetupOpenDTUCompleteScreen.tsx index 6bc4118..c16cda2 100644 --- a/src/views/navigation/screens/SetupOpenDTUCompleteScreen.tsx +++ b/src/views/navigation/screens/SetupOpenDTUCompleteScreen.tsx @@ -21,7 +21,7 @@ const SetupOpenDTUCompleteScreen: FC = ({ const setupConfig = useAppSelector(state => state.opendtu.setup); const handleFinishSetup = useCallback(() => { - if (!setupConfig.userString || !setupConfig.baseUrl) return; + if (!setupConfig.baseUrl) return; dispatch( addDtuConfig({ diff --git a/src/views/navigation/tabs/MainSettingsTab.tsx b/src/views/navigation/tabs/MainSettingsTab.tsx index d719057..b2182be 100644 --- a/src/views/navigation/tabs/MainSettingsTab.tsx +++ b/src/views/navigation/tabs/MainSettingsTab.tsx @@ -2,7 +2,7 @@ import type { NavigationProp, ParamListBase } from '@react-navigation/native'; import { useNavigation } from '@react-navigation/native'; import type { FC } from 'react'; -import { useCallback, useState } from 'react'; +import { useMemo, useCallback, useState } from 'react'; import { useTranslation } from 'react-i18next'; import { ScrollView } from 'react-native'; import { Box } from 'react-native-flex-layout'; @@ -11,6 +11,7 @@ import { Badge, List, useTheme } from 'react-native-paper'; import ChangeLanguageModal from '@/components/modals/ChangeLanguageModal'; import ChangeThemeModal from '@/components/modals/ChangeThemeModal'; +import useDtuState from '@/hooks/useDtuState'; import useHasNewAppVersion from '@/hooks/useHasNewAppVersion'; import useIsConnected from '@/hooks/useIsConnected'; @@ -39,6 +40,28 @@ const MainSettingsTab: FC = () => { usedForIndicatorOnly: true, }); + const hasSystemInformation = !!useDtuState(state => !!state?.systemStatus); + const hasNetworkInformation = !!useDtuState(state => !!state?.networkStatus); + const hasNtpInformation = !!useDtuState(state => !!state?.ntpStatus); + const hasMqttInformation = !!useDtuState(state => !!state?.mqttStatus); + + const systemInformationDisabled = useMemo( + () => !hasSystemInformation || !websocketConnected, + [hasSystemInformation, websocketConnected], + ); + const networkInformationDisabled = useMemo( + () => !hasNetworkInformation || !websocketConnected, + [hasNetworkInformation, websocketConnected], + ); + const ntpInformationDisabled = useMemo( + () => !hasNtpInformation || !websocketConnected, + [hasNtpInformation, websocketConnected], + ); + const mqttInformationDisabled = useMemo( + () => !hasMqttInformation || !websocketConnected, + [hasMqttInformation, websocketConnected], + ); + const handleAbout = useCallback(() => { navigation.navigate('AboutSettingsScreen'); }, [navigation]); @@ -74,32 +97,32 @@ const MainSettingsTab: FC = () => { description={t('opendtu.systemInformationDescription')} left={props => } onPress={handleAboutOpenDTU} - disabled={!websocketConnected} - style={{ opacity: websocketConnected ? 1 : 0.5 }} + disabled={systemInformationDisabled} + style={{ opacity: systemInformationDisabled ? 0.5 : 1 }} /> } onPress={handleNetworkInformation} - disabled={!websocketConnected} - style={{ opacity: websocketConnected ? 1 : 0.5 }} + disabled={networkInformationDisabled} + style={{ opacity: networkInformationDisabled ? 0.5 : 1 }} /> } onPress={handleNtpInformation} - disabled={!websocketConnected} - style={{ opacity: websocketConnected ? 1 : 0.5 }} + disabled={ntpInformationDisabled} + style={{ opacity: ntpInformationDisabled ? 0.5 : 1 }} /> } onPress={handleMqttInformation} - disabled={!websocketConnected} - style={{ opacity: websocketConnected ? 1 : 0.5 }} + disabled={mqttInformationDisabled} + style={{ opacity: mqttInformationDisabled ? 0.5 : 1 }} />