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

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
@@ -15,6 +15,7 @@ class SettingsRemoteDataSourceImpl implements SettingsRemoteDataSource {
@override
Future<Result<ServerConfig>> getServerConfig() async {
try {
// Backend ожидает /api/config
final response = await _dio.get('/api/config');
if (response.statusCode == 200) {
final config = ServerConfig.fromJson(response.data as Map<String, dynamic>);
@@ -23,8 +24,10 @@ class SettingsRemoteDataSourceImpl implements SettingsRemoteDataSource {
return Result.failure(AppError.server(statusCode: 500, message: 'Failed to load config'));
} on DioException catch (e) {
return Result.failure(AppError.network(message: e.message));
} on TypeError catch (e) {
return Result.failure(AppError.unknown(message: 'Data parsing error: ${e.toString()}'));
} catch (e) {
return Result.failure(const AppError.unknown(message: 'Unknown error'));
return Result.failure(AppError.unknown(message: e.toString()));
}
}
}
@@ -20,11 +20,13 @@ class ServerConfig with _$ServerConfig {
_$ServerConfigFromJson(json);
}
@Freezed(toJson: true)
@freezed
class SystemConfig with _$SystemConfig {
const factory SystemConfig({
required String domainUrl,
required bool enableRegistration,
@Default('UTC') String serverTimezone,
@Default('admin') String adminRoute,
}) = _SystemConfig;
factory SystemConfig.fromJson(Map<String, dynamic> json) =>
@@ -54,6 +56,7 @@ class ChatsModuleConfig with _$ChatsModuleConfig {
required int maxGroupParticipants,
required bool allowChatToGroupConversion,
required bool enableFolders,
@Default(false) bool enableAutoClean,
}) = _ChatsModuleConfig;
factory ChatsModuleConfig.fromJson(Map<String, dynamic> json) =>
@@ -78,6 +81,7 @@ class MessagesConfig with _$MessagesConfig {
required bool allowLinks,
required bool allowPolls,
required bool allowPinning,
@Default(0) int maxFileSize,
}) = _MessagesConfig;
factory MessagesConfig.fromJson(Map<String, dynamic> json) =>
@@ -93,6 +97,8 @@ class WebRtcConfig with _$WebRtcConfig {
required bool enableScreenSharing,
required String turnHost,
required int turnPort,
@Default('') String turnUser,
@Default('') String turnSecret,
}) = _WebRtcConfig;
factory WebRtcConfig.fromJson(Map<String, dynamic> json) =>
@@ -104,6 +110,7 @@ class KlipyConfig with _$KlipyConfig {
const factory KlipyConfig({
required bool enabled,
required String appName,
@Default('') String apiKey,
}) = _KlipyConfig;
factory KlipyConfig.fromJson(Map<String, dynamic> json) =>
@@ -113,7 +120,7 @@ class KlipyConfig with _$KlipyConfig {
@Freezed(toJson: true)
class ImportConfig with _$ImportConfig {
const factory ImportConfig({
required bool enabled,
@Default(false) bool enableTelegramImport,
}) = _ImportConfig;
factory ImportConfig.fromJson(Map<String, dynamic> json) =>
@@ -395,6 +395,8 @@ SystemConfig _$SystemConfigFromJson(Map<String, dynamic> json) {
mixin _$SystemConfig {
String get domainUrl => throw _privateConstructorUsedError;
bool get enableRegistration => throw _privateConstructorUsedError;
String get serverTimezone => throw _privateConstructorUsedError;
String get adminRoute => throw _privateConstructorUsedError;
Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
@JsonKey(ignore: true)
@@ -408,7 +410,11 @@ abstract class $SystemConfigCopyWith<$Res> {
SystemConfig value, $Res Function(SystemConfig) then) =
_$SystemConfigCopyWithImpl<$Res, SystemConfig>;
@useResult
$Res call({String domainUrl, bool enableRegistration});
$Res call(
{String domainUrl,
bool enableRegistration,
String serverTimezone,
String adminRoute});
}
/// @nodoc
@@ -426,6 +432,8 @@ class _$SystemConfigCopyWithImpl<$Res, $Val extends SystemConfig>
$Res call({
Object? domainUrl = null,
Object? enableRegistration = null,
Object? serverTimezone = null,
Object? adminRoute = null,
}) {
return _then(_value.copyWith(
domainUrl: null == domainUrl
@@ -436,6 +444,14 @@ class _$SystemConfigCopyWithImpl<$Res, $Val extends SystemConfig>
? _value.enableRegistration
: enableRegistration // ignore: cast_nullable_to_non_nullable
as bool,
serverTimezone: null == serverTimezone
? _value.serverTimezone
: serverTimezone // ignore: cast_nullable_to_non_nullable
as String,
adminRoute: null == adminRoute
? _value.adminRoute
: adminRoute // ignore: cast_nullable_to_non_nullable
as String,
) as $Val);
}
}
@@ -448,7 +464,11 @@ abstract class _$$SystemConfigImplCopyWith<$Res>
__$$SystemConfigImplCopyWithImpl<$Res>;
@override
@useResult
$Res call({String domainUrl, bool enableRegistration});
$Res call(
{String domainUrl,
bool enableRegistration,
String serverTimezone,
String adminRoute});
}
/// @nodoc
@@ -464,6 +484,8 @@ class __$$SystemConfigImplCopyWithImpl<$Res>
$Res call({
Object? domainUrl = null,
Object? enableRegistration = null,
Object? serverTimezone = null,
Object? adminRoute = null,
}) {
return _then(_$SystemConfigImpl(
domainUrl: null == domainUrl
@@ -474,6 +496,14 @@ class __$$SystemConfigImplCopyWithImpl<$Res>
? _value.enableRegistration
: enableRegistration // ignore: cast_nullable_to_non_nullable
as bool,
serverTimezone: null == serverTimezone
? _value.serverTimezone
: serverTimezone // ignore: cast_nullable_to_non_nullable
as String,
adminRoute: null == adminRoute
? _value.adminRoute
: adminRoute // ignore: cast_nullable_to_non_nullable
as String,
));
}
}
@@ -482,7 +512,10 @@ class __$$SystemConfigImplCopyWithImpl<$Res>
@JsonSerializable()
class _$SystemConfigImpl implements _SystemConfig {
const _$SystemConfigImpl(
{required this.domainUrl, required this.enableRegistration});
{required this.domainUrl,
required this.enableRegistration,
this.serverTimezone = 'UTC',
this.adminRoute = 'admin'});
factory _$SystemConfigImpl.fromJson(Map<String, dynamic> json) =>
_$$SystemConfigImplFromJson(json);
@@ -491,10 +524,16 @@ class _$SystemConfigImpl implements _SystemConfig {
final String domainUrl;
@override
final bool enableRegistration;
@override
@JsonKey()
final String serverTimezone;
@override
@JsonKey()
final String adminRoute;
@override
String toString() {
return 'SystemConfig(domainUrl: $domainUrl, enableRegistration: $enableRegistration)';
return 'SystemConfig(domainUrl: $domainUrl, enableRegistration: $enableRegistration, serverTimezone: $serverTimezone, adminRoute: $adminRoute)';
}
@override
@@ -505,12 +544,17 @@ class _$SystemConfigImpl implements _SystemConfig {
(identical(other.domainUrl, domainUrl) ||
other.domainUrl == domainUrl) &&
(identical(other.enableRegistration, enableRegistration) ||
other.enableRegistration == enableRegistration));
other.enableRegistration == enableRegistration) &&
(identical(other.serverTimezone, serverTimezone) ||
other.serverTimezone == serverTimezone) &&
(identical(other.adminRoute, adminRoute) ||
other.adminRoute == adminRoute));
}
@JsonKey(ignore: true)
@override
int get hashCode => Object.hash(runtimeType, domainUrl, enableRegistration);
int get hashCode => Object.hash(
runtimeType, domainUrl, enableRegistration, serverTimezone, adminRoute);
@JsonKey(ignore: true)
@override
@@ -529,7 +573,9 @@ class _$SystemConfigImpl implements _SystemConfig {
abstract class _SystemConfig implements SystemConfig {
const factory _SystemConfig(
{required final String domainUrl,
required final bool enableRegistration}) = _$SystemConfigImpl;
required final bool enableRegistration,
final String serverTimezone,
final String adminRoute}) = _$SystemConfigImpl;
factory _SystemConfig.fromJson(Map<String, dynamic> json) =
_$SystemConfigImpl.fromJson;
@@ -539,6 +585,10 @@ abstract class _SystemConfig implements SystemConfig {
@override
bool get enableRegistration;
@override
String get serverTimezone;
@override
String get adminRoute;
@override
@JsonKey(ignore: true)
_$$SystemConfigImplCopyWith<_$SystemConfigImpl> get copyWith =>
throw _privateConstructorUsedError;
@@ -831,6 +881,7 @@ mixin _$ChatsModuleConfig {
int get maxGroupParticipants => throw _privateConstructorUsedError;
bool get allowChatToGroupConversion => throw _privateConstructorUsedError;
bool get enableFolders => throw _privateConstructorUsedError;
bool get enableAutoClean => throw _privateConstructorUsedError;
Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
@JsonKey(ignore: true)
@@ -848,7 +899,8 @@ abstract class $ChatsModuleConfigCopyWith<$Res> {
{bool supportGroups,
int maxGroupParticipants,
bool allowChatToGroupConversion,
bool enableFolders});
bool enableFolders,
bool enableAutoClean});
}
/// @nodoc
@@ -868,6 +920,7 @@ class _$ChatsModuleConfigCopyWithImpl<$Res, $Val extends ChatsModuleConfig>
Object? maxGroupParticipants = null,
Object? allowChatToGroupConversion = null,
Object? enableFolders = null,
Object? enableAutoClean = null,
}) {
return _then(_value.copyWith(
supportGroups: null == supportGroups
@@ -886,6 +939,10 @@ class _$ChatsModuleConfigCopyWithImpl<$Res, $Val extends ChatsModuleConfig>
? _value.enableFolders
: enableFolders // ignore: cast_nullable_to_non_nullable
as bool,
enableAutoClean: null == enableAutoClean
? _value.enableAutoClean
: enableAutoClean // ignore: cast_nullable_to_non_nullable
as bool,
) as $Val);
}
}
@@ -902,7 +959,8 @@ abstract class _$$ChatsModuleConfigImplCopyWith<$Res>
{bool supportGroups,
int maxGroupParticipants,
bool allowChatToGroupConversion,
bool enableFolders});
bool enableFolders,
bool enableAutoClean});
}
/// @nodoc
@@ -920,6 +978,7 @@ class __$$ChatsModuleConfigImplCopyWithImpl<$Res>
Object? maxGroupParticipants = null,
Object? allowChatToGroupConversion = null,
Object? enableFolders = null,
Object? enableAutoClean = null,
}) {
return _then(_$ChatsModuleConfigImpl(
supportGroups: null == supportGroups
@@ -938,6 +997,10 @@ class __$$ChatsModuleConfigImplCopyWithImpl<$Res>
? _value.enableFolders
: enableFolders // ignore: cast_nullable_to_non_nullable
as bool,
enableAutoClean: null == enableAutoClean
? _value.enableAutoClean
: enableAutoClean // ignore: cast_nullable_to_non_nullable
as bool,
));
}
}
@@ -949,7 +1012,8 @@ class _$ChatsModuleConfigImpl implements _ChatsModuleConfig {
{required this.supportGroups,
required this.maxGroupParticipants,
required this.allowChatToGroupConversion,
required this.enableFolders});
required this.enableFolders,
this.enableAutoClean = false});
factory _$ChatsModuleConfigImpl.fromJson(Map<String, dynamic> json) =>
_$$ChatsModuleConfigImplFromJson(json);
@@ -962,10 +1026,13 @@ class _$ChatsModuleConfigImpl implements _ChatsModuleConfig {
final bool allowChatToGroupConversion;
@override
final bool enableFolders;
@override
@JsonKey()
final bool enableAutoClean;
@override
String toString() {
return 'ChatsModuleConfig(supportGroups: $supportGroups, maxGroupParticipants: $maxGroupParticipants, allowChatToGroupConversion: $allowChatToGroupConversion, enableFolders: $enableFolders)';
return 'ChatsModuleConfig(supportGroups: $supportGroups, maxGroupParticipants: $maxGroupParticipants, allowChatToGroupConversion: $allowChatToGroupConversion, enableFolders: $enableFolders, enableAutoClean: $enableAutoClean)';
}
@override
@@ -982,13 +1049,20 @@ class _$ChatsModuleConfigImpl implements _ChatsModuleConfig {
other.allowChatToGroupConversion ==
allowChatToGroupConversion) &&
(identical(other.enableFolders, enableFolders) ||
other.enableFolders == enableFolders));
other.enableFolders == enableFolders) &&
(identical(other.enableAutoClean, enableAutoClean) ||
other.enableAutoClean == enableAutoClean));
}
@JsonKey(ignore: true)
@override
int get hashCode => Object.hash(runtimeType, supportGroups,
maxGroupParticipants, allowChatToGroupConversion, enableFolders);
int get hashCode => Object.hash(
runtimeType,
supportGroups,
maxGroupParticipants,
allowChatToGroupConversion,
enableFolders,
enableAutoClean);
@JsonKey(ignore: true)
@override
@@ -1010,7 +1084,8 @@ abstract class _ChatsModuleConfig implements ChatsModuleConfig {
{required final bool supportGroups,
required final int maxGroupParticipants,
required final bool allowChatToGroupConversion,
required final bool enableFolders}) = _$ChatsModuleConfigImpl;
required final bool enableFolders,
final bool enableAutoClean}) = _$ChatsModuleConfigImpl;
factory _ChatsModuleConfig.fromJson(Map<String, dynamic> json) =
_$ChatsModuleConfigImpl.fromJson;
@@ -1024,6 +1099,8 @@ abstract class _ChatsModuleConfig implements ChatsModuleConfig {
@override
bool get enableFolders;
@override
bool get enableAutoClean;
@override
@JsonKey(ignore: true)
_$$ChatsModuleConfigImplCopyWith<_$ChatsModuleConfigImpl> get copyWith =>
throw _privateConstructorUsedError;
@@ -1050,6 +1127,7 @@ mixin _$MessagesConfig {
bool get allowLinks => throw _privateConstructorUsedError;
bool get allowPolls => throw _privateConstructorUsedError;
bool get allowPinning => throw _privateConstructorUsedError;
int get maxFileSize => throw _privateConstructorUsedError;
Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
@JsonKey(ignore: true)
@@ -1078,7 +1156,8 @@ abstract class $MessagesConfigCopyWith<$Res> {
bool forbidCopying,
bool allowLinks,
bool allowPolls,
bool allowPinning});
bool allowPinning,
int maxFileSize});
}
/// @nodoc
@@ -1109,6 +1188,7 @@ class _$MessagesConfigCopyWithImpl<$Res, $Val extends MessagesConfig>
Object? allowLinks = null,
Object? allowPolls = null,
Object? allowPinning = null,
Object? maxFileSize = null,
}) {
return _then(_value.copyWith(
dailyMessageLimitPerUser: null == dailyMessageLimitPerUser
@@ -1171,6 +1251,10 @@ class _$MessagesConfigCopyWithImpl<$Res, $Val extends MessagesConfig>
? _value.allowPinning
: allowPinning // ignore: cast_nullable_to_non_nullable
as bool,
maxFileSize: null == maxFileSize
? _value.maxFileSize
: maxFileSize // ignore: cast_nullable_to_non_nullable
as int,
) as $Val);
}
}
@@ -1198,7 +1282,8 @@ abstract class _$$MessagesConfigImplCopyWith<$Res>
bool forbidCopying,
bool allowLinks,
bool allowPolls,
bool allowPinning});
bool allowPinning,
int maxFileSize});
}
/// @nodoc
@@ -1227,6 +1312,7 @@ class __$$MessagesConfigImplCopyWithImpl<$Res>
Object? allowLinks = null,
Object? allowPolls = null,
Object? allowPinning = null,
Object? maxFileSize = null,
}) {
return _then(_$MessagesConfigImpl(
dailyMessageLimitPerUser: null == dailyMessageLimitPerUser
@@ -1289,6 +1375,10 @@ class __$$MessagesConfigImplCopyWithImpl<$Res>
? _value.allowPinning
: allowPinning // ignore: cast_nullable_to_non_nullable
as bool,
maxFileSize: null == maxFileSize
? _value.maxFileSize
: maxFileSize // ignore: cast_nullable_to_non_nullable
as int,
));
}
}
@@ -1311,7 +1401,8 @@ class _$MessagesConfigImpl implements _MessagesConfig {
required this.forbidCopying,
required this.allowLinks,
required this.allowPolls,
required this.allowPinning})
required this.allowPinning,
this.maxFileSize = 0})
: _allowedMediaTypes = allowedMediaTypes;
factory _$MessagesConfigImpl.fromJson(Map<String, dynamic> json) =>
@@ -1354,10 +1445,13 @@ class _$MessagesConfigImpl implements _MessagesConfig {
final bool allowPolls;
@override
final bool allowPinning;
@override
@JsonKey()
final int maxFileSize;
@override
String toString() {
return 'MessagesConfig(dailyMessageLimitPerUser: $dailyMessageLimitPerUser, chatMessageLimit: $chatMessageLimit, allowMedia: $allowMedia, maxMediaSizeBytes: $maxMediaSizeBytes, allowedMediaTypes: $allowedMediaTypes, allowVoiceMessages: $allowVoiceMessages, allowForwarding: $allowForwarding, allowReactions: $allowReactions, allowReplies: $allowReplies, allowQuoting: $allowQuoting, allowMessageDeletion: $allowMessageDeletion, forbidCopying: $forbidCopying, allowLinks: $allowLinks, allowPolls: $allowPolls, allowPinning: $allowPinning)';
return 'MessagesConfig(dailyMessageLimitPerUser: $dailyMessageLimitPerUser, chatMessageLimit: $chatMessageLimit, allowMedia: $allowMedia, maxMediaSizeBytes: $maxMediaSizeBytes, allowedMediaTypes: $allowedMediaTypes, allowVoiceMessages: $allowVoiceMessages, allowForwarding: $allowForwarding, allowReactions: $allowReactions, allowReplies: $allowReplies, allowQuoting: $allowQuoting, allowMessageDeletion: $allowMessageDeletion, forbidCopying: $forbidCopying, allowLinks: $allowLinks, allowPolls: $allowPolls, allowPinning: $allowPinning, maxFileSize: $maxFileSize)';
}
@override
@@ -1395,7 +1489,9 @@ class _$MessagesConfigImpl implements _MessagesConfig {
(identical(other.allowPolls, allowPolls) ||
other.allowPolls == allowPolls) &&
(identical(other.allowPinning, allowPinning) ||
other.allowPinning == allowPinning));
other.allowPinning == allowPinning) &&
(identical(other.maxFileSize, maxFileSize) ||
other.maxFileSize == maxFileSize));
}
@JsonKey(ignore: true)
@@ -1416,7 +1512,8 @@ class _$MessagesConfigImpl implements _MessagesConfig {
forbidCopying,
allowLinks,
allowPolls,
allowPinning);
allowPinning,
maxFileSize);
@JsonKey(ignore: true)
@override
@@ -1449,7 +1546,8 @@ abstract class _MessagesConfig implements MessagesConfig {
required final bool forbidCopying,
required final bool allowLinks,
required final bool allowPolls,
required final bool allowPinning}) = _$MessagesConfigImpl;
required final bool allowPinning,
final int maxFileSize}) = _$MessagesConfigImpl;
factory _MessagesConfig.fromJson(Map<String, dynamic> json) =
_$MessagesConfigImpl.fromJson;
@@ -1485,6 +1583,8 @@ abstract class _MessagesConfig implements MessagesConfig {
@override
bool get allowPinning;
@override
int get maxFileSize;
@override
@JsonKey(ignore: true)
_$$MessagesConfigImplCopyWith<_$MessagesConfigImpl> get copyWith =>
throw _privateConstructorUsedError;
@@ -1502,6 +1602,8 @@ mixin _$WebRtcConfig {
bool get enableScreenSharing => throw _privateConstructorUsedError;
String get turnHost => throw _privateConstructorUsedError;
int get turnPort => throw _privateConstructorUsedError;
String get turnUser => throw _privateConstructorUsedError;
String get turnSecret => throw _privateConstructorUsedError;
Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
@JsonKey(ignore: true)
@@ -1521,7 +1623,9 @@ abstract class $WebRtcConfigCopyWith<$Res> {
bool enableVideoCalls,
bool enableScreenSharing,
String turnHost,
int turnPort});
int turnPort,
String turnUser,
String turnSecret});
}
/// @nodoc
@@ -1543,6 +1647,8 @@ class _$WebRtcConfigCopyWithImpl<$Res, $Val extends WebRtcConfig>
Object? enableScreenSharing = null,
Object? turnHost = null,
Object? turnPort = null,
Object? turnUser = null,
Object? turnSecret = null,
}) {
return _then(_value.copyWith(
enabled: null == enabled
@@ -1569,6 +1675,14 @@ class _$WebRtcConfigCopyWithImpl<$Res, $Val extends WebRtcConfig>
? _value.turnPort
: turnPort // ignore: cast_nullable_to_non_nullable
as int,
turnUser: null == turnUser
? _value.turnUser
: turnUser // ignore: cast_nullable_to_non_nullable
as String,
turnSecret: null == turnSecret
? _value.turnSecret
: turnSecret // ignore: cast_nullable_to_non_nullable
as String,
) as $Val);
}
}
@@ -1587,7 +1701,9 @@ abstract class _$$WebRtcConfigImplCopyWith<$Res>
bool enableVideoCalls,
bool enableScreenSharing,
String turnHost,
int turnPort});
int turnPort,
String turnUser,
String turnSecret});
}
/// @nodoc
@@ -1607,6 +1723,8 @@ class __$$WebRtcConfigImplCopyWithImpl<$Res>
Object? enableScreenSharing = null,
Object? turnHost = null,
Object? turnPort = null,
Object? turnUser = null,
Object? turnSecret = null,
}) {
return _then(_$WebRtcConfigImpl(
enabled: null == enabled
@@ -1633,6 +1751,14 @@ class __$$WebRtcConfigImplCopyWithImpl<$Res>
? _value.turnPort
: turnPort // ignore: cast_nullable_to_non_nullable
as int,
turnUser: null == turnUser
? _value.turnUser
: turnUser // ignore: cast_nullable_to_non_nullable
as String,
turnSecret: null == turnSecret
? _value.turnSecret
: turnSecret // ignore: cast_nullable_to_non_nullable
as String,
));
}
}
@@ -1646,7 +1772,9 @@ class _$WebRtcConfigImpl implements _WebRtcConfig {
required this.enableVideoCalls,
required this.enableScreenSharing,
required this.turnHost,
required this.turnPort});
required this.turnPort,
this.turnUser = '',
this.turnSecret = ''});
factory _$WebRtcConfigImpl.fromJson(Map<String, dynamic> json) =>
_$$WebRtcConfigImplFromJson(json);
@@ -1663,10 +1791,16 @@ class _$WebRtcConfigImpl implements _WebRtcConfig {
final String turnHost;
@override
final int turnPort;
@override
@JsonKey()
final String turnUser;
@override
@JsonKey()
final String turnSecret;
@override
String toString() {
return 'WebRtcConfig(enabled: $enabled, enableVoiceCalls: $enableVoiceCalls, enableVideoCalls: $enableVideoCalls, enableScreenSharing: $enableScreenSharing, turnHost: $turnHost, turnPort: $turnPort)';
return 'WebRtcConfig(enabled: $enabled, enableVoiceCalls: $enableVoiceCalls, enableVideoCalls: $enableVideoCalls, enableScreenSharing: $enableScreenSharing, turnHost: $turnHost, turnPort: $turnPort, turnUser: $turnUser, turnSecret: $turnSecret)';
}
@override
@@ -1684,13 +1818,25 @@ class _$WebRtcConfigImpl implements _WebRtcConfig {
(identical(other.turnHost, turnHost) ||
other.turnHost == turnHost) &&
(identical(other.turnPort, turnPort) ||
other.turnPort == turnPort));
other.turnPort == turnPort) &&
(identical(other.turnUser, turnUser) ||
other.turnUser == turnUser) &&
(identical(other.turnSecret, turnSecret) ||
other.turnSecret == turnSecret));
}
@JsonKey(ignore: true)
@override
int get hashCode => Object.hash(runtimeType, enabled, enableVoiceCalls,
enableVideoCalls, enableScreenSharing, turnHost, turnPort);
int get hashCode => Object.hash(
runtimeType,
enabled,
enableVoiceCalls,
enableVideoCalls,
enableScreenSharing,
turnHost,
turnPort,
turnUser,
turnSecret);
@JsonKey(ignore: true)
@override
@@ -1713,7 +1859,9 @@ abstract class _WebRtcConfig implements WebRtcConfig {
required final bool enableVideoCalls,
required final bool enableScreenSharing,
required final String turnHost,
required final int turnPort}) = _$WebRtcConfigImpl;
required final int turnPort,
final String turnUser,
final String turnSecret}) = _$WebRtcConfigImpl;
factory _WebRtcConfig.fromJson(Map<String, dynamic> json) =
_$WebRtcConfigImpl.fromJson;
@@ -1731,6 +1879,10 @@ abstract class _WebRtcConfig implements WebRtcConfig {
@override
int get turnPort;
@override
String get turnUser;
@override
String get turnSecret;
@override
@JsonKey(ignore: true)
_$$WebRtcConfigImplCopyWith<_$WebRtcConfigImpl> get copyWith =>
throw _privateConstructorUsedError;
@@ -1744,6 +1896,7 @@ KlipyConfig _$KlipyConfigFromJson(Map<String, dynamic> json) {
mixin _$KlipyConfig {
bool get enabled => throw _privateConstructorUsedError;
String get appName => throw _privateConstructorUsedError;
String get apiKey => throw _privateConstructorUsedError;
Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
@JsonKey(ignore: true)
@@ -1757,7 +1910,7 @@ abstract class $KlipyConfigCopyWith<$Res> {
KlipyConfig value, $Res Function(KlipyConfig) then) =
_$KlipyConfigCopyWithImpl<$Res, KlipyConfig>;
@useResult
$Res call({bool enabled, String appName});
$Res call({bool enabled, String appName, String apiKey});
}
/// @nodoc
@@ -1775,6 +1928,7 @@ class _$KlipyConfigCopyWithImpl<$Res, $Val extends KlipyConfig>
$Res call({
Object? enabled = null,
Object? appName = null,
Object? apiKey = null,
}) {
return _then(_value.copyWith(
enabled: null == enabled
@@ -1785,6 +1939,10 @@ class _$KlipyConfigCopyWithImpl<$Res, $Val extends KlipyConfig>
? _value.appName
: appName // ignore: cast_nullable_to_non_nullable
as String,
apiKey: null == apiKey
? _value.apiKey
: apiKey // ignore: cast_nullable_to_non_nullable
as String,
) as $Val);
}
}
@@ -1797,7 +1955,7 @@ abstract class _$$KlipyConfigImplCopyWith<$Res>
__$$KlipyConfigImplCopyWithImpl<$Res>;
@override
@useResult
$Res call({bool enabled, String appName});
$Res call({bool enabled, String appName, String apiKey});
}
/// @nodoc
@@ -1813,6 +1971,7 @@ class __$$KlipyConfigImplCopyWithImpl<$Res>
$Res call({
Object? enabled = null,
Object? appName = null,
Object? apiKey = null,
}) {
return _then(_$KlipyConfigImpl(
enabled: null == enabled
@@ -1823,6 +1982,10 @@ class __$$KlipyConfigImplCopyWithImpl<$Res>
? _value.appName
: appName // ignore: cast_nullable_to_non_nullable
as String,
apiKey: null == apiKey
? _value.apiKey
: apiKey // ignore: cast_nullable_to_non_nullable
as String,
));
}
}
@@ -1830,7 +1993,8 @@ class __$$KlipyConfigImplCopyWithImpl<$Res>
/// @nodoc
@JsonSerializable()
class _$KlipyConfigImpl implements _KlipyConfig {
const _$KlipyConfigImpl({required this.enabled, required this.appName});
const _$KlipyConfigImpl(
{required this.enabled, required this.appName, this.apiKey = ''});
factory _$KlipyConfigImpl.fromJson(Map<String, dynamic> json) =>
_$$KlipyConfigImplFromJson(json);
@@ -1839,10 +2003,13 @@ class _$KlipyConfigImpl implements _KlipyConfig {
final bool enabled;
@override
final String appName;
@override
@JsonKey()
final String apiKey;
@override
String toString() {
return 'KlipyConfig(enabled: $enabled, appName: $appName)';
return 'KlipyConfig(enabled: $enabled, appName: $appName, apiKey: $apiKey)';
}
@override
@@ -1851,12 +2018,13 @@ class _$KlipyConfigImpl implements _KlipyConfig {
(other.runtimeType == runtimeType &&
other is _$KlipyConfigImpl &&
(identical(other.enabled, enabled) || other.enabled == enabled) &&
(identical(other.appName, appName) || other.appName == appName));
(identical(other.appName, appName) || other.appName == appName) &&
(identical(other.apiKey, apiKey) || other.apiKey == apiKey));
}
@JsonKey(ignore: true)
@override
int get hashCode => Object.hash(runtimeType, enabled, appName);
int get hashCode => Object.hash(runtimeType, enabled, appName, apiKey);
@JsonKey(ignore: true)
@override
@@ -1875,7 +2043,8 @@ class _$KlipyConfigImpl implements _KlipyConfig {
abstract class _KlipyConfig implements KlipyConfig {
const factory _KlipyConfig(
{required final bool enabled,
required final String appName}) = _$KlipyConfigImpl;
required final String appName,
final String apiKey}) = _$KlipyConfigImpl;
factory _KlipyConfig.fromJson(Map<String, dynamic> json) =
_$KlipyConfigImpl.fromJson;
@@ -1885,6 +2054,8 @@ abstract class _KlipyConfig implements KlipyConfig {
@override
String get appName;
@override
String get apiKey;
@override
@JsonKey(ignore: true)
_$$KlipyConfigImplCopyWith<_$KlipyConfigImpl> get copyWith =>
throw _privateConstructorUsedError;
@@ -1896,7 +2067,7 @@ ImportConfig _$ImportConfigFromJson(Map<String, dynamic> json) {
/// @nodoc
mixin _$ImportConfig {
bool get enabled => throw _privateConstructorUsedError;
bool get enableTelegramImport => throw _privateConstructorUsedError;
Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
@JsonKey(ignore: true)
@@ -1910,7 +2081,7 @@ abstract class $ImportConfigCopyWith<$Res> {
ImportConfig value, $Res Function(ImportConfig) then) =
_$ImportConfigCopyWithImpl<$Res, ImportConfig>;
@useResult
$Res call({bool enabled});
$Res call({bool enableTelegramImport});
}
/// @nodoc
@@ -1926,12 +2097,12 @@ class _$ImportConfigCopyWithImpl<$Res, $Val extends ImportConfig>
@pragma('vm:prefer-inline')
@override
$Res call({
Object? enabled = null,
Object? enableTelegramImport = null,
}) {
return _then(_value.copyWith(
enabled: null == enabled
? _value.enabled
: enabled // ignore: cast_nullable_to_non_nullable
enableTelegramImport: null == enableTelegramImport
? _value.enableTelegramImport
: enableTelegramImport // ignore: cast_nullable_to_non_nullable
as bool,
) as $Val);
}
@@ -1945,7 +2116,7 @@ abstract class _$$ImportConfigImplCopyWith<$Res>
__$$ImportConfigImplCopyWithImpl<$Res>;
@override
@useResult
$Res call({bool enabled});
$Res call({bool enableTelegramImport});
}
/// @nodoc
@@ -1959,12 +2130,12 @@ class __$$ImportConfigImplCopyWithImpl<$Res>
@pragma('vm:prefer-inline')
@override
$Res call({
Object? enabled = null,
Object? enableTelegramImport = null,
}) {
return _then(_$ImportConfigImpl(
enabled: null == enabled
? _value.enabled
: enabled // ignore: cast_nullable_to_non_nullable
enableTelegramImport: null == enableTelegramImport
? _value.enableTelegramImport
: enableTelegramImport // ignore: cast_nullable_to_non_nullable
as bool,
));
}
@@ -1973,17 +2144,18 @@ class __$$ImportConfigImplCopyWithImpl<$Res>
/// @nodoc
@JsonSerializable()
class _$ImportConfigImpl implements _ImportConfig {
const _$ImportConfigImpl({required this.enabled});
const _$ImportConfigImpl({this.enableTelegramImport = false});
factory _$ImportConfigImpl.fromJson(Map<String, dynamic> json) =>
_$$ImportConfigImplFromJson(json);
@override
final bool enabled;
@JsonKey()
final bool enableTelegramImport;
@override
String toString() {
return 'ImportConfig(enabled: $enabled)';
return 'ImportConfig(enableTelegramImport: $enableTelegramImport)';
}
@override
@@ -1991,12 +2163,13 @@ class _$ImportConfigImpl implements _ImportConfig {
return identical(this, other) ||
(other.runtimeType == runtimeType &&
other is _$ImportConfigImpl &&
(identical(other.enabled, enabled) || other.enabled == enabled));
(identical(other.enableTelegramImport, enableTelegramImport) ||
other.enableTelegramImport == enableTelegramImport));
}
@JsonKey(ignore: true)
@override
int get hashCode => Object.hash(runtimeType, enabled);
int get hashCode => Object.hash(runtimeType, enableTelegramImport);
@JsonKey(ignore: true)
@override
@@ -2013,14 +2186,14 @@ class _$ImportConfigImpl implements _ImportConfig {
}
abstract class _ImportConfig implements ImportConfig {
const factory _ImportConfig({required final bool enabled}) =
const factory _ImportConfig({final bool enableTelegramImport}) =
_$ImportConfigImpl;
factory _ImportConfig.fromJson(Map<String, dynamic> json) =
_$ImportConfigImpl.fromJson;
@override
bool get enabled;
bool get enableTelegramImport;
@override
@JsonKey(ignore: true)
_$$ImportConfigImplCopyWith<_$ImportConfigImpl> get copyWith =>
@@ -36,12 +36,16 @@ _$SystemConfigImpl _$$SystemConfigImplFromJson(Map<String, dynamic> json) =>
_$SystemConfigImpl(
domainUrl: json['domainUrl'] as String,
enableRegistration: json['enableRegistration'] as bool,
serverTimezone: json['serverTimezone'] as String? ?? 'UTC',
adminRoute: json['adminRoute'] as String? ?? 'admin',
);
Map<String, dynamic> _$$SystemConfigImplToJson(_$SystemConfigImpl instance) =>
<String, dynamic>{
'domainUrl': instance.domainUrl,
'enableRegistration': instance.enableRegistration,
'serverTimezone': instance.serverTimezone,
'adminRoute': instance.adminRoute,
};
_$StoriesConfigImpl _$$StoriesConfigImplFromJson(Map<String, dynamic> json) =>
@@ -75,6 +79,7 @@ _$ChatsModuleConfigImpl _$$ChatsModuleConfigImplFromJson(
maxGroupParticipants: (json['maxGroupParticipants'] as num).toInt(),
allowChatToGroupConversion: json['allowChatToGroupConversion'] as bool,
enableFolders: json['enableFolders'] as bool,
enableAutoClean: json['enableAutoClean'] as bool? ?? false,
);
Map<String, dynamic> _$$ChatsModuleConfigImplToJson(
@@ -84,6 +89,7 @@ Map<String, dynamic> _$$ChatsModuleConfigImplToJson(
'maxGroupParticipants': instance.maxGroupParticipants,
'allowChatToGroupConversion': instance.allowChatToGroupConversion,
'enableFolders': instance.enableFolders,
'enableAutoClean': instance.enableAutoClean,
};
_$MessagesConfigImpl _$$MessagesConfigImplFromJson(Map<String, dynamic> json) =>
@@ -106,6 +112,7 @@ _$MessagesConfigImpl _$$MessagesConfigImplFromJson(Map<String, dynamic> json) =>
allowLinks: json['allowLinks'] as bool,
allowPolls: json['allowPolls'] as bool,
allowPinning: json['allowPinning'] as bool,
maxFileSize: (json['maxFileSize'] as num?)?.toInt() ?? 0,
);
Map<String, dynamic> _$$MessagesConfigImplToJson(
@@ -126,6 +133,7 @@ Map<String, dynamic> _$$MessagesConfigImplToJson(
'allowLinks': instance.allowLinks,
'allowPolls': instance.allowPolls,
'allowPinning': instance.allowPinning,
'maxFileSize': instance.maxFileSize,
};
_$WebRtcConfigImpl _$$WebRtcConfigImplFromJson(Map<String, dynamic> json) =>
@@ -136,6 +144,8 @@ _$WebRtcConfigImpl _$$WebRtcConfigImplFromJson(Map<String, dynamic> json) =>
enableScreenSharing: json['enableScreenSharing'] as bool,
turnHost: json['turnHost'] as String,
turnPort: (json['turnPort'] as num).toInt(),
turnUser: json['turnUser'] as String? ?? '',
turnSecret: json['turnSecret'] as String? ?? '',
);
Map<String, dynamic> _$$WebRtcConfigImplToJson(_$WebRtcConfigImpl instance) =>
@@ -146,28 +156,32 @@ Map<String, dynamic> _$$WebRtcConfigImplToJson(_$WebRtcConfigImpl instance) =>
'enableScreenSharing': instance.enableScreenSharing,
'turnHost': instance.turnHost,
'turnPort': instance.turnPort,
'turnUser': instance.turnUser,
'turnSecret': instance.turnSecret,
};
_$KlipyConfigImpl _$$KlipyConfigImplFromJson(Map<String, dynamic> json) =>
_$KlipyConfigImpl(
enabled: json['enabled'] as bool,
appName: json['appName'] as String,
apiKey: json['apiKey'] as String? ?? '',
);
Map<String, dynamic> _$$KlipyConfigImplToJson(_$KlipyConfigImpl instance) =>
<String, dynamic>{
'enabled': instance.enabled,
'appName': instance.appName,
'apiKey': instance.apiKey,
};
_$ImportConfigImpl _$$ImportConfigImplFromJson(Map<String, dynamic> json) =>
_$ImportConfigImpl(
enabled: json['enabled'] as bool,
enableTelegramImport: json['enableTelegramImport'] as bool? ?? false,
);
Map<String, dynamic> _$$ImportConfigImplToJson(_$ImportConfigImpl instance) =>
<String, dynamic>{
'enabled': instance.enabled,
'enableTelegramImport': instance.enableTelegramImport,
};
_$FederationConfigImpl _$$FederationConfigImplFromJson(
@@ -7,7 +7,7 @@ abstract class SettingsRepository {
Future<String?> getLanguageCode();
Future<Result<void>> saveLanguageCode(String code);
Future<Result<ServerConfig>> getServerConfig();
Future<Result<void>> refreshServerConfig();
Future<Result<ServerConfig>> refreshServerConfig();
Future<Result<bool>> getNotificationsEnabled();
Future<Result<void>> setNotificationsEnabled(bool enabled);
Future<Result<bool>> getDarkModeEnabled();
@@ -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(),
@@ -1,56 +1,88 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import '../l10n/app_localizations.dart';
import 'package:get_it/get_it.dart';
import '../../domain/entities/server_config.dart';
import '../bloc/settings_bloc.dart';
import '../bloc/settings_event.dart';
import '../bloc/settings_state.dart';
import 'package:messenger_app/features/settings/presentation/bloc/settings_bloc.dart';
import 'package:messenger_app/features/settings/presentation/bloc/settings_event.dart';
import 'package:messenger_app/features/settings/presentation/bloc/settings_state.dart';
import 'package:messenger_app/features/auth/presentation/bloc/auth_bloc.dart';
import 'package:messenger_app/features/auth/presentation/bloc/auth_state.dart';
import 'package:messenger_app/features/auth/presentation/bloc/auth_event.dart';
import 'package:messenger_app/l10n/app_localizations.dart';
final sl = GetIt.instance;
class SettingsPage extends StatelessWidget {
const SettingsPage({super.key});
@override
Widget build(BuildContext context) {
return BlocBuilder<SettingsBloc, SettingsState>(
builder: (context, state) {
return state.when(
initial: () => const Center(child: CircularProgressIndicator()),
loading: () => const Center(child: CircularProgressIndicator()),
loaded: (apiUrl, serverConfig, languageCode, error) => _SettingsContent(
apiUrl: apiUrl,
serverConfig: serverConfig,
languageCode: languageCode,
error: error,
),
error: (message) => Center(child: Text(message)),
);
},
);
// BlocProvider is now at the top level in app.dart
return const _SettingsContent();
}
}
class _SettingsContent extends StatelessWidget {
const _SettingsContent();
@override
Widget build(BuildContext context) {
final l10n = AppLocalizations.of(context)!;
return Scaffold(
appBar: AppBar(
title: Text(l10n.settings),
),
body: BlocBuilder<SettingsBloc, SettingsState>(
builder: (context, state) {
return state.when(
initial: () => _SettingsContentImpl(
apiUrl: '',
serverConfig: null,
languageCode: 'ru',
connectionStatus: ConnectionStatus.unknown,
),
loading: () => const Center(child: CircularProgressIndicator()),
loaded: (apiUrl, serverConfig, languageCode, error, connectionStatus) => _SettingsContentImpl(
apiUrl: apiUrl,
serverConfig: serverConfig,
languageCode: languageCode,
connectionStatus: connectionStatus,
connectionError: error,
),
error: (message) => Center(child: Text('${l10n.error}: $message')),
);
},
),
);
}
}
class _SettingsContentImpl extends StatelessWidget {
final String apiUrl;
final ServerConfig? serverConfig;
final String languageCode;
final String? error;
final ConnectionStatus connectionStatus;
final String? connectionError;
const _SettingsContent({
const _SettingsContentImpl({
required this.apiUrl,
required this.serverConfig,
required this.languageCode,
this.error,
this.connectionStatus = ConnectionStatus.unknown,
this.connectionError,
});
@override
Widget build(BuildContext context) {
final l10n = AppLocalizations.of(context)!;
return ListView(
children: [
_SectionHeader(title: l10n.serverSettings),
_ApiUrlTile(
apiUrl: apiUrl,
connectionStatus: connectionStatus,
connectionError: connectionError,
onSave: (url) {
context.read<SettingsBloc>().add(SettingsApiUrlSaved(url));
},
@@ -72,16 +104,40 @@ class _SettingsContent extends StatelessWidget {
title: l10n.system,
icon: Icons.computer,
children: [
_ConfigItem(label: l10n.domainUrl, value: serverConfig!.system.domainUrl),
_ConfigItem(label: l10n.enableRegistration, value: serverConfig!.system.enableRegistration ? l10n.enabled : l10n.disabled),
_ConfigItem(
label: l10n.domainUrl,
value: serverConfig!.system.domainUrl.contains('example.com') ? apiUrl : serverConfig!.system.domainUrl,
),
_ConfigItem(label: l10n.registrationStatus, value: serverConfig!.system.enableRegistration ? l10n.enabled : l10n.disabled),
],
),
_ConfigModuleCard(
title: l10n.messages,
icon: Icons.message,
children: [
_ConfigItem(label: l10n.enabled, value: serverConfig!.messages.allowMedia ? l10n.enabled : l10n.disabled),
_ConfigItem(label: 'Max media size', value: '${(serverConfig!.messages.maxMediaSizeBytes / 1024 / 1024).toStringAsFixed(1)} MB'),
_ConfigItem(
label: l10n.dailyLimit,
value: serverConfig!.messages.dailyMessageLimitPerUser == 0 ? l10n.noLimit : '${serverConfig!.messages.dailyMessageLimitPerUser}',
),
_ConfigItem(
label: l10n.historyLimit,
value: serverConfig!.messages.chatMessageLimit == 0 ? l10n.noLimit : '${serverConfig!.messages.chatMessageLimit}',
),
_ConfigItem(
label: l10n.maxFileSize,
value: '${(serverConfig!.messages.maxFileSize / 1024 / 1024).toStringAsFixed(0)} MB',
),
_ConfigItem(label: l10n.allowMedia, value: serverConfig!.messages.allowMedia ? l10n.enabled : l10n.disabled),
_ConfigItem(label: l10n.voice, value: serverConfig!.messages.allowVoiceMessages ? l10n.enabled : l10n.disabled),
_ConfigItem(label: l10n.allowForwarding, value: serverConfig!.messages.allowForwarding ? l10n.enabled : l10n.disabled),
_ConfigItem(label: l10n.allowReactions, value: serverConfig!.messages.allowReactions ? l10n.enabled : l10n.disabled),
_ConfigItem(label: l10n.allowReplies, value: serverConfig!.messages.allowReplies ? l10n.enabled : l10n.disabled),
_ConfigItem(label: l10n.allowQuoting, value: serverConfig!.messages.allowQuoting ? l10n.enabled : l10n.disabled),
_ConfigItem(label: l10n.allowMessageDeletion, value: serverConfig!.messages.allowMessageDeletion ? l10n.enabled : l10n.disabled),
_ConfigItem(label: l10n.forbidCopying, value: serverConfig!.messages.forbidCopying ? l10n.enabled : l10n.disabled),
_ConfigItem(label: l10n.allowLinks, value: serverConfig!.messages.allowLinks ? l10n.enabled : l10n.disabled),
_ConfigItem(label: l10n.allowPolls, value: serverConfig!.messages.allowPolls ? l10n.enabled : l10n.disabled),
_ConfigItem(label: l10n.allowPinning, value: serverConfig!.messages.allowPinning ? l10n.enabled : l10n.disabled),
],
),
_ConfigModuleCard(
@@ -89,8 +145,8 @@ class _SettingsContent extends StatelessWidget {
icon: Icons.call,
children: [
_ConfigItem(label: l10n.enabled, value: serverConfig!.webRtc.enabled ? l10n.enabled : l10n.disabled),
_ConfigItem(label: 'Voice calls', value: serverConfig!.webRtc.enableVoiceCalls ? l10n.enabled : l10n.disabled),
_ConfigItem(label: 'Video calls', value: serverConfig!.webRtc.enableVideoCalls ? l10n.enabled : l10n.disabled),
_ConfigItem(label: l10n.voice, value: serverConfig!.webRtc.enableVoiceCalls ? l10n.enabled : l10n.disabled),
_ConfigItem(label: l10n.video, value: serverConfig!.webRtc.enableVideoCalls ? l10n.enabled : l10n.disabled),
],
),
_ConfigModuleCard(
@@ -98,15 +154,15 @@ class _SettingsContent extends StatelessWidget {
icon: Icons.auto_stories,
children: [
_ConfigItem(label: l10n.enabled, value: serverConfig!.stories.enabled ? l10n.enabled : l10n.disabled),
_ConfigItem(label: 'Lifetime', value: '${serverConfig!.stories.storyLifetimeHours}h'),
_ConfigItem(label: l10n.lifetime, value: '${serverConfig!.stories.storyLifetimeHours}${l10n.hours}'),
],
),
_ConfigModuleCard(
title: l10n.chatsModule,
icon: Icons.chat_bubble,
children: [
_ConfigItem(label: 'Groups', value: serverConfig!.chats.supportGroups ? l10n.enabled : l10n.disabled),
_ConfigItem(label: 'Max participants', value: '${serverConfig!.chats.maxGroupParticipants}'),
_ConfigItem(label: l10n.groups, value: serverConfig!.chats.supportGroups ? l10n.enabled : l10n.disabled),
_ConfigItem(label: l10n.maxParticipants, value: '${serverConfig!.chats.maxGroupParticipants}'),
],
),
if (serverConfig!.federation.enabled)
@@ -114,7 +170,7 @@ class _SettingsContent extends StatelessWidget {
title: l10n.federation,
icon: Icons.public,
children: [
_ConfigItem(label: 'Description', value: serverConfig!.federation.serverDescription),
_ConfigItem(label: l10n.description, value: serverConfig!.federation.serverDescription),
],
),
const Divider(),
@@ -165,19 +221,35 @@ class _SettingsContent extends StatelessWidget {
),
const Divider(),
Padding(
padding: const EdgeInsets.all(16),
child: OutlinedButton(
onPressed: () {},
style: OutlinedButton.styleFrom(
foregroundColor: Colors.red,
side: const BorderSide(color: Colors.red),
),
child: Padding(
padding: const EdgeInsets.symmetric(vertical: 12),
child: Text(l10n.logout),
),
),
BlocBuilder<AuthBloc, AuthState>(
builder: (context, authState) {
final isAuthenticated = authState.when(
initial: () => false,
loading: () => false,
authenticated: (_) => true,
unauthenticated: () => false,
error: (_) => false,
);
if (!isAuthenticated) return const SizedBox.shrink();
return Padding(
padding: const EdgeInsets.all(16),
child: OutlinedButton(
onPressed: () {
context.read<AuthBloc>().add(const AuthEvent.logoutRequested());
},
style: OutlinedButton.styleFrom(
foregroundColor: Colors.red,
side: const BorderSide(color: Colors.red),
),
child: Padding(
padding: const EdgeInsets.symmetric(vertical: 12),
child: Text(l10n.logout),
),
),
);
},
),
],
);
@@ -208,10 +280,14 @@ class _SectionHeader extends StatelessWidget {
class _ApiUrlTile extends StatefulWidget {
final String apiUrl;
final ConnectionStatus connectionStatus;
final String? connectionError;
final ValueChanged<String> onSave;
const _ApiUrlTile({
required this.apiUrl,
required this.connectionStatus,
this.connectionError,
required this.onSave,
});
@@ -243,10 +319,63 @@ class _ApiUrlTileState extends State<_ApiUrlTile> {
super.dispose();
}
Widget _buildStatusIndicator(BuildContext context) {
final l10n = AppLocalizations.of(context)!;
switch (widget.connectionStatus) {
case ConnectionStatus.checking:
return const SizedBox(
width: 16,
height: 16,
child: CircularProgressIndicator(strokeWidth: 2),
);
case ConnectionStatus.connected:
return Row(
mainAxisSize: MainAxisSize.min,
children: [
Icon(Icons.check_circle, color: Colors.green[600], size: 18),
const SizedBox(width: 4),
Text(
l10n.connected,
style: TextStyle(color: Colors.green[700], fontSize: 12),
),
],
);
case ConnectionStatus.error:
return Row(
mainAxisSize: MainAxisSize.min,
children: [
Icon(Icons.error_outline, color: Colors.red[600], size: 18),
const SizedBox(width: 4),
Flexible(
child: Text(
_getLocalError(context, widget.connectionError),
style: TextStyle(color: Colors.red[700], fontSize: 12),
overflow: TextOverflow.ellipsis,
),
),
],
);
case ConnectionStatus.unknown:
return const SizedBox.shrink();
}
}
String _getLocalError(BuildContext context, String? errorKey) {
final l10n = AppLocalizations.of(context)!;
if (errorKey == null) return l10n.serverConnectionError;
switch (errorKey) {
case 'unknownError': return l10n.unknownError;
case 'networkError': return l10n.networkError;
case 'serverError': return l10n.serverError;
case 'parsingError': return l10n.parsingError;
default: return errorKey;
}
}
@override
Widget build(BuildContext context) {
final l10n = AppLocalizations.of(context)!;
if (_isEditing) {
return Padding(
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
@@ -287,11 +416,18 @@ class _ApiUrlTileState extends State<_ApiUrlTile> {
return ListTile(
leading: const Icon(Icons.link),
title: Text(l10n.apiUrl),
subtitle: Text(
widget.apiUrl.isEmpty ? l10n.apiUrlHint : widget.apiUrl,
style: TextStyle(
color: widget.apiUrl.isEmpty ? Colors.grey : null,
),
subtitle: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
widget.apiUrl.isEmpty ? l10n.apiUrlHint : widget.apiUrl,
style: TextStyle(
color: widget.apiUrl.isEmpty ? Colors.grey : null,
),
),
const SizedBox(height: 2),
_buildStatusIndicator(context),
],
),
trailing: const Icon(Icons.edit),
onTap: () => setState(() => _isEditing = true),
@@ -311,7 +447,6 @@ class _LanguageTile extends StatelessWidget {
@override
Widget build(BuildContext context) {
final l10n = AppLocalizations.of(context)!;
return ListTile(
leading: const Icon(Icons.language),
title: Text(l10n.language),