import 'dart:async'; import 'dart:io'; import 'package:cloud_firestore/cloud_firestore.dart'; import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:open_file/open_file.dart'; import 'package:selectable_autolink_text/selectable_autolink_text.dart'; import 'package:telnow_mobile_new/src/api/api_auth_provider.dart'; import 'package:telnow_mobile_new/src/api/jwt_token.dart'; import 'package:telnow_mobile_new/src/layouts/components/template.dart'; import 'package:telnow_mobile_new/src/utils/U.dart'; import 'package:translator/translator.dart'; import 'package:url_launcher/url_launcher.dart'; class MobMessageChatPage extends StatefulWidget { final user; final String noTicket; final String opponent; final String messageId; final bool askBroadcast; final String tenant; const MobMessageChatPage(this.user, this.noTicket, this.opponent, this.messageId, this.askBroadcast, this.tenant, {super.key}); @override State createState() => _MobMessageChatPageState(); } class _MobMessageChatPageState extends State { final ApiAuthProvider apiAuthProvider = ApiAuthProvider(); final JwtToken token = JwtToken(); final translator = GoogleTranslator(); String? idChat; String? imagePath; String? filePath; ScrollController scrollController = ScrollController(); List messageData = []; int page = 0; bool isAfterLoad = false; String username = ''; bool isLoad = false; bool stopLoad = false; bool scrollBottom = false; bool isReverse = false; @override void initState() { if(U.getInternetStatus()){ getData(); setReadStatus(); scrollController.addListener(() => scrollListener()); } super.initState(); } setReadStatus() async { if (!widget.askBroadcast) { await apiAuthProvider.patchData('/api/messages/' + widget.messageId, {'readStatus': 'READ'}, context); } } getData() async { idChat = widget.noTicket; if (!kIsWeb) { imagePath = await token.getPath() + '/TelNow/Images/'; filePath = await token.getPath() + '/TelNow/Files/'; } username = widget.user['userId']; getMessage(); getCollectionData(); } getMessage() async { String url = 'myMessages/$idChat'; String filter = '{"f":["uniqueId","LIKE","%-%"]}'; if (widget.askBroadcast) { url = 'myBroadcast'; filter = '{"f":["1","EQ","1"]}'; } if (!isLoad && !stopLoad) { setState(() => isLoad = true); var mymess = await apiAuthProvider.getData('/api/messages/search/$url', {'isPaged': 'true', 'page': page.toString(), 'size': '20', 'filter': filter, 'tenant': widget.tenant}, context); if (mymess.containsKey('_embedded') && mounted) { List data = mymess['_embedded']['myMessages']; for (int i = 0; i < data.length; i++) { if (username == data[i]['from']['user']) { data[i]['selected'] = false; } else{ if(U.autoTranslate()){ data[i]['translate'] = ''; } } setState(() => messageData.insert(0, data[i])); downloadImage(data[i]); } setState(() { if (page == 0) { scrollBottom = true; } isLoad = false; page++; if (messageData.length >= mymess['page']['totalElements']) { stopLoad = true; } }); if(U.autoTranslate()){ translateMessage(); } } else { setState(() { isLoad = false; stopLoad = true; }); } } } translateMessage(){ var locale = context.locale.toString() == 'zh' ? 'zh-cn' : context.locale.toString(); messageData.forEach((element) async{ if (username != element['from']['user'] && element['translate'] == '') { var translate = await translator.translate(element['msg']??'', to: locale); setState(() { element['translate'] = translate.text; }); } }); } getCollectionData() { FirebaseFirestore.instance.collection("tmMessages").doc('messages').collection(idChat!).snapshots().listen((querySnapshot) { setState(() { querySnapshot.docChanges.forEach((result) async { var data = result.doc.data(); if (result.type == DocumentChangeType.added && isAfterLoad) { if ((username == data!['from']['user'] && messageData.where((element) => element['uniqueId'] == data['uniqueId']).length > 0) || (username != data['from']['user'] && data['readStatus'] == 'DELETED')) { int index = messageData.indexWhere((element) => element['uniqueId'] == data['uniqueId']); messageData[index]['read'] = data['read']; messageData[index]['readStatus'] = data['readStatus']; messageData[index]['imageUrl'] = data['imageUrl']; } else { if(U.autoTranslate()){ var locale = context.locale.toString() == 'zh' ? 'zh-cn' : context.locale.toString(); var translate = await translator.translate(data['msg']??'', to: locale); data['translate'] = translate.text; } messageData.add(data); } downloadImage(data); } else if (result.type == DocumentChangeType.modified && isAfterLoad) { // print("modified"); if (messageData.where((element) => element['uniqueId'] == data!['uniqueId']).length > 0) { int index = messageData.indexWhere((element) => element['uniqueId'] == data!['uniqueId']); messageData[index]['read'] = data!['read']; messageData[index]['readStatus'] = data['readStatus']; } } else if (result.type == DocumentChangeType.removed && isAfterLoad) { // print("removed"); } }); isAfterLoad = true; }); }); } Future cacheImage(String url, BuildContext context) async { bool hasNoError = true; var output = Completer(); precacheImage( NetworkImage(url), context, onError: (e, stackTrace) => hasNoError = false, ).then((_) => output.complete(hasNoError)); return output.future; } deleteCollection() { FirebaseFirestore.instance.collection("tmMessages").doc('messages').collection(idChat!).get().then((value) { for (DocumentSnapshot ds in value.docs) { ds.reference.delete(); } }); } getImageName(imageUrl) { var imgSplit = imageUrl.toString().split('/'); return imgSplit[imgSplit.length - 1]; } scrollToBottom() { scrollController.animateTo(scrollController.position.minScrollExtent, duration: Duration(milliseconds: 1), curve: Curves.decelerate); scrollBottom = false; } scrollListener() { if (scrollController.offset >= scrollController.position.maxScrollExtent) { // print('loadMessage'); getMessage(); } } downloadImage(data) async { if (!kIsWeb && data['imageUrl'] != null && data['imageUrl'] != '') { int index = messageData.indexWhere((element) => element['uniqueId'] == data['uniqueId']); setState(() => messageData[index]['loadingImage'] = true); if (data['imageUrl'].split('.').last == 'pdf') { if (!File(filePath! + getImageName(data['imageUrl'])).existsSync()) { await apiAuthProvider.downloadImage(data['imageUrl'], filePath! + getImageName(data['imageUrl'])); } } else { if (!File(imagePath! + getImageName(data['imageUrl'])).existsSync()) { if (await cacheImage(data['imageUrl'], context)) { await apiAuthProvider.downloadImage(data['imageUrl'], imagePath! + getImageName(data['imageUrl'])); } } } setState(() => messageData[index]['loadingImage'] = false); } } @override Widget build(BuildContext context) { var bodyWidth = U.bodyWidth(context); WidgetsBinding.instance.addPostFrameCallback((_) { if (messageData.length > 0) { if (scrollController.position.maxScrollExtent > 0 && !isReverse) { setState(() => isReverse = true); } if (isAfterLoad && scrollBottom && isReverse) { scrollToBottom(); } } }); return WillPopScope( onWillPop: () async { deleteCollection(); Navigator.of(context).pop(); return true; }, child: Scaffold( backgroundColor: Colors.white, resizeToAvoidBottomInset: true, appBar: appBarTemplate(context: context, title: widget.opponent != 'all_informants' ? widget.opponent : "allInformants".tr()), body: Column( children: [ divider(), U.getInternetStatus()?Expanded( child: Container(width: bodyWidth, child:LayoutBuilder( builder: (context,constraint) { return messageData.length == 0 && !isAfterLoad ? loadingTemplate() : Column( children: [ Expanded( child: SingleChildScrollView( padding: const EdgeInsets.fromLTRB(10, 10, 10, 7), controller: scrollController, reverse: isReverse, child: Column( children: List.generate(messageData.length, (i) { bool hideDate = i == 0 ? false : checkDate(messageData[i]['datetime'], messageData[i - 1]['datetime']); bool isNip = i == 0 ? true : !hideDate ? true : messageData[i]['from']['user'] == messageData[i - 1]['from']['user'] ? false : true; return Column( children: [ !hideDate ? Center( child: Padding( padding: const EdgeInsets.all(10), child: bubble_chat(Text(convertDate(messageData[i]['datetime'], context.locale.toString()), textAlign: TextAlign.center, style: TextStyle(fontSize: 12)), null, false, false), ), ) : Container(), Builder(builder: (context) { var isMe = messageData[i]['from']['user'] == widget.user['userId']; var isFile = messageData[i]['imageUrl'] != null && messageData[i]['imageUrl'] != '' && messageData[i]['readStatus'] != 'DELETED'; var isPdf = isFile && messageData[i]['imageUrl'].split('.').last == 'pdf'; var isImg = isFile && !isPdf; var isTranslate = U.autoTranslate() && !isMe && messageData[i]['msg'] != messageData[i]['translate']; var wdg = messageData[i]['readStatus'] == 'DELETED' ? Text('deletedMessage'.tr(), style: TextStyle(fontSize: 14, color: Colors.black54, fontStyle: FontStyle.italic)) : Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ isPdf ? GestureDetector( child: Builder(builder: (context) { var pdfContainer = Container( padding: EdgeInsets.all(6), decoration: BoxDecoration(color: Colors.black12.withValues(alpha: 0.1), borderRadius: BorderRadius.all(Radius.circular(4))), child: Row( mainAxisSize: MainAxisSize.min, children: [ Icon(Icons.picture_as_pdf), SizedBox( width: 6, ), Flexible(child: Text(getImageName(messageData[i]['imageUrl']))) ], ), ); if (kIsWeb) { return pdfContainer; } else if (File(filePath! + getImageName(messageData[i]['imageUrl'])).existsSync()) { return pdfContainer; } else { return NoImageFound(messageData[i]['loadingImage'] ? false : true); } }), onTap: () async { kIsWeb ? await launchUrl( messageData[i]['imageUrl']) : File(filePath! + getImageName(messageData[i]['imageUrl'])).existsSync() ? await OpenFile.open(filePath! + getImageName(messageData[i]['imageUrl'])) : null; }, ) : isImg ? GestureDetector( child: Container( margin: const EdgeInsets.only(bottom: 5), child: Builder(builder: (context) { if (kIsWeb) { return Image.network(messageData[i]['imageUrl'], fit: BoxFit.cover, width: 200, height: 200); } else if (File(imagePath! + getImageName(messageData[i]['imageUrl'])).existsSync()) { return Image.file(File(imagePath! + getImageName(messageData[i]['imageUrl'])), fit: BoxFit.cover, width: 200, height: 200); } else { return NoImageFound(messageData[i]['loadingImage'] ? false : true); } }) ), onTap: () => kIsWeb ? navigateTo(context, PhotoPreview(widget.opponent, messageData[i]['imageUrl'], true)) : File(imagePath! + getImageName(messageData[i]['imageUrl'])).existsSync() ? navigateTo(context, PhotoPreview(widget.opponent, File(imagePath! + getImageName(messageData[i]['imageUrl'])), false)) : null, ) : SizedBox(height: 1), messageData[i]['msg'] != null && messageData[i]['msg'] != '' ? Column( crossAxisAlignment: !isMe?CrossAxisAlignment.start:CrossAxisAlignment.end, children: [ SizedBox(height: 4), SelectableAutoLinkText( messageData[i]['msg'], style: TextStyle(fontSize: 14, color: Colors.black), linkStyle: TextStyle(color: Colors.blueAccent), highlightedLinkStyle: TextStyle(color: Colors.blueAccent, backgroundColor: Colors.blueAccent.withAlpha(0x33)), onTap: (link) async{ if (await canLaunchUrl(Uri.parse(link))) { await launchUrl(Uri.parse(link)); } }, onLongPress: (link) { Clipboard.setData(new ClipboardData(text: link)).then((value){ showSuccess('link_copied'.tr(), context); }); }, enableInteractiveSelection: false, ), isTranslate ? Container( margin: EdgeInsets.only(top: 2), decoration: BoxDecoration(border: Border(top: BorderSide(color: Colors.black.withValues(alpha: 0.2)))), child: Text(messageData[i]['translate']!=''?'(${messageData[i]['translate']})':'...', style: TextStyle(fontSize: 14, color: Colors.black.withValues(alpha: 0.65), fontStyle: FontStyle.italic)), ) : Container(), ], ) : Container() ], ); var timeBubble = Positioned( bottom: 0, right: isNip ? 6 : 0, child: Text(DateFormat('HH:mm').format(DateTime.parse(messageData[i]['datetime'])), style: TextStyle(fontSize: 10, color: Colors.black45)), ); var expander = Expanded( flex: 8, child: Column( children: [ bubble_chat(wdg, timeBubble, isNip, isMe), SizedBox( height: 5, ), ], crossAxisAlignment: isMe ? CrossAxisAlignment.end : CrossAxisAlignment.start, )); return isMe ? Row( children: [ Expanded( flex: 2, child: Container(), ), expander, ], ) : Row( children: [ expander, Expanded( flex: 2, child: Container(), ), ], ); }), ], ); }), ), ), ), ], ); } ), ), ):Container( margin: EdgeInsets.all(20), child: Text('noInternetDesc'.tr(), style: TextStyle(color: textColor.withValues(alpha: 0.65), fontStyle: FontStyle.italic, height: 1.5, fontSize: 16.0), textAlign: TextAlign.center), ) ], ), ), ); } Widget bubble_chat(Widget child, Widget? time, bool isNip, bool isMe) { bool isDate = false; if (time == null) { isDate = true; time = Container(); } Color color = isMe ? primaryColor.withValues(alpha: 0.3) : isDate ? Color(0xffD5F5FF) : Color(0xffECECEC); var clipPath = ClipPath( child: ConstrainedBox( constraints: BoxConstraints(minWidth: 50), child: Container( decoration: BoxDecoration( color: color, ), child: Padding(padding: EdgeInsets.all(8), child: isDate ? child : Stack( children: [ Padding( padding: EdgeInsets.only( bottom: 15, right: isNip && isMe ? 6 : 0, left: isNip && !isMe ? 6 : 0, ), child: child, ), time ], ), ), ), ), clipper: isMe ? MyClipper(isNip) : YourClipper(isNip), ); return Padding( padding: EdgeInsets.only( right: isNip && isMe ? 0 : 6, left: isNip && !isMe ? 0 : 6 ), child: clipPath ); } bool checkDate(date1, date2) { final dateToCheck1 = DateTime(DateTime.parse(date1).year, DateTime.parse(date1).month, DateTime.parse(date1).day); final dateToCheck2 = DateTime(DateTime.parse(date2).year, DateTime.parse(date2).month, DateTime.parse(date2).day); return dateToCheck1 == dateToCheck2 ? true : false; } String convertDate(date, locale) { final dateToCheck = DateTime.parse(date); final now = DateTime.now(); final today = DateTime(now.year, now.month, now.day); final yesterday = DateTime(now.year, now.month, now.day - 1); final aDate = DateTime(dateToCheck.year, dateToCheck.month, dateToCheck.day); if (aDate == today) { return 'today'.tr(); } else if (aDate == yesterday) { return 'yesterday'.tr(); } else { return DateFormat('dd MMMM yyyy', locale).format(DateTime.parse(date)); } } }