/* Предрейсовые осмотры - мобильное приложение Экран настроек приложения */ //--------------------- //Подключение библиотек //--------------------- 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 ( {isAuthenticated && session ? ( Информация о подключении Имя пользователя: {session.userName || session.userCode || 'Не указано'} Организация: {session.companyName || 'Не указана'} ) : null} Сервер приложений URL сервера {serverUrl || 'Нажмите для ввода адреса'} {serverUrl ? ( ) : null} {mode === APP_MODE.ONLINE || mode === APP_MODE.OFFLINE ? ( Главный экран {alwaysShowScanner ? ( {SCANNER_PRIORITY_LABEL} {SCANNER_PRIORITY_CAMERA} {mainScannerPriority === 'camera' ? : null} {SCANNER_PRIORITY_HARDWARE} {mainScannerPriority === 'hardware' ? : null} ) : null} ) : null} Системные настройки Максимальное время простоя (минут) {idleTimeout || 'Не задано'} Максимальное время ожидания ответа от сервера (секунд) {serverRequestTimeout || String(DEFAULT_SERVER_REQUEST_TIMEOUT)} Идентификатор устройства {deviceId || 'Загрузка...'} {deviceId ? ( ) : null} Управление данными Информация Текущий режим: {getModeLabel(mode)} Адрес сервера: {serverUrl || 'Не настроен'} {serverUrl ? ( ) : null} База данных: {isDbReady ? 'Готова' : 'Загрузка...'} ); } //---------------- //Интерфейс модуля //---------------- module.exports = SettingsScreen;