From 3a6fa7ba14efa74282df774b55a5c0075dd9d38c Mon Sep 17 00:00:00 2001 From: CommanderRedYT Date: Fri, 10 Oct 2025 00:07:20 +0200 Subject: [PATCH 1/4] WIP Refactoring of modals --- src/InnerApp.tsx | 6 +- src/components/BaseModal.tsx | 160 +++++++++++++++--- src/components/Charts/ImportantCharts.tsx | 2 +- src/components/ReleaseChangelog.tsx | 32 +++- src/components/modals/AddDatabaseModal.tsx | 121 ++++++------- src/components/modals/AppChangelogModal.tsx | 43 ++--- src/components/modals/AppOfflineModal.tsx | 22 ++- .../modals/ChangeBooleanValueModal.tsx | 76 +++------ .../modals/ChangeCustomNameModal.tsx | 74 ++++---- .../modals/ChangeEnumValueModal.tsx | 107 +++++------- .../ChangeGraphRefreshIntervalModal.tsx | 64 +++---- src/components/modals/ChangeLanguageModal.tsx | 101 ++++++----- .../modals/ChangeOpendtuCredentialsModal.tsx | 109 +++++------- .../modals/ChangeServerUrlModal.tsx | 61 +++---- .../modals/ChangeTextValueModal.tsx | 91 ++++------ src/components/modals/ChangeThemeModal.tsx | 92 +++++----- .../modals/ConfirmDeleteDeviceModal.tsx | 52 ++---- .../modals/ConfirmUnsavedDataModal.tsx | 51 +++--- .../modals/EnableAppUpdatesModal.tsx | 56 +++--- .../modals/EnableFetchOpenDtuUpdatesModal.tsx | 56 +++--- src/components/modals/GenericRefreshModal.tsx | 42 ++--- src/components/modals/InstallAssetModal.tsx | 5 +- src/components/modals/LimitConfigModal.tsx | 15 +- src/components/modals/LogExtensionModal.tsx | 58 ++++--- src/components/modals/LogLevelModal.tsx | 47 +++-- src/components/modals/ManageDatabaseModal.tsx | 122 ++++++------- .../modals/NTPChangeTimezoneModal.tsx | 126 +++++++------- .../modals/TimeRangeLastNSecondsModal.tsx | 87 +++++----- src/components/styled/SettingsSurface.tsx | 2 +- src/constants.ts | 3 + src/translations | 2 +- .../DeviceGroup/DeviceSettingsScreen.tsx | 2 +- .../GraphsGroup/ManageDatabasesScreen.tsx | 92 +++++++--- .../SettingsGroup/NetworkSettingsScreen.tsx | 2 + 34 files changed, 946 insertions(+), 1035 deletions(-) diff --git a/src/InnerApp.tsx b/src/InnerApp.tsx index 1259346a..48437580 100644 --- a/src/InnerApp.tsx +++ b/src/InnerApp.tsx @@ -368,14 +368,14 @@ const InnerApp: FC = () => { {}} dismissable={false} dismissableBackButton={false} /> {}} dismissable={false} dismissableBackButton={false} diff --git a/src/components/BaseModal.tsx b/src/components/BaseModal.tsx index 5974a572..383053d8 100644 --- a/src/components/BaseModal.tsx +++ b/src/components/BaseModal.tsx @@ -1,60 +1,166 @@ import type { FC } from 'react'; +import React from 'react'; +import { useTranslation } from 'react-i18next'; +import { Box, Flex } from 'react-native-flex-layout'; import type { ModalProps } from 'react-native-paper'; -import { Modal, useTheme } from 'react-native-paper'; +import { Button, Icon, Modal, Text, useTheme } from 'react-native-paper'; import { Platform } from 'react-native'; import { spacing } from '@/constants'; -export interface BaseModalProps extends ModalProps { - backgroundColor?: string; - disableSidePadding?: boolean; - disableSideMargin?: boolean; - isScreen?: boolean; +export interface ExtendableModalProps { + visible: boolean; + onDismiss: () => void; +} + +export interface BaseModalProps extends ExtendableModalProps { + title: string; + dismissButton: + | false + | 'dismiss' + | 'cancel' + | { + label: string; + onPress: () => void; + variant?: 'text' | 'outlined' | 'contained'; + disabled?: boolean; + textColor?: string; + }; + dismissButtonDisabled?: boolean; + actions?: { + label: string; + onPress: () => void; + variant?: 'text' | 'outlined' | 'contained'; + disabled?: boolean; + textColor?: string; + loading?: boolean; + }[]; + description?: string; + icon?: string; + modalProps?: Omit; + fullscreen?: boolean; + hideBottomPadding?: boolean; + children?: React.ReactNode; } const BaseModal: FC = ({ + visible, + onDismiss, + title, + description, + fullscreen, children, - backgroundColor, - disableSidePadding, - disableSideMargin, - isScreen, + dismissButton = 'dismiss', + dismissButtonDisabled, + actions, + icon, + hideBottomPadding, ...rest }) => { const theme = useTheme(); - - if (isScreen) { - disableSideMargin = true; - disableSidePadding = true; - } - - const { visible } = rest; + const { t } = useTranslation(); return ( - {children} + + {icon ? ( + + + + ) : null} + + {title} + + {description ? ( + + {description} + + ) : null} + + {children ? {children} : null} + + + {dismissButton ? ( + + ) : null} + {actions?.map((action, index) => ( + + ))} + + ); }; diff --git a/src/components/Charts/ImportantCharts.tsx b/src/components/Charts/ImportantCharts.tsx index 7ce09c24..ff24060c 100644 --- a/src/components/Charts/ImportantCharts.tsx +++ b/src/components/Charts/ImportantCharts.tsx @@ -1,7 +1,7 @@ import type { FC } from 'react'; import { useCallback, useEffect, useState } from 'react'; import { useTranslation } from 'react-i18next'; -import { Box } from 'react-native-flex-layout'; +import { Box, Flex } from 'react-native-flex-layout'; import { Icon, IconButton, diff --git a/src/components/ReleaseChangelog.tsx b/src/components/ReleaseChangelog.tsx index d5bcd541..ad1b6862 100644 --- a/src/components/ReleaseChangelog.tsx +++ b/src/components/ReleaseChangelog.tsx @@ -1,7 +1,7 @@ import type { FC } from 'react'; import type { RenderRules } from 'react-native-markdown-display'; import Markdown from 'react-native-markdown-display'; -import { useTheme } from 'react-native-paper'; +import { Text, useTheme } from 'react-native-paper'; import { Text as RNText } from 'react-native'; @@ -11,6 +11,36 @@ export interface ReleaseChangelogProps { const rules: RenderRules = { link: (node, children) => {children}, + heading1: (node, children) => ( + + {children} + + ), + heading2: (node, children) => ( + + {children} + + ), + heading3: (node, children) => ( + + {children} + + ), + heading4: (node, children) => ( + + {children} + + ), + heading5: (node, children) => ( + + {children} + + ), + heading6: (node, children) => ( + + {children} + + ), }; const ReleaseChangelog: FC = ({ releaseBody }) => { diff --git a/src/components/modals/AddDatabaseModal.tsx b/src/components/modals/AddDatabaseModal.tsx index fb0b4a34..78fb652a 100644 --- a/src/components/modals/AddDatabaseModal.tsx +++ b/src/components/modals/AddDatabaseModal.tsx @@ -2,8 +2,7 @@ import type { FC } from 'react'; import { useCallback, useMemo, useState } from 'react'; import { useTranslation } from 'react-i18next'; import { Box } from 'react-native-flex-layout'; -import type { ModalProps } from 'react-native-paper'; -import { Button, Portal, Text, useTheme } from 'react-native-paper'; +import { Portal, useTheme } from 'react-native-paper'; import { v4 as uuidv4 } from 'uuid'; @@ -11,13 +10,14 @@ import { addDatabaseConfig } from '@/slices/settings'; import type { DatabaseConfig } from '@/types/settings'; +import type { ExtendableModalProps } from '@/components/BaseModal'; import BaseModal from '@/components/BaseModal'; import StyledTextInput from '@/components/styled/StyledTextInput'; import { DatabaseType } from '@/database'; import { useAppDispatch } from '@/store'; -export type AddDatabaseModalProps = Omit; +export type AddDatabaseModalProps = ExtendableModalProps; const AddDatabaseModal: FC = props => { const { onDismiss } = props; @@ -65,70 +65,59 @@ const AddDatabaseModal: FC = props => { return ( - - - - {t('database.addANewDatabase')} - - - - - - - - - - - - - + + + - - - + + + + + + + + diff --git a/src/components/modals/AppChangelogModal.tsx b/src/components/modals/AppChangelogModal.tsx index ab68d45e..4990df3b 100644 --- a/src/components/modals/AppChangelogModal.tsx +++ b/src/components/modals/AppChangelogModal.tsx @@ -2,13 +2,7 @@ import type { FC } from 'react'; import { useEffect, useState } from 'react'; import { useTranslation } from 'react-i18next'; import { Box } from 'react-native-flex-layout'; -import { - ActivityIndicator, - Button, - Portal, - Text, - useTheme, -} from 'react-native-paper'; +import { ActivityIndicator, Portal, Text } from 'react-native-paper'; import { ScrollView } from 'react-native'; @@ -30,7 +24,6 @@ const log = rootLogging.extend('AppChangelogModal'); const AppChangelogModal: FC = () => { const githubApi = useGithub(); - const theme = useTheme(); const dispatch = useAppDispatch(); const { t } = useTranslation(); @@ -118,27 +111,20 @@ const AppChangelogModal: FC = () => { {}} + dismissButton={false} + actions={[{ label: t('acknowledge'), onPress: handleAcknowledge }]} > - - - - {t('appChangelogModal.newAppVersionInstalled', { - appVersion: appVersion, - })} - - + {appReleaseIsCorrectVersion ? ( - + ) : ( @@ -149,9 +135,6 @@ const AppChangelogModal: FC = () => { )} - diff --git a/src/components/modals/AppOfflineModal.tsx b/src/components/modals/AppOfflineModal.tsx index 46b5f0ad..64cd0abe 100644 --- a/src/components/modals/AppOfflineModal.tsx +++ b/src/components/modals/AppOfflineModal.tsx @@ -1,7 +1,6 @@ import type { FC } from 'react'; import { useTranslation } from 'react-i18next'; -import { Box } from 'react-native-flex-layout'; -import { Portal, Text } from 'react-native-paper'; +import { Portal } from 'react-native-paper'; import BaseModal from '@/components/BaseModal'; @@ -13,16 +12,15 @@ const AppOfflineModal: FC = () => { return ( - - - - {t('offlineModal.title')} - - - {t('offlineModal.description')} - - - + {}} + icon="wifi-off" + /> ); }; diff --git a/src/components/modals/ChangeBooleanValueModal.tsx b/src/components/modals/ChangeBooleanValueModal.tsx index 4e83a5bf..7d00ba10 100644 --- a/src/components/modals/ChangeBooleanValueModal.tsx +++ b/src/components/modals/ChangeBooleanValueModal.tsx @@ -1,21 +1,19 @@ import type { FC } from 'react'; import { useEffect, useState } from 'react'; import { useTranslation } from 'react-i18next'; +import { Flex } from 'react-native-flex-layout'; import type { SwitchProps } from 'react-native-paper'; -import { Button, Portal, Switch, Text } from 'react-native-paper'; - -import { View } from 'react-native'; +import { Portal, Switch, Text } from 'react-native-paper'; import BaseModal from '@/components/BaseModal'; -import { spacing } from '@/constants'; - export interface ChangeBooleanValueModalProps { isOpen?: boolean; + title: string; + switchLabel: string; onClose?: () => void; defaultValue?: boolean; onChange?: (value: boolean) => void; - title?: string; description?: string; inputProps?: Omit; } @@ -28,6 +26,7 @@ const ChangeBooleanValueModal: FC = ({ onChange: onSave, onClose, inputProps, + switchLabel, }) => { const { t } = useTranslation(); @@ -58,51 +57,30 @@ const ChangeBooleanValueModal: FC = ({ return ( - - + - - - {title} - {description} - - { - setWasModified(true); - setValue(value); - }} - {...inputProps} - /> - - {switchLabel} + { + setWasModified(true); + setValue(value); }} - > - - - - + {...inputProps} + /> + ); diff --git a/src/components/modals/ChangeCustomNameModal.tsx b/src/components/modals/ChangeCustomNameModal.tsx index 9bca77b4..7f51b3f2 100644 --- a/src/components/modals/ChangeCustomNameModal.tsx +++ b/src/components/modals/ChangeCustomNameModal.tsx @@ -1,19 +1,17 @@ import type { FC } from 'react'; import { useCallback, useEffect, useState } from 'react'; import { useTranslation } from 'react-i18next'; -import { Box } from 'react-native-flex-layout'; -import type { ModalProps } from 'react-native-paper'; -import { Button, Portal, Text, useTheme } from 'react-native-paper'; +import { Portal, useTheme } from 'react-native-paper'; import { updateDtuCustomName } from '@/slices/settings'; +import type { ExtendableModalProps } from '@/components/BaseModal'; import BaseModal from '@/components/BaseModal'; import StyledTextInput from '@/components/styled/StyledTextInput'; import { useAppDispatch, useAppSelector } from '@/store'; -export interface ChangeCustomNameModalProps - extends Omit { +export interface ChangeCustomNameModalProps extends ExtendableModalProps { index: number; } @@ -59,46 +57,32 @@ const ChangeCustomNameModal: FC = props => { return ( - - - - {t('device.changeTheDeviceName')} - - - - - - - - + + ); diff --git a/src/components/modals/ChangeEnumValueModal.tsx b/src/components/modals/ChangeEnumValueModal.tsx index 23118cb7..2b3b736b 100644 --- a/src/components/modals/ChangeEnumValueModal.tsx +++ b/src/components/modals/ChangeEnumValueModal.tsx @@ -2,27 +2,19 @@ import type { FC } from 'react'; import { useEffect, useState } from 'react'; import { useTranslation } from 'react-i18next'; import { Box } from 'react-native-flex-layout'; -import { - Button, - Portal, - RadioButton, - Text, - useTheme, -} from 'react-native-paper'; +import { Portal, RadioButton, useTheme } from 'react-native-paper'; import { ScrollView, View } from 'react-native'; import BaseModal from '@/components/BaseModal'; -import { spacing } from '@/constants'; - export interface ChangeEnumValueModalProps { isOpen?: boolean; + title: string; onClose?: () => void; defaultValue?: string; onChange?: (value: string) => void; possibleValues: PossibleEnumValues; - title?: string; description?: string; } @@ -67,68 +59,49 @@ const ChangeEnumValueModal: FC = ({ return ( - + - - - {title} - {description} - - + - - - {possibleValues.map(({ label, value }) => ( - - ))} - - - - - - - - + + {possibleValues.map(({ label, value }) => ( + + ))} + + + diff --git a/src/components/modals/ChangeGraphRefreshIntervalModal.tsx b/src/components/modals/ChangeGraphRefreshIntervalModal.tsx index d0f094ee..6d06cf11 100644 --- a/src/components/modals/ChangeGraphRefreshIntervalModal.tsx +++ b/src/components/modals/ChangeGraphRefreshIntervalModal.tsx @@ -1,12 +1,11 @@ import type { FC } from 'react'; import { useCallback, useState } from 'react'; import { useTranslation } from 'react-i18next'; -import { Box } from 'react-native-flex-layout'; -import type { ModalProps } from 'react-native-paper'; -import { Button, Portal, Text, useTheme } from 'react-native-paper'; +import { Portal, useTheme } from 'react-native-paper'; import { setRefreshInterval } from '@/slices/database'; +import type { ExtendableModalProps } from '@/components/BaseModal'; import BaseModal from '@/components/BaseModal'; import StyledTextInput from '@/components/styled/StyledTextInput'; @@ -16,9 +15,7 @@ import { useAppDispatch, useAppSelector } from '@/store'; const log = rootLogging.extend('ChangeGraphRefreshIntervalModal'); -const ChangeGraphRefreshIntervalModal: FC< - Omit -> = props => { +const ChangeGraphRefreshIntervalModal: FC = props => { const { t } = useTranslation(); const { onDismiss } = props; const theme = useTheme(); @@ -54,38 +51,29 @@ const ChangeGraphRefreshIntervalModal: FC< return ( - - - - - {t('configureGraphs.changeRefreshInterval')} - - - } - /> - - - - - - - + + } + /> ); diff --git a/src/components/modals/ChangeLanguageModal.tsx b/src/components/modals/ChangeLanguageModal.tsx index f12cfde7..3d6894a3 100644 --- a/src/components/modals/ChangeLanguageModal.tsx +++ b/src/components/modals/ChangeLanguageModal.tsx @@ -2,7 +2,6 @@ import type { FC } from 'react'; import { useCallback, useState } from 'react'; import { useTranslation } from 'react-i18next'; import { Box } from 'react-native-flex-layout'; -import type { ModalProps } from 'react-native-paper'; import { Button, Portal, @@ -16,6 +15,7 @@ import { Linking } from 'react-native'; import { setLanguage } from '@/slices/settings'; +import type { ExtendableModalProps } from '@/components/BaseModal'; import BaseModal from '@/components/BaseModal'; import useAppLanguage from '@/hooks/useAppLanguage'; @@ -29,7 +29,7 @@ import { supportedLanguages } from '@/translations'; const log = rootLogging.extend('ChangeLanguageModal'); -const ChangeLanguageModal: FC> = props => { +const ChangeLanguageModal: FC = props => { const { onDismiss } = props; const dispatch = useAppDispatch(); const { t } = useTranslation(); @@ -64,58 +64,53 @@ const ChangeLanguageModal: FC> = props => { return ( - - - - {t('settings.changeTheLanguage')} - - - setSelectedLanguage(value as SupportedLanguage) - } + + + setSelectedLanguage(value as SupportedLanguage) + } + > + {supportedLanguages.map(language => ( + + ))} + + + + {t('settings.weblateInfo')} + + - - - - - - - + {t('settings.openWeblate')} + diff --git a/src/components/modals/ChangeOpendtuCredentialsModal.tsx b/src/components/modals/ChangeOpendtuCredentialsModal.tsx index 9d6a0a4b..13c7b659 100644 --- a/src/components/modals/ChangeOpendtuCredentialsModal.tsx +++ b/src/components/modals/ChangeOpendtuCredentialsModal.tsx @@ -2,11 +2,11 @@ import type { FC } from 'react'; import { useCallback, useEffect, useMemo, useState } from 'react'; import { useTranslation } from 'react-i18next'; import { Box } from 'react-native-flex-layout'; -import type { ModalProps } from 'react-native-paper'; -import { Button, HelperText, Portal, Text, useTheme } from 'react-native-paper'; +import { HelperText, Portal, useTheme } from 'react-native-paper'; import { updateDtuUserString } from '@/slices/settings'; +import type { ExtendableModalProps } from '@/components/BaseModal'; import BaseModal from '@/components/BaseModal'; import StyledTextInput from '@/components/styled/StyledTextInput'; @@ -17,7 +17,7 @@ import { defaultUser } from '@/constants'; import { useAppDispatch, useAppSelector } from '@/store'; export interface ChangeOpendtuCredentialsModalProps - extends Omit { + extends ExtendableModalProps { index: number; } @@ -104,69 +104,50 @@ const ChangeOpendtuCredentialsModal: FC< return ( - - - - - {t('settings.changeOpendtuCredentials')} - - - - - - - + + + + + + {error ? ( + {error} - - - - - - + ) : null} ); diff --git a/src/components/modals/ChangeServerUrlModal.tsx b/src/components/modals/ChangeServerUrlModal.tsx index 558b0259..0948646c 100644 --- a/src/components/modals/ChangeServerUrlModal.tsx +++ b/src/components/modals/ChangeServerUrlModal.tsx @@ -1,19 +1,17 @@ import type { FC } from 'react'; import { useCallback, useEffect, useState } from 'react'; import { useTranslation } from 'react-i18next'; -import { Box } from 'react-native-flex-layout'; -import type { ModalProps } from 'react-native-paper'; -import { Button, Portal, Text, useTheme } from 'react-native-paper'; +import { Portal, useTheme } from 'react-native-paper'; import { updateDtuBaseUrl } from '@/slices/settings'; +import type { ExtendableModalProps } from '@/components/BaseModal'; import BaseModal from '@/components/BaseModal'; import StyledTextInput from '@/components/styled/StyledTextInput'; import { useAppDispatch, useAppSelector } from '@/store'; -export interface ChangeServerUrlModalProps - extends Omit { +export interface ChangeServerUrlModalProps extends ExtendableModalProps { index: number; } @@ -46,38 +44,27 @@ const ChangeServerUrlModal: FC = props => { return ( - - - - {t('settings.changeTheServerUrl')} - - - - - - - + + ); diff --git a/src/components/modals/ChangeTextValueModal.tsx b/src/components/modals/ChangeTextValueModal.tsx index 76ad1f2b..e1bd6fdb 100644 --- a/src/components/modals/ChangeTextValueModal.tsx +++ b/src/components/modals/ChangeTextValueModal.tsx @@ -2,37 +2,27 @@ import type { FC } from 'react'; import React, { useEffect, useState } from 'react'; import { useTranslation } from 'react-i18next'; import type { TextInputProps } from 'react-native-paper'; -import { - Button, - HelperText, - Portal, - Text, - TextInput, -} from 'react-native-paper'; - -import { View } from 'react-native'; +import { HelperText, Portal, TextInput, useTheme } from 'react-native-paper'; import BaseModal from '@/components/BaseModal'; import { rootLogging } from '@/utils/log'; -import { spacing } from '@/constants'; - const log = rootLogging.extend('ChangeTextValueModal'); -export interface NewChangeTextValueModalProps { +export interface ChangeTextValueModalProps { isOpen?: boolean; + title: string; onClose?: () => void; defaultValue?: string; onChange?: (value: string) => void; - title?: string; description?: string; inputProps?: Omit; validate?: (value: string) => boolean; allowedRegex?: RegExp; } -const ChangeTextValueModal: FC = ({ +const ChangeTextValueModal: FC = ({ isOpen, title, description, @@ -43,6 +33,7 @@ const ChangeTextValueModal: FC = ({ validate, allowedRegex, }) => { + const theme = useTheme(); const { t } = useTranslation(); const [value, setValue] = useState(defaultValue ?? ''); @@ -94,50 +85,38 @@ const ChangeTextValueModal: FC = ({ return ( - - + { + if (allowedRegex && !allowedRegex.test(value)) { + return; + } + + setWasModified(true); + setError(null); + setValue(value); }} - > - - {title} - {description} - - - { - if (allowedRegex && !allowedRegex.test(value)) { - return; - } - - setWasModified(true); - setError(null); - setValue(value); - }} - error={!!error} - /> - - {error} - - - - - - - + error={!!error} + /> + {error ? ( + + {error} + + ) : null} ); diff --git a/src/components/modals/ChangeThemeModal.tsx b/src/components/modals/ChangeThemeModal.tsx index 7aadbac6..984019fa 100644 --- a/src/components/modals/ChangeThemeModal.tsx +++ b/src/components/modals/ChangeThemeModal.tsx @@ -1,10 +1,7 @@ import type { FC } from 'react'; import { useCallback, useState } from 'react'; import { useTranslation } from 'react-i18next'; -import { Box } from 'react-native-flex-layout'; -import type { ModalProps } from 'react-native-paper'; import { - Button, Portal, RadioButton, Switch, @@ -18,13 +15,14 @@ import { setAllowMaterialYou, setAppTheme } from '@/slices/settings'; import type { SettingsState } from '@/types/settings'; +import type { ExtendableModalProps } from '@/components/BaseModal'; import BaseModal from '@/components/BaseModal'; import { useAppDispatch, useAppSelector } from '@/store'; const isAndroid = Platform.OS === 'android'; -const ChangeThemeModal: FC> = props => { +const ChangeThemeModal: FC = props => { const { onDismiss } = props; const dispatch = useAppDispatch(); const { t } = useTranslation(); @@ -52,57 +50,51 @@ const ChangeThemeModal: FC> = props => { return ( - - - - {t('settings.changeTheTheme')} - - - setSelectedTheme(value as SettingsState['appTheme']) - } - > - - - - - {isAndroid ? ( - - <> - {t('themes.allowMaterialYou')} - - - - ) : null} - + + setSelectedTheme(value as SettingsState['appTheme']) + } + > + + + + + {isAndroid ? ( + - - - - - - + <> + {t('themes.allowMaterialYou')} + + + + ) : null} ); diff --git a/src/components/modals/ConfirmDeleteDeviceModal.tsx b/src/components/modals/ConfirmDeleteDeviceModal.tsx index be22f082..f66da519 100644 --- a/src/components/modals/ConfirmDeleteDeviceModal.tsx +++ b/src/components/modals/ConfirmDeleteDeviceModal.tsx @@ -1,15 +1,14 @@ import type { FC } from 'react'; import { useCallback } from 'react'; import { useTranslation } from 'react-i18next'; -import { Box } from 'react-native-flex-layout'; -import type { ModalProps } from 'react-native-paper'; -import { Button, Portal, Text, useTheme } from 'react-native-paper'; +import { Portal, useTheme } from 'react-native-paper'; import { removeDtuConfig, setSelectedDtuToFirstOrNull, } from '@/slices/settings'; +import type { ExtendableModalProps } from '@/components/BaseModal'; import BaseModal from '@/components/BaseModal'; import { useAppDispatch } from '@/store'; @@ -17,8 +16,7 @@ import { useAppDispatch } from '@/store'; import type { NavigationProp, ParamListBase } from '@react-navigation/native'; import { useNavigation } from '@react-navigation/native'; -export interface ConfirmDeleteDeviceModalProps - extends Omit { +export interface ConfirmDeleteDeviceModalProps extends ExtendableModalProps { index: number; } @@ -45,35 +43,21 @@ const ConfirmDeleteDeviceModal: FC = props => { return ( - - - - - {t('settings.doYouWantToDeleteThisConfig')} - - - - - - - - + ); }; diff --git a/src/components/modals/ConfirmUnsavedDataModal.tsx b/src/components/modals/ConfirmUnsavedDataModal.tsx index ad9af460..a7ce85b9 100644 --- a/src/components/modals/ConfirmUnsavedDataModal.tsx +++ b/src/components/modals/ConfirmUnsavedDataModal.tsx @@ -1,16 +1,15 @@ import type { FC } from 'react'; import { useCallback } from 'react'; import { useTranslation } from 'react-i18next'; -import { Box } from 'react-native-flex-layout'; -import type { ModalProps } from 'react-native-paper'; -import { Button, Portal, Text, useTheme } from 'react-native-paper'; +import { Portal, useTheme } from 'react-native-paper'; +import type { ExtendableModalProps } from '@/components/BaseModal'; import BaseModal from '@/components/BaseModal'; export type ConfirmUnsavedDataModalInput = false | (() => void); export interface ConfirmUnsavedDataModalProps - extends Omit { + extends Omit { visible: ConfirmUnsavedDataModalInput; } @@ -25,37 +24,27 @@ const ConfirmUnsavedDataModal: FC = props => { return ( - - - - {t('unsavedDataTips')} - - - - - - - + }, + textColor: theme.colors.primary, + }, + ]} + /> ); }; diff --git a/src/components/modals/EnableAppUpdatesModal.tsx b/src/components/modals/EnableAppUpdatesModal.tsx index 6bbde763..31d9f8d9 100644 --- a/src/components/modals/EnableAppUpdatesModal.tsx +++ b/src/components/modals/EnableAppUpdatesModal.tsx @@ -1,17 +1,16 @@ import type { FC } from 'react'; import { useCallback } from 'react'; import { useTranslation } from 'react-i18next'; -import { Box } from 'react-native-flex-layout'; -import type { ModalProps } from 'react-native-paper'; -import { Button, Portal, Text } from 'react-native-paper'; +import { Portal } from 'react-native-paper'; import { setEnableAppUpdates } from '@/slices/settings'; +import type { ExtendableModalProps } from '@/components/BaseModal'; import BaseModal from '@/components/BaseModal'; import { useAppDispatch } from '@/store'; -const EnableAppUpdatesModal: FC> = props => { +const EnableAppUpdatesModal: FC = props => { const { onDismiss } = props; const { t } = useTranslation(); const dispatch = useAppDispatch(); @@ -32,37 +31,24 @@ const EnableAppUpdatesModal: FC> = props => { return ( - - - - - {t('settings.doYouWantToEnableAppUpdates')} - - - {t('settings.thisWillMakeRequestsToGithub')} - - - - - - - - + ); }; diff --git a/src/components/modals/EnableFetchOpenDtuUpdatesModal.tsx b/src/components/modals/EnableFetchOpenDtuUpdatesModal.tsx index 1566509e..4347a554 100644 --- a/src/components/modals/EnableFetchOpenDtuUpdatesModal.tsx +++ b/src/components/modals/EnableFetchOpenDtuUpdatesModal.tsx @@ -1,17 +1,16 @@ import type { FC } from 'react'; import { useCallback } from 'react'; import { useTranslation } from 'react-i18next'; -import { Box } from 'react-native-flex-layout'; -import type { ModalProps } from 'react-native-paper'; -import { Button, Portal, Text } from 'react-native-paper'; +import { Portal } from 'react-native-paper'; import { setEnableFetchOpenDTUReleases } from '@/slices/settings'; +import type { ExtendableModalProps } from '@/components/BaseModal'; import BaseModal from '@/components/BaseModal'; import { useAppDispatch } from '@/store'; -const EnableAppUpdatesModal: FC> = props => { +const EnableAppUpdatesModal: FC = props => { const { onDismiss } = props; const { t } = useTranslation(); const dispatch = useAppDispatch(); @@ -32,37 +31,24 @@ const EnableAppUpdatesModal: FC> = props => { return ( - - - - - {t('settings.doYouWantToEnableOpenDtuUpdates')} - - - {t('settings.thisWillMakeRequestsToGithub')} - - - - - - - - + ); }; diff --git a/src/components/modals/GenericRefreshModal.tsx b/src/components/modals/GenericRefreshModal.tsx index 7be407bf..21fa3dc4 100644 --- a/src/components/modals/GenericRefreshModal.tsx +++ b/src/components/modals/GenericRefreshModal.tsx @@ -1,20 +1,19 @@ import type { FC } from 'react'; import { useCallback } from 'react'; import { useTranslation } from 'react-i18next'; -import { Box } from 'react-native-flex-layout'; -import type { ModalProps } from 'react-native-paper'; -import { Button, Portal, Text } from 'react-native-paper'; +import { Portal } from 'react-native-paper'; +import type { ExtendableModalProps } from '@/components/BaseModal'; import BaseModal from '@/components/BaseModal'; -export interface GenericRefreshModalProps extends Omit { +export interface GenericRefreshModalProps extends ExtendableModalProps { onConfirm: () => void; title: string; warningText: string; } const GenericRefreshModal: FC = props => { - const { onDismiss, onConfirm, title, warningText } = props; + const { onDismiss, onConfirm, title, warningText, ...rest } = props; const { t } = useTranslation(); const handleAbort = useCallback(() => { @@ -28,31 +27,14 @@ const GenericRefreshModal: FC = props => { return ( - - - - {title} - - - {warningText} - - - - - - - + ); }; diff --git a/src/components/modals/InstallAssetModal.tsx b/src/components/modals/InstallAssetModal.tsx index 91615373..83742940 100644 --- a/src/components/modals/InstallAssetModal.tsx +++ b/src/components/modals/InstallAssetModal.tsx @@ -2,7 +2,6 @@ import type { FC } from 'react'; import { useCallback, useEffect, useMemo, useState } from 'react'; import { useTranslation } from 'react-i18next'; import { Box } from 'react-native-flex-layout'; -import type { ModalProps } from 'react-native-paper'; import { ActivityIndicator, Button, @@ -15,6 +14,7 @@ import { useTheme, } from 'react-native-paper'; +import type { ExtendableModalProps } from '@/components/BaseModal'; import BaseModal from '@/components/BaseModal'; import { rootLogging } from '@/utils/log'; @@ -26,8 +26,7 @@ import type { ReleaseAsset } from '@octokit/webhooks-types'; const log = rootLogging.extend('InstallAssetModal'); -export interface InstallFirmwareModalProps - extends Omit { +export interface InstallFirmwareModalProps extends ExtendableModalProps { asset: ReleaseAsset | null; version: string | null; closeAll: () => void; diff --git a/src/components/modals/LimitConfigModal.tsx b/src/components/modals/LimitConfigModal.tsx index 6b18901a..d99c715a 100644 --- a/src/components/modals/LimitConfigModal.tsx +++ b/src/components/modals/LimitConfigModal.tsx @@ -2,7 +2,6 @@ import type { FC } from 'react'; import { useCallback, useEffect, useMemo, useState } from 'react'; import { useTranslation } from 'react-i18next'; import { Box } from 'react-native-flex-layout'; -import type { ModalProps } from 'react-native-paper'; import { Button, HelperText, @@ -18,6 +17,7 @@ import type { TFunction } from 'i18next'; import type { LimitConfig } from '@/types/opendtu/control'; import { SetStatus } from '@/types/opendtu/control'; +import type { ExtendableModalProps } from '@/components/BaseModal'; import BaseModal from '@/components/BaseModal'; import StyledTextInput from '@/components/styled/StyledTextInput'; @@ -25,7 +25,7 @@ import useInverterLimits from '@/hooks/useInverterLimits'; import { useApi } from '@/api/ApiHandler'; -export interface LimitConfigModalProps extends Omit { +export interface LimitConfigModalProps extends ExtendableModalProps { inverterSerial: string; } @@ -172,11 +172,14 @@ const LimitConfigModal: FC = ({ return ( - + - - {t('limitStatus.title')} - = ({ return ( - - - {t('settings.extension')} - - - setExtensionFilter(value)} - value={extensionFilter || ''} - > - {( - Array.from(extensionsSet).filter( - e => typeof e === 'string' && e.length, - ) as string[] - ).map(extension => ( - - ))} - - - + setExtensionFilter(null))} + title={t('settings.extension')} + dismissButton="dismiss" + actions={[ + { + label: t('settings.clearFilter'), + onPress: () => setExtensionFilter(null), + variant: 'text', + disabled: extensionFilter === null, + }, + ]} + > + setExtensionFilter(value)} + value={extensionFilter || ''} + > + {( + Array.from(extensionsSet).filter( + e => typeof e === 'string' && e.length, + ) as string[] + ).map(extension => ( + + ))} + ); diff --git a/src/components/modals/LogLevelModal.tsx b/src/components/modals/LogLevelModal.tsx index d70efba5..4cb73fef 100644 --- a/src/components/modals/LogLevelModal.tsx +++ b/src/components/modals/LogLevelModal.tsx @@ -1,8 +1,7 @@ import type { FC } from 'react'; import { useTranslation } from 'react-i18next'; -import { Box } from 'react-native-flex-layout'; import type { ModalProps } from 'react-native-paper'; -import { Portal, RadioButton, Text } from 'react-native-paper'; +import { Portal, RadioButton } from 'react-native-paper'; import BaseModal from '@/components/BaseModal'; @@ -22,28 +21,28 @@ export const LogLevelModal: FC = ({ return ( - - - {t('settings.logLevel')} - - - - setLogLevel(LogLevel[value as never] as unknown as LogLevel) - } - value={LogLevel[logLevel as never]} - > - {Object.keys(LogLevel) - .filter(level => isNaN(level as never)) - .map(level => ( - - ))} - - + {})} + > + + setLogLevel(LogLevel[value as never] as unknown as LogLevel) + } + value={LogLevel[logLevel as never]} + > + {Object.keys(LogLevel) + .filter(level => isNaN(level as never)) + .map(level => ( + + ))} + ); diff --git a/src/components/modals/ManageDatabaseModal.tsx b/src/components/modals/ManageDatabaseModal.tsx index b14cbaa7..1066b8a0 100644 --- a/src/components/modals/ManageDatabaseModal.tsx +++ b/src/components/modals/ManageDatabaseModal.tsx @@ -98,74 +98,62 @@ const ManageDatabaseModal: FC = props => { return ( - - - - - {t('settings.manageDatabase', { name: dbConfig?.name })} - - - - - - - - - - - - - - + + + - - - - + + + + + + + + diff --git a/src/components/modals/NTPChangeTimezoneModal.tsx b/src/components/modals/NTPChangeTimezoneModal.tsx index 02bd9255..c1d42844 100644 --- a/src/components/modals/NTPChangeTimezoneModal.tsx +++ b/src/components/modals/NTPChangeTimezoneModal.tsx @@ -6,7 +6,6 @@ import type { ModalProps } from 'react-native-paper'; import { Appbar, IconButton, - Portal, RadioButton, TextInput, useTheme, @@ -18,7 +17,6 @@ import Fuse from 'fuse.js'; import type { NTPSettings, TimezoneData } from '@/types/opendtu/settings'; -import BaseModal from '@/components/BaseModal'; import type { PossibleEnumValues } from '@/components/modals/ChangeEnumValueModal'; import { rootLogging } from '@/utils/log'; @@ -164,75 +162,73 @@ const NTPChangeTimezoneModal: FC = props => { ]); return ( - - - - - - - - - + + + + + + + + { + setSearchQuery(value); }} - > - { - setSearchQuery(value); - }} - right={} + right={} + /> + { + setSearchQuery(''); + setHasScrolledToSelectedTimezone(false); + }} + icon="close" + /> + + - ( + { - setSearchQuery(''); - setHasScrolledToSelectedTimezone(false); + handleSetTimezone(item.value); + }} + labelVariant="bodyMedium" + style={{ + backgroundColor: theme.colors.background, }} - icon="close" /> - - - } - renderItem={({ item }) => ( - { - handleSetTimezone(item.value); - }} - labelVariant="bodyMedium" - style={{ - backgroundColor: theme.colors.background, - }} - /> - )} - /> - + )} + /> - - + + ); }; diff --git a/src/components/modals/TimeRangeLastNSecondsModal.tsx b/src/components/modals/TimeRangeLastNSecondsModal.tsx index f261a042..942b6a4e 100644 --- a/src/components/modals/TimeRangeLastNSecondsModal.tsx +++ b/src/components/modals/TimeRangeLastNSecondsModal.tsx @@ -1,7 +1,7 @@ import type { FC } from 'react'; import { useCallback, useState } from 'react'; import { useTranslation } from 'react-i18next'; -import { Box } from 'react-native-flex-layout'; +import { Box, Flex } from 'react-native-flex-layout'; import type { ModalProps } from 'react-native-paper'; import { Button, Portal, Text, useTheme } from 'react-native-paper'; @@ -58,60 +58,49 @@ const TimeRangeLastNSecondsModal: FC = ({ return ( - - - - - {t('configureGraphs.setLastNSeconds')} - - - } - /> - - {t('configureGraphs.presets')} - - {Object.entries(Presets).map(([key, value]) => ( + + } + /> + + {t('configureGraphs.presets')} + + {Object.entries(Presets).map(([key, value]) => ( + - ))} - - - - - - - - + + ))} + diff --git a/src/components/styled/SettingsSurface.tsx b/src/components/styled/SettingsSurface.tsx index f9215097..700f0b9f 100644 --- a/src/components/styled/SettingsSurface.tsx +++ b/src/components/styled/SettingsSurface.tsx @@ -22,7 +22,7 @@ const SettingsSurface: FC = ({ children, ...props }) => { const theme = props.theme ?? rnpTheme; return ( - + {children} ); diff --git a/src/constants.ts b/src/constants.ts index c3c719d4..33eb2a79 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -39,5 +39,8 @@ export const weblateUrl = export const bugreportUrl = 'https://github.com/OpenDTU-App/opendtu-react-native/issues/new'; +export const databaseInformationUrl = + 'https://www.opendtu.solar/3rd_party/prometheus_database/'; + export const allowInAppUpdates = Config.DISABLE_IN_APP_UPDATES !== 'true' || __DEV__; diff --git a/src/translations b/src/translations index 00e26054..8a656979 160000 --- a/src/translations +++ b/src/translations @@ -1 +1 @@ -Subproject commit 00e2605463560bc2ef011de8520bf104a3814083 +Subproject commit 8a65697929f3c157e587046ad247ce0c84530e53 diff --git a/src/views/navigation/screens/DeviceGroup/DeviceSettingsScreen.tsx b/src/views/navigation/screens/DeviceGroup/DeviceSettingsScreen.tsx index 766a991e..d3fa964d 100644 --- a/src/views/navigation/screens/DeviceGroup/DeviceSettingsScreen.tsx +++ b/src/views/navigation/screens/DeviceGroup/DeviceSettingsScreen.tsx @@ -250,7 +250,7 @@ const DeviceSettingsScreen: FC = ({ } + left={props => } borderless style={{ borderRadius: 8, diff --git a/src/views/navigation/screens/GraphsGroup/ManageDatabasesScreen.tsx b/src/views/navigation/screens/GraphsGroup/ManageDatabasesScreen.tsx index 9245b3a2..d80b85c0 100644 --- a/src/views/navigation/screens/GraphsGroup/ManageDatabasesScreen.tsx +++ b/src/views/navigation/screens/GraphsGroup/ManageDatabasesScreen.tsx @@ -1,20 +1,32 @@ import type { FC } from 'react'; import { useCallback, useMemo, useState } from 'react'; import { useTranslation } from 'react-i18next'; -import { Box } from 'react-native-flex-layout'; -import { Appbar, Button, List, Text, useTheme } from 'react-native-paper'; +import { Box, Flex } from 'react-native-flex-layout'; +import { + Appbar, + Button, + List, + Surface, + Text, + useTheme, +} from 'react-native-paper'; +import Toast from 'react-native-toast-message'; -import { ScrollView, View } from 'react-native'; +import { Linking, ScrollView, View } from 'react-native'; import AddDatabaseModal from '@/components/modals/AddDatabaseModal'; import ManageDatabaseModal from '@/components/modals/ManageDatabaseModal'; import StyledListItem from '@/components/styled/StyledListItem'; -import { spacing } from '@/constants'; +import { rootLogging } from '@/utils/log'; + +import { databaseInformationUrl, spacing } from '@/constants'; import { useAppSelector } from '@/store'; import { StyledView } from '@/style'; import type { PropsWithNavigation } from '@/views/navigation/NavigationStack'; +const log = rootLogging.extend('ManageDatabaseScreen'); + const ManageDatabasesScreen: FC = ({ navigation }) => { const theme = useTheme(); const { t } = useTranslation(); @@ -37,6 +49,18 @@ const ManageDatabasesScreen: FC = ({ navigation }) => { setOpenAddDatabaseModal(true); }, []); + const handleOpenDatabaseInformation = useCallback(async () => { + if (await Linking.canOpenURL(databaseInformationUrl)) { + await Linking.openURL(databaseInformationUrl); + } else { + log.error('Cannot open url:', databaseInformationUrl); + Toast.show({ + type: 'error', + text1: t('cannotOpenUrl'), + }); + } + }, [t]); + return ( <> @@ -54,31 +78,45 @@ const ManageDatabasesScreen: FC = ({ navigation }) => { alignItems: 'center', }} > + + + + {t('database.clarificationTitle')} + + + {t('database.clarificationText')} + + + + {!hasConfiguredDatabases ? ( - - - - - {t('manageDatabases.noDatabasesConfigured')} - - - - - {t('manageDatabases.noDatabasesConfiguredHint')} - - - + + + + {t('manageDatabases.noDatabasesConfigured')} + - + + + {t('manageDatabases.noDatabasesConfiguredHint')} + + + + ) : ( = ({ navigation }) => { description={t( 'settings.networkSettings.changeDhcpEnabled.description', )} + switchLabel={t('settings.networkSettings.wifiStation.dhcpEnabled')} /> = ({ navigation }) => { description={t( 'settings.networkSettings.changeMdnsEnabled.description', )} + switchLabel={t('settings.networkSettings.mdns.enabled')} /> Date: Fri, 10 Oct 2025 14:34:49 +0200 Subject: [PATCH 2/4] Make adjustments for fullscreen modals --- src/components/BaseModal.tsx | 187 +++++++++++------- .../modals/NTPChangeTimezoneModal.tsx | 48 +++-- 2 files changed, 149 insertions(+), 86 deletions(-) diff --git a/src/components/BaseModal.tsx b/src/components/BaseModal.tsx index 383053d8..a48ba53d 100644 --- a/src/components/BaseModal.tsx +++ b/src/components/BaseModal.tsx @@ -3,7 +3,14 @@ import React from 'react'; import { useTranslation } from 'react-i18next'; import { Box, Flex } from 'react-native-flex-layout'; import type { ModalProps } from 'react-native-paper'; -import { Button, Icon, Modal, Text, useTheme } from 'react-native-paper'; +import { + Button, + Icon, + IconButton, + Modal, + Text, + useTheme, +} from 'react-native-paper'; import { Platform } from 'react-native'; @@ -78,89 +85,131 @@ const BaseModal: FC = ({ }} contentContainerStyle={{ backgroundColor: theme.colors.surfaceVariant, - padding: 24, + padding: fullscreen ? 8 : 24, paddingBottom: 0, borderRadius: fullscreen ? 0 : 28, marginVertical: fullscreen ? 0 : 8, - marginHorizontal: 32, - maxWidth: 450, + marginHorizontal: fullscreen ? 0 : 32, + maxWidth: fullscreen ? undefined : 450, + height: fullscreen ? '100%' : undefined, flex: 1, + justifyContent: 'space-between', }} > - - {icon ? ( - - - - ) : null} - - {title} - - {description ? ( - - {description} - - ) : null} - - {children ? {children} : null} - - - {dismissButton ? ( - + + + {icon && !fullscreen ? ( + + + ) : null} - {actions?.map((action, index) => ( + + + {fullscreen && dismissButton ? ( + + ) : null} + + {title} + + + + {actions?.length && actions.length > 1 && fullscreen ? ( - ))} + ) : null} + {description && !fullscreen ? ( + + {description} + + ) : null} + {children ? ( + + {children} + + ) : null} + {fullscreen ? null : ( + + + {dismissButton ? ( + + ) : null} + {actions?.map((action, index) => ( + + ))} + + + )} ); }; diff --git a/src/components/modals/NTPChangeTimezoneModal.tsx b/src/components/modals/NTPChangeTimezoneModal.tsx index c1d42844..ef83ad03 100644 --- a/src/components/modals/NTPChangeTimezoneModal.tsx +++ b/src/components/modals/NTPChangeTimezoneModal.tsx @@ -2,10 +2,9 @@ import type { FC } from 'react'; import { useCallback, useEffect, useMemo, useRef, useState } from 'react'; import { useTranslation } from 'react-i18next'; import { Box } from 'react-native-flex-layout'; -import type { ModalProps } from 'react-native-paper'; import { - Appbar, IconButton, + Portal, RadioButton, TextInput, useTheme, @@ -17,6 +16,8 @@ import Fuse from 'fuse.js'; import type { NTPSettings, TimezoneData } from '@/types/opendtu/settings'; +import type { ExtendableModalProps } from '@/components/BaseModal'; +import BaseModal from '@/components/BaseModal'; import type { PossibleEnumValues } from '@/components/modals/ChangeEnumValueModal'; import { rootLogging } from '@/utils/log'; @@ -28,8 +29,7 @@ import { FlashList } from '@shopify/flash-list'; const log = rootLogging.extend('NTPChangeTimezoneModal'); -export interface NTPChangeTimezoneModalProps - extends Omit { +export interface NTPChangeTimezoneModalProps extends ExtendableModalProps { timeSettings?: NTPSettings; setTimeSettings?: (settings: NTPSettings) => void; } @@ -113,6 +113,9 @@ const NTPChangeTimezoneModal: FC = props => { const fuse = useMemo(() => { return new Fuse(timezoneValues, { keys: ['label'], + threshold: 0.3, + ignoreLocation: true, + minMatchCharLength: 2, }); }, [timezoneValues]); @@ -162,13 +165,14 @@ const NTPChangeTimezoneModal: FC = props => { ]); return ( - <> - - - - - - + + + = props => { { setSearchQuery(value); }} @@ -190,10 +200,14 @@ const NTPChangeTimezoneModal: FC = props => { /> { + if (!searchQuery) { + return; + } + setSearchQuery(''); setHasScrolledToSelectedTimezone(false); }} - icon="close" + icon="close-circle-outline" /> = props => { refreshing={isRefreshing} onRefresh={handleGetTimezones} colors={[theme.colors.primary]} - progressBackgroundColor={theme.colors.elevation.level3} + progressBackgroundColor={theme.colors.surfaceVariant} tintColor={theme.colors.primary} /> } @@ -221,14 +235,14 @@ const NTPChangeTimezoneModal: FC = props => { }} labelVariant="bodyMedium" style={{ - backgroundColor: theme.colors.background, + backgroundColor: theme.colors.surfaceVariant, }} /> )} /> - - + + ); }; From 6e48ea004c2815a2ede5a60af03dad19ddc6a85e Mon Sep 17 00:00:00 2001 From: CommanderRedYT Date: Sat, 11 Oct 2025 01:32:45 +0200 Subject: [PATCH 3/4] Adjust the missing modals + cleanup --- src/InnerApp.tsx | 4 - src/components/BaseModal.tsx | 126 +++++----- .../{Charts => charts}/AcPowerChart.tsx | 2 +- .../{Charts => charts}/AcVoltageChart.tsx | 2 +- .../{Charts => charts}/DcPowerChart.tsx | 2 +- .../{Charts => charts}/DcVoltageChart.tsx | 2 +- .../{Charts => charts}/ImportantCharts.tsx | 8 +- .../{Charts => charts}/UnifiedLineChart.tsx | 0 src/components/modals/ChangeLanguageModal.tsx | 2 +- .../modals/ChangeTextValueModal.tsx | 2 +- .../modals/EnableAppUpdatesModal.tsx | 4 + .../modals/EnableFetchOpenDtuUpdatesModal.tsx | 4 + src/components/modals/InstallAssetModal.tsx | 82 ++++--- src/components/modals/LimitConfigModal.tsx | 218 ++++++++++-------- src/components/modals/ManageDatabaseModal.tsx | 2 +- .../modals/NTPChangeTimezoneModal.tsx | 2 +- src/components/modals/PowerConfigModal.tsx | 157 +++++++------ src/components/modals/SelectFirmwareModal.tsx | 44 ++-- src/database/index.tsx | 2 +- src/database/prometheus.ts | 2 +- src/translations | 2 +- src/views/navigation/NavigationTabs.tsx | 2 +- ...SetupAuthenticateOpenDTUInstanceScreen.tsx | 2 + src/views/navigation/tabs/GraphTab.tsx | 2 +- 24 files changed, 388 insertions(+), 287 deletions(-) rename src/components/{Charts => charts}/AcPowerChart.tsx (93%) rename src/components/{Charts => charts}/AcVoltageChart.tsx (93%) rename src/components/{Charts => charts}/DcPowerChart.tsx (93%) rename src/components/{Charts => charts}/DcVoltageChart.tsx (93%) rename src/components/{Charts => charts}/ImportantCharts.tsx (96%) rename src/components/{Charts => charts}/UnifiedLineChart.tsx (100%) diff --git a/src/InnerApp.tsx b/src/InnerApp.tsx index 48437580..252f22c5 100644 --- a/src/InnerApp.tsx +++ b/src/InnerApp.tsx @@ -369,16 +369,12 @@ const InnerApp: FC = () => { {}} - dismissable={false} - dismissableBackButton={false} /> {}} - dismissable={false} - dismissableBackButton={false} /> ); diff --git a/src/components/BaseModal.tsx b/src/components/BaseModal.tsx index a48ba53d..f0fb916d 100644 --- a/src/components/BaseModal.tsx +++ b/src/components/BaseModal.tsx @@ -40,6 +40,7 @@ export interface BaseModalProps extends ExtendableModalProps { onPress: () => void; variant?: 'text' | 'outlined' | 'contained'; disabled?: boolean; + buttonColor?: string; textColor?: string; loading?: boolean; }[]; @@ -48,6 +49,7 @@ export interface BaseModalProps extends ExtendableModalProps { modalProps?: Omit; fullscreen?: boolean; hideBottomPadding?: boolean; + legacy?: boolean; children?: React.ReactNode; } @@ -63,6 +65,7 @@ const BaseModal: FC = ({ actions, icon, hideBottomPadding, + legacy, ...rest }) => { const theme = useTheme(); @@ -97,67 +100,85 @@ const BaseModal: FC = ({ }} > - - {icon && !fullscreen ? ( - - - - ) : null} - - - {fullscreen && dismissButton ? ( - + {!legacy ? ( + <> + + {icon && !fullscreen ? ( + + + + ) : null} + + + {fullscreen && dismissButton ? ( + + ) : null} + + {title} + + + + {actions?.length && actions.length > 1 && fullscreen ? ( + + ) : null} + {description && !fullscreen ? ( + + {description} + ) : null} - - {title} - - - {actions?.length && actions.length > 1 && fullscreen ? ( - - ) : null} - {description && !fullscreen ? ( - - {description} - - ) : null} - + + ) : null} {children ? ( - + {children} ) : null} {fullscreen ? null : ( - + {dismissButton ? ( - + ) : ( - - - + + + {t('firmwares.waitingForReconnect')} {error ? ( <> - + = ({ ) : ( - + )} diff --git a/src/components/modals/LimitConfigModal.tsx b/src/components/modals/LimitConfigModal.tsx index d99c715a..acad0bca 100644 --- a/src/components/modals/LimitConfigModal.tsx +++ b/src/components/modals/LimitConfigModal.tsx @@ -1,13 +1,12 @@ import type { FC } from 'react'; import { useCallback, useEffect, useMemo, useState } from 'react'; import { useTranslation } from 'react-i18next'; -import { Box } from 'react-native-flex-layout'; +import { Box, Flex } from 'react-native-flex-layout'; import { Button, HelperText, Portal, RadioButton, - Surface, Text, useTheme, } from 'react-native-paper'; @@ -41,11 +40,26 @@ const buttonColors: Record< const limitStatus = ( t: TFunction, -): Record => ({ - Unknown: { label: t('powerStatus.unknown'), color: '#9E9E9E' }, - Ok: { label: t('powerStatus.ok'), color: '#4CAF50' }, - Failure: { label: t('powerStatus.failure'), color: '#F44336' }, - Pending: { label: t('powerStatus.pending'), color: '#FFC107' }, +): Record< + SetStatus, + { label: string; backgroundColor: string; color: string } +> => ({ + Unknown: { + label: t('powerStatus.unknown'), + backgroundColor: '#9E9E9E', + color: '#FFF', + }, + Ok: { label: t('powerStatus.ok'), backgroundColor: '#4CAF50', color: '#FFF' }, + Failure: { + label: t('powerStatus.failure'), + backgroundColor: '#F44336', + color: '#FFF', + }, + Pending: { + label: t('powerStatus.pending'), + backgroundColor: '#FFC107', + color: '#000', + }, }); const LimitConfigModal: FC = ({ @@ -176,98 +190,120 @@ const LimitConfigModal: FC = ({ {...props} onDismiss={onDismiss} title={t('limitStatus.title')} - dismissButton={false} + icon="tune" + dismissButton="dismiss" hideBottomPadding > - - - - - {t('limitStatus.lastTransmittedStatus')} - - - {calculatedStatus?.label} - - - - - {t('limitStatus.limits')} - {t('units.percent', { value: relativeLimit })} - {t('units.watt', { value: absoluteLimit })} - - - - - setLimitType(value as LimitType)} - value={limitType} + + + + {t('limitStatus.lastTransmittedStatus')} + + - - - + {calculatedStatus?.label} + + + + {t('limitStatus.limits')} + + {t('units.percent', { value: relativeLimit })} + + + {t('units.watt', { value: absoluteLimit })} + - - { - setLimitValue(text); - setError(null); - }} - mode="outlined" - keyboardType="numeric" - error={!!error} - disabled={loading} + + + setLimitType(value as LimitType)} + value={limitType} + > + - {error !== null ? ( - {error} - ) : null} - - + + + + { + setLimitValue(text); + setError(null); + }} + mode="flat" + keyboardType="numeric" + error={!!error} + disabled={loading} style={{ - flexDirection: 'column', - gap: 8, + backgroundColor: theme.colors.elevation.level3, + borderTopLeftRadius: theme.roundness * 3, + borderTopRightRadius: theme.roundness * 3, }} + /> + {error !== null ? ( + {error} + ) : null} + + + - - - + {t('limitStatus.setPermanentLimit')} + + diff --git a/src/components/modals/ManageDatabaseModal.tsx b/src/components/modals/ManageDatabaseModal.tsx index 1066b8a0..f51d26e6 100644 --- a/src/components/modals/ManageDatabaseModal.tsx +++ b/src/components/modals/ManageDatabaseModal.tsx @@ -3,7 +3,7 @@ import { useCallback, useEffect, useMemo, useState } from 'react'; import { useTranslation } from 'react-i18next'; import { Box } from 'react-native-flex-layout'; import type { ModalProps } from 'react-native-paper'; -import { Button, Portal, Text, useTheme } from 'react-native-paper'; +import { Portal, useTheme } from 'react-native-paper'; import { removeDatabaseConfig, updateDatabaseConfig } from '@/slices/settings'; diff --git a/src/components/modals/NTPChangeTimezoneModal.tsx b/src/components/modals/NTPChangeTimezoneModal.tsx index ef83ad03..0b02fd3d 100644 --- a/src/components/modals/NTPChangeTimezoneModal.tsx +++ b/src/components/modals/NTPChangeTimezoneModal.tsx @@ -189,7 +189,7 @@ const NTPChangeTimezoneModal: FC = props => { style={{ flex: 1, marginHorizontal: 8, - backgroundColor: theme.colors.surface, + backgroundColor: theme.colors.elevation.level3, borderTopLeftRadius: theme.roundness * 3, borderTopRightRadius: theme.roundness * 3, }} diff --git a/src/components/modals/PowerConfigModal.tsx b/src/components/modals/PowerConfigModal.tsx index 1fda7ba2..a5023fc2 100644 --- a/src/components/modals/PowerConfigModal.tsx +++ b/src/components/modals/PowerConfigModal.tsx @@ -2,20 +2,20 @@ import type { FC } from 'react'; import { useCallback, useEffect, useMemo, useState } from 'react'; import { useTranslation } from 'react-i18next'; import { Box } from 'react-native-flex-layout'; -import type { ModalProps } from 'react-native-paper'; import { Button, Portal, Text, useTheme } from 'react-native-paper'; import type { TFunction } from 'i18next'; import { PowerSetAction, SetStatus } from '@/types/opendtu/control'; +import type { ExtendableModalProps } from '@/components/BaseModal'; import BaseModal from '@/components/BaseModal'; import useInverterPowerData from '@/hooks/useInverterPowerData'; import { useApi } from '@/api/ApiHandler'; -export interface PowerConfigModalProps extends Omit { +export interface PowerConfigModalProps extends ExtendableModalProps { inverterSerial: string; } @@ -30,11 +30,30 @@ const buttonColors: Record< const powerStatus = ( t: TFunction, -): Record => ({ - Unknown: { label: t('powerStatus.unknown'), color: '#9E9E9E' }, - Ok: { label: t('powerStatus.ok'), color: '#4CAF50' }, - Failure: { label: t('powerStatus.failure'), color: '#F44336' }, - Pending: { label: t('powerStatus.pending'), color: '#FFC107' }, +): Record< + SetStatus, + { label: string; color: string; backgroundColor: string } +> => ({ + Unknown: { + label: t('powerStatus.unknown'), + color: '#FFF', + backgroundColor: '#9E9E9E', + }, + Ok: { + label: t('powerStatus.ok'), + color: '#FFF', + backgroundColor: '#4CAF50', + }, + Failure: { + label: t('powerStatus.failure'), + color: '#FFF', + backgroundColor: '#F44336', + }, + Pending: { + label: t('powerStatus.pending'), + color: '#FFF', + backgroundColor: '#FFC107', + }, }); const PowerConfigModal: FC = ({ @@ -97,74 +116,70 @@ const PowerConfigModal: FC = ({ return ( - - - - {t('powerStatus.title')} - - - - {t('powerStatus.lastTransmittedStatus')} - - - {calculatedStatus?.label} - - - {error !== null ? ( - - - {error} - - - ) : null} - - - - - - + + + {t('powerStatus.lastTransmittedStatus')} + + - + {calculatedStatus?.label} + + + {error !== null ? ( + + + {error} + + ) : null} + + + + diff --git a/src/components/modals/SelectFirmwareModal.tsx b/src/components/modals/SelectFirmwareModal.tsx index b90da69b..e5e66ea1 100644 --- a/src/components/modals/SelectFirmwareModal.tsx +++ b/src/components/modals/SelectFirmwareModal.tsx @@ -69,29 +69,35 @@ const SelectFirmwareModal: FC = ({ return ( <> - - - - + + + + {t('firmwares.installFirmware', { name: release?.name })} - - - {release?.assets.map(asset => ( - - ))} - - + + {release?.assets.map(asset => ( + + ))} + { navigationState={{ index, routes }} onIndexChange={setIndex} renderScene={renderScene} - barStyle={{ backgroundColor: theme.colors.surface }} + barStyle={{ backgroundColor: theme.colors.elevation.level2 }} /> ); }; diff --git a/src/views/navigation/screens/SetupGroup/SetupAuthenticateOpenDTUInstanceScreen.tsx b/src/views/navigation/screens/SetupGroup/SetupAuthenticateOpenDTUInstanceScreen.tsx index 5dfd5bd8..a6f8088d 100644 --- a/src/views/navigation/screens/SetupGroup/SetupAuthenticateOpenDTUInstanceScreen.tsx +++ b/src/views/navigation/screens/SetupGroup/SetupAuthenticateOpenDTUInstanceScreen.tsx @@ -159,6 +159,7 @@ const SetupAuthenticateOpenDTUInstanceScreen: FC = ({ }} disabled={loading || !previousStepValid} error={!!error} + style={{ backgroundColor: theme.colors.elevation.level3 }} /> @@ -179,6 +180,7 @@ const SetupAuthenticateOpenDTUInstanceScreen: FC = ({ onPress={() => setVisible(!visible)} /> } + style={{ backgroundColor: theme.colors.elevation.level3 }} /> diff --git a/src/views/navigation/tabs/GraphTab.tsx b/src/views/navigation/tabs/GraphTab.tsx index 663996d5..e806778d 100644 --- a/src/views/navigation/tabs/GraphTab.tsx +++ b/src/views/navigation/tabs/GraphTab.tsx @@ -1,6 +1,6 @@ import { useTheme } from 'react-native-paper'; -import ImportantCharts from '@/components/Charts/ImportantCharts'; +import ImportantCharts from '@/components/charts/ImportantCharts'; import DeviceOfflineWrapper from '@/components/DeviceOfflineWrapper'; import { StyledScrollView, StyledView } from '@/style'; From c98a51205c06de60710af3080c8ce97df63b66f5 Mon Sep 17 00:00:00 2001 From: CommanderRedYT Date: Sat, 11 Oct 2025 01:38:33 +0200 Subject: [PATCH 4/4] Add units --- src/translations | 2 +- .../screens/SettingsGroup/NTPSettingsScreen.tsx | 12 +++++++++--- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/src/translations b/src/translations index 02e0e8e0..4b8b62bc 160000 --- a/src/translations +++ b/src/translations @@ -1 +1 @@ -Subproject commit 02e0e8e075a84f14a33f9a4c69b623b5ef92e7bf +Subproject commit 4b8b62bca9313345382899d026071136644d2a43 diff --git a/src/views/navigation/screens/SettingsGroup/NTPSettingsScreen.tsx b/src/views/navigation/screens/SettingsGroup/NTPSettingsScreen.tsx index f6e0a32d..6a197ebf 100644 --- a/src/views/navigation/screens/SettingsGroup/NTPSettingsScreen.tsx +++ b/src/views/navigation/screens/SettingsGroup/NTPSettingsScreen.tsx @@ -2,7 +2,7 @@ import type { FC } from 'react'; import { useCallback, useEffect, useMemo, useState } from 'react'; import { useTranslation } from 'react-i18next'; import { Box } from 'react-native-flex-layout'; -import { Appbar, Button, List, useTheme } from 'react-native-paper'; +import { Appbar, Button, List, TextInput, useTheme } from 'react-native-paper'; import { RefreshControl, ScrollView, View } from 'react-native'; @@ -256,7 +256,9 @@ const NTPSettingsScreen: FC = ({ navigation }) => { ( = ({ navigation }) => { ( = ({ navigation }) => { description={t('settings.ntpSettings.changeLatitudeDescription')} inputProps={{ keyboardType: 'numeric', + right: , }} /> = ({ navigation }) => { description={t('settings.ntpSettings.changeLongitudeDescription')} inputProps={{ keyboardType: 'numeric', + right: , }} />