Skip to content

Commit 6244abb

Browse files
Merge pull request #40 from OpenDTU-App/32-implement-debug-menu
2 parents 74c3a3d + 4e3e006 commit 6244abb

File tree

10 files changed

+192
-4
lines changed

10 files changed

+192
-4
lines changed

.githooks/pre-commit

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,13 @@
11
#!/bin/bash
22
# Get latest git tag
33
latest_tag=$(git describe --tags --abbrev=0)
4-
version_from_tag=$(echo "$latest_tag" | grep -o -E '[0-9]+\.[0-9]+\.[0-9]+')
4+
latest_tag_without_v=${latest_tag#"v"}
5+
version_from_tag=$(echo "$latest_tag_without_v" | grep -P '^(?P<major>0|[1-9]\d*)\.(?P<minor>0|[1-9]\d*)\.(?P<patch>0|[1-9]\d*)(?:-(?P<prerelease>(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+(?P<buildmetadata>[0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$')
56

67
# Get the version from package.json
78
version=$(jq -r '.version' package.json)
89

9-
echo "Latest tag: $latest_tag"
10+
echo "Latest tag: $latest_tag_without_v"
1011
echo "Version from tag: $version_from_tag"
1112
echo "Version from package.json: $version"
1213

@@ -15,3 +16,5 @@ if [ "$version" != "$version_from_tag" ]; then
1516
echo "Version in package.json and tag are not the same"
1617
exit 1
1718
fi
19+
20+
echo "Version in package.json and tag are the same"
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
import { useCallback, useRef, useState } from 'react';
2+
3+
const useRequireMultiplePresses = (
4+
onPress: () => void,
5+
requiredPresses = 3,
6+
timeout = 500,
7+
) => {
8+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
9+
const [_, setPresses] = useState(0);
10+
const timeoutRef = useRef<NodeJS.Timeout>();
11+
12+
return useCallback(() => {
13+
setPresses(prevPresses => {
14+
if (prevPresses === 0) {
15+
timeoutRef.current = setTimeout(() => {
16+
setPresses(0);
17+
}, timeout);
18+
}
19+
20+
if (prevPresses + 1 === requiredPresses) {
21+
clearTimeout(timeoutRef.current);
22+
onPress();
23+
return 0;
24+
}
25+
26+
return prevPresses + 1;
27+
});
28+
}, [onPress, requiredPresses, timeout]);
29+
};
30+
31+
export default useRequireMultiplePresses;

src/hooks/useSettings.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import type { EqualityFn } from 'react-redux';
2+
3+
import type { SettingsState } from '@/types/settings';
4+
5+
import { useAppSelector } from '@/store';
6+
7+
const useSettings = <T>(
8+
selector: (state: SettingsState) => T,
9+
equalityFn?: EqualityFn<T>,
10+
): T => useAppSelector(state => selector(state.settings), equalityFn);
11+
12+
export default useSettings;

src/slices/settings.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import type {
1919
UpdateDtuSerialNumberAction,
2020
SetLanguageAction,
2121
EnableAppUpdatesAction,
22+
DebugEnabledAction,
2223
} from '@/types/settings';
2324

2425
const initialState: SettingsState = {
@@ -28,6 +29,7 @@ const initialState: SettingsState = {
2829
selectedDtuConfig: null,
2930
databaseConfigs: [],
3031
enableAppUpdates: null,
32+
debugEnabled: false,
3133
};
3234

3335
const log = logger.createLogger();
@@ -209,6 +211,9 @@ const settingsSlice = createSlice({
209211
setEnableAppUpdates: (state, action: EnableAppUpdatesAction) => {
210212
state.enableAppUpdates = action.payload.enable;
211213
},
214+
setDebugEnabled: (state, action: DebugEnabledAction) => {
215+
state.debugEnabled = action.payload.debugEnabled;
216+
},
212217
},
213218
});
214219

@@ -231,6 +236,7 @@ export const {
231236
updateDatabaseConfig,
232237
updateDTUDatabaseUuid,
233238
setEnableAppUpdates,
239+
setDebugEnabled,
234240
} = settingsSlice.actions;
235241

236242
export const { reducer: SettingsReducer } = settingsSlice;

src/translations/translation-files

src/types/opendtu/github.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ export interface WithTimestamp<T> {
1515
}
1616

1717
export interface GithubState {
18+
// OpenDTU releases
1819
latestRelease: WithTimestamp<Release | null>;
1920
releases: WithTimestamp<Release[]>;
2021

src/types/settings.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ export interface SettingsState {
2929
appTheme: 'light' | 'dark' | 'system';
3030
language: 'en' | 'de';
3131
enableAppUpdates: boolean | null;
32+
debugEnabled: boolean;
3233

3334
// opendtu
3435
dtuConfigs: OpenDTUConfig[];
@@ -105,3 +106,7 @@ export type UpdateDTUDatabaseUuidAction = PayloadAction<{
105106
export type EnableAppUpdatesAction = PayloadAction<{
106107
enable: boolean;
107108
}>;
109+
110+
export type DebugEnabledAction = PayloadAction<{
111+
debugEnabled: boolean;
112+
}>;

src/views/navigation/NavigationStack.tsx

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import { useAppSelector } from '@/store';
1111
import AboutOpenDTUScreen from '@/views/navigation/screens/AboutOpenDTUScreen';
1212
import AboutSettingsScreen from '@/views/navigation/screens/AboutSettingsScreen';
1313
import ConfigureGraphsScreen from '@/views/navigation/screens/ConfigureGraphsScreen';
14+
import DebugScreen from '@/views/navigation/screens/DebugScreen';
1415
import DeviceListScreen from '@/views/navigation/screens/DeviceListScreen';
1516
import DeviceSettingsScreen from '@/views/navigation/screens/DeviceSettingsScreen';
1617
import LicensesScreen from '@/views/navigation/screens/LicensesScreen';
@@ -119,6 +120,11 @@ const NavigationStack: FC = () => {
119120
component={LicensesScreen}
120121
options={{ headerBackVisible: true }}
121122
/>
123+
<Stack.Screen
124+
name="DebugScreen"
125+
component={DebugScreen}
126+
options={{ headerBackVisible: true }}
127+
/>
122128
</Stack.Navigator>
123129
);
124130
};
Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
import moment from 'moment';
2+
3+
import type { FC } from 'react';
4+
import { useCallback } from 'react';
5+
import { useTranslation } from 'react-i18next';
6+
import { ScrollView } from 'react-native';
7+
import { Box } from 'react-native-flex-layout';
8+
import { Appbar, IconButton, List, useTheme } from 'react-native-paper';
9+
10+
import { clearLatestAppRelease, clearLatestRelease } from '@/slices/github';
11+
import { setDebugEnabled } from '@/slices/settings';
12+
13+
import { useAppDispatch, useAppSelector } from '@/store';
14+
import { StyledSafeAreaView } from '@/style';
15+
import type { PropsWithNavigation } from '@/views/navigation/NavigationStack';
16+
17+
const DebugScreen: FC<PropsWithNavigation> = () => {
18+
const { t } = useTranslation();
19+
const theme = useTheme();
20+
const dispatch = useAppDispatch();
21+
22+
const latestAppRelease = useAppSelector(
23+
state => state.github.latestAppRelease.lastUpdate,
24+
);
25+
const latestOpenDtuRelease = useAppSelector(
26+
state => state.github.latestRelease.lastUpdate,
27+
);
28+
29+
const handleClearLatestAppRelease = useCallback(() => {
30+
dispatch(clearLatestAppRelease());
31+
}, [dispatch]);
32+
33+
const handleClearLatestOpenDtuRelease = useCallback(() => {
34+
dispatch(clearLatestRelease());
35+
}, [dispatch]);
36+
37+
const handleDisableDebugMode = useCallback(() => {
38+
dispatch(setDebugEnabled({ debugEnabled: false }));
39+
}, [dispatch]);
40+
41+
return (
42+
<>
43+
<Appbar.Header>
44+
<Appbar.Content title={t('settings.debug')} />
45+
</Appbar.Header>
46+
<StyledSafeAreaView theme={theme}>
47+
<Box style={{ width: '100%', flex: 1 }}>
48+
<ScrollView>
49+
<List.Section>
50+
<List.Subheader>{t('debug.cache')}</List.Subheader>
51+
<List.Item
52+
title={t('debug.latestAppReleaseCacheCreated')}
53+
description={
54+
latestAppRelease
55+
? moment(latestAppRelease).toLocaleString()
56+
: ''
57+
}
58+
right={props => (
59+
<IconButton
60+
{...props}
61+
icon="delete"
62+
onPress={handleClearLatestAppRelease}
63+
/>
64+
)}
65+
/>
66+
<List.Item
67+
title={t('debug.latestOpenDtuReleaseCacheCreated')}
68+
description={
69+
latestOpenDtuRelease
70+
? moment(latestOpenDtuRelease).toLocaleString()
71+
: ''
72+
}
73+
right={props => (
74+
<IconButton
75+
{...props}
76+
icon="delete"
77+
onPress={handleClearLatestOpenDtuRelease}
78+
/>
79+
)}
80+
/>
81+
</List.Section>
82+
<List.Section>
83+
<List.Subheader>{t('debug.other')}</List.Subheader>
84+
<List.Item
85+
title={t('debug.disableDebugMode')}
86+
onPress={handleDisableDebugMode}
87+
/>
88+
</List.Section>
89+
</ScrollView>
90+
</Box>
91+
</StyledSafeAreaView>
92+
</>
93+
);
94+
};
95+
96+
export default DebugScreen;

src/views/navigation/tabs/MainSettingsTab.tsx

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,32 @@
11
import type { NavigationProp, ParamListBase } from '@react-navigation/native';
22
import { useNavigation } from '@react-navigation/native';
3+
import packageJson from '@root/package.json';
34

45
import type { FC } from 'react';
56
import { useMemo, useCallback, useState } from 'react';
67
import { useTranslation } from 'react-i18next';
78
import { ScrollView } from 'react-native';
89
import { Box } from 'react-native-flex-layout';
9-
import { Badge, List, useTheme } from 'react-native-paper';
10+
import { Badge, List, Text, useTheme } from 'react-native-paper';
11+
import { useDispatch } from 'react-redux';
12+
13+
import { setDebugEnabled } from '@/slices/settings';
1014

1115
import ChangeLanguageModal from '@/components/modals/ChangeLanguageModal';
1216
import ChangeThemeModal from '@/components/modals/ChangeThemeModal';
1317

1418
import useDtuState from '@/hooks/useDtuState';
1519
import useHasNewAppVersion from '@/hooks/useHasNewAppVersion';
1620
import useIsConnected from '@/hooks/useIsConnected';
21+
import useRequireMultiplePresses from '@/hooks/useRequireMultiplePresses';
22+
import useSettings from '@/hooks/useSettings';
1723

1824
import { StyledSafeAreaView } from '@/style';
1925

2026
const MainSettingsTab: FC = () => {
2127
const navigation = useNavigation() as NavigationProp<ParamListBase>;
2228
const { t } = useTranslation();
29+
const dispatch = useDispatch();
2330

2431
const theme = useTheme();
2532

@@ -40,6 +47,8 @@ const MainSettingsTab: FC = () => {
4047
usedForIndicatorOnly: true,
4148
});
4249

50+
const showDebugScreen = useSettings(state => state?.debugEnabled);
51+
4352
const hasSystemInformation = !!useDtuState(state => !!state?.systemStatus);
4453
const hasNetworkInformation = !!useDtuState(state => !!state?.networkStatus);
4554
const hasNtpInformation = !!useDtuState(state => !!state?.ntpStatus);
@@ -86,6 +95,15 @@ const MainSettingsTab: FC = () => {
8695
navigation.navigate('MqttInformationScreen');
8796
}, [navigation]);
8897

98+
const handleDebugScreen = useCallback(() => {
99+
navigation.navigate('DebugScreen');
100+
}, [navigation]);
101+
102+
const enableDebugMode = useCallback(() => {
103+
dispatch(setDebugEnabled({ debugEnabled: true }));
104+
}, [dispatch]);
105+
const handleUnlockDebug = useRequireMultiplePresses(enableDebugMode);
106+
89107
return (
90108
<StyledSafeAreaView theme={theme}>
91109
<Box style={{ width: '100%', flex: 1 }}>
@@ -158,7 +176,17 @@ const MainSettingsTab: FC = () => {
158176
left={props => <List.Icon {...props} icon="file-document" />}
159177
onPress={handleLicenses}
160178
/>
179+
{showDebugScreen ? (
180+
<List.Item
181+
title={t('settings.debug')}
182+
left={props => <List.Icon {...props} icon="bug" />}
183+
onPress={handleDebugScreen}
184+
/>
185+
) : null}
161186
</List.Section>
187+
<Text style={{ textAlign: 'center' }} onPress={handleUnlockDebug}>
188+
{t('version')} {packageJson.version}
189+
</Text>
162190
</ScrollView>
163191
</Box>
164192
<ChangeThemeModal

0 commit comments

Comments
 (0)