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,122 +330,80 @@ class _ChatBubbleState extends State<ChatBubble> {
} }
}); });
}, },
child: Bubble( child: Transform.translate(
style: getStyle(), offset: _position,
child: Column( child: Bubble(
children: [ style: getStyle(),
Container( child: Column(
constraints: BoxConstraints( children: [
maxWidth: MediaQuery.of(context).size.width * 0.9, Container(
minWidth: showActorDisplayName constraints: BoxConstraints(
? actorText.size.width maxWidth: MediaQuery.of(context).size.width * 0.9,
: timeText.size.width + (widget.isSender ? widget.spacing + widget.timeIconSize : 0) + 3, minWidth: showActorDisplayName
), ? actorText.size.width
child: Stack( : timeText.size.width + (widget.isSender ? widget.spacing + widget.timeIconSize : 0) + 3,
children: [ ),
Visibility( child: Stack(
visible: showActorDisplayName, children: [
child: Positioned( Visibility(
top: 0, visible: showActorDisplayName,
left: 0, child: Positioned(
child: actorText top: 0,
left: 0,
child: actorText
),
), ),
), Padding(
Padding( padding: EdgeInsets.only(bottom: showBubbleTime ? 18 : 0, top: showActorDisplayName ? 18 : 0),
padding: EdgeInsets.only(bottom: showBubbleTime ? 18 : 0, top: showActorDisplayName ? 18 : 0), child: Column(
child: Column( crossAxisAlignment: CrossAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start, children: [
children: [ if(parent != null && widget.bubbleData.messageType == GetRoomResponseObjectMessageType.comment) ...[
Visibility( AnswerReference(
visible: parent != null && parent.message.isNotEmpty, context: context,
child: Wrap( referenceMessage: parent,
alignment: WrapAlignment.start, selfId: widget.selfId,
clipBehavior: Clip.hardEdge, ),
const SizedBox(height: 5),
],
message.getWidget(),
],
),
),
Visibility(
visible: showBubbleTime,
child: Positioned(
bottom: 0,
right: 0,
child: Row(
children: [ children: [
DecoratedBox( timeText,
decoration: BoxDecoration( if(widget.isSender) ...[
color: isSenderOfParent SizedBox(width: widget.spacing),
? getSelfStyle(false).color?.withGreen(255).withOpacity(0.2) Icon(
: Colors.orange.withOpacity(0.2), widget.isRead ? Icons.done_all_outlined: Icons.done_outlined,
borderRadius: const BorderRadius.all(Radius.circular(5)), size: widget.timeIconSize,
border: Border( color: widget.timeIconColor
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(
Visibility( visible: downloadProgress > 0,
visible: showBubbleTime, child: Positioned(
child: Positioned(
bottom: 0, bottom: 0,
right: 0, right: 0,
child: Row( left: 0,
children: [ child: LinearProgressIndicator(value: downloadProgress/100),
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),
),
),
],
), ),
), ],
], ),
), ),
), ),
), ),
@ -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 {