mirror of
https://github.com/twonlyapp/twonly-app.git
synced 2026-01-15 13:08:42 +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:flutter/material.dart';
|
||||||
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
||||||
import 'package:pie_menu/pie_menu.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/globals.dart';
|
||||||
import 'package:twonly/src/database/daos/contacts_dao.dart';
|
import 'package:twonly/src/database/daos/contacts_dao.dart';
|
||||||
import 'package:twonly/src/database/tables/messages_table.dart';
|
import 'package:twonly/src/database/tables/messages_table.dart';
|
||||||
|
|
@ -68,7 +69,8 @@ class ChatMessagesView extends StatefulWidget {
|
||||||
State<ChatMessagesView> createState() => _ChatMessagesViewState();
|
State<ChatMessagesView> createState() => _ChatMessagesViewState();
|
||||||
}
|
}
|
||||||
|
|
||||||
class _ChatMessagesViewState extends State<ChatMessagesView> {
|
class _ChatMessagesViewState extends State<ChatMessagesView>
|
||||||
|
with SingleTickerProviderStateMixin {
|
||||||
TextEditingController newMessageController = TextEditingController();
|
TextEditingController newMessageController = TextEditingController();
|
||||||
HashSet<int> alreadyReportedOpened = HashSet<int>();
|
HashSet<int> alreadyReportedOpened = HashSet<int>();
|
||||||
late Contact user;
|
late Contact user;
|
||||||
|
|
@ -82,6 +84,9 @@ class _ChatMessagesViewState extends State<ChatMessagesView> {
|
||||||
GlobalKey verifyShieldKey = GlobalKey();
|
GlobalKey verifyShieldKey = GlobalKey();
|
||||||
late FocusNode textFieldFocus;
|
late FocusNode textFieldFocus;
|
||||||
Timer? tutorial;
|
Timer? tutorial;
|
||||||
|
final ItemScrollController itemScrollController = ItemScrollController();
|
||||||
|
int? focusedScrollItem;
|
||||||
|
late AnimationController _animationController;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
|
|
@ -90,6 +95,11 @@ class _ChatMessagesViewState extends State<ChatMessagesView> {
|
||||||
textFieldFocus = FocusNode();
|
textFieldFocus = FocusNode();
|
||||||
initStreams();
|
initStreams();
|
||||||
|
|
||||||
|
_animationController = AnimationController(
|
||||||
|
duration: const Duration(milliseconds: 200),
|
||||||
|
vsync: this,
|
||||||
|
);
|
||||||
|
|
||||||
tutorial = Timer(const Duration(seconds: 1), () async {
|
tutorial = Timer(const Duration(seconds: 1), () async {
|
||||||
tutorial = null;
|
tutorial = null;
|
||||||
if (!mounted) return;
|
if (!mounted) return;
|
||||||
|
|
@ -248,6 +258,27 @@ class _ChatMessagesViewState extends State<ChatMessagesView> {
|
||||||
setState(() {});
|
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
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return GestureDetector(
|
return GestureDetector(
|
||||||
|
|
@ -289,9 +320,10 @@ class _ChatMessagesViewState extends State<ChatMessagesView> {
|
||||||
child: Column(
|
child: Column(
|
||||||
children: [
|
children: [
|
||||||
Expanded(
|
Expanded(
|
||||||
child: ListView.builder(
|
child: ScrollablePositionedList.builder(
|
||||||
reverse: true,
|
reverse: true,
|
||||||
itemCount: messages.length + 1,
|
itemCount: messages.length + 1,
|
||||||
|
itemScrollController: itemScrollController,
|
||||||
itemBuilder: (context, i) {
|
itemBuilder: (context, i) {
|
||||||
if (i == messages.length) {
|
if (i == messages.length) {
|
||||||
return const Padding(
|
return const Padding(
|
||||||
|
|
@ -304,7 +336,17 @@ class _ChatMessagesViewState extends State<ChatMessagesView> {
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
final chatMessage = messages[i].message!;
|
final chatMessage = messages[i].message!;
|
||||||
return ChatListEntry(
|
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()),
|
key: Key(chatMessage.message.messageId.toString()),
|
||||||
chatMessage,
|
chatMessage,
|
||||||
user,
|
user,
|
||||||
|
|
@ -313,12 +355,14 @@ class _ChatMessagesViewState extends State<ChatMessagesView> {
|
||||||
emojiReactionsToMessageId[
|
emojiReactionsToMessageId[
|
||||||
chatMessage.message.messageId] ??
|
chatMessage.message.messageId] ??
|
||||||
[],
|
[],
|
||||||
|
scrollToMessage: scrollToMessage,
|
||||||
onResponseTriggered: () {
|
onResponseTriggered: () {
|
||||||
setState(() {
|
setState(() {
|
||||||
responseToMessage = chatMessage.message;
|
responseToMessage = chatMessage.message;
|
||||||
});
|
});
|
||||||
textFieldFocus.requestFocus();
|
textFieldFocus.requestFocus();
|
||||||
},
|
},
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -20,6 +20,7 @@ class ChatListEntry extends StatefulWidget {
|
||||||
this.lastMessageFromSameUser,
|
this.lastMessageFromSameUser,
|
||||||
this.otherReactions, {
|
this.otherReactions, {
|
||||||
required this.onResponseTriggered,
|
required this.onResponseTriggered,
|
||||||
|
required this.scrollToMessage,
|
||||||
super.key,
|
super.key,
|
||||||
});
|
});
|
||||||
final ChatMessage msg;
|
final ChatMessage msg;
|
||||||
|
|
@ -27,6 +28,7 @@ class ChatListEntry extends StatefulWidget {
|
||||||
final bool lastMessageFromSameUser;
|
final bool lastMessageFromSameUser;
|
||||||
final List<Message> otherReactions;
|
final List<Message> otherReactions;
|
||||||
final List<MemoryItem> galleryItems;
|
final List<MemoryItem> galleryItems;
|
||||||
|
final void Function(int) scrollToMessage;
|
||||||
final void Function() onResponseTriggered;
|
final void Function() onResponseTriggered;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
|
@ -78,6 +80,7 @@ class _ChatListEntryState extends State<ChatListEntry> {
|
||||||
ResponseContainer(
|
ResponseContainer(
|
||||||
msg: widget.msg,
|
msg: widget.msg,
|
||||||
contact: widget.contact,
|
contact: widget.contact,
|
||||||
|
scrollToMessage: widget.scrollToMessage,
|
||||||
child: (textMessage != null)
|
child: (textMessage != null)
|
||||||
? ChatTextEntry(
|
? ChatTextEntry(
|
||||||
message: widget.msg.message,
|
message: widget.msg.message,
|
||||||
|
|
|
||||||
|
|
@ -15,12 +15,14 @@ class ResponseContainer extends StatefulWidget {
|
||||||
required this.msg,
|
required this.msg,
|
||||||
required this.contact,
|
required this.contact,
|
||||||
required this.child,
|
required this.child,
|
||||||
|
required this.scrollToMessage,
|
||||||
super.key,
|
super.key,
|
||||||
});
|
});
|
||||||
|
|
||||||
final ChatMessage msg;
|
final ChatMessage msg;
|
||||||
final Widget child;
|
final Widget child;
|
||||||
final Contact contact;
|
final Contact contact;
|
||||||
|
final void Function(int) scrollToMessage;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<ResponseContainer> createState() => _ResponseContainerState();
|
State<ResponseContainer> createState() => _ResponseContainerState();
|
||||||
|
|
@ -57,7 +59,9 @@ class _ResponseContainerState extends State<ResponseContainer> {
|
||||||
if (widget.msg.responseTo == null) {
|
if (widget.msg.responseTo == null) {
|
||||||
return widget.child;
|
return widget.child;
|
||||||
}
|
}
|
||||||
return Container(
|
return GestureDetector(
|
||||||
|
onTap: () => widget.scrollToMessage(widget.msg.responseTo!.messageId),
|
||||||
|
child: Container(
|
||||||
constraints: BoxConstraints(
|
constraints: BoxConstraints(
|
||||||
maxWidth: MediaQuery.of(context).size.width * 0.8,
|
maxWidth: MediaQuery.of(context).size.width * 0.8,
|
||||||
),
|
),
|
||||||
|
|
@ -96,6 +100,7 @@ class _ResponseContainerState extends State<ResponseContainer> {
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1418,6 +1418,14 @@ packages:
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "3.0.0"
|
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:
|
share_plus:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
|
|
|
||||||
|
|
@ -64,6 +64,7 @@ dependencies:
|
||||||
provider: ^6.1.2
|
provider: ^6.1.2
|
||||||
restart_app: ^1.3.2
|
restart_app: ^1.3.2
|
||||||
screenshot: ^3.0.0
|
screenshot: ^3.0.0
|
||||||
|
scrollable_positioned_list: ^0.3.8
|
||||||
share_plus: ^11.0.0
|
share_plus: ^11.0.0
|
||||||
tutorial_coach_mark: ^1.3.0
|
tutorial_coach_mark: ^1.3.0
|
||||||
url_launcher: ^6.3.1
|
url_launcher: ^6.3.1
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue