From c1eb8de4dc7580e5377c2d2f76d7d979041fc08f Mon Sep 17 00:00:00 2001 From: jack ning Date: Mon, 27 Jun 2022 10:53:47 +0800 Subject: [PATCH] update --- .DS_Store | Bin 6148 -> 6148 bytes .../assets/images/chat/extra_camera.webp | Bin 0 -> 706 bytes .../assets/images/chat/extra_card.webp | Bin 0 -> 622 bytes .../assets/images/chat/extra_favorite.webp | Bin 0 -> 894 bytes .../assets/images/chat/extra_file.webp | Bin 0 -> 300 bytes .../assets/images/chat/extra_localtion.webp | Bin 0 -> 832 bytes .../assets/images/chat/extra_media.webp | Bin 0 -> 844 bytes .../assets/images/chat/extra_photo.webp | Bin 0 -> 512 bytes .../assets/images/chat/extra_red.webp | Bin 0 -> 602 bytes .../assets/images/chat/extra_videocall.webp | Bin 0 -> 370 bytes .../assets/images/chat/extra_voice.webp | Bin 0 -> 1038 bytes .../assets/images/chat/extra_wallet.png | Bin 0 -> 4561 bytes .../assets/images/chat/input_emoji.png | Bin 0 -> 1531 bytes .../assets/images/chat/input_extra.png | Bin 0 -> 1075 bytes .../assets/images/chat/input_keyboard.png | Bin 0 -> 1150 bytes .../assets/images/chat/input_voice.png | Bin 0 -> 1957 bytes .../assets/images/chat/voice_playing_1.png | Bin 0 -> 271 bytes .../assets/images/chat/voice_playing_2.png | Bin 0 -> 487 bytes .../assets/images/chat/voice_playing_3.png | Bin 0 -> 827 bytes .../assets/images/chat/voice_volume_1.png | Bin 0 -> 2100 bytes .../assets/images/chat/voice_volume_2.png | Bin 0 -> 2177 bytes .../assets/images/chat/voice_volume_3.png | Bin 0 -> 2236 bytes .../assets/images/chat/voice_volume_4.png | Bin 0 -> 2292 bytes .../assets/images/chat/voice_volume_5.png | Bin 0 -> 2346 bytes .../assets/images/chat/voice_volume_6.png | Bin 0 -> 2403 bytes .../assets/images/chat/voice_volume_7.png | Bin 0 -> 2442 bytes .../lib/page/history_thread_page.dart | 4 +- bytedesk_demo/pubspec.yaml | 3 +- bytedesk_kefu/CHANGELOG.md | 8 + bytedesk_kefu/README.md | 9 + .../assets/images/chat/extra_camera.webp | Bin 0 -> 706 bytes .../assets/images/chat/extra_card.webp | Bin 0 -> 622 bytes .../assets/images/chat/extra_favorite.webp | Bin 0 -> 894 bytes .../assets/images/chat/extra_file.webp | Bin 0 -> 300 bytes .../assets/images/chat/extra_localtion.webp | Bin 0 -> 832 bytes .../assets/images/chat/extra_media.webp | Bin 0 -> 844 bytes .../assets/images/chat/extra_photo.webp | Bin 0 -> 512 bytes .../assets/images/chat/extra_red.webp | Bin 0 -> 602 bytes .../assets/images/chat/extra_videocall.webp | Bin 0 -> 370 bytes .../assets/images/chat/extra_voice.webp | Bin 0 -> 1038 bytes .../assets/images/chat/extra_wallet.png | Bin 0 -> 4561 bytes .../assets/images/chat/input_emoji.png | Bin 0 -> 1531 bytes .../assets/images/chat/input_extra.png | Bin 0 -> 1075 bytes .../assets/images/chat/input_keyboard.png | Bin 0 -> 1150 bytes .../assets/images/chat/input_voice.png | Bin 0 -> 1957 bytes .../assets/images/chat/voice_playing_1.png | Bin 0 -> 271 bytes .../assets/images/chat/voice_playing_2.png | Bin 0 -> 487 bytes .../assets/images/chat/voice_playing_3.png | Bin 0 -> 827 bytes .../assets/images/chat/voice_volume_1.png | Bin 0 -> 2100 bytes .../assets/images/chat/voice_volume_2.png | Bin 0 -> 2177 bytes .../assets/images/chat/voice_volume_3.png | Bin 0 -> 2236 bytes .../assets/images/chat/voice_volume_4.png | Bin 0 -> 2292 bytes .../assets/images/chat/voice_volume_5.png | Bin 0 -> 2346 bytes .../assets/images/chat/voice_volume_6.png | Bin 0 -> 2403 bytes .../assets/images/chat/voice_volume_7.png | Bin 0 -> 2442 bytes .../assets/images/chat/extra_camera.webp | Bin 0 -> 706 bytes .../assets/images/chat/extra_card.webp | Bin 0 -> 622 bytes .../assets/images/chat/extra_favorite.webp | Bin 0 -> 894 bytes .../assets/images/chat/extra_file.webp | Bin 0 -> 300 bytes .../assets/images/chat/extra_localtion.webp | Bin 0 -> 832 bytes .../assets/images/chat/extra_media.webp | Bin 0 -> 844 bytes .../assets/images/chat/extra_photo.webp | Bin 0 -> 512 bytes .../example/assets/images/chat/extra_red.webp | Bin 0 -> 602 bytes .../assets/images/chat/extra_videocall.webp | Bin 0 -> 370 bytes .../assets/images/chat/extra_voice.webp | Bin 0 -> 1038 bytes .../assets/images/chat/extra_wallet.png | Bin 0 -> 4561 bytes .../assets/images/chat/input_emoji.png | Bin 0 -> 1531 bytes .../assets/images/chat/input_extra.png | Bin 0 -> 1075 bytes .../assets/images/chat/input_keyboard.png | Bin 0 -> 1150 bytes .../assets/images/chat/input_voice.png | Bin 0 -> 1957 bytes .../assets/images/chat/voice_playing_1.png | Bin 0 -> 271 bytes .../assets/images/chat/voice_playing_2.png | Bin 0 -> 487 bytes .../assets/images/chat/voice_playing_3.png | Bin 0 -> 827 bytes .../assets/images/chat/voice_volume_1.png | Bin 0 -> 2100 bytes .../assets/images/chat/voice_volume_2.png | Bin 0 -> 2177 bytes .../assets/images/chat/voice_volume_3.png | Bin 0 -> 2236 bytes .../assets/images/chat/voice_volume_4.png | Bin 0 -> 2292 bytes .../assets/images/chat/voice_volume_5.png | Bin 0 -> 2346 bytes .../assets/images/chat/voice_volume_6.png | Bin 0 -> 2403 bytes .../assets/images/chat/voice_volume_7.png | Bin 0 -> 2442 bytes bytedesk_kefu/example/ios/Podfile.lock | 6 + bytedesk_kefu/example/lib/main.dart | 1 + bytedesk_kefu/example/pubspec.yaml | 1 + .../lib/blocs/message_bloc/message_bloc.dart | 2 +- .../lib/blocs/message_bloc/message_state.dart | 7 + bytedesk_kefu/lib/http/bytedesk_user_api.dart | 1 + bytedesk_kefu/lib/mqtt/bytedesk_mqtt.dart | 4 +- .../lib/ui/chat/page/chat_kf_page.dart | 372 +- bytedesk_kefu/lib/ui/widget/chat_input.dart | 531 +++ .../lib/ui/widget/emoji_picker_view.dart | 198 + bytedesk_kefu/lib/ui/widget/extra_item.dart | 297 ++ bytedesk_kefu/lib/ui/widget/image_button.dart | 40 + .../widget/send_button_visibility_mode.dart | 12 + .../widget/voice_record/custom_overlay.dart | 41 + .../widget/voice_record/voice_animation.dart | 123 + .../ui/widget/voice_record/voice_widget.dart | 537 +++ .../lib/util/bytedesk_constants.dart | 4 +- .../emoji_picker_flutter.dart | 9 + .../src/category_emoji.dart | 13 + .../src/category_icon.dart | 20 + .../src/category_icons.dart | 49 + .../emoji_picker_flutter/src/config.dart | 192 + .../src/default_emoji_picker_view.dart | 375 ++ .../emoji_picker_flutter/src/emoji.dart | 52 + .../emoji_picker_flutter/src/emoji_lists.dart | 3409 +++++++++++++++++ .../src/emoji_picker.dart | 253 ++ .../src/emoji_picker_builder.dart | 20 + .../src/emoji_picker_internal_utils.dart | 198 + .../src/emoji_picker_utils.dart | 59 + .../src/emoji_skin_tones.dart | 22 + .../src/emoji_view_state.dart | 21 + .../src/recent_emoji.dart | 30 + .../src/triangle_shape.dart | 31 + bytedesk_kefu/pubspec.yaml | 16 +- 114 files changed, 6877 insertions(+), 93 deletions(-) create mode 100644 bytedesk_demo/assets/images/chat/extra_camera.webp create mode 100644 bytedesk_demo/assets/images/chat/extra_card.webp create mode 100644 bytedesk_demo/assets/images/chat/extra_favorite.webp create mode 100644 bytedesk_demo/assets/images/chat/extra_file.webp create mode 100644 bytedesk_demo/assets/images/chat/extra_localtion.webp create mode 100644 bytedesk_demo/assets/images/chat/extra_media.webp create mode 100644 bytedesk_demo/assets/images/chat/extra_photo.webp create mode 100644 bytedesk_demo/assets/images/chat/extra_red.webp create mode 100644 bytedesk_demo/assets/images/chat/extra_videocall.webp create mode 100644 bytedesk_demo/assets/images/chat/extra_voice.webp create mode 100755 bytedesk_demo/assets/images/chat/extra_wallet.png create mode 100755 bytedesk_demo/assets/images/chat/input_emoji.png create mode 100755 bytedesk_demo/assets/images/chat/input_extra.png create mode 100755 bytedesk_demo/assets/images/chat/input_keyboard.png create mode 100755 bytedesk_demo/assets/images/chat/input_voice.png create mode 100755 bytedesk_demo/assets/images/chat/voice_playing_1.png create mode 100755 bytedesk_demo/assets/images/chat/voice_playing_2.png create mode 100755 bytedesk_demo/assets/images/chat/voice_playing_3.png create mode 100644 bytedesk_demo/assets/images/chat/voice_volume_1.png create mode 100644 bytedesk_demo/assets/images/chat/voice_volume_2.png create mode 100644 bytedesk_demo/assets/images/chat/voice_volume_3.png create mode 100644 bytedesk_demo/assets/images/chat/voice_volume_4.png create mode 100644 bytedesk_demo/assets/images/chat/voice_volume_5.png create mode 100644 bytedesk_demo/assets/images/chat/voice_volume_6.png create mode 100644 bytedesk_demo/assets/images/chat/voice_volume_7.png create mode 100644 bytedesk_kefu/assets/images/chat/extra_camera.webp create mode 100644 bytedesk_kefu/assets/images/chat/extra_card.webp create mode 100644 bytedesk_kefu/assets/images/chat/extra_favorite.webp create mode 100644 bytedesk_kefu/assets/images/chat/extra_file.webp create mode 100644 bytedesk_kefu/assets/images/chat/extra_localtion.webp create mode 100644 bytedesk_kefu/assets/images/chat/extra_media.webp create mode 100644 bytedesk_kefu/assets/images/chat/extra_photo.webp create mode 100644 bytedesk_kefu/assets/images/chat/extra_red.webp create mode 100644 bytedesk_kefu/assets/images/chat/extra_videocall.webp create mode 100644 bytedesk_kefu/assets/images/chat/extra_voice.webp create mode 100755 bytedesk_kefu/assets/images/chat/extra_wallet.png create mode 100755 bytedesk_kefu/assets/images/chat/input_emoji.png create mode 100755 bytedesk_kefu/assets/images/chat/input_extra.png create mode 100755 bytedesk_kefu/assets/images/chat/input_keyboard.png create mode 100755 bytedesk_kefu/assets/images/chat/input_voice.png create mode 100755 bytedesk_kefu/assets/images/chat/voice_playing_1.png create mode 100755 bytedesk_kefu/assets/images/chat/voice_playing_2.png create mode 100755 bytedesk_kefu/assets/images/chat/voice_playing_3.png create mode 100644 bytedesk_kefu/assets/images/chat/voice_volume_1.png create mode 100644 bytedesk_kefu/assets/images/chat/voice_volume_2.png create mode 100644 bytedesk_kefu/assets/images/chat/voice_volume_3.png create mode 100644 bytedesk_kefu/assets/images/chat/voice_volume_4.png create mode 100644 bytedesk_kefu/assets/images/chat/voice_volume_5.png create mode 100644 bytedesk_kefu/assets/images/chat/voice_volume_6.png create mode 100644 bytedesk_kefu/assets/images/chat/voice_volume_7.png create mode 100644 bytedesk_kefu/example/assets/images/chat/extra_camera.webp create mode 100644 bytedesk_kefu/example/assets/images/chat/extra_card.webp create mode 100644 bytedesk_kefu/example/assets/images/chat/extra_favorite.webp create mode 100644 bytedesk_kefu/example/assets/images/chat/extra_file.webp create mode 100644 bytedesk_kefu/example/assets/images/chat/extra_localtion.webp create mode 100644 bytedesk_kefu/example/assets/images/chat/extra_media.webp create mode 100644 bytedesk_kefu/example/assets/images/chat/extra_photo.webp create mode 100644 bytedesk_kefu/example/assets/images/chat/extra_red.webp create mode 100644 bytedesk_kefu/example/assets/images/chat/extra_videocall.webp create mode 100644 bytedesk_kefu/example/assets/images/chat/extra_voice.webp create mode 100755 bytedesk_kefu/example/assets/images/chat/extra_wallet.png create mode 100755 bytedesk_kefu/example/assets/images/chat/input_emoji.png create mode 100755 bytedesk_kefu/example/assets/images/chat/input_extra.png create mode 100755 bytedesk_kefu/example/assets/images/chat/input_keyboard.png create mode 100755 bytedesk_kefu/example/assets/images/chat/input_voice.png create mode 100755 bytedesk_kefu/example/assets/images/chat/voice_playing_1.png create mode 100755 bytedesk_kefu/example/assets/images/chat/voice_playing_2.png create mode 100755 bytedesk_kefu/example/assets/images/chat/voice_playing_3.png create mode 100644 bytedesk_kefu/example/assets/images/chat/voice_volume_1.png create mode 100644 bytedesk_kefu/example/assets/images/chat/voice_volume_2.png create mode 100644 bytedesk_kefu/example/assets/images/chat/voice_volume_3.png create mode 100644 bytedesk_kefu/example/assets/images/chat/voice_volume_4.png create mode 100644 bytedesk_kefu/example/assets/images/chat/voice_volume_5.png create mode 100644 bytedesk_kefu/example/assets/images/chat/voice_volume_6.png create mode 100644 bytedesk_kefu/example/assets/images/chat/voice_volume_7.png create mode 100644 bytedesk_kefu/lib/ui/widget/chat_input.dart create mode 100644 bytedesk_kefu/lib/ui/widget/emoji_picker_view.dart create mode 100644 bytedesk_kefu/lib/ui/widget/extra_item.dart create mode 100644 bytedesk_kefu/lib/ui/widget/image_button.dart create mode 100644 bytedesk_kefu/lib/ui/widget/send_button_visibility_mode.dart create mode 100644 bytedesk_kefu/lib/ui/widget/voice_record/custom_overlay.dart create mode 100644 bytedesk_kefu/lib/ui/widget/voice_record/voice_animation.dart create mode 100644 bytedesk_kefu/lib/ui/widget/voice_record/voice_widget.dart create mode 100644 bytedesk_kefu/lib/vendors/emoji_picker_flutter/emoji_picker_flutter.dart create mode 100644 bytedesk_kefu/lib/vendors/emoji_picker_flutter/src/category_emoji.dart create mode 100644 bytedesk_kefu/lib/vendors/emoji_picker_flutter/src/category_icon.dart create mode 100644 bytedesk_kefu/lib/vendors/emoji_picker_flutter/src/category_icons.dart create mode 100644 bytedesk_kefu/lib/vendors/emoji_picker_flutter/src/config.dart create mode 100644 bytedesk_kefu/lib/vendors/emoji_picker_flutter/src/default_emoji_picker_view.dart create mode 100644 bytedesk_kefu/lib/vendors/emoji_picker_flutter/src/emoji.dart create mode 100644 bytedesk_kefu/lib/vendors/emoji_picker_flutter/src/emoji_lists.dart create mode 100644 bytedesk_kefu/lib/vendors/emoji_picker_flutter/src/emoji_picker.dart create mode 100644 bytedesk_kefu/lib/vendors/emoji_picker_flutter/src/emoji_picker_builder.dart create mode 100644 bytedesk_kefu/lib/vendors/emoji_picker_flutter/src/emoji_picker_internal_utils.dart create mode 100644 bytedesk_kefu/lib/vendors/emoji_picker_flutter/src/emoji_picker_utils.dart create mode 100644 bytedesk_kefu/lib/vendors/emoji_picker_flutter/src/emoji_skin_tones.dart create mode 100644 bytedesk_kefu/lib/vendors/emoji_picker_flutter/src/emoji_view_state.dart create mode 100644 bytedesk_kefu/lib/vendors/emoji_picker_flutter/src/recent_emoji.dart create mode 100644 bytedesk_kefu/lib/vendors/emoji_picker_flutter/src/triangle_shape.dart diff --git a/.DS_Store b/.DS_Store index c029c5b3dbd1ea3320e651ae03f5fbb6aedd8b07..884dd73b66bd5ee7157a28dfd33ae980d64ae6be 100644 GIT binary patch delta 142 zcmZoMXffDe&ctk8Fk$jMCb`KGOgwDq?~Q9#noORSX^ zb|6Q5@_c4?q1=2Im!zEhBnAcs4z`JQ@nG$eP^lE`Qo<~JPcMMX0~@jV3bTO-0J3^4 Ar~m)} delta 142 zcmZoMXffDe&cuAf^XcSyOmdSWn0VMyPW@+?CNg;rlfvXKWUemrw+{?pRj-)LSQtvZ za)2E1$@7`ng>v&sQ8S~UI^8)dUcLK|)A0ruAd$8>-643w}1|*@h0}W1^ zDUFLLao#*mROg4=?J%z<+)WDn zH${p28}XszuHp>it}=CSNs_gzR`L42&9&&N!~P#h;`-Ox>rrHUV~ zz{~ZJJAn$4oYEgl}zRuOpc6{N(tuh?5KY_28$diE0wBX zsw$-_V38wPrP3TsbEQ-kEOI2NR62m^pp;62MUGfXCHAqun^MXGiyX1P8tKLci~LW1 zjvuLfHIn>?zJYmD{&kBN>&85Ievs>S-}X(CME-rNU&jqZiTuZHd^&C>OZ+)*DNKAh zZY)jU!*P3g0?*HzNEF~aDrb|HmCRN~9-Oz5Dc<@mn+F0Vl;>?X2$U^_Pz!<5rVuig z2#}RYVPtl8PXxkq*EK2nU-ZA|f4^`509H^qAZ!2t0PqR`odGJq0Kfn~kw~FUC8Q%F zAV>hP5(#N;;dZebKoi>ZRl*E{8mdip+Aa`e5YwIGYkyLmMm=B8NM#pvkb}ID8Iok% zwr$(C$8C4~{|`*WtBSbe$$0T1`ab~)(f_0WM_5g+p4mQs5Z<1tNk%MVykImF2nC{?7m61_s6mkmLTfaF z&?lNt042TA_#}J^6mbDQ6;%B9i*hQDPY#vf!}m1z4Dbn!Ahb%eickxUAXM;9!37BM zA;keh+(qFr%D6G-1tXp`H^CqmKAQ4^5)YdahBhP5(#N;;dZebKoi>ZRl*E{8mdip+Aa`e5YreLK&*3@Sk zW1h-NyspPK=8A-?xNl>x70CvSy^j~vB!9vB@AxPR3<+zm#zkvE#_lI^&~7AtbCnBO z)1RZpngw@Nb5c$ISju$(CEEv4I)EB??h;QXmX+5-E}-#dbZ$1nBfqwiWFog)TtNTJ zXfTY&mfIppCx?4wF61nH3-6mU++}-*mH;AO=dv9@`MINT80E&Tb6;)x#1nbm>jGNu zgn?G$k)K;P0Yt7hxPXzjK`D&K1{SX*kiVNnF66EJ3<8>G+(nyg$szJ-BIN+8FP-=S zR~vU~Ld7Q)L>_gzfR2aWpvEIVvcBmcCzoqnNR5ARfm6m^bNNIFBl2r4-vJbE{P|Be z81DSFUp7cmh`bwh0rl6ca6LSZzO%s+L*!nY3+PdJlzTR4a>()dN|#raV@oEH?~A#9 za#hP5(#N;;dZebKoi>ZRl*E{ z8mdip+Aa`e5Y!_TBBe#tlsdG;L|A%L0s|wqvis=6Y82!KgU;lq^ZJl^} z6)U2OH@lP2drB31vVF_^SWjw&scBU>jOvs#Yy|N4LZ|KAu^P&goL0002+3ILq}D!>51 z06vjOp-m;EBO)M30I(7XX>Q?mu^T`W+VoYz41yY}O?KKY5M&V5Qfs!+aDyO*s*_!| yi-Z{jHB_4Iv|J#_A*!U;ZKB}@K@C+VyKNT;G6-s^HP8S6{{OrYpTGbB0001ifqF&& literal 0 HcmV?d00001 diff --git a/bytedesk_demo/assets/images/chat/extra_localtion.webp b/bytedesk_demo/assets/images/chat/extra_localtion.webp new file mode 100644 index 0000000000000000000000000000000000000000..fcd628d13f9904bf48c053627d30090653d893dd GIT binary patch literal 832 zcmV-G1Hb%INk&FE0{{S5MM6+kP&il$0000G0002L006%L06|PpNTmV*00D2~rjaB` z+Q@mOv(7yJP;Ws*L_RT+Cc?`98nR(9ejGGLwy$)Ue8}+{5?MAg*1(XrLAGt%YTFz+ z%p5}))rOgwnOO?A4c`C%fP%NK#@LU|JrVt%0Qvg=*Z==pJimK%{@~{RMcvK8{AhPR zS!IK}FD6Uf9W3Lib@k0a-&U2Qjy)6MUOPV(OD1BiAjf|+k*iP5(1)>5hj_`IHFtld{ia`%nJ`?-wF8>{CD88Vg+(cypCral=JkH&hf;9|+dP^oA)v215zFCKeD zw3(Q;@vw>tru<}qI)5ratlH%F>hro3>xTiq5QKhL);(>yO0S_vh*e) zyCmelAeKGJ+Ec_!J_p?f7#0dpLxAh%U=-0K&>n-UH}Cs1-x+ZGfqD_2mnb`5)g232JlfnKsnd|OraF8$-z7zNMJMo z%yZBOa6OnF1MK6V2@w_|G(L>;nwV17c$eIwrPe9w_w|%&{^?h*K;KwuHUW!A=Epz=_J93Yk0Hg ztx-4~vu9jL%{jE?D2El7{9(tBl<@k^j_<V@6Tw`ktG zgx}t1`i0@?C}xD>#4>Ir!I8_`M;+t~%f%O%%M#(a@*1I4!gNiToz@7~$=hr+C2Xhk z-|kYvcQW{wASsMjU;Rz1h4b?BKb@Bg>qT3?Z59ddIfFaf0}1*1|JVQjZyx|wP&goL z0002+3ILq}D!>5106vjOp-m;EBO)M30I(7XX>Q?mu^T`W+VoYz41yY}O?KKY5M&V5 zQfs!+aDyO*s*_!|i-Z{jHB_4Iv|J#_A*!U;ZKB}@K@C+VyKNT;G6-s^HP8S6{{OrY KpTGbB00009^nh3Z literal 0 HcmV?d00001 diff --git a/bytedesk_demo/assets/images/chat/extra_media.webp b/bytedesk_demo/assets/images/chat/extra_media.webp new file mode 100644 index 0000000000000000000000000000000000000000..2806229e652d5065492b79a8a53eceb03b6dd51c GIT binary patch literal 844 zcmV-S1GD^6Nk&FQ0{{S5MM6+kP&il$0000G0002L006%L06|PpNU{O|009r=u#qIG zp?dkrKVq&Z9SD@loV3(9Y2t5+qUUg zt8Cl0ZQHhOo2mKjH(6)rq@DSA6Vd+(5dZ)E|M&Z~@2!iO^3gHF&7J~=flX$fU_9Gh z&=i1e$4l)!xoI#3^961~7ePSM&=-K;GM|WA1p&>kaC;f!0|}dOzSsqL9 z3)(WOPRfyF_(s$kVsL{{*nCF})pRSSdNTuqpGOQY)01ZHgY<9JU- z{_^Zp{o!=qMlf$DDSdXhTQje=lKeYo+cI-xFUh%bw0<+gR{qT8yOE3&C#y75Y-QYK zY9qv*r0hFb^pA-(v47$==A25_Ow{^-#MQ?+R*Qj?Kwoh3@F#0+LiV7g>rl3GEOiUr zOBUXv`O0xhPVrkvnbat797UOoC@{rY_>Katlu71;;;V!*sc?D1eHLZXqrg!TWdZXF z?8Y%B5nBqZM=~bK=L)RHF($ds3hX8^CgooW9Az;kb-xPSmoO$R8ho`fCfyofDvB`a zR^hnNK=JU=>wF$F()z2x??9w3uyzgq{!OP5BXwUI;G!|mQ)t^MJim1Z@gn6fDqz1U z?4RiFYv}iic#+(59q>5Y**^0Q%dWvlS|oYPg88#DS|noL9>7=(D-zJ-vIj29SP`js zum^q{Xpzhvd*Fr_2_Lb?2{9rycXsHdMp9P1_m#?x1a!W6Ye?u#U-r&x5qjm-5106vjOp-m;EBO)M30I(7XX>Q?mu^T`W+VoYz z41yY}O?KKY5M&V5Qfs!+aDyO*s*_!|i-Z{jHB_4Iv|J#_A*!U;ZKB}@K@C+VyKNT; WG6-s^HP8S6{{OrYpTGbB0000oz>mZL literal 0 HcmV?d00001 diff --git a/bytedesk_demo/assets/images/chat/extra_photo.webp b/bytedesk_demo/assets/images/chat/extra_photo.webp new file mode 100644 index 0000000000000000000000000000000000000000..27607e2a0c3984ca8639a93294357bfca41bfa0e GIT binary patch literal 512 zcmV+b0{{I|Nk&Ha0RRA3MM6+kP&il$0000G0002L006%L06|PpNM->500DpFplu{c zI;ZE+9Dl~A;}8+~@n51?xYbzA+OW5{t*PlvU%%P=)Ohu3QyME~hoTS$fOgxq>E_0@ zST$m^X0ZSN=jbKl-Bb5P^nU`F^ndC9(*M71)z&;jGjCV($PQ6;@TedzMIJ$9 zzwVd_{yW3G=iD46oW@fD@VG;^o(t4>%V;(bL}R~_Vk0p$d)7rST^h|_^-;@^MXS+a zT3K$;dP+~JsRsy|Gtp`O1;XA>3N!8x#gArbv0Q@C&vOE3yj9d5cN1cx4B<b3JfMn!Qc@W>%9HXf<%dGW|wRXmdZFa2No|EDolP&goL0002+3ILq} zD!>5106vjOp-m;EBO)M30I(7XX>Q?mu^T`W+VoYz41yY}O?KKY5M&V5Qfs!+aDyO* zs*_!|i-Z{jHB_4Iv|J#_A*!U;ZKB}@K@C+VyKNT;G6-s^HP8S6{{OrYpTGbB0000) CzU-m^ literal 0 HcmV?d00001 diff --git a/bytedesk_demo/assets/images/chat/extra_red.webp b/bytedesk_demo/assets/images/chat/extra_red.webp new file mode 100644 index 0000000000000000000000000000000000000000..5a0b66b480e3f21ba6991711c7de58356f9713e9 GIT binary patch literal 602 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 literal 0 HcmV?d00001 diff --git a/bytedesk_demo/assets/images/chat/input_emoji.png b/bytedesk_demo/assets/images/chat/input_emoji.png new file mode 100755 index 0000000000000000000000000000000000000000..db61921acbf55f6870dff35889dc6d8bf31c1b7f GIT binary patch literal 1531 zcmXX`3pkT|7$0&SMN=*jndBasQtA|`HkVo2L8*g@5@nC(cGR)EUEDSshU0#Q<ZpCIs# zRX$p3z^^^Ipap@bgk$`%hlo@riN++-SR^WoL}3%DEFx{$Q<)&9QnQGexkL&}X`lp* z!X)PAlIZ}v98uGlX=#bgUT#@E zA0?7b=PmEZB8o)doHHoQ5NR_PL*0Z*5MI7l%T@OIFIE<4dWS-Fw+ z){(`forEHuR$$b*pdrLwa%@nHDO^GpGDwXscCgJ91A`p`n6X){nX!eGYKLRt2#iGB zS;M0$$Fv$v9&rT5-nVUP)z0(W&f`7a`Og+@)q6ZX`T$XwHJh(7BJXaQ5eB59pufe( zxW9{vE4%4(pSBP$bel1#o_+cK;kSd^UE`q&d_fn1_%D2{DdAH|n4;x!N0g4HAfVM_ z+XM5mW;p$P$L?ge;^f=%pN5D$DXd!sH?+ZeT;?h8hYle#?v}M!0f@%N9fAU}Z`P*0 z7Z2*n$iF&9yR?X7*28IMf}~<6m%$g;cREP|_XNCpc>PwaI=kk}VD+Jf&=TD_#>K8h ze7&lLu3!G9x5Jyq6QugKp_#Hrue{n7T|7sv_NeG-A720bd`jayN|!^n-P7Qosl`|(>{4ZfOA2)+2H2g zCO!=IjKMhfYuhRkoWrwCD}CT$*=vG_AUrMchTw?7kKWqbW$tT|J9lBm_sO)?lT$3P z5#f=H($LORm+Khz9;AcT zB_Zwe7Qtj2#qwHzzu7Q#x^j~$VL!tfyEiE+?PuXS@r1;v;HlP)@^k4VbW#oxJg z1vv_=6_mek<5XSZcvu*py0u$$;;l4fUzhWTD`*l&^xmNf)%c7%kM9}0GT9$^DkRm^v*!c9QG{cA-3*gzVk14`rR}dD{ql5!;=h0pE;5r&z_^w{m_S&XOl=v0=Vj!qPXrHUz H5#;{?#&PBZ literal 0 HcmV?d00001 diff --git a/bytedesk_demo/assets/images/chat/input_extra.png b/bytedesk_demo/assets/images/chat/input_extra.png new file mode 100755 index 0000000000000000000000000000000000000000..eddec895675c645c2735331db54ee3b82310a300 GIT binary patch literal 1075 zcmeAS@N?(olHy`uVBq!ia0vp^nIO!;3?%tt{&E1RhXFnzt_>}njV;}ctz8W*-3@I$ z&7J)XEnSVRoj?+Vn!B3XdpkO20@*WX&IEFSQvd(|2kHVVfRgR))4+O#ljZgUPOjwqCHtPM|qZ?O+A1trLLU<}RQhNHNeo4Q)V;-9XR?QrX$m(FZaC zXeLMjNPA-|g5A&t7Hsba84I$mtF3KH!>Uz4FV;7APCl3-1!Q@Z1o;K;2+YsZ&@3t4 zdGDhk|7q5xj*R_3O!mDJoc_0+%QQ7ed3nLPRcaaUo^E({>bLlI)h$K7UJl%ub965` z9X$HF`u>}(0WTCL{QM=%#*p)P(MbjdrtO|Cjv*CsZ*PU?J$4Xa30VJR!JM3@bBfR1 zOnSHLf1}uk>xY(UOq%tU-?_ArT|gy$S@ygR?fr389{evAdzYO*wxROo1^#o zx&L?864w^0b6F*F+G(ldRyKyW3pd?amwMNrq;hYp)?qV-E323TFV);(kNTR{T({)n zn(Yn$D)gLYy=7b*Z1lpbC&-=o+ve(}PW%=!rJH){Y#8<)vJJAD>{`BQ!R$z%T_@5m zty}8N6{Y9DZPNKnkJvk&ZA>chC(7a`H6-nr@Wdd5-?GcyHKI(1RrQDciA1|U6Poyz zMlkp*7R{N!-#A10<)^-kmKYVGqrZH& z2ib6WZc~*^{Gn9hu4LE3YdwjnzT=(O3I3yPU)WcDE&syFFn`*W_O8>unsb|`>ol*Q zkh;fXX}+hV%FDXWNhj`1;EH@3+45ykD)+S@eoG<6Kt5aV%(lF^o+(p29XDjEv; zZrDEInT3vvXE*b+>q`y$RJ;}`9}8dV7=QVw=dNq=F_Txlm|)RqwIXhEmPY3kuNAQq zPi06dsV;8av@^tIu8UWQ`^A`53pB&B7A5I%-cXoYmI%`&#qGk8R(a za$EQFT!tThra9`S`!1{B?wnQWb-@1TgZa-s_^;5I+w{EdWOK-@+({?QWS`FdvB`Ji zO|{Q|a+&L!lkRf;JhH{gT0wm1}QXzjpg{bKmDbS3Qchu95v#>7Z|^rxySHp{eBJ#QindGIzli@UW|$;c$MG7NF&I2u{an^LB{Ts5 DbpZsD literal 0 HcmV?d00001 diff --git a/bytedesk_demo/assets/images/chat/input_keyboard.png b/bytedesk_demo/assets/images/chat/input_keyboard.png new file mode 100755 index 0000000000000000000000000000000000000000..5c53793e3ba8cdb2dd86a7cef854ca3bd6465f72 GIT binary patch literal 1150 zcmeAS@N?(olHy`uVBq!ia0vp^nIO!;3?%tt{&E1RCjmYot_>}n4Xs@bEnSTuvb(vn zzoD%M%7IkUMLWH?9x$ehlGHlR5`5@-d`WVlpUYwHA%Y)e;j7f`Uf5h&XRG^85{8bQiB zn>zY{T(CJHZeuH$4KfOBdwV~~G_VP6ZBszXR;>bx0DW2C+?n{wJrv0DE(!7r-jQ}W zKTlJmq;zNMrH_X9_)oJgWpw<}Z?f-|;Pm#trd-Oa-kn>nmJw9&bi=b#;=i}67WsNP zaA(fZbvpR^X!ZR!0WTCL{QM=ng>5UtMUPE885o%UdAc};RK&f#b-d`dfe34W`lRlv z_j|wVtu`-{`u_jFdbf^1zegAA!9$C83$b?_FIMDuV7o^k?&rr#!Bt-Vnfq$)%Q;P7 ztrM@m@bI@<%m4C0wl~BI#23o0F`laSSB!xt?NZu);n3Ldqj&3+&$2OGc;z4AnVrjJ zB7OJV-+<+}Gnj&N&pJiTWk|ho`RuW;_W~IGwATlf^2nUm+SpTN$MkrW_{xY?30r=s z@PFPZu`oz$*R(uVji*u@r$$9OK78hPNmb#$SI%=&eve(;o;}Xr1l>QL?C9=W+R>2F zXW^sArT8eotNo*pa-T4_!kjXdyG9;r9o;IEPTZ04c*(O#^O4p{9iz!Wd z3C^CRp)j*Zf9WIvm*`1y?h1}JDuNC%+m$^#j&&?QvS>n%hxrN1M~0S?+uMUSGT48V zvFAGK9>iIBNTG6yoX6F!Psg09LbXD(Gkc5@Cm9`^c8P1Tl=MooL#Mo~58S4Q+_h1}rFp{?4`#>YmA!fk7Yeh^TFgF6@7A$N4Sb2S_*k4X z`xq1FFg|m;BxHQ;5|6j?OJ<>L&qKQ>ZaLbvb<4qP9##qGqi#J;Q2BeTkE#4L+uvNf zg6XnOZkJe<6qf~0|7Rw@qr0PfVoB?%sGvgANhvSZsLC3}cmCgh{C_V;;_;|kiYZ3L zk>{7&^8MN8_GjOQKViJ@v=#qfo%=2#;%L?JPu#rB4$W&b*n*f39=GWB$*_^_Gj(qNH_4>8_0L=ic*t zy8LeL=e{SiYoGU)sq8F|{8`@jv;6SS@*h9%M?I()ZvOvPXx7h9kMBzx%b)(qzd!%W elzEqee=)CEt`j0q=M)RfU<{tFelF{r5}E)sE+9Ps literal 0 HcmV?d00001 diff --git a/bytedesk_demo/assets/images/chat/input_voice.png b/bytedesk_demo/assets/images/chat/input_voice.png new file mode 100755 index 0000000000000000000000000000000000000000..537bc29470b8ef80842ffaf3fe75c0acf8835751 GIT binary patch literal 1957 zcmYjR2{@GbADIl^m5E+keI!yfX|9hDq)b zGjd{S2Q@nfXK-Jjs0~fo6X^HBqNwq2&MrOfZ&Kw zCNnEDkBAiz}NxfCjkLT4s0BrZJn+o3@c0>J&Y1-u|crLq_d4j>O&KyN}4 zo1AnLkf+dCAWMTOG1+YRh9H0dU?oy8lE0u)A;1LC`@eN)%!EXs4?u=z0ZcLjmJ1v@ zJq_ScfmSq##bQw;oZ*RIjKma>CG&Wa5#Pj-kOUE!4_FGIeU%Q70Zd?Vl9E6Rb8~YO zxQU5e7zD%_DLfcTVuLPF45k2`K^c|FU}V5GK)^3*fLka8AHaX9(EA&*hk#Xjg@E(C zS6CJsLr|yBo<(XDB9tn)FUhEs_aDEj)Q z)8Eadtcn5>Uft{(Cup70@h)f7x5&=4*-|3{cC20Ti@I7xcz)za>q)-{$i_RA*LkhZ zHSf->lDbbnO0HV18PfLT)`rYjF1Z;lSz3rrAMN>F>dYlKi%Vy0M(}A&{|pUYu{l@S z*n%5;^y#Yr#i$F4gLmx(`;&e}P5)4cleW~t{*4?=cSH4DdCWq(c1*)NtbWZ$aV1o_tAM3*M|c)$y=oh z-otQy{ndek!#ZvUXsvu{6YHev2y9V{oYeK5B0PIhgYjR z($`E*MQCkg)b+}ttHMicKSc3=>Pm4@R&H=*YnfKKL#)sq6`NWrR z7x2qwVT`ec^sVYas_%WxPLv*u;e~3FG#ol`p$>T`dv9=VG|lRZNzDWu9m|lNyL?do z+@*s0>}A7-xMq>G{ybmd+`Wmi*Ku_;Y|G<|?SweUD5%=spdyfI&8{3pXPN0)g;aQz zOJTYn4(KN+Dy;1QrBn(ZvI9wdUnxPj_bE%&)_}{w;Y~}+ii5!A$Wo|0wW5R&d49h-TLx%k_K>em7{A!jFnEY>KH- zbNPu;!6iy$`y|f&*Oiz?UO|gd4D{;@MX;# zS?yloo31-n+Kp?*73x24uz3CC730m3GJP@s)(15YsepxuIdM1m&qZJzaF|Lvzl8q* DFEGmp literal 0 HcmV?d00001 diff --git a/bytedesk_demo/assets/images/chat/voice_playing_1.png b/bytedesk_demo/assets/images/chat/voice_playing_1.png new file mode 100755 index 0000000000000000000000000000000000000000..009de3889bc1517fb7f4e8939f654d59934b7a45 GIT binary patch literal 271 zcmV+q0r38bP)1QLxKP&or7twg0D(W@le=|nU-!YLfUDrw#UbW}(rL$TR>+bW*s z{pSF`d^4FD&r?b%rIb>C4P{Ibu^Pd2R9Auu;RfX?goCN&ZB#o znU~CSjtqQ5kp~xWG<1tObKAgUCS1bfFQ@#a&ndj+!j}}@FxddF6kaf4dzXhi);AmN z&|tAS>@y!lo&|R@Pv)Gr(KEw7QRTsX14rDe6esKnA@}j{F~J%m1fEh#DW#P9wGRn8 V`V{l6M|1!H002ovPDHLkV1o1qZ`%L> literal 0 HcmV?d00001 diff --git a/bytedesk_demo/assets/images/chat/voice_playing_2.png b/bytedesk_demo/assets/images/chat/voice_playing_2.png new file mode 100755 index 0000000000000000000000000000000000000000..399999064d8f3f258ac43887e8e1d255c74472c3 GIT binary patch literal 487 zcmV^3PEf{1SS3it7v7B z*!hBEDOjl>NqnwN81F5nyEE0C?e2m3*m>MJyMcg{larH^lbq0xDfHUMZo-F;A1vC% z_P|%qv`z3R0oTxH58RFlLEpo(hfd;`px13q_Zk6L5i@~k#VX1uBFU;j6bbs+1h}Av zPqTU_K9%!@s5CZC-s>4{%-bQ}iZpnT4DREAc^&vr&Vw}A!y5sAqno)$#OsnqPq261 zXWj^LQu5m?ONM_%Gu!d+%Gs3%2T>yEVdh<``h|%jl_1~@bM{pI(FC{!Rn00d{V#%< z7I~`xr>s6>8hob!CmQgR32d0Oio9m3Z%lycS)SEwu5)>8h)lGP0nKsRmc};Vj9nCs zng%i{4IXDLGpB8t;uqrvi*_;1B~g-J;S>R9wbx?C#8E2O2fyZdk_3SYj|g(RdI5)+ zM_3zua`sXt4SO$$*aI(T^yM@S-x+x=oJ)WhGH?JwjI(%8a~`$gg8 dZEKtI7aTj&XR zCeR1%O?Mu;2|S3Re*r1RW)x~q7hr(U?FjmfJq8C3hOt{yc8clVRnPR{ltc;O=-tit&>ca`yc)7o^q)oDbUo1f1NDsLX}_(@h#(#j_8qcpfuka?ueOD)5Y6jld$*b22ph*lTi_L< zV=fBV8Ym?2R=YR2zdGrs)~*xX&9o7xQDbcrrABIakDmzF+<{h^$;1Ev002ovPDHLk FV1k62fhhn0 literal 0 HcmV?d00001 diff --git a/bytedesk_demo/assets/images/chat/voice_volume_1.png b/bytedesk_demo/assets/images/chat/voice_volume_1.png new file mode 100644 index 0000000000000000000000000000000000000000..8ed01f5aa452e19af0396cfa636c1f36d0f128f1 GIT binary patch literal 2100 zcmZvdcQjn-8^>q#Hmi&1ge2-D5-kQXi@`8jiVzGDQZP1pC!z(*u3my*l?Y*#NKrQi zA?!+&iEau;?}Lfio6I?m-}&ux?!E8#`F=moy??y-oHxM|g@AB~aDYG{h>5X*HL$jR zzu6c;AaGx`pBB)7eXJ31P|bkYBA}x!&8`^&Chfn9L?SILERf0MrKP2nm6g@i)wQ*? z_4W0QjST{U0HpKt^FTx-5&=zb7k?Bm0u18ucmM;Q#{iw7&(c#MqNns0i0BU81o&eN z&_H^!0OsV>v8FRX3lyCJ#L2<`)W^*Cu@n5U{$c;md+5$FaPqq+;gcF*{(lN^#0?uX zfo+KNwMN;1zzmE`%q(mioLrEfxOsT__yq({i%UpK$()f@Qh{n{!*q1@;KrtAC<{xQ z>o;!N-m>TQ#C*T`I>gZw?K_Ex<~*WbNS#<8-l?R($; zcCCRrorPY)xOJwdPDwM%yTK~Zq?^B~%C{@XxIR|;9C*u1v1mFdgjcT2l@%2{_6m#^B4xDb z61X+4_|G15Q8kF-2(pvNCwl(PwWHl+V*$FkU9Z^WMAX=3z)*7S3o>&yW|qfQ82%B2 zzetE_{**rc!t?In@YZ~wlSn@;WFE7qsTA^S_+a6~!NPiSQt{5R)j+`*3h`32|FrU+ zBK)W>)P?gx6*Xfs$V98Z^YI^AFE8&VDXK5KXUP!pjSnR={!$5nj1=Y`>E)+XQhP9D zgO|>TxT{hhjG}4ogJ41jSAz@IJQ&^Kpz$m!HH+- z-HMMxRgBuK7Odwr=`E48bVxMO?n`V;3{h95SU`X3{-oIFSc?!9uc}zuHpI^K*_`@v z{lHW^qRa4rtM5%1Q6wyNMTd2A4qCg<>6ik&U7XcVkqe-txlv65C|hpSpa6WMwgo0o`>a{j0&O%|fZGWaY7i_X9Rd)CKADF0x_eSkhjTQ5VyEsM; zNm`?tG|4U`!B|kq(|bgw^t7N;R}d>oBOY4j_b2bz%Hc|%#m(WY|*tKeGRIHn@LQO+<|_!t}vrrS8aVy4icy z=%8FEm^{a1iGDMDl*RbLVd8;n8?l#(xbVg>vdgDr$~@{Uh|dvKC)37FUE1wF)tJtX z!9pF9Jw|j$2q_-=zWi8(EuWMIs^&qjBlkU2Rbmc|gDzkXWNLUgccXT_v7v9dRGsU( zEN&ECQb=P+WYy-`X6ZDnjVq2nE5Ljh&rz<+0*hxab7ax&fmd>8Xc4nw$USrIiOt5F z^%I-*GQH4W`k)E2l9Z@TE8UNX{qvH<3x0(Q&n=^nVIA315%6g4;I(a! zL)5EK+&%qAVq_nqM86!CHq2JX{X=Bhqc3dk9#yYAvwf~{7D|WDy!GF}8);hC$+hxb zwr z6*y%6!`sOt>ccUZ!*Ak#Izx?@C&ZdYfU{?+#m3v&@`;fg3v$g;>L!d%`(YIlXaJq&9Df)`RDn7%zj;9KJ`J$mmMw zq)uarykB?aJ0xV$3B|YJz1&#swX}0lMuFkn&@&cC(M5i+3=CT?@v4E%DF~*9I^be| z0nFOj2(IkWsd)wPBkh6OEH}D U@tKkY;HUwa7@`bn;ONKy0x+YZE&u=k literal 0 HcmV?d00001 diff --git a/bytedesk_demo/assets/images/chat/voice_volume_2.png b/bytedesk_demo/assets/images/chat/voice_volume_2.png new file mode 100644 index 0000000000000000000000000000000000000000..701f614210707946f0b4cdfd2ab7dc904e873ba3 GIT binary patch literal 2177 zcmZvd2{crD9LHyd>`d8T*^L&AsnGB+mPZCzl5Az^c@ZL>r3TYT^OBt;sWc+A*h9}Y zCYhv>dP!u+KEqgNG?`(_yS+JQPUpS%JNN$o-{0r={oZr#`QLMHnwyKgw8Tyc5C|mg zfVOo9cH;M2Ob7%5Q>x=kfd(AoZf^ss=~tNtbb;Fu4?6&E{a?{&w7I!C27|G%u&}hW zw7k5$va+(ey2@lSXJ%%ADVbqAo4E`82ChB;70^d!1?6I z2mY6YRjTIz2XiIX-Q^ev3=tCECL$&wDJ3lf-M&LsPF~?BRW)@DEp3>-0Rm}eZh^A2 zu|qo^adCBXKX%;H%iG8I#L2)@zXgYcM#sd(pT{KOk}qdw=UmIbb^A^+q2yj^MdgEs zkDfF(HUHV(N$MiM=;`h2fA@Z9_~Qt5bZmTbYIl^u8^6wBr4ucZFW$v?w*^AOV1yC4hSZ2624GgH5nsz0GnH2r4uq9;!$5^C&qx52@N#{c4|TrNPU(!6dYRH6(*eCa?szIOtZ+-;s@w^DzeIxMvmmm8 zAl-?D9~-8)lT=9dX7Ipm6T4a-#?=j7T|p)*Bswpqyfk)RPAM0`>_aD2kg1Z*<1*@( z3|N+RK1k_Nt^x1QXE(^9ucs{{R(ChL=UyHn+_#yI42K%8hpF_%rD*-U}pMnhCb(Aouq;F~(l3GbJD<7v;2Tlo*wXLaSSd z7EdAm;yOjXL8z3{*&Iv==U4)7UkE27foB)O8DevW{O+P4-(MEEps=lR5vmf*NJVCN zdt~3aGUb<+!#o1`ut~!#*o#Ix;V8!!iC;>CcT@41z{fZ)@Q2A@M^de?L_o? z6)VFsI`Q>9PR)i0#lXMGX2;BfsY&fHW-wE>a-fG?^17KrNa3A&NaSrxuKcn){++Q+ zS?-2gIp-bR*#w_})0;)7geWa$klN*ij+xmH1WUnoMjq z%ddUx_mQruAaAXY8sn+5Esi>cZm6~qjX$KQ%VX<~MsmJe;=Q<9q35FRYvuRi-)J@j z*HQDBz1ttI4GK9EgdTT?qucNwNVWQ==6UK1>wQfz^qSK|O3fa>R7c!?1s&&s5*(H+=%a-Anc7v})8q{lbit@r~bfv6Z@$)pqjo?G! z25Z{e#e^aM9MtzsYOhU_R)L-;qu^JAf{9f}JjjZUZLi38-KV2|2w>dy|e3@$Rb4<=E)>-AF-1vlRpzMuWt(~ap|zkC-S z;Lxu$h(uqq##&>8{9ygHC(M-_ziK&|-fw+UF^H7uGljLeM*B}Zy2ITus9Iel8<6ox zJo}}Zm&Hyay%RRChhAigvkg@d2u4mr01Bp=>ld}64 z+6JI&pPA6~I~$Af&tFnQ791C$(-(ctC>Mp_>}{iLbVjKWc{MxEMRvRD>5MX6;T6Q# z&F<#rw-I|H5lwj9&yg6m&Wf+NOsCOcM2Gp=5at{bNwA*quAp{@Tf}5e2L7Z=_J)qI z-j~g0pDa{&&WgcafgQa&vmp;029%hDa(Hf@XXz^K#6n$z9`Y3TLECToezUYNzFRW^ ie@U1Ud`b8qj=DEasyxmu3l03GKn`{;wly|^DSrd?cE@l4 literal 0 HcmV?d00001 diff --git a/bytedesk_demo/assets/images/chat/voice_volume_3.png b/bytedesk_demo/assets/images/chat/voice_volume_3.png new file mode 100644 index 0000000000000000000000000000000000000000..2b57dc2c549a94c841fd6543cbce90ce0b5f473e GIT binary patch literal 2236 zcmZ{l3p7+~8^>oxxnGkeg`r5~dJa)CgUI!SLGG7|RC2$^m>D`1(vTWoWX2TYlO&vD zlE#UVX*7sF#gW^LaUT@TU^x4nS>LSIch2{$z1RDH{?G5f*V^mdYrolUr|o4V_ep|4 zAQ?voTO_c)`tueS0fE5u+Qef(1CB@9+kl=jRAvB8aC7mr1I+Dzs;Q}|>FMd&+1a_d zxrK#=#l=MqhqJV_w7k5`WHNy%o6QDBEEWsUf_CPw0wO?ga&i*D00t03Mt}jnz!5Nj z7PbOM00?~`B_sh2OusJy_I*&Og&5!nOaK;WLHj)*)c+%dq#!7q{y!f81f&oUM1-c$ z7dYQP@b6ClK3^QLL)i()(;gr&L}bTKQE^ErX&G5Ld4*kyyOoqx)nMue4r=Nd7@C?{ zoUnu=Z0sDITu!^XAw9g#c>DPJ`3HoY4ZRS4DKo)FAA-=feK`F6} z^sw@ARrS9c8k>G;eo1M2P3?R`>t?(k82tEYX!!S$(ea5%CTog4J;PaA`Lg%*cPAy0p6Gn*Ji~)j2P@|Z zI>=pf>!DAPmHx5A*)kfhMR^sWai{Wpi6heX;&T5@zTr_G>fjVDL z_k5`Fa(3;2Vw=+Hd=4v(yOTEdGb8*|C+AQFl$+P2OM9_!K>inb$oV(neJ`L9 zrWyEemA`RElQxTQi*#SjOu`XsaYWO(oJSv~`NJ%x(~weDk!Mx~kgMYDl=;FW`jm9#dwr6L`48D%s(#v?1?NB&SgQmtnDyg)?~g0V|;l&WzV2Yj4$TfEsL;jC(#;EaF-+bdaFx1 z1@z>23)R3eK#{5UueyA8R!6wa@m6MYVv1Zs!8W!&YJT#l0;+|CgVamD(qsH5@N>f< zeNt(rN~?y}%h-W->#9NLMZ(gL!^FfS7aR{wY@?*VR#9RT6R$uI!#POG->O{PhH`xt$ogtUWT`Q_qcG zT#A0qHM1nZA%DIva3yPQj~*I^YBo|3V^+<_C|T#!(cxdvJEoPo_Uu+&`RrxbX5X?& z2Z>djyiO$+K;$5L!z{|oA%~~BrIeF2NUo}thJkNiz zBy(hQxcuggDrNcglkaIq?E||XbjX)^DpWtfB>cd(VOKq%-ol?3Z@@*}56z8J%L&h+ zx&)x+FXyVfUuaH7sP{_`ZphKM!~*uh^NV|ul`UJkihqN9cWMWgGX`b7QL=Rkbx+r| z8YMF(Xyf&9zLd(k;N}=hzE`Yel)2}6R*g88<+l+mYoeCJZ}WFHTb(ykF1VokEM8-s zq3;w;E1mliC1C>2Uy93C;`z5xqEH#`63S~Aw$4RdXjn8x{Iyr&I|PgEjRU*0wSOc} zoH8zQ>^w+1id4JRN4QCUlNnitiov!9h)QK`?Lqim?>vlK_c`H%b(J#m0Yx0<_Fa1z zqUt_cjRVV_gL@c4V79^4a`((xMd=?3s90z$g4Pp5@8LRT6!>TKxvpSghheNo>34A} z?(nzvJVC>$2!S#UpKooEESbMf`IV*L-L8E*YLp&ww$Q(I?E07E8+* z&?jAPSkB$KT4~U}3r2wOeiPc;?QI3k#-zHY>b3JK$ kr3(g?|L>=DErJURUoMOP)x3fToNFLQyVJH$Z9+2t01gA}(f|Me literal 0 HcmV?d00001 diff --git a/bytedesk_demo/assets/images/chat/voice_volume_4.png b/bytedesk_demo/assets/images/chat/voice_volume_4.png new file mode 100644 index 0000000000000000000000000000000000000000..7eabb924a49296f4d3936f1df2ee68e4a8355e1e GIT binary patch literal 2292 zcmZvddpMK*AIEQ$)0|bJp2&2-8pRxPSV#;Z=XjDHg^CH{*{ds?0@9+J`_r9+Cmb0Ucw4{n8007b` z+oLYv`tF}wTm%4w+H0;DgN@K-7aL0e*R^vB)Q_DV+^j*9|GS!;oMbQpqxTCqWHPHS}^*pzn=Qnwu1P^ zO*ZP^ra<6ONp}QzRB4eej;?@^u*jy(qT-SeDQOv5Ie7)eEnAiU*rlqb4%OHLgX-V&QWw6wN;?&#|MI{0m9c!WeAoA^$l(kAJQDb^f&;n&jg z3U`gS3pMHp0HXe=qZV#a!?UN>ton{^cYNexZN15wRMAy-HT6>Zn7Z41`8n6Nucf%$ zOKB1b9$Bl1fRH;EZokF+%&{NdJ9{eE9v<>#l%BkI=@%)tJ!TB0h3d4DbaBHtTcqRF zTWpWm_fmn&M%qa*Ma)T@e=mL~vr6N9I@cuvS=IKmUDAst)7vWAqoezD+ogNwLdWsI zelhH}B$XzIGau?YRhlP561C1AN<5o$K^CYF?F&|!Kzz7fD_<8fvvsbuKEG^M@yIFR zu)A3D<}f;dFqBC%hCLg8`2;YbZ3;VqBm@y;kWgh)y=8~0<^Hk;4YYra-HT9ycp@~h zi8Mc??|P}aZmXGVddWnM{&qh94`Kxcs4m7Tmt>bbTTb{H$fN{5UHuV+xc;)&6M6Ht98J+JF**iW5K);+3o`iLt8TzINA9+?%l$tjPh2rltJGL9^ zzaK2%;V%%PdB*PA-}S`gSQaW zLy2Ns0OQ3LyBd zv8PA?FnZ|1my{wY55PP(*F`$!)-qbuE8MfCOZ!iMD(qf$7Cqr(po6g)>&=xYNMtrY z;dO8!Wv_f(ZE2?HYsq&o$A0pt>$U@>xe(*XJdN8(&6~*f@7<{rrvvBlF{W{ws5a<-uCXjG#yj|QR?p}t7BVrw zg~OXM+=`wh@Ws@Fe8UgdZ`&wsyJCZXAu>^O$?>Wlg!! zZ6rL~?4gO{${kNqvHeV&)u)Bs-FFNpG%~fY?IZRIq3$vV563x33^P;t@1r}RQz;pGtv516t+O96#1YqIK025 zLrY4?PComC#QuG?!iQ~UVF7hJt0p&FBs1Jl2IuLeQE!}W+kUm-461e);bNBJv^xD7 z`45;xr>431yjWcG%#g)^)S|d@Nn}tX`Tdjb_e&qE%h3`w393;MQI+HYoj%X5;>JU3 z)|btKjoen#rxk?lUo8^11etE(WYp*3b`0V5)Q`K5Q;+>D^mQK1F(FgP3PHkZ6Ep>F zc0Cr;&*PRU9Ab2GTmJ3{==Vnp&P`mfq)4YaYZN;KLM|o+A6Cvx9JYi_~j&V{^atr^tuDq*hQ2U4cpjR!8H2`aHx>jM-pU<~>=2VR! z+&90_V9$Bj&ttc)4~nC zIhF8O(#Y`1!H2lnn}6$RJnk@g)RGrGEYVoXak8sv#pS=rJ5NxBvx){>-;!v_(W_1O z+1lR}O<+*gJ+D50qVin3rPY!{i-Yf}zlam0=qdL-qYm{Y9(E5YEUAijQ5S;?0J*(Z3d`KqWMU3+S`OS6K!>Sr;f>O|9rW+4aotO^w;tD`$Vsos(M f|5qlSsLK_jMp_u$$4du*zd-6h?d`DGe+ literal 0 HcmV?d00001 diff --git a/bytedesk_demo/assets/images/chat/voice_volume_5.png b/bytedesk_demo/assets/images/chat/voice_volume_5.png new file mode 100644 index 0000000000000000000000000000000000000000..3ee78cf676360324dff148900ea0606bb8b1e68e GIT binary patch literal 2346 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 literal 0 HcmV?d00001 diff --git a/bytedesk_demo/assets/images/chat/voice_volume_6.png b/bytedesk_demo/assets/images/chat/voice_volume_6.png new file mode 100644 index 0000000000000000000000000000000000000000..74a1cf6534efaf2e35e86e560ce4cc8a12e15a04 GIT binary patch literal 2403 zcmb7_cTiL58izxZ-c(o=2xUPoC`4QZsi8_RNEMXc6c7ZYNJ|J!+9D!-l@de&0V$Ct zQWA6p=>ic!iCIWO??_1uWY0A-jN{yY?l*Jhd*A1Me&0XeIdf7i%}w}uPV#_2AbwMr zku|V){`cWx1A$ojYwl_Q4NHi%i2yIS^!WrUp!ZUQF%h;9(7D!1PcCICBI*X3cDW77puQieG$Yd*~dV^9u)r zUxLiYA^g9BfK1%cNCR#zGt}DL2E@Y3#?Ensi-(twUqDbu_~^0YA}1ulQqr=&%gHO8 zQGr}g*V5L}g&G)}z%HAaTUc7#*xFsYZtvjeZQ;G5Pt+6oEKBLn2dV=jIpI z);G7lZSU?;>H7!AlkdL-fsS;V8tGq+_`Kr0?>cr#!o1Ad*x1N;Dot~vY^Y^2rH;!M zsTg3>gZyMZAyy#m!%-RNnP<0>BTHmN^vd7L-l^@C&sUq?ibnYa2M+6mQ(ab-P30?t z#1gHlpwCOXuc`LI^x!j!xRYJ(Je-m5&u!#WPM+!@Tf*f{;pF_R?^ZfqSL|?A;ScT9 zVqI|HreMVNog?WypvSo|K1bHP~H%S@mf~xglNP)qeFIAGp!*(;?k>Xd}NaElQnU8h5 zYCRYG5hY!;YE6OqCGNjAPEfhiAh4KbHPm?Rn)%gIg}#nsh#fC~EGUdE@+MBZEKxV} z8S!@AgdE>#?1qy{twfUh=(xn}fDWmU?fc|&SR{eSaL~BjSY&t%S+l^XdR;Jwwz=@T zZio;jR#K8Rj!Vsq1odXq13M-9I*GJ3UW%J=*W^%ze&ju)e`n9ngHHK_q4a`r%7vI* zN;P@m6Sfbc2lPuVEPu1!Xr{aT*)=$9A00Qg_8=xxA3LARVAsQ#v*1f)aooE{Xo9#` zQD-bCcztmfagLYZ3*lK^+BolE>nab3FmV{P_4V<@twg_vg~nSqH<3Aed`cA+Iz+-( zR%bHStwwzJ)g>d>>LpM6Rd@fh^+D~ko8Xq#W3Lt8ufi|Cfwx6V+@p1nS9zgZ2U{bg ztQUv_*E_lideTB%>>eC?<8$t5FQt~bw{S`_g`IkjkW@`}k;|@C6bSb-bSZxTn(JxC zsL8OE6+Kcj=Ia_YQJQu!4XTm^9h|X9n;ypOg?Q}c+td4XEu#g@zWl+#f}Wv=sLxT3Ir@(<$!_V6M+Lae=bN*pJ6;F-nbiyL z>8c!!UWD4*+%I33SD^dlq-a&15TX_J^`5kF9=P{L=*d;#%EFZ3b223%_@OMj{!CvN z#n42%F&k~n)$;2D&%X84+EvQb6eD(5lm?3(7&=vqFqXHR)>~B3YHwcqKPHrJAV*8L z!`hT|+eA~Y!HYIlREs6RKKa}I6N%Xhko(aiPJ)X9x!_iIP3pq-Q?#L?1Dz+JtDnc#5F-G3e12Tq};Hag1-~w6We%#}a4HKbM z)GLzzXRDc}NJb(o^1LZRRt0@ONe}VqIHHxW{B^u?XC3>t)go_w#GUw9(UNM=kR?|s zw>(bcXq*k29W2LI?s7!;eVnzFBRK~v_WZ#aj&>M_DJsbkbqcnbHno|ipCw4ISCGWK ztJBX59Nq|dMTrr=L47INDcHD@+`dqX%*og^RK%ialXv>DVgb~GksT}DzHsI1vGiNx zMCd(3R7fpHUrV;D!UlRe50dPFQk}6q$fB_E_D?OQt1mqraxD{1jXaAj$|U6lA|u>R z2Mww_9C&7}2orK=0g^w>6{Hi;NY~F=@h6DP0i=0GEr?&u;XrgSKHYukKNV4=7?`|?eTuV8S zICeuXjmr~L2=fPk0 zM&fAIw(5`_HDOQpo%*xip=I0=Rv1Dt4KbG3=Spkp_|R^CA!zjF3L73Nq2A^DxBXT? z9ed^-+N=Ws9n zy5Akafrv=(C-c9BM=h%p^zD6oq#y;o)gjC3g^t4=NNfmNY_UFHxr^B!`DBFh$SZ^+ZupJA6WJ?+MaaV{1)`vCa2ut eTPVZFU{m@#FWXjmQT`E literal 0 HcmV?d00001 diff --git a/bytedesk_demo/assets/images/chat/voice_volume_7.png b/bytedesk_demo/assets/images/chat/voice_volume_7.png new file mode 100644 index 0000000000000000000000000000000000000000..e33ebbba23f24ef4db1e6fca29c3ae67f72f94d3 GIT binary patch literal 2442 zcmZvddoE{yrBDW~FR17O2rAUfgDw?8M zgtB5$t|84B!QnZ|6A9@B4k9v-5er9?!??^Ur%b%Y-`H2nk3FfIuK2J6lT_ z@WlQ7;pYK?z=Y~317HB7U^eEUn(jk0KyHRQBCG(&`L{DQHASINW@l%qROj+Ue~_k4v9cJbPYQ`?|j24Yu)Za|^z;y`%HWu-m5%U z(iLvg4xtLGUFxo1R7+PCbp*FxapTeM@U^wb&ur2d(3xST#i`kOD75H3m#@rsJub_u za6B|Qk+11{>wM*D^~~tNlqHandhW3|XKsHG!_S#z`F$uNOSjQ=Q`P)*QbYRtvY-!LDEp zUct*A5TmbN86|nU9~ODZJ!>Sxyu?~w_794kO|ZOdo7_M;e+nK}6&>A|Tf0GyRd)(% zlM%L2tVGg6g;7&6;VG*Fe;xYOFAi;k_lisS^z2-v3D{cRU%Yf6)K6?uyDN3WQ*@{8Cs*cB{Jf#ml_PBo)kqN788$dtWWhh@oKqLZ0UD5 zXwPFat+#txT|$#g>oh*|wQ|I}1NZaq-gWJa2Kdv4afpxbb(4#7Zsnd z7xZ733})Q|yT~8x$0~MnN_@8-5#*Wo^jbo!h^wa~S^e|(JcA&(#U{cU?^0+unz?_W zo2Do;LBg(g^L^o1;8{rBCe}E(-u{AMm)z`_t2*AOmIVim+_A{+D1jSe!H%)ymYPu6 zi$Sb|nRAD|8C|u_zte?^WSNfD^`i#A5G8+K%*99H@20tTMQ*Ng2uWeRDeL6tge_;ZeGY*z0Z<#TR*1;#*QeTp!i6$g|n|)V`wJ9|8`OFcQ_+kw2q`bR3F34s~ENVquL{qy0UK(`b1Jgznz- zj;fD%*LgQ>5K+x`X)n(3kB@bj$tz@w9o^3;vyylL*>H<>SfFhD%0IIS-bnHCx7t!( zOj^`#p$cC~R1IQ_0avNOz0toPM{>VhK&ADXKhGfq@qnlA)?{i4XT?6Hw!q~Sup060c9RMJPXHX-?3$y&%&jz5AGsHkB@4Ab_e2nS((JBgdR|p^q+gR?cp~Dpgeo96p;%I(Ok{>TZZ19C zB6L%<_T4dgav96ED<&IqAe1RtdOS&S*tE-r`H)vIhf}b|5c!rfUL1doK00Y+xurNv zC)*OV+F4!5x&&XpT+jDQ0g1(ISLxgW@bbt(-Y{lI#vVOPRiQN@DTt|Uo$rZT@=i~g zob@XsGjcD4&z`dbltA^cyegbEVf=}Eolc1C6e663{$*X=A988^*#w^*Bl{&DISE6a z%4g7W2r^mnD=)TeVWt=B6=H=~W6RnNtu!GuQy!(VVx=4-r!GxW+;#Uva*9DyJ=G$i z&CQJ3HGS%hNk`PVLc0Xu7akdZDg#wqwiv#92#q_62-U*{LLtz;E8no0#{^&RNgd+s zb;2)K&PN;2oO)b2z2U2lrCe z`ca){Sal&od*8ok60!T*kuLe}j%dHgJuI4{;}gQ)X8eA+VM$=^m*D`O$|t!(tTk$T zMI;(}=wR*zQo{9X^_O3Xh1Kgisvqs$!Yac{n&W({+hgXhRbiAl*iDmKoFi|3%BV}h zyK0ptwuYQHKV9zQT9&?3vxLoBgyZrqWMTOa^4Lh9?sMnyh50*2(yx{_BX1sqnsy;K z=oogilp?(|guQ_|oF`&>c5kfhfQ`o%Qb { subtitle: Text('${_historyThreadList[index].content}'), onTap: () { // 进入客服页面 - BytedeskKefu.startChatThread(context, _historyThreadList[index]); + // BytedeskKefu.startChatThread(context, _historyThreadList[index]); + BytedeskKefu.startChatThreadIM( + context, _historyThreadList[index]); }, ), itemCount: _historyThreadList.length, diff --git a/bytedesk_demo/pubspec.yaml b/bytedesk_demo/pubspec.yaml index 3b7446f..503ca76 100644 --- a/bytedesk_demo/pubspec.yaml +++ b/bytedesk_demo/pubspec.yaml @@ -46,7 +46,7 @@ dependencies: # 请在ios/Podfile中添加:use_frameworks! vibration: ^1.7.3 # 在线客服 https://pub.dev/packages/bytedesk_kefu - bytedesk_kefu: ^1.3.3 + bytedesk_kefu: ^1.4.0 # The following adds the Cupertino Icons font to your application. # Use with the CupertinoIcons class for iOS style icons. @@ -80,6 +80,7 @@ flutter: # - images/a_dot_ham.jpeg assets: - assets/audio/ + - assets/images/chat/ - assets/images/feedback/ # An image asset can refer to one or more resolution-specific "variants", see diff --git a/bytedesk_kefu/CHANGELOG.md b/bytedesk_kefu/CHANGELOG.md index ff1d6e2..8e93d04 100644 --- a/bytedesk_kefu/CHANGELOG.md +++ b/bytedesk_kefu/CHANGELOG.md @@ -1,5 +1,13 @@ # Upgrade Log +## 1.4.0 + +* optimize user experience + +## 1.3.4 + +* optimize user experience + ## 1.3.3 * optimize user experience diff --git a/bytedesk_kefu/README.md b/bytedesk_kefu/README.md index 76de2d2..40e4167 100644 --- a/bytedesk_kefu/README.md +++ b/bytedesk_kefu/README.md @@ -27,6 +27,15 @@ bytedesk flutter helpdesk sdk ## Getting Started +### Zero Step: Copy Assets dir from bytedesk_demo + +```dart +assets: + - assets/audio/ + - assets/images/chat/ + - assets/images/feedback/ +``` + ### First Step: Register Account - [Register](https://www.bytedesk.com/admin) diff --git a/bytedesk_kefu/assets/images/chat/extra_camera.webp b/bytedesk_kefu/assets/images/chat/extra_camera.webp new file mode 100644 index 0000000000000000000000000000000000000000..fe83d04f09c1a6f984cdc433653fc3614a062379 GIT binary patch literal 706 zcmV;z0zLgwNk&Gx0ssJ4MM6+kP&il$0000G0002L006%L06|PpNGAdS00G~@xNRfH zvD4(Vzl(_Yl!BAd$pCG6JOu~%g-sTs1Wm9SEffbqAi=h6wQZij%*@QpAm90qEMNBf zf|nE((SJv7Bq>sQ8S~UI^8)dUcLK|)A0ruAd$8>-643w}1|*@h0}W1^ zDUFLLao#*mROg4=?J%z<+)WDn zH${p28}XszuHp>it}=CSNs_gzR`L42&9&&N!~P#h;`-Ox>rrHUV~ zz{~ZJJAn$4oYEgl}zRuOpc6{N(tuh?5KY_28$diE0wBX zsw$-_V38wPrP3TsbEQ-kEOI2NR62m^pp;62MUGfXCHAqun^MXGiyX1P8tKLci~LW1 zjvuLfHIn>?zJYmD{&kBN>&85Ievs>S-}X(CME-rNU&jqZiTuZHd^&C>OZ+)*DNKAh zZY)jU!*P3g0?*HzNEF~aDrb|HmCRN~9-Oz5Dc<@mn+F0Vl;>?X2$U^_Pz!<5rVuig z2#}RYVPtl8PXxkq*EK2nU-ZA|f4^`509H^qAZ!2t0PqR`odGJq0Kfn~kw~FUC8Q%F zAV>hP5(#N;;dZebKoi>ZRl*E{8mdip+Aa`e5YwIGYkyLmMm=B8NM#pvkb}ID8Iok% zwr$(C$8C4~{|`*WtBSbe$$0T1`ab~)(f_0WM_5g+p4mQs5Z<1tNk%MVykImF2nC{?7m61_s6mkmLTfaF z&?lNt042TA_#}J^6mbDQ6;%B9i*hQDPY#vf!}m1z4Dbn!Ahb%eickxUAXM;9!37BM zA;keh+(qFr%D6G-1tXp`H^CqmKAQ4^5)YdahBhP5(#N;;dZebKoi>ZRl*E{8mdip+Aa`e5YreLK&*3@Sk zW1h-NyspPK=8A-?xNl>x70CvSy^j~vB!9vB@AxPR3<+zm#zkvE#_lI^&~7AtbCnBO z)1RZpngw@Nb5c$ISju$(CEEv4I)EB??h;QXmX+5-E}-#dbZ$1nBfqwiWFog)TtNTJ zXfTY&mfIppCx?4wF61nH3-6mU++}-*mH;AO=dv9@`MINT80E&Tb6;)x#1nbm>jGNu zgn?G$k)K;P0Yt7hxPXzjK`D&K1{SX*kiVNnF66EJ3<8>G+(nyg$szJ-BIN+8FP-=S zR~vU~Ld7Q)L>_gzfR2aWpvEIVvcBmcCzoqnNR5ARfm6m^bNNIFBl2r4-vJbE{P|Be z81DSFUp7cmh`bwh0rl6ca6LSZzO%s+L*!nY3+PdJlzTR4a>()dN|#raV@oEH?~A#9 za#hP5(#N;;dZebKoi>ZRl*E{ z8mdip+Aa`e5Y!_TBBe#tlsdG;L|A%L0s|wqvis=6Y82!KgU;lq^ZJl^} z6)U2OH@lP2drB31vVF_^SWjw&scBU>jOvs#Yy|N4LZ|KAu^P&goL0002+3ILq}D!>51 z06vjOp-m;EBO)M30I(7XX>Q?mu^T`W+VoYz41yY}O?KKY5M&V5Qfs!+aDyO*s*_!| yi-Z{jHB_4Iv|J#_A*!U;ZKB}@K@C+VyKNT;G6-s^HP8S6{{OrYpTGbB0001ifqF&& literal 0 HcmV?d00001 diff --git a/bytedesk_kefu/assets/images/chat/extra_localtion.webp b/bytedesk_kefu/assets/images/chat/extra_localtion.webp new file mode 100644 index 0000000000000000000000000000000000000000..fcd628d13f9904bf48c053627d30090653d893dd GIT binary patch literal 832 zcmV-G1Hb%INk&FE0{{S5MM6+kP&il$0000G0002L006%L06|PpNTmV*00D2~rjaB` z+Q@mOv(7yJP;Ws*L_RT+Cc?`98nR(9ejGGLwy$)Ue8}+{5?MAg*1(XrLAGt%YTFz+ z%p5}))rOgwnOO?A4c`C%fP%NK#@LU|JrVt%0Qvg=*Z==pJimK%{@~{RMcvK8{AhPR zS!IK}FD6Uf9W3Lib@k0a-&U2Qjy)6MUOPV(OD1BiAjf|+k*iP5(1)>5hj_`IHFtld{ia`%nJ`?-wF8>{CD88Vg+(cypCral=JkH&hf;9|+dP^oA)v215zFCKeD zw3(Q;@vw>tru<}qI)5ratlH%F>hro3>xTiq5QKhL);(>yO0S_vh*e) zyCmelAeKGJ+Ec_!J_p?f7#0dpLxAh%U=-0K&>n-UH}Cs1-x+ZGfqD_2mnb`5)g232JlfnKsnd|OraF8$-z7zNMJMo z%yZBOa6OnF1MK6V2@w_|G(L>;nwV17c$eIwrPe9w_w|%&{^?h*K;KwuHUW!A=Epz=_J93Yk0Hg ztx-4~vu9jL%{jE?D2El7{9(tBl<@k^j_<V@6Tw`ktG zgx}t1`i0@?C}xD>#4>Ir!I8_`M;+t~%f%O%%M#(a@*1I4!gNiToz@7~$=hr+C2Xhk z-|kYvcQW{wASsMjU;Rz1h4b?BKb@Bg>qT3?Z59ddIfFaf0}1*1|JVQjZyx|wP&goL z0002+3ILq}D!>5106vjOp-m;EBO)M30I(7XX>Q?mu^T`W+VoYz41yY}O?KKY5M&V5 zQfs!+aDyO*s*_!|i-Z{jHB_4Iv|J#_A*!U;ZKB}@K@C+VyKNT;G6-s^HP8S6{{OrY KpTGbB00009^nh3Z literal 0 HcmV?d00001 diff --git a/bytedesk_kefu/assets/images/chat/extra_media.webp b/bytedesk_kefu/assets/images/chat/extra_media.webp new file mode 100644 index 0000000000000000000000000000000000000000..2806229e652d5065492b79a8a53eceb03b6dd51c GIT binary patch literal 844 zcmV-S1GD^6Nk&FQ0{{S5MM6+kP&il$0000G0002L006%L06|PpNU{O|009r=u#qIG zp?dkrKVq&Z9SD@loV3(9Y2t5+qUUg zt8Cl0ZQHhOo2mKjH(6)rq@DSA6Vd+(5dZ)E|M&Z~@2!iO^3gHF&7J~=flX$fU_9Gh z&=i1e$4l)!xoI#3^961~7ePSM&=-K;GM|WA1p&>kaC;f!0|}dOzSsqL9 z3)(WOPRfyF_(s$kVsL{{*nCF})pRSSdNTuqpGOQY)01ZHgY<9JU- z{_^Zp{o!=qMlf$DDSdXhTQje=lKeYo+cI-xFUh%bw0<+gR{qT8yOE3&C#y75Y-QYK zY9qv*r0hFb^pA-(v47$==A25_Ow{^-#MQ?+R*Qj?Kwoh3@F#0+LiV7g>rl3GEOiUr zOBUXv`O0xhPVrkvnbat797UOoC@{rY_>Katlu71;;;V!*sc?D1eHLZXqrg!TWdZXF z?8Y%B5nBqZM=~bK=L)RHF($ds3hX8^CgooW9Az;kb-xPSmoO$R8ho`fCfyofDvB`a zR^hnNK=JU=>wF$F()z2x??9w3uyzgq{!OP5BXwUI;G!|mQ)t^MJim1Z@gn6fDqz1U z?4RiFYv}iic#+(59q>5Y**^0Q%dWvlS|oYPg88#DS|noL9>7=(D-zJ-vIj29SP`js zum^q{Xpzhvd*Fr_2_Lb?2{9rycXsHdMp9P1_m#?x1a!W6Ye?u#U-r&x5qjm-5106vjOp-m;EBO)M30I(7XX>Q?mu^T`W+VoYz z41yY}O?KKY5M&V5Qfs!+aDyO*s*_!|i-Z{jHB_4Iv|J#_A*!U;ZKB}@K@C+VyKNT; WG6-s^HP8S6{{OrYpTGbB0000oz>mZL literal 0 HcmV?d00001 diff --git a/bytedesk_kefu/assets/images/chat/extra_photo.webp b/bytedesk_kefu/assets/images/chat/extra_photo.webp new file mode 100644 index 0000000000000000000000000000000000000000..27607e2a0c3984ca8639a93294357bfca41bfa0e GIT binary patch literal 512 zcmV+b0{{I|Nk&Ha0RRA3MM6+kP&il$0000G0002L006%L06|PpNM->500DpFplu{c zI;ZE+9Dl~A;}8+~@n51?xYbzA+OW5{t*PlvU%%P=)Ohu3QyME~hoTS$fOgxq>E_0@ zST$m^X0ZSN=jbKl-Bb5P^nU`F^ndC9(*M71)z&;jGjCV($PQ6;@TedzMIJ$9 zzwVd_{yW3G=iD46oW@fD@VG;^o(t4>%V;(bL}R~_Vk0p$d)7rST^h|_^-;@^MXS+a zT3K$;dP+~JsRsy|Gtp`O1;XA>3N!8x#gArbv0Q@C&vOE3yj9d5cN1cx4B<b3JfMn!Qc@W>%9HXf<%dGW|wRXmdZFa2No|EDolP&goL0002+3ILq} zD!>5106vjOp-m;EBO)M30I(7XX>Q?mu^T`W+VoYz41yY}O?KKY5M&V5Qfs!+aDyO* zs*_!|i-Z{jHB_4Iv|J#_A*!U;ZKB}@K@C+VyKNT;G6-s^HP8S6{{OrYpTGbB0000) CzU-m^ literal 0 HcmV?d00001 diff --git a/bytedesk_kefu/assets/images/chat/extra_red.webp b/bytedesk_kefu/assets/images/chat/extra_red.webp new file mode 100644 index 0000000000000000000000000000000000000000..5a0b66b480e3f21ba6991711c7de58356f9713e9 GIT binary patch literal 602 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 literal 0 HcmV?d00001 diff --git a/bytedesk_kefu/assets/images/chat/input_emoji.png b/bytedesk_kefu/assets/images/chat/input_emoji.png new file mode 100755 index 0000000000000000000000000000000000000000..db61921acbf55f6870dff35889dc6d8bf31c1b7f GIT binary patch literal 1531 zcmXX`3pkT|7$0&SMN=*jndBasQtA|`HkVo2L8*g@5@nC(cGR)EUEDSshU0#Q<ZpCIs# zRX$p3z^^^Ipap@bgk$`%hlo@riN++-SR^WoL}3%DEFx{$Q<)&9QnQGexkL&}X`lp* z!X)PAlIZ}v98uGlX=#bgUT#@E zA0?7b=PmEZB8o)doHHoQ5NR_PL*0Z*5MI7l%T@OIFIE<4dWS-Fw+ z){(`forEHuR$$b*pdrLwa%@nHDO^GpGDwXscCgJ91A`p`n6X){nX!eGYKLRt2#iGB zS;M0$$Fv$v9&rT5-nVUP)z0(W&f`7a`Og+@)q6ZX`T$XwHJh(7BJXaQ5eB59pufe( zxW9{vE4%4(pSBP$bel1#o_+cK;kSd^UE`q&d_fn1_%D2{DdAH|n4;x!N0g4HAfVM_ z+XM5mW;p$P$L?ge;^f=%pN5D$DXd!sH?+ZeT;?h8hYle#?v}M!0f@%N9fAU}Z`P*0 z7Z2*n$iF&9yR?X7*28IMf}~<6m%$g;cREP|_XNCpc>PwaI=kk}VD+Jf&=TD_#>K8h ze7&lLu3!G9x5Jyq6QugKp_#Hrue{n7T|7sv_NeG-A720bd`jayN|!^n-P7Qosl`|(>{4ZfOA2)+2H2g zCO!=IjKMhfYuhRkoWrwCD}CT$*=vG_AUrMchTw?7kKWqbW$tT|J9lBm_sO)?lT$3P z5#f=H($LORm+Khz9;AcT zB_Zwe7Qtj2#qwHzzu7Q#x^j~$VL!tfyEiE+?PuXS@r1;v;HlP)@^k4VbW#oxJg z1vv_=6_mek<5XSZcvu*py0u$$;;l4fUzhWTD`*l&^xmNf)%c7%kM9}0GT9$^DkRm^v*!c9QG{cA-3*gzVk14`rR}dD{ql5!;=h0pE;5r&z_^w{m_S&XOl=v0=Vj!qPXrHUz H5#;{?#&PBZ literal 0 HcmV?d00001 diff --git a/bytedesk_kefu/assets/images/chat/input_extra.png b/bytedesk_kefu/assets/images/chat/input_extra.png new file mode 100755 index 0000000000000000000000000000000000000000..eddec895675c645c2735331db54ee3b82310a300 GIT binary patch literal 1075 zcmeAS@N?(olHy`uVBq!ia0vp^nIO!;3?%tt{&E1RhXFnzt_>}njV;}ctz8W*-3@I$ z&7J)XEnSVRoj?+Vn!B3XdpkO20@*WX&IEFSQvd(|2kHVVfRgR))4+O#ljZgUPOjwqCHtPM|qZ?O+A1trLLU<}RQhNHNeo4Q)V;-9XR?QrX$m(FZaC zXeLMjNPA-|g5A&t7Hsba84I$mtF3KH!>Uz4FV;7APCl3-1!Q@Z1o;K;2+YsZ&@3t4 zdGDhk|7q5xj*R_3O!mDJoc_0+%QQ7ed3nLPRcaaUo^E({>bLlI)h$K7UJl%ub965` z9X$HF`u>}(0WTCL{QM=%#*p)P(MbjdrtO|Cjv*CsZ*PU?J$4Xa30VJR!JM3@bBfR1 zOnSHLf1}uk>xY(UOq%tU-?_ArT|gy$S@ygR?fr389{evAdzYO*wxROo1^#o zx&L?864w^0b6F*F+G(ldRyKyW3pd?amwMNrq;hYp)?qV-E323TFV);(kNTR{T({)n zn(Yn$D)gLYy=7b*Z1lpbC&-=o+ve(}PW%=!rJH){Y#8<)vJJAD>{`BQ!R$z%T_@5m zty}8N6{Y9DZPNKnkJvk&ZA>chC(7a`H6-nr@Wdd5-?GcyHKI(1RrQDciA1|U6Poyz zMlkp*7R{N!-#A10<)^-kmKYVGqrZH& z2ib6WZc~*^{Gn9hu4LE3YdwjnzT=(O3I3yPU)WcDE&syFFn`*W_O8>unsb|`>ol*Q zkh;fXX}+hV%FDXWNhj`1;EH@3+45ykD)+S@eoG<6Kt5aV%(lF^o+(p29XDjEv; zZrDEInT3vvXE*b+>q`y$RJ;}`9}8dV7=QVw=dNq=F_Txlm|)RqwIXhEmPY3kuNAQq zPi06dsV;8av@^tIu8UWQ`^A`53pB&B7A5I%-cXoYmI%`&#qGk8R(a za$EQFT!tThra9`S`!1{B?wnQWb-@1TgZa-s_^;5I+w{EdWOK-@+({?QWS`FdvB`Ji zO|{Q|a+&L!lkRf;JhH{gT0wm1}QXzjpg{bKmDbS3Qchu95v#>7Z|^rxySHp{eBJ#QindGIzli@UW|$;c$MG7NF&I2u{an^LB{Ts5 DbpZsD literal 0 HcmV?d00001 diff --git a/bytedesk_kefu/assets/images/chat/input_keyboard.png b/bytedesk_kefu/assets/images/chat/input_keyboard.png new file mode 100755 index 0000000000000000000000000000000000000000..5c53793e3ba8cdb2dd86a7cef854ca3bd6465f72 GIT binary patch literal 1150 zcmeAS@N?(olHy`uVBq!ia0vp^nIO!;3?%tt{&E1RCjmYot_>}n4Xs@bEnSTuvb(vn zzoD%M%7IkUMLWH?9x$ehlGHlR5`5@-d`WVlpUYwHA%Y)e;j7f`Uf5h&XRG^85{8bQiB zn>zY{T(CJHZeuH$4KfOBdwV~~G_VP6ZBszXR;>bx0DW2C+?n{wJrv0DE(!7r-jQ}W zKTlJmq;zNMrH_X9_)oJgWpw<}Z?f-|;Pm#trd-Oa-kn>nmJw9&bi=b#;=i}67WsNP zaA(fZbvpR^X!ZR!0WTCL{QM=ng>5UtMUPE885o%UdAc};RK&f#b-d`dfe34W`lRlv z_j|wVtu`-{`u_jFdbf^1zegAA!9$C83$b?_FIMDuV7o^k?&rr#!Bt-Vnfq$)%Q;P7 ztrM@m@bI@<%m4C0wl~BI#23o0F`laSSB!xt?NZu);n3Ldqj&3+&$2OGc;z4AnVrjJ zB7OJV-+<+}Gnj&N&pJiTWk|ho`RuW;_W~IGwATlf^2nUm+SpTN$MkrW_{xY?30r=s z@PFPZu`oz$*R(uVji*u@r$$9OK78hPNmb#$SI%=&eve(;o;}Xr1l>QL?C9=W+R>2F zXW^sArT8eotNo*pa-T4_!kjXdyG9;r9o;IEPTZ04c*(O#^O4p{9iz!Wd z3C^CRp)j*Zf9WIvm*`1y?h1}JDuNC%+m$^#j&&?QvS>n%hxrN1M~0S?+uMUSGT48V zvFAGK9>iIBNTG6yoX6F!Psg09LbXD(Gkc5@Cm9`^c8P1Tl=MooL#Mo~58S4Q+_h1}rFp{?4`#>YmA!fk7Yeh^TFgF6@7A$N4Sb2S_*k4X z`xq1FFg|m;BxHQ;5|6j?OJ<>L&qKQ>ZaLbvb<4qP9##qGqi#J;Q2BeTkE#4L+uvNf zg6XnOZkJe<6qf~0|7Rw@qr0PfVoB?%sGvgANhvSZsLC3}cmCgh{C_V;;_;|kiYZ3L zk>{7&^8MN8_GjOQKViJ@v=#qfo%=2#;%L?JPu#rB4$W&b*n*f39=GWB$*_^_Gj(qNH_4>8_0L=ic*t zy8LeL=e{SiYoGU)sq8F|{8`@jv;6SS@*h9%M?I()ZvOvPXx7h9kMBzx%b)(qzd!%W elzEqee=)CEt`j0q=M)RfU<{tFelF{r5}E)sE+9Ps literal 0 HcmV?d00001 diff --git a/bytedesk_kefu/assets/images/chat/input_voice.png b/bytedesk_kefu/assets/images/chat/input_voice.png new file mode 100755 index 0000000000000000000000000000000000000000..537bc29470b8ef80842ffaf3fe75c0acf8835751 GIT binary patch literal 1957 zcmYjR2{@GbADIl^m5E+keI!yfX|9hDq)b zGjd{S2Q@nfXK-Jjs0~fo6X^HBqNwq2&MrOfZ&Kw zCNnEDkBAiz}NxfCjkLT4s0BrZJn+o3@c0>J&Y1-u|crLq_d4j>O&KyN}4 zo1AnLkf+dCAWMTOG1+YRh9H0dU?oy8lE0u)A;1LC`@eN)%!EXs4?u=z0ZcLjmJ1v@ zJq_ScfmSq##bQw;oZ*RIjKma>CG&Wa5#Pj-kOUE!4_FGIeU%Q70Zd?Vl9E6Rb8~YO zxQU5e7zD%_DLfcTVuLPF45k2`K^c|FU}V5GK)^3*fLka8AHaX9(EA&*hk#Xjg@E(C zS6CJsLr|yBo<(XDB9tn)FUhEs_aDEj)Q z)8Eadtcn5>Uft{(Cup70@h)f7x5&=4*-|3{cC20Ti@I7xcz)za>q)-{$i_RA*LkhZ zHSf->lDbbnO0HV18PfLT)`rYjF1Z;lSz3rrAMN>F>dYlKi%Vy0M(}A&{|pUYu{l@S z*n%5;^y#Yr#i$F4gLmx(`;&e}P5)4cleW~t{*4?=cSH4DdCWq(c1*)NtbWZ$aV1o_tAM3*M|c)$y=oh z-otQy{ndek!#ZvUXsvu{6YHev2y9V{oYeK5B0PIhgYjR z($`E*MQCkg)b+}ttHMicKSc3=>Pm4@R&H=*YnfKKL#)sq6`NWrR z7x2qwVT`ec^sVYas_%WxPLv*u;e~3FG#ol`p$>T`dv9=VG|lRZNzDWu9m|lNyL?do z+@*s0>}A7-xMq>G{ybmd+`Wmi*Ku_;Y|G<|?SweUD5%=spdyfI&8{3pXPN0)g;aQz zOJTYn4(KN+Dy;1QrBn(ZvI9wdUnxPj_bE%&)_}{w;Y~}+ii5!A$Wo|0wW5R&d49h-TLx%k_K>em7{A!jFnEY>KH- zbNPu;!6iy$`y|f&*Oiz?UO|gd4D{;@MX;# zS?yloo31-n+Kp?*73x24uz3CC730m3GJP@s)(15YsepxuIdM1m&qZJzaF|Lvzl8q* DFEGmp literal 0 HcmV?d00001 diff --git a/bytedesk_kefu/assets/images/chat/voice_playing_1.png b/bytedesk_kefu/assets/images/chat/voice_playing_1.png new file mode 100755 index 0000000000000000000000000000000000000000..009de3889bc1517fb7f4e8939f654d59934b7a45 GIT binary patch literal 271 zcmV+q0r38bP)1QLxKP&or7twg0D(W@le=|nU-!YLfUDrw#UbW}(rL$TR>+bW*s z{pSF`d^4FD&r?b%rIb>C4P{Ibu^Pd2R9Auu;RfX?goCN&ZB#o znU~CSjtqQ5kp~xWG<1tObKAgUCS1bfFQ@#a&ndj+!j}}@FxddF6kaf4dzXhi);AmN z&|tAS>@y!lo&|R@Pv)Gr(KEw7QRTsX14rDe6esKnA@}j{F~J%m1fEh#DW#P9wGRn8 V`V{l6M|1!H002ovPDHLkV1o1qZ`%L> literal 0 HcmV?d00001 diff --git a/bytedesk_kefu/assets/images/chat/voice_playing_2.png b/bytedesk_kefu/assets/images/chat/voice_playing_2.png new file mode 100755 index 0000000000000000000000000000000000000000..399999064d8f3f258ac43887e8e1d255c74472c3 GIT binary patch literal 487 zcmV^3PEf{1SS3it7v7B z*!hBEDOjl>NqnwN81F5nyEE0C?e2m3*m>MJyMcg{larH^lbq0xDfHUMZo-F;A1vC% z_P|%qv`z3R0oTxH58RFlLEpo(hfd;`px13q_Zk6L5i@~k#VX1uBFU;j6bbs+1h}Av zPqTU_K9%!@s5CZC-s>4{%-bQ}iZpnT4DREAc^&vr&Vw}A!y5sAqno)$#OsnqPq261 zXWj^LQu5m?ONM_%Gu!d+%Gs3%2T>yEVdh<``h|%jl_1~@bM{pI(FC{!Rn00d{V#%< z7I~`xr>s6>8hob!CmQgR32d0Oio9m3Z%lycS)SEwu5)>8h)lGP0nKsRmc};Vj9nCs zng%i{4IXDLGpB8t;uqrvi*_;1B~g-J;S>R9wbx?C#8E2O2fyZdk_3SYj|g(RdI5)+ zM_3zua`sXt4SO$$*aI(T^yM@S-x+x=oJ)WhGH?JwjI(%8a~`$gg8 dZEKtI7aTj&XR zCeR1%O?Mu;2|S3Re*r1RW)x~q7hr(U?FjmfJq8C3hOt{yc8clVRnPR{ltc;O=-tit&>ca`yc)7o^q)oDbUo1f1NDsLX}_(@h#(#j_8qcpfuka?ueOD)5Y6jld$*b22ph*lTi_L< zV=fBV8Ym?2R=YR2zdGrs)~*xX&9o7xQDbcrrABIakDmzF+<{h^$;1Ev002ovPDHLk FV1k62fhhn0 literal 0 HcmV?d00001 diff --git a/bytedesk_kefu/assets/images/chat/voice_volume_1.png b/bytedesk_kefu/assets/images/chat/voice_volume_1.png new file mode 100644 index 0000000000000000000000000000000000000000..8ed01f5aa452e19af0396cfa636c1f36d0f128f1 GIT binary patch literal 2100 zcmZvdcQjn-8^>q#Hmi&1ge2-D5-kQXi@`8jiVzGDQZP1pC!z(*u3my*l?Y*#NKrQi zA?!+&iEau;?}Lfio6I?m-}&ux?!E8#`F=moy??y-oHxM|g@AB~aDYG{h>5X*HL$jR zzu6c;AaGx`pBB)7eXJ31P|bkYBA}x!&8`^&Chfn9L?SILERf0MrKP2nm6g@i)wQ*? z_4W0QjST{U0HpKt^FTx-5&=zb7k?Bm0u18ucmM;Q#{iw7&(c#MqNns0i0BU81o&eN z&_H^!0OsV>v8FRX3lyCJ#L2<`)W^*Cu@n5U{$c;md+5$FaPqq+;gcF*{(lN^#0?uX zfo+KNwMN;1zzmE`%q(mioLrEfxOsT__yq({i%UpK$()f@Qh{n{!*q1@;KrtAC<{xQ z>o;!N-m>TQ#C*T`I>gZw?K_Ex<~*WbNS#<8-l?R($; zcCCRrorPY)xOJwdPDwM%yTK~Zq?^B~%C{@XxIR|;9C*u1v1mFdgjcT2l@%2{_6m#^B4xDb z61X+4_|G15Q8kF-2(pvNCwl(PwWHl+V*$FkU9Z^WMAX=3z)*7S3o>&yW|qfQ82%B2 zzetE_{**rc!t?In@YZ~wlSn@;WFE7qsTA^S_+a6~!NPiSQt{5R)j+`*3h`32|FrU+ zBK)W>)P?gx6*Xfs$V98Z^YI^AFE8&VDXK5KXUP!pjSnR={!$5nj1=Y`>E)+XQhP9D zgO|>TxT{hhjG}4ogJ41jSAz@IJQ&^Kpz$m!HH+- z-HMMxRgBuK7Odwr=`E48bVxMO?n`V;3{h95SU`X3{-oIFSc?!9uc}zuHpI^K*_`@v z{lHW^qRa4rtM5%1Q6wyNMTd2A4qCg<>6ik&U7XcVkqe-txlv65C|hpSpa6WMwgo0o`>a{j0&O%|fZGWaY7i_X9Rd)CKADF0x_eSkhjTQ5VyEsM; zNm`?tG|4U`!B|kq(|bgw^t7N;R}d>oBOY4j_b2bz%Hc|%#m(WY|*tKeGRIHn@LQO+<|_!t}vrrS8aVy4icy z=%8FEm^{a1iGDMDl*RbLVd8;n8?l#(xbVg>vdgDr$~@{Uh|dvKC)37FUE1wF)tJtX z!9pF9Jw|j$2q_-=zWi8(EuWMIs^&qjBlkU2Rbmc|gDzkXWNLUgccXT_v7v9dRGsU( zEN&ECQb=P+WYy-`X6ZDnjVq2nE5Ljh&rz<+0*hxab7ax&fmd>8Xc4nw$USrIiOt5F z^%I-*GQH4W`k)E2l9Z@TE8UNX{qvH<3x0(Q&n=^nVIA315%6g4;I(a! zL)5EK+&%qAVq_nqM86!CHq2JX{X=Bhqc3dk9#yYAvwf~{7D|WDy!GF}8);hC$+hxb zwr z6*y%6!`sOt>ccUZ!*Ak#Izx?@C&ZdYfU{?+#m3v&@`;fg3v$g;>L!d%`(YIlXaJq&9Df)`RDn7%zj;9KJ`J$mmMw zq)uarykB?aJ0xV$3B|YJz1&#swX}0lMuFkn&@&cC(M5i+3=CT?@v4E%DF~*9I^be| z0nFOj2(IkWsd)wPBkh6OEH}D U@tKkY;HUwa7@`bn;ONKy0x+YZE&u=k literal 0 HcmV?d00001 diff --git a/bytedesk_kefu/assets/images/chat/voice_volume_2.png b/bytedesk_kefu/assets/images/chat/voice_volume_2.png new file mode 100644 index 0000000000000000000000000000000000000000..701f614210707946f0b4cdfd2ab7dc904e873ba3 GIT binary patch literal 2177 zcmZvd2{crD9LHyd>`d8T*^L&AsnGB+mPZCzl5Az^c@ZL>r3TYT^OBt;sWc+A*h9}Y zCYhv>dP!u+KEqgNG?`(_yS+JQPUpS%JNN$o-{0r={oZr#`QLMHnwyKgw8Tyc5C|mg zfVOo9cH;M2Ob7%5Q>x=kfd(AoZf^ss=~tNtbb;Fu4?6&E{a?{&w7I!C27|G%u&}hW zw7k5$va+(ey2@lSXJ%%ADVbqAo4E`82ChB;70^d!1?6I z2mY6YRjTIz2XiIX-Q^ev3=tCECL$&wDJ3lf-M&LsPF~?BRW)@DEp3>-0Rm}eZh^A2 zu|qo^adCBXKX%;H%iG8I#L2)@zXgYcM#sd(pT{KOk}qdw=UmIbb^A^+q2yj^MdgEs zkDfF(HUHV(N$MiM=;`h2fA@Z9_~Qt5bZmTbYIl^u8^6wBr4ucZFW$v?w*^AOV1yC4hSZ2624GgH5nsz0GnH2r4uq9;!$5^C&qx52@N#{c4|TrNPU(!6dYRH6(*eCa?szIOtZ+-;s@w^DzeIxMvmmm8 zAl-?D9~-8)lT=9dX7Ipm6T4a-#?=j7T|p)*Bswpqyfk)RPAM0`>_aD2kg1Z*<1*@( z3|N+RK1k_Nt^x1QXE(^9ucs{{R(ChL=UyHn+_#yI42K%8hpF_%rD*-U}pMnhCb(Aouq;F~(l3GbJD<7v;2Tlo*wXLaSSd z7EdAm;yOjXL8z3{*&Iv==U4)7UkE27foB)O8DevW{O+P4-(MEEps=lR5vmf*NJVCN zdt~3aGUb<+!#o1`ut~!#*o#Ix;V8!!iC;>CcT@41z{fZ)@Q2A@M^de?L_o? z6)VFsI`Q>9PR)i0#lXMGX2;BfsY&fHW-wE>a-fG?^17KrNa3A&NaSrxuKcn){++Q+ zS?-2gIp-bR*#w_})0;)7geWa$klN*ij+xmH1WUnoMjq z%ddUx_mQruAaAXY8sn+5Esi>cZm6~qjX$KQ%VX<~MsmJe;=Q<9q35FRYvuRi-)J@j z*HQDBz1ttI4GK9EgdTT?qucNwNVWQ==6UK1>wQfz^qSK|O3fa>R7c!?1s&&s5*(H+=%a-Anc7v})8q{lbit@r~bfv6Z@$)pqjo?G! z25Z{e#e^aM9MtzsYOhU_R)L-;qu^JAf{9f}JjjZUZLi38-KV2|2w>dy|e3@$Rb4<=E)>-AF-1vlRpzMuWt(~ap|zkC-S z;Lxu$h(uqq##&>8{9ygHC(M-_ziK&|-fw+UF^H7uGljLeM*B}Zy2ITus9Iel8<6ox zJo}}Zm&Hyay%RRChhAigvkg@d2u4mr01Bp=>ld}64 z+6JI&pPA6~I~$Af&tFnQ791C$(-(ctC>Mp_>}{iLbVjKWc{MxEMRvRD>5MX6;T6Q# z&F<#rw-I|H5lwj9&yg6m&Wf+NOsCOcM2Gp=5at{bNwA*quAp{@Tf}5e2L7Z=_J)qI z-j~g0pDa{&&WgcafgQa&vmp;029%hDa(Hf@XXz^K#6n$z9`Y3TLECToezUYNzFRW^ ie@U1Ud`b8qj=DEasyxmu3l03GKn`{;wly|^DSrd?cE@l4 literal 0 HcmV?d00001 diff --git a/bytedesk_kefu/assets/images/chat/voice_volume_3.png b/bytedesk_kefu/assets/images/chat/voice_volume_3.png new file mode 100644 index 0000000000000000000000000000000000000000..2b57dc2c549a94c841fd6543cbce90ce0b5f473e GIT binary patch literal 2236 zcmZ{l3p7+~8^>oxxnGkeg`r5~dJa)CgUI!SLGG7|RC2$^m>D`1(vTWoWX2TYlO&vD zlE#UVX*7sF#gW^LaUT@TU^x4nS>LSIch2{$z1RDH{?G5f*V^mdYrolUr|o4V_ep|4 zAQ?voTO_c)`tueS0fE5u+Qef(1CB@9+kl=jRAvB8aC7mr1I+Dzs;Q}|>FMd&+1a_d zxrK#=#l=MqhqJV_w7k5`WHNy%o6QDBEEWsUf_CPw0wO?ga&i*D00t03Mt}jnz!5Nj z7PbOM00?~`B_sh2OusJy_I*&Og&5!nOaK;WLHj)*)c+%dq#!7q{y!f81f&oUM1-c$ z7dYQP@b6ClK3^QLL)i()(;gr&L}bTKQE^ErX&G5Ld4*kyyOoqx)nMue4r=Nd7@C?{ zoUnu=Z0sDITu!^XAw9g#c>DPJ`3HoY4ZRS4DKo)FAA-=feK`F6} z^sw@ARrS9c8k>G;eo1M2P3?R`>t?(k82tEYX!!S$(ea5%CTog4J;PaA`Lg%*cPAy0p6Gn*Ji~)j2P@|Z zI>=pf>!DAPmHx5A*)kfhMR^sWai{Wpi6heX;&T5@zTr_G>fjVDL z_k5`Fa(3;2Vw=+Hd=4v(yOTEdGb8*|C+AQFl$+P2OM9_!K>inb$oV(neJ`L9 zrWyEemA`RElQxTQi*#SjOu`XsaYWO(oJSv~`NJ%x(~weDk!Mx~kgMYDl=;FW`jm9#dwr6L`48D%s(#v?1?NB&SgQmtnDyg)?~g0V|;l&WzV2Yj4$TfEsL;jC(#;EaF-+bdaFx1 z1@z>23)R3eK#{5UueyA8R!6wa@m6MYVv1Zs!8W!&YJT#l0;+|CgVamD(qsH5@N>f< zeNt(rN~?y}%h-W->#9NLMZ(gL!^FfS7aR{wY@?*VR#9RT6R$uI!#POG->O{PhH`xt$ogtUWT`Q_qcG zT#A0qHM1nZA%DIva3yPQj~*I^YBo|3V^+<_C|T#!(cxdvJEoPo_Uu+&`RrxbX5X?& z2Z>djyiO$+K;$5L!z{|oA%~~BrIeF2NUo}thJkNiz zBy(hQxcuggDrNcglkaIq?E||XbjX)^DpWtfB>cd(VOKq%-ol?3Z@@*}56z8J%L&h+ zx&)x+FXyVfUuaH7sP{_`ZphKM!~*uh^NV|ul`UJkihqN9cWMWgGX`b7QL=Rkbx+r| z8YMF(Xyf&9zLd(k;N}=hzE`Yel)2}6R*g88<+l+mYoeCJZ}WFHTb(ykF1VokEM8-s zq3;w;E1mliC1C>2Uy93C;`z5xqEH#`63S~Aw$4RdXjn8x{Iyr&I|PgEjRU*0wSOc} zoH8zQ>^w+1id4JRN4QCUlNnitiov!9h)QK`?Lqim?>vlK_c`H%b(J#m0Yx0<_Fa1z zqUt_cjRVV_gL@c4V79^4a`((xMd=?3s90z$g4Pp5@8LRT6!>TKxvpSghheNo>34A} z?(nzvJVC>$2!S#UpKooEESbMf`IV*L-L8E*YLp&ww$Q(I?E07E8+* z&?jAPSkB$KT4~U}3r2wOeiPc;?QI3k#-zHY>b3JK$ kr3(g?|L>=DErJURUoMOP)x3fToNFLQyVJH$Z9+2t01gA}(f|Me literal 0 HcmV?d00001 diff --git a/bytedesk_kefu/assets/images/chat/voice_volume_4.png b/bytedesk_kefu/assets/images/chat/voice_volume_4.png new file mode 100644 index 0000000000000000000000000000000000000000..7eabb924a49296f4d3936f1df2ee68e4a8355e1e GIT binary patch literal 2292 zcmZvddpMK*AIEQ$)0|bJp2&2-8pRxPSV#;Z=XjDHg^CH{*{ds?0@9+J`_r9+Cmb0Ucw4{n8007b` z+oLYv`tF}wTm%4w+H0;DgN@K-7aL0e*R^vB)Q_DV+^j*9|GS!;oMbQpqxTCqWHPHS}^*pzn=Qnwu1P^ zO*ZP^ra<6ONp}QzRB4eej;?@^u*jy(qT-SeDQOv5Ie7)eEnAiU*rlqb4%OHLgX-V&QWw6wN;?&#|MI{0m9c!WeAoA^$l(kAJQDb^f&;n&jg z3U`gS3pMHp0HXe=qZV#a!?UN>ton{^cYNexZN15wRMAy-HT6>Zn7Z41`8n6Nucf%$ zOKB1b9$Bl1fRH;EZokF+%&{NdJ9{eE9v<>#l%BkI=@%)tJ!TB0h3d4DbaBHtTcqRF zTWpWm_fmn&M%qa*Ma)T@e=mL~vr6N9I@cuvS=IKmUDAst)7vWAqoezD+ogNwLdWsI zelhH}B$XzIGau?YRhlP561C1AN<5o$K^CYF?F&|!Kzz7fD_<8fvvsbuKEG^M@yIFR zu)A3D<}f;dFqBC%hCLg8`2;YbZ3;VqBm@y;kWgh)y=8~0<^Hk;4YYra-HT9ycp@~h zi8Mc??|P}aZmXGVddWnM{&qh94`Kxcs4m7Tmt>bbTTb{H$fN{5UHuV+xc;)&6M6Ht98J+JF**iW5K);+3o`iLt8TzINA9+?%l$tjPh2rltJGL9^ zzaK2%;V%%PdB*PA-}S`gSQaW zLy2Ns0OQ3LyBd zv8PA?FnZ|1my{wY55PP(*F`$!)-qbuE8MfCOZ!iMD(qf$7Cqr(po6g)>&=xYNMtrY z;dO8!Wv_f(ZE2?HYsq&o$A0pt>$U@>xe(*XJdN8(&6~*f@7<{rrvvBlF{W{ws5a<-uCXjG#yj|QR?p}t7BVrw zg~OXM+=`wh@Ws@Fe8UgdZ`&wsyJCZXAu>^O$?>Wlg!! zZ6rL~?4gO{${kNqvHeV&)u)Bs-FFNpG%~fY?IZRIq3$vV563x33^P;t@1r}RQz;pGtv516t+O96#1YqIK025 zLrY4?PComC#QuG?!iQ~UVF7hJt0p&FBs1Jl2IuLeQE!}W+kUm-461e);bNBJv^xD7 z`45;xr>431yjWcG%#g)^)S|d@Nn}tX`Tdjb_e&qE%h3`w393;MQI+HYoj%X5;>JU3 z)|btKjoen#rxk?lUo8^11etE(WYp*3b`0V5)Q`K5Q;+>D^mQK1F(FgP3PHkZ6Ep>F zc0Cr;&*PRU9Ab2GTmJ3{==Vnp&P`mfq)4YaYZN;KLM|o+A6Cvx9JYi_~j&V{^atr^tuDq*hQ2U4cpjR!8H2`aHx>jM-pU<~>=2VR! z+&90_V9$Bj&ttc)4~nC zIhF8O(#Y`1!H2lnn}6$RJnk@g)RGrGEYVoXak8sv#pS=rJ5NxBvx){>-;!v_(W_1O z+1lR}O<+*gJ+D50qVin3rPY!{i-Yf}zlam0=qdL-qYm{Y9(E5YEUAijQ5S;?0J*(Z3d`KqWMU3+S`OS6K!>Sr;f>O|9rW+4aotO^w;tD`$Vsos(M f|5qlSsLK_jMp_u$$4du*zd-6h?d`DGe+ literal 0 HcmV?d00001 diff --git a/bytedesk_kefu/assets/images/chat/voice_volume_5.png b/bytedesk_kefu/assets/images/chat/voice_volume_5.png new file mode 100644 index 0000000000000000000000000000000000000000..3ee78cf676360324dff148900ea0606bb8b1e68e GIT binary patch literal 2346 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 literal 0 HcmV?d00001 diff --git a/bytedesk_kefu/assets/images/chat/voice_volume_6.png b/bytedesk_kefu/assets/images/chat/voice_volume_6.png new file mode 100644 index 0000000000000000000000000000000000000000..74a1cf6534efaf2e35e86e560ce4cc8a12e15a04 GIT binary patch literal 2403 zcmb7_cTiL58izxZ-c(o=2xUPoC`4QZsi8_RNEMXc6c7ZYNJ|J!+9D!-l@de&0V$Ct zQWA6p=>ic!iCIWO??_1uWY0A-jN{yY?l*Jhd*A1Me&0XeIdf7i%}w}uPV#_2AbwMr zku|V){`cWx1A$ojYwl_Q4NHi%i2yIS^!WrUp!ZUQF%h;9(7D!1PcCICBI*X3cDW77puQieG$Yd*~dV^9u)r zUxLiYA^g9BfK1%cNCR#zGt}DL2E@Y3#?Ensi-(twUqDbu_~^0YA}1ulQqr=&%gHO8 zQGr}g*V5L}g&G)}z%HAaTUc7#*xFsYZtvjeZQ;G5Pt+6oEKBLn2dV=jIpI z);G7lZSU?;>H7!AlkdL-fsS;V8tGq+_`Kr0?>cr#!o1Ad*x1N;Dot~vY^Y^2rH;!M zsTg3>gZyMZAyy#m!%-RNnP<0>BTHmN^vd7L-l^@C&sUq?ibnYa2M+6mQ(ab-P30?t z#1gHlpwCOXuc`LI^x!j!xRYJ(Je-m5&u!#WPM+!@Tf*f{;pF_R?^ZfqSL|?A;ScT9 zVqI|HreMVNog?WypvSo|K1bHP~H%S@mf~xglNP)qeFIAGp!*(;?k>Xd}NaElQnU8h5 zYCRYG5hY!;YE6OqCGNjAPEfhiAh4KbHPm?Rn)%gIg}#nsh#fC~EGUdE@+MBZEKxV} z8S!@AgdE>#?1qy{twfUh=(xn}fDWmU?fc|&SR{eSaL~BjSY&t%S+l^XdR;Jwwz=@T zZio;jR#K8Rj!Vsq1odXq13M-9I*GJ3UW%J=*W^%ze&ju)e`n9ngHHK_q4a`r%7vI* zN;P@m6Sfbc2lPuVEPu1!Xr{aT*)=$9A00Qg_8=xxA3LARVAsQ#v*1f)aooE{Xo9#` zQD-bCcztmfagLYZ3*lK^+BolE>nab3FmV{P_4V<@twg_vg~nSqH<3Aed`cA+Iz+-( zR%bHStwwzJ)g>d>>LpM6Rd@fh^+D~ko8Xq#W3Lt8ufi|Cfwx6V+@p1nS9zgZ2U{bg ztQUv_*E_lideTB%>>eC?<8$t5FQt~bw{S`_g`IkjkW@`}k;|@C6bSb-bSZxTn(JxC zsL8OE6+Kcj=Ia_YQJQu!4XTm^9h|X9n;ypOg?Q}c+td4XEu#g@zWl+#f}Wv=sLxT3Ir@(<$!_V6M+Lae=bN*pJ6;F-nbiyL z>8c!!UWD4*+%I33SD^dlq-a&15TX_J^`5kF9=P{L=*d;#%EFZ3b223%_@OMj{!CvN z#n42%F&k~n)$;2D&%X84+EvQb6eD(5lm?3(7&=vqFqXHR)>~B3YHwcqKPHrJAV*8L z!`hT|+eA~Y!HYIlREs6RKKa}I6N%Xhko(aiPJ)X9x!_iIP3pq-Q?#L?1Dz+JtDnc#5F-G3e12Tq};Hag1-~w6We%#}a4HKbM z)GLzzXRDc}NJb(o^1LZRRt0@ONe}VqIHHxW{B^u?XC3>t)go_w#GUw9(UNM=kR?|s zw>(bcXq*k29W2LI?s7!;eVnzFBRK~v_WZ#aj&>M_DJsbkbqcnbHno|ipCw4ISCGWK ztJBX59Nq|dMTrr=L47INDcHD@+`dqX%*og^RK%ialXv>DVgb~GksT}DzHsI1vGiNx zMCd(3R7fpHUrV;D!UlRe50dPFQk}6q$fB_E_D?OQt1mqraxD{1jXaAj$|U6lA|u>R z2Mww_9C&7}2orK=0g^w>6{Hi;NY~F=@h6DP0i=0GEr?&u;XrgSKHYukKNV4=7?`|?eTuV8S zICeuXjmr~L2=fPk0 zM&fAIw(5`_HDOQpo%*xip=I0=Rv1Dt4KbG3=Spkp_|R^CA!zjF3L73Nq2A^DxBXT? z9ed^-+N=Ws9n zy5Akafrv=(C-c9BM=h%p^zD6oq#y;o)gjC3g^t4=NNfmNY_UFHxr^B!`DBFh$SZ^+ZupJA6WJ?+MaaV{1)`vCa2ut eTPVZFU{m@#FWXjmQT`E literal 0 HcmV?d00001 diff --git a/bytedesk_kefu/assets/images/chat/voice_volume_7.png b/bytedesk_kefu/assets/images/chat/voice_volume_7.png new file mode 100644 index 0000000000000000000000000000000000000000..e33ebbba23f24ef4db1e6fca29c3ae67f72f94d3 GIT binary patch literal 2442 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 literal 0 HcmV?d00001 diff --git a/bytedesk_kefu/example/assets/images/chat/extra_localtion.webp b/bytedesk_kefu/example/assets/images/chat/extra_localtion.webp new file mode 100644 index 0000000000000000000000000000000000000000..fcd628d13f9904bf48c053627d30090653d893dd GIT binary patch literal 832 zcmV-G1Hb%INk&FE0{{S5MM6+kP&il$0000G0002L006%L06|PpNTmV*00D2~rjaB` z+Q@mOv(7yJP;Ws*L_RT+Cc?`98nR(9ejGGLwy$)Ue8}+{5?MAg*1(XrLAGt%YTFz+ z%p5}))rOgwnOO?A4c`C%fP%NK#@LU|JrVt%0Qvg=*Z==pJimK%{@~{RMcvK8{AhPR zS!IK}FD6Uf9W3Lib@k0a-&U2Qjy)6MUOPV(OD1BiAjf|+k*iP5(1)>5hj_`IHFtld{ia`%nJ`?-wF8>{CD88Vg+(cypCral=JkH&hf;9|+dP^oA)v215zFCKeD zw3(Q;@vw>tru<}qI)5ratlH%F>hro3>xTiq5QKhL);(>yO0S_vh*e) zyCmelAeKGJ+Ec_!J_p?f7#0dpLxAh%U=-0K&>n-UH}Cs1-x+ZGfqD_2mnb`5)g232JlfnKsnd|OraF8$-z7zNMJMo z%yZBOa6OnF1MK6V2@w_|G(L>;nwV17c$eIwrPe9w_w|%&{^?h*K;KwuHUW!A=Epz=_J93Yk0Hg ztx-4~vu9jL%{jE?D2El7{9(tBl<@k^j_<V@6Tw`ktG zgx}t1`i0@?C}xD>#4>Ir!I8_`M;+t~%f%O%%M#(a@*1I4!gNiToz@7~$=hr+C2Xhk z-|kYvcQW{wASsMjU;Rz1h4b?BKb@Bg>qT3?Z59ddIfFaf0}1*1|JVQjZyx|wP&goL z0002+3ILq}D!>5106vjOp-m;EBO)M30I(7XX>Q?mu^T`W+VoYz41yY}O?KKY5M&V5 zQfs!+aDyO*s*_!|i-Z{jHB_4Iv|J#_A*!U;ZKB}@K@C+VyKNT;G6-s^HP8S6{{OrY KpTGbB00009^nh3Z literal 0 HcmV?d00001 diff --git a/bytedesk_kefu/example/assets/images/chat/extra_media.webp b/bytedesk_kefu/example/assets/images/chat/extra_media.webp new file mode 100644 index 0000000000000000000000000000000000000000..2806229e652d5065492b79a8a53eceb03b6dd51c GIT binary patch literal 844 zcmV-S1GD^6Nk&FQ0{{S5MM6+kP&il$0000G0002L006%L06|PpNU{O|009r=u#qIG zp?dkrKVq&Z9SD@loV3(9Y2t5+qUUg zt8Cl0ZQHhOo2mKjH(6)rq@DSA6Vd+(5dZ)E|M&Z~@2!iO^3gHF&7J~=flX$fU_9Gh z&=i1e$4l)!xoI#3^961~7ePSM&=-K;GM|WA1p&>kaC;f!0|}dOzSsqL9 z3)(WOPRfyF_(s$kVsL{{*nCF})pRSSdNTuqpGOQY)01ZHgY<9JU- z{_^Zp{o!=qMlf$DDSdXhTQje=lKeYo+cI-xFUh%bw0<+gR{qT8yOE3&C#y75Y-QYK zY9qv*r0hFb^pA-(v47$==A25_Ow{^-#MQ?+R*Qj?Kwoh3@F#0+LiV7g>rl3GEOiUr zOBUXv`O0xhPVrkvnbat797UOoC@{rY_>Katlu71;;;V!*sc?D1eHLZXqrg!TWdZXF z?8Y%B5nBqZM=~bK=L)RHF($ds3hX8^CgooW9Az;kb-xPSmoO$R8ho`fCfyofDvB`a zR^hnNK=JU=>wF$F()z2x??9w3uyzgq{!OP5BXwUI;G!|mQ)t^MJim1Z@gn6fDqz1U z?4RiFYv}iic#+(59q>5Y**^0Q%dWvlS|oYPg88#DS|noL9>7=(D-zJ-vIj29SP`js zum^q{Xpzhvd*Fr_2_Lb?2{9rycXsHdMp9P1_m#?x1a!W6Ye?u#U-r&x5qjm-5106vjOp-m;EBO)M30I(7XX>Q?mu^T`W+VoYz z41yY}O?KKY5M&V5Qfs!+aDyO*s*_!|i-Z{jHB_4Iv|J#_A*!U;ZKB}@K@C+VyKNT; WG6-s^HP8S6{{OrYpTGbB0000oz>mZL literal 0 HcmV?d00001 diff --git a/bytedesk_kefu/example/assets/images/chat/extra_photo.webp b/bytedesk_kefu/example/assets/images/chat/extra_photo.webp new file mode 100644 index 0000000000000000000000000000000000000000..27607e2a0c3984ca8639a93294357bfca41bfa0e GIT binary patch literal 512 zcmV+b0{{I|Nk&Ha0RRA3MM6+kP&il$0000G0002L006%L06|PpNM->500DpFplu{c zI;ZE+9Dl~A;}8+~@n51?xYbzA+OW5{t*PlvU%%P=)Ohu3QyME~hoTS$fOgxq>E_0@ zST$m^X0ZSN=jbKl-Bb5P^nU`F^ndC9(*M71)z&;jGjCV($PQ6;@TedzMIJ$9 zzwVd_{yW3G=iD46oW@fD@VG;^o(t4>%V;(bL}R~_Vk0p$d)7rST^h|_^-;@^MXS+a zT3K$;dP+~JsRsy|Gtp`O1;XA>3N!8x#gArbv0Q@C&vOE3yj9d5cN1cx4B<b3JfMn!Qc@W>%9HXf<%dGW|wRXmdZFa2No|EDolP&goL0002+3ILq} zD!>5106vjOp-m;EBO)M30I(7XX>Q?mu^T`W+VoYz41yY}O?KKY5M&V5Qfs!+aDyO* zs*_!|i-Z{jHB_4Iv|J#_A*!U;ZKB}@K@C+VyKNT;G6-s^HP8S6{{OrYpTGbB0000) CzU-m^ literal 0 HcmV?d00001 diff --git a/bytedesk_kefu/example/assets/images/chat/extra_red.webp b/bytedesk_kefu/example/assets/images/chat/extra_red.webp new file mode 100644 index 0000000000000000000000000000000000000000..5a0b66b480e3f21ba6991711c7de58356f9713e9 GIT binary patch literal 602 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 literal 0 HcmV?d00001 diff --git a/bytedesk_kefu/example/assets/images/chat/input_emoji.png b/bytedesk_kefu/example/assets/images/chat/input_emoji.png new file mode 100755 index 0000000000000000000000000000000000000000..db61921acbf55f6870dff35889dc6d8bf31c1b7f GIT binary patch literal 1531 zcmXX`3pkT|7$0&SMN=*jndBasQtA|`HkVo2L8*g@5@nC(cGR)EUEDSshU0#Q<ZpCIs# zRX$p3z^^^Ipap@bgk$`%hlo@riN++-SR^WoL}3%DEFx{$Q<)&9QnQGexkL&}X`lp* z!X)PAlIZ}v98uGlX=#bgUT#@E zA0?7b=PmEZB8o)doHHoQ5NR_PL*0Z*5MI7l%T@OIFIE<4dWS-Fw+ z){(`forEHuR$$b*pdrLwa%@nHDO^GpGDwXscCgJ91A`p`n6X){nX!eGYKLRt2#iGB zS;M0$$Fv$v9&rT5-nVUP)z0(W&f`7a`Og+@)q6ZX`T$XwHJh(7BJXaQ5eB59pufe( zxW9{vE4%4(pSBP$bel1#o_+cK;kSd^UE`q&d_fn1_%D2{DdAH|n4;x!N0g4HAfVM_ z+XM5mW;p$P$L?ge;^f=%pN5D$DXd!sH?+ZeT;?h8hYle#?v}M!0f@%N9fAU}Z`P*0 z7Z2*n$iF&9yR?X7*28IMf}~<6m%$g;cREP|_XNCpc>PwaI=kk}VD+Jf&=TD_#>K8h ze7&lLu3!G9x5Jyq6QugKp_#Hrue{n7T|7sv_NeG-A720bd`jayN|!^n-P7Qosl`|(>{4ZfOA2)+2H2g zCO!=IjKMhfYuhRkoWrwCD}CT$*=vG_AUrMchTw?7kKWqbW$tT|J9lBm_sO)?lT$3P z5#f=H($LORm+Khz9;AcT zB_Zwe7Qtj2#qwHzzu7Q#x^j~$VL!tfyEiE+?PuXS@r1;v;HlP)@^k4VbW#oxJg z1vv_=6_mek<5XSZcvu*py0u$$;;l4fUzhWTD`*l&^xmNf)%c7%kM9}0GT9$^DkRm^v*!c9QG{cA-3*gzVk14`rR}dD{ql5!;=h0pE;5r&z_^w{m_S&XOl=v0=Vj!qPXrHUz H5#;{?#&PBZ literal 0 HcmV?d00001 diff --git a/bytedesk_kefu/example/assets/images/chat/input_extra.png b/bytedesk_kefu/example/assets/images/chat/input_extra.png new file mode 100755 index 0000000000000000000000000000000000000000..eddec895675c645c2735331db54ee3b82310a300 GIT binary patch literal 1075 zcmeAS@N?(olHy`uVBq!ia0vp^nIO!;3?%tt{&E1RhXFnzt_>}njV;}ctz8W*-3@I$ z&7J)XEnSVRoj?+Vn!B3XdpkO20@*WX&IEFSQvd(|2kHVVfRgR))4+O#ljZgUPOjwqCHtPM|qZ?O+A1trLLU<}RQhNHNeo4Q)V;-9XR?QrX$m(FZaC zXeLMjNPA-|g5A&t7Hsba84I$mtF3KH!>Uz4FV;7APCl3-1!Q@Z1o;K;2+YsZ&@3t4 zdGDhk|7q5xj*R_3O!mDJoc_0+%QQ7ed3nLPRcaaUo^E({>bLlI)h$K7UJl%ub965` z9X$HF`u>}(0WTCL{QM=%#*p)P(MbjdrtO|Cjv*CsZ*PU?J$4Xa30VJR!JM3@bBfR1 zOnSHLf1}uk>xY(UOq%tU-?_ArT|gy$S@ygR?fr389{evAdzYO*wxROo1^#o zx&L?864w^0b6F*F+G(ldRyKyW3pd?amwMNrq;hYp)?qV-E323TFV);(kNTR{T({)n zn(Yn$D)gLYy=7b*Z1lpbC&-=o+ve(}PW%=!rJH){Y#8<)vJJAD>{`BQ!R$z%T_@5m zty}8N6{Y9DZPNKnkJvk&ZA>chC(7a`H6-nr@Wdd5-?GcyHKI(1RrQDciA1|U6Poyz zMlkp*7R{N!-#A10<)^-kmKYVGqrZH& z2ib6WZc~*^{Gn9hu4LE3YdwjnzT=(O3I3yPU)WcDE&syFFn`*W_O8>unsb|`>ol*Q zkh;fXX}+hV%FDXWNhj`1;EH@3+45ykD)+S@eoG<6Kt5aV%(lF^o+(p29XDjEv; zZrDEInT3vvXE*b+>q`y$RJ;}`9}8dV7=QVw=dNq=F_Txlm|)RqwIXhEmPY3kuNAQq zPi06dsV;8av@^tIu8UWQ`^A`53pB&B7A5I%-cXoYmI%`&#qGk8R(a za$EQFT!tThra9`S`!1{B?wnQWb-@1TgZa-s_^;5I+w{EdWOK-@+({?QWS`FdvB`Ji zO|{Q|a+&L!lkRf;JhH{gT0wm1}QXzjpg{bKmDbS3Qchu95v#>7Z|^rxySHp{eBJ#QindGIzli@UW|$;c$MG7NF&I2u{an^LB{Ts5 DbpZsD literal 0 HcmV?d00001 diff --git a/bytedesk_kefu/example/assets/images/chat/input_keyboard.png b/bytedesk_kefu/example/assets/images/chat/input_keyboard.png new file mode 100755 index 0000000000000000000000000000000000000000..5c53793e3ba8cdb2dd86a7cef854ca3bd6465f72 GIT binary patch literal 1150 zcmeAS@N?(olHy`uVBq!ia0vp^nIO!;3?%tt{&E1RCjmYot_>}n4Xs@bEnSTuvb(vn zzoD%M%7IkUMLWH?9x$ehlGHlR5`5@-d`WVlpUYwHA%Y)e;j7f`Uf5h&XRG^85{8bQiB zn>zY{T(CJHZeuH$4KfOBdwV~~G_VP6ZBszXR;>bx0DW2C+?n{wJrv0DE(!7r-jQ}W zKTlJmq;zNMrH_X9_)oJgWpw<}Z?f-|;Pm#trd-Oa-kn>nmJw9&bi=b#;=i}67WsNP zaA(fZbvpR^X!ZR!0WTCL{QM=ng>5UtMUPE885o%UdAc};RK&f#b-d`dfe34W`lRlv z_j|wVtu`-{`u_jFdbf^1zegAA!9$C83$b?_FIMDuV7o^k?&rr#!Bt-Vnfq$)%Q;P7 ztrM@m@bI@<%m4C0wl~BI#23o0F`laSSB!xt?NZu);n3Ldqj&3+&$2OGc;z4AnVrjJ zB7OJV-+<+}Gnj&N&pJiTWk|ho`RuW;_W~IGwATlf^2nUm+SpTN$MkrW_{xY?30r=s z@PFPZu`oz$*R(uVji*u@r$$9OK78hPNmb#$SI%=&eve(;o;}Xr1l>QL?C9=W+R>2F zXW^sArT8eotNo*pa-T4_!kjXdyG9;r9o;IEPTZ04c*(O#^O4p{9iz!Wd z3C^CRp)j*Zf9WIvm*`1y?h1}JDuNC%+m$^#j&&?QvS>n%hxrN1M~0S?+uMUSGT48V zvFAGK9>iIBNTG6yoX6F!Psg09LbXD(Gkc5@Cm9`^c8P1Tl=MooL#Mo~58S4Q+_h1}rFp{?4`#>YmA!fk7Yeh^TFgF6@7A$N4Sb2S_*k4X z`xq1FFg|m;BxHQ;5|6j?OJ<>L&qKQ>ZaLbvb<4qP9##qGqi#J;Q2BeTkE#4L+uvNf zg6XnOZkJe<6qf~0|7Rw@qr0PfVoB?%sGvgANhvSZsLC3}cmCgh{C_V;;_;|kiYZ3L zk>{7&^8MN8_GjOQKViJ@v=#qfo%=2#;%L?JPu#rB4$W&b*n*f39=GWB$*_^_Gj(qNH_4>8_0L=ic*t zy8LeL=e{SiYoGU)sq8F|{8`@jv;6SS@*h9%M?I()ZvOvPXx7h9kMBzx%b)(qzd!%W elzEqee=)CEt`j0q=M)RfU<{tFelF{r5}E)sE+9Ps literal 0 HcmV?d00001 diff --git a/bytedesk_kefu/example/assets/images/chat/input_voice.png b/bytedesk_kefu/example/assets/images/chat/input_voice.png new file mode 100755 index 0000000000000000000000000000000000000000..537bc29470b8ef80842ffaf3fe75c0acf8835751 GIT binary patch literal 1957 zcmYjR2{@GbADIl^m5E+keI!yfX|9hDq)b zGjd{S2Q@nfXK-Jjs0~fo6X^HBqNwq2&MrOfZ&Kw zCNnEDkBAiz}NxfCjkLT4s0BrZJn+o3@c0>J&Y1-u|crLq_d4j>O&KyN}4 zo1AnLkf+dCAWMTOG1+YRh9H0dU?oy8lE0u)A;1LC`@eN)%!EXs4?u=z0ZcLjmJ1v@ zJq_ScfmSq##bQw;oZ*RIjKma>CG&Wa5#Pj-kOUE!4_FGIeU%Q70Zd?Vl9E6Rb8~YO zxQU5e7zD%_DLfcTVuLPF45k2`K^c|FU}V5GK)^3*fLka8AHaX9(EA&*hk#Xjg@E(C zS6CJsLr|yBo<(XDB9tn)FUhEs_aDEj)Q z)8Eadtcn5>Uft{(Cup70@h)f7x5&=4*-|3{cC20Ti@I7xcz)za>q)-{$i_RA*LkhZ zHSf->lDbbnO0HV18PfLT)`rYjF1Z;lSz3rrAMN>F>dYlKi%Vy0M(}A&{|pUYu{l@S z*n%5;^y#Yr#i$F4gLmx(`;&e}P5)4cleW~t{*4?=cSH4DdCWq(c1*)NtbWZ$aV1o_tAM3*M|c)$y=oh z-otQy{ndek!#ZvUXsvu{6YHev2y9V{oYeK5B0PIhgYjR z($`E*MQCkg)b+}ttHMicKSc3=>Pm4@R&H=*YnfKKL#)sq6`NWrR z7x2qwVT`ec^sVYas_%WxPLv*u;e~3FG#ol`p$>T`dv9=VG|lRZNzDWu9m|lNyL?do z+@*s0>}A7-xMq>G{ybmd+`Wmi*Ku_;Y|G<|?SweUD5%=spdyfI&8{3pXPN0)g;aQz zOJTYn4(KN+Dy;1QrBn(ZvI9wdUnxPj_bE%&)_}{w;Y~}+ii5!A$Wo|0wW5R&d49h-TLx%k_K>em7{A!jFnEY>KH- zbNPu;!6iy$`y|f&*Oiz?UO|gd4D{;@MX;# zS?yloo31-n+Kp?*73x24uz3CC730m3GJP@s)(15YsepxuIdM1m&qZJzaF|Lvzl8q* DFEGmp literal 0 HcmV?d00001 diff --git a/bytedesk_kefu/example/assets/images/chat/voice_playing_1.png b/bytedesk_kefu/example/assets/images/chat/voice_playing_1.png new file mode 100755 index 0000000000000000000000000000000000000000..009de3889bc1517fb7f4e8939f654d59934b7a45 GIT binary patch literal 271 zcmV+q0r38bP)1QLxKP&or7twg0D(W@le=|nU-!YLfUDrw#UbW}(rL$TR>+bW*s z{pSF`d^4FD&r?b%rIb>C4P{Ibu^Pd2R9Auu;RfX?goCN&ZB#o znU~CSjtqQ5kp~xWG<1tObKAgUCS1bfFQ@#a&ndj+!j}}@FxddF6kaf4dzXhi);AmN z&|tAS>@y!lo&|R@Pv)Gr(KEw7QRTsX14rDe6esKnA@}j{F~J%m1fEh#DW#P9wGRn8 V`V{l6M|1!H002ovPDHLkV1o1qZ`%L> literal 0 HcmV?d00001 diff --git a/bytedesk_kefu/example/assets/images/chat/voice_playing_2.png b/bytedesk_kefu/example/assets/images/chat/voice_playing_2.png new file mode 100755 index 0000000000000000000000000000000000000000..399999064d8f3f258ac43887e8e1d255c74472c3 GIT binary patch literal 487 zcmV^3PEf{1SS3it7v7B z*!hBEDOjl>NqnwN81F5nyEE0C?e2m3*m>MJyMcg{larH^lbq0xDfHUMZo-F;A1vC% z_P|%qv`z3R0oTxH58RFlLEpo(hfd;`px13q_Zk6L5i@~k#VX1uBFU;j6bbs+1h}Av zPqTU_K9%!@s5CZC-s>4{%-bQ}iZpnT4DREAc^&vr&Vw}A!y5sAqno)$#OsnqPq261 zXWj^LQu5m?ONM_%Gu!d+%Gs3%2T>yEVdh<``h|%jl_1~@bM{pI(FC{!Rn00d{V#%< z7I~`xr>s6>8hob!CmQgR32d0Oio9m3Z%lycS)SEwu5)>8h)lGP0nKsRmc};Vj9nCs zng%i{4IXDLGpB8t;uqrvi*_;1B~g-J;S>R9wbx?C#8E2O2fyZdk_3SYj|g(RdI5)+ zM_3zua`sXt4SO$$*aI(T^yM@S-x+x=oJ)WhGH?JwjI(%8a~`$gg8 dZEKtI7aTj&XR zCeR1%O?Mu;2|S3Re*r1RW)x~q7hr(U?FjmfJq8C3hOt{yc8clVRnPR{ltc;O=-tit&>ca`yc)7o^q)oDbUo1f1NDsLX}_(@h#(#j_8qcpfuka?ueOD)5Y6jld$*b22ph*lTi_L< zV=fBV8Ym?2R=YR2zdGrs)~*xX&9o7xQDbcrrABIakDmzF+<{h^$;1Ev002ovPDHLk FV1k62fhhn0 literal 0 HcmV?d00001 diff --git a/bytedesk_kefu/example/assets/images/chat/voice_volume_1.png b/bytedesk_kefu/example/assets/images/chat/voice_volume_1.png new file mode 100644 index 0000000000000000000000000000000000000000..8ed01f5aa452e19af0396cfa636c1f36d0f128f1 GIT binary patch literal 2100 zcmZvdcQjn-8^>q#Hmi&1ge2-D5-kQXi@`8jiVzGDQZP1pC!z(*u3my*l?Y*#NKrQi zA?!+&iEau;?}Lfio6I?m-}&ux?!E8#`F=moy??y-oHxM|g@AB~aDYG{h>5X*HL$jR zzu6c;AaGx`pBB)7eXJ31P|bkYBA}x!&8`^&Chfn9L?SILERf0MrKP2nm6g@i)wQ*? z_4W0QjST{U0HpKt^FTx-5&=zb7k?Bm0u18ucmM;Q#{iw7&(c#MqNns0i0BU81o&eN z&_H^!0OsV>v8FRX3lyCJ#L2<`)W^*Cu@n5U{$c;md+5$FaPqq+;gcF*{(lN^#0?uX zfo+KNwMN;1zzmE`%q(mioLrEfxOsT__yq({i%UpK$()f@Qh{n{!*q1@;KrtAC<{xQ z>o;!N-m>TQ#C*T`I>gZw?K_Ex<~*WbNS#<8-l?R($; zcCCRrorPY)xOJwdPDwM%yTK~Zq?^B~%C{@XxIR|;9C*u1v1mFdgjcT2l@%2{_6m#^B4xDb z61X+4_|G15Q8kF-2(pvNCwl(PwWHl+V*$FkU9Z^WMAX=3z)*7S3o>&yW|qfQ82%B2 zzetE_{**rc!t?In@YZ~wlSn@;WFE7qsTA^S_+a6~!NPiSQt{5R)j+`*3h`32|FrU+ zBK)W>)P?gx6*Xfs$V98Z^YI^AFE8&VDXK5KXUP!pjSnR={!$5nj1=Y`>E)+XQhP9D zgO|>TxT{hhjG}4ogJ41jSAz@IJQ&^Kpz$m!HH+- z-HMMxRgBuK7Odwr=`E48bVxMO?n`V;3{h95SU`X3{-oIFSc?!9uc}zuHpI^K*_`@v z{lHW^qRa4rtM5%1Q6wyNMTd2A4qCg<>6ik&U7XcVkqe-txlv65C|hpSpa6WMwgo0o`>a{j0&O%|fZGWaY7i_X9Rd)CKADF0x_eSkhjTQ5VyEsM; zNm`?tG|4U`!B|kq(|bgw^t7N;R}d>oBOY4j_b2bz%Hc|%#m(WY|*tKeGRIHn@LQO+<|_!t}vrrS8aVy4icy z=%8FEm^{a1iGDMDl*RbLVd8;n8?l#(xbVg>vdgDr$~@{Uh|dvKC)37FUE1wF)tJtX z!9pF9Jw|j$2q_-=zWi8(EuWMIs^&qjBlkU2Rbmc|gDzkXWNLUgccXT_v7v9dRGsU( zEN&ECQb=P+WYy-`X6ZDnjVq2nE5Ljh&rz<+0*hxab7ax&fmd>8Xc4nw$USrIiOt5F z^%I-*GQH4W`k)E2l9Z@TE8UNX{qvH<3x0(Q&n=^nVIA315%6g4;I(a! zL)5EK+&%qAVq_nqM86!CHq2JX{X=Bhqc3dk9#yYAvwf~{7D|WDy!GF}8);hC$+hxb zwr z6*y%6!`sOt>ccUZ!*Ak#Izx?@C&ZdYfU{?+#m3v&@`;fg3v$g;>L!d%`(YIlXaJq&9Df)`RDn7%zj;9KJ`J$mmMw zq)uarykB?aJ0xV$3B|YJz1&#swX}0lMuFkn&@&cC(M5i+3=CT?@v4E%DF~*9I^be| z0nFOj2(IkWsd)wPBkh6OEH}D U@tKkY;HUwa7@`bn;ONKy0x+YZE&u=k literal 0 HcmV?d00001 diff --git a/bytedesk_kefu/example/assets/images/chat/voice_volume_2.png b/bytedesk_kefu/example/assets/images/chat/voice_volume_2.png new file mode 100644 index 0000000000000000000000000000000000000000..701f614210707946f0b4cdfd2ab7dc904e873ba3 GIT binary patch literal 2177 zcmZvd2{crD9LHyd>`d8T*^L&AsnGB+mPZCzl5Az^c@ZL>r3TYT^OBt;sWc+A*h9}Y zCYhv>dP!u+KEqgNG?`(_yS+JQPUpS%JNN$o-{0r={oZr#`QLMHnwyKgw8Tyc5C|mg zfVOo9cH;M2Ob7%5Q>x=kfd(AoZf^ss=~tNtbb;Fu4?6&E{a?{&w7I!C27|G%u&}hW zw7k5$va+(ey2@lSXJ%%ADVbqAo4E`82ChB;70^d!1?6I z2mY6YRjTIz2XiIX-Q^ev3=tCECL$&wDJ3lf-M&LsPF~?BRW)@DEp3>-0Rm}eZh^A2 zu|qo^adCBXKX%;H%iG8I#L2)@zXgYcM#sd(pT{KOk}qdw=UmIbb^A^+q2yj^MdgEs zkDfF(HUHV(N$MiM=;`h2fA@Z9_~Qt5bZmTbYIl^u8^6wBr4ucZFW$v?w*^AOV1yC4hSZ2624GgH5nsz0GnH2r4uq9;!$5^C&qx52@N#{c4|TrNPU(!6dYRH6(*eCa?szIOtZ+-;s@w^DzeIxMvmmm8 zAl-?D9~-8)lT=9dX7Ipm6T4a-#?=j7T|p)*Bswpqyfk)RPAM0`>_aD2kg1Z*<1*@( z3|N+RK1k_Nt^x1QXE(^9ucs{{R(ChL=UyHn+_#yI42K%8hpF_%rD*-U}pMnhCb(Aouq;F~(l3GbJD<7v;2Tlo*wXLaSSd z7EdAm;yOjXL8z3{*&Iv==U4)7UkE27foB)O8DevW{O+P4-(MEEps=lR5vmf*NJVCN zdt~3aGUb<+!#o1`ut~!#*o#Ix;V8!!iC;>CcT@41z{fZ)@Q2A@M^de?L_o? z6)VFsI`Q>9PR)i0#lXMGX2;BfsY&fHW-wE>a-fG?^17KrNa3A&NaSrxuKcn){++Q+ zS?-2gIp-bR*#w_})0;)7geWa$klN*ij+xmH1WUnoMjq z%ddUx_mQruAaAXY8sn+5Esi>cZm6~qjX$KQ%VX<~MsmJe;=Q<9q35FRYvuRi-)J@j z*HQDBz1ttI4GK9EgdTT?qucNwNVWQ==6UK1>wQfz^qSK|O3fa>R7c!?1s&&s5*(H+=%a-Anc7v})8q{lbit@r~bfv6Z@$)pqjo?G! z25Z{e#e^aM9MtzsYOhU_R)L-;qu^JAf{9f}JjjZUZLi38-KV2|2w>dy|e3@$Rb4<=E)>-AF-1vlRpzMuWt(~ap|zkC-S z;Lxu$h(uqq##&>8{9ygHC(M-_ziK&|-fw+UF^H7uGljLeM*B}Zy2ITus9Iel8<6ox zJo}}Zm&Hyay%RRChhAigvkg@d2u4mr01Bp=>ld}64 z+6JI&pPA6~I~$Af&tFnQ791C$(-(ctC>Mp_>}{iLbVjKWc{MxEMRvRD>5MX6;T6Q# z&F<#rw-I|H5lwj9&yg6m&Wf+NOsCOcM2Gp=5at{bNwA*quAp{@Tf}5e2L7Z=_J)qI z-j~g0pDa{&&WgcafgQa&vmp;029%hDa(Hf@XXz^K#6n$z9`Y3TLECToezUYNzFRW^ ie@U1Ud`b8qj=DEasyxmu3l03GKn`{;wly|^DSrd?cE@l4 literal 0 HcmV?d00001 diff --git a/bytedesk_kefu/example/assets/images/chat/voice_volume_3.png b/bytedesk_kefu/example/assets/images/chat/voice_volume_3.png new file mode 100644 index 0000000000000000000000000000000000000000..2b57dc2c549a94c841fd6543cbce90ce0b5f473e GIT binary patch literal 2236 zcmZ{l3p7+~8^>oxxnGkeg`r5~dJa)CgUI!SLGG7|RC2$^m>D`1(vTWoWX2TYlO&vD zlE#UVX*7sF#gW^LaUT@TU^x4nS>LSIch2{$z1RDH{?G5f*V^mdYrolUr|o4V_ep|4 zAQ?voTO_c)`tueS0fE5u+Qef(1CB@9+kl=jRAvB8aC7mr1I+Dzs;Q}|>FMd&+1a_d zxrK#=#l=MqhqJV_w7k5`WHNy%o6QDBEEWsUf_CPw0wO?ga&i*D00t03Mt}jnz!5Nj z7PbOM00?~`B_sh2OusJy_I*&Og&5!nOaK;WLHj)*)c+%dq#!7q{y!f81f&oUM1-c$ z7dYQP@b6ClK3^QLL)i()(;gr&L}bTKQE^ErX&G5Ld4*kyyOoqx)nMue4r=Nd7@C?{ zoUnu=Z0sDITu!^XAw9g#c>DPJ`3HoY4ZRS4DKo)FAA-=feK`F6} z^sw@ARrS9c8k>G;eo1M2P3?R`>t?(k82tEYX!!S$(ea5%CTog4J;PaA`Lg%*cPAy0p6Gn*Ji~)j2P@|Z zI>=pf>!DAPmHx5A*)kfhMR^sWai{Wpi6heX;&T5@zTr_G>fjVDL z_k5`Fa(3;2Vw=+Hd=4v(yOTEdGb8*|C+AQFl$+P2OM9_!K>inb$oV(neJ`L9 zrWyEemA`RElQxTQi*#SjOu`XsaYWO(oJSv~`NJ%x(~weDk!Mx~kgMYDl=;FW`jm9#dwr6L`48D%s(#v?1?NB&SgQmtnDyg)?~g0V|;l&WzV2Yj4$TfEsL;jC(#;EaF-+bdaFx1 z1@z>23)R3eK#{5UueyA8R!6wa@m6MYVv1Zs!8W!&YJT#l0;+|CgVamD(qsH5@N>f< zeNt(rN~?y}%h-W->#9NLMZ(gL!^FfS7aR{wY@?*VR#9RT6R$uI!#POG->O{PhH`xt$ogtUWT`Q_qcG zT#A0qHM1nZA%DIva3yPQj~*I^YBo|3V^+<_C|T#!(cxdvJEoPo_Uu+&`RrxbX5X?& z2Z>djyiO$+K;$5L!z{|oA%~~BrIeF2NUo}thJkNiz zBy(hQxcuggDrNcglkaIq?E||XbjX)^DpWtfB>cd(VOKq%-ol?3Z@@*}56z8J%L&h+ zx&)x+FXyVfUuaH7sP{_`ZphKM!~*uh^NV|ul`UJkihqN9cWMWgGX`b7QL=Rkbx+r| z8YMF(Xyf&9zLd(k;N}=hzE`Yel)2}6R*g88<+l+mYoeCJZ}WFHTb(ykF1VokEM8-s zq3;w;E1mliC1C>2Uy93C;`z5xqEH#`63S~Aw$4RdXjn8x{Iyr&I|PgEjRU*0wSOc} zoH8zQ>^w+1id4JRN4QCUlNnitiov!9h)QK`?Lqim?>vlK_c`H%b(J#m0Yx0<_Fa1z zqUt_cjRVV_gL@c4V79^4a`((xMd=?3s90z$g4Pp5@8LRT6!>TKxvpSghheNo>34A} z?(nzvJVC>$2!S#UpKooEESbMf`IV*L-L8E*YLp&ww$Q(I?E07E8+* z&?jAPSkB$KT4~U}3r2wOeiPc;?QI3k#-zHY>b3JK$ kr3(g?|L>=DErJURUoMOP)x3fToNFLQyVJH$Z9+2t01gA}(f|Me literal 0 HcmV?d00001 diff --git a/bytedesk_kefu/example/assets/images/chat/voice_volume_4.png b/bytedesk_kefu/example/assets/images/chat/voice_volume_4.png new file mode 100644 index 0000000000000000000000000000000000000000..7eabb924a49296f4d3936f1df2ee68e4a8355e1e GIT binary patch literal 2292 zcmZvddpMK*AIEQ$)0|bJp2&2-8pRxPSV#;Z=XjDHg^CH{*{ds?0@9+J`_r9+Cmb0Ucw4{n8007b` z+oLYv`tF}wTm%4w+H0;DgN@K-7aL0e*R^vB)Q_DV+^j*9|GS!;oMbQpqxTCqWHPHS}^*pzn=Qnwu1P^ zO*ZP^ra<6ONp}QzRB4eej;?@^u*jy(qT-SeDQOv5Ie7)eEnAiU*rlqb4%OHLgX-V&QWw6wN;?&#|MI{0m9c!WeAoA^$l(kAJQDb^f&;n&jg z3U`gS3pMHp0HXe=qZV#a!?UN>ton{^cYNexZN15wRMAy-HT6>Zn7Z41`8n6Nucf%$ zOKB1b9$Bl1fRH;EZokF+%&{NdJ9{eE9v<>#l%BkI=@%)tJ!TB0h3d4DbaBHtTcqRF zTWpWm_fmn&M%qa*Ma)T@e=mL~vr6N9I@cuvS=IKmUDAst)7vWAqoezD+ogNwLdWsI zelhH}B$XzIGau?YRhlP561C1AN<5o$K^CYF?F&|!Kzz7fD_<8fvvsbuKEG^M@yIFR zu)A3D<}f;dFqBC%hCLg8`2;YbZ3;VqBm@y;kWgh)y=8~0<^Hk;4YYra-HT9ycp@~h zi8Mc??|P}aZmXGVddWnM{&qh94`Kxcs4m7Tmt>bbTTb{H$fN{5UHuV+xc;)&6M6Ht98J+JF**iW5K);+3o`iLt8TzINA9+?%l$tjPh2rltJGL9^ zzaK2%;V%%PdB*PA-}S`gSQaW zLy2Ns0OQ3LyBd zv8PA?FnZ|1my{wY55PP(*F`$!)-qbuE8MfCOZ!iMD(qf$7Cqr(po6g)>&=xYNMtrY z;dO8!Wv_f(ZE2?HYsq&o$A0pt>$U@>xe(*XJdN8(&6~*f@7<{rrvvBlF{W{ws5a<-uCXjG#yj|QR?p}t7BVrw zg~OXM+=`wh@Ws@Fe8UgdZ`&wsyJCZXAu>^O$?>Wlg!! zZ6rL~?4gO{${kNqvHeV&)u)Bs-FFNpG%~fY?IZRIq3$vV563x33^P;t@1r}RQz;pGtv516t+O96#1YqIK025 zLrY4?PComC#QuG?!iQ~UVF7hJt0p&FBs1Jl2IuLeQE!}W+kUm-461e);bNBJv^xD7 z`45;xr>431yjWcG%#g)^)S|d@Nn}tX`Tdjb_e&qE%h3`w393;MQI+HYoj%X5;>JU3 z)|btKjoen#rxk?lUo8^11etE(WYp*3b`0V5)Q`K5Q;+>D^mQK1F(FgP3PHkZ6Ep>F zc0Cr;&*PRU9Ab2GTmJ3{==Vnp&P`mfq)4YaYZN;KLM|o+A6Cvx9JYi_~j&V{^atr^tuDq*hQ2U4cpjR!8H2`aHx>jM-pU<~>=2VR! z+&90_V9$Bj&ttc)4~nC zIhF8O(#Y`1!H2lnn}6$RJnk@g)RGrGEYVoXak8sv#pS=rJ5NxBvx){>-;!v_(W_1O z+1lR}O<+*gJ+D50qVin3rPY!{i-Yf}zlam0=qdL-qYm{Y9(E5YEUAijQ5S;?0J*(Z3d`KqWMU3+S`OS6K!>Sr;f>O|9rW+4aotO^w;tD`$Vsos(M f|5qlSsLK_jMp_u$$4du*zd-6h?d`DGe+ literal 0 HcmV?d00001 diff --git a/bytedesk_kefu/example/assets/images/chat/voice_volume_5.png b/bytedesk_kefu/example/assets/images/chat/voice_volume_5.png new file mode 100644 index 0000000000000000000000000000000000000000..3ee78cf676360324dff148900ea0606bb8b1e68e GIT binary patch literal 2346 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 literal 0 HcmV?d00001 diff --git a/bytedesk_kefu/example/assets/images/chat/voice_volume_6.png b/bytedesk_kefu/example/assets/images/chat/voice_volume_6.png new file mode 100644 index 0000000000000000000000000000000000000000..74a1cf6534efaf2e35e86e560ce4cc8a12e15a04 GIT binary patch literal 2403 zcmb7_cTiL58izxZ-c(o=2xUPoC`4QZsi8_RNEMXc6c7ZYNJ|J!+9D!-l@de&0V$Ct zQWA6p=>ic!iCIWO??_1uWY0A-jN{yY?l*Jhd*A1Me&0XeIdf7i%}w}uPV#_2AbwMr zku|V){`cWx1A$ojYwl_Q4NHi%i2yIS^!WrUp!ZUQF%h;9(7D!1PcCICBI*X3cDW77puQieG$Yd*~dV^9u)r zUxLiYA^g9BfK1%cNCR#zGt}DL2E@Y3#?Ensi-(twUqDbu_~^0YA}1ulQqr=&%gHO8 zQGr}g*V5L}g&G)}z%HAaTUc7#*xFsYZtvjeZQ;G5Pt+6oEKBLn2dV=jIpI z);G7lZSU?;>H7!AlkdL-fsS;V8tGq+_`Kr0?>cr#!o1Ad*x1N;Dot~vY^Y^2rH;!M zsTg3>gZyMZAyy#m!%-RNnP<0>BTHmN^vd7L-l^@C&sUq?ibnYa2M+6mQ(ab-P30?t z#1gHlpwCOXuc`LI^x!j!xRYJ(Je-m5&u!#WPM+!@Tf*f{;pF_R?^ZfqSL|?A;ScT9 zVqI|HreMVNog?WypvSo|K1bHP~H%S@mf~xglNP)qeFIAGp!*(;?k>Xd}NaElQnU8h5 zYCRYG5hY!;YE6OqCGNjAPEfhiAh4KbHPm?Rn)%gIg}#nsh#fC~EGUdE@+MBZEKxV} z8S!@AgdE>#?1qy{twfUh=(xn}fDWmU?fc|&SR{eSaL~BjSY&t%S+l^XdR;Jwwz=@T zZio;jR#K8Rj!Vsq1odXq13M-9I*GJ3UW%J=*W^%ze&ju)e`n9ngHHK_q4a`r%7vI* zN;P@m6Sfbc2lPuVEPu1!Xr{aT*)=$9A00Qg_8=xxA3LARVAsQ#v*1f)aooE{Xo9#` zQD-bCcztmfagLYZ3*lK^+BolE>nab3FmV{P_4V<@twg_vg~nSqH<3Aed`cA+Iz+-( zR%bHStwwzJ)g>d>>LpM6Rd@fh^+D~ko8Xq#W3Lt8ufi|Cfwx6V+@p1nS9zgZ2U{bg ztQUv_*E_lideTB%>>eC?<8$t5FQt~bw{S`_g`IkjkW@`}k;|@C6bSb-bSZxTn(JxC zsL8OE6+Kcj=Ia_YQJQu!4XTm^9h|X9n;ypOg?Q}c+td4XEu#g@zWl+#f}Wv=sLxT3Ir@(<$!_V6M+Lae=bN*pJ6;F-nbiyL z>8c!!UWD4*+%I33SD^dlq-a&15TX_J^`5kF9=P{L=*d;#%EFZ3b223%_@OMj{!CvN z#n42%F&k~n)$;2D&%X84+EvQb6eD(5lm?3(7&=vqFqXHR)>~B3YHwcqKPHrJAV*8L z!`hT|+eA~Y!HYIlREs6RKKa}I6N%Xhko(aiPJ)X9x!_iIP3pq-Q?#L?1Dz+JtDnc#5F-G3e12Tq};Hag1-~w6We%#}a4HKbM z)GLzzXRDc}NJb(o^1LZRRt0@ONe}VqIHHxW{B^u?XC3>t)go_w#GUw9(UNM=kR?|s zw>(bcXq*k29W2LI?s7!;eVnzFBRK~v_WZ#aj&>M_DJsbkbqcnbHno|ipCw4ISCGWK ztJBX59Nq|dMTrr=L47INDcHD@+`dqX%*og^RK%ialXv>DVgb~GksT}DzHsI1vGiNx zMCd(3R7fpHUrV;D!UlRe50dPFQk}6q$fB_E_D?OQt1mqraxD{1jXaAj$|U6lA|u>R z2Mww_9C&7}2orK=0g^w>6{Hi;NY~F=@h6DP0i=0GEr?&u;XrgSKHYukKNV4=7?`|?eTuV8S zICeuXjmr~L2=fPk0 zM&fAIw(5`_HDOQpo%*xip=I0=Rv1Dt4KbG3=Spkp_|R^CA!zjF3L73Nq2A^DxBXT? z9ed^-+N=Ws9n zy5Akafrv=(C-c9BM=h%p^zD6oq#y;o)gjC3g^t4=NNfmNY_UFHxr^B!`DBFh$SZ^+ZupJA6WJ?+MaaV{1)`vCa2ut eTPVZFU{m@#FWXjmQT`E literal 0 HcmV?d00001 diff --git a/bytedesk_kefu/example/assets/images/chat/voice_volume_7.png b/bytedesk_kefu/example/assets/images/chat/voice_volume_7.png new file mode 100644 index 0000000000000000000000000000000000000000..e33ebbba23f24ef4db1e6fca29c3ae67f72f94d3 GIT binary patch literal 2442 zcmZvddoE{yrBDW~FR17O2rAUfgDw?8M zgtB5$t|84B!QnZ|6A9@B4k9v-5er9?!??^Ur%b%Y-`H2nk3FfIuK2J6lT_ z@WlQ7;pYK?z=Y~317HB7U^eEUn(jk0KyHRQBCG(&`L{DQHASINW@l%qROj+Ue~_k4v9cJbPYQ`?|j24Yu)Za|^z;y`%HWu-m5%U z(iLvg4xtLGUFxo1R7+PCbp*FxapTeM@U^wb&ur2d(3xST#i`kOD75H3m#@rsJub_u za6B|Qk+11{>wM*D^~~tNlqHandhW3|XKsHG!_S#z`F$uNOSjQ=Q`P)*QbYRtvY-!LDEp zUct*A5TmbN86|nU9~ODZJ!>Sxyu?~w_794kO|ZOdo7_M;e+nK}6&>A|Tf0GyRd)(% zlM%L2tVGg6g;7&6;VG*Fe;xYOFAi;k_lisS^z2-v3D{cRU%Yf6)K6?uyDN3WQ*@{8Cs*cB{Jf#ml_PBo)kqN788$dtWWhh@oKqLZ0UD5 zXwPFat+#txT|$#g>oh*|wQ|I}1NZaq-gWJa2Kdv4afpxbb(4#7Zsnd z7xZ733})Q|yT~8x$0~MnN_@8-5#*Wo^jbo!h^wa~S^e|(JcA&(#U{cU?^0+unz?_W zo2Do;LBg(g^L^o1;8{rBCe}E(-u{AMm)z`_t2*AOmIVim+_A{+D1jSe!H%)ymYPu6 zi$Sb|nRAD|8C|u_zte?^WSNfD^`i#A5G8+K%*99H@20tTMQ*Ng2uWeRDeL6tge_;ZeGY*z0Z<#TR*1;#*QeTp!i6$g|n|)V`wJ9|8`OFcQ_+kw2q`bR3F34s~ENVquL{qy0UK(`b1Jgznz- zj;fD%*LgQ>5K+x`X)n(3kB@bj$tz@w9o^3;vyylL*>H<>SfFhD%0IIS-bnHCx7t!( zOj^`#p$cC~R1IQ_0avNOz0toPM{>VhK&ADXKhGfq@qnlA)?{i4XT?6Hw!q~Sup060c9RMJPXHX-?3$y&%&jz5AGsHkB@4Ab_e2nS((JBgdR|p^q+gR?cp~Dpgeo96p;%I(Ok{>TZZ19C zB6L%<_T4dgav96ED<&IqAe1RtdOS&S*tE-r`H)vIhf}b|5c!rfUL1doK00Y+xurNv zC)*OV+F4!5x&&XpT+jDQ0g1(ISLxgW@bbt(-Y{lI#vVOPRiQN@DTt|Uo$rZT@=i~g zob@XsGjcD4&z`dbltA^cyegbEVf=}Eolc1C6e663{$*X=A988^*#w^*Bl{&DISE6a z%4g7W2r^mnD=)TeVWt=B6=H=~W6RnNtu!GuQy!(VVx=4-r!GxW+;#Uva*9DyJ=G$i z&CQJ3HGS%hNk`PVLc0Xu7akdZDg#wqwiv#92#q_62-U*{LLtz;E8no0#{^&RNgd+s zb;2)K&PN;2oO)b2z2U2lrCe z`ca){Sal&od*8ok60!T*kuLe}j%dHgJuI4{;}gQ)X8eA+VM$=^m*D`O$|t!(tTk$T zMI;(}=wR*zQo{9X^_O3Xh1Kgisvqs$!Yac{n&W({+hgXhRbiAl*iDmKoFi|3%BV}h zyK0ptwuYQHKV9zQT9&?3vxLoBgyZrqWMTOa^4Lh9?sMnyh50*2(yx{_BX1sqnsy;K z=oogilp?(|guQ_|oF`&>c5kfhfQ`o%Qb= 2.7.5) - SwiftyGif (5.4.3) - Toast (4.0.0) + - url_launcher_ios (0.0.1): + - Flutter - video_player_avfoundation (0.0.1): - Flutter - wakelock (0.0.1): @@ -84,6 +86,7 @@ DEPENDENCIES: - path_provider_ios (from `.symlinks/plugins/path_provider_ios/ios`) - shared_preferences_ios (from `.symlinks/plugins/shared_preferences_ios/ios`) - sqflite (from `.symlinks/plugins/sqflite/ios`) + - url_launcher_ios (from `.symlinks/plugins/url_launcher_ios/ios`) - video_player_avfoundation (from `.symlinks/plugins/video_player_avfoundation/ios`) - wakelock (from `.symlinks/plugins/wakelock/ios`) - webview_flutter_wkwebview (from `.symlinks/plugins/webview_flutter_wkwebview/ios`) @@ -122,6 +125,8 @@ EXTERNAL SOURCES: :path: ".symlinks/plugins/shared_preferences_ios/ios" sqflite: :path: ".symlinks/plugins/sqflite/ios" + url_launcher_ios: + :path: ".symlinks/plugins/url_launcher_ios/ios" video_player_avfoundation: :path: ".symlinks/plugins/video_player_avfoundation/ios" wakelock: @@ -148,6 +153,7 @@ SPEC CHECKSUMS: sqflite: 6d358c025f5b867b29ed92fc697fd34924e11904 SwiftyGif: 6c3eafd0ce693cad58bb63d2b2fb9bacb8552780 Toast: 91b396c56ee72a5790816f40d3a94dd357abc196 + url_launcher_ios: 839c58cdb4279282219f5e248c3321761ff3c4de video_player_avfoundation: e489aac24ef5cf7af82702979ed16f2a5ef84cff wakelock: d0fc7c864128eac40eba1617cb5264d9c940b46f webview_flutter_wkwebview: 005fbd90c888a42c5690919a1527ecc6649e1162 diff --git a/bytedesk_kefu/example/lib/main.dart b/bytedesk_kefu/example/lib/main.dart index bd6f869..464786e 100644 --- a/bytedesk_kefu/example/lib/main.dart +++ b/bytedesk_kefu/example/lib/main.dart @@ -31,6 +31,7 @@ void main() { // 第一步:初始化 BytedeskKefu.init(_appKey, _subDomain); // 注:如果需要多平台统一用户(用于同步聊天记录等),可使用下列接口,其中:username只能包含数字或字母,不能含有汉字和特殊字符等,nickname可以使用汉字 + // 注:如需切换用户,请首先执行BytedeskKefu.logout() // BytedeskKefu.initWithUsernameAndNicknameAndAvatar('myflutterusername', '我是美女', 'https://bytedesk.oss-cn-shenzhen.aliyuncs.com/avatars/girl.png', _appKey, _subDomain); // BytedeskKefu.initWithUsername('myflutterusername', _appKey, _subDomain); // 其中:username为自定义用户名,可与开发者所在用户系统对接 // 如果还需要自定义昵称/头像,可以使用 initWithUsernameAndNickname或initWithUsernameAndNicknameAndAvatar, diff --git a/bytedesk_kefu/example/pubspec.yaml b/bytedesk_kefu/example/pubspec.yaml index fcd0f95..ab7ea6a 100644 --- a/bytedesk_kefu/example/pubspec.yaml +++ b/bytedesk_kefu/example/pubspec.yaml @@ -58,6 +58,7 @@ flutter: # To add assets to your application, add an assets section, like this: assets: - assets/audio/ + - assets/images/chat/ - assets/images/feedback/ # An image asset can refer to one or more resolution-specific "variants", see diff --git a/bytedesk_kefu/lib/blocs/message_bloc/message_bloc.dart b/bytedesk_kefu/lib/blocs/message_bloc/message_bloc.dart index d051bf9..201ce58 100755 --- a/bytedesk_kefu/lib/blocs/message_bloc/message_bloc.dart +++ b/bytedesk_kefu/lib/blocs/message_bloc/message_bloc.dart @@ -100,7 +100,7 @@ class MessageBloc extends Bloc { emit(SendMessageRestSuccess(jsonResult)); } catch (error) { BytedeskUtils.printLog(error); - emit(SendMessageRestError()); + emit(SendMessageRestError(event.json!)); } } diff --git a/bytedesk_kefu/lib/blocs/message_bloc/message_state.dart b/bytedesk_kefu/lib/blocs/message_bloc/message_state.dart index 666a3d7..a974b06 100755 --- a/bytedesk_kefu/lib/blocs/message_bloc/message_state.dart +++ b/bytedesk_kefu/lib/blocs/message_bloc/message_state.dart @@ -65,6 +65,13 @@ class UpLoadImageError extends MessageState { } class SendMessageRestError extends MessageState { + final String json; + + const SendMessageRestError(this.json); + + @override + List get props => [json]; + @override String toString() => 'SendMessageRestError'; } diff --git a/bytedesk_kefu/lib/http/bytedesk_user_api.dart b/bytedesk_kefu/lib/http/bytedesk_user_api.dart index d215d2e..6ebdd80 100755 --- a/bytedesk_kefu/lib/http/bytedesk_user_api.dart +++ b/bytedesk_kefu/lib/http/bytedesk_user_api.dart @@ -312,6 +312,7 @@ class BytedeskUserHttpApi extends BytedeskBaseHttpApi { User user = User.fromJson(responseJson['data']); // SpUtil.putString(BytedeskConstants.uid, user.uid!); + SpUtil.putString(BytedeskConstants.username, user.username!); SpUtil.putString(BytedeskConstants.nickname, user.nickname!); SpUtil.putString(BytedeskConstants.avatar, user.avatar!); SpUtil.putString(BytedeskConstants.mobile, user.mobile ?? ''); diff --git a/bytedesk_kefu/lib/mqtt/bytedesk_mqtt.dart b/bytedesk_kefu/lib/mqtt/bytedesk_mqtt.dart index 62add9c..52ccca7 100755 --- a/bytedesk_kefu/lib/mqtt/bytedesk_mqtt.dart +++ b/bytedesk_kefu/lib/mqtt/bytedesk_mqtt.dart @@ -984,7 +984,9 @@ class BytedeskMqtt { // 断开长连接 void disconnect() { - mqttClient.disconnect(); + if (mqttClient != null) { + mqttClient.disconnect(); + } } /// The subscribed callback diff --git a/bytedesk_kefu/lib/ui/chat/page/chat_kf_page.dart b/bytedesk_kefu/lib/ui/chat/page/chat_kf_page.dart index 8c60241..eb8dce5 100755 --- a/bytedesk_kefu/lib/ui/chat/page/chat_kf_page.dart +++ b/bytedesk_kefu/lib/ui/chat/page/chat_kf_page.dart @@ -25,6 +25,12 @@ import 'package:fluttertoast/fluttertoast.dart'; import 'package:image_picker/image_picker.dart'; import 'package:pull_to_refresh/pull_to_refresh.dart'; // import 'package:path_provider/path_provider.dart' as path_provider; +import 'package:bytedesk_kefu/ui/widget/chat_input.dart'; +import 'package:bytedesk_kefu/ui/widget/extra_item.dart'; +// import 'package:bytedesk_kefu/ui/widget/send_button_visibility_mode.dart'; +// import 'package:bytedesk_kefu/ui/widget/voice_record/voice_widget.dart'; +import 'package:flutter_chat_ui/flutter_chat_ui.dart' as chat_ui; +// import 'package:flutter_chat_types/flutter_chat_types.dart' as types; // TODO: 接通客服之前,在title显示loading // 客服关闭会话,或者 自动关闭会话,则禁止继续发送消息 @@ -87,6 +93,7 @@ class _ChatKFPageState extends State BytedeskMqtt _bdMqtt = new BytedeskMqtt(); // 当前用户uid String? _currentUid = SpUtil.getString(BytedeskConstants.uid); + String? _currentUsername = SpUtil.getString(BytedeskConstants.username); String? _currentNickname = SpUtil.getString(BytedeskConstants.nickname); String? _currentAvatar = SpUtil.getString(BytedeskConstants.avatar); // 当前会话 @@ -101,6 +108,8 @@ class _ChatKFPageState extends State Timer? _debounce; // 定时拉取聊天记录 Timer? _loadHistoryTimer; + // + Timer? _resendTimer; // 视频压缩 // final _flutterVideoCompress = FlutterVideoCompress(); bool _isRequestingThread = true; @@ -138,6 +147,41 @@ class _ChatKFPageState extends State // // 每隔 1 秒钟会调用一次,如果要结束调用 // // timer.cancel(); }); + _resendTimer = Timer.periodic(Duration(seconds: 2), (timer) { + // TODO: 检测-消息是否超时发送失败 + for (var i = 0; i < _messages.length; i++) { + Message? message = _messages[i].message; + // 自己发送的 && 消息状态为发送中... + if (message!.isSend == 1 && + message.status == BytedeskConstants.MESSAGE_STATUS_SENDING) { + var nowTime = DateTime.now(); + var messageTime = DateTime.parse(message.timestamp!); + int diff = nowTime.difference(messageTime).inSeconds; + if (diff > 15) { + // 超时15秒,设置为消息状态为error + _messageProvider.update( + message.mid, BytedeskConstants.MESSAGE_STATUS_ERROR); + } else if (diff > 5) { + // 5秒没有发送成功,则尝试使用http rest接口发送 + String content = ''; + if (message.type == BytedeskConstants.MESSAGE_TYPE_TEXT) { + content = message.content!; + } else if (message.type == BytedeskConstants.MESSAGE_TYPE_IMAGE) { + content = message.imageUrl!; + } else if (message.type == BytedeskConstants.MESSAGE_TYPE_FILE) { + content = message.fileUrl!; + } else if (message.type == BytedeskConstants.MESSAGE_TYPE_VOICE) { + content = message.voiceUrl!; + } else if (message.type == BytedeskConstants.MESSAGE_TYPE_VIDEO) { + content = message.videoUrl!; + } else { + content = message.content!; + } + this.sendMessageRest(message.mid!, message.type!, content); + } + } + } + }); // BlocProvider.of(context) // ..add(LoadUnreadVisitorMessagesEvent(page: 0, size: 10)); } @@ -401,6 +445,18 @@ class _ChatKFPageState extends State message.mid!, _currentThread!); } } + } else if (state is SendMessageRestSuccess) { + // http rest 发送消息成功 + String jsonMessage = state.jsonResult.data!; + String mid = json.decode(jsonMessage); + _messageProvider.update( + mid, BytedeskConstants.MESSAGE_STATUS_STORED); + } else if (state is SendMessageRestError) { + // http rest 发送消息失败 + String jsonMessage = state.json; + String mid = json.decode(jsonMessage); + _messageProvider.update( + mid, BytedeskConstants.MESSAGE_STATUS_STORED); } }, ), @@ -466,13 +522,81 @@ class _ChatKFPageState extends State decoration: BoxDecoration( color: Theme.of(context).cardColor, ), - child: _textComposerWidget(), + // child: _textComposerWidget(), + child: _chatInput(), ), ], ), ))); } + Widget _chatInput() { + return ChatInput( + // 发送触发事件 + onSendPressed: _handleSendPressed, + sendButtonVisibilityMode: chat_ui.SendButtonVisibilityMode.editing, + // voiceWidget: VoiceRecord(), + // voiceWidget: VoiceWidget( + // startRecord: () {}, + // stopRecord: _handleVoiceSelection, + // // 加入定制化Container的相关属性 + // height: 40.0, + // margin: EdgeInsets.zero, + // ), + extraWidget: ExtraItems( + // 照片 + handleImageSelection: _handleImageSelection, + // 文件 + handleFileSelection: _handleFileSelection, + // 拍摄 + handlePickerSelection: _handlePickerSelection, + // 上传视频 + handleUploadVideo: _handleUploadVideo, + // 录制视频 + handleCaptureVideo: _handleCaptureVideo), + ); + } + + // + // void _handleVoiceSelection(AudioFile? obj) async { + // print('_handleVoiceSelection'); + // if (obj != null) { + + // } + // } + + // + Future _handleSendPressed(String content) async { + print('send: ${content}'); + _handleSubmitted(content); + return true; + } + + void _handleImageSelection() async { + print('_handleImageSelection'); + _pickImage(); + } + + void _handleFileSelection() async { + print('_handleFileSelection'); + } + + Future _handlePickerSelection() async { + print('_handlePickerSelection'); + _takeImage(); + return; + } + + void _handleUploadVideo() async { + print('_handleUploadVideo'); + _pickVideo(); + } + + void _handleCaptureVideo() async { + print('_handleCaptureVideo'); + _captureVideo(); + } + // Widget _textComposerWidget() { return IconTheme( @@ -541,7 +665,7 @@ class _ChatKFPageState extends State @override bool get wantKeepAlive => true; - // + // 发送消息 void _handleSubmitted(String? text) { _textController.clear(); // 内容为空,直接返回 @@ -572,100 +696,175 @@ class _ChatKFPageState extends State } } - // http rest 接口发生文本消息 + // http rest 接口发生文本消息,长链接断开情况下调用 void sendTextMessageRest(String text) { // String? mid = BytedeskUuid.uuid(); - String? timestamp = BytedeskUtils.formatedDateNow(); - String? client = BytedeskUtils.getClient(); - String? type = BytedeskConstants.MESSAGE_TYPE_TEXT; - // - var jsonContent = { - "mid": mid, - "timestamp": timestamp, - "client": client, - "version": "1", - "type": type, - "status": "sending", - "user": { - "uid": this._currentUid, - "nickname": this._currentNickname, - "avatar": this._currentAvatar, - "extra": {"agent": false} - }, - "text": {"content": text}, - "thread": { - "tid": this._currentThread!.tid, - "type": this._currentThread!.type, - "content": text, - "nickname": this._currentThread!.nickname, - "avatar": this._currentThread!.avatar, - "topic": this._currentThread!.topic, - "timestamp": timestamp, - "unreadCount": 0 - } - }; - String? jsonString = json.encode(jsonContent); - BlocProvider.of(context) - ..add(SendMessageRestEvent(json: jsonString)); - // 暂时没有将插入本地函数独立出来,暂时 - Message message = new Message(); - message.mid = mid; - message.type = type; - message.timestamp = timestamp; - message.client = client; - message.avatar = _currentAvatar; - message.topic = this._currentThread!.topic; - message.status = BytedeskConstants.MESSAGE_STATUS_SENDING; - message.isSend = 1; - message.currentUid = this._currentUid; - message.answersJson = ''; - message.thread = this._currentThread; - message.user = User( - uid: this._currentUid, - avatar: this._currentAvatar, - nickname: this._currentNickname); - // - message.content = text; - // 插入本地数据库 - _messageProvider.insert(message); - // - pushToMessageArray(message, true); + sendMessageRest(mid, BytedeskConstants.MESSAGE_TYPE_TEXT, text); } - // http rest 接口发送图片消息 + // http rest 接口发送图片消息,长链接断开情况下调用 void sendImageMessageRest(String imageUrl) { // String? mid = BytedeskUuid.uuid(); + sendMessageRest(mid, BytedeskConstants.MESSAGE_TYPE_IMAGE, imageUrl); + } + + // http rest 接口发送消息,长链接断开情况下调用 + void sendMessageRest(String mid, String type, String content) { + // + // String? mid = BytedeskUuid.uuid(); String? timestamp = BytedeskUtils.formatedDateNow(); String? client = BytedeskUtils.getClient(); - String? type = BytedeskConstants.MESSAGE_TYPE_IMAGE; // - var jsonContent = { - "mid": mid, - "timestamp": timestamp, - "client": client, - "version": "1", - "type": type, - "status": "sending", - "user": { - "uid": this._currentUid, - "nickname": this._currentNickname, - "avatar": this._currentAvatar, - "extra": {"agent": false} - }, - "image": {"imageUrl": imageUrl}, - "thread": { - "tid": this._currentThread!.tid, - "type": this._currentThread!.type, - "content": "[图片]", - "nickname": this._currentThread!.nickname, - "avatar": this._currentThread!.avatar, - "topic": this._currentThread!.topic, + var jsonContent; + + if (type == BytedeskConstants.MESSAGE_TYPE_TEXT) { + jsonContent = { + "mid": mid, "timestamp": timestamp, - "unreadCount": 0 - } - }; + "client": client, + "version": "1", + "type": type, + "status": BytedeskConstants.MESSAGE_STATUS_SENDING, + "user": { + "uid": this._currentUid, + "username": this._currentUsername, + "nickname": this._currentNickname, + "avatar": this._currentAvatar, + "extra": {"agent": false} + }, + "text": {"content": content}, + "thread": { + "tid": this._currentThread!.tid, + "type": this._currentThread!.type, + "content": content, + "nickname": this._currentThread!.nickname, + "avatar": this._currentThread!.avatar, + "topic": this._currentThread!.topic, + "client": client, + "timestamp": timestamp, + "unreadCount": 0 + } + }; + } else if (type == BytedeskConstants.MESSAGE_TYPE_IMAGE) { + jsonContent = { + "mid": mid, + "timestamp": timestamp, + "client": client, + "version": "1", + "type": type, + "status": "sending", + "user": { + "uid": this._currentUid, + "username": this._currentUsername, + "nickname": this._currentNickname, + "avatar": this._currentAvatar, + "extra": {"agent": false} + }, + "image": {"imageUrl": content}, + "thread": { + "tid": this._currentThread!.tid, + "type": this._currentThread!.type, + "content": "[图片]", + "nickname": this._currentThread!.nickname, + "avatar": this._currentThread!.avatar, + "topic": this._currentThread!.topic, + "client": client, + "timestamp": timestamp, + "unreadCount": 0 + } + }; + } else if (type == BytedeskConstants.MESSAGE_TYPE_FILE) { + jsonContent = { + "mid": mid, + "timestamp": timestamp, + "client": client, + "version": "1", + "type": type, + "status": BytedeskConstants.MESSAGE_STATUS_SENDING, + "user": { + "uid": this._currentUid, + "username": this._currentUsername, + "nickname": this._currentNickname, + "avatar": this._currentAvatar, + "extra": {"agent": false} + }, + "file": {"fileUrl": content}, + "thread": { + "tid": this._currentThread!.tid, + "type": this._currentThread!.type, + "content": "[文件]", + "nickname": this._currentThread!.nickname, + "avatar": this._currentThread!.avatar, + "topic": this._currentThread!.topic, + "client": client, + "timestamp": timestamp, + "unreadCount": 0 + } + }; + } else if (type == BytedeskConstants.MESSAGE_TYPE_VOICE) { + jsonContent = { + "mid": mid, + "timestamp": timestamp, + "client": client, + "version": "1", + "type": type, + "status": BytedeskConstants.MESSAGE_STATUS_SENDING, + "user": { + "uid": this._currentUid, + "username": this._currentUsername, + "nickname": this._currentNickname, + "avatar": this._currentAvatar, + "extra": {"agent": false} + }, + "voice": { + "voiceUrl": content, + "length": '0', // TODO:替换为真实值 + "format": "wav", + }, + "thread": { + "tid": this._currentThread!.tid, + "type": this._currentThread!.type, + "content": content, + "nickname": this._currentThread!.nickname, + "avatar": this._currentThread!.avatar, + "topic": this._currentThread!.topic, + "client": client, + "timestamp": timestamp, + "unreadCount": 0 + } + }; + } else if (type == BytedeskConstants.MESSAGE_TYPE_VIDEO) { + jsonContent = { + "mid": mid, + "timestamp": timestamp, + "client": client, + "version": "1", + "type": type, + "status": BytedeskConstants.MESSAGE_STATUS_SENDING, + "user": { + "uid": this._currentUid, + "username": this._currentUsername, + "nickname": this._currentNickname, + "avatar": this._currentAvatar, + "extra": {"agent": false} + }, + "video": {"videoOrShortUrl": content}, + "thread": { + "tid": this._currentThread!.tid, + "type": this._currentThread!.type, + "content": content, + "nickname": this._currentThread!.nickname, + "avatar": this._currentThread!.avatar, + "topic": this._currentThread!.topic, + "client": client, + "timestamp": timestamp, + "unreadCount": 0 + } + }; + } + String? jsonString = json.encode(jsonContent); BlocProvider.of(context) ..add(SendMessageRestEvent(json: jsonString)); @@ -674,7 +873,7 @@ class _ChatKFPageState extends State message.mid = mid; message.type = type; message.timestamp = timestamp; - // message.client = client; + message.client = client; message.avatar = _currentAvatar; message.topic = this._currentThread!.topic; message.status = BytedeskConstants.MESSAGE_STATUS_SENDING; @@ -687,7 +886,7 @@ class _ChatKFPageState extends State avatar: this._currentAvatar, nickname: this._currentNickname); // - message.imageUrl = imageUrl; + message.content = content; // 插入本地数据库 _messageProvider.insert(message); // @@ -1174,6 +1373,7 @@ class _ChatKFPageState extends State WidgetsBinding.instance!.removeObserver(this); _debounce?.cancel(); _loadHistoryTimer?.cancel(); + _resendTimer?.cancel(); // bytedeskEventBus.destroy(); // FIXME: 只能取消监听,不能destroy super.dispose(); } diff --git a/bytedesk_kefu/lib/ui/widget/chat_input.dart b/bytedesk_kefu/lib/ui/widget/chat_input.dart new file mode 100644 index 0000000..b76612a --- /dev/null +++ b/bytedesk_kefu/lib/ui/widget/chat_input.dart @@ -0,0 +1,531 @@ +import 'dart:async'; + +import 'package:bytedesk_kefu/ui/widget/emoji_picker_view.dart'; +import 'package:bytedesk_kefu/ui/widget/image_button.dart'; +import 'package:bytedesk_kefu/vendors/emoji_picker_flutter/emoji_picker_flutter.dart'; +// import 'package:bytedesk_kefu/ui/widget/send_button_visibility_mode.dart'; +// import 'package:emoji_picker_flutter/emoji_picker_flutter.dart'; +// import 'package:emoji_picker_flutter/src/emoji_view_state.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +// import 'package:flutter_chat_types/flutter_chat_types.dart' as types; +import 'package:flutter_chat_ui/flutter_chat_ui.dart'; +// import 'package:flutter_chat_ui/src/widgets/inherited_chat_theme.dart'; +// import 'package:imboy/config/init.dart'; +// import 'package:imboy/service/websocket.dart'; +// import 'package:imboy/store/model/message_model.dart'; + +/** + * 部分代码来自该项目,感谢作者 CaiJingLong https://github.com/CaiJingLong/flutter_like_wechat_input + */ +enum InputType { + text, + voice, + emoji, + extra, +} + +Widget _buildVoiceButton(BuildContext context) { + return Container( + width: double.infinity, + child: TextButton( + // color: Colors.white70, + onPressed: () { + // Get.snackbar('Tips', '语音输入功能暂无实现'); + }, + child: Text( + 'chat_hold_down_talk', + ), + ), + ); +} + +typedef void OnSend(String text); + +InputType _initType = InputType.text; + +double _softKeyHeight = 210; + +class ChatInput extends StatefulWidget { + const ChatInput({ + Key? key, + this.isAttachmentUploading, + this.onAttachmentPressed, + required this.onSendPressed, + this.onTextChanged, + this.onTextFieldTap, + required this.sendButtonVisibilityMode, + this.extraWidget, + this.voiceWidget, + }) : super(key: key); + + /// See [AttachmentButton.onPressed] + final void Function()? onAttachmentPressed; + + /// Whether attachment is uploading. Will replace attachment button with a + /// [CircularProgressIndicator]. Since we don't have libraries for + /// managing media in dependencies we have no way of knowing if + /// something is uploading so you need to set this manually. + final bool? isAttachmentUploading; + + /// Will be called on [SendButton] tap. Has [types.PartialText] which can + /// be transformed to [types.TextMessage] and added to the messages list. + // final Future Function(types.PartialText) onSendPressed; + final Future Function(String) onSendPressed; + + /// Will be called whenever the text inside [TextField] changes + final void Function(String)? onTextChanged; + + /// Will be called on [TextField] tap + final void Function()? onTextFieldTap; + + /// Controls the visibility behavior of the [SendButton] based on the + /// [TextField] state inside the [Input] widget. + /// Defaults to [SendButtonVisibilityMode.editing]. + final SendButtonVisibilityMode sendButtonVisibilityMode; + + final Widget? extraWidget; + final Widget? voiceWidget; + + @override + _ChatInputState createState() => _ChatInputState(); +} + +/// [Input] widget state +class _ChatInputState extends State with TickerProviderStateMixin { + InputType inputType = _initType; + final _inputFocusNode = FocusNode(); + bool _sendButtonVisible = false; + final _textController = TextEditingController(); + late AnimationController _bottomHeightController; + bool emojiShowing = false; + /** + * https://stackoverflow.com/questions/60057840/flutter-how-to-insert-text-in-middle-of-text-field-text + */ + void _setText(String val) { + String text = _textController.text; + TextSelection textSelection = _textController.selection; + int start = textSelection.start > -1 ? textSelection.start : 0; + String newText = text.replaceRange( + start, + textSelection.end > -1 ? textSelection.end : 0, + val, + ); + _textController.text = newText; + int offset = start + val.length; + _textController.selection = textSelection.copyWith( + baseOffset: offset, + extentOffset: offset, + ); + } + + @override + void initState() { + super.initState(); + if (!mounted) { + return; + } + if (widget.sendButtonVisibilityMode == SendButtonVisibilityMode.editing) { + _sendButtonVisible = _textController.text.trim() != ''; + _textController.addListener(_handleTextControllerChange); + } else { + _sendButtonVisible = true; + } + + _bottomHeightController = AnimationController( + vsync: this, + duration: Duration( + milliseconds: 150, + ), + ); + // 解决"重新进入聊天页面的时候_bottomHeightController在开启状态"的问题 + _bottomHeightController.animateBack(0); + + // 接收到新的消息订阅 + // eventBus.on().listen((msg) async { + // if (_textController.text.toString() != msg.text) { + // _setText(msg.text); + // } + // }); + //添加listener监听 + //对应的TextField失去或者获取焦点都会回调此监听 + _inputFocusNode.addListener(() { + // debugPrint(">>> on chatinput ${_inputFocusNode.hasFocus}"); + if (_inputFocusNode.hasFocus) { + updateState(InputType.text); + } else {} + }); + } + + @override + void dispose() { + // Get.delete(); + _inputFocusNode.dispose(); + _textController.dispose(); + super.dispose(); + } + + Future _handleSendPressed() async { + final trimmedText = _textController.text.trim(); + if (trimmedText != '') { + // final _partialText = types.PartialText(text: trimmedText); + // bool res = await widget.onSendPressed(_partialText); + bool res = await widget.onSendPressed(trimmedText); + if (res) { + _textController.clear(); + } else { + // WSService.to.openSocket(); + // 网络原因,发送失败 + } + } + } + + void _handleTextControllerChange() { + setState(() { + _sendButtonVisible = _textController.text.trim() != ''; + }); + } + + void changeBottomHeight(final double height) { + if (height > 0) { + _bottomHeightController.animateTo(1); + } else { + _bottomHeightController.animateBack(0); + } + } + + /** + * 语音按钮事件 + */ + Future _voiceBtnOnPressed(InputType type) async { + if (type == this.inputType) { + return; + } + if (type != InputType.text) { + hideSoftKey(); + } else { + showSoftKey(); + } + + setState(() { + this.inputType = type; + }); + } + + Future updateState(InputType type) async { + if (type == InputType.text || type == InputType.voice) { + _initType = type; + } + if (type == inputType) { + return; + } + this.inputType = type; + // InputTypeNotification(type).dispatch(context); + + if (type != InputType.text) { + hideSoftKey(); + } else { + showSoftKey(); + } + + if (type == InputType.emoji || type == InputType.extra) { + changeBottomHeight(1); + hideSoftKey(); + } else { + changeBottomHeight(0); + } + + setState(() { + this.emojiShowing = type == InputType.emoji; + this.inputType; + }); + } + + void showSoftKey() { + FocusScope.of(context).requestFocus(_inputFocusNode); + changeBottomHeight(0); + // debugPrint(">>> on chatinput showSoftKey"); + } + + void hideSoftKey() { + _inputFocusNode.unfocus(); + + // 隐藏键盘而不丢失文本字段焦点:from https://developer.aliyun.com/article/763095 + SystemChannels.textInput.invokeMethod('TextInput.hide'); + } + + Widget _buildBottomContainer({required Widget child}) { + return SizeTransition( + sizeFactor: _bottomHeightController, + child: Container( + child: child, + height: _softKeyHeight, + ), + ); + } + + Widget _buildBottomItems() { + if (this.inputType == InputType.extra) { + return widget.extraWidget ?? Center(child: Text("其他item")); + } else if (this.inputType == InputType.emoji) { + return Offstage( + offstage: !emojiShowing, + child: SizedBox( + height: 400, + child: EmojiPicker( + onEmojiSelected: (Category category, Emoji emoji) { + _setText(emoji.emoji); + }, + onBackspacePressed: () { + _textController + ..text = _textController.text.characters.skipLast(1).toString() + ..selection = TextSelection.fromPosition( + TextPosition(offset: _textController.text.length), + ); + }, + config: Config( + columns: 7, + // Issue: https://github.com/flutter/flutter/issues/28894 + // emojiSizeMax: 24 * (GetPlatform.isIOS ? 1.30 : 1.0), + verticalSpacing: 0, + horizontalSpacing: 0, + initCategory: Category.RECENT, + bgColor: const Color(0xFFF2F2F2), + indicatorColor: Colors.black87, + iconColorSelected: Colors.black87, + iconColor: Colors.grey, + progressIndicatorColor: Colors.blue, + backspaceColor: Colors.black54, + showRecentsTab: true, + recentsLimit: 19, + // noRecentsText: 'No Recents'.tr, + // noRecentsStyle: const TextStyle( + // fontSize: 20, + // color: Colors.black87, + // ), + tabIndicatorAnimDuration: kTabScrollDuration, + categoryIcons: const CategoryIcons(), + buttonMode: ButtonMode.MATERIAL, + ), + customWidget: (Config config, EmojiViewState state) => + EmojiPickerView( + config, + state, + _handleSendPressed, + ), + ), + ), + ); + } else { + return Container(); + } + } + + Widget _buildInputButton(BuildContext ctx) { + final voiceButton = widget.voiceWidget ?? _buildVoiceButton(ctx); + final inputButton = TextField( + controller: _textController, + // cursorColor: InheritedChatTheme.of(ctx).theme.inputTextCursorColor, + // decoration: InheritedChatTheme.of(ctx).theme.inputTextDecoration.copyWith( + // hintStyle: InheritedChatTheme.of(ctx).theme.inputTextStyle.copyWith( + // color: InheritedChatTheme.of(ctx) + // .theme + // .inputTextColor + // .withOpacity(0.5), + // ), + // hintText: '', + // ), + focusNode: _inputFocusNode, + // maxLength: 400, + maxLines: 6, + minLines: 1, + // 长按是否展示【剪切/复制/粘贴菜单LengthLimitingTextInputFormatter】 + enableInteractiveSelection: true, + keyboardType: TextInputType.multiline, + textCapitalization: TextCapitalization.sentences, + textInputAction: TextInputAction.newline, + onChanged: widget.onTextChanged, + onTap: () { + updateState(inputType); + widget.onTextFieldTap; + }, + // style: InheritedChatTheme.of(ctx).theme.inputTextStyle.copyWith( + // color: InheritedChatTheme.of(ctx).theme.inputTextColor, + // ), + // 点击键盘的动作按钮时的回调,参数为当前输入框中的值 + onSubmitted: (_) => _handleSendPressed(), + ); + + return Stack( + children: [ + Offstage( + child: inputButton, + offstage: inputType == InputType.voice, + ), + Offstage( + child: voiceButton, + offstage: inputType != InputType.voice, + ), + ], + ); + } + + Widget buildLeftButton() { + return ImageButton( + onPressed: () { + if (inputType == InputType.voice) { + _voiceBtnOnPressed(InputType.text); + } else { + _voiceBtnOnPressed(InputType.voice); + } + changeBottomHeight(0); + }, + image: AssetImage( + inputType != InputType.voice + ? 'assets/images/chat/input_voice.png' + : 'assets/images/chat/input_keyboard.png', + ), + ); + } + + /** + * + */ + Widget buildEmojiButton() { + return ImageButton( + image: AssetImage(inputType != InputType.emoji + ? 'assets/images/chat/input_emoji.png' + : 'assets/images/chat/input_keyboard.png'), + onPressed: () { + if (inputType != InputType.emoji) { + updateState(InputType.emoji); + } else { + updateState(InputType.text); + } + }, + ); + } + + /** + * 更多输入消息类型入口 + * More input message types entries + */ + Widget buildExtra() { + return ImageButton( + image: AssetImage('assets/images/chat/input_extra.png'), + onPressed: () { + if (inputType != InputType.extra) { + updateState(InputType.extra); + } else { + updateState(InputType.text); + } + }, + ); + } + + /** + * 实现换行效果 + * Implement line breaks + */ + void _handleNewLine() { + final _newValue = '${_textController.text}\r\n'; + _textController.value = TextEditingValue( + text: _newValue, + selection: TextSelection.fromPosition( + TextPosition(offset: _newValue.length), + ), + ); + } + + @override + Widget build(BuildContext context) { + final _query = MediaQuery.of(context); + + final isAndroid = Theme.of(context).platform == TargetPlatform.android; + final isIOS = Theme.of(context).platform == TargetPlatform.iOS; + + return InkWell( + child: Focus( + autofocus: true, + child: Padding( + padding: EdgeInsets.fromLTRB(15, 0, 0, 0), //InheritedChatTheme.of(context).theme.inputPadding, + child: Material( + borderRadius: const BorderRadius.vertical(top: Radius.circular(10),), + //InheritedChatTheme.of(context).theme.inputBorderRadius, + // color: Color.white, //Color(0xff1d1c21),//InheritedChatTheme.of(context).theme.inputBackgroundColor, + child: Container( + padding: EdgeInsets.fromLTRB( + _query.padding.left, + 4, + _query.padding.right, + 4 + _query.viewInsets.bottom + _query.padding.bottom, + ), + child: Column( + children: [ + Row( + children: [ + // TODO: 录制语音 + // buildLeftButton(), + // input + Expanded( + child: Shortcuts( + shortcuts: isAndroid || isIOS + ? { + LogicalKeySet(LogicalKeyboardKey.enter): + const NewLineIntent(), + LogicalKeySet(LogicalKeyboardKey.enter, + LogicalKeyboardKey.alt): + const NewLineIntent(), + } + : { + LogicalKeySet(LogicalKeyboardKey.enter): + const SendMessageIntent(), + LogicalKeySet(LogicalKeyboardKey.enter, + LogicalKeyboardKey.alt): + const NewLineIntent(), + LogicalKeySet(LogicalKeyboardKey.enter, + LogicalKeyboardKey.shift): + const NewLineIntent(), + }, + child: Actions( + actions: { + SendMessageIntent: + CallbackAction( + onInvoke: (SendMessageIntent intent) => + _handleSendPressed(), + ), + NewLineIntent: CallbackAction( + onInvoke: (NewLineIntent intent) => + _handleNewLine(), + ), + }, + child: _buildInputButton(context), + ), + ), + ), + // emoji + buildEmojiButton(), + //extra + _textController.text.isEmpty + ? buildExtra() + : IconButton( + icon: Icon(Icons.send), + onPressed: _handleSendPressed, + padding: EdgeInsets.only(left: 0), + ), + ], + ), + this.inputType == InputType.emoji || + this.inputType == InputType.extra + ? Divider() + : SizedBox.shrink(), // 横线 + _buildBottomContainer(child: _buildBottomItems()), + ], + ), + ), + ), + ), + ), + ); + } +} diff --git a/bytedesk_kefu/lib/ui/widget/emoji_picker_view.dart b/bytedesk_kefu/lib/ui/widget/emoji_picker_view.dart new file mode 100644 index 0000000..bef4401 --- /dev/null +++ b/bytedesk_kefu/lib/ui/widget/emoji_picker_view.dart @@ -0,0 +1,198 @@ +// import 'package:emoji_picker_flutter/emoji_picker_flutter.dart'; +// import 'package:emoji_picker_flutter/src/category_emoji.dart'; +// import 'package:emoji_picker_flutter/src/config.dart'; +// import 'package:emoji_picker_flutter/src/emoji_picker_builder.dart'; +// import 'package:emoji_picker_flutter/src/emoji_view_state.dart'; +import 'package:bytedesk_kefu/vendors/emoji_picker_flutter/emoji_picker_flutter.dart'; +import 'package:bytedesk_kefu/vendors/emoji_picker_flutter/src/category_emoji.dart'; +// import 'package:bytedesk_kefu/vendors/emoji_picker_flutter/src/config.dart'; +// import 'package:bytedesk_kefu/vendors/emoji_picker_flutter/src/emoji_picker_builder.dart'; +// import 'package:bytedesk_kefu/vendors/emoji_picker_flutter/src/emoji_view_state.dart'; +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; + +/// EmojiPicker Implementation +class EmojiPickerView extends EmojiPickerBuilder { + /// Constructor + EmojiPickerView( + Config config, + EmojiViewState state, + this.handleSendPressed, + ) : super(config, state); + + @override + _EmojiPickerViewState createState() => _EmojiPickerViewState(); + + /// See [AttachmentButton.onPressed] + final void Function()? handleSendPressed; +} + +class _EmojiPickerViewState extends State + with SingleTickerProviderStateMixin { + PageController? _pageController; + TabController? _tabController; + + @override + void initState() { + var initCategory = widget.state.categoryEmoji.indexWhere( + (element) => element.category == widget.config.initCategory); + if (initCategory == -1) { + initCategory = 0; + } + _tabController = TabController( + initialIndex: initCategory, + length: widget.state.categoryEmoji.length, + vsync: this); + _pageController = PageController(initialPage: initCategory); + super.initState(); + } + + @override + Widget build(BuildContext context) { + return LayoutBuilder( + builder: (context, constraints) { + final emojiSize = widget.config.getEmojiSize(constraints.maxWidth); + + return Container( + color: Color.fromRGBO(251, 251, 251, 1.0), //widget.config.bgColor, + child: Column( + children: [ + Row( + children: [ + Expanded( + child: TabBar( + labelColor: widget.config.iconColorSelected, + indicatorColor: widget.config.indicatorColor, + unselectedLabelColor: widget.config.iconColor, + controller: _tabController, + labelPadding: EdgeInsets.zero, + onTap: (index) { + _pageController!.jumpToPage(index); + }, + tabs: widget.state.categoryEmoji + .asMap() + .entries + .map((item) => + _buildCategory(item.key, item.value.category)) + .toList(), + ), + ), + IconButton( + padding: const EdgeInsets.only(bottom: 2), + icon: Icon( + Icons.backspace, + color: widget.config.backspaceColor, + ), + onPressed: () { + widget.state.onBackspacePressed!(); + }, + ), + // IconButton( + // // iconSize: 16, + // onPressed: () { + // widget.handleSendPressed!(); + // }, + // // backgroundColor: Colors.lightGreen, + // icon: Icon(Icons.send), + // ), + ], + ), + Flexible( + child: PageView.builder( + itemCount: widget.state.categoryEmoji.length, + controller: _pageController, + onPageChanged: (index) { + _tabController!.animateTo( + index, + duration: widget.config.tabIndicatorAnimDuration, + ); + }, + itemBuilder: (context, index) => + _buildPage(emojiSize, widget.state.categoryEmoji[index]), + ), + ), + Row( + mainAxisAlignment: MainAxisAlignment.end, + children: [], + ), + ], + ), + ); + }, + ); + } + + Widget _buildCategory(int index, Category category) { + return Tab( + icon: Icon( + widget.config.getIconForCategory(category), + ), + ); + } + + Widget _buildButtonWidget( + {required VoidCallback onPressed, required Widget child}) { + if (widget.config.buttonMode == ButtonMode.MATERIAL) { + return TextButton( + onPressed: onPressed, + child: child, + style: ButtonStyle(padding: MaterialStateProperty.all(EdgeInsets.zero)), + ); + } + return CupertinoButton( + padding: EdgeInsets.zero, onPressed: onPressed, child: child); + } + + Widget _buildPage(double emojiSize, CategoryEmoji categoryEmoji) { + // Display notice if recent has no entries yet + if (categoryEmoji.category == Category.RECENT && + categoryEmoji.emoji.isEmpty) { + return _buildNoRecent(); + } + // Build page normally + return GridView.count( + scrollDirection: Axis.vertical, + physics: const ScrollPhysics(), + shrinkWrap: true, + primary: true, + padding: const EdgeInsets.all(0), + crossAxisCount: widget.config.columns, + mainAxisSpacing: widget.config.verticalSpacing, + crossAxisSpacing: widget.config.horizontalSpacing, + children: categoryEmoji.emoji + .map((item) => _buildEmoji(emojiSize, categoryEmoji, item)) + .toList(), + ); + } + + Widget _buildEmoji( + double emojiSize, + CategoryEmoji categoryEmoji, + Emoji emoji, + ) { + return _buildButtonWidget( + onPressed: () { + widget.state.onEmojiSelected(categoryEmoji.category, emoji); + }, + child: FittedBox( + fit: BoxFit.fill, + child: Text( + emoji.emoji, + textScaleFactor: 1.0, + style: TextStyle( + fontSize: emojiSize, + backgroundColor: Colors.transparent, + ), + ), + )); + } + + Widget _buildNoRecent() { + return Center( + child: Text( '无最近使用表情', + // widget.config.noRecentsText, + // style: widget.config.noRecentsStyle, + textAlign: TextAlign.center, + )); + } +} diff --git a/bytedesk_kefu/lib/ui/widget/extra_item.dart b/bytedesk_kefu/lib/ui/widget/extra_item.dart new file mode 100644 index 0000000..a2e5998 --- /dev/null +++ b/bytedesk_kefu/lib/ui/widget/extra_item.dart @@ -0,0 +1,297 @@ +import 'package:carousel_slider/carousel_slider.dart'; +import 'package:flutter/material.dart'; +// import 'package:flutter/widgets.dart'; +// import 'package:get/get.dart'; +// import 'package:get/get_utils/src/extensions/internacionalization.dart'; +// import 'package:imboy/config/const.dart'; + +class ExtraItem extends StatelessWidget { + const ExtraItem({ + Key? key, + required this.onPressed, + required this.image, + double? this.width, + double? this.height, + required this.title, + }) : super(key: key); + + final ImageProvider image; + final void Function()? onPressed; + final double? width; + final double? height; + final String title; + + + @override + Widget build(BuildContext context) { + return GestureDetector( + onTap: this.onPressed ?? () => { + print('功能暂未实现') + }, + child: Padding( + padding: EdgeInsets.only(left: 15, top: 13, right: 15, bottom: 0), + child: Column( + children: [ + Container( + width: this.width ?? 56, + height: this.height ?? 56, + // margin: EdgeInsets.symmetric(horizontal: 10), + child: Material( + color: AppColors.ChatInputBackgroundColor, + // INK可以实现装饰容器 + child: new Ink( + // 用ink圆角矩形 + decoration: BoxDecoration( + // 背景 + color: AppColors.ChatInputBackgroundColor, + // 设置四周圆角 角度 + borderRadius: BorderRadius.all(Radius.circular(16.0)), + // 设置四周边框 + border: Border.all( + width: 1, + color: AppColors.ChatInputBackgroundColor, + ), + ), + child: Image( + image: this.image, + ), + ), + ), + ), + Text(this.title), + ], + ), + ), + ); + } +} + +class ExtraItems extends StatefulWidget { + const ExtraItems({ + Key? key, + this.handleImageSelection, + this.handleFileSelection, + this.handlePickerSelection, + this.handleUploadVideo, + this.handleCaptureVideo, + }) : super(key: key); + + final void Function()? handleImageSelection; + final void Function()? handleFileSelection; + final void Function()? handlePickerSelection; + final void Function()? handleUploadVideo; + final void Function()? handleCaptureVideo; + + @override + _ExtraItemsState createState() => _ExtraItemsState(); +} + +class _ExtraItemsState extends State { + int _current = 0; + CarouselController _controller = CarouselController(); + + @override + Widget build(BuildContext context) { + var items = [ + Column( + children: [ + Row( + children: [ + ExtraItem( + title: "照片", + image: AssetImage('assets/images/chat/extra_photo.webp'), + onPressed: widget.handleImageSelection, + ), + ExtraItem( + title: "拍摄", + image: AssetImage('assets/images/chat/extra_camera.webp'), + onPressed: widget.handlePickerSelection, + ), + ExtraItem( + title: "上传视频", + image: AssetImage('assets/images/chat/extra_media.webp'), + onPressed: widget.handleUploadVideo, + ), + ExtraItem( + title: "录制视频", + image: AssetImage('assets/images/chat/extra_videocall.webp'), + onPressed: widget.handleCaptureVideo, + ), + // ExtraItem( + // title: "位置", + // image: AssetImage('assets/images/chat/extra_localtion.webp'), + // onPressed: null, + // ), + ], + ), + Row( + children: [ + // TODO: 选择文件 + // ExtraItem( + // title: "文件", + // image: AssetImage('assets/images/chat/extra_file.webp'), + // onPressed: widget.handleFileSelection, + // ), + // ExtraItem( + // title: "语音输入", + // image: AssetImage('assets/images/chat/extra_voice.webp'), + // onPressed: null, + // ), + // ExtraItem( + // title: "收藏", + // image: AssetImage('assets/images/chat/extra_favorite.webp'), + // onPressed: null, + // ), + // ExtraItem( + // title: "个人名片", + // image: AssetImage('assets/images/chat/extra_card.webp'), + // onPressed: null, + // ), + ], + ) + ], + ), + // Column( + // children: [ + // Row(children: [ + // ExtraItem( + // title: "文件", + // image: AssetImage('assets/images/chat/extra_file.webp'), + // onPressed: widget.handleFileSelection, + // ), + // ExtraItem( + // title: "卡券", + // image: AssetImage('assets/images/chat/extra_wallet.png'), + // onPressed: null, + // ), + // ]), + // ], + // ), + ]; + return Column( + children: [ + Expanded( + child: CarouselSlider( + options: CarouselOptions( + height: 50, // Get.height, + viewportFraction: 1.0, + aspectRatio: 2.0, + scrollDirection: Axis.horizontal, + disableCenter: true, + initialPage: 1, + enableInfiniteScroll: false, + onPageChanged: (index, reason) { + setState(() { + _current = index; + }); + }, + ), + items: items.map((tab) { + return Padding( + padding: EdgeInsets.only(left: 8), + child: tab, + ); + }).toList(), + ), + ), + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: items.asMap().entries.map((entry) { + return GestureDetector( + onTap: () => _controller.animateToPage(entry.key), + child: Container( + width: 10.0, + height: 10.0, + margin: EdgeInsets.symmetric( + vertical: 8.0, + horizontal: 6.0, + ), + decoration: BoxDecoration( + shape: BoxShape.circle, + color: (Theme.of(context).brightness == Brightness.dark + ? Colors.white + : Colors.black) + .withOpacity(_current == entry.key ? 0.7 : 0.2), + ), + ), + ); + }).toList(), + ), + ], + ); + } +} + +class AppColors { + static const AppBarColor = Color.fromRGBO(237, 237, 237, 1); + + static const BgColor = Color.fromRGBO(255, 255, 255, 1); + + static const LineColor = Colors.grey; + + static const TipColor = Color.fromRGBO(89, 96, 115, 1.0); + + static const MainTextColor = Color.fromRGBO(115, 115, 115, 1.0); + + static const LabelTextColor = Color.fromRGBO(144, 144, 144, 1.0); + + static const ItemBgColor = Color.fromRGBO(75, 75, 75, 1.0); + + static const ItemOnColor = Color.fromRGBO(68, 68, 68, 1.0); + + static const ButtonTextColor = Color.fromRGBO(112, 113, 135, 1.0); + + static const TitleColor = 0xff181818; + static const ButtonArrowColor = 0xffadadad; + + /// + + /// 主背景 白色 + static const Color primaryBackground = Color.fromARGB(255, 255, 255, 255); + + /// 主文本 灰色 + static const Color primaryText = Color.fromARGB(255, 45, 45, 47); + + /// 主控件-背景 绿色 + static const Color primaryElement = Color.fromARGB(255, 109, 192, 102); + + /// 主控件-文本 白色 + static const Color primaryElementText = Color.fromARGB(255, 255, 255, 255); + + // ***************************************** + + /// 第二种控件-背景色 淡灰色 + static const Color secondaryElement = Color.fromARGB(255, 246, 246, 246); + + /// 第二种控件-文本 浅绿色 + static const Color secondaryElementText = Color.fromRGBO(169, 234, 122, 1.0); + + // ***************************************** + + /// 第三种控件-背景色 石墨色 + static const Color thirdElement = Color.fromARGB(255, 45, 45, 47); + + /// 第三种控件-文本 浅灰色2 + static const Color thirdElementText = Color.fromARGB(255, 141, 141, 142); + + // ***************************************** + + /// tabBar 默认颜色 灰色 + static const Color tabBarElement = Color.fromARGB(255, 208, 208, 208); + + /// tabCellSeparator 单元格底部分隔条 颜色 + static const Color tabCellSeparator = Color.fromARGB(255, 230, 230, 231); + + // for chat + static const ChatBg = Color.fromRGBO(243, 243, 243, 1.0); + static const ChatSendMessgeBgColor = Color.fromRGBO(169, 234, 122, 1.0); + static const ChatSentMessageBodyTextColor = Color.fromRGBO(19, 29, 13, 1.0); + + static const ChatReceivedMessageBodyTextColor = + Color.fromRGBO(25, 25, 25, 1.0); + static const ChatReceivedMessageBodyBgColor = + Color.fromRGBO(255, 255, 255, 1.0); + static const ChatInputBackgroundColor = Color.fromRGBO(240, 240, 240, 1.0); + static const ChatInputFillGgColor = Color.fromRGBO(251, 251, 251, 1.0); + // end for chat +} diff --git a/bytedesk_kefu/lib/ui/widget/image_button.dart b/bytedesk_kefu/lib/ui/widget/image_button.dart new file mode 100644 index 0000000..d67c00d --- /dev/null +++ b/bytedesk_kefu/lib/ui/widget/image_button.dart @@ -0,0 +1,40 @@ +import 'package:flutter/material.dart'; + +class ImageButton extends StatefulWidget { + const ImageButton({ + Key? key, + required this.onPressed, + required this.image, + double? this.width, + double? this.height, + String? this.title, + }) : super(key: key); + + final ImageProvider image; + final void Function()? onPressed; + final double? width; + final double? height; + final String? title; + + @override + _ImageButtonState createState() => _ImageButtonState(); +} + +class _ImageButtonState extends State { + @override + Widget build(BuildContext context) { + return GestureDetector( + onTap: widget.onPressed, + child: Container( + width: widget.width ?? 44, + height: widget.height ?? 44, + alignment: Alignment.center, + child: Image( + image: widget.image, + width: widget.width ?? 35, + height: widget.height ?? 35, + ), + ), + ); + } +} diff --git a/bytedesk_kefu/lib/ui/widget/send_button_visibility_mode.dart b/bytedesk_kefu/lib/ui/widget/send_button_visibility_mode.dart new file mode 100644 index 0000000..0b3e0c9 --- /dev/null +++ b/bytedesk_kefu/lib/ui/widget/send_button_visibility_mode.dart @@ -0,0 +1,12 @@ +/// Used to toggle the visibility behavior of the [SendButton] based on the +/// [TextField] state inside the [Input] widget. +enum SendButtonVisibilityMode { + /// Always show the [SendButton] regardless of the [TextField] state. + always, + + /// The [SendButton] will only appear when the [TextField] is not empty. + editing, + + /// Always hide the [SendButton] regardless of the [TextField] state. + hidden, +} diff --git a/bytedesk_kefu/lib/ui/widget/voice_record/custom_overlay.dart b/bytedesk_kefu/lib/ui/widget/voice_record/custom_overlay.dart new file mode 100644 index 0000000..6a7af9a --- /dev/null +++ b/bytedesk_kefu/lib/ui/widget/voice_record/custom_overlay.dart @@ -0,0 +1,41 @@ +import 'package:flutter/material.dart'; + +class CustomOverlay extends StatelessWidget { + final Widget? icon; + final BoxDecoration decoration; + final double width; + final double height; + + const CustomOverlay({ + Key? key, + this.icon, + this.decoration = const BoxDecoration( + color: Color(0xff77797A), + borderRadius: BorderRadius.all(Radius.circular(20.0)), + ), + this.width = 160, + this.height = 160, + }) : super(key: key); + + @override + Widget build(BuildContext context) { + return Positioned( + top: MediaQuery.of(context).size.height * 0.5 - width / 2, + left: MediaQuery.of(context).size.width * 0.5 - height / 2, + child: Material( + type: MaterialType.transparency, + child: Center( + child: Opacity( + opacity: 0.8, + child: Container( + width: width, + height: height, + decoration: decoration, + child: icon, + ), + ), + ), + ), + ); + } +} diff --git a/bytedesk_kefu/lib/ui/widget/voice_record/voice_animation.dart b/bytedesk_kefu/lib/ui/widget/voice_record/voice_animation.dart new file mode 100644 index 0000000..ddf0e91 --- /dev/null +++ b/bytedesk_kefu/lib/ui/widget/voice_record/voice_animation.dart @@ -0,0 +1,123 @@ +import 'package:flutter/material.dart'; + +class VoiceAnimation extends StatefulWidget { + final double width; + final double height; + int interval; + bool isStop = false; + bool userIsAuthor; + var callStart; + late VoiceAnimationState voiceAnimationImageState; + + VoiceAnimation({ + required this.width, + required this.height, + required this.isStop, + required this.userIsAuthor, + this.interval = 200, + }); + + @override + State createState() { + voiceAnimationImageState = VoiceAnimationState(); + return voiceAnimationImageState; + } + + start() { + voiceAnimationImageState.start(); + } + + stop() { + voiceAnimationImageState.stop(); + } +} + +class VoiceAnimationState extends State + with SingleTickerProviderStateMixin { + // 动画控制 + late Animation _animation; + late AnimationController _controller; + int interval = 200; + + List voicePlayingAsset = []; + + @override + void initState() { + super.initState(); + + voicePlayingAsset.add("voice_playing_1.png"); + voicePlayingAsset.add("voice_playing_2.png"); + voicePlayingAsset.add("voice_playing_3.png"); + + if (widget.interval != null) { + interval = widget.interval; + } + final int imageCount = 3; + final int maxTime = interval * imageCount; + + // 启动动画controller + _controller = AnimationController( + duration: Duration(milliseconds: maxTime), + vsync: this, + ); + _controller.addStatusListener((AnimationStatus status) { + if (status == AnimationStatus.completed) { + _controller.forward(from: 2.0); // 完成后重新开始 + } + }); + + _animation = Tween(begin: -(imageCount - 2).toDouble(), end: 2) + .animate(_controller) + ..addListener(() { + setState(() {}); + }); + } + + @override + void dispose() { + _controller.dispose(); + super.dispose(); + } + + stop() { + _controller.stop(); + } + + start() { + _controller.forward(); + } + + @override + Widget build(BuildContext context) { + if (widget.isStop) { + start(); + } else { + stop(); + } + int ix = _animation.value.floor() % voicePlayingAsset.length; + + List images = []; + String prefix = "assets/images/chat/"; + // 把所有图片都加载进内容,否则每一帧加载时会卡顿 + // receiverImages + for (int i = 0; i < voicePlayingAsset.length; ++i) { + if (i != ix) { + images.add(Image.asset( + prefix + voicePlayingAsset[i], + width: 0, + height: 0, + )); + } + } + images.add(Image.asset( + prefix + voicePlayingAsset[ix], + width: widget.width, + height: widget.height, + color: Colors.black, + )); + return Stack( + alignment: AlignmentDirectional.centerEnd, + children: images, + ); + } +} diff --git a/bytedesk_kefu/lib/ui/widget/voice_record/voice_widget.dart b/bytedesk_kefu/lib/ui/widget/voice_record/voice_widget.dart new file mode 100644 index 0000000..8c1271b --- /dev/null +++ b/bytedesk_kefu/lib/ui/widget/voice_record/voice_widget.dart @@ -0,0 +1,537 @@ +// import 'dart:async'; +// import 'dart:io'; + +// import 'package:audio_session/audio_session.dart'; +// import 'package:flutter/foundation.dart' show kIsWeb; +// import 'package:flutter/material.dart'; +// import 'package:flutter_sound/flutter_sound.dart'; +// // import 'package:get/get.dart'; +// // import 'package:imboy/component/helper/func.dart'; +// import 'package:intl/intl.dart' show DateFormat; +// import 'package:path/path.dart'; +// import 'package:path_provider/path_provider.dart'; +// import 'package:permission_handler/permission_handler.dart'; + +// import 'custom_overlay.dart'; + +// bool strEmpty(String? val) { +// return !strNoEmpty(val); +// } + +// /// 字符串不为空 +// bool strNoEmpty(String? value) { +// if (value == null) return false; + +// return value.trim().isNotEmpty; +// } + +// class AudioFile { +// const AudioFile({ +// required this.file, +// required this.duration, +// required this.mimeType, +// }); + +// final File file; +// final Duration duration; +// final String mimeType; +// } + +// class VoiceWidget extends StatefulWidget { +// final Function()? startRecord; +// final Function(AudioFile? obj)? stopRecord; + +// final double? height; +// final EdgeInsets? margin; +// final Decoration? decoration; + +// /// startRecord 开始录制回调 stopRecord回调 +// VoiceWidget({ +// Key? key, +// this.startRecord, +// this.stopRecord, +// this.height, +// this.decoration, +// this.margin, +// }) : super(key: key); + +// @override +// _VoiceWidgetState createState() => _VoiceWidgetState(); +// } + +// class _VoiceWidgetState extends State { +// // 倒计时总时长 +// int _countTotal = 300; +// double starty = 0.0; +// double offset = 0.0; +// bool isUp = false; +// String textShow = "按住说话"; +// String toastShow = "手指上滑,取消发送"; +// String voiceIco = "assets/images/chat/voice_volume_1.png"; + +// Duration recordingDuration = const Duration(); +// // final List _levels = []; +// late String recordingMimeType; +// late Codec recordCodec; + +// Timer? _timer; +// int _count = 0; +// OverlayEntry? overlayEntry; + +// ///// +// final FlutterSoundRecorder recorderModule = FlutterSoundRecorder(); +// String recorderTxt = '00:00.000'; + +// String filePath = ''; +// StreamSubscription? recorderSubscription; +// int pos = 0; +// double dbLevel = 0; + +// late AudioSession session; + +// @override +// void initState() { +// super.initState(); +// debugPrint(">>> on chat _VoiceWidgetState initState"); +// } + +// ///显示录音悬浮布局 +// buildOverLayView(BuildContext context) { +// if (overlayEntry == null) { +// overlayEntry = OverlayEntry(builder: (content) { +// return CustomOverlay( +// icon: Column( +// children: [ +// Container( +// margin: const EdgeInsets.only(top: 10), +// child: _countTotal - _count < 11 +// ? Center( +// child: Padding( +// padding: const EdgeInsets.only(bottom: 15.0), +// child: Text( +// (_countTotal - _count).toString(), +// style: TextStyle( +// fontSize: 70.0, +// color: Colors.white, +// ), +// ), +// ), +// ) +// : new Image.asset( +// voiceIco, +// width: 100, +// height: 100, +// // package: 'flutter_plugin_record', +// ), +// ), +// Container( +// child: Text( +// toastShow + "\n" + recorderTxt, +// textAlign: TextAlign.center, +// style: TextStyle( +// fontStyle: FontStyle.normal, +// color: Colors.white, +// fontSize: 14, +// ), +// ), +// ) +// ], +// ), +// ); +// }); +// Overlay.of(context)!.insert(overlayEntry!); +// } +// } + +// showVoiceView(BuildContext ctx) { +// setState(() { +// textShow = "松开结束"; +// }); + +// ///显示录音悬浮布局 +// buildOverLayView(ctx); + +// debugPrint(">>> on record showVoiceView"); +// recorderStart(ctx); +// } + +// hideVoiceView(BuildContext ctx) async { +// if (_timer!.isActive) { +// if (_count < 1) { +// Toast.showView( +// context: ctx, +// msg: '说话时间太短', +// icon: Text( +// '!', +// style: TextStyle( +// fontSize: 60, +// color: Colors.white, +// ), +// )); +// isUp = true; +// } +// _timer?.cancel(); +// _count = 0; +// } + +// setState(() { +// textShow = "按住说话"; +// }); + +// recorderStop(recorderModule); +// if (overlayEntry != null) { +// overlayEntry?.remove(); +// overlayEntry = null; +// } +// debugPrint( +// ">>> on record hideVoiceView isUp ${isUp}, filepath: ${filePath}"); +// if (isUp) { +// // print("取消发送"); +// } else { +// debugPrint("进行发送"); + +// widget.stopRecord!.call( +// strEmpty(filePath) +// ? null +// : AudioFile( +// file: File(filePath), +// duration: this.recordingDuration, +// // waveForm: _levels, +// mimeType: recordingMimeType, +// ), +// ); +// } +// } + +// moveVoiceView() { +// // print(offset - start); +// setState(() { +// isUp = starty - offset > 100 ? true : false; +// if (isUp) { +// textShow = "松开手指,取消发送"; +// toastShow = textShow; +// } else { +// textShow = "松开结束"; +// toastShow = "手指上滑,取消发送"; +// } +// }); +// } + +// void cancelRecorderSubscriptions() { +// if (recorderSubscription != null) { +// recorderSubscription!.cancel(); +// recorderSubscription = null; +// } +// } + +// /// Creates an path to a temporary file. +// Future _createTempAacFilePath(String name) async { +// if (kIsWeb) { +// throw Exception( +// 'This method only works for mobile as it creates a temporary AAC file', +// ); +// } +// String path; +// final tmpDir = await getTemporaryDirectory(); +// path = '${join(tmpDir.path, name)}.aac'; +// final parent = dirname(path); +// await Directory(parent).create(recursive: true); + +// return path; +// } + +// Future openTheRecorder() async { +// if (!kIsWeb) { +// var status = await Permission.microphone.request(); +// if (status != PermissionStatus.granted) { +// throw RecordingPermissionException('Microphone permission not granted'); +// } +// } +// if (session == null) { +// session = await AudioSession.instance; +// await session.configure(AudioSessionConfiguration( +// avAudioSessionCategory: AVAudioSessionCategory.playAndRecord, +// avAudioSessionCategoryOptions: +// AVAudioSessionCategoryOptions.allowBluetooth | +// AVAudioSessionCategoryOptions.defaultToSpeaker, +// avAudioSessionMode: AVAudioSessionMode.spokenAudio, +// avAudioSessionRouteSharingPolicy: +// AVAudioSessionRouteSharingPolicy.defaultPolicy, +// avAudioSessionSetActiveOptions: AVAudioSessionSetActiveOptions.none, +// androidAudioAttributes: const AndroidAudioAttributes( +// contentType: AndroidAudioContentType.speech, +// flags: AndroidAudioFlags.none, +// usage: AndroidAudioUsage.voiceCommunication, +// ), +// androidAudioFocusGainType: AndroidAudioFocusGainType.gain, +// androidWillPauseWhenDucked: true, +// )); +// } +// } + +// // ------- Here is the code to playback ----------------------- +// /// 开始录音 +// void recorderStart(BuildContext ctx) async { +// debugPrint(">>> on record start"); +// try { +// var status = await Permission.microphone.request(); +// if (status != PermissionStatus.granted) { +// // Get.snackbar("", "未获取到麦克风权限"); +// throw RecordingPermissionException("未获取到麦克风权限"); +// } + +// await recorderModule.openRecorder(); + +// // String name = "${Xid().toString()}"; +// String name = "recardtmp"; +// if (kIsWeb) { +// if (await recorderModule.isEncoderSupported(Codec.opusWebM)) { +// filePath = '$name.webm'; +// recordCodec = Codec.opusWebM; +// recordingMimeType = 'audio/webm;codecs="opus"'; +// } else { +// filePath = '$name.mp4'; +// recordCodec = Codec.aacMP4; +// recordingMimeType = 'audio/aac'; +// } +// } else { +// filePath = await _createTempAacFilePath(name); +// recordCodec = Codec.aacADTS; +// recordingMimeType = 'audio/aac'; +// } +// // 必须要设置,才能够监听 振幅大小 +// setSubscriptionDuration(40); + +// await recorderModule.startRecorder( +// toFile: filePath, +// codec: recordCodec, +// bitRate: 8000, +// sampleRate: 8000, +// ); + +// /// 监听录音 +// recorderSubscription = recorderModule.onProgress!.listen((e) { +// // debugPrint(">>> on record listen e ${e.toString()}"); +// setState(() { +// pos = e.duration.inMilliseconds; +// }); +// // debugPrint(">>> on record listen pos: ${pos}, dbLevel: ${e.decibels};"); +// if (e != null && e.duration != null) { +// recordingDuration = e.duration; +// // _levels.add(e.decibels ?? 0.0); + +// DateTime date = new DateTime.fromMillisecondsSinceEpoch( +// e.duration.inMilliseconds, +// isUtc: true, +// ); +// String txt = DateFormat('mm:ss.SSS').format(date); +// if (date.second >= _countTotal) { +// // recorderStop(recorderModule); +// hideVoiceView(ctx); +// } +// if (e.decibels != null) { +// setState(() { +// recorderTxt = txt.substring(0, 9); +// dbLevel = e.decibels!.toDouble(); +// // debugPrint(">>> on record 当前振幅:$dbLevel"); +// }); +// } +// } + +// if (e.decibels != null) { +// dbLevel = e.decibels as double; +// double voiceData = ((dbLevel * 100.0).floor()) / 10000; +// if (voiceData > 0 && voiceData < 0.1) { +// voiceIco = "assets/images/chat/voice_volume_2.png"; +// } else if (voiceData > 0.2 && voiceData < 0.3) { +// voiceIco = "assets/images/chat/voice_volume_3.png"; +// } else if (voiceData > 0.3 && voiceData < 0.4) { +// voiceIco = "assets/images/chat/voice_volume_4.png"; +// } else if (voiceData > 0.4 && voiceData < 0.5) { +// voiceIco = "assets/images/chat/voice_volume_5.png"; +// } else if (voiceData > 0.5 && voiceData < 0.6) { +// voiceIco = "assets/images/chat/voice_volume_6.png"; +// } else if (voiceData > 0.6 && voiceData < 0.7) { +// voiceIco = "assets/images/chat/voice_volume_7.png"; +// } else if (voiceData > 0.7 && voiceData < 1) { +// voiceIco = "assets/images/chat/voice_volume_7.png"; +// } else { +// voiceIco = "assets/images/chat/voice_volume_1.png"; +// } +// if (overlayEntry != null) { +// overlayEntry!.markNeedsBuild(); +// } +// // debugPrint( +// // ">>> on record 振幅大小 " + voiceData.toString() + " " + voiceIco); +// setState(() { +// dbLevel = dbLevel; +// voiceIco = voiceIco; +// }); +// } +// }); +// setState(() { +// filePath = filePath; +// }); +// } catch (err) { +// setState(() { +// recorderStop(recorderModule); +// cancelRecorderSubscriptions(); +// }); +// } +// } + +// /// 结束录音 +// Future recorderStop(FlutterSoundRecorder recorder) async { +// try { +// String? filepath = await recorder.stopRecorder(); +// cancelRecorderSubscriptions(); +// setState(() { +// dbLevel = 0.0; +// pos = 0; +// recorderTxt = '00:00.000'; +// }); +// // _getDuration(); +// return filepath; +// } catch (err) { +// print('stopRecorder error: $err'); +// } +// } + +// /** +// * 设置订阅周期 +// */ +// Future setSubscriptionDuration( +// double d) async // d is between 0.0 and 2000 (milliseconds) +// { +// setState(() {}); +// await recorderModule.setSubscriptionDuration( +// Duration(milliseconds: d.floor()), +// ); +// } +// // --------------------- UI ------------------- + +// @override +// Widget build(BuildContext context) { +// return Container( +// child: GestureDetector( +// onLongPressStart: (details) { +// starty = details.globalPosition.dy; +// _timer = Timer.periodic(Duration(milliseconds: 1000), (t) { +// _count++; +// if (_count == _countTotal) { +// hideVoiceView(context); +// } +// }); +// showVoiceView(context); +// }, +// onLongPressEnd: (details) { +// hideVoiceView(context); +// }, +// onLongPressMoveUpdate: (details) { +// offset = details.globalPosition.dy; +// moveVoiceView(); +// }, +// child: Container( +// height: widget.height ?? 60, +// decoration: widget.decoration ?? +// BoxDecoration( +// borderRadius: BorderRadius.circular(6.0), +// border: Border.all( +// width: 1.0, +// color: Colors.white70, +// ), +// color: Colors.white70, +// ), +// margin: widget.margin ?? EdgeInsets.fromLTRB(50, 0, 50, 20), +// child: Center( +// child: Text( +// textShow, +// // '${textShow}(pos: ${pos})', +// ), +// ), +// ), +// ), +// ); +// } + +// @override +// void dispose() { +// recorderStop(recorderModule); +// cancelRecorderSubscriptions(); + +// // Be careful : you must `close` the audio session when you have finished with it. +// recorderModule.closeRecorder(); + +// // recordPlugin?.dispose(); +// _timer?.cancel(); +// super.dispose(); +// } +// } + +// class Toast { +// static showView({ +// BuildContext? context, +// String? msg, +// TextStyle? style, +// Widget? icon, +// Duration duration = const Duration(seconds: 1), +// int count = 3, +// Function? onTap, +// }) { +// OverlayEntry? overlayEntry; +// int _count = 0; + +// void removeOverlay() { +// overlayEntry?.remove(); +// overlayEntry = null; +// } + +// if (overlayEntry == null) { +// overlayEntry = OverlayEntry(builder: (content) { +// return Container( +// child: GestureDetector( +// onTap: () { +// if (onTap != null) { +// removeOverlay(); +// onTap(); +// } +// }, +// child: CustomOverlay( +// icon: Column( +// children: [ +// Padding( +// child: icon, +// padding: const EdgeInsets.only( +// bottom: 10.0, +// ), +// ), +// Container( +// child: Text( +// msg ?? '', +// style: style ?? +// TextStyle( +// fontStyle: FontStyle.normal, +// color: Colors.white, +// fontSize: 16, +// ), +// ), +// ) +// ], +// ), +// ), +// ), +// ); +// }); +// Overlay.of(context!)!.insert(overlayEntry!); +// if (onTap != null) return; +// Timer.periodic(duration, (timer) { +// _count++; +// if (_count == count) { +// _count = 0; +// timer.cancel(); +// removeOverlay(); +// } +// }); +// } +// } +// } diff --git a/bytedesk_kefu/lib/util/bytedesk_constants.dart b/bytedesk_kefu/lib/util/bytedesk_constants.dart index 20d265c..e9115a2 100755 --- a/bytedesk_kefu/lib/util/bytedesk_constants.dart +++ b/bytedesk_kefu/lib/util/bytedesk_constants.dart @@ -18,7 +18,7 @@ class BytedeskConstants { // static const String httpUploadUrl = 'http://' + mqttHost + ':8000'; // static const String host = mqttHost + ':8000'; // static const int mqttPort = 3883; // not secure - // static const String mqttHost = '172.16.0.78'; + // static const String mqttHost = '172.16.0.3'; // 本机测试 // static const bool isDebug = true; @@ -45,7 +45,7 @@ class BytedeskConstants { // static const String httpBaseUrliOS = 'http://' + mqttHost + ':8000'; // static const String httpUploadUrl = 'http://' + mqttHost + ':8000'; // static const String host = mqttHost + ':8000'; - // static const String mqttHost = '192.168.0.106'; + // static const String mqttHost = '192.168.0.104'; // 线上 static const bool isDebug = false; // false; diff --git a/bytedesk_kefu/lib/vendors/emoji_picker_flutter/emoji_picker_flutter.dart b/bytedesk_kefu/lib/vendors/emoji_picker_flutter/emoji_picker_flutter.dart new file mode 100644 index 0000000..5fba8eb --- /dev/null +++ b/bytedesk_kefu/lib/vendors/emoji_picker_flutter/emoji_picker_flutter.dart @@ -0,0 +1,9 @@ +library emoji_picker_flutter; + +export './src/category_icons.dart'; +export './src/config.dart'; +export './src/emoji.dart'; +export './src/emoji_picker.dart'; +export './src/emoji_picker_builder.dart'; +export './src/emoji_picker_utils.dart'; +export './src/emoji_view_state.dart'; diff --git a/bytedesk_kefu/lib/vendors/emoji_picker_flutter/src/category_emoji.dart b/bytedesk_kefu/lib/vendors/emoji_picker_flutter/src/category_emoji.dart new file mode 100644 index 0000000..4930316 --- /dev/null +++ b/bytedesk_kefu/lib/vendors/emoji_picker_flutter/src/category_emoji.dart @@ -0,0 +1,13 @@ +import '../emoji_picker_flutter.dart'; + +/// Container for Category and their emoji +class CategoryEmoji { + /// Constructor + CategoryEmoji(this.category, this.emoji); + + /// Category instance + final Category category; + + /// List of emoji of this category + List emoji; +} diff --git a/bytedesk_kefu/lib/vendors/emoji_picker_flutter/src/category_icon.dart b/bytedesk_kefu/lib/vendors/emoji_picker_flutter/src/category_icon.dart new file mode 100644 index 0000000..18e4783 --- /dev/null +++ b/bytedesk_kefu/lib/vendors/emoji_picker_flutter/src/category_icon.dart @@ -0,0 +1,20 @@ +import 'package:flutter/material.dart'; + +/// Class that defines the icon representing a [Category] +class CategoryIcon { + /// Icon of Category + const CategoryIcon({ + required this.icon, + this.color = const Color.fromRGBO(211, 211, 211, 1), + this.selectedColor = const Color.fromRGBO(178, 178, 178, 1), + }); + + /// The icon to represent the category + final IconData icon; + + /// The default color of the icon + final Color color; + + /// The color of the icon once the category is selected + final Color selectedColor; +} diff --git a/bytedesk_kefu/lib/vendors/emoji_picker_flutter/src/category_icons.dart b/bytedesk_kefu/lib/vendors/emoji_picker_flutter/src/category_icons.dart new file mode 100644 index 0000000..3096680 --- /dev/null +++ b/bytedesk_kefu/lib/vendors/emoji_picker_flutter/src/category_icons.dart @@ -0,0 +1,49 @@ +import './category_icon.dart'; +import 'package:flutter/material.dart'; + +/// Class used to define all the [CategoryIcon] shown for each [Category] +/// +/// This allows the keyboard to be personalized by changing icons shown. +/// If a [CategoryIcon] is set as null or not defined during initialization, +/// the default icons will be used instead +class CategoryIcons { + /// Constructor + const CategoryIcons({ + this.recentIcon = Icons.access_time, + this.smileyIcon = Icons.tag_faces, + this.animalIcon = Icons.pets, + this.foodIcon = Icons.fastfood, + this.activityIcon = Icons.directions_run, + this.travelIcon = Icons.location_city, + this.objectIcon = Icons.lightbulb_outline, + this.symbolIcon = Icons.emoji_symbols, + this.flagIcon = Icons.flag, + }); + + /// Icon for [Category.RECENT] + final IconData recentIcon; + + /// Icon for [Category.SMILEYS] + final IconData smileyIcon; + + /// Icon for [Category.ANIMALS] + final IconData animalIcon; + + /// Icon for [Category.FOODS] + final IconData foodIcon; + + /// Icon for [Category.ACTIVITIES] + final IconData activityIcon; + + /// Icon for [Category.TRAVEL] + final IconData travelIcon; + + /// Icon for [Category.OBJECTS] + final IconData objectIcon; + + /// Icon for [Category.SYMBOLS] + final IconData symbolIcon; + + /// Icon for [Category.FLAGS] + final IconData flagIcon; +} diff --git a/bytedesk_kefu/lib/vendors/emoji_picker_flutter/src/config.dart b/bytedesk_kefu/lib/vendors/emoji_picker_flutter/src/config.dart new file mode 100644 index 0000000..de2e8be --- /dev/null +++ b/bytedesk_kefu/lib/vendors/emoji_picker_flutter/src/config.dart @@ -0,0 +1,192 @@ +import 'dart:math'; + +import './category_icons.dart'; +import './emoji_picker.dart'; +import 'package:flutter/material.dart'; + +/// Default Widget if no recent is available +const DefaultNoRecentsWidget = Text( + 'No Recents', + style: TextStyle(fontSize: 20, color: Colors.black26), + textAlign: TextAlign.center, +); + +/// Config for customizations +class Config { + /// Constructor + const Config( + {this.columns = 7, + this.emojiSizeMax = 32.0, + this.verticalSpacing = 0, + this.horizontalSpacing = 0, + this.gridPadding = EdgeInsets.zero, + this.initCategory = Category.RECENT, + this.bgColor = const Color(0xFFEBEFF2), + this.indicatorColor = Colors.blue, + this.iconColor = Colors.grey, + this.iconColorSelected = Colors.blue, + this.progressIndicatorColor = Colors.blue, + this.backspaceColor = Colors.blue, + this.skinToneDialogBgColor = Colors.white, + this.skinToneIndicatorColor = Colors.grey, + this.enableSkinTones = true, + this.showRecentsTab = true, + this.recentsLimit = 28, + this.replaceEmojiOnLimitExceed = false, + this.noRecents = DefaultNoRecentsWidget, + this.tabIndicatorAnimDuration = kTabScrollDuration, + this.categoryIcons = const CategoryIcons(), + this.buttonMode = ButtonMode.MATERIAL}); + + /// Number of emojis per row + final int columns; + + /// Width and height the emoji will be maximal displayed + /// Can be smaller due to screen size and amount of columns + final double emojiSizeMax; + + /// Verical spacing between emojis + final double verticalSpacing; + + /// Horizontal spacing between emojis + final double horizontalSpacing; + + /// The initial [Category] that will be selected + /// This [Category] will have its button in the bottombar darkened + final Category initCategory; + + /// The background color of the Widget + final Color bgColor; + + /// The color of the category indicator + final Color indicatorColor; + + /// The color of the category icons + final Color iconColor; + + /// The color of the category icon when selected + final Color iconColorSelected; + + /// The color of the loading indicator during initalization + final Color progressIndicatorColor; + + /// The color of the backspace icon button + final Color backspaceColor; + + /// The background color of the skin tone dialog + final Color skinToneDialogBgColor; + + /// Color of the small triangle next to multiple skin tone emoji + final Color skinToneIndicatorColor; + + /// Enable feature to select a skin tone of certain emoji's + final bool enableSkinTones; + + /// Show extra tab with recently used emoji + final bool showRecentsTab; + + /// Limit of recently used emoji that will be saved + final int recentsLimit; + + /// A widget (usually [Text]) to be displayed if no recent emojis to display + final Widget noRecents; + + /// Duration of tab indicator to animate to next category + final Duration tabIndicatorAnimDuration; + + /// Determines the icon to display for each [Category] + final CategoryIcons categoryIcons; + + /// Change between Material and Cupertino button style + final ButtonMode buttonMode; + + /// The padding of GridView, default is [EdgeInsets.zero] + final EdgeInsets gridPadding; + + /// Replace latest emoji on recents list on limit exceed + final bool replaceEmojiOnLimitExceed; + + /// Get Emoji size based on properties and screen width + double getEmojiSize(double width) { + final maxSize = width / columns; + return min(maxSize, emojiSizeMax); + } + + /// Returns the icon for the category + IconData getIconForCategory(Category category) { + switch (category) { + case Category.RECENT: + return categoryIcons.recentIcon; + case Category.SMILEYS: + return categoryIcons.smileyIcon; + case Category.ANIMALS: + return categoryIcons.animalIcon; + case Category.FOODS: + return categoryIcons.foodIcon; + case Category.TRAVEL: + return categoryIcons.travelIcon; + case Category.ACTIVITIES: + return categoryIcons.activityIcon; + case Category.OBJECTS: + return categoryIcons.objectIcon; + case Category.SYMBOLS: + return categoryIcons.symbolIcon; + case Category.FLAGS: + return categoryIcons.flagIcon; + default: + throw Exception('Unsupported Category'); + } + } + + @override + bool operator ==(other) { + return (other is Config) && + other.columns == columns && + other.emojiSizeMax == emojiSizeMax && + other.verticalSpacing == verticalSpacing && + other.horizontalSpacing == horizontalSpacing && + other.initCategory == initCategory && + other.bgColor == bgColor && + other.indicatorColor == indicatorColor && + other.iconColor == iconColor && + other.iconColorSelected == iconColorSelected && + other.progressIndicatorColor == progressIndicatorColor && + other.backspaceColor == backspaceColor && + other.skinToneDialogBgColor == skinToneDialogBgColor && + other.skinToneIndicatorColor == skinToneIndicatorColor && + other.enableSkinTones == enableSkinTones && + other.showRecentsTab == showRecentsTab && + other.recentsLimit == recentsLimit && + other.noRecents == noRecents && + other.tabIndicatorAnimDuration == tabIndicatorAnimDuration && + other.categoryIcons == categoryIcons && + other.buttonMode == buttonMode && + other.gridPadding == gridPadding && + other.replaceEmojiOnLimitExceed == replaceEmojiOnLimitExceed; + } + + @override + int get hashCode => + columns.hashCode ^ + emojiSizeMax.hashCode ^ + verticalSpacing.hashCode ^ + horizontalSpacing.hashCode ^ + initCategory.hashCode ^ + bgColor.hashCode ^ + indicatorColor.hashCode ^ + iconColor.hashCode ^ + iconColorSelected.hashCode ^ + progressIndicatorColor.hashCode ^ + backspaceColor.hashCode ^ + skinToneDialogBgColor.hashCode ^ + skinToneIndicatorColor.hashCode ^ + enableSkinTones.hashCode ^ + showRecentsTab.hashCode ^ + recentsLimit.hashCode ^ + noRecents.hashCode ^ + tabIndicatorAnimDuration.hashCode ^ + categoryIcons.hashCode ^ + buttonMode.hashCode ^ + gridPadding.hashCode ^ + replaceEmojiOnLimitExceed.hashCode; +} diff --git a/bytedesk_kefu/lib/vendors/emoji_picker_flutter/src/default_emoji_picker_view.dart b/bytedesk_kefu/lib/vendors/emoji_picker_flutter/src/default_emoji_picker_view.dart new file mode 100644 index 0000000..5247ef8 --- /dev/null +++ b/bytedesk_kefu/lib/vendors/emoji_picker_flutter/src/default_emoji_picker_view.dart @@ -0,0 +1,375 @@ +import '../emoji_picker_flutter.dart'; +import './category_emoji.dart'; +import './emoji_picker_internal_utils.dart'; +import './emoji_skin_tones.dart'; +import './triangle_shape.dart'; +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; + +/// Default EmojiPicker Implementation +class DefaultEmojiPickerView extends EmojiPickerBuilder { + /// Constructor + DefaultEmojiPickerView(Config config, EmojiViewState state) + : super(config, state); + + @override + _DefaultEmojiPickerViewState createState() => _DefaultEmojiPickerViewState(); +} + +class _DefaultEmojiPickerViewState extends State + with SingleTickerProviderStateMixin { + PageController? _pageController; + TabController? _tabController; + OverlayEntry? _overlay; + late final _scrollController = ScrollController(); + late final _utils = EmojiPickerInternalUtils(); + final int _skinToneCount = 6; + final double tabBarHeight = 46; + + @override + void initState() { + var initCategory = widget.state.categoryEmoji.indexWhere( + (element) => element.category == widget.config.initCategory); + if (initCategory == -1) { + initCategory = 0; + } + _tabController = TabController( + initialIndex: initCategory, + length: widget.state.categoryEmoji.length, + vsync: this); + _pageController = PageController(initialPage: initCategory) + ..addListener(_closeSkinToneDialog); + _scrollController.addListener(_closeSkinToneDialog); + super.initState(); + } + + @override + void dispose() { + _closeSkinToneDialog(); + super.dispose(); + } + + void _closeSkinToneDialog() { + _overlay?.remove(); + _overlay = null; + } + + void _openSkinToneDialog( + Emoji emoji, + double emojiSize, + CategoryEmoji categoryEmoji, + int index, + ) { + _overlay = _buildSkinToneOverlay( + emoji, + emojiSize, + categoryEmoji, + index, + ); + Overlay.of(context)?.insert(_overlay!); + } + + Widget _buildBackspaceButton() { + if (widget.state.onBackspacePressed != null) { + return Material( + type: MaterialType.transparency, + child: IconButton( + padding: const EdgeInsets.only(bottom: 2), + icon: Icon( + Icons.backspace, + color: widget.config.backspaceColor, + ), + onPressed: () { + widget.state.onBackspacePressed!(); + }), + ); + } + return Container(); + } + + @override + Widget build(BuildContext context) { + return LayoutBuilder( + builder: (context, constraints) { + final emojiSize = widget.config.getEmojiSize(constraints.maxWidth); + + return Container( + color: widget.config.bgColor, + child: Column( + children: [ + Row( + children: [ + Expanded( + child: SizedBox( + height: tabBarHeight, + child: TabBar( + labelColor: widget.config.iconColorSelected, + indicatorColor: widget.config.indicatorColor, + unselectedLabelColor: widget.config.iconColor, + controller: _tabController, + labelPadding: EdgeInsets.zero, + onTap: (index) { + _closeSkinToneDialog(); + _pageController!.jumpToPage(index); + }, + tabs: widget.state.categoryEmoji + .asMap() + .entries + .map((item) => + _buildCategory(item.key, item.value.category)) + .toList(), + ), + ), + ), + _buildBackspaceButton(), + ], + ), + Flexible( + child: PageView.builder( + itemCount: widget.state.categoryEmoji.length, + controller: _pageController, + onPageChanged: (index) { + _tabController!.animateTo( + index, + duration: widget.config.tabIndicatorAnimDuration, + ); + }, + itemBuilder: (context, index) => + _buildPage(emojiSize, widget.state.categoryEmoji[index]), + ), + ), + ], + ), + ); + }, + ); + } + + Widget _buildCategory(int index, Category category) { + return Tab( + icon: Icon( + widget.config.getIconForCategory(category), + ), + ); + } + + Widget _buildPage(double emojiSize, CategoryEmoji categoryEmoji) { + // Display notice if recent has no entries yet + if (categoryEmoji.category == Category.RECENT && + categoryEmoji.emoji.isEmpty) { + return _buildNoRecent(); + } + // Build page normally + return GestureDetector( + onTap: _closeSkinToneDialog, + child: GridView.count( + scrollDirection: Axis.vertical, + physics: const ScrollPhysics(), + controller: _scrollController, + shrinkWrap: true, + primary: false, + padding: widget.config.gridPadding, + crossAxisCount: widget.config.columns, + mainAxisSpacing: widget.config.verticalSpacing, + crossAxisSpacing: widget.config.horizontalSpacing, + children: categoryEmoji.emoji.asMap().entries.map((item) { + final index = item.key; + final emoji = item.value; + final onPressed = () { + _closeSkinToneDialog(); + widget.state.onEmojiSelected(categoryEmoji.category, emoji); + }; + + final onLongPressed = () { + if (!emoji.hasSkinTone || !widget.config.enableSkinTones) { + _closeSkinToneDialog(); + return; + } + _closeSkinToneDialog(); + _openSkinToneDialog(emoji, emojiSize, categoryEmoji, index); + }; + + return _buildButtonWidget( + onPressed: onPressed, + onLongPressed: onLongPressed, + child: _buildEmoji( + emojiSize, + categoryEmoji, + emoji, + widget.config.enableSkinTones, + ), + ); + }).toList(), + ), + ); + } + + /// Build and display Emoji centered of its parent + Widget _buildEmoji( + double emojiSize, + CategoryEmoji categoryEmoji, + Emoji emoji, + bool showSkinToneIndicator, + ) { + // FittedBox needed for display, font scale settings + return FittedBox( + fit: BoxFit.fill, + child: Stack(children: [ + emoji.hasSkinTone && showSkinToneIndicator + ? Positioned( + bottom: 0, + right: 0, + child: CustomPaint( + size: const Size(8, 8), + painter: TriangleShape(widget.config.skinToneIndicatorColor), + ), + ) + : Container(), + Text( + emoji.emoji, + textScaleFactor: 1.0, + style: TextStyle( + fontSize: emojiSize, + backgroundColor: Colors.transparent, + ), + ), + ]), + ); + } + + /// Build different Button based on ButtonMode + Widget _buildButtonWidget({ + required VoidCallback onPressed, + required VoidCallback onLongPressed, + required Widget child, + }) { + if (widget.config.buttonMode == ButtonMode.MATERIAL) { + return TextButton( + onPressed: onPressed, + onLongPress: onLongPressed, + child: child, + style: ButtonStyle( + padding: MaterialStateProperty.all(EdgeInsets.zero), + minimumSize: MaterialStateProperty.all(Size.zero), + tapTargetSize: MaterialTapTargetSize.shrinkWrap, + ), + ); + } + return GestureDetector( + onLongPress: onLongPressed, + child: CupertinoButton( + padding: EdgeInsets.zero, + onPressed: onPressed, + child: child, + ), + ); + } + + /// Build Widget for when no recent emoji are available + Widget _buildNoRecent() { + return Center( + child: widget.config.noRecents, + ); + } + + /// Overlay for SkinTone + OverlayEntry _buildSkinToneOverlay( + Emoji emoji, + double emojiSize, + CategoryEmoji categoryEmoji, + int index, + ) { + // Calculate position of emoji in the grid + final row = index ~/ widget.config.columns; + final column = index % widget.config.columns; + // Calculate position for skin tone dialog + final renderBox = context.findRenderObject() as RenderBox; + final offset = renderBox.localToGlobal(Offset.zero); + final emojiSpace = renderBox.size.width / widget.config.columns; + final topOffset = emojiSpace; + final leftOffset = _getLeftOffset(emojiSpace, column); + final left = offset.dx + column * emojiSpace + leftOffset; + final top = tabBarHeight + + offset.dy + + row * emojiSpace - + _scrollController.offset - + topOffset; + + // Generate other skintone options + final skinTonesEmoji = SkinTone.values + .map((skinTone) => _utils.applySkinTone(emoji, skinTone)) + .toList(); + + return OverlayEntry( + builder: (context) => Positioned( + left: left, + top: top, + child: Material( + elevation: 4.0, + child: Container( + padding: const EdgeInsets.symmetric(vertical: 4.0), + color: widget.config.skinToneDialogBgColor, + child: Row( + children: [ + _buildSkinToneEmoji( + categoryEmoji, emoji, emojiSpace, emojiSize), + _buildSkinToneEmoji( + categoryEmoji, skinTonesEmoji[0], emojiSpace, emojiSize), + _buildSkinToneEmoji( + categoryEmoji, skinTonesEmoji[1], emojiSpace, emojiSize), + _buildSkinToneEmoji( + categoryEmoji, skinTonesEmoji[2], emojiSpace, emojiSize), + _buildSkinToneEmoji( + categoryEmoji, skinTonesEmoji[3], emojiSpace, emojiSize), + _buildSkinToneEmoji( + categoryEmoji, skinTonesEmoji[4], emojiSpace, emojiSize), + ], + ), + ), + ), + ), + ); + } + + // Build Emoji inside skin tone dialog + Widget _buildSkinToneEmoji( + CategoryEmoji categoryEmoji, + Emoji emoji, + double width, + double emojiSize, + ) { + return SizedBox( + width: width, + height: width, + child: _buildButtonWidget( + onPressed: () { + widget.state.onEmojiSelected(categoryEmoji.category, emoji); + _closeSkinToneDialog(); + }, + onLongPressed: () {}, + child: _buildEmoji(emojiSize, categoryEmoji, emoji, false), + ), + ); + } + + // Calucates the offset from the middle of selected emoji to the left side + // of the skin tone dialog + // Case 1: Selected Emoji is close to left border and offset needs to be + // reduced + // Case 2: Selected Emoji is close to right border and offset needs to be + // larger than half of the whole width + // Case 3: Enough space to left and right border and offset can be half + // of whole width + double _getLeftOffset(double emojiWidth, int column) { + var remainingColumns = + widget.config.columns - (column + 1 + (_skinToneCount ~/ 2)); + if (column >= 0 && column < 3) { + return -1 * column * emojiWidth; + } else if (remainingColumns < 0) { + return -1 * + ((_skinToneCount ~/ 2 - 1) + -1 * remainingColumns) * + emojiWidth; + } + return -1 * ((_skinToneCount ~/ 2) * emojiWidth) + emojiWidth / 2; + } +} diff --git a/bytedesk_kefu/lib/vendors/emoji_picker_flutter/src/emoji.dart b/bytedesk_kefu/lib/vendors/emoji_picker_flutter/src/emoji.dart new file mode 100644 index 0000000..125db19 --- /dev/null +++ b/bytedesk_kefu/lib/vendors/emoji_picker_flutter/src/emoji.dart @@ -0,0 +1,52 @@ +import 'package:flutter/material.dart'; + +/// A class to store data for each individual emoji +@immutable +class Emoji { + /// Emoji constructor + const Emoji(this.name, this.emoji, {this.hasSkinTone = false}); + + /// The name or description for this emoji + final String name; + + /// The unicode string for this emoji + /// + /// This is the string that should be displayed to view the emoji + final String emoji; + + /// Flag if emoji supports multiple skin tones + final bool hasSkinTone; + + @override + String toString() { + return 'Name: $name, Emoji: $emoji, HasSkinTone: $hasSkinTone'; + } + + /// Parse Emoji from json + static Emoji fromJson(Map json) { + return Emoji( + json['name'] as String, + json['emoji'] as String, + hasSkinTone: + json['hasSkinTone'] != null ? json['hasSkinTone'] as bool : false, + ); + } + + /// Encode Emoji to json + Map toJson() { + return { + 'name': name, + 'emoji': emoji, + 'hasSkinTone': hasSkinTone, + }; + } + + /// Copy method + Emoji copyWith({String? name, String? emoji, bool? hasSkinTone}) { + return Emoji( + name ?? this.name, + emoji ?? this.emoji, + hasSkinTone: hasSkinTone ?? this.hasSkinTone, + ); + } +} diff --git a/bytedesk_kefu/lib/vendors/emoji_picker_flutter/src/emoji_lists.dart b/bytedesk_kefu/lib/vendors/emoji_picker_flutter/src/emoji_lists.dart new file mode 100644 index 0000000..dac3fa3 --- /dev/null +++ b/bytedesk_kefu/lib/vendors/emoji_picker_flutter/src/emoji_lists.dart @@ -0,0 +1,3409 @@ +// Copyright information +// File originally from https://github.com/JeffG05/emoji_picker + +/// Emoji Version +const int version = 2; + +/// Map of all possible emojis along with their names in [Category.SMILEYS] +final Map smileys = Map.fromIterables([ + 'Grinning Face', + 'Grinning Face With Big Eyes', + 'Grinning Face With Smiling Eyes', + 'Beaming Face With Smiling Eyes', + 'Grinning Squinting Face', + 'Grinning Face With Sweat', + 'Rolling on the Floor Laughing', + 'Face With Tears of Joy', + 'Slightly Smiling Face', + 'Upside-Down Face', + 'Winking Face', + 'Smiling Face With Smiling Eyes', + 'Smiling Face With Halo', + 'Smiling Face With Hearts', + 'Smiling Face With Heart-Eyes', + 'Star-Struck', + 'Face Blowing a Kiss', + 'Kissing Face', + 'Smiling Face', + 'Kissing Face With Closed Eyes', + 'Kissing Face With Smiling Eyes', + 'Face Savoring Food', + 'Face With Tongue', + 'Winking Face With Tongue', + 'Zany Face', + 'Squinting Face With Tongue', + 'Money-Mouth Face', + 'Hugging Face', + 'Face With Hand Over Mouth', + 'Shushing Face', + 'Thinking Face', + 'Zipper-Mouth Face', + 'Face With Raised Eyebrow', + 'Neutral Face', + 'Expressionless Face', + 'Face Without Mouth', + 'Smirking Face', + 'Unamused Face', + 'Face With Rolling Eyes', + 'Grimacing Face', + 'Lying Face', + 'Relieved Face', + 'Pensive Face', + 'Sleepy Face', + 'Drooling Face', + 'Sleeping Face', + 'Face With Medical Mask', + 'Face With Thermometer', + 'Face With Head-Bandage', + 'Nauseated Face', + 'Face Vomiting', + 'Sneezing Face', + 'Hot Face', + 'Cold Face', + 'Woozy Face', + 'Dizzy Face', + 'Exploding Head', + 'Cowboy Hat Face', + 'Partying Face', + 'Smiling Face With Sunglasses', + 'Nerd Face', + 'Face With Monocle', + 'Confused Face', + 'Worried Face', + 'Slightly Frowning Face', + 'Frowning Face', + 'Face With Open Mouth', + 'Hushed Face', + 'Astonished Face', + 'Flushed Face', + 'Pleading Face', + 'Frowning Face With Open Mouth', + 'Anguished Face', + 'Fearful Face', + 'Anxious Face With Sweat', + 'Sad but Relieved Face', + 'Crying Face', + 'Loudly Crying Face', + 'Face Screaming in Fear', + 'Confounded Face', + 'Persevering Face', + 'Disappointed Face', + 'Downcast Face With Sweat', + 'Weary Face', + 'Tired Face', + 'Face With Steam From Nose', + 'Pouting Face', + 'Angry Face', + 'Face With Symbols on Mouth', + 'Smiling Face With Horns', + 'Angry Face With Horns', + 'Skull', + 'Skull and Crossbones', + 'Pile of Poo', + 'Clown Face', + 'Ogre', + 'Goblin', + 'Ghost', + 'Alien', + 'Alien Monster', + 'Robot Face', + 'Grinning Cat Face', + 'Grinning Cat Face With Smiling Eyes', + 'Cat Face With Tears of Joy', + 'Smiling Cat Face With Heart-Eyes', + 'Cat Face With Wry Smile', + 'Kissing Cat Face', + 'Weary Cat Face', + 'Crying Cat Face', + 'Pouting Cat Face', + 'Kiss Mark', + 'Waving Hand', + 'Raised Back of Hand', + 'Hand With Fingers Splayed', + 'Raised Hand', + 'Vulcan Salute', + 'OK Hand', + 'Victory Hand', + 'Crossed Fingers', + 'Love-You Gesture', + 'Sign of the Horns', + 'Call Me Hand', + 'Backhand Index Pointing Left', + 'Backhand Index Pointing Right', + 'Backhand Index Pointing Up', + 'Middle Finger', + 'Backhand Index Pointing Down', + 'Index Pointing Up', + 'Thumbs Up', + 'Thumbs Down', + 'Raised Fist', + 'Oncoming Fist', + 'Left-Facing Fist', + 'Right-Facing Fist', + 'Clapping Hands', + 'Raising Hands', + 'Open Hands', + 'Palms Up Together', + 'Handshake', + 'Folded Hands', + 'Writing Hand', + 'Nail Polish', + 'Selfie', + 'Flexed Biceps', + 'Leg', + 'Foot', + 'Ear', + 'Nose', + 'Brain', + 'Tooth', + 'Bone', + 'Eyes', + 'Eye', + 'Tongue', + 'Mouth', + 'Baby', + 'Child', + 'Boy', + 'Girl', + 'Person', + 'Man', + 'Man: Beard', + 'Man: Blond Hair', + 'Man: Red Hair', + 'Man: Curly Hair', + 'Man: White Hair', + 'Man: Bald', + 'Woman', + 'Woman: Blond Hair', + 'Woman: Red Hair', + 'Woman: Curly Hair', + 'Woman: White Hair', + 'Woman: Bald', + 'Older Person', + 'Old Man', + 'Old Woman', + 'Man Frowning', + 'Woman Frowning', + 'Man Pouting', + 'Woman Pouting', + 'Man Gesturing No', + 'Woman Gesturing No', + 'Man Gesturing OK', + 'Woman Gesturing OK', + 'Man Tipping Hand', + 'Woman Tipping Hand', + 'Man Raising Hand', + 'Woman Raising Hand', + 'Man Bowing', + 'Woman Bowing', + 'Man Facepalming', + 'Woman Facepalming', + 'Man Shrugging', + 'Woman Shrugging', + 'Man Health Worker', + 'Woman Health Worker', + 'Man Student', + 'Woman Student', + 'Man Teacher', + 'Woman Teacher', + 'Man Judge', + 'Woman Judge', + 'Man Farmer', + 'Woman Farmer', + 'Man Cook', + 'Woman Cook', + 'Man Mechanic', + 'Woman Mechanic', + 'Man Factory Worker', + 'Woman Factory Worker', + 'Man Office Worker', + 'Woman Office Worker', + 'Man Scientist', + 'Woman Scientist', + 'Man Technologist', + 'Woman Technologist', + 'Man Singer', + 'Woman Singer', + 'Man Artist', + 'Woman Artist', + 'Man Pilot', + 'Woman Pilot', + 'Man Astronaut', + 'Woman Astronaut', + 'Man Firefighter', + 'Woman Firefighter', + 'Man Police Officer', + 'Woman Police Officer', + 'Man Detective', + 'Woman Detective', + 'Man Guard', + 'Woman Guard', + 'Man Construction Worker', + 'Woman Construction Worker', + 'Prince', + 'Princess', + 'Man Wearing Turban', + 'Woman Wearing Turban', + 'Man With Chinese Cap', + 'Woman With Headscarf', + 'Man in Tuxedo', + 'Bride With Veil', + 'Pregnant Woman', + 'Breast-Feeding', + 'Baby Angel', + 'Santa Claus', + 'Mrs. Claus', + 'Man Superhero', + 'Woman Superhero', + 'Man Supervillain', + 'Woman Supervillain', + 'Man Mage', + 'Woman Mage', + 'Man Fairy', + 'Woman Fairy', + 'Man Vampire', + 'Woman Vampire', + 'Merman', + 'Mermaid', + 'Man Elf', + 'Woman Elf', + 'Man Genie', + 'Woman Genie', + 'Man Zombie', + 'Woman Zombie', + 'Man Getting Massage', + 'Woman Getting Massage', + 'Man Getting Haircut', + 'Woman Getting Haircut', + 'Man Walking', + 'Woman Walking', + 'Man Running', + 'Woman Running', + 'Man Dancing', + 'Woman Dancing', + 'Man in Suit Levitating', + 'Men With Bunny Ears', + 'Women With Bunny Ears', + 'Man in Steamy Room', + 'Woman in Steamy Room', + 'Person in Lotus Position', + 'Women Holding Hands', + 'Woman and Man Holding Hands', + 'Men Holding Hands', + 'Kiss', + 'Kiss: Man, Man', + 'Kiss: Woman, Woman', + 'Couple With Heart', + 'Couple With Heart: Man, Man', + 'Couple With Heart: Woman, Woman', + 'Family', + 'Family: Man, Woman, Boy', + 'Family: Man, Woman, Girl', + 'Family: Man, Woman, Girl, Boy', + 'Family: Man, Woman, Boy, Boy', + 'Family: Man, Woman, Girl, Girl', + 'Family: Man, Man, Boy', + 'Family: Man, Man, Girl', + 'Family: Man, Man, Girl, Boy', + 'Family: Man, Man, Boy, Boy', + 'Family: Man, Man, Girl, Girl', + 'Family: Woman, Woman, Boy', + 'Family: Woman, Woman, Girl', + 'Family: Woman, Woman, Girl, Boy', + 'Family: Woman, Woman, Boy, Boy', + 'Family: Woman, Woman, Girl, Girl', + 'Family: Man, Boy', + 'Family: Man, Boy, Boy', + 'Family: Man, Girl', + 'Family: Man, Girl, Boy', + 'Family: Man, Girl, Girl', + 'Family: Woman, Boy', + 'Family: Woman, Boy, Boy', + 'Family: Woman, Girl', + 'Family: Woman, Girl, Boy', + 'Family: Woman, Girl, Girl', + 'Speaking Head', + 'Bust in Silhouette', + 'Busts in Silhouette', + 'Footprints', + 'Luggage', + 'Closed Umbrella', + 'Umbrella', + 'Thread', + 'Yarn', + 'Glasses', + 'Sunglasses', + 'Goggles', + 'Lab Coat', + 'Necktie', + 'T-Shirt', + 'Jeans', + 'Scarf', + 'Gloves', + 'Coat', + 'Socks', + 'Dress', + 'Kimono', + 'Bikini', + 'Woman’s Clothes', + 'Purse', + 'Handbag', + 'Clutch Bag', + 'Backpack', + 'Man’s Shoe', + 'Running Shoe', + 'Hiking Boot', + 'Flat Shoe', + 'High-Heeled Shoe', + 'Woman’s Sandal', + 'Woman’s Boot', + 'Crown', + 'Woman’s Hat', + 'Top Hat', + 'Graduation Cap', + 'Billed Cap', + 'Rescue Worker’s Helmet', + 'Lipstick', + 'Ring', + 'Briefcase' +], [ + '😀', + '😃', + '😄', + '😁', + '😆', + '😅', + '🤣', + '😂', + '🙂', + '🙃', + '😉', + '😊', + '😇', + '🥰', + '😍', + '🤩', + '😘', + '😗', + '☺', + '😚', + '😙', + '😋', + '😛', + '😜', + '🤪', + '😝', + '🤑', + '🤗', + '🤭', + '🤫', + '🤔', + '🤐', + '🤨', + '😐', + '😑', + '😶', + '😏', + '😒', + '🙄', + '😬', + '🤥', + '😌', + '😔', + '😪', + '🤤', + '😴', + '😷', + '🤒', + '🤕', + '🤢', + '🤮', + '🤧', + '🥵', + '🥶', + '🥴', + '😵', + '🤯', + '🤠', + '🥳', + '😎', + '🤓', + '🧐', + '😕', + '😟', + '🙁', + '☹', + '😮', + '😯', + '😲', + '😳', + '🥺', + '😦', + '😧', + '😨', + '😰', + '😥', + '😢', + '😭', + '😱', + '😖', + '😣', + '😞', + '😓', + '😩', + '😫', + '😤', + '😡', + '😠', + '🤬', + '😈', + '👿', + '💀', + '☠', + '💩', + '🤡', + '👹', + '👺', + '👻', + '👽', + '👾', + '🤖', + '😺', + '😸', + '😹', + '😻', + '😼', + '😽', + '🙀', + '😿', + '😾', + '💋', + '👋', + '🤚', + '🖐', + '✋', + '🖖', + '👌', + '✌', + '🤞', + '🤟', + '🤘', + '🤙', + '👈', + '👉', + '👆', + '🖕', + '👇', + '☝', + '👍', + '👎', + '✊', + '👊', + '🤛', + '🤜', + '👏', + '🙌', + '👐', + '🤲', + '🤝', + '🙏', + '✍', + '💅', + '🤳', + '💪', + '🦵', + '🦶', + '👂', + '👃', + '🧠', + '🦷', + '🦴', + '👀', + '👁', + '👅', + '👄', + '👶', + '🧒', + '👦', + '👧', + '🧑', + '👨', + '🧔', + '👱', + '👨‍🦰', + '👨‍🦱', + '👨‍🦳', + '👨‍🦲', + '👩', + '👱‍♀️', + '👩‍🦰', + '👩‍🦱', + '👩‍🦳', + '👩‍🦲', + '🧓', + '👴', + '👵', + '🙍‍♂️', + '🙍‍♀️', + '🙎‍♂️', + '🙎‍♀️', + '🙅‍♂️', + '🙅‍♀️', + '🙆‍♂️', + '🙆‍♀️', + '💁‍♂️', + '💁‍♀️', + '🙋‍♂️', + '🙋‍♀️', + '🙇‍♂️', + '🙇‍♀️', + '🤦‍♂️', + '🤦‍♀️', + '🤷‍♂️', + '🤷‍♀️', + '👨‍⚕️', + '👩‍⚕️', + '👨‍🎓', + '👩‍🎓', + '👨‍🏫', + '👩‍🏫', + '👨‍⚖️', + '👩‍⚖️', + '👨‍🌾', + '👩‍🌾', + '👨‍🍳', + '👩‍🍳', + '👨‍🔧', + '👩‍🔧', + '👨‍🏭', + '👩‍🏭', + '👨‍💼', + '👩‍💼', + '👨‍🔬', + '👩‍🔬', + '👨‍💻', + '👩‍💻', + '👨‍🎤', + '👩‍🎤', + '👨‍🎨', + '👩‍🎨', + '👨‍✈️', + '👩‍✈️', + '👨‍🚀', + '👩‍🚀', + '👨‍🚒', + '👩‍🚒', + '👮‍♂️', + '👮‍♀️', + '🕵️‍♂️', + '🕵️‍♀️', + '💂‍♂️', + '💂‍♀️', + '👷‍♂️', + '👷‍♀️', + '🤴', + '👸', + '👳‍♂️', + '👳‍♀️', + '👲', + '🧕', + '🤵', + '👰', + '🤰', + '🤱', + '👼', + '🎅', + '🤶', + '🦸‍♂️', + '🦸‍♀️', + '🦹‍♂️', + '🦹‍♀️', + '🧙‍♂️', + '🧙‍♀️', + '🧚‍♂️', + '🧚‍♀️', + '🧛‍♂️', + '🧛‍♀️', + '🧜‍♂️', + '🧜‍♀️', + '🧝‍♂️', + '🧝‍♀️', + '🧞‍♂️', + '🧞‍♀️', + '🧟‍♂️', + '🧟‍♀️', + '💆‍♂️', + '💆‍♀️', + '💇‍♂️', + '💇‍♀️', + '🚶‍♂️', + '🚶‍♀️', + '🏃‍♂️', + '🏃‍♀️', + '🕺', + '💃', + '🕴', + '👯‍♂️', + '👯‍♀️', + '🧖‍♂️', + '🧖‍♀️', + '🧘', + '👭', + '👫', + '👬', + '💏', + '👨‍❤️‍💋‍👨', + '👩‍❤️‍💋‍👩', + '💑', + '👨‍❤️‍👨', + '👩‍❤️‍👩', + '👪', + '👨‍👩‍👦', + '👨‍👩‍👧', + '👨‍👩‍👧‍👦', + '👨‍👩‍👦‍👦', + '👨‍👩‍👧‍👧', + '👨‍👨‍👦', + '👨‍👨‍👧', + '👨‍👨‍👧‍👦', + '👨‍👨‍👦‍👦', + '👨‍👨‍👧‍👧', + '👩‍👩‍👦', + '👩‍👩‍👧', + '👩‍👩‍👧‍👦', + '👩‍👩‍👦‍👦', + '👩‍👩‍👧‍👧', + '👨‍👦', + '👨‍👦‍👦', + '👨‍👧', + '👨‍👧‍👦', + '👨‍👧‍👧', + '👩‍👦', + '👩‍👦‍👦', + '👩‍👧', + '👩‍👧‍👦', + '👩‍👧‍👧', + '🗣', + '👤', + '👥', + '👣', + '🧳', + '🌂', + '☂', + '🧵', + '🧶', + '👓', + '🕶', + '🥽', + '🥼', + '👔', + '👕', + '👖', + '🧣', + '🧤', + '🧥', + '🧦', + '👗', + '👘', + '👙', + '👚', + '👛', + '👜', + '👝', + '🎒', + '👞', + '👟', + '🥾', + '🥿', + '👠', + '👡', + '👢', + '👑', + '👒', + '🎩', + '🎓', + '🧢', + '⛑', + '💄', + '💍', + '💼', +]); + +/// Map of all possible emojis along with their names in [Category.ANIMALS] +final Map animals = Map.fromIterables([ + 'Dog Face', + 'Cat Face', + 'Mouse Face', + 'Hamster Face', + 'Rabbit Face', + 'Fox Face', + 'Bear Face', + 'Panda Face', + 'Tiger Face', + 'Lion Face', + 'Cow Face', + 'Pig Face', + 'Pig Nose', + 'Frog Face', + 'Monkey Face', + 'See-No-Evil Monkey', + 'Hear-No-Evil Monkey', + 'Speak-No-Evil Monkey', + 'Monkey', + 'Collision', + 'Dizzy', + 'Sweat Droplets', + 'Dashing Away', + 'Gorilla', + 'Dog', + 'Poodle', + 'Wolf Face', + 'Raccoon', + 'Cat', + 'Tiger', + 'Leopard', + 'Horse Face', + 'Horse', + 'Unicorn Face', + 'Zebra', + 'Ox', + 'Water Buffalo', + 'Cow', + 'Pig', + 'Boar', + 'Ram', + 'Ewe', + 'Goat', + 'Camel', + 'Two-Hump Camel', + 'Llama', + 'Giraffe', + 'Elephant', + 'Rhinoceros', + 'Hippopotamus', + 'Mouse', + 'Rat', + 'Rabbit', + 'Chipmunk', + 'Hedgehog', + 'Bat', + 'Koala', + 'Kangaroo', + 'Badger', + 'Paw Prints', + 'Turkey', + 'Chicken', + 'Rooster', + 'Hatching Chick', + 'Baby Chick', + 'Front-Facing Baby Chick', + 'Bird', + 'Penguin', + 'Dove', + 'Eagle', + 'Duck', + 'Swan', + 'Owl', + 'Peacock', + 'Parrot', + 'Crocodile', + 'Turtle', + 'Lizard', + 'Snake', + 'Dragon Face', + 'Dragon', + 'Sauropod', + 'T-Rex', + 'Spouting Whale', + 'Whale', + 'Dolphin', + 'Fish', + 'Tropical Fish', + 'Blowfish', + 'Shark', + 'Octopus', + 'Spiral Shell', + 'Snail', + 'Butterfly', + 'Bug', + 'Ant', + 'Honeybee', + 'Lady Beetle', + 'Cricket', + 'Spider', + 'Spider Web', + 'Scorpion', + 'Mosquito', + 'Microbe', + 'Bouquet', + 'Cherry Blossom', + 'White Flower', + 'Rosette', + 'Rose', + 'Wilted Flower', + 'Hibiscus', + 'Sunflower', + 'Blossom', + 'Tulip', + 'Seedling', + 'Evergreen Tree', + 'Deciduous Tree', + 'Palm Tree', + 'Cactus', + 'Sheaf of Rice', + 'Herb', + 'Shamrock', + 'Four Leaf Clover', + 'Maple Leaf', + 'Fallen Leaf', + 'Leaf Fluttering in Wind', + 'Mushroom', + 'Chestnut', + 'Crab', + 'Lobster', + 'Shrimp', + 'Squid', + 'Globe Showing Europe-Africa', + 'Globe Showing Americas', + 'Globe Showing Asia-Australia', + 'Globe With Meridians', + 'New Moon', + 'Waxing Crescent Moon', + 'First Quarter Moon', + 'Waxing Gibbous Moon', + 'Full Moon', + 'Waning Gibbous Moon', + 'Last Quarter Moon', + 'Waning Crescent Moon', + 'Crescent Moon', + 'New Moon Face', + 'First Quarter Moon Face', + 'Last Quarter Moon Face', + 'Sun', + 'Full Moon Face', + 'Sun With Face', + 'Star', + 'Glowing Star', + 'Shooting Star', + 'Cloud', + 'Sun Behind Cloud', + 'Cloud With Lightning and Rain', + 'Sun Behind Small Cloud', + 'Sun Behind Large Cloud', + 'Sun Behind Rain Cloud', + 'Cloud With Rain', + 'Cloud With Snow', + 'Cloud With Lightning', + 'Tornado', + 'Fog', + 'Wind Face', + 'Rainbow', + 'Umbrella', + 'Umbrella With Rain Drops', + 'High Voltage', + 'Snowflake', + 'Snowman', + 'Snowman Without Snow', + 'Comet', + 'Fire', + 'Droplet', + 'Water Wave', + 'Christmas Tree', + 'Sparkles', + 'Tanabata Tree', + 'Pine Decoration' +], [ + '🐶', + '🐱', + '🐭', + '🐹', + '🐰', + '🦊', + '🐻', + '🐼', + '🐨', + '🐯', + '🦁', + '🐮', + '🐷', + '🐽', + '🐸', + '🐵', + '🙈', + '🙉', + '🙊', + '🐒', + '💥', + '💫', + '💦', + '💨', + '🦍', + '🐕', + '🐩', + '🐺', + '🦝', + '🐈', + '🐅', + '🐆', + '🐴', + '🐎', + '🦄', + '🦓', + '🐂', + '🐃', + '🐄', + '🐖', + '🐗', + '🐏', + '🐑', + '🐐', + '🐪', + '🐫', + '🦙', + '🦒', + '🐘', + '🦏', + '🦛', + '🐁', + '🐀', + '🐇', + '🐿', + '🦔', + '🦇', + '🦘', + '🦡', + '🐾', + '🦃', + '🐔', + '🐓', + '🐣', + '🐤', + '🐥', + '🐦', + '🐧', + '🕊', + '🦅', + '🦆', + '🦢', + '🦉', + '🦚', + '🦜', + '🐊', + '🐢', + '🦎', + '🐍', + '🐲', + '🐉', + '🦕', + '🦖', + '🐳', + '🐋', + '🐬', + '🐟', + '🐠', + '🐡', + '🦈', + '🐙', + '🐚', + '🐌', + '🦋', + '🐛', + '🐜', + '🐝', + '🐞', + '🦗', + '🕷', + '🕸', + '🦂', + '🦟', + '🦠', + '💐', + '🌸', + '💮', + '🏵', + '🌹', + '🥀', + '🌺', + '🌻', + '🌼', + '🌷', + '🌱', + '🌲', + '🌳', + '🌴', + '🌵', + '🌾', + '🌿', + '☘', + '🍀', + '🍁', + '🍂', + '🍃', + '🍄', + '🌰', + '🦀', + '🦞', + '🦐', + '🦑', + '🌍', + '🌎', + '🌏', + '🌐', + '🌑', + '🌒', + '🌓', + '🌔', + '🌕', + '🌖', + '🌗', + '🌘', + '🌙', + '🌚', + '🌛', + '🌜', + '☀', + '🌝', + '🌞', + '⭐', + '🌟', + '🌠', + '☁', + '⛅', + '⛈', + '🌤', + '🌥', + '🌦', + '🌧', + '🌨', + '🌩', + '🌪', + '🌫', + '🌬', + '🌈', + '☂', + '☔', + '⚡', + '❄', + '☃', + '⛄', + '☄', + '🔥', + '💧', + '🌊', + '🎄', + '✨', + '🎋', + '🎍' +]); + +/// Map of all possible emojis along with their names in [Category.FOODS] +final Map foods = Map.fromIterables([ + 'Grapes', + 'Melon', + 'Watermelon', + 'Tangerine', + 'Lemon', + 'Banana', + 'Pineapple', + 'Mango', + 'Red Apple', + 'Green Apple', + 'Pear', + 'Peach', + 'Cherries', + 'Strawberry', + 'Kiwi Fruit', + 'Tomato', + 'Coconut', + 'Avocado', + 'Eggplant', + 'Potato', + 'Carrot', + 'Ear of Corn', + 'Hot Pepper', + 'Cucumber', + 'Leafy Green', + 'Broccoli', + 'Mushroom', + 'Peanuts', + 'Chestnut', + 'Bread', + 'Croissant', + 'Baguette Bread', + 'Pretzel', + 'Bagel', + 'Pancakes', + 'Cheese Wedge', + 'Meat on Bone', + 'Poultry Leg', + 'Cut of Meat', + 'Bacon', + 'Hamburger', + 'French Fries', + 'Pizza', + 'Hot Dog', + 'Sandwich', + 'Taco', + 'Burrito', + 'Stuffed Flatbread', + 'Cooking', + 'Shallow Pan of Food', + 'Pot of Food', + 'Bowl With Spoon', + 'Green Salad', + 'Popcorn', + 'Salt', + 'Canned Food', + 'Bento Box', + 'Rice Cracker', + 'Rice Ball', + 'Cooked Rice', + 'Curry Rice', + 'Steaming Bowl', + 'Spaghetti', + 'Roasted Sweet Potato', + 'Oden', + 'Sushi', + 'Fried Shrimp', + 'Fish Cake With Swirl', + 'Moon Cake', + 'Dango', + 'Dumpling', + 'Fortune Cookie', + 'Takeout Box', + 'Soft Ice Cream', + 'Shaved Ice', + 'Ice Cream', + 'Doughnut', + 'Cookie', + 'Birthday Cake', + 'Shortcake', + 'Cupcake', + 'Pie', + 'Chocolate Bar', + 'Candy', + 'Lollipop', + 'Custard', + 'Honey Pot', + 'Baby Bottle', + 'Glass of Milk', + 'Hot Beverage', + 'Teacup Without Handle', + 'Mate Drink', + 'Sake', + 'Bottle With Popping Cork', + 'Wine Glass', + 'Cocktail Glass', + 'Tropical Drink', + 'Beer Mug', + 'Clinking Beer Mugs', + 'Clinking Glasses', + 'Tumbler Glass', + 'Cup With Straw', + 'Chopsticks', + 'Fork and Knife With Plate', + 'Fork and Knife', + 'Spoon' +], [ + '🍇', + '🍈', + '🍉', + '🍊', + '🍋', + '🍌', + '🍍', + '🥭', + '🍎', + '🍏', + '🍐', + '🍑', + '🍒', + '🍓', + '🥝', + '🍅', + '🥥', + '🥑', + '🍆', + '🥔', + '🥕', + '🌽', + '🌶', + '🥒', + '🥬', + '🥦', + '🍄', + '🥜', + '🌰', + '🍞', + '🥐', + '🥖', + '🥨', + '🥯', + '🥞', + '🧀', + '🍖', + '🍗', + '🥩', + '🥓', + '🍔', + '🍟', + '🍕', + '🌭', + '🥪', + '🌮', + '🌯', + '🥙', + '🍳', + '🥘', + '🍲', + '🥣', + '🥗', + '🍿', + '🧂', + '🥫', + '🍱', + '🍘', + '🍙', + '🍚', + '🍛', + '🍜', + '🍝', + '🍠', + '🍢', + '🍣', + '🍤', + '🍥', + '🥮', + '🍡', + '🥟', + '🥠', + '🥡', + '🍦', + '🍧', + '🍨', + '🍩', + '🍪', + '🎂', + '🍰', + '🧁', + '🥧', + '🍫', + '🍬', + '🍭', + '🍮', + '🍯', + '🍼', + '🥛', + '☕', + '🍵', + '🧉', + '🍶', + '🍾', + '🍷', + '🍸', + '🍹', + '🍺', + '🍻', + '🥂', + '🥃', + '🥤', + '🥢', + '🍽', + '🍴', + '🥄' +]); + +/// Map of all possible emojis along with their names in [Category.TRAVEL] +final Map travel = Map.fromIterables([ + 'Map of Japan', + 'Snow-Capped Mountain', + 'Mountain', + 'Volcano', + 'Mount Fuji', + 'Camping', + 'Beach With Umbrella', + 'Desert', + 'Desert Island', + 'National Park', + 'Stadium', + 'Classical Building', + 'Building Construction', + 'Houses', + 'Derelict House', + 'House', + 'House With Garden', + 'Office Building', + 'Japanese Post Office', + 'Post Office', + 'Hospital', + 'Bank', + 'Hotel', + 'Love Hotel', + 'Convenience Store', + 'School', + 'Department Store', + 'Factory', + 'Japanese Castle', + 'Castle', + 'Wedding', + 'Tokyo Tower', + 'Statue of Liberty', + 'Church', + 'Mosque', + 'Synagogue', + 'Shinto Shrine', + 'Kaaba', + 'Fountain', + 'Tent', + 'Foggy', + 'Night With Stars', + 'Cityscape', + 'Sunrise Over Mountains', + 'Sunrise', + 'Cityscape at Dusk', + 'Sunset', + 'Bridge at Night', + 'Carousel Horse', + 'Ferris Wheel', + 'Roller Coaster', + 'Locomotive', + 'Railway Car', + 'High-Speed Train', + 'Bullet Train', + 'Train', + 'Metro', + 'Light Rail', + 'Station', + 'Tram', + 'Monorail', + 'Mountain Railway', + 'Tram Car', + 'Bus', + 'Oncoming Bus', + 'Trolleybus', + 'Minibus', + 'Ambulance', + 'Fire Engine', + 'Police Car', + 'Oncoming Police Car', + 'Taxi', + 'Oncoming Taxi', + 'Automobile', + 'Oncoming Automobile', + 'Delivery Truck', + 'Articulated Lorry', + 'Tractor', + 'Racing Car', + 'Motorcycle', + 'Motor Scooter', + 'Bicycle', + 'Kick Scooter', + 'Bus Stop', + 'Railway Track', + 'Fuel Pump', + 'Police Car Light', + 'Horizontal Traffic Light', + 'Vertical Traffic Light', + 'Construction', + 'Anchor', + 'Sailboat', + 'Speedboat', + 'Passenger Ship', + 'Ferry', + 'Motor Boat', + 'Ship', + 'Airplane', + 'Small Airplane', + 'Airplane Departure', + 'Airplane Arrival', + 'Seat', + 'Helicopter', + 'Suspension Railway', + 'Mountain Cableway', + 'Aerial Tramway', + 'Satellite', + 'Rocket', + 'Flying Saucer', + 'Shooting Star', + 'Milky Way', + 'Umbrella on Ground', + 'Fireworks', + 'Sparkler', + 'Moon Viewing Ceremony', + 'Yen Banknote', + 'Dollar Banknote', + 'Euro Banknote', + 'Pound Banknote', + 'Moai', + 'Passport Control', + 'Customs', + 'Baggage Claim', + 'Left Luggage' +], [ + '🗾', + '🏔', + '⛰', + '🌋', + '🗻', + '🏕', + '🏖', + '🏜', + '🏝', + '🏞', + '🏟', + '🏛', + '🏗', + '🏘', + '🏚', + '🏠', + '🏡', + '🏢', + '🏣', + '🏤', + '🏥', + '🏦', + '🏨', + '🏩', + '🏪', + '🏫', + '🏬', + '🏭', + '🏯', + '🏰', + '💒', + '🗼', + '🗽', + '⛪', + '🕌', + '🕍', + '⛩', + '🕋', + '⛲', + '⛺', + '🌁', + '🌃', + '🏙', + '🌄', + '🌅', + '🌆', + '🌇', + '🌉', + '🎠', + '🎡', + '🎢', + '🚂', + '🚃', + '🚄', + '🚅', + '🚆', + '🚇', + '🚈', + '🚉', + '🚊', + '🚝', + '🚞', + '🚋', + '🚌', + '🚍', + '🚎', + '🚐', + '🚑', + '🚒', + '🚓', + '🚔', + '🚕', + '🚖', + '🚗', + '🚘', + '🚚', + '🚛', + '🚜', + '🏎', + '🏍', + '🛵', + '🚲', + '🛴', + '🚏', + '🛤', + '⛽', + '🚨', + '🚥', + '🚦', + '🚧', + '⚓', + '⛵', + '🚤', + '🛳', + '⛴', + '🛥', + '🚢', + '✈', + '🛩', + '🛫', + '🛬', + '💺', + '🚁', + '🚟', + '🚠', + '🚡', + '🛰', + '🚀', + '🛸', + '🌠', + '🌌', + '⛱', + '🎆', + '🎇', + '🎑', + '💴', + '💵', + '💶', + '💷', + '🗿', + '🛂', + '🛃', + '🛄', + '🛅' +]); + +/// Map of all possible emojis along with their names in [Category.ACTIVITIES] +final Map activities = Map.fromIterables([ + 'Man Climbing', + 'Woman Climbing', + 'Horse Racing', + 'Skier', + 'Snowboarder', + 'Man Golfing', + 'Woman Golfing', + 'Man Surfing', + 'Woman Surfing', + 'Man Rowing Boat', + 'Woman Rowing Boat', + 'Man Swimming', + 'Woman Swimming', + 'Man Bouncing Ball', + 'Woman Bouncing Ball', + 'Man Lifting Weights', + 'Woman Lifting Weights', + 'Man Biking', + 'Woman Biking', + 'Man Mountain Biking', + 'Woman Mountain Biking', + 'Man Cartwheeling', + 'Woman Cartwheeling', + 'Men Wrestling', + 'Women Wrestling', + 'Man Playing Water Polo', + 'Woman Playing Water Polo', + 'Man Playing Handball', + 'Woman Playing Handball', + 'Man Juggling', + 'Woman Juggling', + 'Man in Lotus Position', + 'Woman in Lotus Position', + 'Circus Tent', + 'Skateboard', + 'Reminder Ribbon', + 'Admission Tickets', + 'Ticket', + 'Military Medal', + 'Trophy', + 'Sports Medal', + '1st Place Medal', + '2nd Place Medal', + '3rd Place Medal', + 'Soccer Ball', + 'Baseball', + 'Softball', + 'Basketball', + 'Volleyball', + 'American Football', + 'Rugby Football', + 'Tennis', + 'Flying Disc', + 'Bowling', + 'Cricket Game', + 'Field Hockey', + 'Ice Hockey', + 'Lacrosse', + 'Ping Pong', + 'Badminton', + 'Boxing Glove', + 'Martial Arts Uniform', + 'Flag in Hole', + 'Ice Skate', + 'Fishing Pole', + 'Running Shirt', + 'Skis', + 'Sled', + 'Curling Stone', + 'Direct Hit', + 'Pool 8 Ball', + 'Video Game', + 'Slot Machine', + 'Game Die', + 'Jigsaw', + 'Chess Pawn', + 'Performing Arts', + 'Artist Palette', + 'Thread', + 'Yarn', + 'Musical Score', + 'Microphone', + 'Headphone', + 'Saxophone', + 'Guitar', + 'Musical Keyboard', + 'Trumpet', + 'Violin', + 'Drum', + 'Clapper Board', + 'Bow and Arrow' +], [ + '🧗‍♂️', + '🧗‍♀️', + '🏇', + '⛷', + '🏂', + '🏌️‍♂️', + '🏌️‍♀️', + '🏄‍♂️', + '🏄‍♀️', + '🚣‍♂️', + '🚣‍♀️', + '🏊‍♂️', + '🏊‍♀️', + '⛹️‍♂️', + '⛹️‍♀️', + '🏋️‍♂️', + '🏋️‍♀️', + '🚴‍♂️', + '🚴‍♀️', + '🚵‍♂️', + '🚵‍♀️', + '🤸‍♂️', + '🤸‍♀️', + '🤼‍♂️', + '🤼‍♀️', + '🤽‍♂️', + '🤽‍♀️', + '🤾‍♂️', + '🤾‍♀️', + '🤹‍♂️', + '🤹‍♀️', + '🧘‍♂️', + '🧘‍♀️', + '🎪', + '🛹', + '🎗', + '🎟', + '🎫', + '🎖', + '🏆', + '🏅', + '🥇', + '🥈', + '🥉', + '⚽', + '⚾', + '🥎', + '🏀', + '🏐', + '🏈', + '🏉', + '🎾', + '🥏', + '🎳', + '🏏', + '🏑', + '🏒', + '🥍', + '🏓', + '🏸', + '🥊', + '🥋', + '⛳', + '⛸', + '🎣', + '🎽', + '🎿', + '🛷', + '🥌', + '🎯', + '🎱', + '🎮', + '🎰', + '🎲', + '🧩', + '♟', + '🎭', + '🎨', + '🧵', + '🧶', + '🎼', + '🎤', + '🎧', + '🎷', + '🎸', + '🎹', + '🎺', + '🎻', + '🥁', + '🎬', + '🏹' +]); + +/// Map of all possible emojis along with their names in [Category.OBJECTS] +final Map objects = Map.fromIterables([ + 'Love Letter', + 'Hole', + 'Bomb', + 'Person Taking Bath', + 'Person in Bed', + 'Kitchen Knife', + 'Amphora', + 'World Map', + 'Compass', + 'Brick', + 'Barber Pole', + 'Oil Drum', + 'Bellhop Bell', + 'Luggage', + 'Hourglass Done', + 'Hourglass Not Done', + 'Watch', + 'Alarm Clock', + 'Stopwatch', + 'Timer Clock', + 'Mantelpiece Clock', + 'Thermometer', + 'Umbrella on Ground', + 'Firecracker', + 'Balloon', + 'Party Popper', + 'Confetti Ball', + 'Japanese Dolls', + 'Carp Streamer', + 'Wind Chime', + 'Red Envelope', + 'Ribbon', + 'Wrapped Gift', + 'Crystal Ball', + 'Nazar Amulet', + 'Joystick', + 'Teddy Bear', + 'Framed Picture', + 'Thread', + 'Yarn', + 'Shopping Bags', + 'Prayer Beads', + 'Gem Stone', + 'Postal Horn', + 'Studio Microphone', + 'Level Slider', + 'Control Knobs', + 'Radio', + 'Mobile Phone', + 'Mobile Phone With Arrow', + 'Telephone', + 'Telephone Receiver', + 'Pager', + 'Fax Machine', + 'Battery', + 'Electric Plug', + 'Laptop Computer', + 'Desktop Computer', + 'Printer', + 'Keyboard', + 'Computer Mouse', + 'Trackball', + 'Computer Disk', + 'Floppy Disk', + 'Optical Disk', + 'DVD', + 'Abacus', + 'Movie Camera', + 'Film Frames', + 'Film Projector', + 'Television', + 'Camera', + 'Camera With Flash', + 'Video Camera', + 'Videocassette', + 'Magnifying Glass Tilted Left', + 'Magnifying Glass Tilted Right', + 'Candle', + 'Light Bulb', + 'Flashlight', + 'Red Paper Lantern', + 'Notebook With Decorative Cover', + 'Closed Book', + 'Open Book', + 'Green Book', + 'Blue Book', + 'Orange Book', + 'Books', + 'Notebook', + 'Page With Curl', + 'Scroll', + 'Page Facing Up', + 'Newspaper', + 'Rolled-Up Newspaper', + 'Bookmark Tabs', + 'Bookmark', + 'Label', + 'Money Bag', + 'Yen Banknote', + 'Dollar Banknote', + 'Euro Banknote', + 'Pound Banknote', + 'Money With Wings', + 'Credit Card', + 'Receipt', + 'Envelope', + 'E-Mail', + 'Incoming Envelope', + 'Envelope With Arrow', + 'Outbox Tray', + 'Inbox Tray', + 'Package', + 'Closed Mailbox With Raised Flag', + 'Closed Mailbox With Lowered Flag', + 'Open Mailbox With Raised Flag', + 'Open Mailbox With Lowered Flag', + 'Postbox', + 'Ballot Box With Ballot', + 'Pencil', + 'Black Nib', + 'Fountain Pen', + 'Pen', + 'Paintbrush', + 'Crayon', + 'Memo', + 'File Folder', + 'Open File Folder', + 'Card Index Dividers', + 'Calendar', + 'Tear-Off Calendar', + 'Spiral Notepad', + 'Spiral Calendar', + 'Card Index', + 'Chart Increasing', + 'Chart Decreasing', + 'Bar Chart', + 'Clipboard', + 'Pushpin', + 'Round Pushpin', + 'Paperclip', + 'Linked Paperclips', + 'Straight Ruler', + 'Triangular Ruler', + 'Scissors', + 'Card File Box', + 'File Cabinet', + 'Wastebasket', + 'Locked', + 'Unlocked', + 'Locked With Pen', + 'Locked With Key', + 'Key', + 'Old Key', + 'Hammer', + 'Pick', + 'Hammer and Pick', + 'Hammer and Wrench', + 'Dagger', + 'Crossed Swords', + 'Pistol', + 'Shield', + 'Wrench', + 'Nut and Bolt', + 'Gear', + 'Clamp', + 'Balance Scale', + 'Link', + 'Chains', + 'Toolbox', + 'Magnet', + 'Alembic', + 'Test Tube', + 'Petri Dish', + 'DNA', + 'Microscope', + 'Telescope', + 'Satellite Antenna', + 'Syringe', + 'Pill', + 'Door', + 'Bed', + 'Couch and Lamp', + 'Toilet', + 'Shower', + 'Bathtub', + 'Lotion Bottle', + 'Safety Pin', + 'Broom', + 'Basket', + 'Roll of Paper', + 'Soap', + 'Sponge', + 'Fire Extinguisher', + 'Cigarette', + 'Coffin', + 'Funeral Urn', + 'Moai', + 'Potable Water' +], [ + '💌', + '🕳', + '💣', + '🛀', + '🛌', + '🔪', + '🏺', + '🗺', + '🧭', + '🧱', + '💈', + '🛢', + '🛎', + '🧳', + '⌛', + '⏳', + '⌚', + '⏰', + '⏱', + '⏲', + '🕰', + '🌡', + '⛱', + '🧨', + '🎈', + '🎉', + '🎊', + '🎎', + '🎏', + '🎐', + '🧧', + '🎀', + '🎁', + '🔮', + '🧿', + '🕹', + '🧸', + '🖼', + '🧵', + '🧶', + '🛍', + '📿', + '💎', + '📯', + '🎙', + '🎚', + '🎛', + '📻', + '📱', + '📲', + '☎', + '📞', + '📟', + '📠', + '🔋', + '🔌', + '💻', + '🖥', + '🖨', + '⌨', + '🖱', + '🖲', + '💽', + '💾', + '💿', + '📀', + '🧮', + '🎥', + '🎞', + '📽', + '📺', + '📷', + '📸', + '📹', + '📼', + '🔍', + '🔎', + '🕯', + '💡', + '🔦', + '🏮', + '📔', + '📕', + '📖', + '📗', + '📘', + '📙', + '📚', + '📓', + '📃', + '📜', + '📄', + '📰', + '🗞', + '📑', + '🔖', + '🏷', + '💰', + '💴', + '💵', + '💶', + '💷', + '💸', + '💳', + '🧾', + '✉', + '📧', + '📨', + '📩', + '📤', + '📥', + '📦', + '📫', + '📪', + '📬', + '📭', + '📮', + '🗳', + '✏', + '✒', + '🖋', + '🖊', + '🖌', + '🖍', + '📝', + '📁', + '📂', + '🗂', + '📅', + '📆', + '🗒', + '🗓', + '📇', + '📈', + '📉', + '📊', + '📋', + '📌', + '📍', + '📎', + '🖇', + '📏', + '📐', + '✂', + '🗃', + '🗄', + '🗑', + '🔒', + '🔓', + '🔏', + '🔐', + '🔑', + '🗝', + '🔨', + '⛏', + '⚒', + '🛠', + '🗡', + '⚔', + '🔫', + '🛡', + '🔧', + '🔩', + '⚙', + '🗜', + '⚖', + '🔗', + '⛓', + '🧰', + '🧲', + '⚗', + '🧪', + '🧫', + '🧬', + '🔬', + '🔭', + '📡', + '💉', + '💊', + '🚪', + '🛏', + '🛋', + '🚽', + '🚿', + '🛁', + '🧴', + '🧷', + '🧹', + '🧺', + '🧻', + '🧼', + '🧽', + '🧯', + '🚬', + '⚰', + '⚱', + '🗿', + '🚰' +]); + +/// Map of all possible emojis along with their names in [Category.SYMBOLS] +final Map symbols = Map.fromIterables([ + 'Heart With Arrow', + 'Heart With Ribbon', + 'Sparkling Heart', + 'Growing Heart', + 'Beating Heart', + 'Revolving Hearts', + 'Two Hearts', + 'Heart Decoration', + 'Heavy Heart Exclamation', + 'Broken Heart', + 'Red Heart', + 'Orange Heart', + 'Yellow Heart', + 'Green Heart', + 'Blue Heart', + 'Purple Heart', + 'Black Heart', + 'Hundred Points', + 'Anger Symbol', + 'Speech Balloon', + 'Eye in Speech Bubble', + 'Right Anger Bubble', + 'Thought Balloon', + 'Zzz', + 'White Flower', + 'Hot Springs', + 'Barber Pole', + 'Stop Sign', + 'Twelve O’Clock', + 'Twelve-Thirty', + 'One O’Clock', + 'One-Thirty', + 'Two O’Clock', + 'Two-Thirty', + 'Three O’Clock', + 'Three-Thirty', + 'Four O’Clock', + 'Four-Thirty', + 'Five O’Clock', + 'Five-Thirty', + 'Six O’Clock', + 'Six-Thirty', + 'Seven O’Clock', + 'Seven-Thirty', + 'Eight O’Clock', + 'Eight-Thirty', + 'Nine O’Clock', + 'Nine-Thirty', + 'Ten O’Clock', + 'Ten-Thirty', + 'Eleven O’Clock', + 'Eleven-Thirty', + 'Cyclone', + 'Spade Suit', + 'Heart Suit', + 'Diamond Suit', + 'Club Suit', + 'Joker', + 'Mahjong Red Dragon', + 'Flower Playing Cards', + 'Muted Speaker', + 'Speaker Low Volume', + 'Speaker Medium Volume', + 'Speaker High Volume', + 'Loudspeaker', + 'Megaphone', + 'Postal Horn', + 'Bell', + 'Bell With Slash', + 'Musical Note', + 'Musical Notes', + 'ATM Sign', + 'Litter in Bin Sign', + 'Potable Water', + 'Wheelchair Symbol', + 'Men’s Room', + 'Women’s Room', + 'Restroom', + 'Baby Symbol', + 'Water Closet', + 'Warning', + 'Children Crossing', + 'No Entry', + 'Prohibited', + 'No Bicycles', + 'No Smoking', + 'No Littering', + 'Non-Potable Water', + 'No Pedestrians', + 'No One Under Eighteen', + 'Radioactive', + 'Biohazard', + 'Up Arrow', + 'Up-Right Arrow', + 'Right Arrow', + 'Down-Right Arrow', + 'Down Arrow', + 'Down-Left Arrow', + 'Left Arrow', + 'Up-Left Arrow', + 'Up-Down Arrow', + 'Left-Right Arrow', + 'Right Arrow Curving Left', + 'Left Arrow Curving Right', + 'Right Arrow Curving Up', + 'Right Arrow Curving Down', + 'Clockwise Vertical Arrows', + 'Counterclockwise Arrows Button', + 'Back Arrow', + 'End Arrow', + 'On! Arrow', + 'Soon Arrow', + 'Top Arrow', + 'Place of Worship', + 'Atom Symbol', + 'Om', + 'Star of David', + 'Wheel of Dharma', + 'Yin Yang', + 'Latin Cross', + 'Orthodox Cross', + 'Star and Crescent', + 'Peace Symbol', + 'Menorah', + 'Dotted Six-Pointed Star', + 'Aries', + 'Taurus', + 'Gemini', + 'Cancer', + 'Leo', + 'Virgo', + 'Libra', + 'Scorpio', + 'Sagittarius', + 'Capricorn', + 'Aquarius', + 'Pisces', + 'Ophiuchus', + 'Shuffle Tracks Button', + 'Repeat Button', + 'Repeat Single Button', + 'Play Button', + 'Fast-Forward Button', + 'Reverse Button', + 'Fast Reverse Button', + 'Upwards Button', + 'Fast Up Button', + 'Downwards Button', + 'Fast Down Button', + 'Stop Button', + 'Eject Button', + 'Cinema', + 'Dim Button', + 'Bright Button', + 'Antenna Bars', + 'Vibration Mode', + 'Mobile Phone Off', + 'Infinity', + 'Recycling Symbol', + 'Trident Emblem', + 'Name Badge', + 'Japanese Symbol for Beginner', + 'Heavy Large Circle', + 'White Heavy Check Mark', + 'Ballot Box With Check', + 'Heavy Check Mark', + 'Heavy Multiplication X', + 'Cross Mark', + 'Cross Mark Button', + 'Heavy Plus Sign', + 'Heavy Minus Sign', + 'Heavy Division Sign', + 'Curly Loop', + 'Double Curly Loop', + 'Part Alternation Mark', + 'Eight-Spoked Asterisk', + 'Eight-Pointed Star', + 'Sparkle', + 'Double Exclamation Mark', + 'Exclamation Question Mark', + 'Question Mark', + 'White Question Mark', + 'White Exclamation Mark', + 'Exclamation Mark', + 'Copyright', + 'Registered', + 'Trade Mark', + 'Keycap Number Sign', + 'Keycap Digit Zero', + 'Keycap Digit One', + 'Keycap Digit Two', + 'Keycap Digit Three', + 'Keycap Digit Four', + 'Keycap Digit Five', + 'Keycap Digit Six', + 'Keycap Digit Seven', + 'Keycap Digit Eight', + 'Keycap Digit Nine', + 'Keycap: 10', + 'Input Latin Uppercase', + 'Input Latin Lowercase', + 'Input Numbers', + 'Input Symbols', + 'Input Latin Letters', + 'A Button (Blood Type)', + 'AB Button (Blood Type)', + 'B Button (Blood Type)', + 'CL Button', + 'Cool Button', + 'Free Button', + 'Information', + 'ID Button', + 'Circled M', + 'New Button', + 'NG Button', + 'O Button (Blood Type)', + 'OK Button', + 'P Button', + 'SOS Button', + 'Up! Button', + 'Vs Button', + 'Japanese “Here” Button', + 'Japanese “Service Charge” Button', + 'Japanese “Monthly Amount” Button', + 'Japanese “Not Free of Charge” Button', + 'Japanese “Reserved” Button', + 'Japanese “Bargain” Button', + 'Japanese “Discount” Button', + 'Japanese “Free of Charge” Button', + 'Japanese “Prohibited” Button', + 'Japanese “Acceptable” Button', + 'Japanese “Application” Button', + 'Japanese “Passing Grade” Button', + 'Japanese “Vacancy” Button', + 'Japanese “Congratulations” Button', + 'Japanese “Secret” Button', + 'Japanese “Open for Business” Button', + 'Japanese “No Vacancy” Button', + 'Red Circle', + 'Blue Circle', + 'Black Circle', + 'White Circle', + 'Black Large Square', + 'White Large Square', + 'Black Medium Square', + 'White Medium Square', + 'Black Medium-Small Square', + 'White Medium-Small Square', + 'Black Small Square', + 'White Small Square', + 'Large Orange Diamond', + 'Large Blue Diamond', + 'Small Orange Diamond', + 'Small Blue Diamond', + 'Red Triangle Pointed Up', + 'Red Triangle Pointed Down', + 'Diamond With a Dot', + 'White Square Button', + 'Black Square Button' +], [ + '💘', + '💝', + '💖', + '💗', + '💓', + '💞', + '💕', + '💟', + '❣', + '💔', + '❤', + '🧡', + '💛', + '💚', + '💙', + '💜', + '🖤', + '💯', + '💢', + '💬', + '👁‍🗨', + '🗯', + '💭', + '💤', + '💮', + '♨', + '💈', + '🛑', + '🕛', + '🕧', + '🕐', + '🕜', + '🕑', + '🕝', + '🕒', + '🕞', + '🕓', + '🕟', + '🕔', + '🕠', + '🕕', + '🕡', + '🕖', + '🕢', + '🕗', + '🕣', + '🕘', + '🕤', + '🕙', + '🕥', + '🕚', + '🕦', + '🌀', + '♠', + '♥', + '♦', + '♣', + '🃏', + '🀄', + '🎴', + '🔇', + '🔈', + '🔉', + '🔊', + '📢', + '📣', + '📯', + '🔔', + '🔕', + '🎵', + '🎶', + '🏧', + '🚮', + '🚰', + '♿', + '🚹', + '🚺', + '🚻', + '🚼', + '🚾', + '⚠', + '🚸', + '⛔', + '🚫', + '🚳', + '🚭', + '🚯', + '🚱', + '🚷', + '🔞', + '☢', + '☣', + '⬆', + '↗', + '➡', + '↘', + '⬇', + '↙', + '⬅', + '↖', + '↕', + '↔', + '↩', + '↪', + '⤴', + '⤵', + '🔃', + '🔄', + '🔙', + '🔚', + '🔛', + '🔜', + '🔝', + '🛐', + '⚛', + '🕉', + '✡', + '☸', + '☯', + '✝', + '☦', + '☪', + '☮', + '🕎', + '🔯', + '♈', + '♉', + '♊', + '♋', + '♌', + '♍', + '♎', + '♏', + '♐', + '♑', + '♒', + '♓', + '⛎', + '🔀', + '🔁', + '🔂', + '▶', + '⏩', + '◀', + '⏪', + '🔼', + '⏫', + '🔽', + '⏬', + '⏹', + '⏏', + '🎦', + '🔅', + '🔆', + '📶', + '📳', + '📴', + '♾', + '♻', + '🔱', + '📛', + '🔰', + '⭕', + '✅', + '☑', + '✔', + '✖', + '❌', + '❎', + '➕', + '➖', + '➗', + '➰', + '➿', + '〽', + '✳', + '✴', + '❇', + '‼', + '⁉', + '❓', + '❔', + '❕', + '❗', + '©', + '®', + '™', + '#️⃣', + '0️⃣', + '1️⃣', + '2️⃣', + '3️⃣', + '4️⃣', + '5️⃣', + '6️⃣', + '7️⃣', + '8️⃣', + '9️⃣', + '🔟', + '🔠', + '🔡', + '🔢', + '🔣', + '🔤', + '🅰', + '🆎', + '🅱', + '🆑', + '🆒', + '🆓', + 'ℹ', + '🆔', + 'Ⓜ', + '🆕', + '🆖', + '🅾', + '🆗', + '🅿', + '🆘', + '🆙', + '🆚', + '🈁', + '🈂', + '🈷', + '🈶', + '🈯', + '🉐', + '🈹', + '🈚', + '🈲', + '🉑', + '🈸', + '🈴', + '🈳', + '㊗', + '㊙', + '🈺', + '🈵', + '🔴', + '🔵', + '⚫', + '⚪', + '⬛', + '⬜', + '◼', + '◻', + '◾', + '◽', + '▪', + '▫', + '🔶', + '🔷', + '🔸', + '🔹', + '🔺', + '🔻', + '💠', + '🔳', + '🔲' +]); + +/// Map of all possible emojis along with their names in [Category.FLAGS] +final Map flags = Map.fromIterables([ + 'Chequered Flag', + 'Triangular Flag', + 'Crossed Flags', + 'Black Flag', + 'White Flag', + 'Rainbow Flag', + 'Pirate Flag', + 'Flag: Ascension Island', + 'Flag: Andorra', + 'Flag: United Arab Emirates', + 'Flag: Afghanistan', + 'Flag: Antigua & Barbuda', + 'Flag: Anguilla', + 'Flag: Albania', + 'Flag: Armenia', + 'Flag: Angola', + 'Flag: Antarctica', + 'Flag: Argentina', + 'Flag: American Samoa', + 'Flag: Austria', + 'Flag: Australia', + 'Flag: Aruba', + 'Flag: Åland Islands', + 'Flag: Azerbaijan', + 'Flag: Bosnia & Herzegovina', + 'Flag: Barbados', + 'Flag: Bangladesh', + 'Flag: Belgium', + 'Flag: Burkina Faso', + 'Flag: Bulgaria', + 'Flag: Bahrain', + 'Flag: Burundi', + 'Flag: Benin', + 'Flag: St. Barthélemy', + 'Flag: Bermuda', + 'Flag: Brunei', + 'Flag: Bolivia', + 'Flag: Caribbean Netherlands', + 'Flag: Brazil', + 'Flag: Bahamas', + 'Flag: Bhutan', + 'Flag: Bouvet Island', + 'Flag: Botswana', + 'Flag: Belarus', + 'Flag: Belize', + 'Flag: Canada', + 'Flag: Cocos (Keeling) Islands', + 'Flag: Congo - Kinshasa', + 'Flag: Central African Republic', + 'Flag: Congo - Brazzaville', + 'Flag: Switzerland', + 'Flag: Côte d’Ivoire', + 'Flag: Cook Islands', + 'Flag: Chile', + 'Flag: Cameroon', + 'Flag: China', + 'Flag: Colombia', + 'Flag: Clipperton Island', + 'Flag: Costa Rica', + 'Flag: Cuba', + 'Flag: Cape Verde', + 'Flag: Curaçao', + 'Flag: Christmas Island', + 'Flag: Cyprus', + 'Flag: Czechia', + 'Flag: Germany', + 'Flag: Diego Garcia', + 'Flag: Djibouti', + 'Flag: Denmark', + 'Flag: Dominica', + 'Flag: Dominican Republic', + 'Flag: Algeria', + 'Flag: Ceuta & Melilla', + 'Flag: Ecuador', + 'Flag: Estonia', + 'Flag: Egypt', + 'Flag: Western Sahara', + 'Flag: Eritrea', + 'Flag: Spain', + 'Flag: Ethiopia', + 'Flag: European Union', + 'Flag: Finland', + 'Flag: Fiji', + 'Flag: Falkland Islands', + 'Flag: Micronesia', + 'Flag: Faroe Islands', + 'Flag: France', + 'Flag: Gabon', + 'Flag: United Kingdom', + 'Flag: Grenada', + 'Flag: Georgia', + 'Flag: French Guiana', + 'Flag: Guernsey', + 'Flag: Ghana', + 'Flag: Gibraltar', + 'Flag: Greenland', + 'Flag: Gambia', + 'Flag: Guinea', + 'Flag: Guadeloupe', + 'Flag: Equatorial Guinea', + 'Flag: Greece', + 'Flag: South Georgia & South Sandwich Islands', + 'Flag: Guatemala', + 'Flag: Guam', + 'Flag: Guinea-Bissau', + 'Flag: Guyana', + 'Flag: Hong Kong SAR China', + 'Flag: Heard & McDonald Islands', + 'Flag: Honduras', + 'Flag: Croatia', + 'Flag: Haiti', + 'Flag: Hungary', + 'Flag: Canary Islands', + 'Flag: Indonesia', + 'Flag: Ireland', + 'Flag: Israel', + 'Flag: Isle of Man', + 'Flag: India', + 'Flag: British Indian Ocean Territory', + 'Flag: Iraq', + 'Flag: Iran', + 'Flag: Iceland', + 'Flag: Italy', + 'Flag: Jersey', + 'Flag: Jamaica', + 'Flag: Jordan', + 'Flag: Japan', + 'Flag: Kenya', + 'Flag: Kyrgyzstan', + 'Flag: Cambodia', + 'Flag: Kiribati', + 'Flag: Comoros', + 'Flag: St. Kitts & Nevis', + 'Flag: North Korea', + 'Flag: South Korea', + 'Flag: Kuwait', + 'Flag: Cayman Islands', + 'Flag: Kazakhstan', + 'Flag: Laos', + 'Flag: Lebanon', + 'Flag: St. Lucia', + 'Flag: Liechtenstein', + 'Flag: Sri Lanka', + 'Flag: Liberia', + 'Flag: Lesotho', + 'Flag: Lithuania', + 'Flag: Luxembourg', + 'Flag: Latvia', + 'Flag: Libya', + 'Flag: Morocco', + 'Flag: Monaco', + 'Flag: Moldova', + 'Flag: Montenegro', + 'Flag: St. Martin', + 'Flag: Madagascar', + 'Flag: Marshall Islands', + 'Flag: North Macedonia', + 'Flag: Mali', + 'Flag: Myanmar (Burma)', + 'Flag: Mongolia', + 'Flag: Macau Sar China', + 'Flag: Northern Mariana Islands', + 'Flag: Martinique', + 'Flag: Mauritania', + 'Flag: Montserrat', + 'Flag: Malta', + 'Flag: Mauritius', + 'Flag: Maldives', + 'Flag: Malawi', + 'Flag: Mexico', + 'Flag: Malaysia', + 'Flag: Mozambique', + 'Flag: Namibia', + 'Flag: New Caledonia', + 'Flag: Niger', + 'Flag: Norfolk Island', + 'Flag: Nigeria', + 'Flag: Nicaragua', + 'Flag: Netherlands', + 'Flag: Norway', + 'Flag: Nepal', + 'Flag: Nauru', + 'Flag: Niue', + 'Flag: New Zealand', + 'Flag: Oman', + 'Flag: Panama', + 'Flag: Peru', + 'Flag: French Polynesia', + 'Flag: Papua New Guinea', + 'Flag: Philippines', + 'Flag: Pakistan', + 'Flag: Poland', + 'Flag: St. Pierre & Miquelon', + 'Flag: Pitcairn Islands', + 'Flag: Puerto Rico', + 'Flag: Palestinian Territories', + 'Flag: Portugal', + 'Flag: Palau', + 'Flag: Paraguay', + 'Flag: Qatar', + 'Flag: Réunion', + 'Flag: Romania', + 'Flag: Serbia', + 'Flag: Russia', + 'Flag: Rwanda', + 'Flag: Saudi Arabia', + 'Flag: Solomon Islands', + 'Flag: Seychelles', + 'Flag: Sudan', + 'Flag: Sweden', + 'Flag: Singapore', + 'Flag: St. Helena', + 'Flag: Slovenia', + 'Flag: Svalbard & Jan Mayen', + 'Flag: Slovakia', + 'Flag: Sierra Leone', + 'Flag: San Marino', + 'Flag: Senegal', + 'Flag: Somalia', + 'Flag: Suriname', + 'Flag: South Sudan', + 'Flag: São Tomé & Príncipe', + 'Flag: El Salvador', + 'Flag: Sint Maarten', + 'Flag: Syria', + 'Flag: Swaziland', + 'Flag: Tristan Da Cunha', + 'Flag: Turks & Caicos Islands', + 'Flag: Chad', + 'Flag: French Southern Territories', + 'Flag: Togo', + 'Flag: Thailand', + 'Flag: Tajikistan', + 'Flag: Tokelau', + 'Flag: Timor-Leste', + 'Flag: Turkmenistan', + 'Flag: Tunisia', + 'Flag: Tonga', + 'Flag: Turkey', + 'Flag: Trinidad & Tobago', + 'Flag: Tuvalu', + 'Flag: Taiwan', + 'Flag: Tanzania', + 'Flag: Ukraine', + 'Flag: Uganda', + 'Flag: U.S. Outlying Islands', + 'Flag: United Nations', + 'Flag: United States', + 'Flag: Uruguay', + 'Flag: Uzbekistan', + 'Flag: Vatican City', + 'Flag: St. Vincent & Grenadines', + 'Flag: Venezuela', + 'Flag: British Virgin Islands', + 'Flag: U.S. Virgin Islands', + 'Flag: Vietnam', + 'Flag: Vanuatu', + 'Flag: Wallis & Futuna', + 'Flag: Samoa', + 'Flag: Kosovo', + 'Flag: Yemen', + 'Flag: Mayotte', + 'Flag: South Africa', + 'Flag: Zambia', + 'Flag: Zimbabwe' +], [ + '🏁', + '🚩', + '🎌', + '🏴', + '🏳', + '🏳️‍🌈', + '🏴‍☠️', + '🇦🇨', + '🇦🇩', + '🇦🇪', + '🇦🇫', + '🇦🇬', + '🇦🇮', + '🇦🇱', + '🇦🇲', + '🇦🇴', + '🇦🇶', + '🇦🇷', + '🇦🇸', + '🇦🇹', + '🇦🇺', + '🇦🇼', + '🇦🇽', + '🇦🇿', + '🇧🇦', + '🇧🇧', + '🇧🇩', + '🇧🇪', + '🇧🇫', + '🇧🇬', + '🇧🇭', + '🇧🇮', + '🇧🇯', + '🇧🇱', + '🇧🇲', + '🇧🇳', + '🇧🇴', + '🇧🇶', + '🇧🇷', + '🇧🇸', + '🇧🇹', + '🇧🇻', + '🇧🇼', + '🇧🇾', + '🇧🇿', + '🇨🇦', + '🇨🇨', + '🇨🇩', + '🇨🇫', + '🇨🇬', + '🇨🇭', + '🇨🇮', + '🇨🇰', + '🇨🇱', + '🇨🇲', + '🇨🇳', + '🇨🇴', + '🇨🇵', + '🇨🇷', + '🇨🇺', + '🇨🇻', + '🇨🇼', + '🇨🇽', + '🇨🇾', + '🇨🇿', + '🇩🇪', + '🇩🇬', + '🇩🇯', + '🇩🇰', + '🇩🇲', + '🇩🇴', + '🇩🇿', + '🇪🇦', + '🇪🇨', + '🇪🇪', + '🇪🇬', + '🇪🇭', + '🇪🇷', + '🇪🇸', + '🇪🇹', + '🇪🇺', + '🇫🇮', + '🇫🇯', + '🇫🇰', + '🇫🇲', + '🇫🇴', + '🇫🇷', + '🇬🇦', + '🇬🇧', + '🇬🇩', + '🇬🇪', + '🇬🇫', + '🇬🇬', + '🇬🇭', + '🇬🇮', + '🇬🇱', + '🇬🇲', + '🇬🇳', + '🇬🇵', + '🇬🇶', + '🇬🇷', + '🇬🇸', + '🇬🇹', + '🇬🇺', + '🇬🇼', + '🇬🇾', + '🇭🇰', + '🇭🇲', + '🇭🇳', + '🇭🇷', + '🇭🇹', + '🇭🇺', + '🇮🇨', + '🇮🇩', + '🇮🇪', + '🇮🇱', + '🇮🇲', + '🇮🇳', + '🇮🇴', + '🇮🇶', + '🇮🇷', + '🇮🇸', + '🇮🇹', + '🇯🇪', + '🇯🇲', + '🇯🇴', + '🇯🇵', + '🇰🇪', + '🇰🇬', + '🇰🇭', + '🇰🇮', + '🇰🇲', + '🇰🇳', + '🇰🇵', + '🇰🇷', + '🇰🇼', + '🇰🇾', + '🇰🇿', + '🇱🇦', + '🇱🇧', + '🇱🇨', + '🇱🇮', + '🇱🇰', + '🇱🇷', + '🇱🇸', + '🇱🇹', + '🇱🇺', + '🇱🇻', + '🇱🇾', + '🇲🇦', + '🇲🇨', + '🇲🇩', + '🇲🇪', + '🇲🇫', + '🇲🇬', + '🇲🇭', + '🇲🇰', + '🇲🇱', + '🇲🇲', + '🇲🇳', + '🇲🇴', + '🇲🇵', + '🇲🇶', + '🇲🇷', + '🇲🇸', + '🇲🇹', + '🇲🇺', + '🇲🇻', + '🇲🇼', + '🇲🇽', + '🇲🇾', + '🇲🇿', + '🇳🇦', + '🇳🇨', + '🇳🇪', + '🇳🇫', + '🇳🇬', + '🇳🇮', + '🇳🇱', + '🇳🇴', + '🇳🇵', + '🇳🇷', + '🇳🇺', + '🇳🇿', + '🇴🇲', + '🇵🇦', + '🇵🇪', + '🇵🇫', + '🇵🇬', + '🇵🇭', + '🇵🇰', + '🇵🇱', + '🇵🇲', + '🇵🇳', + '🇵🇷', + '🇵🇸', + '🇵🇹', + '🇵🇼', + '🇵🇾', + '🇶🇦', + '🇷🇪', + '🇷🇴', + '🇷🇸', + '🇷🇺', + '🇷🇼', + '🇸🇦', + '🇸🇧', + '🇸🇨', + '🇸🇩', + '🇸🇪', + '🇸🇬', + '🇸🇭', + '🇸🇮', + '🇸🇯', + '🇸🇰', + '🇸🇱', + '🇸🇲', + '🇸🇳', + '🇸🇴', + '🇸🇷', + '🇸🇸', + '🇸🇹', + '🇸🇻', + '🇸🇽', + '🇸🇾', + '🇸🇿', + '🇹🇦', + '🇹🇨', + '🇹🇩', + '🇹🇫', + '🇹🇬', + '🇹🇭', + '🇹🇯', + '🇹🇰', + '🇹🇱', + '🇹🇲', + '🇹🇳', + '🇹🇴', + '🇹🇷', + '🇹🇹', + '🇹🇻', + '🇹🇼', + '🇹🇿', + '🇺🇦', + '🇺🇬', + '🇺🇲', + '🇺🇳', + '🇺🇸', + '🇺🇾', + '🇺🇿', + '🇻🇦', + '🇻🇨', + '🇻🇪', + '🇻🇬', + '🇻🇮', + '🇻🇳', + '🇻🇺', + '🇼🇫', + '🇼🇸', + '🇽🇰', + '🇾🇪', + '🇾🇹', + '🇿🇦', + '🇿🇲', + '🇿🇼' +]); + +/// Set of emoji that support different skin tones +final supportSkinToneList = { + '👋', + '🤚', + '🖐', + '✋', + '🖖', + '👌', + '✌', + '🤞', + '🤟', + '🤘', + '🤙', + '👈', + '👉', + '👆', + '🖕', + '👇', + '☝', + '👍', + '👎', + '✊', + '👊', + '🤛', + '🤜', + '👏', + '🙌', + '👐', + '🤲', + '🤝', + '🙏', + '✍', + '💅', + '🤳', + '💪', + '🦵', + '🦶', + '👂', + '👃', + '👶', + '🧒', + '👦', + '👧', + '🧑', + '👨', + '🧔', + '👱', + '👨‍🦰', + '👨‍🦱', + '👨‍🦳', + '👨‍🦲', + '👩', + '👱‍♀️', + '👩‍🦰', + '👩‍🦱', + '👩‍🦳', + '👩‍🦲', + '🧓', + '👴', + '👵', + '🙍‍♂️', + '🙍‍♀️', + '🙎‍♂️', + '🙎‍♀️', + '🙅‍♂️', + '🙅‍♀️', + '🙆‍♂️', + '🙆‍♀️', + '💁‍♂️', + '💁‍♀️', + '🙋‍♂️', + '🙋‍♀️', + '🙇‍♂️', + '🙇‍♀️', + '🤦‍♂️', + '🤦‍♀️', + '🤷‍♂️', + '🤷‍♀️', + '👨‍⚕️', + '👩‍⚕️', + '👨‍🎓', + '👩‍🎓', + '👨‍🏫', + '👩‍🏫', + '👨‍⚖️', + '👩‍⚖️', + '👨‍🌾', + '👩‍🌾', + '👨‍🍳', + '👩‍🍳', + '👨‍🔧', + '👩‍🔧', + '👨‍🏭', + '👩‍🏭', + '👨‍💼', + '👩‍💼', + '👨‍🔬', + '👩‍🔬', + '👨‍💻', + '👩‍💻', + '👨‍🎤', + '👩‍🎤', + '👨‍🎨', + '👩‍🎨', + '👨‍✈️', + '👩‍✈️', + '👨‍🚀', + '👩‍🚀', + '👨‍🚒', + '👩‍🚒', + '👮‍♂️', + '👮‍♀️', + '🕵️‍♂️', + '🕵️‍♀️', + '💂‍♂️', + '💂‍♀️', + '👷‍♂️', + '👷‍♀️', + '🤴', + '👸', + '👳‍♂️', + '👳‍♀️', + '👲', + '🧕', + '🤵', + '👰', + '🤰', + '🤱', + '👼', + '🎅', + '🤶', + '🦸‍♂️', + '🦸‍♀️', + '🦹‍♂️', + '🦹‍♀️', + '🧙‍♂️', + '🧙‍♀️', + '🧚‍♂️', + '🧚‍♀️', + '🧛‍♂️', + '🧛‍♀️', + '🧜‍♂️', + '🧜‍♀️', + '🧝‍♂️', + '🧝‍♀️', + '💆‍♂️', + '💆‍♀️', + '💇‍♂️', + '💇‍♀️', + '🚶‍♂️', + '🚶‍♀️', + '🏃‍♂️', + '🏃‍♀️', + '🕺', + '💃', + '🕴', + '🧖‍♂️', + '🧖‍♀️', + '🧘', + '👭', + '👫', + '👬', + // Activities + '🧗‍♂️', + '🧗‍♀️', + '🏇', + '🏌️‍♂️', + '🏌️‍♀️', + '🏄‍♂️', + '🏄‍♀️', + '🚣‍♂️', + '🚣‍♀️', + '🏊‍♂️', + '🏊‍♀️', + '⛹️‍♂️', + '⛹️‍♀️', + '🏋️‍♂️', + '🏋️‍♀️', + '🚴‍♂️', + '🚴‍♀️', + '🚵‍♂️', + '🚵‍♀️', + '🤸‍♂️', + '🤸‍♀️', + '🤼‍♂️', + '🤼‍♀️', + '🤽‍♂️', + '🤽‍♀️', + '🤾‍♂️', + '🤾‍♀️', + '🤹‍♂️', + '🤹‍♀️', + '🧘‍♂️', + '🧘‍♀️', +}; diff --git a/bytedesk_kefu/lib/vendors/emoji_picker_flutter/src/emoji_picker.dart b/bytedesk_kefu/lib/vendors/emoji_picker_flutter/src/emoji_picker.dart new file mode 100644 index 0000000..33e0c79 --- /dev/null +++ b/bytedesk_kefu/lib/vendors/emoji_picker_flutter/src/emoji_picker.dart @@ -0,0 +1,253 @@ +import './category_emoji.dart'; +import './config.dart'; +import './default_emoji_picker_view.dart'; +import './emoji.dart'; +import './emoji_picker_internal_utils.dart'; +import './emoji_view_state.dart'; +import './recent_emoji.dart'; +import 'package:flutter/material.dart'; + +/// All the possible categories that [Emoji] can be put into +/// +/// All [Category] are shown in the category bar +enum Category { + /// Recent emojis + RECENT, + + /// Smiley emojis + SMILEYS, + + /// Animal emojis + ANIMALS, + + /// Food emojis + FOODS, + + /// Activity emojis + ACTIVITIES, + + /// Travel emojis + TRAVEL, + + /// Ojects emojis + OBJECTS, + + /// Sumbol emojis + SYMBOLS, + + /// Flag emojis + FLAGS, +} + +/// Extension on Category enum to get its name +extension CategoryExtension on Category { + /// Returns name of Category + String get name { + switch (this) { + case Category.RECENT: + return 'recent'; + case Category.SMILEYS: + return 'smileys'; + case Category.ANIMALS: + return 'animals'; + case Category.FOODS: + return 'foods'; + case Category.ACTIVITIES: + return 'activities'; + case Category.TRAVEL: + return 'travel'; + case Category.OBJECTS: + return 'objects'; + case Category.SYMBOLS: + return 'symbols'; + case Category.FLAGS: + return 'flags'; + } + } +} + +/// Enum to alter the keyboard button style +enum ButtonMode { + /// Android button style - gives the button a splash color with ripple effect + MATERIAL, + + /// iOS button style - gives the button a fade out effect when pressed + CUPERTINO +} + +/// Callback function for when emoji is selected +/// +/// The function returns the selected [Emoji] as well +/// as the [Category] from which it originated +typedef void OnEmojiSelected(Category category, Emoji emoji); + +/// Callback function for backspace button +typedef void OnBackspacePressed(); + +/// Callback function for custom view +typedef EmojiViewBuilder = Widget Function(Config config, EmojiViewState state); + +/// The Emoji Keyboard widget +/// +/// This widget displays a grid of [Emoji] sorted by [Category] +/// which the user can horizontally scroll through. +/// +/// There is also a bottombar which displays all the possible [Category] +/// and allow the user to quickly switch to that [Category] +class EmojiPicker extends StatefulWidget { + /// EmojiPicker for flutter + const EmojiPicker({ + Key? key, + required this.onEmojiSelected, + this.onBackspacePressed, + this.config = const Config(), + this.customWidget, + }) : super(key: key); + + /// Custom widget + final EmojiViewBuilder? customWidget; + + /// The function called when the emoji is selected + final OnEmojiSelected onEmojiSelected; + + /// The function called when backspace button is pressed + final OnBackspacePressed? onBackspacePressed; + + /// Config for customizations + final Config config; + + @override + EmojiPickerState createState() => EmojiPickerState(); +} + +/// EmojiPickerState +class EmojiPickerState extends State { + final List _categoryEmoji = List.empty(growable: true); + List _recentEmoji = List.empty(growable: true); + late Future _updateEmojiFuture; + + // Prevent emojis to be reloaded with every build + bool _loaded = false; + + // Internal helper + final _emojiPickerInternalUtils = EmojiPickerInternalUtils(); + + /// Update recentEmoji list from outside using EmojiPickerUtils + void updateRecentEmoji(List recentEmoji) { + _recentEmoji = recentEmoji; + if (mounted) { + setState(() {}); + } + } + + @override + void initState() { + super.initState(); + _updateEmojiFuture = _updateEmojis(); + } + + @override + void didUpdateWidget(covariant EmojiPicker oldWidget) { + if (oldWidget.config != widget.config) { + // Config changed - rebuild EmojiPickerView completely + _loaded = false; + _updateEmojiFuture = _updateEmojis(); + } + super.didUpdateWidget(oldWidget); + } + + @override + Widget build(BuildContext context) { + if (!_loaded) { + // Load emojis + _updateEmojiFuture.then( + (value) => WidgetsBinding.instance!.addPostFrameCallback((_) { + if (!mounted) return; + setState(() { + _loaded = true; + }); + }), + ); + + // Show loading indicator + return Container( + alignment: Alignment.center, + color: widget.config.bgColor, + child: const CircularProgressIndicator(), + ); + } + if (widget.config.showRecentsTab) { + _categoryEmoji[0].emoji = _recentEmoji.map((e) => e.emoji).toList(); + } + + var state = EmojiViewState( + _categoryEmoji, + _getOnEmojiListener(), + widget.onBackspacePressed, + ); + + // Build + return widget.customWidget == null + ? DefaultEmojiPickerView(widget.config, state) + : widget.customWidget!(widget.config, state); + } + + // Add recent emoji handling to tap listener + OnEmojiSelected _getOnEmojiListener() { + return (category, emoji) { + if (widget.config.showRecentsTab) { + _emojiPickerInternalUtils + .addEmojiToRecentlyUsed(emoji: emoji, config: widget.config) + .then((newRecentEmoji) => { + _recentEmoji = newRecentEmoji, + if (category != Category.RECENT && mounted) + setState(() { + // rebuild to update recent emoji tab + // when it is not current tab + }) + }); + } + widget.onEmojiSelected(category, emoji); + }; + } + + // Initialize emoji data + Future _updateEmojis() async { + _categoryEmoji.clear(); + if (widget.config.showRecentsTab) { + _recentEmoji = await _emojiPickerInternalUtils.getRecentEmojis(); + final recentEmojiMap = _recentEmoji.map((e) => e.emoji).toList(); + _categoryEmoji.add(CategoryEmoji(Category.RECENT, recentEmojiMap)); + } + + final availableCategoryEmoji = + await _emojiPickerInternalUtils.getAvailableCategoryEmoji(); + + availableCategoryEmoji.forEach((category, emojis) async { + _categoryEmoji.add( + CategoryEmoji( + category, + emojis.entries.map((emoji) { + var _emoji = Emoji(emoji.key, emoji.value); + // Emoji with skin tone are only in SMILEY & ACTIVITIES category + if (category == Category.SMILEYS || + category == Category.ACTIVITIES) { + return _updateSkinToneSupport(_emoji); + } else + return _emoji; + }).toList()), + ); + }); + + // Update emoji list version once all categories were cached + _emojiPickerInternalUtils.updateEmojiVersion(); + } + + // Set [hasSkinTone] to true for emoji that support skin tones + Emoji _updateSkinToneSupport(Emoji emoji) { + if (_emojiPickerInternalUtils.hasSkinTone(emoji)) { + return emoji.copyWith(hasSkinTone: true); + } + return emoji; + } +} diff --git a/bytedesk_kefu/lib/vendors/emoji_picker_flutter/src/emoji_picker_builder.dart b/bytedesk_kefu/lib/vendors/emoji_picker_flutter/src/emoji_picker_builder.dart new file mode 100644 index 0000000..2018b31 --- /dev/null +++ b/bytedesk_kefu/lib/vendors/emoji_picker_flutter/src/emoji_picker_builder.dart @@ -0,0 +1,20 @@ +import './config.dart'; +import './emoji_view_state.dart'; +import 'package:flutter/material.dart'; + +/// Template class for custom implementation +/// Inhert this class to create your own EmojiPicker +abstract class EmojiPickerBuilder extends StatefulWidget { + /// Constructor + EmojiPickerBuilder( + this.config, + this.state, { + Key? key, + }) : super(key: key); + + /// Config for customizations + final Config config; + + /// State that holds current emoji data + final EmojiViewState state; +} diff --git a/bytedesk_kefu/lib/vendors/emoji_picker_flutter/src/emoji_picker_internal_utils.dart b/bytedesk_kefu/lib/vendors/emoji_picker_flutter/src/emoji_picker_internal_utils.dart new file mode 100644 index 0000000..370bc3b --- /dev/null +++ b/bytedesk_kefu/lib/vendors/emoji_picker_flutter/src/emoji_picker_internal_utils.dart @@ -0,0 +1,198 @@ +import 'dart:convert'; +import 'dart:io'; +import 'dart:math'; + +import '../emoji_picker_flutter.dart'; +import './emoji_skin_tones.dart'; +import 'package:flutter/services.dart'; +import 'package:shared_preferences/shared_preferences.dart'; +import 'emoji_lists.dart' as emoji_list; +import 'recent_emoji.dart'; + +/// Helper class that provides internal usage +class EmojiPickerInternalUtils { + // Establish communication with native + static const _platform = MethodChannel('emoji_picker_flutter'); + static const _emojiVersion = 'emoji_version'; + + /// Returns true when local emoji list is outdated + Future isEmojiUpdateAvailable() async { + final prefs = await SharedPreferences.getInstance(); + var emojiVersion = prefs.getInt(_emojiVersion) ?? 0; + // != to support downgrading of emoji_picker versions + return emoji_list.version != emojiVersion; + } + + /// Updates local emoji version with current version + Future updateEmojiVersion() async { + final prefs = await SharedPreferences.getInstance(); + prefs.setInt(_emojiVersion, emoji_list.version); + } + + /// Restore locally cached emoji + Future?> _restoreFilteredEmojis(String title) async { + final prefs = await SharedPreferences.getInstance(); + var emojiJson = prefs.getString(title); + if (emojiJson == null) { + return null; + } + var emojis = + Map.from(jsonDecode(emojiJson) as Map); + return emojis; + } + + // Get available emoji for given category title + Future> _getAvailableEmojis(Map map, + {required String title}) async { + Map? newMap; + + // Get Emojis cached locally if available + if (await isEmojiUpdateAvailable() == false) { + newMap = await _restoreFilteredEmojis(title); + } + + if (newMap == null) { + // Check if emoji is available on this platform + newMap = await _getPlatformAvailableEmoji(map); + // Save available Emojis to local storage for faster loading next time + if (newMap != null) { + await _cacheFilteredEmojis(title, newMap); + } + } + + return newMap ?? {}; + } + + /// Returns map of all the available category emojis + Future>> getAvailableCategoryEmoji() async { + final allCategoryEmoji = Map.fromIterables([ + Category.SMILEYS, + Category.ANIMALS, + Category.FOODS, + Category.ACTIVITIES, + Category.TRAVEL, + Category.OBJECTS, + Category.SYMBOLS, + Category.FLAGS + ], [ + emoji_list.smileys, + emoji_list.animals, + emoji_list.foods, + emoji_list.activities, + emoji_list.travel, + emoji_list.objects, + emoji_list.symbols, + emoji_list.flags, + ]); + + final futures = allCategoryEmoji.entries + .map((e) => _getAvailableEmojis(e.value, title: e.key.name)); + + final allAvailableEmojis = await Future.wait(futures); + + return Map.fromIterables(allCategoryEmoji.keys, allAvailableEmojis); + } + + /// Stores filtered emoji locally for faster access next time + Future _cacheFilteredEmojis( + String title, Map emojis) async { + var emojiJson = jsonEncode(emojis); + final prefs = await SharedPreferences.getInstance(); + prefs.setString(title, emojiJson); + } + + /// Check if emoji is available on current platform + Future?> _getPlatformAvailableEmoji( + Map emoji) async { + if (Platform.isAndroid) { + Map? filtered = {}; + var delimiter = '|'; + try { + var entries = emoji.values.join(delimiter); + var keys = emoji.keys.join(delimiter); + var result = (await _platform.invokeMethod('checkAvailability', + {'emojiKeys': keys, 'emojiEntries': entries})) as String; + var resultKeys = result.split(delimiter); + for (var i = 0; i < resultKeys.length; i++) { + filtered[resultKeys[i]] = emoji[resultKeys[i]]!; + } + } on PlatformException catch (_) { + filtered = null; + } + return filtered; + } else { + return emoji; + } + } + + /// Returns list of recently used emoji from cache + Future> getRecentEmojis() async { + final prefs = await SharedPreferences.getInstance(); + var emojiJson = prefs.getString('recent'); + if (emojiJson == null) { + return []; + } + var json = jsonDecode(emojiJson) as List; + return json.map(RecentEmoji.fromJson).toList(); + } + + /// Add an emoji to recently used list or increase its counter + Future> addEmojiToRecentlyUsed( + {required Emoji emoji, Config config = const Config()}) async { + // Remove emoji's skin tone in Recent-Category + if (emoji.hasSkinTone) { + emoji = removeSkinTone(emoji); + } + var recentEmoji = await getRecentEmojis(); + var recentEmojiIndex = + recentEmoji.indexWhere((element) => element.emoji.emoji == emoji.emoji); + if (recentEmojiIndex != -1) { + // Already exist in recent list + // Just update counter + recentEmoji[recentEmojiIndex].counter++; + } else if (recentEmoji.length == config.recentsLimit && + config.replaceEmojiOnLimitExceed) { + // Replace latest emoji with the fresh one + recentEmoji[recentEmoji.length - 1] = RecentEmoji(emoji, 1); + } else { + recentEmoji.add(RecentEmoji(emoji, 1)); + } + // Sort by counter desc + recentEmoji.sort((a, b) => b.counter - a.counter); + // Limit entries to recentsLimit + recentEmoji = + recentEmoji.sublist(0, min(config.recentsLimit, recentEmoji.length)); + // save locally + final prefs = await SharedPreferences.getInstance(); + prefs.setString('recent', jsonEncode(recentEmoji)); + + return recentEmoji; + } + + /// Returns true when the emoji support multiple skin colors + bool hasSkinTone(Emoji emoji) { + return emoji_list.supportSkinToneList.contains(emoji.emoji); + } + + /// Applies skin tone to given emoji + Emoji applySkinTone(Emoji emoji, String color) { + final codeUnits = emoji.emoji.codeUnits; + var result = List.empty(growable: true) + ..addAll(codeUnits.sublist(0, min(codeUnits.length, 2))) + ..addAll(color.codeUnits); + if (codeUnits.length >= 2) { + result.addAll(codeUnits.sublist(2)); + } + return emoji.copyWith(emoji: String.fromCharCodes(result)); + } + + /// Remove skin tone from given emoji + Emoji removeSkinTone(Emoji emoji) { + return emoji.copyWith( + emoji: emoji.emoji.replaceFirst( + RegExp('${SkinTone.values.join('|')}'), + '', + ), + ); + } +} diff --git a/bytedesk_kefu/lib/vendors/emoji_picker_flutter/src/emoji_picker_utils.dart b/bytedesk_kefu/lib/vendors/emoji_picker_flutter/src/emoji_picker_utils.dart new file mode 100644 index 0000000..59d689d --- /dev/null +++ b/bytedesk_kefu/lib/vendors/emoji_picker_flutter/src/emoji_picker_utils.dart @@ -0,0 +1,59 @@ +import '../emoji_picker_flutter.dart'; +import './recent_emoji.dart'; +import 'package:flutter/material.dart'; +import 'emoji_picker_internal_utils.dart'; + +/// Helper class that provides extended usage +class EmojiPickerUtils { + /// Singleton Constructor + factory EmojiPickerUtils() { + return _singleton; + } + + EmojiPickerUtils._internal(); + + static final EmojiPickerUtils _singleton = EmojiPickerUtils._internal(); + final List _allAvailableEmojiEntities = []; + + /// Returns list of recently used emoji from cache + Future> getRecentEmojis() async { + return EmojiPickerInternalUtils().getRecentEmojis(); + } + + /// Search for related emoticons based on keywords + Future> searchEmoji(String keyword) async { + if (keyword.isEmpty) return []; + + if (_allAvailableEmojiEntities.isEmpty) { + final emojiPickerInternalUtils = EmojiPickerInternalUtils(); + + final availableCategoryEmoji = + await emojiPickerInternalUtils.getAvailableCategoryEmoji(); + + // Set all the emoji entities + availableCategoryEmoji.forEach((_, emojis) { + final emojiEntities = + emojis.entries.map((emoji) => Emoji(emoji.key, emoji.value)); + _allAvailableEmojiEntities.addAll(emojiEntities); + }); + } + + return _allAvailableEmojiEntities + .where( + (emoji) => emoji.name.toLowerCase().contains(keyword.toLowerCase()), + ) + .toList(); + } + + /// Add an emoji to recently used list or increase its counter + Future addEmojiToRecentlyUsed({ + required GlobalKey key, + required Emoji emoji, + Config config = const Config(), + }) async { + return EmojiPickerInternalUtils() + .addEmojiToRecentlyUsed(emoji: emoji, config: config) + .then((recentEmojiList) => + key.currentState?.updateRecentEmoji(recentEmojiList)); + } +} diff --git a/bytedesk_kefu/lib/vendors/emoji_picker_flutter/src/emoji_skin_tones.dart b/bytedesk_kefu/lib/vendors/emoji_picker_flutter/src/emoji_skin_tones.dart new file mode 100644 index 0000000..9d20d79 --- /dev/null +++ b/bytedesk_kefu/lib/vendors/emoji_picker_flutter/src/emoji_skin_tones.dart @@ -0,0 +1,22 @@ +/// Alternative skin tones of Emoji +class SkinTone { + SkinTone._(); + + /// Light Skin Tone + static const String light = '🏻'; + + /// Medium-Light Skin Tone + static const String mediumLight = '🏼'; + + /// Medium Skin Tone + static const String medium = '🏽'; + + /// Medium-Dark Skin Tone + static const String mediumDark = '🏾'; + + /// Dark Skin Tone + static const String dark = '🏿'; + + /// Return all values as Array + static const values = [light, mediumLight, medium, mediumDark, dark]; +} diff --git a/bytedesk_kefu/lib/vendors/emoji_picker_flutter/src/emoji_view_state.dart b/bytedesk_kefu/lib/vendors/emoji_picker_flutter/src/emoji_view_state.dart new file mode 100644 index 0000000..f58aee8 --- /dev/null +++ b/bytedesk_kefu/lib/vendors/emoji_picker_flutter/src/emoji_view_state.dart @@ -0,0 +1,21 @@ +import '../emoji_picker_flutter.dart'; +import './category_emoji.dart'; + +/// State that holds current emoji data +class EmojiViewState { + /// Constructor + EmojiViewState( + this.categoryEmoji, + this.onEmojiSelected, + this.onBackspacePressed, + ); + + /// List of all category including their emoji + final List categoryEmoji; + + /// Callback when pressed on emoji + final OnEmojiSelected onEmojiSelected; + + /// Callback when pressed on backspace + final OnBackspacePressed? onBackspacePressed; +} diff --git a/bytedesk_kefu/lib/vendors/emoji_picker_flutter/src/recent_emoji.dart b/bytedesk_kefu/lib/vendors/emoji_picker_flutter/src/recent_emoji.dart new file mode 100644 index 0000000..cd02388 --- /dev/null +++ b/bytedesk_kefu/lib/vendors/emoji_picker_flutter/src/recent_emoji.dart @@ -0,0 +1,30 @@ +import './emoji.dart'; + +/// Class that holds an recent emoji +/// Recent Emoji has an instance of the emoji +/// And a counter, which counts how often this emoji +/// has been used before +class RecentEmoji { + /// Constructor + RecentEmoji(this.emoji, this.counter); + + /// Emoji instance + final Emoji emoji; + + /// Counter how often emoji has been used before + int counter = 0; + + /// Parse RecentEmoji from json + static RecentEmoji fromJson(dynamic json) { + return RecentEmoji( + Emoji.fromJson(json['emoji'] as Map), + json['counter'] as int, + ); + } + + /// Encode RecentEmoji to json + Map toJson() => { + 'emoji': emoji, + 'counter': counter, + }; +} diff --git a/bytedesk_kefu/lib/vendors/emoji_picker_flutter/src/triangle_shape.dart b/bytedesk_kefu/lib/vendors/emoji_picker_flutter/src/triangle_shape.dart new file mode 100644 index 0000000..6254061 --- /dev/null +++ b/bytedesk_kefu/lib/vendors/emoji_picker_flutter/src/triangle_shape.dart @@ -0,0 +1,31 @@ +import 'package:flutter/material.dart'; + +/// Triangle shape used as skin tone indicator +class TriangleShape extends CustomPainter { + /// Constructor + /// Expects color that the triangle will be filled with + TriangleShape(Color color) { + _painter = Paint() + ..color = color + ..style = PaintingStyle.fill; + } + + late final Paint _painter; + + @override + void paint(Canvas canvas, Size size) { + var path = Path() + ..moveTo(size.width, 0) + ..lineTo(0, size.height) + ..lineTo(size.width, size.height) + ..lineTo(size.width, 0) + ..close(); + + canvas.drawPath(path, _painter); + } + + @override + bool shouldRepaint(CustomPainter oldDelegate) { + return false; + } +} diff --git a/bytedesk_kefu/pubspec.yaml b/bytedesk_kefu/pubspec.yaml index 1623d0f..e38f313 100644 --- a/bytedesk_kefu/pubspec.yaml +++ b/bytedesk_kefu/pubspec.yaml @@ -1,6 +1,6 @@ name: bytedesk_kefu description: the best app chat sdk in china, you can chat with the agent freely at anytime. the agent can chat with the visitor by web/pc/mac/ios/android client. -version: 1.3.3 +version: 1.4.0 homepage: https://www.weikefu.net environment: @@ -84,6 +84,20 @@ dependencies: # 保存图片到相册 # https://pub.flutter-io.cn/packages/image_gallery_saver image_gallery_saver: ^1.7.1 + # https://pub.dev/packages/emoji_picker_flutter + # emoji_picker_flutter: ^1.1.2 + # emoji_picker_flutter: + # ./vendors/emoji_picker_flutter + shared_preferences: ^2.0.6 + # # https://pub.dev/packages/carousel_slider + carousel_slider: ^4.1.1 + # # https://pub.dev/packages/flutter_chat_ui + flutter_chat_ui: ^1.5.8 + # 录音 + # flutter_sound: ^9.2.12 + # flutter_sound_platform_interface: ^9.2.12 + # audio_session: ^0.1.6+1 + # permission_handler: ^9.2.0 # https://pub.dev/packages/feedback # feedback: ^2.0.0-beta.0 # feedback: ^1.2.2