From 2ba610ca7f964caee4bb43e89257446511367071 Mon Sep 17 00:00:00 2001 From: abu <3109389044@qq.com> Date: Mon, 22 Sep 2025 14:41:47 +0800 Subject: [PATCH] merge --- assets/animation/voice.json | 1 + assets/images/icon_logout.png | Bin 0 -> 2421 bytes lib/IM/im_message.dart | 5 +- lib/IM/im_message_listeners.dart | 9 + lib/api/common_api.dart | 25 +- lib/components/animation.dart | 41 ++ lib/components/my_qrcode.dart | 54 ++- lib/pages/chat/chat.dart | 61 ++- lib/pages/chat/chat_group.dart | 14 +- lib/pages/chat/chat_no_friend.dart | 15 +- lib/pages/chat/index.dart | 99 ++++- lib/pages/chat/notify/interaction.dart | 306 +++++++------- lib/pages/chat/notify/newFoucs.dart | 291 ++++++------- lib/pages/chat/notify/order.dart | 382 +++++++++--------- .../components/invite_action_sheet.dart | 5 +- .../components/member_action_sheet.dart | 16 +- lib/pages/groupChat/groupList.dart | 7 +- lib/pages/groupChat/index.dart | 9 +- lib/pages/my/all_function.dart | 64 ++- lib/pages/my/delete.dart | 268 ++++++++++++ lib/pages/my/fans.dart | 7 +- lib/pages/my/flowing.dart | 7 +- lib/pages/my/index.dart | 128 +++--- lib/pages/my/merchant/balance/balance.dart | 172 +++++--- lib/pages/my/merchant/balance/controller.dart | 118 +++++- lib/pages/my/mutual_followers.dart | 7 +- lib/pages/my/nick_name.dart | 4 +- lib/pages/my/setting.dart | 9 +- lib/pages/my/user_info.dart | 14 +- lib/pages/my/vloger.dart | 84 ++-- .../upload_video_page/upload_video_page.dart | 2 +- lib/router/index.dart | 11 +- lib/service/http_config.dart | 45 ++- lib/utils/audio_player_service.dart | 15 + lib/utils/index.dart | 9 + lib/utils/notification_banner.dart | 2 +- lib/utils/voice_service.dart | 4 +- lib/utils/wxsdk.dart | 61 +++ pubspec.lock | 8 + pubspec.yaml | 4 +- 40 files changed, 1615 insertions(+), 768 deletions(-) create mode 100644 assets/animation/voice.json create mode 100644 assets/images/icon_logout.png create mode 100644 lib/components/animation.dart create mode 100644 lib/pages/my/delete.dart diff --git a/assets/animation/voice.json b/assets/animation/voice.json new file mode 100644 index 0000000..937e32f --- /dev/null +++ b/assets/animation/voice.json @@ -0,0 +1 @@ +{"v":"5.2.1","fr":29.9700012207031,"ip":0,"op":30.0000012219251,"w":600,"h":600,"nm":"コンポ 1","ddd":0,"assets":[],"layers":[{"ddd":0,"ind":1,"ty":4,"nm":"シェイプレイヤー 5","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[300,300,0],"ix":2},"a":{"a":0,"k":[0,0,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[226,-236],[226,242]],"c":false},"ix":2},"nm":"パス 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"tm","s":{"a":1,"k":[{"i":{"x":[0.52],"y":[0.96]},"o":{"x":[0.48],"y":[0.04]},"n":["0p52_0p96_0p48_0p04"],"t":0,"s":[20],"e":[30]},{"i":{"x":[0.52],"y":[0.96]},"o":{"x":[0.48],"y":[0.04]},"n":["0p52_0p96_0p48_0p04"],"t":5,"s":[30],"e":[10]},{"i":{"x":[0.52],"y":[0.96]},"o":{"x":[0.48],"y":[0.04]},"n":["0p52_0p96_0p48_0p04"],"t":11,"s":[10],"e":[40]},{"i":{"x":[0.52],"y":[0.96]},"o":{"x":[0.48],"y":[0.04]},"n":["0p52_0p96_0p48_0p04"],"t":17,"s":[40],"e":[15]},{"i":{"x":[0.52],"y":[0.96]},"o":{"x":[0.48],"y":[0.04]},"n":["0p52_0p96_0p48_0p04"],"t":25,"s":[15],"e":[20]},{"t":30.0000012219251}],"ix":1},"e":{"a":1,"k":[{"i":{"x":[0.52],"y":[0.96]},"o":{"x":[0.48],"y":[0.04]},"n":["0p52_0p96_0p48_0p04"],"t":0,"s":[80],"e":[70]},{"i":{"x":[0.52],"y":[0.96]},"o":{"x":[0.48],"y":[0.04]},"n":["0p52_0p96_0p48_0p04"],"t":5,"s":[70],"e":[90]},{"i":{"x":[0.52],"y":[0.96]},"o":{"x":[0.48],"y":[0.04]},"n":["0p52_0p96_0p48_0p04"],"t":11,"s":[90],"e":[60]},{"i":{"x":[0.52],"y":[0.96]},"o":{"x":[0.48],"y":[0.04]},"n":["0p52_0p96_0p48_0p04"],"t":17,"s":[60],"e":[85]},{"i":{"x":[0.52],"y":[0.96]},"o":{"x":[0.48],"y":[0.04]},"n":["0p52_0p96_0p48_0p04"],"t":25,"s":[85],"e":[80]},{"t":30.0000012219251}],"ix":2},"o":{"a":0,"k":0,"ix":3},"m":2,"ix":2,"nm":"パスのトリミング 1","mn":"ADBE Vector Filter - Trim","hd":false},{"ty":"st","c":{"a":0,"k":[0,0,0,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":40,"ix":5},"lc":2,"lj":2,"nm":"線 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[1,0,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"nm":"塗り 1","mn":"ADBE Vector Graphic - Fill","hd":true},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"トランスフォーム"}],"nm":"シェイプ 1","np":4,"cix":2,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":150.000006109625,"st":0,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":"シェイプレイヤー 4","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[300,300,0],"ix":2},"a":{"a":0,"k":[0,0,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[114,-236],[114,242]],"c":false},"ix":2},"nm":"パス 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"tm","s":{"a":1,"k":[{"i":{"x":[0.58],"y":[1]},"o":{"x":[0.42],"y":[0]},"n":["0p58_1_0p42_0"],"t":0,"s":[40],"e":[20]},{"i":{"x":[0.58],"y":[1]},"o":{"x":[0.42],"y":[0]},"n":["0p58_1_0p42_0"],"t":7,"s":[20],"e":[40]},{"i":{"x":[0.58],"y":[1]},"o":{"x":[0.42],"y":[0]},"n":["0p58_1_0p42_0"],"t":14,"s":[40],"e":[0]},{"i":{"x":[0.58],"y":[1]},"o":{"x":[0.42],"y":[0]},"n":["0p58_1_0p42_0"],"t":22,"s":[0],"e":[40]},{"t":30.0000012219251}],"ix":1},"e":{"a":1,"k":[{"i":{"x":[0.58],"y":[1]},"o":{"x":[0.42],"y":[0]},"n":["0p58_1_0p42_0"],"t":0,"s":[60],"e":[80]},{"i":{"x":[0.58],"y":[1]},"o":{"x":[0.42],"y":[0]},"n":["0p58_1_0p42_0"],"t":7,"s":[80],"e":[60]},{"i":{"x":[0.58],"y":[1]},"o":{"x":[0.42],"y":[0]},"n":["0p58_1_0p42_0"],"t":14,"s":[60],"e":[100]},{"i":{"x":[0.58],"y":[1]},"o":{"x":[0.42],"y":[0]},"n":["0p58_1_0p42_0"],"t":22,"s":[100],"e":[60]},{"t":30.0000012219251}],"ix":2},"o":{"a":0,"k":0,"ix":3},"m":2,"ix":2,"nm":"パスのトリミング 1","mn":"ADBE Vector Filter - Trim","hd":false},{"ty":"st","c":{"a":0,"k":[0,0,0,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":40,"ix":5},"lc":2,"lj":2,"nm":"線 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[1,0,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"nm":"塗り 1","mn":"ADBE Vector Graphic - Fill","hd":true},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"トランスフォーム"}],"nm":"シェイプ 1","np":4,"cix":2,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":150.000006109625,"st":0,"bm":0},{"ddd":0,"ind":3,"ty":4,"nm":"シェイプレイヤー 3","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[300,300,0],"ix":2},"a":{"a":0,"k":[0,0,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-2,-236],[-2,242]],"c":false},"ix":2},"nm":"パス 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"tm","s":{"a":1,"k":[{"i":{"x":[0.16],"y":[1]},"o":{"x":[0.84],"y":[0]},"n":["0p16_1_0p84_0"],"t":0,"s":[10],"e":[35]},{"i":{"x":[0.16],"y":[1]},"o":{"x":[0.84],"y":[0]},"n":["0p16_1_0p84_0"],"t":8,"s":[35],"e":[25]},{"i":{"x":[0.16],"y":[1]},"o":{"x":[0.84],"y":[0]},"n":["0p16_1_0p84_0"],"t":15,"s":[25],"e":[40]},{"i":{"x":[0.16],"y":[1]},"o":{"x":[0.84],"y":[0]},"n":["0p16_1_0p84_0"],"t":21,"s":[40],"e":[10]},{"t":30.0000012219251}],"ix":1},"e":{"a":1,"k":[{"i":{"x":[0.16],"y":[1]},"o":{"x":[0.84],"y":[0]},"n":["0p16_1_0p84_0"],"t":0,"s":[90],"e":[65]},{"i":{"x":[0.16],"y":[1]},"o":{"x":[0.84],"y":[0]},"n":["0p16_1_0p84_0"],"t":8,"s":[65],"e":[75]},{"i":{"x":[0.16],"y":[1]},"o":{"x":[0.84],"y":[0]},"n":["0p16_1_0p84_0"],"t":15,"s":[75],"e":[60]},{"i":{"x":[0.16],"y":[1]},"o":{"x":[0.84],"y":[0]},"n":["0p16_1_0p84_0"],"t":21,"s":[60],"e":[90]},{"t":30.0000012219251}],"ix":2},"o":{"a":0,"k":0,"ix":3},"m":2,"ix":2,"nm":"パスのトリミング 1","mn":"ADBE Vector Filter - Trim","hd":false},{"ty":"st","c":{"a":0,"k":[0,0,0,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":40,"ix":5},"lc":2,"lj":2,"nm":"線 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[1,0,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"nm":"塗り 1","mn":"ADBE Vector Graphic - Fill","hd":true},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"トランスフォーム"}],"nm":"シェイプ 1","np":4,"cix":2,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":150.000006109625,"st":0,"bm":0},{"ddd":0,"ind":4,"ty":4,"nm":"シェイプレイヤー 2","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[300,300,0],"ix":2},"a":{"a":0,"k":[0,0,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-114,-236],[-114,242]],"c":false},"ix":2},"nm":"パス 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"tm","s":{"a":1,"k":[{"i":{"x":[0.74],"y":[1]},"o":{"x":[0.4],"y":[0.8]},"n":["0p74_1_0p4_0p8"],"t":0,"s":[40],"e":[15]},{"i":{"x":[0.74],"y":[1]},"o":{"x":[0.4],"y":[0.8]},"n":["0p74_1_0p4_0p8"],"t":8,"s":[15],"e":[30]},{"i":{"x":[0.74],"y":[1]},"o":{"x":[0.4],"y":[0.8]},"n":["0p74_1_0p4_0p8"],"t":15,"s":[30],"e":[10]},{"i":{"x":[0.74],"y":[1]},"o":{"x":[0.4],"y":[0.8]},"n":["0p74_1_0p4_0p8"],"t":22,"s":[10],"e":[40]},{"t":30.0000012219251}],"ix":1},"e":{"a":1,"k":[{"i":{"x":[0.74],"y":[1]},"o":{"x":[0.4],"y":[0.8]},"n":["0p74_1_0p4_0p8"],"t":0,"s":[60],"e":[85]},{"i":{"x":[0.74],"y":[1]},"o":{"x":[0.4],"y":[0.8]},"n":["0p74_1_0p4_0p8"],"t":8,"s":[85],"e":[70]},{"i":{"x":[0.74],"y":[1]},"o":{"x":[0.4],"y":[0.8]},"n":["0p74_1_0p4_0p8"],"t":15,"s":[70],"e":[90]},{"i":{"x":[0.74],"y":[1]},"o":{"x":[0.4],"y":[0.8]},"n":["0p74_1_0p4_0p8"],"t":22,"s":[90],"e":[60]},{"t":30.0000012219251}],"ix":2},"o":{"a":0,"k":0,"ix":3},"m":2,"ix":2,"nm":"パスのトリミング 1","mn":"ADBE Vector Filter - Trim","hd":false},{"ty":"st","c":{"a":0,"k":[0,0,0,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":40,"ix":5},"lc":2,"lj":2,"nm":"線 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[1,0,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"nm":"塗り 1","mn":"ADBE Vector Graphic - Fill","hd":true},{"ind":4,"ty":"sh","ix":5,"ks":{"a":0,"k":{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[114,-236],[114,242]],"c":false},"ix":2},"nm":"パス 2","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"トランスフォーム"}],"nm":"シェイプ 1","np":5,"cix":2,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":150.000006109625,"st":0,"bm":0},{"ddd":0,"ind":5,"ty":4,"nm":"シェイプレイヤー 1","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[300,300,0],"ix":2},"a":{"a":0,"k":[0,0,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-226,-236],[-226,242]],"c":false},"ix":2},"nm":"パス 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"tm","s":{"a":1,"k":[{"i":{"x":[0.74],"y":[1]},"o":{"x":[0.4],"y":[0.8]},"n":["0p74_1_0p4_0p8"],"t":0,"s":[20],"e":[40]},{"i":{"x":[0.74],"y":[1]},"o":{"x":[0.4],"y":[0.8]},"n":["0p74_1_0p4_0p8"],"t":8,"s":[40],"e":[10]},{"i":{"x":[0.74],"y":[1]},"o":{"x":[0.4],"y":[0.8]},"n":["0p74_1_0p4_0p8"],"t":15,"s":[10],"e":[35]},{"i":{"x":[0.74],"y":[1]},"o":{"x":[0.4],"y":[0.8]},"n":["0p74_1_0p4_0p8"],"t":22,"s":[35],"e":[20]},{"t":30.0000012219251}],"ix":1},"e":{"a":1,"k":[{"i":{"x":[0.74],"y":[1]},"o":{"x":[0.4],"y":[0.8]},"n":["0p74_1_0p4_0p8"],"t":0,"s":[80],"e":[60]},{"i":{"x":[0.74],"y":[1]},"o":{"x":[0.4],"y":[0.8]},"n":["0p74_1_0p4_0p8"],"t":8,"s":[60],"e":[90]},{"i":{"x":[0.74],"y":[1]},"o":{"x":[0.4],"y":[0.8]},"n":["0p74_1_0p4_0p8"],"t":15,"s":[90],"e":[65]},{"i":{"x":[0.74],"y":[1]},"o":{"x":[0.4],"y":[0.8]},"n":["0p74_1_0p4_0p8"],"t":22,"s":[65],"e":[80]},{"t":30.0000012219251}],"ix":2},"o":{"a":0,"k":0,"ix":3},"m":2,"ix":2,"nm":"パスのトリミング 1","mn":"ADBE Vector Filter - Trim","hd":false},{"ty":"st","c":{"a":0,"k":[0,0,0,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":40,"ix":5},"lc":2,"lj":2,"nm":"線 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[1,0,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"nm":"塗り 1","mn":"ADBE Vector Graphic - Fill","hd":true},{"ind":4,"ty":"sh","ix":5,"ks":{"a":0,"k":{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[226,-236],[226,242]],"c":false},"ix":2},"nm":"パス 2","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"トランスフォーム"}],"nm":"シェイプ 1","np":5,"cix":2,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":150.000006109625,"st":0,"bm":0}],"markers":[]} \ No newline at end of file diff --git a/assets/images/icon_logout.png b/assets/images/icon_logout.png new file mode 100644 index 0000000000000000000000000000000000000000..e8d322de0f6c02f9e6143d393e25620f08755c11 GIT binary patch literal 2421 zcmc(hdo+~m9>)hSUW9i>+i{tAnRg~cQtp||>pCx`ZPJA;mv%cLWk!S$Z#CF5W(G}i zbdhRHNYqB7oP!$CxYWsQv&)Rq(n#!5+3AdR*4qD{wa!}SkLUS)ewXKYe*Zkb_09i2 z$X{36M4Lb$=weJCmeNXA7O0`zYxmS$P#TpamcJLFPD*}5AZXcRK4?g4grXuL<*5hc zH~uwMzx{E*Zodh7s;3wvF)vp;&N9#YCoSZ5yzhMeTg+@j#Vc8?OhpCsK;{5bknzGn z1&DTs=k%1u1I~tf34oONVikvH+85wOK!w1z^4B0NaXaj>PD=&ANR9~?DB1Aq=Jz)d z079_-rZgS_|GVsAqYz^75%lRRH&yVzIoy5}{h0YpoG`X9KHu{#O-@T3xvO}9zi{Q% zZwlT@lm}_=KUa2N+7Nl5dt(UH*a3Cqw zIQfl}4dRxYnR;0@=qmqM#@;Xm7c z{9{j|*i>b)k*SY1FGfYh5cBLn(eB1qxNL%$*w)LTpG-`WUebVHd=c8?0JDVJOakq| zBkR~t&R0~xFVf8?Gc{1YLH_*e-Ky3AyKR8e)hBI*5;Jl%(N?50=sJl3nPy30=ZN`# zf%^&|^pIFyL+muPz=u=2cT@80ynu%H!g@dGhg0A_88xyHz6%mN9|qnxK@Slv^9n^7 zPWZ5;4`gcpISy#>&HG57$*Uzw%*TCJ2pl1cL)`Cs=8j=RF;Zu%nVqb%0=~(_wD4&~@I6KaIO`GvkxfFmjm%b3aNxxqR=*RVrJ~ zg@e`Et3C%8^aM;w^iWlujgyPUq7nc-w8PE~;8_eE#OB}p!Ev=;cz%sKn1Z;8xD=Q5 z4N~6?v!T4`A;Y%bvQB|9{_GmVu#%TS1~0Du-8q#97y)A z|6pyOPpN6fpRszI#~nVIY>CClZWhc0R%ODmRS%w@zlre9Kvm9E;;)N7Fx3f^(Pim>H4e5X6_{y5txuJ$o=QUaiHJ1*ZEXTx!d z0Hv#j#u;3`J~9|wFcmD@31z|isA6OGn(|(s(TR>;>N41PHisk*<3ho4o>n)*67jtxA6uw@l`>^ z9WA738%@0VK$ld*V0!&ZtfZrbXk@93O?B}!NJcbeY-_1^3uLrXxBp>~2GMA}KMfUU zT}kY>Wv)5UxmNqSMBoQivkk)DUh2ELy;u!B#dR2EZIa}`1J{D~(?jJ9NRV@;sdp;} zW>$3@>j1b>~-C;sQgK_ z9gVr$6rUvT*E#^fmx5L9*Y09Iz7~_Su^(Wx1EBUa0LrmHNq^fOvDs#6WO01PMVl__y3Zjp_~B_!YX#XeqNvo1 zX!=)Yu7Eg|uXW0>D%|^QkF6PW&KItOhnD#78YPM0Aoy8{!*hqGgmRr|*o{Tn~=KSs;C(SIw z;fGJJ*)VlFnVz(&rSnU9R!7ku8_kEKkD%!k@8#$7_agVv`JFRu-+r`wX+;FXvobuc zgtJOd8bF}J{BiZm1KK6Jd~_K3{jr#SES^hkqGnOvjH zyC6Hi%aOszZ*K~;r7~|i*{cBE<@5>{rC88lmdL5ua9ZQ_1r1ejkdQAFD;33&k4GmM z1k?l$7K#YC{K*BWDF`#wkbrp-gD)O=nx>`##Af?o5vrij<+%G1rDW0NTXhE#(D?(~ yU+V%;l?sWPrUf%6-o6p%5O7CECGyv!pUkHVcT!9m-VG>q6an)M@~QKR!v75cXA{H# literal 0 HcmV?d00001 diff --git a/lib/IM/im_message.dart b/lib/IM/im_message.dart index 49c2cad..6747703 100644 --- a/lib/IM/im_message.dart +++ b/lib/IM/im_message.dart @@ -23,7 +23,6 @@ class IMMessage { String? groupID, String? cloudCustomData, String? groupName, - bool isPush = true, bool isExcludedFromUnreadCount = false, }) async { // 必须且只能设置一个:toUserID(单聊)或 groupID(群聊) @@ -67,7 +66,7 @@ class IMMessage { isSupportMessageExtension: false, // 支持消息扩展 isExcludedFromContentModeration: false, // 绕过内容审核 needReadReceipt: false, // 已读回执 - offlinePushInfo: isPush ? offlinePushInfo : null, + offlinePushInfo: offlinePushInfo, cloudCustomData: cloudCustomData, localCustomData: "", ); @@ -96,7 +95,7 @@ class IMMessage { isSupportMessageExtension: false, isExcludedFromContentModeration: false, needReadReceipt: false, - offlinePushInfo: isPush ? offlinePushInfo : null, + offlinePushInfo: offlinePushInfo, cloudCustomData: cloudCustomData, localCustomData: "", ); diff --git a/lib/IM/im_message_listeners.dart b/lib/IM/im_message_listeners.dart index fc0950c..0a031f9 100644 --- a/lib/IM/im_message_listeners.dart +++ b/lib/IM/im_message_listeners.dart @@ -13,6 +13,7 @@ import 'package:shirne_dialog/shirne_dialog.dart'; import 'package:tencent_cloud_chat_sdk/enum/V2TimAdvancedMsgListener.dart'; import 'package:tencent_cloud_chat_sdk/enum/message_elem_type.dart'; import 'package:tencent_cloud_chat_sdk/enum/receive_message_opt.dart'; +import 'package:tencent_cloud_chat_sdk/models/v2_tim_conversation.dart'; import 'package:tencent_cloud_chat_sdk/models/v2_tim_group_info.dart'; import 'package:tencent_cloud_chat_sdk/models/v2_tim_message.dart'; import 'package:tencent_cloud_chat_sdk/models/v2_tim_message_receipt.dart'; @@ -123,6 +124,14 @@ class ImMessageListenerService extends GetxService { // 获取失败默认也不提示; return; } + } else { + // 单聊的 + final cRes = await ImService.instance.getConversation(conversationID: 'c2c_${message.userID}'); + final V2TimConversation c2cConv = cRes.data; + if (c2cConv.recvOpt == ReceiveMsgOptType.kTIMRecvMsgOpt_Not_Notify) { + // 开启了免打扰模式, 现在只做了0=初始正常接受,3=在线接受,离线只接受@消息 + return; + } } _debounceTimer?.cancel(); // 每次都取消旧的 diff --git a/lib/api/common_api.dart b/lib/api/common_api.dart index 20b5401..eef33cd 100644 --- a/lib/api/common_api.dart +++ b/lib/api/common_api.dart @@ -1,6 +1,9 @@ class CommonApi { ///----------get static const String getCode = '/resource/sms/code'; // 发送短信验证码 {'phonenumber'} + // /app/member/sms/code + static const String getDelCode = '/app/member/sms/code'; // 发送短信验证码 {'templateId'} + static const String accountInfo = '/app/member/info'; // 账户信息 ///---------post @@ -11,11 +14,31 @@ class CommonApi { ///[source]=wechat_open [clientId]=428a8310cd442757ae699df5d894f051 [grantType]=social [socialState]=1 static const String wxLogin = '/app/member/bind/wechat'; - // 获取字典枚举 + /// 获取字典枚举 用户注销[member_revoked] static const String dictionaryApi = '/app/sys/dict/type/'; // 聚合搜索 static const String aggregationSearchApi = '/app/common/search'; + ///余额充值 [money]金额 + // { + // "orderType": "RECHARGE", + // "clientType": "APP", + // "paymentMethod": "WECHAT", + // "paymentClient": "APP", + // "money": 100 + // } + static const String addBalance = '/app/payment/pay'; + + ///余额提现 + // { + // "money": "1", + // "method": "1", + // } + static const String withdraw = '/app/member/withdraw'; + + ///注销 /app/member/revoked + static const String revoked = '/app/member/revoked'; + ///resource/oss/upload } diff --git a/lib/components/animation.dart b/lib/components/animation.dart new file mode 100644 index 0000000..e8e40a3 --- /dev/null +++ b/lib/components/animation.dart @@ -0,0 +1,41 @@ +import 'package:flutter/material.dart'; +import 'package:lottie/lottie.dart'; + +class VoiceAnimation extends StatelessWidget { + /// 是否正在播放 + final bool isPlaying; + + /// Lottie 动画资源路径 + final String animationAsset; + + /// 静态图标 + final IconData idleIcon; + + /// 动画和图标的大小 + final double size; + + const VoiceAnimation({ + super.key, + required this.isPlaying, + this.animationAsset = 'assets/animation/voice.json', + this.idleIcon = Icons.multitrack_audio, // 静态图标 + this.size = 24.0, + }); + + @override + Widget build(BuildContext context) { + if (isPlaying) { + return Lottie.asset( + animationAsset, + width: size, + height: size, + repeat: true, + ); + } else { + return Icon( + idleIcon, + size: size, + ); + } + } +} diff --git a/lib/components/my_qrcode.dart b/lib/components/my_qrcode.dart index e48cbb2..c37284e 100644 --- a/lib/components/my_qrcode.dart +++ b/lib/components/my_qrcode.dart @@ -17,9 +17,11 @@ class MyQrcode extends StatelessWidget { MyQrcode({ super.key, this.prefix, + this.text, }); final String? prefix; + final String? text; final controller = Get.find(); @@ -106,29 +108,45 @@ class MyQrcode extends StatelessWidget { child: Center( child: Container( alignment: Alignment.topCenter, - decoration: BoxDecoration( + decoration: const BoxDecoration( color: Colors.transparent, ), child: RepaintBoundary( key: _qrKey, - child: PrettyQrView.data( - errorCorrectLevel: QrErrorCorrectLevel.H, // 高容错 - data: data, // 二维码内容 - decoration: PrettyQrDecoration( - background: Colors.transparent, - shape: const PrettyQrShape.custom( - PrettyQrSmoothSymbol(color: FStyle.primaryColor), - finderPattern: PrettyQrSmoothSymbol(color: FStyle.primaryColor), - alignmentPatterns: PrettyQrSmoothSymbol(color: FStyle.primaryColor), + child: Column( + mainAxisSize: MainAxisSize.min, // 根据内容高度自适应 + children: [ + PrettyQrView.data( + errorCorrectLevel: QrErrorCorrectLevel.H, + data: data, // 二维码内容 + decoration: PrettyQrDecoration( + background: Colors.transparent, + shape: const PrettyQrShape.custom( + PrettyQrSmoothSymbol(color: FStyle.primaryColor), + finderPattern: PrettyQrSmoothSymbol(color: FStyle.primaryColor), + alignmentPatterns: PrettyQrSmoothSymbol(color: FStyle.primaryColor), + ), + image: PrettyQrDecorationImage( + image: face, + scale: 0.3, + opacity: 1, + padding: const EdgeInsets.all(8.0), + ), + quietZone: const PrettyQrQuietZone.modules(2), + ), ), - image: PrettyQrDecorationImage( - image: face, - scale: 0.3, // 默认 - opacity: 1, - padding: EdgeInsets.all(8.0), // 图片与二维码的边距 - ), - quietZone: const PrettyQrQuietZone.modules(2), - ), + if (text != null && text!.trim().isNotEmpty) ...[ + const SizedBox(height: 4), + Text( + text!, + style: const TextStyle( + fontSize: 14, + color: Colors.grey, + ), + ), + const SizedBox(height: 4), + ], + ], ), ), ), diff --git a/lib/pages/chat/chat.dart b/lib/pages/chat/chat.dart index 726420e..b0361c8 100644 --- a/lib/pages/chat/chat.dart +++ b/lib/pages/chat/chat.dart @@ -12,6 +12,7 @@ import 'package:loopin/IM/controller/chat_detail_controller.dart'; import 'package:loopin/IM/im_message.dart'; import 'package:loopin/IM/im_result.dart'; import 'package:loopin/IM/im_service.dart'; +import 'package:loopin/components/animation.dart'; import 'package:loopin/components/image_viewer.dart'; import 'package:loopin/components/network_or_asset_image.dart'; import 'package:loopin/components/preview_video.dart'; @@ -56,6 +57,8 @@ class _ChatState extends State with SingleTickerProviderStateMixin { bool hasMore = true; // 是否还有更多数据 final RxBool _throttleFlag = false.obs; // 滚动节流锁 + RxMap voicePlayingMap = {}.obs; + // 表情json List emoJson = emotionData; @@ -498,32 +501,47 @@ class _ChatState extends State with SingleTickerProviderStateMixin { maxWidth: maxWidth, // maxWidth: (item.soundElem!.duration! / 1000) / 60 * 230, ), - child: Row( - mainAxisAlignment: !(item.isSelf ?? false) ? MainAxisAlignment.start : MainAxisAlignment.end, - children: !(item.isSelf ?? false) - ? [ - const Icon(Icons.multitrack_audio), - const SizedBox( - width: 5.0, - ), - Text('$durationSeconds"'), - ] - : [ - Text('$durationSeconds"'), - const SizedBox( - width: 5.0, - ), - const Icon(Icons.multitrack_audio), - ], + child: Obx( + () => Row( + mainAxisAlignment: !(item.isSelf ?? false) ? MainAxisAlignment.start : MainAxisAlignment.end, + children: !(item.isSelf ?? false) + ? [ + // const Icon(Icons.multitrack_audio), + VoiceAnimation( + isPlaying: voicePlayingMap[item.id ?? '${item.timestamp ?? 0}'] ?? false, + ), + const SizedBox( + width: 5.0, + ), + Text('$durationSeconds"'), + ] + : [ + Text('$durationSeconds"'), + const SizedBox( + width: 5.0, + ), + // const Icon(Icons.multitrack_audio), + VoiceAnimation( + isPlaying: voicePlayingMap[item.id ?? '${item.timestamp ?? 0}'] ?? false, + ), + ], + ), ), ), - onTap: () { + onTap: () async { final locUrl = item.soundElem?.path ?? ''; final netUrl = item.soundElem?.url ?? ''; + final msgId = item.id ?? '${item.timestamp ?? 0}'; + logger.w('本地地址$locUrl'); + logger.w('网络地址$netUrl'); if (locUrl.isNotEmpty) { - AudioPlayerService().playNetwork(locUrl); + voicePlayingMap[msgId] = true; + await AudioPlayerService().playLocal(locUrl); + voicePlayingMap[msgId] = false; } else if (netUrl.isNotEmpty) { - AudioPlayerService().playLocal(netUrl); + voicePlayingMap[msgId] = true; + await AudioPlayerService().playNetwork(netUrl); + voicePlayingMap[msgId] = false; } else { MyDialog.toast('音频文件已过期', icon: Icon(Icons.warning), style: ToastStyle(backgroundColor: Colors.red.withAlpha(200))); } @@ -1907,6 +1925,7 @@ class _ChatState extends State with SingleTickerProviderStateMixin { ), onPanStart: (details) async { // 开始录音 + logger.w('开始录音'); final res = await VoiceService().startRecording(); if (res) { setState(() { @@ -1932,7 +1951,7 @@ class _ChatState extends State with SingleTickerProviderStateMixin { }); }, onPanEnd: (details) { - // print('停止录音'); + logger.w('停止录音'); setState(() { switch (voiceType) { case 1: diff --git a/lib/pages/chat/chat_group.dart b/lib/pages/chat/chat_group.dart index 78a437b..6b8070f 100644 --- a/lib/pages/chat/chat_group.dart +++ b/lib/pages/chat/chat_group.dart @@ -55,6 +55,7 @@ class _ChatGroupState extends State with SingleTickerProviderStateMix bool isLoading = false; // 是否在加载中 bool hasMore = true; // 是否还有更多数据 final RxBool _throttleFlag = false.obs; // 滚动节流锁 + RxMap voicePlayingMap = {}.obs; // 表情json List emoJson = emotionData; @@ -457,13 +458,20 @@ class _ChatGroupState extends State with SingleTickerProviderStateMix ], ), ), - onTap: () { + onTap: () async { final locUrl = item.soundElem?.path ?? ''; final netUrl = item.soundElem?.url ?? ''; + final msgId = item.id ?? '${item.timestamp ?? 0}'; + logger.w('本地地址$locUrl'); + logger.w('网络地址$netUrl'); if (locUrl.isNotEmpty) { - AudioPlayerService().playNetwork(locUrl); + voicePlayingMap[msgId] = true; + await AudioPlayerService().playLocal(locUrl); + voicePlayingMap[msgId] = false; } else if (netUrl.isNotEmpty) { - AudioPlayerService().playLocal(netUrl); + voicePlayingMap[msgId] = true; + await AudioPlayerService().playNetwork(netUrl); + voicePlayingMap[msgId] = false; } else { MyDialog.toast('音频文件已过期', icon: Icon(Icons.warning), style: ToastStyle(backgroundColor: Colors.red.withAlpha(200))); } diff --git a/lib/pages/chat/chat_no_friend.dart b/lib/pages/chat/chat_no_friend.dart index 8e64daf..5cd03df 100644 --- a/lib/pages/chat/chat_no_friend.dart +++ b/lib/pages/chat/chat_no_friend.dart @@ -53,6 +53,8 @@ class _ChatNoFriendState extends State with SingleTickerProviderSt bool hasMore = true; // 是否还有更多数据 final RxBool _throttleFlag = false.obs; // 滚动节流锁 + RxMap voicePlayingMap = {}.obs; + // 表情json List emoJson = emotionData; @@ -519,13 +521,20 @@ class _ChatNoFriendState extends State with SingleTickerProviderSt ], ), ), - onTap: () { + onTap: () async { final locUrl = item.soundElem?.path ?? ''; final netUrl = item.soundElem?.url ?? ''; + final msgId = item.id ?? '${item.timestamp ?? 0}'; + logger.w('本地地址$locUrl'); + logger.w('网络地址$netUrl'); if (locUrl.isNotEmpty) { - AudioPlayerService().playNetwork(locUrl); + voicePlayingMap[msgId] = true; + await AudioPlayerService().playLocal(locUrl); + voicePlayingMap[msgId] = false; } else if (netUrl.isNotEmpty) { - AudioPlayerService().playLocal(netUrl); + voicePlayingMap[msgId] = true; + await AudioPlayerService().playNetwork(netUrl); + voicePlayingMap[msgId] = false; } else { MyDialog.toast('音频文件已过期', icon: Icon(Icons.warning), style: ToastStyle(backgroundColor: Colors.red.withAlpha(200))); } diff --git a/lib/pages/chat/index.dart b/lib/pages/chat/index.dart index 5987e68..5d83701 100644 --- a/lib/pages/chat/index.dart +++ b/lib/pages/chat/index.dart @@ -9,8 +9,10 @@ import 'package:loopin/IM/controller/chat_controller.dart'; import 'package:loopin/IM/global_badge.dart'; import 'package:loopin/IM/im_service.dart'; import 'package:loopin/api/shop_api.dart'; +import 'package:loopin/components/empty_tip.dart'; import 'package:loopin/components/network_or_asset_image.dart'; import 'package:loopin/components/scan_util.dart'; +import 'package:loopin/models/conversation_type.dart' as myConversationType; import 'package:loopin/models/conversation_type.dart'; import 'package:loopin/models/conversation_view_model.dart'; import 'package:loopin/pages/chat/menu/add_friend.dart'; @@ -20,6 +22,8 @@ import 'package:loopin/utils/parse_message_summary.dart'; import 'package:loopin/utils/scan_code_type.dart'; // 导入外部枚举 import 'package:shirne_dialog/shirne_dialog.dart'; import 'package:tencent_cloud_chat_sdk/enum/receive_message_opt.dart'; +import 'package:tencent_cloud_chat_sdk/enum/receive_message_opt_enum.dart'; +import 'package:tencent_cloud_chat_sdk/models/v2_tim_conversation_filter.dart'; import '../../behavior/custom_scroll_behavior.dart'; import '../../styles/index.dart'; @@ -103,8 +107,8 @@ class ChatPageState extends State { Get.toNamed('/vloger', arguments: {'memberId': value}); } - // 检测当前用户是否关注博主 - checkFollowType(memberId) async { + // 检测当前用户是否关注博主 + checkFollowType(memberId) async { /// 0:不是好友也没有关注 /// 1:你关注了对方(单向) /// 2:对方关注了你(单向) @@ -118,22 +122,21 @@ class ChatPageState extends State { } } - // 处理推广码,先调用IM互相关注逻辑,成功后在调用绑定关系接口 void _handlePromotionCode(String value) async { await checkFollowType(value); // 检查当前用户是否关注了团长 - if(followed.value == 0 || followed.value == 2){ - final res = await ImService.instance.followUser(userIDList: [value]); - if (res.success) { - followed.value = followed.value == 0 ? 1 : 3; - final res = await Http.post(ShopApi.bindSpreadCodeId, data: {"id": value}); - if (res != null && res['code'] == 200) { - MyDialog.toast('推广码绑定成功', icon: const Icon(Icons.check_circle), style: ToastStyle(backgroundColor: Colors.green.withAlpha(200))); - Get.toNamed('/vloger', arguments: {'memberId': value}); - } + if (followed.value == 0 || followed.value == 2) { + final res = await ImService.instance.followUser(userIDList: [value]); + if (res.success) { + followed.value = followed.value == 0 ? 1 : 3; + final res = await Http.post(ShopApi.bindSpreadCodeId, data: {"id": value}); + if (res != null && res['code'] == 200) { + MyDialog.toast('推广码绑定成功', icon: const Icon(Icons.check_circle), style: ToastStyle(backgroundColor: Colors.green.withAlpha(200))); + Get.toNamed('/vloger', arguments: {'memberId': value}); } - }else{ - Get.toNamed('/vloger', arguments: {'memberId': value}); + } + } else { + Get.toNamed('/vloger', arguments: {'memberId': value}); } } @@ -312,6 +315,19 @@ class ChatPageState extends State { ], ), ), + PopupMenuItem( + value: 'cs', + child: Row( + children: [ + Icon(Icons.qr_code_scanner, color: Colors.white, size: 18), + SizedBox(width: 8), + Text( + '测试', + style: TextStyle(color: Colors.white), + ), + ], + ), + ), ], ); @@ -329,6 +345,10 @@ class ChatPageState extends State { logger.w('点击了扫一扫'); ScanUtil.openScanner(onResult: handleScanResult); break; + case 'cs': + logger.w('去互动'); + Get.toNamed('/newFoucs'); + break; } } }, @@ -424,7 +444,7 @@ class ChatPageState extends State { child: Obx( () { final chatList = controller.chatList; - + if (chatList.isEmpty) return EmptyTip(); return ListView.builder( shrinkWrap: true, physics: BouncingScrollPhysics(), @@ -434,7 +454,8 @@ class ChatPageState extends State { // logger.w(chatList[index].isCustomAdmin); // logger.w(chatList[index].conversation.recvOpt); final item = chatList[index]; - final bool quiet = [ReceiveMsgOptType.kTIMRecvMsgOpt_Not_Notify_Except_At].contains(chatList[index].conversation.recvOpt ?? 0) + final bool quiet = [ReceiveMsgOptType.kTIMRecvMsgOpt_Not_Notify_Except_At, ReceiveMsgOptType.kTIMRecvMsgOpt_Not_Notify] + .contains(chatList[index].conversation.recvOpt ?? 0) ? true : false; // 是否设置了免打扰 final isNoFriend = chatList[index].conversation.conversationGroupList?.contains(ConversationType.noFriend.name) ?? false; @@ -448,8 +469,22 @@ class ChatPageState extends State { motion: const DrawerMotion(), // 可以改成 StretchMotion 或 ScrollMotion ,DrawerMotion children: [ SlidableAction( - onPressed: (_) { - // + onPressed: (_) async { + logger.w(item.conversation.toLogString()); + if (isNoFriend) { + // 单独处理陌生人聊天 这里只能处理一条, + // 获取所有陌生人会话 + final noFriendData = await ImService.instance.getConversationListByFilter( + filter: V2TimConversationFilter(conversationGroup: myConversationType.ConversationType.noFriend.name), + nextSeq: 0, + count: 1, + ); + await ImService.instance.clearConversationUnreadCount( + conversationID: 'c2c_${item.conversation.userID}', cleanTimestamp: item.conversation.lastMessage!.timestamp ?? 0); + } else { + // 清除未读 + await ImService.instance.clearConversationUnreadCount(conversationID: item.conversation.conversationID); + } }, backgroundColor: Colors.blue, foregroundColor: Colors.white, @@ -458,8 +493,30 @@ class ChatPageState extends State { ), if (!(isAdmin || isNoFriend)) SlidableAction( - onPressed: (_) { - // + onPressed: (_) async { + // 设置或取消免打扰 + // logger.e(controller.info.value?.recvOpt); + // 群聊开启免打扰 = 3 单聊开启免打扰=2,关闭免打扰为0 + if (item.conversation.type == 1) { + logger.w('当前单聊recvopt=${item.conversation.recvOpt}'); + // 单聊 + await ImService.instance.setC2CReceiveMessageOpt( + userIDList: ['${item.conversation.userID}'], + opt: item.conversation.recvOpt == 2 + ? ReceiveMsgOptEnum.V2TIM_RECEIVE_MESSAGE // 关闭免打扰 = 0 + : ReceiveMsgOptEnum.V2TIM_RECEIVE_NOT_NOTIFY_MESSAGE); // 3=只接收at消息,2=正常接收不接受离线 + } else if (item.conversation.type == 2) { + //群 + logger.w('当前群聊recvopt=${item.conversation.recvOpt}'); + await ImService.instance.setGroupReceiveMessageOpt( + groupID: item.conversation.groupID ?? '', + opt: item.conversation.recvOpt == 3 + ? ReceiveMsgOptEnum.V2TIM_RECEIVE_MESSAGE + : ReceiveMsgOptEnum.V2TIM_RECEIVE_NOT_NOTIFY_MESSAGE_EXCEPT_AT, + ); + } + // 初始化一次未读总数 + Get.find().initUnreadCount(); }, backgroundColor: FStyle.primaryColor, foregroundColor: Colors.white, @@ -568,7 +625,7 @@ class ChatPageState extends State { ), onTap: () { if (conversationTypeFromString(chatList[index].isCustomAdmin) != null) { - // 跳转对应的通知消息页 + // 跳转对应的通知消息页 notify下的内容 logger.e(chatList[index].isCustomAdmin); Get.toNamed('/${chatList[index].isCustomAdmin}', arguments: chatList[index].conversation); } else if (chatList[index].conversation.conversationGroupList!.contains(ConversationType.noFriend.name)) { diff --git a/lib/pages/chat/notify/interaction.dart b/lib/pages/chat/notify/interaction.dart index 5db6f74..23b48aa 100644 --- a/lib/pages/chat/notify/interaction.dart +++ b/lib/pages/chat/notify/interaction.dart @@ -3,10 +3,12 @@ library; import 'dart:convert'; +import 'package:easy_refresh/easy_refresh.dart'; import 'package:flutter/material.dart'; import 'package:get/get.dart'; import 'package:loopin/IM/im_service.dart'; import 'package:loopin/behavior/custom_scroll_behavior.dart'; +import 'package:loopin/components/empty_tip.dart'; import 'package:loopin/components/network_or_asset_image.dart'; import 'package:loopin/models/conversation_type.dart'; import 'package:loopin/models/notify_message.type.dart'; @@ -29,9 +31,9 @@ class Interaction extends StatefulWidget { class InteractionState extends State with SingleTickerProviderStateMixin { bool isLoading = false; // 是否在加载中 - bool hasMore = true; // 是否还有更多数据 - final RxBool _throttleFlag = false.obs; // 滚动节流锁 - final ScrollController chatController = ScrollController(); + RxBool hasMore = true.obs; // 是否还有更多数据 + // final RxBool _throttleFlag = false.obs; // 滚动节流锁 + // final ScrollController chatController = ScrollController(); String page = ''; ///------------------- @@ -58,24 +60,18 @@ class InteractionState extends State with SingleTickerProviderState msgList.add(lastmsg); } } - chatController.addListener(() { - if (_throttleFlag.value) return; - if (chatController.position.pixels >= chatController.position.maxScrollExtent - 50) { - _throttleFlag.value = true; - getMsgData().then((_) { - // 解锁 - Future.delayed(Duration(milliseconds: 1000), () { - _throttleFlag.value = false; - }); - }); - } - }); - } - - @override - void dispose() { - super.dispose(); - chatController.dispose(); + // chatController.addListener(() { + // if (_throttleFlag.value) return; + // if (chatController.position.pixels >= chatController.position.maxScrollExtent - 50) { + // _throttleFlag.value = true; + // getMsgData().then((_) { + // // 解锁 + // Future.delayed(Duration(milliseconds: 1000), () { + // _throttleFlag.value = false; + // }); + // }); + // } + // }); } // 分页获取全部数据 @@ -90,7 +86,7 @@ class InteractionState extends State with SingleTickerProviderState if (res.success && res.data != null) { msgList.addAll(res.data!); if (res.data!.isEmpty) { - hasMore = false; + hasMore.value = false; } logger.i('聊天数据加载成功'); } else { @@ -230,135 +226,169 @@ class InteractionState extends State with SingleTickerProviderState child: Column( children: [ Expanded( - child: RefreshIndicator( - backgroundColor: Colors.white, - color: Color(0xFFFF5000), - displacement: 10.0, - onRefresh: handleRefresh, - child: Obx(() { - return ListView.builder( - controller: chatController, - shrinkWrap: true, - physics: BouncingScrollPhysics(), - // itemCount: msgList.length, - itemCount: demoList.length, - itemBuilder: (context, index) { - //检测cloudCustomData - // interactionComment, //互动->评论 - // interactionAt, //互动->视频评论中的@ - // interactionLike, //互动->点赞 - // interactionReply, //互动->评论回复 + child: EasyRefresh.builder( + callLoadOverOffset: 20, //触底距离 + callRefreshOverOffset: 20, // 下拉距离 + header: ClassicHeader( + dragText: '下拉刷新', + armedText: '释放刷新', + readyText: '加载中...', + processingText: '加载中...', + processedText: '加载完成', + failedText: '加载失败,请重试', + messageText: '最后更新于 %T', + ), + footer: ClassicFooter( + dragText: '加载更多', + armedText: '释放加载', + readyText: '加载中...', + processingText: '加载中...', + processedText: '加载完成', + noMoreText: '没有更多了~', + failedText: '加载失败,请重试', + messageText: '最后更新于 %T', + ), + onRefresh: () async { + await handleRefresh(); + }, + onLoad: () async { + if (hasMore.value) { + await getMsgData(); + return hasMore.value ? IndicatorResult.success : IndicatorResult.noMore; + } + }, + childBuilder: (context, physics) { + return Obx( + () { + // if (msgList.isEmpty) return EmptyTip(); + if (demoList.isEmpty) return EmptyTip(); + return ListView.builder( + // controller: chatController, + shrinkWrap: true, + physics: physics, + // itemCount: msgList.length, + itemCount: demoList.length, + itemBuilder: (context, index) { + //检测cloudCustomData + // interactionComment, //互动->评论 评论 + // interactionAt, //互动->视频评论中的@ 评论 + // interactionLike, //互动->点赞 视频(暂时不涉及评论点赞) + // interactionReply, //互动->评论回复 评论 - //---- - // final element =msgList[index].customElem!; - // final cloudCustomData = msgList[index].cloudCustomData; - // final desc = msgList[index].customElem!.desc!; - // final jsonData = msgList[index].customElem!.data; + //---- + // final element =msgList[index].customElem!; + // final cloudCustomData = msgList[index].cloudCustomData; + // final desc = msgList[index].customElem!.desc!; + // final jsonData = msgList[index].customElem!.data; - // ----测试数据 - final jsonData = '{"faceUrl":"","nickName":"测试昵称","userID":"213213"}'; - final item = jsonDecode(jsonData); // 数据 - final desc = '测试desc'; - final cloudCustomData = 'interactionLike'; - V2TimMessage element = V2TimMessage(elemType: 2, isRead: index > 2 ? true : false); - return Container( - width: double.infinity, - padding: const EdgeInsets.symmetric(horizontal: 15.0, vertical: 10.0), - child: Row( - spacing: 10.0, - children: [ - // 头像 - InkWell( - onTap: () async { - // 点击头像转到对方主页 - // 先获取视频详情 - // 如果cloudCustomData是interactionComment,interactionAt,interactionReply,传参时带上评论id, - // - // final res = await Http.get('${VideoApi.detail}/${item['vlogID']}'); - // Get.toNamed('/vloger', arguments: res['data']); - Get.toNamed('/vloger'); - }, - child: ClipOval( - child: NetworkOrAssetImage( - imageUrl: item['faceUrl'], - width: 50, - height: 50, - ), - ), - ), - - // 消息 - Expanded( - child: InkWell( - onTap: () { + // ----测试数据 + final jsonData = '{"faceUrl":"","nickName":"测试昵称","userID":"213213"}'; + final item = jsonDecode(jsonData); // 数据 + final desc = '测试desc'; + final cloudCustomData = 'interactionLike'; + V2TimMessage element = V2TimMessage(elemType: 2, isRead: index > 2 ? true : false); + return Container( + width: double.infinity, + padding: const EdgeInsets.symmetric(horizontal: 15.0, vertical: 10.0), + child: Row( + spacing: 10.0, + children: [ + // 头像 + InkWell( + onTap: () async { // 点击头像转到对方主页 // 先获取视频详情 - // 如果cloudCustomData是interactionComment,interactionAt,interactionReply,传参时带上评论id, + // 如果cloudCustomData是interactionComment,interactionAt,interactionReply,传参时带上评论id,这三个是评论相关的 // // final res = await Http.get('${VideoApi.detail}/${item['vlogID']}'); + // 此人存在才做跳转 // Get.toNamed('/vloger', arguments: res['data']); Get.toNamed('/vloger'); }, - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - // 昵称 - Text( - item['nickName'] ?? '未知', - style: TextStyle( - fontSize: 16, - fontWeight: FontWeight.normal, - ), - ), - const SizedBox(height: 2.0), - // 描述内容 - Text( - maxLines: 2, - overflow: TextOverflow.ellipsis, - style: const TextStyle(color: Colors.grey, fontSize: 12.0), - // desc, - '很长文本内容很长文本内容很长文本内容很长文本内容很长文本内容很长文本内容很长文本内容很长文本内容很长文本内容很长文本内容很长文本内容很长文本内容很长文本内容很长文本内容很长文本内容很长文本内容很长文本内容很长文本内容', - ), - Text( - Utils.formatTime(element.timestamp ?? DateTime.now().millisecondsSinceEpoch ~/ 1000), - style: TextStyle( - color: Colors.grey[600], - fontSize: 12, - ), - ), - ], - ), - ), - ), - - // 右侧 - Row( - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - Visibility( - visible: true, - // 视频首图 + child: ClipOval( child: NetworkOrAssetImage( - imageUrl: item['firstFrameImg'], - placeholderAsset: 'assets/images/bk.jpg', - width: 40, - height: 60, + imageUrl: item['faceUrl'], + width: 50, + height: 50, ), ), - const SizedBox(width: 5.0), - // 角标 - Visibility( - visible: !(element.isRead ?? true), - child: FStyle.badge(0, isdot: true), + ), + + // 消息 + Expanded( + child: InkWell( + onTap: () { + // 点击头像转到对方主页 + // 先获取视频详情 + // 如果cloudCustomData是interactionComment,interactionAt,interactionReply,传参时带上评论id, + // + // final res = await Http.get('${VideoApi.detail}/${item['vlogID']}'); + // Get.toNamed('/vloger', arguments: res['data']); + Get.toNamed('/vloger'); + }, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + // 昵称 + Text( + item['nickName'] ?? '未知', + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.normal, + ), + ), + const SizedBox(height: 2.0), + // 描述内容 + Text( + maxLines: 2, + overflow: TextOverflow.ellipsis, + style: const TextStyle(color: Colors.grey, fontSize: 12.0), + // desc, + '很长文本内容很长文本内容很长文本内容很长文本内容很长文本内容很长文本内容很长文本内容很长文本内容很长文本内容很长文本内容很长文本内容很长文本内容很长文本内容很长文本内容很长文本内容很长文本内容很长文本内容很长文本内容', + ), + Text( + Utils.formatTime(element.timestamp ?? DateTime.now().millisecondsSinceEpoch ~/ 1000), + style: TextStyle( + color: Colors.grey[600], + fontSize: 12, + ), + ), + ], + ), ), - ], - ), - ], - ), - ); - }, - ); - })), + ), + + // 右侧 + Row( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Visibility( + visible: true, + // 视频首图 + child: NetworkOrAssetImage( + imageUrl: item['firstFrameImg'], + placeholderAsset: 'assets/images/bk.jpg', + width: 40, + height: 60, + ), + ), + const SizedBox(width: 5.0), + // 角标 + Visibility( + visible: !(element.isRead ?? true), + child: FStyle.badge(0, isdot: true), + ), + ], + ), + ], + ), + ); + }, + ); + }, + ); + }, + ), ), ], ), diff --git a/lib/pages/chat/notify/newFoucs.dart b/lib/pages/chat/notify/newFoucs.dart index ce13bb6..956db5d 100644 --- a/lib/pages/chat/notify/newFoucs.dart +++ b/lib/pages/chat/notify/newFoucs.dart @@ -3,11 +3,13 @@ library; import 'dart:convert'; +import 'package:easy_refresh/easy_refresh.dart'; import 'package:flutter/material.dart'; import 'package:get/get.dart'; import 'package:loopin/IM/im_service.dart'; import 'package:loopin/api/video_api.dart'; import 'package:loopin/behavior/custom_scroll_behavior.dart'; +import 'package:loopin/components/empty_tip.dart'; import 'package:loopin/components/network_or_asset_image.dart'; import 'package:loopin/models/conversation_type.dart'; import 'package:loopin/service/http.dart'; @@ -17,10 +19,7 @@ import 'package:tencent_cloud_chat_sdk/models/v2_tim_conversation.dart'; import 'package:tencent_cloud_chat_sdk/models/v2_tim_custom_elem.dart'; import 'package:tencent_cloud_chat_sdk/models/v2_tim_message.dart'; -// interactionComment, //互动->评论 -// interactionAt, //互动->视频评论中的@ -// interactionLike, //互动->点赞 -// interactionReply, //互动->评论回复 +// newFocus, 新的关注 class Newfoucs extends StatefulWidget { const Newfoucs({super.key}); @@ -31,9 +30,7 @@ class Newfoucs extends StatefulWidget { class NewfoucsState extends State with SingleTickerProviderStateMixin { bool isLoading = false; // 是否在加载中 - bool hasMore = true; // 是否还有更多数据 - final RxBool _throttleFlag = false.obs; // 滚动节流锁 - final ScrollController chatController = ScrollController(); + RxBool hasMore = true.obs; // 是否还有更多数据 String page = ''; ///------------------- @@ -53,24 +50,6 @@ class NewfoucsState extends State with SingleTickerProviderStateMixin msgList.add(lastmsg); } } - chatController.addListener(() { - if (_throttleFlag.value) return; - if (chatController.position.pixels >= chatController.position.maxScrollExtent - 50) { - _throttleFlag.value = true; - getMsgData().then((_) { - // 解锁 - Future.delayed(Duration(milliseconds: 1000), () { - _throttleFlag.value = false; - }); - }); - } - }); - } - - @override - void dispose() { - super.dispose(); - chatController.dispose(); } // 分页获取全部数据 @@ -86,7 +65,7 @@ class NewfoucsState extends State with SingleTickerProviderStateMixin msgList.addAll(res.data!); logger.e(msgList); if (res.data!.isEmpty) { - hasMore = false; + hasMore.value = false; } logger.i('聊天数据加载成功'); } else { @@ -138,69 +117,80 @@ class NewfoucsState extends State with SingleTickerProviderStateMixin child: Column( children: [ Expanded( - child: RefreshIndicator( - backgroundColor: Colors.white, - color: Color(0xFFFF5000), - displacement: 10.0, - onRefresh: handleRefresh, - child: Obx(() { - return ListView.builder( - controller: chatController, - shrinkWrap: true, - physics: BouncingScrollPhysics(), - itemCount: msgList.length, - itemBuilder: (context, index) { - //检测cloudCustomData + child: EasyRefresh.builder( + callLoadOverOffset: 20, //触底距离 + callRefreshOverOffset: 20, // 下拉距离 + header: ClassicHeader( + dragText: '下拉刷新', + armedText: '释放刷新', + readyText: '加载中...', + processingText: '加载中...', + processedText: '加载完成', + failedText: '加载失败,请重试', + messageText: '最后更新于 %T', + ), + footer: ClassicFooter( + dragText: '加载更多', + armedText: '释放加载', + readyText: '加载中...', + processingText: '加载中...', + processedText: '加载完成', + noMoreText: '没有更多了~', + failedText: '加载失败,请重试', + messageText: '最后更新于 %T', + ), + onRefresh: () async { + await handleRefresh(); + }, + onLoad: () async { + if (hasMore.value) { + await getMsgData(); + return hasMore.value ? IndicatorResult.success : IndicatorResult.noMore; + } + }, + childBuilder: (context, physics) { + return Obx( + () { + if (msgList.isEmpty) return EmptyTip(); - //----正式数据 - V2TimMessage msg = msgList[index]; - V2TimCustomElem element = msgList[index].customElem!; - final cloudCustomData = msgList[index].cloudCustomData; - logger.w(cloudCustomData); - final desc = msgList[index].customElem!.desc!; - String? jsonData = msgList[index].customElem!.data; - jsonData = (jsonData == null || jsonData.isEmpty) ? '{"faceUrl":"","nickName":"data为空","userID":"213213"}' : jsonData; + return ListView.builder( + shrinkWrap: true, + physics: physics, + itemCount: msgList.length, + itemBuilder: (context, index) { + //检测cloudCustomData + ///发起关注人的[userID], + ///发起关注人的昵称[nickName], + ///发起关注人的头像地址[faceUrl], - final item = jsonDecode(jsonData ?? '{"faceUrl":"","nickName":"测试昵称","userID":"213213"}'); + //----正式数据 + V2TimMessage msg = msgList[index]; + V2TimCustomElem element = msgList[index].customElem!; + final cloudCustomData = msgList[index].cloudCustomData; + logger.w(cloudCustomData); + final desc = msgList[index].customElem!.desc!; + String? jsonData = msgList[index].customElem!.data; + jsonData = (jsonData == null || jsonData.isEmpty) ? '{"faceUrl":"","nickName":"data为空","userID":"213213"}' : jsonData; - logger.w(element.toJson()); + final item = jsonDecode(jsonData ?? '{"faceUrl":"","nickName":"测试昵称","userID":"213213"}'); - // ----测试数据 - // final jsonData = '{"faceUrl":"","nickName":"测试昵称","userID":"213213"}'; - // final item = jsonDecode(jsonData); // 数据 - // final desc = '测试desc'; - // final cloudCustomData = 'interactionLike'; - // V2TimMessage element = V2TimMessage(elemType: 2, isRead: index > 2 ? true : false); - // ----------- - return Container( - width: double.infinity, - padding: const EdgeInsets.symmetric(horizontal: 15.0, vertical: 10.0), - child: Row( - spacing: 10.0, - children: [ - // 头像 - InkWell( - onTap: () async { - // 点击头像转到对方主页 - // 先获取视频详情 - // 如果cloudCustomData是interactionComment,interactionAt,interactionReply,传参时带上评论id, - // - final res = await Http.get('${VideoApi.detail}/${item['vlogID']}'); - Get.toNamed('/vloger', arguments: res['data']); - // Get.toNamed('/vloger'); - }, - child: ClipOval( - child: NetworkOrAssetImage( - imageUrl: item['faceUrl'], - width: 50, - height: 50, - ), - ), - ), + logger.w(element.toJson()); - // 消息 - Expanded( - child: InkWell( + // ----测试数据 + // final jsonData = '{"faceUrl":"","nickName":"测试昵称","userID":"213213"}'; + // final item = jsonDecode(jsonData); // 数据 + // final desc = '测试desc'; + // final cloudCustomData = 'newFocus'; + // V2TimCustomElem msg = V2TimMessage(elemType: 2, isRead: index > 2 ? true : false); + // ----------- + return Container( + width: double.infinity, + padding: const EdgeInsets.symmetric(horizontal: 15.0, vertical: 10.0), + child: Row( + spacing: 10.0, + children: [ + // 头像 + InkWell( onTap: () async { // 点击头像转到对方主页 // 先获取视频详情 @@ -210,66 +200,87 @@ class NewfoucsState extends State with SingleTickerProviderStateMixin Get.toNamed('/vloger', arguments: res['data']); // Get.toNamed('/vloger'); }, - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - // 昵称 - Text( - item['nickName'] ?? '未知', - style: TextStyle( - fontSize: 16, - fontWeight: FontWeight.normal, - ), - ), - const SizedBox(height: 2.0), - // 描述内容 - Text( - maxLines: 2, - overflow: TextOverflow.ellipsis, - style: const TextStyle(color: Colors.grey, fontSize: 12.0), - desc, - // '很长文本内容很长文本内容很长文本内容很长文本内容很长文本内容很长文本内容很长文本内容很长文本内容很长文本内容很长文本内容很长文本内容很长文本内容很长文本内容很长文本内容很长文本内容很长文本内容很长文本内容很长文本内容', - ), - Text( - Utils.formatTime(msg.timestamp ?? DateTime.now().millisecondsSinceEpoch ~/ 1000), - style: TextStyle( - color: Colors.grey[600], - fontSize: 12, - ), - ), - ], - ), - ), - ), - - // 右侧 - Row( - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - Visibility( - visible: true, - // 视频首图 + child: ClipOval( child: NetworkOrAssetImage( - imageUrl: item['firstFrameImg'], - placeholderAsset: 'assets/images/bk.jpg', - width: 40, - height: 60, + imageUrl: item['faceUrl'], + width: 50, + height: 50, ), ), - const SizedBox(width: 5.0), - // 角标 - Visibility( - visible: !(msg.isRead ?? true), - child: FStyle.badge(0, isdot: true), + ), + + // 消息 + Expanded( + child: InkWell( + onTap: () async { + // 点击头像转到对方主页,先获取视频详情 + final res = await Http.get('${VideoApi.detail}/${item['vlogID']}'); + Get.toNamed('/vloger', arguments: res['data']); + // Get.toNamed('/vloger'); + }, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + // 昵称 + Text( + item['nickName'] ?? '未知', + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.normal, + ), + ), + const SizedBox(height: 2.0), + // 描述内容 + Text( + maxLines: 2, + overflow: TextOverflow.ellipsis, + style: const TextStyle(color: Colors.grey, fontSize: 12.0), + desc, + // '很长文本内容很长文本内容很长文本内容很长文本内容很长文本内容很长文本内容很长文本内容很长文本内容很长文本内容很长文本内容很长文本内容很长文本内容很长文本内容很长文本内容很长文本内容很长文本内容很长文本内容很长文本内容', + ), + Text( + Utils.formatTime(msg.timestamp ?? DateTime.now().millisecondsSinceEpoch ~/ 1000), + style: TextStyle( + color: Colors.grey[600], + fontSize: 12, + ), + ), + ], + ), ), - ], - ), - ], - ), - ); - }, - ); - })), + ), + + // 右侧 + Row( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Visibility( + visible: false, // 新的关注不需要 + // 视频首图 + child: NetworkOrAssetImage( + imageUrl: item['firstFrameImg'], + placeholderAsset: 'assets/images/bk.jpg', + width: 40, + height: 60, + ), + ), + const SizedBox(width: 5.0), + // 角标 + Visibility( + visible: !(msg.isRead ?? true), + child: FStyle.badge(0, isdot: true), + ), + ], + ), + ], + ), + ); + }, + ); + }, + ); + }, + ), ), ], ), diff --git a/lib/pages/chat/notify/order.dart b/lib/pages/chat/notify/order.dart index 439a906..956db5d 100644 --- a/lib/pages/chat/notify/order.dart +++ b/lib/pages/chat/notify/order.dart @@ -1,111 +1,81 @@ -/// 订单通知列表 +/// 新关注通知 library; +import 'dart:convert'; + import 'package:easy_refresh/easy_refresh.dart'; import 'package:flutter/material.dart'; import 'package:get/get.dart'; -import 'package:loopin/IM/controller/chat_controller.dart'; import 'package:loopin/IM/im_service.dart'; +import 'package:loopin/api/video_api.dart'; import 'package:loopin/behavior/custom_scroll_behavior.dart'; +import 'package:loopin/components/empty_tip.dart'; import 'package:loopin/components/network_or_asset_image.dart'; +import 'package:loopin/models/conversation_type.dart'; +import 'package:loopin/service/http.dart'; import 'package:loopin/styles/index.dart'; import 'package:loopin/utils/index.dart'; import 'package:tencent_cloud_chat_sdk/models/v2_tim_conversation.dart'; -import 'package:tencent_cloud_chat_sdk/models/v2_tim_follow_type_check_result.dart'; -import 'package:tencent_cloud_chat_sdk/models/v2_tim_user_full_info.dart'; +import 'package:tencent_cloud_chat_sdk/models/v2_tim_custom_elem.dart'; +import 'package:tencent_cloud_chat_sdk/models/v2_tim_message.dart'; -class UserWithFollow { - final V2TimUserFullInfo userInfo; - int followType; +// newFocus, 新的关注 - UserWithFollow({ - required this.userInfo, - this.followType = 0, - }); -} - -class Order extends StatefulWidget { - const Order({super.key}); +class Newfoucs extends StatefulWidget { + const Newfoucs({super.key}); @override - State createState() => OrderState(); + State createState() => NewfoucsState(); } -class OrderState extends State with SingleTickerProviderStateMixin { +class NewfoucsState extends State with SingleTickerProviderStateMixin { bool isLoading = false; // 是否在加载中 - bool hasMore = true; // 是否还有更多数据 + RxBool hasMore = true.obs; // 是否还有更多数据 String page = ''; - List dataList = []; ///------------------- + V2TimConversation? conv; + RxList msgList = [].obs; @override void initState() { super.initState(); - getData(); + if (Get.arguments != null && Get.arguments is V2TimConversation) { + // 如果有参数 + conv = Get.arguments as V2TimConversation; + logger.e('lastmsg:$conv'); + + final lastmsg = conv?.lastMessage; + if (lastmsg != null) { + msgList.add(lastmsg); + } + } } - // 分页获取陌关注列表数据 - Future getData() async { - /// 0:不是好友也没有关注 - /// 1:你关注了对方(单向) - /// 2:对方关注了你(单向) - /// 3:互相关注(双向好友) - final res = await ImService.instance.getMyFollowingList( - nextCursor: page, + // 分页获取全部数据 + Future getMsgData() async { + // 获取最旧一条消息作为游标 + V2TimMessage? lastRealMsg; + lastRealMsg = msgList.last; + final res = await ImService.instance.getHistoryMessageList( + userID: ConversationType.newFocus.name, // userID为固定的newFocus + lastMsg: lastRealMsg, ); if (res.success && res.data != null) { - logger.i('获取成功:${res.data!.nextCursor}'); - final userInfoList = res.data!.userFullInfoList ?? []; - // 构建数据 - List wrappedList = userInfoList.map((u) { - return UserWithFollow(userInfo: u); - }).toList(); - // 获取id - final userIDList = userInfoList.map((item) => item.userID).whereType().toList(); - if (userIDList.isNotEmpty) { - final shiRes = await ImService.instance.checkFollowType(userIDList: userIDList); - if (shiRes.success && shiRes.data != null) { - final shipResData = shiRes.data!; - for (final uwf in wrappedList) { - final userID = uwf.userInfo.userID; - if (userID != null) { - // 查找对应关系 - final match = shipResData.firstWhere( - (e) => e.userID == userID, - orElse: () => V2TimFollowTypeCheckResult(userID: ''), - ); - if (match.userID?.isNotEmpty ?? false) { - uwf.followType = match.followType ?? 0; - } - } - } - } + msgList.addAll(res.data!); + logger.e(msgList); + if (res.data!.isEmpty) { + hasMore.value = false; } - final isFinished = res.data!.nextCursor == null || res.data!.nextCursor!.isEmpty; - if (isFinished) { - setState(() { - hasMore = false; - }); - // 加载没数据了 - page = ''; - } else { - page = res.data!.nextCursor ?? ''; - } - logger.i('获取数据成功:$userInfoList'); - setState(() { - dataList.addAll(wrappedList); - }); + logger.i('聊天数据加载成功'); } else { - logger.e('获取数据失败:${res.desc}'); + logger.e('聊天数据加载失败:${res.desc}'); } } // 下拉刷新 Future handleRefresh() async { - dataList.clear(); - page = ''; - getData(); + await Future.delayed(Duration(seconds: 5)); setState(() {}); } @@ -117,15 +87,28 @@ class OrderState extends State with SingleTickerProviderStateMixin { centerTitle: true, forceMaterialTransparency: true, bottom: PreferredSize( - preferredSize: const Size.fromHeight(1.0), + preferredSize: Size.fromHeight(1.0), child: Container( color: Colors.grey[300], height: 1.0, ), ), - title: const Text( - '关注', - style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold), + title: Container( + padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + const SizedBox(width: 4), + Text( + '新的关注', + style: TextStyle( + color: Colors.black, + fontSize: 16, + fontWeight: FontWeight.bold, + ), + ), + ], + ), ), actions: [], ), @@ -151,7 +134,8 @@ class OrderState extends State with SingleTickerProviderStateMixin { armedText: '释放加载', readyText: '加载中...', processingText: '加载中...', - processedText: hasMore ? '加载完成' : '没有更多了~', + processedText: '加载完成', + noMoreText: '没有更多了~', failedText: '加载失败,请重试', messageText: '最后更新于 %T', ), @@ -159,137 +143,139 @@ class OrderState extends State with SingleTickerProviderStateMixin { await handleRefresh(); }, onLoad: () async { - if (hasMore) { - await getData(); + if (hasMore.value) { + await getMsgData(); + return hasMore.value ? IndicatorResult.success : IndicatorResult.noMore; } }, childBuilder: (context, physics) { - return ListView.builder( - physics: physics, - itemCount: dataList.length, - itemBuilder: (context, index) { - final item = dataList[index]; - return Ink( - key: ValueKey(item.userInfo.userID), - child: Container( - padding: const EdgeInsets.symmetric(horizontal: 15.0, vertical: 10.0), - child: Row( - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - // 左侧部分(头像 + 昵称 + 描述) - Expanded( - child: InkWell( - onTap: () { - Get.toNamed( - '/vloger', - arguments: item.userInfo.userID, - ); + return Obx( + () { + if (msgList.isEmpty) return EmptyTip(); + + return ListView.builder( + shrinkWrap: true, + physics: physics, + itemCount: msgList.length, + itemBuilder: (context, index) { + //检测cloudCustomData + ///发起关注人的[userID], + ///发起关注人的昵称[nickName], + ///发起关注人的头像地址[faceUrl], + + //----正式数据 + V2TimMessage msg = msgList[index]; + V2TimCustomElem element = msgList[index].customElem!; + final cloudCustomData = msgList[index].cloudCustomData; + logger.w(cloudCustomData); + final desc = msgList[index].customElem!.desc!; + String? jsonData = msgList[index].customElem!.data; + jsonData = (jsonData == null || jsonData.isEmpty) ? '{"faceUrl":"","nickName":"data为空","userID":"213213"}' : jsonData; + + final item = jsonDecode(jsonData ?? '{"faceUrl":"","nickName":"测试昵称","userID":"213213"}'); + + logger.w(element.toJson()); + + // ----测试数据 + // final jsonData = '{"faceUrl":"","nickName":"测试昵称","userID":"213213"}'; + // final item = jsonDecode(jsonData); // 数据 + // final desc = '测试desc'; + // final cloudCustomData = 'newFocus'; + // V2TimCustomElem msg = V2TimMessage(elemType: 2, isRead: index > 2 ? true : false); + // ----------- + return Container( + width: double.infinity, + padding: const EdgeInsets.symmetric(horizontal: 15.0, vertical: 10.0), + child: Row( + spacing: 10.0, + children: [ + // 头像 + InkWell( + onTap: () async { + // 点击头像转到对方主页 + // 先获取视频详情 + // 如果cloudCustomData是interactionComment,interactionAt,interactionReply,传参时带上评论id, + // + final res = await Http.get('${VideoApi.detail}/${item['vlogID']}'); + Get.toNamed('/vloger', arguments: res['data']); + // Get.toNamed('/vloger'); }, - child: Row( - children: [ - ClipOval( - child: NetworkOrAssetImage( - imageUrl: item.userInfo.faceUrl, - width: 50, - height: 50, - ), - ), - const SizedBox(width: 10), - Expanded( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - item.userInfo.nickName?.isNotEmpty == true ? item.userInfo.nickName! : '未知昵称', - style: const TextStyle( - fontSize: 16, - fontWeight: FontWeight.normal, - ), - ), - if (item.userInfo.selfSignature?.isNotEmpty ?? false) ...[ - const SizedBox(height: 2.0), - Text( - item.userInfo.selfSignature!, - style: const TextStyle( - color: Colors.grey, - fontSize: 13.0, - ), - maxLines: 2, - overflow: TextOverflow.ellipsis, - ), - ], - ], - ), - ), - ], + child: ClipOval( + child: NetworkOrAssetImage( + imageUrl: item['faceUrl'], + width: 50, + height: 50, + ), ), ), - ), - SizedBox(width: 10), - - // 右侧按钮 - TextButton( - style: TextButton.styleFrom( - backgroundColor: item.followType == 3 ? Colors.grey : FStyle.primaryColor, - minimumSize: const Size(70, 32), - padding: const EdgeInsets.symmetric(horizontal: 12), - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(4), + // 消息 + Expanded( + child: InkWell( + onTap: () async { + // 点击头像转到对方主页,先获取视频详情 + final res = await Http.get('${VideoApi.detail}/${item['vlogID']}'); + Get.toNamed('/vloger', arguments: res['data']); + // Get.toNamed('/vloger'); + }, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + // 昵称 + Text( + item['nickName'] ?? '未知', + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.normal, + ), + ), + const SizedBox(height: 2.0), + // 描述内容 + Text( + maxLines: 2, + overflow: TextOverflow.ellipsis, + style: const TextStyle(color: Colors.grey, fontSize: 12.0), + desc, + // '很长文本内容很长文本内容很长文本内容很长文本内容很长文本内容很长文本内容很长文本内容很长文本内容很长文本内容很长文本内容很长文本内容很长文本内容很长文本内容很长文本内容很长文本内容很长文本内容很长文本内容很长文本内容', + ), + Text( + Utils.formatTime(msg.timestamp ?? DateTime.now().millisecondsSinceEpoch ~/ 1000), + style: TextStyle( + color: Colors.grey[600], + fontSize: 12, + ), + ), + ], + ), ), ), - onPressed: () async { - final ctl = Get.find(); - final checkRes = await ImService.instance.checkFollowType(userIDList: [item.userInfo.userID!]); - int realFollowType = 0; - if (checkRes.success && checkRes.data != null) { - realFollowType = checkRes.data!.first.followType ?? 0; - if ([1, 3].contains(realFollowType)) { - // 取关 - final unRes = await ImService.instance.unfollowUser(userIDList: [item.userInfo.userID!]); - if (unRes.success) { - setState(() { - item.followType = 2; - }); - ctl.mergeNoFriend(conversationID: 'c2c_${item.userInfo.userID!}'); - } - } else { - // 关注 - final res = await ImService.instance.followUser(userIDList: [item.userInfo.userID!]); - if (res.success) { - setState(() { - item.followType = realFollowType == 0 - ? 1 - : realFollowType == 2 - ? 3 - : 0; - }); - final chatRes = await ImService.instance.followUser(userIDList: [item.userInfo.userID!]); - if (chatRes.success) { - final res = await ImService.instance.getConversation(conversationID: 'c2c_${item.userInfo.userID}'); - if (res.success) { - V2TimConversation conversation = res.data; - if (conversation.conversationGroupList?.isNotEmpty ?? false) { - await ImService.instance.deleteConversationsFromGroup( - groupName: conversation.conversationGroupList!.first!, - conversationIDList: [conversation.conversationID], - ); - ctl.updateNoFriendMenu(); - } - } - } - } - } - } - }, - child: Text( - Utils.getTipText(item.followType), - style: const TextStyle(color: Colors.white, fontSize: 14), + + // 右侧 + Row( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Visibility( + visible: false, // 新的关注不需要 + // 视频首图 + child: NetworkOrAssetImage( + imageUrl: item['firstFrameImg'], + placeholderAsset: 'assets/images/bk.jpg', + width: 40, + height: 60, + ), + ), + const SizedBox(width: 5.0), + // 角标 + Visibility( + visible: !(msg.isRead ?? true), + child: FStyle.badge(0, isdot: true), + ), + ], ), - ), - ], - ), - ), + ], + ), + ); + }, ); }, ); diff --git a/lib/pages/groupChat/components/invite_action_sheet.dart b/lib/pages/groupChat/components/invite_action_sheet.dart index dc6d151..3528dea 100644 --- a/lib/pages/groupChat/components/invite_action_sheet.dart +++ b/lib/pages/groupChat/components/invite_action_sheet.dart @@ -163,14 +163,15 @@ class _MemberActionSheetState extends State { armedText: '释放加载', readyText: '加载中...', processingText: '加载中...', - processedText: hasMore ? '加载完成' : '没有更多了~', + processedText: '加载完成', + noMoreText: '没有更多了~', failedText: '加载失败,请重试', messageText: '最后更新于 %T', ), onLoad: () async { - // if (hasMore) { await getMemberData(); + return hasMore ? IndicatorResult.success : IndicatorResult.noMore; } }, child: ListView.builder( diff --git a/lib/pages/groupChat/components/member_action_sheet.dart b/lib/pages/groupChat/components/member_action_sheet.dart index cacc9cf..50138b2 100644 --- a/lib/pages/groupChat/components/member_action_sheet.dart +++ b/lib/pages/groupChat/components/member_action_sheet.dart @@ -69,6 +69,7 @@ class _MemberActionSheetState extends State { final mem = res.data!.memberInfoList ?? []; setState(() { members.addAll(mem); + hasMore = res.data!.nextSeq == '0' ? false : true; loading = false; }); } @@ -197,18 +198,19 @@ class _MemberActionSheetState extends State { armedText: '释放加载', readyText: '加载中...', processingText: '加载中...', - processedText: hasMore ? '加载完成' : '没有更多了~', + processedText: '加载完成', + noMoreText: '没有更多了~', failedText: '加载失败,请重试', messageText: '最后更新于 %T', ), onLoad: () async { // - if (hasMore) { - if (_query.isNotEmpty) { - await searchMember(loadMore: true); - } else { - await getMemberData(); - } + if (_query.isNotEmpty && (!isFinished)) { + await searchMember(loadMore: true); + return !isFinished ? IndicatorResult.success : IndicatorResult.noMore; + } else if (hasMore) { + await getMemberData(); + return hasMore ? IndicatorResult.success : IndicatorResult.noMore; } }, child: ListView.builder( diff --git a/lib/pages/groupChat/groupList.dart b/lib/pages/groupChat/groupList.dart index 13b229d..9f6308b 100644 --- a/lib/pages/groupChat/groupList.dart +++ b/lib/pages/groupChat/groupList.dart @@ -6,6 +6,7 @@ import 'package:flutter/material.dart'; import 'package:get/get.dart'; import 'package:loopin/IM/im_service.dart'; import 'package:loopin/behavior/custom_scroll_behavior.dart'; +import 'package:loopin/components/empty_tip.dart'; import 'package:loopin/components/network_or_asset_image.dart'; import 'package:tencent_cloud_chat_sdk/models/v2_tim_conversation.dart'; import 'package:tencent_cloud_chat_sdk/models/v2_tim_group_info.dart'; @@ -103,7 +104,8 @@ class GrouplistState extends State with SingleTickerProviderStateMixi armedText: '释放加载', readyText: '加载中...', processingText: '加载中...', - processedText: hasMore ? '加载完成' : '没有更多了~', + processedText: '加载完成', + noMoreText: '没有更多了~', failedText: '加载失败,请重试', messageText: '最后更新于 %T', ), @@ -113,9 +115,12 @@ class GrouplistState extends State with SingleTickerProviderStateMixi onLoad: () async { if (hasMore) { await getData(); + return hasMore ? IndicatorResult.success : IndicatorResult.noMore; } }, childBuilder: (context, physics) { + if (dataList.isEmpty) return EmptyTip(); + return ListView.builder( physics: physics, itemCount: dataList.length, diff --git a/lib/pages/groupChat/index.dart b/lib/pages/groupChat/index.dart index 03fd9bd..80943c5 100644 --- a/lib/pages/groupChat/index.dart +++ b/lib/pages/groupChat/index.dart @@ -135,7 +135,6 @@ class _StartGroupChatPageState extends State { msg: msgRes.data!.messageInfo!, groupID: groupID, isExcludedFromUnreadCount: true, - isPush: false, groupName: groupName, cloudCustomData: 'tips', ); @@ -304,13 +303,17 @@ class _StartGroupChatPageState extends State { armedText: '释放加载', readyText: '加载中...', processingText: '加载中...', - processedText: hasMore ? '加载完成' : '没有更多了~', + processedText: '加载完成', failedText: '加载失败,请重试', + noMoreText: '没有更多了~', messageText: '最后更新于 %T', ), onRefresh: () async => _loadData(reset: true), onLoad: () async { - if (hasMore) await _loadData(); + if (hasMore) { + await _loadData(); + return hasMore ? IndicatorResult.success : IndicatorResult.noMore; + } }, child: filteredList.isEmpty ? _emptyTip('暂无数据') diff --git a/lib/pages/my/all_function.dart b/lib/pages/my/all_function.dart index f3f902a..7df67c0 100644 --- a/lib/pages/my/all_function.dart +++ b/lib/pages/my/all_function.dart @@ -4,6 +4,7 @@ import 'package:loopin/IM/controller/im_user_info_controller.dart'; import 'package:loopin/components/my_qrcode.dart'; import 'package:loopin/pages/my/merchant/balance/balance.dart'; import 'package:loopin/pages/my/merchant/balance/controller.dart'; +import 'package:loopin/utils/index.dart'; import 'package:loopin/utils/scan_code_type.dart'; class AllFunctionsPage extends StatefulWidget { @@ -20,8 +21,8 @@ class _AllFunctionsPageState extends State { 'title': '主页展示', 'items': [ {'id': 'home_order', 'icon': 'assets/images/ico_order.png', 'label': '订单'}, - {'id': 'home_balance', 'icon': 'assets/images/ico_dhx.png', 'label': '余额logout'}, - {'id': 'home_withdraw', 'icon': 'assets/images/ico_sh.png', 'label': '提现vloger'}, + {'id': 'home_balance', 'icon': 'assets/images/ico_dhx.png', 'label': '余额'}, + {'id': 'home_hym', 'icon': 'assets/images/ico_tgm.png', 'label': '好友码'}, {'id': 'home_promo', 'icon': 'assets/images/ico_tgm.png', 'label': '推广码'}, ] }, @@ -72,7 +73,13 @@ class _AllFunctionsPageState extends State { itemCount: functionList.length, itemBuilder: (context, sectionIndex) { final section = functionList[sectionIndex]; - return _buildSection(section); + final role = controller.role.value; + final isSeller = Utils.hasRole(role, 2); + if (section['title'] == '更多功能' && !isSeller) { + return SizedBox(); + } else { + return _buildSection(section); + } }, ), ), @@ -80,6 +87,8 @@ class _AllFunctionsPageState extends State { } Widget _buildSection(Map section) { + final role = controller.role.value; + final isSeller = Utils.hasRole(role, 2); return Column( crossAxisAlignment: CrossAxisAlignment.start, mainAxisSize: MainAxisSize.min, @@ -112,7 +121,8 @@ class _AllFunctionsPageState extends State { itemBuilder: (context, index) { final item = (section['items'] as List)[index]; final role = controller.role.value; - if (item['id'] == 'home_promo' && role != 4) { + final hasRole = Utils.hasRole(role, 5); + if (item['id'] == 'home_promo' && !hasRole) { return SizedBox(); } else { return _buildFunctionItem(item); @@ -121,7 +131,8 @@ class _AllFunctionsPageState extends State { ), // 分隔线 - 只在不是最后一个section时显示 - if (functionList.indexOf(section) != functionList.length - 1) + // if (functionList.indexOf(section) != functionList.length - 1) + if (isSeller) Container( height: 2, margin: const EdgeInsets.symmetric(vertical: 8, horizontal: 20), @@ -191,14 +202,12 @@ class _AllFunctionsPageState extends State { Get.to(() => Balance()); // showLogoutDialog(context); break; - case 'home_withdraw': - Get.toNamed('/vloger'); + case 'home_hym': + qrcodeAlertDialog(context); break; case 'home_promo': // 推广码 - Get.to(() => MyQrcode( - prefix: QrTypeCode.tgm, - )); + qrcodeAlertDialog(context); break; case 'more_seller_order': Get.toNamed('/sellerOrder'); @@ -209,6 +218,41 @@ class _AllFunctionsPageState extends State { } } + // 二维码名片弹窗 + void qrcodeAlertDialog(BuildContext context) { + final role = controller.role.value; + final isLeader = Utils.hasRole(role, 5); + showDialog( + context: context, + builder: (context) { + return UnconstrainedBox( + constrainedAxis: Axis.vertical, + child: SizedBox( + width: 350.0, + child: AlertDialog( + contentPadding: const EdgeInsets.symmetric(horizontal: 10.0, vertical: 20.0), + backgroundColor: Colors.white, + surfaceTintColor: Colors.white, + shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(15.0)), + content: Padding( + padding: const EdgeInsets.symmetric(horizontal: 10.0), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + MyQrcode( + prefix: QrTypeCode.tgm, + text: isLeader ? '推广码' : '', + ) + ], + ), + ), + ), + ), + ); + }, + ); + } + // 退出登录弹窗 void showLogoutDialog(BuildContext context) { showDialog( diff --git a/lib/pages/my/delete.dart b/lib/pages/my/delete.dart new file mode 100644 index 0000000..c5de581 --- /dev/null +++ b/lib/pages/my/delete.dart @@ -0,0 +1,268 @@ +/// 注册模板 +library; + +import 'dart:async'; + +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:get/get.dart'; +import 'package:loopin/IM/im_service.dart'; +import 'package:loopin/api/common_api.dart'; +import 'package:loopin/controller/video_module_controller.dart'; +import 'package:loopin/service/http.dart'; +import 'package:loopin/utils/common.dart'; +import 'package:shirne_dialog/shirne_dialog.dart'; + +import '../../utils/index.dart'; + +class Delete extends StatefulWidget { + const Delete({super.key}); + + @override + State createState() => _DeleteState(); +} + +class _DeleteState extends State { + final Map authObj = {'phonenumber': '', 'smsCode': '', 'clientId': '428a8310cd442757ae699df5d894f051', 'grantType': 'sms'}; + + final fieldController = TextEditingController(); + Timer? timer; + String vcodeText = '获取验证码'; + bool disabled = false; + int time = 60; + + @override + void initState() { + super.initState(); + } + + @override + void dispose() { + super.dispose(); + timer?.cancel(); + } + + // 清空文本框 + void handleClear() { + fieldController.clear(); + setState(() { + authObj['phonenumber'] = ''; + }); + } + + //先退出,在注销 + void handleLogout() async {} + + // 提交表单 + void handleSubmit() async { + FocusScope.of(context).unfocus(); // 收起键盘 + if (authObj['phonenumber'] == '') { + MyDialog.toast('手机号不能为空', icon: Icon(Icons.warning), style: ToastStyle(backgroundColor: Colors.red.withAlpha(200))); + } else if (!Utils.checkTel(authObj['phonenumber'])) { + MyDialog.toast('手机号格式不正确', icon: Icon(Icons.warning), style: ToastStyle(backgroundColor: Colors.red.withAlpha(200))); + } else if (authObj['smsCode'] == '') { + MyDialog.toast('验证码不能为空', icon: Icon(Icons.warning), style: ToastStyle(backgroundColor: Colors.red.withAlpha(200))); + } else { + final dialogController = MyDialog.loading('注销中...'); + // 执行退出登录逻辑,执行注销逻辑 + final loginRes = await ImService.instance.logout(); + if (loginRes.success) { + // 清除存储信息 + Common.logout(); + // 初始化视频 + final videoController = Get.find(); + videoController.init(); + // 执行主席到逻辑 + final del = await Http.post('${CommonApi.revoked}?smsCode=${authObj['smsCode']}'); + logger.w(del); + // + Get.offAllNamed('/'); + } + } + } + + // 60s倒计时 + void handleVcode() { + logger.i(authObj); + FocusScope.of(context).unfocus(); // 收起键盘 + if (authObj['phonenumber'] == '') { + MyDialog.toast('手机号不能为空', icon: Icon(Icons.warning), style: ToastStyle(backgroundColor: Colors.red.withAlpha(200))); + } else if (!Utils.checkTel(authObj['phonenumber'])) { + MyDialog.toast('手机号格式不正确', icon: Icon(Icons.warning), style: ToastStyle(backgroundColor: Colors.red.withAlpha(200))); + } else { + setState(() { + disabled = true; + }); + startTimer(); + getCode(); + } + } + + startTimer() { + timer = Timer.periodic(const Duration(seconds: 1), (timer) { + setState(() { + if (time > 0) { + vcodeText = '获取验证码(${time--})'; + } else { + vcodeText = '获取验证码'; + time = 60; + disabled = false; + timer.cancel(); + } + }); + }); + } + + void getCode() async { + // + final res = await Http.get('${CommonApi.dictionaryApi}sms_template_id'); + final dictData = res['data'] as List; + final templeId = dictData.first['dictValue']; + logger.w(templeId); + final resCode = await Http.get(CommonApi.getDelCode, params: {'templateId': templeId}); + logger.i('注销验证短信:$resCode'); + } + + @override + Widget build(BuildContext context) { + final phoneNum = authObj['phonenumber'] as String; + return GestureDetector( + behavior: HitTestBehavior.translucent, + onTap: () => FocusScope.of(context).unfocus(), + child: Scaffold( + appBar: AppBar( + title: Text( + '注销账号', + style: TextStyle(color: Colors.black), + ), + forceMaterialTransparency: true, + ), + body: Container( + alignment: Alignment.center, + child: Column( + children: [ + Container( + height: 40.0, + margin: const EdgeInsets.symmetric(vertical: 5.0, horizontal: 30.0), + decoration: BoxDecoration( + color: Color(0xFFFAF8F5), + borderRadius: BorderRadius.circular(15.0), + ), + child: Row( + children: [ + Expanded( + child: TextField( + keyboardType: TextInputType.phone, + controller: fieldController, + inputFormatters: [ + LengthLimitingTextInputFormatter(11), // 限制输入长度为 11 + FilteringTextInputFormatter.digitsOnly, // 限制只能输入数字 + ], + decoration: InputDecoration( + hintText: '输入手机号', + hintStyle: const TextStyle(color: Colors.black38), + suffixIcon: Visibility( + visible: phoneNum.isNotEmpty, + child: InkWell( + hoverColor: Colors.transparent, + highlightColor: Colors.transparent, + splashColor: Colors.transparent, + onTap: handleClear, + child: const Icon( + Icons.clear, + color: Colors.grey, + size: 16.0, + ), + ), + ), + contentPadding: const EdgeInsets.symmetric(vertical: 0, horizontal: 12.0), + border: const OutlineInputBorder(borderSide: BorderSide.none), + ), + onChanged: (value) { + setState(() { + authObj['phonenumber'] = value; + }); + }, + ), + ) + ], + ), + ), + Container( + height: 40.0, + margin: const EdgeInsets.symmetric(vertical: 5.0, horizontal: 30.0), + decoration: BoxDecoration(color: Color(0xFFFAF8F5), borderRadius: BorderRadius.circular(15.0)), + child: Row( + children: [ + Expanded( + child: TextField( + keyboardType: TextInputType.phone, + inputFormatters: [ + LengthLimitingTextInputFormatter(6), + FilteringTextInputFormatter.digitsOnly, // 限制只能输入数字 + ], + decoration: const InputDecoration( + hintText: '验证码', + hintStyle: TextStyle(color: Colors.black38), + contentPadding: EdgeInsets.symmetric(vertical: 0, horizontal: 12.0), + border: OutlineInputBorder(borderSide: BorderSide.none), + ), + onChanged: (value) { + setState(() { + authObj['smsCode'] = value; + }); + }, + ), + ), + SizedBox( + height: 25.0, + child: Container( + margin: const EdgeInsets.only(right: 8.0), + child: ElevatedButton( + style: ButtonStyle( + backgroundColor: WidgetStateProperty.all(Colors.white), + shape: WidgetStatePropertyAll(RoundedRectangleBorder(borderRadius: BorderRadius.circular(8.0))), + padding: WidgetStateProperty.all(EdgeInsets.symmetric(horizontal: 15.0))), + onPressed: !disabled ? handleVcode : null, + child: Text(vcodeText, style: const TextStyle(fontSize: 13.0)), + ), + ), + ) + ], + ), + ), + Text( + '提示:\n' + '1. 账号注销后,平台会保留您的数据 7 天。\n' + '2. 若 7 天内未登录,平台将销毁所有保留数据。\n' + '3. 若在 7 天内重新登录,则视为放弃注销。\n' + '请谨慎操作。', + style: TextStyle(fontSize: 14, color: Colors.red[700]), + ), + Container( + margin: const EdgeInsets.symmetric(vertical: 10.0, horizontal: 30.0), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(15.0), + ), + child: SizedBox( + width: double.infinity, + height: 45.0, + child: FilledButton( + style: ButtonStyle(shape: WidgetStatePropertyAll(RoundedRectangleBorder(borderRadius: BorderRadius.circular(30.0)))), + onPressed: handleSubmit, + child: const Text( + '确认注销', + style: TextStyle(fontSize: 16.0), + ), + ), + ), + ), + const SizedBox( + height: 10.0, + ), + ], + ), + ), + )); + } +} diff --git a/lib/pages/my/fans.dart b/lib/pages/my/fans.dart index 2e35c89..70c37c4 100644 --- a/lib/pages/my/fans.dart +++ b/lib/pages/my/fans.dart @@ -7,6 +7,7 @@ import 'package:get/get.dart'; import 'package:loopin/IM/controller/chat_controller.dart'; import 'package:loopin/IM/im_service.dart'; import 'package:loopin/behavior/custom_scroll_behavior.dart'; +import 'package:loopin/components/empty_tip.dart'; import 'package:loopin/components/network_or_asset_image.dart'; import 'package:loopin/styles/index.dart'; import 'package:loopin/utils/index.dart'; @@ -151,7 +152,8 @@ class FansState extends State with SingleTickerProviderStateMixin { armedText: '释放加载', readyText: '加载中...', processingText: '加载中...', - processedText: hasMore ? '加载完成' : '没有更多了~', + processedText: '加载完成', + noMoreText: '没有更多了~', failedText: '加载失败,请重试', messageText: '最后更新于 %T', ), @@ -161,9 +163,12 @@ class FansState extends State with SingleTickerProviderStateMixin { onLoad: () async { if (hasMore) { await getData(); + return hasMore ? IndicatorResult.success : IndicatorResult.noMore; } }, childBuilder: (context, physics) { + if (dataList.isEmpty) return EmptyTip(); + return ListView.builder( physics: physics, itemCount: dataList.length, diff --git a/lib/pages/my/flowing.dart b/lib/pages/my/flowing.dart index ce6414f..51486eb 100644 --- a/lib/pages/my/flowing.dart +++ b/lib/pages/my/flowing.dart @@ -7,6 +7,7 @@ import 'package:get/get.dart'; import 'package:loopin/IM/controller/chat_controller.dart'; import 'package:loopin/IM/im_service.dart'; import 'package:loopin/behavior/custom_scroll_behavior.dart'; +import 'package:loopin/components/empty_tip.dart'; import 'package:loopin/components/network_or_asset_image.dart'; import 'package:loopin/styles/index.dart'; import 'package:loopin/utils/index.dart'; @@ -151,7 +152,8 @@ class FlowingState extends State with SingleTickerProviderStateMixin { armedText: '释放加载', readyText: '加载中...', processingText: '加载中...', - processedText: hasMore ? '加载完成' : '没有更多了~', + processedText: '加载完成', + noMoreText: '没有更多了~', failedText: '加载失败,请重试', messageText: '最后更新于 %T', ), @@ -161,9 +163,12 @@ class FlowingState extends State with SingleTickerProviderStateMixin { onLoad: () async { if (hasMore) { await getData(); + return hasMore ? IndicatorResult.success : IndicatorResult.noMore; } }, childBuilder: (context, physics) { + if (dataList.isEmpty) return EmptyTip(); + return ListView.builder( physics: physics, itemCount: dataList.length, diff --git a/lib/pages/my/index.dart b/lib/pages/my/index.dart index 2bd9526..0dda29d 100644 --- a/lib/pages/my/index.dart +++ b/lib/pages/my/index.dart @@ -13,8 +13,9 @@ import 'package:loopin/components/my_qrcode.dart'; import 'package:loopin/components/network_or_asset_image.dart'; import 'package:loopin/components/only_down_scroll_physics.dart'; import 'package:loopin/controller/video_module_controller.dart'; +import 'package:loopin/pages/my/merchant/balance/balance.dart'; +import 'package:loopin/pages/my/merchant/balance/controller.dart'; import 'package:loopin/service/http.dart'; -import 'package:loopin/styles/index.dart'; import 'package:loopin/utils/index.dart'; import 'package:loopin/utils/scan_code_type.dart'; import 'package:nested_scroll_view_plus/nested_scroll_view_plus.dart'; @@ -279,6 +280,8 @@ class MyPageState extends State with SingleTickerProviderStateMixin { // 二维码名片弹窗 void qrcodeAlertDialog(BuildContext context) { + final role = imUserInfoController?.role.value ?? 0; + final isLeader = Utils.hasRole(role, 5); showDialog( context: context, builder: (context) { @@ -288,7 +291,7 @@ class MyPageState extends State with SingleTickerProviderStateMixin { width: 350.0, child: AlertDialog( contentPadding: const EdgeInsets.symmetric(horizontal: 10.0, vertical: 20.0), - backgroundColor: const Color(0xff07c160), + backgroundColor: Colors.white, surfaceTintColor: Colors.white, shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(15.0)), content: Padding( @@ -296,14 +299,10 @@ class MyPageState extends State with SingleTickerProviderStateMixin { child: Column( mainAxisSize: MainAxisSize.min, children: [ - MyQrcode(prefix: QrTypeCode.tgm) - // Image.asset('assets/images/pic1.jpg', width: 250.0, fit: BoxFit.contain), - // const SizedBox(height: 15.0), - // const Text('扫一扫,加好友', - // style: TextStyle( - // color: Colors.white38, - // fontSize: 14.0, - // )), + MyQrcode( + prefix: QrTypeCode.tgm, + text: isLeader ? '推广码' : '', + ), ], ), ), @@ -748,6 +747,7 @@ class MyPageState extends State with SingleTickerProviderStateMixin { ), child: Text( nickname.isNotEmpty ? nickname : '昵称', + // '啊啊啊啊啊啊啊啊', style: const TextStyle( fontSize: 20, fontWeight: FontWeight.bold, @@ -759,51 +759,52 @@ class MyPageState extends State with SingleTickerProviderStateMixin { ), // 团长的二维码 - Obx( - () { - // MERCHANT(2, "商家"), - // AGENT(3, "代理"), - // PLATFORM(4, "平台"), - // REFERENCE(5, "团长") - final role = imUserInfoController?.role.value; - if (role == 4) { - return Row(children: [ - SizedBox(width: 8), - // - InkWell( - onTap: () => qrcodeAlertDialog(context), - child: Container( - padding: const EdgeInsets.symmetric(horizontal: 5, vertical: 5), - decoration: BoxDecoration( - color: Colors.black.withAlpha(176), - borderRadius: BorderRadius.circular(20), - ), - child: Row( - children: [ - Icon( - Icons.qr_code_outlined, - size: 18, - color: FStyle.secondaryColor, - ), - SizedBox(width: 4), - Text( - '团长邀请码', - style: TextStyle( - fontSize: 12, - fontWeight: FontWeight.bold, - color: FStyle.secondaryColor, // 彩色文字 - ), - ) - ], - ), - ), - ), - ]); - } else { - return const SizedBox(); - } - }, - ), + // Obx( + // () { + // // MERCHANT(2, "商家"), + // // AGENT(3, "代理"), + // // PLATFORM(4, "平台"), + // // REFERENCE(5, "团长") + // final role = imUserInfoController?.role.value; + // final isLeader = Utils.hasRole(role ?? 0, 5); + // if (!isLeader) { + // return Row(children: [ + // SizedBox(width: 8), + // // + // InkWell( + // onTap: () => qrcodeAlertDialog(context), + // child: Container( + // padding: const EdgeInsets.symmetric(horizontal: 5, vertical: 5), + // decoration: BoxDecoration( + // color: Colors.black.withAlpha(176), + // borderRadius: BorderRadius.circular(20), + // ), + // child: Row( + // children: [ + // Icon( + // Icons.qr_code_outlined, + // size: 18, + // color: FStyle.secondaryColor, + // ), + // SizedBox(width: 4), + // Text( + // '团长邀请码', + // style: TextStyle( + // fontSize: 12, + // fontWeight: FontWeight.bold, + // color: FStyle.secondaryColor, // 彩色文字 + // ), + // ) + // ], + // ), + // ), + // ), + // ]); + // } else { + // return const SizedBox(); + // } + // }, + // ), ], ), const SizedBox(height: 8), @@ -907,6 +908,8 @@ class MyPageState extends State with SingleTickerProviderStateMixin { } Widget _buildOrderCard(BuildContext context) { + final role = imUserInfoController?.role.value ?? 0; + final isLeader = Utils.hasRole(role, 5); return Container( decoration: BoxDecoration( color: Colors.white, @@ -946,15 +949,16 @@ class MyPageState extends State with SingleTickerProviderStateMixin { _buildOrderIcon('assets/images/ico_order.png', '订单', () { Get.toNamed('/myOrder'); }), - _buildOrderIcon('assets/images/ico_dhx.png', '余额logout', () { + _buildOrderIcon('assets/images/ico_dhx.png', '余额', () { + Get.put(BalanceController()); + Get.to(() => Balance()); + }), + _buildOrderIcon('assets/images/ico_tgm.png', isLeader ? '推广码' : '好友码', () { + qrcodeAlertDialog(context); + }), + _buildOrderIcon('assets/images/icon_logout.png', '退出登录', () { showLogoutDialog(context); }), - _buildOrderIcon('assets/images/ico_sh.png', '提现vloger', () { - Get.toNamed('/vloger'); - }), - _buildOrderIcon('assets/images/ico_tgm.png', '推广码', () { - logger.e('推广码'); - }), ], ), ), diff --git a/lib/pages/my/merchant/balance/balance.dart b/lib/pages/my/merchant/balance/balance.dart index 5ed1a32..128e904 100644 --- a/lib/pages/my/merchant/balance/balance.dart +++ b/lib/pages/my/merchant/balance/balance.dart @@ -2,9 +2,11 @@ import 'package:easy_refresh/easy_refresh.dart'; import 'package:flutter/material.dart'; import 'package:get/get.dart'; -import 'package:loopin/IM/im_service.dart'; +import 'package:loopin/IM/controller/im_user_info_controller.dart'; import 'package:loopin/behavior/custom_scroll_behavior.dart'; +import 'package:loopin/components/empty_tip.dart'; import 'package:loopin/pages/my/merchant/balance/controller.dart'; +import 'package:loopin/utils/index.dart'; class Balance extends StatefulWidget { const Balance({super.key}); @@ -14,12 +16,18 @@ class Balance extends StatefulWidget { } class _BalanceState extends State { - final controller = Get.put(BalanceController()); + final controller = Get.find(); + + @override + void dispose() { + Get.delete(); + super.dispose(); + } @override Widget build(BuildContext context) { return Scaffold( - appBar: AppBar(title: const Text("我的钱包")), + appBar: AppBar(title: Text("我的余额")), body: ScrollConfiguration( behavior: CustomScrollBehavior().copyWith(scrollbars: false), child: Column( @@ -30,6 +38,9 @@ class _BalanceState extends State { color: Colors.blueAccent, width: double.infinity, child: Obx(() { + final ctl = Get.find(); + final role = ctl.role.value; + final isLeader = Utils.hasRole(role, 5); return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ @@ -46,13 +57,35 @@ class _BalanceState extends State { fontWeight: FontWeight.bold, ), ), - const SizedBox(height: 12), - ElevatedButton( - onPressed: () => controller.recharge(100.0), - style: ElevatedButton.styleFrom( - backgroundColor: Colors.orange, - ), - child: const Text("充值 100 元"), + // if (isLeader) const SizedBox(height: 12), + Row( + children: [ + ElevatedButton( + onPressed: () { + // 加充值 + controller.recharge(money: '0.1'); + // http + }, + style: ElevatedButton.styleFrom( + backgroundColor: Colors.orange, + ), + child: const Text("充值"), + ), + SizedBox(width: 20), + // + ElevatedButton( + onPressed: () { + // 提现 + // controller.recharge(money: '0.1'); + controller.withDraw(money: '1000'); + // http + }, + style: ElevatedButton.styleFrom( + backgroundColor: Colors.orange, + ), + child: const Text("提现"), + ), + ], ), ], ); @@ -61,61 +94,74 @@ class _BalanceState extends State { // 下半部分:流水记录分页列表 Expanded( - child: Obx(() { - return EasyRefresh.builder( - callLoadOverOffset: 20, //触底距离 - callRefreshOverOffset: 20, // 下拉距离 - header: ClassicHeader( - dragText: '下拉刷新', - armedText: '释放刷新', - readyText: '加载中...', - processingText: '加载中...', - processedText: '加载完成', - failedText: '加载失败,请重试', - messageText: '最后更新于 %T', - ), - footer: ClassicFooter( - dragText: '加载更多', - armedText: '释放加载', - readyText: '加载中...', - processingText: '加载中...', - processedText: controller.hasMore.value ? '加载完成' : '没有更多了~', - failedText: '加载失败,请重试', - messageText: '最后更新于 %T', - succeededIcon: controller.hasMore.value ? Icon(Icons.check_circle, color: Colors.green) : Icon(Icons.warning, color: Colors.orange), - ), - onRefresh: () async { - await controller.getData(reset: true); - }, - onLoad: () async { - if (controller.hasMore.value) { - await controller.getData(); - } - }, - childBuilder: (context, physics) { - return ListView.builder( - physics: physics, - itemCount: controller.data.length, - itemBuilder: (context, index) { - final tx = controller.data[index]; - logger.w(tx.source); + child: EasyRefresh.builder( + callLoadOverOffset: 20, //触底距离 + callRefreshOverOffset: 20, // 下拉距离 + header: ClassicHeader( + dragText: '下拉刷新', + armedText: '释放刷新', + readyText: '加载中...', + processingText: '加载中...', + processedText: '加载完成', + failedText: '加载失败,请重试', + messageText: '最后更新于 %T', + ), + footer: ClassicFooter( + dragText: '加载更多', + armedText: '释放加载', + readyText: '加载中...', + processingText: '加载中...', + processedText: '加载完成', + noMoreText: '没有更多了~', + failedText: '加载失败,请重试', + messageText: '最后更新于 %T', + ), - return ListTile( - title: Text(tx.source), - subtitle: Text(tx.createTime), - trailing: Text( - "变动金额:${tx.changeType == 1 ? '+' : '-'}${tx.changeAmount}", // 变动金额 - style: TextStyle( - fontWeight: FontWeight.bold, - color: tx.changeType == 1 ? Colors.green : Colors.red, - ), - ), + onRefresh: () async { + await controller.getData(reset: true); + }, + onLoad: () async { + if (controller.hasMore.value) { + await controller.getData(); + return controller.hasMore.value ? IndicatorResult.success : IndicatorResult.noMore; + } + }, + childBuilder: (context, physics) { + return Obx( + () { + // 数据加载中 + if (controller.isLoading.value && controller.data.isEmpty) { + return Center( + child: CircularProgressIndicator(), ); - }, - ); - }, - ); - }), + } + //空 + if (controller.data.isEmpty) { + return EmptyTip(); + } + return ListView.builder( + physics: physics, + itemCount: controller.data.length, + itemBuilder: (context, index) { + final tx = controller.data[index]; + + return ListTile( + title: Text(tx.source), + subtitle: Text(tx.createTime), + trailing: Text( + "变动金额:${tx.changeType == 1 ? '+' : '-'}${tx.changeAmount}${tx.id}", // 变动金额 + style: TextStyle( + fontWeight: FontWeight.bold, + color: tx.changeType == 1 ? Colors.green : Colors.red, + ), + ), + ); + }, + ); + }, + ); + }, + ), ), ], ), diff --git a/lib/pages/my/merchant/balance/controller.dart b/lib/pages/my/merchant/balance/controller.dart index 223644a..c843bde 100644 --- a/lib/pages/my/merchant/balance/controller.dart +++ b/lib/pages/my/merchant/balance/controller.dart @@ -1,5 +1,11 @@ +import 'package:flutter/material.dart'; import 'package:get/get.dart'; +import 'package:loopin/IM/controller/im_user_info_controller.dart'; +import 'package:loopin/IM/im_friend_listeners.dart'; +import 'package:loopin/api/common_api.dart'; import 'package:loopin/pages/my/merchant/balance/model.dart'; +import 'package:loopin/service/http.dart'; +import 'package:loopin/utils/wxsdk.dart'; class BalanceController extends GetxController { /// 钱包余额 @@ -21,36 +27,130 @@ class BalanceController extends GetxController { } /// 充值 - void recharge(double amount) { - balance.value += amount; - // 同时加一条流水 + Future recharge({required String money}) async { + // 获取支付参数 + final data = {"orderType": "RECHARGE", "clientType": "APP", "paymentMethod": "WECHAT", "paymentClient": "APP", "money": money}; + final res = await Http.post(CommonApi.addBalance, data: data); + logger.w(res); + final payParams = res['data']; + logger.w(payParams); + // 拉起支付 + await Wxsdk.payWithWx( + appId: payParams['appid'], + partnerId: payParams['partnerid'], + prepayId: payParams['prepayid'], + packageValue: payParams['package'], + nonceStr: payParams['noncestr'], + timestamp: int.parse(payParams['timestamp']), + sign: payParams['sign'], + ); + // 在回调结果中获取新的数据 + // getData(reset: true); + } + + /// 提现 + Future withDraw({required String money}) async { + final infoCtl = Get.find(); + final openId = infoCtl.customInfo['openId']; + if (openId == null || openId.isEmpty) { + showDialog( + context: Get.context!, + builder: (context) { + return AlertDialog( + title: Text('微信授权'), + content: const Text('余额提现至您的微信零钱内,是否前往微信授权?', style: TextStyle(fontSize: 16.0)), + backgroundColor: Colors.white, + surfaceTintColor: Colors.white, + shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(15.0)), + elevation: 2.0, + actionsPadding: const EdgeInsets.all(15.0), + actions: [ + TextButton(onPressed: () => {Get.back()}, child: Text('取消', style: TextStyle(color: Colors.red))), + TextButton( + onPressed: () async { + //去授权 + await Wxsdk.login(); + Get.back(); + }, + child: Text('确认')), + ], + ); + }, + ); + return; + } + // + // final res = await Http.post(CommonApi.withdraw, data: { + // "money": money, + // "method": "1", + // }); + // MyDialog.('提现成功,系统将在一个工作日内将余额提现至您绑定的微信内'); + showDialog( + context: Get.context!, + builder: (context) { + return AlertDialog( + title: Text('提现成功'), + content: const Text('系统将在一个工作日内将余额提现至您绑定的微信内', style: TextStyle(fontSize: 16.0)), + backgroundColor: Colors.white, + surfaceTintColor: Colors.white, + shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(15.0)), + elevation: 2.0, + actionsPadding: const EdgeInsets.all(15.0), + actions: [ + TextButton(onPressed: () => {Get.back()}, child: Text('确认')), + ], + ); + }, + ); + getData(reset: true); } /// 分页数据 Future getData({bool reset = false}) async { - if (isLoading.value) return; + if (isLoading.value) { + logger.w('正在加载中,跳过'); + return; + } + isLoading.value = true; + logger.w('开始加载数据,reset: $reset, currentPage: $currentPage'); + + await Future.delayed(const Duration(seconds: 2)); if (reset) { + logger.w('重置数据'); currentPage = 1; data.clear(); hasMore.value = true; } - await Future.delayed(const Duration(seconds: 3)); // 模拟网络延迟 - List newData = List.generate( 10, - (index) => AccountBill(id: index), + (index) { + int id = currentPage * 10 + index + 1; + // logger.w('生成数据: id=$id'); + return AccountBill( + id: id, + source: '来源 $currentPage-${index + 1}', + changeAmount: (index + 1) * 10.0, + changeType: index % 2 + 1, + createTime: DateTime.now().toString(), + ); + }, ); data.addAll(newData); - currentPage++; + logger.w('添加了 ${newData.length} 条数据,总数据量: ${data.length}'); - if (currentPage > 3) { + currentPage++; + logger.w('页码增加到: $currentPage'); + + if (currentPage > 5) { hasMore.value = false; + logger.w('没有更多数据了'); } isLoading.value = false; + logger.w('加载完成'); } } diff --git a/lib/pages/my/mutual_followers.dart b/lib/pages/my/mutual_followers.dart index c8efc0b..1bd0eea 100644 --- a/lib/pages/my/mutual_followers.dart +++ b/lib/pages/my/mutual_followers.dart @@ -7,6 +7,7 @@ import 'package:get/get.dart'; import 'package:loopin/IM/controller/chat_controller.dart'; import 'package:loopin/IM/im_service.dart'; import 'package:loopin/behavior/custom_scroll_behavior.dart'; +import 'package:loopin/components/empty_tip.dart'; import 'package:loopin/components/network_or_asset_image.dart'; import 'package:loopin/styles/index.dart'; import 'package:loopin/utils/index.dart'; @@ -151,7 +152,8 @@ class MutualFollowersState extends State with SingleTickerProvi armedText: '释放加载', readyText: '加载中...', processingText: '加载中...', - processedText: hasMore ? '加载完成' : '没有更多了~', + processedText: '加载完成', + noMoreText: '没有更多了~', failedText: '加载失败,请重试', messageText: '最后更新于 %T', ), @@ -161,9 +163,12 @@ class MutualFollowersState extends State with SingleTickerProvi onLoad: () async { if (hasMore) { await getData(); + return hasMore ? IndicatorResult.success : IndicatorResult.noMore; } }, childBuilder: (context, physics) { + if (dataList.isEmpty) return EmptyTip(); + return ListView.builder( physics: physics, itemCount: dataList.length, diff --git a/lib/pages/my/nick_name.dart b/lib/pages/my/nick_name.dart index d2e2338..cfba427 100644 --- a/lib/pages/my/nick_name.dart +++ b/lib/pages/my/nick_name.dart @@ -65,10 +65,10 @@ class _NickNameState extends State { border: OutlineInputBorder(), contentPadding: EdgeInsets.symmetric(horizontal: 12, vertical: 10), ), - maxLength: 20, + maxLength: 8, validator: FormBuilderValidators.compose([ FormBuilderValidators.required(errorText: '昵称不能为空'), - FormBuilderValidators.maxLength(20, errorText: '昵称不能超过20个字符'), + FormBuilderValidators.maxLength(8, errorText: '昵称不能超过8个字符'), ]), ), const SizedBox(height: 8), diff --git a/lib/pages/my/setting.dart b/lib/pages/my/setting.dart index a110511..6e7147e 100644 --- a/lib/pages/my/setting.dart +++ b/lib/pages/my/setting.dart @@ -14,9 +14,12 @@ class Setting extends StatelessWidget { 'onTap': () => Get.toNamed('/userInfo'), }, { - 'icon': Icons.notifications, - 'title': '通知设置', - 'onTap': () => Get.toNamed('/notifications'), + // 'icon': Icons.notifications, + // 'title': '通知设置', + // 'onTap': () => Get.toNamed('/notifications'), + 'icon': Icons.logout, + 'title': '注销账号', + 'onTap': () => Get.toNamed('/delete'), }, { 'icon': Icons.lock, diff --git a/lib/pages/my/user_info.dart b/lib/pages/my/user_info.dart index 4420f27..94f1d20 100644 --- a/lib/pages/my/user_info.dart +++ b/lib/pages/my/user_info.dart @@ -8,6 +8,7 @@ import 'package:get/get.dart'; import 'package:image_cropper/image_cropper.dart'; import 'package:loopin/IM/controller/im_user_info_controller.dart'; import 'package:loopin/api/common_api.dart'; +import 'package:loopin/components/my_toast.dart'; import 'package:loopin/components/network_or_asset_image.dart'; import 'package:loopin/service/http.dart'; import 'package:loopin/styles/index.dart'; @@ -68,7 +69,12 @@ class _UserInfoState extends State { /// 微信授权 Future wechatLogin() async { - await Wxsdk.login(); + final isNeed = userInfoController.customInfo['openId']; + if (isNeed == null || isNeed == '') { + await Wxsdk.login(); + } else { + MyToast().tip(title: '您已授权无需重复操作'); + } } /// 选性别 @@ -239,7 +245,7 @@ class _UserInfoState extends State { if (sizeInMB > 200) { MyDialog.toast('图片大小不能超过200MB', icon: const Icon(Icons.check_circle), style: ToastStyle(backgroundColor: Colors.red.withAlpha(200))); } else { - print("视频合法,大小:$sizeInMB MB"); + print("图片合法,大小:$sizeInMB MB"); //走upload(file)上传图片拿到url地址 final istance = MyDialog.loading('上传中'); final res = await Http.upload(CommonApi.uploadFile, filePath: file.path); @@ -371,10 +377,6 @@ class _UserInfoState extends State { final imageUrl = userInfoController.customInfo['coverBg']; return GestureDetector( onTap: () => pickCover(context), - // child: Image( - // image: (imageUrl != null && imageUrl.isNotEmpty) ? NetworkImage(imageUrl) : const AssetImage('assets/images/pic2.jpg') as ImageProvider, - // fit: BoxFit.cover, - // ), child: NetworkOrAssetImage( imageUrl: imageUrl, placeholderAsset: 'assets/images/bk.jpg', diff --git a/lib/pages/my/vloger.dart b/lib/pages/my/vloger.dart index 127d48b..4fbf501 100644 --- a/lib/pages/my/vloger.dart +++ b/lib/pages/my/vloger.dart @@ -4,14 +4,14 @@ import 'package:get/get.dart'; import 'package:get/get_rx/src/rx_typedefs/rx_typedefs.dart'; import 'package:loopin/IM/controller/chat_controller.dart'; import 'package:loopin/IM/im_service.dart'; -import 'package:loopin/service/http.dart'; -import 'package:loopin/components/custom_sticky_header.dart'; import 'package:loopin/api/common_api.dart'; import 'package:loopin/api/video_api.dart'; -import 'package:loopin/utils/index.dart'; +import 'package:loopin/components/custom_sticky_header.dart'; import 'package:loopin/components/network_or_asset_image.dart'; import 'package:loopin/components/only_down_scroll_physics.dart'; +import 'package:loopin/service/http.dart'; import 'package:loopin/styles/index.dart'; +import 'package:loopin/utils/index.dart'; import 'package:nested_scroll_view_plus/nested_scroll_view_plus.dart'; import 'package:shirne_dialog/shirne_dialog.dart'; import 'package:tencent_cloud_chat_sdk/models/v2_tim_conversation.dart'; @@ -82,7 +82,7 @@ class MyPageState extends State with SingleTickerProviderStateMixin { void initState() { super.initState(); args = Get.arguments ?? {}; - print('argsssssssssssssssssssssss${args}'); + print('argsssssssssssssssssssssss$args'); itemsParams = PageParams(); favoriteParams = PageParams(); selfInfo(); @@ -125,44 +125,43 @@ class MyPageState extends State with SingleTickerProviderStateMixin { super.dispose(); } - // 获取用户的所有视频的点赞数量 + // 获取用户的所有视频的点赞数量 void getUserLikesCount() async { try { - final resData = await Http.get(CommonApi.accountInfo+'?memberId=${args['memberId']}'); - print('aaaaaaaaaaaaaaaaaaa${resData}'); - if(resData != null && resData['code'] == 200){ - vlogLikeCount = resData['data']['vlogLikeCount']??0; - } - } catch (e) { + final resData = await Http.get('${CommonApi.accountInfo}?memberId=${args['memberId']}'); + print('aaaaaaaaaaaaaaaaaaa$resData'); + if (resData != null && resData['code'] == 200) { + vlogLikeCount = resData['data']['vlogLikeCount'] ?? 0; } - + } catch (e) {} } + void loadData([int? tabIndex]) async { final index = tabIndex ?? currentTabIndex.value; if (index == 0) { if (itemsParams.isLoading || !itemsParams.hasMore) return; - itemsParams.isLoading = true; - final res = await Http.post(VideoApi.getVideoListByMemberId, data: { - "memberId": args['memberId'], - "current": itemsParams.page, - "size": itemsParams.pageSize, - }); - final obj = res['data']; - final total = obj['total']; - final row = obj['records'] ?? []; - logger.i(res['data']); - // 判断是否还有更多数据 - logger.e(items.length); - // 添加新数据,触发响应式更新 - items.addAll(row); - logger.e(obj); - if (items.length >= total) { - itemsParams.hasMore = false; - } - // 页码加一 - itemsParams.page++; - // - itemsParams.isLoading = false; + itemsParams.isLoading = true; + final res = await Http.post(VideoApi.getVideoListByMemberId, data: { + "memberId": args['memberId'], + "current": itemsParams.page, + "size": itemsParams.pageSize, + }); + final obj = res['data']; + final total = obj['total']; + final row = obj['records'] ?? []; + logger.i(res['data']); + // 判断是否还有更多数据 + logger.e(items.length); + // 添加新数据,触发响应式更新 + items.addAll(row); + logger.e(obj); + if (items.length >= total) { + itemsParams.hasMore = false; + } + // 页码加一 + itemsParams.page++; + // + itemsParams.isLoading = false; } } @@ -370,7 +369,7 @@ class MyPageState extends State with SingleTickerProviderStateMixin { return GestureDetector( onTap: () { // 点击跳转到视频播放详情页面 - Get.toNamed('/videoDetail', arguments: {'videoId': item['id']}); + Get.toNamed('/videoDetail', arguments: {'videoId': item['id']}); }, child: Container( decoration: BoxDecoration( @@ -394,7 +393,7 @@ class MyPageState extends State with SingleTickerProviderStateMixin { width: double.infinity, height: double.infinity, fit: BoxFit.cover, - // placeholderAsset: 'assets/images/video_placeholder.png', + placeholderAsset: 'assets/images/bk.jpg', ), // 半透明渐变底部背景 Positioned( @@ -421,7 +420,8 @@ class MyPageState extends State with SingleTickerProviderStateMixin { bottom: 8, child: Row( children: [ - const Icon(Icons.favorite, + const Icon( + Icons.favorite, color: Colors.white, size: 16, ), @@ -457,9 +457,7 @@ class MyPageState extends State with SingleTickerProviderStateMixin { child: Padding( padding: const EdgeInsets.symmetric(vertical: 20.0), child: Center( - child: params.hasMore - ? const CircularProgressIndicator() - : const Text('没有更多数据了'), + child: params.hasMore ? const CircularProgressIndicator() : const Text('没有更多数据了'), ), ), ), @@ -566,7 +564,11 @@ class MyPageState extends State with SingleTickerProviderStateMixin { child: Row( mainAxisAlignment: MainAxisAlignment.spaceAround, children: [ - Column(children: [Text('${Utils.graceNumber(vlogLikeCount)}', style: TextStyle(fontSize: 16.0, fontWeight: FontWeight.bold)), SizedBox(height: 3.0), Text('获赞')]), + Column(children: [ + Text(Utils.graceNumber(vlogLikeCount), style: TextStyle(fontSize: 16.0, fontWeight: FontWeight.bold)), + SizedBox(height: 3.0), + Text('获赞') + ]), Column(children: [ Text('${followInfo.value.followingCount}', style: TextStyle(fontSize: 16.0, fontWeight: FontWeight.bold)), SizedBox(height: 3.0), diff --git a/lib/pages/upload_video_page/upload_video_page.dart b/lib/pages/upload_video_page/upload_video_page.dart index 835cdde..bab3a02 100644 --- a/lib/pages/upload_video_page/upload_video_page.dart +++ b/lib/pages/upload_video_page/upload_video_page.dart @@ -56,7 +56,7 @@ class _UploadVideoPageState extends State { Future pickVideo() async { descFocusNode.unfocus(); - final hasPer = await Permissions.requestVideoPermission(); + final hasPer = await Permissions.requestPhotoPermission(); if (!hasPer) { Permissions.showPermissionDialog('相册'); diff --git a/lib/router/index.dart b/lib/router/index.dart index f59f508..0c96b78 100644 --- a/lib/router/index.dart +++ b/lib/router/index.dart @@ -13,16 +13,17 @@ import 'package:loopin/pages/chat/notify/noFriend.dart'; import 'package:loopin/pages/chat/notify/system.dart'; import 'package:loopin/pages/groupChat/groupList.dart'; import 'package:loopin/pages/groupChat/index.dart'; +import 'package:loopin/pages/my/all_function.dart'; +import 'package:loopin/pages/my/delete.dart'; import 'package:loopin/pages/my/des.dart'; import 'package:loopin/pages/my/fans.dart'; import 'package:loopin/pages/my/flowing.dart'; +import 'package:loopin/pages/my/merchant/income.dart'; import 'package:loopin/pages/my/mutual_followers.dart'; import 'package:loopin/pages/my/nick_name.dart'; import 'package:loopin/pages/my/setting.dart'; import 'package:loopin/pages/my/user_info.dart'; import 'package:loopin/pages/my/vloger.dart'; -import 'package:loopin/pages/my/all_function.dart'; -import 'package:loopin/pages/my/merchant/income.dart'; import 'package:loopin/pages/order/my_order.dart'; import 'package:loopin/pages/order/seller_order.dart'; import 'package:loopin/pages/search/index.dart'; @@ -36,9 +37,9 @@ import '../pages/auth/login.dart'; // 商品详细 import '../pages/goods/detail.dart'; import '../pages/order/detail.dart'; -import '../pages/order/seller_detail.dart'; // 订单 import '../pages/order/index.dart'; +import '../pages/order/seller_detail.dart'; // 引入工具类 import '../utils/common.dart'; @@ -68,9 +69,11 @@ final Map routes = { '/about': const Setting(), '/des': const Des(), '/nickName': const NickName(), + '/delete': const Delete(), + //通知相关 '/noFriend': const Nofriend(), - '/newFocus': const Newfoucs(), + '/newFoucs': const Newfoucs(), '/system': const System(), '/interaction': const Interaction(), //关系链 diff --git a/lib/service/http_config.dart b/lib/service/http_config.dart index 73bfc6d..15f8b93 100644 --- a/lib/service/http_config.dart +++ b/lib/service/http_config.dart @@ -2,6 +2,9 @@ import 'package:dio/dio.dart'; import 'package:flutter/material.dart'; import 'package:get/get.dart'; import 'package:get_storage/get_storage.dart'; +import 'package:loopin/IM/im_service.dart'; +import 'package:loopin/controller/video_module_controller.dart'; +import 'package:loopin/utils/common.dart'; class HttpConfig { static final Dio dio = Dio(BaseOptions( @@ -9,10 +12,10 @@ class HttpConfig { // baseUrl: 'http://111.62.22.190:8080', // baseUrl: 'http://cjh.wuzhongjie.com.cn', // baseUrl: 'http://82.156.121.2:8880', - baseUrl: 'https://www.wuzhongjie.com.cn/prod-api', + // baseUrl: 'https://www.wuzhongjie.com.cn/prod-api', // baseUrl: 'http://192.168.1.65:8880', - // baseUrl: 'http://192.168.1.22:8080', + baseUrl: 'http://192.168.1.22:8080', // connectTimeout: Duration(seconds: 30), // receiveTimeout: Duration(seconds: 30), @@ -23,6 +26,24 @@ class HttpConfig { static final box = GetStorage(); + // 退出登录 + static void handleLogout() async { + Get.offAllNamed( + '/login', + predicate: (route) { + return route.settings.name == '/'; + }, + ); + final loginRes = await ImService.instance.logout(); + if (loginRes.success) { + // 清除存储信息 + Common.logout(); + // 初始化视频 + final videoController = Get.find(); + videoController.init(); + } + } + static void init() { dio.interceptors.add( InterceptorsWrapper( @@ -46,6 +67,26 @@ class HttpConfig { // logger.e(response.requestOptions.data); final data = response.data; if (data is Map) { + // 处理token失效,强制退出 + if (data['code'] == 20006) { + handleLogout(); + Get.snackbar( + '登录状态已失效', + '您当前登录状态已失效,请重新登录', + duration: Duration(seconds: 5), + backgroundColor: Colors.red.withAlpha(230), + colorText: Colors.white, + icon: const Icon(Icons.error_outline, color: Colors.white), + ); + return handler.reject( + DioException( + requestOptions: response.requestOptions, + error: data['msg'], + response: response, + ), + ); + } + // 其他异常 if (data['code'] != 200) { Get.snackbar( '错误码${data['code']}', diff --git a/lib/utils/audio_player_service.dart b/lib/utils/audio_player_service.dart index 1392b95..64ead54 100644 --- a/lib/utils/audio_player_service.dart +++ b/lib/utils/audio_player_service.dart @@ -1,5 +1,8 @@ +import 'dart:io'; + import 'package:audioplayers/audioplayers.dart'; import 'package:loopin/IM/im_core.dart'; +import 'package:loopin/components/my_toast.dart'; class AudioPlayerService { static final AudioPlayerService _instance = AudioPlayerService._internal(); @@ -11,10 +14,19 @@ class AudioPlayerService { /// 播放本地文件 Future playLocal(String filePath) async { try { + final file = File(filePath); + if (!file.existsSync()) { + logger.e('本地音频文件不存在:$filePath'); + MyToast().tip(title: '音频文件不存在'); + return; + } await _audioPlayer.setSourceDeviceFile(filePath); await _audioPlayer.resume(); + // 等待播放完成 + await _audioPlayer.onPlayerComplete.first; } catch (e) { logger.e('播放本地音频失败: $e'); + MyToast().tip(title: '音频文件不存在'); } } @@ -23,8 +35,11 @@ class AudioPlayerService { try { await _audioPlayer.setSourceUrl(url); await _audioPlayer.resume(); + // 等待播放完成 + await _audioPlayer.onPlayerComplete.first; } catch (e) { logger.e('播放网络音频失败: $e'); + MyToast().tip(title: '音频文件不存在'); } } diff --git a/lib/utils/index.dart b/lib/utils/index.dart index 96ebaa7..445e08c 100644 --- a/lib/utils/index.dart +++ b/lib/utils/index.dart @@ -234,4 +234,13 @@ class Utils { } return defaultValue; } + + // MERCHANT(2, "商家"), + // AGENT(3, "代理"), + // PLATFORM(4, "平台"), + // REFERENCE(5, "团长") + // 规则是将int按string去拼接 + static bool hasRole(int roleValue, int targetRole) { + return roleValue.toString().contains(targetRole.toString()); + } } diff --git a/lib/utils/notification_banner.dart b/lib/utils/notification_banner.dart index 28af460..9fdea4b 100644 --- a/lib/utils/notification_banner.dart +++ b/lib/utils/notification_banner.dart @@ -30,7 +30,7 @@ class NotificationBanner { Get.snackbar( '', '', - duration: const Duration(minutes: 1), + duration: const Duration(seconds: 5), margin: const EdgeInsets.all(12), backgroundColor: FStyle.primaryColor.withAlpha(220), titleText: Row( diff --git a/lib/utils/voice_service.dart b/lib/utils/voice_service.dart index bee4098..8288b3a 100644 --- a/lib/utils/voice_service.dart +++ b/lib/utils/voice_service.dart @@ -10,13 +10,15 @@ class VoiceService { VoiceService._internal(); final AudioRecorder _recorder = AudioRecorder(); + String? _voiceFilePath; DateTime? _startTime; /// 开始录音 Future startRecording() async { if (await _recorder.hasPermission()) { - final dir = await getTemporaryDirectory(); // 临时目录 + // final dir = await getTemporaryDirectory(); // 临时目录 + final dir = await getApplicationDocumentsDirectory(); // 临时目录在ios不行 final filePath = '${dir.path}/${DateTime.now().millisecondsSinceEpoch}.m4a'; _voiceFilePath = filePath; _startTime = DateTime.now(); diff --git a/lib/utils/wxsdk.dart b/lib/utils/wxsdk.dart index 0b8f7c7..4119614 100644 --- a/lib/utils/wxsdk.dart +++ b/lib/utils/wxsdk.dart @@ -5,6 +5,7 @@ import 'package:get/get.dart'; import 'package:loopin/IM/controller/im_user_info_controller.dart'; import 'package:loopin/IM/im_service.dart'; import 'package:loopin/api/common_api.dart'; +import 'package:loopin/pages/my/merchant/balance/controller.dart'; import 'package:loopin/service/http.dart'; class Wxsdk { @@ -48,10 +49,28 @@ class Wxsdk { info.customInfo.refresh(); logger.w(serverRes['data']['openId']); } + // } else { logger.w('微信授权失败: ${res.errStr}-类型:${res.state}'); } } + // 支付 + if (res is WeChatPaymentResponse) { + logger.e(res); + if (res.isSuccessful) { + logger.i("微信支付成功"); + // 获取新的数据 + final ctl = Get.find(); + // 刷新记录 + ctl.getData(reset: true); + } else { + if (res.errCode == -2) { + logger.w("用户取消支付"); + } else { + logger.e("微信支付失败: code=${res.errCode}, msg=${res.errStr}"); + } + } + } // 分享 if (res is WeChatShareResponse) { logger.w(res.isSuccessful); @@ -141,4 +160,46 @@ class Wxsdk { ); Fluwx().open(target: miniProgram); } + + /// 微信支付 + /// [appId] 微信开放平台申请的 appId + /// [partnerId] 商户号 + /// [prepayId] 预支付交易会话ID + /// [packageValue] 固定值:Sign=WXPay + /// [nonceStr] 随机字符串 + /// [timestamp] 时间戳(秒级,int 类型) + /// [sign] 签名 + /// [signType] 非必传,默认 MD5 + /// [extData] 业务自定义 + static Future payWithWx({ + required String appId, + required String partnerId, + required String prepayId, + required String packageValue, + required String nonceStr, + required int timestamp, + required String sign, + String? signType, + String? extData, + }) async { + try { + final payParams = Payment( + appId: appId, + partnerId: partnerId, + prepayId: prepayId, + packageValue: packageValue, + nonceStr: nonceStr, + timestamp: timestamp, + sign: sign, + signType: signType, + extData: extData, + ); + logger.e(payParams.arguments); + final result = await fluwx.pay(which: payParams); + + logger.i("调用微信支付结果: $result"); + } catch (e) { + logger.e("调用微信支付失败: $e"); + } + } } diff --git a/pubspec.lock b/pubspec.lock index 3f5e7f6..f185a7a 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -829,6 +829,14 @@ packages: url: "https://pub.flutter-io.cn" source: hosted version: "1.3.0" + lottie: + dependency: "direct main" + description: + name: lottie + sha256: c5fa04a80a620066c15cf19cc44773e19e9b38e989ff23ea32e5903ef1015950 + url: "https://pub.flutter-io.cn" + source: hosted + version: "3.3.1" lpinyin: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index f80a46a..c64eb30 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -99,6 +99,7 @@ dependencies: cached_network_image: ^3.4.1 image_cropper: ^9.1.0 pretty_qr_code: ^3.5.0 + lottie: ^3.3.1 dev_dependencies: flutter_launcher_icons: ^0.13.1 # 使用最新版本 @@ -140,7 +141,8 @@ flutter: - assets/images/emotion/face05/ #update - assets/images/update/rocket.png - + - assets/animation/ +