diff --git a/example/lib/main.dart b/example/lib/main.dart index ca6a576..ab485b0 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -2,7 +2,6 @@ import 'dart:io'; import 'package:chatwoot_sdk/chatwoot_sdk.dart'; import 'package:flutter/material.dart'; -import 'package:flutter/services.dart'; import 'package:image/image.dart' as image; import 'package:image_picker/image_picker.dart' as image_picker; import 'package:path_provider/path_provider.dart'; diff --git a/lib/data/remote/service/chatwoot_client_auth_service.dart b/lib/data/remote/service/chatwoot_client_auth_service.dart index ee584a6..349c8e4 100644 --- a/lib/data/remote/service/chatwoot_client_auth_service.dart +++ b/lib/data/remote/service/chatwoot_client_auth_service.dart @@ -44,7 +44,7 @@ class ChatwootClientAuthServiceImpl extends ChatwootClientAuthService { createResponse.statusMessage ?? "unknown error", ChatwootClientExceptionType.CREATE_CONTACT_FAILED); } - } on DioException catch (e) { + } on DioError catch (e) { throw ChatwootClientException( e.message ?? "", ChatwootClientExceptionType.CREATE_CONTACT_FAILED); } @@ -67,7 +67,7 @@ class ChatwootClientAuthServiceImpl extends ChatwootClientAuthService { createResponse.statusMessage ?? "unknown error", ChatwootClientExceptionType.CREATE_CONVERSATION_FAILED); } - } on DioException catch (e) { + } on DioError catch (e) { throw ChatwootClientException(e.message ?? "", ChatwootClientExceptionType.CREATE_CONVERSATION_FAILED); } diff --git a/lib/data/remote/service/chatwoot_client_service.dart b/lib/data/remote/service/chatwoot_client_service.dart index d8d7bae..baa950b 100644 --- a/lib/data/remote/service/chatwoot_client_service.dart +++ b/lib/data/remote/service/chatwoot_client_service.dart @@ -85,7 +85,7 @@ class ChatwootClientServiceImpl extends ChatwootClientService { createResponse.statusMessage ?? "unknown error", ChatwootClientExceptionType.SEND_MESSAGE_FAILED); } - } on DioException catch (e) { + } on DioError catch (e) { throw ChatwootClientException( e.message ?? "", ChatwootClientExceptionType.SEND_MESSAGE_FAILED); } @@ -107,7 +107,7 @@ class ChatwootClientServiceImpl extends ChatwootClientService { createResponse.statusMessage ?? "unknown error", ChatwootClientExceptionType.GET_MESSAGES_FAILED); } - } on DioException catch (e) { + } on DioError catch (e) { throw ChatwootClientException( e.message ?? "", ChatwootClientExceptionType.GET_MESSAGES_FAILED); } @@ -126,7 +126,7 @@ class ChatwootClientServiceImpl extends ChatwootClientService { getResponse.statusMessage ?? "unknown error", ChatwootClientExceptionType.GET_CONTACT_FAILED); } - } on DioException catch (e) { + } on DioError catch (e) { throw ChatwootClientException( e.message ?? "", ChatwootClientExceptionType.GET_CONTACT_FAILED); } @@ -147,7 +147,7 @@ class ChatwootClientServiceImpl extends ChatwootClientService { createResponse.statusMessage ?? "unknown error", ChatwootClientExceptionType.GET_CONVERSATION_FAILED); } - } on DioException catch (e) { + } on DioError catch (e) { throw ChatwootClientException( e.message ?? "", ChatwootClientExceptionType.GET_CONVERSATION_FAILED); } @@ -167,7 +167,7 @@ class ChatwootClientServiceImpl extends ChatwootClientService { updateResponse.statusMessage ?? "unknown error", ChatwootClientExceptionType.UPDATE_CONTACT_FAILED); } - } on DioException catch (e) { + } on DioError catch (e) { throw ChatwootClientException( e.message ?? "", ChatwootClientExceptionType.UPDATE_CONTACT_FAILED); } @@ -189,7 +189,7 @@ class ChatwootClientServiceImpl extends ChatwootClientService { updateResponse.statusMessage ?? "unknown error", ChatwootClientExceptionType.UPDATE_MESSAGE_FAILED); } - } on DioException catch (e) { + } on DioError catch (e) { throw ChatwootClientException( e.message ?? "", ChatwootClientExceptionType.UPDATE_MESSAGE_FAILED); } @@ -242,7 +242,7 @@ class ChatwootClientServiceImpl extends ChatwootClientService { throw ChatwootClientException(response.statusMessage ?? "unknown error", ChatwootClientExceptionType.GET_CSAT_FEEDBACK); } - } on DioException catch (e) { + } on DioError catch (e) { throw ChatwootClientException( e.message ?? "", ChatwootClientExceptionType.GET_CSAT_FEEDBACK); } @@ -264,7 +264,7 @@ class ChatwootClientServiceImpl extends ChatwootClientService { throw ChatwootClientException(response.statusMessage ?? "unknown error", ChatwootClientExceptionType.SEND_CSAT_FEEDBACK); } - } on DioException catch (e) { + } on DioError catch (e) { throw ChatwootClientException( e.message ?? "", ChatwootClientExceptionType.SEND_CSAT_FEEDBACK); } diff --git a/lib/ui/chatwoot_chat_page.dart b/lib/ui/chatwoot_chat_page.dart index ab694a6..6cd7133 100644 --- a/lib/ui/chatwoot_chat_page.dart +++ b/lib/ui/chatwoot_chat_page.dart @@ -13,14 +13,11 @@ import 'package:chatwoot_sdk/ui/chatwoot_chat_theme.dart'; import 'package:chatwoot_sdk/ui/chatwoot_l10n.dart'; import 'package:chatwoot_sdk/ui/link_preview.dart'; import 'package:chatwoot_sdk/ui/media_widgets.dart'; -import 'package:chatwoot_sdk/ui/video_preview.dart'; import 'package:easy_image_viewer/easy_image_viewer.dart'; import 'package:flutter/material.dart'; import 'package:flutter_chat_types/flutter_chat_types.dart' as types; import 'package:flutter_chat_ui/flutter_chat_ui.dart'; import 'package:intl/intl.dart'; -import 'package:media_kit/media_kit.dart'; -import 'package:media_kit_video/media_kit_video.dart'; import 'package:mime/mime.dart'; import 'package:path_provider/path_provider.dart'; import 'package:uuid/uuid.dart'; @@ -190,16 +187,10 @@ class _ChatwootChatState extends State ChatwootClient? chatwootClient; late final ChatwootCallbacks chatwootCallbacks; - late VideoController controller; - late VideoPreviewLoader videoPreviewLoader; @override void initState() { super.initState(); - MediaKit.ensureInitialized(); - controller = VideoController(Player()); - videoPreviewLoader = VideoPreviewLoader(controller: controller); - videoPreviewLoader.listen(_handleVideoPreviewLoaded); if (widget.user == null) { _user = types.User(id: idGen.v4()); } else { @@ -417,23 +408,6 @@ class _ChatwootChatState extends State status: messageStatus ?? types.Status.seen, createdAt: DateTime.parse(message.createdAt).millisecondsSinceEpoch); - } else if (message.attachments!.first.fileType == "video") { - final videoMessage = types.VideoMessage( - id: echoId ?? message.id.toString(), - author: author, - height: 500, - width: 500, - name: fileName, - metadata: metadata, - size: message.attachments!.first.fileSize ?? 0, - uri: message.attachments!.first.dataUrl!, - status: messageStatus ?? types.Status.seen, - createdAt: - DateTime.parse(message.createdAt).millisecondsSinceEpoch); - - videoPreviewLoader.getPreview( - jobId: videoMessage.id, uri: videoMessage.uri); - return videoMessage; } else if (message.attachments!.first.fileType == "audio") { return types.AudioMessage( id: echoId ?? message.id.toString(), @@ -564,18 +538,6 @@ class _ChatwootChatState extends State }); } - void _handleVideoPreviewLoaded(VideoMessagePreviewResult result) { - final index = _messages.indexWhere((element) => element.id == result.jobId); - if (index > -1) { - WidgetsBinding.instance.addPostFrameCallback((_) { - setState(() { - _messages[index] = - _messages[index].copyWith(metadata: {"preview": result.preview}); - }); - }); - } - } - void _handleSendPressed(String message) { final textMessage = types.TextMessage( author: _user, @@ -604,18 +566,6 @@ class _ChatwootChatState extends State uri: attachment.path, size: attachment.bytes.length, status: types.Status.sending); - } else if (lookupMimeType(attachment.name)?.startsWith("video") ?? - false) { - message = types.VideoMessage( - author: _user, - createdAt: DateTime.now().millisecondsSinceEpoch, - id: const Uuid().v4(), - name: attachment.name, - uri: attachment.path, - size: attachment.bytes.length, - status: types.Status.sending); - - videoPreviewLoader.getPreview(jobId: message.id, uri: attachment.path); } else if (lookupMimeType(attachment.name)?.startsWith("audio") ?? false) { message = types.AudioMessage( @@ -657,14 +607,6 @@ class _ChatwootChatState extends State body: Stack( children: [ //offscreen video player used to fetch first frame of video messages. media_kit screenshot doesn't work without - //controller tied to the video widget - IgnorePointer( - child: Opacity( - opacity: 0, - child: Video( - controller: controller, - fit: BoxFit.contain, - ))), //actual chat Column( children: [ @@ -797,14 +739,6 @@ class _ChatwootChatState extends State void dispose() { super.dispose(); chatwootClient?.dispose(); - videoPreviewLoader.dispose(); LinkMetadata.dispose(); - controller.player.dispose(); - _messages.forEach((m) { - if (m is types.VideoMessage) { - final controller = m.metadata?["controller"] as VideoController?; - controller?.player.dispose(); - } - }); } } diff --git a/lib/ui/media_widgets.dart b/lib/ui/media_widgets.dart index f6bd6f0..f0bfff7 100644 --- a/lib/ui/media_widgets.dart +++ b/lib/ui/media_widgets.dart @@ -1,13 +1,9 @@ - import 'package:chatwoot_sdk/data/remote/responses/csat_survey_response.dart'; import 'package:chatwoot_sdk/ui/chatwoot_chat_theme.dart'; import 'package:chatwoot_sdk/ui/chatwoot_l10n.dart'; import 'package:chatwoot_sdk/ui/link_preview.dart'; -import 'package:chatwoot_sdk/ui/video_preview.dart'; import 'package:flutter/material.dart'; import 'package:flutter_markdown/flutter_markdown.dart'; -import 'package:media_kit/media_kit.dart'; -import 'package:media_kit_video/media_kit_video.dart'; import 'package:flutter_chat_types/flutter_chat_types.dart' as types; import 'package:url_launcher/url_launcher.dart'; @@ -21,20 +17,13 @@ class FullScreenMediaViewer extends StatefulWidget { } class _FullScreenMediaViewerState extends State { - late final Player player; - late VideoController _controller; - @override void initState() { super.initState(); - player = Player(); - _controller = VideoController(player); - player.open(Media(widget.uri)); // Replace with your media URL } @override void dispose() { - player.dispose(); super.dispose(); } @@ -44,12 +33,6 @@ class _FullScreenMediaViewerState extends State { backgroundColor: Colors.black, body: Stack( children: [ - Center( - child: Video( - controller: _controller, - fit: BoxFit.contain, - ), - ), Positioned( top: 20.0, right: 20.0, @@ -66,30 +49,37 @@ class _FullScreenMediaViewerState extends State { } } - class AudioChatMessage extends StatefulWidget { final ChatwootChatTheme theme; final types.AudioMessage message; final bool isMine; - const AudioChatMessage({Key? key, required this.message, required this.isMine, required this.theme}) : super(key: key); + const AudioChatMessage( + {Key? key, + required this.message, + required this.isMine, + required this.theme}) + : super(key: key); @override _AudioChatMessageState createState() => _AudioChatMessageState(); } class _AudioChatMessageState extends State { - - void playAudio() { Navigator.push( context, - MaterialPageRoute(builder: (context) => FullScreenMediaViewer(uri: widget.message.uri,)), + MaterialPageRoute( + builder: (context) => FullScreenMediaViewer( + uri: widget.message.uri, + )), ); } @override Widget build(BuildContext context) { - final activeColor = widget.isMine ? widget.theme.sentMessageBodyTextStyle.color : widget.theme.receivedMessageBodyTextStyle.color; + final activeColor = widget.isMine + ? widget.theme.sentMessageBodyTextStyle.color + : widget.theme.receivedMessageBodyTextStyle.color; return Container( padding: const EdgeInsets.symmetric(horizontal: 10.0, vertical: 8.0), child: Row( @@ -125,13 +115,18 @@ class _AudioChatMessageState extends State { } } - class VideoChatMessage extends StatefulWidget { final ChatwootChatTheme theme; final types.VideoMessage message; final bool isMine; final int maxWidth; - const VideoChatMessage({Key? key, required this.theme, required this.message, required this.isMine, required this.maxWidth}) : super(key: key); + const VideoChatMessage( + {Key? key, + required this.theme, + required this.message, + required this.isMine, + required this.maxWidth}) + : super(key: key); @override _VideoChatMessageState createState() => _VideoChatMessageState(); @@ -142,11 +137,14 @@ class _VideoChatMessageState extends State { void playVideo() { Navigator.push( context, - MaterialPageRoute(builder: (context) => FullScreenMediaViewer(uri: widget.message.uri,)), + MaterialPageRoute( + builder: (context) => FullScreenMediaViewer( + uri: widget.message.uri, + )), ); } - VideoMessagePreview? get previewData =>widget.message.metadata?["preview"]; + get previewData => widget.message.metadata?["preview"]; @override void initState() { @@ -162,8 +160,13 @@ class _VideoChatMessageState extends State { width: widget.maxWidth.toDouble(), child: Stack( children: [ - if(previewData != null) - RawImage(image: previewData?.firstFrame?.image, fit: BoxFit.cover, width: widget.maxWidth.toDouble(), height: height,), + if (previewData != null) + RawImage( + image: previewData?.firstFrame?.image, + fit: BoxFit.cover, + width: widget.maxWidth.toDouble(), + height: height, + ), Positioned.fill( child: Center( child: Container( @@ -188,71 +191,81 @@ class _VideoChatMessageState extends State { } } - - class TextChatMessage extends StatefulWidget { final ChatwootChatTheme theme; final types.TextMessage message; final bool isMine; final int maxWidth; - final void Function(types.TextMessage,types.PreviewData) onPreviewFetched; - const TextChatMessage({ - Key? key, - required this.theme, - required this.message, - required this.isMine, - required this.maxWidth, - required this.onPreviewFetched - }) : super(key: key); + final void Function(types.TextMessage, types.PreviewData) onPreviewFetched; + const TextChatMessage( + {Key? key, + required this.theme, + required this.message, + required this.isMine, + required this.maxWidth, + required this.onPreviewFetched}) + : super(key: key); @override _TextChatMessageState createState() => _TextChatMessageState(); } class _TextChatMessageState extends State { - @override Widget build(BuildContext context) { print("link: $messageLink"); final styleSheet = MarkdownStyleSheet.fromTheme(Theme.of(context)); - final textColor = widget.isMine ? widget.theme.sentMessageBodyTextStyle.color: widget.theme.receivedMessageBodyTextStyle.color; + final textColor = widget.isMine + ? widget.theme.sentMessageBodyTextStyle.color + : widget.theme.receivedMessageBodyTextStyle.color; return Container( - padding: EdgeInsets.symmetric(vertical: widget.theme.messageInsetsVertical, horizontal: widget.theme.messageInsetsHorizontal), + padding: EdgeInsets.symmetric( + vertical: widget.theme.messageInsetsVertical, + horizontal: widget.theme.messageInsetsHorizontal), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - if(messageLink != null) + if (messageLink != null) LinkPreview( url: messageLink!, // This disables tap event ), - if(messageLink != null) - SizedBox(height: 8.0), - if(messageLink != widget.message.text.trim()) + if (messageLink != null) SizedBox(height: 8.0), + if (messageLink != widget.message.text.trim()) MarkdownBody( - data: widget.message.text, - onTapLink: (text,href,title){ - if(href != null){ - launchUrl(Uri.parse(href)); - } - }, - styleSheet: styleSheet.copyWith( - code: widget.isMine ? widget.theme.sentMessageBodyCodeTextStyle: widget.theme.receivedMessageBodyCodeTextStyle, - p: widget.isMine ? widget.theme.sentMessageBodyTextStyle: widget.theme.receivedMessageBodyTextStyle, - h1: styleSheet.h1?.copyWith(color: textColor), - h2: styleSheet.h2?.copyWith(color: textColor), - h3: styleSheet.h3?.copyWith(color: textColor), - h4: styleSheet.h4?.copyWith(color: textColor), - h5: styleSheet.h5?.copyWith(color: textColor), - h6: styleSheet.h6?.copyWith(color: textColor), - tableBody: styleSheet.tableBody?.copyWith(color: textColor), - tableHead: styleSheet.tableHead?.copyWith(color: textColor), - a: widget.isMine ? styleSheet.a?.copyWith(color: Colors.white): styleSheet.a?.copyWith(color: widget.theme.primaryColor), - ) - ), - if(widget.message.metadata?["sentAt"] != null) + data: widget.message.text, + onTapLink: (text, href, title) { + if (href != null) { + launchUrl(Uri.parse(href)); + } + }, + styleSheet: styleSheet.copyWith( + code: widget.isMine + ? widget.theme.sentMessageBodyCodeTextStyle + : widget.theme.receivedMessageBodyCodeTextStyle, + p: widget.isMine + ? widget.theme.sentMessageBodyTextStyle + : widget.theme.receivedMessageBodyTextStyle, + h1: styleSheet.h1?.copyWith(color: textColor), + h2: styleSheet.h2?.copyWith(color: textColor), + h3: styleSheet.h3?.copyWith(color: textColor), + h4: styleSheet.h4?.copyWith(color: textColor), + h5: styleSheet.h5?.copyWith(color: textColor), + h6: styleSheet.h6?.copyWith(color: textColor), + tableBody: styleSheet.tableBody?.copyWith(color: textColor), + tableHead: styleSheet.tableHead?.copyWith(color: textColor), + a: widget.isMine + ? styleSheet.a?.copyWith(color: Colors.white) + : styleSheet.a + ?.copyWith(color: widget.theme.primaryColor), + )), + if (widget.message.metadata?["sentAt"] != null) Text( widget.message.metadata!["sentAt"], - style: (widget.isMine ? widget.theme.sentMessageBodyTextStyle: widget.theme.receivedMessageBodyTextStyle.copyWith(color: Colors.grey)).copyWith(fontSize: 12), + style: (widget.isMine + ? widget.theme.sentMessageBodyTextStyle + : widget.theme.receivedMessageBodyTextStyle + .copyWith(color: Colors.grey)) + .copyWith(fontSize: 12), ) ], ), @@ -274,24 +287,20 @@ class _TextChatMessageState extends State { } } - - - class CSATChatMessage extends StatefulWidget { - final ChatwootChatTheme theme; final ChatwootL10n l10n; final types.CustomMessage message; final int maxWidth; - final void Function(int,String) sendCsatResults; - const CSATChatMessage({ - Key? key, - required this.theme, - required this.message, - required this.l10n, - required this.maxWidth, - required this.sendCsatResults - }) : super(key: key); + final void Function(int, String) sendCsatResults; + const CSATChatMessage( + {Key? key, + required this.theme, + required this.message, + required this.l10n, + required this.maxWidth, + required this.sendCsatResults}) + : super(key: key); @override _CSATChatMessageState createState() => _CSATChatMessageState(); @@ -301,7 +310,7 @@ class _CSATChatMessageState extends State { String? selectedOption; String? feedback; bool isSentTapped = false; - late List options ; + late List options; final TextEditingController feedbackController = TextEditingController(); @override @@ -315,6 +324,7 @@ class _CSATChatMessageState extends State { ]; super.initState(); } + @override void dispose() { feedbackController.dispose(); @@ -341,11 +351,13 @@ class _CSATChatMessageState extends State { Column( children: options.map((option) { return GestureDetector( - onTap: isSentTapped ? null : () { - setState(() { - selectedOption = option; - }); - }, + onTap: isSentTapped + ? null + : () { + setState(() { + selectedOption = option; + }); + }, child: Container( margin: const EdgeInsets.symmetric(vertical: 4.0), padding: const EdgeInsets.symmetric( @@ -407,7 +419,7 @@ class _CSATChatMessageState extends State { vertical: 10.0, ), ), - onChanged: (text){ + onChanged: (text) { feedback = text; }, ), @@ -415,58 +427,58 @@ class _CSATChatMessageState extends State { const SizedBox(height: 16.0), // Send Button - if(!isSentTapped) + if (!isSentTapped) SizedBox( - width: double.infinity, - child: ElevatedButton( - onPressed: selectedOption == null - ? null - : () { - // Handle submission - final rating = options.indexWhere((e)=>e == selectedOption)+1; - widget.sendCsatResults(rating, feedback??''); - setState(() { - isSentTapped = true; - }); - }, - style: ElevatedButton.styleFrom( - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(8.0), + width: double.infinity, + child: ElevatedButton( + onPressed: selectedOption == null + ? null + : () { + // Handle submission + final rating = + options.indexWhere((e) => e == selectedOption) + 1; + widget.sendCsatResults(rating, feedback ?? ''); + setState(() { + isSentTapped = true; + }); + }, + style: ElevatedButton.styleFrom( + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(8.0), + ), + backgroundColor: selectedOption == null + ? Colors.grey + : widget.theme.primaryColor, // Disabled button styling ), - backgroundColor: selectedOption == null - ? Colors.grey - : widget.theme.primaryColor, // Disabled button styling - ), - child: Text( + child: Text( "Send", - style: widget.theme.sentMessageBodyTextStyle, + style: widget.theme.sentMessageBodyTextStyle, + ), ), ), - ), ], ), ); } } - -class RecordedCsatChatMessage extends StatelessWidget{ - - +class RecordedCsatChatMessage extends StatelessWidget { final ChatwootChatTheme theme; final ChatwootL10n l10n; final types.CustomMessage message; final int maxWidth; List get options => [ - l10n.csatVeryUnsatisfied, - l10n.csatUnsatisfied, - l10n.csatOK, - l10n.csatSatisfied, - l10n.csatVerySatisfied, - ]; - CsatSurveyFeedbackResponse get feedback => message.metadata!["feedback"]! as CsatSurveyFeedbackResponse; - String get selectedOption => options[(feedback.csatSurveyResponse?.rating ?? 3)-1]; - String get feedBackText => feedback.csatSurveyResponse?.feedbackMessage??''; + l10n.csatVeryUnsatisfied, + l10n.csatUnsatisfied, + l10n.csatOK, + l10n.csatSatisfied, + l10n.csatVerySatisfied, + ]; + CsatSurveyFeedbackResponse get feedback => + message.metadata!["feedback"]! as CsatSurveyFeedbackResponse; + String get selectedOption => + options[(feedback.csatSurveyResponse?.rating ?? 3) - 1]; + String get feedBackText => feedback.csatSurveyResponse?.feedbackMessage ?? ''; RecordedCsatChatMessage({ Key? key, @@ -474,7 +486,7 @@ class RecordedCsatChatMessage extends StatelessWidget{ required this.message, required this.l10n, required this.maxWidth, - }):super(key: key); + }) : super(key: key); @override Widget build(BuildContext context) { @@ -545,12 +557,10 @@ class RecordedCsatChatMessage extends StatelessWidget{ feedBackText, style: theme.receivedMessageBodyTextStyle, ), - ], ), ); } - } class PlaceholderCircle extends StatelessWidget { @@ -584,15 +594,11 @@ class PlaceholderCircle extends StatelessWidget { style: textStyle ?? TextStyle( color: textColor, - fontSize: size / 2.5, // Scales font size relative to the widget size + fontSize: + size / 2.5, // Scales font size relative to the widget size fontWeight: FontWeight.bold, ), ), ); } } - - - - - diff --git a/lib/ui/video_preview.dart b/lib/ui/video_preview.dart deleted file mode 100644 index 5a9e492..0000000 --- a/lib/ui/video_preview.dart +++ /dev/null @@ -1,153 +0,0 @@ -import 'dart:async'; -import 'dart:collection'; -import 'dart:typed_data'; -import 'dart:ui' as ui; -import 'package:cached_network_image/cached_network_image.dart'; -import 'package:media_kit/media_kit.dart'; -import 'package:media_kit_video/media_kit_video.dart'; - -class VideoMessagePreview { - final ui.FrameInfo? firstFrame; - final double width; - final double height; - - VideoMessagePreview({this.firstFrame, this.width = 0, this.height = 0}); -} - -class VideoMessagePreviewJob { - final String jobId; - final String uri; - - VideoMessagePreviewJob({required this.jobId, required this.uri}); -} - -class VideoMessagePreviewResult { - ///Fetch preview job identification - final String jobId; - - ///Video uri - final String uri; - - ///Preview results - final VideoMessagePreview preview; - - VideoMessagePreviewResult( - {required this.jobId, required this.uri, required this.preview}); -} - -class VideoPreviewLoader { - ///Video controller for loading first video frame. See [ChatwootChat.build] - VideoController controller; - - ///Queue for registering preview jobs - final _previewJobQueue = Queue(); - - ///Stream to send preview results - final _previewResponseStreamController = - StreamController(); - - StreamSubscription? _previewResponseStreamSubscription; - - VideoPreviewLoader({required this.controller}); - - void getPreview({required String jobId, required String uri}) async { - if (_previewJobQueue.isEmpty) { - //no video previews are being fetched - _previewJobQueue.add(VideoMessagePreviewJob(jobId: jobId, uri: uri)); - _fromUri(jobId, uri); - } else { - //ongoing video preview fetch. add to queue - _previewJobQueue.add(VideoMessagePreviewJob(jobId: jobId, uri: uri)); - } - } - - Future _getCacheVideoPreview( - String jobId, String uri) async { - final fileInfo = await CachedNetworkImageProvider(uri) - .cacheManager! - .getFileFromCache(uri); - if (fileInfo != null) { - // return cached preview - final codec = - await ui.instantiateImageCodec(await fileInfo.file.readAsBytes()); - final frame = await codec.getNextFrame(); - final firstframe = frame; - final width = frame.image.width.toDouble(); - final height = frame.image.height.toDouble(); - final cachedPreview = VideoMessagePreview( - firstFrame: firstframe, width: width, height: height); - return cachedPreview; - } - return null; - } - - ///Loads first frame from video url - Future _fromUri(String jobId, String uri) async { - //check for cached preview - final cachedPreview = await _getCacheVideoPreview(jobId, uri); - if (cachedPreview != null) { - // return cached preview - _previewResponseStreamController.add(VideoMessagePreviewResult( - jobId: jobId, uri: uri, preview: cachedPreview)); - //check for pending jobs and execute - if (_previewJobQueue.isNotEmpty) { - final job = _previewJobQueue.removeFirst(); - _fromUri(job.jobId, job.uri); - } - return; - } - - //fetch video preview - ui.FrameInfo? firstframe; - double width = 0; - double height = 0; - try { - // Load the video into the player - await controller.player.setVolume(0); - await controller.player.open(Media(uri)); - await Future.delayed(Duration(seconds: 10)); - await controller.player.stream.position - .firstWhere((d) => d > Duration.zero); - Uint8List? frameBytes = await controller.player.screenshot(); - - if (frameBytes != null) { - // Convert the Uint8List to a ui.Image - final codec = await ui.instantiateImageCodec(frameBytes); - final frame = await codec.getNextFrame(); - firstframe = frame; - width = frame.image.width.toDouble(); - height = frame.image.height.toDouble(); - await CachedNetworkImageProvider(uri) - .cacheManager! - .putFile(uri, frameBytes); - } - } catch (e) { - print('Error capturing first frame: $e'); - } finally { - // Dispose the player - controller.player.stop(); - } - - final p = VideoMessagePreview( - firstFrame: firstframe, width: width, height: height); - - //send preview result - _previewResponseStreamController - .add(VideoMessagePreviewResult(jobId: jobId, uri: uri, preview: p)); - - //check for pending jobs and execute - if (_previewJobQueue.isNotEmpty) { - final job = _previewJobQueue.removeFirst(); - _fromUri(job.jobId, job.uri); - } - } - - listen(void Function(VideoMessagePreviewResult) callback) { - _previewResponseStreamSubscription = - _previewResponseStreamController.stream.listen(callback); - } - - dispose() { - _previewResponseStreamSubscription?.cancel(); - } -} diff --git a/pubspec.lock b/pubspec.lock index a81d387..2fcd3b5 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -22,14 +22,6 @@ packages: url: "https://pub.dev" source: hosted version: "6.7.0" - archive: - dependency: transitive - description: - name: archive - sha256: cb6a278ef2dbb298455e1a713bda08524a175630ec643a242c399c932a0a1f7d - url: "https://pub.dev" - source: hosted - version: "3.6.1" args: dependency: transitive description: @@ -214,14 +206,6 @@ packages: url: "https://pub.dev" source: hosted version: "2.3.7" - dbus: - dependency: transitive - description: - name: dbus - sha256: "365c771ac3b0e58845f39ec6deebc76e3276aa9922b0cc60840712094d9047ac" - url: "https://pub.dev" - source: hosted - version: "0.7.10" diffutil_dart: dependency: transitive description: @@ -509,14 +493,6 @@ packages: url: "https://pub.dev" source: hosted version: "4.0.2" - image: - dependency: transitive - description: - name: image - sha256: f31d52537dc417fdcde36088fdf11d191026fd5e4fae742491ebd40e5a8bea7d - url: "https://pub.dev" - source: hosted - version: "4.3.0" intl: dependency: "direct main" description: @@ -637,14 +613,6 @@ packages: url: "https://pub.dev" source: hosted version: "0.11.1" - media_kit: - dependency: "direct main" - description: - name: media_kit - sha256: "1f1deee148533d75129a6f38251ff8388e33ee05fc2d20a6a80e57d6051b7b62" - url: "https://pub.dev" - source: hosted - version: "1.1.11" media_kit_libs_android_video: dependency: transitive description: @@ -701,14 +669,6 @@ packages: url: "https://pub.dev" source: hosted version: "1.0.9" - media_kit_video: - dependency: "direct main" - description: - name: media_kit_video - sha256: "2cc3b966679963ba25a4ce5b771e532a521ebde7c6aa20e9802bec95d9916c8f" - url: "https://pub.dev" - source: hosted - version: "1.2.5" meta: dependency: transitive description: @@ -749,22 +709,6 @@ packages: url: "https://pub.dev" source: hosted version: "2.1.0" - package_info_plus: - dependency: transitive - description: - name: package_info_plus - sha256: da8d9ac8c4b1df253d1a328b7bf01ae77ef132833479ab40763334db13b91cce - url: "https://pub.dev" - source: hosted - version: "8.1.1" - package_info_plus_platform_interface: - dependency: transitive - description: - name: package_info_plus_platform_interface - sha256: ac1f4a4847f1ade8e6a87d1f39f5d7c67490738642e2542f559ec38c37489a66 - url: "https://pub.dev" - source: hosted - version: "3.0.1" path: dependency: transitive description: @@ -821,14 +765,6 @@ packages: url: "https://pub.dev" source: hosted version: "2.3.0" - petitparser: - dependency: transitive - description: - name: petitparser - sha256: c15605cd28af66339f8eb6fbe0e541bfe2d1b72d5825efc6598f3e0a31b9ad27 - url: "https://pub.dev" - source: hosted - version: "6.0.2" photo_view: dependency: transitive description: @@ -893,62 +829,6 @@ packages: url: "https://pub.dev" source: hosted version: "0.27.7" - safe_local_storage: - dependency: transitive - description: - name: safe_local_storage - sha256: ede4eb6cb7d88a116b3d3bf1df70790b9e2038bc37cb19112e381217c74d9440 - url: "https://pub.dev" - source: hosted - version: "1.0.2" - screen_brightness: - dependency: transitive - description: - name: screen_brightness - sha256: ed8da4a4511e79422fc1aa88138e920e4008cd312b72cdaa15ccb426c0faaedd - url: "https://pub.dev" - source: hosted - version: "0.2.2+1" - screen_brightness_android: - dependency: transitive - description: - name: screen_brightness_android - sha256: "3df10961e3a9e968a5e076fe27e7f4741fa8a1d3950bdeb48cf121ed529d0caf" - url: "https://pub.dev" - source: hosted - version: "0.1.0+2" - screen_brightness_ios: - dependency: transitive - description: - name: screen_brightness_ios - sha256: "99adc3ca5490b8294284aad5fcc87f061ad685050e03cf45d3d018fe398fd9a2" - url: "https://pub.dev" - source: hosted - version: "0.1.0" - screen_brightness_macos: - dependency: transitive - description: - name: screen_brightness_macos - sha256: "64b34e7e3f4900d7687c8e8fb514246845a73ecec05ab53483ed025bd4a899fd" - url: "https://pub.dev" - source: hosted - version: "0.1.0+1" - screen_brightness_platform_interface: - dependency: transitive - description: - name: screen_brightness_platform_interface - sha256: b211d07f0c96637a15fb06f6168617e18030d5d74ad03795dd8547a52717c171 - url: "https://pub.dev" - source: hosted - version: "0.1.0" - screen_brightness_windows: - dependency: transitive - description: - name: screen_brightness_windows - sha256: "9261bf33d0fc2707d8cf16339ce25768100a65e70af0fcabaf032fc12408ba86" - url: "https://pub.dev" - source: hosted - version: "0.1.3" scroll_to_index: dependency: transitive description: @@ -1122,22 +1002,6 @@ packages: url: "https://pub.dev" source: hosted version: "1.4.0" - universal_platform: - dependency: transitive - description: - name: universal_platform - sha256: "64e16458a0ea9b99260ceb5467a214c1f298d647c659af1bff6d3bf82536b1ec" - url: "https://pub.dev" - source: hosted - version: "1.1.0" - uri_parser: - dependency: transitive - description: - name: uri_parser - sha256: "6543c9fd86d2862fac55d800a43e67c0dcd1a41677cb69c2f8edfe73bbcf1835" - url: "https://pub.dev" - source: hosted - version: "2.0.2" url_launcher: dependency: "direct main" description: @@ -1234,30 +1098,6 @@ packages: url: "https://pub.dev" source: hosted version: "14.2.5" - volume_controller: - dependency: transitive - description: - name: volume_controller - sha256: c71d4c62631305df63b72da79089e078af2659649301807fa746088f365cb48e - url: "https://pub.dev" - source: hosted - version: "2.0.8" - wakelock_plus: - dependency: transitive - description: - name: wakelock_plus - sha256: bf4ee6f17a2fa373ed3753ad0e602b7603f8c75af006d5b9bdade263928c0484 - url: "https://pub.dev" - source: hosted - version: "1.2.8" - wakelock_plus_platform_interface: - dependency: transitive - description: - name: wakelock_plus_platform_interface - sha256: "422d1cdbb448079a8a62a5a770b69baa489f8f7ca21aef47800c726d404f9d16" - url: "https://pub.dev" - source: hosted - version: "1.2.1" watcher: dependency: transitive description: @@ -1330,14 +1170,6 @@ packages: url: "https://pub.dev" source: hosted version: "1.1.0" - xml: - dependency: transitive - description: - name: xml - sha256: b015a8ad1c488f66851d762d3090a21c600e479dc75e68328c52774040cf9226 - url: "https://pub.dev" - source: hosted - version: "6.5.0" yaml: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index b792bf2..7cf4187 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -10,7 +10,7 @@ environment: dependencies: flutter: sdk: flutter - flutter_chat_ui: ^1.1.9 + flutter_chat_ui: ^1.6.15 hive: ^2.2.3 hive_flutter: ^1.1.0 uuid: ^3.0.4 @@ -29,8 +29,8 @@ dependencies: url_launcher: ^6.1.10 path_provider: ^2.0.13 cached_network_image: ^3.3.1 - media_kit: ^1.1.11 # Primary package. - media_kit_video: ^1.2.5 # For video rendering. + # media_kit: ^1.1.11 # Primary package. + # media_kit_video: ^1.2.5 # For video rendering. media_kit_libs_video: ^1.0.5 # Native video dependencies. easy_image_viewer: ^1.5.1 flutter_markdown: ^0.7.4+3 diff --git a/test/data/remote/chatwoot_client_auth_service_test.dart b/test/data/remote/chatwoot_client_auth_service_test.dart index d16b935..b747316 100644 --- a/test/data/remote/chatwoot_client_auth_service_test.dart +++ b/test/data/remote/chatwoot_client_auth_service_test.dart @@ -86,7 +86,7 @@ void main() { 'Given contact creation fails when createNewContact is called, then throw error', () async { //GIVEN - final testError = DioException(requestOptions: RequestOptions(path: "")); + final testError = DioError(requestOptions: RequestOptions(path: "")); when(mockDio.post(any, data: testUser.toJson())).thenThrow(testError); //WHEN @@ -146,7 +146,7 @@ void main() { 'Given conversation creation fails when createNewConversation is called, then throw error', () async { //GIVEN - final testError = DioException(requestOptions: RequestOptions(path: "")); + final testError = DioError(requestOptions: RequestOptions(path: "")); when(mockDio.post(any)).thenThrow(testError); //WHEN diff --git a/test/data/remote/chatwoot_client_service_test.dart b/test/data/remote/chatwoot_client_service_test.dart index 3a4e322..2e080c9 100644 --- a/test/data/remote/chatwoot_client_service_test.dart +++ b/test/data/remote/chatwoot_client_service_test.dart @@ -98,7 +98,7 @@ void main() { 'Given sending message fails when createMessage is called, then throw error', () async { //GIVEN - final testError = DioException(requestOptions: RequestOptions(path: "")); + final testError = DioError(requestOptions: RequestOptions(path: "")); final request = ChatwootNewMessageRequest(content: "test message", echoId: "id"); when(mockDio.post(any, data: anyNamed("data"))).thenThrow(testError); @@ -160,7 +160,7 @@ void main() { 'Given fetch messages fails when getAllMessages is called, then throw error', () async { //GIVEN - final testError = DioException(requestOptions: RequestOptions(path: "")); + final testError = DioError(requestOptions: RequestOptions(path: "")); when(mockDio.get(any)).thenThrow(testError); //WHEN @@ -218,7 +218,7 @@ void main() { 'Given fetch contact fails when getContact is called, then throw error', () async { //GIVEN - final testError = DioException(requestOptions: RequestOptions(path: "")); + final testError = DioError(requestOptions: RequestOptions(path: "")); when(mockDio.get(any)).thenThrow(testError); //WHEN @@ -278,7 +278,7 @@ void main() { 'Given fetch conversations fails when getConversations is called, then throw error', () async { //GIVEN - final testError = DioException(requestOptions: RequestOptions(path: "")); + final testError = DioError(requestOptions: RequestOptions(path: "")); when(mockDio.get(any)).thenThrow(testError); //WHEN @@ -339,7 +339,7 @@ void main() { () async { //GIVEN final update = {"name": "Updated name"}; - final testError = DioException(requestOptions: RequestOptions(path: "")); + final testError = DioError(requestOptions: RequestOptions(path: "")); when(mockDio.patch(any, data: update)).thenThrow(testError); //WHEN @@ -404,7 +404,7 @@ void main() { //GIVEN final testMessageId = "id"; final update = {"content": "Updated content"}; - final testError = DioException(requestOptions: RequestOptions(path: "")); + final testError = DioError(requestOptions: RequestOptions(path: "")); when(mockDio.patch(any, data: update)).thenThrow(testError); //WHEN @@ -486,7 +486,7 @@ void main() { final testConversationUuid = "conversation-uuid"; final feedbackRequest = SendCsatSurveyRequest(rating: 1, feedbackMessage: "test message"); - final testError = DioException(requestOptions: RequestOptions(path: "")); + final testError = DioError(requestOptions: RequestOptions(path: "")); final requestBody = { "message": { "submitted_values": {"csat_survey_response": feedbackRequest.toJson()}