From 58577bb9e8c65df64f1dd44ba908372e742f928e Mon Sep 17 00:00:00 2001 From: Krille Date: Wed, 6 Nov 2024 14:10:31 +0100 Subject: [PATCH] refactor: Performance boost for avatar widget --- lib/widgets/avatar.dart | 211 +++++++++++++++--------------- lib/widgets/presence_builder.dart | 56 +++++--- 2 files changed, 148 insertions(+), 119 deletions(-) diff --git a/lib/widgets/avatar.dart b/lib/widgets/avatar.dart index c6bcaf9f1..ecd137973 100644 --- a/lib/widgets/avatar.dart +++ b/lib/widgets/avatar.dart @@ -19,7 +19,7 @@ class Avatar extends StatelessWidget { final IconData? icon; final BorderSide? border; - const Avatar({ + Avatar({ this.mxContent, this.name, this.size = defaultSize, @@ -31,122 +31,129 @@ class Avatar extends StatelessWidget { this.border, this.icon, super.key, - }); + }) : fallbackLetters = name?.firstTwoCharsOrFallback ?? '@', + textColor = name?.lightColorAvatar, + noPic = mxContent == null || + mxContent.toString().isEmpty || + mxContent.toString() == 'null'; + + final String fallbackLetters; + final Color? textColor; + final bool noPic; @override Widget build(BuildContext context) { final theme = Theme.of(context); - var fallbackLetters = '@'; - final name = this.name; - if (name != null) { - if (name.runes.length >= 2) { - fallbackLetters = String.fromCharCodes(name.runes, 0, 2); - } else if (name.runes.length == 1) { - fallbackLetters = name; - } - } - final noPic = mxContent == null || - mxContent.toString().isEmpty || - mxContent.toString() == 'null'; - final textColor = name?.lightColorAvatar; - final textWidget = Container( - color: textColor, - alignment: Alignment.center, - child: Text( - fallbackLetters, - style: TextStyle( - color: Colors.white, - fontWeight: FontWeight.bold, - fontSize: (size / 3).roundToDouble(), - ), - ), - ); final borderRadius = this.borderRadius ?? BorderRadius.circular(size / 2); final presenceUserId = this.presenceUserId; - final container = Stack( - children: [ - SizedBox( - width: size, - height: size, - child: Material( - color: theme.brightness == Brightness.light - ? Colors.white - : Colors.black, - shape: RoundedRectangleBorder( - borderRadius: borderRadius, - side: border ?? BorderSide.none, - ), - clipBehavior: Clip.hardEdge, - child: noPic - ? textWidget - : MxcImage( - client: client, - key: ValueKey(mxContent.toString()), - cacheKey: '${mxContent}_$size', - uri: mxContent, - fit: BoxFit.cover, - width: size, - height: size, - placeholder: (_) => Center( - child: Icon( - Icons.person_2, - color: theme.colorScheme.tertiary, - size: size / 1.5, + + return InkWell( + onTap: onTap, + borderRadius: borderRadius, + child: Stack( + children: [ + SizedBox( + width: size, + height: size, + child: Material( + color: theme.brightness == Brightness.light + ? Colors.white + : Colors.black, + shape: RoundedRectangleBorder( + borderRadius: borderRadius, + side: border ?? BorderSide.none, + ), + clipBehavior: Clip.hardEdge, + child: noPic + ? Container( + color: textColor, + alignment: Alignment.center, + child: Text( + fallbackLetters, + style: TextStyle( + color: Colors.white, + fontWeight: FontWeight.bold, + fontSize: (size / 3).roundToDouble(), + ), + ), + ) + : MxcImage( + client: client, + key: ValueKey(mxContent.toString()), + cacheKey: '${mxContent}_$size', + uri: mxContent, + fit: BoxFit.cover, + width: size, + height: size, + placeholder: (_) => Center( + child: Icon( + Icons.person_2, + color: theme.colorScheme.tertiary, + size: size / 1.5, + ), ), ), - ), + ), ), - ), - if (presenceUserId != null) - PresenceBuilder( - client: client, - userId: presenceUserId, - builder: (context, presence) { - if (presence == null || - (presence.presence == PresenceType.offline && - presence.lastActiveTimestamp == null)) { - return const SizedBox.shrink(); - } - final dotColor = presence.presence.isOnline - ? Colors.green - : presence.presence.isUnavailable - ? Colors.orange - : Colors.grey; - return Positioned( - bottom: -3, - right: -3, - child: Container( - width: 16, - height: 16, - decoration: BoxDecoration( - color: presenceBackgroundColor ?? theme.colorScheme.surface, - borderRadius: BorderRadius.circular(32), - ), - alignment: Alignment.center, + if (presenceUserId != null) + PresenceBuilder( + client: client, + userId: presenceUserId, + builder: (context, presence) { + if (presence == null || + (presence.presence == PresenceType.offline && + presence.lastActiveTimestamp == null)) { + return const SizedBox.shrink(); + } + final dotColor = presence.presence.isOnline + ? Colors.green + : presence.presence.isUnavailable + ? Colors.orange + : Colors.grey; + return Positioned( + bottom: -3, + right: -3, child: Container( - width: 10, - height: 10, + width: 16, + height: 16, decoration: BoxDecoration( - color: dotColor, - borderRadius: BorderRadius.circular(16), - border: Border.all( - width: 1, - color: theme.colorScheme.surface, + color: + presenceBackgroundColor ?? theme.colorScheme.surface, + borderRadius: BorderRadius.circular(32), + ), + alignment: Alignment.center, + child: Container( + width: 10, + height: 10, + decoration: BoxDecoration( + color: dotColor, + borderRadius: BorderRadius.circular(16), + border: Border.all( + width: 1, + color: theme.colorScheme.surface, + ), ), ), ), - ), - ); - }, - ), - ], - ); - if (onTap == null) return container; - return InkWell( - onTap: onTap, - borderRadius: borderRadius, - child: container, + ); + }, + ), + ], + ), ); } } + +extension on String { + String get firstTwoCharsOrFallback { + var fallbackLetters = '@'; + if (runes.length >= 2) { + fallbackLetters = String.fromCharCodes(runes, 0, 2); + } else if (runes.length == 1) { + fallbackLetters = this; + } + + return fallbackLetters; + } +} diff --git a/lib/widgets/presence_builder.dart b/lib/widgets/presence_builder.dart index 2ca963c84..58964ab2c 100644 --- a/lib/widgets/presence_builder.dart +++ b/lib/widgets/presence_builder.dart @@ -1,10 +1,12 @@ +import 'dart:async'; + import 'package:flutter/material.dart'; import 'package:matrix/matrix.dart'; import 'package:fluffychat/widgets/matrix.dart'; -class PresenceBuilder extends StatelessWidget { +class PresenceBuilder extends StatefulWidget { final Widget Function(BuildContext context, CachedPresence? presence) builder; final String? userId; final Client? client; @@ -17,21 +19,41 @@ class PresenceBuilder extends StatelessWidget { }); @override - Widget build(BuildContext context) { - final userId = this.userId; - if (userId == null) return builder(context, null); - - final client = this.client ?? Matrix.of(context).client; - return FutureBuilder( - future: client.fetchCurrentPresence(userId), - builder: (context, cachedPresenceSnapshot) => StreamBuilder( - stream: client.onPresenceChanged.stream - .where((cachedPresence) => cachedPresence.userid == userId), - builder: (context, snapshot) => builder( - context, - snapshot.data ?? cachedPresenceSnapshot.data, - ), - ), - ); + State createState() => _PresenceBuilderState(); +} + +class _PresenceBuilderState extends State { + CachedPresence? _presence; + StreamSubscription? _sub; + + @override + void initState() { + final client = widget.client ?? Matrix.of(context).client; + final userId = widget.userId; + if (userId != null) { + WidgetsBinding.instance.addPostFrameCallback((_) async { + final presence = await client.fetchCurrentPresence(userId); + setState(() { + _presence = presence; + _sub = client.onPresenceChanged.stream.listen((presence) { + if (!mounted) return; + setState(() { + _presence = presence; + }); + }); + }); + }); + } + + super.initState(); } + + @override + void dispose() { + _sub?.cancel(); + super.dispose(); + } + + @override + Widget build(BuildContext context) => widget.builder(context, _presence); }