import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:get_it/get_it.dart'; import '../../domain/entities/server_config.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) { // 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( 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 ConnectionStatus connectionStatus; final String? connectionError; const _SettingsContentImpl({ required this.apiUrl, required this.serverConfig, required this.languageCode, 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().add(SettingsApiUrlSaved(url)); }, ), const Divider(), _SectionHeader(title: l10n.language), _LanguageTile( currentCode: languageCode, onChanged: (code) { context.read().add(SettingsLanguageChanged(code)); }, ), const Divider(), if (serverConfig != null) ...[ _SectionHeader(title: l10n.serverConfig), _ConfigModuleCard( title: l10n.system, icon: Icons.computer, children: [ _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.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( title: l10n.calls, icon: Icons.call, children: [ _ConfigItem(label: l10n.enabled, value: serverConfig!.webRtc.enabled ? 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( title: l10n.stories, icon: Icons.auto_stories, children: [ _ConfigItem(label: l10n.enabled, value: serverConfig!.stories.enabled ? l10n.enabled : l10n.disabled), _ConfigItem(label: l10n.lifetime, value: '${serverConfig!.stories.storyLifetimeHours}${l10n.hours}'), ], ), _ConfigModuleCard( title: l10n.chatsModule, icon: Icons.chat_bubble, children: [ _ConfigItem(label: l10n.groups, value: serverConfig!.chats.supportGroups ? l10n.enabled : l10n.disabled), _ConfigItem(label: l10n.maxParticipants, value: '${serverConfig!.chats.maxGroupParticipants}'), ], ), if (serverConfig!.federation.enabled) _ConfigModuleCard( title: l10n.federation, icon: Icons.public, children: [ _ConfigItem(label: l10n.description, value: serverConfig!.federation.serverDescription), ], ), const Divider(), ], if (serverConfig == null) ListTile( leading: const Icon(Icons.refresh), title: Text(l10n.retry), onTap: () { context.read().add(const SettingsRefreshConfig()); }, ), _SectionHeader(title: l10n.settings), _SettingsTile( icon: Icons.notifications_outlined, title: l10n.notifications, onTap: () {}, ), _SettingsTile( icon: Icons.security, title: l10n.privacy, onTap: () {}, ), _SettingsTile( icon: Icons.volume_up, title: l10n.soundsAndVibration, onTap: () {}, ), _SettingsTile( icon: Icons.storage, title: l10n.dataAndStorage, onTap: () {}, ), const Divider(), _SettingsTile( icon: Icons.info_outline, title: l10n.aboutApp, subtitle: '${l10n.version} 1.0.0', onTap: () {}, ), _SettingsTile( icon: Icons.help_outline, title: l10n.help, onTap: () {}, ), const Divider(), BlocBuilder( 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().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), ), ), ); }, ), ], ); } } class _SectionHeader extends StatelessWidget { final String title; const _SectionHeader({required this.title}); @override Widget build(BuildContext context) { return Padding( padding: const EdgeInsets.fromLTRB(16, 16, 16, 8), child: Text( title.toUpperCase(), style: TextStyle( fontSize: 13, fontWeight: FontWeight.w600, color: Theme.of(context).colorScheme.primary, letterSpacing: 0.5, ), ), ); } } class _ApiUrlTile extends StatefulWidget { final String apiUrl; final ConnectionStatus connectionStatus; final String? connectionError; final ValueChanged onSave; const _ApiUrlTile({ required this.apiUrl, required this.connectionStatus, this.connectionError, required this.onSave, }); @override State<_ApiUrlTile> createState() => _ApiUrlTileState(); } class _ApiUrlTileState extends State<_ApiUrlTile> { late final TextEditingController _controller; bool _isEditing = false; @override void initState() { super.initState(); _controller = TextEditingController(text: widget.apiUrl); } @override void didUpdateWidget(covariant _ApiUrlTile oldWidget) { super.didUpdateWidget(oldWidget); if (oldWidget.apiUrl != widget.apiUrl) { _controller.text = widget.apiUrl; } } @override void dispose() { _controller.dispose(); 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), child: Row( children: [ Expanded( child: TextField( controller: _controller, decoration: InputDecoration( labelText: l10n.apiUrl, hintText: l10n.apiUrlHint, border: const OutlineInputBorder(), contentPadding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8), ), keyboardType: TextInputType.url, ), ), const SizedBox(width: 8), IconButton( icon: const Icon(Icons.check, color: Colors.green), onPressed: () { widget.onSave(_controller.text.trim()); setState(() => _isEditing = false); }, ), IconButton( icon: const Icon(Icons.close, color: Colors.red), onPressed: () { _controller.text = widget.apiUrl; setState(() => _isEditing = false); }, ), ], ), ); } return ListTile( leading: const Icon(Icons.link), title: Text(l10n.apiUrl), 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), ); } } class _LanguageTile extends StatelessWidget { final String currentCode; final ValueChanged onChanged; const _LanguageTile({ required this.currentCode, required this.onChanged, }); @override Widget build(BuildContext context) { final l10n = AppLocalizations.of(context)!; return ListTile( leading: const Icon(Icons.language), title: Text(l10n.language), subtitle: Text(currentCode == 'ru' ? l10n.russian : l10n.english), trailing: DropdownButton( value: currentCode, underline: const SizedBox(), items: [ DropdownMenuItem(value: 'ru', child: Text(l10n.russian)), DropdownMenuItem(value: 'en', child: Text(l10n.english)), ], onChanged: (value) { if (value != null) onChanged(value); }, ), ); } } class _ConfigModuleCard extends StatelessWidget { final String title; final IconData icon; final List children; const _ConfigModuleCard({ required this.title, required this.icon, required this.children, }); @override Widget build(BuildContext context) { return Card( margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 4), child: ExpansionTile( leading: Icon(icon, color: Theme.of(context).colorScheme.primary), title: Text(title), children: [ Padding( padding: const EdgeInsets.fromLTRB(16, 0, 16, 12), child: Column( children: children, ), ), ], ), ); } } class _ConfigItem extends StatelessWidget { final String label; final String value; const _ConfigItem({ required this.label, required this.value, }); @override Widget build(BuildContext context) { return Padding( padding: const EdgeInsets.symmetric(vertical: 4), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text( label, style: TextStyle( color: Colors.grey[600], fontSize: 13, ), ), Text( value, style: const TextStyle( fontWeight: FontWeight.w500, fontSize: 13, ), ), ], ), ); } } class _SettingsTile extends StatelessWidget { final IconData icon; final String title; final String? subtitle; final VoidCallback onTap; const _SettingsTile({ required this.icon, required this.title, this.subtitle, required this.onTap, }); @override Widget build(BuildContext context) { return ListTile( leading: Icon(icon), title: Text(title), subtitle: subtitle != null ? Text(subtitle!) : null, trailing: const Icon(Icons.chevron_right), onTap: onTap, ); } }