From f3aa929cb14c03bd8babb345fbc5e830d5117f72 Mon Sep 17 00:00:00 2001 From: Sikander Saleem Date: Tue, 18 Nov 2025 12:15:04 +0300 Subject: [PATCH] view all attachments added. --- .../cx_module/chat/chat_api_client.dart | 8 +- lib/modules/cx_module/chat/chat_page.dart | 2 +- lib/modules/cx_module/chat/chat_provider.dart | 6 +- .../chat/model/chat_attachment_model.dart | 60 ++++ .../chat/view_all_attachment_page.dart | 269 +++++++++++++++++- 5 files changed, 328 insertions(+), 17 deletions(-) create mode 100644 lib/modules/cx_module/chat/model/chat_attachment_model.dart diff --git a/lib/modules/cx_module/chat/chat_api_client.dart b/lib/modules/cx_module/chat/chat_api_client.dart index 37add788..d7f42e9a 100644 --- a/lib/modules/cx_module/chat/chat_api_client.dart +++ b/lib/modules/cx_module/chat/chat_api_client.dart @@ -10,6 +10,7 @@ import 'package:test_sa/controllers/api_routes/api_manager.dart'; import 'package:test_sa/controllers/api_routes/urls.dart'; import 'package:test_sa/extensions/string_extensions.dart'; import 'package:http/http.dart' as http; +import 'package:test_sa/modules/cx_module/chat/model/chat_attachment_model.dart'; import 'api_client.dart'; import 'model/chat_login_response_model.dart'; import 'model/chat_participant_model.dart'; @@ -76,13 +77,14 @@ class ChatApiClient { } } - Future viewAllDocuments(int moduleId, int referenceId) async { + Future> viewAllDocuments(int moduleId, int referenceId) async { Response response = await ApiClient().getJsonForResponse("${URLs.chatHubUrlApi}/attachments/conversation?referenceId=$referenceId&moduleCode=$moduleId", token: chatLoginResponse!.token); if (response.statusCode == 200) { - return ChatParticipantModel.fromJson(jsonDecode(response.body)); + List data = jsonDecode(response.body)["response"]; + return data.map((elemet) => ChatAttachment.fromJson(elemet)).toList(); } else { - return null; + return []; } } diff --git a/lib/modules/cx_module/chat/chat_page.dart b/lib/modules/cx_module/chat/chat_page.dart index 4bab968c..aff8bceb 100644 --- a/lib/modules/cx_module/chat/chat_page.dart +++ b/lib/modules/cx_module/chat/chat_page.dart @@ -641,7 +641,7 @@ class _ChatPageState extends State { // // ) // // else - ChatFileViewer(chatResponse!.downloadUrl!, chatResponse.fileTypeResponse!), + ChatFileViewer(chatResponse!.downloadUrl!, chatResponse.fileTypeResponse!), // Container( // width: 250, diff --git a/lib/modules/cx_module/chat/chat_provider.dart b/lib/modules/cx_module/chat/chat_provider.dart index 5ad5d1d5..0d00a6f3 100644 --- a/lib/modules/cx_module/chat/chat_provider.dart +++ b/lib/modules/cx_module/chat/chat_provider.dart @@ -47,6 +47,7 @@ import 'package:uuid/uuid.dart'; import 'package:flutter/material.dart' as Material; import 'chat_api_client.dart'; +import 'model/chat_attachment_model.dart'; import 'model/chat_participant_model.dart'; import 'model/get_search_user_chat_model.dart'; import 'model/get_single_user_chat_list_model.dart'; @@ -193,11 +194,6 @@ class ChatProvider with ChangeNotifier, DiagnosticableTreeMixin { // } catch (e) {} // } - Future viewAllDocuments(int moduleId, int requestId) async { - try { - await ChatApiClient().viewAllDocuments(moduleId, requestId); - } catch (ex) {} - } Future connectToHub(int moduleId, int requestId, String myId, String assigneeEmployeeNumber) async { userChatHistoryLoading = true; diff --git a/lib/modules/cx_module/chat/model/chat_attachment_model.dart b/lib/modules/cx_module/chat/model/chat_attachment_model.dart new file mode 100644 index 00000000..3705ae05 --- /dev/null +++ b/lib/modules/cx_module/chat/model/chat_attachment_model.dart @@ -0,0 +1,60 @@ +class ChatAttachment { + String? id; + String? originalFileName; + String? storedFileName; + String? contentType; + int? sizeBytes; + String? downloadUrl; + String? relativePath; + String? createdOn; + bool? isContextual; + String? moduleCode; + String? referenceId; + String? conversationId; + + ChatAttachment( + {this.id, + this.originalFileName, + this.storedFileName, + this.contentType, + this.sizeBytes, + this.downloadUrl, + this.relativePath, + this.createdOn, + this.isContextual, + this.moduleCode, + this.referenceId, + this.conversationId}); + + ChatAttachment.fromJson(Map json) { + id = json['id']; + originalFileName = json['originalFileName']; + storedFileName = json['storedFileName']; + contentType = json['contentType']; + sizeBytes = json['sizeBytes']; + downloadUrl = json['downloadUrl']; + relativePath = json['relativePath']; + createdOn = json['createdOn']; + isContextual = json['isContextual']; + moduleCode = json['moduleCode']; + referenceId = json['referenceId']; + conversationId = json['conversationId']; + } + + Map toJson() { + final Map data = new Map(); + data['id'] = this.id; + data['originalFileName'] = this.originalFileName; + data['storedFileName'] = this.storedFileName; + data['contentType'] = this.contentType; + data['sizeBytes'] = this.sizeBytes; + data['downloadUrl'] = this.downloadUrl; + data['relativePath'] = this.relativePath; + data['createdOn'] = this.createdOn; + data['isContextual'] = this.isContextual; + data['moduleCode'] = this.moduleCode; + data['referenceId'] = this.referenceId; + data['conversationId'] = this.conversationId; + return data; + } +} diff --git a/lib/modules/cx_module/chat/view_all_attachment_page.dart b/lib/modules/cx_module/chat/view_all_attachment_page.dart index a92d9831..331997ee 100644 --- a/lib/modules/cx_module/chat/view_all_attachment_page.dart +++ b/lib/modules/cx_module/chat/view_all_attachment_page.dart @@ -1,10 +1,23 @@ +import 'dart:io'; + import 'package:flutter/material.dart'; +import 'package:open_file/open_file.dart'; +import 'package:path_provider/path_provider.dart'; import 'package:provider/provider.dart'; import 'package:test_sa/extensions/context_extension.dart'; +import 'package:test_sa/extensions/int_extensions.dart'; +import 'package:test_sa/extensions/string_extensions.dart'; +import 'package:test_sa/extensions/text_extensions.dart'; import 'package:test_sa/extensions/widget_extensions.dart'; import 'package:test_sa/modules/cx_module/chat/chat_provider.dart'; +import 'package:test_sa/modules/cx_module/chat/model/chat_attachment_model.dart'; import 'package:test_sa/new_views/app_style/app_color.dart'; +import 'package:test_sa/new_views/app_style/app_text_style.dart'; import 'package:test_sa/new_views/common_widgets/default_app_bar.dart'; +import 'package:test_sa/views/widgets/loaders/no_data_found.dart'; +import 'package:test_sa/views/widgets/sound/sound_player.dart'; + +import 'chat_api_client.dart'; class ViewAllAttachmentPage extends StatefulWidget { int moduleId; @@ -19,10 +32,11 @@ class ViewAllAttachmentPage extends StatefulWidget { } class _ViewAllAttachmentPageState extends State { + List? attachments; + @override void initState() { super.initState(); - Provider.of(context, listen: false).viewAllDocuments(widget.moduleId, widget.requestId); } @override @@ -30,27 +44,266 @@ class _ViewAllAttachmentPageState extends State { super.dispose(); } + Future> viewAllDocuments(int moduleId, int requestId) async { + try { + attachments ??= await ChatApiClient().viewAllDocuments(moduleId, requestId); + return attachments!; + } catch (ex) {} + return []; + } + @override Widget build(BuildContext context) { return Scaffold( backgroundColor: AppColor.neutral100, appBar: const DefaultAppBar(title: "Documents"), - body: FutureBuilder( - future: Provider.of(context, listen: false).viewAllDocuments(widget.moduleId, widget.requestId), - builder: (BuildContext context, AsyncSnapshot snapshot) { + body: FutureBuilder>( + future: viewAllDocuments(widget.moduleId, widget.requestId), + builder: (BuildContext context, AsyncSnapshot> snapshot) { bool isLoading = false; if (snapshot.connectionState == ConnectionState.waiting) { isLoading = true; } - return GridView.builder( - itemCount: 9, //isLoading? 9: 2, + if (snapshot.hasData && snapshot.data!.isEmpty) { + return NoDataFound(message: context.translation.noDataFound).center; + } + + return ListView.separated( + itemCount: isLoading ? 9 : snapshot.data!.length, padding: const EdgeInsets.all(16), - gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(crossAxisCount: context.isTablet() ? 4 : 3, childAspectRatio: 1, crossAxisSpacing: 8, mainAxisSpacing: 8), + separatorBuilder: (cxt, index) => 12.height, itemBuilder: (BuildContext context, int index) { - return Container().toShimmer(context: context, isShow: isLoading); + if (isLoading) { + return Container(height: 48).toShimmer(context: context, isShow: isLoading); + } + return AttachmentFileViewer(snapshot.data![index]); }, ); }), ); } } + +class AttachmentFileViewer extends StatelessWidget { + ChatAttachment chatAttachment; + + AttachmentFileViewer(this.chatAttachment, {Key? key}) : super(key: key); + + @override + Widget build(BuildContext context) { + return FutureBuilder( + future: checkFileInLocalStorage(), + builder: (BuildContext context, AsyncSnapshot snapshot) { + bool isImage = chatAttachment.storedFileName!.split(".").last.toLowerCase() == "png" || + chatAttachment.storedFileName!.split(".").last.toLowerCase() == "jpg" || + chatAttachment.storedFileName!.split(".").last.toLowerCase() == "jpeg"; + bool isPdf = chatAttachment.storedFileName!.split(".").last.toLowerCase() == "pdf"; + bool isExcel = chatAttachment.storedFileName!.split(".").last.toLowerCase() == "xlsx"; + bool isAudio = chatAttachment.storedFileName!.split(".").last.toLowerCase() == "m4a" || chatAttachment.storedFileName!.split(".").last.toLowerCase() == "mp3"; + + bool isLoading = snapshot.connectionState == ConnectionState.waiting; + + Widget widget; + if (isLoading) { + if (isAudio) { + return Container( + width: double.infinity, + height: 48, + decoration: ShapeDecoration( + color: AppColor.background(context), + shape: RoundedRectangleBorder( + side: BorderSide(width: 1, color: (context.isDark ? AppColor.neutral20 : AppColor.neutral30)), + borderRadius: BorderRadius.circular(32), + ), + ), + child: SizedBox(width: 24, height: 24, child: const CircularProgressIndicator(color: AppColor.primary10, strokeWidth: 2).center), + ); + } + widget = const SizedBox(width: 24, height: 24, child: CircularProgressIndicator(color: AppColor.primary10, strokeWidth: 2)); + } else { + if (isImage) { + widget = Image.file(snapshot.data!, width: 48, height: 48, fit: BoxFit.contain); + } else { + widget = Image.asset( + "assets/images/${isPdf ? "pdf" : isExcel ? "excel" : "doc"}.png", + fit: BoxFit.contain, + width: 48, + height: 48); + } + } + if (isAudio) { + if (snapshot.data == null) { + return Text("Failed to load", style: AppTextStyle.tiny.copyWith(color: context.isDark ? AppColor.red50 : AppColor.red60)); + } + return Container( + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(20), + ), + padding: const EdgeInsets.all(8), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + ASoundPlayer(audio: snapshot.data!.path), + 4.height, + Text( + chatAttachment.createdOn!.toServiceRequestDetailsFormat, + style: AppTextStyles.textFieldLabelStyle.copyWith(color: context.isDark ? AppColor.neutral30 : AppColor.neutral50), + ).paddingOnly(start: 60), + ], + )); + } + + return Container( + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(20), + ), + padding: const EdgeInsets.all(8), + child: Row( + children: [ + ClipRRect(borderRadius: BorderRadius.circular(8.0), child: widget), + 12.width, + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + chatAttachment.storedFileName!, + style: AppTextStyles.textFieldLabelStyle.copyWith(color: context.isDark ? AppColor.neutral30 : AppColor.neutral50), + ), + 4.height, + Text( + chatAttachment.createdOn!.toServiceRequestDetailsFormat, + style: AppTextStyles.textFieldLabelStyle.copyWith(color: context.isDark ? AppColor.neutral30 : AppColor.neutral50), + ), + ], + ).expanded + ], + ), + ).onPress(() { + if (isImage) { + Navigator.of(context).push( + MaterialPageRoute( + builder: (_) => Scaffold( + appBar: const DefaultAppBar(), + body: SafeArea( + child: InteractiveViewer(child: Image.file(snapshot.data!)).center, + ), + ), + ), + ); + } else { + OpenFile.open(snapshot.data!.path); + } + }); + return Container( + width: double.infinity, + height: 48, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(12), + color: Colors.white, + ), + child: snapshot.connectionState == ConnectionState.waiting + ? const SizedBox( + height: 24, + width: 24, + child: CircularProgressIndicator(color: AppColor.primary10, strokeWidth: 2), + ).center + : snapshot.hasData + ? Row( + children: [ + getImage(snapshot.data!), + getFile(context, snapshot.data!), + ], + ) + : const Icon(Icons.broken_image_rounded), + ); + + return Container( + width: double.infinity, + height: 48, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(12), + color: Colors.white, + ), + child: snapshot.connectionState == ConnectionState.waiting + ? const SizedBox( + height: 24, + width: 24, + child: CircularProgressIndicator(color: AppColor.primary10, strokeWidth: 2), + ).center + : snapshot.hasData + ? Row( + children: [ + getImage(snapshot.data!), + getFile(context, snapshot.data!), + ], + ) + : const Icon(Icons.broken_image_rounded), + ); + }); + } + + Widget getFile(BuildContext context, File file) { + bool isImage = file.path.split(".").last.toLowerCase() == "png" || file.path.split(".").last.toLowerCase() == "jpg" || file.path.split(".").last.toLowerCase() == "jpeg"; + bool isPdf = file.path.split(".").last.toLowerCase() == "pdf"; + bool isExcel = file.path.split(".").last.toLowerCase() == "xlsx"; + + return MaterialButton( + padding: EdgeInsets.zero, + onPressed: () async { + if (isImage) { + Navigator.of(context).push( + MaterialPageRoute( + builder: (_) => Scaffold( + appBar: const DefaultAppBar(), + body: SafeArea( + child: InteractiveViewer(child: Image.file(file)).center, + ), + ), + ), + ); + } else { + OpenFile.open(file.path); + } + // else { + // // if (!await launchUrl(Uri.parse(URLs.getFileUrl(file.path)!), mode: LaunchMode.externalApplication)) { + // // Fluttertoast.showToast(msg: "UnExpected Error with file."); + // // throw Exception('Could not launch'); + // // } + // } + }, + ); + } + + Widget getImage(File file) { + bool isImage = file.path.split(".").last.toLowerCase() == "png" || file.path.split(".").last.toLowerCase() == "jpg" || file.path.split(".").last.toLowerCase() == "jpeg"; + bool isPdf = file.path.split(".").last.toLowerCase() == "pdf"; + bool isExcel = file.path.split(".").last.toLowerCase() == "xlsx"; + + if (isImage) { + return Image.file(file); + } + return Image.asset("assets/images/${isPdf ? "pdf" : isExcel ? "excel" : "doc"}.png"); + } + + Future checkFileInLocalStorage() async { + Directory tempDir = await getTemporaryDirectory(); + String tempPath = '${tempDir.path}/${chatAttachment.storedFileName}'; + File tempFile = File(tempPath); + bool exists = await tempFile.exists(); + if (exists) { + return tempFile; + } else { + return downloadFile(); + } + } + + Future downloadFile() async { + try { + return await ChatApiClient().downloadFileWithHttp(chatAttachment.downloadUrl!, fileName: chatAttachment.storedFileName!, fileTypeDescription: "", fileSource: 0); + } catch (ex) { + return null; + } + } +}