12 Commits 653ccfc318 ... 617e268148

Autore SHA1 Messaggio Data
  Yulian 617e268148 fix:forum blank 1 mese fa
  Yulian 77a072d50a fix: mekanisme load server version 2 mesi fa
  Yulian c188da9f49 fix: contact center hilang setelah refresh web 2 mesi fa
  Yulian ba81791b3a update version & pubscpec 2 mesi fa
  Yulian c06ab20812 fix: temuan qc 2 mesi fa
  Yulian e204d648c4 update lang di web done 3 mesi fa
  Yulian a15f3b8327 update lang di mobile done 3 mesi fa
  Yulian 1141dcb855 update multi lang di login 4 mesi fa
  Yulian b827178a1e update lang & tombol wa: tahap 1 4 mesi fa
  Masarifyuli 38a030cc4d fix: safe area some arabic 4 mesi fa
  Masarifyuli 64cb3a0200 fix: handle loginme 5 mesi fa
  Masarifyuli 39f6913713 update index terbaru 5 mesi fa
70 ha cambiato i file con 5552 aggiunte e 1675 eliminazioni
  1. 25 0
      assets/iconsax/ar.svg
  2. 5 0
      assets/iconsax/de.svg
  3. 7 0
      assets/iconsax/en.svg
  4. 5 0
      assets/iconsax/fr.svg
  5. 25 0
      assets/iconsax/hi.svg
  6. 4 0
      assets/iconsax/id.svg
  7. 11 0
      assets/iconsax/ja.svg
  8. 24 0
      assets/iconsax/ko.svg
  9. 5 0
      assets/iconsax/nl.svg
  10. 2 0
      assets/iconsax/phone-circle.svg
  11. 25 0
      assets/iconsax/phone-number.svg
  12. 11 0
      assets/iconsax/zh.svg
  13. BIN
      assets/image/icon/call.png
  14. BIN
      assets/image/icon/contact_us.png
  15. BIN
      assets/image/icon/online.png
  16. 337 0
      assets/lang/ar.json
  17. 337 0
      assets/lang/de.json
  18. 9 3
      assets/lang/en.json
  19. 337 0
      assets/lang/fr.json
  20. 337 0
      assets/lang/hi.json
  21. 9 4
      assets/lang/id.json
  22. 9 3
      assets/lang/ja.json
  23. 9 3
      assets/lang/ko.json
  24. 337 0
      assets/lang/nl.json
  25. 9 3
      assets/lang/zh.json
  26. 15 4
      lib/app_router.dart
  27. 76 40
      lib/app_router.gr.dart
  28. 11 7
      lib/main.dart
  29. 10 8
      lib/src/api/api_auth_provider.dart
  30. 6 1
      lib/src/api/jwt_token.dart
  31. 247 185
      lib/src/layouts/auth/login.dart
  32. 110 103
      lib/src/layouts/auth/qr.dart
  33. 37 35
      lib/src/layouts/components/camera.dart
  34. 27 9
      lib/src/layouts/components/template.dart
  35. 25 7
      lib/src/layouts/functions/account.dart
  36. 2 0
      lib/src/layouts/functions/history.dart
  37. 26 0
      lib/src/layouts/functions/home.dart
  38. 35 2
      lib/src/layouts/functions/message.dart
  39. 5 3
      lib/src/layouts/functions/request.dart
  40. 321 0
      lib/src/layouts/landing/landing_page.dart
  41. 8 3
      lib/src/layouts/mobile/app_mobile.dart
  42. 38 36
      lib/src/layouts/mobile/history_detail.dart
  43. 206 175
      lib/src/layouts/mobile/history_forum.dart
  44. 64 44
      lib/src/layouts/mobile/menu_account.dart
  45. 13 13
      lib/src/layouts/mobile/menu_history.dart
  46. 247 192
      lib/src/layouts/mobile/menu_home.dart
  47. 1 1
      lib/src/layouts/mobile/message_broadcast.dart
  48. 1 1
      lib/src/layouts/mobile/message_chat.dart
  49. 250 61
      lib/src/layouts/mobile/message_list.dart
  50. 1 1
      lib/src/layouts/mobile/message_select.dart
  51. 37 35
      lib/src/layouts/mobile/password.dart
  52. 74 70
      lib/src/layouts/mobile/request_create.dart
  53. 3 3
      lib/src/layouts/mobile/request_select.dart
  54. 14 10
      lib/src/layouts/mobile/request_success.dart
  55. 1 2
      lib/src/layouts/web/history_detail.dart
  56. 1 1
      lib/src/layouts/web/history_detail_pending.dart
  57. 29 26
      lib/src/layouts/web/history_forum.dart
  58. 64 46
      lib/src/layouts/web/menu_account.dart
  59. 5 5
      lib/src/layouts/web/menu_history.dart
  60. 45 1
      lib/src/layouts/web/menu_home.dart
  61. 1159 276
      lib/src/layouts/web/message_list.dart
  62. 1 1
      lib/src/layouts/web/request_select.dart
  63. 2 2
      lib/src/layouts/web/request_success.dart
  64. 13 0
      lib/src/utils/C.dart
  65. 83 3
      lib/src/utils/U.dart
  66. 32 0
      lib/src/utils/provider.dart
  67. 51 43
      pubspec.lock
  68. 5 4
      pubspec.yaml
  69. 272 200
      web/index.html
  70. BIN
      web/telnow.zip

File diff suppressed because it is too large
+ 25 - 0
assets/iconsax/ar.svg


+ 5 - 0
assets/iconsax/de.svg

@@ -0,0 +1,5 @@
+<svg xmlns="http://www.w3.org/2000/svg" id="flag-icons-de" viewBox="0 0 640 480">
+  <path fill="#fc0" d="M0 320h640v160H0z"/>
+  <path fill="#000001" d="M0 0h640v160H0z"/>
+  <path fill="red" d="M0 160h640v160H0z"/>
+</svg>

+ 7 - 0
assets/iconsax/en.svg

@@ -0,0 +1,7 @@
+<svg xmlns="http://www.w3.org/2000/svg" id="flag-icons-gb" viewBox="0 0 640 480">
+  <path fill="#012169" d="M0 0h640v480H0z"/>
+  <path fill="#FFF" d="m75 0 244 181L562 0h78v62L400 241l240 178v61h-80L320 301 81 480H0v-60l239-178L0 64V0z"/>
+  <path fill="#C8102E" d="m424 281 216 159v40L369 281zm-184 20 6 35L54 480H0zM640 0v3L391 191l2-44L590 0zM0 0l239 176h-60L0 42z"/>
+  <path fill="#FFF" d="M241 0v480h160V0zM0 160v160h640V160z"/>
+  <path fill="#C8102E" d="M0 193v96h640v-96zM273 0v480h96V0z"/>
+</svg>

+ 5 - 0
assets/iconsax/fr.svg

@@ -0,0 +1,5 @@
+<svg xmlns="http://www.w3.org/2000/svg" id="flag-icons-fr" viewBox="0 0 640 480">
+  <path fill="#fff" d="M0 0h640v480H0z"/>
+  <path fill="#000091" d="M0 0h213.3v480H0z"/>
+  <path fill="#e1000f" d="M426.7 0H640v480H426.7z"/>
+</svg>

+ 25 - 0
assets/iconsax/hi.svg

@@ -0,0 +1,25 @@
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" id="flag-icons-in" viewBox="0 0 640 480">
+  <path fill="#f93" d="M0 0h640v160H0z"/>
+  <path fill="#fff" d="M0 160h640v160H0z"/>
+  <path fill="#128807" d="M0 320h640v160H0z"/>
+  <g transform="matrix(3.2 0 0 3.2 320 240)">
+    <circle r="20" fill="#008"/>
+    <circle r="17.5" fill="#fff"/>
+    <circle r="3.5" fill="#008"/>
+    <g id="in-d">
+      <g id="in-c">
+        <g id="in-b">
+          <g id="in-a" fill="#008">
+            <circle r=".9" transform="rotate(7.5 -8.8 133.5)"/>
+            <path d="M0 17.5.6 7 0 2l-.6 5z"/>
+          </g>
+          <use xlink:href="#in-a" width="100%" height="100%" transform="rotate(15)"/>
+        </g>
+        <use xlink:href="#in-b" width="100%" height="100%" transform="rotate(30)"/>
+      </g>
+      <use xlink:href="#in-c" width="100%" height="100%" transform="rotate(60)"/>
+    </g>
+    <use xlink:href="#in-d" width="100%" height="100%" transform="rotate(120)"/>
+    <use xlink:href="#in-d" width="100%" height="100%" transform="rotate(-120)"/>
+  </g>
+</svg>

+ 4 - 0
assets/iconsax/id.svg

@@ -0,0 +1,4 @@
+<svg xmlns="http://www.w3.org/2000/svg" id="flag-icons-id" viewBox="0 0 640 480">
+  <path fill="#e70011" d="M0 0h640v240H0Z"/>
+  <path fill="#fff" d="M0 240h640v240H0Z"/>
+</svg>

+ 11 - 0
assets/iconsax/ja.svg

@@ -0,0 +1,11 @@
+<svg xmlns="http://www.w3.org/2000/svg" id="flag-icons-jp" viewBox="0 0 640 480">
+  <defs>
+    <clipPath id="jp-a">
+      <path fill-opacity=".7" d="M-88 32h640v480H-88z"/>
+    </clipPath>
+  </defs>
+  <g fill-rule="evenodd" stroke-width="1pt" clip-path="url(#jp-a)" transform="translate(88 -32)">
+    <path fill="#fff" d="M-128 32h720v480h-720z"/>
+    <circle cx="523.1" cy="344.1" r="194.9" fill="#bc002d" transform="translate(-168.4 8.6)scale(.76554)"/>
+  </g>
+</svg>

+ 24 - 0
assets/iconsax/ko.svg

@@ -0,0 +1,24 @@
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" id="flag-icons-kr" viewBox="0 0 640 480">
+  <defs>
+    <clipPath id="kr-a">
+      <path fill-opacity=".7" d="M-95.8-.4h682.7v512H-95.8z"/>
+    </clipPath>
+  </defs>
+  <g fill-rule="evenodd" clip-path="url(#kr-a)" transform="translate(89.8 .4)scale(.9375)">
+    <path fill="#fff" d="M-95.8-.4H587v512H-95.8Z"/>
+    <g transform="rotate(-56.3 361.6 -101.3)scale(10.66667)">
+      <g id="kr-c">
+        <path id="kr-b" fill="#000001" d="M-6-26H6v2H-6Zm0 3H6v2H-6Zm0 3H6v2H-6Z"/>
+        <use xlink:href="#kr-b" width="100%" height="100%" y="44"/>
+      </g>
+      <path stroke="#fff" d="M0 17v10"/>
+      <path fill="#cd2e3a" d="M0-12a12 12 0 0 1 0 24Z"/>
+      <path fill="#0047a0" d="M0-12a12 12 0 0 0 0 24A6 6 0 0 0 0 0Z"/>
+      <circle cy="-6" r="6" fill="#cd2e3a"/>
+    </g>
+    <g transform="rotate(-123.7 191.2 62.2)scale(10.66667)">
+      <use xlink:href="#kr-c" width="100%" height="100%"/>
+      <path stroke="#fff" d="M0-23.5v3M0 17v3.5m0 3v3"/>
+    </g>
+  </g>
+</svg>

+ 5 - 0
assets/iconsax/nl.svg

@@ -0,0 +1,5 @@
+<svg xmlns="http://www.w3.org/2000/svg" id="flag-icons-nl" viewBox="0 0 640 480">
+  <path fill="#ae1c28" d="M0 0h640v160H0z"/>
+  <path fill="#fff" d="M0 160h640v160H0z"/>
+  <path fill="#21468b" d="M0 320h640v160H0z"/>
+</svg>

File diff suppressed because it is too large
+ 2 - 0
assets/iconsax/phone-circle.svg


+ 25 - 0
assets/iconsax/phone-number.svg

@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
+<!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
+<svg height="800px" width="800px" version="1.1" id="_x32_" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" 
+	 viewBox="0 0 512 512"  xml:space="preserve">
+<style type="text/css">
+	.st0{fill:#000000;}
+</style>
+<g>
+	<path class="st0" d="M255.998,0.002C114.606,0.012,0.01,114.604,0,256c0.01,141.406,114.65,255.328,255.926,255.998h0.334
+		l0.297-0.009c27.124,0.038,49.507-8.527,64.961-22.594c15.468-14.01,23.727-33.254,23.708-52.736
+		c0.02-9.148-1.914-18.306-5.521-27.024c6.086-3.464,10.143-6.612,11.301-7.444c4.152-2.957,16-18.766,7.693-31.79
+		c-8.344-13.014-38.042-42.678-46.152-47.702c-8.086-5.015-21.598-0.124-28.105,9.426c-6.526,9.55-11.674,6.689-11.674,6.689
+		s-18.516-14.957-44.124-66.621c-25.607-51.694-26.263-75.454-26.263-75.454s0.833-5.847,12.388-5.263
+		c11.53,0.621,23.598-7.168,24.516-16.66c0.928-9.464-4.698-51.091-10-65.598c-5.316-14.516-25.062-14.65-29.928-13.138
+		c-4.89,1.502-55.033,13.712-59.014,66.21c-3.966,52.506,9.565,100.18,28.943,139.309c19.387,39.119,49.128,78.765,93.3,107.406
+		c17.89,11.598,35.058,13.1,49.493,10.67c2.483,5.54,3.718,11.291,3.746,16.985c-0.028,11.292-4.621,22.354-14.066,30.966
+		c-9.469,8.564-24.071,14.928-45.2,14.967l-0.516,0.009C130.797,481.96,29.387,381.09,29.397,256
+		c0.01-62.621,25.339-119.186,66.367-160.237c41.053-41.023,97.612-66.354,160.234-66.364c62.621,0.01,119.181,25.34,160.232,66.364
+		c41.033,41.052,66.354,97.606,66.373,160.237c-0.01,38.67-9.666,74.966-26.698,106.784c-9.531,17.837-21.397,34.23-35.177,48.812
+		c-5.569,5.905-5.301,15.206,0.594,20.776c5.894,5.578,15.205,5.32,20.784-0.584c15.54-16.46,28.937-34.976,39.712-55.139
+		C501.071,340.717,512,299.589,512,256C511.98,114.604,397.389,0.012,255.998,0.002z"/>
+</g>
+</svg>

+ 11 - 0
assets/iconsax/zh.svg

@@ -0,0 +1,11 @@
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" id="flag-icons-cn" viewBox="0 0 640 480">
+  <defs>
+    <path id="cn-a" fill="#ff0" d="M-.6.8 0-1 .6.8-1-.3h2z"/>
+  </defs>
+  <path fill="#ee1c25" d="M0 0h640v480H0z"/>
+  <use xlink:href="#cn-a" width="30" height="20" transform="matrix(71.9991 0 0 72 120 120)"/>
+  <use xlink:href="#cn-a" width="30" height="20" transform="matrix(-12.33562 -20.5871 20.58684 -12.33577 240.3 48)"/>
+  <use xlink:href="#cn-a" width="30" height="20" transform="matrix(-3.38573 -23.75998 23.75968 -3.38578 288 95.8)"/>
+  <use xlink:href="#cn-a" width="30" height="20" transform="matrix(6.5991 -23.0749 23.0746 6.59919 288 168)"/>
+  <use xlink:href="#cn-a" width="30" height="20" transform="matrix(14.9991 -18.73557 18.73533 14.99929 240 216)"/>
+</svg>

BIN
assets/image/icon/call.png


BIN
assets/image/icon/contact_us.png


BIN
assets/image/icon/online.png


+ 337 - 0
assets/lang/ar.json

@@ -0,0 +1,337 @@
+{
+  "account": "الحساب",
+  "addNote": "أضف ملاحظة",
+  "addNoteCancel": "أضف ملاحظة الإلغاء",
+  "all": "الكل",
+  "anyNote": "هل هناك ملاحظة للموظف؟",
+  "appointment": "موعد",
+  "attach": "أرفق صورة",
+  "buttonCancel": "إلغاء المهمة",
+  "buttonLogin": "تسجيل الدخول",
+  "buttonNo": "لا",
+  "buttonResubmit": "إعادة الإرسال",
+  "buttonScan": "مسح رمز QR",
+  "buttonSendEmergency": "إرسال طارئ",
+  "buttonSendComplaint": "إرسال شكوى",
+  "buttonSendRequest": "إرسال طلب",
+  "buttonYes": "نعم",
+  "buttonSure": "نعم، بالتأكيد",
+  "buttonBack": "رجوع",
+  "canceledBy": "تم الإلغاء بواسطة",
+  "category": "الفئة",
+  "scope": "النطاق",
+  "changeScope": "تغيير النطاق",
+  "chatClosed": "تم إغلاق هذه المحادثة.",
+  "chooseService": "اختر الخدمة التي تريدها",
+  "complaintText": "ما هي المشكلة؟ دع الخبراء يتعاملون معها.",
+  "complaint": "شكوى",
+  "defaultLocation": "الموقع الافتراضي",
+  "detail": "تفاصيل",
+  "disatisfied": "غير راضٍ",
+  "emergencyText": "كل الأمور العاجلة التي يجب التعامل معها فوراً.",
+  "emergency": "طارئ",
+  "forum": "منتدى",
+  "frequentlyText": "الطلبات المتكررة تم جمعها هنا.",
+  "frequently": "مطلوب بشكل متكرر",
+  "frequentlyTitle": "مطلوب بشكل متكرر",
+  "frequentlyTitleText": "البلاغات المتكررة",
+  "history": "السجل",
+  "lessSatisfied": "راضٍ بشكل أقل",
+  "location": "الموقع",
+  "logout": "تسجيل الخروج",
+  "menuAccount": "الحساب",
+  "menuHistory": "السجل",
+  "menuService": "الخدمة",
+  "menuHome": "الرئيسية",
+  "message": "رسالة",
+  "note": "ملاحظة",
+  "passwordEmpty": "كلمة المرور لا يمكن أن تكون فارغة",
+  "password": "كلمة المرور",
+  "phoneNumber": "رقم الهاتف",
+  "placeholderLocation": "اكتب الموقع",
+  "placeholderNote": "اكتب ملاحظة",
+  "rateMission": "قيّم هذه المهمة",
+  "reallyPleased": "راضٍ جداً",
+  "requestText2": "ما هو الطلب؟",
+  "requestDesc": "إذا كنت بحاجة إلى مساعدة، لا تتردد.",
+  "requestTitle": "ما نوع المساعدة التي تحتاجها؟",
+  "request": "طلب",
+  "satisfactionAsk": "هل أنت راضٍ؟",
+  "satisfied": "راضٍ",
+  "scanQr": "يرجى مسح رمز QR للبدء باستخدام التطبيق.",
+  "serialNumber": "الرقم التسلسلي",
+  "servantGroup": "مجموعة البلاغات",
+  "servant": "المنفذ",
+  "service": "الخدمة",
+  "specifyAnotherLocation": "أنا في موقع آخر",
+  "specifyLocation": "حدد الموقع",
+  "startDoing": "ابدأ التنفيذ",
+  "stateCancel": "إلغاء",
+  "stateCanceled": "تم الإلغاء",
+  "stateDone": "تم التنفيذ",
+  "stateFinish": "تم الانتهاء",
+  "stateQueue": "في الانتظار",
+  "stateRequested": "تم الطلب",
+  "state": "الحالة",
+  "sure": "هل أنت متأكد؟",
+  "switch": "تبديل",
+  "system": "النظام",
+  "textCancel": "إلغاء",
+  "textHistory": "البلاغات التي قمت بها",
+  "textLogout": "لن تتمكن من تلقي الإشعارات أو إنشاء البلاغات أو القيام بأنشطة أخرى. هل تريد الخروج؟",
+  "textResubmit": "ستقوم بإعادة إرسال هذا البلاغ إلى الموظف.",
+  "ticketNumber": "رقم التذكرة",
+  "timeline": "الجدول الزمني",
+  "today": "اليوم",
+  "unrated": "لم يتم تقييم هذه المهمة بعد",
+  "userEmpty": "اسم المستخدم لا يمكن أن يكون فارغاً",
+  "userId": "اسم المستخدم",
+  "version": "الإصدار",
+  "verySatisfied": "راضٍ جداً",
+  "whatComplain": "ما هي الشكوى؟",
+  "whatHappen": "ماذا حدث؟",
+  "whatNext": "ابقَ هادئاً. ما هي المعلومات التالية؟",
+  "whatProblem": "ما هي المشكلة؟",
+  "writeMessage": "اكتب رسالة",
+  "yesterday": "أمس",
+  "language": "اللغة",
+  "bahasa": "Indonesia",
+  "english": "English",
+  "japanese": "日本語",
+  "korean": "한국어",
+  "chinese": "中文",
+  "german": "Deutsch",
+  "french": "Français",
+  "hindi": "हिंदी",
+  "arabic": "العربية",
+  "dutch": "Nederlands",
+  "chooseLanguage": "اختر اللغة",
+  "rate": "التقييم",
+  "rated": "تم تقديم التقييم.",
+  "rateText": "أنت [satisfaction] بشأن هذه المهمة.",
+  "settingPassword": "تغيير كلمة المرور",
+  "oldPassword": "كلمة المرور القديمة",
+  "newPassword": "كلمة المرور الجديدة",
+  "confirmPassword": "تأكيد كلمة المرور الجديدة",
+  "buttonSave": "احفظ كلمة المرور الجديدة",
+  "settingPasswordText": "قم بإعداد كلمة المرور هنا",
+  "autoResponseText": "سيتم الرد على طلبك من قبل النظام قريباً.",
+  "reply": "رد",
+  "selectPicture": "اختر وسائط",
+  "fromGallery": "من المعرض",
+  "fromCamera": "التقاط صورة",
+  "sendPicture": "أرسل صورة",
+  "seeAttachment": "عرض المرفق",
+  "confirmCancel": "هل أنت متأكد من الإلغاء؟",
+  "selectDate": "اختر التاريخ",
+  "lengthMax": "الحد الأقصى لطول الملاحظة هو 128 حرفاً.",
+  "locRequired": "الموقع لا يمكن أن يكون فارغاً.",
+  "idRequired": "المعرف لا يمكن أن يكون فارغاً.",
+  "dateRequired": "التاريخ لا يمكن أن يكون فارغاً.",
+  "sendLater": "أرسل لاحقاً",
+  "thank": "شكراً لك",
+  "pleaseWait": "يرجى الانتظار",
+  "sendSuccess": "تم الإرسال بنجاح",
+  "followUp": "سيقوم الموظف بمتابعة الأمر قريباً.",
+  "okGot": "حسناً، فهمت",
+  "okWait": "حسناً، سأنتظر",
+  "errorConnection": "خطأ في الاتصال. حاول مرة أخرى لاحقاً.",
+  "noInternet": "أوه، لا يوجد اتصال بالإنترنت.",
+  "invalidLogin": "اسم المستخدم أو كلمة المرور غير صحيحة.",
+  "invalidPhone": "رقم الهاتف غير مسجل.",
+  "expAccount": "انتهت صلاحية الحساب.",
+  "errorServer": "حدث خطأ في الخادم. حاول مرة أخرى لاحقاً.",
+  "accessDenied": "تم رفض الوصول",
+  "licenseProblem": "لا يمكن استخدام التطبيق بسبب مشكلة في الترخيص.",
+  "invalidVersion": "إصدار التطبيق غير متوافق",
+  "sugestVersion": "استخدم التطبيق بالإصدار",
+  "currentVersion": "إصدار التطبيق المثبت هو",
+  "contactAdmin": "يرجى الاتصال بالمسؤول لمزيد من المعلومات.",
+  "pressAgain": "اضغط مرة أخرى للخروج.",
+  "notFound": "لم يتم العثور على البيانات.",
+  "notFound2": "ما تبحث عنه غير موجود هنا..!",
+  "noInternetTitle": "لا يوجد اتصال بالإنترنت.",
+  "noInternetDesc": "تحقق من اتصال الإنترنت لديك. سنكمل لاحقاً!",
+  "errorConnectTitle": "فشل الاتصال بالخادم.",
+  "errorConnectDesc": "اسأل المسؤول، يجب أن تكون قادراً على الاتصال بالخادم.",
+  "errorServerTitle": "هناك مشكلة في الخادم.",
+  "errorServerDesc": "لا داعي للذعر، نحن نعمل بجد لضمان استقرار الأمور.",
+  "invalidAccountTitle": "الحساب غير مسجل.",
+  "invalidAccountDesc": "أوه! لا يمكن التحقق من حسابك. يرجى سؤال المسؤول.",
+  "expAccountTitle": "انتهت صلاحية الحساب.",
+  "expAccountDesc": "لا يمكنك الدخول إلى التطبيق لأن حسابك منتهي الصلاحية. إذا كنت تعتقد أن هذا خطأ، اتصل بالمسؤول فوراً.",
+  "inProcess": "قيد المعالجة",
+  "policy": "سياسة الخصوصية",
+  "info": "معلومات إضافية",
+  "refresh": "تحديث",
+  "messageCamPermission": "تم رفض الوصول إلى الكاميرا. يرجى السماح لتطبيق TelMessenger بالوصول إلى الكاميرا من خلال إعدادات الجهاز.",
+  "messageInvalidCode": "الرمز غير صالح. يرجى الاتصال بالمسؤول.",
+  "messagePassChanged": "تم تغيير كلمة المرور بنجاح.",
+  "wrongOldPass": "كلمة المرور القديمة غير صحيحة.",
+  "checkNewPass": "يرجى التحقق من كلمة المرور الجديدة.",
+  "cannotEmpty": "لا يمكن أن تكون البيانات فارغة.",
+  "deletedMessage": "تم حذف هذه الرسالة.",
+  "resend": "إعادة الإرسال",
+  "delete": "حذف",
+  "alreadyUsePass": "لا يمكن استخدام كلمة المرور هذه، جرب كلمة أخرى.",
+  "notFoundImg": "لم يتم العثور على الصورة",
+  "notFoundWorktime": "لم يتم العثور على وقت العمل. يرجى الاتصال بالمسؤول.",
+  "seeAll": "عرض الكل",
+  "quickAction": "إجراء سريع",
+  "quickDesc": "لتسريع طلبك.",
+  "times": " مرات",
+  "search": "بحث",
+  "searchAsk": "ابحث...",
+  "helpHint": "جرب كتابة مشكلتك...",
+  "inProgress": " طلبك قيد المعالجة.",
+  "currentActiveText": "بلاغك الذي لا يزال قيد المعالجة",
+  "photo": "صورة",
+  "selectRecipient": "اختر المستلم",
+  "composeMessage": "إنشاء رسالة",
+  "to": "إلى",
+  "addAttachment": "إضافة مرفق",
+  "editAttachment": "تعديل المرفق",
+  "unsupportedFile": "تنسيق الملف غير مدعوم",
+  "pdfFile": "ملف PDF",
+  "other": "أخرى",
+  "noMessageText": "لا توجد رسائل معروضة.",
+  "noDataText": "لا توجد بيانات للعرض.",
+  "noPIDText": "لم يتم العثور على رمز الوصول.",
+  "noPIDText2": "احصل على رمز الوصول من خلال مسح رمز QR أو عبر الرابط المقدم من صاحب ترخيص TelMessenger.",
+  "loading": "جارٍ التحميل...",
+  "backToLogin": "العودة إلى صفحة تسجيل الدخول",
+  "deleteHistory": "حذف سجل البيانات",
+  "deleteHistoryConfirm": "هل أنت متأكد أنك تريد حذف هذه البيانات؟",
+  "couldnotDelete": "لا يمكن حذف هذه البيانات.",
+  "allInformants": "جميع المبلغين",
+  "welcome": "مرحباً بك",
+  "createNewPassText": "يرجى إنشاء كلمة مرور جديدة قبل البدء باستخدام التطبيق.",
+  "description": "الوصف",
+  "sorry": "عذراً..!",
+  "outOfWorkTime": "بلاغك حالياً خارج وقت عمل المنفذ.",
+  "willResponse": "سيعودون في: ",
+  "canNotCancel1": "لا يمكن إلغاء هذا البلاغ بعد أن بدأ المنفذ في تنفيذه.",
+  "canNotCancel2": "لا يمكن إلغاء هذا البلاغ.",
+  "priorityHigh": "عالية",
+  "priorityLow": "منخفضة",
+  "priorityNormal": "عادية",
+  "priority": "الأولوية",
+  "reported": "تم الإبلاغ",
+  "escalationNoteBySystem": "تعيين",
+  "finishAttachment": "مرفق الإنجاز",
+  "pleaseScan": "يرجى مسح رمز QR",
+  "mustScan": "يجب عليك مسح رمز QR لإعادة الإبلاغ.",
+  "changeCodeMsg": "سيتم الاتصال بـ <company_name>. هل ترغب في المتابعة؟",
+  "currentServer": "الخادم الحالي",
+  "destinationServer": "الخادم المستهدف",
+  "textContinue": "متابعة",
+  "transferNoteBySystem": "تحويل",
+  "activity": "نشاط",
+  "image": "صورة",
+  "titlePassOld": "أدخل كلمة المرور القديمة",
+  "titlePassNew": "سيتم استبدال كلمة المرور القديمة بالجديدة.",
+  "setting": "الإعدادات",
+  "more": "المزيد",
+  "topMenu": "القائمة الرئيسية",
+  "availableMenu": "القائمة المتاحة",
+  "customize": "تخصيص",
+  "done": "تم",
+  "ongoing": "جارٍ التنفيذ",
+  "maxMenu": "الحد الأقصى ٧ قوائم.",
+  "searchIn": "ابحث في",
+  "yourLocation": "موقعك",
+  "addImage": "هل تريد إضافة صورة؟",
+  "addImageMsg": "يمكنك إرسال صورة كدليل للمنفذ.",
+  "removeImageMsg": "لا أعتقد أن هناك حاجة. احذف الصورة فقط.",
+  "sendingOpt": "خيارات الإرسال",
+  "sendingOptMsg": "هل ترغب في الإرسال كطلب مجدول أم فوراً؟",
+  "scheduled": "جدولة",
+  "sendNow": "أرسل الآن",
+  "scheduledMsg": "* جدولة = سيتم إرسال الطلب في الوقت الذي تحدده في الملاحظة.",
+  "reqSuccessMsg": "يرجى الانتظار. يتم الآن معالجة طلبك.",
+  "noImgAttach": "لا توجد صورة مرفقة.",
+  "btnWait": "حسناً، سأنتظر",
+  "rateReq": "قيّم طلبك",
+  "reqAgain": "اطلب مرة أخرى",
+  "notAvailable": "القائمة غير متوفرة حالياً.",
+  "emptyMenuAlert": "لم يتم اختيار أي قائمة.",
+  "redirecting": "جارٍ التحميل...",
+  "initializing": "جارٍ المعالجة...",
+  "selectAlert": "يرجى اختيار القائمة أولاً.",
+  "scopeDesc": "استخدم هذا لتغيير نطاقك.",
+  "notFoundKeyword": "لم يتم العثور على بيانات مطابقة للكلمة المفتاحية ",
+  "findQRcode": "ابحث عن رمز QR بالقرب منك",
+  "isWorking": " يقوم بتنفيذ طلبك.",
+  "noteLongAlert": "يمكنك إضافة ملاحظة بحد أقصى 128 حرفاً فقط.",
+  "reqCodeNotFound": "رمز البلاغ غير صالح. يرجى الاتصال بالمسؤول.",
+  "noImage": "لا توجد صورة",
+  "related": "الخدمات ذات الصلة",
+  "broadcastPermission": "لا يمكن إرسال الرسالة، ليس لديك إذن.",
+  "broadcastTenant": "لا يمكن إرسال الرسالة، المستلم غير صالح.",
+  "reqForOthers": "بالطبع، أحب مساعدة الآخرين.",
+  "reqForMySelf": "لا، هذا الطلب لي شخصياً.",
+  "requestedBy": "تم الطلب بواسطة",
+  "requestedFor": "تم الطلب لـ",
+  "typeId": "اكتب معرف الشخص",
+  "idNotMatch": "فشل إرسال الطلب. لا يُسمح بهذا الطلب للمعرف #ID.",
+  "idNotFound": "لم يتم العثور على المعرف المُدخل. يرجى تجربة معرف آخر.",
+  "informantNotRegistered": "أنت غير مسجل كمستقبل أو موظف غرفة.",
+  "link_copied": "تم نسخ الرابط إلى الحافظة.",
+  "do_not_disturb": "عدم الإزعاج",
+  "set_dnd_status": "تعيين حالة عدم الإزعاج",
+  "active_dnd": "نشط",
+  "inactive_dnd": "غير نشط",
+  "msg_dnd": "يمكنك تعطيل حالة عدم الإزعاج من خلال قائمة الحساب.",
+  "msg_change_active": "هل ترغب في تفعيل حالة عدم الإزعاج؟",
+  "msg_change_inactive": "هل ترغب في تعطيل حالة عدم الإزعاج؟",
+  "info_dnd": "تم تفعيل حالة عدم الإزعاج.",
+  "invalid_bridge": "رابط Bridge غير صالح.",
+  "someone_else": "هل ترغب في تقديم طلب نيابة عن شخص آخر؟",
+  "askScheduleTime": "متى يجب تنفيذ طلبك؟",
+  "scheduleMessage": "سيتم تنفيذ طلبك لاحقاً في",
+  "scan_qr": "اعرض رمز QR الخاص بي",
+  "show_qr": "اعرض رمز QR",
+  "show_qr_desc": "اعرض هذا الرمز لأصدقائك أو زملائك الذين يحتاجون إلى الوصول إلى التطبيق.",
+  "dismiss": "تخطي",
+  "not_allow_permission": "جهازك لا يسمح للتطبيق بتلقي الإشعارات.",
+  "tap_here": "اضغط هنا للذهاب إلى إعدادات الجهاز.",
+  "doNotDisturb": "عدم الإزعاج",
+  "info_label": "معلومات",
+  "display_menu": "عرض القائمة",
+  "req_group": "مجموعة البلاغات",
+  "ser_group": "مجموعة المنفذين",
+  "save_rating": "احفظ التقييم",
+  "auto_translate": "ترجمة تلقائية",
+  "hold": "معلق",
+  "holdHistory": "سجل التعليق",
+  "pending": "قيد الانتظار",
+  "pending_info_success": "وجدنا مشكلة في الاتصال. طلبك ينتظر القرار وسيتم إرساله عند عودة الاتصال.",
+  "pending_info_tiles": "انتظر الاتصال بالإنترنت لإرسال طلبك.",
+  "pending_info_detail": "لم يتم إرسال طلبك بسبب مشكلة في الاتصال.",
+  "cancel_pending": "ألغِ طلبي فقط",
+  "sending": "جارٍ الإرسال",
+  "queued": "في قائمة الانتظار",
+  "onProgress": "قيد التنفيذ",
+  "showLess": "عرض أقل",
+  "customTopMenu": "تخصيص القائمة الرئيسية",
+  "limitTopMenu": "القائمة الرئيسية محدودة بـ 7 عناصر فقط!",
+  "searchButton": "بحث",
+  "allInformantsInfo": "جميع المبلغين سيستلمون رسالتك.",
+  "action": "إجراء",
+  "whatMakesYou": "ما الذي يجعلك",
+  "letMeWrite": "لدي رأي مختلف!",
+  "writeHere": "اكتب هنا..",
+  "ratingAspect": "جانب التقييم",
+  "sendingRequest": "جارٍ إرسال الطلب",
+  "changePriority": "تغيير الأولوية",
+  "referenceNumber": "رقم المرجع",
+  "enterTicketNumber": "أدخل رقم التذكرة",
+  "subject": "الموضوع",
+  "requestReference": "مرجع الطلب",
+  "endSessionTitle": "انتهت جلستك.",
+  "endSessionDesc": "عذراً، يبدو أنك لم تستخدم التطبيق لفترة. لأمانك، يرجى تسجيل الدخول مرة أخرى قبل المتابعة.",
+  "invalidParentTicket": "رقم المرجع غير صالح.",
+  "failedLoad": "فشل تحميل التطبيق.",
+  "tap2retry": "اضغط لإعادة المحاولة.",
+  "chat": "محادثة"
+}

+ 337 - 0
assets/lang/de.json

@@ -0,0 +1,337 @@
+{
+  "account": "Konto",
+  "addNote": "Notiz hinzufügen",
+  "addNoteCancel": "Stornierungsnotiz hinzufügen",
+  "all": "Alle",
+  "anyNote": "Gibt es eine Notiz für den Mitarbeiter?",
+  "appointment": "Ernennung",
+  "attach": "Bild anhängen",
+  "buttonCancel": "Mission abbrechen",
+  "buttonLogin": "Anmelden",
+  "buttonNo": "NEIN",
+  "buttonResubmit": "ERNEUT MELDEN",
+  "buttonScan": "QR scannen",
+  "buttonSendEmergency": "NOTFALL SENDEN",
+  "buttonSendComplaint": "BESCHWERDE SENDEN",
+  "buttonSendRequest": "Anfrage senden",
+  "buttonYes": "JA",
+  "buttonSure": "JA, SICHER",
+  "buttonBack": "Zurück",
+  "canceledBy": "Abgebrochen von",
+  "category": "Kategorie",
+  "scope": "Umfang",
+  "changeScope": "Umfang ändern",
+  "chatClosed": "Dieses Gespräch wurde geschlossen.",
+  "chooseService": "Wähle den gewünschten Dienst",
+  "complaintText": "Was ist das Problem? Lass die Experten sich darum kümmern.",
+  "complaint": "Beschwerde",
+  "defaultLocation": "Standardstandort",
+  "detail": "Details",
+  "disatisfied": "Unzufrieden",
+  "emergencyText": "Alle dringenden und kritischen Angelegenheiten, die sofort behandelt werden müssen.",
+  "emergency": "Notfall",
+  "forum": "Forum",
+  "frequentlyText": "Was du oft brauchst, haben wir hier gesammelt.",
+  "frequently": "Häufig angefordert",
+  "frequentlyTitle": "Häufig angefordert",
+  "frequentlyTitleText": "Häufig angeforderte Berichte",
+  "history": "Verlauf",
+  "lessSatisfied": "Wenig zufrieden",
+  "location": "Standort",
+  "logout": "Abmelden",
+  "menuAccount": "Konto",
+  "menuHistory": "Verlauf",
+  "menuService": "Dienst",
+  "menuHome": "Startseite",
+  "message": "Nachricht",
+  "note": "Notiz",
+  "passwordEmpty": "Passwort darf nicht leer sein",
+  "password": "Passwort",
+  "phoneNumber": "Telefonnummer",
+  "placeholderLocation": "Standort eingeben",
+  "placeholderNote": "Notiz schreiben",
+  "rateMission": "Mission bewerten",
+  "reallyPleased": "Sehr zufrieden",
+  "requestText2": "Was wird angefordert?",
+  "requestDesc": "Wenn du Hilfe brauchst, zögere nicht.",
+  "requestTitle": "Wobei brauchst du Hilfe?",
+  "request": "Anfrage",
+  "satisfactionAsk": "Bist du zufrieden?",
+  "satisfied": "Ziemlich zufrieden",
+  "scanQr": "Bitte scanne zuerst den QR-Code, um die App zu verwenden.",
+  "serialNumber": "Seriennummer",
+  "servantGroup": "Berichtsgruppe",
+  "servant": "Ausführender",
+  "service": "Dienst",
+  "specifyAnotherLocation": "Ich bin an einem anderen Ort",
+  "specifyLocation": "Standort festlegen",
+  "startDoing": "Beginnen",
+  "stateCancel": "Abbrechen",
+  "stateCanceled": "Abgebrochen",
+  "stateDone": "Bearbeitet",
+  "stateFinish": "Abgeschlossen",
+  "stateQueue": "Warteschlange",
+  "stateRequested": "Angefordert",
+  "state": "Status",
+  "sure": "Bist du sicher?",
+  "switch": "Wechseln",
+  "system": "System",
+  "textCancel": "Abbrechen",
+  "textHistory": "Berichte, die du erstellt hast",
+  "textLogout": "Du wirst keine Benachrichtigungen mehr erhalten und keine Berichte mehr erstellen können. Bist du sicher, dass du dich abmelden möchtest?",
+  "textResubmit": "Du wirst diesen Bericht erneut an den Mitarbeiter senden.",
+  "ticketNumber": "Ticketnummer",
+  "timeline": "Zeitverlauf",
+  "today": "Heute",
+  "unrated": "Keine Bewertung für diese Mission",
+  "userEmpty": "Benutzername darf nicht leer sein",
+  "userId": "Benutzername",
+  "version": "Version",
+  "verySatisfied": "Zufrieden",
+  "whatComplain": "Was ist die Beschwerde?",
+  "whatHappen": "Was ist passiert?",
+  "whatNext": "Bleib ruhig. Was kommt als Nächstes?",
+  "whatProblem": "Was ist das Problem?",
+  "writeMessage": "Nachricht schreiben",
+  "yesterday": "Gestern",
+  "language": "Sprache",
+  "bahasa": "Indonesia",
+  "english": "English",
+  "japanese": "日本語",
+  "korean": "한국어",
+  "chinese": "中文",
+  "german": "Deutsch",
+  "french": "Français",
+  "hindi": "हिंदी",
+  "arabic": "العربية",
+  "dutch": "Nederlands",
+  "chooseLanguage": "Sprache wählen",
+  "rate": "Bewertung",
+  "rated": "Bewertung wurde abgegeben.",
+  "rateText": "Du bist [satisfaction] mit dieser Mission.",
+  "settingPassword": "Passwort ändern",
+  "oldPassword": "Altes Passwort",
+  "newPassword": "Neues Passwort",
+  "confirmPassword": "Neues Passwort bestätigen",
+  "buttonSave": "Neues Passwort speichern",
+  "settingPasswordText": "Passwort hier festlegen",
+  "autoResponseText": "Deine Anfrage wird vom System sofort bearbeitet.",
+  "reply": "Antwort",
+  "selectPicture": "Medien auswählen",
+  "fromGallery": "Aus Galerie",
+  "fromCamera": "Foto aufnehmen",
+  "sendPicture": "Bild senden",
+  "seeAttachment": "Anhang anzeigen",
+  "confirmCancel": "Bist du sicher, dass du abbrechen möchtest?",
+  "selectDate": "Datum auswählen",
+  "lengthMax": "Maximale Notizlänge: 128 Zeichen.",
+  "locRequired": "Standort darf nicht leer sein.",
+  "idRequired": "ID darf nicht leer sein.",
+  "dateRequired": "Datum darf nicht leer sein.",
+  "sendLater": "Später senden",
+  "thank": "Danke",
+  "pleaseWait": "Bitte warten",
+  "sendSuccess": "Erfolgreich gesendet",
+  "followUp": "Der Mitarbeiter wird sich bald darum kümmern.",
+  "okGot": "OK, VERSTANDEN",
+  "okWait": "OK, WIRD GEWARTET",
+  "errorConnection": "Verbindungsfehler. Bitte später erneut versuchen.",
+  "noInternet": "Oh nein, keine Internetverbindung.",
+  "invalidLogin": "Benutzername oder Passwort falsch.",
+  "invalidPhone": "Telefonnummer nicht registriert.",
+  "expAccount": "Konto ist abgelaufen.",
+  "errorServer": "Serverfehler. Bitte später erneut versuchen.",
+  "accessDenied": "Zugriff verweigert",
+  "licenseProblem": "Die App kann aufgrund eines Lizenzproblems nicht verwendet werden.",
+  "invalidVersion": "App-Version nicht kompatibel",
+  "sugestVersion": "Bitte verwende die App-Version",
+  "currentVersion": "Installierte App-Version ist",
+  "contactAdmin": "Bitte kontaktiere den Administrator für weitere Informationen.",
+  "pressAgain": "Nochmals drücken zum Beenden.",
+  "notFound": "Daten nicht gefunden.",
+  "notFound2": "Was du suchst, ist hier nicht vorhanden..!",
+  "noInternetTitle": "Keine Internetverbindung.",
+  "noInternetDesc": "Überprüfe zuerst deine Internetverbindung. Dann machen wir weiter!",
+  "errorConnectTitle": "Verbindung zum Server fehlgeschlagen.",
+  "errorConnectDesc": "Frag den Administrator, du solltest dich mit dem Server verbinden können.",
+  "errorServerTitle": "Es gibt ein Problem mit dem Server.",
+  "errorServerDesc": "Keine Panik, wir arbeiten hart daran, alles unter Kontrolle zu bringen.",
+  "invalidAccountTitle": "Konto nicht registriert.",
+  "invalidAccountDesc": "Ups! Dein Konto konnte nicht verifiziert werden. Frag am besten den Administrator.",
+  "expAccountTitle": "Konto ist abgelaufen.",
+  "expAccountDesc": "Du kannst die App nicht verwenden, da dein Konto abgelaufen ist. Wenn du denkst, das ist ein Fehler, kontaktiere bitte den Administrator.",
+  "inProcess": "Wird bearbeitet",
+  "policy": "Datenschutzrichtlinie",
+  "info": "Zusätzliche Informationen",
+  "refresh": "Aktualisieren",
+  "messageCamPermission": "Kamerazugriff verweigert. Erlaube TelMessenger den Zugriff über die Einstellungen.",
+  "messageInvalidCode": "Ungültiger Code. Bitte kontaktiere den Administrator.",
+  "messagePassChanged": "Passwort erfolgreich geändert.",
+  "wrongOldPass": "Falsches altes Passwort.",
+  "checkNewPass": "Bitte überprüfe dein neues Passwort erneut.",
+  "cannotEmpty": "Daten dürfen nicht leer sein.",
+  "deletedMessage": "Diese Nachricht wurde gelöscht.",
+  "resend": "Erneut senden",
+  "delete": "Löschen",
+  "alreadyUsePass": "Dieses Passwort kann nicht verwendet werden, bitte versuche ein anderes.",
+  "notFoundImg": "Bild nicht gefunden",
+  "notFoundWorktime": "Arbeitszeit nicht gefunden. Bitte kontaktiere den Administrator.",
+  "seeAll": "Alle anzeigen",
+  "quickAction": "Schnelle Aktion",
+  "quickDesc": "Damit deine Anfrage schneller bearbeitet wird.",
+  "times": " mal",
+  "search": "Suche",
+  "searchAsk": "Suchen...",
+  "helpHint": "Beschreibe dein Problem...",
+  "inProgress": " deine Anfrage wird gerade bearbeitet.",
+  "currentActiveText": "Dein laufender Bericht",
+  "photo": "Bild",
+  "selectRecipient": "Empfänger auswählen",
+  "composeMessage": "Nachricht verfassen",
+  "to": "An",
+  "addAttachment": "Anhang hinzufügen",
+  "editAttachment": "Anhang bearbeiten",
+  "unsupportedFile": "Dateiformat nicht unterstützt",
+  "pdfFile": "PDF-Datei",
+  "other": "andere",
+  "noMessageText": "Keine Nachrichten angezeigt.",
+  "noDataText": "Keine Daten zum Anzeigen.",
+  "noPIDText": "Kein Zugriffscode gefunden.",
+  "noPIDText2": "Erhalte den Zugriffscode durch Scannen des QR-Codes oder über den Link vom TelMessenger-Lizenzinhaber.",
+  "loading": "Wird geladen...",
+  "backToLogin": "Zurück zur Login-Seite",
+  "deleteHistory": "Verlauf löschen",
+  "deleteHistoryConfirm": "Möchtest du diese Daten wirklich löschen?",
+  "couldnotDelete": "Diese Daten können nicht gelöscht werden.",
+  "allInformants": "Alle Melder",
+  "welcome": "Willkommen",
+  "createNewPassText": "Bitte erstelle ein neues Passwort, bevor du die App verwendest.",
+  "description": "Beschreibung",
+  "sorry": "Tut mir leid..!",
+  "outOfWorkTime": "Dein Bericht liegt außerhalb der Arbeitszeit des Ausführenden.",
+  "willResponse": "Sie sind wieder verfügbar am: ",
+  "canNotCancel1": "Dieser Bericht kann nicht storniert werden, da er bereits bearbeitet wird.",
+  "canNotCancel2": "Dieser Bericht kann nicht storniert werden.",
+  "priorityHigh": "Hoch",
+  "priorityLow": "Niedrig",
+  "priorityNormal": "Normal",
+  "priority": "Priorität",
+  "reported": "Gemeldet",
+  "escalationNoteBySystem": "Ernennung",
+  "finishAttachment": "Abschlussanhang",
+  "pleaseScan": "Bitte QR-Code scannen",
+  "mustScan": "Du musst den QR-Code scannen, um erneut zu melden.",
+  "changeCodeMsg": "Du wirst mit <company_name> verbunden. Möchtest du fortfahren?",
+  "currentServer": "Aktueller Server",
+  "destinationServer": "Zielserver",
+  "textContinue": "Fortfahren",
+  "transferNoteBySystem": "Übertragung",
+  "activity": "Aktivität",
+  "image": "Bild",
+  "titlePassOld": "Gib dein altes Passwort ein",
+  "titlePassNew": "Dein altes Passwort wird durch ein neues ersetzt.",
+  "setting": "Einstellungen",
+  "more": "Mehr",
+  "topMenu": "Hauptmenü",
+  "availableMenu": "Verfügbare Menüs",
+  "customize": "Anpassen",
+  "done": "Fertig",
+  "ongoing": "Laufend",
+  "maxMenu": "Maximal 7 Menüs.",
+  "searchIn": "Suchen in",
+  "yourLocation": "Dein Standort",
+  "addImage": "Möchtest du ein Bild hinzufügen?",
+  "addImageMsg": "Du kannst ein Bild als Hinweis für den Ausführenden senden.",
+  "removeImageMsg": "Ich denke, es ist nicht nötig. Entferne einfach das Bild.",
+  "sendingOpt": "Sendeoptionen",
+  "sendingOptMsg": "Möchtest du die Anfrage geplant oder sofort senden?",
+  "scheduled": "Planen",
+  "sendNow": "Jetzt senden",
+  "scheduledMsg": "* Planen = Anfrage wird zum gewählten Zeitpunkt gesendet.",
+  "reqSuccessMsg": "Bitte warten. Deine Anfrage wird bearbeitet.",
+  "noImgAttach": "Kein Bild angehängt.",
+  "btnWait": "Okay, ich warte",
+  "rateReq": "Bewerte deine Anfrage",
+  "reqAgain": "Erneut anfragen",
+  "notAvailable": "Dieses Menü ist noch nicht verfügbar.",
+  "emptyMenuAlert": "Noch kein Menü ausgewählt.",
+  "redirecting": "Lädt...",
+  "initializing": "Wird verarbeitet...",
+  "selectAlert": "Bitte wähle zuerst ein Menü.",
+  "scopeDesc": "Nutze dies, um deinen Bereich zu ändern.",
+  "notFoundKeyword": "Keine Daten gefunden für das Suchwort ",
+  "findQRcode": "Finde den QR-Code in deiner Nähe",
+  "isWorking": " bearbeitet gerade deine Anfrage.",
+  "noteLongAlert": "Du darfst maximal 128 Zeichen in der Notiz verwenden.",
+  "reqCodeNotFound": "Ungültiger Berichtscode. Bitte kontaktiere den Administrator.",
+  "noImage": "Kein Bild vorhanden",
+  "related": "Verwandte Dienste",
+  "broadcastPermission": "Nachricht kann nicht gesendet werden, keine Berechtigung.",
+  "broadcastTenant": "Nachricht kann nicht gesendet werden, Empfänger-Tenant ungültig.",
+  "reqForOthers": "Klar, ich helfe gerne anderen.",
+  "reqForMySelf": "Nein, diese Anfrage ist für mich selbst.",
+  "requestedBy": "Angefragt von",
+  "requestedFor": "Angefragt für",
+  "typeId": "Gib die ID der Person ein",
+  "idNotMatch": "Anfrage fehlgeschlagen. Diese Anfrage ist für die ID # nicht erlaubt.",
+  "idNotFound": "ID nicht gefunden. Bitte versuche eine andere.",
+  "informantNotRegistered": "Du bist nicht als Rezeptionist oder Zimmerpersonal registriert.",
+  "link_copied": "Link wurde in die Zwischenablage kopiert.",
+  "do_not_disturb": "Nicht stören",
+  "set_dnd_status": "DND-Status festlegen",
+  "active_dnd": "Aktiv",
+  "inactive_dnd": "Inaktiv",
+  "msg_dnd": "Du kannst den DND-Status im Kontomenü deaktivieren.",
+  "msg_change_active": "Möchtest du den DND-Status aktivieren?",
+  "msg_change_inactive": "Möchtest du den DND-Status deaktivieren?",
+  "info_dnd": "DND-Status aktiviert.",
+  "invalid_bridge": "Ungültige Bridge-URL.",
+  "someone_else": "Möchtest du eine Anfrage im Namen einer anderen Person stellen?",
+  "askScheduleTime": "Wann soll deine Anfrage bearbeitet werden?",
+  "scheduleMessage": "Deine Anfrage wird später bearbeitet am",
+  "scan_qr": "Meinen QR anzeigen",
+  "show_qr": "QR anzeigen",
+  "show_qr_desc": "Zeige diesen QR-Code deinen Freunden oder Kollegen, die Zugriff auf die App benötigen.",
+  "dismiss": "Überspringen",
+  "not_allow_permission": "Dein Gerät erlaubt der App keine Benachrichtigungen.",
+  "tap_here": "Tippe hier, um zu den Einstellungen zu gelangen.",
+  "doNotDisturb": "Nicht stören",
+  "info_label": "Info",
+  "display_menu": "Menüanzeige",
+  "req_group": "Berichtsgruppe",
+  "ser_group": "Ausführungsgruppe",
+  "save_rating": "Bewertung speichern",
+  "auto_translate": "Automatisch übersetzen",
+  "hold": "Zurückgestellt",
+  "holdHistory": "Verlaufsprotokoll der Zurückstellungen",
+  "pending": "Ausstehend",
+  "pending_info_success": "Wir haben ein Verbindungsproblem festgestellt. Ihre Anfrage wartet auf eine Entscheidung. Sie wird gesendet, sobald Sie wieder online sind.",
+  "pending_info_tiles": "Warten Sie auf eine Internetverbindung, um Ihre Anfrage zu senden.",
+  "pending_info_detail": "Ihre Anfrage wurde aufgrund eines Verbindungsproblems nicht gesendet.",
+  "cancel_pending": "Nur meine Anfrage stornieren",
+  "sending": "Wird gesendet",
+  "queued": "In Warteschlange",
+  "onProgress": "In Bearbeitung",
+  "showLess": "Weniger anzeigen",
+  "customTopMenu": "Hauptmenü anpassen",
+  "limitTopMenu": "Das Hauptmenü ist auf 7 Elemente begrenzt!",
+  "searchButton": "Suchen",
+  "allInformantsInfo": "Alle Melder erhalten Ihre Nachricht.",
+  "action": "Aktion",
+  "whatMakesYou": "Was macht dich",
+  "letMeWrite": "Ich habe eine andere Meinung!",
+  "writeHere": "Hier schreiben...",
+  "ratingAspect": "Bewertungsaspekt",
+  "sendingRequest": "Anfrage wird gesendet",
+  "changePriority": "Priorität ändern",
+  "referenceNumber": "Referenznummer",
+  "enterTicketNumber": "Ticketnummer eingeben",
+  "subject": "Betreff",
+  "requestReference": "Anfragereferenz",
+  "endSessionTitle": "Ihre Sitzung ist abgelaufen.",
+  "endSessionDesc": "Oh, Entschuldigung. Es sieht so aus, als hätten Sie die App eine Weile nicht benutzt. Bitte melden Sie sich erneut an, um fortzufahren.",
+  "invalidParentTicket": "Ungültige Referenznummer.",
+  "failedLoad": "App konnte nicht geladen werden.",
+  "tap2retry": "Tippen, um es erneut zu versuchen.",
+  "chat": "Gespräch"
+}

+ 9 - 3
assets/lang/en.json

@@ -95,11 +95,16 @@
    "writeMessage": "Write message",
    "yesterday": "Yesterday",
    "language": "Language",
-   "bahasa": "Bahasa Indonesia",
+   "bahasa": "Indonesia",
    "english": "English",
    "japanese": "日本語",
    "korean": "한국어",
-   "chinese": "中国語",
+   "chinese": "中文",
+   "german": "Deutsch",
+   "french": "Français",
+   "hindi": "हिंदी",
+   "arabic": "العربية",
+   "dutch": "Nederlands",
    "chooseLanguage": "Select Language",
    "rate": "Rate",
    "rated": "This mission have been rated.",
@@ -327,5 +332,6 @@
    "endSessionDesc": "Oops, sorry. We have not see you for along time. It\\'s kindly your session has expired. Please re-login to continue using application.",
    "invalidParentTicket": "Invalid parent ticket.",
    "failedLoad": "Failed to load Application.",
-   "tap2retry": "Tap to retry."
+   "tap2retry": "Tap to retry.",
+   "chat": "Chat"
 }

+ 337 - 0
assets/lang/fr.json

@@ -0,0 +1,337 @@
+{
+  "account": "Compte",
+  "addNote": "Ajouter une note",
+  "addNoteCancel": "Ajouter une note d'annulation",
+  "all": "Tous",
+  "anyNote": "Y a-t-il une note pour l'agent ?",
+  "appointment": "Nomination",
+  "attach": "Joindre une image",
+  "buttonCancel": "Annuler la mission",
+  "buttonLogin": "Connexion",
+  "buttonNo": "NON",
+  "buttonResubmit": "RÉSOUMETTRE",
+  "buttonScan": "Scanner le QR",
+  "buttonSendEmergency": "ENVOYER EN URGENCE",
+  "buttonSendComplaint": "ENVOYER UNE PLAINTE",
+  "buttonSendRequest": "Envoyer une demande",
+  "buttonYes": "OUI",
+  "buttonSure": "OUI, CERTAINEMENT",
+  "buttonBack": "Retour",
+  "canceledBy": "Annulé par",
+  "category": "Catégorie",
+  "scope": "Portée",
+  "changeScope": "Changer la portée",
+  "chatClosed": "Cette conversation est fermée.",
+  "chooseService": "Choisissez le service que vous souhaitez",
+  "complaintText": "Quel est le problème ? Laissez les experts s'en occuper.",
+  "complaint": "Plainte",
+  "defaultLocation": "Emplacement par défaut",
+  "detail": "Détail",
+  "disatisfied": "Insatisfait",
+  "emergencyText": "Tout ce qui est urgent et critique à traiter immédiatement.",
+  "emergency": "Urgence",
+  "forum": "Forum",
+  "frequentlyText": "Ce que vous demandez souvent est regroupé ici.",
+  "frequently": "Demandé fréquemment",
+  "frequentlyTitle": "Demandes fréquentes",
+  "frequentlyTitleText": "Rapports fréquemment demandés",
+  "history": "Historique",
+  "lessSatisfied": "Moins satisfait",
+  "location": "Emplacement",
+  "logout": "Déconnexion",
+  "menuAccount": "Compte",
+  "menuHistory": "Historique",
+  "menuService": "Service",
+  "menuHome": "Accueil",
+  "message": "Message",
+  "note": "Note",
+  "passwordEmpty": "Le mot de passe ne peut pas être vide",
+  "password": "Mot de passe",
+  "phoneNumber": "Numéro de téléphone",
+  "placeholderLocation": "Écrire l'emplacement",
+  "placeholderNote": "Écrire une note",
+  "rateMission": "Évaluer cette mission",
+  "reallyPleased": "Très satisfait",
+  "requestText2": "Quelle est la demande ?",
+  "requestDesc": "Si vous avez besoin d'aide, n'hésitez pas.",
+  "requestTitle": "De quoi avez-vous besoin ?",
+  "request": "Demande",
+  "satisfactionAsk": "Êtes-vous satisfait ?",
+  "satisfied": "Assez satisfait",
+  "scanQr": "Veuillez scanner le QR pour commencer à utiliser l'application.",
+  "serialNumber": "Numéro de série",
+  "servantGroup": "Groupe de rapport",
+  "servant": "Exécutant",
+  "service": "Service",
+  "specifyAnotherLocation": "Je suis à un autre endroit",
+  "specifyLocation": "Spécifier l'emplacement",
+  "startDoing": "Commencer",
+  "stateCancel": "Annuler",
+  "stateCanceled": "Annulé",
+  "stateDone": "En cours",
+  "stateFinish": "Terminé",
+  "stateQueue": "En file d'attente",
+  "stateRequested": "Demandé",
+  "state": "Statut",
+  "sure": "Êtes-vous sûr ?",
+  "switch": "Changement",
+  "system": "Système",
+  "textCancel": "Annuler",
+  "textHistory": "Rapports que vous avez créés",
+  "textLogout": "Vous ne recevrez plus de notifications ni ne pourrez faire de rapports. Êtes-vous sûr de vouloir vous déconnecter ?",
+  "textResubmit": "Vous allez renvoyer ce rapport à l'agent.",
+  "ticketNumber": "Numéro de ticket",
+  "timeline": "Chronologie",
+  "today": "Aujourd'hui",
+  "unrated": "Aucune évaluation pour cette mission",
+  "userEmpty": "Le nom d'utilisateur ne peut pas être vide",
+  "userId": "Nom d'utilisateur",
+  "version": "Version",
+  "verySatisfied": "Satisfait",
+  "whatComplain": "Quelle est la plainte ?",
+  "whatHappen": "Que s'est-il passé ?",
+  "whatNext": "Restez calme. Quelle est la prochaine étape ?",
+  "whatProblem": "Quel est le problème ?",
+  "writeMessage": "Écrire un message",
+  "yesterday": "Hier",
+  "language": "Langue",
+  "bahasa": "Indonesia",
+  "english": "English",
+  "japanese": "日本語",
+  "korean": "한국어",
+  "chinese": "中文",
+  "german": "Deutsch",
+  "french": "Français",
+  "hindi": "हिंदी",
+  "arabic": "العربية",
+  "dutch": "Nederlands",
+  "chooseLanguage": "Choisir la langue",
+  "rate": "Évaluation",
+  "rated": "Évaluation déjà donnée.",
+  "rateText": "Vous êtes [satisfaction] pour cette mission.",
+  "settingPassword": "Changer le mot de passe",
+  "oldPassword": "Ancien mot de passe",
+  "newPassword": "Nouveau mot de passe",
+  "confirmPassword": "Confirmer le nouveau mot de passe",
+  "buttonSave": "Enregistrer le nouveau mot de passe",
+  "settingPasswordText": "Définissez votre mot de passe ici",
+  "autoResponseText": "Votre demande sera traitée immédiatement par le système.",
+  "reply": "Réponse",
+  "selectPicture": "Choisir un média",
+  "fromGallery": "Depuis la galerie",
+  "fromCamera": "Prendre une photo",
+  "sendPicture": "Envoyer une image",
+  "seeAttachment": "Voir la pièce jointe",
+  "confirmCancel": "Êtes-vous sûr de vouloir annuler ?",
+  "selectDate": "Choisir une date",
+  "lengthMax": "Longueur maximale de la note : 128 caractères.",
+  "locRequired": "L'emplacement ne peut pas être vide.",
+  "idRequired": "L'ID ne peut pas être vide.",
+  "dateRequired": "La date ne peut pas être vide.",
+  "sendLater": "Envoyer plus tard",
+  "thank": "Merci",
+  "pleaseWait": "Veuillez patienter",
+  "sendSuccess": "Envoyé avec succès",
+  "followUp": "L'agent suivra bientôt.",
+  "okGot": "OK, COMPRIS",
+  "okWait": "OK, EN ATTENTE",
+  "errorConnection": "Erreur de connexion. Réessayez plus tard.",
+  "noInternet": "Oups, pas d'internet.",
+  "invalidLogin": "Nom d'utilisateur ou mot de passe incorrect.",
+  "invalidPhone": "Numéro de téléphone non enregistré.",
+  "expAccount": "Compte expiré.",
+  "errorServer": "Erreur du serveur. Réessayez plus tard.",
+  "accessDenied": "Accès refusé",
+  "licenseProblem": "L'application ne peut pas être utilisée en raison d'un problème de licence.",
+  "invalidVersion": "Version de l'application incorrecte",
+  "sugestVersion": "Utilisez l'application avec la version",
+  "currentVersion": "La version installée de l'application est",
+  "contactAdmin": "Veuillez contacter l'administrateur pour plus d'informations.",
+  "pressAgain": "Appuyez à nouveau pour quitter.",
+  "notFound": "Données non trouvées.",
+  "notFound2": "Ce que vous cherchez n'est pas ici..!",
+  "noInternetTitle": "Pas de connexion Internet.",
+  "noInternetDesc": "Vérifiez d'abord votre connexion Internet. Nous continuerons ensuite !",
+  "errorConnectTitle": "Échec de la connexion au serveur.",
+  "errorConnectDesc": "Demandez à l'administrateur, vous devriez pouvoir vous connecter au serveur.",
+  "errorServerTitle": "Il y a un problème avec le serveur.",
+  "errorServerDesc": "Ne paniquez pas, nous faisons de notre mieux pour tout sécuriser.",
+  "invalidAccountTitle": "Compte non enregistré.",
+  "invalidAccountDesc": "Oups ! Votre compte ne peut pas être vérifié. Veuillez contacter l'administrateur.",
+  "expAccountTitle": "Compte expiré.",
+  "expAccountDesc": "Vous ne pouvez pas accéder à l'application car votre compte a expiré. Si vous pensez que c'est une erreur, contactez l'administrateur.",
+  "inProcess": "En cours de traitement",
+  "policy": "Politique de confidentialité",
+  "info": "Informations supplémentaires",
+  "refresh": "Actualiser",
+  "messageCamPermission": "Accès à la caméra refusé. Autorisez TelMessenger à accéder à la caméra via les paramètres.",
+  "messageInvalidCode": "Code invalide. Veuillez contacter l'administrateur.",
+  "messagePassChanged": "Mot de passe modifié avec succès.",
+  "wrongOldPass": "Ancien mot de passe incorrect.",
+  "checkNewPass": "Vérifiez à nouveau le nouveau mot de passe.",
+  "cannotEmpty": "Les données ne peuvent pas être vides.",
+  "deletedMessage": "Ce message a été supprimé.",
+  "resend": "Renvoyer",
+  "delete": "Supprimer",
+  "alreadyUsePass": "Ce mot de passe ne peut pas être utilisé, essayez-en un autre.",
+  "notFoundImg": "Image non trouvée",
+  "notFoundWorktime": "Heures de travail non trouvées. Veuillez contacter l'administrateur.",
+  "seeAll": "Voir tout",
+  "quickAction": "Action rapide",
+  "quickDesc": "Pour que votre demande soit plus rapide.",
+  "times": " fois",
+  "search": "Recherche",
+  "searchAsk": "Rechercher...",
+  "helpHint": "Essayez d'écrire votre problème...",
+  "inProgress": " votre demande est en cours de traitement.",
+  "currentActiveText": "Votre rapport en cours de traitement",
+  "photo": "Image",
+  "selectRecipient": "Sélectionner le destinataire",
+  "composeMessage": "Rédiger un message",
+  "to": "À",
+  "addAttachment": "Ajouter une pièce jointe",
+  "editAttachment": "Modifier la pièce jointe",
+  "unsupportedFile": "Format de fichier non pris en charge",
+  "pdfFile": "Fichier PDF",
+  "other": "autres",
+  "noMessageText": "Aucun message à afficher.",
+  "noDataText": "Aucune donnée à afficher.",
+  "noPIDText": "Aucun code d'accès trouvé.",
+  "noPIDText2": "Obtenez le code d'accès en scannant le QR ou via le lien fourni par le titulaire de la licence TelMessenger.",
+  "loading": "Chargement...",
+  "backToLogin": "Retour à la page de connexion",
+  "deleteHistory": "Supprimer l'historique",
+  "deleteHistoryConfirm": "Êtes-vous sûr de vouloir supprimer ces données ?",
+  "couldnotDelete": "Ces données ne peuvent pas être supprimées.",
+  "allInformants": "Tous les rapporteurs",
+  "welcome": "Bienvenue",
+  "createNewPassText": "Veuillez créer un nouveau mot de passe avant d'utiliser l'application.",
+  "description": "Description",
+  "sorry": "Désolé..!",
+  "outOfWorkTime": "Votre rapport est actuellement en dehors des heures de travail de l'exécutant.",
+  "willResponse": "Ils reviendront le : ",
+  "canNotCancel1": "Ce rapport ne peut pas être annulé une fois qu'il a été traité par l'exécutant.",
+  "canNotCancel2": "Ce rapport ne peut pas être annulé.",
+  "priorityHigh": "Élevée",
+  "priorityLow": "Faible",
+  "priorityNormal": "Normale",
+  "priority": "Priorité",
+  "reported": "Signalé",
+  "escalationNoteBySystem": "Nomination",
+  "finishAttachment": "Pièce jointe de clôture",
+  "pleaseScan": "Veuillez scanner le code QR",
+  "mustScan": "Vous devez scanner le code QR pour soumettre à nouveau.",
+  "changeCodeMsg": "Vous serez connecté à <company_name>. Voulez-vous continuer ?",
+  "currentServer": "Serveur actuel",
+  "destinationServer": "Serveur de destination",
+  "textContinue": "Continuer",
+  "transferNoteBySystem": "Transfert",
+  "activity": "Activité",
+  "image": "Image",
+  "titlePassOld": "Entrez votre ancien mot de passe",
+  "titlePassNew": "Votre ancien mot de passe sera remplacé par le nouveau.",
+  "setting": "Paramètres",
+  "more": "Plus",
+  "topMenu": "Menu principal",
+  "availableMenu": "Menu disponible",
+  "customize": "Personnaliser",
+  "done": "Terminé",
+  "ongoing": "En cours",
+  "maxMenu": "Maximum de 7 menus.",
+  "searchIn": "Rechercher dans",
+  "yourLocation": "Votre emplacement",
+  "addImage": "Souhaitez-vous ajouter une image ?",
+  "addImageMsg": "Vous pouvez envoyer une image comme indication pour l'exécutant.",
+  "removeImageMsg": "Je pense que ce n'est pas nécessaire. Supprimez simplement l'image.",
+  "sendingOpt": "Options d'envoi",
+  "sendingOptMsg": "Souhaitez-vous envoyer comme demande planifiée ou immédiatement ?",
+  "scheduled": "Planifier",
+  "sendNow": "Envoyer maintenant",
+  "scheduledMsg": "* Planifier = la demande sera envoyée à l'heure que vous avez choisie dans la note.",
+  "reqSuccessMsg": "Veuillez patienter. Votre demande est en cours de traitement.",
+  "noImgAttach": "Aucune image jointe.",
+  "btnWait": "OK, je vais attendre",
+  "rateReq": "Évaluer votre demande",
+  "reqAgain": "Demander à nouveau",
+  "notAvailable": "Ce menu n'est pas encore disponible.",
+  "emptyMenuAlert": "Aucun menu sélectionné.",
+  "redirecting": "Chargement...",
+  "initializing": "Traitement...",
+  "selectAlert": "Veuillez d'abord sélectionner un menu.",
+  "scopeDesc": "Utilisez ceci pour changer votre portée.",
+  "notFoundKeyword": "Aucune donnée trouvée correspondant au mot-clé ",
+  "findQRcode": "Trouvez le code QR autour de vous",
+  "isWorking": " est en train de traiter votre demande.",
+  "noteLongAlert": "Vous ne pouvez ajouter qu'une note de 128 caractères maximum.",
+  "reqCodeNotFound": "Code de rapport invalide. Veuillez contacter l'administrateur.",
+  "noImage": "Pas d'image",
+  "related": "Services associés",
+  "broadcastPermission": "Impossible d'envoyer le message, vous n'avez pas la permission.",
+  "broadcastTenant": "Impossible d'envoyer le message, le locataire destinataire n'est pas valide.",
+  "reqForOthers": "Bien sûr, j'aime aider les autres.",
+  "reqForMySelf": "Non, cette demande est pour moi.",
+  "requestedBy": "Demandé par",
+  "requestedFor": "Demandé pour",
+  "typeId": "Entrez l'ID de cette personne",
+  "idNotMatch": "Échec de l'envoi de la demande. Cette demande n'est pas autorisée pour l'ID #.",
+  "idNotFound": "Impossible de trouver l'ID fourni. Veuillez essayer un autre ID.",
+  "informantNotRegistered": "Vous n'êtes pas enregistré comme réceptionniste ou agent.",
+  "link_copied": "Le lien a été copié dans le presse-papiers.",
+  "do_not_disturb": "Ne pas déranger",
+  "set_dnd_status": "Définir le statut DND",
+  "active_dnd": "Actif",
+  "inactive_dnd": "Inactif",
+  "msg_dnd": "Vous pouvez désactiver le statut DND via le menu Compte.",
+  "msg_change_active": "Souhaitez-vous activer le statut DND ?",
+  "msg_change_inactive": "Souhaitez-vous désactiver le statut DND ?",
+  "info_dnd": "Statut DND activé.",
+  "invalid_bridge": "URL de passerelle invalide.",
+  "someone_else": "Souhaitez-vous faire une demande au nom de quelqu'un d'autre ?",
+  "askScheduleTime": "Quand votre demande doit-elle être traitée ?",
+  "scheduleMessage": "Votre demande sera traitée plus tard à",
+  "scan_qr": "Afficher mon QR",
+  "show_qr": "Afficher le QR",
+  "show_qr_desc": "Montrez ce QR à vos amis ou collègues qui ont besoin d'accéder à l'application.",
+  "dismiss": "Ignorer",
+  "not_allow_permission": "Votre appareil n'autorise pas l'application à recevoir des notifications.",
+  "tap_here": "Appuyez ici pour accéder aux paramètres.",
+  "doNotDisturb": "Ne pas déranger",
+  "info_label": "Info",
+  "display_menu": "Affichage du menu",
+  "req_group": "Groupe de rapports",
+  "ser_group": "Groupe d'exécutants",
+  "save_rating": "Enregistrer l'évaluation",
+  "auto_translate": "Traduction automatique",
+  "hold": "Suspendu",
+  "holdHistory": "Historique des suspensions",
+  "pending": "En attente",
+  "pending_info_success": "Nous avons détecté un problème de connexion. Votre demande est en attente de décision. Elle sera renvoyée lorsque vous serez de nouveau en ligne.",
+  "pending_info_tiles": "Attendez la connexion Internet pour envoyer votre demande.",
+  "pending_info_detail": "Votre demande n'a pas été envoyée en raison d'un problème de connexion.",
+  "cancel_pending": "Annuler simplement ma demande",
+  "sending": "Envoi",
+  "queued": "En file d'attente",
+  "onProgress": "En cours",
+  "showLess": "Afficher moins",
+  "customTopMenu": "Personnaliser le menu principal",
+  "limitTopMenu": "Le menu principal est limité à 7 éléments !",
+  "searchButton": "Rechercher",
+  "allInformantsInfo": "Tous les rapporteurs recevront votre message.",
+  "action": "Action",
+  "whatMakesYou": "Qu'est-ce qui vous rend",
+  "letMeWrite": "J'ai une autre opinion !",
+  "writeHere": "Écrivez ici...",
+  "ratingAspect": "Aspect de l'évaluation",
+  "sendingRequest": "Envoi de la demande",
+  "changePriority": "Changer la priorité",
+  "referenceNumber": "Numéro de référence",
+  "enterTicketNumber": "Entrez le numéro du ticket",
+  "subject": "Sujet",
+  "requestReference": "Référence de la demande",
+  "endSessionTitle": "Votre session est terminée.",
+  "endSessionDesc": "Oh, désolé. Il semble que vous n'ayez pas utilisé l'application depuis un moment. Pour des raisons de sécurité, veuillez vous reconnecter avant de continuer.",
+  "invalidParentTicket": "Numéro de référence invalide.",
+  "failedLoad": "Échec du chargement de l'application.",
+  "tap2retry": "Appuyez pour réessayer.",
+  "chat": "Conversation"
+}

+ 337 - 0
assets/lang/hi.json

@@ -0,0 +1,337 @@
+{
+  "account": "खाता",
+  "addNote": "नोट जोड़ें",
+  "addNoteCancel": "रद्द करने का नोट जोड़ें",
+  "all": "सभी",
+  "anyNote": "क्या अधिकारी के लिए कोई नोट है?",
+  "appointment": "नियुक्ति",
+  "attach": "छवि संलग्न करें",
+  "buttonCancel": "मिशन रद्द करें",
+  "buttonLogin": "लॉगिन करें",
+  "buttonNo": "नहीं",
+  "buttonResubmit": "फिर से रिपोर्ट करें",
+  "buttonScan": "QR स्कैन करें",
+  "buttonSendEmergency": "आपातकाल भेजें",
+  "buttonSendComplaint": "शिकायत भेजें",
+  "buttonSendRequest": "अनुरोध भेजें",
+  "buttonYes": "हाँ",
+  "buttonSure": "हाँ, निश्चित",
+  "buttonBack": "वापस जाएं",
+  "canceledBy": "द्वारा रद्द किया गया",
+  "category": "श्रेणी",
+  "scope": "दायरा",
+  "changeScope": "दायरा बदलें",
+  "chatClosed": "यह बातचीत बंद हो गई है।",
+  "chooseService": "वांछित सेवा चुनें",
+  "complaintText": "क्या समस्या है? विशेषज्ञों को इसे संभालने दें।",
+  "complaint": "शिकायत",
+  "defaultLocation": "डिफ़ॉल्ट स्थान",
+  "detail": "विवरण",
+  "disatisfied": "असंतुष्ट",
+  "emergencyText": "सभी तात्कालिक और गंभीर मामलों को तुरंत संभालना आवश्यक है।",
+  "emergency": "आपातकाल",
+  "forum": "फोरम",
+  "frequentlyText": "जो आप अक्सर मांगते हैं, हमने उन्हें यहाँ इकट्ठा किया है।",
+  "frequently": "अक्सर अनुरोधित",
+  "frequentlyTitle": "अक्सर अनुरोधित",
+  "frequentlyTitleText": "अक्सर अनुरोधित रिपोर्टें",
+  "history": "इतिहास",
+  "lessSatisfied": "कम संतुष्ट",
+  "location": "स्थान",
+  "logout": "लॉगआउट करें",
+  "menuAccount": "खाता",
+  "menuHistory": "इतिहास",
+  "menuService": "सेवा",
+  "menuHome": "होम",
+  "message": "संदेश",
+  "note": "नोट",
+  "passwordEmpty": "पासवर्ड खाली नहीं हो सकता",
+  "password": "पासवर्ड",
+  "phoneNumber": "फोन नंबर",
+  "placeholderLocation": "स्थान दर्ज करें",
+  "placeholderNote": "नोट लिखें",
+  "rateMission": "इस मिशन को रेट करें",
+  "reallyPleased": "बहुत संतुष्ट",
+  "requestText2": "क्या अनुरोध किया गया है?",
+  "requestDesc": "अगर आपको मदद चाहिए, तो संकोच न करें।",
+  "requestTitle": "आपको किस चीज़ में मदद चाहिए?",
+  "request": "अनुरोध",
+  "satisfactionAsk": "क्या आप संतुष्ट हैं?",
+  "satisfied": "काफी संतुष्ट",
+  "scanQr": "ऐप का उपयोग शुरू करने के लिए कृपया पहले QR स्कैन करें।",
+  "serialNumber": "क्रम संख्या",
+  "servantGroup": "रिपोर्ट समूह",
+  "servant": "कार्यान्वयनकर्ता",
+  "service": "सेवा",
+  "specifyAnotherLocation": "मैं किसी अन्य स्थान पर हूँ",
+  "specifyLocation": "स्थान निर्दिष्ट करें",
+  "startDoing": "शुरू करें",
+  "stateCancel": "रद्द करें",
+  "stateCanceled": "रद्द किया गया",
+  "stateDone": "किया गया",
+  "stateFinish": "पूरा हुआ",
+  "stateQueue": "प्रतीक्षा सूची",
+  "stateRequested": "अनुरोधित",
+  "state": "स्थिति",
+  "sure": "क्या आप निश्चित हैं?",
+  "switch": "स्विच करें",
+  "system": "सिस्टम",
+  "textCancel": "रद्द करें",
+  "textHistory": "आपके द्वारा बनाई गई रिपोर्टें",
+  "textLogout": "आपको अब सूचनाएं नहीं मिलेंगी और आप रिपोर्ट नहीं बना पाएंगे। क्या आप वाकई लॉगआउट करना चाहते हैं?",
+  "textResubmit": "आप यह रिपोर्ट फिर से अधिकारी को भेजेंगे।",
+  "ticketNumber": "टिकट नंबर",
+  "timeline": "समयरेखा",
+  "today": "आज",
+  "unrated": "इस मिशन के लिए कोई रेटिंग नहीं है",
+  "userEmpty": "उपयोगकर्ता नाम खाली नहीं हो सकता",
+  "userId": "उपयोगकर्ता नाम",
+  "version": "संस्करण",
+  "verySatisfied": "संतुष्ट",
+  "whatComplain": "शिकायत क्या है?",
+  "whatHappen": "क्या हुआ?",
+  "whatNext": "शांत रहें। अगली जानकारी क्या है?",
+  "whatProblem": "क्या समस्या है?",
+  "writeMessage": "संदेश लिखें",
+  "yesterday": "कल",
+  "language": "भाषा",
+  "bahasa": "Indonesia",
+  "english": "English",
+  "japanese": "日本語",
+  "korean": "한국어",
+  "chinese": "中文",
+  "german": "Deutsch",
+  "french": "Français",
+  "hindi": "हिंदी",
+  "arabic": "العربية",
+  "dutch": "Nederlands",
+  "chooseLanguage": "भाषा चुनें",
+  "rate": "रेटिंग",
+  "rated": "रेटिंग दी जा चुकी है।",
+  "rateText": "आप इस मिशन के लिए [satisfaction] हैं।",
+  "settingPassword": "पासवर्ड बदलें",
+  "oldPassword": "पुराना पासवर्ड",
+  "newPassword": "नया पासवर्ड",
+  "confirmPassword": "नया पासवर्ड पुष्टि करें",
+  "buttonSave": "नया पासवर्ड सहेजें",
+  "settingPasswordText": "यहाँ अपना पासवर्ड सेट करें",
+  "autoResponseText": "आपका अनुरोध सिस्टम द्वारा तुरंत संसाधित किया जाएगा।",
+  "reply": "उत्तर",
+  "selectPicture": "मीडिया चुनें",
+  "fromGallery": "गैलरी से",
+  "fromCamera": "फोटो लें",
+  "sendPicture": "छवि भेजें",
+  "seeAttachment": "संलग्नक देखें",
+  "confirmCancel": "क्या आप वाकई रद्द करना चाहते हैं?",
+  "selectDate": "तारीख चुनें",
+  "lengthMax": "नोट की अधिकतम लंबाई 128 अक्षर है।",
+  "locRequired": "स्थान खाली नहीं हो सकता।",
+  "idRequired": "ID खाली नहीं हो सकता।",
+  "dateRequired": "तारीख खाली नहीं हो सकती।",
+  "sendLater": "बाद में भेजें",
+  "thank": "धन्यवाद",
+  "pleaseWait": "कृपया प्रतीक्षा करें",
+  "sendSuccess": "सफलतापूर्वक भेजा गया",
+  "followUp": "अधिकारी जल्द ही कार्रवाई करेगा।",
+  "okGot": "ठीक है, समझ गया",
+  "okWait": "ठीक है, प्रतीक्षा करें",
+  "errorConnection": "कनेक्शन त्रुटि। कृपया बाद में पुनः प्रयास करें।",
+  "noInternet": "ओह, इंटरनेट बंद है।",
+  "invalidLogin": "उपयोगकर्ता नाम या पासवर्ड गलत है।",
+  "invalidPhone": "फोन नंबर पंजीकृत नहीं है।",
+  "expAccount": "खाता समाप्त हो गया है।",
+  "errorServer": "सर्वर में त्रुटि हुई। कृपया बाद में पुनः प्रयास करें।",
+  "accessDenied": "पहुंच अस्वीकृत",
+  "licenseProblem": "लाइसेंस समस्या के कारण ऐप का उपयोग नहीं किया जा सकता।",
+  "invalidVersion": "ऐप संस्करण असंगत है",
+  "sugestVersion": "इस संस्करण के साथ ऐप का उपयोग करें",
+  "currentVersion": "स्थापित ऐप संस्करण है",
+  "contactAdmin": "अधिक जानकारी के लिए कृपया व्यवस्थापक से संपर्क करें।",
+  "pressAgain": "बाहर निकलने के लिए फिर से दबाएं।",
+  "notFound": "डेटा नहीं मिला।",
+  "notFound2": "जो आप खोज रहे हैं वह यहाँ नहीं है..!",
+  "noInternetTitle": "कोई इंटरनेट कनेक्शन नहीं।",
+  "noInternetDesc": "पहले अपना इंटरनेट कनेक्शन जांचें। फिर हम आगे बढ़ेंगे!",
+  "errorConnectTitle": "सर्वर से कनेक्ट करने में विफल।",
+  "errorConnectDesc": "व्यवस्थापक से पूछें, आपको सर्वर से जुड़ना चाहिए।",
+  "errorServerTitle": "सर्वर में समस्या है।",
+  "errorServerDesc": "घबराएं नहीं, हम सब कुछ नियंत्रित करने की पूरी कोशिश कर रहे हैं।",
+  "invalidAccountTitle": "खाता पंजीकृत नहीं है।",
+  "invalidAccountDesc": "उफ़! आपका खाता सत्यापित नहीं हो सका। कृपया व्यवस्थापक से संपर्क करें।",
+  "expAccountTitle": "खाता समाप्त हो गया है।",
+  "expAccountDesc": "आप ऐप में लॉगिन नहीं कर सकते क्योंकि आपका खाता समाप्त हो गया है। यदि आपको लगता है कि यह एक गलती है, तो कृपया व्यवस्थापक से संपर्क करें।",
+  "inProcess": "प्रसंस्करण में है",
+  "policy": "गोपनीयता नीति",
+  "info": "अतिरिक्त जानकारी",
+  "refresh": "रीफ़्रेश करें",
+  "messageCamPermission": "कैमरा एक्सेस अस्वीकृत। सेटिंग्स से TelMessenger को कैमरा एक्सेस की अनुमति दें।",
+  "messageInvalidCode": "अमान्य कोड। कृपया व्यवस्थापक से संपर्क करें।",
+  "messagePassChanged": "पासवर्ड सफलतापूर्वक बदला गया।",
+  "wrongOldPass": "पुराना पासवर्ड गलत है।",
+  "checkNewPass": "नया पासवर्ड दोबारा जांचें।",
+  "cannotEmpty": "डेटा खाली नहीं हो सकता।",
+  "deletedMessage": "यह संदेश हटा दिया गया है।",
+  "resend": "फिर से भेजें",
+  "delete": "हटाएं",
+  "alreadyUsePass": "यह पासवर्ड उपयोग नहीं किया जा सकता, कृपया कोई और प्रयास करें।",
+  "notFoundImg": "छवि नहीं मिली",
+  "notFoundWorktime": "कार्य समय नहीं मिला। कृपया व्यवस्थापक से संपर्क करें।",
+  "seeAll": "सभी देखें",
+  "quickAction": "त्वरित कार्रवाई",
+  "quickDesc": "ताकि आपका अनुरोध जल्दी पूरा हो सके।",
+  "times": " बार",
+  "search": "खोज",
+  "searchAsk": "खोजें...",
+  "helpHint": "अपनी समस्या लिखने की कोशिश करें...",
+  "inProgress": " आपका अनुरोध प्रसंस्करण में है।",
+  "currentActiveText": "आपकी रिपोर्ट जो अभी भी प्रसंस्करण में है",
+  "photo": "छवि",
+  "selectRecipient": "प्राप्तकर्ता चुनें",
+  "composeMessage": "संदेश लिखें",
+  "to": "को",
+  "addAttachment": "संलग्नक जोड़ें",
+  "editAttachment": "संलग्नक संपादित करें",
+  "unsupportedFile": "फ़ाइल प्रारूप समर्थित नहीं है",
+  "pdfFile": "PDF फ़ाइल",
+  "other": "अन्य",
+  "noMessageText": "कोई संदेश प्रदर्शित नहीं किया गया।",
+  "noDataText": "प्रदर्शित करने के लिए कोई डेटा नहीं है।",
+  "noPIDText": "कोई एक्सेस कोड नहीं मिला।",
+  "noPIDText2": "QR स्कैन करके या TelMessenger लाइसेंसधारी द्वारा प्रदान किए गए लिंक से एक्सेस कोड प्राप्त करें।",
+  "loading": "लोड हो रहा है...",
+  "backToLogin": "लॉगिन पृष्ठ पर वापस जाएं",
+  "deleteHistory": "इतिहास डेटा हटाएं",
+  "deleteHistoryConfirm": "क्या आप वाकई इस डेटा को हटाना चाहते हैं?",
+  "couldnotDelete": "इस डेटा को हटाया नहीं जा सकता।",
+  "allInformants": "सभी रिपोर्टकर्ता",
+  "welcome": "स्वागत है",
+  "createNewPassText": "ऐप का उपयोग शुरू करने से पहले कृपया नया पासवर्ड बनाएं।",
+  "description": "विवरण",
+  "sorry": "माफ़ कीजिए..!",
+  "outOfWorkTime": "आपकी रिपोर्ट वर्तमान में कार्य समय के बाहर है।",
+  "willResponse": "वे फिर से उपलब्ध होंगे: ",
+  "canNotCancel1": "यह रिपोर्ट रद्द नहीं की जा सकती क्योंकि इसे पहले ही निष्पादित किया जा चुका है।",
+  "canNotCancel2": "यह रिपोर्ट रद्द नहीं की जा सकती।",
+  "priorityHigh": "उच्च",
+  "priorityLow": "निम्न",
+  "priorityNormal": "सामान्य",
+  "priority": "प्राथमिकता",
+  "reported": "रिपोर्ट किया गया",
+  "escalationNoteBySystem": "नियुक्ति",
+  "finishAttachment": "समाप्ति संलग्नक",
+  "pleaseScan": "कृपया QR कोड स्कैन करें",
+  "mustScan": "आपको फिर से रिपोर्ट करने के लिए QR कोड स्कैन करना होगा।",
+  "changeCodeMsg": "आप <company_name> से जुड़ने जा रहे हैं। क्या आप जारी रखना चाहते हैं?",
+  "currentServer": "वर्तमान सर्वर",
+  "destinationServer": "गंतव्य सर्वर",
+  "textContinue": "जारी रखें",
+  "transferNoteBySystem": "स्थानांतरण",
+  "activity": "गतिविधि",
+  "image": "छवि",
+  "titlePassOld": "अपना पुराना पासवर्ड दर्ज करें",
+  "titlePassNew": "आपका पुराना पासवर्ड नए से बदल दिया जाएगा।",
+  "setting": "सेटिंग्स",
+  "more": "अधिक",
+  "topMenu": "मुख्य मेनू",
+  "availableMenu": "उपलब्ध मेनू",
+  "customize": "अनुकूलित करें",
+  "done": "हो गया",
+  "ongoing": "चल रहा है",
+  "maxMenu": "अधिकतम 7 मेनू आइटम।",
+  "searchIn": "में खोजें",
+  "yourLocation": "आपका स्थान",
+  "addImage": "क्या आप छवि जोड़ना चाहते हैं?",
+  "addImageMsg": "आप कार्यान्वयनकर्ता के लिए संकेत के रूप में छवि भेज सकते हैं।",
+  "removeImageMsg": "मुझे लगता है कि इसकी आवश्यकता नहीं है। बस छवि हटा दें।",
+  "sendingOpt": "भेजने के विकल्प",
+  "sendingOptMsg": "क्या आप इसे शेड्यूल अनुरोध के रूप में भेजना चाहते हैं या तुरंत?",
+  "scheduled": "शेड्यूल करें",
+  "sendNow": "अभी भेजें",
+  "scheduledMsg": "* शेड्यूल = अनुरोध उस समय भेजा जाएगा जिसे आपने नोट में चुना है।",
+  "reqSuccessMsg": "कृपया प्रतीक्षा करें। आपका अनुरोध संसाधित किया जा रहा है।",
+  "noImgAttach": "कोई छवि संलग्न नहीं है।",
+  "btnWait": "ठीक है, मैं प्रतीक्षा करूंगा",
+  "rateReq": "अपने अनुरोध को रेट करें",
+  "reqAgain": "फिर से अनुरोध करें",
+  "notAvailable": "यह मेनू अभी उपलब्ध नहीं है।",
+  "emptyMenuAlert": "कोई मेनू चयनित नहीं है।",
+  "redirecting": "लोड हो रहा है...",
+  "initializing": "प्रारंभ किया जा रहा है...",
+  "selectAlert": "कृपया पहले एक मेनू चुनें।",
+  "scopeDesc": "अपना कार्य क्षेत्र बदलने के लिए इसका उपयोग करें।",
+  "notFoundKeyword": "खोज शब्द के लिए कोई डेटा नहीं मिला ",
+  "findQRcode": "अपने आस-पास QR कोड खोजें",
+  "isWorking": " आपके अनुरोध पर कार्य कर रहा है।",
+  "noteLongAlert": "आप केवल अधिकतम 128 वर्णों का नोट जोड़ सकते हैं।",
+  "reqCodeNotFound": "रिपोर्ट कोड अमान्य है। कृपया व्यवस्थापक से संपर्क करें।",
+  "noImage": "कोई छवि नहीं",
+  "related": "संबंधित सेवाएं",
+  "broadcastPermission": "संदेश नहीं भेजा जा सकता, आपके पास अनुमति नहीं है।",
+  "broadcastTenant": "संदेश नहीं भेजा जा सकता, प्राप्तकर्ता टेनेंट अमान्य है।",
+  "reqForOthers": "बिलकुल, मुझे दूसरों की मदद करना पसंद है।",
+  "reqForMySelf": "नहीं, यह अनुरोध मेरे लिए है।",
+  "requestedBy": "द्वारा अनुरोधित",
+  "requestedFor": "के लिए अनुरोधित",
+  "typeId": "उस व्यक्ति की ID दर्ज करें",
+  "idNotMatch": "अनुरोध भेजने में विफल। यह अनुरोध #ID द्वारा नहीं किया जा सकता।",
+  "idNotFound": "दर्ज की गई ID नहीं मिली। कृपया कोई अन्य ID आज़माएं।",
+  "informantNotRegistered": "आप रिसेप्शनिस्ट या रूम स्टाफ के रूप में पंजीकृत नहीं हैं।",
+  "link_copied": "लिंक क्लिपबोर्ड पर कॉपी कर लिया गया है।",
+  "do_not_disturb": "डू नॉट डिस्टर्ब",
+  "set_dnd_status": "DND स्थिति सेट करें",
+  "active_dnd": "सक्रिय",
+  "inactive_dnd": "निष्क्रिय",
+  "msg_dnd": "आप खाता मेनू से DND स्थिति को अक्षम कर सकते हैं।",
+  "msg_change_active": "क्या आप DND स्थिति को सक्रिय करना चाहते हैं?",
+  "msg_change_inactive": "क्या आप DND स्थिति को निष्क्रिय करना चाहते हैं?",
+  "info_dnd": "DND स्थिति सक्रिय की गई।",
+  "invalid_bridge": "अमान्य ब्रिज URL।",
+  "someone_else": "क्या आप किसी और के लिए अनुरोध करना चाहते हैं?",
+  "askScheduleTime": "आपका अनुरोध कब निष्पादित किया जाना चाहिए?",
+  "scheduleMessage": "आपका अनुरोध बाद में निष्पादित किया जाएगा",
+  "scan_qr": "मेरा QR दिखाएं",
+  "show_qr": "QR दिखाएं",
+  "show_qr_desc": "इस QR को उन दोस्तों या सहयोगियों को दिखाएं जिन्हें ऐप का उपयोग करना है।",
+  "dismiss": "छोड़ें",
+  "not_allow_permission": "आपका डिवाइस ऐप को सूचनाएं प्राप्त करने की अनुमति नहीं देता है।",
+  "tap_here": "सेटिंग मेनू पर जाने के लिए यहां टैप करें।",
+  "doNotDisturb": "परेशान न करें",
+  "info_label": "जानकारी",
+  "display_menu": "मेनू प्रदर्शन",
+  "req_group": "रिपोर्ट समूह",
+  "ser_group": "कार्यान्वयन समूह",
+  "save_rating": "रेटिंग सहेजें",
+  "auto_translate": "स्वचालित अनुवाद",
+  "hold": "स्थगित",
+  "holdHistory": "स्थगन इतिहास",
+  "pending": "लंबित",
+  "pending_info_success": "हमें एक कनेक्शन समस्या मिली है। आपका अनुरोध निर्णय की प्रतीक्षा कर रहा है। जब आप ऑनलाइन होंगे, तो हम इसे फिर से भेजेंगे।",
+  "pending_info_tiles": "अपना अनुरोध भेजने के लिए इंटरनेट कनेक्शन की प्रतीक्षा करें।",
+  "pending_info_detail": "कनेक्शन समस्या के कारण आपका अनुरोध अभी तक नहीं भेजा गया है।",
+  "cancel_pending": "केवल मेरा अनुरोध रद्द करें",
+  "sending": "भेजा जा रहा है",
+  "queued": "प्रतीक्षा में",
+  "onProgress": "प्रगति पर है",
+  "showLess": "कम दिखाएं",
+  "customTopMenu": "मुख्य मेनू अनुकूलित करें",
+  "limitTopMenu": "मुख्य मेनू अधिकतम 7 आइटम तक सीमित है!",
+  "searchButton": "खोजें",
+  "allInformantsInfo": "आपका संदेश सभी रिपोर्टकर्ताओं को प्राप्त होगा।",
+  "action": "क्रिया",
+  "whatMakesYou": "क्या चीज़ आपको",
+  "letMeWrite": "मेरे पास एक अलग राय है!",
+  "writeHere": "यहाँ लिखें...",
+  "ratingAspect": "रेटिंग पहलू",
+  "sendingRequest": "अनुरोध भेजा जा रहा है",
+  "changePriority": "प्राथमिकता बदलें",
+  "referenceNumber": "संदर्भ संख्या",
+  "enterTicketNumber": "टिकट नंबर दर्ज करें",
+  "subject": "विषय",
+  "requestReference": "अनुरोध संदर्भ",
+  "endSessionTitle": "आपका सत्र समाप्त हो गया है।",
+  "endSessionDesc": "ओह, क्षमा करें। ऐसा लगता है कि आपने ऐप का लंबे समय से उपयोग नहीं किया है। कृपया आगे बढ़ने से पहले फिर से लॉगिन करें।",
+  "invalidParentTicket": "अमान्य संदर्भ संख्या।",
+  "failedLoad": "ऐप लोड करने में विफल।",
+  "tap2retry": "पुनः प्रयास करने के लिए टैप करें।",
+  "chat": "बातचीत"
+}

+ 9 - 4
assets/lang/id.json

@@ -95,11 +95,16 @@
    "writeMessage": "Tulis pesan",
    "yesterday": "Kemarin",
    "language": "Bahasa",
-   "bahasa": "Bahasa Indonesia",
+   "bahasa": "Indonesia",
    "english": "English",
    "japanese": "日本語",
    "korean": "한국어",
-   "chinese": "中国語",
+   "chinese": "中文",
+   "german": "Deutsch",
+   "french": "Français",
+   "hindi": "हिंदी",
+   "arabic": "العربية",
+   "dutch": "Nederlands",
    "chooseLanguage": "Pilih Bahasa",
    "rate": "Penilaian",
    "rated": "Penilaian sudah diberikan.",
@@ -327,6 +332,6 @@
    "endSessionDesc": "Oh, maaf. Sepertinya kamu sudah lama tidak menggunakan aplikasi. Biar aman silakan login ulang sebelum lanjut.",
    "invalidParentTicket": "Nomor referensi tidak valid.",
    "failedLoad": "Gagal memuat aplikasi.",
-   "tap2retry": "Ketuk untuk mencoba kembali."
-
+   "tap2retry": "Ketuk untuk mencoba kembali.",
+   "chat": "Percakapan"
 }

+ 9 - 3
assets/lang/ja.json

@@ -95,11 +95,16 @@
    "writeMessage": "メッセージを書く",
    "yesterday": "昨日",
    "language": "言語",
-   "bahasa": "Bahasa Indonesia",
+   "bahasa": "Indonesia",
    "english": "English",
    "japanese": "日本語",
    "korean": "한국어",
-   "chinese": "中国語",
+   "chinese": "中文",
+   "german": "Deutsch",
+   "french": "Français",
+   "hindi": "हिंदी",
+   "arabic": "العربية",
+   "dutch": "Nederlands",
    "chooseLanguage": "言語を選択する",
    "rate": "レート",
    "rated": "このミッションは評価されています。",
@@ -325,5 +330,6 @@
    "requestReference": "リクエストリファレンス",
    "invalidParentTicket": "親チケットが無効です。",
    "failedLoad": "アプリケーションのロードに失敗しました。",
-   "tap2retry": "タップして再試行します。"
+   "tap2retry": "タップして再試行します。",
+   "chat": "会話"
 }

+ 9 - 3
assets/lang/ko.json

@@ -95,11 +95,16 @@
    "writeMessage": "메시지 쓰기",
    "yesterday": "어제",
    "language": "언어",
-   "bahasa": "Bahasa Indonesia",
+   "bahasa": "Indonesia",
    "english": "English",
    "japanese": "日本語",
    "korean": "한국어",
-   "chinese": "中国語",
+   "chinese": "中文",
+   "german": "Deutsch",
+   "french": "Français",
+   "hindi": "हिंदी",
+   "arabic": "العربية",
+   "dutch": "Nederlands",
    "chooseLanguage": "언어 선택",
    "rate": "비율",
    "rated": "이 임무는 평가되었습니다.",
@@ -325,5 +330,6 @@
    "requestReference": "참조 요청",
    "invalidParentTicket": "부모 티켓이 유효하지 않습니다.",
    "failedLoad": "애플리케이션을 로드하지 못했습니다.",
-   "tap2retry": "다시 시도하려면 탭하세요."
+   "tap2retry": "다시 시도하려면 탭하세요.",
+   "chat": "대화"
 }

+ 337 - 0
assets/lang/nl.json

@@ -0,0 +1,337 @@
+{
+  "account": "Account",
+  "addNote": "Notitie toevoegen",
+  "addNoteCancel": "Annuleringsnotitie toevoegen",
+  "all": "Alles",
+  "anyNote": "Is er een notitie voor de medewerker?",
+  "appointment": "Aanwijzing",
+  "attach": "Afbeelding bijvoegen",
+  "buttonCancel": "Missie annuleren",
+  "buttonLogin": "Inloggen",
+  "buttonNo": "NEE",
+  "buttonResubmit": "OPNIEUW MELDEN",
+  "buttonScan": "QR scannen",
+  "buttonSendEmergency": "NOODGEVAL VERZENDEN",
+  "buttonSendComplaint": "KLACHT VERZENDEN",
+  "buttonSendRequest": "Verzoek verzenden",
+  "buttonYes": "JA",
+  "buttonSure": "JA, ZEKER",
+  "buttonBack": "Terug",
+  "canceledBy": "Geannuleerd door",
+  "category": "Categorie",
+  "scope": "Bereik",
+  "changeScope": "Bereik wijzigen",
+  "chatClosed": "Dit gesprek is gesloten.",
+  "chooseService": "Kies de gewenste dienst",
+  "complaintText": "Wat is het probleem? Laat de experts het voor je oplossen.",
+  "complaint": "Klacht",
+  "defaultLocation": "Standaardlocatie",
+  "detail": "Details",
+  "disatisfied": "Ontevreden",
+  "emergencyText": "Alle urgente en kritieke zaken die onmiddellijk moeten worden afgehandeld.",
+  "emergency": "Noodgeval",
+  "forum": "Forum",
+  "frequentlyText": "Wat je vaak vraagt, hebben we hier verzameld.",
+  "frequently": "Vaak gevraagd",
+  "frequentlyTitle": "Vaak gevraagd",
+  "frequentlyTitleText": "Vaak aangevraagde rapporten",
+  "history": "Geschiedenis",
+  "lessSatisfied": "Minder tevreden",
+  "location": "Locatie",
+  "logout": "Uitloggen",
+  "menuAccount": "Account",
+  "menuHistory": "Geschiedenis",
+  "menuService": "Dienst",
+  "menuHome": "Startpagina",
+  "message": "Bericht",
+  "note": "Notitie",
+  "passwordEmpty": "Wachtwoord mag niet leeg zijn",
+  "password": "Wachtwoord",
+  "phoneNumber": "Telefoonnummer",
+  "placeholderLocation": "Voer locatie in",
+  "placeholderNote": "Schrijf een notitie",
+  "rateMission": "Beoordeel deze missie",
+  "reallyPleased": "Zeer tevreden",
+  "requestText2": "Wat wordt er gevraagd?",
+  "requestDesc": "Als je hulp nodig hebt, aarzel dan niet.",
+  "requestTitle": "Waar heb je hulp bij nodig?",
+  "request": "Verzoek",
+  "satisfactionAsk": "Ben je tevreden?",
+  "satisfied": "Redelijk tevreden",
+  "scanQr": "Scan eerst de QR-code om de app te gebruiken.",
+  "serialNumber": "Serienummer",
+  "servantGroup": "Rapportgroep",
+  "servant": "Uitvoerder",
+  "service": "Dienst",
+  "specifyAnotherLocation": "Ik ben op een andere locatie",
+  "specifyLocation": "Locatie opgeven",
+  "startDoing": "Beginnen",
+  "stateCancel": "Annuleren",
+  "stateCanceled": "Geannuleerd",
+  "stateDone": "Uitgevoerd",
+  "stateFinish": "Voltooid",
+  "stateQueue": "Wachtrij",
+  "stateRequested": "Aangevraagd",
+  "state": "Status",
+  "sure": "Weet je het zeker?",
+  "switch": "Overschakelen",
+  "system": "Systeem",
+  "textCancel": "Annuleren",
+  "textHistory": "Rapporten die je hebt gemaakt",
+  "textLogout": "Je ontvangt geen meldingen meer en kunt geen rapporten meer maken. Weet je zeker dat je wilt uitloggen?",
+  "textResubmit": "Je gaat dit rapport opnieuw naar de medewerker sturen.",
+  "ticketNumber": "Ticketnummer",
+  "timeline": "Tijdlijn",
+  "today": "Vandaag",
+  "unrated": "Geen beoordeling voor deze missie",
+  "userEmpty": "Gebruikersnaam mag niet leeg zijn",
+  "userId": "Gebruikersnaam",
+  "version": "Versie",
+  "verySatisfied": "Tevreden",
+  "whatComplain": "Wat is de klacht?",
+  "whatHappen": "Wat is er gebeurd?",
+  "whatNext": "Blijf rustig. Wat is de volgende stap?",
+  "whatProblem": "Wat is het probleem?",
+  "writeMessage": "Bericht schrijven",
+  "yesterday": "Gisteren",
+  "language": "Taal",
+  "bahasa": "Indonesia",
+  "english": "English",
+  "japanese": "日本語",
+  "korean": "한국어",
+  "chinese": "中文",
+  "german": "Deutsch",
+  "french": "Français",
+  "hindi": "हिंदी",
+  "arabic": "العربية",
+  "dutch": "Nederlands",
+  "chooseLanguage": "Kies taal",
+  "rate": "Beoordeling",
+  "rated": "Beoordeling is al gegeven.",
+  "rateText": "Je bent [satisfaction] over deze missie.",
+  "settingPassword": "Wachtwoord wijzigen",
+  "oldPassword": "Oud wachtwoord",
+  "newPassword": "Nieuw wachtwoord",
+  "confirmPassword": "Nieuw wachtwoord bevestigen",
+  "buttonSave": "Nieuw wachtwoord opslaan",
+  "settingPasswordText": "Stel hier je wachtwoord in",
+  "autoResponseText": "Je verzoek wordt direct door het systeem behandeld.",
+  "reply": "Antwoord",
+  "selectPicture": "Media selecteren",
+  "fromGallery": "Uit galerij",
+  "fromCamera": "Foto maken",
+  "sendPicture": "Afbeelding verzenden",
+  "seeAttachment": "Bijlage bekijken",
+  "confirmCancel": "Weet je zeker dat je wilt annuleren?",
+  "selectDate": "Datum kiezen",
+  "lengthMax": "Maximale lengte van notitie is 128 tekens.",
+  "locRequired": "Locatie mag niet leeg zijn.",
+  "idRequired": "ID mag niet leeg zijn.",
+  "dateRequired": "Datum mag niet leeg zijn.",
+  "sendLater": "Later verzenden",
+  "thank": "Dank je wel",
+  "pleaseWait": "Even geduld",
+  "sendSuccess": "Succesvol verzonden",
+  "followUp": "De medewerker zal dit snel opvolgen.",
+  "okGot": "OK, BEGREPEN",
+  "okWait": "OK, WACHTEN",
+  "errorConnection": "Verbindingsfout. Probeer het later opnieuw.",
+  "noInternet": "Oeps, geen internetverbinding.",
+  "invalidLogin": "Gebruikersnaam of wachtwoord is onjuist.",
+  "invalidPhone": "Telefoonnummer niet geregistreerd.",
+  "expAccount": "Account is verlopen.",
+  "errorServer": "Er is een fout opgetreden op de server. Probeer het later opnieuw.",
+  "accessDenied": "Toegang geweigerd",
+  "licenseProblem": "App kan niet worden gebruikt vanwege een licentieprobleem.",
+  "invalidVersion": "App-versie is niet compatibel",
+  "sugestVersion": "Gebruik de app-versie",
+  "currentVersion": "Geïnstalleerde app-versie is",
+  "contactAdmin": "Neem contact op met de beheerder voor meer informatie.",
+  "pressAgain": "Druk nogmaals om af te sluiten.",
+  "notFound": "Gegevens niet gevonden.",
+  "notFound2": "Wat je zoekt is hier niet beschikbaar..!",
+  "noInternetTitle": "Geen internetverbinding.",
+  "noInternetDesc": "Controleer eerst je internetverbinding. Daarna gaan we verder!",
+  "errorConnectTitle": "Verbinding met server mislukt.",
+  "errorConnectDesc": "Vraag het aan de beheerder, je zou verbinding moeten kunnen maken met de server.",
+  "errorServerTitle": "Er is een probleem met de server.",
+  "errorServerDesc": "Geen paniek, we doen ons best om alles onder controle te krijgen.",
+  "invalidAccountTitle": "Account niet geregistreerd.",
+  "invalidAccountDesc": "Oeps! Je account kon niet worden geverifieerd. Neem contact op met de beheerder.",
+  "expAccountTitle": "Account is verlopen.",
+  "expAccountDesc": "Je kunt de app niet gebruiken omdat je account is verlopen. Als je denkt dat dit een fout is, neem dan contact op met de beheerder.",
+  "inProcess": "Wordt verwerkt",
+  "policy": "Privacybeleid",
+  "info": "Aanvullende informatie",
+  "refresh": "Vernieuwen",
+  "messageCamPermission": "Toegang tot camera geweigerd. Sta TelMessenger toe toegang te krijgen via instellingen.",
+  "messageInvalidCode": "Ongeldige code. Neem contact op met de beheerder.",
+  "messagePassChanged": "Wachtwoord succesvol gewijzigd.",
+  "wrongOldPass": "Oud wachtwoord is onjuist.",
+  "checkNewPass": "Controleer het nieuwe wachtwoord opnieuw.",
+  "cannotEmpty": "Gegevens mogen niet leeg zijn.",
+  "deletedMessage": "Dit bericht is verwijderd.",
+  "resend": "Opnieuw verzenden",
+  "delete": "Verwijderen",
+  "alreadyUsePass": "Wachtwoord kan niet worden gebruikt, probeer een ander.",
+  "notFoundImg": "Afbeelding niet gevonden",
+  "notFoundWorktime": "Werkuren niet gevonden. Neem contact op met de beheerder.",
+  "seeAll": "Alles bekijken",
+  "quickAction": "Snelle actie",
+  "quickDesc": "Zodat je verzoek sneller wordt verwerkt.",
+  "times": " keer",
+  "search": "Zoeken",
+  "searchAsk": "Zoek...",
+  "helpHint": "Probeer je probleem te beschrijven...",
+  "inProgress": " je verzoek wordt verwerkt.",
+  "currentActiveText": "Je lopende rapport",
+  "photo": "Afbeelding",
+  "selectRecipient": "Ontvanger selecteren",
+  "composeMessage": "Bericht opstellen",
+  "to": "Aan",
+  "addAttachment": "Bijlage toevoegen",
+  "editAttachment": "Bijlage bewerken",
+  "unsupportedFile": "Bestandsformaat niet ondersteund",
+  "pdfFile": "PDF-bestand",
+  "other": "overige",
+  "noMessageText": "Geen berichten om weer te geven.",
+  "noDataText": "Geen gegevens om weer te geven.",
+  "noPIDText": "Geen toegangscode gevonden.",
+  "noPIDText2": "Verkrijg de toegangscode door de QR te scannen of via de link van de TelMessenger-licentiehouder.",
+  "loading": "Bezig met laden...",
+  "backToLogin": "Terug naar inlogpagina",
+  "deleteHistory": "Verwijder geschiedenisgegevens",
+  "deleteHistoryConfirm": "Weet je zeker dat je deze gegevens wilt verwijderen?",
+  "couldnotDelete": "Deze gegevens kunnen niet worden verwijderd.",
+  "allInformants": "Alle melders",
+  "welcome": "Welkom",
+  "createNewPassText": "Maak een nieuw wachtwoord aan voordat je de app gebruikt.",
+  "description": "Beschrijving",
+  "sorry": "Sorry..!",
+  "outOfWorkTime": "Je rapport valt buiten de werktijden van de uitvoerder.",
+  "willResponse": "Ze zijn weer beschikbaar op: ",
+  "canNotCancel1": "Dit rapport kan niet worden geannuleerd zodra het is verwerkt.",
+  "canNotCancel2": "Dit rapport kan niet worden geannuleerd.",
+  "priorityHigh": "Hoog",
+  "priorityLow": "Laag",
+  "priorityNormal": "Normaal",
+  "priority": "Prioriteit",
+  "reported": "Gerapporteerd",
+  "escalationNoteBySystem": "Aanwijzing",
+  "finishAttachment": "Afsluitende bijlage",
+  "pleaseScan": "Scan de QR-code",
+  "mustScan": "Je moet de QR-code scannen om opnieuw te rapporteren.",
+  "changeCodeMsg": "Je wordt verbonden met <company_name>. Wil je doorgaan?",
+  "currentServer": "Huidige server",
+  "destinationServer": "Doelserver",
+  "textContinue": "Doorgaan",
+  "transferNoteBySystem": "Overdracht",
+  "activity": "Activiteit",
+  "image": "Afbeelding",
+  "titlePassOld": "Voer je oude wachtwoord in",
+  "titlePassNew": "Je oude wachtwoord wordt vervangen door een nieuw wachtwoord.",
+  "setting": "Instellingen",
+  "more": "Meer",
+  "topMenu": "Hoofdmenu",
+  "availableMenu": "Beschikbare menu's",
+  "customize": "Aanpassen",
+  "done": "Klaar",
+  "ongoing": "Bezig",
+  "maxMenu": "Maximaal 7 menu-items.",
+  "searchIn": "Zoeken in",
+  "yourLocation": "Je locatie",
+  "addImage": "Wil je een afbeelding toevoegen?",
+  "addImageMsg": "Je kunt een afbeelding sturen als aanwijzing voor de uitvoerder.",
+  "removeImageMsg": "Ik denk dat het niet nodig is. Verwijder gewoon de afbeelding.",
+  "sendingOpt": "Verzendopties",
+  "sendingOptMsg": "Wil je het verzoek plannen of direct verzenden?",
+  "scheduled": "Plannen",
+  "sendNow": "Nu verzenden",
+  "scheduledMsg": "* Plannen = verzoek wordt verzonden op het tijdstip dat je in de notitie hebt gekozen.",
+  "reqSuccessMsg": "Even geduld. Je verzoek wordt verwerkt.",
+  "noImgAttach": "Geen afbeelding toegevoegd.",
+  "btnWait": "Oké, ik wacht",
+  "rateReq": "Beoordeel je verzoek",
+  "reqAgain": "Opnieuw aanvragen",
+  "notAvailable": "Dit menu is nog niet beschikbaar.",
+  "emptyMenuAlert": "Er is nog geen menu geselecteerd.",
+  "redirecting": "Bezig met laden...",
+  "initializing": "Bezig met verwerken...",
+  "selectAlert": "Selecteer eerst een menu.",
+  "scopeDesc": "Gebruik dit om je bereik te wijzigen.",
+  "notFoundKeyword": "Geen gegevens gevonden voor zoekterm ",
+  "findQRcode": "Zoek de QR-code in je omgeving",
+  "isWorking": " is bezig met je verzoek.",
+  "noteLongAlert": "Je mag maximaal 128 tekens in de notitie gebruiken.",
+  "reqCodeNotFound": "Ongeldige rapportcode. Neem contact op met de beheerder.",
+  "noImage": "Geen afbeelding",
+  "related": "Gerelateerde diensten",
+  "broadcastPermission": "Kan geen bericht verzenden, je hebt geen toestemming.",
+  "broadcastTenant": "Kan geen bericht verzenden, ontvanger-tenant is ongeldig.",
+  "reqForOthers": "Natuurlijk, ik help graag anderen.",
+  "reqForMySelf": "Nee, dit verzoek is voor mijzelf.",
+  "requestedBy": "Aangevraagd door",
+  "requestedFor": "Aangevraagd voor",
+  "typeId": "Voer het ID van die persoon in",
+  "idNotMatch": "Verzenden mislukt. Dit verzoek is niet toegestaan voor ID #.",
+  "idNotFound": "ID niet gevonden. Probeer een ander ID.",
+  "informantNotRegistered": "Je bent niet geregistreerd als receptionist of kamerpersoneel.",
+  "link_copied": "Link is gekopieerd naar klembord.",
+  "do_not_disturb": "Niet storen",
+  "set_dnd_status": "DND-status instellen",
+  "active_dnd": "Actief",
+  "inactive_dnd": "Niet actief",
+  "msg_dnd": "Je kunt de DND-status uitschakelen via het menu Account.",
+  "msg_change_active": "Wil je de DND-status activeren?",
+  "msg_change_inactive": "Wil je de DND-status deactiveren?",
+  "info_dnd": "DND-status geactiveerd.",
+  "invalid_bridge": "Ongeldige bridge-URL.",
+  "someone_else": "Wil je een verzoek indienen namens iemand anders?",
+  "askScheduleTime": "Wanneer moet je verzoek worden uitgevoerd?",
+  "scheduleMessage": "Je verzoek wordt later uitgevoerd op",
+  "scan_qr": "Mijn QR tonen",
+  "show_qr": "QR tonen",
+  "show_qr_desc": "Toon deze QR aan je vrienden of collega's die toegang tot de app nodig hebben.",
+  "dismiss": "Overslaan",
+  "not_allow_permission": "Je apparaat staat de app niet toe om meldingen te ontvangen.",
+  "tap_here": "Tik hier om naar het instellingenmenu te gaan.",
+  "doNotDisturb": "Niet storen",
+  "info_label": "Info",
+  "display_menu": "Menuweergave",
+  "req_group": "Rapportgroep",
+  "ser_group": "Uitvoerdersgroep",
+  "save_rating": "Beoordeling opslaan",
+  "auto_translate": "Automatisch vertalen",
+  "hold": "Uitgesteld",
+  "holdHistory": "Uitstelgeschiedenis",
+  "pending": "In afwachting",
+  "pending_info_success": "We hebben een verbindingsprobleem gevonden. Je verzoek wacht op goedkeuring. We sturen het opnieuw zodra je weer online bent.",
+  "pending_info_tiles": "Wacht op een internetverbinding om je verzoek te verzenden.",
+  "pending_info_detail": "Je verzoek is nog niet verzonden vanwege een verbindingsprobleem.",
+  "cancel_pending": "Annuleer alleen mijn verzoek",
+  "sending": "Verzenden",
+  "queued": "In wachtrij",
+  "onProgress": "Bezig",
+  "showLess": "Minder weergeven",
+  "customTopMenu": "Hoofdmenu aanpassen",
+  "limitTopMenu": "Hoofdmenu is beperkt tot 7 items!",
+  "searchButton": "Zoeken",
+  "allInformantsInfo": "Alle melders ontvangen je bericht.",
+  "action": "Actie",
+  "whatMakesYou": "Wat maakt jou",
+  "letMeWrite": "Ik heb een andere mening!",
+  "writeHere": "Schrijf hier...",
+  "ratingAspect": "Beoordelingsaspect",
+  "sendingRequest": "Verzoek verzenden",
+  "changePriority": "Prioriteit wijzigen",
+  "referenceNumber": "Referentienummer",
+  "enterTicketNumber": "Voer ticketnummer in",
+  "subject": "Onderwerp",
+  "requestReference": "Verwijsnummer verzoek",
+  "endSessionTitle": "Je sessie is verlopen.",
+  "endSessionDesc": "Oeps, het lijkt erop dat je de app al een tijdje niet hebt gebruikt. Log opnieuw in om verder te gaan.",
+  "invalidParentTicket": "Ongeldig referentienummer.",
+  "failedLoad": "App kon niet worden geladen.",
+  "tap2retry": "Tik om opnieuw te proberen.",
+  "chat": "Gesprek"
+}

+ 9 - 3
assets/lang/zh.json

@@ -95,11 +95,16 @@
    "writeMessage": "写信息",
    "yesterday": "昨天",
    "language": "语言",
-   "bahasa": "Bahasa Indonesia",
+   "bahasa": "Indonesia",
    "english": "English",
    "japanese": "日本語",
    "korean": "한국어",
-   "chinese": "中国語",
+   "chinese": "中文",
+   "german": "Deutsch",
+   "french": "Français",
+   "hindi": "हिंदी",
+   "arabic": "العربية",
+   "dutch": "Nederlands",
    "chooseLanguage": "选择语言",
    "rate": "速度",
    "rated": "该任务已被评级。",
@@ -325,5 +330,6 @@
    "requestReference": "请求参考",
    "invalidParentTicket": "父母票无效。",
    "failedLoad": "无法加载应用程序。",
-   "tap2retry": "点击重试。"
+   "tap2retry": "点击重试。",
+   "chat": "对话"
 }

+ 15 - 4
lib/app_router.dart

@@ -1,10 +1,8 @@
 import 'package:auto_route/auto_route.dart';
 import 'package:flutter/foundation.dart';
-// import 'package:flutter_native_splash/flutter_native_splash.dart';
 import 'package:telnow_mobile_new/app_router.gr.dart';
 import 'package:telnow_mobile_new/src/api/api_auth_provider.dart';
 import 'package:telnow_mobile_new/src/injector/injector.dart';
-import 'package:telnow_mobile_new/src/layouts/auth/end_session.dart';
 import 'package:telnow_mobile_new/src/storage/sharedpreferences/shared_preferences_manager.dart';
 import 'package:telnow_mobile_new/src/utils/U.dart';
 
@@ -24,6 +22,8 @@ class AppRouter extends RootStackRouter {
         path: '/app/:pid', page: HomeGuardRoute.page, guards: appGuards,
         children: [
           AutoRoute(path: 'login', page: LoginRoute.page, guards: loginGuards, initial: true),
+          //TODO: tambah pengecekan sv disini
+          AutoRoute(path: 'landing-page', page: LandingRoute.page,),
           AutoRoute(path: 'loginme', page: AutoLoginRoute.page, guards: loginGuards),
           AutoRoute(path: 'license', page: ErrorRoute.page),
           AutoRoute(
@@ -58,7 +58,10 @@ class AppRouter extends RootStackRouter {
       var pid;
       bool stb = false;
 
-      rawPid = vRedirector.pathParams.getString('pid');
+      if(vRedirector.params.isNotEmpty){
+        rawPid = vRedirector.params.getString('pid');
+      }
+
       try {
         pid = Uri.decodeComponent(rawPid);
       } catch (e) {
@@ -102,7 +105,7 @@ class AppRouter extends RootStackRouter {
             var ver = await U.reloadLicense();
             await _sharedPreferencesManager.putInt(SharedPreferencesManager.version, ver['version']);
           } catch (e) {
-            print('error sini 2: ${e.toString()}');
+            print('Error get2: ${e.toString()}');
             await _sharedPreferencesManager.clearKey(SharedPreferencesManager.keyBaseUrl);
             await _sharedPreferencesManager.clearKey(SharedPreferencesManager.keyAccessCode);
             await _sharedPreferencesManager.clearKey(SharedPreferencesManager.keySerialCode);
@@ -119,11 +122,19 @@ class AppRouter extends RootStackRouter {
 
   List<AutoRouteGuard> get loginGuards => [
     AutoRouteGuard.simple((resolver, router) async{
+      if(resolver.route.fragment.contains("/")){
+        if(resolver.route.fragment.split("/")[1] == 'landing'){
+          await resolver.redirectUntil(LandingRoute());
+        }
+      }
+
       router.removeLast();
       if (!_isValidVersion()) {
         router.pushPath('/app/${Uri.encodeComponent(U.getAccessCode()!)}/license');
       } else if (_isLoggedIn()) {
         !kIsWeb? router.pushAndPopUntil(AppResponsive(), predicate: (_) => false) : router.pushPath('/app/${Uri.encodeComponent(U.getAccessCode()!)}/menu');
+      } else if(router.currentUrl.contains('/loginme') && resolver.routeName != AutoLoginRoute.name){
+        await resolver.redirectUntil(AutoLoginRoute());
       } else{
         resolver.next();
       }

+ 76 - 40
lib/app_router.gr.dart

@@ -9,28 +9,30 @@
 // coverage:ignore-file
 
 // ignore_for_file: no_leading_underscores_for_library_prefixes
-import 'package:auto_route/auto_route.dart' as _i9;
+import 'package:auto_route/auto_route.dart' as _i10;
+import 'package:flutter/material.dart' as _i11;
 import 'package:telnow_mobile_new/main.dart' as _i6;
 import 'package:telnow_mobile_new/src/layouts/auth/change_code.dart' as _i3;
 import 'package:telnow_mobile_new/src/layouts/auth/end_session.dart' as _i4;
-import 'package:telnow_mobile_new/src/layouts/auth/login.dart' as _i7;
-import 'package:telnow_mobile_new/src/layouts/auth/qr.dart' as _i8;
+import 'package:telnow_mobile_new/src/layouts/auth/login.dart' as _i8;
+import 'package:telnow_mobile_new/src/layouts/auth/qr.dart' as _i9;
 import 'package:telnow_mobile_new/src/layouts/components/auto_login.dart'
     as _i2;
 import 'package:telnow_mobile_new/src/layouts/components/error_page.dart'
     as _i5;
 import 'package:telnow_mobile_new/src/layouts/components/responsive.dart'
     as _i1;
+import 'package:telnow_mobile_new/src/layouts/landing/landing_page.dart' as _i7;
 
 /// generated route for
 /// [_i1.AccountResponsive]
-class AccountResponsive extends _i9.PageRouteInfo<void> {
-  const AccountResponsive({List<_i9.PageRouteInfo>? children})
+class AccountResponsive extends _i10.PageRouteInfo<void> {
+  const AccountResponsive({List<_i10.PageRouteInfo>? children})
     : super(AccountResponsive.name, initialChildren: children);
 
   static const String name = 'AccountResponsive';
 
-  static _i9.PageInfo page = _i9.PageInfo(
+  static _i10.PageInfo page = _i10.PageInfo(
     name,
     builder: (data) {
       return const _i1.AccountResponsive();
@@ -40,13 +42,13 @@ class AccountResponsive extends _i9.PageRouteInfo<void> {
 
 /// generated route for
 /// [_i1.AppResponsive]
-class AppResponsive extends _i9.PageRouteInfo<void> {
-  const AppResponsive({List<_i9.PageRouteInfo>? children})
+class AppResponsive extends _i10.PageRouteInfo<void> {
+  const AppResponsive({List<_i10.PageRouteInfo>? children})
     : super(AppResponsive.name, initialChildren: children);
 
   static const String name = 'AppResponsive';
 
-  static _i9.PageInfo page = _i9.PageInfo(
+  static _i10.PageInfo page = _i10.PageInfo(
     name,
     builder: (data) {
       return const _i1.AppResponsive();
@@ -56,13 +58,13 @@ class AppResponsive extends _i9.PageRouteInfo<void> {
 
 /// generated route for
 /// [_i2.AutoLoginPage]
-class AutoLoginRoute extends _i9.PageRouteInfo<void> {
-  const AutoLoginRoute({List<_i9.PageRouteInfo>? children})
+class AutoLoginRoute extends _i10.PageRouteInfo<void> {
+  const AutoLoginRoute({List<_i10.PageRouteInfo>? children})
     : super(AutoLoginRoute.name, initialChildren: children);
 
   static const String name = 'AutoLoginRoute';
 
-  static _i9.PageInfo page = _i9.PageInfo(
+  static _i10.PageInfo page = _i10.PageInfo(
     name,
     builder: (data) {
       return _i2.AutoLoginPage();
@@ -72,13 +74,13 @@ class AutoLoginRoute extends _i9.PageRouteInfo<void> {
 
 /// generated route for
 /// [_i3.ChangeCodePage]
-class ChangeCodeRoute extends _i9.PageRouteInfo<void> {
-  const ChangeCodeRoute({List<_i9.PageRouteInfo>? children})
+class ChangeCodeRoute extends _i10.PageRouteInfo<void> {
+  const ChangeCodeRoute({List<_i10.PageRouteInfo>? children})
     : super(ChangeCodeRoute.name, initialChildren: children);
 
   static const String name = 'ChangeCodeRoute';
 
-  static _i9.PageInfo page = _i9.PageInfo(
+  static _i10.PageInfo page = _i10.PageInfo(
     name,
     builder: (data) {
       return const _i3.ChangeCodePage();
@@ -88,13 +90,13 @@ class ChangeCodeRoute extends _i9.PageRouteInfo<void> {
 
 /// generated route for
 /// [_i4.EndSessionPage]
-class EndSessionRoute extends _i9.PageRouteInfo<void> {
-  const EndSessionRoute({List<_i9.PageRouteInfo>? children})
+class EndSessionRoute extends _i10.PageRouteInfo<void> {
+  const EndSessionRoute({List<_i10.PageRouteInfo>? children})
     : super(EndSessionRoute.name, initialChildren: children);
 
   static const String name = 'EndSessionRoute';
 
-  static _i9.PageInfo page = _i9.PageInfo(
+  static _i10.PageInfo page = _i10.PageInfo(
     name,
     builder: (data) {
       return const _i4.EndSessionPage();
@@ -104,11 +106,11 @@ class EndSessionRoute extends _i9.PageRouteInfo<void> {
 
 /// generated route for
 /// [_i5.ErrorPage]
-class ErrorRoute extends _i9.PageRouteInfo<ErrorRouteArgs> {
+class ErrorRoute extends _i10.PageRouteInfo<ErrorRouteArgs> {
   ErrorRoute({
     required dynamic code,
     required dynamic errMsg,
-    List<_i9.PageRouteInfo>? children,
+    List<_i10.PageRouteInfo>? children,
   }) : super(
          ErrorRoute.name,
          args: ErrorRouteArgs(code: code, errMsg: errMsg),
@@ -117,7 +119,7 @@ class ErrorRoute extends _i9.PageRouteInfo<ErrorRouteArgs> {
 
   static const String name = 'ErrorRoute';
 
-  static _i9.PageInfo page = _i9.PageInfo(
+  static _i10.PageInfo page = _i10.PageInfo(
     name,
     builder: (data) {
       final args = data.argsAs<ErrorRouteArgs>();
@@ -141,13 +143,13 @@ class ErrorRouteArgs {
 
 /// generated route for
 /// [_i1.HistoryResponsive]
-class HistoryResponsive extends _i9.PageRouteInfo<void> {
-  const HistoryResponsive({List<_i9.PageRouteInfo>? children})
+class HistoryResponsive extends _i10.PageRouteInfo<void> {
+  const HistoryResponsive({List<_i10.PageRouteInfo>? children})
     : super(HistoryResponsive.name, initialChildren: children);
 
   static const String name = 'HistoryResponsive';
 
-  static _i9.PageInfo page = _i9.PageInfo(
+  static _i10.PageInfo page = _i10.PageInfo(
     name,
     builder: (data) {
       return const _i1.HistoryResponsive();
@@ -157,13 +159,13 @@ class HistoryResponsive extends _i9.PageRouteInfo<void> {
 
 /// generated route for
 /// [_i6.HomeGuardPage]
-class HomeGuardRoute extends _i9.PageRouteInfo<void> {
-  const HomeGuardRoute({List<_i9.PageRouteInfo>? children})
+class HomeGuardRoute extends _i10.PageRouteInfo<void> {
+  const HomeGuardRoute({List<_i10.PageRouteInfo>? children})
     : super(HomeGuardRoute.name, initialChildren: children);
 
   static const String name = 'HomeGuardRoute';
 
-  static _i9.PageInfo page = _i9.PageInfo(
+  static _i10.PageInfo page = _i10.PageInfo(
     name,
     builder: (data) {
       return const _i6.HomeGuardPage();
@@ -173,13 +175,13 @@ class HomeGuardRoute extends _i9.PageRouteInfo<void> {
 
 /// generated route for
 /// [_i1.HomeResponsive]
-class HomeResponsive extends _i9.PageRouteInfo<void> {
-  const HomeResponsive({List<_i9.PageRouteInfo>? children})
+class HomeResponsive extends _i10.PageRouteInfo<void> {
+  const HomeResponsive({List<_i10.PageRouteInfo>? children})
     : super(HomeResponsive.name, initialChildren: children);
 
   static const String name = 'HomeResponsive';
 
-  static _i9.PageInfo page = _i9.PageInfo(
+  static _i10.PageInfo page = _i10.PageInfo(
     name,
     builder: (data) {
       return const _i1.HomeResponsive();
@@ -188,33 +190,67 @@ class HomeResponsive extends _i9.PageRouteInfo<void> {
 }
 
 /// generated route for
-/// [_i7.LoginPage]
-class LoginRoute extends _i9.PageRouteInfo<void> {
-  const LoginRoute({List<_i9.PageRouteInfo>? children})
+/// [_i7.LandingPage]
+class LandingRoute extends _i10.PageRouteInfo<LandingRouteArgs> {
+  LandingRoute({_i11.Key? key, List<_i10.PageRouteInfo>? children})
+    : super(
+        LandingRoute.name,
+        args: LandingRouteArgs(key: key),
+        initialChildren: children,
+      );
+
+  static const String name = 'LandingRoute';
+
+  static _i10.PageInfo page = _i10.PageInfo(
+    name,
+    builder: (data) {
+      final args = data.argsAs<LandingRouteArgs>(
+        orElse: () => const LandingRouteArgs(),
+      );
+      return _i7.LandingPage(key: args.key);
+    },
+  );
+}
+
+class LandingRouteArgs {
+  const LandingRouteArgs({this.key});
+
+  final _i11.Key? key;
+
+  @override
+  String toString() {
+    return 'LandingRouteArgs{key: $key}';
+  }
+}
+
+/// generated route for
+/// [_i8.LoginPage]
+class LoginRoute extends _i10.PageRouteInfo<void> {
+  const LoginRoute({List<_i10.PageRouteInfo>? children})
     : super(LoginRoute.name, initialChildren: children);
 
   static const String name = 'LoginRoute';
 
-  static _i9.PageInfo page = _i9.PageInfo(
+  static _i10.PageInfo page = _i10.PageInfo(
     name,
     builder: (data) {
-      return const _i7.LoginPage();
+      return const _i8.LoginPage();
     },
   );
 }
 
 /// generated route for
-/// [_i8.NewQrPage]
-class NewQrRoute extends _i9.PageRouteInfo<void> {
-  const NewQrRoute({List<_i9.PageRouteInfo>? children})
+/// [_i9.NewQrPage]
+class NewQrRoute extends _i10.PageRouteInfo<void> {
+  const NewQrRoute({List<_i10.PageRouteInfo>? children})
     : super(NewQrRoute.name, initialChildren: children);
 
   static const String name = 'NewQrRoute';
 
-  static _i9.PageInfo page = _i9.PageInfo(
+  static _i10.PageInfo page = _i10.PageInfo(
     name,
     builder: (data) {
-      return const _i8.NewQrPage();
+      return const _i9.NewQrPage();
     },
   );
 }

+ 11 - 7
lib/main.dart

@@ -18,7 +18,6 @@ import 'package:quick_notify_2/quick_notify.dart';
 import 'package:shared_preferences/shared_preferences.dart';
 import 'package:telnow_mobile_new/src/api/api_auth_provider.dart';
 import 'package:telnow_mobile_new/src/injector/injector.dart';
-import 'package:telnow_mobile_new/src/layouts/components/template.dart';
 import 'package:telnow_mobile_new/src/layouts/mobile/history_forum.dart';
 import 'package:telnow_mobile_new/src/layouts/mobile/message_list.dart';
 import 'package:telnow_mobile_new/src/storage/sharedpreferences/shared_preferences_manager.dart';
@@ -79,7 +78,12 @@ void main() async{
               Locale('id'),
               Locale('ja'),
               Locale('zh'),
-              Locale('ko')
+              Locale('ko'),
+              Locale('ar'),
+              Locale('fr'),
+              Locale('de'),
+              Locale('hi'),
+              Locale('nl')
             ],
             startLocale: Locale('id'),
             saveLocale: true,
@@ -131,7 +135,7 @@ class MyApp extends StatelessWidget {
               statusBarIconBrightness: Brightness.dark,
               statusBarBrightness: Brightness.dark
           ),
-        )),
+        ), colorScheme: ColorScheme.fromSeed(seedColor: Color(0xff078C84))),
         routerConfig: _appRouter.config(),
       ),
     );
@@ -157,7 +161,7 @@ class _HomeGuardPageState extends State<HomeGuardPage> {
   @override
   Widget build(BuildContext context) {
     eventBus.on().listen((event) {
-      print("eventBus => ${event.toString()}");
+      // print("eventBus => ${event.toString()}");
       if(event.toString() == 'logout') {
         // token.logout();
         context.navigateToPath('/end-session');
@@ -192,7 +196,7 @@ class NotificationClass {
       // print(token);
       if (token != null) {
         _apiAuthProvider.postDataNoAuth('/api/notifications/received/$token/$mid').then((value) =>
-            print('sukses kirim confirm')
+            print('Send confirm succes!')
         );
       }
 
@@ -253,10 +257,10 @@ class NotificationClass {
     return activeNotif;
   }
 
-  startNotification(BuildContext context) async {
+  startNotification(BuildContext context, {code}) async {
     var token = await U.getFcmToken();
     if (token != null) {
-      Map data = {'token': token, 'language': context.locale.toString().toUpperCase()};
+      Map data = {'token': token, 'language': code ?? context.locale.toString().toUpperCase()};
       var res = _apiAuthProvider.postData('/api/fcmTokens/register', null, data, context);
       return res;
     }

+ 10 - 8
lib/src/api/api_auth_provider.dart

@@ -24,9 +24,9 @@ class ApiAuthProvider {
   final Dio _dio = new Dio();
   final SharedPreferencesManager _sharedPreferencesManager = locator<SharedPreferencesManager>();
 
-  final String displayVersion = '4.0.16'; //versi aplikasi untuk di tampilkan
+  final String displayVersion = '4.0.17'; //versi aplikasi untuk di tampilkan
   final int currentVersion = 40; //versi aplikasi yang digunakan untuk pengecekan versi
-  final String buildNumber = '2525.02';
+  final String buildNumber = '2539.01';
 
 //  final String companyName = '999';
 //  final String _baseUrl = 'http://139.162.7.140:9090/';
@@ -59,7 +59,7 @@ class ApiAuthProvider {
   init(){
     // print('ApiAuthProvider called');
     // print("U.getAccessCode() ==> ${U.getAccessCode()}");
-    isDebug = _sharedPreferencesManager.getBool(SharedPreferencesManager.keyIsDebug);
+    isDebug = _sharedPreferencesManager.getBool(SharedPreferencesManager.keyIsDebug)??false;
     if (_sharedPreferencesManager.isKeyExists(SharedPreferencesManager.keyAccessCode)! && _sharedPreferencesManager.isKeyExists(SharedPreferencesManager.keyBaseUrl)!) {
       baseUrl = U.decodeBase64Url(U.getBaseUrl()!);
       _dio.options.baseUrl = baseUrl + U.decodeBase64Url(Uri.decodeComponent(U.getAccessCode()!));
@@ -185,7 +185,7 @@ class ApiAuthProvider {
         }
       } else if (error.response!.statusCode! >= 500) {
         if (secondCheck) {
-          print('secondCheck');
+          // print('secondCheck');
           handlingError(context, 2); //error connection
         } else {
           await Future.delayed(Duration(milliseconds: 200));
@@ -196,7 +196,7 @@ class ApiAuthProvider {
         handlingError(context, 3); //error auth
       } else {
         if (secondCheck) {
-          print('secondCheck');
+          // print('secondCheck');
           handlingError(context, 2); //error connection
         } else {
           await Future.delayed(Duration(milliseconds: 200));
@@ -242,7 +242,7 @@ class ApiAuthProvider {
       } else {
         if(!isOpen){
           if (secondCheck) {
-            print('secondCheck');
+            // print('secondCheck');
             handlingError(context, 2); //error connection
           } else {
             return getData(path, params, context, secondCheck: true);
@@ -267,7 +267,7 @@ class ApiAuthProvider {
       );
       return response.data;
     } on DioException catch (error) {
-     print(error);
+     debugPrint('Error post: ${error.toString()}');
       if (error.response == null) {
         try {
           final result = await InternetAddress.lookup('google.com');
@@ -290,7 +290,9 @@ class ApiAuthProvider {
           showError(context, 'broadcastPermission'.tr());
         } else if(error.response?.data['message'] == 'request did not match informant rights'){
           showError(context, 'idNotMatch'.tr().replaceAll("#ID", data['user_id']));
-        } else if(error.response?.data['message'] == 'Informant user id not found.'){
+        } else if(error.response?.data['message'] == 'Informant user id not found.' ||
+            error.response?.data['message'] == 'invalid user informant requested'
+        ){
           showError(context, 'idNotFound'.tr());
         } else if(error.response?.data['message'] == 'You are not registered as receptionist or room attendant.'){
           showError(context, 'informantNotRegistered'.tr());

+ 6 - 1
lib/src/api/jwt_token.dart

@@ -98,7 +98,12 @@ class JwtToken{
       }
 
       if(user != null){
-        context.setLocale(Locale(user['language'].toLowerCase()));
+        if(U.newServerVersion(1754624839)){
+          var code = U.getLang(user['language']).toLowerCase();
+          context.setLocale(Locale(code));
+        } else {
+          context.setLocale(Locale(user['language'].toLowerCase()));
+        }
       }
 
       if(U.getInternetStatus()){

+ 247 - 185
lib/src/layouts/auth/login.dart

@@ -14,6 +14,7 @@ import 'package:telnow_mobile_new/src/injector/injector.dart';
 import 'package:telnow_mobile_new/src/layouts/components/template.dart';
 import 'package:telnow_mobile_new/src/model/login/login_body.dart';
 import 'package:telnow_mobile_new/src/storage/sharedpreferences/shared_preferences_manager.dart';
+import 'package:telnow_mobile_new/src/utils/C.dart';
 import 'package:telnow_mobile_new/src/utils/U.dart';
 import 'package:telnow_mobile_new/src/utils/cache_manager.dart';
 import 'package:toggle_switch/toggle_switch.dart';
@@ -47,6 +48,7 @@ class _LoginPageState extends State<LoginPage> {
   @override
   void initState() {
     try{imageUrl = apiAuthProvider.baseUrl + U.decodeBase64Url(Uri.decodeComponent(U.getAccessCode()!))+'/assets/background/tn';}catch(e){}
+    U.getServerVersion();
     getCompanyName();
     // TODO: implement initState
     super.initState();
@@ -54,15 +56,16 @@ class _LoginPageState extends State<LoginPage> {
 
   getCompanyName() async {
     var lics = await U.reloadLicense();
-    if (lics != null && mounted) {
-      setState(() {
-        companyName = lics['companyName']??'';
-        serialNumber = lics['serialNumber']??'';
-        companyLogo = lics['logo']??'';
-        if(1698720817 <= lics['serverVersion']){
-          lang = lics['languages'] != null ? lics['languages'].split(',') : [];
-        }
-      });
+    if (lics != null) {
+      companyName = lics['companyName']??'';
+      serialNumber = lics['serialNumber']??'';
+      companyLogo = lics['logo']??'';
+      if(await U.isCompatibleWith(VersionKey.multiBahasa)){
+        lang = lics['_validLang'] ?? [];
+      } else if(1698720817 <= lics['serverVersion']){
+        lang = lics['languages'] != null ? lics['languages'].split(',') : [];
+      }
+      if(mounted) setState(() {});
     }
   }
 
@@ -72,7 +75,7 @@ class _LoginPageState extends State<LoginPage> {
       resizeToAvoidBottomInset: false,
       body: Stack(
         children: [
-          Container(
+          SizedBox(
             width: double.infinity, height: double.infinity,
             child: Image.network(imageUrl, fit: BoxFit.cover, width: double.infinity, height: double.infinity, errorBuilder: (context, error, stackTrace) {
               return Image.asset('assets/image/background/background.jpg', fit: BoxFit.cover, width: double.infinity, height: double.infinity);
@@ -80,142 +83,162 @@ class _LoginPageState extends State<LoginPage> {
           ),
           SingleChildScrollView(
             child: Container(
-              padding: EdgeInsets.fromLTRB(16, kIsWeb?80:50, 16, 16),
+              padding: EdgeInsets.all(16),
               width: MediaQuery.of(context).size.width,
-              height: kIsWeb && MediaQuery.of(context).size.height<700 ? 700 : MediaQuery.of(context).size.height,
+              height: kIsWeb && MediaQuery.of(context).size.height<820 ? 820 : MediaQuery.of(context).size.height,
               decoration: BoxDecoration(gradient: LinearGradient(begin: Alignment.topCenter, end: Alignment.bottomCenter, colors: [primaryColor.withValues(alpha: 0.50), Colors.black.withValues(alpha: 0.91)])),
-              child: Column(
-                children: [
-                  kIsWeb?Container():Container(
-                    margin: EdgeInsets.only(bottom: companyLogo.isNotEmpty?50:120),
-                    child: GestureDetector(
+              child: SafeArea(
+                child: Column(
+                  children: [
+                    Container(
+                      padding: EdgeInsets.symmetric(horizontal: 8, vertical: 16),
+                      // height: 90,
                       child: Row(
+                        crossAxisAlignment: CrossAxisAlignment.center,
+                        mainAxisAlignment: MainAxisAlignment.spaceBetween,
+                        // padding: EdgeInsets.symmetric(horizontal: 16),
                         children: [
-                          U.iconsax('arrow-left', color: Colors.white),
-                          SizedBox(width: 15),
-                          Column(
-                            crossAxisAlignment: CrossAxisAlignment.start,
-                            children: [
-                              Text('buttonBack'.tr(), style: TextStyle(color: Colors.white, fontSize: 16, fontWeight: FontWeight.w500)),
-                              Text('buttonScan'.tr(), style: TextStyle(color: Colors.white, fontSize: 16, fontWeight: FontWeight.w500)),
-                            ],
-                          )
+                          backButton(),
+                          langWidget()
                         ],
                       ),
-                      onTap: (){
-                        context.router.removeLast();
-                        context.navigateToPath('/qr?new=true');
-                      },
-                    ),
-                  ),
-                  Image.asset('assets/image/logo/logo.png', height: companyLogo.isNotEmpty?25:40),
-                  companyLogo.isNotEmpty?Container(
-                    margin: EdgeInsets.only(top: 30, bottom: 40),
-                    width: 160, height: 160,
-                    decoration: BoxDecoration(
-                      borderRadius: BorderRadius.all(Radius.circular(80)),
-                      image: DecorationImage(image: CachedNetworkImageProvider(companyLogo+'?bridge-cache=true', cacheManager: CacheManager(CacheMan.config(companyLogo))), fit: BoxFit.cover)
                     ),
-                  ):SizedBox(height: 115),
-                  Expanded(
-                    child: Column(
-                      mainAxisAlignment: MainAxisAlignment.start,
-                      children: [
-                        Container(
-                          alignment: Alignment.topCenter,
-                          width: U.bodyWidth(context),
-                          child: Column(
-                            children: [
-                              loginFieldTemplate(placeholder: 'userId'.tr(), value: username, action: (val)=>username = val, submit: (val)=>loginAction()),
-                              SizedBox(height: 12),
-                              loginFieldTemplate(placeholder: 'password'.tr(), value: password, action: (val)=>password = val, isPassword: true, submit: (val)=>loginAction()),
-                              GestureDetector(
-                                child: Container(
-                                  width: double.infinity,
-                                  margin: EdgeInsets.only(top: 40, bottom: 20),
-                                  padding: EdgeInsets.symmetric(vertical: 16),
-                                  child: !loading?Text('buttonLogin'.tr(), style: TextStyle(color: Colors.white, fontSize: 17), textAlign: TextAlign.center):Center(
-                                    child: SizedBox(
-                                      width: 22, height: 22, child: CircularProgressIndicator(color: Colors.white),
+                    Image.asset('assets/image/logo/logo.png', height: companyLogo.isNotEmpty?25:40),
+                    companyLogo.isNotEmpty?Container(
+                      margin: EdgeInsets.only(top: 30, bottom: 40),
+                      width: 160, height: 160,
+                      decoration: BoxDecoration(
+                        borderRadius: BorderRadius.all(Radius.circular(80)),
+                        image: DecorationImage(image: CachedNetworkImageProvider(companyLogo+'?bridge-cache=true', cacheManager: CacheManager(CacheMan.config(companyLogo))), fit: BoxFit.cover)
+                      ),
+                    ):SizedBox(height: 115),
+                    Expanded(
+                      child: Column(
+                        mainAxisAlignment: MainAxisAlignment.start,
+                        children: [
+                          Container(
+                            alignment: Alignment.topCenter,
+                            width: U.bodyWidth(context),
+                            child: Column(
+                              children: [
+                                loginFieldTemplate(placeholder: 'userId'.tr(), value: username, action: (val)=>username = val, submit: (val)=>loginAction()),
+                                SizedBox(height: 12),
+                                loginFieldTemplate(placeholder: 'password'.tr(), value: password, action: (val)=>password = val, isPassword: true, submit: (val)=>loginAction()),
+                                GestureDetector(
+                                  child: Container(
+                                    width: double.infinity,
+                                    margin: EdgeInsets.only(top: 40, bottom: 20),
+                                    padding: EdgeInsets.symmetric(vertical: 16),
+                                    decoration: BoxDecoration(
+                                        color: primaryColor.withValues(alpha: 0.50),
+                                        border: Border.all(color: Colors.white),
+                                        borderRadius: BorderRadius.all(Radius.circular(12))
+                                    ),
+                                    child: !loading ? Text('buttonLogin'.tr(), style: TextStyle(color: Colors.white, fontSize: 17), textAlign: TextAlign.center):Center(
+                                      child: SizedBox(
+                                        width: 22, height: 22, child: CircularProgressIndicator(color: Colors.white),
+                                      ),
                                     ),
                                   ),
-                                  decoration: BoxDecoration(
-                                      color: primaryColor.withValues(alpha: 0.50),
-                                      border: Border.all(color: Colors.white),
-                                      borderRadius: BorderRadius.all(Radius.circular(12))
-                                  ),
+                                  onTap: ()=>loginAction(),
                                 ),
-                                onTap: ()=>loginAction(),
-                              ),
-                            ],
+                              ],
+                            ),
                           ),
-                        ),
-                        companyName.isNotEmpty?Text(companyName, style: TextStyle(fontSize: 16, color: Colors.white), textAlign: TextAlign.center):Container(),
-                        SizedBox(height: companyName.isNotEmpty?5:0),
-                        serialNumber.isNotEmpty?Text('serialNumber'.tr() + ' ' + serialNumber, style: TextStyle(color: Colors.white, fontSize: 14)):Container(),
-                      ],
+                          companyName.isNotEmpty?Text(companyName, style: TextStyle(fontSize: 16, color: Colors.white), textAlign: TextAlign.center):Container(),
+                          SizedBox(height: companyName.isNotEmpty?5:0),
+                          serialNumber.isNotEmpty?Text('serialNumber'.tr() + ' ' + serialNumber, style: TextStyle(color: Colors.white, fontSize: 14)):Container(),
+                        ],
+                      ),
                     ),
-                  ),
-                  Container(
-                    margin: EdgeInsets.only(top: 20, bottom: 5),
-                    alignment: Alignment.bottomCenter,
-                    child: GestureDetector(
-                      child: Text('policy'.tr(), style: TextStyle(color: Colors.white, fontSize: 14, decoration: TextDecoration.underline)),
-                      onTap: () async {
-                        Uri _url = Uri.parse('https://telmessenger.com/privacy-police/telnow.html');
-                        await canLaunchUrl(_url) ? await launchUrl(_url) : print('Could not launch $_url');
-                      },
+                    Container(
+                      margin: EdgeInsets.only(top: 20, bottom: 5),
+                      alignment: Alignment.bottomCenter,
+                      child: GestureDetector(
+                        child: Text('policy'.tr(), style: TextStyle(color: Colors.white, fontSize: 14, decoration: TextDecoration.underline)),
+                        onTap: () async {
+                          Uri _url = Uri.parse('https://telmessenger.com/privacy-police/telnow.html');
+                          await canLaunchUrl(_url) ? await launchUrl(_url) : print('Could not launch $_url');
+                        },
+                      ),
                     ),
-                  ),
-                  Text('${'version'.tr()} ${ApiAuthProvider().displayVersion}', style: TextStyle(color: Colors.white, fontSize: 14)),
-                  SizedBox(height: 5),
-                  Text('Build ${ApiAuthProvider().buildNumber}', style: TextStyle(color: Colors.white, fontSize: 13)),
-                ],
-              ),
-            ),
-          ),
-          Container(
-            margin: EdgeInsets.only(top: kIsWeb?16:50, right: 16),
-            child: Align(
-              alignment: Alignment.topRight,
-              child: lang.length > 0 ? GestureDetector(
-                child: Container(
-                  width: 65, height: 30, padding: EdgeInsets.symmetric(horizontal: 10),
-                  child: Row(
-                    mainAxisAlignment: MainAxisAlignment.spaceBetween,
-                    children: [
-                      U.iconsax('bold/global', color: Colors.white),
-                      Text(context.locale.toString().toUpperCase(), style: TextStyle(color: Colors.white, fontSize: 15, fontWeight: FontWeight.bold))
-                    ],
-                  ),
-                  decoration: BoxDecoration(
-                    color: Colors.deepOrange,
-                    borderRadius: BorderRadius.all(Radius.circular(50))
-                  ),
+                    Text('${'version'.tr()} ${ApiAuthProvider().displayVersion}', style: TextStyle(color: Colors.white, fontSize: 14)),
+                    SizedBox(height: 5),
+                    Text('Build ${ApiAuthProvider().buildNumber}', style: TextStyle(color: Colors.white, fontSize: 13)),
+                  ],
                 ),
-                onTap: () => changeLangLogin(context, lang),
-              ) : ToggleSwitch(
-                minWidth: 40,
-                minHeight: 30,
-                cornerRadius: 20,
-                initialLabelIndex: context.locale.toString() == 'en' ? 0 : 1,
-                activeBgColor: [Colors.deepOrange],
-                activeFgColor: Colors.white,
-                inactiveBgColor: Colors.white54,
-                inactiveFgColor: Colors.white,
-                labels: ['EN', 'ID'],
-                onToggle: (index) {
-                  if (index == 0) {
-                    context.setLocale(Locale('en'));
-                  } else {
-                    context.setLocale(Locale('id'));
-                  }
-                },
               ),
             ),
+          ),
+        ],
+      ),
+    );
+  }
+
+  Widget backButton(){
+    return GestureDetector(
+      child: Row(
+        children: [
+          U.iconsax('arrow-left', color: Colors.white),
+          SizedBox(width: 15),
+          Column(
+            crossAxisAlignment: CrossAxisAlignment.start,
+            children: [
+              Text('buttonBack'.tr(), style: TextStyle(color: Colors.white, fontSize: 16, fontWeight: FontWeight.w500)),
+              Text('buttonScan'.tr(), style: TextStyle(color: Colors.white, fontSize: 16, fontWeight: FontWeight.w500)),
+            ],
           )
         ],
       ),
+      onTap: (){
+        context.router.removeLast();
+        context.navigateToPath('/qr?new=true');
+      },
+    );
+  }
+
+  Widget langWidget(){
+    return Center(
+      child: Container(
+        margin: EdgeInsets.only(right: 16),
+        child: Align(
+          alignment: Alignment.topRight,
+          child: lang.isNotEmpty ? GestureDetector(
+            child: Container(
+              width: 65, height: 30, padding: EdgeInsets.symmetric(horizontal: 10),
+              decoration: BoxDecoration(
+                  color: Colors.deepOrange,
+                  borderRadius: BorderRadius.all(Radius.circular(50))
+              ),
+              child: Row(
+                mainAxisAlignment: MainAxisAlignment.spaceBetween,
+                children: [
+                  U.iconsax('bold/global', color: Colors.white),
+                  Text(context.locale.toString().toUpperCase(), style: TextStyle(color: Colors.white, fontSize: 15, fontWeight: FontWeight.bold))
+                ],
+              ),
+            ),
+            onTap: () => changeLangLogin(context, lang),
+          ) : ToggleSwitch(
+            minWidth: 40,
+            minHeight: 30,
+            cornerRadius: 20,
+            initialLabelIndex: context.locale.toString() == 'en' ? 0 : 1,
+            activeBgColor: [Colors.deepOrange],
+            activeFgColor: Colors.white,
+            inactiveBgColor: Colors.white54,
+            inactiveFgColor: Colors.white,
+            labels: ['EN', 'ID'],
+            onToggle: (index) {
+              if (index == 0) {
+                context.setLocale(Locale('en'));
+              } else {
+                context.setLocale(Locale('id'));
+              }
+            },
+          ),
+        ),
+      ),
     );
   }
 
@@ -280,8 +303,25 @@ class _LoginPageState extends State<LoginPage> {
 
   switchLang(BuildContext context, code, tkn) async {
     // print("switchLang called");
+    var lg = code.toUpperCase();
+    if(lang.indexOf(code) > 1){
+      lg = lang.indexOf(code) - 1;
+    }
+    var p;
+    if(U.newServerVersion(1754624839)){
+      p = {
+        'userId': tkn['user_name'].toString().replaceFirst('inf-', ""),
+        'language': 'ID',
+        '_language': lg
+      };
+    } else {
+      p = {
+        'userId': tkn['user_name'].toString().replaceFirst('inf-', ""),
+        'language': lg
+      };
+    }
     try{
-      var res = await apiAuthProvider.patchData('/api/informants/' + tkn['userId'].toString(), {'userId': tkn['user_name'].toString().replaceFirst('inf-', ""), 'language': code.toUpperCase()}, context);
+      var res = await apiAuthProvider.patchData('/api/informants/' + tkn['userId'].toString(), p, context);
       if (res != null) {
         // print("res $res");
         // context.setLocale(Locale(code));
@@ -304,69 +344,91 @@ class _LoginPageState extends State<LoginPage> {
   }
 }
 
+var lg = {
+  "ja": {
+    "text": 'japanese'
+  }, "zh": {
+    "text": 'chinese'
+  }, "ko": {
+    "text": 'korean'
+  }, "ar": {
+    "text": 'arabic'
+  }, "fr": {
+    "text": 'france'
+  }, "de": {
+    "text": 'german'
+  }, "nl": {
+    "text": 'dutch'
+  }, "hi": {
+    "text": 'hindi'
+  }
+};
+
 changeLangLogin(BuildContext context, List lang){
   showModalBottomSheet(
     context: context,
     backgroundColor: Colors.white,
     builder: (BuildContext context) {
-      return Column(
-        mainAxisSize: MainAxisSize.min,
-        children: <Widget>[
-          Padding(
-            padding: EdgeInsets.symmetric(vertical: 16),
-            child: Center(
-              child: Text('chooseLanguage'.tr(), style: TextStyle(color: textColor)),
-            ),
-          ),
-          divider(),
-          SizedBox(height: 16),
-          Column(
-            crossAxisAlignment: CrossAxisAlignment.start,
-            children: [
-              ListTile(
-                title: Text('bahasa'.tr(), style: TextStyle(color: textColor, fontSize: 14)),
-                trailing: Icon(context.locale.toString()=='id'?Icons.radio_button_checked:Icons.radio_button_off, color: primaryColor, size: 18),
-                onTap: context.locale.toString() != 'id' ? () async {
-                  Navigator.of(context).pop();
-                  context.setLocale(Locale('id'));
-                } : null,
+      return SafeArea(
+        child: Column(
+          mainAxisSize: MainAxisSize.min,
+          children: <Widget>[
+            Padding(
+              padding: EdgeInsets.symmetric(vertical: 16),
+              child: Center(
+                child: Text('chooseLanguage'.tr(), style: TextStyle(color: textColor)),
               ),
-              ListTile(
-                title: Text('english'.tr(), style: TextStyle(color: textColor, fontSize: 14)),
-                trailing: Icon(context.locale.toString()=='en'?Icons.radio_button_checked:Icons.radio_button_off, color: primaryColor, size: 18),
-                onTap: context.locale.toString() != 'en' ? () async {
-                  Navigator.of(context).pop();
-                  context.setLocale(Locale('en'));
-                } : null,
-              ),
-              lang.contains('ja') ? ListTile(
-                title: Text('japanese'.tr(), style: TextStyle(color: textColor, fontSize: 14)),
-                trailing: Icon(context.locale.toString()=='ja'?Icons.radio_button_checked:Icons.radio_button_off, color: primaryColor, size: 18),
-                onTap: context.locale.toString() != 'ja' ? () async {
-                  Navigator.of(context).pop();
-                  context.setLocale(Locale('ja'));
-                } : null,
-              ) : Container(),
-              lang.contains('zh') ? ListTile(
-                title: Text('chinese'.tr(), style: TextStyle(color: textColor, fontSize: 14)),
-                trailing: Icon(context.locale.toString()=='zh'?Icons.radio_button_checked:Icons.radio_button_off, color: primaryColor, size: 18),
-                onTap: context.locale.toString() != 'zh' ? () async {
-                  Navigator.of(context).pop();
-                  context.setLocale(Locale('zh'));
-                } : null,
-              ) : Container(),
-              lang.contains('ko') ? ListTile(
-                title: Text('korean'.tr(), style: TextStyle(color: textColor, fontSize: 14)),
-                trailing: Icon(context.locale.toString()=='ko'?Icons.radio_button_checked:Icons.radio_button_off, color: primaryColor, size: 18),
-                onTap: context.locale.toString() != 'ko' ? () async {
-                  Navigator.of(context).pop();
-                  context.setLocale(Locale('ko'));
-                } : null,
-              ) : Container()
-            ],
-          ),
-          SizedBox(height: 16)
-        ],
+            ),
+            divider(),
+            SizedBox(height: 16),
+            Column(
+              crossAxisAlignment: CrossAxisAlignment.start,
+              children: [
+                ListTile(
+                  title: Text('bahasa'.tr(), style: TextStyle(color: textColor, fontSize: 14)),
+                  trailing: Icon(context.locale.toString()=='id'?Icons.radio_button_checked:Icons.radio_button_off, color: primaryColor, size: 18),
+                  onTap: context.locale.toString() != 'id' ? () async {
+                    Navigator.of(context).pop();
+                    context.setLocale(Locale('id'));
+                  } : null,
+                ),
+                ListTile(
+                  title: Text('english'.tr(), style: TextStyle(color: textColor, fontSize: 14)),
+                  trailing: Icon(context.locale.toString()=='en'?Icons.radio_button_checked:Icons.radio_button_off, color: primaryColor, size: 18),
+                  onTap: context.locale.toString() != 'en' ? () async {
+                    Navigator.of(context).pop();
+                    context.setLocale(Locale('en'));
+                  } : null,
+                ),
+                lang.length - 1 > 1 ? ListTile(
+                  title: Text(lg[lang[2]]!['text'].toString().tr(), style: TextStyle(color: textColor, fontSize: 14)),
+                  trailing: Icon(context.locale.toString()==lang[2] ? Icons.radio_button_checked:Icons.radio_button_off, color: primaryColor, size: 18),
+                  onTap: context.locale.toString() != lang[2] ? () async {
+                    Navigator.of(context).pop();
+                    context.setLocale(Locale(lang[2]));
+                  } : null,
+                ) : SizedBox(),
+                lang.length - 1 > 2 ? ListTile(
+                  title: Text(lg[lang[3]]!['text'].toString().tr(), style: TextStyle(color: textColor, fontSize: 14)),
+                  trailing: Icon(context.locale.toString()==lang[3] ? Icons.radio_button_checked:Icons.radio_button_off, color: primaryColor, size: 18),
+                  onTap: context.locale.toString() != lang[3] ? () async {
+                    Navigator.of(context).pop();
+                    context.setLocale(Locale(lang[3]));
+                  } : null,
+                ) : SizedBox(),
+                lang.length - 1 > 3 ? ListTile(
+                  title: Text(lg[lang[4]]!['text'].toString().tr(), style: TextStyle(color: textColor, fontSize: 14)),
+                  trailing: Icon(context.locale.toString()==lang[4] ? Icons.radio_button_checked:Icons.radio_button_off, color: primaryColor, size: 18),
+                  onTap: context.locale.toString() != lang[4] ? () async {
+                    Navigator.of(context).pop();
+                    context.setLocale(Locale(lang[4]));
+                  } : null,
+                ) : SizedBox(),
+              ],
+            ),
+            SizedBox(height: 16)
+          ],
+        ),
       );
     }
   );

+ 110 - 103
lib/src/layouts/auth/qr.dart

@@ -38,6 +38,7 @@ class _NewQrPageState extends State<NewQrPage> {
   String imageUrl = '';
   List lang = [];
   bool useAsset = false;
+  int sv = 0;
 
   @override
   void initState() {
@@ -60,7 +61,9 @@ class _NewQrPageState extends State<NewQrPage> {
         setState(() {
           companyName = lics['companyName']??'';
           serialNumber = lics['serialNumber']??'';
-          if(1698720817 <= lics['serverVersion']){
+          if(U.newServerVersion(1754624839)){
+            lang = lics['_validLang'] ?? [];
+          } else if(1698720817 <= lics['serverVersion']){
             lang = lics['languages'] != null ? lics['languages'].split(',') : [];
           }
         });
@@ -106,73 +109,75 @@ class _NewQrPageState extends State<NewQrPage> {
                       ]
                   )
               ),
-              child: Column(
-                crossAxisAlignment: CrossAxisAlignment.center,
-                children: [
-                  Image.asset('assets/image/logo/logo.png', width: 160),
-                  Expanded(
-                    child: Column(
-                      mainAxisAlignment: MainAxisAlignment.start,
-                      children: [
-                        Container(
-                          margin: EdgeInsets.only(top: 100, left: 16, right: 16, bottom: 48),
-                          child: kIsWeb?Column(
-                            children: [
-                              Text('noPIDText'.tr(), style: TextStyle(color: Colors.white, fontSize: 16, fontWeight: FontWeight.w500), textAlign: TextAlign.center),
-                              SizedBox(height: 16),
-                              Text('noPIDText2'.tr(), style: TextStyle(color: Colors.white, fontSize: 16), textAlign: TextAlign.center),
-                            ],
-                          ):Text('scanQr'.tr(), style: TextStyle(color: Colors.white, fontSize: 16, fontWeight: FontWeight.w400), textAlign: TextAlign.center),
-                        ),
-                        !kIsWeb?GestureDetector(
-                          onTap: scanDialog,
-                          child: Container(
-                            padding: EdgeInsets.symmetric(vertical: 32, horizontal: 64),
-                            decoration: BoxDecoration(
-                                color: primaryColor.withValues(alpha: 0.50),
-                                border: Border.all(color: Colors.white),
-                                borderRadius: BorderRadius.all(Radius.circular(12))
-                            ),
-                            child: Row(
-                              mainAxisSize: MainAxisSize.min,
+              child: SafeArea(
+                child: Column(
+                  crossAxisAlignment: CrossAxisAlignment.center,
+                  children: [
+                    Image.asset('assets/image/logo/logo.png', width: 160),
+                    Expanded(
+                      child: Column(
+                        mainAxisAlignment: MainAxisAlignment.start,
+                        children: [
+                          Container(
+                            margin: EdgeInsets.only(top: 100, left: 16, right: 16, bottom: 48),
+                            child: kIsWeb?Column(
                               children: [
-                                Text('buttonScan'.tr(), style: TextStyle(color: Colors.white, fontSize: 16)),
-                                SizedBox(width: 10),
-                                U.iconsax('scan', color: Colors.white)
+                                Text('noPIDText'.tr(), style: TextStyle(color: Colors.white, fontSize: 16, fontWeight: FontWeight.w500), textAlign: TextAlign.center),
+                                SizedBox(height: 16),
+                                Text('noPIDText2'.tr(), style: TextStyle(color: Colors.white, fontSize: 16), textAlign: TextAlign.center),
                               ],
-                            ),
+                            ):Text('scanQr'.tr(), style: TextStyle(color: Colors.white, fontSize: 16, fontWeight: FontWeight.w400), textAlign: TextAlign.center),
                           ),
-                        ):Container(),
-                        !kIsWeb && U.getAccessCode() != null ? Padding(
-                            padding: EdgeInsets.only(top: 16),
-                            child: GestureDetector(
-                              child: Text('backToLogin'.tr(), style: TextStyle(color: Colors.white, fontSize: 14, decoration: TextDecoration.underline)),
-                              onTap: (){
-                                context.router.removeLast();
-                                context.navigateToPath("/app/${U.getAccessCode()}/login");
-                              },
-                            )
-                        ) : Container(),
-                      ],
+                          !kIsWeb?GestureDetector(
+                            onTap: scanDialog,
+                            child: Container(
+                              padding: EdgeInsets.symmetric(vertical: 32, horizontal: 64),
+                              decoration: BoxDecoration(
+                                  color: primaryColor.withValues(alpha: 0.50),
+                                  border: Border.all(color: Colors.white),
+                                  borderRadius: BorderRadius.all(Radius.circular(12))
+                              ),
+                              child: Row(
+                                mainAxisSize: MainAxisSize.min,
+                                children: [
+                                  Text('buttonScan'.tr(), style: TextStyle(color: Colors.white, fontSize: 16)),
+                                  SizedBox(width: 10),
+                                  U.iconsax('scan', color: Colors.white)
+                                ],
+                              ),
+                            ),
+                          ):Container(),
+                          !kIsWeb && U.getAccessCode() != null ? Padding(
+                              padding: EdgeInsets.only(top: 16),
+                              child: GestureDetector(
+                                child: Text('backToLogin'.tr(), style: TextStyle(color: Colors.white, fontSize: 14, decoration: TextDecoration.underline)),
+                                onTap: (){
+                                  context.router.removeLast();
+                                  context.navigateToPath("/app/${U.getAccessCode()}/login");
+                                },
+                              )
+                          ) : Container(),
+                        ],
+                      ),
                     ),
-                  ),
-
-                  companyName.isNotEmpty?Text(companyName, style: TextStyle(fontSize: 16, color: Colors.white), textAlign: TextAlign.center):Container(),
-                  SizedBox(height: 5),
-                  serialNumber.isNotEmpty?Text('${'serialNumber'.tr()} $serialNumber', style: TextStyle(color: Colors.white, fontSize: 14)):Container(),
-                  SizedBox(height: 5),
-                  GestureDetector(
-                    child: Text('policy'.tr(), style: TextStyle(color: Colors.white, fontSize: 14, decoration: TextDecoration.underline)),
-                    onTap: () async {
-                      Uri _url = Uri.parse('https://telmessenger.com/privacy-police/telnow.html');
-                      await canLaunchUrl(_url) ? await launchUrl(_url) : print('Could not launch $_url');
-                    },
-                  ),
-                  SizedBox(height: 5),
-                  Text('${'version'.tr()} ${ApiAuthProvider().displayVersion}', style: TextStyle(color: Colors.white, fontSize: 14)),
-                  SizedBox(height: 5),
-                  Text('Build ${ApiAuthProvider().buildNumber}', style: TextStyle(color: Colors.white, fontSize: 13)),
-                ],
+                
+                    companyName.isNotEmpty?Text(companyName, style: TextStyle(fontSize: 16, color: Colors.white), textAlign: TextAlign.center):Container(),
+                    SizedBox(height: 5),
+                    serialNumber.isNotEmpty?Text('${'serialNumber'.tr()} $serialNumber', style: TextStyle(color: Colors.white, fontSize: 14)):Container(),
+                    SizedBox(height: 5),
+                    GestureDetector(
+                      child: Text('policy'.tr(), style: TextStyle(color: Colors.white, fontSize: 14, decoration: TextDecoration.underline)),
+                      onTap: () async {
+                        Uri _url = Uri.parse('https://telmessenger.com/privacy-police/telnow.html');
+                        await canLaunchUrl(_url) ? await launchUrl(_url) : print('Could not launch $_url');
+                      },
+                    ),
+                    SizedBox(height: 5),
+                    Text('${'version'.tr()} ${ApiAuthProvider().displayVersion}', style: TextStyle(color: Colors.white, fontSize: 14)),
+                    SizedBox(height: 5),
+                    Text('Build ${ApiAuthProvider().buildNumber}', style: TextStyle(color: Colors.white, fontSize: 13)),
+                  ],
+                ),
               ),
             ),
           ),
@@ -230,47 +235,49 @@ class _NewQrPageState extends State<NewQrPage> {
           context: context,
           backgroundColor: Colors.white,
           builder: (BuildContext contextt) {
-            return Column(
-              mainAxisSize: MainAxisSize.min,
-              children: <Widget>[
-                Padding(
-                  padding: const EdgeInsets.only(top: 15, bottom: 15),
-                  child: Center(
-                    child: Text('buttonScan'.tr(), style: TextStyle(color: textColor, fontWeight: FontWeight.w600)),
+            return SafeArea(
+              child: Column(
+                mainAxisSize: MainAxisSize.min,
+                children: <Widget>[
+                  Padding(
+                    padding: const EdgeInsets.only(top: 15, bottom: 15),
+                    child: Center(
+                      child: Text('buttonScan'.tr(), style: TextStyle(color: textColor, fontWeight: FontWeight.w600)),
+                    ),
                   ),
-                ),
-                divider(),
-                ListTile(
-                  leading: Icon(Icons.image),
-                  title: Text('fromGallery'.tr(), style: TextStyle(color: textColor)),
-                  onTap: () async{
-                    Navigator.of(contextt).pop();
-                    XFile? file = await ImagePicker().pickImage(source: ImageSource.gallery);
-                    if(file != null){
-                      final BarcodeCapture? barcodeCapture = await MobileScannerController().analyzeImage(file.path);
-                      String? str = barcodeCapture?.barcodes.singleOrNull?.displayValue;
-                      if (str != null) {
-                        scan(str);
-                      }
-                      else{
-                        showError(context, 'messageInvalidCode'.tr());
+                  divider(),
+                  ListTile(
+                    leading: Icon(Icons.image),
+                    title: Text('fromGallery'.tr(), style: TextStyle(color: textColor)),
+                    onTap: () async{
+                      Navigator.of(contextt).pop();
+                      XFile? file = await ImagePicker().pickImage(source: ImageSource.gallery);
+                      if(file != null){
+                        final BarcodeCapture? barcodeCapture = await MobileScannerController().analyzeImage(file.path);
+                        String? str = barcodeCapture?.barcodes.singleOrNull?.displayValue;
+                        if (str != null) {
+                          scan(str);
+                        }
+                        else{
+                          showError(context, 'messageInvalidCode'.tr());
+                        }
                       }
-                    }
-                  },
-                ),
-                ListTile(
-                  leading: Icon(Icons.camera),
-                  title: Text('fromCamera'.tr(), style: TextStyle(color: textColor)),
-                  onTap: () async{
-                    Navigator.of(contextt).pop();
-                    Navigator.push(context, MaterialPageRoute(builder: (context) => const ScanBarcodePage())).then((barcode){
-                      if(barcode != null){
-                        scan(barcode);
-                      }
-                    });
-                  },
-                )
-              ],
+                    },
+                  ),
+                  ListTile(
+                    leading: Icon(Icons.camera),
+                    title: Text('fromCamera'.tr(), style: TextStyle(color: textColor)),
+                    onTap: () async{
+                      Navigator.of(contextt).pop();
+                      Navigator.push(context, MaterialPageRoute(builder: (context) => const ScanBarcodePage())).then((barcode){
+                        if(barcode != null){
+                          scan(barcode);
+                        }
+                      });
+                    },
+                  )
+                ],
+              ),
             );
           }
         );

+ 37 - 35
lib/src/layouts/components/camera.dart

@@ -58,41 +58,43 @@ class _OpenCameraState extends State<OpenCamera> {
   @override
   Widget build(BuildContext context) {
     return Scaffold(
-        body: Container(
-          width: double.infinity, height: double.infinity,
-          child: Stack(children: [
-            (_cameraController.value.isInitialized) ? CameraPreview(_cameraController) : Container(color: Colors.black, child: const Center(child: CircularProgressIndicator())),
-            Align(
-                alignment: Alignment.bottomCenter,
-                child: Container(
-                  width: double.infinity,
-                  height: MediaQuery.of(context).size.height * 0.15,
-                  decoration: const BoxDecoration(color: Colors.black),
-                  child: Row(crossAxisAlignment: CrossAxisAlignment.center, children: [
-                    Expanded(
-                        child: IconButton(
-                          padding: EdgeInsets.zero,
-                          iconSize: 30,
-                          icon: Icon(_isRearCameraSelected ? Icons.cameraswitch_outlined : Icons.cameraswitch_rounded, color: Colors.white),
-                          onPressed: () {
-                            setState(() => _isRearCameraSelected = !_isRearCameraSelected);
-                            initCamera(widget.cameras![_isRearCameraSelected ? 0 : 1]);
-                          },
-                        )
-                    ),
-                    Expanded(
-                        child: IconButton(
-                          onPressed: takePicture,
-                          iconSize: 60,
-                          padding: EdgeInsets.zero,
-                          constraints: const BoxConstraints(),
-                          icon: const Icon(Icons.circle, color: Colors.white),
-                        )
-                    ),
-                    const Spacer(),
-                  ]),
-                )),
-          ]),
+        body: SafeArea(
+          child: Container(
+            width: double.infinity, height: double.infinity,
+            child: Stack(children: [
+              (_cameraController.value.isInitialized) ? CameraPreview(_cameraController) : Container(color: Colors.black, child: const Center(child: CircularProgressIndicator())),
+              Align(
+                  alignment: Alignment.bottomCenter,
+                  child: Container(
+                    width: double.infinity,
+                    height: MediaQuery.of(context).size.height * 0.15,
+                    decoration: const BoxDecoration(color: Colors.black),
+                    child: Row(crossAxisAlignment: CrossAxisAlignment.center, children: [
+                      Expanded(
+                          child: IconButton(
+                            padding: EdgeInsets.zero,
+                            iconSize: 30,
+                            icon: Icon(_isRearCameraSelected ? Icons.cameraswitch_outlined : Icons.cameraswitch_rounded, color: Colors.white),
+                            onPressed: () {
+                              setState(() => _isRearCameraSelected = !_isRearCameraSelected);
+                              initCamera(widget.cameras![_isRearCameraSelected ? 0 : 1]);
+                            },
+                          )
+                      ),
+                      Expanded(
+                          child: IconButton(
+                            onPressed: takePicture,
+                            iconSize: 60,
+                            padding: EdgeInsets.zero,
+                            constraints: const BoxConstraints(),
+                            icon: const Icon(Icons.circle, color: Colors.white),
+                          )
+                      ),
+                      const Spacer(),
+                    ]),
+                  )),
+            ]),
+          ),
         )
     );
   }

+ 27 - 9
lib/src/layouts/components/template.dart

@@ -1,7 +1,7 @@
 import 'dart:async';
 import 'dart:convert';
 import 'dart:io';
-import 'dart:ui';
+import 'dart:ui' as ui;
 
 import 'package:another_flushbar/flushbar.dart';
 import 'package:auto_route/auto_route.dart';
@@ -83,7 +83,7 @@ Widget textHorizontal(String title, String subtitle, {double size = 14, double o
         child: copy?Row(
           mainAxisAlignment: MainAxisAlignment.end,
           children: [
-            Expanded(child: Text(subtitle, style: TextStyle(color: textColor, fontSize: size), maxLines: 3, overflow: TextOverflow.ellipsis, textAlign: TextAlign.right)),
+            Expanded(child: Text(subtitle, style: TextStyle(color: textColor, fontSize: size), maxLines: 3, overflow: TextOverflow.ellipsis, textAlign: TextAlign.end)),
             SizedBox(width: 5),
             InkWell(
               child: Icon(Icons.copy, color: textColor, size: size+2),
@@ -93,14 +93,17 @@ Widget textHorizontal(String title, String subtitle, {double size = 14, double o
               },
             ),
           ],
-        ):Text(subtitle, style: TextStyle(color: textColor, fontSize: size), maxLines: 3, overflow: TextOverflow.ellipsis, textAlign: TextAlign.right),
+        ):Text("$subtitle", style: TextStyle(color: textColor, fontSize: size), maxLines: 3, overflow: TextOverflow.ellipsis, textAlign: TextAlign.end,),
       ),
     ],
   );
 }
 
-Widget divider({opacity = 0.15}){
-  return Divider(height: 1, thickness: 1, color: textColor.withValues(alpha: opacity));
+Widget divider({opacity = 0.15, EdgeInsetsGeometry padding = const EdgeInsets.only(top: 0)}){
+  return Padding(
+    padding: padding,
+    child: Divider(height: 1, thickness: 1, color: textColor.withValues(alpha: opacity)),
+  );
 }
 
 Widget dashed({double top = 8, double bottom = 8, double left = 0, double right = 0, Color color = textColor, double width = double.infinity}){
@@ -167,7 +170,7 @@ Widget shimmerTopMenu(BuildContext context){
 Widget requestTiles({required String image, required String title, required String subtitle, bool border = false, double vertical = 16}){
   return Container(
     margin: EdgeInsets.symmetric(vertical: vertical),
-    padding: EdgeInsets.only(right: border?16:0),
+    padding: EdgeInsetsDirectional.only(end: border?16:0),
     child: Row(
       children: [
         imageTiles(imageUrl: image),
@@ -272,6 +275,21 @@ Widget loadingTemplate(VoidCallback setState){
   );
 }
 
+Widget loadingTemplateNoVoid(){
+  return Center(
+    child: Container(
+      height: 36,
+      child: LoadingIndicator(
+        indicatorType: Indicator.ballPulseRise,
+        colors: U.defaultRainbowColors(),
+        strokeWidth: 2,
+        backgroundColor: Colors.black.withValues(alpha: 0),
+        pathBackgroundColor: Colors.black,
+      ),
+    ),
+  );
+}
+
 Widget showButton(BuildContext context){
   var pid = U.getPidFromUrl(context.router.currentUrl);
   return Center(
@@ -492,7 +510,7 @@ navigateTo(BuildContext context, child){
 }
 
 navigateToThen(BuildContext context, child, act){
-  return Navigator.push(context, PageTransition(type: PageTransitionType.rightToLeft, child: child)).then((v) => act());
+  Navigator.push(context, PageTransition(type: PageTransitionType.rightToLeft, child: child)).then((v) => act());
 }
 
 navigateBack(BuildContext context, {exc = false}){
@@ -529,7 +547,7 @@ void showSuccess(context, message) {
   )..show(context);
 }
 
-String convertDate(date, locale) {
+String convertDate(date, String locale) {
   if (date == "-" || date.isEmpty) {
     return date;
   }
@@ -751,7 +769,7 @@ class NoImageFound extends StatelessWidget {
           ClipRRect(
             // Clip it cleanly.
             child: BackdropFilter(
-              filter: ImageFilter.blur(sigmaX: 10, sigmaY: 10),
+              filter: ui.ImageFilter.blur(sigmaX: 10, sigmaY: 10),
               child: Container(
                 color: Colors.grey.withValues(alpha: 0.5),
                 alignment: Alignment.center,

+ 25 - 7
lib/src/layouts/functions/account.dart

@@ -7,6 +7,7 @@ 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/storage/sharedpreferences/shared_preferences_manager.dart';
+import 'package:telnow_mobile_new/src/utils/C.dart';
 import 'package:telnow_mobile_new/src/utils/U.dart';
 import 'package:telnow_mobile_new/src/utils/provider.dart';
 
@@ -28,14 +29,31 @@ class AccountFunction{
 
   switchLang(BuildContext context, code, user) async {
     showLoading(context);
-    var res = await apiAuthProvider.patchData('/api/informants/' + user['id'].toString(), {'userId': user['userId'], 'language': code.toUpperCase()}, context);
-    if (res != null) {
-      closeLoading(context);
-      context.setLocale(Locale(code));
-      notification.startNotification(context);
-      Navigator.of(context).pop();
+    var locale = 'id';
+    if(code is int){
+      var vl = U.retValidLang();
+      locale = vl[code+1];
+      code = code.toString();
+    } else {
+      locale = code;
     }
-    else{
+    try{
+      var p;
+      if(await U.isCompatibleWith(VersionKey.multiBahasa)){
+        p = {'userId': user['userId'], 'language': 'ID', '_language': code.toUpperCase()};
+      } else {
+        p = {'userId': user['userId'], 'language': code.toUpperCase()};
+      }
+      var res = await apiAuthProvider.patchData('/api/informants/' + user['id'].toString(), p, context);
+      if (res != null) {
+        closeLoading(context);
+        context.setLocale(Locale(locale));
+        notification.startNotification(context, code: code);
+        // Navigator.of(context).pop();
+      } else {
+        closeLoading(context);
+      }
+    }catch(e){
       closeLoading(context);
     }
   }

+ 2 - 0
lib/src/layouts/functions/history.dart

@@ -146,6 +146,8 @@ class HistoryFunction{
             }
             Provider.of<HistoryModule>(context, listen: false).addPage();
           }
+          // print("tempData");
+          // print(tempData);
           Provider.of<HistoryModule>(context, listen: false).setMisi(tempData);
 
           if (Provider.of<HistoryModule>(context, listen: false).getMisiLength() >= misi['page']['totalElements']) {

+ 26 - 0
lib/src/layouts/functions/home.dart

@@ -5,6 +5,7 @@ import 'package:telnow_mobile_new/src/injector/injector.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:provider/provider.dart';
+import 'package:telnow_mobile_new/src/utils/C.dart';
 import 'package:telnow_mobile_new/src/utils/U.dart';
 import 'package:telnow_mobile_new/src/utils/cache_manager.dart';
 import 'package:telnow_mobile_new/src/utils/provider.dart';
@@ -268,6 +269,18 @@ class HomeFunction{
     }
   }
 
+  getContactCenter(BuildContext context) async {
+    try{
+      var url = "/api/contactCenter/whatsapp" ;
+      var res = await apiAuthProvider.getData(url, null, context);
+      if (res != null) {
+        Provider.of<ServiceModule>(context, listen: false).setContactCenter(res);
+      }
+    }catch(e){
+      Provider.of<ServiceModule>(context, listen: false).setContactCenter('');
+    }
+  }
+
   getUnreadMessages(BuildContext context) async {
     try {
       var res = await apiAuthProvider.getData('/api/messages/search/myMessages', null, context);
@@ -291,6 +304,19 @@ class HomeFunction{
     } catch (e) {
       print(e.toString());
     }
+
+    if(await U.isCompatibleWith(VersionKey.multiBahasa) == false) return;
+    try{
+      String url = '/api/messages/search/myForum';
+      var res = await apiAuthProvider.getData(url, null, context);
+      if (res != null) {
+        for (int i = 0; i < res.length; i++) {
+          if (res[i]['readStatus'] == 'UNREAD' && res[i]['userId'] != Provider.of<UserModule>(context, listen: false).user()['userId']){
+            Provider.of<ServiceModule>(context, listen: false).setUnreadMessage(true);
+          }
+        }
+      }
+    }catch(e){}
   }
 
   onRefresh(BuildContext context){

+ 35 - 2
lib/src/layouts/functions/message.dart

@@ -1,6 +1,8 @@
 import 'package:easy_localization/easy_localization.dart';
 import 'package:flutter/cupertino.dart';
 import 'package:flutter/foundation.dart';
+import 'package:flutter/material.dart';
+import 'package:flutter/services.dart';
 import 'package:provider/provider.dart';
 import 'package:telnow_mobile_new/src/api/api_auth_provider.dart';
 import 'package:telnow_mobile_new/src/api/jwt_token.dart';
@@ -8,9 +10,11 @@ import 'package:telnow_mobile_new/src/layouts/mobile/message_broadcast.dart';
 import 'package:telnow_mobile_new/src/layouts/mobile/message_select.dart';
 import 'package:telnow_mobile_new/src/layouts/components/template.dart';
 import 'package:telnow_mobile_new/src/layouts/web/message_broadcast.dart';
+import 'package:telnow_mobile_new/src/utils/C.dart';
 import 'package:telnow_mobile_new/src/utils/U.dart';
 import 'package:telnow_mobile_new/src/utils/cache_manager.dart';
 import 'package:telnow_mobile_new/src/utils/provider.dart';
+import 'package:url_launcher/url_launcher.dart';
 
 class MessageFunction{
   final ApiAuthProvider apiAuthProvider = ApiAuthProvider();
@@ -25,6 +29,7 @@ class MessageFunction{
   }
 
   getDataMessages(BuildContext context) async {
+    if(await U.isCompatibleWith(VersionKey.multiBahasa)) getDataForum(context);
     String url = '/api/messages/search/myMessages';
     var val = await CacheMan.readData(url);
     if (val != null) {
@@ -43,12 +48,40 @@ class MessageFunction{
       CacheMan.writeData(url, tempData);
       Provider.of<MessageModule>(context, listen: false).setData(tempData);
       Provider.of<MessageModule>(context, listen: false).setFirstLoad(true);
-    }
-    else{
+    } else {
       Provider.of<MessageModule>(context, listen: false).setFirstLoad(true);
     }
   }
 
+  getDataForum(BuildContext context) async{
+    String url = '/api/messages/search/myForum';
+    var val = await CacheMan.readData(url);
+    final messageModule = Provider.of<MessageModule>(context,listen: false);
+    if (val != null){
+      messageModule.setForum(val['data']);
+    }
+
+    var res = await apiAuthProvider.getData(url, null, context);
+    if (res != null) {
+      List tempData = [];
+      for (int i = 0; i < res.length; i++) {
+        tempData.add(res[i]);
+      }
+
+      CacheMan.writeData(url, tempData);
+      messageModule.setForum(tempData);
+      messageModule.setFirstLoad(true);
+    } else {
+      messageModule.setFirstLoad(true);
+    }
+  }
+
+  setAsRead(context, ticketNo) async {
+    var res = await apiAuthProvider.postData('/api/notifications/readMyForum/$ticketNo', null, null, context);
+    if (res != null) {
+    }
+  }
+
   createMessage(BuildContext context) async{
     String url = '/api/tenants/search/customFind';
     List dataTenant = [];

+ 5 - 3
lib/src/layouts/functions/request.dart

@@ -39,7 +39,7 @@ class RequestFunction{
         String url = '';
 
         if(_keyword != null){
-          String subject = U.langColumn(context, 'subject');
+          String subject = U.realColumn(context.locale, 'subject');
           search = ',{"f":["$subject","LIKE","%$_keyword%"]}';
         }
 
@@ -223,16 +223,18 @@ class RequestFunction{
 
       try{
         var res = await apiAuthProvider.postData('/api/receptionists/send/request', null, data, context);
-        // print("res ##> $res");
+        print("res ##> $res");
         if (res != null) {
           Future.delayed(Duration(seconds: 3), (){
             closeLoading(context);
-            navigateTo(context, U.webView(context)?WebReqSuccessPage(user: widget.user, ticketNo: res['ticketNo'], fromSearch: widget.fromSearch):MobReqSuccessPage(user: widget.user, ticketNo: res['ticketNo'], fromSearch: widget.fromSearch)).then((res){
+            navigateTo(context, U.webView(context) ? WebReqSuccessPage(user: widget.user, ticketNo: res['ticketNo'], fromSearch: widget.fromSearch) : MobReqSuccessPage(user: widget.user, ticketNo: res['ticketNo'], fromSearch: widget.fromSearch)).then((res){
               while (Navigator.canPop(context)){
                 Navigator.pop(context);
               }
             });
           });
+        } else {
+          closeLoading(context);
         }
       }catch(e){
         print("error: $e");

+ 321 - 0
lib/src/layouts/landing/landing_page.dart

@@ -0,0 +1,321 @@
+import 'dart:convert';
+import 'dart:typed_data';
+
+import 'package:auto_route/annotations.dart';
+import 'package:auto_route/auto_route.dart';
+import 'package:easy_localization/easy_localization.dart';
+import 'package:flutter/material.dart';
+import 'package:flutter_svg/svg.dart';
+import 'package:telnow_mobile_new/src/utils/U.dart';
+import 'package:url_launcher/url_launcher.dart';
+
+import '../../api/api_auth_provider.dart';
+
+@RoutePage()
+class LandingPage extends StatefulWidget {
+  const LandingPage({super.key});
+
+  @override
+  State<LandingPage> createState() => _LandingPageState();
+}
+
+class _LandingPageState extends State<LandingPage> {
+  final ApiAuthProvider _apiAuthProvider = ApiAuthProvider();
+  var data = {};
+  var lic = {};
+  var languages = [];
+  var promo = [];
+  bool isLoading = true;
+  String accessCode = '';
+  String locale = 'id';
+
+  getData() async{
+    try {
+      lic = await U.getLicense();
+      data = await _apiAuthProvider.getDataNoAuth('/api/landingPage/config');
+      promo = await _apiAuthProvider.getDataNoAuth('/api/landingPage/promo');
+      isLoading = false;
+      setState((){});
+    }catch(e){
+      print(e.toString());
+    }
+  }
+
+  setLang(List<dynamic> l){
+    if(l.isNotEmpty){
+      l.forEach((v){
+        languages.add({
+          "val": v,
+          "icon": '$v.svg',
+          'label': v.toUpperCase()
+        });
+      });
+    }
+  }
+
+  @override
+  void initState() {
+    // TODO: implement initState
+    accessCode = U.getAccessCode()!;
+    getData().then((t){
+      setLang(lic['_validLang']);
+    });
+    super.initState();
+  }
+
+  @override
+  Widget build(BuildContext context) {
+    locale = context.locale.toString();
+    return isLoading ? Center(child: CircularProgressIndicator(),) : Scaffold(
+      appBar: AppBar(
+        backgroundColor: Colors.white,
+        foregroundColor: Colors.black,
+        title: Row(
+          children: [
+            Padding(
+              padding: EdgeInsets.symmetric(
+                  horizontal: 8.0, vertical: 8.0),
+              child: Image.network(getImageUrl(data['logo']), height: 36,),
+            ),
+            Text(data['title'], style: TextStyle(fontWeight: FontWeight.w600),),
+          ],
+        ),
+        actions: [
+          Container(
+            decoration: BoxDecoration(
+              // border: Border.all(),
+                borderRadius: BorderRadius.all(Radius.circular(12))
+            ),
+            padding: EdgeInsets.only(right: 16),
+            child: ddLang(context)
+          )
+        ],
+      ),
+      body: LayoutBuilder(
+          builder: (BuildContext context, constraint) {
+            return SingleChildScrollView(
+              child: Column(
+                crossAxisAlignment: CrossAxisAlignment.start,
+                children: [
+                  SizedBox(
+                    width: constraint.maxWidth,
+                    child: Image.network(getImageUrl(data['background-url'])),
+                  ),
+                  SizedBox(height: 24,),
+                  container(label(data['content']['title'][locale])),
+                  container(Text(data['content']['description'][locale])),
+                  promos(promo),
+                  cta(data['cta'], () => context.navigateToPath('/app/$accessCode/login')),
+                  connectUs(data)
+                ],
+              ),
+            );
+          }
+      ),
+    );
+  }
+
+  getByLang(String label){
+    String val = '';
+    switch (locale){
+      case 'id':
+        val = label;
+        break;
+      case 'en':
+        val = '${label}En';
+        break;
+      default:
+        val = '_$label${locale[0].toUpperCase()}${locale.substring(1)}';
+        break;
+    }
+    // print(val);
+    return val;
+  }
+
+  ddLang(BuildContext context){
+    String selectedValue = context.locale.toString();
+    List<dynamic> items = languages;
+
+    return DropdownButton<String>(
+      value: selectedValue,
+      elevation: 0,
+      underline: SizedBox(),
+      focusColor: Color(0x0000ffff),
+      items: items.map((item) {
+        return DropdownMenuItem<String>(
+          value: item['val'],
+          child: SizedBox(
+            width: 52,
+            child: Row(
+              children: [
+                SvgPicture.network(getImageUrl(item['icon']), width: 16, height: 16,),
+                // Image.network(getImage(item['icon']), width: 24, height: 24,),
+                SizedBox(width: 4,),
+                Text(item['label']),
+              ],
+            ),
+          ),
+        );
+      }).toList(),
+      onChanged: (newValue) {
+        // print(newValue);
+        context.setLocale(Locale(newValue!));
+        setState(() {
+          selectedValue = newValue;
+        });
+      },
+    );
+
+  }
+
+  getImageUrl<String>(String filename){
+    return '${_apiAuthProvider.baseUrl}${U.decodeBase64Url(Uri.decodeComponent(U.getAccessCode()!))}/assets/landingPage/images/$filename';
+  }
+
+  frameImg(String img){
+    return Container(
+      height: 240,
+      decoration: BoxDecoration(
+          image: DecorationImage(image: NetworkImage(img), fit: BoxFit.cover),
+          borderRadius: BorderRadius.all(Radius.circular(16))
+      ),
+    );
+  }
+
+  container(Widget child){
+    return Padding(
+      padding: EdgeInsets.symmetric(
+        vertical: 6,
+        horizontal: 16,
+      ),
+      child: child,
+    );
+  }
+
+  label(String txt, {double size = 24}){
+    return Text(
+      txt,
+      style: TextStyle(
+          fontWeight: FontWeight.w700,
+          fontSize: size
+      ),
+    );
+  }
+
+  promos(data){
+    return Column(
+      children: List.generate(data.length, (i){
+        return Column(
+          crossAxisAlignment: CrossAxisAlignment.start,
+          children: [
+            SizedBox(height: 24,),
+            container(frameImg(data[i]['image'])),
+            container(label(data[i][getByLang('title')], size: 18)),
+            container(Text(data[i][getByLang('description')])),
+            SizedBox(height: 12,),
+          ],
+        );
+      }),
+    );
+  }
+
+  cta(d, cb){
+    var h = 220.0;
+    return Stack(
+      children: [
+        Container(
+          height: h,
+          decoration: BoxDecoration(
+            image: DecorationImage(image: NetworkImage(getImageUrl(d['image'])), fit: BoxFit.cover),
+          ),
+          // color: Colors.black.withValues(alpha: 0.5),
+        ),
+        Container(
+          height: h,
+          color: Colors.black.withValues(alpha: 0.5),
+        ),
+        Container(
+          padding: EdgeInsets.all(24.0),
+          height: h,
+          child: Column(
+            spacing: 8,
+            mainAxisAlignment: MainAxisAlignment.end,
+            crossAxisAlignment: CrossAxisAlignment.start,
+            children: [
+              Text(
+                d['title'][locale],
+                style: TextStyle(
+                    color: Colors.white,
+                    fontSize: 20,
+                    fontWeight: FontWeight.w600
+                ),
+              ),
+              Text(d['description'][locale], style: TextStyle(color: Colors.white),),
+              SizedBox(height: 16,),
+              ElevatedButton(
+                style: ElevatedButton.styleFrom(
+                  padding: EdgeInsets.all(20),
+                  backgroundColor: Color(0xFFFFA800), // Background color of the button
+                  foregroundColor: Colors.white, // Color of the text and icon
+                ),
+                onPressed: cb,
+                child: Text(d['label'][locale]),
+              )
+            ],
+          ),
+        )
+      ],
+    );
+  }
+
+  connectUs(data){
+    var dt = data['link'];
+    return Padding(
+      padding: EdgeInsets.symmetric(vertical: 24, horizontal: 16),
+      child: Column(
+        crossAxisAlignment: CrossAxisAlignment.start,
+        children: [
+          Text(
+            data['contact'][locale],
+            style: TextStyle(
+                fontSize: 22,
+                fontWeight: FontWeight.w700
+            ),
+          ),
+          SizedBox(height: 24,),
+          Wrap(
+            runSpacing: 20.0,
+            children: List.generate(dt.length, (i){
+              return LayoutBuilder(builder: (BuildContext context, constraint){
+                return SizedBox(
+                  width: constraint.maxWidth/2-10,
+                  child: GestureDetector(
+                    onTap: dt[i]['url'] == '' ? (){} : () async {
+                      var url = Uri.parse(dt[i]['url']);
+                      await canLaunchUrl(url) ? await launchUrl(url) : print('Could not launch $url');
+                    },
+                    child: Row(
+                      children: [
+                        Image.network(getImageUrl(dt[i]['icon']), width: 24, height: 24,),
+                        SizedBox(width: 12,),
+                        Text(dt[i]['label'], style: TextStyle(fontSize: 16, fontWeight: FontWeight.w600),)
+                      ],
+                    ),
+                  ),
+                );
+              });
+            }),
+          ),
+          SizedBox(height: 24,),
+          Divider(),
+          Padding(
+            padding: EdgeInsets.only(top: 8),
+            child: Center(
+              child: Text(data['footer']),
+            ),
+          )
+        ],
+      ),
+    );
+  }
+}

+ 8 - 3
lib/src/layouts/mobile/app_mobile.dart

@@ -13,7 +13,6 @@ import 'package:telnow_mobile_new/src/injector/injector.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:upgrader/upgrader.dart';
 
 class MobAppPage extends StatefulWidget {
   final Widget child;
@@ -68,7 +67,13 @@ class _MobAppPageState extends State<MobAppPage>with TickerProviderStateMixin {
       if(connected){
         setState(()=>isConnected = true);
         notification.initFirebaseMessaging(context);
-        notification.startNotification(context);
+        var valLang = U.retValidLang();
+        var langCode = context.locale.toString();
+        var code;
+        if(valLang.indexOf(langCode) > 1){
+          code = valLang.indexOf(langCode) - 1;
+        }
+        notification.startNotification(context, code: code);
 
         if(U.newServerVersion(1709864293)){
           U.checkPendingRequest(context);
@@ -138,7 +143,7 @@ class _MobMenuTemplateState extends State<MobMenuTemplate> {
     }
     FirebaseMessaging.onMessage.listen((event) {
       if(event.data['type'] != 'MESSAGE'){
-        if(!context.router.currentUrl.endsWith("history")){
+        if(!context.router.currentUrl.endsWith("history") && mounted){
           setState(() => historyMark = true);
           sharedPreferencesManager.putBool(SharedPreferencesManager.keyHistoryMark, true);
         }

+ 38 - 36
lib/src/layouts/mobile/history_detail.dart

@@ -48,8 +48,8 @@ class _MobHistoryDetailPageState extends State<MobHistoryDetailPage> {
       user = res['user'];
       list = res['list'];
     });
-    print(list['parentTicket']);
-    print(list['_parentTicket']);
+    // print(list);
+    // print(list['_parentTicket']);
   }
 
   @override
@@ -60,8 +60,13 @@ class _MobHistoryDetailPageState extends State<MobHistoryDetailPage> {
           Center(
             child: GestureDetector(
               child: Container(
-                margin: EdgeInsets.only(right: 16),
+                margin: EdgeInsetsDirectional.only(end: 16),
                 padding: EdgeInsets.fromLTRB(16, 9, 16, 9),
+                decoration: BoxDecoration(
+                    color: primaryColor.withValues(alpha: 0.1),
+                    border: Border.all(color: primaryColor),
+                    borderRadius: BorderRadius.all(Radius.circular(50))
+                ),
                 child: Row(
                   children: [
                     Text('forum'.tr(), style: TextStyle(color: primaryColor, fontSize: 14)),
@@ -69,11 +74,6 @@ class _MobHistoryDetailPageState extends State<MobHistoryDetailPage> {
                     U.iconsax('messages-3', color: primaryColor, size: 20)
                   ],
                 ),
-                decoration: BoxDecoration(
-                    color: primaryColor.withValues(alpha: 0.1),
-                    border: Border.all(color: primaryColor),
-                    borderRadius: BorderRadius.all(Radius.circular(50))
-                ),
               ),
               onTap: (){
                 Provider.of<HistoryModule>(context, listen: false).setForumFalse(widget.index);
@@ -98,7 +98,7 @@ class _MobHistoryDetailPageState extends State<MobHistoryDetailPage> {
                         child: Column(
                           crossAxisAlignment: CrossAxisAlignment.start,
                           children: [
-                            Text(list[U.langColumn(context, 'requestGroupDescription')], style: TextStyle(color: textColor, fontWeight: FontWeight.w600)),
+                            Text(list[U.langColumn(context, 'requestGroupDescription')]??'', style: TextStyle(color: textColor, fontWeight: FontWeight.w600)),
                             requestTiles(
                                 image: list['_requestImage'] ?? "null",
                                 title: list[U.langColumn(context, 'requestSubject')],
@@ -333,34 +333,36 @@ class _MobHistoryDetailPageState extends State<MobHistoryDetailPage> {
               child: list['currentState'] == 'DIMULAI' && list['_canNotCancel'] ? Text('canNotCancel2'.tr(), style: TextStyle(color: textColor, fontSize: 14), textAlign: TextAlign.center) : Container(
                 alignment: Alignment.bottomCenter,
                 width: U.bodyWidth(context),
-                child: Column(
-                  children: [
-                    SizedBox(
-                      width: double.infinity,
-                      child: TextField(
-                        controller: controllerNote,
-                        style: const TextStyle(fontSize: 14, color: Colors.black),
-                        maxLength: 128,
-                        decoration: InputDecoration(
-                            counterText: '',
-                            counterStyle: TextStyle(color: Colors.transparent, fontSize: 0),
-                            hintText: 'addNoteCancel'.tr(),
-                            hintStyle: TextStyle(color: textColor.withValues(alpha: 0.5), fontSize: 14),
-                            filled: true,
-                            fillColor: backgroundColor,
-                            hoverColor: Colors.black.withValues(alpha: 0.1),
-                            contentPadding: EdgeInsets.all(13),
-                            prefixIcon: Padding(padding: EdgeInsets.only(left: 13, right: 13), child: U.iconsax('edit-2', color: textColor, size: 24)),
-                            border: InputBorder.none,
-                            enabledBorder: OutlineInputBorder(borderRadius: BorderRadius.circular(12), borderSide: BorderSide(color: Color(0xff262626).withValues(alpha: 0.5))),
-                            focusedBorder: OutlineInputBorder(borderRadius: BorderRadius.circular(12), borderSide: const BorderSide(color: primaryColor)),
-                            isDense: true
+                child: SafeArea(
+                  child: Column(
+                    children: [
+                      SizedBox(
+                        width: double.infinity,
+                        child: TextField(
+                          controller: controllerNote,
+                          style: const TextStyle(fontSize: 14, color: Colors.black),
+                          maxLength: 128,
+                          decoration: InputDecoration(
+                              counterText: '',
+                              counterStyle: TextStyle(color: Colors.transparent, fontSize: 0),
+                              hintText: 'addNoteCancel'.tr(),
+                              hintStyle: TextStyle(color: textColor.withValues(alpha: 0.5), fontSize: 14),
+                              filled: true,
+                              fillColor: backgroundColor,
+                              hoverColor: Colors.black.withValues(alpha: 0.1),
+                              contentPadding: EdgeInsets.all(13),
+                              prefixIcon: Padding(padding: EdgeInsets.only(left: 13, right: 13), child: U.iconsax('edit-2', color: textColor, size: 24)),
+                              border: InputBorder.none,
+                              enabledBorder: OutlineInputBorder(borderRadius: BorderRadius.circular(12), borderSide: BorderSide(color: Color(0xff262626).withValues(alpha: 0.5))),
+                              focusedBorder: OutlineInputBorder(borderRadius: BorderRadius.circular(12), borderSide: const BorderSide(color: primaryColor)),
+                              isDense: true
+                          ),
                         ),
                       ),
-                    ),
-                    SizedBox(height: 10),
-                    buttonTemplate(text: 'buttonCancel'.tr(), backgroundColor: textColor.withValues(alpha: 0.1), textColor: textColor.withValues(alpha: 0.75), borderColor: textColor.withValues(alpha: 0.20), action: () => detFunc.cancelRequest(context, list, controllerNote))
-                  ],
+                      SizedBox(height: 10),
+                      buttonTemplate(text: 'buttonCancel'.tr(), backgroundColor: textColor.withValues(alpha: 0.1), textColor: textColor.withValues(alpha: 0.75), borderColor: textColor.withValues(alpha: 0.20), action: () => detFunc.cancelRequest(context, list, controllerNote))
+                    ],
+                  ),
                 ),
               ),
             )
@@ -537,7 +539,7 @@ class _MobHistoryDetailPageState extends State<MobHistoryDetailPage> {
     return TimelineTile(
       nodeAlign: TimelineNodeAlign.start,
       contents: Container(
-        margin: EdgeInsets.fromLTRB(15, 10, 0, 10),
+        margin: EdgeInsets.fromLTRB(16, 10, 16, 10),
         child: Row(
           children: [
             Text(label, style: TextStyle(fontSize: 14, color: textColor.withValues(alpha: 0.85))),

+ 206 - 175
lib/src/layouts/mobile/history_forum.dart

@@ -55,6 +55,7 @@ class _MobHistoryForumPageState extends State<MobHistoryForumPage> {
   bool isReverse = false;
   bool openChat = false;
   Uint8List? _image;
+  String locale = 'id';
 
   @override
   void initState() {
@@ -66,6 +67,13 @@ class _MobHistoryForumPageState extends State<MobHistoryForumPage> {
     super.initState();
   }
 
+  @override
+  void didChangeDependencies() {
+    super.didChangeDependencies();
+    locale = context.locale.toString() == 'zh' ? 'zh-cn' : context.locale.toString();
+  }
+
+
   getData() async {
     idChat = U.decodeBase64Url(_sharedPreferencesManager.getString(SharedPreferencesManager.keyAccessCode)!) + '-' + widget.data['ticketNo'];
     if (!kIsWeb) imagePath = await token.getPath() + '/TelNow/Images/';
@@ -73,7 +81,7 @@ class _MobHistoryForumPageState extends State<MobHistoryForumPage> {
     if (res != null) {
       username = res['userId'];
       getMessage();
-      getCollectionData();
+      // getCollectionData();
     }
   }
 
@@ -91,16 +99,15 @@ class _MobHistoryForumPageState extends State<MobHistoryForumPage> {
       if (mymess.containsKey('_embedded')) {
         List data = mymess['_embedded']['myMessages'];
         for (int i = 0; i < data.length; i++) {
-          if (username == data[i]['from']['user']) {
+          if (username == data[i]['from']['user'] && data[i]['senderType'] != 'SERVANT') {
             data[i]['selected'] = false;
-          }
-          else{
+          } else {
             if(U.autoTranslate()){
               data[i]['translate'] = '';
             }
           }
 
-          setState(() => messageData.insert(0, data[i]));
+          messageData.insert(0, data[i]);
           downloadImage(data[i]);
         }
 
@@ -126,40 +133,56 @@ class _MobHistoryForumPageState extends State<MobHistoryForumPage> {
         });
       }
     }
+
+    getCollectionData();
   }
 
-  translateMessage(){
-    var locale = context.locale.toString() == 'zh' ? 'zh-cn' : context.locale.toString();
+  translateMessage() async{
+    var loc = locale == 'zh' ? 'zh-cn' : locale;
     messageData.forEach((element) async{
-      if (username != element['from']['user'] && element['translate'] == '') {
-        var translate = await translator.translate(element['msg']??'', to: locale);
-        setState(() {
+      if (element['senderType'] == 'SERVANT' && element['translate'] == '') { // && username != element['from']['user']
+        try {
+          var translate = await translator.translate(element['msg'] ?? '', to: loc);
           element['translate'] = translate.text;
-        });
+        }catch(e){
+          print("Error translate: ${e.toString()}");
+        }
       }
     });
+    setState(() {});
   }
 
   getCollectionData() {
-    FirebaseFirestore.instance.collection("tmMessages").doc('messages').collection(idChat!).snapshots().listen((querySnapshot) {
-      setState(() {
+    try{
+      FirebaseFirestore.instance.collection("tmMessages").doc('messages').collection(idChat!).snapshots().listen((querySnapshot) {
         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')) {
+            if (
+            (
+                (username == data!['from']['user'] && data!['senderType'] != 'SERVANT') &&
+                    messageData.where((element) => element['uniqueId'] == data['uniqueId']).isNotEmpty
+            ) ||
+                (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;
+                try{
+                  var translate = await translator.translate(data['msg']??'', to: locale);
+                  data['translate'] = translate.text;
+                }catch(e){
+                  print("Error translate: ${e.toString()}");
+                }
+              }
+              bool alreadyExists = messageData.any((element) => element['uniqueId'] == data['uniqueId']);
+              if (!alreadyExists) {
+                messageData.add(data);
               }
-              messageData.add(data);
             }
-
             downloadImage(data);
           } else if (result.type == DocumentChangeType.modified && isAfterLoad) {
             if (messageData.where((element) => element['uniqueId'] == data!['uniqueId']).length > 0) {
@@ -168,10 +191,16 @@ class _MobHistoryForumPageState extends State<MobHistoryForumPage> {
               messageData[index]['readStatus'] = data['readStatus'];
             }
           }
+          if(mounted) setState(() {});
         });
+      });
+    }catch(e){}
+
+    if(mounted) {
+      setState(() {
         isAfterLoad = true;
       });
-    });
+    }
   }
 
   Future<bool> cacheImage(String url, BuildContext context) async {
@@ -292,9 +321,7 @@ class _MobHistoryForumPageState extends State<MobHistoryForumPage> {
             child: Container(
               alignment: Alignment.topCenter,
               width: U.bodyWidth(context),
-              child: messageData.length == 0 && !isAfterLoad ? loadingTemplate(() {
-
-              },) : SingleChildScrollView(
+              child: messageData.length == 0 && !isAfterLoad ? loadingTemplateNoVoid() : SingleChildScrollView(
                 controller: scrollController,
                 reverse: isReverse,
                 padding: EdgeInsets.fromLTRB(10, 10, 10, 7),
@@ -304,7 +331,7 @@ class _MobHistoryForumPageState extends State<MobHistoryForumPage> {
                     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(),
+                        !hideDate ? Container(margin: EdgeInsets.only(bottom: 5), child: bubleChat(Text(convertDate(messageData[i]['datetime'], locale), 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';
@@ -375,7 +402,7 @@ class _MobHistoryForumPageState extends State<MobHistoryForumPage> {
                                         onTap: () => kIsWeb ? navigateTo(context, PhotoPreview('forum'.tr(), messageData[i]['imageUrl'], true)) : File(imagePath! + getImageName(messageData[i]['imageUrl'])).existsSync() ? navigateTo(context, PhotoPreview('forum'.tr(), File(imagePath! + getImageName(messageData[i]['imageUrl'])), false)) : null,
                                       ),
                                     ) : 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]['readStatus'] == 'DELETED' ? Text('deletedMessage'.tr(), style: TextStyle(fontSize: 14, 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),
@@ -406,7 +433,7 @@ class _MobHistoryForumPageState extends State<MobHistoryForumPage> {
                             text: TextSpan(children: [
                               TextSpan(text: DateFormat('HH:mm').format(DateTime.parse(messageData[i]['datetime'])), style: TextStyle(fontSize: 12, color: Colors.black38)),
                               WidgetSpan(
-                                  child: Padding(
+                                  child: !isMe ? SizedBox() : 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)
                                   )
@@ -418,7 +445,7 @@ class _MobHistoryForumPageState extends State<MobHistoryForumPage> {
                               flex: 8,
                               child: Column(
                                 children: [
-                                  bubble_chat(widget, timeBubble, isNip, isMe),
+                                  bubleChat(widget, timeBubble, isNip, isMe),
                                   SizedBox(height: 5),
                                 ],
                                 crossAxisAlignment: isMe ? CrossAxisAlignment.end : CrossAxisAlignment.start,
@@ -453,162 +480,166 @@ class _MobHistoryForumPageState extends State<MobHistoryForumPage> {
             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),
           ),
-          openChat?divider():Container(),
-          openChat?Container(
-            alignment: Alignment.bottomCenter,
-            width: U.bodyWidth(context), color: Colors.white,
-            padding: EdgeInsets.symmetric(vertical: 10, horizontal: 16),
-            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(horizontal: kIsWeb?15:13, vertical: kIsWeb?20:13),
-                          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
-                              };
-
-                              if (kIsWeb) {
-                                await getImage(ImageSource.gallery).then((value) {
-                                  if (_image != null) {
-                                    navigateTo(context, PhotoChat(data, ImageSource.gallery, false, _image!)).then((value) {
-                                      if (value != null) {
-                                        sendMessage(value);
-                                      }
-                                    });
-                                  }
-                                });
-                              } else{
-                                showModalBottomSheet<void>(
-                                    context: context,
-                                    backgroundColor: Colors.white,
-                                    builder: (BuildContext context) {
-                                      return new Column(
-                                        mainAxisSize: MainAxisSize.min,
-                                        children: <Widget>[
-                                          Padding(
-                                            padding: const EdgeInsets.only(top: 15, bottom: 15),
-                                            child: Center(
-                                              child: Text('selectPicture'.tr()),
-                                            ),
-                                          ),
-                                          Padding(
-                                            padding: const EdgeInsets.only(left: 15, right: 15),
-                                            child: Divider(color: Colors.grey, height: 1.0),
-                                          ),
-                                          ListTile(
-                                            leading: Icon(Icons.image),
-                                            title: Text('fromGallery'.tr()),
-                                            onTap: () {
-                                              Navigator.of(context).pop();
-                                              navigateTo(context, PhotoChat(data, ImageSource.gallery, false, null)).then((value) {
-                                                if (value != null) {
-                                                  sendMessage(value);
-                                                }
-                                              });
-                                            },
-                                          ),
-                                          ListTile(
-                                            leading: Icon(Icons.camera),
-                                            title: Text('fromCamera'.tr()),
-                                            onTap: () {
-                                              Navigator.of(context).pop();
-                                              navigateTo(context, PhotoChat(data, ImageSource.camera, false, null)).then((value) {
-                                                if (value != null) {
-                                                  sendMessage(value);
-                                                }
-                                              });
-                                            },
-                                          )
-                                        ],
-                                      );
+          openChat ? divider() : Container(),
+          openChat ? SafeArea(
+            child: Container(
+              alignment: Alignment.bottomCenter,
+              width: U.bodyWidth(context), color: Colors.white,
+              padding: EdgeInsets.symmetric(vertical: 10, horizontal: 16),
+              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(horizontal: kIsWeb?15:13, vertical: kIsWeb?20:13),
+                            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
+                                };
+
+                                if (kIsWeb) {
+                                  await getImage(ImageSource.gallery).then((value) {
+                                    if (_image != null) {
+                                      navigateTo(context, PhotoChat(data, ImageSource.gallery, false, _image!)).then((value) {
+                                        if (value != null) {
+                                          sendMessage(value);
+                                        }
+                                      });
                                     }
-                                );
+                                  });
+                                } else{
+                                  showModalBottomSheet<void>(
+                                      context: context,
+                                      backgroundColor: Colors.white,
+                                      builder: (BuildContext context) {
+                                        return SafeArea(
+                                          child: Column(
+                                            mainAxisSize: MainAxisSize.min,
+                                            children: <Widget>[
+                                              Padding(
+                                                padding: const EdgeInsets.only(top: 15, bottom: 15),
+                                                child: Center(
+                                                  child: Text('selectPicture'.tr()),
+                                                ),
+                                              ),
+                                              Padding(
+                                                padding: const EdgeInsets.only(left: 15, right: 15),
+                                                child: Divider(color: Colors.grey, height: 1.0),
+                                              ),
+                                              ListTile(
+                                                leading: Icon(Icons.image),
+                                                title: Text('fromGallery'.tr()),
+                                                onTap: () {
+                                                  Navigator.of(context).pop();
+                                                  navigateTo(context, PhotoChat(data, ImageSource.gallery, false, null)).then((value) {
+                                                    if (value != null) {
+                                                      sendMessage(value);
+                                                    }
+                                                  });
+                                                },
+                                              ),
+                                              ListTile(
+                                                leading: Icon(Icons.camera),
+                                                title: Text('fromCamera'.tr()),
+                                                onTap: () {
+                                                  Navigator.of(context).pop();
+                                                  navigateTo(context, PhotoChat(data, ImageSource.camera, false, null)).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: U.bodyWidth(context) - 100);
+                              final line = tp.computeLineMetrics().length;
+                              if (line == 1) {
+                                borderRadius = 50.0;
+                              } else {
+                                borderRadius = 25.0;
                               }
-                            },
-                          ),
-                          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: U.bodyWidth(context) - 100);
-                            final line = tp.computeLineMetrics().length;
-                            if (line == 1) {
-                              borderRadius = 50.0;
+
+                              isSend = true;
                             } else {
-                              borderRadius = 25.0;
+                              isSend = false;
                             }
-
-                            isSend = true;
-                          } else {
-                            isSend = false;
-                          }
-                        });
-                      },
+                          });
+                        },
+                      ),
                     ),
                   ),
-                ),
-                SizedBox(width: 12),
-                GestureDetector(
-                  child: Container(
-                    padding: EdgeInsets.all(12),
-                    child: Image.asset('assets/image/icon/Send.png', width: 25, height: 25),
-                    decoration: BoxDecoration(
-                        color: primaryColor,
-                        borderRadius: BorderRadius.all(Radius.circular(50))
+                  SizedBox(width: 12),
+                  GestureDetector(
+                    child: Container(
+                      padding: EdgeInsets.all(12),
+                      child: Image.asset('assets/image/icon/Send.png', width: 25, height: 25),
+                      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);
-                    }
-                  },
-                )
-              ],
+                    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),
+            child: bubleChat(Text('chatClosed'.tr(), textAlign: TextAlign.center, style: TextStyle(fontSize: 12)), null, false, false),
           )):Container()
         ],
       ),
@@ -658,7 +689,7 @@ class _MobHistoryForumPageState extends State<MobHistoryForumPage> {
     }
   }
 
-  Widget bubble_chat(Widget child, Widget? time, bool isNip, bool isMe) {
+  Widget bubleChat(Widget child, Widget? time, bool isNip, bool isMe) {
     bool isDate = false;
     if (time == null) {
       isDate = true;
@@ -666,6 +697,7 @@ class _MobHistoryForumPageState extends State<MobHistoryForumPage> {
     }
     Color color = isMe ? primaryColor.withValues(alpha: 0.3) : isDate ? Color(0xffD5F5FF) : Color(0xffECECEC);
     var clipPath = ClipPath(
+      clipper: isMe ? MyClipper(isNip) : YourClipper(isNip),
       child: ConstrainedBox(
         constraints: BoxConstraints(minWidth: 75),
         child: Container(
@@ -690,7 +722,6 @@ class _MobHistoryForumPageState extends State<MobHistoryForumPage> {
           ),
         ),
       ),
-      clipper: isMe ? MyClipper(isNip) : YourClipper(isNip),
     );
 
     return Padding(padding: EdgeInsets.only(right: isNip && isMe ? 0 : 6, left: isNip && !isMe ? 0 : 6), child: clipPath);

+ 64 - 44
lib/src/layouts/mobile/menu_account.dart

@@ -13,6 +13,8 @@ import 'package:telnow_mobile_new/src/utils/U.dart';
 import 'package:telnow_mobile_new/src/utils/provider.dart';
 import 'package:permission_handler/permission_handler.dart';
 
+import '../../utils/C.dart';
+
 class MobAccountPage extends StatefulWidget {
   const MobAccountPage({super.key});
 
@@ -29,6 +31,7 @@ class _MobAccountPageState extends State<MobAccountPage> {
   bool autoTranslate = false;
   List lang = [];
   bool _timeLimit = false;
+  var codeOflang = {};
 
   @override
   void initState() {
@@ -54,22 +57,31 @@ class _MobAccountPageState extends State<MobAccountPage> {
 
   setToggle() async{
     var license = await U.getLicense();
+    if(await U.isCompatibleWith(VersionKey.multiBahasa)){
+      lang = license['_validLang'] ?? [];
+    } else {
+      lang = license['languages'] != null ? license['languages'].split(',') : [];
+    }
     setState(() {
       dnd = Provider.of<UserModule>(context, listen: false).dndStatus();
       serDis = U.servantDisplay();
       autoTranslate = U.autoTranslate();
-      lang = license['languages'] != null ? license['languages'].split(',') : [];
     });
   }
 
   @override
   Widget build(BuildContext context) {
-    var codeOflang = {
+    codeOflang = {
       "id": 'bahasa'.tr(),
       "en": 'english'.tr(),
       "ja": 'japanese'.tr(),
       "zh": 'chinese'.tr(),
       "ko": 'korean'.tr(),
+      "ar": 'arabic'.tr(),
+      "de": 'german'.tr(),
+      "fr": 'french'.tr(),
+      "hi": 'hindi'.tr(),
+      "nl": 'dutch'.tr()
     };
     return Provider.of<UserModule>(context).user().isNotEmpty ? Scaffold(
       backgroundColor: backgroundColor,
@@ -113,9 +125,9 @@ class _MobAccountPageState extends State<MobAccountPage> {
                         children: [
                           Text('info_label'.tr(), style: TextStyle(color: textColor, fontSize: 16, fontWeight: FontWeight.w500)),
                           SizedBox(height: 20),
-                          textHorizontal('userId'.tr(), Provider.of<UserModule>(context).user()['relatedTo'] != null ? Provider.of<UserModule>(context).user()['relatedTo'] : Provider.of<UserModule>(context).user()['userId'], size: 16, opacity: 0.75),
+                          textHorizontal('userId'.tr(), Provider.of<UserModule>(context).user()['relatedTo'] ?? Provider.of<UserModule>(context).user()['userId'], size: 16, opacity: 0.75),
                           SizedBox(height: 12),
-                          textHorizontal('location'.tr(), Provider.of<UserModule>(context).user()['location'] != null ? Provider.of<UserModule>(context).user()['location'] : '-', size: 16, opacity: 0.75),
+                          textHorizontal('location'.tr(), Provider.of<UserModule>(context).user()['location'] ?? '-', size: 16, opacity: 0.75),
                           SizedBox(height: 12),
                           textHorizontal('servantGroup'.tr(), Provider.of<UserModule>(context).user()['requestTypeListDescriptionMobile'] != null ? Provider.of<UserModule>(context).user()['requestTypeListDescriptionMobile'].toLowerCase() == 'semua' ? 'all'.tr() : Provider.of<UserModule>(context).user()['requestTypeListDescriptionMobile'] : '-', size: 16, opacity: 0.75),
                         ],
@@ -446,45 +458,53 @@ class _MobAccountPageState extends State<MobAccountPage> {
   }
 
   listOfLang(user){
-    return Column(
-      crossAxisAlignment: CrossAxisAlignment.start,
-      children: [
-        ListTile(
-          title: Text('bahasa'.tr(), style: TextStyle(color: textColor, fontSize: 14)),
-          trailing: Icon(context.locale.toString()=='id'?Icons.radio_button_checked:Icons.radio_button_off, color: primaryColor, size: 18),
-          onTap: context.locale.toString() != 'id' ? () async {
-            accFunc.switchLang(context, 'id', user);
-          } : null,
-        ),
-        ListTile(
-          title: Text('english'.tr(), style: TextStyle(color: textColor, fontSize: 14)),
-          trailing: Icon(context.locale.toString()=='en'?Icons.radio_button_checked:Icons.radio_button_off, color: primaryColor, size: 18),
-          onTap: context.locale.toString() != 'en' ? () async {
-            accFunc.switchLang(context, 'en', user);
-          } : null,
-        ),
-        lang.contains('ja') ? ListTile(
-          title: Text('japanese'.tr(), style: TextStyle(color: textColor, fontSize: 14)),
-          trailing: Icon(context.locale.toString()=='ja'?Icons.radio_button_checked:Icons.radio_button_off, color: primaryColor, size: 18),
-          onTap: context.locale.toString() != 'ja' ? () async {
-            accFunc.switchLang(context, 'ja', user);
-          } : null,
-        ) : Container(),
-        lang.contains('zh') ? ListTile(
-          title: Text('chinese'.tr(), style: TextStyle(color: textColor, fontSize: 14)),
-          trailing: Icon(context.locale.toString()=='zh'?Icons.radio_button_checked:Icons.radio_button_off, color: primaryColor, size: 18),
-          onTap: context.locale.toString() != 'zh' ? () async {
-            accFunc.switchLang(context, 'zh', user);
-          } : null,
-        ) : Container(),
-        lang.contains('ko') ? ListTile(
-          title: Text('korean'.tr(), style: TextStyle(color: textColor, fontSize: 14)),
-          trailing: Icon(context.locale.toString()=='ko'?Icons.radio_button_checked:Icons.radio_button_off, color: primaryColor, size: 18),
-          onTap: context.locale.toString() != 'ko' ? () async {
-            accFunc.switchLang(context, 'ko', user);
-          } : null,
-        ) : Container()
-      ],
+    return SafeArea(
+      child: Column(
+        crossAxisAlignment: CrossAxisAlignment.start,
+        children: [
+          ListTile(
+            title: Text('bahasa'.tr(), style: TextStyle(color: textColor, fontSize: 14)),
+            trailing: Icon(context.locale.toString()=='id'?Icons.radio_button_checked:Icons.radio_button_off, color: primaryColor, size: 18),
+            onTap: context.locale.toString() != 'id' ? () async {
+              accFunc.switchLang(context, 'id', user);
+              Navigator.of(context).pop();
+            } : null,
+          ),
+          ListTile(
+            title: Text('english'.tr(), style: TextStyle(color: textColor, fontSize: 14)),
+            trailing: Icon(context.locale.toString()=='en'?Icons.radio_button_checked:Icons.radio_button_off, color: primaryColor, size: 18),
+            onTap: context.locale.toString() != 'en' ? () async {
+              accFunc.switchLang(context, 'en', user);
+              Navigator.of(context).pop();
+            } : null,
+          ),
+          //TODO: kene
+          lang.length - 1 > 1 ? ListTile(
+            title: Text(codeOflang[lang[2]], style: TextStyle(color: textColor, fontSize: 14)),
+            trailing: Icon(context.locale.toString()==lang[2]?Icons.radio_button_checked:Icons.radio_button_off, color: primaryColor, size: 18),
+            onTap: context.locale.toString() != lang[2] ? () async {
+              accFunc.switchLang(context, 1, user);
+              Navigator.of(context).pop();
+            } : null,
+          ) : Container(),
+          lang.length - 1 > 2 ? ListTile(
+            title: Text(codeOflang[lang[3]], style: TextStyle(color: textColor, fontSize: 14)),
+            trailing: Icon(context.locale.toString()==lang[3]?Icons.radio_button_checked:Icons.radio_button_off, color: primaryColor, size: 18),
+            onTap: context.locale.toString() != lang[3] ? () async {
+              accFunc.switchLang(context, 2, user);
+              Navigator.of(context).pop();
+            } : null,
+          ) : Container(),
+          lang.length - 1 > 3 ? ListTile(
+            title: Text(codeOflang[lang[4]], style: TextStyle(color: textColor, fontSize: 14)),
+            trailing: Icon(context.locale.toString()==lang[4]?Icons.radio_button_checked:Icons.radio_button_off, color: primaryColor, size: 18),
+            onTap: context.locale.toString() != lang[4] ? () async {
+              accFunc.switchLang(context, 3, user);
+              Navigator.of(context).pop();
+            } : null,
+          ) : Container(),
+        ],
+      ),
     );
   }
-}
+}

File diff suppressed because it is too large
+ 13 - 13
lib/src/layouts/mobile/menu_history.dart


+ 247 - 192
lib/src/layouts/mobile/menu_home.dart

@@ -19,6 +19,7 @@ import 'package:telnow_mobile_new/src/layouts/mobile/request_create.dart';
 import 'package:telnow_mobile_new/src/layouts/mobile/request_select.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/C.dart';
 import 'package:telnow_mobile_new/src/utils/U.dart';
 import 'package:telnow_mobile_new/src/utils/cache_manager.dart';
 import 'package:telnow_mobile_new/src/utils/provider.dart';
@@ -35,6 +36,7 @@ class _MobHomePageState extends State<MobHomePage> with WidgetsBindingObserver {
   final HomeFunction homeFunc = HomeFunction();
   final SharedPreferencesManager sharedPreferencesManager = locator<SharedPreferencesManager>();
   bool _timeLimit = false;
+  String _localeIndex = 'id';
 
   @override
   void initState() {
@@ -53,10 +55,18 @@ class _MobHomePageState extends State<MobHomePage> with WidgetsBindingObserver {
       });
     });
     WidgetsBinding.instance.addObserver(this);
+    checkCompatibility();
     // TODO: implement initState
     super.initState();
   }
 
+  checkCompatibility() async{
+    var isCompatible = await U.isCompatibleWith(VersionKey.multiBahasa);
+    if(isCompatible) {
+      homeFunc.getContactCenter(context);
+    }
+  }
+
   @override
   void dispose() {
     WidgetsBinding.instance.removeObserver(this);
@@ -65,69 +75,103 @@ class _MobHomePageState extends State<MobHomePage> with WidgetsBindingObserver {
 
   @override
   Widget build(BuildContext context) {
+    _localeIndex = U.newServerVersion(1754624839) ? U.getLangIndex(context.locale.toString()) : context.locale.toString();
     return Provider.of<UserModule>(context).user().isNotEmpty?Scaffold(
       backgroundColor: backgroundColor,
-      appBar: PreferredSize(preferredSize: Size.fromHeight(0), child: AppBar(elevation: 0, backgroundColor: primaryColor)),
       body: Column(
         children: [
           Container(
             width: double.infinity,
             color: primaryColor,
-            padding: EdgeInsets.fromLTRB(16, 8, 16, 8),
-            child: Column(
-              crossAxisAlignment: CrossAxisAlignment.start,
-              children: [
-                Text('${Provider.of<UserModule>(context).user()['name']}${(Provider.of<UserModule>(context).user()['guestName']!=null&&Provider.of<UserModule>(context).user()['guestName']!=''?' - ${Provider.of<UserModule>(context).user()['guestName']}':'')}', style: TextStyle(color: Colors.white, fontSize: 18), overflow: TextOverflow.ellipsis),
-                Provider.of<UserModule>(context).profile()['greeting'][context.locale.toString()] != null ? Text(Provider.of<UserModule>(context).profile()['greeting'][context.locale.toString()], style: TextStyle(color: Colors.white, fontSize: 14), overflow: TextOverflow.ellipsis) : Container(),
-                SizedBox(height: 16),
-                Row(
-                  children: [
-                    Expanded(
-                      child: GestureDetector(
-                        child: Container(
-                          padding: EdgeInsets.fromLTRB(20, 13, 20, 13),
-                          child: Row(
-                            children: [
-                              U.iconsax('search-normal-1', color: textColor),
-                              SizedBox(width: 13),
-                              Expanded(child: Text(Provider.of<UserModule>(context).profile()['searchText'][context.locale.toString()] != null ? Provider.of<UserModule>(context).profile()['searchText'][context.locale.toString()] : 'searchAsk'.tr(), style: TextStyle(color: textColor.withValues(alpha: 0.5), fontSize: 14), overflow: TextOverflow.ellipsis))
-                            ],
+            padding: EdgeInsets.symmetric(horizontal: 16, vertical: 16),
+            child: SafeArea(
+              child: Column(
+                crossAxisAlignment: CrossAxisAlignment.start,
+                children: [
+                  Row(
+                    mainAxisAlignment: MainAxisAlignment.spaceBetween,
+                    children: [
+                      Text('${Provider.of<UserModule>(context).user()['name']}${(Provider.of<UserModule>(context).user()['guestName'] != null && Provider.of<UserModule>(context).user()['guestName'] != '' ? ' - ${Provider.of<UserModule>(context).user()['guestName']}' : '')}', style: TextStyle(color: Colors.white, fontSize: 18), overflow: TextOverflow.ellipsis),
+                      Row(
+                        children: [
+                          //TODO: tambah pengecekan server version disini
+                          FutureBuilder<bool>(
+                            future: U.isCompatibleWith(VersionKey.multiBahasa),
+                            builder: (context, snapshot) {
+                              if (!snapshot.hasData) {
+                                return SizedBox(); // or a loading spinner
+                              }
+
+                              final isCompatible = snapshot.data!;
+                              final hasContactCenter = Provider.of<ServiceModule>(context, listen: false)
+                                  .contactCenter()
+                                  .toString()
+                                  .isNotEmpty;
+
+                              if (isCompatible && hasContactCenter) {
+                                return GestureDetector(
+                                    onTap: () => launchUrl(Uri.parse(Provider.of<ServiceModule>(context, listen: false).contactCenter().toString())),
+                                    child: U.iconsax('phone-number', color: Colors.white, size: 26.0)
+                                ); // replace with the widget you want to show
+                              } else {
+                                return SizedBox(); // or nothing
+                              }
+                            },
                           ),
-                          decoration: BoxDecoration(
-                              color: Colors.white,
-                              borderRadius: BorderRadius.all(Radius.circular(50))
+                          // U.isCompatibleWith(VersionKey.multiBahasa) && Provider.of<ServiceModule>(context, listen: false).contactCenter().toString() != ''  ? GestureDetector(
+                          //   onTap: () => launchUrl(Uri.parse(Provider.of<ServiceModule>(context, listen: false).contactCenter().toString())),
+                          //   child: U.iconsax('phone-number', color: Colors.white, size: 26.0)
+                          // ):SizedBox(),
+                          SizedBox(width: 8),
+                          GestureDetector(
+                            child: Stack(
+                              alignment: Alignment.topRight,
+                              children: [
+                                U.iconsax('sms-notification', color: Colors.white, size: 32.0),
+                                Provider.of<ServiceModule>(context).unreadMessage() ? Container(
+                                  width: 13, height: 13, margin: EdgeInsets.only(top: 1.3),
+                                  decoration: BoxDecoration(color: Colors.red, borderRadius: BorderRadius.all(Radius.circular(50))),
+                                ):Container()
+                              ],
+                            ),
+                            onTap: ()=>navigateTo(context, MobMessageListPage(Provider.of<UserModule>(context, listen: false).user())).then((_){
+                              homeFunc.getUnreadMessages(context);
+                            }),
+                          )
+                        ],
+                      )
+                    ],
+                  ),
+                  Provider.of<UserModule>(context).profile()['greeting'][context.locale.toString()] != null ? Text(Provider.of<UserModule>(context).profile()['greeting'][context.locale.toString()], style: TextStyle(color: Colors.white, fontSize: 14), overflow: TextOverflow.ellipsis) : Container(),
+                  SizedBox(height: 8),
+                  Row(
+                    children: [
+                      Expanded(
+                        child: GestureDetector(
+                          child: Container(
+                            padding: EdgeInsets.symmetric(horizontal: 20, vertical: 12,),
+                            decoration: BoxDecoration(
+                                color: Colors.white,
+                                borderRadius: BorderRadius.all(Radius.circular(50))
+                            ),
+                            child: Row(
+                              children: [
+                                U.iconsax('search-normal-1', color: textColor),
+                                SizedBox(width: 13),
+                                Expanded(child: Text(Provider.of<UserModule>(context).profile()['searchText'][context.locale.toString()] != null ? Provider.of<UserModule>(context).profile()['searchText'][context.locale.toString()] : 'searchAsk'.tr(), style: TextStyle(color: textColor.withValues(alpha: 0.5), fontSize: 14), overflow: TextOverflow.ellipsis))
+                              ],
+                            ),
                           ),
-                        ),
-                        onTap: () => navigateTo(context, MobReqSelectPage(user: Provider.of<UserModule>(context, listen: false).user(), title: 'search'.tr(), scope: Provider.of<ServiceModule>(context, listen: false).scoopeValue(), placeholder: Provider.of<UserModule>(context, listen: false).profile()['searchText'][context.locale.toString()] != null ? Provider.of<UserModule>(context, listen: false).profile()['searchText'][context.locale.toString()] : 'searchAsk'.tr())).then((val){
-                          val = val??true;
-                          if(val) homeFunc.getFrequentlyRequested(context);
-                        }),
-                      ),
-                    ),
-                    SizedBox(width: 12),
-                    GestureDetector(
-                      child: Container(
-                        padding: EdgeInsets.all(13),
-                        child: Stack(
-                          alignment: Alignment.topRight,
-                          children: [
-                            U.iconsax('sms-notification', color: primaryColor),
-                            Provider.of<ServiceModule>(context).unreadMessage() ? Container(
-                              width: 11, height: 11, margin: EdgeInsets.only(top: 1.3),
-                              decoration: BoxDecoration(color: Colors.red, borderRadius: BorderRadius.all(Radius.circular(50))),
-                            ):Container()
-                          ],
-                        ),
-                        decoration: BoxDecoration(
-                            color: Colors.white,
-                            borderRadius: BorderRadius.all(Radius.circular(50))
+                          onTap: () => navigateTo(context, MobReqSelectPage(user: Provider.of<UserModule>(context, listen: false).user(), title: 'search'.tr(), scope: Provider.of<ServiceModule>(context, listen: false).scoopeValue(), placeholder: Provider.of<UserModule>(context, listen: false).profile()['searchText'][context.locale.toString()] != null ? Provider.of<UserModule>(context, listen: false).profile()['searchText'][context.locale.toString()] : 'searchAsk'.tr())).then((val){
+                            val = val??true;
+                            if(val) homeFunc.getFrequentlyRequested(context);
+                          }),
                         ),
                       ),
-                      onTap: ()=>navigateTo(context, MobMessageListPage(Provider.of<UserModule>(context, listen: false).user())),
-                    )
-                  ],
-                )
-              ],
+                    ],
+                  )
+                ],
+              ),
             ),
           ),
           Expanded(
@@ -172,55 +216,57 @@ class _MobHomePageState extends State<MobHomePage> with WidgetsBindingObserver {
                                     context: context,
                                     backgroundColor: Colors.white,
                                     builder: (BuildContext contexts) {
-                                      return Column(
-                                        mainAxisSize: MainAxisSize.min,
-                                        children: [
-                                          Container(
-                                            margin: EdgeInsets.symmetric(vertical: 16),
-                                            child: Text('changeScope'.tr(), style: TextStyle(color: textColor, fontSize: 14), textAlign: TextAlign.center),
-                                          ),
-                                          divider(),
-                                          SizedBox(height: 16),
-                                          Column(
-                                            children: List.generate(Provider.of<ServiceModule>(context).getScoopeLength(), (i){
-                                              return GestureDetector(
-                                                  child: Container(
-                                                    color: Colors.white,
-                                                    padding: EdgeInsets.symmetric(vertical: 16, horizontal: 16),
-                                                    child: Row(
-                                                      mainAxisAlignment: MainAxisAlignment.spaceBetween,
-                                                      children: [
-                                                        Text(Provider.of<ServiceModule>(context).scoope()[i]['value'], style: TextStyle(color: textColor, fontSize: 14)),
-                                                        Icon(Provider.of<ServiceModule>(context).checkScoope(Provider.of<ServiceModule>(context).scoope()[i]['key'])?Icons.radio_button_checked:Icons.radio_button_off, color: Provider.of<ServiceModule>(context).checkScoope(Provider.of<ServiceModule>(context).scoope()[i]['key'])?primaryColor:Colors.black45, size: 22),
-                                                      ],
+                                      return SafeArea(
+                                        child: Column(
+                                          mainAxisSize: MainAxisSize.min,
+                                          children: [
+                                            Container(
+                                              margin: EdgeInsets.symmetric(vertical: 16),
+                                              child: Text('changeScope'.tr(), style: TextStyle(color: textColor, fontSize: 14), textAlign: TextAlign.center),
+                                            ),
+                                            divider(),
+                                            SizedBox(height: 16),
+                                            Column(
+                                              children: List.generate(Provider.of<ServiceModule>(context).getScoopeLength(), (i){
+                                                return GestureDetector(
+                                                    child: Container(
+                                                      color: Colors.white,
+                                                      padding: EdgeInsets.symmetric(vertical: 16, horizontal: 16),
+                                                      child: Row(
+                                                        mainAxisAlignment: MainAxisAlignment.spaceBetween,
+                                                        children: [
+                                                          Text(Provider.of<ServiceModule>(context).scoope()[i]['value'], style: TextStyle(color: textColor, fontSize: 14)),
+                                                          Icon(Provider.of<ServiceModule>(context).checkScoope(Provider.of<ServiceModule>(context).scoope()[i]['key'])?Icons.radio_button_checked:Icons.radio_button_off, color: Provider.of<ServiceModule>(context).checkScoope(Provider.of<ServiceModule>(context).scoope()[i]['key'])?primaryColor:Colors.black45, size: 22),
+                                                        ],
+                                                      ),
                                                     ),
-                                                  ),
-                                                  onTap: () async{
-                                                    await sharedPreferencesManager.putString(SharedPreferencesManager.keyScoope, Provider.of<ServiceModule>(context, listen: false).scoope()[i]['key']);
-                                                    Provider.of<ServiceModule>(context, listen: false).setScoopeValue(Provider.of<ServiceModule>(context, listen: false).scoope()[i]['key']);
-                                                    Provider.of<ServiceModule>(context, listen: false).setScoopeName(Provider.of<ServiceModule>(context, listen: false).scoope()[i]['value']);
-                                                    Navigator.of(contexts).pop();
-
-                                                    var profile = Provider.of<UserModule>(context, listen: false).profile();
-                                                    if(profile['topMenu']['show'] != null && profile['topMenu']['show'] == true) {
-                                                      if(U.newServerVersion(1709864293)){
-                                                        homeFunc.getTopMenuNew(context);
-                                                      }
-                                                      else{
-                                                        homeFunc.getTopMenu(context);
-                                                        homeFunc.getReqGroup(context);
+                                                    onTap: () async{
+                                                      await sharedPreferencesManager.putString(SharedPreferencesManager.keyScoope, Provider.of<ServiceModule>(context, listen: false).scoope()[i]['key']);
+                                                      Provider.of<ServiceModule>(context, listen: false).setScoopeValue(Provider.of<ServiceModule>(context, listen: false).scoope()[i]['key']);
+                                                      Provider.of<ServiceModule>(context, listen: false).setScoopeName(Provider.of<ServiceModule>(context, listen: false).scoope()[i]['value']);
+                                                      Navigator.of(contexts).pop();
+                                        
+                                                      var profile = Provider.of<UserModule>(context, listen: false).profile();
+                                                      if(profile['topMenu']['show'] != null && profile['topMenu']['show'] == true) {
+                                                        if(U.newServerVersion(1709864293)){
+                                                          homeFunc.getTopMenuNew(context);
+                                                        }
+                                                        else{
+                                                          homeFunc.getTopMenu(context);
+                                                          homeFunc.getReqGroup(context);
+                                                        }
                                                       }
+                                                      if(profile['specialOffer'] != null && profile['specialOffer']['show'] == true) homeFunc.getSpecialOffer(context);
+                                                      if(profile['frequentlyRequested'] != null && profile['frequentlyRequested']['show'] == true) homeFunc.getFrequentlyRequested(context);
+                                                      if(profile['banner'] != null && profile['banner']['show'] == true) homeFunc.getBanner(context);
+                                                      if(profile['quickAction'] != null && profile['quickAction']['show'] == true) homeFunc.getQuickAction(context);
                                                     }
-                                                    if(profile['specialOffer'] != null && profile['specialOffer']['show'] == true) homeFunc.getSpecialOffer(context);
-                                                    if(profile['frequentlyRequested'] != null && profile['frequentlyRequested']['show'] == true) homeFunc.getFrequentlyRequested(context);
-                                                    if(profile['banner'] != null && profile['banner']['show'] == true) homeFunc.getBanner(context);
-                                                    if(profile['quickAction'] != null && profile['quickAction']['show'] == true) homeFunc.getQuickAction(context);
-                                                  }
-                                              );
-                                            }),
-                                          ),
-                                          SizedBox(height: 16),
-                                        ],
+                                                );
+                                              }),
+                                            ),
+                                            SizedBox(height: 16),
+                                          ],
+                                        ),
                                       );
                                     },
                                   );
@@ -291,7 +337,12 @@ class _MobHomePageState extends State<MobHomePage> with WidgetsBindingObserver {
                         child: Column(
                           crossAxisAlignment: CrossAxisAlignment.start,
                           children: [
-                            Padding(padding: EdgeInsets.only(left: 16), child: Text(Provider.of<UserModule>(context).profile()['specialOffer']['label'][context.locale.toString()]??'Not Set', style: TextStyle(color: textColor, fontSize: 17, fontWeight: FontWeight.w500))),
+                            Padding(
+                              padding: EdgeInsetsDirectional.only(start: 16),
+                              child: Text(
+                                Provider.of<UserModule>(context).profile()['specialOffer']['label'][_localeIndex]??'Not Set',
+                                style: TextStyle(color: textColor, fontSize: 17, fontWeight: FontWeight.w500),
+                              )),
                             SizedBox(height: 12),
                             Provider.of<ServiceModule>(context).specialOffer().length > 0 ? SingleChildScrollView(
                               scrollDirection: Axis.horizontal,
@@ -301,6 +352,11 @@ class _MobHomePageState extends State<MobHomePage> with WidgetsBindingObserver {
                                     width: U.bodyWidth(context) - 60,
                                     margin: EdgeInsets.only(left: i==0?16:0, right: 16),
                                     padding: EdgeInsets.only(right: 16),
+                                    decoration: BoxDecoration(
+                                        color: Colors.white,
+                                        border: Border.all(color: textColor.withValues(alpha: 0.15)),
+                                        borderRadius: BorderRadius.all(Radius.circular(12))
+                                    ),
                                     child: Row(
                                       children: [
                                         imageTiles(imageUrl: Provider.of<ServiceModule>(context).specialOffer()[i]['_mobileImage'] ?? "null"),
@@ -317,11 +373,6 @@ class _MobHomePageState extends State<MobHomePage> with WidgetsBindingObserver {
                                         )
                                       ],
                                     ),
-                                    decoration: BoxDecoration(
-                                        color: Colors.white,
-                                        border: Border.all(color: textColor.withValues(alpha: 0.15)),
-                                        borderRadius: BorderRadius.all(Radius.circular(12))
-                                    ),
                                   ),
                                   onTap: ()=>navigateTo(context, MobReqCreatePage(user: Provider.of<UserModule>(context, listen: false).user(), request: Provider.of<ServiceModule>(context, listen: false).specialOffer()[i])),
                                 )),
@@ -336,7 +387,7 @@ class _MobHomePageState extends State<MobHomePage> with WidgetsBindingObserver {
                         child: Column(
                           crossAxisAlignment: CrossAxisAlignment.start,
                           children: [
-                            Padding(padding: EdgeInsets.only(left: 16), child: Text(Provider.of<UserModule>(context).profile()['frequentlyRequested']['label'][context.locale.toString()]??'Not Set', style: TextStyle(color: textColor, fontSize: 17, fontWeight: FontWeight.w500))),
+                            Padding(padding: EdgeInsetsDirectional.only(start: 16), child: Text(Provider.of<UserModule>(context).profile()['frequentlyRequested']['label'][_localeIndex]??'Not Set', style: TextStyle(color: textColor, fontSize: 17, fontWeight: FontWeight.w500))),
                             SizedBox(height: 12),
                             Provider.of<ServiceModule>(context).data().length > 0 ? SingleChildScrollView(
                               scrollDirection: Axis.horizontal,
@@ -347,6 +398,11 @@ class _MobHomePageState extends State<MobHomePage> with WidgetsBindingObserver {
                                     child: Container(
                                       margin: EdgeInsets.only(left: i==0?16:0, right: 16),
                                       width: U.bodyWidth(context)/(kIsWeb?3.8:2.6), height: U.bodyWidth(context)/(kIsWeb?3:2),
+                                      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: [
@@ -369,7 +425,7 @@ class _MobHomePageState extends State<MobHomePage> with WidgetsBindingObserver {
                                             child: Column(
                                               crossAxisAlignment: CrossAxisAlignment.start,
                                               children: [
-                                                Text(Provider.of<ServiceModule>(context).data()[i][U.langColumn(context, 'subject')], style: TextStyle(color: textColor), overflow: TextOverflow.ellipsis),
+                                                Text(Provider.of<ServiceModule>(context).data()[i][U.langColumn(context, 'subject')]??'', style: TextStyle(color: textColor), overflow: TextOverflow.ellipsis),
                                                 SizedBox(height: 5),
                                                 Text(Provider.of<ServiceModule>(context).data()[i]['_frequentlyCount'].toString() + 'times'.tr(), style: TextStyle(color: textColor, fontSize: 14, fontWeight: FontWeight.w500), overflow: TextOverflow.ellipsis)
                                               ],
@@ -377,11 +433,6 @@ class _MobHomePageState extends State<MobHomePage> with WidgetsBindingObserver {
                                           )
                                         ],
                                       ),
-                                      decoration: BoxDecoration(
-                                          color: Colors.white,
-                                          border: Border.all(color: textColor.withValues(alpha: 0.15)),
-                                          borderRadius: BorderRadius.all(Radius.circular(12))
-                                      ),
                                     ),
                                     onTap: ()=>navigateTo(context, MobReqCreatePage(user: Provider.of<UserModule>(context, listen: false).user(), request: Provider.of<ServiceModule>(context, listen: false).data()[i])).then((val){
                                       val = val??true;
@@ -400,7 +451,7 @@ class _MobHomePageState extends State<MobHomePage> with WidgetsBindingObserver {
                         child: Column(
                           crossAxisAlignment: CrossAxisAlignment.start,
                           children: [
-                            Text(Provider.of<UserModule>(context).profile()['banner']['label'][context.locale.toString()]??'Not Set', style: TextStyle(color: textColor, fontSize: 17, fontWeight: FontWeight.w500)),
+                            Text(Provider.of<UserModule>(context).profile()['banner']['label'][_localeIndex]??'Not Set', style: TextStyle(color: textColor, fontSize: 17, fontWeight: FontWeight.w500)),
                             SizedBox(height: 12),
                             Provider.of<ServiceModule>(context).banner().isNotEmpty ? CarouselSlider.builder(
                               itemCount: Provider.of<ServiceModule>(context).banner().length,
@@ -419,6 +470,11 @@ class _MobHomePageState extends State<MobHomePage> with WidgetsBindingObserver {
                               itemBuilder: (BuildContext context, int i, int pageViewIndex) => GestureDetector(
                                 child: Container(
                                   width: double.infinity,
+                                  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: [
@@ -474,11 +530,6 @@ class _MobHomePageState extends State<MobHomePage> with WidgetsBindingObserver {
                                       )
                                     ],
                                   ),
-                                  decoration: BoxDecoration(
-                                      color: Colors.white,
-                                      border: Border.all(color: textColor.withValues(alpha: 0.15)),
-                                      borderRadius: BorderRadius.all(Radius.circular(12))
-                                  ),
                                 ),
                                 onTap: ()=>navigateTo(context, MobBannerDetailPage(user: Provider.of<UserModule>(context, listen: false).user(), data: Provider.of<ServiceModule>(context, listen: false).banner()[i])).then((val){
                                   val = val??true;
@@ -495,14 +546,28 @@ class _MobHomePageState extends State<MobHomePage> with WidgetsBindingObserver {
                         child: Column(
                           crossAxisAlignment: CrossAxisAlignment.start,
                           children: [
-                            Text(Provider.of<UserModule>(context).profile()['quickAction']['label'][context.locale.toString()]??'Not Set', style: TextStyle(color: textColor, fontSize: 17, fontWeight: FontWeight.w500)),
+                            Text(Provider.of<UserModule>(context).profile()['quickAction']['label'][_localeIndex]??'Not Set', style: TextStyle(color: textColor, fontSize: 17, fontWeight: FontWeight.w500)),
                             SizedBox(height: 12),
                             Provider.of<ServiceModule>(context).quickAct().length > 0 ? Container(
+                              decoration: BoxDecoration(
+                                  color: Colors.white,
+                                  border: Border.all(color: textColor.withValues(alpha: 0.15)),
+                                  borderRadius: BorderRadius.all(Radius.circular(12))
+                              ),
                               child: Column(
                                 children: List.generate(Provider.of<ServiceModule>(context).quickAct().length, (i){
                                   return GestureDetector(
                                     child: Container(
                                       padding: EdgeInsets.all(12),
+                                      decoration: BoxDecoration(
+                                          color: Colors.white,
+                                          borderRadius: BorderRadius.only(
+                                            topLeft: Radius.circular(i==0?12:0),
+                                            topRight: Radius.circular(i==0?12:0),
+                                            bottomLeft: Radius.circular(i==Provider.of<ServiceModule>(context).quickAct().length-1?12:0),
+                                            bottomRight: Radius.circular(i==Provider.of<ServiceModule>(context).quickAct().length-1?12:0),
+                                          )
+                                      ),
                                       child: Row(
                                         children: [
                                           imageTiles(imageUrl: Provider.of<ServiceModule>(context).quickAct()[i]['_mobileImage'] ?? "null", width: 50, height: 40, radius: 5),
@@ -513,25 +578,11 @@ class _MobHomePageState extends State<MobHomePage> with WidgetsBindingObserver {
                                           U.iconsax('arrow-right-3', size: 14, color: textColor.withValues(alpha: 0.75)),
                                         ],
                                       ),
-                                      decoration: BoxDecoration(
-                                          color: Colors.white,
-                                          borderRadius: BorderRadius.only(
-                                            topLeft: Radius.circular(i==0?12:0),
-                                            topRight: Radius.circular(i==0?12:0),
-                                            bottomLeft: Radius.circular(i==Provider.of<ServiceModule>(context).quickAct().length-1?12:0),
-                                            bottomRight: Radius.circular(i==Provider.of<ServiceModule>(context).quickAct().length-1?12:0),
-                                          )
-                                      ),
                                     ),
                                     onTap: ()=>navigateTo(context, MobReqCreatePage(user: Provider.of<UserModule>(context, listen: false).user(), request: Provider.of<ServiceModule>(context, listen: false).quickAct()[i])),
                                   );
                                 }),
                               ),
-                              decoration: BoxDecoration(
-                                  color: Colors.white,
-                                  border: Border.all(color: textColor.withValues(alpha: 0.15)),
-                                  borderRadius: BorderRadius.all(Radius.circular(12))
-                              ),
                             ) : emptyWidget(left: 0)
                           ],
                         ),
@@ -619,11 +670,11 @@ class _MobMenuDisplayPageState extends State<MobMenuDisplayPage> {
             child: Container(
               padding: EdgeInsets.symmetric(horizontal: 16, vertical: 4),
               margin: EdgeInsets.only(right: 15),
-              child: Text('customize'.tr(), style: TextStyle(color: primaryColor, fontSize: 14)),
               decoration: BoxDecoration(
                   color: primaryColor.withValues(alpha: 0.1),
                   borderRadius: BorderRadius.all(Radius.circular(50))
               ),
+              child: Text('customize'.tr(), style: TextStyle(color: primaryColor, fontSize: 14)),
             ),
             onTap: (){
               navigateTo(context, MobMenuEditorPage(scope: widget.scope));
@@ -669,30 +720,32 @@ class _MobMenuDisplayPageState extends State<MobMenuDisplayPage> {
           Expanded(
             child: SingleChildScrollView(
               padding: EdgeInsets.fromLTRB(16, 5, 16, 16),
-              child: Column(
-                children: List.generate((Provider.of<ServiceModule>(context).reqGroup()!.length/4).ceil(), (i){
-                  return Row(
-                    mainAxisAlignment: MainAxisAlignment.spaceBetween,
-                    crossAxisAlignment: CrossAxisAlignment.start,
-                    children: List.generate(4, (j){
-                      int index = i==0?j:j+(4*i);
-                      return index < Provider.of<ServiceModule>(context).reqGroup()!.length ? GestureDetector(
-                        child: Column(
-                          children: [
-                            i>0?SizedBox(height: 16):Container(),
-                            categoryContainer(context: context, iconUrl: Provider.of<ServiceModule>(context).reqGroup()![index]['iconUrl']),
-                            SizedBox(height: 8),
-                            SizedBox(
-                              width: 70,
-                              child: Text(U.servantDisplay() ? Provider.of<ServiceModule>(context).reqGroup()![index]['description'] : Provider.of<ServiceModule>(context).reqGroup()![index][U.langColumn(context, 'description')], style: TextStyle(color: textColor, fontSize: 12, fontWeight: FontWeight.w300), maxLines: 2, overflow: TextOverflow.ellipsis, textAlign: TextAlign.center),
-                            )
-                          ],
-                        ),
-                        onTap: ()=>navigateTo(context, MobReqSelectPage(user: Provider.of<UserModule>(context, listen: false).user(), title: U.servantDisplay() ? Provider.of<ServiceModule>(context, listen: false).reqGroup()![index]['description'] : Provider.of<ServiceModule>(context, listen: false).reqGroup()![index][U.langColumn(context, 'description')], scope: widget.scope, groupCode: Provider.of<ServiceModule>(context, listen: false).reqGroup()![index]['code'])),
-                      ) : Container(width: 70);
-                    }),
-                  );
-                }),
+              child: SafeArea(
+                child: Column(
+                  children: List.generate((Provider.of<ServiceModule>(context).reqGroup()!.length/4).ceil(), (i){
+                    return Row(
+                      mainAxisAlignment: MainAxisAlignment.spaceBetween,
+                      crossAxisAlignment: CrossAxisAlignment.start,
+                      children: List.generate(4, (j){
+                        int index = i==0?j:j+(4*i);
+                        return index < Provider.of<ServiceModule>(context).reqGroup()!.length ? GestureDetector(
+                          child: Column(
+                            children: [
+                              i>0?SizedBox(height: 16):Container(),
+                              categoryContainer(context: context, iconUrl: Provider.of<ServiceModule>(context).reqGroup()![index]['iconUrl']),
+                              SizedBox(height: 8),
+                              SizedBox(
+                                width: 70,
+                                child: Text(U.servantDisplay() ? Provider.of<ServiceModule>(context).reqGroup()![index]['description'] : Provider.of<ServiceModule>(context).reqGroup()![index][U.langColumn(context, 'description')], style: TextStyle(color: textColor, fontSize: 12, fontWeight: FontWeight.w300), maxLines: 2, overflow: TextOverflow.ellipsis, textAlign: TextAlign.center),
+                              )
+                            ],
+                          ),
+                          onTap: ()=>navigateTo(context, MobReqSelectPage(user: Provider.of<UserModule>(context, listen: false).user(), title: U.servantDisplay() ? Provider.of<ServiceModule>(context, listen: false).reqGroup()![index]['description'] : Provider.of<ServiceModule>(context, listen: false).reqGroup()![index][U.langColumn(context, 'description')], scope: widget.scope, groupCode: Provider.of<ServiceModule>(context, listen: false).reqGroup()![index]['code'])),
+                        ) : Container(width: 70);
+                      }),
+                    );
+                  }),
+                ),
               ),
             ),
           )
@@ -829,42 +882,44 @@ class _MobMenuEditorPageState extends State<MobMenuEditorPage> {
               margin: EdgeInsets.fromLTRB(16, 16, 16, 13),
               child: Text('availableMenu'.tr(), style: TextStyle(color: textColor, fontSize: 17, fontWeight: FontWeight.w500)),
             ),
-            Column(
-              children: List.generate(available.length, (i){
-                return Container(
-                  padding: EdgeInsets.symmetric(vertical: 8, horizontal: 16),
-                  child: Row(
-                    children: [
-                      categoryContainer(context: context, iconUrl: available[i]['iconUrl']),
-                      SizedBox(width: 12),
-                      Expanded(
-                        child: Column(
-                          crossAxisAlignment: CrossAxisAlignment.start,
-                          children: [
-                            Text(available[i][U.langColumn(context, 'description')], style: TextStyle(color: textColor, fontSize: 14), overflow: TextOverflow.ellipsis),
-                            SizedBox(height: 4),
-                            Text(available[i][U.langColumn(context, 'description')], style: TextStyle(color: textColor, fontSize: 12, fontWeight: FontWeight.w300), overflow: TextOverflow.ellipsis),
-                          ],
+            SafeArea(
+              child: Column(
+                children: List.generate(available.length, (i){
+                  return Container(
+                    padding: EdgeInsets.symmetric(vertical: 8, horizontal: 16),
+                    child: Row(
+                      children: [
+                        categoryContainer(context: context, iconUrl: available[i]['iconUrl']),
+                        SizedBox(width: 12),
+                        Expanded(
+                          child: Column(
+                            crossAxisAlignment: CrossAxisAlignment.start,
+                            children: [
+                              Text(available[i][U.langColumn(context, 'description')], style: TextStyle(color: textColor, fontSize: 14), overflow: TextOverflow.ellipsis),
+                              SizedBox(height: 4),
+                              Text(available[i][U.langColumn(context, 'description')], style: TextStyle(color: textColor, fontSize: 12, fontWeight: FontWeight.w300), overflow: TextOverflow.ellipsis),
+                            ],
+                          ),
                         ),
-                      ),
-                      GestureDetector(
-                        child: U.iconsax('bold/add-circle', color: Color(0xffD81010)),
-                        onTap: (){
-                          if(data.length < 7){
-                            setState(() {
-                              data.add(available[i]);
-                              available.remove(available[i]);
-                            });
-                          }
-                          else{
-                            showError(context, 'maxMenu'.tr());
-                          }
-                        },
-                      ),
-                    ],
-                  ),
-                );
-              }),
+                        GestureDetector(
+                          child: U.iconsax('bold/add-circle', color: Color(0xffD81010)),
+                          onTap: (){
+                            if(data.length < 7){
+                              setState(() {
+                                data.add(available[i]);
+                                available.remove(available[i]);
+                              });
+                            }
+                            else{
+                              showError(context, 'maxMenu'.tr());
+                            }
+                          },
+                        ),
+                      ],
+                    ),
+                  );
+                }),
+              ),
             ),
             SizedBox(height: 8),
           ],

+ 1 - 1
lib/src/layouts/mobile/message_broadcast.dart

@@ -275,7 +275,7 @@ class _MobMessageBroadcastPageState extends State<MobMessageBroadcastPage> {
             ),
           )
         ],
-      ):loadingTemplate(() {},),
+      ):loadingTemplateNoVoid(),
     );
   }
 }

+ 1 - 1
lib/src/layouts/mobile/message_chat.dart

@@ -261,7 +261,7 @@ class _MobMessageChatPageState extends State<MobMessageChatPage> {
               child: Container(width: bodyWidth,
                 child:LayoutBuilder(
                     builder: (context,constraint) {
-                      return messageData.length == 0 && !isAfterLoad ? loadingTemplate(() {},) : Column(
+                      return messageData.length == 0 && !isAfterLoad ? loadingTemplateNoVoid() : Column(
                         children: [
                           Expanded(
                             child: SingleChildScrollView(

File diff suppressed because it is too large
+ 250 - 61
lib/src/layouts/mobile/message_list.dart


+ 1 - 1
lib/src/layouts/mobile/message_select.dart

@@ -62,7 +62,7 @@ class _MobMessageSelectPageState extends State<MobMessageSelectPage> {
     return Scaffold(
       backgroundColor: Colors.white,
       appBar: appBarTemplate(context: context, title: 'selectRecipient'.tr()),
-      body: !isLoaded ? loadingTemplate(() {},) : Column(
+      body: !isLoaded ? loadingTemplateNoVoid() : Column(
         children: [
           divider(),
           Expanded(

+ 37 - 35
lib/src/layouts/mobile/password.dart

@@ -73,46 +73,48 @@ class _MobPasswordPageState extends State<MobPasswordPage> {
               ),
             ),
           ),
-          Container(
-            padding: EdgeInsets.symmetric(vertical: 10, horizontal: 16),
-            decoration: BoxDecoration(
-                color: Colors.white,
-                border: Border(top: BorderSide(color: textColor.withValues(alpha: 0.15)))
-            ),
-            child: buttonTemplate(text: 'buttonSave'.tr(), action: ()async{
-              if(controllerOld.trim().isNotEmpty && controllerNew.trim().isNotEmpty && controllerConfirm.trim().isNotEmpty){
-                if(controllerNew == controllerConfirm){
-                  var data = {
-                    'oldPassword':controllerOld.trim(),
-                    'password':controllerNew.trim()
-                  };
-
-                  var res = await apiAuthProvider.patchData('/api/informants/'+widget.user['id'].toString()+'/changePassword', data, context);
-                  if(res != null){
-                    String username = widget.user['_isRelated']?widget.user['relatedTo']:widget.user['userId'];
-                    String? password = widget.user['_isRelated'] && widget.user['tenantCode']!=null && widget.user['tenantCode']!=''?widget.user['tenantCode']+' '+data['password']:data['password'];
-
-                    LoginBody loginBody = LoginBody(username, password!, 'password');
-                    Token token = await apiAuthRepository.postLoginUser(loginBody);
-                    if (token.error != null) {
-                      showError(context, token.error);
-                    }
-                    else{
-                      await sharedPreferencesManager.putString(SharedPreferencesManager.keyAccessToken, token.accessToken!);
-                      await sharedPreferencesManager.putString(SharedPreferencesManager.keyRefreshToken, token.refreshToken!);
-                      await sharedPreferencesManager.putString(SharedPreferencesManager.keyUsername, widget.user['_isRelated']?widget.user['tenantCode']!=null && widget.user['tenantCode']!=''?widget.user['tenantCode']+' '+data['password']!:data['password']!:widget.user['userId']);
-                      Navigator.of(context).pop(true);
+          SafeArea(
+            child: Container(
+              padding: EdgeInsets.symmetric(vertical: 10, horizontal: 16),
+              decoration: BoxDecoration(
+                  color: Colors.white,
+                  border: Border(top: BorderSide(color: textColor.withValues(alpha: 0.15)))
+              ),
+              child: buttonTemplate(text: 'buttonSave'.tr(), action: ()async{
+                if(controllerOld.trim().isNotEmpty && controllerNew.trim().isNotEmpty && controllerConfirm.trim().isNotEmpty){
+                  if(controllerNew == controllerConfirm){
+                    var data = {
+                      'oldPassword':controllerOld.trim(),
+                      'password':controllerNew.trim()
+                    };
+            
+                    var res = await apiAuthProvider.patchData('/api/informants/'+widget.user['id'].toString()+'/changePassword', data, context);
+                    if(res != null){
+                      String username = widget.user['_isRelated']?widget.user['relatedTo']:widget.user['userId'];
+                      String? password = widget.user['_isRelated'] && widget.user['tenantCode']!=null && widget.user['tenantCode']!=''?widget.user['tenantCode']+' '+data['password']:data['password'];
+            
+                      LoginBody loginBody = LoginBody(username, password!, 'password');
+                      Token token = await apiAuthRepository.postLoginUser(loginBody);
+                      if (token.error != null) {
+                        showError(context, token.error);
+                      }
+                      else{
+                        await sharedPreferencesManager.putString(SharedPreferencesManager.keyAccessToken, token.accessToken!);
+                        await sharedPreferencesManager.putString(SharedPreferencesManager.keyRefreshToken, token.refreshToken!);
+                        await sharedPreferencesManager.putString(SharedPreferencesManager.keyUsername, widget.user['_isRelated']?widget.user['tenantCode']!=null && widget.user['tenantCode']!=''?widget.user['tenantCode']+' '+data['password']!:data['password']!:widget.user['userId']);
+                        Navigator.of(context).pop(true);
+                      }
                     }
                   }
+                  else{
+                    showError(context, 'checkNewPass'.tr());
+                  }
                 }
                 else{
-                  showError(context, 'checkNewPass'.tr());
+                  showError(context, 'cannotEmpty'.tr());
                 }
-              }
-              else{
-                showError(context, 'cannotEmpty'.tr());
-              }
-            }),
+              }),
+            ),
           )
         ],
       ),

+ 74 - 70
lib/src/layouts/mobile/request_create.dart

@@ -513,58 +513,60 @@ class _MobReqCreatePageState extends State<MobReqCreatePage> {
             alignment: Alignment.bottomCenter,
             width: U.bodyWidth(context), color: Colors.white,
             padding: EdgeInsets.symmetric(vertical: 10, horizontal: 16),
-            child: Column(
-              children: [
-                widget.request['autoResponse'] ? Container() : Container(
-                  margin: EdgeInsets.only(bottom: 10),
-                  width: double.infinity,
-                  child: TextField(
-                    controller: controllerNote,
-                    style: TextStyle(fontSize: 14, color: Colors.black),
-                    minLines: 1,
-                    maxLines: 3,
-                    keyboardType: widget.request['noteFormat'] == 'NUMBER' ? TextInputType.number : null,
-                    decoration: InputDecoration(
-                        hintText: 'placeholderNote'.tr(),
-                        hintStyle: TextStyle(color: textColor.withValues(alpha: 0.5), fontSize: 14),
-                        filled: true,
-                        fillColor: backgroundColor,
-                        hoverColor: Colors.black.withValues(alpha: 0.1),
-                        contentPadding: EdgeInsets.all(13),
-                        prefixIcon: Padding(padding: EdgeInsets.only(left: 13, right: 13), child: U.iconsax('edit-2', color: textColor, size: 24)),
-                        border: InputBorder.none,
-                        enabledBorder: OutlineInputBorder(borderRadius: BorderRadius.circular(12), borderSide: BorderSide(color: Color(0xff262626).withValues(alpha: 0.5))),
-                        focusedBorder: OutlineInputBorder(borderRadius: BorderRadius.circular(12), borderSide: const BorderSide(color: primaryColor)),
-                        isDense: true
+            child: SafeArea(
+              child: Column(
+                children: [
+                  widget.request['autoResponse'] ? Container() : Container(
+                    margin: EdgeInsets.only(bottom: 10),
+                    width: double.infinity,
+                    child: TextField(
+                      controller: controllerNote,
+                      style: TextStyle(fontSize: 14, color: Colors.black),
+                      minLines: 1,
+                      maxLines: 3,
+                      keyboardType: widget.request['noteFormat'] == 'NUMBER' ? TextInputType.number : null,
+                      decoration: InputDecoration(
+                          hintText: 'placeholderNote'.tr(),
+                          hintStyle: TextStyle(color: textColor.withValues(alpha: 0.5), fontSize: 14),
+                          filled: true,
+                          fillColor: backgroundColor,
+                          hoverColor: Colors.black.withValues(alpha: 0.1),
+                          contentPadding: EdgeInsets.all(13),
+                          prefixIcon: Padding(padding: EdgeInsets.only(left: 13, right: 13), child: U.iconsax('edit-2', color: textColor, size: 24)),
+                          border: InputBorder.none,
+                          enabledBorder: OutlineInputBorder(borderRadius: BorderRadius.circular(12), borderSide: BorderSide(color: Color(0xff262626).withValues(alpha: 0.5))),
+                          focusedBorder: OutlineInputBorder(borderRadius: BorderRadius.circular(12), borderSide: const BorderSide(color: primaryColor)),
+                          isDense: true
+                      ),
                     ),
                   ),
-                ),
-                buttonTemplate(text: 'buttonSendRequest'.tr(), action: () async{
-                  if ((Provider.of<CreateSerModule>(context, listen: false).locationType() == 2 && controllerLocation.text.trim().isNotEmpty) || (Provider.of<CreateSerModule>(context, listen: false).locationType() == 1 && Provider.of<CreateSerModule>(context, listen: false).defaultLocation() != '')) {
-                    if (controllerNote.text.length <= 128) {
-                      if (widget.user['roomAttendant'] && Provider.of<CreateSerModule>(context, listen: false).others() == 1){
-                        if (controllerUserId.text.trim().isNotEmpty){
-                          if (controllerDateString.text.trim().isEmpty && widget.request['noteFormat'] == 'DATE'){
-                            showError(context, 'dateRequired'.tr());
+                  buttonTemplate(text: 'buttonSendRequest'.tr(), action: () async{
+                    if ((Provider.of<CreateSerModule>(context, listen: false).locationType() == 2 && controllerLocation.text.trim().isNotEmpty) || (Provider.of<CreateSerModule>(context, listen: false).locationType() == 1 && Provider.of<CreateSerModule>(context, listen: false).defaultLocation() != '')) {
+                      if (controllerNote.text.length <= 128) {
+                        if (widget.user['roomAttendant'] && Provider.of<CreateSerModule>(context, listen: false).others() == 1){
+                          if (controllerUserId.text.trim().isNotEmpty){
+                            if (controllerDateString.text.trim().isEmpty && widget.request['noteFormat'] == 'DATE'){
+                              showError(context, 'dateRequired'.tr());
+                            } else {
+                              reqFunc.sendRequestToOthers(context, widget, controllerUserId, controllerNote, controllerReferenceNumber, controllerLocation, controllerDateString, _currentSliderValue);
+                            }
                           } else {
-                            reqFunc.sendRequestToOthers(context, widget, controllerUserId, controllerNote, controllerReferenceNumber, controllerLocation, controllerDateString, _currentSliderValue);
+                            showError(context, 'idRequired'.tr());
                           }
+                        } else if (controllerDateString.text.trim().isNotEmpty && widget.request['noteFormat'] == 'DATE'){
+                          dialogSendLater();
                         } else {
-                          showError(context, 'idRequired'.tr());
+                          reqFunc.sendRequest(context, widget, controllerNote, controllerReferenceNumber, controllerLocation, controllerDateString, _currentSliderValue);
                         }
-                      } else if (controllerDateString.text.trim().isNotEmpty && widget.request['noteFormat'] == 'DATE'){
-                        dialogSendLater();
                       } else {
-                        reqFunc.sendRequest(context, widget, controllerNote, controllerReferenceNumber, controllerLocation, controllerDateString, _currentSliderValue);
+                        showError(context, 'lengthMax'.tr());
                       }
                     } else {
-                      showError(context, 'lengthMax'.tr());
+                      showError(context, 'locRequired'.tr()+(!kIsWeb && widget.request['scanToRequest']?' ${'pleaseScan'.tr()}.':''));
                     }
-                  } else {
-                    showError(context, 'locRequired'.tr()+(!kIsWeb && widget.request['scanToRequest']?' ${'pleaseScan'.tr()}.':''));
-                  }
-                })
-              ],
+                  })
+                ],
+              ),
             ),
           )
         ],
@@ -674,35 +676,37 @@ class _MobReqCreatePageState extends State<MobReqCreatePage> {
             context: context,
             backgroundColor: Colors.white,
             builder: (BuildContext context) {
-              return Column(
-                mainAxisSize: MainAxisSize.min,
-                children: <Widget>[
-                  Padding(
-                    padding: const EdgeInsets.only(top: 15, bottom: 15),
-                    child: Center(
-                      child: Text('selectPicture'.tr(), style: TextStyle(color: textColor, fontWeight: FontWeight.w600)),
+              return SafeArea(
+                child: Column(
+                  mainAxisSize: MainAxisSize.min,
+                  children: <Widget>[
+                    Padding(
+                      padding: const EdgeInsets.only(top: 15, bottom: 15),
+                      child: Center(
+                        child: Text('selectPicture'.tr(), style: TextStyle(color: textColor, fontWeight: FontWeight.w600)),
+                      ),
                     ),
-                  ),
-                  divider(),
-                  ListTile(
-                    leading: Icon(Icons.image),
-                    title: Text('fromGallery'.tr(), style: TextStyle(color: textColor)),
-                    onTap: () async{
-                      var image = await reqFunc.getImageNew(context, ImageSource.gallery);
-                      Provider.of<CreateSerModule>(context, listen: false).setImages(image);
-                      Navigator.of(context).pop();
-                    },
-                  ),
-                  ListTile(
-                    leading: Icon(Icons.camera),
-                    title: Text('fromCamera'.tr(), style: TextStyle(color: textColor)),
-                    onTap: () async{
-                      var image = await reqFunc.getImageNew(context, ImageSource.camera);
-                      Provider.of<CreateSerModule>(context, listen: false).setImages(image);
-                      Navigator.of(context).pop();
-                    },
-                  )
-                ],
+                    divider(),
+                    ListTile(
+                      leading: Icon(Icons.image),
+                      title: Text('fromGallery'.tr(), style: TextStyle(color: textColor)),
+                      onTap: () async{
+                        var image = await reqFunc.getImageNew(context, ImageSource.gallery);
+                        Provider.of<CreateSerModule>(context, listen: false).setImages(image);
+                        Navigator.of(context).pop();
+                      },
+                    ),
+                    ListTile(
+                      leading: Icon(Icons.camera),
+                      title: Text('fromCamera'.tr(), style: TextStyle(color: textColor)),
+                      onTap: () async{
+                        var image = await reqFunc.getImageNew(context, ImageSource.camera);
+                        Provider.of<CreateSerModule>(context, listen: false).setImages(image);
+                        Navigator.of(context).pop();
+                      },
+                    )
+                  ],
+                ),
               );
             }
         );

+ 3 - 3
lib/src/layouts/mobile/request_select.dart

@@ -84,7 +84,7 @@ class _MobReqSelectPageState extends State<MobReqSelectPage> {
             child: Container(
               alignment: Alignment.topCenter,
               width: U.bodyWidth(context),
-              child: Provider.of<RequestModule>(context).isLoad()? loadingTemplate(() {},) : SingleChildScrollView(
+              child: Provider.of<RequestModule>(context).isLoad()? loadingTemplateNoVoid() : SingleChildScrollView(
                 padding: EdgeInsets.symmetric(horizontal: 16),
                 child: Provider.of<RequestModule>(context).isEmpty() ? Center(child: Padding(
                     padding: const EdgeInsets.only(top: 24.0),
@@ -109,8 +109,8 @@ class _MobReqSelectPageState extends State<MobReqSelectPage> {
                     return GestureDetector(
                       child: requestTiles(
                         image: Provider.of<RequestModule>(context).data()[i]['_mobileImage'] ?? "null",
-                        title: Provider.of<RequestModule>(context).data()[i][U.langColumn(context, 'subject')],
-                        subtitle: Provider.of<RequestModule>(context).data()[i][U.langColumn(context, 'subjectDescription')],
+                        title: Provider.of<RequestModule>(context).data()[i][U.langColumn(context, 'subject')] ?? '-',
+                        subtitle: Provider.of<RequestModule>(context).data()[i][U.langColumn(context, 'subjectDescription')]??'-',
                       ),
                       onTap: ()=>navigateTo(context, MobReqCreatePage(user: widget.user, request: Provider.of<RequestModule>(context, listen: false).data()[i], fromSearch: true)),
                     );

+ 14 - 10
lib/src/layouts/mobile/request_success.dart

@@ -176,11 +176,13 @@ class _MobReqSuccessPageState extends State<MobReqSuccessPage> {
           list.isNotEmpty?Column(
             children: [
               divider(),
-              Container(
-                alignment: Alignment.bottomCenter,
-                width: U.bodyWidth(context), color: Colors.white,
-                padding: EdgeInsets.symmetric(vertical: 10, horizontal: 16),
-                child: buttonTemplate(text: 'btnWait'.tr(), action: ()=>backAction()),
+              SafeArea(
+                child: Container(
+                  alignment: Alignment.bottomCenter,
+                  width: U.bodyWidth(context), color: Colors.white,
+                  padding: EdgeInsets.symmetric(vertical: 10, horizontal: 16),
+                  child: buttonTemplate(text: 'btnWait'.tr(), action: ()=>backAction()),
+                ),
               )
             ],
           ):Container()
@@ -341,11 +343,13 @@ class _MobReqSuccessPendingPageState extends State<MobReqSuccessPendingPage> {
           Column(
             children: [
               divider(),
-              Container(
-                alignment: Alignment.bottomCenter,
-                width: U.bodyWidth(context), color: Colors.white,
-                padding: EdgeInsets.symmetric(vertical: 10, horizontal: 16),
-                child: buttonTemplate(text: 'btnWait'.tr(), action: ()=>backAction()),
+              SafeArea(
+                child: Container(
+                  alignment: Alignment.bottomCenter,
+                  width: U.bodyWidth(context), color: Colors.white,
+                  padding: EdgeInsets.symmetric(vertical: 10, horizontal: 16),
+                  child: buttonTemplate(text: 'btnWait'.tr(), action: ()=>backAction()),
+                ),
               )
             ],
           )

+ 1 - 2
lib/src/layouts/web/history_detail.dart

@@ -257,7 +257,6 @@ class _WebHistoryDetailPageState extends State<WebHistoryDetailPage> {
                               SizedBox(height: 16),
                               Text('timeline'.tr(), style: TextStyle(color: textColor, fontSize: 14)),
                               SizedBox(height: 16),
-
                               Column(
                                 crossAxisAlignment: CrossAxisAlignment.start,
                                 children: [
@@ -549,7 +548,7 @@ class _WebHistoryDetailPageState extends State<WebHistoryDetailPage> {
     return TimelineTile(
       nodeAlign: TimelineNodeAlign.start,
       contents: Container(
-        margin: EdgeInsets.fromLTRB(15, 10, 0, 10),
+        margin: EdgeInsets.fromLTRB(16, 10, 16, 10),
         child: Row(
           children: [
             Text(label, style: TextStyle(fontSize: 14, color: textColor.withValues(alpha: 0.85))),

+ 1 - 1
lib/src/layouts/web/history_detail_pending.dart

@@ -112,7 +112,7 @@ class _WebHistoryDetailPendingPageState extends State<WebHistoryDetailPendingPag
                     ],
                   )
                 ],
-              ):loadingTemplate(() {},),
+              ):loadingTemplateNoVoid(),
             ),
           ),
           Container(

+ 29 - 26
lib/src/layouts/web/history_forum.dart

@@ -149,36 +149,39 @@ class _WebHistoryForumPageState extends State<WebHistoryForumPage> {
   }
 
   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;
+    try{
+      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);
               }
-              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'];
+            } 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;
       });
-    });
+    }catch(e){}
+
+    isAfterLoad = true;
   }
 
   deleteCollection() {
@@ -441,7 +444,7 @@ class _WebHistoryForumPageState extends State<WebHistoryForumPage> {
                     child: Column(
                       children: [
                         Expanded(
-                          child: messageData.length == 0 && !isAfterLoad ? loadingTemplate(() {},) : SingleChildScrollView(
+                          child: messageData.length == 0 && !isAfterLoad ? loadingTemplateNoVoid() : SingleChildScrollView(
                             controller: scrollController,
                             reverse: isReverse,
                             child: Column(

+ 64 - 46
lib/src/layouts/web/menu_account.dart

@@ -13,6 +13,8 @@ import 'package:telnow_mobile_new/src/utils/U.dart';
 import 'package:telnow_mobile_new/src/utils/provider.dart';
 import 'package:permission_handler/permission_handler.dart';
 
+import '../../utils/C.dart';
+
 class WebAccountPage extends StatefulWidget {
   const WebAccountPage({super.key});
 
@@ -28,6 +30,7 @@ class _WebAccountPageState extends State<WebAccountPage> {
   bool dnd = false;
   bool autoTranslate = false;
   List lang = [];
+  var codeOflang = {};
   bool _timeLimit = false;
 
   @override
@@ -37,7 +40,6 @@ class _WebAccountPageState extends State<WebAccountPage> {
     accFunc.getUser(context);
     setToggle();
     checkPermission(0);
-
     // TODO: implement initState
     super.initState();
   }
@@ -54,22 +56,31 @@ class _WebAccountPageState extends State<WebAccountPage> {
 
   setToggle() async{
     var license = await U.getLicense();
+    if(await U.isCompatibleWith(VersionKey.multiBahasa)){
+      lang = license['_validLang'] ?? [];
+    } else {
+      lang = license['languages'] != null ? license['languages'].split(',') : [];
+    }
     setState(() {
       dnd = Provider.of<UserModule>(context, listen: false).dndStatus();
       serDis = U.servantDisplay();
       autoTranslate = U.autoTranslate();
-      lang = license['languages'] != null ? license['languages'].split(',') : [];
     });
   }
 
   @override
   Widget build(BuildContext context) {
-    var codeOflang = {
+    codeOflang = {
       "id": 'bahasa'.tr(),
       "en": 'english'.tr(),
       "ja": 'japanese'.tr(),
       "zh": 'chinese'.tr(),
       "ko": 'korean'.tr(),
+      "ar": 'arabic'.tr(),
+      "de": 'german'.tr(),
+      "fr": 'french'.tr(),
+      "hi": 'hindi'.tr(),
+      "nl": 'dutch'.tr()
     };
     return Provider.of<UserModule>(context).user().isNotEmpty ? Scaffold(
       backgroundColor: backgroundColor,
@@ -267,7 +278,7 @@ class _WebAccountPageState extends State<WebAccountPage> {
                                       ],
                                     ),
                                   ),
-                                  onTap: ()=>changeLang(context, Provider.of<UserModule>(context, listen: false).user()),
+                                  onTap: () => changeLang(context, Provider.of<UserModule>(context, listen: false).user()),
                                 ),
                                 divider(),
                                 Container(
@@ -452,7 +463,7 @@ class _WebAccountPageState extends State<WebAccountPage> {
   changeLang(context, user) {
     showDialog(
         context: context,
-        builder: (BuildContext context) {
+        builder: (BuildContext contextDialog) {
           return AlertDialog(
             contentPadding: EdgeInsets.symmetric(vertical: 0, horizontal: 10),
             content: Column(
@@ -466,7 +477,7 @@ class _WebAccountPageState extends State<WebAccountPage> {
                 ),
                 divider(),
                 SizedBox(height: 16),
-                listOfLang(user),
+                listOfLang(user, contextDialog),
                 SizedBox(height: 16)
               ],
             ),
@@ -475,46 +486,53 @@ class _WebAccountPageState extends State<WebAccountPage> {
     );
   }
 
-  listOfLang(user){
-    return Column(
-      crossAxisAlignment: CrossAxisAlignment.start,
-      children: [
-        ListTile(
-          title: Text('bahasa'.tr(), style: TextStyle(color: textColor, fontSize: 14)),
-          trailing: Icon(context.locale.toString()=='id'?Icons.radio_button_checked:Icons.radio_button_off, color: primaryColor, size: 18),
-          onTap: context.locale.toString() != 'id' ? () async {
-            accFunc.switchLang(context, 'id', user);
-          } : null,
-        ),
-        ListTile(
-          title: Text('english'.tr(), style: TextStyle(color: textColor, fontSize: 14)),
-          trailing: Icon(context.locale.toString()=='en'?Icons.radio_button_checked:Icons.radio_button_off, color: primaryColor, size: 18),
-          onTap: context.locale.toString() != 'en' ? () async {
-            accFunc.switchLang(context, 'en', user);
-          } : null,
-        ),
-        lang.contains('ja') ? ListTile(
-          title: Text('japanese'.tr(), style: TextStyle(color: textColor, fontSize: 14)),
-          trailing: Icon(context.locale.toString()=='ja'?Icons.radio_button_checked:Icons.radio_button_off, color: primaryColor, size: 18),
-          onTap: context.locale.toString() != 'ja' ? () async {
-            accFunc.switchLang(context, 'ja', user);
-          } : null,
-        ) : Container(),
-        lang.contains('zh') ? ListTile(
-          title: Text('chinese'.tr(), style: TextStyle(color: textColor, fontSize: 14)),
-          trailing: Icon(context.locale.toString()=='zh'?Icons.radio_button_checked:Icons.radio_button_off, color: primaryColor, size: 18),
-          onTap: context.locale.toString() != 'zh' ? () async {
-            accFunc.switchLang(context, 'zh', user);
-          } : null,
-        ) : Container(),
-        lang.contains('ko') ? ListTile(
-          title: Text('korean'.tr(), style: TextStyle(color: textColor, fontSize: 14)),
-          trailing: Icon(context.locale.toString()=='ko'?Icons.radio_button_checked:Icons.radio_button_off, color: primaryColor, size: 18),
-          onTap: context.locale.toString() != 'ko' ? () async {
-            accFunc.switchLang(context, 'ko', user);
-          } : null,
-        ) : Container()
-      ],
+  listOfLang(user, cd){
+    return !mounted ? SizedBox() : SafeArea(
+      child: Column(
+        crossAxisAlignment: CrossAxisAlignment.start,
+        children: [
+          ListTile(
+            title: Text('bahasa'.tr(), style: TextStyle(color: textColor, fontSize: 14)),
+            trailing: Icon(context.locale.toString()=='id'?Icons.radio_button_checked:Icons.radio_button_off, color: primaryColor, size: 18),
+            onTap: context.locale.toString() != 'id' ? () async {
+              accFunc.switchLang(context, 'id', user);
+              Navigator.of(cd).pop();
+            } : null,
+          ),
+          ListTile(
+            title: Text('english'.tr(), style: TextStyle(color: textColor, fontSize: 14)),
+            trailing: Icon(context.locale.toString()=='en'?Icons.radio_button_checked:Icons.radio_button_off, color: primaryColor, size: 18),
+            onTap: context.locale.toString() != 'en' ? () async {
+              accFunc.switchLang(context, 'en', user);
+              Navigator.of(cd).pop();
+            } : null,
+          ),
+          lang.length - 1 > 1 ? ListTile(
+            title: Text(codeOflang[lang[2]], style: TextStyle(color: textColor, fontSize: 14)),
+            trailing: Icon(context.locale.toString()==lang[2]?Icons.radio_button_checked:Icons.radio_button_off, color: primaryColor, size: 18),
+            onTap: context.locale.toString() != lang[2] ? () async {
+              accFunc.switchLang(context, 1, user);
+              Navigator.of(cd).pop();
+            } : null,
+          ) : Container(),
+          lang.length - 1 > 2 ? ListTile(
+            title: Text(codeOflang[lang[3]], style: TextStyle(color: textColor, fontSize: 14)),
+            trailing: Icon(context.locale.toString()==lang[3]?Icons.radio_button_checked:Icons.radio_button_off, color: primaryColor, size: 18),
+            onTap: context.locale.toString() != lang[3] ? () async {
+              accFunc.switchLang(context, 2, user);
+              Navigator.of(cd).pop();
+            } : null,
+          ) : Container(),
+          lang.length - 1 > 3 ? ListTile(
+            title: Text(codeOflang[lang[4]], style: TextStyle(color: textColor, fontSize: 14)),
+            trailing: Icon(context.locale.toString()==lang[4]?Icons.radio_button_checked:Icons.radio_button_off, color: primaryColor, size: 18),
+            onTap: context.locale.toString() != lang[4] ? () async {
+              accFunc.switchLang(context, 3, user);
+              Navigator.of(cd).pop();
+            } : null,
+          ) : Container(),
+        ],
+      ),
     );
   }
 }

+ 5 - 5
lib/src/layouts/web/menu_history.dart

@@ -318,6 +318,7 @@ class _WebHistoryPageState extends State<WebHistoryPage> with TickerProviderStat
                                 renderRequested(Provider.of<HistoryModule>(context).dataMisi()[i]),
                                 Row(
                                   mainAxisAlignment: MainAxisAlignment.spaceBetween,
+                                  crossAxisAlignment: CrossAxisAlignment.center ,
                                   children: [
                                     Text('location'.tr()+': '+Provider.of<HistoryModule>(context).dataMisi()[i]['ipphoneExtLocation'], style: TextStyle(color: textColor, fontSize: 14), overflow: TextOverflow.ellipsis),
                                     Provider.of<HistoryModule>(context).dataMisi()[i]['_activeHoldRequest'] != null ? Container(
@@ -331,9 +332,8 @@ class _WebHistoryPageState extends State<WebHistoryPage> with TickerProviderStat
                                           Text(Provider.of<HistoryModule>(context).dataMisi()[i]['_activeHoldRequest']['description'], style: TextStyle(color: textColor, fontSize: 14))
                                         ],
                                       ),
-                                    ) : !Provider.of<HistoryModule>(context).dataMisi()[i]['autoResponse'] && Provider.of<HistoryModule>(context).dataMisi()[i]['_hasForum'] && Provider.of<HistoryModule>(context).dataMisi()[i]['_forumMsg'] != null ? Container(
-                                      width: double.infinity,
-                                      margin: EdgeInsets.only(top: 16),
+                                    ) : Provider.of<HistoryModule>(context).dataMisi()[i]['_hasForum'] && Provider.of<HistoryModule>(context).dataMisi()[i]['_forumMsg'] != null ? ConstrainedBox(
+                                      constraints: BoxConstraints(minWidth: 10),
                                       child: Row(
                                         children: [
                                           FadeTransition(opacity: _animationController, child: U.iconsax('messages-3', color: primaryColor)),
@@ -360,7 +360,7 @@ class _WebHistoryPageState extends State<WebHistoryPage> with TickerProviderStat
             }),
           ),
           !Provider.of<HistoryModule>(context).stopLoadHistory() || (Provider.of<HistoryModule>(context).isLoadHistory() && Provider.of<HistoryModule>(context).page() > 0)
-              ? Container(margin: EdgeInsets.only(top: Provider.of<HistoryModule>(context).page() > 0 ? 0 : 50), child: loadingTemplate(() {},))
+              ? Container(margin: EdgeInsets.only(top: Provider.of<HistoryModule>(context).page() > 0 ? 0 : 50), child: loadingTemplateNoVoid())
               : Provider.of<HistoryModule>(context).dataMisi().isEmpty && Provider.of<HistoryModule>(context).dataPending().isEmpty
               ? NoDataPage()
               : Container(),
@@ -495,7 +495,7 @@ class _WebHistoryPageState extends State<WebHistoryPage> with TickerProviderStat
               })
           ),
           !Provider.of<HistoryModule>(context).stopLoadHistory() || (Provider.of<HistoryModule>(context).isLoadHistory() && Provider.of<HistoryModule>(context).page() > 0)
-              ? Container(margin: EdgeInsets.only(top: Provider.of<HistoryModule>(context).page() > 0 ? 0 : 50), child: loadingTemplate(() {},))
+              ? Container(margin: EdgeInsets.only(top: Provider.of<HistoryModule>(context).page() > 0 ? 0 : 50), child: loadingTemplateNoVoid())
               : Provider.of<HistoryModule>(context).dataMisi().length == 0 ? NoDataPage() : Container(),
         ],
       ),

+ 45 - 1
lib/src/layouts/web/menu_home.dart

@@ -22,6 +22,8 @@ import 'package:telnow_mobile_new/src/utils/cache_manager.dart';
 import 'package:telnow_mobile_new/src/utils/provider.dart';
 import 'package:url_launcher/url_launcher.dart';
 
+import '../../utils/C.dart';
+
 class WebHomePage extends StatefulWidget {
   const WebHomePage({super.key});
 
@@ -49,11 +51,19 @@ class _WebHomePageState extends State<WebHomePage> {
         }
       });
     });
+    checkCompatibility();
     // TODO: implement initState
     super.initState();
   }
   bool _timeLimit = false;
 
+  checkCompatibility() async{
+    var isCompatible = await U.isCompatibleWith(VersionKey.multiBahasa);
+    if(isCompatible) {
+      homeFunc.getContactCenter(context);
+    }
+  }
+
   @override
   Widget build(BuildContext context) {
     return Provider.of<UserModule>(context).user().isNotEmpty?Scaffold(
@@ -95,6 +105,38 @@ class _WebHomePageState extends State<WebHomePage> {
                             ),
                           ),
                           SizedBox(width: 30),
+                          FutureBuilder(future: U.isCompatibleWith(VersionKey.multiBahasa), builder: (context, snapshot){
+                            if(!snapshot.hasData){
+                              return SizedBox();
+                            }
+                            final isCompatible = snapshot.data!;
+                            final hasContactCenter = Provider.of<ServiceModule>(context, listen: false)
+                                .contactCenter()
+                                .toString()
+                                .isNotEmpty;
+
+                            if (isCompatible && hasContactCenter) {
+                              return Container(
+                                padding: EdgeInsets.all(8),
+                                decoration: BoxDecoration(color: Colors.white, borderRadius: BorderRadius.all(Radius.circular(50))),
+                                child: GestureDetector(
+                                    onTap: () => launchUrl(Uri.parse(Provider.of<ServiceModule>(context, listen: false).contactCenter().toString())),
+                                    child: U.iconsax('phone-number', color: primaryColor, size: 26.0)
+                                ),
+                              ); // replace with the widget you want to show
+                            } else {
+                              return SizedBox(); // or nothing
+                            }
+                          }),
+                          // U.isCompatibleWith(VersionKey.multiBahasa) && Provider.of<ServiceModule>(context, listen: false).contactCenter().toString() != ''  ? Container(
+                          //   padding: EdgeInsets.all(8),
+                          //   decoration: BoxDecoration(color: Colors.white, borderRadius: BorderRadius.all(Radius.circular(50))),
+                          //   child: GestureDetector(
+                          //       onTap: () => launchUrl(Uri.parse(Provider.of<ServiceModule>(context, listen: false).contactCenter().toString())),
+                          //       child: U.iconsax('phone-number', color: primaryColor, size: 26.0)
+                          //   ),
+                          // ):SizedBox(),
+                          SizedBox(width: 10),
                           GestureDetector(
                             child: Container(
                               padding: EdgeInsets.all(8),
@@ -110,7 +152,9 @@ class _WebHomePageState extends State<WebHomePage> {
                                 ],
                               ),
                             ),
-                            onTap: ()=>navigateTo(context, WebMessageListPage(Provider.of<UserModule>(context, listen: false).user())),
+                            onTap: ()=>navigateTo(context, WebMessageListPage(Provider.of<UserModule>(context, listen: false).user())).then((_){
+                              homeFunc.getUnreadMessages(context);
+                            }),
                           ),
                           SizedBox(width: 10),
                           GestureDetector(

File diff suppressed because it is too large
+ 1159 - 276
lib/src/layouts/web/message_list.dart


+ 1 - 1
lib/src/layouts/web/request_select.dart

@@ -102,7 +102,7 @@ class _WebReqSelectPageState extends State<WebReqSelectPage> {
           ),
           divider(),
           Expanded(
-            child: Provider.of<RequestModule>(context).isLoad()? loadingTemplate(() {},) : Container(
+            child: Provider.of<RequestModule>(context).isLoad()? loadingTemplateNoVoid() : Container(
               width: double.infinity,
               child: SingleChildScrollView(
                 padding: EdgeInsets.symmetric(vertical: 25, horizontal: 100),

+ 2 - 2
lib/src/layouts/web/request_success.dart

@@ -197,7 +197,7 @@ class _WebReqSuccessPageState extends State<WebReqSuccessPage> {
                   )
                 ],
               ),
-            ):loadingTemplate(() {},),
+            ):loadingTemplateNoVoid(),
           )
         ],
       ),
@@ -378,7 +378,7 @@ class _WebReqSuccessPendingPageState extends State<WebReqSuccessPendingPage> {
                     ],
                   )
                 ],
-              ):loadingTemplate(() {},),
+              ):loadingTemplateNoVoid(),
             ),
           )
         ],

+ 13 - 0
lib/src/utils/C.dart

@@ -0,0 +1,13 @@
+enum VersionKey {
+  none,
+  multiBahasa
+}
+
+class C{
+
+  static const Map<String, int> version = {
+    'multiBahasa': 1754624839 //penambahan jadi 10 bahasa, identifikasi berdasarkan ordinal 1,2,3
+  };
+
+  static int get(String key) => version[key]?? 0;
+}

+ 83 - 3
lib/src/utils/U.dart

@@ -1,3 +1,5 @@
+import 'dart:async';
+
 import 'package:easy_localization/easy_localization.dart';
 import 'package:firebase_messaging/firebase_messaging.dart';
 import 'package:flutter/foundation.dart';
@@ -19,6 +21,7 @@ import 'package:telnow_mobile_new/src/storage/sharedpreferences/shared_preferenc
 import 'package:synchronized/synchronized.dart';
 
 import 'cache_manager.dart';
+import 'C.dart';
 
 const Color backgroundColor = Colors.white;
 const Color primaryColor = Color(0xff078C84);
@@ -93,7 +96,7 @@ class U {
       RefreshTokenBody? refreshTokenBody = RefreshTokenBody('refresh_token', refreshToken);
       refreshAuth(refreshTokenBody);
     } else {
-      print(JwtDecoder.getRemainingTime(_sharedPreferencesManager.getString(SharedPreferencesManager.keyAccessToken)!));
+      // print(JwtDecoder.getRemainingTime(_sharedPreferencesManager.getString(SharedPreferencesManager.keyAccessToken)!));
     }
     // return _lockAuth.synchronized(() async {
     //   print("wait refresh token OK");
@@ -305,12 +308,17 @@ class U {
   }
 
   static int _serverVersion = 0;
+  static final Completer<void> _versionReady = Completer<void>();
+  static List validLang = [];
+
   static getServerVersion() async{
     String url = '/api/license';
     try {
       var dt = await ApiAuthProvider().getJsonDataNoAuth(url);
       _serverVersion = dt['serverVersion'];
+      validLang = newServerVersion(1754624839) ? dt["_validLang"] : dt['languages'];
       CacheMan.writeData(url, _serverVersion);
+      _versionReady.complete();
     } catch (error) {
       var val = await CacheMan.readData(url);
       if (val != null) {
@@ -321,10 +329,62 @@ class U {
     // print('checking current server version: $_serverVersion');
   }
 
+  static Future<void> _waitUntilServerVersionReady() async {
+    if (!_versionReady.isCompleted) {
+      await _versionReady.future;
+    }
+  }
+
+  static String getLang(code){
+    if(code is int){
+      return validLang[code+1];
+    } else {
+      return code;
+    }
+  }
+
+  static String getLangIndex(code){
+    if(code == 'id' || code == 'en'){
+      return code;
+    } else {
+      var t = validLang.indexOf(code)-1;
+      return t.toString();
+    }
+  }
+
+  static List retValidLang(){
+    return validLang;
+  }
+
   static newServerVersion(int version){
     return version <= _serverVersion;
   }
 
+  static final Map<String, Future<bool>> _compatibilityCache = {};
+  static Future<bool> isCompatibleWith(VersionKey key) {
+    final keyName = key.name.toString();
+    if (_compatibilityCache.containsKey(keyName)) {
+      return _compatibilityCache[keyName]!;
+    }
+
+    final future = _loadAndCompare(keyName);
+    _compatibilityCache[keyName] = future;
+    return future;
+  }
+
+  static Future<bool> _loadAndCompare(String keyName) async {
+    try{
+      await _waitUntilServerVersionReady();
+      final value = C.get(keyName);
+
+      return value <= _serverVersion;
+    }catch(e){
+
+      return false;
+    }
+  }
+
+
   static int retServerVersion(){
     return _serverVersion;
   }
@@ -466,7 +526,8 @@ class U {
       'assets/iconsax/${name}.svg',
       colorFilter: ColorFilter.mode(color, BlendMode.srcIn),
       width: size, height: size,
-      fit: BoxFit.scaleDown
+      fit: BoxFit.scaleDown,
+      matchTextDirection: true,
     );
   }
 
@@ -489,7 +550,26 @@ class U {
 
   static String langColumn(BuildContext context, String text){
     var code = context.locale.toString();
-    return code == 'id' ? text : '$text${code[0].toUpperCase()}${code.substring(1).toLowerCase()}';
+    var prefix = code == 'id' || code == 'en' && text.substring(1) != '_' ? '' : '_';
+    return code == 'id' ? text : '$prefix$text${code[0].toUpperCase()}${code.substring(1).toLowerCase()}';
+  }
+
+  static String realColumn(Locale loc, String text){
+    var lc = loc.toString();
+    if(lc == 'id' || lc == 'en'){
+      if(lc == 'id') {
+        return text;
+      } else {
+        return "${text}En";
+      }
+    } else {
+      try {
+        var i = validLang.indexOf(loc.toString()) - 1;
+        return text = text + i.toString();
+      } catch (e) {
+        return text;
+      }
+    }
   }
 
   static bool hidePayload = false;

+ 32 - 0
lib/src/utils/provider.dart

@@ -152,6 +152,7 @@ class ServiceModule with ChangeNotifier {
   static List? _reqGroup;
   static List _offer = [];
   static List _banner = [];
+  static String _contactCenter = '';
 
   String scoopeName() => _scoopeName;
   dynamic scoopeValue() => _scoopeValue;
@@ -171,12 +172,18 @@ class ServiceModule with ChangeNotifier {
   List? reqGroup() => _reqGroup;
   List specialOffer() => _offer;
   List banner() => _banner;
+  String contactCenter() => _contactCenter;
 
   void setInitialized(value) {
     _initialized = value;
     notifyListeners();
   }
 
+  void setContactCenter(value) {
+    _contactCenter = value;
+    notifyListeners();
+  }
+
   void setScoopeName(value) {
     _scoopeName = value;
     notifyListeners();
@@ -516,10 +523,16 @@ class MessageModule with ChangeNotifier{
   Map<String, dynamic> _user = {};
   List _data = [];
   bool _firstLoad = false;
+  bool _isAfterLoad = false;
+  List _forum = [];
+  int _selectedTab = 0;
 
   Map<String, dynamic> user() => _user;
   List data() => _data;
   bool firstLoad() => _firstLoad;
+  bool isAfterLoad() => _isAfterLoad;
+  List forum() => _forum;
+  int activeTab() => _selectedTab;
 
   void setUser(value) {
     _user = value;
@@ -536,9 +549,28 @@ class MessageModule with ChangeNotifier{
     notifyListeners();
   }
 
+  void setAfterLoad(value) {
+    _isAfterLoad = value;
+    notifyListeners();
+  }
+
   void reset(){
     _user = {};
     _data = [];
     _firstLoad = false;
+    _isAfterLoad = false;
+    _forum = [];
+    _selectedTab = 0;
+  }
+
+  void setForum(val){
+    _forum = val;
+    notifyListeners();
+  }
+
+  void setActiveTab(val){
+    // print("setActiveTab($val)");
+    _selectedTab = val;
+    notifyListeners();
   }
 }

+ 51 - 43
pubspec.lock

@@ -69,10 +69,10 @@ packages:
     dependency: transitive
     description:
       name: async
-      sha256: d2872f9c19731c2e5f10444b14686eb7cc85c76274bd6c16e1816bff9a3bab63
+      sha256: "947bfcf187f74dbc5e146c9eb9c0f10c9f8b30743e341481c1e2ed3ecc18c20c"
       url: "https://pub.dev"
     source: hosted
-    version: "2.12.0"
+    version: "2.11.0"
   auto_route:
     dependency: "direct main"
     description:
@@ -109,10 +109,10 @@ packages:
     dependency: transitive
     description:
       name: boolean_selector
-      sha256: "8aab1771e1243a5063b8b0ff68042d67334e3feab9e95b9490f9a6ebf73b42ea"
+      sha256: "6cfb5af12253eaf2b368f07bacc5a80d1301a071c73360d746b7f2e32d762c66"
       url: "https://pub.dev"
     source: hosted
-    version: "2.1.2"
+    version: "2.1.1"
   build:
     dependency: transitive
     description:
@@ -213,10 +213,18 @@ packages:
     dependency: "direct main"
     description:
       name: camera
-      sha256: "413d2b34fe28496c35c69ede5b232fb9dd5ca2c3a4cb606b14efc1c7546cc8cb"
+      sha256: d6ec2cbdbe2fa8f5e0d07d8c06368fe4effa985a4a5ddade9cc58a8cd849557d
       url: "https://pub.dev"
     source: hosted
-    version: "0.11.1"
+    version: "0.11.2"
+  camera_android:
+    dependency: "direct main"
+    description:
+      name: camera_android
+      sha256: "4f40d053a67e99029b5be7f00ef8047b63edb65ccc4e2546b84d47e302c6bf62"
+      url: "https://pub.dev"
+    source: hosted
+    version: "0.10.10+4"
   camera_android_camerax:
     dependency: transitive
     description:
@@ -237,10 +245,10 @@ packages:
     dependency: transitive
     description:
       name: camera_platform_interface
-      sha256: "953e7baed3a7c8fae92f7200afeb2be503ff1a17c3b4e4ed7b76f008c2810a31"
+      sha256: "2f757024a48696ff4814a789b0bd90f5660c0fb25f393ab4564fb483327930e2"
       url: "https://pub.dev"
     source: hosted
-    version: "2.9.0"
+    version: "2.10.0"
   camera_web:
     dependency: transitive
     description:
@@ -269,10 +277,10 @@ packages:
     dependency: transitive
     description:
       name: characters
-      sha256: f71061c654a3380576a52b451dd5532377954cf9dbd272a78fc8479606670803
+      sha256: "04a925763edad70e8443c99234dc3328f442e811f1d8fd1a72f1c8ad0f69a605"
       url: "https://pub.dev"
     source: hosted
-    version: "1.4.0"
+    version: "1.3.0"
   checked_yaml:
     dependency: transitive
     description:
@@ -285,10 +293,10 @@ packages:
     dependency: transitive
     description:
       name: clock
-      sha256: fddb70d9b5277016c77a80201021d40a2247104d9f4aa7bab7157b7e3f05b84b
+      sha256: cb6d7f03e1de671e34607e909a7213e31d7752be4fb66a86d29fe1eb14bfb5cf
       url: "https://pub.dev"
     source: hosted
-    version: "1.1.2"
+    version: "1.1.1"
   cloud_firestore:
     dependency: "direct main"
     description:
@@ -325,10 +333,10 @@ packages:
     dependency: transitive
     description:
       name: collection
-      sha256: "2f5709ae4d3d59dd8f7cd309b4e023046b57d8a6c82130785d2b0e5868084e76"
+      sha256: a1ace0a119f20aabc852d165077c036cd864315bd99b7eaa10a60100341941bf
       url: "https://pub.dev"
     source: hosted
-    version: "1.19.1"
+    version: "1.19.0"
   convert:
     dependency: transitive
     description:
@@ -485,10 +493,10 @@ packages:
     dependency: transitive
     description:
       name: fake_async
-      sha256: "6a95e56b2449df2273fd8c45a662d6947ce1ebb7aafe80e550a3f68297f3cacc"
+      sha256: "511392330127add0b769b75a987850d136345d9227c6b94c96a04cf4a391bf78"
       url: "https://pub.dev"
     source: hosted
-    version: "1.3.2"
+    version: "1.3.1"
   ffi:
     dependency: transitive
     description:
@@ -743,10 +751,10 @@ packages:
     dependency: "direct main"
     description:
       name: flutter_svg
-      sha256: c200fd79c918a40c5cd50ea0877fa13f81bdaf6f0a5d3dbcc2a13e3285d6aa1b
+      sha256: cd57f7969b4679317c17af6fd16ee233c1e60a82ed209d8a475c54fd6fd6f845
       url: "https://pub.dev"
     source: hosted
-    version: "2.0.17"
+    version: "2.2.0"
   flutter_test:
     dependency: "direct dev"
     description: flutter
@@ -953,18 +961,18 @@ packages:
     dependency: transitive
     description:
       name: leak_tracker
-      sha256: c35baad643ba394b40aac41080300150a4f08fd0fd6a10378f8f7c6bc161acec
+      sha256: "7bb2830ebd849694d1ec25bf1f44582d6ac531a57a365a803a6034ff751d2d06"
       url: "https://pub.dev"
     source: hosted
-    version: "10.0.8"
+    version: "10.0.7"
   leak_tracker_flutter_testing:
     dependency: transitive
     description:
       name: leak_tracker_flutter_testing
-      sha256: f8b613e7e6a13ec79cfdc0e97638fddb3ab848452eff057653abd3edba760573
+      sha256: "9491a714cca3667b60b5c420da8217e6de0d1ba7a5ec322fab01758f6998f379"
       url: "https://pub.dev"
     source: hosted
-    version: "3.0.9"
+    version: "3.0.8"
   leak_tracker_testing:
     dependency: transitive
     description:
@@ -1017,10 +1025,10 @@ packages:
     dependency: transitive
     description:
       name: matcher
-      sha256: dc58c723c3c24bf8d3e2d3ad3f2f9d7bd9cf43ec6feaa64181775e60190153f2
+      sha256: d2323aa2060500f906aa31a895b4030b6da3ebdcc5619d14ce1aada65cd161cb
       url: "https://pub.dev"
     source: hosted
-    version: "0.12.17"
+    version: "0.12.16+1"
   material_color_utilities:
     dependency: transitive
     description:
@@ -1033,10 +1041,10 @@ packages:
     dependency: transitive
     description:
       name: meta
-      sha256: e3641ec5d63ebf0d9b41bd43201a66e3fc79a65db5f61fc181f04cd27aab950c
+      sha256: bdb68674043280c3428e9ec998512fb681678676b3c54e773629ffe74419f8c7
       url: "https://pub.dev"
     source: hosted
-    version: "1.16.0"
+    version: "1.15.0"
   mime:
     dependency: transitive
     description:
@@ -1177,10 +1185,10 @@ packages:
     dependency: transitive
     description:
       name: path
-      sha256: "75cca69d1490965be98c73ceaea117e8a04dd21217b37b292c9ddbec0d955bc5"
+      sha256: "087ce49c3f0dc39180befefc60fdb4acd8f8620e5682fe2476afd0b3688bb4af"
       url: "https://pub.dev"
     source: hosted
-    version: "1.9.1"
+    version: "1.9.0"
   path_drawing:
     dependency: transitive
     description:
@@ -1518,10 +1526,10 @@ packages:
     dependency: transitive
     description:
       name: source_span
-      sha256: "254ee5351d6cb365c859e20ee823c3bb479bf4a293c22d17a9f1bf144ce86f7c"
+      sha256: "53e943d4206a5e30df338fd4c6e7a077e02254531b138a15aec3bd143c1a8b3c"
       url: "https://pub.dev"
     source: hosted
-    version: "1.10.1"
+    version: "1.10.0"
   sprintf:
     dependency: transitive
     description:
@@ -1574,18 +1582,18 @@ packages:
     dependency: transitive
     description:
       name: stack_trace
-      sha256: "8b27215b45d22309b5cddda1aa2b19bdfec9df0e765f2de506401c071d38d1b1"
+      sha256: "9f47fd3630d76be3ab26f0ee06d213679aa425996925ff3feffdec504931c377"
       url: "https://pub.dev"
     source: hosted
-    version: "1.12.1"
+    version: "1.12.0"
   stream_channel:
     dependency: transitive
     description:
       name: stream_channel
-      sha256: "969e04c80b8bcdf826f8f16579c7b14d780458bd97f56d107d3950fdbeef059d"
+      sha256: ba2aa5d8cc609d96bbb2899c28934f9e1af5cddbd60a827822ea467161eb54e7
       url: "https://pub.dev"
     source: hosted
-    version: "2.1.4"
+    version: "2.1.2"
   stream_transform:
     dependency: transitive
     description:
@@ -1598,10 +1606,10 @@ packages:
     dependency: transitive
     description:
       name: string_scanner
-      sha256: "921cd31725b72fe181906c6a94d987c78e3b98c2e205b397ea399d4054872b43"
+      sha256: "688af5ed3402a4bde5b3a6c15fd768dbf2621a614950b17f04626c431ab3c4c3"
       url: "https://pub.dev"
     source: hosted
-    version: "1.4.1"
+    version: "1.3.0"
   synchronized:
     dependency: "direct main"
     description:
@@ -1614,18 +1622,18 @@ packages:
     dependency: transitive
     description:
       name: term_glyph
-      sha256: "7f554798625ea768a7518313e58f83891c7f5024f88e46e7182a4558850a4b8e"
+      sha256: a29248a84fbb7c79282b40b8c72a1209db169a2e0542bce341da992fe1bc7e84
       url: "https://pub.dev"
     source: hosted
-    version: "1.2.2"
+    version: "1.2.1"
   test_api:
     dependency: transitive
     description:
       name: test_api
-      sha256: fb31f383e2ee25fbbfe06b40fe21e1e458d14080e3c67e7ba0acfde4df4e0bbd
+      sha256: "664d3a9a64782fcdeb83ce9c6b39e78fd2971d4e37827b9b06c3aa1edc5e760c"
       url: "https://pub.dev"
     source: hosted
-    version: "0.7.4"
+    version: "0.7.3"
   timelines_plus:
     dependency: "direct main"
     description:
@@ -1806,10 +1814,10 @@ packages:
     dependency: transitive
     description:
       name: vm_service
-      sha256: "0968250880a6c5fe7edc067ed0a13d4bae1577fe2771dcf3010d52c4a9d3ca14"
+      sha256: f6be3ed8bd01289b34d679c2b62226f63c0e69f9fd2e50a6b3c1c729a961041b
       url: "https://pub.dev"
     source: hosted
-    version: "14.3.1"
+    version: "14.3.0"
   watcher:
     dependency: transitive
     description:
@@ -1931,5 +1939,5 @@ packages:
     source: hosted
     version: "3.1.1"
 sdks:
-  dart: ">=3.7.0-0 <4.0.0"
+  dart: ">=3.6.1 <4.0.0"
   flutter: ">=3.27.0"

+ 5 - 4
pubspec.yaml

@@ -16,8 +16,8 @@ publish_to: 'none' # Remove this line if you wish to publish to pub.dev
 # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
 # In Windows, build-name is used as the major, minor, and patch parts
 # of the product and file versions while build-number is used as the build suffix.
-#version: 4.0.15+48
-version: 4.0.16+49
+#version: 4.0.16+50
+version: 4.0.17+51
 
 environment:
   sdk: ^3.6.1
@@ -56,7 +56,8 @@ dependencies:
   image_picker: ^1.1.2
   path_provider: ^2.1.5
   uuid: ^4.5.1
-  camera: ^0.11.1
+  camera: ^0.11.2
+  camera_android: ^0.10.10+4
   another_flushbar: ^1.12.30
   fluttertoast: ^8.2.10
   synchronized: ^3.3.0+3
@@ -87,7 +88,7 @@ dependencies:
   carousel_slider: ^5.0.0
   simple_connection_checker: ^0.3.4
   flutter_linkify: ^6.0.0
-  flutter_svg: ^2.0.17
+  flutter_svg: ^2.2.0
   selectable_autolink_text: ^2.6.0
   flutter_native_splash: ^2.4.4
   searchfield: ^1.2.4

+ 272 - 200
web/index.html

@@ -2,210 +2,282 @@
 <html>
 
 <head>
-  <!--
-    If you are serving your web app in a path other than the root, change the
-    href value below to reflect the base path you are serving from.
-
-    The path provided below has to start and end with a slash "/" in order for
-    it to work correctly.
-
-    For more details:
-    * https://developer.mozilla.org/en-US/docs/Web/HTML/Element/base
-
-    This is a placeholder for base href that will be replaced by the value of
-    the `--base-href` argument provided to `flutter build`.
-  -->
-  <base href="$FLUTTER_BASE_HREF">
-
-  <meta charset="UTF-8">
-  <meta content="IE=Edge" http-equiv="X-UA-Compatible">
-  <meta name="description" content="Datacom Solusindo">
-
-  <!-- iOS meta tags & icons -->
-  <meta name="mobile-web-app-capable" content="yes">
-  <meta name="apple-mobile-web-app-status-bar-style" content="black">
-  <meta name="apple-mobile-web-app-title" content="TelNow Application">
-  <link rel="apple-touch-icon" href="icons/Icon-192.png">
-
-  <!-- Favicon -->
-  <link rel="icon" type="image/png" href="favicon.ico" />
-
-  <title>TelNow Application</title>
-  <link rel="manifest" href="manifest.json">
-  <style>
-    .loader {
-      border: 5px solid #f3f3f3;
-      border-radius: 50%;
-      border-top: 5px solid #01beb5;
-      width: 40px;
-      height: 40px;
-      -webkit-animation: spin 2s linear infinite;
-      /* Safari */
-      animation: spin 2s linear infinite;
-    }
-
-    .center {
-      position: absolute;
-      top: 50%;
-      left: 50%;
-      margin-top: -60px;
-      margin-left: -120px;
-      width: 240px;
-      height: 40px;
-      text-align: center;
-      font-family: Arial, Helvetica, sans-serif;
-    }
-
-    .center-align {
-      position: absolute;
-      top: 50%;
-      left: 50%;
-      margin-top: 20px;
-      margin-left: -60px;
-      width: 40px;
-      height: 40px;
-    }
-
-    .center-count {
-      position: absolute;
-      top: 50%;
-      left: 50%;
-      margin-top: -30px;
-      margin-left: -120px;
-      width: 240px;
-      height: 40px;
-      font-size: 20px;
-      font-weight: bold;
-      text-align: center;
-      font-family: Arial, Helvetica, sans-serif;
-    }
-
-    /* Safari */
-    @-webkit-keyframes spin {
-      0% {
-        -webkit-transform: rotate(0deg);
-      }
-
-      100% {
-        -webkit-transform: rotate(360deg);
-      }
-    }
-
-    @keyframes spin {
-      0% {
-        transform: rotate(0deg);
-      }
-
-      100% {
-        transform: rotate(360deg);
-      }
-    }
-
-    .countdown {
-      display: inline-block;
-      text-indent: -100vw;
-      position: relative;
-      width: 1ch;
-    }
-
-    .countdown::after {
-      content: '10';
-      position: absolute;
-      top: 0;
-      left: 0;
-      text-indent: 0;
-      animation: 18s countdown 1s forwards;
-    }
-
-    @keyframes countdown {
-      0% {
-        content: '10';
-      }
-
-      10% {
-        content: '9';
-      }
-
-      20% {
-        content: '8';
-      }
-
-      30% {
-        content: '7';
-      }
-
-      35% {
-        content: '7';
-      }
-
-      40% {
-        content: '6';
-      }
-
-      45% {
-        content: '6';
-      }
-
-      50% {
-        content: '5';
-      }
-
-      55% {
-        content: '5';
-      }
-
-      60% {
-        content: '4';
-      }
-
-      65% {
-        content: '4';
-      }
-
-      70% {
-        content: '3';
-      }
-
-      75% {
-        content: '2';
-      }
-
-      80% {
-        content: '2';
-      }
-
-      85% {
-        content: '2';
-      }
-
-      90% {
-        content: '1';
-      }
-
-      95% {
-        content: '1';
-      }
-
-      100% {
-        content: '0';
-      }
-    }
-  </style>
+    <!--
+      If you are serving your web app in a path other than the root, change the
+      href value below to reflect the base path you are serving from.
+
+      The path provided below has to start and end with a slash "/" in order for
+      it to work correctly.
+
+      For more details:
+      * https://developer.mozilla.org/en-US/docs/Web/HTML/Element/base
+
+      This is a placeholder for base href that will be replaced by the value of
+      the `--base-href` argument provided to `flutter build`.
+    -->
+    <base href="$FLUTTER_BASE_HREF">
+
+    <meta charset="UTF-8">
+    <meta content="IE=Edge" http-equiv="X-UA-Compatible">
+    <meta name="description" content="Aplikasi Telnow Web">
+
+    <!-- iOS meta tags & icons -->
+    <meta name="mobile-web-app-capable" content="yes">
+    <meta name="apple-mobile-web-app-status-bar-style" content="black">
+    <meta name="apple-mobile-web-app-title" content="TelNow Application">
+    <link rel="apple-touch-icon" href="icons/Icon-192.png">
+    <!-- Favicon -->
+    <link rel="icon" type="image/png" href="icons/launcher_icon.png" />
+
+    <title>TelNow Application</title>
+    <link rel="manifest" href="manifest.json">
+    <style>
+        .loader {
+          border: 5px solid #f3f3f3;
+          border-radius: 50%;
+          border-top: 5px solid #01beb5;
+          width: 40px;
+          height: 40px;
+          -webkit-animation: spin 2s linear infinite;
+          /* Safari */
+          animation: spin 2s linear infinite;
+        }
+
+        .center {
+          position: absolute;
+          top: 50%;
+          left: 50%;
+          margin-top: -60px;
+          margin-left: -120px;
+          width: 240px;
+          height: 40px;
+          text-align: center;
+          font-family: Arial, Helvetica, sans-serif;
+        }
+
+        .center-align {
+          position: absolute;
+          top: 50%;
+          left: 50%;
+          margin-top: 0px;
+          margin-left: -60px;
+          width: 40px;
+          height: 40px;
+        }
+
+        .center-count {
+          position: absolute;
+          top: 50%;
+          left: 50%;
+          margin-top: -30px;
+          margin-left: -120px;
+          width: 240px;
+          height: 40px;
+          font-size: 20px;
+          font-weight: bold;
+          text-align: center;
+          font-family: Arial, Helvetica, sans-serif;
+        }
+
+        /* Safari */
+        @-webkit-keyframes spin {
+          0% {
+            -webkit-transform: rotate(0deg);
+          }
+
+          100% {
+            -webkit-transform: rotate(360deg);
+          }
+        }
+
+        @keyframes spin {
+          0% {
+            transform: rotate(0deg);
+          }
+
+          100% {
+            transform: rotate(360deg);
+          }
+        }
+
+        .countdown {
+          display: inline-block;
+          text-indent: -100vw;
+          position: relative;
+          width: 1ch;
+        }
+
+        .countdown::after {
+          content: '10';
+          position: absolute;
+          top: 0;
+          left: 0;
+          text-indent: 0;
+          animation: 18s countdown 1s forwards;
+        }
+
+        @keyframes countdown {
+          0% {
+            content: '10';
+          }
+
+          10% {
+            content: '9';
+          }
+
+          20% {
+            content: '8';
+          }
+
+          30% {
+            content: '7';
+          }
+
+          35% {
+            content: '7';
+          }
+
+          40% {
+            content: '6';
+          }
+
+          45% {
+            content: '6';
+          }
+
+          50% {
+            content: '5';
+          }
+
+          55% {
+            content: '5';
+          }
+
+          60% {
+            content: '4';
+          }
+
+          65% {
+            content: '4';
+          }
+
+          70% {
+            content: '3';
+          }
+
+          75% {
+            content: '2';
+          }
+
+          80% {
+            content: '2';
+          }
+
+          85% {
+            content: '2';
+          }
+
+          90% {
+            content: '1';
+          }
+
+          95% {
+            content: '1';
+          }
+
+          100% {
+            content: '0';
+          }
+        }
+    </style>
 </head>
 
-<body>
-  <script src="flutter_bootstrap.js" async></script>
 
-  <div class="center">
+<body>
+<div class="center">
     <h4 id="label">Loading TelNow page</h4>
-  </div>
-  <div class="center-align">
+</div>
+<div class="center-align">
+    <!--<div class="loader"></div>-->
     <img src="cat.gif" width="120" height="120">
-  </div>
-  <div class="center-count">
+</div>
+<div class="center-count">
     <span class="countdown"></span>
-  </div>
-  <script src="flutter_bootstrap.js" async></script>
-</body>
+</div>
+
+<script src="https://www.gstatic.com/firebasejs/8.10.1/firebase-app.js"></script>
+<!--  <script src="https://www.gstatic.com/firebasejs/8.10.1/firebase-auth.js"></script>-->
+<script src="https://www.gstatic.com/firebasejs/8.10.1/firebase-messaging.js"></script>
+<!--  <script src="https://www.gstatic.com/firebasejs/8.10.1/firebase-firestore.js"></script>-->
+<script>
+    {{flutter_js}}
+    {{flutter_build_config}}
+
+    var jsPathVersion = "2517.01";
+    _flutter.buildConfig.builds[0].mainJsPath += "?v=" + jsPathVersion;
+    var serviceWorkerVersion = jsPathVersion;
+
+    _flutter.loader.load({
+      onEntrypointLoaded: async function (engineInitializer) {
+        let appRunner = await engineInitializer.initializeEngine({
+          // JsFlutterConfiguration goes here...
+          // canvasKitBaseUrl: "/canvaskit/",
+        });
+        appRunner.runApp();
+      },
+    });
+
+    if ('serviceWorker' in navigator) {
+      // Service workers are supported. Use them.
+      window.addEventListener('load', function () {
+        // Wait for registration to finish before dropping the <script> tag.
+        // Otherwise, the browser will load the script multiple times,
+        // potentially different versions.
+        var serviceWorkerUrl = 'flutter_service_worker.js?v=' + serviceWorkerVersion;
+        navigator.serviceWorker.register(serviceWorkerUrl)
+          .then((reg) => {
+            function waitForActivation(serviceWorker) {
+              serviceWorker.addEventListener('statechange', () => {
+                if (serviceWorker.state == 'activated') {
+                  console.log('Installed new service worker.');
+                  // loadMainDartJs();
+                }
+              });
+            }
+            if (!reg.active && (reg.installing || reg.waiting)) {
+              // No active web worker and we have installed or are installing
+              // one for the first time. Simply wait for it to activate.
+              waitForActivation(reg.installing || reg.waiting);
+            } else if (!reg.active.scriptURL.endsWith(serviceWorkerVersion)) {
+              // When the app updates the serviceWorkerVersion changes, so we
+              // need to ask the service worker to update.
+              console.log('New service worker available.');
+              reg.update();
+              waitForActivation(reg.installing);
+            } else {
+              // Existing service worker is still good.
+              console.log('Loading app from service worker.');
+              // loadMainDartJs();
+            }
+          });
+
+        navigator.serviceWorker.register("firebase-messaging-sw.js").then((reg) => {
+          if (!reg.active && (reg.installing || reg.waiting)) {
+            // No active web worker and we have installed or are installing
+            // one for the first time. Simply wait for it to activate.
+            console.log('Firebase messaging has been installing.');
+          } else if (!reg.active.scriptURL.endsWith(serviceWorkerVersion)) {
+            // When the app updates the serviceWorkerVersion changes, so we
+            // need to ask the service worker to update.
+            console.log('Firebase messaging is available.');
+          } else {
+            // Existing service worker is still good.
+            console.log('Firebase messaging has been running.');
+          }
+        });
+      });
+    }
 
-</html>
+</script>
+</body>
+</html>

BIN
web/telnow.zip