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

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;
}
}
@@ -3,6 +3,8 @@ import '../../domain/entities/server_config.dart';
part 'settings_state.freezed.dart';
enum ConnectionStatus { unknown, checking, connected, error }
@freezed
class SettingsState with _$SettingsState {
const factory SettingsState.initial() = SettingsInitial;
@@ -12,6 +14,7 @@ class SettingsState with _$SettingsState {
required ServerConfig? serverConfig,
required String languageCode,
String? error,
@Default(ConnectionStatus.unknown) ConnectionStatus connectionStatus,
}) = SettingsLoaded;
const factory SettingsState.error(String message) = SettingsError;
}
@@ -20,8 +20,12 @@ mixin _$SettingsState {
TResult when<TResult extends Object?>({
required TResult Function() initial,
required TResult Function() loading,
required TResult Function(String apiUrl, ServerConfig? serverConfig,
String languageCode, String? error)
required TResult Function(
String apiUrl,
ServerConfig? serverConfig,
String languageCode,
String? error,
ConnectionStatus connectionStatus)
loaded,
required TResult Function(String message) error,
}) =>
@@ -30,8 +34,12 @@ mixin _$SettingsState {
TResult? whenOrNull<TResult extends Object?>({
TResult? Function()? initial,
TResult? Function()? loading,
TResult? Function(String apiUrl, ServerConfig? serverConfig,
String languageCode, String? error)?
TResult? Function(
String apiUrl,
ServerConfig? serverConfig,
String languageCode,
String? error,
ConnectionStatus connectionStatus)?
loaded,
TResult? Function(String message)? error,
}) =>
@@ -40,8 +48,12 @@ mixin _$SettingsState {
TResult maybeWhen<TResult extends Object?>({
TResult Function()? initial,
TResult Function()? loading,
TResult Function(String apiUrl, ServerConfig? serverConfig,
String languageCode, String? error)?
TResult Function(
String apiUrl,
ServerConfig? serverConfig,
String languageCode,
String? error,
ConnectionStatus connectionStatus)?
loaded,
TResult Function(String message)? error,
required TResult orElse(),
@@ -132,8 +144,12 @@ class _$SettingsInitialImpl implements SettingsInitial {
TResult when<TResult extends Object?>({
required TResult Function() initial,
required TResult Function() loading,
required TResult Function(String apiUrl, ServerConfig? serverConfig,
String languageCode, String? error)
required TResult Function(
String apiUrl,
ServerConfig? serverConfig,
String languageCode,
String? error,
ConnectionStatus connectionStatus)
loaded,
required TResult Function(String message) error,
}) {
@@ -145,8 +161,12 @@ class _$SettingsInitialImpl implements SettingsInitial {
TResult? whenOrNull<TResult extends Object?>({
TResult? Function()? initial,
TResult? Function()? loading,
TResult? Function(String apiUrl, ServerConfig? serverConfig,
String languageCode, String? error)?
TResult? Function(
String apiUrl,
ServerConfig? serverConfig,
String languageCode,
String? error,
ConnectionStatus connectionStatus)?
loaded,
TResult? Function(String message)? error,
}) {
@@ -158,8 +178,12 @@ class _$SettingsInitialImpl implements SettingsInitial {
TResult maybeWhen<TResult extends Object?>({
TResult Function()? initial,
TResult Function()? loading,
TResult Function(String apiUrl, ServerConfig? serverConfig,
String languageCode, String? error)?
TResult Function(
String apiUrl,
ServerConfig? serverConfig,
String languageCode,
String? error,
ConnectionStatus connectionStatus)?
loaded,
TResult Function(String message)? error,
required TResult orElse(),
@@ -252,8 +276,12 @@ class _$SettingsLoadingImpl implements SettingsLoading {
TResult when<TResult extends Object?>({
required TResult Function() initial,
required TResult Function() loading,
required TResult Function(String apiUrl, ServerConfig? serverConfig,
String languageCode, String? error)
required TResult Function(
String apiUrl,
ServerConfig? serverConfig,
String languageCode,
String? error,
ConnectionStatus connectionStatus)
loaded,
required TResult Function(String message) error,
}) {
@@ -265,8 +293,12 @@ class _$SettingsLoadingImpl implements SettingsLoading {
TResult? whenOrNull<TResult extends Object?>({
TResult? Function()? initial,
TResult? Function()? loading,
TResult? Function(String apiUrl, ServerConfig? serverConfig,
String languageCode, String? error)?
TResult? Function(
String apiUrl,
ServerConfig? serverConfig,
String languageCode,
String? error,
ConnectionStatus connectionStatus)?
loaded,
TResult? Function(String message)? error,
}) {
@@ -278,8 +310,12 @@ class _$SettingsLoadingImpl implements SettingsLoading {
TResult maybeWhen<TResult extends Object?>({
TResult Function()? initial,
TResult Function()? loading,
TResult Function(String apiUrl, ServerConfig? serverConfig,
String languageCode, String? error)?
TResult Function(
String apiUrl,
ServerConfig? serverConfig,
String languageCode,
String? error,
ConnectionStatus connectionStatus)?
loaded,
TResult Function(String message)? error,
required TResult orElse(),
@@ -342,7 +378,8 @@ abstract class _$$SettingsLoadedImplCopyWith<$Res> {
{String apiUrl,
ServerConfig? serverConfig,
String languageCode,
String? error});
String? error,
ConnectionStatus connectionStatus});
$ServerConfigCopyWith<$Res>? get serverConfig;
}
@@ -362,6 +399,7 @@ class __$$SettingsLoadedImplCopyWithImpl<$Res>
Object? serverConfig = freezed,
Object? languageCode = null,
Object? error = freezed,
Object? connectionStatus = null,
}) {
return _then(_$SettingsLoadedImpl(
apiUrl: null == apiUrl
@@ -380,6 +418,10 @@ class __$$SettingsLoadedImplCopyWithImpl<$Res>
? _value.error
: error // ignore: cast_nullable_to_non_nullable
as String?,
connectionStatus: null == connectionStatus
? _value.connectionStatus
: connectionStatus // ignore: cast_nullable_to_non_nullable
as ConnectionStatus,
));
}
@@ -403,7 +445,8 @@ class _$SettingsLoadedImpl implements SettingsLoaded {
{required this.apiUrl,
required this.serverConfig,
required this.languageCode,
this.error});
this.error,
this.connectionStatus = ConnectionStatus.unknown});
@override
final String apiUrl;
@@ -413,10 +456,13 @@ class _$SettingsLoadedImpl implements SettingsLoaded {
final String languageCode;
@override
final String? error;
@override
@JsonKey()
final ConnectionStatus connectionStatus;
@override
String toString() {
return 'SettingsState.loaded(apiUrl: $apiUrl, serverConfig: $serverConfig, languageCode: $languageCode, error: $error)';
return 'SettingsState.loaded(apiUrl: $apiUrl, serverConfig: $serverConfig, languageCode: $languageCode, error: $error, connectionStatus: $connectionStatus)';
}
@override
@@ -429,12 +475,14 @@ class _$SettingsLoadedImpl implements SettingsLoaded {
other.serverConfig == serverConfig) &&
(identical(other.languageCode, languageCode) ||
other.languageCode == languageCode) &&
(identical(other.error, error) || other.error == error));
(identical(other.error, error) || other.error == error) &&
(identical(other.connectionStatus, connectionStatus) ||
other.connectionStatus == connectionStatus));
}
@override
int get hashCode =>
Object.hash(runtimeType, apiUrl, serverConfig, languageCode, error);
int get hashCode => Object.hash(
runtimeType, apiUrl, serverConfig, languageCode, error, connectionStatus);
@JsonKey(ignore: true)
@override
@@ -448,12 +496,17 @@ class _$SettingsLoadedImpl implements SettingsLoaded {
TResult when<TResult extends Object?>({
required TResult Function() initial,
required TResult Function() loading,
required TResult Function(String apiUrl, ServerConfig? serverConfig,
String languageCode, String? error)
required TResult Function(
String apiUrl,
ServerConfig? serverConfig,
String languageCode,
String? error,
ConnectionStatus connectionStatus)
loaded,
required TResult Function(String message) error,
}) {
return loaded(apiUrl, serverConfig, languageCode, this.error);
return loaded(
apiUrl, serverConfig, languageCode, this.error, connectionStatus);
}
@override
@@ -461,12 +514,17 @@ class _$SettingsLoadedImpl implements SettingsLoaded {
TResult? whenOrNull<TResult extends Object?>({
TResult? Function()? initial,
TResult? Function()? loading,
TResult? Function(String apiUrl, ServerConfig? serverConfig,
String languageCode, String? error)?
TResult? Function(
String apiUrl,
ServerConfig? serverConfig,
String languageCode,
String? error,
ConnectionStatus connectionStatus)?
loaded,
TResult? Function(String message)? error,
}) {
return loaded?.call(apiUrl, serverConfig, languageCode, this.error);
return loaded?.call(
apiUrl, serverConfig, languageCode, this.error, connectionStatus);
}
@override
@@ -474,14 +532,19 @@ class _$SettingsLoadedImpl implements SettingsLoaded {
TResult maybeWhen<TResult extends Object?>({
TResult Function()? initial,
TResult Function()? loading,
TResult Function(String apiUrl, ServerConfig? serverConfig,
String languageCode, String? error)?
TResult Function(
String apiUrl,
ServerConfig? serverConfig,
String languageCode,
String? error,
ConnectionStatus connectionStatus)?
loaded,
TResult Function(String message)? error,
required TResult orElse(),
}) {
if (loaded != null) {
return loaded(apiUrl, serverConfig, languageCode, this.error);
return loaded(
apiUrl, serverConfig, languageCode, this.error, connectionStatus);
}
return orElse();
}
@@ -529,12 +592,14 @@ abstract class SettingsLoaded implements SettingsState {
{required final String apiUrl,
required final ServerConfig? serverConfig,
required final String languageCode,
final String? error}) = _$SettingsLoadedImpl;
final String? error,
final ConnectionStatus connectionStatus}) = _$SettingsLoadedImpl;
String get apiUrl;
ServerConfig? get serverConfig;
String get languageCode;
String? get error;
ConnectionStatus get connectionStatus;
@JsonKey(ignore: true)
_$$SettingsLoadedImplCopyWith<_$SettingsLoadedImpl> get copyWith =>
throw _privateConstructorUsedError;
@@ -606,8 +671,12 @@ class _$SettingsErrorImpl implements SettingsError {
TResult when<TResult extends Object?>({
required TResult Function() initial,
required TResult Function() loading,
required TResult Function(String apiUrl, ServerConfig? serverConfig,
String languageCode, String? error)
required TResult Function(
String apiUrl,
ServerConfig? serverConfig,
String languageCode,
String? error,
ConnectionStatus connectionStatus)
loaded,
required TResult Function(String message) error,
}) {
@@ -619,8 +688,12 @@ class _$SettingsErrorImpl implements SettingsError {
TResult? whenOrNull<TResult extends Object?>({
TResult? Function()? initial,
TResult? Function()? loading,
TResult? Function(String apiUrl, ServerConfig? serverConfig,
String languageCode, String? error)?
TResult? Function(
String apiUrl,
ServerConfig? serverConfig,
String languageCode,
String? error,
ConnectionStatus connectionStatus)?
loaded,
TResult? Function(String message)? error,
}) {
@@ -632,8 +705,12 @@ class _$SettingsErrorImpl implements SettingsError {
TResult maybeWhen<TResult extends Object?>({
TResult Function()? initial,
TResult Function()? loading,
TResult Function(String apiUrl, ServerConfig? serverConfig,
String languageCode, String? error)?
TResult Function(
String apiUrl,
ServerConfig? serverConfig,
String languageCode,
String? error,
ConnectionStatus connectionStatus)?
loaded,
TResult Function(String message)? error,
required TResult orElse(),