CEMROS_hauler_pti_app/rn/app/src/screens/SettingsScreen.js

761 lines
36 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/*
Предрейсовые осмотры - мобильное приложение
Экран настроек приложения
*/
//---------------------
//Подключение библиотек
//---------------------
const React = require('react'); //React и хуки
const { ScrollView, View, Pressable } = require('react-native'); //Базовые компоненты
const AdaptiveView = require('../components/common/AdaptiveView'); //Адаптивный контейнер
const AppText = require('../components/common/AppText'); //Общий текст
const AppButton = require('../components/common/AppButton'); //Кнопка
const AppSwitch = require('../components/common/AppSwitch'); //Переключатель
const CopyButton = require('../components/common/CopyButton'); //Кнопка копирования
const InputDialog = require('../components/common/InputDialog'); //Диалог ввода
const AppHeader = require('../components/layout/AppHeader'); //Заголовок
const { useAppMessagingContext } = require('../components/layout/AppMessagingProvider'); //Контекст сообщений
const { useAppModeContext } = require('../components/layout/AppModeProvider'); //Контекст режима
const { useAppNavigationContext } = require('../components/layout/AppNavigationProvider'); //Контекст навигации
const { useHardwareScannerContext } = require('../components/layout/HardwareScannerProvider'); //Встроенный сканер
const { useAppLocalDbContext } = require('../components/layout/AppLocalDbProvider'); //Контекст локальной БД
const { useAppAuthContext } = require('../components/layout/AppAuthProvider'); //Контекст авторизации
const { AUTH_SETTINGS_KEYS, DEFAULT_SERVER_REQUEST_TIMEOUT } = require('../config/authConfig'); //Конфиг авторизации
const { DIALOG_BUTTON_TYPE, DIALOG_CANCEL_BUTTON, getConfirmButtonOptions } = require('../config/dialogButtons'); //Кнопки диалогов
const { getAppInfo, getModeLabel } = require('../utils/appInfo'); //Информация о приложении и режиме
const { validateServerUrlAllowEmpty, validateIdleTimeoutAllowEmpty, validateServerRequestTimeout } = require('../utils/validation'); //Валидация
const {
APP_ABOUT_TITLE,
SETTINGS_SERVER_SAVED_MESSAGE,
SETTINGS_RESET_SUCCESS_MESSAGE,
MENU_ITEM_ABOUT,
SCANNER_SETTING_LABEL,
SCANNER_PRIORITY_LABEL,
SCANNER_PRIORITY_CAMERA,
SCANNER_PRIORITY_HARDWARE
} = require('../config/messages'); //Сообщения
const styles = require('../styles/screens/SettingsScreen.styles'); //Стили экрана
//-----------
//Тело модуля
//-----------
function SettingsScreen() {
const { showInfo, showError, showSuccess } = useAppMessagingContext();
const { APP_MODE, mode, setNotConnected } = useAppModeContext();
const { goBack, canGoBack } = useAppNavigationContext();
const { getSetting, setSetting, clearSettings, vacuum, isDbReady } = useAppLocalDbContext();
const { session, isAuthenticated, getDeviceId, setLastSavedServerUrlFromSettings } = useAppAuthContext();
const [serverUrl, setServerUrl] = React.useState('');
const [hideServerUrl, setHideServerUrl] = React.useState(false);
const [idleTimeout, setIdleTimeout] = React.useState('');
const [deviceId, setDeviceId] = React.useState('');
const [isLoading, setIsLoading] = React.useState(false);
const [isServerUrlDialogVisible, setIsServerUrlDialogVisible] = React.useState(false);
const [isIdleTimeoutDialogVisible, setIsIdleTimeoutDialogVisible] = React.useState(false);
const [serverRequestTimeout, setServerRequestTimeout] = React.useState('');
const [isServerRequestTimeoutDialogVisible, setIsServerRequestTimeoutDialogVisible] = React.useState(false);
const [alwaysShowScanner, setAlwaysShowScanner] = React.useState(false);
const [mainScannerPriority, setMainScannerPriority] = React.useState('camera');
const { registerHandler, unregisterHandler, SCREENS: scannerScreens } = useHardwareScannerContext();
const serverUrlDialogRef = React.useRef(null);
const idleTimeoutDialogRef = React.useRef(null);
const serverRequestTimeoutDialogRef = React.useRef(null);
//Предотвращение повторной загрузки настроек
const settingsLoadedRef = React.useRef(false);
//Обработчик встроенного сканера: только при открытом диалоге ввода — замена значения в поле (данные как есть); иначе ничего не делать
const handleHardwareScan = React.useCallback(
barcode => {
if (isServerUrlDialogVisible && serverUrlDialogRef.current) {
serverUrlDialogRef.current.setValueFromScanner(barcode);
} else if (isIdleTimeoutDialogVisible && idleTimeoutDialogRef.current) {
idleTimeoutDialogRef.current.setValueFromScanner(barcode);
} else if (isServerRequestTimeoutDialogVisible && serverRequestTimeoutDialogRef.current) {
serverRequestTimeoutDialogRef.current.setValueFromScanner(barcode);
}
},
[isServerUrlDialogVisible, isIdleTimeoutDialogVisible, isServerRequestTimeoutDialogVisible]
);
const settingsScanHandlerRef = React.useRef(handleHardwareScan);
settingsScanHandlerRef.current = handleHardwareScan;
const stableSettingsScanHandler = React.useCallback(barcode => {
if (typeof settingsScanHandlerRef.current === 'function') settingsScanHandlerRef.current(barcode);
}, []);
//Регистрация обработчика встроенного сканера на экране настроек
React.useEffect(() => {
registerHandler(scannerScreens.SETTINGS, stableSettingsScanHandler);
return function cleanup() {
unregisterHandler(scannerScreens.SETTINGS);
};
}, [registerHandler, unregisterHandler, scannerScreens.SETTINGS, stableSettingsScanHandler]);
//Загрузка сохраненных настроек при готовности БД
React.useEffect(() => {
//Выходим, если БД не готова или уже загрузили настройки
if (!isDbReady || settingsLoadedRef.current) {
return;
}
settingsLoadedRef.current = true;
const loadSettings = async () => {
setIsLoading(true);
try {
const savedUrl = await getSetting(AUTH_SETTINGS_KEYS.SERVER_URL);
if (savedUrl) {
setServerUrl(savedUrl);
}
const savedHideServerUrl = await getSetting(AUTH_SETTINGS_KEYS.HIDE_SERVER_URL);
setHideServerUrl(savedHideServerUrl === 'true' || savedHideServerUrl === true);
const savedIdleTimeout = await getSetting(AUTH_SETTINGS_KEYS.IDLE_TIMEOUT);
setIdleTimeout(savedIdleTimeout != null ? String(savedIdleTimeout).trim() : '');
const savedServerRequestTimeout = await getSetting(AUTH_SETTINGS_KEYS.SERVER_REQUEST_TIMEOUT);
const serverRequestTimeoutValue = savedServerRequestTimeout != null ? String(savedServerRequestTimeout).trim() : '';
if (serverRequestTimeoutValue) {
setServerRequestTimeout(serverRequestTimeoutValue);
} else {
const defaultValue = String(DEFAULT_SERVER_REQUEST_TIMEOUT);
setServerRequestTimeout(defaultValue);
await setSetting(AUTH_SETTINGS_KEYS.SERVER_REQUEST_TIMEOUT, defaultValue);
}
//Получаем или генерируем идентификатор устройства
const currentDeviceId = await getDeviceId();
setDeviceId(currentDeviceId || '');
const savedAlwaysShowScanner = await getSetting(AUTH_SETTINGS_KEYS.ALWAYS_SHOW_SCANNER);
setAlwaysShowScanner(savedAlwaysShowScanner === 'true' || savedAlwaysShowScanner === true);
const savedPriority = await getSetting(AUTH_SETTINGS_KEYS.MAIN_SCANNER_PRIORITY);
setMainScannerPriority(savedPriority === 'hardware' ? 'hardware' : 'camera');
} catch (error) {
showError('Не удалось загрузить настройки');
} finally {
setIsLoading(false);
}
};
loadSettings();
}, [isDbReady, getSetting, setSetting, showError, getDeviceId]);
//Доступность редактирования URL сервера (только в режиме "Не подключено")
const isServerUrlEditable = React.useMemo(
() => !isLoading && isDbReady && mode === APP_MODE.NOT_CONNECTED,
[isLoading, isDbReady, mode, APP_MODE.NOT_CONNECTED]
);
//Стиль поля URL сервера при нажатии
const getServerUrlFieldPressableStyle = React.useCallback(
({ pressed }) => [
styles.serverUrlField,
isServerUrlEditable ? (pressed ? styles.serverUrlFieldPressed : null) : styles.serverUrlFieldDisabled
],
[isServerUrlEditable]
);
//Стиль поля времени простоя при нажатии
const getIdleTimeoutFieldPressableStyle = React.useCallback(
({ pressed }) => [styles.serverUrlField, pressed && styles.serverUrlFieldPressed],
[]
);
//Стиль поля времени ожидания сервера при нажатии
const getServerRequestTimeoutFieldPressableStyle = React.useCallback(
({ pressed }) => [styles.serverUrlField, pressed && styles.serverUrlFieldPressed],
[]
);
//Открытие диалога ввода URL сервера (только в режиме "Не подключено")
const handleOpenServerUrlDialog = React.useCallback(() => {
if (!isServerUrlEditable) {
return;
}
setIsServerUrlDialogVisible(true);
}, [isServerUrlEditable]);
//Закрытие диалога ввода URL сервера
const handleCloseServerUrlDialog = React.useCallback(() => {
setIsServerUrlDialogVisible(false);
}, []);
//Сохранение настроек сервера
const handleSaveServerUrl = React.useCallback(
async url => {
setIsServerUrlDialogVisible(false);
setIsLoading(true);
const valueToSave = url != null ? String(url).trim() : '';
try {
const success = await setSetting(AUTH_SETTINGS_KEYS.SERVER_URL, valueToSave);
if (success) {
setServerUrl(valueToSave);
if (valueToSave) {
setLastSavedServerUrlFromSettings(valueToSave);
}
showSuccess(SETTINGS_SERVER_SAVED_MESSAGE);
} else {
showError('Не удалось сохранить настройки');
}
} catch (error) {
showError('Не удалось сохранить настройки');
}
setIsLoading(false);
},
[setSetting, showError, showSuccess, setLastSavedServerUrlFromSettings]
);
//Переключатель «Всегда отображать сканер на главном экране»
const handleToggleAlwaysShowScanner = React.useCallback(
async value => {
try {
const success = await setSetting(AUTH_SETTINGS_KEYS.ALWAYS_SHOW_SCANNER, value ? 'true' : 'false');
if (success) {
setAlwaysShowScanner(value);
showSuccess('Настройка сохранена');
} else {
showError('Не удалось сохранить настройку');
}
} catch (error) {
showError('Не удалось сохранить настройку');
}
},
[setSetting, showSuccess, showError]
);
//Выбор приоритета на главном экране: камера или встроенный сканер
const handleSelectScannerPriority = React.useCallback(
async value => {
if (value !== 'camera' && value !== 'hardware') return;
try {
const success = await setSetting(AUTH_SETTINGS_KEYS.MAIN_SCANNER_PRIORITY, value);
if (success) {
setMainScannerPriority(value);
showSuccess('Настройка сохранена');
} else {
showError('Не удалось сохранить настройку');
}
} catch (error) {
showError('Не удалось сохранить настройку');
}
},
[setSetting, showSuccess, showError]
);
//Выбор приоритета: камера
const handleSelectCameraPriority = React.useCallback(() => {
handleSelectScannerPriority('camera');
}, [handleSelectScannerPriority]);
//Выбор приоритета: встроенный сканер
const handleSelectHardwarePriority = React.useCallback(() => {
handleSelectScannerPriority('hardware');
}, [handleSelectScannerPriority]);
//Переключатель скрытия URL сервера в окне логина
const handleToggleHideServerUrl = React.useCallback(
async value => {
try {
const success = await setSetting(AUTH_SETTINGS_KEYS.HIDE_SERVER_URL, value ? 'true' : 'false');
if (success) {
setHideServerUrl(value);
showSuccess('Настройка сохранена');
} else {
showError('Не удалось сохранить настройку');
}
} catch (error) {
showError('Не удалось сохранить настройку');
}
},
[setSetting, showSuccess, showError]
);
//Открытие диалога ввода времени простоя
const handleOpenIdleTimeoutDialog = React.useCallback(() => {
setIsIdleTimeoutDialogVisible(true);
}, []);
//Закрытие диалога ввода времени простоя
const handleCloseIdleTimeoutDialog = React.useCallback(() => {
setIsIdleTimeoutDialogVisible(false);
}, []);
//Открытие диалога ввода времени ожидания ответа от сервера
const handleOpenServerRequestTimeoutDialog = React.useCallback(() => {
setIsServerRequestTimeoutDialogVisible(true);
}, []);
//Закрытие диалога ввода времени ожидания ответа от сервера
const handleCloseServerRequestTimeoutDialog = React.useCallback(() => {
setIsServerRequestTimeoutDialogVisible(false);
}, []);
//Сохранение времени простоя
const handleSaveIdleTimeout = React.useCallback(
async value => {
setIsIdleTimeoutDialogVisible(false);
setIsLoading(true);
try {
const trimmedValue = value ? value.trim() : '';
const success = await setSetting(AUTH_SETTINGS_KEYS.IDLE_TIMEOUT, trimmedValue);
if (success) {
setIdleTimeout(trimmedValue);
showSuccess('Время простоя сохранено');
} else {
showError('Не удалось сохранить настройку');
}
} catch (error) {
showError('Не удалось сохранить настройку');
}
setIsLoading(false);
},
[setSetting, showSuccess, showError]
);
//Сохранение времени ожидания ответа от сервера
const handleSaveServerRequestTimeout = React.useCallback(
async value => {
setIsServerRequestTimeoutDialogVisible(false);
setIsLoading(true);
try {
const trimmedValue = value != null ? String(value).trim() : '';
const success = await setSetting(AUTH_SETTINGS_KEYS.SERVER_REQUEST_TIMEOUT, trimmedValue);
if (success) {
setServerRequestTimeout(trimmedValue);
showSuccess('Время ожидания сервера сохранено');
} else {
showError('Не удалось сохранить настройку');
}
} catch (error) {
showError('Не удалось сохранить настройку');
}
setIsLoading(false);
},
[setSetting, showSuccess, showError]
);
//Выполнение очистки кэша
const performClearCache = React.useCallback(async () => {
showSuccess('Кэш успешно очищен');
}, [showSuccess]);
//Очистка кэша — диалог подтверждения, по подтверждению ничего не выполняется
const handleClearCache = React.useCallback(() => {
const confirmButton = getConfirmButtonOptions(DIALOG_BUTTON_TYPE.ERROR, 'Очистить', performClearCache);
showInfo('Очистить кэш приложения?', {
title: 'Подтверждение',
buttons: [DIALOG_CANCEL_BUTTON, confirmButton]
});
}, [showInfo, performClearCache]);
//Выполнение сброса настроек (для диалога подтверждения)
//Подключён (онлайн/офлайн): сбрасываем только непричастные к подключению настройки; не подключён: полный сброс
const performResetSettings = React.useCallback(async () => {
try {
if (mode === APP_MODE.NOT_CONNECTED) {
const success = await clearSettings();
if (success) {
const defaultServerTimeout = String(DEFAULT_SERVER_REQUEST_TIMEOUT);
await setSetting(AUTH_SETTINGS_KEYS.SERVER_REQUEST_TIMEOUT, defaultServerTimeout);
setServerUrl('');
setHideServerUrl(false);
setAlwaysShowScanner(false);
setMainScannerPriority('camera');
setIdleTimeout('');
setServerRequestTimeout(defaultServerTimeout);
setNotConnected();
showSuccess(SETTINGS_RESET_SUCCESS_MESSAGE);
} else {
showError('Не удалось сбросить настройки');
}
} else {
//Подключён (онлайн или офлайн): сбрасываем только время простоя и таймаут сервера
const defaultServerTimeout = String(DEFAULT_SERVER_REQUEST_TIMEOUT);
await setSetting(AUTH_SETTINGS_KEYS.IDLE_TIMEOUT, '');
await setSetting(AUTH_SETTINGS_KEYS.SERVER_REQUEST_TIMEOUT, defaultServerTimeout);
setIdleTimeout('');
setServerRequestTimeout(defaultServerTimeout);
showSuccess(SETTINGS_RESET_SUCCESS_MESSAGE);
}
} catch (error) {
showError('Не удалось сбросить настройки');
}
}, [
mode,
APP_MODE.NOT_CONNECTED,
setServerUrl,
setHideServerUrl,
setIdleTimeout,
setServerRequestTimeout,
setNotConnected,
clearSettings,
setSetting,
showSuccess,
showError
]);
//Сброс настроек: при подключении (онлайн/офлайн) — без настроек подключения; при отсутствии подключения — все настройки
const handleResetSettings = React.useCallback(() => {
const confirmButton = getConfirmButtonOptions(DIALOG_BUTTON_TYPE.WARNING, 'Сбросить', performResetSettings);
showInfo('Сбросить все настройки к значениям по умолчанию?', {
title: 'Подтверждение сброса',
buttons: [DIALOG_CANCEL_BUTTON, confirmButton]
});
}, [showInfo, performResetSettings]);
//Оптимизация базы данных
const handleOptimizeDb = React.useCallback(async () => {
setIsLoading(true);
try {
const success = await vacuum();
if (success) {
showSuccess('База данных оптимизирована');
} else {
showError('Не удалось оптимизировать базу данных');
}
} catch (error) {
showError('Не удалось оптимизировать базу данных');
} finally {
setIsLoading(false);
}
}, [vacuum, showSuccess, showError]);
//Показ информации о приложении
const handleShowAppInfo = React.useCallback(() => {
const appInfo = getAppInfo({
mode,
serverUrl,
isDbReady
});
showInfo(appInfo, {
title: APP_ABOUT_TITLE
});
}, [mode, serverUrl, isDbReady, showInfo]);
//Обработчик кнопки назад
const handleBackPress = React.useCallback(() => {
if (canGoBack) {
goBack();
}
}, [canGoBack, goBack]);
//Обработчик копирования адреса сервера
const handleCopyServerUrl = React.useCallback(() => {
showSuccess('Адрес сервера скопирован');
}, [showSuccess]);
//Обработчик ошибки копирования
const handleCopyError = React.useCallback(() => {
showError('Не удалось скопировать адрес');
}, [showError]);
//Обработчик копирования идентификатора устройства
const handleCopyDeviceId = React.useCallback(() => {
showSuccess('Идентификатор устройства скопирован');
}, [showSuccess]);
return (
<AdaptiveView padding={false}>
<AppHeader showBackButton={true} onBackPress={handleBackPress} showMenuButton={false} />
<ScrollView style={styles.scrollView} contentContainerStyle={styles.scrollContent} showsVerticalScrollIndicator={false}>
{isAuthenticated && session ? (
<View style={styles.section}>
<AppText style={styles.sectionTitle} variant="h3" weight="semibold">
Информация о подключении
</AppText>
<View style={styles.infoRow}>
<AppText style={styles.infoLabel} variant="body">
Имя пользователя:
</AppText>
<AppText style={styles.infoValue} variant="body" weight="medium">
{session.userName || session.userCode || 'Не указано'}
</AppText>
</View>
<View style={styles.infoRow}>
<AppText style={styles.infoLabel} variant="body">
Организация:
</AppText>
<AppText style={styles.infoValue} variant="body" weight="medium">
{session.companyName || 'Не указана'}
</AppText>
</View>
</View>
) : null}
<View style={styles.section}>
<AppText style={styles.sectionTitle} variant="h3" weight="semibold">
Сервер приложений
</AppText>
<AppText style={styles.fieldLabel} variant="caption" weight="medium">
URL сервера
</AppText>
<View style={styles.serverUrlRow}>
<Pressable style={getServerUrlFieldPressableStyle} onPress={handleOpenServerUrlDialog} disabled={!isServerUrlEditable}>
<AppText
style={[
styles.serverUrlText,
!serverUrl && styles.serverUrlPlaceholder,
!isServerUrlEditable && styles.serverUrlTextDisabled
]}
numberOfLines={1}
>
{serverUrl || 'Нажмите для ввода адреса'}
</AppText>
</Pressable>
{serverUrl ? (
<CopyButton value={serverUrl} onCopy={handleCopyServerUrl} onError={handleCopyError} style={styles.serverUrlCopyButton} />
) : null}
</View>
<View style={styles.switchRow}>
<AppSwitch
label="Не отображать адрес сервера в окне логина"
value={hideServerUrl}
onValueChange={handleToggleHideServerUrl}
disabled={isLoading || !isDbReady}
/>
</View>
</View>
{mode === APP_MODE.ONLINE || mode === APP_MODE.OFFLINE ? (
<View style={styles.section}>
<AppText style={styles.sectionTitle} variant="h3" weight="semibold">
Главный экран
</AppText>
<View style={styles.switchRow}>
<AppSwitch
label={SCANNER_SETTING_LABEL}
value={alwaysShowScanner}
onValueChange={handleToggleAlwaysShowScanner}
disabled={isLoading || !isDbReady}
/>
</View>
{alwaysShowScanner ? (
<View style={styles.prioritySection}>
<AppText style={styles.fieldLabel} variant="caption" weight="medium">
{SCANNER_PRIORITY_LABEL}
</AppText>
<Pressable
style={[styles.priorityRow, mainScannerPriority === 'camera' && styles.priorityRowSelected]}
onPress={handleSelectCameraPriority}
disabled={isLoading || !isDbReady}
>
<AppText style={styles.priorityRowText}>{SCANNER_PRIORITY_CAMERA}</AppText>
{mainScannerPriority === 'camera' ? <AppText style={styles.priorityCheck}></AppText> : null}
</Pressable>
<Pressable
style={[styles.priorityRow, mainScannerPriority === 'hardware' && styles.priorityRowSelected]}
onPress={handleSelectHardwarePriority}
disabled={isLoading || !isDbReady}
>
<AppText style={styles.priorityRowText}>{SCANNER_PRIORITY_HARDWARE}</AppText>
{mainScannerPriority === 'hardware' ? <AppText style={styles.priorityCheck}></AppText> : null}
</Pressable>
</View>
) : null}
</View>
) : null}
<View style={styles.section}>
<AppText style={styles.sectionTitle} variant="h3" weight="semibold">
Системные настройки
</AppText>
<AppText style={styles.fieldLabel} variant="caption" weight="medium">
Максимальное время простоя (минут)
</AppText>
<Pressable style={getIdleTimeoutFieldPressableStyle} onPress={handleOpenIdleTimeoutDialog} disabled={isLoading || !isDbReady}>
<AppText style={[styles.serverUrlText, !idleTimeout && styles.serverUrlPlaceholder]} numberOfLines={1}>
{idleTimeout || 'Не задано'}
</AppText>
</Pressable>
<AppText style={[styles.fieldLabel, styles.fieldLabelMarginTop]} variant="caption" weight="medium">
Максимальное время ожидания ответа от сервера (секунд)
</AppText>
<Pressable
style={getServerRequestTimeoutFieldPressableStyle}
onPress={handleOpenServerRequestTimeoutDialog}
disabled={isLoading || !isDbReady}
>
<AppText style={styles.serverUrlText} numberOfLines={1}>
{serverRequestTimeout || String(DEFAULT_SERVER_REQUEST_TIMEOUT)}
</AppText>
</Pressable>
<AppText style={[styles.fieldLabel, styles.fieldLabelMarginTop]} variant="caption" weight="medium">
Идентификатор устройства
</AppText>
<View style={styles.serverUrlRow}>
<View style={[styles.serverUrlField, styles.deviceIdField]}>
<AppText style={styles.serverUrlText} numberOfLines={1}>
{deviceId || 'Загрузка...'}
</AppText>
</View>
{deviceId ? (
<CopyButton value={deviceId} onCopy={handleCopyDeviceId} onError={handleCopyError} style={styles.serverUrlCopyButton} />
) : null}
</View>
</View>
<View style={styles.section}>
<AppText style={styles.sectionTitle} variant="h3" weight="semibold">
Управление данными
</AppText>
<AppButton
title="Очистить кэш"
onPress={handleClearCache}
disabled={!isDbReady}
style={[styles.actionButton, styles.clearCacheButton]}
textStyle={styles.clearCacheButtonText}
/>
<AppButton
title="Сбросить настройки"
onPress={handleResetSettings}
disabled={!isDbReady}
style={[styles.actionButton, styles.resetButton]}
textStyle={styles.resetButtonText}
/>
<AppButton
title="Оптимизировать БД"
onPress={handleOptimizeDb}
disabled={isLoading || !isDbReady}
style={[styles.actionButton, styles.optimizeButton]}
textStyle={styles.optimizeButtonText}
/>
</View>
<View style={styles.section}>
<AppText style={styles.sectionTitle} variant="h3" weight="semibold">
Информация
</AppText>
<AppButton title={MENU_ITEM_ABOUT} onPress={handleShowAppInfo} style={[styles.actionButton, styles.infoButton]} />
<View style={styles.infoRow}>
<AppText style={styles.infoLabel} variant="body">
Текущий режим:
</AppText>
<AppText style={styles.infoValue} variant="body" weight="medium">
{getModeLabel(mode)}
</AppText>
</View>
<View style={styles.infoRow}>
<AppText style={styles.infoLabel} variant="body">
Адрес сервера:
</AppText>
<View style={styles.infoValueWithAction}>
<AppText style={styles.infoValueFlex} variant="body" weight="medium" numberOfLines={1}>
{serverUrl || 'Не настроен'}
</AppText>
{serverUrl ? (
<CopyButton value={serverUrl} onCopy={handleCopyServerUrl} onError={handleCopyError} style={styles.copyButton} />
) : null}
</View>
</View>
<View style={styles.infoRow}>
<AppText style={styles.infoLabel} variant="body">
База данных:
</AppText>
<AppText style={styles.infoValue} variant="body" weight="medium">
{isDbReady ? 'Готова' : 'Загрузка...'}
</AppText>
</View>
</View>
</ScrollView>
<InputDialog
ref={serverUrlDialogRef}
visible={isServerUrlDialogVisible}
title="Адрес сервера"
label="URL сервера приложений"
value={serverUrl}
placeholder="https://example.com/api"
keyboardType="url"
confirmText="Сохранить"
cancelText="Отмена"
onConfirm={handleSaveServerUrl}
onCancel={handleCloseServerUrlDialog}
validator={validateServerUrlAllowEmpty}
/>
<InputDialog
ref={idleTimeoutDialogRef}
visible={isIdleTimeoutDialogVisible}
title="Время простоя"
label="Максимальное время простоя (минут)"
value={idleTimeout}
placeholder="Например: 30 или пусто"
keyboardType="numeric"
confirmText="Сохранить"
cancelText="Отмена"
onConfirm={handleSaveIdleTimeout}
onCancel={handleCloseIdleTimeoutDialog}
validator={validateIdleTimeoutAllowEmpty}
/>
<InputDialog
ref={serverRequestTimeoutDialogRef}
visible={isServerRequestTimeoutDialogVisible}
title="Время ожидания сервера"
label="Максимальное время ожидания ответа от сервера (секунд)"
value={serverRequestTimeout}
placeholder="Например: 60"
keyboardType="numeric"
confirmText="Сохранить"
cancelText="Отмена"
onConfirm={handleSaveServerRequestTimeout}
onCancel={handleCloseServerRequestTimeoutDialog}
validator={validateServerRequestTimeout}
/>
</AdaptiveView>
);
}
//----------------
//Интерфейс модуля
//----------------
module.exports = SettingsScreen;