195 lines
		
	
	
		
			6.1 KiB
		
	
	
	
		
			Dart
		
	
	
	
	
	
			
		
		
	
	
			195 lines
		
	
	
		
			6.1 KiB
		
	
	
	
		
			Dart
		
	
	
	
	
	
| import 'dart:math' as math;
 | |
| 
 | |
| import 'package:easy_localization/easy_localization.dart';
 | |
| import 'package:flutter/material.dart';
 | |
| import 'package:gap/gap.dart';
 | |
| import 'package:island/models/chat.dart';
 | |
| import 'package:island/pods/chat/call.dart';
 | |
| import 'package:island/widgets/content/markdown.dart';
 | |
| import 'package:material_symbols_icons/material_symbols_icons.dart';
 | |
| import 'package:pretty_diff_text/pretty_diff_text.dart';
 | |
| import 'package:styled_widget/styled_widget.dart';
 | |
| 
 | |
| class MessageContent extends StatelessWidget {
 | |
|   final SnChatMessage item;
 | |
|   final String? translatedText;
 | |
|   final bool isSelectable;
 | |
| 
 | |
|   const MessageContent({
 | |
|     super.key,
 | |
|     required this.item,
 | |
|     this.translatedText,
 | |
|     this.isSelectable = true,
 | |
|   });
 | |
| 
 | |
|   @override
 | |
|   Widget build(BuildContext context) {
 | |
|     if (item.type == 'messages.delete' || item.deletedAt != null) {
 | |
|       return Row(
 | |
|         mainAxisSize: MainAxisSize.min,
 | |
|         crossAxisAlignment: CrossAxisAlignment.center,
 | |
|         children: [
 | |
|           Icon(
 | |
|             Symbols.delete,
 | |
|             size: 16,
 | |
|             color: Theme.of(
 | |
|               context,
 | |
|             ).colorScheme.onSurfaceVariant.withOpacity(0.6),
 | |
|           ),
 | |
|           const Gap(4),
 | |
|           Text(
 | |
|             item.content ?? 'Deleted a message',
 | |
|             style: Theme.of(context).textTheme.bodySmall?.copyWith(
 | |
|               fontSize: 13,
 | |
|               color: Theme.of(
 | |
|                 context,
 | |
|               ).colorScheme.onSurfaceVariant.withOpacity(0.6),
 | |
|               fontStyle: FontStyle.italic,
 | |
|             ),
 | |
|           ),
 | |
|         ],
 | |
|       );
 | |
|     }
 | |
| 
 | |
|     switch (item.type) {
 | |
|       case 'call.start':
 | |
|       case 'call.ended':
 | |
|         return _MessageContentCall(
 | |
|           isEnded: item.type == 'call.ended',
 | |
|           duration: item.meta['duration']?.toDouble(),
 | |
|         );
 | |
|       case 'messages.update':
 | |
|       case 'messages.update.links':
 | |
|         return Row(
 | |
|           mainAxisSize: MainAxisSize.min,
 | |
|           crossAxisAlignment: CrossAxisAlignment.start,
 | |
|           children: [
 | |
|             Icon(
 | |
|               item.type == 'messages.update.links'
 | |
|                   ? Symbols.link
 | |
|                   : Symbols.edit,
 | |
|               size: 16,
 | |
|               color: Theme.of(
 | |
|                 context,
 | |
|               ).colorScheme.onSurfaceVariant.withOpacity(0.6),
 | |
|             ),
 | |
|             const Gap(4),
 | |
|             if (item.meta['previous_content'] is String)
 | |
|               Flexible(
 | |
|                 child: PrettyDiffText(
 | |
|                   oldText: item.meta['previous_content'],
 | |
|                   newText:
 | |
|                       item.content ??
 | |
|                       (item.type == 'messages.update.links'
 | |
|                           ? 'messageUpdateLinks'.tr()
 | |
|                           : 'messageUpdateEdited'.tr()),
 | |
|                   defaultTextStyle: Theme.of(
 | |
|                     context,
 | |
|                   ).textTheme.bodyMedium!.copyWith(
 | |
|                     color: Theme.of(context).colorScheme.onSurfaceVariant,
 | |
|                   ),
 | |
|                   addedTextStyle: TextStyle(
 | |
|                     backgroundColor: Theme.of(
 | |
|                       context,
 | |
|                     ).colorScheme.primaryFixedDim.withOpacity(0.4),
 | |
|                   ),
 | |
|                   deletedTextStyle: TextStyle(
 | |
|                     decoration: TextDecoration.lineThrough,
 | |
|                     color: Theme.of(
 | |
|                       context,
 | |
|                     ).colorScheme.onSurfaceVariant.withOpacity(0.7),
 | |
|                   ),
 | |
|                 ),
 | |
|               )
 | |
|             else
 | |
|               Text(
 | |
|                 item.content ?? 'Edited a message',
 | |
|                 style: Theme.of(context).textTheme.bodySmall?.copyWith(
 | |
|                   color: Theme.of(
 | |
|                     context,
 | |
|                   ).colorScheme.onSurfaceVariant.withOpacity(0.6),
 | |
|                 ),
 | |
|               ),
 | |
|           ],
 | |
|         );
 | |
|       case 'text':
 | |
|       default:
 | |
|         return Column(
 | |
|           mainAxisSize: MainAxisSize.min,
 | |
|           crossAxisAlignment: CrossAxisAlignment.start,
 | |
|           children: [
 | |
|             Flexible(
 | |
|               child: MouseRegion(
 | |
|                 cursor: SystemMouseCursors.text,
 | |
|                 child: MarkdownTextContent(
 | |
|                   content: item.content ?? '*${item.type} has no content*',
 | |
|                   isSelectable: isSelectable,
 | |
|                   linesMargin: EdgeInsets.zero,
 | |
|                 ),
 | |
|               ),
 | |
|             ),
 | |
|             if (translatedText?.isNotEmpty ?? false)
 | |
|               ...([
 | |
|                 ConstrainedBox(
 | |
|                   constraints: BoxConstraints(
 | |
|                     maxWidth: math.min(
 | |
|                       280,
 | |
|                       MediaQuery.of(context).size.width * 0.4,
 | |
|                     ),
 | |
|                   ),
 | |
|                   child: Row(
 | |
|                     mainAxisSize: MainAxisSize.min,
 | |
|                     children: [
 | |
|                       Text('translated').tr().fontSize(11).opacity(0.75),
 | |
|                       const Gap(8),
 | |
|                       Flexible(child: Divider()),
 | |
|                     ],
 | |
|                   ).padding(vertical: 4),
 | |
|                 ),
 | |
|                 MouseRegion(
 | |
|                   cursor: SystemMouseCursors.text,
 | |
|                   child: MarkdownTextContent(
 | |
|                     content: translatedText!,
 | |
|                     isSelectable: isSelectable,
 | |
|                     linesMargin: EdgeInsets.zero,
 | |
|                   ),
 | |
|                 ),
 | |
|               ]),
 | |
|           ],
 | |
|         );
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   static bool hasContent(SnChatMessage item) {
 | |
|     return item.type != 'text' || (item.content?.isNotEmpty ?? false);
 | |
|   }
 | |
| }
 | |
| 
 | |
| class _MessageContentCall extends StatelessWidget {
 | |
|   final bool isEnded;
 | |
|   final double? duration;
 | |
| 
 | |
|   const _MessageContentCall({required this.isEnded, this.duration});
 | |
| 
 | |
|   @override
 | |
|   Widget build(BuildContext context) {
 | |
|     return Row(
 | |
|       mainAxisSize: MainAxisSize.min,
 | |
|       children: [
 | |
|         Icon(
 | |
|           isEnded ? Symbols.call_end : Symbols.phone_in_talk,
 | |
|           size: 16,
 | |
|           color: Theme.of(context).colorScheme.primary,
 | |
|         ),
 | |
|         Gap(4),
 | |
|         Text(
 | |
|           isEnded
 | |
|               ? 'Call ended after ${formatDuration(Duration(seconds: duration!.toInt()))}'
 | |
|               : 'Call started',
 | |
|           style: TextStyle(color: Theme.of(context).colorScheme.primary),
 | |
|         ),
 | |
|       ],
 | |
|     );
 | |
|   }
 | |
| }
 |