Настройки, авторизация
This commit is contained in:
@@ -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),
|
||||
|
||||
Reference in New Issue
Block a user