import 'package:dio/dio.dart'; import 'package:dropdown_button2/dropdown_button2.dart'; import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:gap/gap.dart'; import 'package:material_symbols_icons/symbols.dart'; import 'package:provider/provider.dart'; import 'package:styled_widget/styled_widget.dart'; import 'package:surface/providers/sn_network.dart'; import 'package:surface/types/chat.dart'; import 'package:surface/types/realm.dart'; import 'package:surface/widgets/account/account_image.dart'; import 'package:surface/widgets/dialog.dart'; import 'package:surface/widgets/loading_indicator.dart'; import 'package:uuid/uuid.dart'; class ChatManageScreen extends StatefulWidget { final String? editingChannelAlias; const ChatManageScreen({super.key, this.editingChannelAlias}); @override State createState() => _ChatManageScreenState(); } class _ChatManageScreenState extends State { bool _isBusy = false; final _aliasController = TextEditingController(); final _nameController = TextEditingController(); final _descriptionController = TextEditingController(); List? _realms; SnRealm? _belongToRealm; Future _fetchRealms() async { setState(() => _isBusy = true); try { final sn = context.read(); final resp = await sn.client.get('/cgi/id/realms/me/available'); _realms = List.from( resp.data?.map((e) => SnRealm.fromJson(e)) ?? [], ); } catch (err) { if (mounted) context.showErrorDialog(err); } finally { setState(() => _isBusy = false); } } SnChannel? _editingChannel; Future _fetchChannel() async { setState(() => _isBusy = true); try { final sn = context.read(); final resp = await sn.client.get( '/cgi/im/channels/${widget.editingChannelAlias}', ); _editingChannel = SnChannel.fromJson(resp.data); } catch (err) { if (!mounted) return; context.showErrorDialog(err); } finally { setState(() => _isBusy = false); } } Future _performAction() async { final uuid = const Uuid(); final sn = context.read(); setState(() => _isBusy = true); final scope = _belongToRealm != null ? _belongToRealm!.alias : 'global'; final payload = { 'alias': _aliasController.text.isNotEmpty ? _aliasController.text.toLowerCase() : uuid.v4().replaceAll('-', '').substring(0, 12), 'name': _nameController.text, 'description': _descriptionController.text, }; try { final resp = await sn.client.request( widget.editingChannelAlias != null ? '/cgi/im/channels/$scope/${widget.editingChannelAlias}' : '/cgi/im/channels/$scope', data: payload, options: Options( method: widget.editingChannelAlias != null ? 'PUT' : 'POST', ), ); // ignore: use_build_context_synchronously if (context.mounted) Navigator.pop(context, resp.data); } catch (err) { // ignore: use_build_context_synchronously if (context.mounted) context.showErrorDialog(err); } setState(() => _isBusy = false); } @override void initState() { super.initState(); if (widget.editingChannelAlias != null) _fetchChannel(); _fetchRealms(); } @override void dispose() { super.dispose(); _aliasController.dispose(); _nameController.dispose(); _descriptionController.dispose(); } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: widget.editingChannelAlias != null ? Text('screenChatManage').tr() : Text('screenChatNew').tr(), ), body: SingleChildScrollView( child: Column( children: [ LoadingIndicator(isActive: _isBusy), if (_editingChannel != null) MaterialBanner( leading: const Icon(Symbols.edit), leadingPadding: const EdgeInsets.only(left: 10, right: 20), dividerColor: Colors.transparent, content: Text( 'channelEditingNotice' .tr(args: ['#${_editingChannel!.alias}']), ), actions: [ TextButton( child: Text('cancel').tr(), onPressed: () { Navigator.pop(context); }, ), ], ), DropdownButtonHideUnderline( child: DropdownButton2( isExpanded: true, hint: Text( 'fieldChatBelongToRealm'.tr(), style: TextStyle( color: Theme.of(context).hintColor, ), ), items: [ ...(_realms?.map( (SnRealm item) => DropdownMenuItem( value: item, child: Row( children: [ AccountImage( content: item.avatar, radius: 16, fallbackWidget: const Icon( Symbols.group, size: 16, ), ), const Gap(12), Expanded( child: Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, children: [ Text(item.name).textStyle(Theme.of(context) .textTheme .bodyMedium!), Text( item.description, maxLines: 1, overflow: TextOverflow.ellipsis, ).textStyle( Theme.of(context).textTheme.bodySmall!), ], ), ), ], ), ), ) ?? []), DropdownMenuItem( value: null, child: Row( children: [ CircleAvatar( radius: 16, backgroundColor: Colors.transparent, foregroundColor: Theme.of(context).colorScheme.onSurface, child: const Icon(Symbols.clear), ), const Gap(12), Expanded( child: Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, children: [ Text('fieldChatBelongToRealmUnset') .tr() .textStyle( Theme.of(context).textTheme.bodyMedium!, ), ], ), ), ], ), ), ], value: _belongToRealm, onChanged: (SnRealm? value) { setState(() => _belongToRealm = value); }, buttonStyleData: const ButtonStyleData( padding: EdgeInsets.only(right: 16), height: 60, ), menuItemStyleData: const MenuItemStyleData( height: 60, ), ), ), const Divider(height: 1), const Gap(12), Column( children: [ TextField( controller: _aliasController, decoration: InputDecoration( border: const UnderlineInputBorder(), labelText: 'fieldChatAlias'.tr(), helperText: 'fieldChatAliasHint'.tr(), helperMaxLines: 2, ), onTapOutside: (_) => FocusManager.instance.primaryFocus?.unfocus(), ), const Gap(4), TextField( controller: _nameController, decoration: InputDecoration( border: const UnderlineInputBorder(), labelText: 'fieldChatName'.tr(), ), onTapOutside: (_) => FocusManager.instance.primaryFocus?.unfocus(), ), const Gap(4), TextField( controller: _descriptionController, maxLines: null, minLines: 3, decoration: InputDecoration( border: const UnderlineInputBorder(), labelText: 'fieldChatDescription'.tr(), ), onTapOutside: (_) => FocusManager.instance.primaryFocus?.unfocus(), ), const Gap(12), Row( mainAxisAlignment: MainAxisAlignment.end, children: [ ElevatedButton.icon( onPressed: _isBusy ? null : _performAction, icon: const Icon(Symbols.save), label: Text('apply').tr(), ), ], ), ], ).padding(horizontal: 24), ], ), ), ); } }