implemented avatar management for user profiles and chat rooms, including 1:1 cropping, integrated OCS and Spreed avatar APIs, added cache invalidation logic, and updated the account settings view to display user info and profile pictures.
This commit is contained in:
@@ -6,7 +6,9 @@ import '../../../../api/marianumcloud/talk/get_participants/get_participants_cac
|
||||
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';
|
||||
@@ -25,6 +27,8 @@ class ChatInfo extends StatefulWidget {
|
||||
class _ChatInfoState extends State<ChatInfo> {
|
||||
GetParticipantsResponse? participants;
|
||||
late bool _isFavorite;
|
||||
int _avatarVersion = 0;
|
||||
bool _avatarBusy = false;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
@@ -66,28 +70,101 @@ class _ChatInfoState extends State<ChatInfo> {
|
||||
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<void> _editAvatar() async {
|
||||
final result = await showAvatarActionsSheet(context, allowRemove: true);
|
||||
if (result == null || !mounted) return;
|
||||
|
||||
if (result is AvatarRemoveResult) {
|
||||
var confirmed = false;
|
||||
await showDialog<void>(
|
||||
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: GestureDetector(
|
||||
child: UserAvatar(
|
||||
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,
|
||||
),
|
||||
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,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
@@ -153,3 +230,39 @@ class _ChatInfoState extends State<ChatInfo> {
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
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,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user