From 58dd0279148e5565137f6d30fd84388633718482 Mon Sep 17 00:00:00 2001 From: Jganenokk Date: Sun, 12 Jul 2026 13:21:40 +0700 Subject: [PATCH] =?UTF-8?q?opt:=20=D0=BE=D0=BF=D1=82=D0=B8=D0=BC=D0=B8?= =?UTF-8?q?=D0=B7=D0=B0=D1=86=D0=B8=D1=8F=20=D0=B0=D0=BD=D0=B8=D0=BC=D0=BE?= =?UTF-8?q?=D0=B4=D0=B6=D0=B8=20=D0=BF=D1=80=D0=B8=20=D0=BE=D0=BA=D1=82?= =?UTF-8?q?=D1=80=D1=8B=D1=82=D0=B8=D0=B8=20=D0=BF=D0=B0=D0=BD=D0=B5=D0=BB?= =?UTF-8?q?=D0=B8=20=D0=B8=20=D0=B7=D0=B0=D1=85=D0=BE=D0=B4=D0=B5=20=D0=B2?= =?UTF-8?q?=20=D1=87=D0=B0=D1=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../chats/chat/sticker_panel_controller.dart | 9 +++++ .../chats/chat/view/sticker_panel_view.dart | 12 ++++--- lib/frontend/screens/chats/chat_screen.dart | 9 ++++- lib/frontend/widgets/lottie_image.dart | 35 +++++++++++++++++-- 4 files changed, 57 insertions(+), 8 deletions(-) diff --git a/lib/frontend/screens/chats/chat/sticker_panel_controller.dart b/lib/frontend/screens/chats/chat/sticker_panel_controller.dart index 2667a09..4f403a6 100644 --- a/lib/frontend/screens/chats/chat/sticker_panel_controller.dart +++ b/lib/frontend/screens/chats/chat/sticker_panel_controller.dart @@ -14,6 +14,7 @@ class StickerPanelController { duration: const Duration(milliseconds: 240), reverseDuration: const Duration(milliseconds: 200), ); + anim.addStatusListener(_onAnimStatus); showPanel.addListener(_onToggle); } @@ -21,11 +22,17 @@ class StickerPanelController { late final AnimationController anim; final ValueNotifier showPanel = ValueNotifier(false); + final ValueNotifier panelHold = ValueNotifier(true); double panelHeight = 300; Timer? _typingTimer; void hide() => showPanel.value = false; + void _onAnimStatus(AnimationStatus status) { + final held = status != AnimationStatus.completed; + if (panelHold.value != held) panelHold.value = held; + } + void _onToggle() { if (showPanel.value) { anim.forward(); @@ -50,7 +57,9 @@ class StickerPanelController { void dispose() { _typingTimer?.cancel(); showPanel.removeListener(_onToggle); + anim.removeStatusListener(_onAnimStatus); anim.dispose(); showPanel.dispose(); + panelHold.dispose(); } } diff --git a/lib/frontend/screens/chats/chat/view/sticker_panel_view.dart b/lib/frontend/screens/chats/chat/view/sticker_panel_view.dart index f61cce8..374857f 100644 --- a/lib/frontend/screens/chats/chat/view/sticker_panel_view.dart +++ b/lib/frontend/screens/chats/chat/view/sticker_panel_view.dart @@ -1,6 +1,7 @@ import 'package:flutter/material.dart'; import 'package:komet/frontend/screens/chats/chat/sticker_panel_controller.dart'; +import 'package:komet/frontend/widgets/lottie_image.dart'; import 'package:komet/frontend/widgets/sticker_panel.dart'; import 'package:komet/models/animoji.dart'; import 'package:komet/models/sticker.dart'; @@ -21,10 +22,13 @@ class StickerPanelView extends StatelessWidget { Widget build(BuildContext context) { return AnimatedBuilder( animation: stickers.anim, - child: StickerPanel( - height: stickers.panelHeight, - onStickerTap: onStickerTap, - onEmojiTap: onEmojiTap, + child: LottieHoldScope( + isHeld: stickers.panelHold, + child: StickerPanel( + height: stickers.panelHeight, + onStickerTap: onStickerTap, + onEmojiTap: onEmojiTap, + ), ), builder: (context, child) { final t = Curves.easeOutCubic.transform( diff --git a/lib/frontend/screens/chats/chat_screen.dart b/lib/frontend/screens/chats/chat_screen.dart index ff7c375..51a8af0 100644 --- a/lib/frontend/screens/chats/chat_screen.dart +++ b/lib/frontend/screens/chats/chat_screen.dart @@ -79,6 +79,7 @@ import '../../widgets/confirm_dialog.dart'; import '../../widgets/connection_status.dart'; import '../../widgets/message_bubble.dart'; import '../../widgets/message_actions_overlay.dart'; +import '../../widgets/lottie_image.dart'; import '../../widgets/attachment_panel.dart'; import '../../widgets/attachment/attachment_sheet.dart'; import '../../widgets/sticker_pack_sheet.dart'; @@ -465,6 +466,7 @@ class _ChatScreenState extends State final Map _separatorKeys = {}; String? _lastSentId; final ValueNotifier _otherUnread = ValueNotifier(0); + final ValueNotifier _animojiHold = ValueNotifier(true); final ValueNotifier> _selectedIds = ValueNotifier(const {}); late final AnimationController _selectionAnim; @@ -740,6 +742,7 @@ class _ChatScreenState extends State } void _kickoffHistory() { + _animojiHold.value = false; if (_historyKickedOff) return; _historyKickedOff = true; _shimmerStartTimer = Timer(const Duration(milliseconds: 150), () { @@ -1312,6 +1315,7 @@ class _ChatScreenState extends State WidgetsBinding.instance.removeObserver(this); chats.chatsChanged.removeListener(_onChatsBump); _otherUnread.dispose(); + _animojiHold.dispose(); _saveDraft(); _messageController.removeListener(_onTextChanged); _scrollController.removeListener(_onScrollForDate); @@ -3990,7 +3994,10 @@ class _ChatScreenState extends State ), child: AnimatedBuilder( animation: _searchAnim, - child: underlap ? _buildUnderlapBody() : _buildColorBody(), + child: LottieHoldScope( + isHeld: _animojiHold, + child: underlap ? _buildUnderlapBody() : _buildColorBody(), + ), builder: (context, body) => Scaffold( backgroundColor: cs.surface, extendBodyBehindAppBar: underlap, diff --git a/lib/frontend/widgets/lottie_image.dart b/lib/frontend/widgets/lottie_image.dart index fea63fd..5192010 100644 --- a/lib/frontend/widgets/lottie_image.dart +++ b/lib/frontend/widgets/lottie_image.dart @@ -64,6 +64,24 @@ class LottieScrollScope extends InheritedWidget { !identical(oldWidget.isScrolling, isScrolling); } +class LottieHoldScope extends InheritedWidget { + final ValueListenable isHeld; + + const LottieHoldScope({ + super.key, + required this.isHeld, + required super.child, + }); + + static ValueListenable? of(BuildContext context) => context + .dependOnInheritedWidgetOfExactType() + ?.isHeld; + + @override + bool updateShouldNotify(LottieHoldScope oldWidget) => + !identical(oldWidget.isHeld, isHeld); +} + class LottiePlayer extends StatefulWidget { final String lottieUrl; final String? fallbackUrl; @@ -97,6 +115,7 @@ class _LottiePlayerState extends State late final bool _native; RlottieClip? _clip; ValueListenable? _scrollState; + ValueListenable? _holdState; int? _px; bool _started = false; bool _showedFrames = false; @@ -110,8 +129,10 @@ class _LottiePlayerState extends State double? _lastElapsedMs; bool get _isScrolling => _scrollState?.value ?? false; + bool get _isHeld => _holdState?.value ?? false; bool get _canLoad => !_isScrolling && + !_isHeld && (widget.eager || !LottieLoadGovernor.instance.throttled.value); @override @@ -131,6 +152,12 @@ class _LottiePlayerState extends State _scrollState = state; _scrollState?.addListener(_onGateChanged); } + final hold = LottieHoldScope.of(context); + if (!identical(hold, _holdState)) { + _holdState?.removeListener(_onGateChanged); + _holdState = hold; + _holdState?.addListener(_onGateChanged); + } } @override @@ -155,6 +182,7 @@ class _LottiePlayerState extends State _deferTimer?.cancel(); LottieLoadGovernor.instance.throttled.removeListener(_onGateChanged); _scrollState?.removeListener(_onGateChanged); + _holdState?.removeListener(_onGateChanged); _ticker.dispose(); _releaseClip(); _frameIndex.dispose(); @@ -232,15 +260,16 @@ class _LottiePlayerState extends State if (_started) return; if (_canLoad) { _startLoad(); - } else if (!_isScrolling) { - // Blocked only by the frame-time governor: defer, but never starve. + } else if (!_isScrolling && !_isHeld) { _deferTimer ??= Timer(_maxLoadDefer, _forceDeferredLoad); } } void _forceDeferredLoad() { _deferTimer = null; - if (mounted && !_started && _clip == null && !_isScrolling) _startLoad(); + if (mounted && !_started && _clip == null && !_isScrolling && !_isHeld) { + _startLoad(); + } } void _startLoad() {