From 74e47971ca1353e93a64636d32feba9232e35528 Mon Sep 17 00:00:00 2001 From: spasolreisa Date: Sun, 19 Apr 2026 03:18:42 +0800 Subject: [PATCH] =?UTF-8?q?0419=200318=20=E6=9B=B4=E6=96=B04?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- images/UI_GAM_Gauge_DXScoreIcon_01.png | Bin 0 -> 948 bytes images/UI_GAM_Gauge_DXScoreIcon_02.png | Bin 0 -> 1589 bytes images/UI_GAM_Gauge_DXScoreIcon_03.png | Bin 0 -> 2202 bytes images/UI_GAM_Gauge_DXScoreIcon_04.png | Bin 0 -> 2756 bytes images/UI_GAM_Gauge_DXScoreIcon_05.png | Bin 0 -> 2829 bytes images/UI_MSS_MBase_Icon_AP.png | Bin 0 -> 8410 bytes images/UI_MSS_MBase_Icon_APp.png | Bin 0 -> 8685 bytes images/UI_MSS_MBase_Icon_FC.png | Bin 0 -> 8992 bytes images/UI_MSS_MBase_Icon_FCp.png | Bin 0 -> 9198 bytes images/UI_MSS_MBase_Icon_FS.png | Bin 0 -> 8795 bytes images/UI_MSS_MBase_Icon_FSD.png | Bin 0 -> 9206 bytes images/UI_MSS_MBase_Icon_FSDp.png | Bin 0 -> 9607 bytes images/UI_MSS_MBase_Icon_FSp.png | Bin 0 -> 8991 bytes images/UI_MSS_MBase_Icon_Sync.png | Bin 0 -> 8018 bytes lib/pages/home/home_page.dart | 4 +- lib/pages/music/music_page.dart | 220 +++-- lib/pages/music/score_single.dart | 792 +++++++++++++----- lib/pages/score/score_page.dart | 374 ++++++--- lib/pages/user/userpage.dart | 125 ++- lib/service/recommendation_helper.dart | 2 +- linux/flutter/generated_plugin_registrant.cc | 4 + linux/flutter/generated_plugins.cmake | 1 + macos/Flutter/GeneratedPluginRegistrant.swift | 2 + pubspec.lock | 64 ++ pubspec.yaml | 4 +- 25 files changed, 1221 insertions(+), 371 deletions(-) create mode 100644 images/UI_GAM_Gauge_DXScoreIcon_01.png create mode 100644 images/UI_GAM_Gauge_DXScoreIcon_02.png create mode 100644 images/UI_GAM_Gauge_DXScoreIcon_03.png create mode 100644 images/UI_GAM_Gauge_DXScoreIcon_04.png create mode 100644 images/UI_GAM_Gauge_DXScoreIcon_05.png create mode 100644 images/UI_MSS_MBase_Icon_AP.png create mode 100644 images/UI_MSS_MBase_Icon_APp.png create mode 100644 images/UI_MSS_MBase_Icon_FC.png create mode 100644 images/UI_MSS_MBase_Icon_FCp.png create mode 100644 images/UI_MSS_MBase_Icon_FS.png create mode 100644 images/UI_MSS_MBase_Icon_FSD.png create mode 100644 images/UI_MSS_MBase_Icon_FSDp.png create mode 100644 images/UI_MSS_MBase_Icon_FSp.png create mode 100644 images/UI_MSS_MBase_Icon_Sync.png diff --git a/images/UI_GAM_Gauge_DXScoreIcon_01.png b/images/UI_GAM_Gauge_DXScoreIcon_01.png new file mode 100644 index 0000000000000000000000000000000000000000..8632773a498af90abb6bccf89c8579562e244343 GIT binary patch literal 948 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 literal 0 HcmV?d00001 diff --git a/images/UI_GAM_Gauge_DXScoreIcon_04.png b/images/UI_GAM_Gauge_DXScoreIcon_04.png new file mode 100644 index 0000000000000000000000000000000000000000..f1adfbe1b6baa8ea5cf7631ce3c1c27a9565b9e2 GIT binary patch literal 2756 zcmV;#3On_QP)_7zt*L7+;%<2j< zAgp$+?x<~N>~=;O#*W+WI8)Ts-D$h6Q>(RWnYzQ;vZK3x6lYx=SJ71q11KtJ*Z>J4 z5DLURNC+?FCF#BICFBF;L*O50&&wfA`#T@3}YRzc1W?>5-fJb-G;OcA~s6 zkSh=ffOYYeq!7ut-D5Ne&_|F2uZQ)>2!dsh9*TC!vDQD?m}= zXX>%rM+EqE5=kXoIXO8&6KyRefUX8h+-E8MeSHD_Jx@~n#e3k0=b#o!#9T_#45TEf zPg|{45@-WJ!+Bt`7>HA;PdDido8lL!kPHouNdlU+B^??nF9<XKckv`cT$SE8kjf#cnqeJ)_T(=|Sf z;Mi9X7%aO$gay{bM@TAGy+Q!da*~8bo`?NPBx2CcV)iw_%kmboG#sTI32Pv2#h%x1 z8pM?yG?9bXXF{c(vTXeB8fYbxuB0i-cpHJP zK|>E(sd6;octggUvkiS3AFHLGeXeD_G^r&mLN_6&Cod5Az~dXt2#9d@N{7jrfAc#i zlCQaxmzGz}ffijnh{7wx4Z})+w+sS2lyw-eb(@(4u;}ZrAnW45(H?zyd!SWQ#A2YsR%?Z=-*{F``mD=Q&hwcz zt7DTudFowW3Qt-H!kqUBG|$@#-R-UC@FaP=2$e`+JR&DrqMSvM2#x~qeKR1e0C)EK z*YI!%%Poyc^P~6F$Ps!K?A8sD7J#2O|gC8MZ<|fl(rVo81 zRhRPloeu%L^lOVtJ>{ z^xFp!sby$=nH>YYza|6(SC3-xpb?LcXUY~vK)=9%SKx53(=Xhs`3o%m@nVpr|MWm@ zLmObw9?n>Cq80@ug(z%P=bXlXyPq^~;3mjs zt1tm4et_-l_zfsARiF$FKlOt+hKQv<6@eLD(TpCZJU%dN#8q9FKy!XR zSYp?KJa+vWt$b;U*TVDWMVBTkO)+8gX&x*`p?osjAJ}m%7k;8hDvJz@fA5`#mihS} z3kTe?MqGi%&P4FZF%QYXgrAGizTjjmLNGL-t2?^8x83nW=;F>sj<7o^J$am>#V2&T zk<+>uT!r&}5DT$}GPLtlp{3l8+PZw}ObWg@*-Ht419nUlAry)oE+w%Q6N<9 zyqCPU5R&&onijeFSN}xT1z-5-r5~atom2uc8OgS^wtmSBo6q90NQemxG!fv*2;q+K zS65fJH6|{;ynf04!i+`BNI33yav)6u>pubG!k<8GecL|Qi2Yl)-F}OZ1YFgwwsxRf z0iwkCm~sqU9gzu&&wmxz;g-*!hROjGO`p?h4Vz#FY0A5_wzXqRWVoUb67$M4*1k(Z z#ou{G;T~u~vF)S!hx?qC(H;7^gn(}eSI7$4SGSgDF9i1;Ub9-1mWQ@ zf+8R(3JUO^=sA*6$jKr{B$?rnVGz%m2k0REyrcbbL1fDA{Dj)$9JGei66iAMkA+I$ zOz2z%n0k6gALvN@|I-Aq|0DpvU_+HJ~u`u^FTpG3xaL0voyim_$9I4nR!#G*4 z?a;#9LR{7x^!Q#pdh4I;^Xp`3TDPSEV|&R^X7(j)U7NAv?6YL>-=(AZ=eUo}GH|v& zCObbF9cA?0N3&*KOcMikW25?s5&myrlB%k^x?E4;8X2@Dk2djy11u0@2S5;vW0*P9 zqu2HK0FDaCYyN!yQ${e!=Mg0000< KMNUMnLSTZpq%;Kp literal 0 HcmV?d00001 diff --git a/images/UI_GAM_Gauge_DXScoreIcon_05.png b/images/UI_GAM_Gauge_DXScoreIcon_05.png new file mode 100644 index 0000000000000000000000000000000000000000..28c6d2b6f3003a521f869b142aa7dac56567f579 GIT binary patch literal 2829 zcmV+o3-a`dP)l8(SBx{Qd9S4O>lnep#2(oFK*7U?tCO&~4yrj4;tw(`zL}WBQ6wHJe_&=ks0e{tL z#(*hIM3OZMXxS6ZWMZ-zIIfmVMM*qOMj8|;Tzk`!#eeDP@wq`5*_8x6Q{otIjfKy7p{%UX{_pno@8Z&1Fh?|}fOAaN z9XVzvbMrD-^ifB_3u5qUk-n4!5TOqp0%2r7^0_d=;hi=o#zoeTu%Nbr=M8Q zbZEwa5h5n0c1A`;0rav-Q^YV&=H;Q6r7i}1?RYPl8D{<=i48oId{&eJ40M!ArY(iZ z7!^RF)yzzi;UMZV=jd>w+3Fon)~Z!8;X`m(e+IB@&z6>pf=iHvhH0vL4E1bv*j)f&ZK1;UOK5}L8}qHEje~*E z@!8rsTfjG^ zm*)5nK!yDloZSAW!GP)bm45eby!rHt8vI28ugx{(K3-}oU0Ym~$E3g#;#Z`leA}S9 z&y)fq`wep5Nl1LVs4!<|QE^T!j@nB2Z{vT>{SbQkan;jCZe^X{ z|H<2-(BJ{&yggIH88H^mUQx0SMvBV5*R%A-o0cvtnB#9Lm|VIxlxr9z8Mb-Q z(|{Pu)-j%f?W;$`s&BP_+*KcqNm*e&N!y(U%(2;kun0~`Jw6}YT!sY-LBo}HH0;An zjX>HBmEsuRhR+Wo=j9uva%MjRfo5$Xh(OK!v}~9TacuUh$2evsjSvccXK$>zsJP$XH=un z(>f;pm^1IUcY_(Ayr7W*=N<$tXu2|iV@SKAdqEnIg09d0b0QiUIYw-uSQ?9Fx|vJC z!$_qlazkffm}w5L=8Rk}`5|((40Zrxp8pEmxaSc^1%`9mHq2pSBsnf{Ec+ew1vbKk zcGs_Bp`qth4IB)F1YvbT-sPvABa$MbJgV9(wKR^F4B7(s|Z+#>jJfR9m zSGPC2Mve=x^;Ctu2Mg7&)#WhetY6p&|7vNi$J-;IMAS`)iqt(-OLBG*nG2mGMvgI_ zjLQ-D%ssFHuWja4n_smRyaPtzq-H?j6VU8?06IIH@029zG%!-ip1k~dsyzX}e~S#RO=&XxWqk3?z)i)9qa!zGPi`|4ut-fc@4( zaIWQ}7ZKzUbwS%VAYh9MxSHg=qdRy~M;kO_xV(esFFF^lTz=>&yKla_))l%Ry1OsD zsDhoup0gp-rX=90Xi~sJR8~id)^>>Os9t{#^x^k#zON1jv*l%ZWLF@zVkhxzk|ard z8-y&un=IDlt8&dhQE!6MzEyF#c{xrmlKW^lg8O13CHtwQo@=qCsHb_{@snu}Z`!v$ zZMT;Hynf=s_qiTs86!v-iuoAIH4Zz^vacYrW~gxl_@;2tVG`Pz@*UkyXUVb+zRI)ruXEfW`;hV6`2A z;^Kz;hOY!qgA{o^rOXt5wyl4`&&fO-x%RYVXG~#u_(WAb9Dutd9jSH5Zk?KwcXor(A)Rv9b{+8GRY&4qy!UE zsaHA#+DHIbmiOYhhUd1N)SiiG z9AMtWujfsMO=PTe6!L{ucUn2UAT5aPUNxSLzQWy`TV1C01T0?1!cy>!ghcIvLema| zzGO{#xtU?Xh3`a{G!BJ;X)A_P0Qk|JbX6J161TRl&2`I=*XwIjlq-7^pwS-ly|b}L z0pShEASN2kYAsh^SDM#Yvoy?G*}rNl!cbbd?8)98+}T2FBiw$&oxGr1xB30$C39koYI(8Y%TP_5g1!!DF=V z08B`>Ct*OpOKQ0sl(Z1?A43Dq5|-ON`e^S!LBsXeAFZk=bpwN-`N3ysB@$7=Qh1R$ zg_uLO0}wOTVqaY#d0oCXLGbNP>Y1%Qh%c)P8<)e0*jEp6QtCPa6ObQX#X>I zKpYyt9=w&*@`nGV*vj>vMAp z>R`dv!{4-Hg>^T862sHN{)`INLjX*O;b8`zJ67Yf&)jpXpjOGhvmiCOr_qiSxM*6YB8eq`tt#2`# z>uxK!%S=9nV~zyLnko~X`n{9@Nzi_Lh1xnw-<8SR57VT%uxC=hJgYVIa^f3RBp5sq zE&a}eq^gs7QZBWVV<}OWX*P@R3=F!?^Z8G#BsXM^d?!qUW^GS`rv0`Q9vwY)zW3HT zZSFmmT&L-Y<*8K9njKTEUGRM6V)$`#KK6r1@?~YN?Mc!&z_K#t30BLYh!{EE>;5c| zSZxAUlZ#2&;vB01l;R1?scghN8$H?C10ih=P0AP(Nbyz;Pkfr0y**O^o`RZSZs<$X f^Yy<=zv}c~jjTx0g6{g7zP~__+@b+2*@T7AcTaFjjWx{($(Fy z{NJhSq|+dYnSgih^YG<4sp_gzr%t`~E$?^E=@xYzy%(7ZN5$6ZSUHsxDk7CSDv4AY zsqCb3A(i7)4pNy$#Ye>(-X5dU7H&@kbhZsqk*H+QVJB_lso1G(q%wp`I+gX|cBu|3 zsHBDa!R|M@b7wi$BI?UVpr&}7Ec7;nZu}W*Wj>V*d9sIJAN-mW^xUhYjqM$k4u8JRB z{xvjE#uzGNjjaR^JET?^bQwZ|L95&4bChD6v6qRJ_!p4>EdZxenPqIB#QB|1$Z~YW zQ1s_fQf8-548)lzqrEjSbCDlieoO$F!QMpWF1&>7upU9p9b;^@0b`-2kAXORxYGF| zMN}ev{B{$(Ya1Z8l;~nhu_hiVxk_B&RY-4th4Ezkz@Mos1){X}tjbvfP!~9GB1^Qz zL_MU>P(M~*QkFRrHGc~*Yyxn7K0%s*V3iG0YAU2zbKBixRG*?gOq&2H3#eYSoKs`J z7KDsz5!+BJ3dg(6PSP08S<4A*Z5jy`O@Rmy< z?A;3PolkVE#iRO+$&Yc*Ze}<8>)Cw3vkBnx;FH$b+Ci(tfWwprjf2|GozQobLHCBO z%w>mO-vqU$^alZ`lpZv;;_&m#ZRsBPlzotLa-hw<7wV|70{`hoja$2!0l5^P;0mgC zGtQEn4FG4Wp9srCuUNpP>Lzduz|yUxIa{C?Wf_$?S4;QVw>4P@85 z`#{J@MN1m79=98MFbJ+>7fACsb;e|Y0Vq81pDKpnD*QJZakl~D>NEYYX8=G^G{)*k zR8_s6dre=i{#Au?`834b_BK#nj?iykH_mgmflzERwAK*RmY{h}s1c#UYDl|wnQ>W0 zm!h=RKK0)>|xM~_rZ6f6>@b&N4t__YMdoG+w5>wGU9L}ki~d&4fq%b zcxNnuCxVmgMjavO0BS;jXCS11Uyaa^XJGmH!r_R;b4ry{5uGi6I#bVmMgVLFkL~M^ z{~7ja^E=LQ=>x$}yd1*UZzEV#iGVL;9FLox1#x;J>dvF!IW*T)yz)}`xkezlhR`vK zUoL^waTG$xYh0&g4up+qUuiOJhk;LaL2GM;PRxaAAoT#2)?{Gdz>YpxWU@7b)`$M>C2wKNnh+t}e zGFd-xfXw&!Q3F7G$pOGiAeXd88X)t8+is+KDuL_6d&Xhg(P0o>qY!t|rLYeAzsBXUq!t@yii1tF`aIZo9Cjb;9*T~4{-cpu_N)>3oc@eg;_nx*r z;o|9tTU3Mc#4LnHOaXtQ6qa>=L(CU{MQDEs1(9mAo@D^Ugd_<41{j|;jVEAhfkRCM zjt{VtuX80sO;r(;D$wLTg8C0Wf_vsnq}+BJkbUJzca|c37%~;|@@1%=MeHWEtWeT z&fPPIqOS)r*NlKIet zivNeS#jnCPY|d%BB2Bsmu7|II_qkON7Jl)l^6=DNrGR30QSa0qgtygLE&hqz@wNFB zFrGhs{eTa??>>b;E}cxX1g=%9;s0cbF>n(5)O(Qf=5|z7iuNM(%@>x+6 zk^tqi2SXS*6mhrxrlWq_ryr2^JqFu^B%?IFcmRin{}fpt-v{Rve>`oMTu;zj$2@9XG;(6edEe@+9*boJ-->5K-%+_wDm^#Tn9-g@{5Jf(6U?C(B(CyOhae<^xMOpUGKq2-R zLNB@UjaaLel9QhOpu$&{i8@cDIW9h`nkF|v-nSYRj!|%zuYtJeDeT;k3fY?q6tW58 zuZ((}F|ZerCZ`>PM#Up3B^HdLwY0?J;EppGcA$-0i+=<83QmRu%m zErw-!I{5s{zz-eQQH_CD>B@n-Yz@!>o}WyYO=S}oM3DBZL^>iq+eQ!9))K;=uTCKE zGAkTT8+LzD4!O;&qwyMoTBC7YyL7%@puYe?>I^^Dj)yj3P|aN!{QFlT{gMk1_;M@! zAASPo-8aMW%rDXU$Xk+rb<MXsfXs`0F7EUsl6%LlL-( zFGr|ij{%`G zMs-Ki=q2bnc%DZf5PpFW z)0Y`_)xVc5AuV+G6b=z}J*xk!4((%da0{{j^N3c&Ab7A6^8HI7zS)54lnLOQs^M7u zwDBnZ!IU|CwZgc>m6M98TdwVBFc31Gwk^qyUH5E2;i4_1hfXwB1>kVlqQINJc`n36 zO1~O|ksjH@!3~B4GyO$&3!fXT&?Ki;dxHs*GKHnXs zh1U4shdl#O>b6Eiw&9_mUlwsW@wl({LO*yObbAmwOV3(W*m5&a-zNiNQaq%!&qLW) z2G?V6V9y;dcQny&?r_o?8NuaYYmvwtTiYOCnuna*Gf=XKEoM>B+8RJjJ!KaHhY(RS zfwO`W2&|HA1mGb8uAS=m!ax!!Pnm(l>GvaX%0VP8S&FT9euehIz;AxB2zk%nf#%9% z^wI_BK5{1g9H@W03Ms#x0oQniETHlO@n_a305!d9BkhR^55yKKW5z>NGNBfjQB!XV z896Q63b*7y89o)3FDO%3xe}Oq1!VVVv=-NNG}3oU67jh*qj2$k!126!V*g=a;tWV0 z@|7i3keh>4ED#(7u&+~~kn1DGJ(zf8E~zg?WgD3yA2x4c5HfD2c(=b1M_%4<9BSNI zL;lZ8n(l^NRNc{>|J!nes(i5IO@(dOW~e_)n@Wc_{7_ItV-;0b5_$IfjbZp(^PnH8 zhu+Xe0K^#Jg-RL`GbIbHqb5Qq+Xr#=yT%2Hzj^~jPe-yPM|LvYBV7or-fn{D?qh0T zfWSCW4k^Wt{#RvT-+z~oVMy>dk!H7@#LHX+c=8NtY?AreMs$X!$R655`P#?`h?abe zShyACAC^L{Hq*kYHCvEzaX#AC9|oTi16@{ONsWVV)qW)0kPEJ01Gwt8OFIK7s(d#9 zrgQ^fW_Y^|hPM@CM?+n;(>TOH)Pg|+L~&*!f+-oWt|8h`QUvL-6_k?oMb&$e$+P6k zh8bI}_?Q8n<+8~LIq8AFT1xTFS>N73m1v95r+f2U~idzIrLawpzEw~3; z=C3jKlg4FW+_RL%#oa<6CYZ%C0q)To3Ahpyz%7OFTsECvnrMJ$JWszkLkn?33PPSZ zSa#Pa^ebxb<>9|ExK z5K?B1hs)(btGArqu7wD)*+Jeyu7b3(9Xy~E!Kx-y?J|eE*fw!Q0zC6Cp-%~B6e zxptJS@zRwps8#f`Jr!_G7>@dF1x6J|fM`HfH3gUnuzJQq+_@oFyvElN8I*2DC`I_u zy~yYwc$Q)GriP?IuWK<%@GzNMULF+BcnGz{z>(v?fK;Py$*XS@Y0;VP*wP8WaV~h*mlyz^Dy+k7+|L4@vK0sYS!utK z7``_QW<=^wxmqJ0$!Dii5IYL%z$^lp^dr{;O-+KT3^0NM*QSiuV?<-#koSkWghoUy zx)!$qput`zAiKB*s?Ds^J6Jm2KrrBiqiGFy6G5cWH3PggJ`8c zH))vXR<;yXgXVo5eCRW>>!HLynU9K4jX+>0q(NigzUOw>1`mRBP@<7zvs_zVxBw<- zNe@s_bZz4802&&o(v6u;@W~YYs0f3mLYhdOTK9#aeTD`y$-4rzpMTNO z1W$h{QwK!aOwKem-fdNAKGFn~R{;HAAf;jfz-wrrjs7&!U;ea2Apy=|xfuN)W{^p{ zD2L1y2p0Z^0Nf6V4XzS^&Bc+3+kdo`^6C;e#tuNryt__mk%5T)rUrH#>rMl?Qgkgx zjmhodjFpOs!;NdA&b!E{5@TU&p@hDK%vqu=V}J)O@4aWNduAY5rM~c7PSLi6f?KMG zwC{2X09B|iEHq5-`067_zq^GR>uWUV%FRc=ybUO!IucP~CMt12i?O_!y+X$A?2s~D zIR8JxQP4}=uy7x&wG?q%2)qktpzT;Zsy{nS9d;U5#CS++iEyi05Z@WtC2h%Sv z!03gOz$V&bje&#CrjUVzDVYX<(H$WI>@TJ0zme`sCR$H!DnNixa~iouwl$1(WFEbX zN!T{!-Hxm6ZnK0m7ebAt-~TN3g!aA2d_XsA(8Z{g_1`DmEn*L`$(=?6L<2zgK%yKn z8h0G-sFILR9gE4Q4u(@e78A$cL4|gO_9azAJ2grGUXl&`zQ7oCFoosLGW6rOlZG9E z<_m(eXBrbn0&520cb(!fG;jYcAER>9ZbSQ+^uK@-p85bRYTwhR4;K5W$E@g5#BMdL zI}PPc4Ge*zW*|HeuqhG%m@n&p9ciD~Kl(ahL>G;k8kj_&y;FW8j{eiz)(8VMAYn z4n?7Kv7U!o%)>T15#AONUthl*0kU5UZ-c6nkJHV}K_qSRhcv{*$hhD^D|C%RQzd~Z zQlJPXn_lFkaRA<;niE%@k97mj8JX7P?Jigpigs}pG*@_0^UdcS=aQ$e`P|G-wZBB| z$L|sWZ;EO|h_NE+VyaudoG`9JzX(V@i@9xMc*`1MJs{FraixJ5XIv?^;kEH-wpw-GI+r*?MM&iQb~jX|(z8^q97#N|vtTxK$SrICa@=%**Iwt>$v zeGAh(MOO@}q>4V-I{J82#xu}bt)qC2f?@NB>sbgq)$#ogA~&}09qBmM z&1jW~{#{pqNu!ubjlzD~Oc)h8$E?x9G|)}lDHztk6CI~rE_YZ1J>es}4B);GJSMeU z%HX=OFSN=s=%k^x^M<4U#lvvmolb)zY--aVfZS;%rcOr3X#&RHA|n0*Gw1}o5$Cn{ z?GVyp>LJ8AO$}^+E(VUPQwOTds4wJ10Mxr7Ga_QUuE4S;B~huD3Pic3p+baL7S-Al zP^hT2)dtd^@SWsbp?=TwHm3It6YP=P^)*j~R zicORj&A$NBjStaBFB^Tuz`Np5NB~+WCl-lsigT_6CK5;<;(;YBty9SFnL(z7fK6`I z(NeF$nZO&{tsf|m(mm*x*oKxQ_NBU1SPl?pT(br8R1f9s^nzV+U@uuHJp>Eunv?uY zyL;ofE+G_=fUboUiVJWCClLcjbl--|wzmVk5jSdZ4N&pbQ_OcssQBzVlq`%VD&jUiL*4_qupW}b z0dGwW3Kvjz<%-ypTvAQoQDkKVo3@hM^na}l8j1@7DBc>t;0NwQQsY)4*>umue#Uu6 zX+2phwY8OAYROImKyBg}%Izs@NP=0~Mei<^v!h$#gM6V0V3Fo0t!vv>2J7IyunuuU zj5Xkub6$gW4*$$MfI7DU7NjH=X@R&H0bvRQa=Rz$U3La;}Q=L6G8yR#P zd~nyIQ^z@Ne4G(B78SzDFiQCGgaweg=4In$KLz&Q{k| zl7j6dmoX$f9b$qP8H0=I8XK|OvDd6<4HR-Mkn*!2CdGnJa1bA}8xd91$czKo$eLC~ z0HPBCc1AU)g-4+K4%3lL#Tut0dnRejr|pCxdS1su?X(;J;H4%0c7$rF;6c2OTXDB-R5jEqhrz$+Ff43Pv_E@AL5QAYh-rDGkt;CvK1%G0 zI5ZMp<28v5ftYPE@azGWtRVYx@(%leh7uLSZ()HbgvO&34(g(rmq4&NGgXLA9sVl5 zeNA77*wX??I-lCiqG(o^0lQ9qacE!480R7Q?OKSdo=0oeFoeR8~3ChPu7=$+B_cWm!00*FJi3H4-F&1|VySGA`cQ1m+DeF#jL6Qf2$-;9TumkHBhRA*K0x5v zAlOYuYD-T?`B9DlgaMx1z(Mxy?9`agP(lV?BN2uE7bYVyI|;?>4j2H+jTAwBEm5~; z{EmUgMD~&PoE#J-HU_}s@F#x6DNS(R^(=%`51JQ~$yOXPCb^Vxv*1u0;41b(uW#)b zO^=fL?To1H#(M0H#!u}MK@pR?0Wf;xhs{qeVp=FNE!_UC0UpypR+RdBBwafXTNnQs zgQvzCIcSuG6NRd6L~Q=nfQuJq87`)1GrNfu@dG*7<4#JR=zLwVOj=0PB*_B77CEme zWY!ft3cvKhPn2cw-{~wQx7j(XAJxK&cCgoRdXWHmu~skXxRGZ%-Mv zKlMKl`Z&R5gf%7{Slh||xZ8(8BLCQ08H2oAMxo&KZ_u)Ww5V_N@KBJAIyYLVZSsch zW(az;Pm;Ysq-3~||KcAY&AJuxhBpyh`8!zY-oESZr$FmKmQ{n+eBF1{F8QWLk zw}~>I^h&rLiZyKp^a>xD%h0H5aX)zu7x_-0>fWW~rZO??_5P^YwibL%4B}=y2jt%X z-`a;Ex!S-N93e2v;7D`9-7hYx9j()XQMcQ{*faRN$v z%k472YjjVgpiu(b=&L)fw_3?pd!|CaIve`LW3befLf!HgbMVD$uHMI{o_*z0#C-Hj z$oU&u_`$w+#Y^twwB3hK0k2(Lw=JQtYC+YlZ=&iB7@F9&V=)57pBo?=K$2*)%Fyz} z9S|$Zpw&kQ5%3}QxkQtC1Q*`|k&UnZO1vKJOR{HmH-Hr_BEIWUeaP-i`d_it;^$9O0-plmy7Qq|*P-$AODO0416*?+0-u_JxHmq7R#gtQhInRW znX%7=mjv*XL5P`yjjiG`BPd(*;(zG@w0>!hcd=+2q<|4bA9z}dVap}WlTM%LYi}=5 z#iJ#<_-lLypR%gsL}kzL5HY-G7dY9ds(I;5^(A$C^&=aHqz(EVELS{n+H3mU@O#)N zk3z-d`%(Am*Kk~T9_7ws;d|?K@N2#?z_N^)0C9Xigq#typAXJynlc|WeVtZYiNL1S z2$pVxR$Ba=!J;Q=>Gd@ zoh7!@D-z}ll`0hOP#ov^GKuv{0tQLrac^}pp1H6fthhBv*K=g0<2!R7%Q)ZHneoyAZS9<{U zaa*j#Sd^Rh6&a}$D|2B<clA*?PHcy*S_8M2t?n|vxF~&AL==D=`_P>BM>PJcTUSDV;Q@$4 z>A(%juj$Z7=85R%=^2S(03w9mh~O~Z*(>VO*+?wbOmmD53KKZEuA_I^Cskb$v;T?0DlUH@kMfiPFfl@R)qN?Ik4=z=cN9hfxZ+#i~iwv9WNidkOKso`QxKH^R^6cH*-vI}v}^<-pc&QTNedgbtJwr;CHYia(qr zr&3ZbOWn?b1rqwfnve{xrU|;0Ogten-o_2ir%Xd;OFmSg;7=ieEu)^VenKAL7nEzZ z;w;G7`_YM+edbG(V!y>(2cN?WubFm6KsxW9L4MH(dE@IypT zHYbA_fQDp7zMF#P?Lfivk@n{BFz;X$=9H&VZEn*L@)E{22C{@E~dGR0BZoQ;!=U+9nfCak{|qgoHp8h)6Vo z1WYrTYE>z;Qi|R!mEeLcuv8Ohyh%&-k%2wx)#4ISRLv$ij8*nW&R)sIL2by_{fR@y;J^gEWPdN`@Kjl1t{gm?n_EXLS z*iShRU_a%n1(0|hY|-;tD#=v*vWvg=L`xa5N_(`9&7y-{{IZ5hA(c?Huyco|p)K04 zj;~z)IW$nkL@JZ`Q9&MiORwg18Bz+T)t&MlDzJl}!OGvq+IDF@07`B?eaFp0Og^V9~hRRAHPHVqZ`O*M1B>{rW z(o8wkjF@xvxAZ5qRjyPc7yw331+FV5NK+85bwJ6?gff3or+bVWQ{0Q$Q=tq2>Xxq- z^rTNDDd!p_H8;r8iO+y%-)EZqtfOG|lftqO9;Wk0c$-A>k=mI$?7w{38n{4vOM8w1 z$6w`6g?!0P{M0OOg7NMNV0$UTBa&dBJPl%6PM;kb4HSLK-$1ye1h$JOlLi+ea^b}g zZI?mXza7HM>r8CK{pQ>m4+y{dfPL6szRU@JnEJWiNRt$zy0C0i6PFfv#)&?Qdw18s(R%|ED*$T6C2(Kh~61qS%8wo%%LmO#QuM&!C zAZzbUhLn?uKo+qcuNP)G4554vNb>}J?hJ_mDBbfPI!5B__$^v+I|p&?xt{Df08p0s zS{;e5>(>c?Fjwn;(V<;28wuZk38<<<w;q_G=~bKJpw%tw%SBm5GkpHvS*JK zmsRvCT6;Z=ft7IPj)Oczgdw^h+U<}?lSM^=SyLtJ_I4>2Tu)UyPv}@B zsFuT?cpOs1$J-gXL*ZcBSCLM~(Gb%;Fgn^{5_4f1NF#u)JslW2v}+8u1wTQA$PIxA zxghYkQoYXN?H?$6G<)>v=;F@9z7>}N@x)E%Jx_>Qdk_#ZV*1@ykO$67y!WT}Gmi-Y* z(W0)pTIqg7{{A6!0?KGIIf815SW5b_t>eZVG!~$SSHd-Fr zgR(K#Am^=n;kxoSXB?9SUa#UI5Em1OUjV>=M96f_Uz?ZG)QGCAK6AZLa~F8F%DqT@ z`w`T9R0(PLEXX70Ah?zM-_{pk)RZB-`M-ISfl>s5-QLxOW?Eday+elIZ-bhlz&&>{ zMP83Jl*|W7Ghc(QHY4G?!=SwvMCDbFBjW`>ne~de;po92>fU}Q?!ChM=RwJ^`l{Qo z2Q}+XbY7Z6g+-!cy+I+LHH1EL<(siluOKJA;9gCvsgzBTNV9uDTs1=_%{#CbC*0%V ztyvHJVHtM3p9$5M2ka@Q8rI|lzOWaQ6D|jajrSrgBMHoZ1_Bxm?`njvk(mk$E_B$o zysLh!JzF_!hmBk&IZzzGWj!YOZ$SE8tN7JJ_bx-t+kZpgSQURAA>WpimxYl(AJNrU z!r%!gd20ufg*5hZs41Y7`u&?=inrkmKJn}|umSV6RTM~uQFcKk^z#+4U78KC_;QG& zCU;e1;MI5v;H@O^mM_C@4qlK_j?ZnZASkLq{NjeIK7f(dBj|5}12G ztb*EM)iFc^VWWk&>y+;8CHe~ykgn*ocHOrHV>=c@)e3R+x7H2QsfEOkVd(8D+K;v) zv5JE6=xgZw^N6)M#}xDpf+?}d`FdVGdjJpM5Bzu)rD&zw`Dy4cM<9Jv2ix_f5H7g_ zk?MUMJll!A!0(p9SFdpJtV!x{=Vid=NsJ;L>sqCZM1{o@8&0)Z?{xz&F*g|l90TKq zV;FQ3OcSCgA`}Y2f1(Z>oCrLmlON^k{kcM>Y<`=x@%KG1$Y{jb z$Pc@;kICV!#Quj9tw=!la1GSEUxEBWGmo|`U?i@6gij?nf-;AX-{4I=1(}$&^_s2@ zL(!cTjx;Cs-0?n2mTx6JbfKj-gv3N^Ex!}IOFviyIh9PcB^(=(GrEo+p-&0~(0HT; zCtf&-(ltjokoIoc%4E`GGvF=uBHTn-Pzqi3Xld^A5AWhXdzw##QA z;-U$EypaI>3}$O9(x>Eh)n{5LN>)y*cUXQe;$9A0N+Cb@ZMy?om#pXKX_Ip>`B#+2 zxo@UfrdY)`6&W)(;n=!kU0QhH=O1C8u9F)v;C3ft&faX;@GzvjJXHNA6Wf-?FB#gA zz>kBAJN;h|8uK}RF;0ZWXzu$mhU$|iLa(exXzOPXQmp~$ZRDS_Qc|cxKq+jvwiUH|tmQ6tOdXqoyd{kPMorX_gQRh8U>ytf zUhAe)uwmNBG)YcKKqL}@a;$}X=yYNLI(-7G8%rfs3PrAg0@9~OgOMJOn2aF$78P9t z&o(*(k&ziN8w0$AkCM3+6+z3J0;!>l=q8!K1)02V`R`w%AsG}BXpre%cqiMTAF~z> zq>K#sHr1l>z)56YeldiyEr^^t2uC&nIN1Z=yX73f(}fL~t^3&^+8s!wKRcbb5JmGv z!Hh}MX{|-s3Ic|v^Yf(PMA(Nf%RUW?NAW z7{2EqMkr-V8%6w+`6w&W4*psgS#t$!`=#YqgT4St?$}CWv9#0ow<8Qjex5K8#*`l; zV8G^P8d$c;qDv;iaifs?3^*2mTm$uC6S+=^=@k8_NW*49nMN^S(}!I9xCSyQx)KfV zec087NPii#E{OG+o@)(wM=jcpk)y7vrrAACO2q*USvpe2h!nRdDV#z)vneG z-ROuB!k!n}kU!Ujgme@2`$gzA@tK4z&w=cTHn=lIY+Tlksy#fVWTv%|og|(HpaZ>& z(Z}@&OUzq1h#WxgP@)_vT6P`ns*+Mn045ZZ(oF4~4m?wSK!st_xut4Uw@wMbIr+dZ zxACOk5;k-d=E++~!;Zo5haos~`GX@C7Fgl;IwOy3-ocySLd}-FT>F^h&!&W@&Opg( z6@!bM7+4rW+GrW>kz*(wRUkMNgi8-Yt@VRx;Gqp60*~kA47j^HN7j?H$bD*9Um7Y} z8W^EOTn$(0FcWoK#(!Ttw4IKW9iNa?JQSxB}zIJ^WAwL;*Wd#S2_uFbMw)hF? zlb`Z_*}B$gD=^)q?^Mpi7Y#Kvz_J$kG#r=)J4gdNqAM9}DCO(lCS3_qQ3(W!kA;l8 zbv9&E>LvXlrvv{w!Y~2eS%s)205!JsC7IAPN*9~R?8-zqCZxg_knzcNs}UmmwTTX* zXcU=ED_4+hP6Q(c5)z0)+(-0D6%cGB2I!fK(zQ>*9=0KOl7z4$el*a%Q&48n7Xy>+}t=fnBzrWXYEqx|?o}1gP_D5)V>s2D)t#N%w z33jAiLUpTF6UMa&Iq$;YZ>HeT^0;a+Rh1W`)nV=T>Lvm=2rpGKLwA9TDJ|`?%Iq%#p#wYfAeH>!bo(RcQ6;64vLCJkFF;kkhrY)vIh(oo0nF&KQw zXdHUE8&_kGHiIFk-F9M{6lw%R!lavJ4EUNAbV9zEuIzs)f(sIwAi7E{ z6bhF;gF;WNYjw|yIR4V&P7SmqA%2WnkDx0@iT9a>(WO+aN!cc=f#zx%K2_EmPC=uh zH`F20a0re;IZ!eS(3V|++Grz+!zfrnk=HYv184cYqiEh#)wKv|h<1oZr-4w5EtD25 z`5KfP?xUAJw)%>px4BV50NN-gmWgl53$FsE5lDH&1It-jr;*>Yf=qybO(&ikXfohR z5&3cZpEM}hc}P#~Kp-s}hB62VpActUw-f5DJj&VW274%9rkgBP9L6Y?{bl1%T{lP2 z<1|h}&s46rr7ucFJqoYLG?SIq5REgMnA~sfg__H(RZ;vQhm0|Bwb-v_DM+IOR8B~tLCK$B-#jd6s0 z{B#2EgSZj*0pNw`U6P|w=UzOA#kk!uz7G0TqY>P96gIXfI+)#}Fyy>+$XP|Ck#Atq zU6k0B3TPp|CK_aNp(q zaD;3xBsQ#E0w`rNq>`P)CMX{t%L#4F?-}vWKu!nn0`xAifcX?A;dW{7b|^R9iSWr& zfOK%9T-xOMNYtC* zDI?O{)ZVq49w$vZ8ByDdcdkL@)HD7tw(wmtWE1l}Mm8W_)1 z!)(>L#YTOTH}tkaGUI)c>I)+y$AjW09)mJ}G1T{8KzPl6!cL#cP(yRC>c<%!qeFUrQ28y>} T<;s$pK3Vw>d)co$~$M-*@!! zl9@+r6lnmubS%xXWG%c!_&?r;7Y7yD14_Rf{$rgAa*xxuL4 zu@Pc@0^D9%$iDm&O}rsh?GIdxg+>d^H>p-eE}}`_1_< z@~3BPlYIRz;hZrZ)idr!*-&p;$!GJqdCuGRE0^76o!53jlIr6fAl)t-1fRDUngtG z(yl^3L~cXh-%slS1W@Eigg>%5+2+=?O4WvJm>loiGOF zbCS$>9-}Ho-n?GN&w%R!ZyIhwKSi#3YuZ^gu&HL8lTueJ-*YWm^!pa3M zeCMxEF%UCW4TEy^)!k|+&`<0_sALPmlt#6kJOPKt3un;~T#$Do!gwyMp>MSf6a zd8_sC+?HWSVm0NnhX}BSAP3E8qTHJ3jgTH)cWR)#do%fpG-AeSR=ddK-{Jr&3y98N zkAH>r&GvI5vaE#BFVU@JS&||ah!1VxATj_MkY+~ChdO zq;(n2U{F(4BmJWH>IT62){G$3+eElBa-di!?wkv?` zpQ7>gqlg@;B2MRq#7Z!lBxh1mt}5tj&vqzf5bP=G5b9fD+R4OIV(V?fh+@h#RJP?q zm$p3}k=QoBCHgw@0RKX{W;?zF`SN~rVqWqRWqQ)5c=7PB@%SGsJ0l=nx6dWN=!d%b zAR;AIh@7m4GcN=7QDgB%#7wsy1~UM;%}t%#mstv$jANK7uESvQ6}lb!oDD!!o;3eQh!O$L0U(r=QqP zhxd%jWQc~wz7c7ay%>QubiTGE7F3R57!?yo2)tuJv{aon_dTcb&+y}8onZXGlz#>Q z|6j{j4`9?C2|A^9>|q{rRV)s$9lp%JF}B7yHp>v$j*U+DW&I!r1jb@I#Mq7jvs?bp zw_%)%AIARTa$W=bDdz#~r<@0{pK>0+e#&_O`zhxE?5CUuu%Gh3x$Wc8qeH+x00000 LNkvXXu0mjfmz{DD literal 0 HcmV?d00001 diff --git a/images/UI_MSS_MBase_Icon_FC.png b/images/UI_MSS_MBase_Icon_FC.png new file mode 100644 index 0000000000000000000000000000000000000000..79780a51246c64e983cc743b9ef28dd4c8f5b27b GIT binary patch literal 8992 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

#_2iEKX*?jzop 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%0Ax#Kpgq?P_}8z8|9CCTs~$nnArL`! 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 literal 0 HcmV?d00001 diff --git a/images/UI_MSS_MBase_Icon_FSp.png b/images/UI_MSS_MBase_Icon_FSp.png new file mode 100644 index 0000000000000000000000000000000000000000..a427a44c691fe0160b78d64af905f73e47f4cc42 GIT binary patch literal 8991 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 literal 0 HcmV?d00001 diff --git a/images/UI_MSS_MBase_Icon_Sync.png b/images/UI_MSS_MBase_Icon_Sync.png new file mode 100644 index 0000000000000000000000000000000000000000..c0badc5ffcd438a394cc26ff95377aad112895ae GIT binary patch literal 8018 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 literal 0 HcmV?d00001 diff --git a/lib/pages/home/home_page.dart b/lib/pages/home/home_page.dart index c490e4e..5220c23 100644 --- a/lib/pages/home/home_page.dart +++ b/lib/pages/home/home_page.dart @@ -592,7 +592,7 @@ class _RecommendedSongsSection extends StatelessWidget { } return SizedBox( - height: 200, + height: 220, child: ListView.builder( scrollDirection: Axis.horizontal, itemCount: songs.length, @@ -664,7 +664,7 @@ class _SongItemCard extends StatelessWidget { ClipRRect( borderRadius: const BorderRadius.vertical(top: Radius.circular(12)), child: SizedBox( - height: 120, + height: 140, width: double.infinity, child: Image.network( _getCoverUrl(song.id), diff --git a/lib/pages/music/music_page.dart b/lib/pages/music/music_page.dart index 48fb80c..1ce08ea 100644 --- a/lib/pages/music/music_page.dart +++ b/lib/pages/music/music_page.dart @@ -24,8 +24,11 @@ class _MusicPageState extends State with SingleTickerProviderStateMix // 筛选 String _searchQuery = ''; int? _filterLevelType; - String? _filterFromVersion; - String? _filterGenre; + + // --- 修改点 1: 将单选 String? 改为多选 Set --- + Set _filterVersions = {}; + Set _filterGenres = {}; + double? _selectedMinLevel; double? _selectedMaxLevel; bool _isAdvancedFilterExpanded = false; @@ -99,6 +102,8 @@ class _MusicPageState extends State with SingleTickerProviderStateMix _allSongs = uniqueSongs.values.toList(); _allSongs.sort((a, b) => (b.players ?? 0).compareTo(a.players ?? 0)); _displaySongs = List.from(_allSongs); + + // 更新可用选项 _availableVersions = songs.map((s) => s.from).where((v) => v.isNotEmpty).toSet(); _availableGenres = songs.map((s) => s.genre).where((g) => g.isNotEmpty).toSet(); @@ -110,7 +115,7 @@ class _MusicPageState extends State with SingleTickerProviderStateMix } } - // 加载用户成绩 —— ✅ 修复 1:直接用完整ID存储 + // 加载用户成绩 Future _loadUserScores() async { try { final userProvider = UserProvider.instance; @@ -171,8 +176,12 @@ class _MusicPageState extends State with SingleTickerProviderStateMix if (!match) return false; } - if (_filterFromVersion != null && song.from != _filterFromVersion) return false; - if (_filterGenre != null && song.genre != _filterGenre) return false; + // --- 修改点 2: 多选过滤逻辑 --- + // 如果选择了版本,歌曲版本必须在选中集合中 + if (_filterVersions.isNotEmpty && !_filterVersions.contains(song.from)) return false; + + // 如果选择了流派,歌曲流派必须在选中集合中 + if (_filterGenres.isNotEmpty && !_filterGenres.contains(song.genre)) return false; if (_filterLevelType != null) { bool hasLevel = false; @@ -248,8 +257,8 @@ class _MusicPageState extends State with SingleTickerProviderStateMix setState(() { _searchQuery = ''; _filterLevelType = null; - _filterFromVersion = null; - _filterGenre = null; + _filterVersions.clear(); // 清空集合 + _filterGenres.clear(); // 清空集合 _selectedMinLevel = null; _selectedMaxLevel = null; }); @@ -359,6 +368,123 @@ class _MusicPageState extends State with SingleTickerProviderStateMix ); } + // --- 新增:多选 Chip 构建器 --- + Widget _buildMultiSelectChip({ + required String label, + required Set selectedValues, + required Set allOptions, + required Function(Set) onSelectionChanged, + }) { + return InkWell( + onTap: () { + _showMultiSelectDialog( + context, + title: label, + allOptions: allOptions.toList()..sort(), + selectedValues: selectedValues, + onConfirm: (newSelection) { + setState(() { + onSelectionChanged(newSelection); + }); + _applyFilters(); + }, + ); + }, + child: Container( + padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 12), + decoration: BoxDecoration( + border: Border.all(color: Colors.grey.shade400), + borderRadius: BorderRadius.circular(4), + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text(label, style: const TextStyle(fontWeight: FontWeight.bold)), + Flexible( + child: Text( + selectedValues.isEmpty ? "全部" : "已选 ${selectedValues.length}", + style: TextStyle(color: Colors.grey[600], fontSize: 12), + overflow: TextOverflow.ellipsis, + ), + ), + const Icon(Icons.arrow_drop_down, color: Colors.grey), + ], + ), + ), + ); + } + + void _showMultiSelectDialog( + BuildContext context, { + required String title, + required List allOptions, + required Set selectedValues, + required Function(Set) onConfirm, + }) { + // 临时存储选择状态 + final tempSelection = Set.from(selectedValues); + + showDialog( + context: context, + builder: (ctx) { + return StatefulBuilder( + builder: (context, setDialogState) { + return AlertDialog( + title: Text(title), + content: SizedBox( + width: double.maxFinite, + child: ListView.builder( + shrinkWrap: true, + itemCount: allOptions.length, + itemBuilder: (context, index) { + final option = allOptions[index]; + final isSelected = tempSelection.contains(option); + return CheckboxListTile( + title: Text(option), + value: isSelected, + onChanged: (bool? value) { + setDialogState(() { + if (value == true) { + tempSelection.add(option); + } else { + tempSelection.remove(option); + } + }); + }, + controlAffinity: ListTileControlAffinity.leading, + dense: true, + ); + }, + ), + ), + actions: [ + TextButton( + onPressed: () { + setDialogState(() { + tempSelection.clear(); + }); + }, + child: const Text("清空"), + ), + TextButton( + onPressed: () => Navigator.pop(ctx), + child: const Text("取消"), + ), + ElevatedButton( + onPressed: () { + onConfirm(tempSelection); + Navigator.pop(ctx); + }, + child: const Text("确定"), + ), + ], + ); + }, + ); + }, + ); + } + @override Widget build(BuildContext context) { return Scaffold( @@ -460,34 +586,27 @@ class _MusicPageState extends State with SingleTickerProviderStateMix decoration: BoxDecoration(color: Colors.grey.withAlpha(20), border: Border(top: BorderSide(color: Colors.grey.shade300))), child: Column( children: [ + // --- 修改点 3: UI 替换为多选 Chip --- Row( children: [ Expanded( - child: _buildDropdown( + child: _buildMultiSelectChip( label: "版本", - value: _filterFromVersion, - items: [ - const DropdownMenuItem(value: null, child: Text("全部")), - ..._availableVersions.map((v) => DropdownMenuItem(value: v, child: Text(v))).toList() - ], - onChanged: (v) { - setState(() => _filterFromVersion = v); - _applyFilters(); + selectedValues: _filterVersions, + allOptions: _availableVersions, + onSelectionChanged: (newSet) { + _filterVersions = newSet; }, ), ), const SizedBox(width: 8), Expanded( - child: _buildDropdown( + child: _buildMultiSelectChip( label: "流派", - value: _filterGenre, - items: [ - const DropdownMenuItem(value: null, child: Text("全部")), - ..._availableGenres.map((g) => DropdownMenuItem(value: g, child: Text(g))).toList() - ], - onChanged: (v) { - setState(() => _filterGenre = v); - _applyFilters(); + selectedValues: _filterGenres, + allOptions: _availableGenres, + onSelectionChanged: (newSet) { + _filterGenres = newSet; }, ), ), @@ -517,10 +636,12 @@ class _MusicPageState extends State with SingleTickerProviderStateMix ), ); } + String getLeftEllipsisText(String text, int maxLength) { if (text.length <= maxLength) return text; return '...${text.substring(text.length - maxLength + 3)}'; // +3 是因为 "..." 占3个字符 } + Widget _buildDropdown({required String label, required dynamic value, required List items, required ValueChanged onChanged}) { return DropdownButtonFormField( value: value, @@ -531,7 +652,13 @@ class _MusicPageState extends State with SingleTickerProviderStateMix ); } - bool _hasFilters() => _searchQuery.isNotEmpty || _filterLevelType != null || _filterFromVersion != null || _filterGenre != null || _selectedMinLevel != null || _selectedMaxLevel != null; + bool _hasFilters() => + _searchQuery.isNotEmpty || + _filterLevelType != null || + _filterVersions.isNotEmpty || // 修改判断逻辑 + _filterGenres.isNotEmpty || // 修改判断逻辑 + _selectedMinLevel != null || + _selectedMaxLevel != null; Widget _buildBody() { if (_isLoading) return const Center(child: CircularProgressIndicator()); @@ -558,33 +685,19 @@ class _MusicPageState extends State with SingleTickerProviderStateMix if (song.ut != null && _userScoreCache.containsKey(100000 + song.id)) hasScore = true; // --- 新增:处理别名逻辑 --- - // 1. 获取原始列表 List rawAliases = song.albums ?? []; - - // 2. 去重 (保留插入顺序可以使用 LinkedHashSet,或者简单转为 Set 再转 List) - // 注意:Set.from 可能会打乱顺序,如果顺序不重要可以直接用。 - // 如果希望保持原顺序去重: final seen = {}; final uniqueAliases = rawAliases.where((alias) => seen.add(alias)).toList(); - - // 3. 过滤掉与标题或艺术家完全相同的重复项(可选,为了更整洁) final filteredAliases = uniqueAliases.where((alias) => alias != song.title && alias != song.artist ).toList(); - // 4. 拼接字符串,如果太长则截断 - // 我们设定一个最大显示长度或者最大行数,这里采用“尝试放入尽可能多的词,直到超过一定长度”的策略 String aliasDisplayText = ""; if (filteredAliases.isNotEmpty) { - // 尝试加入前 N 个别名,总长度控制在合理范围,例如 60-80 个字符,或者固定个数如 5-8 个 - // 这里采用固定个数策略,因为每个词长度不一,固定个数更容易预测高度 int maxAliasCount = 6; List displayList = filteredAliases.take(maxAliasCount).toList(); - aliasDisplayText = displayList.join(" · "); - - // 如果还有更多未显示的,加上省略提示 if (filteredAliases.length > maxAliasCount) { aliasDisplayText += " ..."; } @@ -632,12 +745,10 @@ class _MusicPageState extends State with SingleTickerProviderStateMix ), ), const SizedBox(height: 2), - // 这一行:id + 左侧标签 Row( mainAxisAlignment: MainAxisAlignment.end, crossAxisAlignment: CrossAxisAlignment.center, children: [ - // 标签区域(cn / jp / m2l / long) if (song.cn == true) _buildTag( "CN", @@ -669,8 +780,6 @@ class _MusicPageState extends State with SingleTickerProviderStateMix ), ), - - // id 文本 Text( " ${song.id}", style: const TextStyle( @@ -703,7 +812,6 @@ class _MusicPageState extends State with SingleTickerProviderStateMix Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, - // 关键:让内容在垂直方向上尽量分布,或者使用 MainAxisSize.min 配合 Spacer mainAxisSize: MainAxisSize.min, children: [ Text( @@ -725,13 +833,12 @@ class _MusicPageState extends State with SingleTickerProviderStateMix const SizedBox(height: 6), LayoutBuilder( builder: (context, constraints) { - // 可选:根据剩余宽度动态决定显示多少行,这里简单处理为最多2行 return Text( aliasDisplayText, style: TextStyle( fontSize: 10, color: Colors.grey[500], - height: 1.2, // 紧凑行高 + height: 1.2, ), maxLines: 5, overflow: TextOverflow.ellipsis, @@ -757,16 +864,11 @@ class _MusicPageState extends State with SingleTickerProviderStateMix } Widget _buildTag( String text, { - // 背景:纯色 或 渐变 二选一 Color? backgroundColor, Gradient? gradient, - - // 阴影配置 Color? shadowColor, double shadowBlurRadius = 2, Offset shadowOffset = const Offset(0, 1), - - // 可选样式扩展 double borderRadius = 2, double fontSize = 8, Color textColor = Colors.white, @@ -775,11 +877,9 @@ class _MusicPageState extends State with SingleTickerProviderStateMix margin: const EdgeInsets.only(right: 3), padding: const EdgeInsets.symmetric(horizontal: 3, vertical: 1), decoration: BoxDecoration( - // 优先使用渐变,没有渐变则使用纯色 color: gradient == null ? (backgroundColor ?? Colors.blueAccent) : null, gradient: gradient, borderRadius: BorderRadius.circular(borderRadius), - // 阴影:只有传入 shadowColor 才显示 boxShadow: shadowColor != null ? [ BoxShadow( @@ -802,8 +902,6 @@ class _MusicPageState extends State with SingleTickerProviderStateMix } List _getDifficultyChipsByType(SongModel song) { final diffs = _getAllDifficultiesWithType(song); - - // 按 type 分组:SD / DX / UT final Map> typeGroups = {}; for (var item in diffs) { final type = item['type'] as String; @@ -811,7 +909,6 @@ class _MusicPageState extends State with SingleTickerProviderStateMix typeGroups[type]!.add(item); } - // 固定顺序:SD → DX → UT final order = ['SD', 'DX', 'UT']; List rows = []; @@ -819,7 +916,6 @@ class _MusicPageState extends State with SingleTickerProviderStateMix final items = typeGroups[type]; if (items == null || items.isEmpty) continue; - // 每一个类型 = 一行 Wrap final row = Wrap( spacing: 6, runSpacing: 4, @@ -837,8 +933,6 @@ class _MusicPageState extends State with SingleTickerProviderStateMix if (isBanquet) { color = Colors.pinkAccent; - - // 多UT按ID显示对应名称 if (type == 'UT') { final utTitleMap = song.utTitle as Map?; if (utTitleMap != null && utTitleMap.isNotEmpty) { @@ -882,7 +976,7 @@ class _MusicPageState extends State with SingleTickerProviderStateMix } return Container( - padding: const EdgeInsets.symmetric(horizontal: 5, vertical: 2), + padding: const EdgeInsets.symmetric(horizontal: 2, vertical: 1), decoration: BoxDecoration( color: color.withOpacity(hasScore ? 0.25 : 0.1), borderRadius: BorderRadius.circular(4), @@ -892,7 +986,7 @@ class _MusicPageState extends State with SingleTickerProviderStateMix isBanquet ? label : "$label ${lvValue.toStringAsFixed(1)}", style: TextStyle( color: color, - fontSize: 10, + fontSize: 9, fontWeight: FontWeight.bold, ), ), @@ -916,4 +1010,4 @@ class _MusicPageState extends State with SingleTickerProviderStateMix return "https://cdn.godserver.cn/resource/static/mai/cover/$displayId.png"; } } -} +} \ No newline at end of file diff --git a/lib/pages/music/score_single.dart b/lib/pages/music/score_single.dart index c063d4d..89b062f 100644 --- a/lib/pages/music/score_single.dart +++ b/lib/pages/music/score_single.dart @@ -1,10 +1,15 @@ +import 'dart:io'; import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; +import 'package:http/http.dart' as http; +import 'package:path_provider/path_provider.dart'; +import 'package:open_file/open_file.dart'; // 引入打开文件的包 import '../../../model/song_model.dart'; import '../../../providers/user_provider.dart'; import '../../../service/song_service.dart'; import '../../../service/user_service.dart'; import '../../../widgets/score_progress_chart.dart'; +import '../../tool/gradientText.dart'; class SongDetailPage extends StatefulWidget { final SongModel song; @@ -22,8 +27,11 @@ class SongDetailPage extends StatefulWidget { class _SongDetailPageState extends State { String? _selectedType; + bool _isAliasExpanded = false; + + // 用于跟踪下载状态,key为 "type_levelId" + final Map _isDownloading = {}; - // 缓存图表数据: Key为 "SD" / "DX" / "UT_realId" final Map>> _chartDataCache = {}; final Map _isLoadingChart = {}; @@ -74,9 +82,90 @@ class _SongDetailPageState extends State { return all; } - // 核心:SD/DX 只加载一次,UT 每个谱面加载一次 + // ===================== 【新增】下载并打开 ADX 谱面功能 ===================== + Future _downloadAndOpenAdx(String type, Map diff) async { + int levelId = diff['level_id'] ?? 0; + String downloadKey = "${type}_$levelId"; + + // 防止重复点击 + if (_isDownloading[downloadKey] == true) return; + + setState(() { + _isDownloading[downloadKey] = true; + }); + + try { + // 1. 构建 URL + int songId = widget.song.id; + String url; + + // UT 谱面通常没有标准的 CDN adx 下载地址,这里只处理 SD 和 DX + if (type == 'UT') { + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar(content: Text("UT 宴会谱暂不支持直接下载 ADX 文件")), + ); + setState(() => _isDownloading[downloadKey] = false); + return; + } + + if (type == 'SD') { + // SD: cdn.godserver.cn/resource/static/adx/00000/{songId}.adx + String idStr = songId.toString().padLeft(5, '0'); + url = "https://cdn.godserver.cn/resource/static/adx/$idStr.adx"; + } else { + // DX: cdn.godserver.cn/resource/static/adx/00000/{songId+10000}.adx + int dxId = songId + 10000; + String idStr = dxId.toString().padLeft(5, '0'); + url = "https://cdn.godserver.cn/resource/static/adx/$idStr.adx"; + } + + // 2. 下载文件 + final response = await http.get(Uri.parse(url)); + + if (response.statusCode != 200) { + throw Exception("下载失败: HTTP ${response.statusCode}"); + } + + // 3. 获取临时目录并保存文件 + final directory = await getApplicationDocumentsDirectory(); + // 注意:iOS 上 getApplicationDocumentsDirectory 是持久化的。 + // 如果希望每次下载都清理,可以使用 getTemporaryDirectory()。 + // 这里为了稳定性,使用 DocumentsDirectory,但文件名保持唯一性。 + + // 生成文件名: SongTitle_Type_Level.adx + String safeTitle = widget.song.title?.replaceAll(RegExp(r'[^\w\s\u4e00-\u9fa5]'), '_') ?? "song"; + String fileName = "${safeTitle}_${type}_Lv${levelId}.adx"; + + // 确保文件名不冲突,可以加时间戳或者随机数,这里简单处理 + final filePath = "${directory.path}/$fileName"; + final file = File(filePath); + + await file.writeAsBytes(response.bodyBytes); + + // 4. 调用系统原生打开 + // open_file 会尝试寻找能打开 .adx 的应用 + final result = await OpenFile.open(filePath); + + if (result.type != ResultType.done) { + // 如果没有安装能打开 adx 的应用,提示用户 + ScaffoldMessenger.of(context).showSnackBar( + SnackBar(content: Text("无法打开文件: ${result.message},请确保已安装谱面编辑器")), + ); + } + + } catch (e) { + print("Download error: $e"); + ScaffoldMessenger.of(context).showSnackBar( + SnackBar(content: Text("下载出错: $e")), + ); + } finally { + setState(() { + _isDownloading[downloadKey] = false; + }); + } + } + Future _loadTypeChartData(String type) async { - // 已经加载/正在加载 → 直接返回 if (_chartDataCache.containsKey(type) || _isLoadingChart[type] == true) { return; } @@ -94,7 +183,6 @@ class _SongDetailPageState extends State { return; } - // 获取当前类型的 API ID int apiMusicId; if (type == 'SD') { apiMusicId = widget.song.id; @@ -105,11 +193,9 @@ class _SongDetailPageState extends State { return; } - // 调用一次 API,拿到全部难度数据 final logs = await UserService.getChartLog(token, [apiMusicId]); logs.sort((a, b) => a.time.compareTo(b.time)); - // 转换数据 final chartList = logs.map((log) => { 'achievement': log.segaChartNew?.achievement ?? 0, 'time': log.time, @@ -118,7 +204,7 @@ class _SongDetailPageState extends State { 'syncStatus': log.segaChartNew?.syncStatus ?? 0, 'deluxscoreMax': log.segaChartNew?.deluxscoreMax ?? 0, 'playCount': log.segaChartNew?.playCount ?? 0, - 'level': log.segaChartNew?.level ?? 0, // 保存难度等级用于过滤 + 'level': log.segaChartNew?.level ?? 0, }).toList(); setState(() { @@ -131,7 +217,6 @@ class _SongDetailPageState extends State { } } - // UT 单独加载 Future _loadUtChartData(String cacheKey, int realId) async { if (_chartDataCache.containsKey(cacheKey) || _isLoadingChart[cacheKey] == true) { return; @@ -170,6 +255,73 @@ class _SongDetailPageState extends State { } } + Widget _buildAliasSection(List rawAliases) { + final uniqueAliases = rawAliases + .where((e) => e != null && e.trim().isNotEmpty) + .map((e) => e.trim()) + .toSet() + .toList(); + + if (uniqueAliases.isEmpty) return const SizedBox.shrink(); + + final displayLimit = _isAliasExpanded ? uniqueAliases.length : 3; + final displayedAliases = uniqueAliases.take(displayLimit).toList(); + final hasMore = uniqueAliases.length > 3; + + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Wrap( + spacing: 6.0, + runSpacing: 4.0, + children: [ + ...displayedAliases.map((alias) => Container( + decoration: BoxDecoration( + color: Colors.grey.shade100, + borderRadius: BorderRadius.circular(4), + border: Border.all(color: Colors.grey.shade300, width: 0.5), + ), + padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 2), + child: Text( + alias, + style: TextStyle( + fontSize: 11, + color: Colors.grey[700], + fontStyle: FontStyle.italic, + ), + ), + )).toList(), + ], + ), + if (hasMore) + InkWell( + onTap: () { + setState(() { + _isAliasExpanded = !_isAliasExpanded; + }); + }, + child: Padding( + padding: const EdgeInsets.only(top: 4), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Text( + _isAliasExpanded ? "收起" : "查看更多 (${uniqueAliases.length - 3})", + style: TextStyle(fontSize: 11), + ), + Icon( + _isAliasExpanded ? Icons.keyboard_arrow_up : Icons.keyboard_arrow_down, + size: 14, + color: Theme.of(context).primaryColor, + ), + ], + ), + ), + ), + ], + ); + } + @override Widget build(BuildContext context) { final coverUrl = _getCoverUrl(widget.song.id); @@ -177,129 +329,133 @@ class _SongDetailPageState extends State { final availableTypes = _getAvailableTypes(); return Scaffold( - appBar: AppBar(title: Text(widget.song.title ?? "歌曲详情")), + appBar: AppBar( + title: Text(widget.song.title ?? "歌曲详情"), + elevation: 2, + ), body: SingleChildScrollView( - padding: const EdgeInsets.all(16), + padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 16), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - // 歌曲封面 + 信息 - Row( - crossAxisAlignment: CrossAxisAlignment.start, - // 修复:这里缺少了括号 - children: [ - ClipRRect( - borderRadius: BorderRadius.circular(12), - child: Image.network( - coverUrl, - width: 120, - height: 120, - fit: BoxFit.cover, - errorBuilder: (_, __, ___) => Container( - width: 120, - height: 120, - color: Colors.grey[200], - child: const Icon(Icons.music_note, size: 40), - ), - ), - ), - const SizedBox(width: 16), - Expanded( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - "[${widget.song.id}] ${widget.song.title}", - style: const TextStyle( - fontSize: 18, - fontWeight: FontWeight.bold, + Card( + shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)), + elevation: 4, + shadowColor: Colors.purpleAccent.withOpacity(0.2), + child: Padding( + padding: const EdgeInsets.all(14), + child: Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + ClipRRect( + borderRadius: BorderRadius.circular(10), + child: Image.network( + coverUrl, + width: 90, + height: 90, + fit: BoxFit.cover, + errorBuilder: (_, __, ___) => Container( + width: 90, + height: 90, + color: Colors.grey[200], + child: const Icon(Icons.music_note, size: 36), ), ), - const SizedBox(height: 4), - Text( - "艺术家:${widget.song.artist ?? '未知'}", - style: const TextStyle(fontSize: 14), - ), - Text( - "流派:${widget.song.genre} | 版本:${widget.song.from}", - style: const TextStyle(fontSize: 13, color: Colors.grey), - ), - Text( - "BPM:${widget.song.bpm ?? '未知'}", - style: const TextStyle(fontSize: 13, color: Colors.grey), - ), - ], - ), - ), - ], - ), - const SizedBox(height: 24), - - // 难度选择器 - if (availableTypes.length > 1) ...[ - Row( - children: [ - const Text( - "难度详情", - style: TextStyle( - fontSize: 17, - fontWeight: FontWeight.bold, ), - ), - const SizedBox(width: 10), - Expanded( - child: CupertinoSlidingSegmentedControl( - groupValue: _selectedType, - backgroundColor: Colors.grey.shade200, - thumbColor: Theme.of(context).primaryColor.withOpacity(0.8), - children: { - for (var type in availableTypes) - type: Padding( - padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8), - child: Text( - type, - style: TextStyle( - color: _selectedType == type ? Colors.white : Colors.black87, - fontWeight: FontWeight.bold, - ), + const SizedBox(width: 14), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + "[${widget.song.id}] ${widget.song.title}", + style: const TextStyle( + fontSize: 17, + fontWeight: FontWeight.bold, ), + maxLines: 2, + overflow: TextOverflow.ellipsis, ), - }, - onValueChanged: (value) { - if (value != null) { - setState(() { - _selectedType = value; - }); - } - }, + const SizedBox(height: 6), + Text( + "艺术家:${widget.song.artist ?? '未知'}", + style: const TextStyle(fontSize: 13), + ), + const SizedBox(height: 4), + if (widget.song.albums?.isNotEmpty == true) + _buildAliasSection(widget.song.albums!), + const SizedBox(height: 6), + Text( + "${widget.song.genre ?? ''} | ${widget.song.from ?? ''}", + style: TextStyle(fontSize: 12, color: Colors.grey[600]), + ), + const SizedBox(height: 2), + Text( + "BPM:${widget.song.bpm ?? '未知'}", + style: TextStyle(fontSize: 12, color: Colors.grey[600]), + ), + ], + ), ), - ), - ], + ], + ), ), - ] else ...[ - const Text( - "难度详情", - style: TextStyle(fontSize: 17, fontWeight: FontWeight.bold), - ), - ], - const SizedBox(height: 12), + ), + + const SizedBox(height: 20), + + if (availableTypes.isNotEmpty) ...[ + Padding( + padding: const EdgeInsets.symmetric(horizontal: 4), + child: Row( + children: [ + const Text( + "难度详情", + style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold), + ), + const SizedBox(width: 12), + if (availableTypes.length > 1) + Expanded( + child: CupertinoSlidingSegmentedControl( + groupValue: _selectedType, + backgroundColor: Colors.grey.shade200, + thumbColor: Theme.of(context).primaryColor, + children: { + for (var type in availableTypes) + type: Padding( + padding: const EdgeInsets.symmetric(vertical: 6, horizontal: 8), + child: Text( + type, + style: TextStyle( + color: _selectedType == type ? Colors.white : Colors.black87, + fontWeight: FontWeight.w600, + ), + ), + ), + }, + onValueChanged: (val) { + if (val != null) setState(() => _selectedType = val); + }, + ), + ), + ], + ), + ), + const SizedBox(height: 12), + ], - // 难度列表 if (diffs.isEmpty) - Center( + const Center( child: Padding( - padding: const EdgeInsets.all(20.0), - child: Text( - "暂无 ${_selectedType ?? ''} 谱面数据", - style: const TextStyle(color: Colors.grey), - ), + padding: EdgeInsets.all(30), + child: Text("暂无谱面数据", style: TextStyle(color: Colors.grey)), ), ) else ...diffs.map((item) => _diffItem( type: item['type'], diff: item['diff'], - )).toList(), + )), const SizedBox(height: 30), ], @@ -310,8 +466,7 @@ class _SongDetailPageState extends State { Widget _diffItem({required String type, required Map diff}) { int levelId = diff['level_id'] ?? 0; - final double lvValue = - double.tryParse(diff['level_value']?.toString() ?? '') ?? 0; + final double lvValue = double.tryParse(diff['level_value']?.toString() ?? '') ?? 0; final designer = diff['note_designer'] ?? "-"; final notes = diff['notes'] ?? {}; final total = notes['total'] ?? 0; @@ -323,9 +478,7 @@ class _SongDetailPageState extends State { int realId; String? utTitleName; String cacheKey = ""; - bool hasScoreData = false; - // 加载逻辑:SD/DX 只加载一次,UT 每个加载一次 if (type == 'UT') { realId = (diff['id'] as num?)?.toInt() ?? 0; final utTitleMap = widget.song.utTitle as Map?; @@ -339,31 +492,26 @@ class _SongDetailPageState extends State { }); } else { realId = _getRealMusicId(type); - cacheKey = type; // SD/DX 用类型做缓存 key + cacheKey = type; WidgetsBinding.instance.addPostFrameCallback((_) { _loadTypeChartData(type); }); } - // 图表数据:SD/DX 自动过滤当前难度,UT 直接使用 List> chartHistory = []; if (_chartDataCache.containsKey(cacheKey)) { if (type == 'UT') { chartHistory = _chartDataCache[cacheKey]!; } else { - // SD/DX 从全量数据中过滤当前难度 chartHistory = _chartDataCache[cacheKey]! .where((e) => e['level'] == levelId) .toList(); } } - hasScoreData = chartHistory.isNotEmpty; - - // 成绩信息 + bool hasScoreData = chartHistory.isNotEmpty; final score = widget.userScoreCache[realId]?[levelId]; bool hasUserScore = score != null; - // 显示名称与颜色 String name = ""; Color color = Colors.grey; bool isBanquet = type == 'UT'; @@ -382,11 +530,23 @@ class _SongDetailPageState extends State { } } + final rating990 = _calculateRating(lvValue, 99.0); + final rating995 = _calculateRating(lvValue, 99.5); + final rating1000 = _calculateRating(lvValue, 100.0); + final rating1003 = _calculateRating(lvValue, 100.3); + final rating1005 = _calculateRating(lvValue, 100.5); + + // 下载状态的 Key + String downloadKey = "${type}_$levelId"; + bool isDownloading = _isDownloading[downloadKey] ?? false; + return Card( - margin: const EdgeInsets.only(bottom: 10), - shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)), + margin: const EdgeInsets.symmetric(horizontal: 4, vertical: 8), + shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(14)), + elevation: 5, + shadowColor: color.withOpacity(0.2), child: Padding( - padding: const EdgeInsets.all(14), + padding: const EdgeInsets.all(16), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ @@ -394,11 +554,11 @@ class _SongDetailPageState extends State { mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Container( - padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4), + padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 6), decoration: BoxDecoration( color: color.withOpacity(0.15), - borderRadius: BorderRadius.circular(6), - border: Border.all(color: color.withOpacity(0.3)), + borderRadius: BorderRadius.circular(8), + border: Border.all(color: color.withOpacity(0.4), width: 1.2), ), child: Text( isBanquet ? name : "$name (${lvValue.toStringAsFixed(1)})", @@ -409,20 +569,116 @@ class _SongDetailPageState extends State { ), ), ), - if (hasUserScore) - const Icon(Icons.star, color: Colors.amber, size: 20), + Row( + children: [ + if (hasUserScore) + const Icon(Icons.star_rounded, color: Colors.amber, size: 22), + + // ===================== 【新增】下载/预览按钮 ===================== + const SizedBox(width: 8), + InkWell( + onTap: isDownloading || type == 'UT' ? null : () { + _downloadAndOpenAdx(type, diff); + }, + borderRadius: BorderRadius.circular(20), + child: Container( + padding: const EdgeInsets.all(6), + decoration: BoxDecoration( + color: type == 'UT' ? Colors.grey.shade300 : Theme.of(context).primaryColor.withOpacity(0.1), + shape: BoxShape.circle, + ), + child: isDownloading + ? SizedBox( + width: 16, + height: 16, + child: CircularProgressIndicator( + strokeWidth: 2, + valueColor: AlwaysStoppedAnimation(color), + ), + ) + : Icon( + Icons.download_rounded, + size: 18, + color: type == 'UT' ? Colors.grey : color, + ), + ), + ), + ], + ), ], ), + const SizedBox(height: 10), + Text("谱师:$designer", style: const TextStyle(fontSize: 14)), const SizedBox(height: 8), - Text("谱师:$designer", style: const TextStyle(fontSize: 13)), - const SizedBox(height: 4), - Text( - "物量:$total | TAP:$tap HOLD:$hold SLIDE:$slide BRK:$brk", - style: const TextStyle(color: Colors.grey, fontSize: 12), + + Table( + columnWidths: const { + 0: FixedColumnWidth(40), + 1: FixedColumnWidth(40), + 2: FixedColumnWidth(40), + 3: FixedColumnWidth(40), + 4: FixedColumnWidth(40), + }, + children: [ + TableRow( + children: [ + _tableCell("总物量", color), + _tableCell("TAP", color), + _tableCell("HOLD", color), + _tableCell("SLIDE", color), + _tableCell("BRK", color), + ], + ), + TableRow( + children: [ + _tableCell(total.toString(), color, isHeader: false), + _tableCell(tap.toString(), color, isHeader: false), + _tableCell(hold.toString(), color, isHeader: false), + _tableCell(slide.toString(), color, isHeader: false), + _tableCell(brk.toString(), color, isHeader: false), + ], + ), + ], + ), + const SizedBox(height: 10), + + Table( + columnWidths: const { + 0: FixedColumnWidth(50), + 1: FixedColumnWidth(50), + 2: FixedColumnWidth(50), + 3: FixedColumnWidth(50), + 4: FixedColumnWidth(50), + 5: FixedColumnWidth(50), + }, + children: [ + TableRow( + children: [ + _tableCell("完成度", color), + _tableCell("99.0%", color), + _tableCell("99.5%", color), + _tableCell("100.0%", color), + _tableCell("100.3%", color), + _tableCell("100.5%", color), + _tableCell("", color), + ], + ), + TableRow( + children: [ + _tableCell("Rating", color, isHeader: false), + _tableCell(rating990.toStringAsFixed(2), color, isHeader: false), + _tableCell(rating995.toStringAsFixed(2), color, isHeader: false), + _tableCell(rating1000.toStringAsFixed(2), color, isHeader: false), + _tableCell(rating1003.toStringAsFixed(2), color, isHeader: false), + _tableCell(rating1005.toStringAsFixed(2), color, isHeader: false), + _tableCell("", color, isHeader: false), + ], + ), + ], ), - const SizedBox(height: 12), - // ✅ 修复:没有成绩直接显示文字,绝不显示加载圈 + const SizedBox(height: 14), + if (hasScoreData) Column( crossAxisAlignment: CrossAxisAlignment.start, @@ -430,30 +686,32 @@ class _SongDetailPageState extends State { Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - const Text("推分进程", style: TextStyle(fontSize: 12, color: Colors.grey)), + const Text("推分进程", style: TextStyle(fontSize: 13, color: Colors.grey)), Text( "最近: ${(chartHistory.last['achievement'] / 10000.0).toStringAsFixed(4)}%", - style: TextStyle(fontSize: 12, color: color), + style: TextStyle(fontSize: 13, color: color, fontWeight: FontWeight.w600), ), ], ), - const SizedBox(height: 4), + const SizedBox(height: 8), ScoreProgressChart( historyScores: chartHistory, lineColor: color, fillColor: color, ), - const SizedBox(height: 5), ], ) else - const Text("暂无历史记录", style: TextStyle(fontSize: 12, color: Colors.grey)), + const Padding( + padding: EdgeInsets.symmetric(vertical: 8), + child: Text("暂无历史记录", style: TextStyle(fontSize: 13, color: Colors.grey)), + ), if (hasUserScore) ...[ - const SizedBox(height: 10), - const Divider(height: 1), - const SizedBox(height: 6), - _buildScoreInfo(score!), + const SizedBox(height: 12), + Divider(height: 1, color: Colors.grey[300]), + const SizedBox(height: 12), + _buildScoreInfo(score!,diff), ], ], ), @@ -461,41 +719,212 @@ class _SongDetailPageState extends State { ); } - Widget _buildScoreInfo(Map score) { + Widget _tableCell(String text, Color color, {bool isHeader = true}) { + return Center( + child: Padding( + padding: const EdgeInsets.symmetric(vertical: 4), + child: Text( + text, + style: TextStyle( + fontSize: isHeader ? 11 : 12, + color: isHeader ? color : color.withOpacity(0.99), + fontWeight: isHeader ? FontWeight.bold : FontWeight.w500, + ), + ), + ), + ); + } + + static int _calculateRating(double diff, double achievementPercent) { + double sys = 22.4; + double ach = achievementPercent; + + if (ach >= 100.5000) { + return (diff * 22.512).floor(); + } + if (ach == 100.4999) { + sys = 22.2; + } else if (ach >= 100.0000) { + sys = 21.6; + } else if (ach == 99.9999) { + sys = 21.4; + } else if (ach >= 99.5000) { + sys = 21.1; + } else if (ach >= 99.0000) { + sys = 20.8; + } else if (ach >= 98.0000) { + sys = 20.3; + } else if (ach >= 97.0000) { + sys = 20.0; + } else if (ach >= 94.0000) { + sys = 16.8; + } else if (ach >= 90.0000) { + sys = 15.2; + } else if (ach >= 80.0000) { + sys = 13.6; + } else if (ach >= 75.0000) { + sys = 12.0; + } else if (ach >= 70.0000) { + sys = 11.2; + } else if (ach >= 60.0000) { + sys = 9.6; + } else if (ach >= 50.0000) { + sys = 8.0; + } else { + sys = 0.0; + } + + if (sys == 0.0) return 0; + return (diff * sys * ach / 100).floor(); + } + + Widget _buildScoreInfo(Map score, Map diff) { final ach = (score['achievement'] ?? 0) / 10000; final rank = _getRankText(score['scoreRank'] ?? 0); - final combo = _comboText(score['comboStatus'] ?? 0); - final sync = _syncText(score['syncStatus'] ?? 0); final dxScore = score['deluxscoreMax'] ?? 0; final playCount = score['playCount'] ?? 0; + final rating = score['rating'] ?? 0; - return Column( + int comboStatus = score['comboStatus'] ?? 0; + int syncStatus = score['syncStatus'] ?? 0; + + Color achColor = _getColorByAchievement(ach); + Color rankColor = _getColorByRank(rank); + + int totalNotes = diff['notes']['total'] ?? 0; + int allDx = totalNotes * 3; + double perc = allDx > 0 ? dxScore / allDx : 0.0; + + String? comboIconPath; + switch (comboStatus) { + case 1: comboIconPath = "images/UI_MSS_MBase_Icon_FC.png"; break; + case 2: comboIconPath = "images/UI_MSS_MBase_Icon_FCp.png"; break; + case 3: comboIconPath = "images/UI_MSS_MBase_Icon_AP.png"; break; + case 4: comboIconPath = "images/UI_MSS_MBase_Icon_APp.png"; break; + } + + String? syncIconPath; + switch (syncStatus) { + case 1: syncIconPath = "images/UI_MSS_MBase_Icon_FS.png"; break; + case 2: syncIconPath = "images/UI_MSS_MBase_Icon_FSp.png"; break; + case 3: syncIconPath = "images/UI_MSS_MBase_Icon_FSD.png"; break; + case 4: syncIconPath = "images/UI_MSS_MBase_Icon_FSDp.png"; break; + case 5: syncIconPath = "images/UI_MSS_MBase_Icon_Sync.png"; break; + } + + return Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ - const Text("你的成绩:", - style: TextStyle(fontWeight: FontWeight.bold, fontSize: 13)), - const SizedBox(height: 4), - Row( - children: [ - Text("达成率:${ach.toStringAsFixed(4)}%", - style: TextStyle( - color: _getColorByAchievement(ach), - fontWeight: FontWeight.bold)), - const Spacer(), - Text("评级:$rank", - style: TextStyle( - color: _getColorByRank(rank), fontWeight: FontWeight.bold)), - ], + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const Row( + children: [ + Icon(Icons.emoji_events_rounded, color: Colors.amber, size: 18), + SizedBox(width: 4), + Text( + "你的成绩", + style: TextStyle( + fontWeight: FontWeight.bold, + fontSize: 15, + height: 1.1, + ), + ), + ], + ), + const SizedBox(height: 10), + Row( + crossAxisAlignment: CrossAxisAlignment.end, + children: [ + GradientText( + data: "${ach.toStringAsFixed(4)}%", + style: const TextStyle(fontSize: 30, fontWeight: FontWeight.bold), + gradientLayers: [ + GradientLayer( + gradient: const LinearGradient( + colors: [Colors.pinkAccent, Colors.blue], + ), + blendMode: BlendMode.srcIn, + ), + ], + ), + const SizedBox(width: 8), + _buildTag(rank, rankColor), + if (comboIconPath != null) + Padding( + padding: const EdgeInsets.only(left: 2), + child: Image.asset(comboIconPath, width: 40, height: 40), + ), + if (syncIconPath != null) + Padding( + padding: const EdgeInsets.only(left: 4), + child: Image.asset(syncIconPath, width: 40, height: 40), + ), + ], + ), + const SizedBox(height: 12), + Row( + children: [ + const Icon(Icons.score_rounded, size: 14, color: Colors.blueGrey), + const SizedBox(width: 4), + Text("DX:$dxScore / $allDx", style: TextStyle(fontSize: 13, color: Colors.grey[700])), + const SizedBox(width: 10), + if (perc >= 0.85) + Padding( + padding: const EdgeInsets.only(right: 4), + child: _getDxStarIcon(perc), + ), + const Spacer(), + Icon(Icons.star_rate_rounded, size: 14, color: Colors.amber), + const SizedBox(width: 3), + Text("Rating:$rating", style: TextStyle(fontSize: 12, color: Colors.grey[700], fontWeight: FontWeight.w500)), + const SizedBox(width: 10), + Icon(Icons.play_circle_outline_rounded, size: 14, color: Colors.grey[600]), + const SizedBox(width: 3), + Text("$playCount 次", style: TextStyle(fontSize: 12, color: Colors.grey[600])), + ], + ), + ], + ), ), - const SizedBox(height: 2), - Text("Combo:$combo | Sync:$sync", - style: const TextStyle(fontSize: 12, color: Colors.blueGrey)), - Text("DX分数:$dxScore | 游玩次数:$playCount", - style: const TextStyle(fontSize: 12, color: Colors.grey)), ], ); } + Widget _getDxStarIcon(double perc) { + String assetPath; + if (perc >= 0.97) { + assetPath = "images/UI_GAM_Gauge_DXScoreIcon_05.png"; + } else if (perc >= 0.95) { + assetPath = "images/UI_GAM_Gauge_DXScoreIcon_04.png"; + } else if (perc >= 0.93) { + assetPath = "images/UI_GAM_Gauge_DXScoreIcon_03.png"; + } else if (perc >= 0.90) { + assetPath = "images/UI_GAM_Gauge_DXScoreIcon_02.png"; + } else if (perc >= 0.85) { + assetPath = "images/UI_GAM_Gauge_DXScoreIcon_01.png"; + } else { + return const SizedBox(); + } + return Image.asset(assetPath, width: 30, height: 30, fit: BoxFit.contain); + } + + Widget _buildTag(String text, Color color) { + return Container( + padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 5), + decoration: BoxDecoration( + color: color.withOpacity(0.15), + borderRadius: BorderRadius.circular(8), + border: Border.all(color: color.withOpacity(0.5), width: 1.2), + ), + child: Text( + text, + style: TextStyle(color: color, fontWeight: FontWeight.bold, fontSize: 14), + ), + ); + } + Color _getColorByAchievement(double ach) { if (ach >= 100.5) return const Color(0xFFD4AF37); if (ach >= 100.0) return Colors.purple; @@ -507,9 +936,10 @@ class _SongDetailPageState extends State { } Color _getColorByRank(String rank) { + if (rank.contains("SSS+")) return const Color(0xFFD4AF37); if (rank.contains("SSS")) return Colors.purple; - if (rank.contains("SS")) return Colors.deepPurple; - if (rank.contains("S")) return Colors.blue; + if (rank.contains("SS+") || rank.contains("SS")) return Colors.deepPurple; + if (rank.contains("S+") || rank.contains("S")) return Colors.blue; return Colors.green; } @@ -542,26 +972,6 @@ class _SongDetailPageState extends State { } } - String _comboText(int s) { - switch (s) { - case 1: return "FC"; - case 2: return "FC+"; - case 3: return "AP"; - case 4: return "AP+"; - default: return "无"; - } - } - - String _syncText(int s) { - switch (s) { - case 1: return "FS"; - case 2: return "FS+"; - case 3: return "FDX"; - case 4: return "FDX+"; - default: return "无"; - } - } - int _getRealMusicId(String type) { if (type == "SD") return widget.song.id; if (type == "DX") return 10000 + widget.song.id; diff --git a/lib/pages/score/score_page.dart b/lib/pages/score/score_page.dart index 1bd2aed..6ff07d1 100644 --- a/lib/pages/score/score_page.dart +++ b/lib/pages/score/score_page.dart @@ -32,8 +32,9 @@ class _ScorePageState extends State with SingleTickerProviderStateMix bool _isAdvancedFilterExpanded = false; double? _minAchievement; late final TextEditingController _minAchievementController; - String? _filterFromVersion; - String? _filterGenre; + // 改为多选 + List _filterFromVersions = []; + List _filterGenres = []; double? _selectedMinLevel; double? _selectedMaxLevel; int? _filterComboStatus; @@ -57,7 +58,7 @@ class _ScorePageState extends State with SingleTickerProviderStateMix String get _currentDataSource => UserProvider.instance.scoreDataSource; String? get _currentSegaId => UserProvider.instance.selectedSegaId; - String? get _currentCnUserName => UserProvider.instance.selectedCnUserName; // 新增 + String? get _currentCnUserName => UserProvider.instance.selectedCnUserName; @override void initState() { @@ -148,7 +149,6 @@ class _ScorePageState extends State with SingleTickerProviderStateMix } } - /// 【核心修改】加载数据逻辑 Future _loadData({bool isInitialLoad = false}) async { if (isInitialLoad) { setState(() { _isLoading = true; }); @@ -161,7 +161,6 @@ class _ScorePageState extends State with SingleTickerProviderStateMix final token = userProvider.token; if (token == null) throw "未登录,请先登录"; - // 1. 获取歌曲元数据 if (_allSongs.isEmpty || isInitialLoad) { final songs = await SongService.getAllSongs(); _allSongs = songs; @@ -170,9 +169,7 @@ class _ScorePageState extends State with SingleTickerProviderStateMix _availableGenres = songs.map((s) => s.genre).where((g) => g.isNotEmpty).toSet(); } - // 2. 获取用户成绩数据 if (_currentDataSource == 'sega') { - // --- Sega ID 模式 --- final segaId = _currentSegaId; if (segaId == null || segaId.isEmpty) { throw "请选择一个有效的 Sega ID"; @@ -207,8 +204,6 @@ class _ScorePageState extends State with SingleTickerProviderStateMix }).toList(); } else { - // --- 国服模式 --- - // 【修改点】传递选中的用户名 final scoreData = await SongService.getUserAllScores(token,name: _currentCnUserName); if (scoreData.containsKey('userScoreAll_')) { @@ -243,13 +238,12 @@ class _ScorePageState extends State with SingleTickerProviderStateMix } } - /// 【核心修改】获取过滤并排序后的列表 List get _filteredMusicList { bool isNoFilter = _searchQuery.isEmpty && _filterLevelType == null && _filterRank == null && - _filterFromVersion == null && - _filterGenre == null && + _filterFromVersions.isEmpty && + _filterGenres.isEmpty && _selectedMinLevel == null && _selectedMaxLevel == null && _minAchievement == null && @@ -315,10 +309,12 @@ class _ScorePageState extends State with SingleTickerProviderStateMix } if (song != null) { - if (_filterFromVersion != null && song.from != _filterFromVersion) return false; - if (_filterGenre != null && song.genre != _filterGenre) return false; + // 版本多选过滤 + if (_filterFromVersions.isNotEmpty && !_filterFromVersions.contains(song.from)) return false; + // 流派多选过滤 + if (_filterGenres.isNotEmpty && !_filterGenres.contains(song.genre)) return false; } else { - if (_filterFromVersion != null || _filterGenre != null) { + if (_filterFromVersions.isNotEmpty || _filterGenres.isNotEmpty) { return false; } } @@ -373,8 +369,8 @@ class _ScorePageState extends State with SingleTickerProviderStateMix _searchQuery = ''; _filterLevelType = null; _filterRank = null; - _filterFromVersion = null; - _filterGenre = null; + _filterFromVersions.clear(); + _filterGenres.clear(); _selectedMinLevel = null; _selectedMaxLevel = null; _minAchievement = null; @@ -415,16 +411,13 @@ class _ScorePageState extends State with SingleTickerProviderStateMix ); } - /// 【核心修改】构建数据源选择器,支持国服多账号 Widget _buildDataSourceSelector() { final userProvider = UserProvider.instance; final isSegaMode = userProvider.scoreDataSource == 'sega'; - // 国服相关数据 final cnUserNames = userProvider.availableCnUserNames; final currentCnUserName = userProvider.selectedCnUserName; - // Sega相关数据 final segaCards = userProvider.availableSegaCards; final currentSegaId = userProvider.selectedSegaId; @@ -459,10 +452,10 @@ class _ScorePageState extends State with SingleTickerProviderStateMix flex: 1, child: DropdownButtonHideUnderline( child: DropdownButtonFormField( - // 如果是 Sega 模式,显示 SegaID;如果是国服模式,显示用户名 value: isSegaMode ? currentSegaId - : (currentCnUserName != null && currentCnUserName.isNotEmpty ? currentCnUserName : null), decoration: InputDecoration( + : (currentCnUserName != null && currentCnUserName.isNotEmpty ? currentCnUserName : null), + decoration: InputDecoration( hintText: isSegaMode ? "选择卡片" : "选择账号", border: OutlineInputBorder(borderRadius: BorderRadius.circular(8)), contentPadding: const EdgeInsets.symmetric(horizontal: 8, vertical: 0), @@ -476,7 +469,6 @@ class _ScorePageState extends State with SingleTickerProviderStateMix ); }).toList() : [ - // 国服选项:第一个是“全部/默认”,其余是具体用户名 const DropdownMenuItem( value: null, child: Text("全部/默认", overflow: TextOverflow.ellipsis), @@ -598,27 +590,31 @@ class _ScorePageState extends State with SingleTickerProviderStateMix children: [ Row( children: [ + // 版本多选按钮 Expanded( - child: _buildDropdown( - label: "版本", - value: _filterFromVersion, - items: [ - const DropdownMenuItem(value: null, child: Text("全部")), - ..._availableVersions.map((v) => DropdownMenuItem(value: v, child: Text(v))).toList() - ], - onChanged: (val) => setState(() => _filterFromVersion = val), + child: _buildMultiSelectButton( + title: "版本", + selectedList: _filterFromVersions, + allItems: _availableVersions.toList(), + onConfirm: (selected) { + setState(() { + _filterFromVersions = selected; + }); + }, ), ), const SizedBox(width: 8), + // 流派多选按钮 Expanded( - child: _buildDropdown( - label: "流派", - value: _filterGenre, - items: [ - const DropdownMenuItem(value: null, child: Text("全部")), - ..._availableGenres.map((g) => DropdownMenuItem(value: g, child: Text(g))).toList() - ], - onChanged: (val) => setState(() => _filterGenre = val), + child: _buildMultiSelectButton( + title: "流派", + selectedList: _filterGenres, + allItems: _availableGenres.toList(), + onConfirm: (selected) { + setState(() { + _filterGenres = selected; + }); + }, ), ), ], @@ -707,6 +703,115 @@ class _ScorePageState extends State with SingleTickerProviderStateMix ); } + // 多选弹窗组件 + Widget _buildMultiSelectButton({ + required String title, + required List selectedList, + required List allItems, + required Function(List) onConfirm, + }) { + return InkWell( + onTap: () { + _showMultiSelectDialog( + context: context, + title: title, + selected: List.from(selectedList), + items: allItems, + onConfirm: onConfirm, + ); + }, + child: Container( + padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 12), + decoration: BoxDecoration( + border: Border.all(color: Colors.grey.shade400), + borderRadius: BorderRadius.circular(4), + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text(title, style: TextStyle(fontWeight: FontWeight.bold)), + Expanded( + child: Text( + selectedList.isEmpty + ? "全部" + : selectedList.join(", "), + style: TextStyle(color: Colors.grey[600], fontSize: 13), + maxLines: 1, + overflow: TextOverflow.ellipsis, + textAlign: TextAlign.right, + ), + ), + const Icon(Icons.arrow_drop_down, color: Colors.grey), + ], + ), + ), + ); + } + // 显示多选弹窗 + Future _showMultiSelectDialog({ + required BuildContext context, + required String title, + required List selected, + required List items, + required Function(List) onConfirm, + }) async { + List tempSelected = List.from(selected); + + await showDialog( + context: context, + builder: (dialogContext) => AlertDialog( + title: Text("选择$title"), + content: SingleChildScrollView( + child: StatefulBuilder( // <--- 添加 StatefulBuilder + builder: (context, setState) { // <--- 这里的 setState 只用于刷新 Dialog 内容 + return Column( + mainAxisSize: MainAxisSize.min, + children: items.map((item) { + return CheckboxListTile( + title: Text(item), + value: tempSelected.contains(item), + onChanged: (isChecked) { + // 使用 StatefulBuilder 提供的 setState + setState(() { + if (isChecked == true) { + tempSelected.add(item); + } else { + tempSelected.remove(item); + } + }); + }, + ); + }).toList(), + ); + }, + ), + ), + actions: [ + TextButton( + onPressed: () { + Navigator.pop(dialogContext); + }, + child: const Text("取消"), + ), + TextButton( + onPressed: () { + onConfirm(tempSelected); + Navigator.pop(dialogContext); + }, + child: const Text("确定"), + ), + TextButton( + onPressed: () { + onConfirm([]); + Navigator.pop(dialogContext); + }, + child: const Text("重置", style: TextStyle(color: Colors.red)), + ), + ], + ), + ); + } + void _showLevelPickerDialog() { int targetMinIndex = 0; if (_selectedMinLevel != null) { @@ -884,8 +989,8 @@ class _ScorePageState extends State with SingleTickerProviderStateMix return _searchQuery.isNotEmpty || _filterLevelType != null || _filterRank != null || - _filterFromVersion != null || - _filterGenre != null || + _filterFromVersions.isNotEmpty || + _filterGenres.isNotEmpty || _selectedMinLevel != null || _selectedMaxLevel != null || _minAchievement != null || @@ -1145,77 +1250,7 @@ class _ScorePageState extends State with SingleTickerProviderStateMix ), ], ), - subtitle: Padding( - padding: const EdgeInsets.only(top: 8.0), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Row( - children: [ - Icon(Icons.play_circle_outline, size: 16, color: Colors.grey[600]), - const SizedBox(width: 4), - Text( - "Play: ${detail['playCount'] ?? 0}", - style: TextStyle(fontSize: 12, color: Colors.grey[700]), - ), - const SizedBox(width: 12), - Icon(Icons.score, size: 16, color: Colors.grey[600]), - const SizedBox(width: 4), - Text( - "DX: ${detail['deluxscoreMax'] ?? 0}", - style: TextStyle(fontSize: 12, color: Colors.grey[700]), - ), - const SizedBox(width: 12), - Icon(Icons.attach_file, size: 16, color: Colors.grey[600]), - const SizedBox(width: 4), - Text( - "Rating: ${detail['rating'] ?? 0}", - style: TextStyle(fontSize: 12, color: Colors.grey[700]), - ), - ], - ), - const SizedBox(height: 4), - Row( - children: [ - if (comboStatus > 0) - Container( - margin: const EdgeInsets.only(right: 8), - padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 2), - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(4), - border: Border.all(color: comboColor.withAlpha(100)), - ), - child: Row( - mainAxisSize: MainAxisSize.min, - children: [ - if (comboIcon != null) Icon(comboIcon, size: 12, color: comboColor), - if (comboIcon != null) const SizedBox(width: 2), - Text(comboText, style: TextStyle(fontSize: 14, color: comboColor, fontWeight: FontWeight.bold)), - ], - ), - ), - - if (syncStatus > 0) - Container( - padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 2), - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(4), - border: Border.all(color: syncColor.withAlpha(100)), - ), - child: Row( - mainAxisSize: MainAxisSize.min, - children: [ - if (syncIcon != null) Icon(syncIcon, size: 12, color: syncColor), - if (syncIcon != null) const SizedBox(width: 2), - Text(syncText, style: TextStyle(fontSize: 14, color: syncColor, fontWeight: FontWeight.bold)), - ], - ), - ), - ], - ), - ], - ), - ), + subtitle: _buildScoreInfo(detail,songInfo!), trailing: Column( mainAxisAlignment: MainAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.end, @@ -1273,7 +1308,122 @@ class _ScorePageState extends State with SingleTickerProviderStateMix } return Colors.black; } + Widget _buildScoreInfo(Map score, SongModel song) { + final dxScore = score['deluxscoreMax'] ?? 0; + final playCount = score['playCount'] ?? 0; + final type = score['type'] ?? ''; + final level = score['level'] ?? 0; + final rating = score['rating'] ?? 0; + int comboStatus = score['comboStatus'] ?? 0; + int syncStatus = score['syncStatus'] ?? 0; + + int totalNotes = 0; + if ((type == 'dx' && song.dx != null) || (type == 'sd' && song.sd != null)) { + final levelKey = level.toString(); + final levelData = type == 'dx' ? song.dx![levelKey] : song.sd![levelKey]; + if (levelData != null && levelData['notes'] != null) { + totalNotes = levelData['notes']['total'] ?? 0; + } + } + + final allDx = totalNotes * 3; + final perc = allDx > 0 ? dxScore / allDx : 0.0; + + String? comboIconPath; + switch (comboStatus) { + case 1: comboIconPath = "images/UI_MSS_MBase_Icon_FC.png"; break; + case 2: comboIconPath = "images/UI_MSS_MBase_Icon_FCp.png"; break; + case 3: comboIconPath = "images/UI_MSS_MBase_Icon_AP.png"; break; + case 4: comboIconPath = "images/UI_MSS_MBase_Icon_APp.png"; break; + } + + String? syncIconPath; + switch (syncStatus) { + case 1: syncIconPath = "images/UI_MSS_MBase_Icon_FS.png"; break; + case 2: syncIconPath = "images/UI_MSS_MBase_Icon_FSp.png"; break; + case 3: syncIconPath = "images/UI_MSS_MBase_Icon_FSD.png"; break; + case 4: syncIconPath = "images/UI_MSS_MBase_Icon_FSDp.png"; break; + case 5: syncIconPath = "images/UI_MSS_MBase_Icon_Sync.png"; break; + } + + return Padding( + padding: const EdgeInsets.symmetric(vertical: 4.0), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Flexible( + child: Row( + children: [ + const Icon(Icons.score_rounded, size: 14, color: Colors.blueGrey), + const SizedBox(width: 4), + Text( + "DX: $dxScore / $allDx", + style: const TextStyle(fontSize: 13, color: Colors.blueGrey, fontWeight: FontWeight.w500), + ), + const SizedBox(width: 10), + + const Icon(Icons.play_arrow_rounded, size: 14, color: Colors.grey), + const SizedBox(width: 4), + Text( + "$playCount 次", + style: const TextStyle(fontSize: 13, color: Colors.grey), + ), + const SizedBox(width: 10), + + const Icon(Icons.star_rate_rounded, size: 14, color: Colors.amber), + const SizedBox(width: 4), + Text( + "Rating: $rating", + style: const TextStyle(fontSize: 13,fontWeight: FontWeight.w500), + ), + + const SizedBox(width: 8), + + if (comboIconPath != null) + Padding( + padding: const EdgeInsets.only(left: 4), + child: Image.asset(comboIconPath, width: 30, height: 30), + ), + + if (syncIconPath != null) + Padding( + padding: const EdgeInsets.only(left: 4), + child: Image.asset(syncIconPath, width: 30, height: 30), + ), + ], + ), + ), + + if (perc >= 0.85) _dxStarIcon(perc), + ], + ), + ); + } + + Widget _dxStarIcon(double perc) { + String asset; + + if (perc >= 0.97) { + asset = "images/UI_GAM_Gauge_DXScoreIcon_05.png"; + } else if (perc >= 0.95) { + asset = "images/UI_GAM_Gauge_DXScoreIcon_04.png"; + } else if (perc >= 0.93) { + asset = "images/UI_GAM_Gauge_DXScoreIcon_03.png"; + } else if (perc >= 0.90) { + asset = "images/UI_GAM_Gauge_DXScoreIcon_02.png"; + } else { + asset = "images/UI_GAM_Gauge_DXScoreIcon_01.png"; + } + + return Image.asset( + asset, + width: 30, + height: 30, + fit: BoxFit.contain, + ); + } Color _getRankColor(int rank) { return Colors.blueGrey; } diff --git a/lib/pages/user/userpage.dart b/lib/pages/user/userpage.dart index 4aab21c..65f5a5e 100644 --- a/lib/pages/user/userpage.dart +++ b/lib/pages/user/userpage.dart @@ -34,7 +34,7 @@ class _UserPageState extends State { Future _loadRadarData() async { final provider = Provider.of(context, listen: false); try { - final data = await provider.fetchRadarData("684a6ee7f62aed83538ded34"); + final data = await provider.fetchRadarData(provider.user?.id ?? 'default_id'); setState(() { _radarData = data; }); @@ -648,6 +648,123 @@ class _UserPageState extends State { ); } + // 移除 Sega 账号 + Future _removeSegaCard(SegaCard card, UserModel user) async { + final confirm = await showDialog( + context: context, + builder: (ctx) => AlertDialog( + title: const Text("确认移除"), + content: Text("确定要删除 ${card.segaId} 吗?"), + actions: [ + TextButton(onPressed: () => Navigator.pop(ctx), child: const Text("取消")), + TextButton( + onPressed: () => Navigator.pop(ctx, true), + style: TextButton.styleFrom(foregroundColor: Colors.red), + child: const Text("删除"), + ), + ], + ), + ); + + if (confirm != true) return; + + final provider = Provider.of(context, listen: false); + + // 直接删除,不使用 copyWith + List newList = List.from(user.segaCards ?? [])..remove(card); + + // 完全沿用你原来的 UserModel 构造方式 + UserModel updatedUser = UserModel( + id: user.id, + name: user.name, + userId: user.userId, + teamId: user.teamId, + email: user.email, + password: user.password, + twoFactorKey: user.twoFactorKey, + apiKey: user.apiKey, + apiBindKey: user.apiBindKey, + protectRole: user.protectRole, + risks: user.risks, + mcName: user.mcName, + userName2userId: user.userName2userId, + lxKey: user.lxKey, + dfUsername: user.dfUsername, + dfPassword: user.dfPassword, + nuoId: user.nuoId, + botId: user.botId, + spasolBotId: user.spasolBotId, + githubId: user.githubId, + rating: user.rating, + ratingMax: user.ratingMax, + iconId: user.iconId, + plateId: user.plateId, + plateIds: user.plateIds, + frameId: user.frameId, + charaSlots: user.charaSlots, + qiandaoDay: user.qiandaoDay, + inviter: user.inviter, + successLogoutTime: user.successLogoutTime, + lastLoginTime: user.lastLoginTime, + friendIds: user.friendIds, + bio: user.bio, + friendBio: user.friendBio, + sex: user.sex, + isDisagreeRecommend: user.isDisagreeRecommend, + isDisagreeFriend: user.isDisagreeFriend, + points: user.points, + planPoints: user.planPoints, + cardIds: user.cardIds, + userCards: user.userCards, + tags: user.tags, + useBeta: user.useBeta, + useNuo: user.useNuo, + useServer: user.useServer, + useB50Type: user.useB50Type, + userHot: user.userHot, + chatInGroupNumbers: user.chatInGroupNumbers, + sc: user.sc, + id2pcNuo: user.id2pcNuo, + mai2links: user.mai2links, + key2KeychipEn: user.key2KeychipEn, + key2key2KeychipEn: user.key2key2KeychipEn, + mai2link: user.mai2link, + userRegion: user.userRegion, + rinUsernameOrEmail: user.rinUsernameOrEmail, + rinPassword: user.rinPassword, + rinChusanUser: user.rinChusanUser, + segaCards: newList, // 这里更新 + placeList: user.placeList, + lastKeyChip: user.lastKeyChip, + token: user.token, + timesRegionData: user.timesRegionData, + yearTotal: user.yearTotal, + yearTotalComment: user.yearTotalComment, + userCollCardMap: user.userCollCardMap, + collName2musicIds: user.collName2musicIds, + ai: user.ai, + pkScore: user.pkScore, + pkScoreStr: user.pkScoreStr, + pkScoreReality: user.pkScoreReality, + pkUserId: user.pkUserId, + limitPkTimestamp: user.limitPkTimestamp, + hasAcceptPk: user.hasAcceptPk, + pkPlayNum: user.pkPlayNum, + pkWin: user.pkWin, + userData: user.userData, + banState: user.banState, + ); + + provider.updateUser(updatedUser); + await provider.saveUserInfo(); + + if (mounted) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar(content: Text("✅ 已移除:${card.segaId}")), + ); + } + } + Widget _buildScoreCheckerCard(UserModel user) { return _webCard( child: Column( @@ -1148,6 +1265,12 @@ class _UserPageState extends State { const SizedBox(width: 10), Expanded(child: Text(card.segaId ?? "")), const SizedBox(width: 6), + TextButton( + style: TextButton.styleFrom(foregroundColor: Colors.red), + onPressed: () => _removeSegaCard(card, user), + child: const Text("移除", style: TextStyle(fontSize: 12)), + ), + const SizedBox(width: 4), TextButton( onPressed: () => _verifyBoundSega(card), child: const Text("验证", style: TextStyle(fontSize: 12)), diff --git a/lib/service/recommendation_helper.dart b/lib/service/recommendation_helper.dart index 52a802e..dbddb59 100644 --- a/lib/service/recommendation_helper.dart +++ b/lib/service/recommendation_helper.dart @@ -47,7 +47,6 @@ class RecommendationHelper { // 提取关键字段 final int musicId = detail['musicId'] ?? detail['id'] ?? 0; - if(musicId>16000) continue; final int level = detail['level'] ?? detail['levelIndex'] ?? 3; // 默认 Master final int achievement = detail['achievement'] ?? 0; // 确保 rating 是 double @@ -100,6 +99,7 @@ class RecommendationHelper { for (var song in allSongs) { // 过滤无效 ID if (song.id < 100) continue; + if (song.id > 16000) continue; // 获取 Master (Level 3) 的定数,如果没有则获取 Expert (Level 2) double? masterLevel = _getSongLevel(song, 3); diff --git a/linux/flutter/generated_plugin_registrant.cc b/linux/flutter/generated_plugin_registrant.cc index f6f23bf..a78b14c 100644 --- a/linux/flutter/generated_plugin_registrant.cc +++ b/linux/flutter/generated_plugin_registrant.cc @@ -6,9 +6,13 @@ #include "generated_plugin_registrant.h" +#include #include void fl_register_plugins(FlPluginRegistry* registry) { + g_autoptr(FlPluginRegistrar) open_file_linux_registrar = + fl_plugin_registry_get_registrar_for_plugin(registry, "OpenFileLinuxPlugin"); + open_file_linux_plugin_register_with_registrar(open_file_linux_registrar); g_autoptr(FlPluginRegistrar) url_launcher_linux_registrar = fl_plugin_registry_get_registrar_for_plugin(registry, "UrlLauncherPlugin"); url_launcher_plugin_register_with_registrar(url_launcher_linux_registrar); diff --git a/linux/flutter/generated_plugins.cmake b/linux/flutter/generated_plugins.cmake index df8d2f7..0c65fa2 100644 --- a/linux/flutter/generated_plugins.cmake +++ b/linux/flutter/generated_plugins.cmake @@ -3,6 +3,7 @@ # list(APPEND FLUTTER_PLUGIN_LIST + open_file_linux url_launcher_linux ) diff --git a/macos/Flutter/GeneratedPluginRegistrant.swift b/macos/Flutter/GeneratedPluginRegistrant.swift index c968169..0bdafcd 100644 --- a/macos/Flutter/GeneratedPluginRegistrant.swift +++ b/macos/Flutter/GeneratedPluginRegistrant.swift @@ -6,6 +6,7 @@ import FlutterMacOS import Foundation import geolocator_apple +import open_file_mac import package_info_plus import share_plus import shared_preferences_foundation @@ -13,6 +14,7 @@ import url_launcher_macos func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { GeolocatorPlugin.register(with: registry.registrar(forPlugin: "GeolocatorPlugin")) + OpenFilePlugin.register(with: registry.registrar(forPlugin: "OpenFilePlugin")) FPPPackageInfoPlusPlugin.register(with: registry.registrar(forPlugin: "FPPPackageInfoPlusPlugin")) SharePlusMacosPlugin.register(with: registry.registrar(forPlugin: "SharePlusMacosPlugin")) SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin")) diff --git a/pubspec.lock b/pubspec.lock index b08be07..b6d7da4 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -536,6 +536,70 @@ packages: url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/" source: hosted version: "9.3.0" + open_file: + dependency: "direct dev" + description: + name: open_file + sha256: b22decdae85b459eac24aeece48f33845c6f16d278a9c63d75c5355345ca236b + url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/" + source: hosted + version: "3.5.11" + open_file_android: + dependency: transitive + description: + name: open_file_android + sha256: "58141fcaece2f453a9684509a7275f231ac0e3d6ceb9a5e6de310a7dff9084aa" + url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/" + source: hosted + version: "1.0.6" + open_file_ios: + dependency: transitive + description: + name: open_file_ios + sha256: a5acd07ba1f304f807a97acbcc489457e1ad0aadff43c467987dd9eef814098f + url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/" + source: hosted + version: "1.0.4" + open_file_linux: + dependency: transitive + description: + name: open_file_linux + sha256: d189f799eecbb139c97f8bc7d303f9e720954fa4e0fa1b0b7294767e5f2d7550 + url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/" + source: hosted + version: "0.0.5" + open_file_mac: + dependency: transitive + description: + name: open_file_mac + sha256: cd293f6750de6438ab2390513c99128ade8c974825d4d8128886d1cda8c64d01 + url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/" + source: hosted + version: "1.0.4" + open_file_platform_interface: + dependency: transitive + description: + name: open_file_platform_interface + sha256: "101b424ca359632699a7e1213e83d025722ab668b9fd1412338221bf9b0e5757" + url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/" + source: hosted + version: "1.0.3" + open_file_web: + dependency: transitive + description: + name: open_file_web + sha256: e3dbc9584856283dcb30aef5720558b90f88036360bd078e494ab80a80130c4f + url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/" + source: hosted + version: "0.0.4" + open_file_windows: + dependency: transitive + description: + name: open_file_windows + sha256: d26c31ddf935a94a1a3aa43a23f4fff8a5ff4eea395fe7a8cb819cf55431c875 + url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/" + source: hosted + version: "0.0.3" package_config: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index acfdf66..87f9fb6 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -60,12 +60,14 @@ dev_dependencies: fl_chart: ^0.68.0 url_launcher: ^6.2.2 share_plus: ^7.2.2 + open_file: ^3.3.2 # For information on the generic Dart part of this file, see the # following page: https://dart.dev/tools/pub/pubspec # The following section is specific to Flutter packages. flutter: - + assets: + - images/ # The following line ensures that the Material Icons font is # included with your application, so that you can use the icons in # the material Icons class.