From c4a7533315ceeb44c357cf1ca53ac272666159b3 Mon Sep 17 00:00:00 2001 From: Pupsi28 Date: Thu, 11 Apr 2024 18:22:55 +0200 Subject: [PATCH 1/9] added option to react to messages --- .../talk/sendMessage/sendMessageParams.dart | 2 +- .../talk/sendMessage/sendMessageParams.g.dart | 2 +- lib/model/chatList/chatProps.dart | 7 + .../pages/talk/components/chatBubble.dart | 5 + .../pages/talk/components/chatTextfield.dart | 200 ++++++++++-------- 5 files changed, 129 insertions(+), 87 deletions(-) diff --git a/lib/api/marianumcloud/talk/sendMessage/sendMessageParams.dart b/lib/api/marianumcloud/talk/sendMessage/sendMessageParams.dart index e9150c4..d467246 100644 --- a/lib/api/marianumcloud/talk/sendMessage/sendMessageParams.dart +++ b/lib/api/marianumcloud/talk/sendMessage/sendMessageParams.dart @@ -7,7 +7,7 @@ part 'sendMessageParams.g.dart'; @JsonSerializable(explicitToJson: true, includeIfNull: false) class SendMessageParams extends ApiParams { String message; - int? replyTo; + String? replyTo; SendMessageParams(this.message, {this.replyTo}); diff --git a/lib/api/marianumcloud/talk/sendMessage/sendMessageParams.g.dart b/lib/api/marianumcloud/talk/sendMessage/sendMessageParams.g.dart index 50255b4..76093c2 100644 --- a/lib/api/marianumcloud/talk/sendMessage/sendMessageParams.g.dart +++ b/lib/api/marianumcloud/talk/sendMessage/sendMessageParams.g.dart @@ -9,7 +9,7 @@ part of 'sendMessageParams.dart'; SendMessageParams _$SendMessageParamsFromJson(Map json) => SendMessageParams( json['message'] as String, - replyTo: json['replyTo'] as int?, + replyTo: json['replyTo'] as String?, ); Map _$SendMessageParamsToJson(SendMessageParams instance) { diff --git a/lib/model/chatList/chatProps.dart b/lib/model/chatList/chatProps.dart index bd0a749..7fd2f6c 100644 --- a/lib/model/chatList/chatProps.dart +++ b/lib/model/chatList/chatProps.dart @@ -10,6 +10,8 @@ class ChatProps extends DataHolder { GetChatResponse? _getChatResponse; GetChatResponse get getChatResponse => _getChatResponse!; + int? referenceMessageId; + @override List properties() => [_getChatResponse]; @@ -30,6 +32,11 @@ class ChatProps extends DataHolder { ); } + void setReferenceMessageId(int? messageId) { + referenceMessageId = messageId; + run(); + } + void setQueryToken(String token) { _queryToken = token; _getChatResponse = null; diff --git a/lib/view/pages/talk/components/chatBubble.dart b/lib/view/pages/talk/components/chatBubble.dart index 6c589c5..f8fb90c 100644 --- a/lib/view/pages/talk/components/chatBubble.dart +++ b/lib/view/pages/talk/components/chatBubble.dart @@ -288,6 +288,11 @@ class _ChatBubbleState extends State { children: [ GestureDetector( + onHorizontalDragEnd: (DragEndDetails details) { + if(widget.bubbleData.isReplyable) { + Provider.of(context, listen: false).setReferenceMessageId(widget.bubbleData.id); + } + }, onLongPress: showOptionsDialog, onDoubleTap: showOptionsDialog, onTap: () { diff --git a/lib/view/pages/talk/components/chatTextfield.dart b/lib/view/pages/talk/components/chatTextfield.dart index 3628006..a4199cd 100644 --- a/lib/view/pages/talk/components/chatTextfield.dart +++ b/lib/view/pages/talk/components/chatTextfield.dart @@ -78,6 +78,7 @@ class _ChatTextfieldState extends State { void initState() { super.initState(); settings = Provider.of(context, listen: false); + Provider.of(context, listen: false).referenceMessageId = null; } @override @@ -91,102 +92,131 @@ class _ChatTextfieldState extends State { child: Container( padding: const EdgeInsets.only(left: 10, bottom: 3, top: 3, right: 10), width: double.infinity, - child: Row( - children: [ - GestureDetector( - onTap: (){ - showDialog(context: context, builder: (context) => SimpleDialog( + child: Column( + children: [ + Consumer( + builder: (context, data, child) { + // Text(data.referenceMessageId.toString()); + if(data.referenceMessageId != null) { + var referenceMessage = data.getChatResponse.sortByTimestamp().where((element) => element.id == data.referenceMessageId).first; + return Row( children: [ - ListTile( - leading: const Icon(Icons.file_open), - title: const Text('Aus Dateien auswählen'), - onTap: () { - FilePick.documentPick().then(mediaUpload); - Navigator.of(context).pop(); - }, - ), - Visibility( - visible: !Platform.isIOS, - child: ListTile( - leading: const Icon(Icons.image), - title: const Text('Aus Gallerie auswählen'), - onTap: () { - FilePick.multipleGalleryPick().then((value) { - if(value != null) mediaUpload(value.map((e) => e.path).toList()); - }); - Navigator.of(context).pop(); - }, - ), + IconButton( + onPressed: () => data.setReferenceMessageId(null), + icon: const Icon(Icons.close_outlined), + padding: const EdgeInsets.only(left: 0), ), + Text(referenceMessage.message), ], - )); + ); + } else { + return const SizedBox.shrink(); + } }, - child: Material( - elevation: 5, - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(30), + ), + Row( + children: [ + GestureDetector( + onTap: (){ + showDialog(context: context, builder: (context) => SimpleDialog( + children: [ + ListTile( + leading: const Icon(Icons.file_open), + title: const Text('Aus Dateien auswählen'), + onTap: () { + FilePick.documentPick().then(mediaUpload); + Navigator.of(context).pop(); + }, + ), + Visibility( + visible: !Platform.isIOS, + child: ListTile( + leading: const Icon(Icons.image), + title: const Text('Aus Gallerie auswählen'), + onTap: () { + FilePick.multipleGalleryPick().then((value) { + if(value != null) mediaUpload(value.map((e) => e.path).toList()); + }); + Navigator.of(context).pop(); + }, + ), + ), + ], + )); + }, + child: Material( + elevation: 5, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(30), + ), + child: Container( + height: 30, + width: 30, + decoration: BoxDecoration( + color: Theme.of(context).primaryColor, + borderRadius: BorderRadius.circular(30), + ), + child: const Icon(Icons.attach_file_outlined, color: Colors.white, size: 20, ), + ), + ) ), - child: Container( - height: 30, - width: 30, - decoration: BoxDecoration( - color: Theme.of(context).primaryColor, - borderRadius: BorderRadius.circular(30), + const SizedBox(width: 15), + Expanded( + child: TextField( + autocorrect: true, + textCapitalization: TextCapitalization.sentences, + controller: _textBoxController, + maxLines: 7, + minLines: 1, + decoration: const InputDecoration( + hintText: 'Nachricht schreiben...', + border: InputBorder.none, + ), + onChanged: (String text) { + if(text.trim().toLowerCase() == 'marbot marbot marbot') { + var newText = 'Roboter sind cool und so, aber Marbots sind besser!'; + _textBoxController.text = newText; + text = newText; + } + setDraft(text); + }, + onTapOutside: (PointerDownEvent event) => FocusBehaviour.textFieldTapOutside(context), ), - child: const Icon(Icons.attach_file_outlined, color: Colors.white, size: 20, ), ), - ) - ), - const SizedBox(width: 15), - Expanded( - child: TextField( - autocorrect: true, - textCapitalization: TextCapitalization.sentences, - controller: _textBoxController, - maxLines: 7, - minLines: 1, - decoration: const InputDecoration( - hintText: 'Nachricht schreiben...', - border: InputBorder.none, - ), - onChanged: (String text) { - if(text.trim().toLowerCase() == 'marbot marbot marbot') { - var newText = 'Roboter sind cool und so, aber Marbots sind besser!'; - _textBoxController.text = newText; - text = newText; - } - setDraft(text); - }, - onTapOutside: (PointerDownEvent event) => FocusBehaviour.textFieldTapOutside(context), - ), - ), - const SizedBox(width: 15), - FloatingActionButton( - mini: true, - onPressed: (){ - if(_textBoxController.text.isEmpty) return; - if(isLoading) return; + const SizedBox(width: 15), + FloatingActionButton( + mini: true, + onPressed: (){ + if(_textBoxController.text.isEmpty) return; + if(isLoading) return; - setState(() { - isLoading = true; - }); - SendMessage(widget.sendToToken, SendMessageParams(_textBoxController.text)).run().then((value) { - _query(); - setState(() { - isLoading = false; - }); - _textBoxController.text = ''; - setDraft(''); - }); - }, - backgroundColor: Theme.of(context).primaryColor, - elevation: 5, - child: isLoading - ? Container(padding: const EdgeInsets.all(10), child: const CircularProgressIndicator(color: Colors.white, strokeWidth: 2)) - : const Icon(Icons.send, color: Colors.white, size: 18), + setState(() { + isLoading = true; + }); + SendMessage(widget.sendToToken, SendMessageParams( + _textBoxController.text, + replyTo: Provider.of(context, listen: false).referenceMessageId.toString() + )).run().then((value) { + _query(); + setState(() { + isLoading = false; + }); + _textBoxController.text = ''; + setDraft(''); + }); + Provider.of(context, listen: false).referenceMessageId = null; + }, + backgroundColor: Theme.of(context).primaryColor, + elevation: 5, + child: isLoading + ? Container(padding: const EdgeInsets.all(10), child: const CircularProgressIndicator(color: Colors.white, strokeWidth: 2)) + : const Icon(Icons.send, color: Colors.white, size: 18), + ), + ], ), ], ), + ), ), ], From ae6b6511d72f247705d43cb9b34bf46933ca16dc Mon Sep 17 00:00:00 2001 From: Pupsi28 Date: Fri, 19 Apr 2024 18:55:07 +0200 Subject: [PATCH 2/9] parent message gets shown in chat bubble --- .../talk/chat/getChatResponse.dart | 7 +- .../talk/chat/getChatResponse.g.dart | 5 + .../pages/talk/components/chatBubble.dart | 133 +++++++++++------- 3 files changed, 95 insertions(+), 50 deletions(-) diff --git a/lib/api/marianumcloud/talk/chat/getChatResponse.dart b/lib/api/marianumcloud/talk/chat/getChatResponse.dart index b62b897..7055417 100644 --- a/lib/api/marianumcloud/talk/chat/getChatResponse.dart +++ b/lib/api/marianumcloud/talk/chat/getChatResponse.dart @@ -38,6 +38,7 @@ class GetChatResponseObject { Map? reactions; List? reactionsSelf; @JsonKey(fromJson: _fromJson) Map? messageParameters; + GetChatResponseObject? parent; GetChatResponseObject( this.id, @@ -53,7 +54,8 @@ class GetChatResponseObject { this.message, this.messageParameters, this.reactions, - this.reactionsSelf + this.reactionsSelf, + this.parent, ); factory GetChatResponseObject.fromJson(Map json) => _$GetChatResponseObjectFromJson(json); @@ -78,7 +80,8 @@ class GetChatResponseObject { text, null, null, - null + null, + null, ); } diff --git a/lib/api/marianumcloud/talk/chat/getChatResponse.g.dart b/lib/api/marianumcloud/talk/chat/getChatResponse.g.dart index 3da4655..0b241cb 100644 --- a/lib/api/marianumcloud/talk/chat/getChatResponse.g.dart +++ b/lib/api/marianumcloud/talk/chat/getChatResponse.g.dart @@ -52,6 +52,10 @@ GetChatResponseObject _$GetChatResponseObjectFromJson( (json['reactionsSelf'] as List?) ?.map((e) => e as String) .toList(), + json['parent'] == null + ? null + : GetChatResponseObject.fromJson( + json['parent'] as Map), ); Map _$GetChatResponseObjectToJson( @@ -74,6 +78,7 @@ Map _$GetChatResponseObjectToJson( 'reactionsSelf': instance.reactionsSelf, 'messageParameters': instance.messageParameters?.map((k, e) => MapEntry(k, e.toJson())), + 'parent': instance.parent?.toJson(), }; const _$GetRoomResponseObjectMessageActorTypeEnumMap = { diff --git a/lib/view/pages/talk/components/chatBubble.dart b/lib/view/pages/talk/components/chatBubble.dart index f8fb90c..c2a63c2 100644 --- a/lib/view/pages/talk/components/chatBubble.dart +++ b/lib/view/pages/talk/components/chatBubble.dart @@ -2,6 +2,7 @@ import 'package:better_open_file/better_open_file.dart'; import 'package:bubble/bubble.dart'; import 'package:emoji_picker_flutter/emoji_picker_flutter.dart' as emojis; import 'package:flowder/flowder.dart'; +import 'package:flutter/cupertino.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; @@ -266,6 +267,7 @@ class _ChatBubbleState extends State { message = ChatMessage(originalMessage: widget.bubbleData.message, originalData: widget.bubbleData.messageParameters); var showActorDisplayName = widget.bubbleData.messageType == GetRoomResponseObjectMessageType.comment && widget.chatData.type != GetRoomResponseObjectConversationType.oneToOne; var showBubbleTime = widget.bubbleData.messageType != GetRoomResponseObjectMessageType.system; + var parent = widget.bubbleData.parent; var actorText = Text( widget.bubbleData.actorDisplayName, @@ -343,57 +345,92 @@ class _ChatBubbleState extends State { }, child: Bubble( style: getStyle(), - child: Container( - constraints: BoxConstraints( - maxWidth: MediaQuery.of(context).size.width * 0.9, - minWidth: showActorDisplayName - ? actorText.size.width - : timeText.size.width + (widget.isSender ? widget.spacing + widget.timeIconSize : 0) + 3, - ), - child: Stack( - children: [ - Padding( - padding: EdgeInsets.only(bottom: showBubbleTime ? 18 : 0, top: showActorDisplayName ? 18 : 0), - child: message.getWidget() + child: Column( + children: [ + Visibility( + visible: parent != null && parent.message.isNotEmpty, + child: Wrap( + alignment: WrapAlignment.start, + clipBehavior: Clip.hardEdge, + children: [ + DecoratedBox( + decoration: BoxDecoration( + color: Theme.of(context).secondaryHeaderColor, + ), + child: Text( + parent?.message ?? '', + maxLines: 2, + style: const TextStyle( + overflow: TextOverflow.ellipsis, + ), + ), + ), + ], ), - Visibility( - visible: showActorDisplayName, - child: Positioned( - top: 0, - left: 0, - child: actorText - ), + // SizedBox( + // width: parentMessageWidth < MediaQuery.of(context).size.width * 0.9 + // ? parentMessageWidth.toDouble() + // : MediaQuery.of(context).size.width * 0.9, + // height: 20, + // child: Text( + // parentMessage?.message ?? 'Ranz', + // overflow: TextOverflow.clip, + // ), + // ), + ), + Container( + constraints: BoxConstraints( + maxWidth: MediaQuery.of(context).size.width * 0.9, + minWidth: showActorDisplayName + ? actorText.size.width + : timeText.size.width + (widget.isSender ? widget.spacing + widget.timeIconSize : 0) + 3, ), - Visibility( - visible: showBubbleTime, - child: Positioned( - bottom: 0, - right: 0, - child: Row( - children: [ - timeText, - if(widget.isSender) ...[ - SizedBox(width: widget.spacing), - if(widget.isRead) - Icon(Icons.done_all_outlined, size: widget.timeIconSize, color: widget.timeIconColor) - else - Icon(Icons.done_outlined, size: widget.timeIconSize, color: widget.timeIconColor) - ] - ], - ) - ), + child: Stack( + children: [ + Padding( + padding: EdgeInsets.only(bottom: showBubbleTime ? 18 : 0, top: showActorDisplayName ? 18 : 0), + child: message.getWidget() + ), + Visibility( + visible: showActorDisplayName, + child: Positioned( + top: 0, + left: 0, + child: actorText + ), + ), + Visibility( + visible: showBubbleTime, + child: Positioned( + bottom: 0, + right: 0, + child: Row( + children: [ + timeText, + if(widget.isSender) ...[ + SizedBox(width: widget.spacing), + if(widget.isRead) + Icon(Icons.done_all_outlined, size: widget.timeIconSize, color: widget.timeIconColor) + else + Icon(Icons.done_outlined, size: widget.timeIconSize, color: widget.timeIconColor) + ] + ], + ) + ), + ), + Visibility( + visible: downloadProgress > 0, + child: Positioned( + bottom: 0, + right: 0, + left: 0, + child: LinearProgressIndicator(value: downloadProgress/100), + ), + ), + ], ), - Visibility( - visible: downloadProgress > 0, - child: Positioned( - bottom: 0, - right: 0, - left: 0, - child: LinearProgressIndicator(value: downloadProgress/100), - ), - ), - ], - ), + ), + ], ), ), ), From 91ef689d2a244092acec8fc6cf28ba00c42c7c8e Mon Sep 17 00:00:00 2001 From: Pupsi28 Date: Wed, 1 May 2024 17:37:03 +0200 Subject: [PATCH 3/9] replies now get displayed --- lib/view/pages/talk/chatView.dart | 5 +- .../pages/talk/components/chatBubble.dart | 107 ++++++++++++------ .../pages/talk/components/chatTextfield.dart | 49 +++++++- 3 files changed, 122 insertions(+), 39 deletions(-) diff --git a/lib/view/pages/talk/chatView.dart b/lib/view/pages/talk/chatView.dart index c66725f..34577bb 100644 --- a/lib/view/pages/talk/chatView.dart +++ b/lib/view/pages/talk/chatView.dart @@ -71,6 +71,7 @@ class _ChatViewState extends State { chatData: widget.room, refetch: _query, isRead: element.id <= commonRead, + selfId: widget.selfId, ) ); }); @@ -129,8 +130,8 @@ class _ChatViewState extends State { Container( color: Theme.of(context).colorScheme.background, child: TalkNavigator.isSecondaryVisible(context) - ? ChatTextfield(widget.room.token) - : SafeArea(child: ChatTextfield(widget.room.token) + ? ChatTextfield(widget.room.token, selfId: widget.selfId) + : SafeArea(child: ChatTextfield(widget.room.token, selfId: widget.selfId) ), ) ], diff --git a/lib/view/pages/talk/components/chatBubble.dart b/lib/view/pages/talk/components/chatBubble.dart index c2a63c2..428a168 100644 --- a/lib/view/pages/talk/components/chatBubble.dart +++ b/lib/view/pages/talk/components/chatBubble.dart @@ -30,6 +30,7 @@ class ChatBubble extends StatefulWidget { final GetChatResponseObject bubbleData; final GetRoomResponseObject chatData; final bool isRead; + final String? selfId; final double spacing = 3; final double timeIconSize = 11; @@ -44,6 +45,7 @@ class ChatBubble extends StatefulWidget { required this.chatData, required this.refetch, this.isRead = false, + this.selfId, super.key}); @override @@ -267,7 +269,14 @@ class _ChatBubbleState extends State { message = ChatMessage(originalMessage: widget.bubbleData.message, originalData: widget.bubbleData.messageParameters); var showActorDisplayName = widget.bubbleData.messageType == GetRoomResponseObjectMessageType.comment && widget.chatData.type != GetRoomResponseObjectConversationType.oneToOne; var showBubbleTime = widget.bubbleData.messageType != GetRoomResponseObjectMessageType.system; + var parent = widget.bubbleData.parent; + var isSenderOfParent = parent != null && parent.actorId == widget.selfId; + var parentMessage = parent == null + ? '' + : ChatMessage(originalMessage: parent.message, originalData: parent.messageParameters).containsFile + ? 'Datei' + : parent.message; var actorText = Text( widget.bubbleData.actorDisplayName, @@ -347,37 +356,6 @@ class _ChatBubbleState extends State { style: getStyle(), child: Column( children: [ - Visibility( - visible: parent != null && parent.message.isNotEmpty, - child: Wrap( - alignment: WrapAlignment.start, - clipBehavior: Clip.hardEdge, - children: [ - DecoratedBox( - decoration: BoxDecoration( - color: Theme.of(context).secondaryHeaderColor, - ), - child: Text( - parent?.message ?? '', - maxLines: 2, - style: const TextStyle( - overflow: TextOverflow.ellipsis, - ), - ), - ), - ], - ), - // SizedBox( - // width: parentMessageWidth < MediaQuery.of(context).size.width * 0.9 - // ? parentMessageWidth.toDouble() - // : MediaQuery.of(context).size.width * 0.9, - // height: 20, - // child: Text( - // parentMessage?.message ?? 'Ranz', - // overflow: TextOverflow.clip, - // ), - // ), - ), Container( constraints: BoxConstraints( maxWidth: MediaQuery.of(context).size.width * 0.9, @@ -387,10 +365,6 @@ class _ChatBubbleState extends State { ), child: Stack( children: [ - Padding( - padding: EdgeInsets.only(bottom: showBubbleTime ? 18 : 0, top: showActorDisplayName ? 18 : 0), - child: message.getWidget() - ), Visibility( visible: showActorDisplayName, child: Positioned( @@ -399,6 +373,69 @@ class _ChatBubbleState extends State { child: actorText ), ), + Padding( + padding: EdgeInsets.only(bottom: showBubbleTime ? 18 : 0, top: showActorDisplayName ? 18 : 0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Visibility( + visible: parent != null && parent.message.isNotEmpty, + child: Wrap( + alignment: WrapAlignment.start, + clipBehavior: Clip.hardEdge, + children: [ + DecoratedBox( + decoration: BoxDecoration( + color: isSenderOfParent + ? getSelfStyle(false).color?.withGreen(255).withOpacity(0.2) + : Colors.orange.withOpacity(0.2), + borderRadius: const BorderRadius.all(Radius.circular(5)), + border: Border( + left: BorderSide( + color: isSenderOfParent + ? getSelfStyle(false).color!.withGreen(255) + : Colors.orange, + width: 5, + ), + ), + ), + child: Padding( + padding: const EdgeInsets.all(5).add(const EdgeInsets.only(left: 5)), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + parent?.actorDisplayName ?? '', + maxLines: 1, + style: TextStyle( + overflow: TextOverflow.ellipsis, + color: isSenderOfParent + ? getSelfStyle(false).color?.withGreen(255) + : Colors.orange, + fontSize: 12, + ), + ), + Text( + parentMessage, + maxLines: 2, + style: TextStyle( + overflow: TextOverflow.ellipsis, + color: Theme.of(context).colorScheme.onSurface.withOpacity(0.9), + fontSize: 12, + ), + ), + ], + ), + ), + ), + ], + ), + ), + const SizedBox(height: 5), + message.getWidget(), + ], + ), + ), Visibility( visible: showBubbleTime, child: Positioned( diff --git a/lib/view/pages/talk/components/chatTextfield.dart b/lib/view/pages/talk/components/chatTextfield.dart index a4199cd..734f641 100644 --- a/lib/view/pages/talk/components/chatTextfield.dart +++ b/lib/view/pages/talk/components/chatTextfield.dart @@ -1,5 +1,6 @@ import 'dart:io'; +import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:nextcloud/nextcloud.dart'; import 'package:persistent_bottom_nav_bar_v2/persistent_bottom_nav_bar_v2.dart'; @@ -18,7 +19,8 @@ import '../../files/filesUploadDialog.dart'; class ChatTextfield extends StatefulWidget { final String sendToToken; - const ChatTextfield(this.sendToToken, {super.key}); + final String? selfId; + const ChatTextfield(this.sendToToken, {this.selfId, super.key}); @override State createState() => _ChatTextfieldState(); @@ -106,7 +108,50 @@ class _ChatTextfieldState extends State { icon: const Icon(Icons.close_outlined), padding: const EdgeInsets.only(left: 0), ), - Text(referenceMessage.message), + Flexible( + child: DecoratedBox( + decoration: BoxDecoration( + color: referenceMessage.actorId == widget.selfId + ? Colors.green.withOpacity(0.2) + : Colors.orange.withOpacity(0.2), + borderRadius: const BorderRadius.all(Radius.circular(5)), + border: Border(left: BorderSide( + color: referenceMessage.actorId == widget.selfId + ? Colors.green + : Colors.orange, + width: 5 + )), + ), + child: Padding( + padding: const EdgeInsets.all(5).add(const EdgeInsets.only(left: 5)), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + referenceMessage.actorDisplayName, + maxLines: 1, + style: TextStyle( + overflow: TextOverflow.ellipsis, + color: referenceMessage.actorId == widget.selfId + ? Colors.green + : Colors.orange, + fontSize: 12, + ), + ), + Text( + referenceMessage.message, + maxLines: 2, + style: TextStyle( + overflow: TextOverflow.ellipsis, + color: Theme.of(context).colorScheme.onSurface.withOpacity(0.9), + fontSize: 12, + ), + ), + ], + ), + ), + ), + ), ], ); } else { From fc72391a75662e2b127b9b4d544357c9c83f9807 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Elias=20M=C3=BCller?= Date: Tue, 7 May 2024 09:01:30 +0200 Subject: [PATCH 4/9] fixed bug when changing accounts --- lib/app.dart | 7 ++++--- lib/main.dart | 3 +++ lib/notification/notificationTasks.dart | 4 ++-- 3 files changed, 9 insertions(+), 5 deletions(-) diff --git a/lib/app.dart b/lib/app.dart index 11ea0ac..a250380 100644 --- a/lib/app.dart +++ b/lib/app.dart @@ -11,6 +11,7 @@ import 'package:badges/badges.dart' as badges; import 'api/mhsl/breaker/getBreakers/getBreakersResponse.dart'; import 'api/mhsl/server/userIndex/update/updateUserindex.dart'; +import 'main.dart'; import 'model/breakers/Breaker.dart'; import 'model/breakers/BreakerProps.dart'; import 'model/chatList/chatListProps.dart'; @@ -28,13 +29,12 @@ import 'view/pages/timetable/timetable.dart'; class App extends StatefulWidget { const App({super.key}); - static PersistentTabController bottomNavigator = PersistentTabController(initialIndex: 0); - @override State createState() => _AppState(); } class _AppState extends State with WidgetsBindingObserver { + late Timer refetchChats; late Timer updateTimings; @@ -58,6 +58,7 @@ class _AppState extends State with WidgetsBindingObserver { @override void initState() { + Main.bottomNavigator = PersistentTabController(initialIndex: 0); WidgetsBinding.instance.addObserver(this); WidgetsBinding.instance.addPostFrameCallback((timeStamp) { Provider.of(context, listen: false).run(); @@ -95,7 +96,7 @@ class _AppState extends State with WidgetsBindingObserver { @override Widget build(BuildContext context) => PersistentTabView( - controller: App.bottomNavigator, + controller: Main.bottomNavigator, navBarOverlap: const NavBarOverlap.none(), backgroundColor: Theme.of(context).colorScheme.primary, diff --git a/lib/main.dart b/lib/main.dart index 10b741b..0e1aa43 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -9,6 +9,7 @@ import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:jiffy/jiffy.dart'; import 'package:loader_overlay/loader_overlay.dart'; +import 'package:persistent_bottom_nav_bar_v2/persistent_bottom_nav_bar_v2.dart'; import 'package:provider/provider.dart'; import 'package:flutter_localizations/flutter_localizations.dart'; @@ -74,6 +75,8 @@ Future main() async { class Main extends StatefulWidget { const Main({super.key}); + static PersistentTabController bottomNavigator = PersistentTabController(initialIndex: 0); + @override State
createState() => _MainState(); diff --git a/lib/notification/notificationTasks.dart b/lib/notification/notificationTasks.dart index 9719b67..b5d50cd 100644 --- a/lib/notification/notificationTasks.dart +++ b/lib/notification/notificationTasks.dart @@ -3,7 +3,7 @@ import 'package:flutter/cupertino.dart'; import 'package:flutter_app_badger/flutter_app_badger.dart'; import 'package:provider/provider.dart'; -import '../app.dart'; +import '../main.dart'; import '../model/chatList/chatListProps.dart'; import '../model/chatList/chatProps.dart'; @@ -18,6 +18,6 @@ class NotificationTasks { } static void navigateToTalk() { - App.bottomNavigator.jumpToTab(1); + Main.bottomNavigator.jumpToTab(1); } } From 7a393bf6300a675152559efc7b4d0a6835cd7843 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Elias=20M=C3=BCller?= Date: Tue, 7 May 2024 10:51:46 +0200 Subject: [PATCH 5/9] refactored answer references and added animation --- lib/model/chatList/chatProps.dart | 5 +- .../talk/components/answerReference.dart | 58 ++++ .../pages/talk/components/chatBubble.dart | 253 +++++++----------- .../talk/components/chatBubbleStyles.dart | 54 ++++ .../pages/talk/components/chatTextfield.dart | 55 +--- 5 files changed, 215 insertions(+), 210 deletions(-) create mode 100644 lib/view/pages/talk/components/answerReference.dart create mode 100644 lib/view/pages/talk/components/chatBubbleStyles.dart diff --git a/lib/model/chatList/chatProps.dart b/lib/model/chatList/chatProps.dart index 7fd2f6c..7f77107 100644 --- a/lib/model/chatList/chatProps.dart +++ b/lib/model/chatList/chatProps.dart @@ -6,12 +6,11 @@ import '../dataHolder.dart'; class ChatProps extends DataHolder { String _queryToken = ''; DateTime _lastTokenSet = DateTime.now(); + int? referenceMessageId; GetChatResponse? _getChatResponse; GetChatResponse get getChatResponse => _getChatResponse!; - int? referenceMessageId; - @override List properties() => [_getChatResponse]; @@ -34,7 +33,7 @@ class ChatProps extends DataHolder { void setReferenceMessageId(int? messageId) { referenceMessageId = messageId; - run(); + notifyListeners(); } void setQueryToken(String token) { diff --git a/lib/view/pages/talk/components/answerReference.dart b/lib/view/pages/talk/components/answerReference.dart new file mode 100644 index 0000000..212785c --- /dev/null +++ b/lib/view/pages/talk/components/answerReference.dart @@ -0,0 +1,58 @@ +import 'package:flutter/material.dart'; + +import '../../../../api/marianumcloud/talk/chat/getChatResponse.dart'; +import 'chatBubbleStyles.dart'; + +class AnswerReference extends StatelessWidget { + final BuildContext context; + final GetChatResponseObject referenceMessage; + final String? selfId; + const AnswerReference({required this.context, required this.referenceMessage, required this.selfId, super.key}); + + @override + Widget build(BuildContext context) { + var style = ChatBubbleStyles(context); + return DecoratedBox( + decoration: BoxDecoration( + color: referenceMessage.actorId == selfId + ? style.getSelfStyle(false).color!.withGreen(200).withOpacity(0.2) + : style.getRemoteStyle(false).color!.withWhite(200).withOpacity(0.2), + borderRadius: const BorderRadius.all(Radius.circular(5)), + border: Border(left: BorderSide( + color: referenceMessage.actorId == selfId + ? style.getSelfStyle(false).color!.withGreen(200) + : style.getRemoteStyle(false).color!.withWhite(200), + width: 5 + )), + ), + child: Padding( + padding: const EdgeInsets.all(5).add(const EdgeInsets.only(left: 5)), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + referenceMessage.actorDisplayName, + maxLines: 1, + style: TextStyle( + overflow: TextOverflow.ellipsis, + color: referenceMessage.actorId == selfId + ? style.getSelfStyle(false).color!.withGreen(200) + : style.getRemoteStyle(false).color!.withWhite(200), + fontSize: 12, + ), + ), + Text( + referenceMessage.message, + maxLines: 2, + style: TextStyle( + overflow: TextOverflow.ellipsis, + color: Theme.of(context).colorScheme.onSurface, + fontSize: 12, + ), + ), + ], + ), + ), + ); + } +} diff --git a/lib/view/pages/talk/components/chatBubble.dart b/lib/view/pages/talk/components/chatBubble.dart index 428a168..85b6280 100644 --- a/lib/view/pages/talk/components/chatBubble.dart +++ b/lib/view/pages/talk/components/chatBubble.dart @@ -18,9 +18,10 @@ import '../../../../api/marianumcloud/talk/reactMessage/reactMessage.dart'; import '../../../../api/marianumcloud/talk/reactMessage/reactMessageParams.dart'; import '../../../../api/marianumcloud/talk/room/getRoomResponse.dart'; import '../../../../model/chatList/chatProps.dart'; -import '../../../../theming/appTheme.dart'; import '../../../../widget/debug/debugTile.dart'; import '../../files/fileElement.dart'; +import 'answerReference.dart'; +import 'chatBubbleStyles.dart'; import 'chatMessage.dart'; import '../messageReactions.dart'; @@ -52,53 +53,24 @@ class ChatBubble extends StatefulWidget { State createState() => _ChatBubbleState(); } -class _ChatBubbleState extends State { - - BubbleStyle getSystemStyle() => BubbleStyle( - color: AppTheme.isDarkMode(context) ? const Color(0xff182229) : Colors.white, - borderWidth: 1, - elevation: 2, - margin: const BubbleEdges.only(bottom: 20, top: 10), - alignment: Alignment.center, - ); - - BubbleStyle getRemoteStyle(bool seamless) { - var color = AppTheme.isDarkMode(context) ? const Color(0xff202c33) : Colors.white; - return BubbleStyle( - nip: BubbleNip.leftTop, - color: seamless ? Colors.transparent : color, - borderWidth: seamless ? 0 : 1, - elevation: seamless ? 0 : 1, - margin: const BubbleEdges.only(bottom: 10, left: 10, right: 50), - alignment: Alignment.topLeft, - ); - } - - BubbleStyle getSelfStyle(bool seamless) { - var color = AppTheme.isDarkMode(context) ? const Color(0xff005c4b) : const Color(0xffd3d3d3); - return BubbleStyle( - nip: BubbleNip.rightBottom, - color: seamless ? Colors.transparent : color, - borderWidth: seamless ? 0 : 1, - elevation: seamless ? 0 : 1, - margin: const BubbleEdges.only(bottom: 10, right: 10, left: 50), - alignment: Alignment.topRight, - ); - } - +class _ChatBubbleState extends State with SingleTickerProviderStateMixin { late ChatMessage message; double downloadProgress = 0; Future? downloadCore; + late Offset _position = const Offset(0, 0); + late Offset _dragStartPosition = Offset.zero; + BubbleStyle getStyle() { + var styles = ChatBubbleStyles(context); if(widget.bubbleData.messageType == GetRoomResponseObjectMessageType.comment) { if(widget.isSender) { - return getSelfStyle(false); + return styles.getSelfStyle(false); } else { - return getRemoteStyle(false); + return styles.getRemoteStyle(false); } } else { - return getSystemStyle(); + return styles.getSystemStyle(); } } @@ -268,16 +240,10 @@ class _ChatBubbleState extends State { Widget build(BuildContext context) { message = ChatMessage(originalMessage: widget.bubbleData.message, originalData: widget.bubbleData.messageParameters); var showActorDisplayName = widget.bubbleData.messageType == GetRoomResponseObjectMessageType.comment && widget.chatData.type != GetRoomResponseObjectConversationType.oneToOne; - var showBubbleTime = widget.bubbleData.messageType != GetRoomResponseObjectMessageType.system; + var showBubbleTime = widget.bubbleData.messageType != GetRoomResponseObjectMessageType.system + && widget.bubbleData.messageType != GetRoomResponseObjectMessageType.deletedComment; var parent = widget.bubbleData.parent; - var isSenderOfParent = parent != null && parent.actorId == widget.selfId; - var parentMessage = parent == null - ? '' - : ChatMessage(originalMessage: parent.message, originalData: parent.messageParameters).containsFile - ? 'Datei' - : parent.message; - var actorText = Text( widget.bubbleData.actorDisplayName, textAlign: TextAlign.start, @@ -299,7 +265,19 @@ class _ChatBubbleState extends State { children: [ GestureDetector( + onHorizontalDragStart: (details) { + _dragStartPosition = _position; + }, + onHorizontalDragUpdate: (details) { + var dx = details.delta.dx - _dragStartPosition.dx; + setState(() { + _position = _position.dx.abs() > 30 ? Offset(_position.dx, 0) : Offset(_position.dx + dx, 0); + }); + }, onHorizontalDragEnd: (DragEndDetails details) { + setState(() { + _position = const Offset(0, 0); + }); if(widget.bubbleData.isReplyable) { Provider.of(context, listen: false).setReferenceMessageId(widget.bubbleData.id); } @@ -352,122 +330,80 @@ class _ChatBubbleState extends State { } }); }, - child: Bubble( - style: getStyle(), - child: Column( - children: [ - Container( - constraints: BoxConstraints( - maxWidth: MediaQuery.of(context).size.width * 0.9, - minWidth: showActorDisplayName - ? actorText.size.width - : timeText.size.width + (widget.isSender ? widget.spacing + widget.timeIconSize : 0) + 3, - ), - child: Stack( - children: [ - Visibility( - visible: showActorDisplayName, - child: Positioned( - top: 0, - left: 0, - child: actorText + child: Transform.translate( + offset: _position, + child: Bubble( + style: getStyle(), + child: Column( + children: [ + Container( + constraints: BoxConstraints( + maxWidth: MediaQuery.of(context).size.width * 0.9, + minWidth: showActorDisplayName + ? actorText.size.width + : timeText.size.width + (widget.isSender ? widget.spacing + widget.timeIconSize : 0) + 3, + ), + child: Stack( + children: [ + Visibility( + visible: showActorDisplayName, + child: Positioned( + top: 0, + left: 0, + child: actorText + ), ), - ), - Padding( - padding: EdgeInsets.only(bottom: showBubbleTime ? 18 : 0, top: showActorDisplayName ? 18 : 0), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Visibility( - visible: parent != null && parent.message.isNotEmpty, - child: Wrap( - alignment: WrapAlignment.start, - clipBehavior: Clip.hardEdge, + Padding( + padding: EdgeInsets.only(bottom: showBubbleTime ? 18 : 0, top: showActorDisplayName ? 18 : 0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + if(parent != null && widget.bubbleData.messageType == GetRoomResponseObjectMessageType.comment) ...[ + AnswerReference( + context: context, + referenceMessage: parent, + selfId: widget.selfId, + ), + const SizedBox(height: 5), + ], + message.getWidget(), + ], + ), + ), + Visibility( + visible: showBubbleTime, + child: Positioned( + bottom: 0, + right: 0, + child: Row( children: [ - DecoratedBox( - decoration: BoxDecoration( - color: isSenderOfParent - ? getSelfStyle(false).color?.withGreen(255).withOpacity(0.2) - : Colors.orange.withOpacity(0.2), - borderRadius: const BorderRadius.all(Radius.circular(5)), - border: Border( - left: BorderSide( - color: isSenderOfParent - ? getSelfStyle(false).color!.withGreen(255) - : Colors.orange, - width: 5, - ), - ), - ), - child: Padding( - padding: const EdgeInsets.all(5).add(const EdgeInsets.only(left: 5)), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - parent?.actorDisplayName ?? '', - maxLines: 1, - style: TextStyle( - overflow: TextOverflow.ellipsis, - color: isSenderOfParent - ? getSelfStyle(false).color?.withGreen(255) - : Colors.orange, - fontSize: 12, - ), - ), - Text( - parentMessage, - maxLines: 2, - style: TextStyle( - overflow: TextOverflow.ellipsis, - color: Theme.of(context).colorScheme.onSurface.withOpacity(0.9), - fontSize: 12, - ), - ), - ], - ), - ), - ), + timeText, + if(widget.isSender) ...[ + SizedBox(width: widget.spacing), + Icon( + widget.isRead ? Icons.done_all_outlined: Icons.done_outlined, + size: widget.timeIconSize, + color: widget.timeIconColor + ) + ] ], - ), - ), - const SizedBox(height: 5), - message.getWidget(), - ], + ) + ), ), - ), - Visibility( - visible: showBubbleTime, - child: Positioned( + Visibility( + visible: downloadProgress > 0, + child: Positioned( bottom: 0, right: 0, - child: Row( - children: [ - timeText, - if(widget.isSender) ...[ - SizedBox(width: widget.spacing), - if(widget.isRead) - Icon(Icons.done_all_outlined, size: widget.timeIconSize, color: widget.timeIconColor) - else - Icon(Icons.done_outlined, size: widget.timeIconSize, color: widget.timeIconColor) - ] - ], - ) + left: 0, + child: LinearProgressIndicator(value: downloadProgress/100), + ), ), - ), - Visibility( - visible: downloadProgress > 0, - child: Positioned( - bottom: 0, - right: 0, - left: 0, - child: LinearProgressIndicator(value: downloadProgress/100), - ), - ), - ], + ], + ), ), - ), - ], + ], + ), ), ), ), @@ -496,9 +432,7 @@ class _ChatBubbleState extends State { DeleteReactMessage( chatToken: widget.chatData.token, messageId: widget.bubbleData.id, - params: DeleteReactMessageParams( - e.key - ), + params: DeleteReactMessageParams(e.key), ).run().then((value) => widget.refetch(renew: true)); } else { @@ -506,11 +440,8 @@ class _ChatBubbleState extends State { ReactMessage( chatToken: widget.chatData.token, messageId: widget.bubbleData.id, - params: ReactMessageParams( - e.key - ) + params: ReactMessageParams(e.key) ).run().then((value) => widget.refetch(renew: true)); - } }, ), diff --git a/lib/view/pages/talk/components/chatBubbleStyles.dart b/lib/view/pages/talk/components/chatBubbleStyles.dart new file mode 100644 index 0000000..c640ca2 --- /dev/null +++ b/lib/view/pages/talk/components/chatBubbleStyles.dart @@ -0,0 +1,54 @@ +import 'package:bubble/bubble.dart'; +import 'package:flutter/material.dart'; + +import '../../../../theming/appTheme.dart'; + +extension ColorExtensions on Color { + Color invert() { + final r = 255 - red; + final g = 255 - green; + final b = 255 - blue; + + return Color.fromARGB((opacity * 255).round(), r, g, b); + } + + Color withWhite(int whiteValue) => Color.fromARGB(alpha, whiteValue, whiteValue, whiteValue); +} + +class ChatBubbleStyles { + final BuildContext context; + + ChatBubbleStyles(this.context); + + BubbleStyle getSystemStyle() => BubbleStyle( + color: AppTheme.isDarkMode(context) ? const Color(0xff182229) : Colors.white, + borderWidth: 1, + elevation: 2, + margin: const BubbleEdges.only(bottom: 20, top: 10), + alignment: Alignment.center, + ); + + BubbleStyle getRemoteStyle(bool seamless) { + var color = AppTheme.isDarkMode(context) ? const Color(0xff202c33) : Colors.white; + return BubbleStyle( + nip: BubbleNip.leftTop, + color: seamless ? Colors.transparent : color, + borderWidth: seamless ? 0 : 1, + elevation: seamless ? 0 : 1, + margin: const BubbleEdges.only(bottom: 10, left: 10, right: 50), + alignment: Alignment.topLeft, + ); + } + + BubbleStyle getSelfStyle(bool seamless) { + var color = AppTheme.isDarkMode(context) ? const Color(0xff005c4b) : const Color(0xffd3d3d3); + return BubbleStyle( + nip: BubbleNip.rightBottom, + color: seamless ? Colors.transparent : color, + borderWidth: seamless ? 0 : 1, + elevation: seamless ? 0 : 1, + margin: const BubbleEdges.only(bottom: 10, right: 10, left: 50), + alignment: Alignment.topRight, + ); + } +} \ No newline at end of file diff --git a/lib/view/pages/talk/components/chatTextfield.dart b/lib/view/pages/talk/components/chatTextfield.dart index 734f641..4916cd5 100644 --- a/lib/view/pages/talk/components/chatTextfield.dart +++ b/lib/view/pages/talk/components/chatTextfield.dart @@ -16,6 +16,7 @@ import '../../../../storage/base/settingsProvider.dart'; import '../../../../widget/filePick.dart'; import '../../../../widget/focusBehaviour.dart'; import '../../files/filesUploadDialog.dart'; +import 'answerReference.dart'; class ChatTextfield extends StatefulWidget { final String sendToToken; @@ -98,60 +99,22 @@ class _ChatTextfieldState extends State { children: [ Consumer( builder: (context, data, child) { - // Text(data.referenceMessageId.toString()); if(data.referenceMessageId != null) { - var referenceMessage = data.getChatResponse.sortByTimestamp().where((element) => element.id == data.referenceMessageId).first; + var referenceMessage = data.getChatResponse.sortByTimestamp().where((element) => element.id == data.referenceMessageId).last; return Row( children: [ + Expanded( + child: AnswerReference( + context: context, + referenceMessage: referenceMessage, + selfId: widget.selfId, + ), + ), IconButton( onPressed: () => data.setReferenceMessageId(null), icon: const Icon(Icons.close_outlined), padding: const EdgeInsets.only(left: 0), ), - Flexible( - child: DecoratedBox( - decoration: BoxDecoration( - color: referenceMessage.actorId == widget.selfId - ? Colors.green.withOpacity(0.2) - : Colors.orange.withOpacity(0.2), - borderRadius: const BorderRadius.all(Radius.circular(5)), - border: Border(left: BorderSide( - color: referenceMessage.actorId == widget.selfId - ? Colors.green - : Colors.orange, - width: 5 - )), - ), - child: Padding( - padding: const EdgeInsets.all(5).add(const EdgeInsets.only(left: 5)), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - referenceMessage.actorDisplayName, - maxLines: 1, - style: TextStyle( - overflow: TextOverflow.ellipsis, - color: referenceMessage.actorId == widget.selfId - ? Colors.green - : Colors.orange, - fontSize: 12, - ), - ), - Text( - referenceMessage.message, - maxLines: 2, - style: TextStyle( - overflow: TextOverflow.ellipsis, - color: Theme.of(context).colorScheme.onSurface.withOpacity(0.9), - fontSize: 12, - ), - ), - ], - ), - ), - ), - ), ], ); } else { From 81f5b662b79d6f4c9064f3915901cef8134b4011 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Elias=20M=C3=BCller?= Date: Tue, 7 May 2024 11:01:46 +0200 Subject: [PATCH 6/9] added support for rich object messages --- lib/view/pages/talk/components/answerReference.dart | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/view/pages/talk/components/answerReference.dart b/lib/view/pages/talk/components/answerReference.dart index 212785c..ce02745 100644 --- a/lib/view/pages/talk/components/answerReference.dart +++ b/lib/view/pages/talk/components/answerReference.dart @@ -1,6 +1,7 @@ import 'package:flutter/material.dart'; import '../../../../api/marianumcloud/talk/chat/getChatResponse.dart'; +import '../../../../api/marianumcloud/talk/chat/richObjectStringProcessor.dart'; import 'chatBubbleStyles.dart'; class AnswerReference extends StatelessWidget { @@ -42,7 +43,7 @@ class AnswerReference extends StatelessWidget { ), ), Text( - referenceMessage.message, + RichObjectStringProcessor.parseToString(referenceMessage.message, referenceMessage.messageParameters), maxLines: 2, style: TextStyle( overflow: TextOverflow.ellipsis, From 0f84257ebad3589779d9ec56022a8f4b7467af57 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Elias=20M=C3=BCller?= Date: Fri, 10 May 2024 22:27:24 +0200 Subject: [PATCH 7/9] saving message references in draft --- lib/model/chatList/chatProps.dart | 24 +++++++++++++++---- lib/storage/talk/talkSettings.dart | 3 ++- lib/storage/talk/talkSettings.g.dart | 2 ++ .../pages/talk/components/chatBubble.dart | 2 +- .../pages/talk/components/chatTextfield.dart | 16 ++++++------- lib/view/settings/defaultSettings.dart | 1 + 6 files changed, 34 insertions(+), 14 deletions(-) diff --git a/lib/model/chatList/chatProps.dart b/lib/model/chatList/chatProps.dart index 7f77107..80b1038 100644 --- a/lib/model/chatList/chatProps.dart +++ b/lib/model/chatList/chatProps.dart @@ -1,16 +1,23 @@ +import 'package:flutter/cupertino.dart'; +import 'package:provider/provider.dart'; + import '../../api/apiResponse.dart'; import '../../api/marianumcloud/talk/chat/getChatCache.dart'; import '../../api/marianumcloud/talk/chat/getChatResponse.dart'; +import '../../storage/base/settingsProvider.dart'; import '../dataHolder.dart'; class ChatProps extends DataHolder { String _queryToken = ''; DateTime _lastTokenSet = DateTime.now(); - int? referenceMessageId; + int? _referenceMessageId; GetChatResponse? _getChatResponse; GetChatResponse get getChatResponse => _getChatResponse!; + int? get getReferenceMessageId => _referenceMessageId; + set unsafeInternalSetReferenceMessageId(int? reference) => _referenceMessageId = reference; + @override List properties() => [_getChatResponse]; @@ -31,9 +38,18 @@ class ChatProps extends DataHolder { ); } - void setReferenceMessageId(int? messageId) { - referenceMessageId = messageId; - notifyListeners(); + void setReferenceMessageId(int? messageId, BuildContext context, String sendToToken) { + Future.microtask(() { + _referenceMessageId = messageId; + notifyListeners(); + + var settings = Provider.of(context, listen: false); + if(messageId != null) { + settings.val(write: true).talkSettings.draftReplies[sendToToken] = messageId; + } else { + settings.val(write: true).talkSettings.draftReplies.removeWhere((key, value) => key == sendToToken); + } + }); } void setQueryToken(String token) { diff --git a/lib/storage/talk/talkSettings.dart b/lib/storage/talk/talkSettings.dart index a0df6a2..7c3123a 100644 --- a/lib/storage/talk/talkSettings.dart +++ b/lib/storage/talk/talkSettings.dart @@ -7,8 +7,9 @@ class TalkSettings { bool sortFavoritesToTop; bool sortUnreadToTop; Map drafts; + Map draftReplies; - TalkSettings({required this.sortFavoritesToTop, required this.sortUnreadToTop, required this.drafts}); + TalkSettings({required this.sortFavoritesToTop, required this.sortUnreadToTop, required this.drafts, required this.draftReplies}); factory TalkSettings.fromJson(Map json) => _$TalkSettingsFromJson(json); Map toJson() => _$TalkSettingsToJson(this); diff --git a/lib/storage/talk/talkSettings.g.dart b/lib/storage/talk/talkSettings.g.dart index 83a356d..ad15998 100644 --- a/lib/storage/talk/talkSettings.g.dart +++ b/lib/storage/talk/talkSettings.g.dart @@ -10,6 +10,7 @@ TalkSettings _$TalkSettingsFromJson(Map json) => TalkSettings( sortFavoritesToTop: json['sortFavoritesToTop'] as bool, sortUnreadToTop: json['sortUnreadToTop'] as bool, drafts: Map.from(json['drafts'] as Map), + draftReplies: Map.from(json['draftReplies'] as Map), ); Map _$TalkSettingsToJson(TalkSettings instance) => @@ -17,4 +18,5 @@ Map _$TalkSettingsToJson(TalkSettings instance) => 'sortFavoritesToTop': instance.sortFavoritesToTop, 'sortUnreadToTop': instance.sortUnreadToTop, 'drafts': instance.drafts, + 'draftReplies': instance.draftReplies, }; diff --git a/lib/view/pages/talk/components/chatBubble.dart b/lib/view/pages/talk/components/chatBubble.dart index 85b6280..997185e 100644 --- a/lib/view/pages/talk/components/chatBubble.dart +++ b/lib/view/pages/talk/components/chatBubble.dart @@ -279,7 +279,7 @@ class _ChatBubbleState extends State with SingleTickerProviderStateM _position = const Offset(0, 0); }); if(widget.bubbleData.isReplyable) { - Provider.of(context, listen: false).setReferenceMessageId(widget.bubbleData.id); + Provider.of(context, listen: false).setReferenceMessageId(widget.bubbleData.id, context, widget.chatData.token); } }, onLongPress: showOptionsDialog, diff --git a/lib/view/pages/talk/components/chatTextfield.dart b/lib/view/pages/talk/components/chatTextfield.dart index 4916cd5..2da2f9a 100644 --- a/lib/view/pages/talk/components/chatTextfield.dart +++ b/lib/view/pages/talk/components/chatTextfield.dart @@ -1,6 +1,5 @@ import 'dart:io'; -import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:nextcloud/nextcloud.dart'; import 'package:persistent_bottom_nav_bar_v2/persistent_bottom_nav_bar_v2.dart'; @@ -81,7 +80,8 @@ class _ChatTextfieldState extends State { void initState() { super.initState(); settings = Provider.of(context, listen: false); - Provider.of(context, listen: false).referenceMessageId = null; + Provider.of(context, listen: false).unsafeInternalSetReferenceMessageId = + settings.val().talkSettings.draftReplies[widget.sendToToken]; } @override @@ -99,8 +99,8 @@ class _ChatTextfieldState extends State { children: [ Consumer( builder: (context, data, child) { - if(data.referenceMessageId != null) { - var referenceMessage = data.getChatResponse.sortByTimestamp().where((element) => element.id == data.referenceMessageId).last; + if(data.getReferenceMessageId != null) { + var referenceMessage = data.getChatResponse.sortByTimestamp().where((element) => element.id == data.getReferenceMessageId).last; return Row( children: [ Expanded( @@ -111,7 +111,7 @@ class _ChatTextfieldState extends State { ), ), IconButton( - onPressed: () => data.setReferenceMessageId(null), + onPressed: () => data.setReferenceMessageId(null, context, widget.sendToToken), icon: const Icon(Icons.close_outlined), padding: const EdgeInsets.only(left: 0), ), @@ -194,7 +194,7 @@ class _ChatTextfieldState extends State { const SizedBox(width: 15), FloatingActionButton( mini: true, - onPressed: (){ + onPressed: () { if(_textBoxController.text.isEmpty) return; if(isLoading) return; @@ -203,7 +203,7 @@ class _ChatTextfieldState extends State { }); SendMessage(widget.sendToToken, SendMessageParams( _textBoxController.text, - replyTo: Provider.of(context, listen: false).referenceMessageId.toString() + replyTo: Provider.of(context, listen: false).getReferenceMessageId.toString() )).run().then((value) { _query(); setState(() { @@ -211,8 +211,8 @@ class _ChatTextfieldState extends State { }); _textBoxController.text = ''; setDraft(''); + Provider.of(context, listen: false).setReferenceMessageId(null, context, widget.sendToToken); }); - Provider.of(context, listen: false).referenceMessageId = null; }, backgroundColor: Theme.of(context).primaryColor, elevation: 5, diff --git a/lib/view/settings/defaultSettings.dart b/lib/view/settings/defaultSettings.dart index 20ce747..1ff2d77 100644 --- a/lib/view/settings/defaultSettings.dart +++ b/lib/view/settings/defaultSettings.dart @@ -29,6 +29,7 @@ class DefaultSettings { sortFavoritesToTop: true, sortUnreadToTop: false, drafts: {}, + draftReplies: {}, ), fileSettings: FileSettings( sortFoldersToTop: true, From 1f30e2d97ffc6eb9889d5e0c59ea7d6deef6363e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Elias=20M=C3=BCller?= Date: Fri, 10 May 2024 22:29:40 +0200 Subject: [PATCH 8/9] not replyable messages are no longer swipable --- lib/view/pages/talk/components/chatBubble.dart | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/view/pages/talk/components/chatBubble.dart b/lib/view/pages/talk/components/chatBubble.dart index 997185e..4ee74e0 100644 --- a/lib/view/pages/talk/components/chatBubble.dart +++ b/lib/view/pages/talk/components/chatBubble.dart @@ -269,6 +269,7 @@ class _ChatBubbleState extends State with SingleTickerProviderStateM _dragStartPosition = _position; }, onHorizontalDragUpdate: (details) { + if(!widget.bubbleData.isReplyable) return; var dx = details.delta.dx - _dragStartPosition.dx; setState(() { _position = _position.dx.abs() > 30 ? Offset(_position.dx, 0) : Offset(_position.dx + dx, 0); From 3fb48b5bcfe80f1d938c031c29c8c17699d09a9e Mon Sep 17 00:00:00 2001 From: Pupsi28 Date: Sun, 12 May 2024 14:30:41 +0200 Subject: [PATCH 9/9] added option to reply on long press (and better possibilities to play with chat bubble drag) --- lib/view/pages/talk/components/chatBubble.dart | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/lib/view/pages/talk/components/chatBubble.dart b/lib/view/pages/talk/components/chatBubble.dart index 4ee74e0..8110c1c 100644 --- a/lib/view/pages/talk/components/chatBubble.dart +++ b/lib/view/pages/talk/components/chatBubble.dart @@ -182,6 +182,17 @@ class _ChatBubbleState extends State with SingleTickerProviderStateM ], ), ), + Visibility( + visible: widget.bubbleData.isReplyable, + child: ListTile( + leading: const Icon(Icons.reply_outlined), + title: const Text('Antworten'), + onTap: () => { + Provider.of(context, listen: false).setReferenceMessageId(widget.bubbleData.id, context, widget.chatData.token), + Navigator.of(context).pop(), + }, + ), + ), Visibility( visible: canReact, child: ListTile( @@ -272,7 +283,7 @@ class _ChatBubbleState extends State with SingleTickerProviderStateM if(!widget.bubbleData.isReplyable) return; var dx = details.delta.dx - _dragStartPosition.dx; setState(() { - _position = _position.dx.abs() > 30 ? Offset(_position.dx, 0) : Offset(_position.dx + dx, 0); + _position = (_position.dx + dx).abs() > 30 ? Offset(_position.dx, 0) : Offset(_position.dx + dx, 0); }); }, onHorizontalDragEnd: (DragEndDetails details) {