Настройки, авторизация

This commit is contained in:
Халимов Рустам
2026-05-14 12:06:34 +03:00
parent e8161d23d4
commit ccffc5a53c
41 changed files with 2556 additions and 1375 deletions
@@ -1,4 +1,7 @@
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';
@@ -17,54 +20,64 @@ class SettingsBloc extends Bloc<SettingsEvent, SettingsState> {
Future<void> _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();
configResult.when(
onSuccess: (config) {
emit(SettingsState.loaded(
apiUrl: apiUrl,
serverConfig: config,
languageCode: languageCode,
));
},
onFailure: (error) {
emit(SettingsState.loaded(
apiUrl: apiUrl,
serverConfig: null,
languageCode: languageCode,
error: 'Failed to load server config',
));
},
);
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<void> _onConfigRequested(SettingsConfigRequested event, emit) async {
if (state is! SettingsLoaded) return;
final currentState = state as SettingsLoaded;
emit(const SettingsState.loading());
final configResult = await _repository.getServerConfig();
configResult.when(
onSuccess: (config) {
emit(SettingsState.loaded(
apiUrl: currentState.apiUrl,
serverConfig: config,
languageCode: currentState.languageCode,
));
},
onFailure: (error) {
emit(SettingsState.loaded(
apiUrl: currentState.apiUrl,
serverConfig: null,
languageCode: currentState.languageCode,
error: 'Failed to refresh config',
));
},
);
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) {
@@ -74,6 +87,7 @@ class SettingsBloc extends Bloc<SettingsEvent, SettingsState> {
apiUrl: event.url,
serverConfig: currentState.serverConfig,
languageCode: currentState.languageCode,
connectionStatus: ConnectionStatus.unknown,
));
}
@@ -81,25 +95,64 @@ class SettingsBloc extends Bloc<SettingsEvent, SettingsState> {
if (state is! SettingsLoaded) return;
final currentState = state as SettingsLoaded;
await _repository.saveApiUrl(event.url);
// Нормализуем URL: убираем дублирование /api и другие проблемные суффиксы
final normalizedUrl = _normalizeApiUrl(event.url);
await _repository.saveApiUrl(normalizedUrl);
// Обновляем baseUrl у Dio, чтобы последующие запросы шли на новый адрес
final dio = GetIt.instance<Dio>();
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<void> _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,
));
}
@@ -107,4 +160,33 @@ class SettingsBloc extends Bloc<SettingsEvent, SettingsState> {
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;
}
}