message_list.dart 62 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441
  1. import 'dart:async';
  2. import 'package:cloud_firestore/cloud_firestore.dart';
  3. import 'package:easy_localization/easy_localization.dart';
  4. import 'package:easy_refresh/easy_refresh.dart';
  5. import 'package:flutter/material.dart';
  6. import 'package:flutter/services.dart';
  7. import 'package:image_picker/image_picker.dart';
  8. import 'package:provider/provider.dart';
  9. import 'package:selectable_autolink_text/selectable_autolink_text.dart';
  10. import 'package:telnow_mobile_new/src/api/api_auth_provider.dart';
  11. import 'package:telnow_mobile_new/src/layouts/functions/message.dart';
  12. import 'package:telnow_mobile_new/src/layouts/components/template.dart';
  13. import 'package:telnow_mobile_new/src/utils/C.dart';
  14. import 'package:telnow_mobile_new/src/utils/U.dart';
  15. import 'package:telnow_mobile_new/src/utils/provider.dart';
  16. import 'package:translator/translator.dart';
  17. import 'package:url_launcher/url_launcher.dart';
  18. import 'dart:ui' as ui;
  19. import 'package:uuid/uuid.dart';
  20. import '../components/photo_chat.dart';
  21. class WebMessageListPage extends StatefulWidget {
  22. final Map<String, dynamic>? user;
  23. const WebMessageListPage(this.user, {super.key});
  24. @override
  25. State<WebMessageListPage> createState() => _WebMessageListPageState();
  26. }
  27. class _WebMessageListPageState extends State<WebMessageListPage> {
  28. final MessageFunction messageFunc = MessageFunction();
  29. final ApiAuthProvider apiAuthProvider = ApiAuthProvider();
  30. final translator = GoogleTranslator();
  31. StreamSubscription<QuerySnapshot>? subscription;
  32. String? idChat;
  33. String tenant = '';
  34. String opponent = '';
  35. ScrollController scrollController = ScrollController();
  36. ScrollController forumScrollController = ScrollController();
  37. List messageData = [];
  38. List forumData = [];
  39. int page = 0;
  40. bool isAfterLoad = false;
  41. String username = '';
  42. bool isLoad = false;
  43. bool stopLoad = false;
  44. bool scrollBottom = false;
  45. bool forumScrollBottom = false;
  46. bool isReverse = false;
  47. bool isForumReverse = false;
  48. bool askBroadcast = false;
  49. bool openChat = false;
  50. bool isSend = false;
  51. double borderRadius = 10.0;
  52. Uint8List? _image;
  53. String forumHeader = '';
  54. String activeForumId = '';
  55. String forumChatId = '';
  56. TextEditingController controllerForum = new TextEditingController();
  57. String locale = 'id';
  58. late MessageModule messageModule;
  59. @override
  60. void initState() {
  61. messageModule = Provider.of<MessageModule>(context, listen: false);
  62. messageModule.reset();
  63. if(widget.user == null){
  64. messageFunc.getUser(context);
  65. } else {
  66. messageModule.setUser(widget.user!);
  67. messageFunc.getDataMessages(context);
  68. }
  69. username = messageModule.user()['userId'];
  70. // TODO: implement initState
  71. super.initState();
  72. }
  73. @override
  74. void dispose() {
  75. subscription?.cancel();
  76. super.dispose();
  77. }
  78. selectMessage(data, isMe) async{
  79. if(idChat != data['chatId']){
  80. setState(() {
  81. idChat = data['chatId'];
  82. askBroadcast = isMe;
  83. opponent = isMe ? data['recipientName'] : data['senderName'];
  84. tenant = data['recipient'];
  85. messageData = [];
  86. page = 0;
  87. isAfterLoad = false;
  88. isLoad = false;
  89. stopLoad = false;
  90. scrollBottom = false;
  91. isReverse = false;
  92. });
  93. scrollController.addListener(() => scrollListener());
  94. if (!isMe) {
  95. await apiAuthProvider.patchData('/api/messages/' + data['id'].toString(), {'readStatus': 'READ'});
  96. }
  97. getMessage();
  98. getCollectionData();
  99. }
  100. }
  101. selectMessageForum(dt){
  102. forumChatId = '${U.decodeBase64Url(U.getAccessCode()!)}-${dt['ticketNo']}';
  103. activeForumId = dt['ticketNo'];
  104. openChat = !(dt['currentState'] == 'DIBATALKAN' || dt['currentState'] == 'TUNTAS');
  105. forumHeader = dt[U.langColumn(context, 'requestSubject')];
  106. forumScrollController.addListener(() => forumScrollListener());
  107. forumData = [];
  108. page = 0;
  109. stopLoad = false;
  110. // unsub dulu biar ga double2
  111. subscription?.cancel();
  112. subscription = null;
  113. getMessageForum();
  114. getCollectionDataForum();
  115. }
  116. getMessage() async {
  117. String url = 'myMessages/$idChat';
  118. String filter = '{"f":["uniqueId","LIKE","%-%"]}';
  119. if (askBroadcast) {
  120. url = 'myBroadcast';
  121. filter = '{"f":["1","EQ","1"]}';
  122. }
  123. if (!isLoad && !stopLoad) {
  124. isLoad = true;
  125. var mymess = await apiAuthProvider.getData('/api/messages/search/$url', {'isPaged': 'true', 'page': page.toString(), 'size': '20', 'filter': filter, 'tenant': tenant});
  126. if (mymess.containsKey('_embedded') && mounted) {
  127. List data = mymess['_embedded']['myMessages'];
  128. for (int i = 0; i < data.length; i++) {
  129. if (username == data[i]['from']['user']) {
  130. data[i]['selected'] = false;
  131. }
  132. else{
  133. if(U.autoTranslate()){
  134. data[i]['translate'] = '';
  135. }
  136. }
  137. messageData.insert(0, data[i]);
  138. }
  139. setState(() {
  140. if (page == 0) {
  141. scrollBottom = true;
  142. }
  143. isLoad = false;
  144. page++;
  145. if (messageData.length >= mymess['page']['totalElements']) {
  146. stopLoad = true;
  147. }
  148. });
  149. if(U.autoTranslate()){
  150. translateMessage(0);
  151. }
  152. } else {
  153. setState(() {
  154. isLoad = false;
  155. stopLoad = true;
  156. });
  157. }
  158. }
  159. }
  160. getMessageForum()async{
  161. if(stopLoad) return;
  162. try{
  163. var mymess = await apiAuthProvider.getData(
  164. '/api/messages/search/myMessages/${forumChatId}',
  165. {'isPaged': 'true', 'page': page.toString(), 'size': '20'});
  166. if (mymess.containsKey('_embedded')) {
  167. List data = mymess['_embedded']['myMessages'];
  168. for (var item in data) {
  169. if (username == item['from']['user'] && item['senderType'] != 'SERVANT') {
  170. item['selected'] = false;
  171. } else {
  172. if(U.autoTranslate()){
  173. item['translate'] = '';
  174. }
  175. }
  176. final exists = forumData.any((e) => e['id'] == item['id']);
  177. if (!exists) {
  178. forumData.insert(0, item);
  179. }
  180. }
  181. if(mymess['page']['number'] == page) page++;
  182. if(mounted) {
  183. setState(() {
  184. isAfterLoad = true;
  185. });
  186. }
  187. }
  188. if(U.autoTranslate()){
  189. translateMessage(1);
  190. }
  191. if (forumData.length >= mymess['page']['totalElements']) {
  192. stopLoad = true;
  193. }
  194. } catch(e) {
  195. print(e.toString());
  196. }
  197. }
  198. getCollectionData() {
  199. FirebaseFirestore.instance.collection("tmMessages").doc('messages').collection(idChat!).snapshots().listen((querySnapshot) {
  200. querySnapshot.docChanges.forEach((result) async {
  201. var data = result.doc.data();
  202. if (result.type == DocumentChangeType.added && isAfterLoad) {
  203. if ((username == data!['from']['user'] && messageData.where((element) => element['uniqueId'] == data['uniqueId']).isNotEmpty) || (username != data['from']['user'] && data['readStatus'] == 'DELETED')) {
  204. int index = messageData.indexWhere((element) => element['uniqueId'] == data['uniqueId']);
  205. messageData[index]['read'] = data['read'];
  206. messageData[index]['readStatus'] = data['readStatus'];
  207. messageData[index]['imageUrl'] = data['imageUrl'];
  208. } else {
  209. try{
  210. if(U.autoTranslate()){
  211. var loc = locale == 'zh' ? 'zh-cn' : locale;
  212. var translate = await translator.translate(data['msg']??'', to: loc);
  213. data['translate'] = translate.text;
  214. }
  215. }catch(e){
  216. print("Error: ${e.toString()}");
  217. }
  218. messageData.add(data);
  219. }
  220. } else if (result.type == DocumentChangeType.modified && isAfterLoad) {
  221. // print("modified");
  222. if (messageData.where((element) => element['uniqueId'] == data!['uniqueId']).isNotEmpty) {
  223. int index = messageData.indexWhere((element) => element['uniqueId'] == data!['uniqueId']);
  224. messageData[index]['read'] = data!['read'];
  225. messageData[index]['readStatus'] = data['readStatus'];
  226. }
  227. } else if (result.type == DocumentChangeType.removed && isAfterLoad) {
  228. // print("removed");
  229. }
  230. });
  231. if(mounted) {
  232. setState(() {
  233. isAfterLoad = true;
  234. });
  235. }
  236. });
  237. }
  238. getCollectionDataForum() {
  239. subscription = FirebaseFirestore.instance.collection("tmMessages").doc('messages').collection(forumChatId).snapshots().listen((querySnapshot) {
  240. querySnapshot.docChanges.forEach((result) async {
  241. var data = result.doc.data();
  242. print(data);
  243. if(!mounted) return;
  244. if (result.type == DocumentChangeType.added && isAfterLoad) {
  245. if((username == data!['from']['user'] && forumData.where((element) => element['uniqueId'] == data['uniqueId']).isNotEmpty) || (username != data['from']['user'] && data['readStatus'] == 'DELETED')) {
  246. int index = forumData.indexWhere((element) => element['uniqueId'] == data['uniqueId']);
  247. forumData[index]['read'] = data['read'];
  248. forumData[index]['readStatus'] = data['readStatus'];
  249. forumData[index]['imageUrl'] = data['imageUrl'];
  250. } else {
  251. try{
  252. if(U.autoTranslate()){
  253. var loc = locale == 'zh' ? 'zh-cn' : locale;
  254. var translate = await translator.translate(data['msg']??'', to: loc);
  255. data['translate'] = translate.text;
  256. }
  257. }catch(e){
  258. print("Error f: ${e.toString()}");
  259. }
  260. forumData.add(data);
  261. updateMessageDisplay(data);
  262. }
  263. } else if (result.type == DocumentChangeType.modified && isAfterLoad) {
  264. // print("modified");
  265. if (forumData.where((element) => element['uniqueId'] == data!['uniqueId']).isNotEmpty) {
  266. int index = forumData.indexWhere((element) => element['uniqueId'] == data!['uniqueId']);
  267. forumData[index]['read'] = data!['read'];
  268. forumData[index]['readStatus'] = data['readStatus'];
  269. }
  270. } else if (result.type == DocumentChangeType.removed && isAfterLoad) {
  271. // print("removed");
  272. }
  273. if(mounted) setState(() {});
  274. });
  275. if(mounted) {
  276. setState(() {
  277. isAfterLoad = true;
  278. });
  279. }
  280. });
  281. }
  282. void updateMessageDisplay(Map<String, dynamic> data) {
  283. final forum = messageModule.forum();
  284. final res = <Map<String, dynamic>>[];
  285. for (int i = 0; i < forum.length; i++) {
  286. final tmp = Map<String, dynamic>.from(forum[i]);
  287. if (tmp['ticket']['ticketNo'] == activeForumId) {
  288. tmp['msg'] = data['msg'];
  289. }
  290. res.add(tmp);
  291. }
  292. messageModule.setForum(res);
  293. }
  294. translateMessage(int t){
  295. var dt = t == 0 ? messageData : forumData;
  296. var loc = locale == 'zh' ? 'zh-cn' : locale;
  297. dt.forEach((element) async {
  298. if (element['senderType'] == 'SERVANT' && element['translate'] == '') { //&& username != element['from']['user']
  299. try {
  300. var translate = await translator.translate(
  301. element['msg'] ?? '', to: loc);
  302. element['translate'] = translate.text;
  303. }catch(e){
  304. element['translate'] = '';
  305. }
  306. setState(() {});
  307. }
  308. });
  309. }
  310. deleteCollection() {
  311. var ids = [idChat, forumChatId];
  312. for(int i=0; i < ids.length; i++){
  313. var id = ids[i]??'';
  314. if(id != '') {
  315. try {
  316. FirebaseFirestore.instance.collection("tmMessages").doc('messages')
  317. .collection(id).get()
  318. .then((value) {
  319. for (DocumentSnapshot ds in value.docs) {
  320. ds.reference.delete();
  321. }
  322. });
  323. } catch (e) {}
  324. }
  325. }
  326. }
  327. scrollToBottom() {
  328. if(scrollController.hasClients) {
  329. scrollController.animateTo(scrollController.position.minScrollExtent,
  330. duration: Duration(milliseconds: 1), curve: Curves.decelerate);
  331. scrollBottom = false;
  332. }
  333. }
  334. scrollListener() {
  335. if (scrollController.hasClients && scrollController.offset >= scrollController.position.maxScrollExtent) {
  336. // print('loadMessage');
  337. getMessage();
  338. }
  339. }
  340. forumScrollListener() {
  341. if(forumScrollController.hasClients && forumScrollController.offset >= forumScrollController.position.maxScrollExtent) {
  342. getMessageForum();
  343. }
  344. }
  345. @override
  346. Widget build(BuildContext context) {
  347. locale = context.locale.toString();
  348. if (messageData.length > 0) {
  349. try{
  350. if (scrollController.hasClients && scrollController.position.maxScrollExtent > 0 && !isReverse) {
  351. setState(() => isReverse = true);
  352. }
  353. if (isAfterLoad && scrollBottom && isReverse) {
  354. scrollToBottom();
  355. }
  356. }catch(e){
  357. print(e.toString());
  358. }
  359. }
  360. Widget tabForum(){
  361. return Row(
  362. mainAxisAlignment: MainAxisAlignment.start,
  363. children: [
  364. GestureDetector(
  365. onTap: () {
  366. Provider.of<MessageModule>(context, listen: false).setActiveTab(0);
  367. },
  368. child: Container(
  369. margin: EdgeInsetsDirectional.only(start: 100),
  370. padding: EdgeInsets.symmetric(vertical: 12, horizontal: 16),
  371. decoration: BoxDecoration(
  372. color: Colors.white,
  373. border: Border(bottom: BorderSide(color: Provider.of<MessageModule>(context).activeTab() == 0 ? primaryColor : primaryColor.withValues(alpha: 0.3), width: Provider.of<MessageModule>(context).activeTab() == 0 ? 2 : 1))),
  374. child: Text('chat'.tr(), style: TextStyle(color: Provider.of<MessageModule>(context).activeTab() == 0 ? textColor : textColor.withValues(alpha: 0.65), fontSize: 16)),
  375. ),
  376. ),
  377. GestureDetector(
  378. onTap: () {
  379. Provider.of<MessageModule>(context, listen: false).setActiveTab(1);
  380. },
  381. child: Container(
  382. padding: EdgeInsets.symmetric(vertical: 12, horizontal: 16),
  383. decoration: BoxDecoration(
  384. color: Colors.white,
  385. border: Border(bottom: BorderSide(color: Provider.of<MessageModule>(context).activeTab() == 1 ? primaryColor : primaryColor.withValues(alpha: 0.3), width: Provider.of<MessageModule>(context).activeTab() == 1 ? 2 : 1))),
  386. child: Text('forum'.tr(), style: TextStyle(color: Provider.of<MessageModule>(context).activeTab() == 1 ? textColor : textColor.withValues(alpha: 0.65), fontSize: 16)),
  387. ),
  388. ),
  389. ],
  390. );
  391. }
  392. // Widget chat(){
  393. // final messageModule = Provider.of<MessageModule>(context);
  394. // final dataList = messageModule.data();
  395. // final currentUser = messageModule.user();
  396. // final forumList = messageModule.forum();
  397. //
  398. // return dataList.isEmpty ? Expanded(
  399. // child: Center(
  400. // child: Text('noMessageText'.tr()),
  401. // ),
  402. // ) : SingleChildScrollView(
  403. // padding: EdgeInsets.symmetric(vertical: 20, horizontal: 10),
  404. // child: Column(
  405. // mainAxisSize: MainAxisSize.max,
  406. // children: List.generate(dataList.length, (i) {
  407. // final item = dataList[i];
  408. // final isMe = item['userId'] == currentUser['userId'];
  409. // final avatar = item['senderAvatar'];
  410. // final chatIdMatch = idChat != null && idChat == item['chatId'];
  411. // final recipientColor = U.getColor(isMe ? item['recipient'] : item['recipientId']);
  412. // final recipientName = item['recipientName'];
  413. // final senderName = item['senderName'];
  414. // final lastText = item['lastText'] ?? '';
  415. // final isImage = item['isImage'] ?? false;
  416. // final fileType = item['fileType'] ?? '';
  417. // final lastDate = item['lastDateTimeSend'];
  418. // final lastReadStatus = item['lastReadStatus'];
  419. // final forumReadStatus = forumList[i]['readStatus'];
  420. //
  421. // return GestureDetector(
  422. // onTap: () => selectMessage(item, isMe),
  423. // child: Container(
  424. // padding: EdgeInsets.all(10),
  425. // decoration: BoxDecoration(
  426. // color: chatIdMatch ? Color(0xff26DA17).withAlpha(50) : Colors.white,
  427. // borderRadius: BorderRadius.circular(12),
  428. // ),
  429. // child: Row(
  430. // children: [
  431. // avatar != null && avatar != ''
  432. // ? CircleAvatar(
  433. // backgroundImage: NetworkImage(avatar),
  434. // radius: 24,
  435. // )
  436. // : Container(
  437. // height: 48,
  438. // width: 48,
  439. // decoration: BoxDecoration(
  440. // shape: BoxShape.circle,
  441. // color: Color(recipientColor),
  442. // ),
  443. // child: Center(
  444. // child: Text(
  445. // isMe
  446. // ? recipientName == "all_informants"
  447. // ? "allInformants".tr()[0]
  448. // : recipientName[0]
  449. // : senderName[0],
  450. // style: TextStyle(
  451. // color: Colors.white,
  452. // fontWeight: FontWeight.bold,
  453. // fontSize: 18,
  454. // ),
  455. // ),
  456. // ),
  457. // ),
  458. // SizedBox(width: 20),
  459. // Expanded(
  460. // child: Column(
  461. // crossAxisAlignment: CrossAxisAlignment.start,
  462. // children: [
  463. // Text(
  464. // isMe
  465. // ? recipientName == "all_informants"
  466. // ? "allInformants".tr()
  467. // : recipientName
  468. // : senderName,
  469. // style: TextStyle(fontWeight: FontWeight.w600),
  470. // ),
  471. // SizedBox(height: 5),
  472. // Row(
  473. // children: [
  474. // if (isMe)
  475. // Padding(
  476. // padding: EdgeInsets.only(right: 5),
  477. // child: Icon(Icons.done_all, size: 18, color: Colors.blueGrey),
  478. // ),
  479. // if (isImage)
  480. // Row(
  481. // children: [
  482. // Icon(
  483. // fileType == 'pdf' ? Icons.picture_as_pdf : Icons.image,
  484. // color: Colors.black45,
  485. // size: 16,
  486. // ),
  487. // SizedBox(width: 6),
  488. // if (lastText == '')
  489. // Text(fileType == 'pdf' ? 'pdfFile'.tr() : 'photo'.tr()),
  490. // ],
  491. // ),
  492. // Expanded(
  493. // child: Text(
  494. // lastText,
  495. // style: TextStyle(fontSize: 13),
  496. // maxLines: 1,
  497. // overflow: TextOverflow.ellipsis,
  498. // ),
  499. // ),
  500. // ],
  501. // ),
  502. // ],
  503. // ),
  504. // ),
  505. // SizedBox(width: 20),
  506. // Column(
  507. // crossAxisAlignment: CrossAxisAlignment.end,
  508. // children: [
  509. // Text(
  510. // convertDate(lastDate, context.locale.toString()),
  511. // style: TextStyle(
  512. // fontSize: 11,
  513. // fontWeight: forumReadStatus == 'UNREAD' ? FontWeight.w600 : FontWeight.w300,
  514. // color: lastReadStatus == 'UNREAD' && !isMe ? primaryColor : Colors.black45,
  515. // ),
  516. // ),
  517. // SizedBox(height: 12),
  518. // Icon(
  519. // Icons.circle,
  520. // size: 12,
  521. // color: primaryColor.withAlpha(lastReadStatus == 'UNREAD' && !isMe ? 1 : 0),
  522. // ),
  523. // ],
  524. // ),
  525. // ],
  526. // ),
  527. // ),
  528. // );
  529. // }),
  530. // ),
  531. // );
  532. // }
  533. Widget webChat(BuildContext context) {
  534. final messageModule = Provider.of<MessageModule>(context);
  535. final dataList = messageModule.data();
  536. final currentUser = messageModule.user();
  537. final isFirstLoad = messageModule.firstLoad();
  538. return Container(
  539. padding: EdgeInsets.symmetric(vertical: 20, horizontal: 10),
  540. alignment: Alignment.topCenter,
  541. child: dataList.isEmpty && !isFirstLoad
  542. ? loadingTemplateNoVoid()
  543. : dataList.isEmpty
  544. ? Center(child: Text('noMessageText'.tr()))
  545. : ListView.builder(
  546. itemCount: dataList.length,
  547. itemBuilder: (context, i) {
  548. final item = dataList[i];
  549. final isMe = item['userId'] == currentUser['userId'];
  550. final avatar = item['senderAvatar'];
  551. final recipientName = item['recipientName'];
  552. final senderName = item['senderName'];
  553. final lastText = item['lastText'] ?? '';
  554. final isImage = item['isImage'] ?? false;
  555. final fileType = item['fileType'] ?? '';
  556. final lastDate = item['lastDateTimeSend'];
  557. final lastReadStatus = item['lastReadStatus'];
  558. final chatId = item['chatId'];
  559. final recipientId = item['recipientId'];
  560. final recipient = item['recipient'];
  561. // final id = item['id'];
  562. return GestureDetector(
  563. onTap: () {
  564. selectMessage(item, isMe);
  565. // navigateTo(
  566. // context,
  567. // MobMessageChatPage(
  568. // currentUser,
  569. // chatId,
  570. // isMe ? recipientName : senderName,
  571. // id.toString(),
  572. // isMe,
  573. // recipient,
  574. // ),
  575. // );
  576. messageFunc.onRefresh(context);
  577. },
  578. child: Container(
  579. padding: EdgeInsets.all(10),
  580. margin: EdgeInsets.only(bottom: 12),
  581. decoration: BoxDecoration(
  582. color: idChat != null && idChat == chatId
  583. ? Color(0xff26DA17).withAlpha(50)
  584. : Colors.white,
  585. borderRadius: BorderRadius.circular(12),
  586. ),
  587. child: Row(
  588. children: [
  589. avatar != null && avatar != ''
  590. ? CircleAvatar(
  591. backgroundImage: NetworkImage(avatar),
  592. radius: 24,
  593. )
  594. : Container(
  595. height: 48,
  596. width: 48,
  597. decoration: BoxDecoration(
  598. shape: BoxShape.circle,
  599. color: Color(U.getColor(isMe ? recipient : recipientId)),
  600. ),
  601. child: Center(
  602. child: Text(
  603. isMe
  604. ? recipientName == "all_informants"
  605. ? "allInformants".tr()[0]
  606. : recipientName[0]
  607. : senderName[0],
  608. style: TextStyle(
  609. color: Colors.white,
  610. fontWeight: FontWeight.bold,
  611. fontSize: 18,
  612. ),
  613. ),
  614. ),
  615. ),
  616. SizedBox(width: 20),
  617. Expanded(
  618. child: Column(
  619. crossAxisAlignment: CrossAxisAlignment.start,
  620. children: [
  621. Text(
  622. isMe
  623. ? recipientName == "all_informants"
  624. ? "allInformants".tr()
  625. : recipientName
  626. : senderName,
  627. style: TextStyle(fontWeight: FontWeight.w600),
  628. ),
  629. SizedBox(height: 5),
  630. Row(
  631. children: [
  632. if (isMe)
  633. Padding(
  634. padding: EdgeInsets.only(right: 4),
  635. child: Icon(Icons.done_all, size: 18, color: Colors.blueGrey),
  636. ),
  637. if (isImage)
  638. Row(
  639. children: [
  640. Icon(
  641. fileType == 'pdf' ? Icons.picture_as_pdf : Icons.image,
  642. color: Colors.black45,
  643. size: 16,
  644. ),
  645. SizedBox(width: 6),
  646. if (lastText == '')
  647. Text(fileType == 'pdf' ? 'pdfFile'.tr() : 'photo'.tr()),
  648. ],
  649. ),
  650. Expanded(
  651. child: Text(
  652. lastText,
  653. style: TextStyle(fontSize: 13),
  654. maxLines: 1,
  655. overflow: TextOverflow.ellipsis,
  656. ),
  657. ),
  658. ],
  659. ),
  660. ],
  661. ),
  662. ),
  663. SizedBox(width: 20),
  664. Column(
  665. crossAxisAlignment: CrossAxisAlignment.end,
  666. children: [
  667. Text(
  668. messageFunc.timeSet(lastDate),
  669. style: TextStyle(
  670. fontSize: 11,
  671. color: lastReadStatus == 'UNREAD' && !isMe ? Colors.green : Colors.black45,
  672. ),
  673. ),
  674. SizedBox(height: 4),
  675. Icon(
  676. Icons.circle,
  677. color: Colors.green.withAlpha(lastReadStatus == 'UNREAD' && !isMe ? 1 : 0),
  678. ),
  679. ],
  680. ),
  681. ],
  682. ),
  683. ),
  684. );
  685. },
  686. ),
  687. );
  688. }
  689. // tampilan list pesan forum di sebelah kiri
  690. Widget forum(){
  691. final messageModule = Provider.of<MessageModule>(context);
  692. return messageModule.forum().isEmpty ? Center(
  693. child: Text('noMessageText'.tr()),
  694. ) : SingleChildScrollView(
  695. padding: EdgeInsets.symmetric(vertical: 20, horizontal: 10),
  696. child: Column(
  697. mainAxisSize: MainAxisSize.max,
  698. children: List.generate(messageModule.forum().length, (i){
  699. bool isMe0 = messageModule.forum()[i]['userId'] == messageModule.user()['userId'] ? true : false;
  700. return GestureDetector(
  701. onTap: (){
  702. var dt = messageModule.forum()[i]['ticket'];
  703. messageFunc.setAsRead(context, dt['ticketNo']);
  704. messageModule.forum()[i]['readStatus'] = 'READ';
  705. selectMessageForum(dt);
  706. setState(() {
  707. isAfterLoad = false;
  708. forumData = [];
  709. });
  710. },
  711. child: Container(
  712. decoration: BoxDecoration(
  713. color: activeForumId != '' && messageModule.forum()[i]['ticket']['ticketNo'] == activeForumId ? Color(0xff26DA17).withValues(alpha: 0.2) : Colors.white,
  714. borderRadius: BorderRadius.all(Radius.circular(12)),
  715. ),
  716. width: double.infinity,
  717. child: Column(
  718. children: [
  719. Padding(
  720. padding: const EdgeInsets.all(10.0),
  721. child: Row(
  722. children: [
  723. Container(
  724. height: 48,
  725. width: 48,
  726. decoration: BoxDecoration(
  727. image: DecorationImage(image: NetworkImage(messageModule.forum()[i]['ticket']['_requestImage'])),
  728. // color: Colors.green,
  729. border: Border.all(color: Colors.black12),
  730. borderRadius: BorderRadius.circular(50),
  731. ),
  732. ),
  733. SizedBox(width: 20,),
  734. Expanded(
  735. child: Column(
  736. crossAxisAlignment: CrossAxisAlignment.start,
  737. children: [
  738. Row(
  739. mainAxisAlignment: MainAxisAlignment.spaceBetween,
  740. crossAxisAlignment: CrossAxisAlignment.start,
  741. children: [
  742. Flexible(
  743. child: Text(
  744. messageModule.forum()[i]['ticket'][U.langColumn(context, 'requestSubject')],
  745. maxLines: 1,
  746. overflow: TextOverflow.ellipsis,
  747. softWrap: false,
  748. style: TextStyle(
  749. fontWeight: FontWeight.w600,
  750. ),
  751. ),
  752. ),
  753. SizedBox(width: 4),
  754. Text(
  755. convertDate(messageModule.forum()[i]['datetime'], context.locale.toString()),
  756. style: TextStyle(
  757. fontSize: 11,
  758. fontWeight: messageModule.forum()[i]['readStatus']=='UNREAD' ? FontWeight.w600 : FontWeight.w300,
  759. color: messageModule.forum()[i]['readStatus']=='UNREAD' && !isMe0 ? primaryColor : Colors.black45,
  760. ),
  761. ),
  762. ],
  763. ),
  764. SizedBox(height: 8,),
  765. Row(
  766. crossAxisAlignment: CrossAxisAlignment.start,
  767. mainAxisAlignment: MainAxisAlignment.spaceBetween,
  768. children: [
  769. Flexible(
  770. child: Text(
  771. messageModule.forum()[i]['msg'],
  772. overflow: TextOverflow.ellipsis,
  773. maxLines: 2,
  774. ),
  775. ),
  776. SizedBox(width: 12,),
  777. Icon(
  778. Icons.circle,
  779. size: 12,
  780. color: messageModule.forum()[i]['readStatus']=='UNREAD' && !isMe0 ? primaryColor : Colors.transparent,
  781. ),
  782. ],
  783. ),
  784. ],
  785. ),
  786. ),
  787. ],
  788. ),
  789. ),
  790. ],
  791. )
  792. ),
  793. );
  794. }),
  795. ),
  796. );
  797. }
  798. Widget chatList(){
  799. return idChat != null ? Column(
  800. children: [
  801. Container(
  802. width: double.infinity, padding: EdgeInsets.all(20),
  803. child: Text(opponent != 'all_informants' ? opponent : "allInformants".tr(), style: TextStyle(color: textColor, fontSize: 16)),
  804. ),
  805. divider(),
  806. Expanded(
  807. child: messageData.isEmpty && !isAfterLoad ? loadingTemplateNoVoid() : SingleChildScrollView(
  808. padding: const EdgeInsets.fromLTRB(10, 10, 10, 7),
  809. controller: scrollController,
  810. reverse: isReverse,
  811. child: Column(
  812. children: List.generate(messageData.length, (i) {
  813. bool hideDate = i == 0 ? false : checkDate(messageData[i]['datetime'], messageData[i - 1]['datetime']);
  814. bool isNip = i == 0 ? true : !hideDate ? true : messageData[i]['from']['user'] == messageData[i - 1]['from']['user'] ? false : true;
  815. return Column(
  816. children: [
  817. !hideDate ? Center(
  818. child: Padding(
  819. padding: const EdgeInsets.all(10),
  820. child: bubble_chat(Text(convertDate(messageData[i]['datetime'], context.locale.toString()), textAlign: TextAlign.center, style: TextStyle(fontSize: 12)), null, false, false),
  821. ),
  822. ) : Container(),
  823. Builder(builder: (context) {
  824. var isMe = messageData[i]['from']['user'] == Provider.of<MessageModule>(context, listen: false).user()['userId'];
  825. var isFile = messageData[i]['imageUrl'] != null && messageData[i]['imageUrl'] != '' && messageData[i]['readStatus'] != 'DELETED';
  826. var isPdf = isFile && messageData[i]['imageUrl'].split('.').last == 'pdf';
  827. var isImg = isFile && !isPdf;
  828. var isTranslate = U.autoTranslate() && !isMe && messageData[i]['msg'] != messageData[i]['translate'];
  829. var wdg = messageData[i]['readStatus'] == 'DELETED' ? Text('deletedMessage'.tr(), style: TextStyle(fontSize: 14, color: Colors.black54, fontStyle: FontStyle.italic)) : Column(
  830. crossAxisAlignment: CrossAxisAlignment.start,
  831. children: [
  832. isPdf ? GestureDetector(
  833. child: Builder(builder: (context) {
  834. var pdfContainer = Container(
  835. padding: EdgeInsets.all(6),
  836. decoration: BoxDecoration(color: Colors.black12.withValues(alpha: 0.1), borderRadius: BorderRadius.all(Radius.circular(4))),
  837. child: Row(
  838. mainAxisSize: MainAxisSize.min,
  839. children: [
  840. Icon(Icons.picture_as_pdf),
  841. SizedBox(width: 6),
  842. Flexible(child: Text(getImageName(messageData[i]['imageUrl'])))
  843. ],
  844. ),
  845. );
  846. return pdfContainer;
  847. }),
  848. onTap: () async {
  849. await launchUrl( messageData[i]['imageUrl']);
  850. },
  851. ) : isImg ? GestureDetector(
  852. child: Container(
  853. margin: const EdgeInsets.only(bottom: 5),
  854. child: Builder(builder: (context) {
  855. return Image.network(messageData[i]['imageUrl'], fit: BoxFit.cover, width: 200, height: 200);
  856. })
  857. ),
  858. onTap: () => navigateTo(context, PhotoPreview(opponent, messageData[i]['imageUrl'], true)),
  859. ) : SizedBox(height: 1),
  860. messageData[i]['msg'] != null && messageData[i]['msg'] != '' ? Column(
  861. crossAxisAlignment: !isMe?CrossAxisAlignment.start:CrossAxisAlignment.end,
  862. children: [
  863. SizedBox(height: 4),
  864. SelectableAutoLinkText(
  865. messageData[i]['msg'],
  866. style: TextStyle(fontSize: 14, color: Colors.black),
  867. linkStyle: TextStyle(color: Colors.blueAccent),
  868. highlightedLinkStyle: TextStyle(color: Colors.blueAccent, backgroundColor: Colors.blueAccent.withAlpha(0x33)),
  869. onTap: (link) async{
  870. if (await canLaunchUrl(Uri.parse(link))) {
  871. await launchUrl(Uri.parse(link));
  872. }
  873. },
  874. onLongPress: (link) {
  875. Clipboard.setData(new ClipboardData(text: link)).then((value){
  876. showSuccess('link_copied'.tr(), context);
  877. });
  878. },
  879. enableInteractiveSelection: false,
  880. ),
  881. isTranslate ? Container(
  882. margin: EdgeInsets.only(top: 2), decoration: BoxDecoration(border: Border(top: BorderSide(color: Colors.black.withValues(alpha: 0.2)))),
  883. child: Text(messageData[i]['translate'] != '' ? '(${messageData[i]['translate']})' : '...', style: TextStyle(fontSize: 14, color: Colors.black.withValues(alpha: 0.65), fontStyle: FontStyle.italic)),
  884. ) : Container(),
  885. ],
  886. ) : Container()
  887. ],
  888. );
  889. var timeBubble = Positioned(
  890. bottom: 2, right: isNip ? 6 : 0,
  891. child: Text(DateFormat('HH:mm').format(DateTime.parse(messageData[i]['datetime'])), style: TextStyle(fontSize: 10, color: Colors.black45)),
  892. );
  893. var expander = Expanded(
  894. flex: 8,
  895. child: Column(
  896. crossAxisAlignment: isMe ? CrossAxisAlignment.end : CrossAxisAlignment.start,
  897. children: [
  898. bubble_chat(wdg, timeBubble, isNip, isMe),
  899. SizedBox(height: 5),
  900. ],
  901. )
  902. );
  903. return isMe ? Row(
  904. children: [
  905. Expanded(flex: 2, child: Container()),
  906. expander,
  907. ],
  908. ) : Row(
  909. children: [
  910. expander,
  911. Expanded(flex: 2, child: Container()),
  912. ],
  913. );
  914. }),
  915. ],
  916. );
  917. }),
  918. ),
  919. ),
  920. )
  921. ],
  922. ) : Center(
  923. child: Text("noMessageText".tr()),
  924. );
  925. }
  926. sendMessage(data) async {
  927. isSend = false;
  928. controllerForum.clear();
  929. forumData.add({
  930. 'msg': data['text'],
  931. 'datetime': DateTime.now().toString(),
  932. 'read': null,
  933. 'imageUrl': data['images'],
  934. 'readStatus': '',
  935. 'from': {'name': 'my name', 'user': data['userId']},
  936. 'uniqueId': data['uniqueId'],
  937. 'selected': false,
  938. 'senderType': 'INFORMANT'
  939. });
  940. scrollBottom = true;
  941. var res = await apiAuthProvider.postData('/api/messages', null, data);
  942. if (res != null) {
  943. int index = forumData.indexWhere((element) => element['uniqueId'] == data['uniqueId']);
  944. forumData[index]['read'] = false;
  945. } else {
  946. int index = forumData.indexWhere((element) => element['uniqueId'] == data['uniqueId']);
  947. forumData[index]['readStatus'] = 'FAILED';
  948. }
  949. setState(() {});
  950. }
  951. // tampilan list chat forum yg sebelah kanan
  952. Widget forumList(){
  953. return activeForumId == '' ? Center(child: Text("noMessageText".tr())) : Column(
  954. children: [
  955. Container(
  956. width: double.infinity, padding: EdgeInsets.all(20),
  957. child: Text(forumHeader, style: TextStyle(color: textColor, fontSize: 16)),
  958. ),
  959. divider(),
  960. Expanded(
  961. child: forumData.isEmpty && !isAfterLoad ? loadingTemplateNoVoid() : Align(
  962. alignment: Alignment.topCenter,
  963. child: SingleChildScrollView(
  964. padding: const EdgeInsets.fromLTRB(10, 10, 10, 7),
  965. controller: forumScrollController,
  966. reverse: true,
  967. child: Column(
  968. children: List.generate(forumData.length, (i) {
  969. bool hideDate = i == 0 ? false : checkDate(forumData[i]['datetime'], forumData[i - 1]['datetime']);
  970. bool isNip = i == 0 ? true : !hideDate ? true : forumData[i]['from']['user'] == forumData[i - 1]['from']['user'] ? false : true;
  971. return Column(
  972. children: [
  973. !hideDate ? Container(margin: EdgeInsets.only(bottom: 5), child: bubble_chat(Text(convertDate(forumData[i]['datetime'], context.locale.toString()), textAlign: TextAlign.center, style: TextStyle(fontSize: 12)), null, false, false)) : Container(),
  974. Builder(builder: (context) {
  975. var isMe = username == forumData[i]['from']['user'] && forumData[i]['senderType'] == 'INFORMANT';
  976. var isImage = forumData[i]['imageUrl'] != null && forumData[i]['imageUrl'] != '' && forumData[i]['readStatus'] != 'DELETED';
  977. var isTranslate = U.autoTranslate() && !isMe && forumData[i]['msg'] != forumData[i]['translate'];
  978. var widget = Row(
  979. mainAxisSize: MainAxisSize.min,
  980. children: [
  981. forumData[i]['readStatus'] == 'FAILED' ? Padding(
  982. padding: const EdgeInsets.only(right: 15),
  983. child: PopupMenuButton<int>(
  984. itemBuilder: (context) => [
  985. PopupMenuItem(value: 1, height: 40, child: Text("delete".tr(), style: TextStyle(fontSize: 14))),
  986. PopupMenuItem(value: 2, height: 40, child: Text("resend".tr(), style: TextStyle(fontSize: 14))),
  987. ],
  988. offset: Offset(MediaQuery.of(context).size.width, 0),
  989. onSelected: (value) {
  990. if (value == 1) {
  991. forumData.removeWhere((item) => item['uniqueId'] == forumData[i]['uniqueId']);
  992. } else if (value == 2) {
  993. var text = forumData[i]['msg'];
  994. var images = forumData[i]['imageUrl'];
  995. forumData.removeWhere((item) => item['uniqueId'] == forumData[i]['uniqueId']);
  996. var uuid = Uuid().v1().replaceAll('-', '');
  997. var data = {
  998. "uniqueId": uuid,
  999. "userId": username,
  1000. "recipientId": "#forum",
  1001. "senderType": "INFORMANT",
  1002. "text": text,
  1003. "chatId": idChat,
  1004. "images": images
  1005. };
  1006. sendMessage(data);
  1007. }
  1008. },
  1009. child: Icon(Icons.refresh, color: Colors.red),
  1010. ),
  1011. ) : Container(),
  1012. Flexible(
  1013. child: Column(
  1014. crossAxisAlignment: CrossAxisAlignment.start,
  1015. children: <Widget>[
  1016. !isMe ? Padding(
  1017. padding: const EdgeInsets.only(bottom: 6.0,),
  1018. child: Text(
  1019. forumData[i]['from']['name'],
  1020. style: TextStyle(color: Color(U.getColor(forumData[i]['from']['user'])), fontSize: 12.0),
  1021. textAlign: TextAlign.start,
  1022. ),
  1023. ) : SizedBox(),
  1024. isImage ? Padding(
  1025. padding: EdgeInsets.only(bottom: 2),
  1026. child: GestureDetector(
  1027. child: Container(
  1028. margin: const EdgeInsets.only(bottom: 5),
  1029. child: Builder(builder: (context) {
  1030. return Image.network(forumData[i]['imageUrl']);
  1031. }),
  1032. ),
  1033. onTap: () => navigateTo(context, PhotoPreview('forum'.tr(), forumData[i]['imageUrl'], true)),
  1034. ),
  1035. ) : Container(width: 1),
  1036. forumData[i]['readStatus'] == 'DELETED' ? Text('deletedMessage'.tr(), style: TextStyle(fontSize: 14, color: Colors.white70, fontStyle: FontStyle.italic)) : forumData[i]['msg'] != null && forumData[i]['msg'] != '' ? SelectableAutoLinkText(
  1037. forumData[i]['msg'],
  1038. style: TextStyle(fontSize: 14, color: Colors.black),
  1039. linkStyle: TextStyle(color: Colors.blueAccent),
  1040. highlightedLinkStyle: TextStyle(color: Colors.blueAccent, backgroundColor: Colors.blueAccent.withAlpha(0x33)),
  1041. onTap: (link) async{
  1042. if (await canLaunchUrl(Uri.parse(link))) {
  1043. await launchUrl(Uri.parse(link));
  1044. }
  1045. },
  1046. onLongPress: (link) {
  1047. Clipboard.setData(new ClipboardData(text: link)).then((value){
  1048. showSuccess('link_copied'.tr(), context);
  1049. });
  1050. },
  1051. textAlign: TextAlign.start,
  1052. ) : Container(width: 1),
  1053. isTranslate ? Container(
  1054. margin: EdgeInsets.only(top: 2), decoration: BoxDecoration(border: Border(top: BorderSide(color: Colors.black.withValues(alpha: 0.2)))),
  1055. child: Text(forumData[i]['translate']!=''?'(${forumData[i]['translate']})':'...', style: TextStyle(fontSize: 14, color: Colors.black.withValues(alpha: 0.65), fontStyle: FontStyle.italic)),
  1056. ) : Container(),
  1057. ],
  1058. ),
  1059. )
  1060. ],
  1061. );
  1062. var timeBubble = RichText(
  1063. text: TextSpan(children: [
  1064. TextSpan(text: DateFormat('HH:mm').format(DateTime.parse(forumData[i]['datetime'])), style: TextStyle(fontSize: 12, color: Colors.black38)),
  1065. WidgetSpan(
  1066. child: Padding(
  1067. padding: const EdgeInsets.only(left: 3),
  1068. child: isMe ? Icon(
  1069. forumData[i]['read'] == null ? Icons.done : Icons.done_all,
  1070. color: forumData[i]['read'] != null && forumData[i]['read'] ? Colors.green : Colors.black38,
  1071. size: 15,
  1072. ) : SizedBox()
  1073. )
  1074. ),
  1075. ]),
  1076. );
  1077. var expander = Expanded(
  1078. flex: 8,
  1079. child: Column(
  1080. crossAxisAlignment: isMe ? CrossAxisAlignment.end : CrossAxisAlignment.start,
  1081. children: [
  1082. bubble_chat(widget, timeBubble, isNip, isMe),
  1083. SizedBox(height: 5),
  1084. ],
  1085. )
  1086. );
  1087. return isMe ? Row(
  1088. children: [
  1089. Expanded(
  1090. flex: 2,
  1091. child: Container(),
  1092. ),
  1093. expander,
  1094. ],
  1095. ) : Row(
  1096. children: [
  1097. expander,
  1098. Expanded(
  1099. flex: 2,
  1100. child: Container(),
  1101. ),
  1102. ],
  1103. );
  1104. })
  1105. ],
  1106. );
  1107. }),
  1108. ),
  1109. ),
  1110. ),
  1111. ),
  1112. openChat ? divider() : Container(),
  1113. openChat ? Container(
  1114. alignment: Alignment.bottomCenter,
  1115. // color: Colors.white,
  1116. padding: EdgeInsets.all(12),
  1117. child: Row(
  1118. children: [
  1119. Expanded(
  1120. child: SizedBox(
  1121. width: double.infinity,
  1122. child: TextField(
  1123. controller: controllerForum,
  1124. maxLength: 256,
  1125. style: const TextStyle(fontSize: 14, color: Colors.black),
  1126. keyboardType: TextInputType.multiline,
  1127. minLines: 1,
  1128. maxLines: 5,
  1129. decoration: InputDecoration(
  1130. counterText: '',
  1131. hintText: 'writeMessage'.tr()+'..',
  1132. hintStyle: TextStyle(color: textColor.withValues(alpha: 0.5), fontSize: 14),
  1133. filled: true,
  1134. fillColor: backgroundColor,
  1135. hoverColor: Colors.black.withValues(alpha: 0.1),
  1136. contentPadding: EdgeInsets.symmetric(vertical: 17, horizontal: 20),
  1137. suffixIcon: GestureDetector(
  1138. child: Padding(
  1139. padding: EdgeInsets.only(left: 13, right: 13),
  1140. child: U.iconsax('paperclip-2', color: textColor, size: 24),
  1141. ),
  1142. onTap: ()async{
  1143. var uuid = Uuid().v1().replaceAll('-', '');
  1144. var data = {
  1145. "uniqueId": uuid,
  1146. "userId": username,
  1147. "recipientId": "#forum",
  1148. "senderType": "INFORMANT",
  1149. "text": controllerForum.text.trim(),
  1150. "chatId": forumChatId,
  1151. "images": null
  1152. };
  1153. await getImage(ImageSource.gallery).then((value) {
  1154. if (_image != null) {
  1155. navigateTo(context, PhotoChat(data, ImageSource.gallery, false, _image!)).then((value) {
  1156. if (value != null) {
  1157. sendMessage(value);
  1158. }
  1159. });
  1160. }
  1161. });
  1162. },
  1163. ),
  1164. border: InputBorder.none,
  1165. enabledBorder: OutlineInputBorder(borderRadius: BorderRadius.circular(borderRadius), borderSide: BorderSide(color: textColor)),
  1166. focusedBorder: OutlineInputBorder(borderRadius: BorderRadius.circular(borderRadius), borderSide: BorderSide(color: primaryColor)),
  1167. isDense: true
  1168. ),
  1169. onChanged: (val) {
  1170. setState(() {
  1171. if (val.trim() != '') {
  1172. final tp = TextPainter(text: TextSpan(text: val), textDirection: ui.TextDirection.ltr);
  1173. tp.layout(maxWidth: 400);
  1174. final line = tp.computeLineMetrics().length;
  1175. if (line == 1) {
  1176. borderRadius = 50.0;
  1177. } else {
  1178. borderRadius = 20.0;
  1179. }
  1180. isSend = true;
  1181. } else {
  1182. isSend = false;
  1183. }
  1184. });
  1185. },
  1186. ),
  1187. ),
  1188. ),
  1189. SizedBox(width: 12),
  1190. GestureDetector(
  1191. child: Container(
  1192. padding: EdgeInsets.all(12),
  1193. child: Image.asset('assets/image/icon/Send.png', width: 20, height: 20),
  1194. decoration: BoxDecoration(color: primaryColor, borderRadius: BorderRadius.all(Radius.circular(50))),
  1195. ),
  1196. onTap: (){
  1197. if(controllerForum.text.isNotEmpty){
  1198. var uuid = Uuid().v1().replaceAll('-', '');
  1199. var data = {
  1200. "uniqueId": uuid,
  1201. "userId": username,
  1202. "recipientId": "#forum",
  1203. "senderType": "INFORMANT",
  1204. "text": controllerForum.text.trim(),
  1205. "chatId": forumChatId,
  1206. "images": null
  1207. };
  1208. sendMessage(data);
  1209. }
  1210. },
  1211. )
  1212. ],
  1213. ),
  1214. ) : U.getInternetStatus() ? Center(child: Padding(
  1215. padding: EdgeInsets.symmetric(vertical: 20),
  1216. child: bubble_chat(Text('chatClosed'.tr(), textAlign: TextAlign.center, style: TextStyle(fontSize: 12)), null, false, false),
  1217. )):Container()
  1218. ],
  1219. );
  1220. }
  1221. return Scaffold(
  1222. backgroundColor: backgroundColor,
  1223. appBar: PreferredSize(preferredSize: Size.fromHeight(0), child: AppBar(elevation: 0, backgroundColor: primaryColor)),
  1224. body: Column(
  1225. children: [
  1226. Container(
  1227. padding: EdgeInsets.symmetric(vertical: 25, horizontal: 100),
  1228. child: Row(
  1229. mainAxisAlignment: MainAxisAlignment.spaceBetween,
  1230. children: [
  1231. Text('message'.tr(), style: TextStyle(color: textColor, fontSize: 17, fontWeight: FontWeight.w500), overflow: TextOverflow.ellipsis),
  1232. GestureDetector(
  1233. child: Text('buttonBack'.tr(), style: TextStyle(color: primaryColor, fontSize: 14)),
  1234. onTap: (){
  1235. deleteCollection();
  1236. navigateBack(context);
  1237. },
  1238. )
  1239. ],
  1240. ),
  1241. ),
  1242. divider(),
  1243. FutureBuilder(future: U.isCompatibleWith(VersionKey.multiBahasa), builder: (context, snapshot){
  1244. if (snapshot.connectionState != ConnectionState.done) {
  1245. return CircularProgressIndicator(); // atau loading spinner
  1246. }
  1247. if (snapshot.hasError) {
  1248. print("Error in isCompatibleWith: ${snapshot.error}");
  1249. return SizedBox(); // atau widget fallback
  1250. }
  1251. if (snapshot.data == true) {
  1252. return tabForum();
  1253. } else {
  1254. return SizedBox();
  1255. }
  1256. }),
  1257. Expanded(
  1258. child: Container(
  1259. width: double.infinity, height: double.infinity,
  1260. padding: EdgeInsets.symmetric(vertical: 12, horizontal: 100),
  1261. child: Row(
  1262. children: [
  1263. Expanded(
  1264. child: Stack(
  1265. children: [
  1266. Container(
  1267. width: double.infinity, height: double.infinity,
  1268. decoration: BoxDecoration(color: Colors.white, border: Border.all(color: textColor.withValues(alpha: 0.15)), borderRadius: BorderRadius.all(Radius.circular(12))),
  1269. child: EasyRefresh(
  1270. header: MaterialHeader(clamping: true, color: primaryColor),
  1271. onRefresh: () => messageFunc.onRefresh(context),
  1272. child: !Provider.of<MessageModule>(context).firstLoad() ? loadingTemplateNoVoid()
  1273. : Provider.of<MessageModule>(context).activeTab() == 0 ? webChat(context) : forum()
  1274. ),
  1275. ),
  1276. Provider.of<MessageModule>(context).user().isNotEmpty && Provider.of<MessageModule>(context).user()['canSendMessage'] ? Align(
  1277. alignment: Alignment.bottomRight,
  1278. child: GestureDetector(
  1279. child: Container(
  1280. margin: EdgeInsets.only(right: 20, bottom: 20),
  1281. width: 50, height: 50, alignment: Alignment.center,
  1282. decoration: BoxDecoration(
  1283. color: primaryColor,
  1284. borderRadius: BorderRadius.all(Radius.circular(50))
  1285. ),
  1286. child: U.iconsax('message', color: Colors.white),
  1287. ),
  1288. onTap: ()=>messageFunc.createMessage(context),
  1289. ),
  1290. ) : Container()
  1291. ],
  1292. ),
  1293. ),
  1294. SizedBox(width: 24),
  1295. Expanded(
  1296. child: Container(
  1297. decoration: BoxDecoration(color: Colors.white, border: Border.all(color: textColor.withValues(alpha: 0.15)), borderRadius: BorderRadius.all(Radius.circular(12))),
  1298. child: Provider.of<MessageModule>(context).activeTab() == 0 ? chatList() : forumList(),
  1299. )
  1300. )
  1301. ],
  1302. ),
  1303. ),
  1304. )
  1305. ],
  1306. ),
  1307. );
  1308. }
  1309. getImageName(imageUrl) {
  1310. var imgSplit = imageUrl.toString().split('/');
  1311. return imgSplit[imgSplit.length - 1];
  1312. }
  1313. Widget bubble_chat(Widget child, Widget? time, bool isNip, bool isMe) {
  1314. bool isDate = false;
  1315. if (time == null) {
  1316. isDate = true;
  1317. time = Container();
  1318. }
  1319. Color color = isMe ? primaryColor.withValues(alpha: 0.3) : isDate ? Color(0xffD5F5FF) : Color(0xffECECEC);
  1320. var clipPath = ClipPath(
  1321. clipper: isMe ? MyClipper(isNip) : YourClipper(isNip),
  1322. child: ConstrainedBox(
  1323. constraints: BoxConstraints(minWidth: 50),
  1324. child: Container(
  1325. decoration: BoxDecoration(
  1326. color: color,
  1327. ),
  1328. child: Padding(padding: EdgeInsets.all(8),
  1329. child: isDate ? child : Stack(
  1330. children: [
  1331. Padding(
  1332. padding: EdgeInsets.only(
  1333. bottom: 15,
  1334. right: isNip && isMe ? 6 : 0,
  1335. left: isNip && !isMe ? 6 : 0,
  1336. ),
  1337. child: child,
  1338. ),
  1339. Positioned(
  1340. bottom: 2,
  1341. right: 2,
  1342. child: time,
  1343. ),
  1344. // time
  1345. ],
  1346. ),
  1347. ),
  1348. ),
  1349. ),
  1350. );
  1351. return Padding(
  1352. padding: EdgeInsets.only(
  1353. right: isNip && isMe ? 0 : 6,
  1354. left: isNip && !isMe ? 0 : 6
  1355. ),
  1356. child: clipPath
  1357. );
  1358. }
  1359. bool checkDate(date1, date2) {
  1360. final dateToCheck1 = DateTime(DateTime.parse(date1).year, DateTime.parse(date1).month, DateTime.parse(date1).day);
  1361. final dateToCheck2 = DateTime(DateTime.parse(date2).year, DateTime.parse(date2).month, DateTime.parse(date2).day);
  1362. return dateToCheck1 == dateToCheck2 ? true : false;
  1363. }
  1364. getImage(ImageSource gallery) {}
  1365. }