mirror of
https://github.com/twonlyapp/twonly-app.git
synced 2026-04-18 14:42:54 +00:00
smoother response animation
Some checks are pending
Flutter analyze & test / flutter_analyze_and_test (push) Waiting to run
Some checks are pending
Flutter analyze & test / flutter_analyze_and_test (push) Waiting to run
This commit is contained in:
parent
527bf51bff
commit
f419b3709d
4 changed files with 72 additions and 34 deletions
|
|
@ -20,7 +20,7 @@ import 'package:twonly/src/views/chats/chat_messages_components/entries/chat_dat
|
||||||
import 'package:twonly/src/views/chats/chat_messages_components/message_input.dart';
|
import 'package:twonly/src/views/chats/chat_messages_components/message_input.dart';
|
||||||
import 'package:twonly/src/views/chats/chat_messages_components/response_container.dart';
|
import 'package:twonly/src/views/chats/chat_messages_components/response_container.dart';
|
||||||
import 'package:twonly/src/views/components/avatar_icon.component.dart';
|
import 'package:twonly/src/views/components/avatar_icon.component.dart';
|
||||||
import 'package:twonly/src/views/components/blink.component.dart';
|
import 'package:twonly/src/views/chats/chat_messages_components/blink.component.dart';
|
||||||
import 'package:twonly/src/views/components/flame.dart';
|
import 'package:twonly/src/views/components/flame.dart';
|
||||||
import 'package:twonly/src/views/components/verified_shield.dart';
|
import 'package:twonly/src/views/components/verified_shield.dart';
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -19,7 +19,7 @@ import 'package:twonly/src/views/chats/chat_messages_components/entries/chat_med
|
||||||
import 'package:twonly/src/views/chats/chat_messages_components/entries/chat_text_entry.dart';
|
import 'package:twonly/src/views/chats/chat_messages_components/entries/chat_text_entry.dart';
|
||||||
import 'package:twonly/src/views/chats/chat_messages_components/entries/chat_unkown.entry.dart';
|
import 'package:twonly/src/views/chats/chat_messages_components/entries/chat_unkown.entry.dart';
|
||||||
import 'package:twonly/src/views/chats/chat_messages_components/entries/common.dart';
|
import 'package:twonly/src/views/chats/chat_messages_components/entries/common.dart';
|
||||||
import 'package:twonly/src/views/chats/chat_messages_components/message_actions.dart';
|
import 'package:twonly/src/views/chats/chat_messages_components/message_reply_drag.dart';
|
||||||
import 'package:twonly/src/views/chats/chat_messages_components/message_context_menu.dart';
|
import 'package:twonly/src/views/chats/chat_messages_components/message_context_menu.dart';
|
||||||
import 'package:twonly/src/views/chats/chat_messages_components/response_container.dart';
|
import 'package:twonly/src/views/chats/chat_messages_components/response_container.dart';
|
||||||
import 'package:twonly/src/views/components/avatar_icon.component.dart';
|
import 'package:twonly/src/views/components/avatar_icon.component.dart';
|
||||||
|
|
@ -74,8 +74,9 @@ class _ChatListEntryState extends State<ChatListEntry> {
|
||||||
|
|
||||||
Future<void> initAsync() async {
|
Future<void> initAsync() async {
|
||||||
if (widget.message.mediaId != null) {
|
if (widget.message.mediaId != null) {
|
||||||
final mediaFileStream =
|
final mediaFileStream = twonlyDB.mediaFilesDao.watchMedia(
|
||||||
twonlyDB.mediaFilesDao.watchMedia(widget.message.mediaId!);
|
widget.message.mediaId!,
|
||||||
|
);
|
||||||
mediaFileSub = mediaFileStream.listen((mediaFiles) {
|
mediaFileSub = mediaFileStream.listen((mediaFiles) {
|
||||||
if (mediaFiles != null) {
|
if (mediaFiles != null) {
|
||||||
mediaService = MediaFileService(mediaFiles);
|
mediaService = MediaFileService(mediaFiles);
|
||||||
|
|
@ -87,8 +88,9 @@ class _ChatListEntryState extends State<ChatListEntry> {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
final stream =
|
final stream = twonlyDB.reactionsDao.watchReactions(
|
||||||
twonlyDB.reactionsDao.watchReactions(widget.message.messageId);
|
widget.message.messageId,
|
||||||
|
);
|
||||||
|
|
||||||
reactionsSub = stream.listen((update) {
|
reactionsSub = stream.listen((update) {
|
||||||
setState(() {
|
setState(() {
|
||||||
|
|
@ -159,8 +161,10 @@ class _ChatListEntryState extends State<ChatListEntry> {
|
||||||
);
|
);
|
||||||
|
|
||||||
final seen = <String>{};
|
final seen = <String>{};
|
||||||
var reactionsForWidth =
|
var reactionsForWidth = reactions
|
||||||
reactions.where((t) => seen.add(t.emoji)).toList().length;
|
.where((t) => seen.add(t.emoji))
|
||||||
|
.toList()
|
||||||
|
.length;
|
||||||
if (reactionsForWidth > 4) reactionsForWidth = 4;
|
if (reactionsForWidth > 4) reactionsForWidth = 4;
|
||||||
|
|
||||||
Widget child = Stack(
|
Widget child = Stack(
|
||||||
|
|
@ -205,7 +209,7 @@ class _ChatListEntryState extends State<ChatListEntry> {
|
||||||
);
|
);
|
||||||
|
|
||||||
if (widget.onResponseTriggered != null) {
|
if (widget.onResponseTriggered != null) {
|
||||||
child = MessageActions(
|
child = MessageReplyDrag(
|
||||||
message: widget.message,
|
message: widget.message,
|
||||||
onResponseTriggered: widget.onResponseTriggered!,
|
onResponseTriggered: widget.onResponseTriggered!,
|
||||||
child: child,
|
child: child,
|
||||||
|
|
@ -228,8 +232,9 @@ class _ChatListEntryState extends State<ChatListEntry> {
|
||||||
child: Padding(
|
child: Padding(
|
||||||
padding: padding,
|
padding: padding,
|
||||||
child: Row(
|
child: Row(
|
||||||
mainAxisAlignment:
|
mainAxisAlignment: right
|
||||||
right ? MainAxisAlignment.end : MainAxisAlignment.start,
|
? MainAxisAlignment.end
|
||||||
|
: MainAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
if (!right && !widget.group.isDirectChat)
|
if (!right && !widget.group.isDirectChat)
|
||||||
hideContactAvatar
|
hideContactAvatar
|
||||||
|
|
@ -306,6 +311,6 @@ class _ChatListEntryState extends State<ChatListEntry> {
|
||||||
bottomRight: Radius.circular(bottomRight),
|
bottomRight: Radius.circular(bottomRight),
|
||||||
bottomLeft: Radius.circular(bottomLeft),
|
bottomLeft: Radius.circular(bottomLeft),
|
||||||
),
|
),
|
||||||
hideContactAvatar
|
hideContactAvatar,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,8 +5,8 @@ import 'package:flutter/services.dart';
|
||||||
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
||||||
import 'package:twonly/src/database/twonly.db.dart';
|
import 'package:twonly/src/database/twonly.db.dart';
|
||||||
|
|
||||||
class MessageActions extends StatefulWidget {
|
class MessageReplyDrag extends StatefulWidget {
|
||||||
const MessageActions({
|
const MessageReplyDrag({
|
||||||
required this.child,
|
required this.child,
|
||||||
required this.message,
|
required this.message,
|
||||||
required this.onResponseTriggered,
|
required this.onResponseTriggered,
|
||||||
|
|
@ -17,27 +17,49 @@ class MessageActions extends StatefulWidget {
|
||||||
final VoidCallback onResponseTriggered;
|
final VoidCallback onResponseTriggered;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<MessageActions> createState() => _SlidingResponseWidgetState();
|
State<MessageReplyDrag> createState() => _SlidingResponseWidgetState();
|
||||||
}
|
}
|
||||||
|
|
||||||
class _SlidingResponseWidgetState extends State<MessageActions> {
|
class _SlidingResponseWidgetState extends State<MessageReplyDrag> {
|
||||||
double _offsetX = 0;
|
double _offsetX = 0;
|
||||||
bool gotFeedback = false;
|
bool gotFeedback = false;
|
||||||
|
double _dragProgress = 0;
|
||||||
|
double _animatedScale = 1;
|
||||||
|
|
||||||
|
Future<void> _triggerPopAnimation() async {
|
||||||
|
setState(() {
|
||||||
|
_animatedScale = 1.3;
|
||||||
|
});
|
||||||
|
|
||||||
|
await Future.delayed(Duration(milliseconds: 50));
|
||||||
|
|
||||||
|
if (mounted) {
|
||||||
|
setState(() {
|
||||||
|
_animatedScale = 1.0;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void _onHorizontalDragUpdate(DragUpdateDetails details) {
|
void _onHorizontalDragUpdate(DragUpdateDetails details) {
|
||||||
setState(() {
|
setState(() {
|
||||||
_offsetX += details.delta.dx;
|
_offsetX += details.delta.dx;
|
||||||
|
if (_offsetX <= 0) _offsetX = 0;
|
||||||
if (_offsetX > 40) {
|
if (_offsetX > 40) {
|
||||||
_offsetX = 40;
|
|
||||||
if (!gotFeedback) {
|
if (!gotFeedback) {
|
||||||
unawaited(HapticFeedback.heavyImpact());
|
unawaited(HapticFeedback.heavyImpact());
|
||||||
gotFeedback = true;
|
gotFeedback = true;
|
||||||
|
unawaited(_triggerPopAnimation());
|
||||||
}
|
}
|
||||||
|
_dragProgress = 1;
|
||||||
|
} else {
|
||||||
|
_dragProgress = _offsetX / 40;
|
||||||
}
|
}
|
||||||
if (_offsetX < 30) {
|
if (_offsetX < 30) {
|
||||||
gotFeedback = false;
|
gotFeedback = false;
|
||||||
}
|
}
|
||||||
if (_offsetX <= 0) _offsetX = 0;
|
if (_offsetX > 50) {
|
||||||
|
_offsetX = 50;
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -47,6 +69,7 @@ class _SlidingResponseWidgetState extends State<MessageActions> {
|
||||||
}
|
}
|
||||||
setState(() {
|
setState(() {
|
||||||
_offsetX = 0.0;
|
_offsetX = 0.0;
|
||||||
|
_dragProgress = 0;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -54,6 +77,32 @@ class _SlidingResponseWidgetState extends State<MessageActions> {
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Stack(
|
return Stack(
|
||||||
children: [
|
children: [
|
||||||
|
if (_dragProgress > 0.2)
|
||||||
|
Positioned(
|
||||||
|
left: _dragProgress * 10,
|
||||||
|
top: 0,
|
||||||
|
bottom: 0,
|
||||||
|
child: Transform.scale(
|
||||||
|
scale: 1 * _dragProgress,
|
||||||
|
child: AnimatedScale(
|
||||||
|
duration: const Duration(milliseconds: 50),
|
||||||
|
scale: _animatedScale,
|
||||||
|
curve: Curves.easeInOut,
|
||||||
|
child: Opacity(
|
||||||
|
opacity: 1 * _dragProgress,
|
||||||
|
child: const Column(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
FaIcon(
|
||||||
|
FontAwesomeIcons.reply,
|
||||||
|
size: 14,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
Transform.translate(
|
Transform.translate(
|
||||||
offset: Offset(_offsetX, 0),
|
offset: Offset(_offsetX, 0),
|
||||||
child: GestureDetector(
|
child: GestureDetector(
|
||||||
|
|
@ -62,22 +111,6 @@ class _SlidingResponseWidgetState extends State<MessageActions> {
|
||||||
child: widget.child,
|
child: widget.child,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
if (_offsetX >= 40)
|
|
||||||
const Positioned(
|
|
||||||
left: 20,
|
|
||||||
top: 0,
|
|
||||||
bottom: 0,
|
|
||||||
child: Column(
|
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
|
||||||
children: [
|
|
||||||
FaIcon(
|
|
||||||
FontAwesomeIcons.reply,
|
|
||||||
size: 14,
|
|
||||||
// color: Colors.green,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
Loading…
Reference in a new issue