diff --git a/packages/neon/neon/lib/neon.dart b/packages/neon/neon/lib/neon.dart index 0de68ae2..6992799e 100644 --- a/packages/neon/neon/lib/neon.dart +++ b/packages/neon/neon/lib/neon.dart @@ -93,7 +93,6 @@ part 'src/utils/storage.dart'; part 'src/utils/stream_listenable.dart'; part 'src/utils/theme.dart'; part 'src/utils/validators.dart'; -part 'src/widgets/account_avatar.dart'; part 'src/widgets/account_settings_tile.dart'; part 'src/widgets/account_tile.dart'; part 'src/widgets/app_implementation_icon.dart'; @@ -109,6 +108,7 @@ part 'src/widgets/nextcloud_logo.dart'; part 'src/widgets/relative_time.dart'; part 'src/widgets/result_builder.dart'; part 'src/widgets/text_settings_tile.dart'; +part 'src/widgets/user_avatar.dart'; Future runNeon({ required final WidgetsBinding binding, diff --git a/packages/neon/neon/lib/src/pages/home.dart b/packages/neon/neon/lib/src/pages/home.dart index a3cc9f7b..3e165ad7 100644 --- a/packages/neon/neon/lib/src/pages/home.dart +++ b/packages/neon/neon/lib/src/pages/home.dart @@ -225,7 +225,7 @@ class _HomePageState extends State { }, tooltip: account.client.humanReadableID, icon: IntrinsicHeight( - child: NeonAccountAvatar( + child: NeonUserAvatar( account: account, ), ), @@ -496,7 +496,7 @@ class _HomePageState extends State { }, tooltip: AppLocalizations.of(context).settingsAccount, icon: IntrinsicWidth( - child: NeonAccountAvatar( + child: NeonUserAvatar( account: account, ), ), diff --git a/packages/neon/neon/lib/src/widgets/account_avatar.dart b/packages/neon/neon/lib/src/widgets/account_avatar.dart deleted file mode 100644 index 4cbf95fe..00000000 --- a/packages/neon/neon/lib/src/widgets/account_avatar.dart +++ /dev/null @@ -1,103 +0,0 @@ -part of '../../neon.dart'; - -const kAvatarSize = 40.0; - -class NeonAccountAvatar extends StatelessWidget { - const NeonAccountAvatar({ - required this.account, - super.key, - }); - - final Account account; - - @override - Widget build(final BuildContext context) { - final isDark = Theme.of(context).brightness == Brightness.dark; - final size = (kAvatarSize * MediaQuery.of(context).devicePixelRatio).toInt(); - final userStatusBloc = Provider.of(context, listen: false).getUserStatusBloc(account); - return SizedBox.square( - dimension: kAvatarSize, - child: Stack( - alignment: Alignment.center, - children: [ - CircleAvatar( - child: ClipOval( - child: NeonCachedApiImage( - account: account, - cacheKey: 'avatar-${account.id}-${isDark ? 'dark' : 'light'}$size', - download: () async { - if (isDark) { - return account.client.core.getDarkAvatar( - userId: account.username, - size: size, - ); - } else { - return account.client.core.getAvatar( - userId: account.username, - size: size, - ); - } - }, - ), - ), - ), - ResultBuilder( - stream: userStatusBloc.userStatus, - builder: (final context, final userStatus) { - final hasEmoji = userStatus.data?.icon != null; - final factor = hasEmoji ? 2 : 3; - return Align( - alignment: Alignment.bottomRight, - child: Container( - height: kAvatarSize / factor, - width: kAvatarSize / factor, - decoration: userStatus.loading || userStatus.error != null || userStatus.data == null || hasEmoji - ? null - : BoxDecoration( - shape: BoxShape.circle, - color: _userStatusToColor(userStatus.data!), - ), - child: userStatus.loading - ? CircularProgressIndicator( - strokeWidth: 1.5, - color: Theme.of(context).colorScheme.onPrimary, - ) - : userStatus.error != null && - (userStatus.error is! NextcloudApiException || - (userStatus.error! as NextcloudApiException).statusCode != 404) - ? NeonException( - userStatus.error, - onRetry: userStatusBloc.refresh, - onlyIcon: true, - iconSize: kAvatarSize / factor, - ) - : hasEmoji - ? Text( - userStatus.data!.icon!, - style: const TextStyle( - fontSize: 16, - ), - ) - : null, - ), - ); - }, - ), - ], - ), - ); - } - - Color _userStatusToColor(final NextcloudUserStatusStatus userStatus) { - switch (userStatus.status) { - case NextcloudUserStatusType.online: - return const Color(0xFF49B382); - case NextcloudUserStatusType.away: - return const Color(0xFFF4A331); - case NextcloudUserStatusType.dnd: - return const Color(0xFFED484C); - default: - return Colors.transparent; - } - } -} diff --git a/packages/neon/neon/lib/src/widgets/account_tile.dart b/packages/neon/neon/lib/src/widgets/account_tile.dart index b93dacb9..4da61494 100644 --- a/packages/neon/neon/lib/src/widgets/account_tile.dart +++ b/packages/neon/neon/lib/src/widgets/account_tile.dart @@ -34,7 +34,7 @@ class NeonAccountTile extends StatelessWidget { ) : null, leading: IntrinsicWidth( - child: NeonAccountAvatar( + child: NeonUserAvatar( account: account, ), ), diff --git a/packages/neon/neon/lib/src/widgets/user_avatar.dart b/packages/neon/neon/lib/src/widgets/user_avatar.dart new file mode 100644 index 00000000..c19285bc --- /dev/null +++ b/packages/neon/neon/lib/src/widgets/user_avatar.dart @@ -0,0 +1,126 @@ +// ignore_for_file: use_late_for_private_fields_and_variables +// ^ This is a really strange false positive, it goes of at a very random place without any meaning. Hopefully fixed soon? + +part of '../../neon.dart'; + +const kAvatarSize = 40.0; + +class NeonUserAvatar extends StatefulWidget { + NeonUserAvatar({ + required this.account, + final String? username, + this.showStatus = true, + this.size = kAvatarSize, + super.key, + }) : username = username ?? account.client.username!; + + final Account account; + final String username; + final bool showStatus; + final double size; + + @override + State createState() => _UserAvatarState(); +} + +class _UserAvatarState extends State { + late final _userStatusBloc = Provider.of(context, listen: false).getUserStatusesBloc(widget.account); + + @override + void initState() { + super.initState(); + + unawaited(_userStatusBloc.load(widget.username)); + } + + @override + Widget build(final BuildContext context) { + final isDark = Theme.of(context).brightness == Brightness.dark; + final size = (widget.size * MediaQuery.of(context).devicePixelRatio).toInt(); + return Stack( + alignment: Alignment.center, + children: [ + CircleAvatar( + radius: widget.size / 2, + child: ClipOval( + child: NeonCachedApiImage( + account: widget.account, + cacheKey: 'avatar-${widget.username}-${isDark ? 'dark' : 'light'}$size', + download: () async { + if (isDark) { + return widget.account.client.core.getDarkAvatar( + userId: widget.username, + size: size, + ); + } else { + return widget.account.client.core.getAvatar( + userId: widget.username, + size: size, + ); + } + }, + ), + ), + ), + if (widget.showStatus) ...[ + ResultBuilder( + stream: _userStatusBloc.statuses.map((final statuses) => statuses[widget.username]), + builder: _userStatusIconBuilder, + ), + ], + ], + ); + } + + Widget _userStatusIconBuilder(final BuildContext context, final Result result) { + final hasEmoji = result.data?.icon != null; + final scaledSize = widget.size / (hasEmoji ? 2 : 3); + + Widget? child; + Decoration? decoration; + if (result.loading) { + child = CircularProgressIndicator( + strokeWidth: 1.5, + color: Theme.of(context).colorScheme.onPrimary, + ); + } else if (result.error != null) { + child = Icon( + Icons.error_outline, + size: scaledSize, + color: Theme.of(context).colorScheme.error, + ); + } else if (hasEmoji) { + child = Text( + result.data!.icon!, + style: const TextStyle( + fontSize: 16, + ), + ); + } else if (result.data != null) { + decoration = BoxDecoration( + shape: BoxShape.circle, + color: _userStatusToColor(result.data!.status), + ); + } + + return SizedBox.square( + dimension: widget.size, + child: Align( + alignment: Alignment.bottomRight, + child: Container( + width: scaledSize, + height: scaledSize, + decoration: decoration, + child: child, + ), + ), + ); + } + + Color? _userStatusToColor(final NextcloudUserStatusType userStatusType) => switch (userStatusType) { + NextcloudUserStatusType.online => const Color(0xFF49B382), + NextcloudUserStatusType.away => const Color(0xFFF4A331), + NextcloudUserStatusType.dnd => const Color(0xFFED484C), + _ => null, + }; +}