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

@ -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<ApiResponse?> properties() => [_getChatResponse];
@ -34,7 +33,7 @@ class ChatProps extends DataHolder {
void setReferenceMessageId(int? messageId) {
referenceMessageId = messageId;
run();
notifyListeners();
}
void setQueryToken(String token) {

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

@ -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<ChatBubble> createState() => _ChatBubbleState();
}
class _ChatBubbleState extends State<ChatBubble> {
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<ChatBubble> with SingleTickerProviderStateMixin {
late ChatMessage message;
double downloadProgress = 0;
Future<DownloaderCore>? 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<ChatBubble> {
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<ChatBubble> {
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<ChatProps>(context, listen: false).setReferenceMessageId(widget.bubbleData.id);
}
@ -352,122 +330,80 @@ class _ChatBubbleState extends State<ChatBubble> {
}
});
},
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<ChatBubble> {
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<ChatBubble> {
ReactMessage(
chatToken: widget.chatData.token,
messageId: widget.bubbleData.id,
params: ReactMessageParams(
e.key
)
params: ReactMessageParams(e.key)
).run().then((value) => widget.refetch(renew: true));
}
},
),

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

@ -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<ChatTextfield> {
children: [
Consumer<ChatProps>(
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 {