import 'dart:async'; import 'dart:io'; import 'package:flutter/material.dart'; import 'package:flutter_svg/svg.dart'; import 'package:twonly/locator.dart'; import 'package:twonly/src/database/twonly.db.dart'; import 'package:twonly/src/utils/avatars.dart'; import 'package:twonly/src/utils/log.dart'; import 'package:vector_graphics/vector_graphics.dart'; class AvatarIcon extends StatefulWidget { const AvatarIcon({ super.key, this.group, this.contactId, this.myAvatar = false, this.fontSize = 20, this.color, }); final Group? group; final int? contactId; final bool myAvatar; final double? fontSize; final Color? color; @override State createState() => _AvatarIconState(); } class _AvatarIconState extends State { List _avatarContacts = []; String? _myAvatarPath; StreamSubscription>? groupStream; StreamSubscription>? contactsStream; StreamSubscription? contactStream; StreamSubscription? _userSub; @override void initState() { super.initState(); initAsync(); } @override void dispose() { groupStream?.cancel(); contactStream?.cancel(); contactsStream?.cancel(); _userSub?.cancel(); super.dispose(); } Widget errorBuilder(_, _, _) { return const SvgPicture( AssetBytesLoader('assets/images/default_avatar.svg.vec'), ); } Widget getAvatarForContact(Contact contact) { final avatarFile = avatarPNGFile(contact.userId); if (avatarFile.existsSync()) { return Image.file( avatarFile, errorBuilder: errorBuilder, ); } Log.warn( 'PNG avatar file for contact ${contact.userId} does not exist. Generating in background.', ); unawaited(createPushAvatars(forceForUserId: contact.userId)); if (contact.avatarSvgCompressed != null) { return SvgPicture.string( getAvatarSvg(contact.avatarSvgCompressed!), errorBuilder: errorBuilder, ); } return errorBuilder(null, null, null); } Future initAsync() async { if (widget.group != null) { groupStream = twonlyDB.groupsDao .watchGroupContact(widget.group!.groupId) .listen((contacts) { _avatarContacts = []; if (contacts.length == 1) { if (contacts.first.avatarSvgCompressed != null) { _avatarContacts.add(contacts.first); } } else { for (final contact in contacts) { if (contact.avatarSvgCompressed != null) { _avatarContacts.add(contact); } } } setState(() {}); }); } else if (widget.myAvatar) { _userSub = userService.onUserUpdated.listen((_) { unawaited(_updateMyAvatar()); }); unawaited(_updateMyAvatar()); } else if (widget.contactId != null) { contactStream = twonlyDB.contactsDao .watchContact(widget.contactId!) .listen((contact) { if (contact != null && contact.avatarSvgCompressed != null) { _avatarContacts = [contact]; setState(() {}); } }); } if (mounted) setState(() {}); } Future _updateMyAvatar() async { final avatarSvg = userService.currentUser.avatarSvg; if (avatarSvg == null) { if (mounted) { setState(() { _myAvatarPath = null; _avatarContacts = []; }); } return; } final path = await getUserAvatar(); if (mounted) { setState(() { _myAvatarPath = path; }); } } @override Widget build(BuildContext context) { final proSize = (widget.fontSize == null) ? 40 : (widget.fontSize! * 2); Widget avatars = Container(); if (widget.myAvatar) { if (_myAvatarPath != null) { avatars = Image.file( File(_myAvatarPath!), errorBuilder: errorBuilder, ); } else { avatars = const SvgPicture( AssetBytesLoader('assets/images/default_avatar.svg.vec'), ); } } else if (_avatarContacts.length == 1) { avatars = getAvatarForContact(_avatarContacts.first); } else if (_avatarContacts.length >= 2) { final a = getAvatarForContact(_avatarContacts.first); final b = getAvatarForContact(_avatarContacts[1]); if (_avatarContacts.length >= 3) { final c = getAvatarForContact(_avatarContacts[2]); avatars = Stack( children: [ Transform.translate( offset: const Offset(-15, 5), child: Transform.scale( scale: 0.8, child: c, ), ), Transform.translate( offset: const Offset(15, 5), child: Transform.scale( scale: 0.8, child: b, ), ), a, ], ); } else { avatars = Stack( children: [ Transform.translate( offset: const Offset(-10, 5), child: Transform.scale( scale: 0.8, child: b, ), ), Transform.translate(offset: const Offset(10, 0), child: a), ], ); } } else { avatars = const SvgPicture( AssetBytesLoader('assets/images/default_avatar.svg.vec'), ); } return Container( constraints: BoxConstraints( minHeight: 2 * (widget.fontSize ?? 20), minWidth: 2 * (widget.fontSize ?? 20), maxWidth: 2 * (widget.fontSize ?? 20), maxHeight: 2 * (widget.fontSize ?? 20), ), child: Center( child: ClipRRect( borderRadius: BorderRadius.circular(12), child: Container( height: proSize as double, width: proSize, color: widget.color, child: Center(child: avatars), ), ), ), ); } }