mirror of
https://github.com/twonlyapp/twonly-app.git
synced 2026-01-15 07:48:40 +00:00
scroll to responded message
This commit is contained in:
parent
90d6b048f3
commit
991f86802f
5 changed files with 113 additions and 52 deletions
|
|
@ -6,6 +6,7 @@ import 'dart:io';
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
||||
import 'package:pie_menu/pie_menu.dart';
|
||||
import 'package:scrollable_positioned_list/scrollable_positioned_list.dart';
|
||||
import 'package:twonly/globals.dart';
|
||||
import 'package:twonly/src/database/daos/contacts_dao.dart';
|
||||
import 'package:twonly/src/database/tables/messages_table.dart';
|
||||
|
|
@ -68,7 +69,8 @@ class ChatMessagesView extends StatefulWidget {
|
|||
State<ChatMessagesView> createState() => _ChatMessagesViewState();
|
||||
}
|
||||
|
||||
class _ChatMessagesViewState extends State<ChatMessagesView> {
|
||||
class _ChatMessagesViewState extends State<ChatMessagesView>
|
||||
with SingleTickerProviderStateMixin {
|
||||
TextEditingController newMessageController = TextEditingController();
|
||||
HashSet<int> alreadyReportedOpened = HashSet<int>();
|
||||
late Contact user;
|
||||
|
|
@ -82,6 +84,9 @@ class _ChatMessagesViewState extends State<ChatMessagesView> {
|
|||
GlobalKey verifyShieldKey = GlobalKey();
|
||||
late FocusNode textFieldFocus;
|
||||
Timer? tutorial;
|
||||
final ItemScrollController itemScrollController = ItemScrollController();
|
||||
int? focusedScrollItem;
|
||||
late AnimationController _animationController;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
|
|
@ -90,6 +95,11 @@ class _ChatMessagesViewState extends State<ChatMessagesView> {
|
|||
textFieldFocus = FocusNode();
|
||||
initStreams();
|
||||
|
||||
_animationController = AnimationController(
|
||||
duration: const Duration(milliseconds: 200),
|
||||
vsync: this,
|
||||
);
|
||||
|
||||
tutorial = Timer(const Duration(seconds: 1), () async {
|
||||
tutorial = null;
|
||||
if (!mounted) return;
|
||||
|
|
@ -248,6 +258,27 @@ class _ChatMessagesViewState extends State<ChatMessagesView> {
|
|||
setState(() {});
|
||||
}
|
||||
|
||||
Future<void> scrollToMessage(int messageId) async {
|
||||
final index = messages.indexWhere(
|
||||
(x) => x.isMessage && x.message!.message.messageId == messageId);
|
||||
if (index == -1) return;
|
||||
await itemScrollController.scrollTo(
|
||||
index: index,
|
||||
duration: const Duration(milliseconds: 400),
|
||||
alignment: 0.5,
|
||||
);
|
||||
setState(() {
|
||||
focusedScrollItem = index;
|
||||
_animationController.forward().then((_) {
|
||||
_animationController.reverse().then((_) {
|
||||
setState(() {
|
||||
_animationController.value = 0.0;
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return GestureDetector(
|
||||
|
|
@ -289,9 +320,10 @@ class _ChatMessagesViewState extends State<ChatMessagesView> {
|
|||
child: Column(
|
||||
children: [
|
||||
Expanded(
|
||||
child: ListView.builder(
|
||||
child: ScrollablePositionedList.builder(
|
||||
reverse: true,
|
||||
itemCount: messages.length + 1,
|
||||
itemScrollController: itemScrollController,
|
||||
itemBuilder: (context, i) {
|
||||
if (i == messages.length) {
|
||||
return const Padding(
|
||||
|
|
@ -304,21 +336,33 @@ class _ChatMessagesViewState extends State<ChatMessagesView> {
|
|||
);
|
||||
} else {
|
||||
final chatMessage = messages[i].message!;
|
||||
return ChatListEntry(
|
||||
key: Key(chatMessage.message.messageId.toString()),
|
||||
chatMessage,
|
||||
user,
|
||||
galleryItems,
|
||||
isLastMessageFromSameUser(messages, i),
|
||||
emojiReactionsToMessageId[
|
||||
chatMessage.message.messageId] ??
|
||||
[],
|
||||
onResponseTriggered: () {
|
||||
setState(() {
|
||||
responseToMessage = chatMessage.message;
|
||||
});
|
||||
textFieldFocus.requestFocus();
|
||||
},
|
||||
return ScaleTransition(
|
||||
scale: Tween<double>(
|
||||
begin: 1,
|
||||
end: (focusedScrollItem == i) ? 1.03 : 1)
|
||||
.animate(
|
||||
CurvedAnimation(
|
||||
parent: _animationController,
|
||||
curve: Curves.easeInOut,
|
||||
),
|
||||
),
|
||||
child: ChatListEntry(
|
||||
key: Key(chatMessage.message.messageId.toString()),
|
||||
chatMessage,
|
||||
user,
|
||||
galleryItems,
|
||||
isLastMessageFromSameUser(messages, i),
|
||||
emojiReactionsToMessageId[
|
||||
chatMessage.message.messageId] ??
|
||||
[],
|
||||
scrollToMessage: scrollToMessage,
|
||||
onResponseTriggered: () {
|
||||
setState(() {
|
||||
responseToMessage = chatMessage.message;
|
||||
});
|
||||
textFieldFocus.requestFocus();
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
},
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@ class ChatListEntry extends StatefulWidget {
|
|||
this.lastMessageFromSameUser,
|
||||
this.otherReactions, {
|
||||
required this.onResponseTriggered,
|
||||
required this.scrollToMessage,
|
||||
super.key,
|
||||
});
|
||||
final ChatMessage msg;
|
||||
|
|
@ -27,6 +28,7 @@ class ChatListEntry extends StatefulWidget {
|
|||
final bool lastMessageFromSameUser;
|
||||
final List<Message> otherReactions;
|
||||
final List<MemoryItem> galleryItems;
|
||||
final void Function(int) scrollToMessage;
|
||||
final void Function() onResponseTriggered;
|
||||
|
||||
@override
|
||||
|
|
@ -78,6 +80,7 @@ class _ChatListEntryState extends State<ChatListEntry> {
|
|||
ResponseContainer(
|
||||
msg: widget.msg,
|
||||
contact: widget.contact,
|
||||
scrollToMessage: widget.scrollToMessage,
|
||||
child: (textMessage != null)
|
||||
? ChatTextEntry(
|
||||
message: widget.msg.message,
|
||||
|
|
|
|||
|
|
@ -15,12 +15,14 @@ class ResponseContainer extends StatefulWidget {
|
|||
required this.msg,
|
||||
required this.contact,
|
||||
required this.child,
|
||||
required this.scrollToMessage,
|
||||
super.key,
|
||||
});
|
||||
|
||||
final ChatMessage msg;
|
||||
final Widget child;
|
||||
final Contact contact;
|
||||
final void Function(int) scrollToMessage;
|
||||
|
||||
@override
|
||||
State<ResponseContainer> createState() => _ResponseContainerState();
|
||||
|
|
@ -57,44 +59,47 @@ class _ResponseContainerState extends State<ResponseContainer> {
|
|||
if (widget.msg.responseTo == null) {
|
||||
return widget.child;
|
||||
}
|
||||
return Container(
|
||||
constraints: BoxConstraints(
|
||||
maxWidth: MediaQuery.of(context).size.width * 0.8,
|
||||
),
|
||||
decoration: BoxDecoration(
|
||||
color: getMessageColor(widget.msg.message),
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(top: 4, right: 4, left: 4),
|
||||
child: Container(
|
||||
key: _preview,
|
||||
width: minWidth,
|
||||
decoration: BoxDecoration(
|
||||
color: context.color.surface.withAlpha(150),
|
||||
borderRadius: const BorderRadius.only(
|
||||
topRight: Radius.circular(8),
|
||||
topLeft: Radius.circular(8),
|
||||
bottomLeft: Radius.circular(4),
|
||||
bottomRight: Radius.circular(4),
|
||||
return GestureDetector(
|
||||
onTap: () => widget.scrollToMessage(widget.msg.responseTo!.messageId),
|
||||
child: Container(
|
||||
constraints: BoxConstraints(
|
||||
maxWidth: MediaQuery.of(context).size.width * 0.8,
|
||||
),
|
||||
decoration: BoxDecoration(
|
||||
color: getMessageColor(widget.msg.message),
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(top: 4, right: 4, left: 4),
|
||||
child: Container(
|
||||
key: _preview,
|
||||
width: minWidth,
|
||||
decoration: BoxDecoration(
|
||||
color: context.color.surface.withAlpha(150),
|
||||
borderRadius: const BorderRadius.only(
|
||||
topRight: Radius.circular(8),
|
||||
topLeft: Radius.circular(8),
|
||||
bottomLeft: Radius.circular(4),
|
||||
bottomRight: Radius.circular(4),
|
||||
),
|
||||
),
|
||||
child: ResponsePreview(
|
||||
contact: widget.contact,
|
||||
message: widget.msg.responseTo!,
|
||||
showBorder: false,
|
||||
),
|
||||
),
|
||||
child: ResponsePreview(
|
||||
contact: widget.contact,
|
||||
message: widget.msg.responseTo!,
|
||||
showBorder: false,
|
||||
),
|
||||
),
|
||||
),
|
||||
SizedBox(
|
||||
key: _message,
|
||||
width: minWidth,
|
||||
child: widget.child,
|
||||
),
|
||||
],
|
||||
SizedBox(
|
||||
key: _message,
|
||||
width: minWidth,
|
||||
child: widget.child,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1418,6 +1418,14 @@ packages:
|
|||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.0.0"
|
||||
scrollable_positioned_list:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: scrollable_positioned_list
|
||||
sha256: "1b54d5f1329a1e263269abc9e2543d90806131aa14fe7c6062a8054d57249287"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.3.8"
|
||||
share_plus:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
|
|
|
|||
|
|
@ -64,6 +64,7 @@ dependencies:
|
|||
provider: ^6.1.2
|
||||
restart_app: ^1.3.2
|
||||
screenshot: ^3.0.0
|
||||
scrollable_positioned_list: ^0.3.8
|
||||
share_plus: ^11.0.0
|
||||
tutorial_coach_mark: ^1.3.0
|
||||
url_launcher: ^6.3.1
|
||||
|
|
|
|||
Loading…
Reference in a new issue