import 'package:cached_network_image/cached_network_image.dart'; import 'package:flutter/material.dart'; import 'package:stream_chat_flutter/stream_chat_flutter.dart'; /// WidgetBuilder for [StreamUserAvatar]. typedef StreamUserAvatarBuilder = Widget Function( BuildContext context, User user, // ignore: avoid_positional_boolean_parameters bool isSelected, ); /// {@template streamUserAvatar} /// Displays a user's avatar. /// {@endtemplate} class StreamUserAvatar extends StatelessWidget { /// {@macro streamUserAvatar} const StreamUserAvatar({ super.key, required this.user, this.constraints, this.onlineIndicatorConstraints, this.onTap, this.onLongPress, this.showOnlineStatus = true, this.borderRadius, this.onlineIndicatorAlignment = Alignment.topRight, this.selected = false, this.selectionColor, this.selectionThickness = 4, this.placeholder, }); /// User whose avatar is to be displayed final User user; /// Alignment of the online indicator /// /// Defaults to `Alignment.topRight` final Alignment onlineIndicatorAlignment; /// Sizing constraints of the avatar final BoxConstraints? constraints; /// [BorderRadius] of the image final BorderRadius? borderRadius; /// Sizing constraints of the online indicator final BoxConstraints? onlineIndicatorConstraints; /// {@macro onUserAvatarTap} final OnUserAvatarPress? onTap; /// {@macro onUserAvatarTap} final OnUserAvatarPress? onLongPress; /// Flag for showing online status /// /// Defaults to `true` final bool showOnlineStatus; /// Flag for if avatar is selected /// /// Defaults to `false` final bool selected; /// Color of selection final Color? selectionColor; /// Selection thickness around the avatar /// /// Defaults to `4` final double selectionThickness; /// {@macro placeholderUserImage} final PlaceholderUserImage? placeholder; @override Widget build(BuildContext context) { final hasImage = user.image != null && user.image!.isNotEmpty; final streamChatTheme = StreamChatTheme.of(context); final streamChatConfig = StreamChatConfiguration.of(context); final placeholder = this.placeholder ?? streamChatConfig.placeholderUserImage; final backupGradientAvatar = ClipRRect( borderRadius: borderRadius ?? streamChatTheme.ownMessageTheme.avatarTheme?.borderRadius ?? BorderRadius.zero, child: streamChatConfig.defaultUserImage(context, user), ); Widget avatar = FittedBox( fit: BoxFit.cover, child: Container( constraints: constraints ?? streamChatTheme.ownMessageTheme.avatarTheme?.constraints, child: hasImage ? CachedNetworkImage( fit: BoxFit.cover, filterQuality: FilterQuality.high, imageUrl: user.image!, errorWidget: (context, __, ___) => backupGradientAvatar, placeholder: placeholder != null ? (context, __) => placeholder(context, user) : null, imageBuilder: (context, imageProvider) => DecoratedBox( decoration: BoxDecoration( borderRadius: borderRadius ?? streamChatTheme .ownMessageTheme.avatarTheme?.borderRadius, image: DecorationImage( image: imageProvider, fit: BoxFit.cover, ), ), ), ) : backupGradientAvatar, ), ); if (selected) { avatar = ClipRRect( borderRadius: (borderRadius ?? streamChatTheme.ownMessageTheme.avatarTheme?.borderRadius ?? BorderRadius.zero) + BorderRadius.circular(selectionThickness), child: Container( constraints: constraints ?? streamChatTheme.ownMessageTheme.avatarTheme?.constraints, color: selectionColor ?? streamChatTheme.colorTheme.accentPrimary, child: Padding( padding: EdgeInsets.all(selectionThickness), child: avatar, ), ), ); } return GestureDetector( onTap: onTap != null ? () => onTap!(user) : null, onLongPress: onLongPress != null ? () => onLongPress!(user) : null, child: Stack( children: [ avatar, if (showOnlineStatus && user.online) Positioned.fill( child: Align( alignment: onlineIndicatorAlignment, child: Material( type: MaterialType.circle, color: streamChatTheme.colorTheme.barsBg, child: Container( margin: const EdgeInsets.all(2), constraints: onlineIndicatorConstraints ?? const BoxConstraints.tightFor( width: 8, height: 8, ), child: Material( shape: const CircleBorder(), color: streamChatTheme.colorTheme.accentInfo, ), ), ), ), ), ], ), ); } }