diff --git a/lib/frontend/screens/chats/chat/view/chat_header.dart b/lib/frontend/screens/chats/chat/view/chat_header.dart index 3f3de97..4fb57f3 100644 --- a/lib/frontend/screens/chats/chat/view/chat_header.dart +++ b/lib/frontend/screens/chats/chat/view/chat_header.dart @@ -2,7 +2,6 @@ import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:cached_network_image/cached_network_image.dart'; import 'package:material_symbols_icons/symbols.dart'; -import 'package:komet/frontend/widgets/avatar_hero.dart'; import 'package:komet/frontend/widgets/glossy_pill.dart'; import 'package:komet/frontend/widgets/online_dot.dart'; @@ -90,33 +89,28 @@ class ChatHeaderRow extends StatelessWidget { children: [ _withOnlineDot( cs, - AvatarHero( - tag: 'chatAvatar_$chatId', - name: name, - imageUrl: imageUrl.isNotEmpty ? imageUrl : null, - child: imageUrl.isNotEmpty - ? CircleAvatar( - radius: 22, - backgroundImage: CachedNetworkImageProvider( - imageUrl, - maxWidth: 144, - maxHeight: 144, - ), - ) - : CircleAvatar( - radius: 22, - backgroundColor: cs.primaryContainer, - child: Text( - name.isNotEmpty ? name[0].toUpperCase() : '?', - style: TextStyle( - color: cs.onPrimaryContainer, - fontSize: 16, - fontWeight: FontWeight.w600, - fontFamily: 'Outfit', - ), + imageUrl.isNotEmpty + ? CircleAvatar( + radius: 22, + backgroundImage: CachedNetworkImageProvider( + imageUrl, + maxWidth: 144, + maxHeight: 144, + ), + ) + : CircleAvatar( + radius: 22, + backgroundColor: cs.primaryContainer, + child: Text( + name.isNotEmpty ? name[0].toUpperCase() : '?', + style: TextStyle( + color: cs.onPrimaryContainer, + fontSize: 16, + fontWeight: FontWeight.w600, + fontFamily: 'Outfit', ), ), - ), + ), ), const SizedBox(width: 12), Expanded( @@ -195,7 +189,11 @@ class ChatHeaderRow extends StatelessWidget { ), if (showCall) IconButton( - icon: Icon(Symbols.call, weight: 500, color: cs.onSurface), + icon: Icon( + Symbols.call, + weight: 500, + color: cs.onSurface, + ), onPressed: onCall, ), Builder( @@ -244,31 +242,26 @@ class ChatHeaderRow extends StatelessWidget { children: [ _withOnlineDot( cs, - AvatarHero( - tag: 'chatAvatar_$chatId', - name: name, - imageUrl: imageUrl.isNotEmpty ? imageUrl : null, - child: imageUrl.isNotEmpty - ? CircleAvatar( - radius: 18, - backgroundImage: CachedNetworkImageProvider( - imageUrl, - maxWidth: 144, - maxHeight: 144, - ), - ) - : CircleAvatar( - radius: 18, - backgroundColor: cs.primaryContainer, - child: Text( - name.isNotEmpty ? name[0].toUpperCase() : '?', - style: TextStyle( - color: cs.onPrimaryContainer, - fontSize: 12, - ), + imageUrl.isNotEmpty + ? CircleAvatar( + radius: 18, + backgroundImage: CachedNetworkImageProvider( + imageUrl, + maxWidth: 144, + maxHeight: 144, + ), + ) + : CircleAvatar( + radius: 18, + backgroundColor: cs.primaryContainer, + child: Text( + name.isNotEmpty ? name[0].toUpperCase() : '?', + style: TextStyle( + color: cs.onPrimaryContainer, + fontSize: 12, ), ), - ), + ), dotSize: 11, ), const SizedBox(width: 12), diff --git a/lib/frontend/screens/chats/chat_info_screen.dart b/lib/frontend/screens/chats/chat_info_screen.dart index fdaf2fd..7627c01 100644 --- a/lib/frontend/screens/chats/chat_info_screen.dart +++ b/lib/frontend/screens/chats/chat_info_screen.dart @@ -10,7 +10,6 @@ import '../../../core/utils/format.dart'; import '../../../l10n/app_localizations.dart'; import '../../../models/chat_info.dart'; import '../../../models/contact_info.dart'; -import '../../widgets/avatar_hero.dart'; import '../../widgets/connection_status.dart'; import '../../widgets/glossy_pill.dart'; import '../../widgets/komet_avatar.dart'; @@ -58,7 +57,6 @@ class ChatInfoScreen extends StatefulWidget { class _ChatInfoScreenState extends State { final _tabScrollController = ScrollController(); - final _avatarHeroKey = GlobalKey(); int _myId = 0; bool _isLoading = true; @@ -255,7 +253,7 @@ class _ChatInfoScreenState extends State { crossAxisAlignment: CrossAxisAlignment.center, children: [ const SizedBox(height: 4), - _heroAvatar(), + _avatar(), const SizedBox(height: 14), Text( widget.name, @@ -1131,18 +1129,12 @@ class _ChatInfoScreenState extends State { ); } - Widget _heroAvatar() { - return AvatarHero( - key: _avatarHeroKey, - tag: 'chatAvatar_${widget.chatId}', + Widget _avatar() { + return KometAvatar( name: widget.name, - imageUrl: widget.imageUrl.isNotEmpty ? widget.imageUrl : null, - child: KometAvatar( - name: widget.name, - imageUrl: widget.imageUrl, - size: 96, - fontSize: 36, - ), + imageUrl: widget.imageUrl, + size: 96, + fontSize: 36, ); } @@ -1159,15 +1151,7 @@ class _ChatInfoScreenState extends State { return ListView( padding: const EdgeInsets.fromLTRB(16, 60, 16, 0), children: [ - Center( - child: AvatarHero( - key: _avatarHeroKey, - tag: 'chatAvatar_${widget.chatId}', - name: widget.name, - imageUrl: widget.imageUrl.isNotEmpty ? widget.imageUrl : null, - child: block(96, 96, r: 48), - ), - ), + Center(child: block(96, 96, r: 48)), const SizedBox(height: 14), Center(child: block(160, 22, r: 8)), const SizedBox(height: 8), diff --git a/lib/frontend/screens/chats/chat_list_screen.dart b/lib/frontend/screens/chats/chat_list_screen.dart index ff81f91..8113a5d 100644 --- a/lib/frontend/screens/chats/chat_list_screen.dart +++ b/lib/frontend/screens/chats/chat_list_screen.dart @@ -10,7 +10,6 @@ import 'chat_screen.dart'; import 'search_screen.dart'; import 'create_group_flow.dart'; import '../../widgets/adaptive_shell.dart'; -import '../../widgets/avatar_hero.dart'; import '../../widgets/online_dot.dart'; import '../../widgets/custom_notification.dart'; import '../../widgets/glossy_pill.dart'; @@ -2252,7 +2251,7 @@ class _ChatListScreenState extends State messageItalic, ); - Widget avatarCircle = CircleAvatar( + final Widget avatarCircle = CircleAvatar( radius: 24, backgroundColor: cs.surfaceContainerHighest, backgroundImage: imageUrl.isNotEmpty @@ -2269,15 +2268,6 @@ class _ChatListScreenState extends State ) : null, ); - if (widget.onChatSelected == null) { - avatarCircle = AvatarHero( - tag: 'chatAvatar_$id', - name: name, - imageUrl: imageUrl.isNotEmpty ? imageUrl : null, - child: avatarCircle, - ); - } - return InkWell( key: ValueKey('chat_$id'), onTap: () { diff --git a/lib/frontend/widgets/avatar_hero.dart b/lib/frontend/widgets/avatar_hero.dart deleted file mode 100644 index 1c94132..0000000 --- a/lib/frontend/widgets/avatar_hero.dart +++ /dev/null @@ -1,69 +0,0 @@ -import 'package:cached_network_image/cached_network_image.dart'; -import 'package:flutter/material.dart'; - -class AvatarHero extends StatelessWidget { - final Object tag; - final String name; - final String? imageUrl; - final Widget child; - - const AvatarHero({ - super.key, - required this.tag, - required this.name, - required this.imageUrl, - required this.child, - }); - - @override - Widget build(BuildContext context) { - return Hero( - tag: tag, - flightShuttleBuilder: (context, animation, direction, fromContext, toContext) => - _AvatarHeroFlight(name: name, imageUrl: imageUrl), - child: child, - ); - } -} - -class _AvatarHeroFlight extends StatelessWidget { - final String name; - final String? imageUrl; - - const _AvatarHeroFlight({required this.name, required this.imageUrl}); - - @override - Widget build(BuildContext context) { - final cs = Theme.of(context).colorScheme; - final url = imageUrl; - final hasImage = url != null && url.isNotEmpty; - return Container( - clipBehavior: Clip.antiAlias, - decoration: BoxDecoration( - shape: BoxShape.circle, - color: cs.primaryContainer, - ), - child: hasImage - ? Image( - image: CachedNetworkImageProvider(url), - fit: BoxFit.cover, - gaplessPlayback: true, - ) - : Center( - child: FittedBox( - fit: BoxFit.scaleDown, - child: Padding( - padding: const EdgeInsets.all(10), - child: Text( - name.isNotEmpty ? name[0].toUpperCase() : '?', - style: TextStyle( - color: cs.onPrimaryContainer, - fontWeight: FontWeight.bold, - ), - ), - ), - ), - ), - ); - } -} diff --git a/lib/frontend/widgets/sticker_pack_sheet.dart b/lib/frontend/widgets/sticker_pack_sheet.dart index 67c1942..0c718f2 100644 --- a/lib/frontend/widgets/sticker_pack_sheet.dart +++ b/lib/frontend/widgets/sticker_pack_sheet.dart @@ -275,6 +275,7 @@ class _StickerPackSheetState extends State<_StickerPackSheet> { peekId: item.id, url: item.url, lottieUrl: item.lottieUrl, + tags: item.tags, child: Padding( padding: const EdgeInsets.all(6), child: StickerImage( diff --git a/lib/frontend/widgets/sticker_peek.dart b/lib/frontend/widgets/sticker_peek.dart index a32147c..d744013 100644 --- a/lib/frontend/widgets/sticker_peek.dart +++ b/lib/frontend/widgets/sticker_peek.dart @@ -9,8 +9,9 @@ import 'sticker_image.dart'; class _PeekData { final String? url; final String? lottieUrl; + final List tags; - const _PeekData(this.url, this.lottieUrl); + const _PeekData(this.url, this.lottieUrl, this.tags); } class StickerPeekScope extends StatefulWidget { @@ -71,7 +72,11 @@ class StickerPeekScopeState extends State final cell = _hitTest(globalPos); if (cell == null) return; _currentId = cell.widget.peekId; - _current.value = _PeekData(cell.widget.url, cell.widget.lottieUrl); + _current.value = _PeekData( + cell.widget.url, + cell.widget.lottieUrl, + cell.widget.tags, + ); _showEntry(); _anim.forward(from: 0); Haptics.medium(); @@ -82,7 +87,11 @@ class StickerPeekScopeState extends State final cell = _hitTest(globalPos); if (cell == null || cell.widget.peekId == _currentId) return; _currentId = cell.widget.peekId; - _current.value = _PeekData(cell.widget.url, cell.widget.lottieUrl); + _current.value = _PeekData( + cell.widget.url, + cell.widget.lottieUrl, + cell.widget.tags, + ); Haptics.selection(); } @@ -137,6 +146,7 @@ class StickerPeekable extends StatefulWidget { final Object peekId; final String? url; final String? lottieUrl; + final List tags; final Widget child; const StickerPeekable({ @@ -144,6 +154,7 @@ class StickerPeekable extends StatefulWidget { required this.peekId, this.url, this.lottieUrl, + this.tags = const [], required this.child, }); @@ -214,14 +225,23 @@ class _PeekOverlay extends StatelessWidget { valueListenable: data, builder: (context, d, _) { if (d == null) return const SizedBox.shrink(); - return SizedBox( - width: previewSize, - height: previewSize, - child: StickerImage( - url: d.url, - lottieUrl: d.lottieUrl, - size: previewSize, - ), + return Column( + mainAxisSize: MainAxisSize.min, + children: [ + if (d.tags.isNotEmpty) ...[ + _PeekTags(tags: d.tags), + const SizedBox(height: 18), + ], + SizedBox( + width: previewSize, + height: previewSize, + child: StickerImage( + url: d.url, + lottieUrl: d.lottieUrl, + size: previewSize, + ), + ), + ], ); }, ), @@ -235,3 +255,23 @@ class _PeekOverlay extends StatelessWidget { ); } } + +class _PeekTags extends StatelessWidget { + final List tags; + + const _PeekTags({required this.tags}); + + @override + Widget build(BuildContext context) { + return Row( + mainAxisSize: MainAxisSize.min, + children: [ + for (final tag in tags) + Padding( + padding: const EdgeInsets.symmetric(horizontal: 6), + child: Text(tag, style: const TextStyle(fontSize: 28)), + ), + ], + ); + } +} diff --git a/lib/models/sticker.dart b/lib/models/sticker.dart index 9ef51a3..4d038fb 100644 --- a/lib/models/sticker.dart +++ b/lib/models/sticker.dart @@ -38,6 +38,7 @@ class StickerItem { final int? setId; final int? width; final int? height; + final List tags; const StickerItem({ required this.id, @@ -46,6 +47,7 @@ class StickerItem { this.setId, this.width, this.height, + this.tags = const [], }); bool get isAnimated => lottieUrl != null && lottieUrl!.isNotEmpty; @@ -57,5 +59,16 @@ class StickerItem { setId: map['setId'] as int?, width: map['width'] as int?, height: map['height'] as int?, + tags: _parseTags(map['tags']), ); + + static List _parseTags(dynamic raw) { + if (raw is! List) return const []; + final result = []; + for (final e in raw) { + final s = e?.toString(); + if (s != null && s.isNotEmpty) result.add(s); + } + return result; + } }