import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import '../../../../api/marianumcloud/talk/actions/talk_actions.dart'; import '../../../../api/marianumcloud/talk/get_participants/get_participants_cache.dart'; import '../../../../api/marianumcloud/talk/get_participants/get_participants_response.dart'; import '../../../../api/marianumcloud/talk/room/get_room_response.dart'; import '../../../../state/app/modules/chat_list/bloc/chat_list_bloc.dart'; import '../../../../widget/app_progress_indicator.dart'; import '../../../../widget/async_action_button.dart'; import '../../../../widget/avatar_actions_sheet.dart'; import '../../../../widget/confirm_dialog.dart'; import '../../../../widget/large_profile_picture_view.dart'; import '../../../../widget/loading_spinner.dart'; import '../../../../widget/user_avatar.dart'; import '../talk_navigator.dart'; import 'participants_list_view.dart'; class ChatInfo extends StatefulWidget { final GetRoomResponseObject room; const ChatInfo(this.room, {super.key}); @override State createState() => _ChatInfoState(); } class _ChatInfoState extends State { GetParticipantsResponse? participants; late bool _isFavorite; int _avatarVersion = 0; bool _avatarBusy = false; @override void initState() { super.initState(); _isFavorite = widget.room.isFavorite; GetParticipantsCache( chatToken: widget.room.token, onUpdate: (GetParticipantsResponse data) { setState(() { participants = data; }); }, ); } void _refreshList() => context.read().refresh(); Future _toggleFavorite() async { final next = !_isFavorite; await SetFavorite(widget.room.token, next).run(); if (!mounted) return; setState(() => _isFavorite = next); _refreshList(); } Future _confirmLeave() async { final closed = await showDialog( context: context, builder: (_) => ConfirmDialog( title: 'Talk-Chat verlassen', content: 'Du benötigst ggf. eine Einladung um erneut beizutreten.', confirmButton: 'Verlassen', onConfirmAsync: () async { await LeaveRoom(widget.room.token).run(); if (mounted) _refreshList(); }, ), ); if (closed == true && mounted) Navigator.of(context).pop(); } // Spreed's POST /room/{token}/avatar requires moderator rights and rejects // 1:1, changelog and note-to-self rooms server-side. Mirror that here so // the edit affordance only shows when the upload would actually succeed. bool _canEditAvatar() { final room = widget.room; const editableTypes = { GetRoomResponseObjectConversationType.group, GetRoomResponseObjectConversationType.public, }; if (!editableTypes.contains(room.type)) return false; // Owner=1, Moderator=2, GuestModerator=6. return room.participantType == 1 || room.participantType == 2 || room.participantType == 6; } Future _editAvatar() async { final result = await showAvatarActionsSheet(context, allowRemove: true); if (result == null || !mounted) return; if (result is AvatarRemoveResult) { var confirmed = false; await showDialog( context: context, builder: (_) => ConfirmDialog( title: 'Gruppenbild entfernen', content: 'Möchtest du das Gruppenbild wirklich entfernen?', confirmButton: 'Entfernen', onConfirm: () => confirmed = true, ), ); if (!confirmed || !mounted) return; } setState(() => _avatarBusy = true); final ok = await runWithErrorDialog(context, () async { if (result is AvatarUploadResult) { await SetRoomAvatar(widget.room.token, result.bytes).run(); } else { await DeleteRoomAvatar(widget.room.token).run(); } }); if (!mounted) return; setState(() => _avatarBusy = false); if (!ok) return; invalidateAvatarCache(id: widget.room.token, isGroup: true); setState(() => _avatarVersion++); _refreshList(); } @override Widget build(BuildContext context) { var isGroup = widget.room.type != GetRoomResponseObjectConversationType.oneToOne; final canEdit = _canEditAvatar(); return Scaffold( appBar: AppBar(title: Text(widget.room.displayName)), body: ListView( children: [ const SizedBox(height: 30), Center( child: SizedBox( width: 180, height: 180, child: Stack( clipBehavior: Clip.none, children: [ Center( child: GestureDetector( child: UserAvatar( key: ValueKey(_avatarVersion), id: isGroup ? widget.room.token : widget.room.name, isGroup: isGroup, size: 80, ), onTap: () => TalkNavigator.pushSplitView( context, LargeProfilePictureView( id: isGroup ? widget.room.token : widget.room.name, isGroup: isGroup, ), ), ), ), if (canEdit) Positioned( right: 18, bottom: 18, child: _AvatarEditBadge( busy: _avatarBusy, onTap: _avatarBusy ? null : _editAvatar, ), ), ], ), ), ), const SizedBox(height: 30), Text( widget.room.displayName, textAlign: TextAlign.center, style: const TextStyle(fontSize: 30), ), if (!isGroup) Text(widget.room.name, textAlign: TextAlign.center), const SizedBox(height: 10), if (isGroup) Padding( padding: const EdgeInsets.symmetric(horizontal: 24), child: Text( widget.room.description, textAlign: TextAlign.center, ), ), const SizedBox(height: 30), if (participants == null) const Center(child: LoadingSpinner()) else ListTile( leading: const Icon(Icons.supervised_user_circle), title: Text('${participants!.data.length} Mitglieder'), trailing: const Icon(Icons.arrow_right), onTap: () => TalkNavigator.pushSplitView( context, ParticipantsListView( participants!, showDirectMessageAction: isGroup, ), ), ), if (_isFavorite) AsyncListTile( leading: const Icon(Icons.stars_outlined), title: const Text('Von Favoriten entfernen'), onPressed: _toggleFavorite, ) else AsyncListTile( leading: const Icon(Icons.star_outline), title: const Text('Zu Favoriten hinzufügen'), onPressed: _toggleFavorite, ), const Divider(), ListTile( leading: Icon( Icons.delete_outline, color: Theme.of(context).colorScheme.error, ), title: Text( 'Talk-Chat verlassen', style: TextStyle(color: Theme.of(context).colorScheme.error), ), onTap: _confirmLeave, ), ], ), ); } } class _AvatarEditBadge extends StatelessWidget { final bool busy; final VoidCallback? onTap; const _AvatarEditBadge({required this.busy, required this.onTap}); @override Widget build(BuildContext context) { final theme = Theme.of(context); return Material( color: theme.colorScheme.primary, shape: const CircleBorder(), elevation: 2, child: InkWell( customBorder: const CircleBorder(), onTap: onTap, child: SizedBox( width: 30, height: 30, child: busy ? Padding( padding: const EdgeInsets.all(7), child: AppProgressIndicator.small( color: theme.colorScheme.onPrimary, ), ) : Icon( Icons.edit, size: 15, color: theme.colorScheme.onPrimary, ), ), ), ); } }