import 'dart:async'; import 'dart:typed_data'; import 'dart:ui' as ui; import 'package:cloud_firestore/cloud_firestore.dart'; import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_linkify/flutter_linkify.dart'; import 'package:image_picker/image_picker.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/injector/injector.dart'; import 'package:telnow_mobile_new/src/layouts/components/photo_chat.dart'; import 'package:telnow_mobile_new/src/layouts/functions/detail.dart'; import 'package:telnow_mobile_new/src/layouts/components/template.dart'; import 'package:telnow_mobile_new/src/storage/sharedpreferences/shared_preferences_manager.dart'; import 'package:telnow_mobile_new/src/utils/U.dart'; import 'package:timelines_plus/timelines_plus.dart'; import 'package:translator/translator.dart'; import 'package:url_launcher/url_launcher.dart'; import 'package:image/image.dart' as img; import 'package:uuid/uuid.dart'; class WebHistoryForumPage extends StatefulWidget { final Map data; final Map user; const WebHistoryForumPage({required this.data, required this.user, super.key}); @override State createState() => _WebHistoryForumPageState(); } class _WebHistoryForumPageState extends State { final DetailFunction detFunc = DetailFunction(); final ApiAuthProvider apiAuthProvider = ApiAuthProvider(); final JwtToken token = JwtToken(); final translator = GoogleTranslator(); final SharedPreferencesManager _sharedPreferencesManager = locator(); var rating = [ {'key': 1, 'image': "assets/image/icon/very_dissatisfied.png", 'label': 'disatisfied'.tr()}, {'key': 2, 'image': "assets/image/icon/dissatisfied.png", 'label': 'lessSatisfied'.tr()}, {'key': 3, 'image': "assets/image/icon/neutral.png", 'label': 'satisfied'.tr()}, {'key': 4, 'image': "assets/image/icon/satisfied.png", 'label': 'verySatisfied'.tr()}, {'key': 5, 'image': "assets/image/icon/very_satisfied.png", 'label': 'reallyPleased'.tr()}, ]; String? idChat; String? imagePath; TextEditingController controllerPesan = new TextEditingController(); ScrollController scrollController = ScrollController(); List messageData = []; List deletedMessage = []; int page = 0; double borderRadius = 50.0; String username = ''; bool isAfterLoad = false; bool isSend = false; bool isLoad = false; bool stopLoad = false; bool scrollBottom = false; bool isReverse = false; bool openChat = false; Uint8List? _image; @override void initState() { if(U.getInternetStatus()){ openChat = widget.data['currentState'] == 'DIMULAI' || widget.data['currentState'] == 'DISELESAIKAN'; getData(); scrollController.addListener(() => scrollListener()); } super.initState(); } getData() async { idChat = U.decodeBase64Url(_sharedPreferencesManager.getString(SharedPreferencesManager.keyAccessCode)!) + '-' + widget.data['ticketNo']; var res = await token.getUserData(context); if (res != null) { username = res['userId']; getMessage(); getCollectionData(); } } setAsRead(ticketNo) async { var res = await apiAuthProvider.postData('/api/notifications/readMyForum/$ticketNo', null, null, context); if (res != null) { } } getMessage() async { if (!isLoad && !stopLoad) { setState(() => isLoad = true); setAsRead(widget.data['ticketNo']); var mymess = await apiAuthProvider.getData('/api/messages/search/myMessages/' + idChat!, {'isPaged': 'true', 'page': page.toString(), 'size': '20'}, context); if (mymess.containsKey('_embedded')) { 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])); } 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); } } else if (result.type == DocumentChangeType.modified && isAfterLoad) { 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']; } } }); isAfterLoad = true; }); }); } 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]; } sendMessage(data) async { setState(() { isSend = false; controllerPesan.clear(); messageData.add({ 'msg': data['text'], 'datetime': DateTime.now().toString(), 'read': null, 'imageUrl': data['images'], 'readStatus': '', 'from': {'name': 'my name', 'user': data['userId']}, 'uniqueId': data['uniqueId'], 'selected': false, 'senderType': 'INFORMANT' }); scrollBottom = true; }); var res = await apiAuthProvider.postData('/api/messages', null, data, context); if (res != null) { int index = messageData.indexWhere((element) => element['uniqueId'] == data['uniqueId']); setState(() { messageData[index]['read'] = false; }); } else { int index = messageData.indexWhere((element) => element['uniqueId'] == data['uniqueId']); setState(() => messageData[index]['readStatus'] = 'FAILED'); } } scrollToBottom() { scrollController.animateTo(scrollController.position.minScrollExtent, duration: Duration(milliseconds: 1), curve: Curves.decelerate); scrollBottom = false; } scrollListener() { if (scrollController.offset >= scrollController.position.maxScrollExtent) { getMessage(); } } @override Widget build(BuildContext context) { WidgetsBinding.instance.addPostFrameCallback((_) { if (messageData.length > 0) { if (scrollController.position.maxScrollExtent > 0 && !isReverse) { setState(() => isReverse = true); } if (isAfterLoad && scrollBottom && isReverse) { scrollToBottom(); } } }); return Scaffold( backgroundColor: backgroundColor, appBar: PreferredSize(preferredSize: Size.fromHeight(0), child: AppBar(elevation: 0, backgroundColor: primaryColor)), body: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Container( padding: EdgeInsets.symmetric(vertical: 25, horizontal: 100), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text('forum'.tr(), style: TextStyle(color: textColor, fontSize: 17, fontWeight: FontWeight.w500), overflow: TextOverflow.ellipsis), GestureDetector( child: Text('buttonBack'.tr(), style: TextStyle(color: primaryColor, fontSize: 14)), onTap: (){ deleteCollection(); navigateBack(context); }, ) ], ), ), divider(), Expanded( child: Container( padding: EdgeInsets.symmetric(vertical: 25, horizontal: 100), child: Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ Expanded( child: SingleChildScrollView( child: Container( padding: EdgeInsets.all(20), decoration: BoxDecoration(color: Colors.white, border: Border.all(color: textColor.withValues(alpha: 0.15)), borderRadius: BorderRadius.all(Radius.circular(12))), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text(widget.data[U.langColumn(context, 'requestGroupDescription')], style: TextStyle(color: textColor, fontWeight: FontWeight.w600)), requestTiles(image: widget.data['_requestImage'] ?? "null", title: widget.data[U.langColumn(context, 'requestSubject')], subtitle: widget.data[U.langColumn(context, '_subjectDescription')]??'', border: true), SizedBox(height: 16), textHorizontal('ticketNumber'.tr(), widget.data['ticketNo']), SizedBox(height: 16), widget.user.isNotEmpty?renderRequested():Container(), textHorizontal('location'.tr(), widget.data['ipphoneExtLocation']), SizedBox(height: 16), divider(opacity: 0.05), SizedBox(height: 16), widget.data['autoResponse'] ? Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text('note'.tr(), style: TextStyle(color: textColor)), SizedBox(height: 5), widget.data['responseText']!=null&&widget.data['responseText']!=''?Linkify( text: widget.data['responseText'], style: TextStyle(color: textColor, fontSize: 12, fontWeight: FontWeight.w300), onOpen: (link) async { if (await canLaunchUrl(Uri.parse(link.url))) { await launchUrl(Uri.parse(link.url)); } }, ):Container(), widget.data['responseAttachment']!=null&&widget.data['responseAttachment']!=''?widget.data['_isPdf']?GestureDetector( child: Container( width: double.infinity, margin: EdgeInsets.only(top: 8), child: Center( child: Column( children: [ Icon(Icons.picture_as_pdf, color: Colors.deepOrange, size: 50), Text('seeAttachment'.tr(), style: TextStyle(color: Colors.black45, fontSize: 12)) ], ), ), decoration: BoxDecoration(border: Border.all(color: Colors.deepOrange), borderRadius: BorderRadius.all(Radius.circular(12))) ), onTap: () => detFunc.openAttachment(widget.data) ):GestureDetector( child: LayoutBuilder( builder: (context, constraints) { return Container( child:Image.network(widget.data['_mobileResponseAttachment'], fit: BoxFit.cover, width: double.infinity, height: constraints.maxWidth/(1.7), loadingBuilder:(BuildContext? context, Widget? child,ImageChunkEvent? loadingProgress) { if (loadingProgress == null) return child!; return Container( height: constraints.maxWidth/(1.7), child: Center( child: CircularProgressIndicator( value: loadingProgress.expectedTotalBytes != null ? loadingProgress.cumulativeBytesLoaded / loadingProgress.expectedTotalBytes! : null, ), ), ); }), ); }, ), onTap: ()=>navigateTo(context, PhotoPreview('image'.tr(), widget.data['_mobileResponseAttachment'], true)) ):Container() ], ) : attachment_new(widget.data), widget.data['autoResponse'] ? Container() : SizedBox(height: 16), widget.data['autoResponse'] ? Container() : textVertical('note'.tr(), widget.data['requestNote']!=''?widget.data['requestNote']:'-'), widget.data['autoResponse'] ? Container() : SizedBox(height: 16), widget.data['autoResponse'] || widget.data['datetimeScheduled'] == null || widget.data['datetimeScheduled'] == '' ? Container() : Text("${"scheduleMessage".tr()} ${widget.data['datetimeScheduled']}."), separator(), SizedBox(height: 16), widget.data['autoResponse'] ? Container() : Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text('activity'.tr(), style: TextStyle(color: textColor, fontWeight: FontWeight.w600)), SizedBox(height: 16), widget.data['currentState'] != 'DIBATALKAN' && widget.data['currentState'] != 'DIANTRIKAN' && widget.data['currentState'] != 'DIPROSES' && widget.data['_collaboratorDataFilter'].length > 0?Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text('servant'.tr(), style: TextStyle(color: textColor, fontSize: 14)), Container( padding: EdgeInsets.only(top: 10, left: 16), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text('1. ${widget.data['servantNameStart']??'-'}', style: TextStyle(color: textColor, fontSize: 14), overflow: TextOverflow.ellipsis), Column( crossAxisAlignment: CrossAxisAlignment.start, children: List.generate(widget.data['_collaboratorDataFilter'].length, (i) { return Text('${i+2}. ${widget.data['_collaboratorDataFilter'][i]['name']}', style: TextStyle(color: textColor, fontSize: 14), overflow: TextOverflow.ellipsis); }), ) ], ), ) ], ) : textHorizontal( widget.data['currentState'] == 'DIBATALKAN'?'canceledBy'.tr():'servant'.tr(), widget.data['currentState'] == 'DIBATALKAN'?widget.data['servantNameCancel']??'-':widget.data['currentState'] != 'DIANTRIKAN' && widget.data['currentState'] != 'DIPROSES'?widget.data['servantNameStart']??'-':'-' ), SizedBox(height: 16), divider(opacity: 0.05), SizedBox(height: 16), Text('timeline'.tr(), style: TextStyle(color: textColor, fontSize: 14)), SizedBox(height: 16), Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ timeline('stateRequested'.tr(), widget.data['datetimeRequest'] != null ? convertDate(widget.data['datetimeRequest'], context.locale.toString()) : '-', null, null, current: widget.data['currentState'] == 'DIANTRIKAN' || widget.data['currentState'] == 'DIPROSES', first: true), widget.data['currentState'] == 'DIMULAI' ? timeline('stateDone'.tr(), widget.data['datetimeStart'] != null ? convertDate(widget.data['datetimeStart'], context.locale.toString()) : '-', widget.data['noteStart'], widget.data['noteStartTranslate'], current: widget.data['currentState'] == 'DIMULAI') : Container(), !widget.data['autoResponse'] && (widget.data['currentState'] == 'DISELESAIKAN' || widget.data['currentState'] == 'TUNTAS') ? timeline('startDoing'.tr(), widget.data['datetimeStart'] != null ? convertDate(widget.data['datetimeStart'], context.locale.toString()) : '-', widget.data['noteStart'], widget.data['noteStartTranslate']) : Container(), !widget.data['autoResponse'] && (widget.data['currentState'] == 'DISELESAIKAN' || widget.data['currentState'] == 'TUNTAS') ? timeline('stateFinish'.tr(), widget.data['datetimeFinish'] != null ? convertDate(widget.data['datetimeFinish'], context.locale.toString()) : '-', widget.data['noteFinish'], widget.data['noteFinishTranslate'], current: widget.data['currentState'] == 'DISELESAIKAN' || widget.data['currentState'] == 'TUNTAS') : Container(), widget.data['autoResponse'] && widget.data['currentState'] == 'TUNTAS' ? timeline('stateFinish'.tr(), widget.data['datetimeComplete'] != null ? convertDate(widget.data['datetimeComplete'], context.locale.toString()) : '-', widget.data['noteComplete'], widget.data['noteCompleteTranslate'], current: widget.data['currentState'] == 'TUNTAS') : Container(), widget.data['currentState'] == 'DIBATALKAN' ? timeline('stateCanceled'.tr(), widget.data['datetimeCancel'] != null ? convertDate(widget.data['datetimeCancel'], context.locale.toString()) : '-', widget.data['noteCancel'], widget.data['noteCancelTranslate'], current: widget.data['currentState'] == 'DIBATALKAN') : Container(), ], ), suspendPanel(widget.data), finish_att_new(widget.data), !widget.data['autoResponse'] && (widget.data['currentState'] == 'TUNTAS' || widget.data['currentState'] == 'DISELESAIKAN') ? Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ SizedBox(height: 16), divider(opacity: 0.05), SizedBox(height: 16), Row( children: [ Text('rate'.tr(), style: TextStyle(color: textColor.withValues(alpha: 0.75), fontSize: 14)), Expanded( child: widget.data['satisfactionRate'] > 0 ? Row( mainAxisAlignment: MainAxisAlignment.end, children: [ Text(rating[widget.data['satisfactionRate']-1]['label'].toString(), style: TextStyle(color: textColor, fontSize: 14), overflow: TextOverflow.ellipsis), SizedBox(width: 5), Image(image: AssetImage(rating[widget.data['satisfactionRate']-1]['image'].toString()), width: 25), ], ) : Text('unrated'.tr(), style: TextStyle(color: textColor, fontSize: 14), textAlign: TextAlign.end, overflow: TextOverflow.ellipsis), ), ], ) ], ) : Container() ], ) ], ), ), ), ), SizedBox(width: 30), Expanded( child: Column( children: [ Expanded( child: messageData.length == 0 && !isAfterLoad ? loadingTemplate() : SingleChildScrollView( 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 ? Container(margin: EdgeInsets.only(bottom: 5), 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 = username == messageData[i]['from']['user'] && messageData[i]['senderType'] == 'INFORMANT'; var isImage = messageData[i]['imageUrl'] != null && messageData[i]['imageUrl'] != '' && messageData[i]['readStatus'] != 'DELETED'; var isTranslate = U.autoTranslate() && !isMe && messageData[i]['msg'] != messageData[i]['translate']; var widget = Row( mainAxisSize: MainAxisSize.min, children: [ messageData[i]['readStatus'] == 'FAILED' ? Padding( padding: const EdgeInsets.only(right: 15), child: PopupMenuButton( itemBuilder: (context) => [ PopupMenuItem(value: 1, height: 40, child: Text("delete".tr(), style: TextStyle(fontSize: 14))), PopupMenuItem(value: 2, height: 40, child: Text("resend".tr(), style: TextStyle(fontSize: 14))), ], offset: Offset(MediaQuery.of(context).size.width, 0), onSelected: (value) { if (value == 1) { messageData.removeWhere((item) => item['uniqueId'] == messageData[i]['uniqueId']); } else if (value == 2) { var text = messageData[i]['msg']; var images = messageData[i]['imageUrl']; messageData.removeWhere((item) => item['uniqueId'] == messageData[i]['uniqueId']); var uuid = Uuid().v1().replaceAll('-', ''); var data = { "uniqueId": uuid, "userId": username, "recipientId": "#forum", "senderType": "INFORMANT", "text": text, "chatId": idChat, "images": images }; sendMessage(data); } }, child: Icon(Icons.refresh, color: Colors.red), ), ) : Container(), Flexible( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ !isMe ? Padding( padding: const EdgeInsets.only(bottom: 6.0,), child: Text( messageData[i]['from']['name'], style: TextStyle(color: Color(U.getColor(messageData[i]['from']['user'])), fontSize: 12.0), textAlign: TextAlign.start, ), ) : SizedBox(), isImage ? Padding( padding: EdgeInsets.only(bottom: 2), child: GestureDetector( child: Container( margin: const EdgeInsets.only(bottom: 5), child: Builder(builder: (context) { return Image.network(messageData[i]['imageUrl']); }), ), onTap: () => navigateTo(context, PhotoPreview('forum'.tr(), messageData[i]['imageUrl'], true)), ), ) : Container(width: 1), messageData[i]['readStatus'] == 'DELETED' ? Text('deletedMessage'.tr(), style: TextStyle(fontSize: 14, color: Colors.white70, fontStyle: FontStyle.italic)) : messageData[i]['msg'] != null && messageData[i]['msg'] != '' ? 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); }); }, textAlign: TextAlign.start, ) : Container(width: 1), 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(), ], ), ) ], ); var timeBubble = RichText( text: TextSpan(children: [ TextSpan(text: DateFormat('HH:mm').format(DateTime.parse(messageData[i]['datetime'])), style: TextStyle(fontSize: 12, color: Colors.black38)), WidgetSpan( child: Padding( padding: const EdgeInsets.only(left: 3), child: Icon(messageData[i]['read'] == null ? Icons.done : Icons.done_all, color: messageData[i]['read'] != null && messageData[i]['read'] ? Colors.green : Colors.black38, size: 15) ) ), ]), ); var expander = Expanded( flex: 8, child: Column( children: [ bubble_chat(widget, 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(), ), ], ); }) ], ); }), ), ), ), openChat?divider():Container(), openChat?Container( alignment: Alignment.bottomCenter, color: Colors.white, padding: EdgeInsets.only(top: 20), child: Row( children: [ Expanded( child: SizedBox( width: double.infinity, child: TextField( controller: controllerPesan, maxLength: 256, style: const TextStyle(fontSize: 14, color: Colors.black), keyboardType: TextInputType.multiline, minLines: 1, maxLines: 5, decoration: InputDecoration( counterText: '', hintText: 'writeMessage'.tr()+'..', hintStyle: TextStyle(color: textColor.withValues(alpha: 0.5), fontSize: 14), filled: true, fillColor: backgroundColor, hoverColor: Colors.black.withValues(alpha: 0.1), contentPadding: EdgeInsets.symmetric(vertical: 17, horizontal: 20), suffixIcon: GestureDetector( child: Padding(padding: EdgeInsets.only(left: 13, right: 13), child: U.iconsax('paperclip-2', color: textColor, size: 24)), onTap: ()async{ var uuid = Uuid().v1().replaceAll('-', ''); var data = { "uniqueId": uuid, "userId": username, "recipientId": "#forum", "senderType": "INFORMANT", "text": controllerPesan.text.trim(), "chatId": idChat, "images": null }; await getImage(ImageSource.gallery).then((value) { if (_image != null) { navigateTo(context, PhotoChat(data, ImageSource.gallery, false, _image!)).then((value) { if (value != null) { sendMessage(value); } }); } }); }, ), border: InputBorder.none, enabledBorder: OutlineInputBorder(borderRadius: BorderRadius.circular(borderRadius), borderSide: BorderSide(color: textColor)), focusedBorder: OutlineInputBorder(borderRadius: BorderRadius.circular(borderRadius), borderSide: BorderSide(color: primaryColor)), isDense: true ), onChanged: (val) { setState(() { if (val.trim() != '') { final tp = TextPainter(text: TextSpan(text: val), textDirection: ui.TextDirection.ltr); tp.layout(maxWidth: 400); final line = tp.computeLineMetrics().length; if (line == 1) { borderRadius = 50.0; } else { borderRadius = 20.0; } isSend = true; } else { isSend = false; } }); }, ), ), ), SizedBox(width: 12), GestureDetector( child: Container( padding: EdgeInsets.all(12), child: Image.asset('assets/image/icon/Send.png', width: 20, height: 20), decoration: BoxDecoration(color: primaryColor, borderRadius: BorderRadius.all(Radius.circular(50))), ), onTap: (){ if(controllerPesan.text.isNotEmpty){ var uuid = Uuid().v1().replaceAll('-', ''); var data = { "uniqueId": uuid, "userId": username, "recipientId": "#forum", "senderType": "INFORMANT", "text": controllerPesan.text.trim(), "chatId": idChat, "images": null }; sendMessage(data); } }, ) ], ), ):U.getInternetStatus()?Center(child: Padding( padding: EdgeInsets.symmetric(vertical: 20), child: bubble_chat(Text('chatClosed'.tr(), textAlign: TextAlign.center, style: TextStyle(fontSize: 12)), null, false, false), )):Container() ], ), ) ], ), ), ) ], ), ); } Future getImage(media) async { try { var pickedFile = await ImagePicker().pickImage(source: media); if (pickedFile != null) { var image = img.decodeImage(await pickedFile.readAsBytes()); var imgPercent = (1000 / (image!.width / 100)).toDouble(); if (image.width > 1000) { image = img.copyResize(image, width: ((image.width / 100) * imgPercent).toInt(), height: ((image.height / 100) * imgPercent).toInt()); } var compressed = img.encodeJpg(image, quality: 60); setState(() { _image = compressed as Uint8List?; }); } } catch (e) { print(e.toString()); } } 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)); } } 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: 20, right: isNip && isMe ? 6 : 0, left: isNip && !isMe ? 6 : 0, ), child: child, ), Positioned(bottom: 0.0, right: isNip && isMe ? 6 : 0, 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); } //----------------------------------------------------------------------------------------------------------------------------- Widget renderRequested(){ if(widget.data['receptionistId'] != null){ if(widget.user['roomAttendant'] != null && widget.user['roomAttendant'] && widget.user['userId'] != widget.data['informantUserId'] && widget.user['userId'] == widget.data['receptionistId']){ return Column( children: [ textHorizontal('requestedFor'.tr(), widget.data['informantName']??'-'), SizedBox(height: 16), ], ); } if(widget.user['userId'] == widget.data['informantUserId'] && widget.user['userId'] != widget.data['receptionistId']){ return Column( children: [ textHorizontal('requestedBy'.tr(), widget.data['receptionistName']??'-'), SizedBox(height: 16), ], ); } } return Container(); } Widget attachment_new(list){ List imageList = []; if(widget.data['_attachment'] != null){ for(var i = 1; i <= 5; i++){ if(widget.data['_attachment']['_mobileRequestAtt$i'] != null){ imageList.add({'thumb': widget.data['_attachment']['_mobileRequestThumb$i'], 'image': widget.data['_attachment']['_mobileRequestAtt$i']}); } } } return imageList.length > 0 ? Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text('image'.tr(), style: TextStyle(color: textColor)), SizedBox(height: 5), LayoutBuilder( builder: (context, constraints) { var imageWidth = ((constraints.maxWidth-32)/5)-5; return Row( children: List.generate(imageList.length, (i){ return GestureDetector( child: Container( width: imageWidth, height: imageWidth, alignment: Alignment.topRight, margin: EdgeInsets.only(right: i == 4 ? 0 : 6), decoration: BoxDecoration( color: Colors.black12, borderRadius: BorderRadius.all(Radius.circular(5)), border: Border.all(color: Colors.black26, width: 0.5), image: imageList[i]['thumb'] != null ? DecorationImage(image: NetworkImage(imageList[i]['thumb']), fit: BoxFit.cover) : DecorationImage(image: AssetImage('assets/image/error/ImageNotFound.png'), fit: BoxFit.cover) ), ), onTap: ()=>navigateTo(context, PhotoPreview('image'.tr(), imageList[i]['image'], true)) ); }), ); }, ) ], ) : textVertical('image'.tr(), 'noImgAttach'.tr()); } Widget finish_att_new(list){ List imageList = []; if((widget.data['currentState'] == 'DISELESAIKAN' || widget.data['currentState'] == 'TUNTAS')){ if(widget.data['_attachment'] != null){ for(var i = 1; i <= 5; i++){ if(widget.data['_attachment']['_mobileFinishAtt$i'] != null){ imageList.add({'thumb': widget.data['_attachment']['_mobileFinishThumb$i'], 'image': widget.data['_attachment']['_mobileFinishAtt$i']}); } } } } return imageList.length > 0 ? Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ SizedBox(height: 16), Text('finishAttachment'.tr(), style: TextStyle(color: textColor)), SizedBox(height: 8), LayoutBuilder( builder: (context, constraints) { var imageWidth = ((constraints.maxWidth-32)/5)-5; return Row( children: List.generate(imageList.length, (i){ return GestureDetector( child: Container( width: imageWidth, height: imageWidth, alignment: Alignment.topRight, margin: EdgeInsets.only(right: i == 4 ? 0 : 6), decoration: BoxDecoration( color: Colors.black12, borderRadius: BorderRadius.all(Radius.circular(5)), border: Border.all(color: Colors.black26, width: 0.5), image: imageList[i]['thumb'] != null ? DecorationImage(image: NetworkImage(imageList[i]['thumb']), fit: BoxFit.cover) : DecorationImage(image: AssetImage('assets/image/error/ImageNotFound.png'), fit: BoxFit.cover) ), ), onTap: ()=>navigateTo(context, PhotoPreview('finishAttachment'.tr(), imageList[i]['image'], true)) ); }), ); }, ) ], ) : Container(); } Widget suspendPanel(list){ var activeHold = widget.data['_activeHoldRequest']; var requestHold = widget.data['_holdRequest']; return Column( children: [ activeHold != null ? Padding(padding: EdgeInsets.symmetric(vertical: 15), child: divider(opacity: 0.05)) : Container(), activeHold != null ? Column( children: [ Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text('state'.tr(), style: TextStyle(fontSize: 14, color: textColor)), Container( child: Row( mainAxisAlignment: MainAxisAlignment.end, children: [ Image(image: AssetImage('assets/image/general/Watch.png'), width: 20, height: 20), SizedBox(width: 5), Text('hold'.tr(), style: TextStyle(fontSize: 14, color: primaryColor)), ], ), ) ], ), SizedBox(height: 16), textHorizontal('description'.tr(), activeHold['description']) ], ) : Container(), requestHold == null || requestHold.length == 0 || (requestHold.length == 1 && requestHold[0]['datetimeEnd'] == null) ? Container() : Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Padding(padding: EdgeInsets.symmetric(vertical: 15), child: divider(opacity: 0.05)), Text('holdHistory'.tr(), style: TextStyle(fontSize: 14, color: Colors.black)), SizedBox(height: 5), Column( children: List.generate(requestHold.length, (i) { return requestHold[i]['datetimeEnd'] != null ? Container( padding: EdgeInsets.symmetric(vertical: 5), child: Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text('${i+1}.', style: TextStyle(fontSize: 14, color: Colors.black)), SizedBox(width: 5), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text(requestHold[i]['description'], style: TextStyle(fontSize: 14, color: Colors.black), textAlign: TextAlign.start), SizedBox(height: 3), Text('${DateFormat('dd MMM yyyy, HH:mm', context.locale.toString()).format(DateTime.parse(requestHold[i]['datetimeStart']))} - ${DateFormat('dd MMM yyyy, HH:mm', context.locale.toString()).format(DateTime.parse(requestHold[i]['datetimeEnd']))}', style: TextStyle(fontSize: 14, color: primaryColor)) ], ), ) ], ), ) : Container(); }), ) ], ) ], ); } Widget timeline(label, text, note, noteTranslate, {bool first = false, bool current = false}) { return TimelineTile( nodeAlign: TimelineNodeAlign.start, contents: Container( margin: EdgeInsets.fromLTRB(15, 10, 0, 10), child: Row( children: [ Text(label, style: TextStyle(fontSize: 14, color: textColor.withValues(alpha: 0.85))), SizedBox(width: 5), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.end, children: [ Text(text, style: TextStyle(fontSize: 13, color: textColor)), note != null && note != '' ? Text(note, style: TextStyle(fontSize: 12, color: primaryColor), textAlign: TextAlign.right) : Container(), U.autoTranslate() && noteTranslate != null && noteTranslate != '' && note != noteTranslate ? Container( margin: EdgeInsets.only(top: 1), decoration: BoxDecoration(border: Border(top: BorderSide(color: primaryColor.withValues(alpha: 0.3)))), child: Text('($noteTranslate)', style: TextStyle(fontSize: 12, color: primaryColor.withValues(alpha: 0.7), fontStyle: FontStyle.italic), textAlign: TextAlign.right), ) : Container(), ], ), ), ], ), ), node: TimelineNode( indicator: DotIndicator(color: current ? primaryColor : Color(0xffE8E8E8)), startConnector: SolidLineConnector(color: first ? Colors.transparent : Color(0xffE8E8E8)), endConnector: SolidLineConnector(color: current ? Colors.transparent : Color(0xffE8E8E8)), ), ); } }