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 {