refactored answer references and added animation
This commit is contained in:
parent
fc72391a75
commit
7a393bf630
@ -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) {
|
||||
|
58
lib/view/pages/talk/components/answerReference.dart
Normal file
58
lib/view/pages/talk/components/answerReference.dart
Normal 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,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
@ -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));
|
||||
|
||||
}
|
||||
},
|
||||
),
|
||||
|
54
lib/view/pages/talk/components/chatBubbleStyles.dart
Normal file
54
lib/view/pages/talk/components/chatBubbleStyles.dart
Normal 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,
|
||||
);
|
||||
}
|
||||
}
|
@ -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 {
|
||||
|
Loading…
x
Reference in New Issue
Block a user