From 4daef13fddfb0dc608c1dd16b5de2f214f47915b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=8E=8B=E5=AD=90=E8=B4=A4?= Date: Thu, 25 Dec 2025 16:09:08 +0800 Subject: [PATCH] =?UTF-8?q?=E5=AE=8C=E6=88=90=E5=B9=BF=E5=9C=BA=E6=8E=92?= =?UTF-8?q?=E7=89=88?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- assets/images/comment_icon.png | Bin 0 -> 1000 bytes assets/images/img.png | Bin 0 -> 1157 bytes assets/images/like_icon.png | Bin 0 -> 824 bytes assets/images/publish.png | Bin 0 -> 12447 bytes lib/controller/home/home_controller.dart | 21 +- .../home/send_timeline_controller.dart | 107 +++++++ lib/generated/assets.dart | 7 +- lib/pages/home/all_timeline.dart | 156 +++++++++++ lib/pages/home/home_page.dart | 16 +- lib/pages/home/recommend_window.dart | 92 ++++++ lib/pages/home/send_timeline.dart | 263 ++++++++++++++++++ lib/pages/home/timeline_info.dart | 212 ++++++++++++++ lib/pages/home/timeline_item.dart | 119 ++++++++ lib/pages/home/timeline_window.dart | 111 ++++++++ lib/pages/main/main_page.dart | 60 +++- 15 files changed, 1144 insertions(+), 20 deletions(-) create mode 100644 assets/images/comment_icon.png create mode 100644 assets/images/img.png create mode 100644 assets/images/like_icon.png create mode 100644 assets/images/publish.png create mode 100644 lib/controller/home/send_timeline_controller.dart create mode 100644 lib/pages/home/all_timeline.dart create mode 100644 lib/pages/home/recommend_window.dart create mode 100644 lib/pages/home/send_timeline.dart create mode 100644 lib/pages/home/timeline_info.dart create mode 100644 lib/pages/home/timeline_item.dart create mode 100644 lib/pages/home/timeline_window.dart diff --git a/assets/images/comment_icon.png b/assets/images/comment_icon.png new file mode 100644 index 0000000000000000000000000000000000000000..7be1757b7e1c99a1c3dca02ca3f2d0a9badd89bb GIT binary patch literal 1000 zcmV>P)Px#1am@3R0s$N2z&@+hyVZsl1W5CRA_^OFvHtps+wT?aU zA3OGVG5`Yu0|Nv51I9EWA_f2~X;0!1MNx>DhC#xKs6@m`%#AumNGZ=k9zrYOJr-=z z`T;ZwCnAQ3m&v-dA(Z&Csg6_lu8OW8@;pCwU9<`h5G|yXU!%Jg2_hb;pRB1~)5u$e zbIvbaMx~Tzu*Pbd8s8wf#Mgv3Eo5!poo&%HSb3h;bY^BjgXrVFiUt74^ZZ!N#u`La znq`?$f!P{{?vdNV`950)GxJq|+xOXOu#dDI^{jG%f#n@>8ES%v?$2hkd!g-YcINZ> zK}Z`lNWxdI_KsT&d5vf;E-oH(&VL}{_07%Af^+_%ZQCl->GUZP{X)d+tE;QOMD#wS zy={xFY=Hpa7cN3>xLhtz0pPGw^kO=l9=Ynf$g=D;B0f=n4-j#Detv$~*vJoCxrggQ zBoEDkEXy85*m-0YESJj=JHl7NJJkb`wHsvFza)mGOkziqTKIRfV6j;IjIi^~EEtVO zF?O6J6`tq$sg&|90DK03PXO?IJRbj!u=7$%`3?ZS0Ki88cy@Af@+W0qM=6Z~02D>> zCXt<2DeSmNJ=J%HLHJ$c&I(VyxK(}rbq+VF?6^oe;wp%7iHH+KoT%Wy+IDQ`>2>LP zsm;*V(;|cp@q}=MwCPrR2fQq>Eco6oQD( zY>^!G&QHJyNw*0V#{@%sbm6j=obw}a3t@=px>lpBcwZ-rTT{d{gf7F`ovrFcQIu!XOM7VM31?cdw3*%n!!aF(J|UE^6itzO^C=Tqe})*4oySc(NG z(Sd^jxWDcyoLLcV7Pl2oi*pVTuNx|GryVX@WgqhL*J+V)Q7!c~b94zRlT=@{BB2p$+17#JAXBmM)` WEZMmIC=|^A0000gu00001b5ch_0Itp) z=>Px#1am@3R0s$N2z&@+hyVZtFG)l}RCt{2T+5N-Fc5tWlLeRA807niP$m+XNH!FD z?s5)$avItL5+z(lKg(D$ysD`JOSV*xZuQqg@Sy8D2|#MCzW_+z+>L<&fQWvSQtX>Z zQy06gI|G2$`c!yhO~Md)o*Wxr=(_IwCTk9aJNw~HUL3|8?SZSY z&|3dpXG_iD^KdxOFboVB34BXl8no6Y&yoo@mFyefIXRd_;kcJQk(m)gumA%hb5;^5GPkJ2mJ z1cbYRP(a=?iVYOUoKiJWx?X2j1@Ykjd^{1%W+ z;GE=x*7}mWZf(dS(DBr>VUxD4Ba6Ti$p<$eZyC8NEDNn)>N%?+*$Nx=_0F*&P^cJX z#+ycQM;ZXT@pB*`mkX@_ROG0z^bHcx@8}X|tkDdL$HJ~!j>5W4B1eT8CPaGxX>yE+ z$aHhY`c)xW0wcpPiO5WM>iV@I%ZL!sPmP7B@sTlQ<()H{f%%!DECQ8MXCcHa0!t(n zoEDTXT;uHEJP$q(n1z7rubN6;*LBj#{K)iO&O&g# z*^!7jGuTs0Wg_~`QP~#yb&7>@mdFy%GC#(p;2F0Vs;kNwscRf?T2ft$7zt$HTFn|& zxn#+Ww5GZiAp*Vii;{1&wrLY+k%(J-hq2PFx-DZo zHKkv&_+AS_DlDI|iCPhn{NNn|60wyxic%5V-@TjYs=tGIqv%{_TWuh=TBwv_vW+~E z&*phsYXUNI-ZfwV+Gr!I4fcO%Rh18H2)FQa4*-(F>I%6FlPS` XBs7KfbF4`A00000NkvXXu0mjfjmZ-- literal 0 HcmV?d00001 diff --git a/assets/images/like_icon.png b/assets/images/like_icon.png new file mode 100644 index 0000000000000000000000000000000000000000..48aebfcdb516136b3acf380a88cc45d9a63d8437 GIT binary patch literal 824 zcmV-81IPS{P)Px#1am@3R0s$N2z&@+hyVZr;z>k7RA_SH;@ywJ%L`w zud*j&KNdO|10q6O`@W{=#DteXNFeOQIF2oVmWaOXQ0qO`0AP%{DW&udnO6X?GFS3) zWC8%~QGkdpdxTqst|esx(5Z%Q8kea+RGD!cTOzt2khwW=K*YEb3P{Qnm@FSr7DeN> z3^T^uoN$4W3zl)SP$u-u$@JN<@r@0cFvfI}fh*%;hNZZI!!%9zm_j)+W2{RCu8fnE z+M@tdX43<50#C4O8aI|0!1i08mP4V@wBcNw?5;rnBS@!_ax5@qpV>9LReh z&9WYbq03Q#!_D@Q$>NaQ)R+G-PNJIA{EnEW$@ss0;~L;6>tHVxVXQW|d-_Z^e858Q zO?6%c$;Dmlxdo;5A4zISV4iRW1$ zooaxe1w}tmm*NPWY8qgjx)9OjEbIl&qBp=YC%L9qE`Mvp&Z3XiQphRWcV{nD&~kq- z&TafmWM38wp07qqL2I$kNp+KwCH0b!^y1#PFwWK)S4SRO8G9Q@`ba&4FaFXz&${n> zBBItC{?+$A&GW4J`_G20RsLqfS}FC1zZ}mLidTq%K_yXVBl~g1j zR=r*uM@vNaQVWgslEa1pJDBr6UZkxYkiDxdyoKzZK&0oL6^AwA`cT88#7u9rXXO zx+)#iglkehiYEaMRolZ`EY?oItM|So7NtvUHSiAz&NWXOMp5Aa0000hV24XM+$9hoKn@7*u6g>=som|F>DiX)>i%rBhMEEvIypK309ZdRI9Sd{*yLGaL0 zkOt}|DfeD(kUgZ8bkJUo05t0;0H6hwWTkX`^NzmwWl_QW>Vtdsd3ni?@M$GMv-VBj zmy*Me+^`I!rA3Ji25h6{dg(33Pze~aCqDfmSYd2&J#OCgmkE0#ARTgTityn6B}(D7 z#nB3F!xFKg{g;_>VN>&GArIS0@rvq)@n8Fa)O*=;rn;tcb0?Mi$5r)cDl@#UYuEeB z2r?fl*;_M9$|}m|kkY7ef164zgdLVgi-*+VbIa0~%2ff9$?x%Gk@4{q!Yi*{V+jzn z8>TrT`jWe;i_+!Kda4O5cKW+3{kZ)p{ckek@Y^)eH`%tUU8}bxSbbvu2c4j5`PYc|)-{dBQxU;kjo)iSV;>hL|Oc z6KLi9Ku(!5%#DT3=$63xme$d%SnQ|(oz~^?P3joIDYu+>9PPOjk<6=thn2>)F?U53 z>Thswgx0`|`8Hi6ti|acMP&)HXp$Y2RF-KG&@%@t90_<4voZ(x?z>Y zYXIazZSYO9YFPU!p=7`C5}Eb~%p43aS1Lj=mXo`_>DcJ(lIZyIheP)}sRaxx3I!wK z`gORYJOU58@_y=#eh&1^`1K(QR3Ub`ajWuqs8MGsg+b4RW)(8j5L8O8auiq8KYbl5 zL^2Z+gC|(@Y=rHQY`ReM>u!wYAtYNtzlJ%8YtQRlEV`}@)8MTyVgei%guvo zWTU4AY@*-%-_-~LUDa!3u5v}rq`UrCFSG|)pP*EGOO!LmD>Z+AW>QRw=Iu?VClCo@ zVUXlimY}O6&u47~>a7m&NySq1m^K~a!vxLzE?1wjt28PNAe+#KYL=l6c2Bmikv7U! zuy?Ad>JTe5UyYgH-!GFdTJ5To%ihCK+-}0ONnYO?vhw39df*Mcf1#Trg11x@2hjGO zWW2RJ`2;dr4R0-=tnl3B!T*HMH4i7&r+o`t<4WPM{zTNmP^^ub2fl_{Hq(lX0FnkF zdH^S2^w}QOO+Jb_tO6mBd}A*Kx0MsCxwGEXFYp(^&dG2}XHBId2~Bq|_80WcnOF(mP6{gqiY$bsrmJxI_)Nc$yTZ{bFLVk}L9dw)n@??A7wwI{vR z%s8}>gGKo-I!~mg^>3f8pwrh4(}w(znnxndVO6iYIg18# zqPWpSbrCx3IYJOZ`b)^aJIG~Ago2GBB%Dd&c)vyD(z>~|a~`9|)(GQJt((vev_7+$ zLpDd#o&X=2ZxrY(NwVDo&g)g*YFmsaBA8;wJhWsCWBjM|)zt|#wTUzq+U%Wg2{Zf6 z$Ujo{z9}>Ttntd}2i^(&6a~ByR;A#KNx_gJU|9Mwwa8=LeAIA^Xy z1-{FvSWoOn^tsv!%Z_NA!v*-FL?B;nGPE8W2j2F@s`SZ82cU0E=W)T)`1Dq{*0Trb z@IL4ctoRYMJ}uo#uvl)4Mxfe?>da`*!h;Sn%IOjaa+s1lv~N&Z>n!N|R8WLO<#<4K zu8^6tyQm~%*6%0|Xws`K_s?vrRr+IL3B>XM}paxyU;7o#XV z>?$6aNSgw*69((Poi9r^h%y0h_|3#ekmL~wHmDZt;Pt)_=?S+mo`#13vTF{`E`ENS z+Fnc-ldaYRreZyJh(ePloU;{yR+4ZnZrm-RkT}G!j*S{9MEH|XVs8*)eP~>VAeQ8+ zJxYR;gP))Ck5eBWMlN<`>D31pr#7Kti1R4+KX?S1YVk&+U)M{=(0m0%W7;Lv?@U6F zl@_JlvSzHE?k{vIGa8rhpa;;OF-iq>CI9olC{PXwo8@=f@@b+qD-yyiYe-9P{xfJebew#yB zbW((WWu<|)oZauAU@s>@TtnH6AS4rsb#F3E)Ga0|Oc(E&MpxL2qR6P)c?OO!<1YRdgIx&-Vm~^uNk$SuxWF#$Jat z!cqOG)p1SahVz%iCEzVoK8wqds88%NZ&2vQco*w-y6=)HvY0a(-t>dg2IK81?yM`3 zOIstJzcbfi)+xrd-TTw5;u`oNXz;<;1SQ;&me)$|gEqS8UPC2^czzgGU!n7trOo;4nbRKBNidIULh3Dscf%sh$hn^%J$NihNe_FUU zcV!#!pdBvb?ktx|90FG8=dd2cts2_V#x$hESEHQd=h3vv+5=gYiJdnv+R<~}L}}{B z=ey)zI_y@etX!-UV{Et5xHg8PxNve3cR9&$Q=zMw8@pH98DuB~H{CB{L(HD`8=ch5 z;e8z-9`&8W26J2aT{e-TVKIwp;NeD^*0;E>M)^wCwJDBi^Qe&1Ho+{WIX8o=*Cs0ac2VZ7EP7ewM24mQkCmJgBA=u{#V99qE*-*20vxW2|N zHfy7*k9BJg-w9h@e?5Cx!siNb(`FeNvQt}zpt_!d>hbp|>Z*>ux00LEAIq3WkLzrq zJS)FfM_KFLjqxT{al=L8^ZUeleW@K67MFEC+`$`GrFU($o&9QOOqP>p#_r8;$_cKS zmZpqOIwa(Kl^_~}=>9V8f?L|8#k^m`)zF*uSw2}$>+%8~S;;cLw}8L8(+CI=a~8eZ zoXFv5GXHY7!>`Ls=8hd&B&AcTIW^k-Rdz=x=R$J1Vn}Wfil+-bt}+6869&<|DeYki zNPiF)MhV6H0_IdexDt^w9ls6CZl@={;yupd5-n?;0TwI+rZlRoQ^} zb}+TW%0JlK#3t*%o8oT%u5lEa;nrrW^Pib0a?t~*ncfLQhDNW%Vm{~^)qa@D<9Q=7 zp_}*p8gG5c_uoe6z=uf{{k!hF+JX?q`t41u?W!>ZQTnQ-VMb0M0OIa5-oc(^=oucF zM>~45WSi}~kb3gh#!Y?$E-;?iJ?d5d!1jqebE#nnrXb*8^}i0eCe-JSaOHPLPI}4S zS8-$c_tQ7DiPnfDi(bDSssbxj2!v#R8H&pdA+_G@>nxLQGuE@3F^mR?*N0Wr&T!6! z)o3@Z0)D51Kcg?-gqk8ZCv4=<9$24Ez0mhBQI^GPs+~LTbM-1PVwKrEmJY3*c^zbF z*=nG08qo&ILaL@l$3uJ7!no*FOXJ_vn`|-N%dvVqruS9q3s7G3_cG-74%CWls%h%j z2EPXgO{5zB*v3RZ1S(Q6u)8LPIO<5UmY6c>lkbo$ybCu%%Z84oK$`Op8GHoM1LEtx zBTqGJJ0fwqu9coAEtciKrn%`z5DGGJ7;1%L2}ZJLPOn1|$>-j0Qj-u#H8+i1p@8{ei^0tG4Y8j{ecDTTd;ACN}17t=Re=cBL3N_4``p! zx7t=QIr0>wF{IzE?X7;ITu-6#KAt+s+B{(K8oEkVHZfz;kL5XL!=HH8GvX$`NzSzq zlHB}bqC8I+QxHm?5pcewuKu_A@jvq0KX-d6K3FxgxSNXwG5ZAiZ|_1AL#ZM@H#@D$ z>E)1UGA8+sW;}lL2qxunwx2wBGl)|^>2&88Nm-rlS9lc#E#RGSVs!Zt&!d*D)=co3 z6?1DOhpDHtsfdirdAy*r>^EF~X3Lrs`%HKGsvvm3*S@h6)gC(dCYdcyw%5lEtg`rx zFY(;PmE-BIBjnPPEj6z3ASqcT9~O8j-=>&s+xWo$O6%6Jq%$KdS>108feyToM#t>; znoQ*G?9*oF-wTd98E1}seAFiISQw7gzBH-!4(IL2EY^epd0{8l2bX?~T=iFaW<@jT zgNrQ(tW#>jX801f+Zkudl~V)aLki@I31!KI9RTu>mapG(7RAVl$b-qaUktR)9NJFN z?V+F^;9|)yF4%wyG#p4W?(le1q9IvN&OPunbiIlDcc(S0eSj~LNOpet$`3j0hr@L_ zgbADxsaSx)y#6rcBk;6Q;^{Z{rxJ!GypZ`(i3W#1Fp;<+tMPU+|F(aA0EqCY@cyw{ zxfUS+{yn`_z5RQQbkHX_Xj~}xO$owg#eiFX!F8J0;^)N2@OcLXmf=jNSd~A>;Vl{h zWjF7P$cHA9Y*kIsS_9ks2kXw3+HoZyE;$(7H|XA^Gy@o6!K`lekKZ_cLGKigp!z5iO&r zyIMN$hRzs|<npu4mVf+B4NvA)xyuVi!J)7+J8*_Al4?{S(-e#J!>wBa?E%joL4iv;fA52ESW zL55yC5%qLx7Q&H{=DY!~UDAn-qQA1U*e$!ts%7!-!LE)B4c6KO)X#U2&Gt0zuI8{` zpG^9g+89+9cUew1;yFo#F-v#Ik8@z`K*qOdy4%VFH(C!^B~hQG(y>!z>7Zpi>v zzI`*}&SX~yD+9lY8A1khRh_Snp zAq$>-1fvdIxVOwZF8%X`TlAE28fDFnk-da?Taw4$KqL=={eLj_rgFU$j-SDo35u_lG zC6F~?CsJj8S8gd%>#lfu)<`wENcy9hqc$6M*Z#!qpO>Iw^{&BVh66) z5<}JqO*VuJm_UuOipx~5`VHEP=AuCvd}xqR*6Zv1)GLA@b~YR$t%0W>4oD6NtVNnx zN-DO+v&0IAf4;ox_r0uOK{t(0k(Y_1+zZ;$h)HFGYn4ovx;Yv^kVMDzut;minU7S14kg2KU&CYi-BP8MNOcEO6dROsPr`fH@#x}BATkTDU6`W| z9AzLLdCjgP<28E=axBi>nJYE218%dS+-yX#GlTp0*Qg%8ZsEu(Wc&VU2g|(|8%-~m!QzKChWqlA`NP*|uLMsbL@Rw!Act&UZBdW#kr`=OYI6%RVq%WIWB zVKWSRZjMOFou^i_wZ<5i!x43 z{G-b5P0qw7(JG+ueg37OSocgwyz+LPt2c<cH}Z)LwB z^DDbm!4M`<81oV6U}|Ig$~(ra5BKD)3Gg+hDIcXN8blt!S%>ADkD+Iz!fv^hknlBu zI8%1e-aR|fiQV4@vS|8S!`yQ=pUsn>1Ne-J9bQ~l)q;5caY7yBQeoi0`3v`BaLa&z zeo6hOrch|%jb3}$tu3DNLri+haG~9aWu1J-DEf}y1XoJzVftM{D{GmlT^XkU-+;%a z!q*FccAi^3P98C->0pgnQ)0C5GP|fa#XCE_#hz~bX9D%&w=NMK(^#{*nQE~;?MhBZ zk$nZ>+d)$p-aKbSdJ7aU2RRXGvi^8ZrMacwqtF&8baUIOWdBs0=4~4L!U5heZfXGd zN0&}I?SV*M#^DRYrfS1Sl_vexUj`rRj|>p!6WzZ}D?6QqGQaI044spiku;Ik?Sx*H zP;zv3+?JZ!mT|J79sOcLxz?CI?Q|)&KY0aWkx$Ti`CPR2zOInwZym)*Co1t6An)XP z%QWx1c47`=r!r0@`wbe6YQ9c8ozk7c$?cHzpDC|8W2>j%vGrbbFotFYsHE{RD6nxn zW~Beb=U@hFmL}q3ecFZ@n&`WcoL&{DwUkXSe7pV3435@e!_AlZj<02+56RCK7~AOJ zOgvm8>2n7;8`93B!g)%86S%a2Sn?!$-0uha+m3=Ie#bGC9B3)2)@LdG>`IKI3| zGO(ioT20e-+?wD?(?zAYGG_jwhYo@_D%B1`VDb(5b#( z()4lC>W$@%ILZ3IhV&K_Jc7(7S!rC^PPJ;HLfGWMghadqFd6g68~{Y)ZI0QS_4K@4s3HIt9R1OY=`)NzGaqq%mEiXFR?)N*Xj zasNeZKUzN~rs^eRK^cqHS%Lha@z*ceN`|1)uA9l(EGU1FFVRbg_O&%;5AO|W<31k< z(xt*Wt+y(6_K8OD{gm0FN4Hjrz~Jp4Z%Q1=RM$OQ6qG5lN()zw^N`9WfO}_umLlBI z*hDfk-&8A&nF($x9ZDo|Z@W)oUwGdPq7 z8dd0q_i&@M@4hB62?pJk*dG3}V|Ttu$?)z7EO3$^cw1Ig4Vh)-J#*<@s1(Y7PWDp~ zwKICBq~e6Y>N~|)z@W>b>(rPHMHGIYQcq~u!E>9@m-xa`d!28F@S8sDegN$wwG09f zZ2vY$i=U%t25N(Llv5Py5i?GR7FYY+*N5><@kduf$ANep z<|I^pBJGn(=DBJKsdx38&Nc38+pX!1)^mHi1~#HgRLBW_6AaVA$d)r)gb4_114J`J zQKLDN9kw<@D2re)0oU{`!p+f2c0v6&`|(PsRr4aJ_v(kMBuqbi7Ba;!sPOaTh>bMj z-FJ3Dw17il?fHe4D+sJN0RDC7lMzRTH|ynLl42f|9`&y=gp=4@1CugF)-=9W(Ur9* zz&c~fveMIjD*=74L^2oZLaLyOj(7S^Nj$a;P8YZOIHw^*2TZ=-13!JiiwJ`ib!fxlrP+*RFd(2kCd{c&o* zTS3&w1ddd{jYemAI`O_iM<_B2f~;=KCqk(Uq38-m0)Pg=6}4DcN)%$FP%4>1p0~mZ z%-J{LMAK;s$-P;zM2U$b1AVZAH*L5&7PWPXS(&tvRp+5Ag9ZJ>zwR?ktBlH3XITf5ia#_3x%t^fY>^XF$Yn&g@mlXS$CvEo+_SxT6ReBw-v?$ znAV#p9(UP;mo0hVZI43B&3D5>1ISof149+J_h&4#V-gf{;aSh0(}XPA{m5(2yb(0r zQrC!IZ?vGyYOHW7M3O9>I?As!+gpSx`^T>HE&n?03Mig3>DH&7i%9fp;;-v6>N&^h zqj6T(s-JesC~_MvBuQ6rmZq!B3OA@6Q-Rp;J_kpY7$X(&CrXxyQGyTz$tcsbQKB0P zA1@B1?;pKLtM$rob_J@|ly2`LCDeKd=MMK~&TZ8XSbYph#&TC@6R7a|XXCw#oTt!y zvaL%>u1i<)1?+XaK(oxpgiNAw=jjMnCF4KJ%AK4Q5Pm zASx-R>J29Q3j$pqD{)x|euo-n&=@w9Gu?B^+z73ME=@(5l$PVYy>toENJXTqJ-Ii@ ziTT-^8i}&@uMCVv1Qa|nfHEq_b%r@R<~5AtE3#9u{B0+rZvoC2e^slMpzHB4-N|$= z)uW74VevFa`1NjFqcvnG0qsva+i&f;p>Vr*Y!dQ=x8q^vnLciwi!)2*SUiq@CRHDJ zVjjMjpKnI?aJ)jmyzu0sryp3s4VcyL#%I2J_P*MG6oZ&{x1e-fx4i_b6?H%AV>No771F!?hz+ss;&j4?WKZ1!_|tts{h^L_i`Cv-^C03BMy#|dNbcvkkU ztkBQ5szE~hFB04LNhN zlrNlVzksNWwT^?M1M9)m*Rfj@Y;x8=(e_q`C=D8uu(3mRlZ9i1~Oc}si$^H zpDL;v)!WO8c-76NC)4=V{k_3iXXHh0Z)t2ALHibdy0*|ln9!z;Ph-)Q_LpedOW$i~ z36Cms^5(hHGDCTi484`qMyKjdFm^v^-SB%}2A>Zs&^Wo^+UQm7DrbT>7^$>%v9oL- z@wt0*&!q*tCh=UVAl)rWoIiSY-DgZPmF85z93*(MN~5;2{9-$!V{_X)@Xxc{1cF2M zJi>3Wbw}T1mtX+X)Bjv@TJy4mOWH4FQLI0hvX(VB|Dc$lwFsE#NY+rR+kVM)G>Tz5 z3#2HgJ*xG_7(#Q=5pC}|qzml<-%$6iR0Mkcz3~%Ud09N8uTVt6)0N^ z^qP@WpGtD4v0jM)jqVOo04DAA*?&28Z6`Sw@za*3>1{ zl6`(u(~G%VM8mdWo4_UCh`I83p5d34T*}Qo8h(HBkT)+8OAK`E=U?$6iV-GBzhHD9A zZ*j87FNkJsLo@~tOEvh-m}htBYhj|w)&$7%c#nMZYr)2R{jAp2$xuL}EN?yQFAr?7e{jW$U?TvWR((C>+?v$IbPfluDL zVm4ro&p#^ROxxZHLKBb?AL1!ODnd|KvWTIP&vlt}&*-Ba`S=*n? zHcMSz{umXMthTKGv!HM6;D{E(gR!Rfs=TdvS5Od4ZBJ=1V_*VkxTF?T3=B!uTzEWM zGWp>LiuqqNqe{@|o>->~l9w--7y-WLH0n$NEwlHLIR{@MW-CPig{FA5T zVu52GCnn?Y)xQK5(N>c-FvvqmzNknANIRhB253M zLdSrngY(T24&euF+t^JQ`faQJ3AGFRTcF@Y#eX#F=7tM7=9q)JP$hq1{H$q|D1}JiI0*}Ad$Km8X-;Xi#AI&Rjnx2J=nvAS+vXgKGxu_jimtd!yE=`2n zJ8Y0t+-k&1h0scx?NVpwxdyxXTpk%#=991Ym$O0Z8q1^|*DDSmgCMfCh!&5LZhNTb z@=Ul8nmZDgMchqry~Z{0mq9%ye2-VlJl4)g0xe37&mdcVO?5U0+qnrDsc(@rV(bD4 z*{Ib&p>S!RmPG7pf&%@&^RSGX&a^FSw{#0VGxzh}G>)=%yNmdx8aDh4pburD{oFw{ zp&x&tT`-fJgC(WQ!VM|7Cri|hJ==r;)97OeBc#af2H9^K*=?9UeW<=-N_8en`TbzR zVXmFE&CltKCXW3&^8qCDvuBxmLuco;2Lt#2%&cl1_l=nc8j!nsL_-g%Lp0m4|`+~4%hJorOn=)4hxSR;`Qsnko#;p>~|=&ik8;K%#u z#)cB-87HYd6&21gRx#`4f!8zh#g0EwjjP3?R!%V!e+%|_#m`QXsE$peyK0r-py;C- zG)w*`8dnGX$T{#WQL5))Tvc5E@vj3**fR0qOY<`>r|Dp`Dt) zvSDkXfv=Baq~%1FTR&3el86+qonBcDS=#t0f2{$Y4;RIQu%TI@)#`a(wbSw-N9JV5 zuZV&Na-V6hs1Id&;_IHL@$s>*C~z<)<;@0ALZ;p9Y)Un8!gf90jo~Wj&D!_?R!B8D zuJQ@WM}oD6M1%Xf??W`-+ilT;aES!ww6~Y;p^X*#6`1~UZ+7;{$J^RbJFex5LAOM{ zzYCdibGUJ477YF)ERC`ekw9(!u#!zwcN za(akpIe2UAi}O1loTH5FInZWE`=f*{#_Fihp;7wCbi2!c^}29(9okW@{od*m*Y%;- z+o_=mMmI6_r%i{$Ws(uUqrwKSqU$4399j3AM2dB)=UJbG-b*p!m1QY_B0bhFTqzj= z-d*jr`ftR+(ztn}KtO_f>uFxLFKIEoD(K;_6-(Xi){UCXB`e_YTv$2q4*>{4{`@_+ z{|_Ky>&6ni?RhP{A?AMrJ(3uzw|3iNJC-h#z%JJlNHJ_-?RRWt6nt+9jy+~qo1|g z+6Zk+D+X~l`_#wkI^T577)qr)BVP2LH**?vgWIK_@~R1pH%uSsy|dpj_>$81FmD`e zX7@Y3Gv-6+eb2Xf}; zM`oQI4<;c2@^3S%77_k|Zj*5}&%+|Np4jrs#0uT2X9Ge4Fu z!hmQ1zK_{eSjb3tYLqqemm~pWS_o+7x^P!n9Z?zGyqT88k369Q3-^Apl1J>>ri>_q=Du$V)FK&2>zL$@~HD}kZuo_fZ4 zCoLXvT0k_8lsQy_((zM5==Q(Ty-XhrC1j|e@{UOI!a)3tl@cGm11Jk%CNPmI9%4t| zw;{$MB@Qw4zdfKKzpQ@gprRS|pW`98x3M@!d?)<`(m>oNH&n(-sb~9Bzro-xCq}>8 zcw7|PgIQcd%SsLj{o0AI(Mkp&pI0&GE0DpQTRc(KS-i!yqvDsQi#hmSxW3kf9hk|>k7QY98Io66kdJmVJT%di>4&w)Mv?5u{i<4~ybhN11 zf3=jO9Xq75DMJt}W*&z>u(!Uw52twn(g-1hRU_yi$a(e|LY}@T&|~(A402)Mhd_~A z>h4_L7f{<3+rKQ_f2Sb3n2-x8437k0Jp_eSA`cQjhK=bk&!W8`-h!!7M5)}mA{Yk; z&e*^qi~7Mn8hvYX-tw9+zBkt8^*`at3RET+QThU4-dge_qTcE(0Z9e$K`Tix)Wr6p zG)2io#Gt22lA+kWZ1-h0xeH@3e)xfw_bnbt2*x~XA0uL>(fgC*00ZC6U9|NUm831G!7h}WuPTZs^C4XN^ zkjtAWj1BJIb?I-MS19GdknnxrvQod@UA)#@S$UOoz58e!)6tB)3HnBsJ2`9&*t3Gk zxhH@LNrn)*>txpecuC2?rn(7AvyIJjy5{~c!GBgSh zN_{RFYM^Y}FOP6pSZ{q0dtyaZH{dZTMAg)z!4|Cn8sFiFy}yHh@eNm^PatkT&UYb1 zt)LcxQMR=3@@!!5z$`)#1WG)247nCsEUD;fwqT8f4tE>sP3oQ86k@@__0^BI%)_Op z^?lJ4L$D}tG|zlYY$@Mgc#05XSZ$22Xscuxo3RJ6e$@Y_%1F@T6xDzvYYxk+_m^%;MbE!e6W|83M;0S#k5a(l^6q))3H7wn(DNZvZ}neuu0J}yLV7O^br-Fve#Ox3Fmx;OSxe)A#ud-O>JU84+zH* z7J@qaO``#^)pV{4RnrknSV(F%Y~QWfj3HL5jfFOGyTjJ3UTWvvkI@u8>HPFsI2Me( zD*q)4fMbH)KyCNg|KpXT{OlRn1l^@Pb zrDBcS`-gTP0h1E~!9iF8rO6|*)$6qzn1s4NJNx|zS+`~@{_vIbh3&e#*%ugtKKoa7 z00mzVTci*zGJ048*g>}jLK;Ki1o|O~ij)zj>WIR~aN5@+Zjsqg{&l#cA3@QwzPZD$ zKKfi`v~bQfi5{fKgZ!Mc4)xlxy~p(Kv{y2$FCCwV%_Yut4Pwd1pqm*= zYNQG;4^U=T|36^WB2SiISV=~Bl?Vn%;f|pL8F4rm)98gLlCiiGjJf=tyhUnA_k6u` zY&{;>GN>3#U^(nIcz5^Xj1&>YNMkW9zTxyb1A>0v$QS{@xLlUc;Tg;mAJ6Q*20KKJ z`7mugyBPks6aCIujrIbv`%gHkOEU!#yMMB04Il(_o-c^c4?4R05*Y${ra#qVx?X^f zKh^`f{Cu5cnGt!weg#B=Z{CT+nA&8Nt|(Nfk5;lO(bHpy1Tip3Muk0%nWJ`9ShX+= zAgOc6oZwzt5?|2PPZ!g3*?Jsas`l9i_}}40lDu{_Y%qc_k2X2TB|_vcZ5{AHU)R}u zSb3g>oa9;qrEa@W+fx!qtoXSpi5^^vX$(;-a+aX0zt>R+-AiR_Wo%|vdu|Uw87JzvuFw%h3#5F7>Y4rmP^ROY zQ9_NujHRVdiV|Y4K^`)V&ZAr6P!>ectw6YGBKX5*dXV>_&hKU`CE~0${P_##uRGUZ YW$-jezHZ{>Cj&r9PEEE>+9K@#18n)`Q2+n{ literal 0 HcmV?d00001 diff --git a/lib/controller/home/home_controller.dart b/lib/controller/home/home_controller.dart index 07214f8..04e837b 100644 --- a/lib/controller/home/home_controller.dart +++ b/lib/controller/home/home_controller.dart @@ -21,7 +21,11 @@ class HomeController extends GetxController { // 当前标签页索引 final selectedTabIndex = 0.obs; - + + final topTab = 0.obs; + + final timelineTab = 0.obs; + // 分页大小 final pageSize = 10; @@ -268,6 +272,21 @@ class HomeController extends GetxController { update(); } + + void setTopTab(int index) { + print('Setting selected tab index to: $index'); + topTab.value = index; + // 确保UI能够更新 + update(); + } + + void setTimelineTab(int index) { + print('Setting selected tab index to: $index'); + timelineTab.value = index; + // 确保UI能够更新 + update(); + } + /// 获取当前标签页的列表数据 List getFeedListByTab(int tabIndex) { return tabIndex == 0 ? List.from(recommendFeed) : List.from(nearbyFeed); diff --git a/lib/controller/home/send_timeline_controller.dart b/lib/controller/home/send_timeline_controller.dart new file mode 100644 index 0000000..f29a20e --- /dev/null +++ b/lib/controller/home/send_timeline_controller.dart @@ -0,0 +1,107 @@ +import 'package:dating_touchme_app/config/emoji_config.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:get/get.dart'; + +class SendTimelineController extends GetxController { + final title = "".obs; + final message = ''.obs; + final TextEditingController messageController = TextEditingController(); + + final focusNode = FocusNode().obs; + + final isEmojiVisible = false.obs; + + @override + void onInit() { + super.onInit(); + focusNode.value.addListener(() { + if (focusNode.value.hasFocus) { + // 输入框获得焦点(键盘弹起),关闭所有控制面板 + isEmojiVisible.value = false; + } + }); + } + + @override + void onClose() { + super.onClose(); + focusNode.value.dispose(); + } + + + void toggleEmojiPanel() { + isEmojiVisible.value = !isEmojiVisible.value; + FocusManager.instance.primaryFocus?.unfocus(); + } + + void handleEmojiSelected(EmojiItem emoji) { + // 将表情添加到输入框 + final currentText = messageController.text; + final emojiText = '[emoji:${emoji.id}]'; + messageController.text = currentText + emojiText; + // 将光标移到末尾 + messageController.selection = TextSelection.fromPosition( + TextPosition(offset: messageController.text.length), + ); + } + + + /// 构建输入框内容(文本+表情) + List buildInputContentWidgets() { + final List widgets = []; + final text = messageController.value.text; + final RegExp emojiRegex = RegExp(r'\[emoji:(\d+)\]'); + + int lastMatchEnd = 0; + final matches = emojiRegex.allMatches(text); + + for (final match in matches) { + // 添加表情之前的文本 + if (match.start > lastMatchEnd) { + final textPart = text.substring(lastMatchEnd, match.start); + widgets.add( + Text( + textPart, + style: TextStyle(fontSize: 14.sp, color: Colors.black), + ), + ); + } + + // 添加表情图片 + final emojiId = match.group(1); + if (emojiId != null) { + final emoji = EmojiConfig.getEmojiById(emojiId); + if (emoji != null) { + widgets.add( + Padding( + padding: EdgeInsets.symmetric(horizontal: 2.w), + child: Image.asset( + emoji.path, + width: 24.w, + height: 24.w, + fit: BoxFit.contain, + ), + ), + ); + } + } + + lastMatchEnd = match.end; + } + + // 添加剩余的文本 + if (lastMatchEnd < text.length) { + final textPart = text.substring(lastMatchEnd); + widgets.add( + Text( + textPart, + style: TextStyle(fontSize: 14.sp, color: Colors.black), + ), + ); + } + + return widgets; + } + +} \ No newline at end of file diff --git a/lib/generated/assets.dart b/lib/generated/assets.dart index ee639aa..486cd9e 100644 --- a/lib/generated/assets.dart +++ b/lib/generated/assets.dart @@ -94,6 +94,7 @@ class Assets { static const String imagesChatUserBgBottom = 'assets/images/chat_user_bg_bottom.png'; static const String imagesCheck = 'assets/images/check.png'; static const String imagesCloseArrow = 'assets/images/close_arrow.png'; + static const String imagesCommentIcon = 'assets/images/comment_icon.png'; static const String imagesCustomer = 'assets/images/customer.png'; static const String imagesDiscoverNol = 'assets/images/discover_nol.png'; static const String imagesDiscoverPre = 'assets/images/discover_pre.png'; @@ -124,8 +125,11 @@ class Assets { static const String imagesHiIcon = 'assets/images/hi_icon.png'; static const String imagesHomeNol = 'assets/images/home_nol.png'; static const String imagesHomePre = 'assets/images/home_pre.png'; + static const String imagesImCoinIcon = 'assets/images/im_coin_icon.png'; + static const String imagesImg = 'assets/images/img.png'; static const String imagesInformationBg = 'assets/images/information_bg.png'; static const String imagesLastMsgIcon = 'assets/images/last_msg_icon.png'; + static const String imagesLikeIcon = 'assets/images/like_icon.png'; static const String imagesLimitTime = 'assets/images/limit_time.png'; static const String imagesLiveIcon = 'assets/images/live_icon.png'; static const String imagesLocationIcon = 'assets/images/location_icon.png'; @@ -163,6 +167,7 @@ class Assets { static const String imagesPlatVoiceMessageSelf = 'assets/images/plat_voice_message_self.png'; static const String imagesPlayIcon = 'assets/images/play_icon.png'; static const String imagesPlayer = 'assets/images/player.png'; + static const String imagesPublish = 'assets/images/publish.png'; static const String imagesQuestionIcon = 'assets/images/question_icon.png'; static const String imagesRealChecked = 'assets/images/real_checked.png'; static const String imagesRealName = 'assets/images/real_name.png'; @@ -221,5 +226,5 @@ class Assets { static const String imagesWallet = 'assets/images/wallet.png'; static const String imagesWechatPay = 'assets/images/wechat_pay.png'; static const String imagesWomenIcon = 'assets/images/women_icon.png'; - static const String imagesImCoinIcon = 'assets/images/im_coin_icon.png'; + } diff --git a/lib/pages/home/all_timeline.dart b/lib/pages/home/all_timeline.dart new file mode 100644 index 0000000..fe6fd47 --- /dev/null +++ b/lib/pages/home/all_timeline.dart @@ -0,0 +1,156 @@ +import 'package:dating_touchme_app/pages/home/timeline_item.dart'; +import 'package:easy_refresh/easy_refresh.dart'; +import 'package:flutter/material.dart'; +import 'package:get/get.dart'; +import 'package:dating_touchme_app/controller/home/home_controller.dart'; +import 'package:dating_touchme_app/pages/home/content_card.dart'; + +/// 推荐列表 Tab +class AllTimeline extends StatefulWidget { + const AllTimeline({super.key}); + + @override + State createState() => _AllTimelineState(); +} + +class _AllTimelineState extends State + with AutomaticKeepAliveClientMixin { + final HomeController controller = Get.find(); + late final EasyRefreshController _refreshController; + + @override + void initState() { + super.initState(); + _refreshController = EasyRefreshController(controlFinishRefresh: true, controlFinishLoad: true); + } + + @override + void dispose() { + _refreshController.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + super.build(context); + // 获取底部安全区域高度和 tabbar 高度(约64) + final bottomPadding = MediaQuery.of(context).padding.bottom; + final tabBarHeight = 64.0; + final totalBottomPadding = bottomPadding + tabBarHeight; + return Obx(() { + if (controller.recommendIsLoading.value && controller.recommendFeed.isEmpty) { + return Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + CircularProgressIndicator(), + SizedBox(height: 16), + Text('加载数据中...'), + ], + ), + ); + } + return EasyRefresh( + controller: _refreshController, + header: const ClassicHeader( + dragText: '下拉刷新', + armedText: '释放刷新', + readyText: '刷新中...', + processingText: '刷新中...', + processedText: '刷新完成', + failedText: '刷新失败', + noMoreText: '没有更多数据', + showMessage: false + ), + footer: ClassicFooter( + dragText: '上拉加载', + armedText: '释放加载', + readyText: '加载中...', + processingText: '加载中...', + processedText: '加载完成', + failedText: '加载失败', + noMoreText: '没有更多数据', + showMessage: false + ), + // 下拉刷新 + onRefresh: () async { + print('推荐列表下拉刷新被触发'); + try { + await controller.refreshRecommendData(); + print( '推荐列表刷新完成, hasMore: $controller.recommendHasMore.value'); + _refreshController.finishRefresh(); + _refreshController.resetFooter(); + } catch (e) { + print('推荐列表刷新失败: $e'); + _refreshController.finishRefresh(IndicatorResult.fail); + } + }, + // 上拉加载更多 + onLoad: () async { + print('推荐列表上拉加载被触发, hasMore: $controller.recommendHasMore.value'); + try { + await controller.loadRecommendMoreData(); + // 完成加载,根据是否有更多数据决定 + if (controller.recommendHasMore.value) { + _refreshController.finishLoad(IndicatorResult.success); + print('推荐列表加载更多成功'); + } else { + _refreshController.finishLoad(IndicatorResult.noMore); + print('推荐列表没有更多数据了'); + } + } catch (e) { + print('推荐列表加载更多失败: $e'); + _refreshController.finishLoad(IndicatorResult.fail); + } + }, + child: ListView.separated( + // 关键:始终允许滚动,即使内容不足 + // 移除顶部 padding,让刷新指示器可以正确显示在 AppBar 下方 + padding: EdgeInsets.only(left: 12, right: 12), + itemBuilder: (context, index) { + // 空数据状态 + if (controller.recommendFeed.isEmpty && index == 0) { + // 使用足够的高度确保可以滚动 + if (controller.recommendIsLoading.value) { + return Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + CircularProgressIndicator(), + SizedBox(height: 16), + Text('加载数据中...'), + ], + ), + ); + } else { + return Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text('暂无数据'), + ], + ), + ); + } + } + // 数据项 + // final item = controller.recommendFeed[index]; + return TimelineItem(); + }, + separatorBuilder: (context, index) { + // 空状态或加载状态时不显示分隔符 + if (controller.recommendFeed.isEmpty) { + return const SizedBox.shrink(); + } + return const SizedBox(height: 12); + }, + // 至少显示一个 item(用于显示加载或空状态) + itemCount: 10, + ) + ); + }); + } + + @override + bool get wantKeepAlive => true; +} diff --git a/lib/pages/home/home_page.dart b/lib/pages/home/home_page.dart index 83b67eb..63f94b9 100644 --- a/lib/pages/home/home_page.dart +++ b/lib/pages/home/home_page.dart @@ -1,4 +1,6 @@ import 'package:dating_touchme_app/generated/assets.dart'; +import 'package:dating_touchme_app/pages/home/recommend_window.dart'; +import 'package:dating_touchme_app/pages/home/timeline_window.dart'; import 'package:flutter/material.dart'; import 'package:get/get.dart'; import 'package:dating_touchme_app/controller/home/home_controller.dart'; @@ -43,12 +45,12 @@ class _HomePageState extends State body: Obx(() { // 使用 IndexedStack 保持两个列表的状态,根据当前选中的标签显示对应的列表 return IndexedStack( - index: controller.selectedTabIndex.value, + index: controller.topTab.value, children: const [ // 推荐列表 - RecommendTab(), + RecommendWindow(), // 同城列表 - NearbyTab(), + TimelineWindow(), ], ); }), @@ -72,7 +74,7 @@ class _HomePageState extends State children: [ _buildTabButton(title: '推荐', index: 0, controller: controller), const SizedBox(width: 28), - _buildTabButton(title: '同城', index: 1, controller: controller), + _buildTabButton(title: '广场', index: 1, controller: controller), ], ), bottom: const PreferredSize( @@ -87,12 +89,12 @@ class _HomePageState extends State required int index, required HomeController controller, }) { - final bool selected = controller.selectedTabIndex.value == index; + final bool selected = controller.topTab.value == index; return GestureDetector( onTap: () { print('Tab $index clicked'); - if (controller.selectedTabIndex.value != index) { - controller.setSelectedTabIndex(index); + if (controller.topTab.value != index) { + controller.setTopTab(index); // 确保状态更新后刷新UI controller.update(); } diff --git a/lib/pages/home/recommend_window.dart b/lib/pages/home/recommend_window.dart new file mode 100644 index 0000000..5fb45ee --- /dev/null +++ b/lib/pages/home/recommend_window.dart @@ -0,0 +1,92 @@ +import 'package:dating_touchme_app/controller/home/home_controller.dart'; +import 'package:dating_touchme_app/pages/home/nearby_tab.dart'; +import 'package:dating_touchme_app/pages/home/recommend_tab.dart'; +import 'package:flutter/material.dart'; +import 'package:get/get.dart'; +import 'package:tdesign_flutter/tdesign_flutter.dart'; + +class RecommendWindow extends StatefulWidget { + const RecommendWindow({super.key}); + + @override + State createState() => _RecommendWindowState(); +} + +class _RecommendWindowState extends State with AutomaticKeepAliveClientMixin, SingleTickerProviderStateMixin { + + + late TabController tabController; + final HomeController controller = Get.find(); + + @override + void initState() { + super.initState(); + + tabController = TabController(length: 2, vsync: this); + } + + @override + Widget build(BuildContext context) { + super.build(context); + return Column( + children: [ + TDTabBar( + tabs: [ + TDTab( + child: Padding( + padding: EdgeInsets.only(right: 16, left: 16), + child: Text('推荐'), + ), + ), + TDTab( + child: Padding( + padding: EdgeInsets.only(right: 16, left: 16), + child: Text('同城'), + ), + ), + ], + backgroundColor: Colors.transparent, + labelPadding: const EdgeInsets.only(right: 4, top: 10, bottom: 10, left: 4), + selectedBgColor: const Color.fromRGBO(108, 105, 244, 1), + unSelectedBgColor: Colors.transparent, + labelColor: Colors.white, + dividerHeight: 0, + tabAlignment: TabAlignment.start, + outlineType: TDTabBarOutlineType.capsule, + controller: tabController, + showIndicator: false, + isScrollable: true, + onTap: (index) async { + print('相亲页面 Tab: $index'); + if (controller.selectedTabIndex.value != index) { + controller.setSelectedTabIndex(index); + // 确保状态更新后刷新UI + controller.update(); + } + }, + ), + + Expanded( + child: Obx(() { + // 使用 IndexedStack 保持两个列表的状态,根据当前选中的标签显示对应的列表 + return IndexedStack( + index: controller.selectedTabIndex.value, + children: const [ + // 推荐列表 + RecommendTab(), + // 同城列表 + NearbyTab(), + ], + ); + }), + ), + ], + ); + } + + + @override + bool get wantKeepAlive => true; +} + + diff --git a/lib/pages/home/send_timeline.dart b/lib/pages/home/send_timeline.dart new file mode 100644 index 0000000..1060226 --- /dev/null +++ b/lib/pages/home/send_timeline.dart @@ -0,0 +1,263 @@ +import 'package:dating_touchme_app/components/page_appbar.dart'; +import 'package:dating_touchme_app/config/emoji_config.dart'; +import 'package:dating_touchme_app/controller/home/send_timeline_controller.dart'; +import 'package:dating_touchme_app/extension/ex_widget.dart'; +import 'package:dating_touchme_app/generated/assets.dart'; +import 'package:dating_touchme_app/widget/emoji_panel.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:get/get.dart'; + +class SendTimeline extends StatefulWidget { + const SendTimeline({super.key}); + + @override + State createState() => _SendTimelineState(); +} + +class _SendTimelineState extends State { + + String title = ""; + String message = ''; + final TextEditingController messageController = TextEditingController(); + + final FocusNode focusNode = FocusNode(); + + bool isEmojiVisible = false; + + @override + void initState() { + super.initState(); + focusNode.addListener(() { + if (focusNode.hasFocus) { + // 输入框获得焦点(键盘弹起),关闭所有控制面板 + isEmojiVisible = false; + setState(() { + + }); + } + }); + } + + @override + void dispose() { + super.dispose(); + focusNode.dispose(); + } + + + + void toggleEmojiPanel() { + isEmojiVisible = !isEmojiVisible; + FocusManager.instance.primaryFocus?.unfocus(); + setState(() { + + }); + } + + void handleEmojiSelected(EmojiItem emoji) { + // 将表情添加到输入框 + final currentText = messageController.text; + final emojiText = '[emoji:${emoji.id}]'; + messageController.text = currentText + emojiText; + // 将光标移到末尾 + messageController.selection = TextSelection.fromPosition( + TextPosition(offset: messageController.text.length), + ); + setState(() {}); // 刷新显示 + } + + + /// 构建输入框内容(文本+表情) + List buildInputContentWidgets() { + final List widgets = []; + final text = messageController.value.text; + final RegExp emojiRegex = RegExp(r'\[emoji:(\d+)\]'); + + int lastMatchEnd = 0; + final matches = emojiRegex.allMatches(text); + + for (final match in matches) { + // 添加表情之前的文本 + if (match.start > lastMatchEnd) { + final textPart = text.substring(lastMatchEnd, match.start); + widgets.add( + Text( + textPart, + style: TextStyle(fontSize: 14.sp, color: Colors.black), + ), + ); + } + + // 添加表情图片 + final emojiId = match.group(1); + if (emojiId != null) { + final emoji = EmojiConfig.getEmojiById(emojiId); + if (emoji != null) { + widgets.add( + Padding( + padding: EdgeInsets.symmetric(horizontal: 2.w), + child: Image.asset( + emoji.path, + width: 24.w, + height: 24.w, + fit: BoxFit.contain, + ), + ), + ); + } + } + + lastMatchEnd = match.end; + } + + // 添加剩余的文本 + if (lastMatchEnd < text.length) { + final textPart = text.substring(lastMatchEnd); + widgets.add( + Text( + textPart, + style: TextStyle(fontSize: 14.sp, color: Colors.black), + ), + ); + } + + return widgets; + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: PageAppbar(title: "", right: Container( + width: 53.w, + height: 26.w, + margin: EdgeInsets.only(right: 17.w), + decoration: BoxDecoration( + borderRadius: BorderRadius.all(Radius.circular(8.w)), + color: const Color.fromRGBO(117, 98, 249, 1) + ), + child: Center( + child: Text( + "发送", + style: TextStyle( + fontSize: 13.w, + color: Colors.white + ), + ), + ), + ),), + body: Container( + padding: EdgeInsets.symmetric(horizontal: 17.w, vertical: 10.w), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Expanded( + child: Stack( + children: [ + TextField( + controller: messageController, + + focusNode: focusNode, + minLines: 1, + maxLines: null, // 关键 + style: TextStyle( + fontSize: 14.sp, + color: messageController.text.contains('[emoji:') + ? Colors.transparent + : Colors.black, + ), + decoration: InputDecoration( + contentPadding: EdgeInsets.symmetric( + vertical: 0, + horizontal: 0 + ), + hintText: "分享你的日常,让缘分更早来临~", + hintStyle: TextStyle( + fontSize: 14.sp, + color: Colors.grey, + ), + border: const OutlineInputBorder( + borderSide: BorderSide.none, // 这将移除边框 // 可选:设置圆角 + ), + // 如果你希望聚焦时和未聚焦时都没有边框,也可以设置 focusedBorder 和 enabledBorder + focusedBorder: const OutlineInputBorder( + borderSide: BorderSide.none, + borderRadius: BorderRadius.all(Radius.circular(8.0)), + ), + enabledBorder: const OutlineInputBorder( + borderSide: BorderSide.none, + borderRadius: BorderRadius.all(Radius.circular(8.0)), + ), + ), + onChanged: (value){ + setState(() { + + }); + }, + ), + if (messageController.text.contains('[emoji:')) + Positioned.fill( + child: IgnorePointer( + child: SingleChildScrollView( + child: Wrap( + children: buildInputContentWidgets(), + ), + ), + ), + ), + ], + ), + ), + Container( + width: 98.w, + height: 26.w, + margin: EdgeInsets.symmetric(vertical: 16.w), + decoration: BoxDecoration( + borderRadius: BorderRadius.all(Radius.circular(26.w)), + color: const Color.fromRGBO(245, 245, 245, 1) + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Icon( + Icons.location_on, + size: 16.w, + color: const Color.fromRGBO(51, 51, 51, 1), + ), + SizedBox(width: 5.w,), + Text( + "开启定位", + style: TextStyle( + fontSize: 12.w, + fontWeight: FontWeight.w500 + ), + ) + ], + ), + ), + Row( + children: [ + Image.asset( + Assets.imagesImg, + width: 20.w, + ), + SizedBox(width: 25.w,), + Image.asset( + Assets.imagesEmoji, + width: 18.w, + ).onTap(toggleEmojiPanel) + ], + ), + + // 表情面板 + EmojiPanel( + isVisible: isEmojiVisible, + onEmojiSelected: handleEmojiSelected, + ), + ], + ), + ), + ); + } +} + diff --git a/lib/pages/home/timeline_info.dart b/lib/pages/home/timeline_info.dart new file mode 100644 index 0000000..fe282b4 --- /dev/null +++ b/lib/pages/home/timeline_info.dart @@ -0,0 +1,212 @@ +import 'package:dating_touchme_app/components/page_appbar.dart'; +import 'package:dating_touchme_app/generated/assets.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; + +class TimelineInfo extends StatefulWidget { + const TimelineInfo({super.key}); + + @override + State createState() => _TimelineInfoState(); +} + +class _TimelineInfoState extends State { + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: PageAppbar(title: "详情"), + body: SingleChildScrollView( + child: Container( + padding: EdgeInsets.symmetric( + horizontal: 16.w, + vertical: 10.w + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Row( + children: [ + Image.asset( + Assets.imagesUserAvatar, + width: 40.w, + height: 40.w, + ), + SizedBox(width: 8.w,), + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + "刘美玲", + style: TextStyle( + fontSize: 13.w, + fontWeight: FontWeight.w500 + ), + ), + Text( + "15:16", + style: TextStyle( + fontSize: 11.w, + color: const Color.fromRGBO(51, 51, 51, .6), + fontWeight: FontWeight.w500 + ), + ) + ], + ) + ], + ), + Icon( + Icons.keyboard_control, + size: 15.w, + color: const Color.fromRGBO(51, 51, 51, 1), + ) + ], + ), + Container( + margin: EdgeInsets.symmetric(vertical: 11.w), + child: Text( + "你总顾及别人,那谁来顾及你。你总顾及别人,那谁来顾及你。你总顾及别人,那谁来顾及你。你总顾及别人,那谁来顾及你。" + ), + ), + Image.asset( + Assets.imagesRoseBanner, + width: 343.w, + ), + SizedBox(height: 15.w,), + Row( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + Row( + children: [ + Image.asset( + Assets.imagesLikeIcon, + width: 14.w, + height: 12.w, + ), + SizedBox(width: 6.w,), + Text( + "47", + style: TextStyle( + fontSize: 11.w, + color: const Color.fromRGBO(144, 144, 144, .6) + ), + ) + ], + ), + SizedBox(width: 33.w,), + Row( + children: [ + Image.asset( + Assets.imagesCommentIcon, + width: 15.w, + height: 15.w, + ), + SizedBox(width: 6.w,), + Text( + "23", + style: TextStyle( + fontSize: 11.w, + color: const Color.fromRGBO(144, 144, 144, .6) + ), + ) + ], + ), + ], + ), + SizedBox(height: 18.w,), + Text( + "全部评论(23)", + style: TextStyle( + fontSize: 13.w, + color: const Color.fromRGBO(144, 144, 144, 1), + fontWeight: FontWeight.w500 + ), + ), + SizedBox(height: 20.w,), + CommentItem(), + CommentItem(), + CommentItem(), + CommentItem(), + CommentItem(), + CommentItem(), + CommentItem(), + ], + ), + ), + ), + ); + } +} + +class CommentItem extends StatefulWidget { + const CommentItem({super.key}); + + @override + State createState() => _CommentItemState(); +} + +class _CommentItemState extends State { + @override + Widget build(BuildContext context) { + return Container( + margin: EdgeInsets.only(bottom: 20.w), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Expanded( + child: Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Image.asset( + Assets.imagesUserAvatar, + width: 40.w, + height: 40.w, + ), + SizedBox(width: 8.w,), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + "刘美玲", + style: TextStyle( + fontSize: 13.w, + color: const Color.fromRGBO(144, 144, 144, 1), + ), + ), + SizedBox(height: 5.w,), + SizedBox( + child: Text( + "看起来还不错!看起来还不错!看起来还不错!看起来还不错!看起来还不错!看起来还不错!看起来还不错!看起来还不错!", + style: TextStyle( + fontSize: 13.w, + fontWeight: FontWeight.w500 + ), + ), + ), + SizedBox(height: 5.w,), + Text( + "15:16·回复", + style: TextStyle( + fontSize: 11.w, + color: const Color.fromRGBO(51, 51, 51, .6), + ), + ), + ], + ), + ) + ], + ), + ), + Image.asset( + Assets.imagesLikeIcon, + width: 14.w, + ) + ], + ), + ); + } +} + diff --git a/lib/pages/home/timeline_item.dart b/lib/pages/home/timeline_item.dart new file mode 100644 index 0000000..67c5455 --- /dev/null +++ b/lib/pages/home/timeline_item.dart @@ -0,0 +1,119 @@ +import 'package:dating_touchme_app/extension/ex_widget.dart'; +import 'package:dating_touchme_app/generated/assets.dart'; +import 'package:dating_touchme_app/pages/home/timeline_info.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:get/get.dart'; + +class TimelineItem extends StatefulWidget { + const TimelineItem({super.key}); + + @override + State createState() => _TimelineItemState(); +} + +class _TimelineItemState extends State { + @override + Widget build(BuildContext context) { + return Container( + margin: EdgeInsets.only(bottom: 15.w), + child: Column( + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Row( + children: [ + Image.asset( + Assets.imagesUserAvatar, + width: 40.w, + height: 40.w, + ), + SizedBox(width: 8.w,), + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + "刘美玲", + style: TextStyle( + fontSize: 13.w, + fontWeight: FontWeight.w500 + ), + ), + Text( + "15:16", + style: TextStyle( + fontSize: 11.w, + color: const Color.fromRGBO(51, 51, 51, .6), + fontWeight: FontWeight.w500 + ), + ) + ], + ) + ], + ), + Icon( + Icons.keyboard_control, + size: 15.w, + color: const Color.fromRGBO(51, 51, 51, 1), + ) + ], + ), + Container( + margin: EdgeInsets.symmetric(vertical: 11.w), + child: Text( + "你总顾及别人,那谁来顾及你。你总顾及别人,那谁来顾及你。你总顾及别人,那谁来顾及你。你总顾及别人,那谁来顾及你。" + ), + ), + Image.asset( + Assets.imagesRoseBanner, + width: 375.w - 24, + ), + SizedBox(height: 14.w,), + Row( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + Row( + children: [ + Image.asset( + Assets.imagesLikeIcon, + width: 14.w, + height: 12.w, + ), + SizedBox(width: 6.w,), + Text( + "47", + style: TextStyle( + fontSize: 11.w, + color: const Color.fromRGBO(144, 144, 144, .6) + ), + ) + ], + ), + SizedBox(width: 33.w,), + Row( + children: [ + Image.asset( + Assets.imagesCommentIcon, + width: 15.w, + height: 15.w, + ), + SizedBox(width: 6.w,), + Text( + "23", + style: TextStyle( + fontSize: 11.w, + color: const Color.fromRGBO(144, 144, 144, .6) + ), + ) + ], + ), + ], + ) + ], + ), + ).onTap((){ + Get.to(() => TimelineInfo()); + }); + } +} diff --git a/lib/pages/home/timeline_window.dart b/lib/pages/home/timeline_window.dart new file mode 100644 index 0000000..bd86ebd --- /dev/null +++ b/lib/pages/home/timeline_window.dart @@ -0,0 +1,111 @@ +import 'package:dating_touchme_app/controller/home/home_controller.dart'; +import 'package:dating_touchme_app/pages/home/all_timeline.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:get/get.dart'; +import 'package:tdesign_flutter/tdesign_flutter.dart'; + +class TimelineWindow extends StatefulWidget { + const TimelineWindow({super.key}); + + @override + State createState() => _TimelineWindowState(); +} + +class _TimelineWindowState extends State with AutomaticKeepAliveClientMixin, SingleTickerProviderStateMixin { + + + late TabController tabController; + final HomeController controller = Get.find(); + + @override + void initState() { + super.initState(); + + tabController = TabController(length: 2, vsync: this); + } + + @override + Widget build(BuildContext context) { + super.build(context); + return Column( + children: [ + Container( + height: 25.w, + color: const Color.fromRGBO(128, 91, 253, 1), + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Icon( + Icons.location_on, + size: 14.w, + color: Colors.white, + ), + SizedBox(width: 7.w,), + Text( + "点击开启定位,即可查看附近的人", + style: TextStyle( + fontSize: 12.w, + color: Colors.white, + fontWeight: FontWeight.w500 + ), + ) + ], + ), + ), + TDTabBar( + tabs: [ + TDTab( + child: Padding( + padding: EdgeInsets.only(right: 16, left: 16), + child: Text('全部'), + ), + ), + TDTab( + child: Padding( + padding: EdgeInsets.only(right: 16, left: 16), + child: Text('好友动态'), + ), + ), + ], + backgroundColor: Colors.transparent, + labelPadding: const EdgeInsets.only(right: 4, top: 10, bottom: 10, left: 4), + selectedBgColor: const Color.fromRGBO(108, 105, 244, 1), + unSelectedBgColor: Colors.transparent, + labelColor: Colors.white, + dividerHeight: 0, + tabAlignment: TabAlignment.start, + outlineType: TDTabBarOutlineType.capsule, + controller: tabController, + showIndicator: false, + isScrollable: true, + onTap: (index) async { + print('相亲页面 Tab: $index'); + if (controller.timelineTab.value != index) { + controller.setTimelineTab(index); + // 确保状态更新后刷新UI + controller.update(); + } + }, + ), + Expanded( + child: Obx(() { + // 使用 IndexedStack 保持两个列表的状态,根据当前选中的标签显示对应的列表 + return IndexedStack( + index: controller.timelineTab.value, + children: const [ + // 推荐列表 + AllTimeline(), + // 同城列表 + ], + ); + }), + ), + ], + ); + } + + + @override + bool get wantKeepAlive => true; +} diff --git a/lib/pages/main/main_page.dart b/lib/pages/main/main_page.dart index 957e777..0fbb51f 100644 --- a/lib/pages/main/main_page.dart +++ b/lib/pages/main/main_page.dart @@ -1,4 +1,6 @@ +import 'package:dating_touchme_app/extension/ex_widget.dart'; import 'package:dating_touchme_app/generated/assets.dart'; +import 'package:dating_touchme_app/pages/home/send_timeline.dart'; import 'package:dating_touchme_app/pages/message/message_page.dart'; import 'package:dating_touchme_app/pages/mine/mine_page.dart'; import 'package:dating_touchme_app/rtc/rtm_manager.dart'; @@ -85,17 +87,53 @@ class _MainPageState extends State { minePage, ], ), - bottomNavigationBar: TDBottomTabBar( - currentIndex: currentIndex, - TDBottomTabBarBasicType.iconText, - componentType: TDBottomTabBarComponentType.normal, - useVerticalDivider: false, - navigationTabs: [ - tabItem('首页', Assets.imagesHomePre, Assets.imagesHomeNol, 0), - // tabItem('找对象', Assets.imagesDiscoverPre, Assets.imagesDiscoverNol, 1), - tabItem('消息', Assets.imagesMessagePre, Assets.imagesMessageNol, 1), - tabItem('我的', Assets.imagesMinePre, Assets.imagesMineNol, 2), - ] + bottomNavigationBar: Stack( + clipBehavior: Clip.none, + children: [ + TDBottomTabBar( + currentIndex: currentIndex, + TDBottomTabBarBasicType.iconText, + componentType: TDBottomTabBarComponentType.normal, + useVerticalDivider: false, + navigationTabs: [ + tabItem('首页', Assets.imagesHomePre, Assets.imagesHomeNol, 0), + // tabItem('找对象', Assets.imagesDiscoverPre, Assets.imagesDiscoverNol, 1), + TDBottomTabBarTabConfig( + tabText: "", + selectedIcon: Icon(Icons.add, size: 25, color: Colors.transparent,), + unselectedIcon: Icon(Icons.add, size: 25, color: Colors.transparent,), + selectTabTextStyle: TextStyle(color: Color(0xFFED4AC3)), + unselectTabTextStyle: TextStyle(color: Color(0xFF999999)), + onTap: () { + Get.to(() => SendTimeline()); + }, + ), + tabItem('消息', Assets.imagesMessagePre, Assets.imagesMessageNol, 1), + tabItem('我的', Assets.imagesMinePre, Assets.imagesMineNol, 2), + ] + ), + Positioned( + left: 110, + top: -10, + child: Image.asset( + Assets.imagesPublish, + width: 50, + height: 50, + ).onTap(() { + Get.to(() => SendTimeline()); + }), + ), + Positioned( + left: 89.5, + bottom: 0, + child: SizedBox( + width: 89.5, + height: 55.5, + ).onTap(() { + Get.to(() => SendTimeline()); + }), + ) + ], ) ), );