feat: анимации с реанимации

This commit is contained in:
Jganenokk
2026-07-03 19:45:51 +07:00
parent 53125b60a3
commit 646e05dd27
4 changed files with 174 additions and 69 deletions
@@ -7,6 +7,7 @@ import '../../../core/cache/info_cache.dart';
import '../../../core/config/app_show_extra_info.dart';
import '../../../core/storage/app_database.dart';
import '../../../core/utils/format.dart';
import '../../widgets/avatar_hero.dart';
import '../../widgets/connection_status.dart';
import '../../widgets/glossy_pill.dart';
import '../../widgets/komet_avatar.dart';
@@ -263,12 +264,7 @@ class _ChatInfoScreenState extends State<ChatInfoScreen> {
crossAxisAlignment: CrossAxisAlignment.center,
children: [
const SizedBox(height: 4),
KometAvatar(
name: widget.name,
imageUrl: widget.imageUrl,
size: 96,
fontSize: 36,
),
_heroAvatar(),
const SizedBox(height: 14),
Text(
widget.name,
@@ -1104,6 +1100,20 @@ class _ChatInfoScreenState extends State<ChatInfoScreen> {
// ─── SHIMMER ─────────────────────────────────────────────────────────────
Widget _heroAvatar() {
return AvatarHero(
tag: 'chatAvatar_${widget.chatId}',
name: widget.name,
imageUrl: widget.imageUrl.isNotEmpty ? widget.imageUrl : null,
child: KometAvatar(
name: widget.name,
imageUrl: widget.imageUrl,
size: 96,
fontSize: 36,
),
);
}
Widget _buildShimmer(ColorScheme cs) {
Widget block(double w, double h, {double r = 8}) => Container(
width: w,
@@ -1117,7 +1127,14 @@ class _ChatInfoScreenState extends State<ChatInfoScreen> {
return ListView(
padding: const EdgeInsets.fromLTRB(16, 60, 16, 0),
children: [
Center(child: block(96, 96, r: 48)),
Center(
child: AvatarHero(
tag: 'chatAvatar_${widget.chatId}',
name: widget.name,
imageUrl: widget.imageUrl.isNotEmpty ? widget.imageUrl : null,
child: block(96, 96, r: 48),
),
),
const SizedBox(height: 14),
Center(child: block(160, 22, r: 8)),
const SizedBox(height: 8),
@@ -11,6 +11,7 @@ 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';
@@ -2300,6 +2301,28 @@ class _ChatListScreenState extends State<ChatListScreen>
messageItalic,
);
Widget avatarCircle = CircleAvatar(
radius: 24,
backgroundColor: cs.surfaceContainerHighest,
backgroundImage: imageUrl.isNotEmpty
? CachedNetworkImageProvider(imageUrl, maxWidth: 144, maxHeight: 144)
: null,
child: imageUrl.isEmpty
? Text(
name.isNotEmpty ? name[0].toUpperCase() : '?',
style: TextStyle(color: cs.onSurfaceVariant, fontSize: 20),
)
: 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: () {
@@ -2353,26 +2376,7 @@ class _ChatListScreenState extends State<ChatListScreen>
children: [
Stack(
children: [
CircleAvatar(
radius: 24,
backgroundColor: cs.surfaceContainerHighest,
backgroundImage: imageUrl.isNotEmpty
? CachedNetworkImageProvider(
imageUrl,
maxWidth: 144,
maxHeight: 144,
)
: null,
child: imageUrl.isEmpty
? Text(
name.isNotEmpty ? name[0].toUpperCase() : '?',
style: TextStyle(
color: cs.onSurfaceVariant,
fontSize: 20,
),
)
: null,
),
avatarCircle,
if (isSelected)
Positioned(
right: -2,
+57 -42
View File
@@ -54,6 +54,7 @@ import '../../../models/attachment.dart';
import '../../../models/sticker.dart';
import '../../commands/command_registry.dart';
import '../../commands/slash_command.dart';
import '../../widgets/avatar_hero.dart';
import '../../widgets/glossy_pill.dart';
import '../../widgets/rich_message_controller.dart';
import '../../../core/utils/text_format.dart';
@@ -2116,30 +2117,37 @@ class _ChatScreenState extends State<ChatScreen>
children: [
_withOnlineDot(
cs,
widget.imageUrl.isNotEmpty
? CircleAvatar(
radius: 22,
backgroundImage: CachedNetworkImageProvider(
widget.imageUrl,
maxWidth: 144,
maxHeight: 144,
),
)
: CircleAvatar(
radius: 22,
backgroundColor: cs.primaryContainer,
child: Text(
widget.name.isNotEmpty
? widget.name[0].toUpperCase()
: '?',
style: TextStyle(
color: cs.onPrimaryContainer,
fontSize: 16,
fontWeight: FontWeight.w600,
fontFamily: 'Outfit',
AvatarHero(
tag: 'chatAvatar_${widget.chatId}',
name: widget.name,
imageUrl: widget.imageUrl.isNotEmpty
? widget.imageUrl
: null,
child: widget.imageUrl.isNotEmpty
? CircleAvatar(
radius: 22,
backgroundImage: CachedNetworkImageProvider(
widget.imageUrl,
maxWidth: 144,
maxHeight: 144,
),
)
: CircleAvatar(
radius: 22,
backgroundColor: cs.primaryContainer,
child: Text(
widget.name.isNotEmpty
? widget.name[0].toUpperCase()
: '?',
style: TextStyle(
color: cs.onPrimaryContainer,
fontSize: 16,
fontWeight: FontWeight.w600,
fontFamily: 'Outfit',
),
),
),
),
),
),
const SizedBox(width: 12),
Expanded(
@@ -2276,28 +2284,35 @@ class _ChatScreenState extends State<ChatScreen>
children: [
_withOnlineDot(
cs,
widget.imageUrl.isNotEmpty
? CircleAvatar(
radius: 18,
backgroundImage: CachedNetworkImageProvider(
widget.imageUrl,
maxWidth: 144,
maxHeight: 144,
),
)
: CircleAvatar(
radius: 18,
backgroundColor: cs.primaryContainer,
child: Text(
widget.name.isNotEmpty
? widget.name[0].toUpperCase()
: '?',
style: TextStyle(
color: cs.onPrimaryContainer,
fontSize: 12,
AvatarHero(
tag: 'chatAvatar_${widget.chatId}',
name: widget.name,
imageUrl: widget.imageUrl.isNotEmpty
? widget.imageUrl
: null,
child: widget.imageUrl.isNotEmpty
? CircleAvatar(
radius: 18,
backgroundImage: CachedNetworkImageProvider(
widget.imageUrl,
maxWidth: 144,
maxHeight: 144,
),
)
: CircleAvatar(
radius: 18,
backgroundColor: cs.primaryContainer,
child: Text(
widget.name.isNotEmpty
? widget.name[0].toUpperCase()
: '?',
style: TextStyle(
color: cs.onPrimaryContainer,
fontSize: 12,
),
),
),
),
),
dotSize: 11,
),
const SizedBox(width: 12),
+69
View File
@@ -0,0 +1,69 @@
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,
),
),
),
),
),
);
}
}