import 'package:better_open_file/better_open_file.dart'; import 'package:bubble/bubble.dart'; import 'package:flowder/flowder.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:jiffy/jiffy.dart'; import 'package:provider/provider.dart'; import '../../../api/marianumcloud/talk/chat/getChatResponse.dart'; import '../../../api/marianumcloud/talk/deleteMessage/deleteMessage.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 'chatMessage.dart'; class ChatBubble extends StatefulWidget { final BuildContext context; final bool isSender; final GetChatResponseObject bubbleData; final GetRoomResponseObject chatData; const ChatBubble({ required this.context, required this.isSender, required this.bubbleData, required this.chatData, Key? key}) : super(key: key); @override State createState() => _ChatBubbleState(); } class _ChatBubbleState extends State { BubbleStyle getSystemStyle() { return 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) { return BubbleStyle( nip: BubbleNip.rightBottom, color: seamless ? Colors.transparent : const Color(0xff005c4b), borderWidth: seamless ? 0 : 1, elevation: seamless ? 0 : 1, margin: const BubbleEdges.only(bottom: 10, right: 10, left: 50), alignment: Alignment.topRight, ); } late ChatMessage message; double downloadProgress = 0; Future? downloadCore; Size _textSize(String text, TextStyle style) { final TextPainter textPainter = TextPainter( text: TextSpan(text: text, style: style), maxLines: 1, textDirection: TextDirection.ltr) ..layout(minWidth: 0, maxWidth: double.infinity); return textPainter.size; } BubbleStyle getStyle() { if(widget.bubbleData.messageType == GetRoomResponseObjectMessageType.comment) { if(widget.isSender) { return getSelfStyle(message.containsFile); } else { return getRemoteStyle(message.containsFile); } } else { return getSystemStyle(); } } @override Widget build(BuildContext context) { message = ChatMessage(originalMessage: widget.bubbleData.message, originalData: widget.bubbleData.messageParameters); bool showActorDisplayName = widget.bubbleData.messageType == GetRoomResponseObjectMessageType.comment && widget.chatData.type != GetRoomResponseObjectConversationType.oneToOne; bool showBubbleTime = widget.bubbleData.messageType != GetRoomResponseObjectMessageType.system; var actorTextStyle = TextStyle(color: Theme.of(context).primaryColor, fontWeight: FontWeight.bold); return GestureDetector( child: Bubble( style: getStyle(), child: Container( constraints: BoxConstraints( maxWidth: MediaQuery.of(context).size.width * 0.9, minWidth: showActorDisplayName ? _textSize(widget.bubbleData.actorDisplayName, actorTextStyle).width : 30, ), child: Stack( children: [ Padding( padding: EdgeInsets.only(bottom: showBubbleTime ? 18 : 0, top: showActorDisplayName ? 18 : 0), child: FutureBuilder( future: message.getWidget(), builder: (context, snapshot) { if(!snapshot.hasData) return const CircularProgressIndicator(); return snapshot.data ?? const Icon(Icons.error); }, ) ), Visibility( visible: showActorDisplayName, child: Positioned( top: 0, left: 0, child: Text( widget.bubbleData.actorDisplayName, textAlign: TextAlign.start, style: actorTextStyle, ), ), ), Visibility( visible: showBubbleTime, child: Positioned( bottom: 0, right: 0, child: Text( Jiffy.parseFromMillisecondsSinceEpoch(widget.bubbleData.timestamp * 1000).format(pattern: "HH:mm"), textAlign: TextAlign.end, style: const TextStyle(color: Colors.grey, fontSize: 12), ), ), ), Visibility( visible: downloadProgress > 0, child: Positioned( top: 0, left: 0, right: 0, bottom: 0, child: Stack( children: [ const Center(child: Icon(Icons.download)), const Center(child: CircularProgressIndicator(color: Colors.white)), Center(child: CircularProgressIndicator(value: downloadProgress/100)), ], ) ), ), ], ), ), ), onLongPress: () { showDialog(context: context, builder: (context) { return SimpleDialog( children: [ Visibility( visible: !message.containsFile && widget.bubbleData.messageType == GetRoomResponseObjectMessageType.comment, child: ListTile( leading: const Icon(Icons.copy), title: const Text("Nachricht kopieren"), onTap: () => { Clipboard.setData(ClipboardData(text: widget.bubbleData.message)), Navigator.of(context).pop(), }, ), ), Visibility( visible: !widget.isSender && widget.chatData.type != GetRoomResponseObjectConversationType.oneToOne, child: ListTile( leading: const Icon(Icons.sms_outlined), title: Text("Private Nachricht an '${widget.bubbleData.actorDisplayName}'"), onTap: () => {}, ), ), Visibility( visible: widget.isSender, child: ListTile( leading: const Icon(Icons.delete_outline), title: const Text("Nachricht löschen"), onTap: () { DeleteMessage(widget.chatData.token, widget.bubbleData.id).run().then((value) { Provider.of(context, listen: false).run(); Navigator.of(context).pop(); }); }, ), ), DebugTile(widget.bubbleData.toJson()).asTile(context), ], ); }); }, onTap: () { if(message.file == null) return; if(downloadProgress > 0) { showDialog(context: context, builder: (context) { return AlertDialog( title: const Text("Download abbrechen?"), content: const Text("Möchtest du den Download abbrechen?"), actions: [ TextButton(onPressed: () { Navigator.of(context).pop(); }, child: const Text("Nein")), TextButton(onPressed: () { downloadCore?.then((value) { if(!value.isCancelled) value.cancel(); Navigator.of(context).pop(); }); setState(() { downloadProgress = 0; downloadCore = null; }); }, child: const Text("Ja, Abbrechen")) ], ); }); return; } downloadProgress = 1; downloadCore = FileElement.download(message.file!.path!, message.file!.name, (progress) { if(progress > 1) { setState(() { downloadProgress = progress; }); } }, (result) { setState(() { downloadProgress = 0; }); if(result.type != ResultType.done) { showDialog(context: context, builder: (context) { return AlertDialog( content: Text(result.message), ); }); } }); }, ); } }