import 'package:dio/dio.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:get_it/get_it.dart'; import '../../../../core/errors/errors.dart'; import '../../domain/repositories/settings_repository.dart'; import 'settings_event.dart'; import 'settings_state.dart'; class SettingsBloc extends Bloc { final SettingsRepository _repository; SettingsBloc(this._repository) : super(const SettingsState.initial()) { on(_onStarted); on(_onConfigRequested); on(_onApiUrlChanged); on(_onApiUrlSaved); on(_onLanguageChanged); on(_onRefreshConfig); } Future _onStarted(SettingsStarted event, emit) async { emit(const SettingsState.loading()); final apiUrl = await _repository.getApiUrl() ?? ''; final languageCode = await _repository.getLanguageCode() ?? 'ru'; if (apiUrl.isEmpty) { emit(SettingsState.loaded( apiUrl: '', serverConfig: null, languageCode: languageCode, )); return; } final configResult = await _repository.getServerConfig(); if (configResult.isSuccess) { emit(SettingsState.loaded( apiUrl: apiUrl, serverConfig: configResult.data, languageCode: languageCode, connectionStatus: ConnectionStatus.connected, )); } else { emit(SettingsState.loaded( apiUrl: apiUrl, serverConfig: null, languageCode: languageCode, connectionStatus: ConnectionStatus.error, error: _errorMessage(configResult.error!), )); } } Future _onConfigRequested(SettingsConfigRequested event, emit) async { if (state is! SettingsLoaded) return; final currentState = state as SettingsLoaded; if (currentState.apiUrl.isEmpty) return; emit(currentState.copyWith(connectionStatus: ConnectionStatus.checking)); final configResult = await _repository.refreshServerConfig(); if (configResult.isSuccess) { emit(SettingsState.loaded( apiUrl: currentState.apiUrl, serverConfig: configResult.data, languageCode: currentState.languageCode, connectionStatus: ConnectionStatus.connected, )); } else { emit(SettingsState.loaded( apiUrl: currentState.apiUrl, serverConfig: null, languageCode: currentState.languageCode, connectionStatus: ConnectionStatus.error, error: _errorMessage(configResult.error!), )); } } void _onApiUrlChanged(SettingsApiUrlChanged event, emit) { if (state is! SettingsLoaded) return; final currentState = state as SettingsLoaded; emit(SettingsState.loaded( apiUrl: event.url, serverConfig: currentState.serverConfig, languageCode: currentState.languageCode, connectionStatus: ConnectionStatus.unknown, )); } Future _onApiUrlSaved(SettingsApiUrlSaved event, emit) async { if (state is! SettingsLoaded) return; final currentState = state as SettingsLoaded; // Нормализуем URL: убираем дублирование /api и другие проблемные суффиксы final normalizedUrl = _normalizeApiUrl(event.url); await _repository.saveApiUrl(normalizedUrl); // Обновляем baseUrl у Dio, чтобы последующие запросы шли на новый адрес final dio = GetIt.instance(); dio.options.baseUrl = normalizedUrl; if (event.url.isEmpty) { emit(SettingsState.loaded( apiUrl: '', serverConfig: null, languageCode: currentState.languageCode, connectionStatus: ConnectionStatus.unknown, )); return; } // Проверяем подключение к новому адресу emit(SettingsState.loaded( apiUrl: event.url, serverConfig: currentState.serverConfig, languageCode: currentState.languageCode, connectionStatus: ConnectionStatus.checking, )); final configResult = await _repository.refreshServerConfig(); if (configResult.isSuccess) { emit(SettingsState.loaded( apiUrl: event.url, serverConfig: configResult.data, languageCode: currentState.languageCode, connectionStatus: ConnectionStatus.connected, )); } else { emit(SettingsState.loaded( apiUrl: event.url, serverConfig: null, languageCode: currentState.languageCode, connectionStatus: ConnectionStatus.error, error: _errorMessage(configResult.error!), )); } } Future _onLanguageChanged(SettingsLanguageChanged event, emit) async { if (state is! SettingsLoaded) return; final currentState = state as SettingsLoaded; await _repository.saveLanguageCode(event.code); emit(SettingsState.loaded( apiUrl: currentState.apiUrl, serverConfig: currentState.serverConfig, languageCode: event.code, connectionStatus: currentState.connectionStatus, )); } Future _onRefreshConfig(SettingsRefreshConfig event, emit) async { add(const SettingsConfigRequested()); await Future.delayed(const Duration(milliseconds: 100)); } String _errorMessage(AppError error) => error.when( unknown: (msg) => msg?.contains('parsing') == true ? 'parsingError' : 'unknownError', network: (msg) => 'networkError', unauthorized: (msg) => 'unauthorized', forbidden: (msg) => 'forbidden', notFound: (msg) => 'notFound', server: (code, msg) => 'serverError', database: (msg) => 'databaseError', validation: (message, errors) => 'validationError', parsing: (msg) => 'parsingError', ); String _normalizeApiUrl(String url) { if (url.isEmpty) return url; // Убираем все слэши в конце String normalized = url.replaceAll(RegExp(r'/+$'), ''); // Если URL заканчивается на /api, убираем этот суффикс // (так как мы всегда добавляем /api к путям при запросе) if (normalized.endsWith('/api')) { normalized = normalized.substring(0, normalized.length - 4); normalized = normalized.replaceAll(RegExp(r'/+$'), ''); } return normalized; } }