refactored answer references and added animation

This commit is contained in:
Elias Müller 2024-05-07 10:51:46 +02:00
parent fc72391a75
commit 7a393bf630
5 changed files with 215 additions and 210 deletions

View File

@ -6,12 +6,11 @@ import '../dataHolder.dart';
class ChatProps extends DataHolder { class ChatProps extends DataHolder {
String _queryToken = ''; String _queryToken = '';
DateTime _lastTokenSet = DateTime.now(); DateTime _lastTokenSet = DateTime.now();
int? referenceMessageId;
GetChatResponse? _getChatResponse; GetChatResponse? _getChatResponse;
GetChatResponse get getChatResponse => _getChatResponse!; GetChatResponse get getChatResponse => _getChatResponse!;
int? referenceMessageId;
@override @override
List<ApiResponse?> properties() => [_getChatResponse]; List<ApiResponse?> properties() => [_getChatResponse];
@ -34,7 +33,7 @@ class ChatProps extends DataHolder {
void setReferenceMessageId(int? messageId) { void setReferenceMessageId(int? messageId) {
referenceMessageId = messageId; referenceMessageId = messageId;
run(); notifyListeners();
} }
void setQueryToken(String token) { void setQueryToken(String token) {

View File

@ -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,
),
),
],
),
),
);
}
}

View File

@ -18,9 +18,10 @@ import '../../../../api/marianumcloud/talk/reactMessage/reactMessage.dart';
import '../../../../api/marianumcloud/talk/reactMessage/reactMessageParams.dart'; import '../../../../api/marianumcloud/talk/reactMessage/reactMessageParams.dart';
import '../../../../api/marianumcloud/talk/room/getRoomResponse.dart'; import '../../../../api/marianumcloud/talk/room/getRoomResponse.dart';
import '../../../../model/chatList/chatProps.dart'; import '../../../../model/chatList/chatProps.dart';
import '../../../../theming/appTheme.dart';
import '../../../../widget/debug/debugTile.dart'; import '../../../../widget/debug/debugTile.dart';
import '../../files/fileElement.dart'; import '../../files/fileElement.dart';
import 'answerReference.dart';
import 'chatBubbleStyles.dart';
import 'chatMessage.dart'; import 'chatMessage.dart';
import '../messageReactions.dart'; import '../messageReactions.dart';
@ -52,53 +53,24 @@ class ChatBubble extends StatefulWidget {
State<ChatBubble> createState() => _ChatBubbleState(); State<ChatBubble> createState() => _ChatBubbleState();
} }
class _ChatBubbleState extends State<ChatBubble> { class _ChatBubbleState extends State<ChatBubble> with SingleTickerProviderStateMixin {
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,
);
}
late ChatMessage message; late ChatMessage message;
double downloadProgress = 0; double downloadProgress = 0;
Future<DownloaderCore>? downloadCore; Future<DownloaderCore>? downloadCore;
late Offset _position = const Offset(0, 0);
late Offset _dragStartPosition = Offset.zero;
BubbleStyle getStyle() { BubbleStyle getStyle() {
var styles = ChatBubbleStyles(context);
if(widget.bubbleData.messageType == GetRoomResponseObjectMessageType.comment) { if(widget.bubbleData.messageType == GetRoomResponseObjectMessageType.comment) {
if(widget.isSender) { if(widget.isSender) {
return getSelfStyle(false); return styles.getSelfStyle(false);
} else { } else {
return getRemoteStyle(false); return styles.getRemoteStyle(false);
} }
} else { } else {
return getSystemStyle(); return styles.getSystemStyle();
} }
} }
@ -268,16 +240,10 @@ class _ChatBubbleState extends State<ChatBubble> {
Widget build(BuildContext context) { Widget build(BuildContext context) {
message = ChatMessage(originalMessage: widget.bubbleData.message, originalData: widget.bubbleData.messageParameters); message = ChatMessage(originalMessage: widget.bubbleData.message, originalData: widget.bubbleData.messageParameters);
var showActorDisplayName = widget.bubbleData.messageType == GetRoomResponseObjectMessageType.comment && widget.chatData.type != GetRoomResponseObjectConversationType.oneToOne; 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 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( var actorText = Text(
widget.bubbleData.actorDisplayName, widget.bubbleData.actorDisplayName,
textAlign: TextAlign.start, textAlign: TextAlign.start,
@ -299,7 +265,19 @@ class _ChatBubbleState extends State<ChatBubble> {
children: [ children: [
GestureDetector( 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) { onHorizontalDragEnd: (DragEndDetails details) {
setState(() {
_position = const Offset(0, 0);
});
if(widget.bubbleData.isReplyable) { if(widget.bubbleData.isReplyable) {
Provider.of<ChatProps>(context, listen: false).setReferenceMessageId(widget.bubbleData.id); Provider.of<ChatProps>(context, listen: false).setReferenceMessageId(widget.bubbleData.id);
} }
@ -352,6 +330,8 @@ class _ChatBubbleState extends State<ChatBubble> {
} }
}); });
}, },
child: Transform.translate(
offset: _position,
child: Bubble( child: Bubble(
style: getStyle(), style: getStyle(),
child: Column( child: Column(
@ -378,60 +358,14 @@ class _ChatBubbleState extends State<ChatBubble> {
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
Visibility( if(parent != null && widget.bubbleData.messageType == GetRoomResponseObjectMessageType.comment) ...[
visible: parent != null && parent.message.isNotEmpty, AnswerReference(
child: Wrap( context: context,
alignment: WrapAlignment.start, referenceMessage: parent,
clipBehavior: Clip.hardEdge, selfId: widget.selfId,
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), const SizedBox(height: 5),
],
message.getWidget(), message.getWidget(),
], ],
), ),
@ -446,10 +380,11 @@ class _ChatBubbleState extends State<ChatBubble> {
timeText, timeText,
if(widget.isSender) ...[ if(widget.isSender) ...[
SizedBox(width: widget.spacing), SizedBox(width: widget.spacing),
if(widget.isRead) Icon(
Icon(Icons.done_all_outlined, size: widget.timeIconSize, color: widget.timeIconColor) widget.isRead ? Icons.done_all_outlined: Icons.done_outlined,
else size: widget.timeIconSize,
Icon(Icons.done_outlined, size: widget.timeIconSize, color: widget.timeIconColor) color: widget.timeIconColor
)
] ]
], ],
) )
@ -471,6 +406,7 @@ class _ChatBubbleState extends State<ChatBubble> {
), ),
), ),
), ),
),
Visibility( Visibility(
visible: widget.bubbleData.reactions != null, visible: widget.bubbleData.reactions != null,
child: Transform.translate( child: Transform.translate(
@ -496,9 +432,7 @@ class _ChatBubbleState extends State<ChatBubble> {
DeleteReactMessage( DeleteReactMessage(
chatToken: widget.chatData.token, chatToken: widget.chatData.token,
messageId: widget.bubbleData.id, messageId: widget.bubbleData.id,
params: DeleteReactMessageParams( params: DeleteReactMessageParams(e.key),
e.key
),
).run().then((value) => widget.refetch(renew: true)); ).run().then((value) => widget.refetch(renew: true));
} else { } else {
@ -506,11 +440,8 @@ class _ChatBubbleState extends State<ChatBubble> {
ReactMessage( ReactMessage(
chatToken: widget.chatData.token, chatToken: widget.chatData.token,
messageId: widget.bubbleData.id, messageId: widget.bubbleData.id,
params: ReactMessageParams( params: ReactMessageParams(e.key)
e.key
)
).run().then((value) => widget.refetch(renew: true)); ).run().then((value) => widget.refetch(renew: true));
} }
}, },
), ),

View File

@ -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,
);
}
}

View File

@ -16,6 +16,7 @@ import '../../../../storage/base/settingsProvider.dart';
import '../../../../widget/filePick.dart'; import '../../../../widget/filePick.dart';
import '../../../../widget/focusBehaviour.dart'; import '../../../../widget/focusBehaviour.dart';
import '../../files/filesUploadDialog.dart'; import '../../files/filesUploadDialog.dart';
import 'answerReference.dart';
class ChatTextfield extends StatefulWidget { class ChatTextfield extends StatefulWidget {
final String sendToToken; final String sendToToken;
@ -98,60 +99,22 @@ class _ChatTextfieldState extends State<ChatTextfield> {
children: [ children: [
Consumer<ChatProps>( Consumer<ChatProps>(
builder: (context, data, child) { builder: (context, data, child) {
// Text(data.referenceMessageId.toString());
if(data.referenceMessageId != null) { 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( return Row(
children: [ children: [
Expanded(
child: AnswerReference(
context: context,
referenceMessage: referenceMessage,
selfId: widget.selfId,
),
),
IconButton( IconButton(
onPressed: () => data.setReferenceMessageId(null), onPressed: () => data.setReferenceMessageId(null),
icon: const Icon(Icons.close_outlined), icon: const Icon(Icons.close_outlined),
padding: const EdgeInsets.only(left: 0), 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 { } else {