From 9daf2753107ffceee4947362fcc062de15fce79e Mon Sep 17 00:00:00 2001 From: otsmr Date: Tue, 16 Jun 2026 10:30:22 +0200 Subject: [PATCH] fix svg performance issue --- lib/src/utils/avatars.dart | 27 +++++++--- .../visual/components/avatar_icon.comp.dart | 54 ++++++++++++------- .../components/profile_qr_code.comp.dart | 21 ++++++-- 3 files changed, 73 insertions(+), 29 deletions(-) diff --git a/lib/src/utils/avatars.dart b/lib/src/utils/avatars.dart index aa5d0f6e..255a7253 100644 --- a/lib/src/utils/avatars.dart +++ b/lib/src/utils/avatars.dart @@ -60,10 +60,26 @@ File avatarPNGFile(int contactId) { return File('${avatarsDirectory.path}/$contactId.png'); } -Future getUserAvatar() async { +File currentUserAvatarFile(int avatarCounter) { + final avatarsDirectory = Directory( + '${AppEnvironment.cacheDir}/avatars', + ); + + if (!avatarsDirectory.existsSync()) { + avatarsDirectory.createSync(recursive: true); + } + return File('${avatarsDirectory.path}/user_$avatarCounter.png'); +} + +Future getUserAvatar() async { if (userService.currentUser.avatarSvg == null) { - final data = await rootBundle.load('assets/images/default_avatar.png'); - return data.buffer.asUint8List(); + return null; + } + + final avatarCounter = userService.currentUser.avatarCounter; + final file = currentUserAvatarFile(avatarCounter); + if (file.existsSync()) { + return file.path; } final pictureInfo = await vg.loadPicture( @@ -76,9 +92,8 @@ Future getUserAvatar() async { final byteData = await image.toByteData(format: ui.ImageByteFormat.png); final pngBytes = byteData!.buffer.asUint8List(); - final file = avatarPNGFile(userService.currentUser.userId) - ..writeAsBytesSync(pngBytes); + await file.writeAsBytes(pngBytes); pictureInfo.picture.dispose(); - return file.readAsBytesSync(); + return file.path; } diff --git a/lib/src/visual/components/avatar_icon.comp.dart b/lib/src/visual/components/avatar_icon.comp.dart index 1d3e2116..eb23a144 100644 --- a/lib/src/visual/components/avatar_icon.comp.dart +++ b/lib/src/visual/components/avatar_icon.comp.dart @@ -1,4 +1,5 @@ import 'dart:async'; +import 'dart:io'; import 'package:flutter/material.dart'; import 'package:flutter_svg/svg.dart'; @@ -29,7 +30,7 @@ class AvatarIcon extends StatefulWidget { class _AvatarIconState extends State { List _avatarContacts = []; - String? _avatarSvg; + String? _myAvatarPath; StreamSubscription>? groupStream; StreamSubscription>? contactsStream; @@ -101,19 +102,9 @@ class _AvatarIconState extends State { }); } else if (widget.myAvatar) { _userSub = userService.onUserUpdated.listen((_) { - if (mounted) { - setState(() { - if (userService.currentUser.avatarSvg != null) { - _avatarSvg = userService.currentUser.avatarSvg; - } else { - _avatarContacts = []; - } - }); - } + unawaited(_updateMyAvatar()); }); - if (userService.currentUser.avatarSvg != null) { - _avatarSvg = userService.currentUser.avatarSvg; - } + unawaited(_updateMyAvatar()); } else if (widget.contactId != null) { contactStream = twonlyDB.contactsDao .watchContact(widget.contactId!) @@ -127,17 +118,44 @@ class _AvatarIconState extends State { 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 (_avatarSvg != null) { - avatars = SvgPicture.string( - _avatarSvg!, - errorBuilder: errorBuilder, - ); + 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) { diff --git a/lib/src/visual/components/profile_qr_code.comp.dart b/lib/src/visual/components/profile_qr_code.comp.dart index 86c05c7a..5c194703 100644 --- a/lib/src/visual/components/profile_qr_code.comp.dart +++ b/lib/src/visual/components/profile_qr_code.comp.dart @@ -1,5 +1,6 @@ -import 'dart:typed_data'; +import 'dart:io'; import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; import 'package:qr_flutter/qr_flutter.dart'; import 'package:twonly/src/utils/avatars.dart'; import 'package:twonly/src/utils/misc.dart'; @@ -32,11 +33,20 @@ class _ProfileQrCodeCompState extends State { Future _loadData() async { final qr = await QrCodeUtils.publicProfileLink(); - final avatar = widget.showAvatar ? await getUserAvatar() : null; + Uint8List? avatarBytes; + if (widget.showAvatar) { + final avatarPath = await getUserAvatar(); + if (avatarPath != null) { + avatarBytes = await File(avatarPath).readAsBytes(); + } else { + final data = await rootBundle.load('assets/images/default_avatar.png'); + avatarBytes = data.buffer.asUint8List(); + } + } if (mounted) { setState(() { _qrCode = qr; - _userAvatar = avatar; + _userAvatar = avatarBytes; _isLoading = false; }); } @@ -53,7 +63,6 @@ class _ProfileQrCodeCompState extends State { child: loaded ? Container( key: const ValueKey('qr_code_container'), - // padding: const EdgeInsets.all(3), decoration: BoxDecoration( color: context.color.primary, borderRadius: BorderRadius.circular(12), @@ -79,7 +88,9 @@ class _ProfileQrCodeCompState extends State { borderRadius: 2, ), gapless: false, - embeddedImage: (widget.showAvatar && _userAvatar != null) ? MemoryImage(_userAvatar!) : null, + embeddedImage: (widget.showAvatar && _userAvatar != null) + ? MemoryImage(_userAvatar!) + : null, embeddedImageStyle: QrEmbeddedImageStyle( size: const Size(60, 66), embeddedImageShape: EmbeddedImageShape.square,