From 7d2a20cce25b2e90ea3bc84f5c5e61e1adcd7646 Mon Sep 17 00:00:00 2001 From: mylxsw Date: Wed, 31 Jul 2024 13:38:55 +0800 Subject: [PATCH 01/26] Adjust Management Interface: Model Price Default Value --- lib/page/admin/models_add.dart | 6 +++--- lib/page/admin/models_edit.dart | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/lib/page/admin/models_add.dart b/lib/page/admin/models_add.dart index 3af00357..fa174e2a 100644 --- a/lib/page/admin/models_add.dart +++ b/lib/page/admin/models_add.dart @@ -110,9 +110,9 @@ class _AdminModelCreatePageState extends State { }); // 初始值设置 - maxContextController.value = const TextEditingValue(text: '3500'); - inputPriceController.value = const TextEditingValue(text: '1'); - outputPriceController.value = const TextEditingValue(text: '1'); + maxContextController.value = const TextEditingValue(text: '7500'); + inputPriceController.value = const TextEditingValue(text: '0'); + outputPriceController.value = const TextEditingValue(text: '0'); providers.add(AdminModelProvider()); super.initState(); diff --git a/lib/page/admin/models_edit.dart b/lib/page/admin/models_edit.dart index d67f511b..c4fa4154 100644 --- a/lib/page/admin/models_edit.dart +++ b/lib/page/admin/models_edit.dart @@ -120,9 +120,9 @@ class _AdminModelEditPageState extends State { }); // 初始值设置 - maxContextController.value = const TextEditingValue(text: '3500'); - inputPriceController.value = const TextEditingValue(text: '1'); - outputPriceController.value = const TextEditingValue(text: '1'); + maxContextController.value = const TextEditingValue(text: '7500'); + inputPriceController.value = const TextEditingValue(text: '0'); + outputPriceController.value = const TextEditingValue(text: '0'); super.initState(); } From 544001cd023f6f633d3070be7cba30e440804759 Mon Sep 17 00:00:00 2001 From: mylxsw Date: Wed, 31 Jul 2024 15:40:36 +0800 Subject: [PATCH 02/26] The model tag supports multiple tag configurations, separated by commas. --- lib/page/component/model_item.dart | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/lib/page/component/model_item.dart b/lib/page/component/model_item.dart index 81ecdf27..29fe2e6e 100644 --- a/lib/page/component/model_item.dart +++ b/lib/page/component/model_item.dart @@ -92,12 +92,16 @@ class _ModelItemState extends State { var tags = []; if (item.tag != null) { - tags.add(buildTag( - customColors, - item.tag!, - tagTextColor: item.tagTextColor, - tagBgColor: item.tagBgColor, - )); + item.tag!.split(",").forEach((tag) { + if (tag.isEmpty) return; + + tags.add(buildTag( + customColors, + tag, + tagTextColor: item.tagTextColor, + tagBgColor: item.tagBgColor, + )); + }); } if (item.supportVision) { From ea8c3e8e590c80081d2ca62712f2145716289f16 Mon Sep 17 00:00:00 2001 From: mylxsw Date: Tue, 6 Aug 2024 01:05:09 +0800 Subject: [PATCH 03/26] Added support for pdf/docx/text/md file upload during conversations --- assets/icons/doc.png | Bin 0 -> 10188 bytes assets/icons/file.png | Bin 0 -> 3010 bytes assets/icons/mp3.png | Bin 0 -> 9315 bytes assets/icons/pdf.png | Bin 0 -> 8183 bytes assets/icons/txt.png | Bin 0 -> 8430 bytes lib/bloc/chat_message_bloc.dart | 16 +- lib/data/migrate.dart | 5 + lib/helper/constant.dart | 2 +- lib/helper/model_resolver.dart | 8 +- lib/helper/upload.dart | 22 ++ lib/lang/lang.dart | 58 +++- lib/page/chat/component/model_switcher.dart | 12 +- lib/page/chat/home_chat.dart | 1 + lib/page/chat/room_chat.dart | 72 ++++- lib/page/component/chat/chat_input.dart | 318 ++++++++++++++------ lib/page/component/chat/chat_preview.dart | 106 +++++-- lib/page/component/enhanced_popup_menu.dart | 13 +- lib/page/component/file_preview.dart | 63 ++++ lib/repo/model/chat_message.dart | 54 ++-- lib/repo/model/message.dart | 7 +- lib/repo/openai_repo.dart | 3 +- pubspec.yaml | 5 + 22 files changed, 598 insertions(+), 167 deletions(-) create mode 100644 assets/icons/doc.png create mode 100644 assets/icons/file.png create mode 100644 assets/icons/mp3.png create mode 100644 assets/icons/pdf.png create mode 100644 assets/icons/txt.png create mode 100644 lib/page/component/file_preview.dart diff --git a/assets/icons/doc.png b/assets/icons/doc.png new file mode 100644 index 0000000000000000000000000000000000000000..2f30604be5d5ea445bc7129049c33ed8df18cfc7 GIT binary patch literal 10188 zcmch72T+q+xAvQW6zL$+i=v`}AcFJ~JR*9e9Z^B4f(l5lK?o#@jgF!eX?heqN*77! z0VPC`=AoC6NL5N8)X!*{<2m3_ep{F}aklI_>jbRc{lnd}}bl>sxJ~j$2NxVfD1Nb#f~#-*UdT z&TYQDFU<6heeEouTLoBnRg-MpyuyXEt%FZiD~UEI?t0e?UwenCw;Iizp@6i*gg{p1U6=!_eQCu*fr28H{E4TW0Q+un|T5K?O zc_6epicUZ79em*7hyWmj0!U#TH290iIq2TDz07tS2OXYjQ5AYdojJaumxQ$Y^(T*_ zh8}PhfTD0B+jX|r{k}iYhce)JX=+sZSDf%74`cFIJwP_(^#cTR=A!hLZ}5;DlgGH$ z5b=SLAaD?XBn^bq99P&N494ku>dRCT2{2wo9EsYJG(Q%22c)EV`tjhPlGTA zYakY|r5bZCTulCpdr=tj}_%V|4@0(zxAW+!9Bg_ak z$e$Te(4GIG>HA}VSfD8$3qZg;4Hwv#kHD0fwgZ+vZ~+O;wiGN=AEW``DUrmlnj+WiQ~SteLW2-(0LwF9QJ zK^TnZu-&B@v2Ds*v;$!3smyzbNoLVrjMS%EpkA?yCaoV(5IU)q&$l~Lt9tdW5+g{U zVt?hP!X<2XI4AUa58iR!F+*nGNx{dLBF-+acIbBA`+|>gGME=N-m#vgz@`}6v!6$p z8IHSy;R}8gyKqiW@nC2XaG_5xN_HpKzwg4K9X4a8`c@>|cZ?NGg)-e_ z2U}n&VE|*DGnDfmH&MXu!U5w9n%U7TV>TDCvyvE2$p3dJyn(hWw+N$i|oPcmTwSTLn5 zm~Iw~I}1jg1tY8Y~!b77PyyW)BMn%7THg*sx&KSTOroFkCDcHrOL}P#XuqK=XVV&aRYEI(i+@qTft zS_!-!Pz6#97l>A2Uoi;O2=BdlvaYpspeYPc{$WRtwK#|xS%u|PL!hJ$PP)<2EWc4e z(rfE&R&|ra!P`0{Zq}w&)vgL!FlfMt{gm#rn?HS|PB= z8tOWw*=0h>+Jv}ZRvPJEMO|B@(|*!QM|p-i#MV~h_su)a!5=fc1j3q39i>%d2LfM* zlDjYaPmg+)cDqKtKKdwcD#l?-RTOI&TuL3vdV`z0Wk!{G2geV-9!*xwI#GuX|3vNk zB7$pKEVI+ocBs)f)wQv`mgu0ual|b)YJy%lF>6NI`t^Ra`Cjv=6MwuIaS;BR8n8Z~ zUow&DE&@PdKvJ?mv78F6PFmX~DZ%*9EPU?Az33FSPB?!;Ir%tsTeU4>!kHert{11L zs-PZ-JM(9UTbR%5e*1G@jAW!$Q`XiI?BIb}6Yx2PGjYa|d2p7A=IA=Lpl(WYFLGW$ zAQ8h6=Sv1-65Qub8StLE9+krLNBPhOam47Vv@AB$Yr(d~Kgb15#Vo4Rg4XA{rWTt< zY$Da^eIXznEJNH+@9yhsEoy3d#H!rN z>DJ3L=|RKLS{&}z3G5u5_(lZxGj}5b?VrD~q?)s)b7C=i(RV{kQKffusmhARYox6~ zR!aagV7~n&(r>16w7=2od%o$}Z;xL88r9=$8a`}0-l3J{9g!Ssw-9W!8s~Uib}$>x zh430XKeHMsV^sL$DN^M(5<2cTutcDmw*SD-(WqAm&t1{dvzq)J`;cXUpMq@^@H|%# zZxYz^4;yJ2Nr5+`b?N}+s4H5kiWFmbnG-% zw6&I|amQ1ywZl_#rhHp`x6HNqpC4GL^lr;m5JaY`QHsckgreZg6CH(V3d{=j^A8F? zoZqxHz;|PAqQlF;CNuk`XZO^8rzgmOpg${e??Kk!)#sFIeXgY{j2vv9@IiqAh~J1x zXb#emDGfE(ck>>^e9e)`P@WWfxmh`-g+5pN_XD2S9@t1&zZF8DV- zVjU;;4aiV3Pjj>$sTo;z%QzSFTl!ghvoqlp`1#S{+Q^E2dDzRP#j);F?wK)G?D z90lilrVx#1jynzXRCk>ozGsxEQFI^QYsIdD% zrJL3qx1Fk#F75j~vHsA@D95(lZfBne8+|hIaApIsPM@u|Ql>tk4GHnc_V{N#?!V<5 zVmNQ9OxrdO+s90TJ5|W;eXqB$wQ|L!)Y3yoM$PTBi+iLF<+q#lO_R-QZY;InbQG=c z>i_*pi;c-49g$z~4JG_-G-NE}rgHYaL=)PL`G>s+e=jv|Gi~t=j1`-x3@7+`V(;0O zsvbN%sn|GaNT`_GUar6C-Ciix9^`wvDO5&v|AUx5KGzGgFW%+F5VNz7Yw}uRvcKUy z-v}vY?X7UissZ!HaJe?PQTQ`~7UxnuY*9O{rBmHG7_w@&*%V_mU^R#K9yX4Kueyd$ zS@@h#=t^yDBvSROwGHzBta@mf?{*>&rTE}D1KT`^B(Ge!P(>hKhl{1n5jm#4-D?^-Kd<&85HA=o~v z%)01_$1GPUbd!;$UhiZYvhMKwqIL)aP=+|s$`3c`E#hf+#-0eX`D)VzpZ1h{ADK}xHMR(+O z|D_+_+a9VA;)NKTZteqOF}5<<7Dk~>H}fqVzSadg7o1>oG235V`^fW?QGcFyT*q^I zdX}WV>p_RLuf3o; z%vKx@x5-G&iqEx9Hmo~Z+*kY2#1mm>xt&+v8MHYMMPQ8xZXxbl0W)oHIi6l5?@m6>J~p&kUw7C(4L> zdVYEvHonhSaCm?%>h@NK9KonJEMu(og{c;$z-Yg04Bl+5dx88A)HGDS3~)Drc}yt% zI_O*xPdqvn65KMTXz=OF4;MLGt>??49Ss3Vcltgg5YK=5X*ujrg)!x9Un`dNnnkGs z>?q*p51>L4QY}&!&|73Ty(tp8w7v3vByvqxGfBcsJ6)aDJ46Z0+xsHVJcYS+a-jyF zhz?#V^a}~q-~KzVwBnDJsS!-!ukEQ~ilkFjFeERTj-cFek8ur_iqr%RfE=Bl&L7S! zSXpl*9(67MblG&)nD`v~y+2(ICqzjNgSyQICXG@W9r!Yp6&aKSf%5QuC0gI#p!#o} znCJ-6{GN|26>&~iB$9aJ0TeZS;K@i9qGL^^q2`8g3oyg6YUPZ!eLfFI0(-ejBDJp*wJYfc_Pq*4AoC_cTJf;5~lHeMua{xyYkgG zx^x=cSaXyIT{a0H@QI0L;+L804*Dwjl6}qClM_KR%bDSKodSv21Ov8bXNP(2ghO!%{7-BBu;-8_T6ufAkc-)T^vM1cHP0 zrsS)sleERDg~+_o6BDMZZ?9b_QqjZrMBr=FLQ`Gv&Bsl>+JiPFi)r8~6s*m#zPF*g zRx2q1%6)b#PgeQ^E2!tj-)es-!qCTJmV{i2*`nU>68}EPZpY#KUR%I<+iE4}z>&8T z0T4rjD+YtIe^FPPkePpJLQXn*YNYMm0T#p2Ca)xZ_y(0TFG<<535mZQ2#Gweq3rhlf$F*Ie0lNq5&bPE30)s0N=~ zBg}ljMhk8~>k9a&6k>z)9EHEj-)`f-lBTbFO5IIf6kr%YU9aIza=tWRAeFkxr(aiT zW6-41;T8;`&7!9hy^MbFjxose`o*95V+37n zzSyhO>j`hwecdYb4Sc#ZdG)Xbx2tpm1K{g>W4 zUEXT_pkflYvk)!|UaWGoSO91PF?!z&LSAjWMT_rqb$y-Zgus>;Z=8dbhL4>PMJ7-= zoF>3Cf-j4W&pP5hGF;lp0eo-y9+@C6oe@bP=wFKHX?nx0Id-8G)rHqg5VzW05K=US zhQhUn?8PLU_uNBvOnFA#A-{t9)X{{t7V93mqpQwnY*|laN@&)1R;7>$annh=T#Lf{ zD}V1TdffD3{=9gU>sIy4upjZ;`!7i&vEdrc9c6P~<<&x*gbWHw#bUhJ<;zr+$BHn@DOXoZ|hk8>;3Wx`k!iVF|x{LppZ)^_<5fBr_{r ziR?~tK&j51U)t2s$R^vtV=ARQz4(*?c1Fq0T7pl!${IaS*+NpJxKl4FbKA-q>1hu9 z#Y5h-5!nAurTxe{VQVY2ifE3wT_$Q&My1PDOG(=9MuIJ>xinfM>cWR9`unS?cKTwk zE~vL6L$%w+UTPbrhc=IIi>dHvLUvC3dyEV;)4fivgWm$nYt)7CO)*1+UK4~~_REWb zZsT7=H!i@BP5PWIA*?;iy|-(?Tecs8KCh9ouWJI?$=R11(MzlA@jxG`3q$yu&d&>! zg!t(W%#Vd{|4JophZ4^}v(Z+t7$rZ4T-{wKh#8{0y?#mhi_e0U-z0Ps}?G zy}LmvTNNnx_%?ny(dSaTC9^<-uMLStZN&DkT%H5!Zk;pxaOp_6eI>&_sh%EZaPr|ap?_1jISO;<#92{EJU5G z6z;A5=nK3+@qAY7V&9tm`2f2JO1_H3&7p(n(B&Ao4*-$6^?}iYD?>JU&je^$*Xvfayr~10j+upX znJBxW8nTvNPRMd%S_Q`UtpcV&^-yNLPRQN5Q0I%=vYd^_V|?N5UlxLDqs}p6+xZH3 zl0Kl zL*95HqQ2gGzrqo$dArIX0W??aP&HL~J4Gq^RQ^cT`XKoa2vcR}=ZjmA_~_;VG+!E} zZHjG1H(V$o7_uQUD@;9y|JrI#`FXFj$gDeNVDuP~g6*Z`suV;e-NAqM#I-7fs|0Nk zmnOis7f;WMNUy~??jE%@Mz7RRQxG-rY)EM}1q02@4(5Ler?t9%N|fp9;hj zg>H0q*0I4&@YeA;THVVAaNo7SOQM*Z+aIzXpKzpx`C=Hb4a!V^>1hJHqc%jf(s2hXfa;`JOp&x&+VJ%# zc)CsY%jut3J@VehQ5~W(=L1am_k^h)fq88w{cDe`YdFxI4IMruTXqFs089e)oRr#r z%zTcKL679{SZM?H1)F8f$BOrH<^2+>`(>(zbFatl@h}{*$uSAamQ9;&nLj`tZ&b>u zZZVD^kb*o96v(~ExiU zUO#y?*lP-ba@4IOJ|c+k)EoH$HS5R~WX0sr&nER%!kbitEiQkiy>EMa(YD*e`P94~ zgre(djn>-_Kw*H!vgYDc*i9|8ULSw(viXxL!@TIz)bJlmF(#=l31<`VLF>jR=A}GZ zPg^rrOo{8(BEs{1m*d8)iYGB(_b z@V>pNii7jVmDk;x2xtsxCLlVn(*Zq{7*BpvWtlDaFC0ivQ<9v-x~ znr2HVdN1}aUusU;)0`d|&&M(EI92wWhu#-~yB~yQoEl=xRfdwH zHGTB#-rYNN_3X^}Y6&5-S6XbWOg+#*FRihjIPUu_HsWI(+IIZCjh_6GHzOI%Zig9f zZ)mSv{nPX6{@ho#=}MxPncua~EE0ug4L^CuIQSE<_tC!0JTBX+Ay! zLGjzOiwGN|vV1-Mrv0BVd&{+!2NtDP1K;Ic#}@m@sFiArQi>f7bGD}rFdU-S8=?K* zo>n{7o0&);R7tQ?R48Y4tZ-q|rI~kzTN1LSvXwbYeCMP*PjGZS=%ZGxaGW<2kAs)H zrdISD7iGx>7hDmn=5E5^9UbAXhr)RpKkKSyrr#MN+|!bdEAuXb>&@Szd$sU+9sKjX&pOra zozyJenEGw3FgjT+{p67eT$P|&5;nlUXho-KIvW4&iJ=v(1OA-8S+_`c7`_)>%~Psk z`~de(GiODId59#HyBOjg_X=QcPvUQur&%v_t}YesaT?8ducyX5!!mRZ`#*Wj?kBvn z`~P29u(t%vw1t4hfVDw&kUsCKo&>PCNNVb0G>U>x0pk;%s#)O1;k@9l$Od!F?vFeJ zO21)E7aaHTM#l|^Jivi0)*yqo5nmqnW*dFHF0w^V+g|uX-ynxYwp_SJNxGmP_ zLl!n2IoZFCtzb`7^MhY&7G83Ner<+ydUed|_eySoHOp2S(0c9|1m2Z&**NO1I7MMW zpZmi0`F)Otb}FychHhYewt2vr=Y$qG4+5{QMbRe-j~&&ijK14&s6Id7UhhrsLoSH{ zP*T_=yZ-nbfEvl1!-js&0iiC|N-d<}?gL@2Pjf;$=<#(c;KY({Q8w2BQoo+Orr?lp8ExwfdRnyZMqpVQ-?Qe3UuxY-=nIz8f|QpvQ`fIPtpaY%+BQSPZZ0%j{{A@ z0C@nU!MKt8_g&0GC&0jm{`(s|2onGb`>$0Z{$S7laH~hsWpf^m29mP;!I}J^Og{!P zzBym9U`NdsfSM$6D?(u**`Ph=hawPpJtcM&P(fr&*aY)cF~E4rU_61p*0~9MJ!ik< zyAL?au0I2cwW%yvJe;?Fg$s4df(zxPECSf`p)!77ve=7iiuK$!2Y;s|R5B6`=?tBj zIYkyn%!mNrvc!RSj6JZh0aUyWY6O*Q>mQFb2K)-SD!y<-J8j|J9<6E%y-L8ywZYj# z5ckXqG}anexGl6G3o2)<=AF-2ym8-WaK9>9NCbYy1EDs*-X?Rrn!=TDIY3bu5y-rX zL(n3yTu|`{TU|63*MJ@o^7Hg)xWL{SI64^NnS~c;^WIh<-B1*Ub=<8WkHs3JwH*1j zo+fj@a@+dLr+AASHIjf*4V4D0Hiu{eYoAG~K$t4~h|SBOXOQlI&&0x7jXZJNa8)R+ zBxtyq!z$lqdxYRQ7HwRvr9jI2yURisMOIz3?pxd111tNDgiO)Yfk1S90%cOyM1l2( z^>evjEY73Qu_q$tf4+#0gvuv!QRQ+ca#wQiDjtKK@;7&YPe_LUX5GXWQRQaf>d^zt(f+$DpB)&dAH{;@3>ec7jM+=Y39F5m&z84qoRJp&XD!|*` kf8x4CU#nDxq2ZPyHTOsL-wwre0Q|dPVsWOV literal 0 HcmV?d00001 diff --git a/assets/icons/file.png b/assets/icons/file.png new file mode 100644 index 0000000000000000000000000000000000000000..dfd28b3bbbb4d908e84b6513d3157a5af57a2ec0 GIT binary patch literal 3010 zcmdT`X;f257QQbDA*f+-VGqQ%(H=#mkBW%41{=f$S8zkrXak~v?28SOhdQo}NV|Zd z4JxAT$SR_3fILKGUqnMfASB2t62J`!5MEc*Sx(Q7nd6+9bEeL%TVLJ!Ue#B1>%H?% ztXt#A9Ai8N0GKP5+iw7%AV>iQ6*WbvUzehW8nkqU3xmOEY*{NuT`m9RTZ0f?q^{V~ zZxX{%&?wkpOR%$_XK>iAKo1BD3!Alfzjx3NyZk+7`33Gt>|JCGfbC!BXEgf6)Qz;RYF;nP>z%!RY+m2q_eJ}73~r29i2~o zbGJy=C2sB#%U?<4FC{HKjV;}cFM66<71CBkb6c;hO(ARVm9_WD+xuENkoC8GJYEyW z-VT(W1;HEEI0KbFX52&r6Z1K?i=5W%cK6!n9~^z;8Yd^(ub-F| zXzaRsn{?fbJH7idH|eE3;A*_~cojKZw<%+{wWZ>y`?LeSvib5MS=q%o6Fd_=D}9@@ zBKiubj;G^|Ud6}sN-u05UVEvR6-dbDkDUPkW7y`7W zduf`hw17iwmxSJN2R7GBC&}_pz%a}nipV=>3bZ>3tiR$K-(Ktlxu)tyxU}O|^(dDr?-l(uF1csM2D~8S?e)*Zn-a%Xq zh89Zh<&&CcW^y-UY*!)*u4(uz#yB}RO9S`9_*kiarUCAQ4MVBEKOo?!2BrvTpbAIE z0^1GH9KbLD&I5o)8lXE1aR2|`AvTln(<-I+d#>Wl#qYaPu3>r4#_KZW(hIm3jGc)+ zg~fZdj??>BA4C0ig*s}UL$jw2-nL*HbCgu90h;~P1mAcyp-4@r5KL2#<$PpJL5zYC z7MiHMQ!trrOnfb)O*haTlRR<}_kjxDSBScGfOUo`AFw4>TA6T^O^sY@{LKy4RUfz! zY0R6YtDS7gGmNrzdoetq>XI=qe}2Z|@Pz}S7pY`YfzrK28rJ;doIYNxiK>P#G57o5 zrKxdd)rW@)Talt_cG@w3m0NpqHa_3#risD1-*PW?Oq~sQaqhLLBuiI^`>XQ=y!x>S zdkr0pw{4fa42uqj{D<&vs><3>CwZFz-kb7)3JFZ`)|cy9O^C(sjm>0S{NY?XQVQ$wAUN5hqdv3^o9C23wH>f_a9?>tG++01^Nqp?Y)Pb z?_$s)N$3E>H%h<5t~74(Tftdc@?9FidF$)ai5cFC*GSeRI8sa4j#eS20G#4L9-YFS z!hmgs+J{Z)3$#N`do(9gIKTjm4Ky5d;mZq02yVJ@Ok&cv4d6EZJd>CgfWSl}ZDM0q z3%KdCw26?{0CGLK9#_vxuWMedcXABd#4h)cug1c0frf21;($!^BS^}ZYxv`nE;K?z z52?^Q%Oot;BQPPJNjStIIg?DZ36G};80Y|)VP=?qw0`ssYCc`U$kub8A>Nc(#6D(s z`Y=m#*zWj$bS{6;#BqT1D+?n$>t`Rg2^wgc2S@Mw+qRxecs3TsRj4)U|D1ke8=!QO z!Q=5~A~5W7tKXQ_hgsD&Si&C6u**pwpMxgF9Rc*CA)HAgmgz$7Ar^hXxIoh$8?nw( znmUt|&uK-}FD&?N%o4q}uMS3ke}BG!MhwB0p63B49QWwq@4;%mFy02UdebRLO^npX z66WGJ)~p8GRp4YuL~f5ia7B~*2!0H~x-hFa@lJSW>WlV+J0>1|JQo+=>Au zg)nHx_-BcqFC^t3Z|A@0Y}AoIFp9a`9<3CtXW>(h&aK|hgzz+5&>Xk-veXp%H-x}| zQJoEcG02tO&@(S3AoKtbK|)OIyS&wny2B9qgF_%Rp^laWs*>B(Oy!YpWBSI*Tcedb z2OUD-lB#2FZh_$lB|NJ@IiFv-*X^L{DI32zotD{tZbi(^KDs%mMh{P-cxf96!#j8Z z#Und^4H&I5yK7wWfh@1#kqy%^AKQJdA9yOV*l|WDIYiw^@d~{pQkjwOC!3Q|WSMQJ z%*V#Z{e$1Y{rx)xk-Nl$!x{VUOyt~E@rG`y{9DU!oZ`LoPWw*c(pVS~OAgQQT;e)g zw2<_Cl@;+5X}&pz_c%(mt{5))PTNcl@;vb=p)GN#-DW#{NAHjB!>EZsa|r7 zy;aEA-0XlJjV$$QUVG^FtIE^Y%PjTEgv_?wOUx*%xn*AO%cnk^aNS%aOx$DRM&$8$ zJAn=F-XGs$`$zMWoy|{dEZol$vQo2B`4?Z8;2N}w?*BDyfK4kQIi%SyUO&Oxo%hWK z8$y!rSgulQ4vk(ujQjC5ue=*0Hrz4NF|0KbHC&I``dQw#bcQ^scdUXY{`1f{CC)a+ zHZ93FWrEKN&oK#_< znx^~}Z?}p82|RE*B+2T=kPd|%se+>sNKp1=h!tg55Tnv|02~Eyyt@gLI`lkQ=V>1d z3E8Ne`<+M{iA&bpejkX#1V9YVm}z$C+>s#8=oS7N7t_ZNyg)cWpr6FBdes>>*)aUC z)Fo<-f&km#iAjB2!EzX*kryRxEemrmhdSkZ)IGn!<phIWt4GvRb7T|#04Cs_6 zNxu6LYm9korc7_GiB^HY#t88>OVYqSMc{XiA3?Cf)_--&{;S*P)4#e0|E0u7aZNQG zaEKxS;8^+xO$3zHkSrRk$+DX#w82q4Km!oLhZhhB0s#31@(K=MV28{a5`UBjjSv6` z@Zd5;8-@p9qbQI-LWOz|hL~WWsRnq?6u=04z$bbbCqWuM08%Ff=J$7kWWj zOuW1%bk||*`}qv1bt?gd-8dum-`{<3}APxdUz6Q2xr;kzZFr4Bu2fyB2_q z{1mK7E(cQ+3yyO9vLGP>_nLEpwPUQS=>WMzBk-Bc+A|M{QM== zV z)*u_!A?aMm5Er7ug@~?0oVbuSE<}?H5m|>IxsX~eWG@#Y%;f@K=kl5hQR6~{xLml9 zEG}dh7b3{z!i7BMLUwQ=0$eU!$Sp1e$%XK9xxm)BT;)QxaUpzME?me3E@TTA!pr5t zg`D9+HgO>cE*CE3C>J8mh465>fORgKT!;u40_Sq!LUwQ=0$d1;%Y_Ts#DyTZ5byvA zLjn;5fW8p`z&VMJU}pogAYe!UUBJ-*%3%Z^K*)3T_ zJ(EJJQl<9x#Mzm7DaOM-2T{GIN%Dc&C`Pl~Dw#s;N%I^lf1EOXphy@$pv*)y>OYj# z?gy_Fc%w)6`3xlsI)L2J0pd>TOK&uwXlA$aMDd z;z8elqPSNaK;Qq1`2OPw`L6gKytT=$H!>R}p1I;dQs+pXDDw?kJCJ4LazjEY#9X)k@3I0^lB84|=S z1GWt@^~B=B`&l~r4_8?SVq$-_Wk=Lg%85OgLlxKsJSKsi_fG!PO=ig6OpLaEgvM|T zB_KL_CzZZJ3|6#m{c=DnzL)4<`0UH(n0H?-I(!?Rjo+S(uR0wY5mqrznkSOXE}mXU zp`PDBQrFBc>rp~*CP;PheciZJiciNd#lV8@k=mk?9lLE*d9*~VbQe6d{C!&9T|>J+ z$vvo;5Wh~J?$8FYAql@=kNiPYbJ@nlu+4lg*~HR6=qKVb9b4uiSGp~FQAytZ1=A%F zt|}jyWcfZ!xe}4?Nd0NyHTv0Osj;tl4{L;?d(bh7v=9Dk)jpqnKR^O6#c4THE#IaE zhDyCVgIpWZpLn}HL~AZuzs%)j8>7viRv(mER+)2*bb1BJ(zPSFgv>l%NMc*>udfdF z$;y^AF}{jxAMp0b3sN5VbQD7o8Z@GPtzMmxZ~Ym%O*W;u!$Xxy$5TDGHa?e*bEA@D z<`RxI^_7BM9ZljSBq6j#nApV5=&v)ifXWk zp_Q6@2e+vJ1Rj)C2adgZnYp2Wzh3pqu`0gJq_d9~G1b9zx47?fDK2%a)w{egKZCZ( zm;dAn*z@w$^bA-meV+aiwW>Dov_z8Ez*>7J5F!?=TE8RkIPXh&D=$H9_2oDehd^%& zIlVx}60VLcfVrXT5AT3o%y81{#zlB_(B-VswDyud1Qa*iuH;+ap*+C{)z)DeDpTanrAb)TQX#2Zl80$LYV_tN517%8M1@p`!u@baW3n(p!u|ZE6k+z|-{`vggX*m%fa;W2Irdr)>Ze(egC8 z8^JCvo}zf2(2($B9+(dWf^JO}PSyW3n%l(${l zyqH-3A*V?FxQe4_*H_fHuI*pTP1x)B^AZSGT2d?cp2qk?L-dl8mGr4qjFmpH@r_iz zb}V?ltE;&5i9^f2y(OQ!NEu^k3>V$MWQ33C`}<|gy>fb}_4sx8X|)X(=cU;mocRqn zE3R1I?${aKT+gue5wr)Dn^lVpLXG==d8NvXD1ix(o zzR6qCRELvnVx@5spwq!)Jn=@SZFpVM;q$q5S`8N~J?B5fNVZ;tObFnA@}al+4tP`3 zwEOORwPPvp{$*#`@r+Q>xf#0o?-TIHJNv8?cGEN*^oSdijvR^>k+L)2%pp)n4Q2s@apYCFKU?bSvZwZLJnTiz(ihPqw!$Zl+NM9on_KJA5pG)yE=aU z0eX6>Z24rmm9??8IoJTpNkLkfwqreI_o)>j0LdKI0ye8{^B=J4t_zW;;ORGD;sd4T z5)G`7^23z7X$Dp+@Y9)A(j{j8vtCJX&L&{Co1b9NeWN*HrkQv?ZtiCy*wxWAH_|C+ z9q?+^>_Y>VMsieR>azl`4G-$xNiKnCiGvgcP=L#%`!tz(2P~3aPmxqW+>AS~M>E6D z5lh)0(P>8z+l)$ec1gRYudM_)GSB4r3+(oIE*1xZoRmAjnp^@}40aPe3q!=<_50r! z*zFGW&F-2(o;`gve*UrNiSt}!tl82!l(pLT^J9EPhuW;^V1en8KrIW-s_R9de;{-_KJAm z1Zw9##0;8*C?z>qZv=8OU~WMZt5;aH@?v=Kn&paDq(N$FW*Ue6H@H)F4d(nO3a%<( zST&_i)%rz!Bu?Dj2)s59&hc8>#dz~oLw?Kd;U8adgmoE7p7b$G65plG#ngh?@lnSm zQ>jo3CcQlSdG)v;K9;@`Hj)Ypzh3^Sd6eaI;q`3F4yDvIolAx&j^}sy(7~8)yV)DM zMd>GV;vaA~J|nLt{O!b)gsh=*j{;^I6F+tl*Uq z5>Kr@C4%m(P%rbuo;D>b2v7Gf)!h2fSNLU&)}y=LthrAgyLy}l_ardKZ|4TM8R!~l z#plhWxOq?+FS^y>&_)XnUIv0%qUyEXs`E>ib43C&Tg(P5v%Ag}HYzcbq9^GTS8)V- zBJPXl(z~~Tky&GDs?|>kjH0hhyp>{o4=oJ=p@7xI!5A}QAH40NV-lG|Ju4*L8 z-8GWpJJoGS2}Yd-uj{47G;y*j9k^Zdu6Ox`MmOgyoHg23bl55QY=BMxXKBCM4CeAy z0^c=f)GzRQua>*0yvs0ancJ{r%hH9X^XiTbn{JxrYcL~iV#wI5{b}E>bXLJ`f)3+E zGZu~v_GmEIIz2o1)qCZVJmu3}hg&-{hNs=t6qUe7X~}aEhI%44LnJBca878YkMG-} zhmSCc9PS%o;??%ErokoG&4U%w3I`4r+~l2ktM#a%_Q`~v$4>CkpVjg1NZhyk0Sl4I z0y9l6g{PweefDY*{R}wu{){3x@&%jOz=vX;n+;*zPVzXq1ALS$I9Kggo{-RedX@*T zaQ({Oi7X}fSW>T-AZM%a+eR=qmH#vTPZ$2TvLOcW^hyTvn0nu@lpE6D=stIN(c9*1 zetiE!ky9+p!+=2aH1W|M%U`$3hh@a1h4Axr!;4*W`GpZ0)Q216fc&oWFNnp_f$mQm z^WN~XYX#h?9VYH<)cZ=#Re2=dpJWX-5pR2aao0}XqUs7u&#)qRv`gS-8@ta)z0i<`|l=G8Q5PRG^F??I{UNp^tMA_gI)P|+N$*K)cWXSAp`9n&kps)3x!fy zHq9oeZr%2WF!8FXMKp)>PB^kQXlm;4wNYG0tol=%Q84yPspID5b{+ci!!O6%a<9Up zaLREdQL0h}6`EC_i+BEIOW(>di>MI|d3nonB@Scv$?R>B%a00P^q#1)*oExPsIkp+ zG-#7|zZ)*A(Q<*bXmW5VllCP$Vlu+Tg={5u|3X?{K)J8`PKU6nL1k8cVEbMzS_G9k zsAW`=wB3G&cCyLJ2(dN`1Iz_CjoU(aH+f?9Ui*roEd*KT=*U%GmKQa) z@KgqS#?T=5H*h2v>(>6|)AQZ%^dQ5@@Bq?CPyvoLR8;g{KKUN5g;6mn&ACavJOL_I zP+<*Gmo6$4@%?@Pnyi-{5)*wd=7YKo14N`sw|pU}NvZ*e#>?4@o} z@G)a>X0*)De;?L~VQw*az)NtGZ>>&1HI&UY$d|_8mO}Eb(e|3I^V2tNlUUDpfmgn@ z!?SlJ?R(Ybbv%~B2^;!K$esX(1jZ{;Lij+LW%RplMt#%tL;ZMRIyCW&=NCuZ@BygxbSGv>}h}J2Lu`$73jBm$@buj3#VYRk|8-*V|JNGFT#=R^_-nHNb9n5 z8Qc#0Ol~M|qGxH)9Z#ugwM`aHx7}h#7w^VbgC&S)#+|fpk*G+k%e$3-XLd3gGYl0)ZJ+;V-j1vFU% z-E>H?sj?-M{O(Hu(XD$Xh;rWIJyrw1xx&+FN~xEljZo@FFP7aXC#8yFMxuM5GXi)` zQ`S$|?DEMltXDC{N$-I9h{G&AovvhXvnzajwy}MZ*lKbqy2`JPF_ts?4SI|U(ml?0 z6bViW<2yq}hE>m5v5W^x(H?m+*cR`xB(Ge>uDhfctuS%QZ>V^)#$$H{nLqU7OAdDb zC4@tk7_%#fr$TZ|t7)q%^mssqZV5_g1(cpmgcDh-YsD6%p`5JHoRG_H0#j#40qt?~ z44E=r(G&RB&?RC+gBZbk&uXB~wNm{3F!{d079kE8&fdkW@uwyE5Z%R-OGxEoh9!0Z$@wy*qZcV zX9>gHT&oUQxWsPBg*uN1QWCxdD~+{E2%U|{L)EtGrBdl{QTKE2E|aHwe*MfqSxLtW zF}4Pbxb&2pu0T^_BPAIZ~|e(F7e5S5h35 zGZrGQUpv3sy-tFa_=9|VvbM`^u;r%p#lmB(@%r<&7h#+=4td`%>U?PYt%5FJFN3o+ z=OjYk5VR*Orq@0DZpxf9WGtk_1#RX83P5t94aAf!1)d<7Ra>_948ysH_~>KrxVmf8 z8*DeUdcrwfkc}GA+L+}JC6R9uSClTFW&E~YhZ0U8&=vy@^v3qqX#8o(h5LCoS4}y= zY%ZSrYWz(lmA_Q1eQ6;gw(l&q7G>K(YHz2k%uHn-WL7=f3@dW!H}s&Y0t6mdN4zjS zNZ9r9a_HCGh0$-Hw5MpU&CBK0lp!9EIr$Yibt#r9LPS#{c(CY;KScyq$M(`h@Q&c?U)((jH2Is;T?j;)mF=`C(Q zcl=U#q2jhm4`z&Yy+S2%>`nLXe{gU?hVlH=0Vb1DBW8M!aJ8;FR=(l*s9VUy+7pyw zLGJv1>M*&k{&br^(_?xN zmK?9-4pZ`#ZtRI$Tulne;1Dpsk(Qij_eF5`o7nnNQ8iXJW4{8kqJz*DJGmp^O3>3^ zs*?T=2DFA3D#d--^f=>{Ika=F%}PKBlVjQhenlU8pXihPZ7H^d%5&dQUHyU8CQE^VQDI&|04k7+Whar=`lP` z@6q5y^Qx^nR2x21T=s&j;fGVwkf&o5XLci}Vi}@1#>!{E5A|n|pquDtmikQ?a!>b$ zv`x^)n?=P8@*&VN28F{GrKA1(U=M;oW zd$tU6)%z~=26qEK2s=`fSU;ozRRUR_{rB9O%QS!-Dg?aIVw*F$4Es@<(nCuFdU%Up;t_D-A zdVVJ4gY{H9UPWy);`7$Xm0B#blhMI8>SiT!@8V6D8GIM|WkWj*zZBXWZNCaVz*={s z;;8qfm|j|QkUq3mIS+5T>3|UXxD*bM`xqOjoJt5R7ETg+0TeKw{m?NTjdlfMjew2= zO_Rr^02~cK))MG1uRaA`LK6_r;v^Ax!(76ZQrX2$XFJ$EZs6Mig#bE5v?*YwG`1$v zcp)PvF8@gTLtlMW7}EFP`w(u~F3rdh$}0S0bp~UE13zNMXyCz3Lmoqr##)6LI@>Eq z&wUj}SG$JRQiKuY#hdqOz$sA#I!*ffRDg&=gLv?7`O4`)0!Sr0dmvJOcWA)5PyUZ8 z{??}QdG<*Jnr)b~1lUUn{AZW-C2&tx05ol7PkBNZc#!M@M*|N{aqy_*PpHuO^TE-6 z9Z=R5c0d{2Y6C+8`Tfw~!UISffh}2X55a6P2cWn1`v;P7IRL5XR0X~Jzqe?afHkgZ z7r-Bc5_f-x0B{h)cl$;1@uH{d%9&cQ8d)W$oG_R<);4GVOhRyuBoLF?0^U&5L4O|1 z=hXD$cQA8X-zefS;FBj%)(K!Q^RPLZqX~gIQoy@;O6vr0Q)!*@mIOV!;C)xpL0hYC zjus{1>K+jU8ZOKdz_$wE38O)xGWaOz(RqR}IJ*%x(`d3)56G#u{+N0BQ_rP1?;4^f z#dd!uy@XCOga6xylz2=38GCpeVOuGYJ5~Rq%()@Kn z`n*A>UV?vce3x&3D`y-Y{v8E-a9p5g z?O$0gKY(viZq2qYNOAGbZmv8(pX(~p6K9KIwwv6(B;tU#)Ndy;-(bc){gYLV6BeVx zY%zpU`Hy)mdCi`mg(?d%t5?{fzP1_sKwd3pFc+TXn+;vZN*mfT*8=&PDT}!Nrli&rkTH z>sIVK(LY*>el-4G8Ew5k6WMD2;8$DTV~>%KC2TLN?N5+MzI|YY_n`brT?G5Z{hVT_ zS>q%5BiVwa#C*bstE8e4>uLFT^R?KfI=JePx|G8bVUoahV|$~Kwup{5kUPu{e|?glKG4#h|7@|hW&N4c zg8aeeG$F4_Y3FO{r{Fx`Yw2v@No8;2vaAG8rjv)l!DbA2qXQD^k9BQxV2=)r#&1t9 zwgfmA)ih7Cx8GSC^k-vC&Gg%N?ni<$zZGNRklhOu|A`?7J{T{6zb@rxRJ5$8PKn~L zmd&IfBLNR6sNEnbZsjp$ud?U>BrZ&ffj9KRyhIJ9u>LW;oGVJ9xsv1fPgWKxcrA z2<`$vBs981*@b`|o;vi%1Xy79NMxmvH1F^u$D#Y##D-`^%U`r0NvmV{yPAe%bZT$Mq zjPnwtKCb!~dC$C~JGPEj?%A^o-|Xq?5;5DN{eOUpW92^pLtYBUE2}F17*LZuz%Gwn z3iw0n=;v4R(p$6;u0Nz&%6Z5BgK4fD_KJVUR5;;5d)j8#cR|=OEdZtrLu_#;qYw36 zd^S&*HZC~orJ9jNf#sd#sTZ_YWzh{wBzh(089_r$KmYLUG+ zt0&hr1SEE5Hu@%g@3-KV%-J85gBN#gk&Q1G4?wRMJ5(m@iNdi;nCD-Q`*PwI!6%Uo z2o$dgDK%;5WB6HE!XI@Zps=w%$xkY$UW7w$IX8W9`lbJevwQE4Kjm$Jlz>zYezx?9 zIkTc@!1?|Omk=AL4m2qkm@;~29$UK5_vxjRRV_x_{Kus3&X(6An4eA@blLMr|FNl5 zj+Nx&>~2v;#j9=VlrsE+8Yd`_RQImyK|9`Vs52?z68buI{%&`=s0a%#>}7X<0&0Zy zX}{Jv_>TMhY3u#(NPn|up}_MmJri20|Lnj2!lnMg%l=9Ft1tkW`k#P*3j_aSk{01O zBvEVM|Dpe{8yaAMPY4$KkC{R%x)T${pa%@>L`^NI1;v%WqCpFwrR0CEjk|K`ZT3h@ zdwom5&7#E@s8zjk$@Xn z*B)Z8`S|)29Z5i$sXKvz*q_YE?7gBRW0LSM)vcX_DoTVk?;~8zw}I1=Hm+rYDc;h4 zHd<3_)uWbFm`uit#;!*s3F$RCod$Y#o6JB#JrP0NAxqajwL@r^RFQ{k9$eZ06#(Vk z)!J_p++SYue!(ue9B)0@zypk*Va7V`vA9Etgd<T`>Vu+B5!|(Gmx_e&@vv&Y@GsxyAJL zVHYr=DHoBr!3sL8xyKY|Rj7=BnNw@Owex5d+U5Hyx4KOMj z$@KixXgZrb7J+}cOB;;em^mPhNd*6N83J<=~@5#RJw}`RfTEJs-&}-!Io9z6TH9a%<sN>%6k4Q=oF=hwwmB${}Zq4QB%7kCAD>+vg zry(=w+}`oSHJ?Su<7m8$#CNu(C~|?a-X?+Q9FSv0)$xuiOcE9x(XUov?{O~aASFsM zj%#jtabg$GnA+6#?R$b+EXz-*=Ccg^+MO3ME@_+SRqJ?lb1Es}dS3B6Y}^&HyX!_b zN7hF1nS>(_Dni9JziLxNm<(6qow%G=ZvK;7ClDCEY%A=MMmxs2ynIoQ(NgTpNW!NH zmeQHf{3}EeA;L1zVTz4d=14p4q3Me$#4;h@xi&v84U4x&i!(j^elT*SS83)V=o5Mb zv}ugBzpl4RPDq}!pgRhoPF_)FoPOMLrhS4-(>t%+mR-T6@p(^Y?C)Ra zDmGF?Ha=>ei0eJ8stk*d0?X34m@C{y<=qo@p+)U!eCpn`lw_a5qy&r5=u~u4qm8u;53tPtO$JrIie!C(JHr{^oa zewZ08rG5(?r4&j~*6(;64eKjjJq%RnHM{`Tl0P}%OD@&6E-|B^m%k30-oJ+~>a5oqfta`$dPfW~3IWBIEkLSj`$#hNU)j@DD8@8_sC z*hWhbLvxRcfw8E{eP|oM8j38J!|+U)4u!Drad75X@)-DSlT{mHpQco=;l)X6FqQ7$ zv9?SSJ@@Ktxtve!1`!(=W4(4H9Ktx4mg{#4#VJJW{qljz=)!i3uWpVvN8KoB`ZAFI zF13Oe0n~bbFX<3gk{Kfa&)yHTWr{38N+Q-X+&u#8WozvnH

$W5 zMw8Cn%)rYII(u4FUG<=4o}ApG)6IOqX5?nTTYMV zRi?G-Ha8r%LY#XaB&J93UD;q5b{hf``PFP#eY)<v>Bh|Upu~Ny8YeQomq%F zpe3K@A>zF)(L)Oh(nT#fw9L7oRjH~H7oJJ0yLAV#+K@t-@=9qz*5+(V&YC`=q34v+ zC`&)+mZ)L5cNSF$Sl!1ZN+14729}08fc4X*qz1V+j0(D@REZ4+Vy-I)i z<+Ss$_w-09M=zo3yu(egn}pHgniCEkmYZ{`q%zscxsNE2et;XO#9 zO7DN7hGf(;Fzum1x}4b6>Tk<|-cb{!nu1AK{Se7{snoLqJm6g0v9;N}Z$V_5d#;*i z^Uo#ym`7WP-rl&zv6*X6?wc9E)xDUNtq+5uppq*OkZaoGPOlW$PD&6W+qw?z>wH}1 zS+4N4U1B6m8+CsCh&*}3P%xx+u2%wbR3#Qx7;sF&EV|8VcA%Qk*)w~_B;OF_HK{QI z6+bc`je2e=Y1yZEw(}!E%t$l0Y+UPO+5$IjW>dIM;qV3KdjZ1K&rKutq56Kz#&K2- zI4HYRD%e0gk7ty+maDh>aJ&&GA0}+%KT&1>l@c#Op$JMLfj|`{bUQIZG;cw}UA%Q} zN&d5R6b{b}J%F#NJW5T(9D2PvW+8YQ3BFZkf$Rl*0apH{MuSwjNyz9Xl&ZKY@P!tH zzB}O;L0zALvAk6vxSHY?Z#8H=7xhcWW|(8Z{^A*(8G@{mIS=^tAnfiZqZa0wUVfl} zsJr>5-{ftoUIni{%yvbPWe*HknSt!L?=kN?jzb?r0{MfcIM!FuM{9141-%M^tUsmY z?}?uA^j z{|s;+B)78sOp5_pt0%@>uTST^kadD*Rca!TzZ@LL+1(U zu`@wJ(Ags9&iJ<(TqP-M0;y%w8aQ$b+q_5?4u5cCKvoDgS!fd3!QY2HrJ@h!IdQ7q zQEyitXm~0(-}6^*flN`Dt^&J)YU!OVyRO+oEH4) zf{QJbmtjz7jMrR@4tP9DWjHRf;MVfgsx#Lar@4^0C(w@*y-%zMR^18fDy8u3V6%=8 zeW_6dGmW8NsTU2~gc#;jUjt`D#Dlm(8m86_YQ{xmFt>fkMWycFp0MK%4}HZRurJ^njhQ1k zbzOUe1GgtiNGrRE@k#CED8n^HF|;Rs@U7=wv?rD^;p-#mR!r?FA<#MeYg=Jd#%gC?YW;r5(e7t(bi__5 zS{(iuME^iq7xyRX2RE{?ZobKQ?h%dNtfxVzt0Y~^x?|vW+mkR-Hx#^%g$sz!(U=c^ zDW11m@T!tms5RSC>Z%hremR_;VLt4aTzivpLL1b5TYudPMX}!n2(xFL<iE; zgX~@Orb)ro^C3)8D9a~MJWR7wyd!;z4))}4R$#W@%!Ul)XFA5sC{7VxrNaE299co;NA#w)L1 z%b$zbVD*St>(M3Z+Mmrci5v~Y%CV2AKM7uAwxvg2fD%;n{3MevTL@jN5my-(KY1Rhz&{BCRI22!~pK|`|XFb zw?Z!aiPB>wD8`-}Be|kt(zgB&Rl)%%%0x#rcKHqyb!i<{acy*qetdMjY^v86h|vIB zujzZkZz=S)-x71Ci}7Q`T^}^HL5HS$iF!Ps?jwHvp~Tfx5zir%mDLZAXogp4>_R3p zv@t!bHef;&`s%{I@ucaNHjudNR%u%ad$0iqp6P1ZY-Bl3FwgZA)49;!Il0dw!I%L| z&LhCDX6j2Htue1xKyCG$1?qv?VrLBSk+Iy4=uki2T2610s)Gz78_gU`(m2wXU+ZxQ z($QKX$zhozJ#wPdB4rN}i19ye6xjGIXTx(JTYE(Z_B;dwr9!1FMlc2kS-e9My ztcSU4c}jM{ia;vdVoszmz-jOM$m23SfH#hT3 z4L7}lh#%z1bVG%Tjee16A&g}ed8|*pML*MzE~}Pt5tO90=0&)>xCpHhG%YtGI#hc= zy8FmRSD@8;zW1D4m_8;b7_~jNBERw7SUh@R5XG$a5W%NDaP{ECE^NPrq7k1ULMvM4 zh6UCe<>G2+Jhw%%i=4kD8XHo1m@92%r7t-8#3mX#i|!A%Q&U$GW>yYnhqZAJ_ngzj z%vE~0>sK`$Lx-X^y79p>+Ntcl1{!L=K}DDA+zmYoVdK$Rv~Pj^GCHfz^*NvI@m!8h zkA;o|Lf>Aft>kvbEHw}XYz(>IEest>II4c(IVPeutY;0HebZnly}i&et|oJ)fb`DB zb7juKWs2% zl(O<^ZhR#4{fZBxLI=OBGtZLDPKFCESi5vQE=)S|;Ep z@H-77lzpe4-5Mqa9YX+I3w&*Gfyz`7Q0JdTs=u#N;J|+>kNkJxxIQF~ON12{CTaYO z7ZNTevMvV|jop^uP|*ddZhPyMrMf_5FDgWq0%dk6s3Wrmb=n@eEV@IT!YgHKBJ8*@ z3FB0e2Adkzr&N#}(3RNG(vA(00A%~MGqI2JEyehq)b0fP2;pJg99giUV5N#B_5RIJdr&w(bsO}!~-8Q(1+wSlt`(GfBHYWN4==sw^YGH1_Z8ztYD zIAk4q4WKrk;=&UakB<+*fRQ-8gqc^KD!^Ssxs2ad$TKN$raj#$W2m&Wx3mOYEizh* zhdBj}r(dX9S7%c4F`XsLog+#_3xxG~tk*?=vPAmDi5xsHH4tD}7MwM70P6!1R!?iz zwhugm&*p4Q!i!k_&OW$frRGVvmkk+F}L70GJ!52 z0M#lBYTC)iq54;9Y(QHav<0Bcg$rC40f-l$V*^zeR5>s0v};CE4G_stloqqVGjb_w z0uP|3@m6RRdXLG@xJ~L}7qQ=y2M%34T&|^RI=W+n7G)u^T@#wVx6-wlhbKb?Kuikd zSL{|RabjoWKBb2-$HwTUZcEc4HesplIqG%~ek-^a8g=bz)UcWAp>Fi}(sFaDMxDzu zCp6(WX}Q3mxksSx!K(DXDx&>qdul=Z^^ek<7}SFOw>P?x8&rw-M&-b5Te=~@Ivrk@ z000dG8#4enbS+pu0qO{LAZS0hO_T*Md(`!I8|0ZhC!w>fxM2ao5r|jK>O6L-X=*y+ z6f9myQy3zF3Fvz5*G_)0>1$bZJqGsr`*l6)CLA|71maF9_rFAFwN(Z9rOSeiaE=)0 z6}DiiQ>7&UFAqxIhw4+;;5cS66iJ>=O`k{ru9o6YmKOE^WAd7ll`|)hb{Uw}&`>~V zmBqr%6H@q$;IF^GI$s&bzq5I*Q-`g+_5~tueM&hu z8KKpqa>q|J%@Qs~(|CchQ)I>Etn_xF19SG`eu(aQdqnR_SwaKk1ePzBvNQ!#_R(E1 zSev&u3MAVdpgYP4apjqlR<&-xskLtQcV@fu=KVcdtGab*ALf2~ruvdq>(m>&HC3rL z(TyQtZ`iqCruGJy`4k%$**FM-1R3>vLmKU68>;Xx+xjdFsR|YK0b5r=z;{}`oEc@Q z){qXGEs(;$42P)=yhvuP`|XPCT#9YQ+CuyCMNL?I9CG%m*XUD}zW5MheDMfQ@7;+Z z#g+j3qSSB43EhvL299T+qcmfT(#tEQEGeR%rFJHu1jG5p z?y%Z=2#8<-MUV(8SO66TDMBm=D#ZdKhAL8&rlRzaSP&Hzw`@hE1j|NI z5Jjq#ut8ue(nN^V(2MjMLP&DwP4S%bob#Rg?LY42d4P8&zgcU|TEDesy^~u8LNRArnY_%Pep!Fd)49s z;pD1I#wRZw_jS7z;C#^)1OxXaY zLK;(j_&o>>}^e^M9= zJZ4^SjwByW-r!eRz2b`s*bmU8M-~f4H47TY-6O<%R{>>!!o82?5ZxMEe%s3sBVlmh z$X}m0Z@wz%A90fe!id6k{$vWC zh2u`2Ms2OEmf=pn*va6O-{U_ARMJlAACN}uy?+82IKRZ8ad3m*o-8O$nmY349l)jc z`-Hh_2Sspq6_qiFy{4X%#C3j^QhY%xK*ES5*91~H5pcmP3>il8d8gg^BE?A+!x+Ai zsM>~m(mZSAfVKa1EO#G;;PDv~K3alwPJgdJ|8Ta-Fa6II*Yfmh0=_sBBp|Y14B+nd z?+j5qurz-yGW@m}$Y6mqz&(ST5QaoI-2wKKplEPQl!QT$*g6;xG(drM@<15}uz<8* z2q%mI!r;CKbT%Sn>Hi-g(!Xqio+`G(LKC>2gvN5i_&0_apmde%$A9((bnE|qh;mvg zhe9K&5eFiwp_l&4a6cj(4LuFHhXwE#(waL&T#5`4=fh9`%P^2+I~DWzjxh;|*SS7x z>f@XIXIW~h_M%8A1(3dPam;EL-hvh;0dVZYIhI>=I#;o2)x5gCS%ES_9PG{(|Ar@* z_=~017%p|Iexp5j8=$xXY~MFt-^>?qwnZa4jW2*bFS?BXT=%T^jaepN*y`-j9iRD& zzw0P0_LB?I;O*A5`jKLY**hN2SRy8E$De%MaB7-X9`AX%de+waJQaTSB;ClEI6J;& zv)>glTK$^a_`vU8ZG&%l-oF20=jxm%CwaKrkJ24{Pp?3saRA&ChMpt=@ZjTy;_xfV z+}-ZUrz>J;8W$szcv>v%S)K1bo!-Oy)LegdSVF=;AK^3#ykIWh6SY*9D{Ikf-(B+B zUTv#n)g4`*@#Lo%9(JnFwl}9P`WkQJ{f$4V8#rhBBm0eZDlJ1L^%O-H#=vmPTU$jK zqwBUl8_f*SW+gDSzE>BDl{Bm84`R_c@Iwa0H2?*;7E1&0cZXO2_LBe(@Usx|4{iWf zMTAJO;zCYBj=?e^j3hWxGMvQFFkun4a>K)0`L~87z%K?ozVhk-A`AnN!0_44fanYAjKpx)2&>{4*}$o0HVN$00AUd0Pzw)#XONT2{>CxFNbq=@jPoD@K;1d!zdDFVnY0mMuISt^hs%$KrK0NF2q zNDHJ0Aaj847XtxAN+3l@;1>a8hX5iekRpKO3Lskq5OIMNw7@R{$T|T8BakA11PUMu z0*I(U3QFJ?0c5!VA}o+1fb0@LBn1$(Knf7}1uR_$gh^KbWFHAY4kNjT)HO6x3n4)P z8kqya$Q<$^77`DjaR4shP9O;Gl7F4TYFZl5m~yc+xAHTOT+eCAX`x87^zW5IC4Pmp zMY~^@U++}1Tr6>}@j5F)-fR?&&AG*iiLHx0AS9JE{oLu(+*xOnk+A3j;4$$D_B=7Z z4A8p@-EBlxlv2<;ogoK|i9?)fELzRCE#i*<4jfo+z~Jcl{Z)*5C0R3@D($>T6J;4F(*ZTo3Lz1kB#9;uTcKN(q7neLwcH7q9uDe350LL6R~%AaR7@zj9{K#<}T< zx%t?InPf%hpQ*-AsqZeNv72^Bgst2VI^lit32S(tiRHQ(qg4q8ej2H?Z2eH(fP=F} zTDl8^4LT#~T(>ZSLWX;40|zg@V+Gj^dO%aezSa8Z``o*f*WD&24cm6Nsn6lHchO~gd*{Y)V zugJvCu0O`V760ZsL(y{WD|y!LBKd5t$zY(ryoNDew~Rd~{-bN7K64usBaDlbdSC8K z&MwKebpNV3;MW%N?y0d6XW&-7IWhX8?ywRiTsFVVZ_cvlJEe)t`aGS-ZW3>;PV~J5 za|UL9Qs9NXA666*h%@Kr+Jx?*r(em}zo!1keV#p-X||C=EBL`_JI`QlRu7As=&I-o zEAl*gyJZ7mH;rwEc;-la;FTw7x#|vev{rX{O?j^oZ5MXN0~VRly;-QV(?u0O!#?5N z8b%{??8wgS&$nBR;*(n$E-xK$ry74izZ1ooJO!5B43^YI{$|X{0aneY1JC z`kFRnz_;<4T|2(Ovm@-KZh7cz7BNFPClHmwiskNCt!4ImQ;2Nd%xc}DrkYD_jrMtk zjkoIE_w|onJKRNd9Ty{K4?LrsR!yZ)v>J_Uf5YFgU}|w)eY8)*-gV5jN7m-B*Qi#4 zeW5ay+VU}f^orb?Y?d)mrR0wjcGGmth}5VAuUYwP1$$KD9xwTW~Oy*xXuEu%h+8=cz8 z(tN6K>pN9K@$AThha$ve;(&J2*}_?a98YSxoB}o|)~@FeG2F}d-u#OsY9b}{F7cz7 z*9H6A8ihBSqj*Hsu-IZ^5uagFSm8g_SC#yR7HJ=^YM9#VhVtx@`;hSES(oPS8<~}y z{RXM=^qlcsDRT4k6Ka1>=N*;^uEyQQ7lh}uDk-n$hD~PTeI`64PKA$D`>Vvwc`i|q z+TFgUzN==3VTtyby2C_U%3V$IJC@rDE-{#P#Icc(*_;9TsMf;scbwX~R+rayIdgYz z&E<|V)1LF_Yu)8smWw@Xw`tcJ(Q5pfh=A_WphbA##a}}j$^tvxYM#3Iv37UM+&9PxT)d~CA=8d+fkrAVeA)ZUzTgq| z?G=Z{cV|1lJmm>sF`^myLvWmtBpyahLwEKd8>Jpo<6-yK>%F83^<2@(+=BX|Eh< zY*1Qc>F>#2VAAF#D+y&4ntfX~GAR)P1Dnm;>FpKXwpunF6_ISI9SW5*GhNGsA_*t1 z^3GE%cpya@$iIxg7)8nr^$ICC5vpc>=k}pFeP}z^=SlWu6=GCHy$55)S8{y9*($0F z1D7|1?bM`FZ@!dC9Aa6NEhuuWg3V6t+9gi6rt^JySD+)uBTBZ~bN0(Jn_?>P{d^pzx z`w5;o`>OESdcXhlpZoE7yuwCQoI6i~yXi@*k0g}NHnZ02Fo?w7a|3!EZMRviB3yDq z(>fD_SZU|GrkftbE_7cth6m@{ff_p`Mu<)8I~)FdUee%6J){iWzB-}udrx&0U|EYfq5axyF!BPq#y)ENIn<~eX zTFIO!g1&*~!)hBc(U?E4Qqi]aeFA0xm2rRCccA)>$N8I6_AsHCo=Gk80L$I6+q z8M<#@ZZfAZdpC1QxP?8FTTP%e9QgggBFE4cw`fkDgk0%|-873yzZC$@qtn6FdOnBZ zM2g=>zbvg!U%Zqz5A#E9J<>}QQG&C~Em2;rN8>&bINUYJXL0%{`Fmuqm`Ff5QpKZNCFtFyi45nj}3SqYDs=p&<;7fyN=fgc?ydWWVUAm4MW2v;B>&A0N za}h_OeZuGqDx>OFl`}@u$+ypHbJ3{V1|M$xlFpQfpq@_+b9X zyt|T<$MY4EDtLK)S2wk+k)K&ZNZ^FIK}J%td2h{Ar5metd~Y0UwTL%dt!Fb}d_|ym zU@LKO+q!t&fVI?v%t^YP!BRg5*WJF1bWfdb)h5f` z)E(9Wo*H;D7qKg)FHg~Hmu%v2BW2J%%J-To=H5cjVHDR}Na1L_Y4f8$uS`tzvf}t? zl*4n_HrY9LH_r?`g?$W94r1=9uHHRk5pSq(d-ZfF`kj%)poEtWYovchDb&ndwn)&G zgQQjeht$&stmdTi!##s`s=eW~wEVH5LfBNaYKpfyn9_OrTm7WO)Xlwa-5b6h^h{xtXxMAfPt z!KX)QB_?KSp(*Wwm7UqUFXt7pXNGk4M!nvKE?y}IAQ)D|UPe)AX;~$$Uyq^j$`(I8 zCZpM|PnZpTOEFmpA_lqK!_X z+-hiB%!8ost*1OtZJZv^I81uEde{YAIjVW}tU@M9#mVp+Bsh6CS40+(RdLetpsEYuTDm z$eGjSzf7R9vEXsO*X7av#t4EzmN~J1&@ZNst)J7QZOqdN^T-u^dN}cA;^%zs{tIiO zJ1ukGA1{*Ovk&JgR_xidUs@?9uKz^9fax4TU-3{>q;Q!kt2eJ~Ml^WwYW~@SA$^e~ z83iaQK#s=^^{gUy$Z3A_tf9-q%-pq^gv5(nOCDil^Bj%^n>K^4&M9;KbACpl>}r$a zdOfVrU5vO^T4f?DqItyh1{Wx_2V_QS_F45CI#=5_zmIB8bI4kI>)}5*rCc+!>JfA8 z)W>-hMYcENvkxn-QSmRo{Gg!|`Q&fhLy;S~laHn@evHxUDB!Rb)Fa~x$WFN)WqQ9{ z5C97F0;e4cjw6Le#`t5^R;kk;o#=bf{2A{i>;p2@CyOY^@&<^J^$o zboq$7Xg7hgA;Jm1-{hI!4q2l8dyn;`QETtJZ58reXe*PaSozNoEABm8U~u%v8Cp)XU-Fv!lSu+WI4IMUZD zT_2+2i52_sM%1L0WHWNh7vp;x@-1kDrEAWZj9fV};?->&@W*r*yVe`$QE(~GdTg-e z=R?;Sojd(w_zwxYX(x|aE*hqf&YdVE2cG-%Ll(fr<3LM}K z_r63SEv;#-~-5J^dwaJtwitdh)Q1Z)itn&y8-?eKC|J zX^$zrnN5*#_Uc;`KgPmlpP6is^nzwm@WAN6p|vfH@<>;|&SY*-+EaSa{LSw27RwN;yJtTRmG*NFCSF?oZ!d!& zq5o}xB(EX(A8!YE74F}b{ntyU|D*?AqatqoryDCPFJQMr8hrbf$8Qb5}X}8 zy7K@$wA#7XGzD5dui>J?ik0w_*zJB#kpU68wQ()GF@nZ{X|{249sItjR4aXn)KK=Y zEJzV6PQa}6J_24KR}Ri;a76&RBgRKe*tC0=av4vDd>em`8E`!htg@RLHpgusLfxZM zq=@abRao#a<=n74Cw>9)I+vP4+9xWGa~+)R3*i_{!-lY!rQAVP21ywkI9Ygqmd7MK zZ<6vs5|4&i6+zpYH!v(DR5~xXJO8YGUr6j%_iEl2eb)oAi%}BF5WWNM3RnuQW1tb z#sJbMuOAMZfiMXGQ%Er;e1La5DNRN06!?IK-&cezQQ#&vc;Sz7B9wBk6acA|*IAKn zvuBCp<3~|g@NOSCmf<(HCz=Ao3(mI9=jeQon-hUDOg%cu-dR#C3HYvazqyA(Df;t; zCX|jl6u~0EO~P+Qp1xsqix8_BT*zOal*Xp$X^xOAB=N-I?&GYkYlN;BN-^}T5;91_ z@ejIx3jY6aT?seyi)zp&EV4CNX8-g3B|vULA&Ofzk-c+Q2$TLH;$I2(PdcODAs{S( ztB_fMhNf}Xai^BA1b0QzxQp_T20y&9V4XYCf;S*jui@0=Gsx5fIAzV95+;Fxb8zbA z8#ooU3Ijd|!8dhMgq97wYdrIbz%E6TR-!&gVn}DcZVJz<5Vn&?)iy!e9&kq$FF}o) z_C4BzIx^8!#Fjx7x5*rFaROEaPmaX~m=*o84A@Ibl5SIt!FW9gR6Fhtd7GLWMKlHH z(=mZ10QKsmjp+15MH1KPIH@Es1Xsp1E zHQP2ce~Tg+iY~=94IckF6?rh*z-Mv1$5zzucW?dSUr7dPW0--FO91y-X?E-uYwN$WJ{LJH#hmM`;U7#CCArR2T9`pa)<&wI7WkF6@T&}%+_-8umD!hRzk zE39z@R{od;6SpiBrgpUPQP*S<8e1qmNB(Lkv^1%z%w*p1sD|A9BGL}!4%&A6?K@-f zq8o8OU51w#354N&54Zb1HrPy;zfv7gZEn9U5Ov$Xx7u*W=wYRlPkANRZ5i9f&-cAI zqncT}9ZS~xpkMECUucnM!3WLMq7S~QA3vnuVSbI+ObDAZinD4px1T6K1K`g=V~hQ7 I_c~quKet>g(f|Me literal 0 HcmV?d00001 diff --git a/lib/bloc/chat_message_bloc.dart b/lib/bloc/chat_message_bloc.dart index 2f4aa397..54cd9db2 100644 --- a/lib/bloc/chat_message_bloc.dart +++ b/lib/bloc/chat_message_bloc.dart @@ -73,7 +73,7 @@ class ChatMessageBloc extends BlocExt { roomId, userId: APIServer().localUserID(), ), - error: '选择的数字人不存在', + error: 'The selected item does not exist', )); return; } @@ -133,7 +133,7 @@ class ChatMessageBloc extends BlocExt { roomId, userId: APIServer().localUserID(), ), - error: '选择的数字人不存在', + error: 'The selected item does not exist', )); return; } @@ -187,7 +187,7 @@ class ChatMessageBloc extends BlocExt { } } - /// 消息发送事件处理 + /// Message sending event processing Future _messageSendEventHandler(event, emit) async { if (event.message is! Message) { return; @@ -224,7 +224,7 @@ class ChatMessageBloc extends BlocExt { userId: APIServer().localUserID(), chatHistoryId: localChatHistoryId, ), - error: '选择的数字人不存在', + error: 'The selected item does not exist', chatHistory: localChatHistory, )); return; @@ -392,7 +392,8 @@ class ChatMessageBloc extends BlocExt { // 失败处理 for (var e in items) { if (e.code != null && e.code! > 0) { - error = RequestFailedException(e.error ?? '请求处理失败', e.code!); + error = RequestFailedException( + e.error ?? 'Request processing failure', e.code!); } } }); @@ -411,7 +412,7 @@ class ChatMessageBloc extends BlocExt { waitMessage.text = waitMessage.text.trim(); if (waitMessage.text.isEmpty) { - error = RequestFailedException('机器人没有回答任何内容', 500); + error = RequestFailedException('The answer is empty', 500); } if (error != null) { @@ -419,7 +420,8 @@ class ChatMessageBloc extends BlocExt { } } catch (e) { if (waitMessage.text.isEmpty) { - Logger.instance.e('响应过程中出错了: $e'); + Logger.instance + .e('An error occurred during the response process: $e'); rethrow; } } diff --git a/lib/data/migrate.dart b/lib/data/migrate.dart index c224bc90..674da35f 100644 --- a/lib/data/migrate.dart +++ b/lib/data/migrate.dart @@ -118,6 +118,10 @@ Future migrate(db, oldVersion, newVersion) async { if (oldVersion < 26) { await db.execute('ALTER TABLE chat_message ADD COLUMN images TEXT NULL;'); } + + if (oldVersion < 27) { + await db.execute('ALTER TABLE chat_message ADD COLUMN file TEXT NULL;'); + } } /// 数据库初始化 @@ -164,6 +168,7 @@ void initDatabase(db, version) async { quota_consumed INTEGER NULL, model TEXT, images TEXT NULL, + file TEXT NULL, ts INTEGER NOT NULL ) '''); diff --git a/lib/helper/constant.dart b/lib/helper/constant.dart index 957e095a..bf1ecb57 100644 --- a/lib/helper/constant.dart +++ b/lib/helper/constant.dart @@ -3,7 +3,7 @@ import 'package:flutter/material.dart'; // 客户端应用版本号 const clientVersion = '1.0.15'; // 本地数据库版本号 -const databaseVersion = 26; +const databaseVersion = 27; const maxRoomNumForNonVIP = 50; const coinSign = '个'; diff --git a/lib/helper/model_resolver.dart b/lib/helper/model_resolver.dart index c96345a1..587bab63 100644 --- a/lib/helper/model_resolver.dart +++ b/lib/helper/model_resolver.dart @@ -200,11 +200,15 @@ class ModelResolver { ? ChatMessage( role: OpenAIChatMessageRole.assistant, content: e.text, - images: e.images) + images: e.images, + file: e.file, + ) : ChatMessage( role: OpenAIChatMessageRole.user, content: e.text, - images: e.images)) + images: e.images, + file: e.file, + )) .toList(); if (contextMessages.length > room.maxContext * 2) { diff --git a/lib/helper/upload.dart b/lib/helper/upload.dart index b1abcdaa..8158ef67 100644 --- a/lib/helper/upload.dart +++ b/lib/helper/upload.dart @@ -303,4 +303,26 @@ class QiniuUploader { return Future.error(ex); } } + + Future uploadFile(String path, {String? usage}) async { + try { + var filename = path.substring(path.lastIndexOf('/') + 1); + final initResp = await APIServer() + .uploadInit(filename, File(path).lengthSync(), usage: usage); + + var storage = Storage(config: Config(retryLimit: 3)); + + await storage.putFile( + File(path), + initResp.token, + options: PutOptions( + key: initResp.key, + ), + ); + + return UploadedFile(filename, initResp.url); + } catch (ex) { + return Future.error(ex); + } + } } diff --git a/lib/lang/lang.dart b/lib/lang/lang.dart index ce913c36..8bd52422 100644 --- a/lib/lang/lang.dart +++ b/lib/lang/lang.dart @@ -34,6 +34,7 @@ mixin AppLocale { static const String cancel = 'cancel'; static const String select = 'select'; static const String tips = 'tips'; + static const String goodTips = 'good-tips'; static const String basicInfo = 'basic-info'; static const String delete = 'delete'; static const String edit = 'edit'; @@ -50,14 +51,29 @@ mixin AppLocale { static const String continueMessage = 'continue'; static const String messageInputTips = 'message-input-tips'; static const String uploadImage = 'upload-image'; + static const String uploadDocument = 'upload-document'; + static const String upload = 'upload'; static const String longPressSpeak = 'long-press-speak'; static const String send = 'send'; + static const String sendRetry = 'send-retry'; + static const String sendRetryS = 'send-retry-s'; + static const String selectText = 'select-text'; + static const String text = 'text'; static const String uploading = 'uploading'; static const String robotIsThinkingMessage = 'robot-is-thinking-message'; static const String robotHasSomeError = 'robot-has-some-error'; static const String questionExamples = 'question-examples'; static const String noRecords = 'no-records'; static const String contextBreakMessage = 'context-break-message'; + static const String translateFinished = 'translate-finished'; + static const String textCopied = 'text-copied'; + static const String copy = 'copy'; + static const String translate = 'translate'; + static const String hide = 'hide'; + static const String readByVoice = 'read-by-voice'; + static const String unknownFile = 'unknown-file'; + static const String switchModel = 'switch-model'; + static const String switchModelTitle = 'switch-model-title'; static const String room = "room"; static const String createRoom = "create-room"; @@ -210,6 +226,9 @@ mixin AppLocale { static const String visionTag = 'vision-tag'; static const String newTag = 'new-tag'; + static const String uploadImageLimit4 = 'upload-image-limit-4'; + static const String confirmStopOutput = 'confirm-stop-output'; + static const Map zh = { required: '必填', systemInfo: '系统信息', @@ -218,6 +237,7 @@ mixin AppLocale { cancel: '取消', select: '选择', tips: '提示', + goodTips: '温馨提示', basicInfo: '基本信息', delete: '删除', edit: '编辑', @@ -233,8 +253,14 @@ mixin AppLocale { continueMessage: '继续', messageInputTips: '有问题尽管问我', uploadImage: '上传图片', + uploadDocument: '上传文档', + upload: '上传', longPressSpeak: '长按说话', send: '发送', + sendRetry: '重新发送', + sendRetryS: '重发', + selectText: '选择文本', + text: '文本', uploading: '上传中...', robotIsThinkingMessage: '数字人正在思考中...', robotHasSomeError: '发送失败,重发该消息?', @@ -282,6 +308,15 @@ mixin AppLocale { questionExamples: '问题示例', noRecords: '暂无记录', contextBreakMessage: '~ 以下是新的对话 ~', + translateFinished: '翻译完成', + textCopied: '已复制到剪贴板', + copy: '复制', + translate: '翻译', + hide: '隐藏', + readByVoice: '朗读', + unknownFile: '未知文件', + switchModel: '切换对话模型', + switchModelTitle: '选择要切换的对话模型', noMessageSelected: '没有选择任何消息', modelUsage: '模型用于设置采用的 AI 数字人类型', promptUsage: '领域设定用于设置 AI 数字人的行为', @@ -407,6 +442,8 @@ mixin AppLocale { recentlyUsed: '最近使用', visionTag: '视觉', newTag: '新', + uploadImageLimit4: '最多只能上传 4 张图片', + confirmStopOutput: '确定要停止当前输出?', }; static const Map en = { @@ -417,6 +454,7 @@ mixin AppLocale { cancel: 'Cancel', select: 'Select', tips: 'Tips', + goodTips: 'Tips', basicInfo: 'Basic', delete: 'Delete', edit: 'Edit', @@ -431,9 +469,15 @@ mixin AppLocale { examples: 'Examples', continueMessage: 'Continue', messageInputTips: 'Ask me something...', - uploadImage: 'Upload', + uploadImage: 'Upload Image', + uploadDocument: 'Upload Document', + upload: 'Upload', longPressSpeak: 'Long press to speak', send: 'Send', + sendRetry: 'Retry', + sendRetryS: 'Retry', + selectText: 'Select Text', + text: 'Text', uploading: 'Uploading...', robotIsThinkingMessage: 'Thinking...', robotHasSomeError: @@ -482,6 +526,15 @@ mixin AppLocale { questionExamples: 'Question Examples', noRecords: 'No Records', contextBreakMessage: '~ Context cleared ~', + translateFinished: 'Translate finished', + textCopied: 'Text copied', + copy: 'Copy', + translate: 'Translate', + hide: 'Hide', + readByVoice: 'Voice', + unknownFile: 'Unknown File', + switchModel: 'Switch Model', + switchModelTitle: 'Select a model to switch', noMessageSelected: 'No message selected', modelUsage: 'The model is used to set the type of AI character used', promptUsage: 'Prompt is used to set the behavior of the AI character', @@ -563,6 +616,7 @@ mixin AppLocale { 'Reference Image\n\nAI will create based on the reference image provided.', selectReferenceImage: 'Please select a reference image', random: 'Random', + followSystem: 'Follow System', darkThemeMode: 'Dark mode', lightThemeMode: 'Light mode', forgotPassword: 'Forgot password', @@ -620,6 +674,8 @@ mixin AppLocale { recentlyUsed: 'Recently Used', visionTag: 'Vision', newTag: 'New', + uploadImageLimit4: 'You can only upload up to 4 images', + confirmStopOutput: 'Are you sure you want to stop current output?', }; } diff --git a/lib/page/chat/component/model_switcher.dart b/lib/page/chat/component/model_switcher.dart index 1f8f0455..d9baf1c0 100644 --- a/lib/page/chat/component/model_switcher.dart +++ b/lib/page/chat/component/model_switcher.dart @@ -1,10 +1,12 @@ import 'package:askaide/helper/haptic_feedback.dart'; +import 'package:askaide/lang/lang.dart'; import 'package:askaide/page/chat/room_create.dart'; import 'package:askaide/page/component/random_avatar.dart'; import 'package:askaide/page/component/theme/custom_theme.dart'; import 'package:flutter/material.dart'; import 'package:askaide/repo/model/model.dart' as mm; import 'package:flutter_initicon/flutter_initicon.dart'; +import 'package:flutter_localization/flutter_localization.dart'; class ModelSwitcher extends StatelessWidget { final mm.Model? value; @@ -30,11 +32,15 @@ class ModelSwitcher extends StatelessWidget { }, initValue: value?.uid(), enableClear: true, - title: '选择要切换的对话模型', + title: AppLocale.switchModelTitle.getString(context), ); }, icon: value == null - ? const Icon(Icons.smart_toy_outlined) + ? const Icon(Icons.alternate_email_outlined) + // Icons.theater_comedy_outlined + // Icons.model_training_outlined + // Icons.switch_access_shortcut_outlined + // Icons.assistant_outlined : value!.avatarUrl == null ? Initicon( text: value!.name.split('、').join(' '), @@ -49,7 +55,7 @@ class ModelSwitcher extends StatelessWidget { ), color: customColors.chatInputPanelText, splashRadius: 20, - tooltip: '切换对话模型', + tooltip: AppLocale.switchModel.getString(context), ); } } diff --git a/lib/page/chat/home_chat.dart b/lib/page/chat/home_chat.dart index 7ac43129..47d555c6 100644 --- a/lib/page/chat/home_chat.dart +++ b/lib/page/chat/home_chat.dart @@ -253,6 +253,7 @@ class _HomeChatPageState extends State { title: Text(AppLocale.select.getString(context)), backgroundColor: Colors.transparent, centerTitle: true, + leadingWidth: 80, leading: TextButton( onPressed: () { chatPreviewController.exitSelectMode(); diff --git a/lib/page/chat/room_chat.dart b/lib/page/chat/room_chat.dart index 0e5f91cd..3abcc9ee 100644 --- a/lib/page/chat/room_chat.dart +++ b/lib/page/chat/room_chat.dart @@ -1,3 +1,5 @@ +import 'dart:convert'; + import 'package:askaide/bloc/free_count_bloc.dart'; import 'package:askaide/helper/ability.dart'; import 'package:askaide/helper/constant.dart'; @@ -67,9 +69,12 @@ class _RoomChatPageState extends State { bool showAudioPlayer = false; bool audioLoadding = false; + // The selected image files for image upload List selectedImageFiles = []; + // The selected file for file upload + FileUpload? selectedFile; - /// 当前选择的模型 + /// Currently selected model mm.Model? tempModel; // 全量模型列表 @@ -223,6 +228,10 @@ class _RoomChatPageState extends State { } } + final enableImageUpload = tempModel == null + ? (roomModel != null && roomModel!.supportVision) + : (tempModel?.supportVision ?? false); + return _chatPreviewController.selectMode ? buildSelectModeToolbars( context, @@ -235,16 +244,21 @@ class _RoomChatPageState extends State { _handleSubmit(value); FocusManager.instance.primaryFocus?.unfocus(); }, - enableImageUpload: tempModel == null - ? (roomModel != null && - roomModel!.supportVision) - : (tempModel?.supportVision ?? false), + enableImageUpload: + enableImageUpload && selectedFile == null, onImageSelected: (files) { setState(() { selectedImageFiles = files; }); }, selectedImageFiles: selectedImageFiles, + enableFileUpload: selectedImageFiles.isEmpty, + onFileSelected: (file) { + setState(() { + selectedFile = file; + }); + }, + selectedFile: selectedFile, onNewChat: () => handleResetContext(context), hintText: hintText, onVoiceRecordTappedEvent: () { @@ -291,6 +305,7 @@ class _RoomChatPageState extends State { if (state is ChatMessagesLoaded && state.error == null) { setState(() { selectedImageFiles = []; + selectedFile = null; }); } // 显示错误提示 @@ -424,6 +439,7 @@ class _RoomChatPageState extends State { ), centerTitle: true, elevation: 0, + leadingWidth: 80, leading: TextButton( onPressed: () { _chatPreviewController.exitSelectMode(); @@ -521,11 +537,49 @@ class _RoomChatPageState extends State { _inputEnabled.value = false; }); + if (selectedFile != null) { + final cancel = BotToast.showCustomLoading( + toastBuilder: (cancel) { + return const LoadingIndicator( + message: '正在上传,请稍后...', + ); + }, + allowClick: false, + ); + + try { + final uploader = QiniuUploader(widget.setting); + + if (!selectedFile!.uploaded) { + final path = selectedFile!.file.path; + if (path != null && path.isNotEmpty) { + final uploadRes = + await uploader.uploadFile(path, usage: 'document'); + selectedFile!.setUrl(uploadRes.url); + } else if (selectedFile!.file.bytes != null && + selectedFile!.file.bytes!.isNotEmpty) { + final uploadRes = await uploader.upload( + 'file-${DateTime.now().millisecondsSinceEpoch}.${selectedFile!.file.name}', + selectedFile!.file.bytes!, + usage: 'document', + ); + selectedFile!.setUrl(uploadRes.url); + } + } + } catch (e) { + // ignore: use_build_context_synchronously + showErrorMessageEnhanced(context, e); + return; + } finally { + cancel(); + } + } + if (selectedImageFiles.isNotEmpty) { final cancel = BotToast.showCustomLoading( toastBuilder: (cancel) { return const LoadingIndicator( - message: '正在上传图片,请稍后...', + message: '正在上传,请稍后...', ); }, allowClick: false, @@ -579,6 +633,12 @@ class _RoomChatPageState extends State { .where((e) => e.uploaded) .map((e) => e.url!) .toList(), + file: selectedFile != null && selectedFile!.uploaded + ? jsonEncode({ + 'name': selectedFile!.file.name, + 'url': selectedFile!.url, + }) + : null, ), index: index, isResent: isResent, diff --git a/lib/page/component/chat/chat_input.dart b/lib/page/component/chat/chat_input.dart index 6c2ca9e3..f844e073 100644 --- a/lib/page/component/chat/chat_input.dart +++ b/lib/page/component/chat/chat_input.dart @@ -7,11 +7,14 @@ import 'package:askaide/lang/lang.dart'; import 'package:askaide/page/component/chat/file_upload.dart'; import 'package:askaide/page/component/chat/voice_record.dart'; import 'package:askaide/page/component/dialog.dart'; +import 'package:askaide/page/component/enhanced_popup_menu.dart'; +import 'package:askaide/page/component/file_preview.dart'; import 'package:askaide/page/component/theme/custom_size.dart'; import 'package:askaide/repo/settings_repo.dart'; import 'package:file_picker/file_picker.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; +import 'package:flutter/widgets.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_localization/flutter_localization.dart'; import 'package:flutter_slidable/flutter_slidable.dart'; @@ -32,6 +35,12 @@ class ChatInput extends StatefulWidget { final Function()? onStopGenerate; final Function(bool hasFocus)? onFocusChange; + // Whether to enable file uploading + final bool enableFileUpload; + // Selected file for uploading + final FileUpload? selectedFile; + final Function(FileUpload? file)? onFileSelected; + const ChatInput({ super.key, required this.onSubmit, @@ -46,6 +55,9 @@ class ChatInput extends StatefulWidget { this.selectedImageFiles, this.onStopGenerate, this.onFocusChange, + this.enableFileUpload = false, + this.selectedFile, + this.onFileSelected, }); @override @@ -100,6 +112,17 @@ class _ChatInputState extends State with TickerProviderStateMixin { super.dispose(); } + // Whether the user can upload images + bool get canUploadImage => + widget.enableImageUpload && + Ability().supportImageUploader && + widget.onImageSelected != null && + Ability().supportWebSocket; + + // Whether the user can upload files + bool get canUploadFile => + widget.enableFileUpload && Ability().supportWebSocket; + @override Widget build(BuildContext context) { final customColors = Theme.of(context).extension()!; @@ -113,70 +136,13 @@ class _ChatInputState extends State with TickerProviderStateMixin { return SafeArea( child: Column( children: [ + // 选中的图片预览 if (widget.selectedImageFiles != null && widget.selectedImageFiles!.isNotEmpty) - SizedBox( - height: 110, - child: ListView( - scrollDirection: Axis.horizontal, - children: widget.selectedImageFiles! - .map( - (e) => Container( - margin: const EdgeInsets.only(right: 8), - padding: const EdgeInsets.all(5), - child: Stack( - children: [ - ClipRRect( - borderRadius: BorderRadius.circular(5), - child: e.file.bytes != null - ? Image.memory( - e.file.bytes!, - fit: BoxFit.cover, - width: 100, - height: 100, - ) - : Image.file( - File(e.file.path!), - fit: BoxFit.cover, - width: 100, - height: 100, - ), - ), - if (widget.enableNotifier.value) - Positioned( - right: 5, - top: 5, - child: InkWell( - onTap: () { - setState(() { - widget.selectedImageFiles!.remove(e); - widget.onImageSelected?.call( - widget.selectedImageFiles!); - }); - }, - child: Container( - padding: const EdgeInsets.all(3), - decoration: BoxDecoration( - borderRadius: - BorderRadius.circular(10), - color: - customColors.chatRoomBackground, - ), - child: Icon( - Icons.close, - size: 10, - color: customColors.weakTextColor, - ), - ), - ), - ), - ], - ), - ), - ) - .toList(), - ), - ), + buildSelectedImagePreview(customColors), + // 选中文件预览 + if (widget.selectedFile != null) + buildSelectedFilePreview(customColors), // 工具栏 if (widget.toolbar != null) widget.toolbar!, // if (widget.toolbar != null) @@ -211,12 +177,8 @@ class _ChatInputState extends State with TickerProviderStateMixin { if (widget.leftSideToolsBuilder != null) ...widget.leftSideToolsBuilder!(), if (widget.enableNotifier.value && - widget.enableImageUpload && - Ability().supportImageUploader && - widget.onImageSelected != null && - Ability().supportWebSocket) - _buildImageUploadButton( - context, setting, customColors), + (canUploadImage || canUploadFile)) + buildUploadButtons(context, setting, customColors), ], ), // 聊天输入框 @@ -279,6 +241,122 @@ class _ChatInputState extends State with TickerProviderStateMixin { ); } + Widget buildSelectedFilePreview(CustomColors customColors) { + var maxWidth = MediaQuery.of(context).size.width * 0.8; + if (maxWidth > 300) { + maxWidth = 300; + } + + return SizedBox( + height: 30, + child: ListView( + scrollDirection: Axis.horizontal, + children: [ + Container( + margin: const EdgeInsets.only(right: 8), + padding: const EdgeInsets.all(5), + child: Stack( + children: [ + FilePreview( + fileType: widget.selectedFile!.file.extension ?? '', + maxWidth: maxWidth, + filename: widget.selectedFile!.file.name, + ), + if (widget.enableNotifier.value) + Positioned( + right: 5, + top: 5, + child: InkWell( + onTap: () { + setState(() { + widget.onFileSelected?.call(null); + }); + }, + child: Container( + padding: const EdgeInsets.all(3), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(10), + color: customColors.chatRoomBackground, + ), + child: Icon( + Icons.close, + size: 10, + color: customColors.weakTextColor, + ), + ), + ), + ), + ], + ), + ), + ], + ), + ); + } + + Widget buildSelectedImagePreview(CustomColors customColors) { + return SizedBox( + height: 110, + child: ListView( + scrollDirection: Axis.horizontal, + children: widget.selectedImageFiles! + .map( + (e) => Container( + margin: const EdgeInsets.only(right: 8), + padding: const EdgeInsets.all(5), + child: Stack( + children: [ + ClipRRect( + borderRadius: BorderRadius.circular(5), + child: e.file.bytes != null + ? Image.memory( + e.file.bytes!, + fit: BoxFit.cover, + width: 100, + height: 100, + ) + : Image.file( + File(e.file.path!), + fit: BoxFit.cover, + width: 100, + height: 100, + ), + ), + if (widget.enableNotifier.value) + Positioned( + right: 5, + top: 5, + child: InkWell( + onTap: () { + setState(() { + widget.selectedImageFiles!.remove(e); + widget.onImageSelected + ?.call(widget.selectedImageFiles!); + }); + }, + child: Container( + padding: const EdgeInsets.all(3), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(10), + color: customColors.chatRoomBackground, + ), + child: Icon( + Icons.close, + size: 10, + color: customColors.weakTextColor, + ), + ), + ), + ), + ], + ), + ), + ) + .toList(), + ), + ); + } + /// 构建发送或者语音按钮 Widget _buildSendOrVoiceButton( BuildContext context, @@ -290,7 +368,7 @@ class _ChatInputState extends State with TickerProviderStateMixin { if (widget.onStopGenerate != null) { openConfirmDialog( context, - '确定要停止当前输出?', + AppLocale.confirmStopOutput.getString(context), () { widget.onStopGenerate!(); HapticFeedbackHelper.heavyImpact(); @@ -347,36 +425,90 @@ class _ChatInputState extends State with TickerProviderStateMixin { ); } - /// 构建图片上传按钮 - Widget _buildImageUploadButton( + // Image upload button event + void onImageUploadButtonPressed() async { + HapticFeedbackHelper.mediumImpact(); + if (widget.selectedImageFiles != null && + widget.selectedImageFiles!.length >= 4) { + showSuccessMessage(AppLocale.uploadImageLimit4.getString(context)); + return; + } + + FilePickerResult? result = await FilePicker.platform.pickFiles( + type: FileType.image, + allowMultiple: true, + allowCompression: true, + ); + if (result != null && result.files.isNotEmpty) { + final files = widget.selectedImageFiles ?? []; + files.addAll(result.files.map((e) => FileUpload(file: e)).toList()); + widget.onImageSelected + ?.call(files.sublist(0, files.length > 4 ? 4 : files.length)); + } + } + + // File upload button event + void onFileUploadButtonPressed() async { + HapticFeedbackHelper.mediumImpact(); + + FilePickerResult? result = await FilePicker.platform.pickFiles( + type: FileType.custom, + allowedExtensions: ['pdf', 'docx', 'txt', 'md'], + allowMultiple: false, + ); + if (result != null && result.files.isNotEmpty) { + if (widget.onFileSelected != null) { + widget.onFileSelected?.call(FileUpload(file: result.files.first)); + } + } + } + + /// Build image or file upload button + Widget buildUploadButtons( BuildContext context, SettingRepository setting, CustomColors customColors, ) { - return IconButton( - onPressed: () async { - HapticFeedbackHelper.mediumImpact(); - if (widget.selectedImageFiles != null && - widget.selectedImageFiles!.length >= 4) { - showSuccessMessage('最多只能上传 4 张图片'); - return; - } + if (canUploadImage && !canUploadFile) { + return IconButton( + onPressed: onImageUploadButtonPressed, + icon: const Icon(Icons.camera_alt), + color: customColors.chatInputPanelText, + splashRadius: 20, + tooltip: AppLocale.uploadImage.getString(context), + ); + } - FilePickerResult? result = await FilePicker.platform.pickFiles( - type: FileType.image, - allowMultiple: true, - ); - if (result != null && result.files.isNotEmpty) { - final files = widget.selectedImageFiles ?? []; - files.addAll(result.files.map((e) => FileUpload(file: e)).toList()); - widget.onImageSelected - ?.call(files.sublist(0, files.length > 4 ? 4 : files.length)); - } - }, - icon: const Icon(Icons.camera_alt), + if (!canUploadImage && canUploadFile) { + return IconButton( + onPressed: onFileUploadButtonPressed, + icon: const Icon(Icons.upload_file), + color: customColors.chatInputPanelText, + splashRadius: 20, + tooltip: AppLocale.uploadDocument.getString(context), + ); + } + + return EnhancedPopupMenu( + icon: Icons.upload_file, color: customColors.chatInputPanelText, - splashRadius: 20, - tooltip: AppLocale.uploadImage.getString(context), + tooltip: AppLocale.upload.getString(context), + items: [ + EnhancedPopupMenuItem( + title: AppLocale.uploadImage.getString(context), + icon: Icons.camera_alt, + onTap: (ctx) async { + onImageUploadButtonPressed(); + }, + ), + EnhancedPopupMenuItem( + title: AppLocale.uploadDocument.getString(context), + icon: Icons.attach_file, + onTap: (ctx) { + onFileUploadButtonPressed(); + }, + ), + ], ); } diff --git a/lib/page/component/chat/chat_preview.dart b/lib/page/component/chat/chat_preview.dart index d8f6f515..1f97856b 100644 --- a/lib/page/component/chat/chat_preview.dart +++ b/lib/page/component/chat/chat_preview.dart @@ -13,6 +13,7 @@ import 'package:askaide/page/component/chat/enhanced_selectable_text.dart'; import 'package:askaide/page/component/chat/file_upload.dart'; import 'package:askaide/page/component/chat/message_state_manager.dart'; import 'package:askaide/page/component/dialog.dart'; +import 'package:askaide/page/component/file_preview.dart'; import 'package:askaide/page/component/random_avatar.dart'; import 'package:askaide/page/component/theme/custom_size.dart'; import 'package:askaide/repo/api_server.dart'; @@ -224,20 +225,47 @@ class _ChatPreviewState extends State { mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.end, children: [ + if (message.file != null) + Container( + margin: message.role == Role.sender + ? const EdgeInsets.fromLTRB(0, 0, 10, 7) + : const EdgeInsets.fromLTRB(10, 0, 0, 7), + padding: const EdgeInsets.only(bottom: 5, left: 5), + constraints: BoxConstraints( + maxWidth: _chatBoxFilePreviewWidth(context), + ), + child: Builder(builder: (context) { + try { + final file = jsonDecode(message.file!); + final filename = file['name']; + // final fileUrl = file['url']; + + return FilePreview( + filename: filename, + fileType: filename.split('.').last, + mainAxisAlignment: MainAxisAlignment.end, + ); + } catch (e) { + return FilePreview( + fileType: '', + filename: AppLocale.unknownFile.getString(context), + mainAxisAlignment: MainAxisAlignment.end, + ); + } + }), + ), if (message.images != null && message.images!.isNotEmpty) Container( margin: message.role == Role.sender ? const EdgeInsets.fromLTRB(0, 0, 10, 7) : const EdgeInsets.fromLTRB(10, 0, 0, 7), - child: ConstrainedBox( - constraints: BoxConstraints( - maxWidth: _chatBoxImagePreviewWidth( - context, - (message.images ?? []).length, - ), + constraints: BoxConstraints( + maxWidth: _chatBoxImagePreviewWidth( + context, + (message.images ?? []).length, ), - child: FileUploadPreview(images: message.images ?? []), ), + child: FileUploadPreview(images: message.images ?? []), ), Row( mainAxisSize: MainAxisSize.min, @@ -390,7 +418,8 @@ class _ChatPreviewState extends State { confirmBtnText: AppLocale.gotIt .getString(context), showCancelBtn: false, - title: '温馨提示', + title: AppLocale.goodTips + .getString(context), child: Markdown( data: extraInfo, onUrlTap: (value) { @@ -446,18 +475,19 @@ class _ChatPreviewState extends State { customColors.chatRoomSenderText, ), ), - const Row( + Row( mainAxisSize: MainAxisSize.min, children: [ - Icon( + const Icon( Icons.check_circle, size: 12, color: Colors.green, ), - SizedBox(width: 5), + const SizedBox(width: 5), Text( - '翻译完成', - style: TextStyle( + AppLocale.translateFinished + .getString(context), + style: const TextStyle( fontSize: 12, color: Color.fromARGB( 255, 145, 145, 145), @@ -522,7 +552,7 @@ class _ChatPreviewState extends State { widget.onResentEvent!(message, index); }, title: Text(AppLocale.robotHasSomeError.getString(context)), - confirmText: '重新发送', + confirmText: AppLocale.sendRetry.getString(context), ); }, child: const Icon(Icons.error, color: Colors.red, size: 20), @@ -612,7 +642,7 @@ class _ChatPreviewState extends State { text: message.text, ), ), - title: '选择文本', + title: AppLocale.selectText.getString(context), ); cancel(); @@ -626,9 +656,9 @@ class _ChatPreviewState extends State { color: const Color.fromARGB(255, 255, 255, 255), size: 14, ), - const Text( - "文本", - style: TextStyle(fontSize: 12, color: Colors.white), + Text( + AppLocale.text.getString(context), + style: const TextStyle(fontSize: 12, color: Colors.white), ), ], ), @@ -636,22 +666,22 @@ class _ChatPreviewState extends State { TextButton.icon( onPressed: () { FlutterClipboard.copy(message.text).then((value) { - showSuccessMessage('已复制到剪贴板'); + showSuccessMessage(AppLocale.textCopied.getString(context)); }); cancel(); }, label: const Text(''), - icon: const Column( + icon: Column( mainAxisSize: MainAxisSize.min, children: [ - Icon( + const Icon( Icons.copy, color: Color.fromARGB(255, 255, 255, 255), size: 14, ), Text( - "复制", - style: TextStyle(fontSize: 12, color: Colors.white), + AppLocale.copy.getString(context), + style: const TextStyle(fontSize: 12, color: Colors.white), ), ], ), @@ -713,7 +743,9 @@ class _ChatPreviewState extends State { size: 14, ), Text( - showTranslate ? '隐藏' : '翻译', + showTranslate + ? AppLocale.hide.getString(context) + : AppLocale.translate.getString(context), style: const TextStyle(fontSize: 12, color: Colors.white), ) ], @@ -836,17 +868,17 @@ class _ChatPreviewState extends State { widget.onSpeakEvent!(message); }, label: const Text(''), - icon: const Column( + icon: Column( mainAxisSize: MainAxisSize.min, children: [ - Icon( + const Icon( Icons.record_voice_over, color: Color.fromARGB(255, 255, 255, 255), size: 14, ), Text( - '朗读', - style: TextStyle(fontSize: 12, color: Colors.white), + AppLocale.readByVoice.getString(context), + style: const TextStyle(fontSize: 12, color: Colors.white), ) ], ), @@ -858,17 +890,17 @@ class _ChatPreviewState extends State { cancel(); }, label: const Text(''), - icon: const Column( + icon: Column( mainAxisSize: MainAxisSize.min, children: [ - Icon( + const Icon( Icons.restore, color: Color.fromARGB(255, 255, 255, 255), size: 14, ), Text( - '重发', - style: TextStyle(fontSize: 12, color: Colors.white), + AppLocale.sendRetryS.getString(context), + style: const TextStyle(fontSize: 12, color: Colors.white), ), ], ), @@ -894,6 +926,16 @@ class _ChatPreviewState extends State { final max = imageCount > 1 ? 600.0 : 400.0; return expect > max ? max : expect; } + + // 获取文件预览的最大宽度 + double _chatBoxFilePreviewWidth(BuildContext context) { + var maxWidth = MediaQuery.of(context).size.width * 0.8; + if (maxWidth > 300) { + maxWidth = 300; + } + + return maxWidth; + } } /// ChatPreview 控制器 diff --git a/lib/page/component/enhanced_popup_menu.dart b/lib/page/component/enhanced_popup_menu.dart index a2943f37..cc710905 100644 --- a/lib/page/component/enhanced_popup_menu.dart +++ b/lib/page/component/enhanced_popup_menu.dart @@ -17,12 +17,21 @@ class EnhancedPopupMenuItem { class EnhancedPopupMenu extends StatelessWidget { final List items; final IconData? icon; - const EnhancedPopupMenu({super.key, required this.items, this.icon}); + final Color? color; + final String? tooltip; + const EnhancedPopupMenu({ + super.key, + required this.items, + this.icon, + this.color, + this.tooltip, + }); @override Widget build(BuildContext context) { return PopupMenuButton( - icon: Icon(icon ?? Icons.more_horiz), + icon: Icon(icon ?? Icons.more_horiz, color: color), + tooltip: tooltip, splashRadius: 20, elevation: 0, shape: RoundedRectangleBorder( diff --git a/lib/page/component/file_preview.dart b/lib/page/component/file_preview.dart new file mode 100644 index 00000000..3dd1837d --- /dev/null +++ b/lib/page/component/file_preview.dart @@ -0,0 +1,63 @@ +import 'package:flutter/material.dart'; +import 'package:flutter/widgets.dart'; + +class FilePreview extends StatelessWidget { + final String? filename; + final String? fileUrl; + final String fileType; + final double maxWidth; + final MainAxisAlignment mainAxisAlignment; + + const FilePreview({ + super.key, + this.filename, + this.fileUrl, + this.maxWidth = 300, + this.mainAxisAlignment = MainAxisAlignment.start, + required this.fileType, + }); + + @override + Widget build(BuildContext context) { + var iconFilePath = 'assets/icons/file.png'; + switch (fileType) { + case 'pdf': + iconFilePath = 'assets/icons/pdf.png'; + break; + case 'docx': + iconFilePath = 'assets/icons/doc.png'; + break; + case 'txt': + iconFilePath = 'assets/icons/txt.png'; + break; + } + return ConstrainedBox( + constraints: BoxConstraints( + maxWidth: maxWidth, + maxHeight: 25, + ), + child: Row( + mainAxisAlignment: mainAxisAlignment, + mainAxisSize: MainAxisSize.min, + children: [ + Image.asset( + iconFilePath, + width: 20, + height: 20, + ), + if (filename != null && filename != '') ...[ + const SizedBox(width: 5), + Flexible( + child: Text( + filename!, + maxLines: 1, + overflow: TextOverflow.ellipsis, + style: const TextStyle(fontSize: 12), + ), + ), + ], + ], + ), + ); + } +} diff --git a/lib/repo/model/chat_message.dart b/lib/repo/model/chat_message.dart index cf98d7bb..aea63d46 100644 --- a/lib/repo/model/chat_message.dart +++ b/lib/repo/model/chat_message.dart @@ -1,34 +1,52 @@ +import 'dart:convert'; + import 'package:dart_openai/openai.dart'; class ChatMessage extends OpenAIChatCompletionChoiceMessageModel { final List? images; - ChatMessage({required super.role, required super.content, this.images}); + final String? file; + ChatMessage( + {required super.role, required super.content, this.images, this.file}); @override Map toMap() { - if (images == null || images!.isEmpty) { - return { - "role": role.name, - "content": content, - }; - } - - return { + final Map res = { "role": role.name, "content": content, - "multipart_content": [ - ...(images + }; + + if (file != null || (images != null && images!.isNotEmpty)) { + final multipartContent = []; + + if (file != null) { + try { + multipartContent.add({ + 'type': 'file', + 'file': jsonDecode(file!), + }); + } catch (ignore) { + // ignore + } + } + + if (images != null && images!.isNotEmpty) { + multipartContent.addAll(images ?.map((e) => { 'type': 'image_url', 'image_url': {'url': e} }) .toList() ?? - []), - { - 'type': 'text', - 'text': content, - }, - ], - }; + []); + } + + multipartContent.add({ + 'type': 'text', + 'text': content, + }); + + res['multipart_content'] = multipartContent; + } + + return res; } } diff --git a/lib/repo/model/message.dart b/lib/repo/model/message.dart index 15838797..1731d4bd 100644 --- a/lib/repo/model/message.dart +++ b/lib/repo/model/message.dart @@ -63,6 +63,8 @@ class Message { /// 消息图片列表 List? images; + // Uploaded file by user (json(name, url)) + String? file; Message( this.role, @@ -84,6 +86,7 @@ class Message { this.avatarUrl, this.senderName, this.images, + this.file, }); /// 获取消息附加信息 @@ -165,6 +168,7 @@ class Message { 'token_consumed': tokenConsumed, 'quota_consumed': quotaConsumed, 'images': images != null ? jsonEncode(images) : null, + 'file': file, }; } @@ -190,7 +194,8 @@ class Message { images = map['images'] == null ? null : (jsonDecode(map['images'] as String) as List) - .cast(); + .cast(), + file = map['file'] as String?; } enum Role { diff --git a/lib/repo/openai_repo.dart b/lib/repo/openai_repo.dart index b5545ee2..2ea6762a 100644 --- a/lib/repo/openai_repo.dart +++ b/lib/repo/openai_repo.dart @@ -4,6 +4,7 @@ import 'dart:io'; import 'package:askaide/helper/ability.dart'; import 'package:askaide/helper/constant.dart'; import 'package:askaide/helper/env.dart'; +import 'package:askaide/helper/logger.dart'; import 'package:askaide/helper/platform.dart'; import 'package:askaide/helper/queue.dart'; import 'package:askaide/repo/model/chat_message.dart'; @@ -354,7 +355,7 @@ class OpenAIRepository { : roomId, // n 参数暂时用不到,复用作为 roomId }); - // Logger.instance.d('send chat request: $data'); + Logger.instance.d('send chat request: $data'); channel.sink.add(data); } else { diff --git a/pubspec.yaml b/pubspec.yaml index 1e841596..427b2397 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -200,6 +200,11 @@ flutter: - assets/zhifubao.png - assets/stripe.png - assets/apple.webp + - assets/icons/doc.png + - assets/icons/file.png + - assets/icons/mp3.png + - assets/icons/pdf.png + - assets/icons/txt.png # - images/a_dot_ham.jpeg From ee8d242dfebc45b8aa9e61253f1d4dbf824653c7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=AE=A1=E5=AE=9C=E5=B0=A7?= Date: Thu, 10 Oct 2024 22:35:06 +0800 Subject: [PATCH 04/26] update --- lib/helper/upload.dart | 8 + lib/page/component/icon_box_button.dart | 52 +++ lib/page/creative_island/draw/draw_list.dart | 104 +++-- lib/page/setting/account_security.dart | 298 +++++++------ lib/page/setting/setting_screen.dart | 447 ++++++++++--------- lib/repo/model/misc.dart | 13 +- 6 files changed, 510 insertions(+), 412 deletions(-) create mode 100644 lib/page/component/icon_box_button.dart diff --git a/lib/helper/upload.dart b/lib/helper/upload.dart index 8158ef67..ea254167 100644 --- a/lib/helper/upload.dart +++ b/lib/helper/upload.dart @@ -288,6 +288,10 @@ class QiniuUploader { final initResp = await APIServer().uploadInit(filename, data.length, usage: usage); + if (initResp.uploaded) { + return UploadedFile(filename, initResp.url); + } + var storage = Storage(config: Config(retryLimit: 3)); await storage.putBytes( @@ -310,6 +314,10 @@ class QiniuUploader { final initResp = await APIServer() .uploadInit(filename, File(path).lengthSync(), usage: usage); + if (initResp.uploaded) { + return UploadedFile(filename, initResp.url); + } + var storage = Storage(config: Config(retryLimit: 3)); await storage.putFile( diff --git a/lib/page/component/icon_box_button.dart b/lib/page/component/icon_box_button.dart new file mode 100644 index 00000000..04ac4120 --- /dev/null +++ b/lib/page/component/icon_box_button.dart @@ -0,0 +1,52 @@ +import 'package:flutter/material.dart'; + +class IconBoxButton extends StatelessWidget { + final double? width; + final String title; + final IconData icon; + final IconData? smallIcon; + final Function()? onTap; + + const IconBoxButton({ + super.key, + this.width, + required this.title, + required this.icon, + this.smallIcon, + this.onTap, + }); + + @override + Widget build(BuildContext context) { + return InkWell( + borderRadius: BorderRadius.circular(8), + onTap: () => onTap?.call(), + child: Container( + height: 75, + width: width, + padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 10), + decoration: BoxDecoration( + color: Colors.grey.withAlpha(20), + border: Border.all( + color: Colors.grey.withAlpha(50), + ), + borderRadius: BorderRadius.circular(8), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Icon(icon), + const SizedBox(height: 5), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text(title), + Icon(smallIcon ?? Icons.keyboard_arrow_right), + ], + ), + ], + ), + ), + ); + } +} diff --git a/lib/page/creative_island/draw/draw_list.dart b/lib/page/creative_island/draw/draw_list.dart index e86a27a8..a5a5ba14 100644 --- a/lib/page/creative_island/draw/draw_list.dart +++ b/lib/page/creative_island/draw/draw_list.dart @@ -113,52 +113,64 @@ class _DrawListScreenState extends State { .where((e) => e.size != 'large' && e.size != 'medium') .toList(); - return SingleChildScrollView( - child: Column( - children: [ - GridView.count( - physics: const NeverScrollableScrollPhysics(), - padding: const EdgeInsets.only(top: 0, left: 10, right: 10), - crossAxisCount: _calCrossAxisCount(context), - childAspectRatio: 2, - shrinkWrap: true, - children: largeItems - .map((e) => Container( - padding: const EdgeInsets.symmetric( - horizontal: 5, vertical: 10), - child: e, - )) - .toList(), - ), - GridView.count( - physics: const NeverScrollableScrollPhysics(), - padding: const EdgeInsets.only(top: 5, left: 10, right: 10), - crossAxisCount: _calCrossAxisCount(context) * 2, - childAspectRatio: 1, - shrinkWrap: true, - children: mediumItems - .map((e) => Container( - padding: const EdgeInsets.symmetric( - horizontal: 5, vertical: 5), - child: e, - )) - .toList(), - ), - GridView.count( - physics: const NeverScrollableScrollPhysics(), - padding: const EdgeInsets.only(top: 5, left: 10, right: 10), - crossAxisCount: _calCrossAxisCount(context) * 2, - childAspectRatio: 2, - shrinkWrap: true, - children: otherItems - .map((e) => Container( - padding: const EdgeInsets.symmetric( - horizontal: 5, vertical: 5), - child: e, - )) - .toList(), - ), - ], + return RefreshIndicator( + onRefresh: () async { + context + .read() + .add(CreativeIslandItemsV2LoadEvent(forceRefresh: true)); + }, + color: customColors.linkColor, + displacement: 20, + child: SingleChildScrollView( + child: Column( + children: [ + GridView.count( + physics: const NeverScrollableScrollPhysics(), + padding: + const EdgeInsets.only(top: 0, left: 10, right: 10), + crossAxisCount: _calCrossAxisCount(context), + childAspectRatio: 2, + shrinkWrap: true, + children: largeItems + .map((e) => Container( + padding: const EdgeInsets.symmetric( + horizontal: 5, vertical: 10), + child: e, + )) + .toList(), + ), + GridView.count( + physics: const NeverScrollableScrollPhysics(), + padding: + const EdgeInsets.only(top: 5, left: 10, right: 10), + crossAxisCount: _calCrossAxisCount(context) * 2, + childAspectRatio: 1, + shrinkWrap: true, + children: mediumItems + .map((e) => Container( + padding: const EdgeInsets.symmetric( + horizontal: 5, vertical: 5), + child: e, + )) + .toList(), + ), + GridView.count( + physics: const NeverScrollableScrollPhysics(), + padding: + const EdgeInsets.only(top: 5, left: 10, right: 10), + crossAxisCount: _calCrossAxisCount(context) * 2, + childAspectRatio: 2, + shrinkWrap: true, + children: otherItems + .map((e) => Container( + padding: const EdgeInsets.symmetric( + horizontal: 5, vertical: 5), + child: e, + )) + .toList(), + ), + ], + ), ), ); } diff --git a/lib/page/setting/account_security.dart b/lib/page/setting/account_security.dart index cdb4193a..220c3ea1 100644 --- a/lib/page/setting/account_security.dart +++ b/lib/page/setting/account_security.dart @@ -123,106 +123,71 @@ class _AccountSecurityScreenState extends State { buildWhen: (previous, current) => current is AccountLoaded, builder: (_, state) { if (state is AccountLoaded) { - return buildSettingsList([ - SettingsSection( - title: const Text('基础信息'), - tiles: [ - SettingsTile( - title: const Text('昵称'), - trailing: Row( - children: [ - Text( - state.user!.user.name == null || - state.user!.user.name == '' - ? '未设置' - : state.user!.user.name!, - style: TextStyle( - color: - customColors.weakTextColor?.withAlpha(200), - fontSize: 13, - ), - ), - const Icon( - CupertinoIcons.chevron_forward, - size: 18, - color: Colors.grey, - ), - ], - ), - onPressed: (context) { - openTextFieldDialog( - context, - title: '设置昵称', - hint: '请输入你的昵称', - maxLine: 1, - maxLength: 30, - defaultValue: state.user?.user.name, - onSubmit: (value) { - context - .read() - .add(AccountUpdateEvent(realname: value)); - return true; - }, - ); - }, - ), - SettingsTile( - title: const Text('手机号'), - trailing: Row( - children: [ - Text( - state.user!.user.phone == null || - state.user!.user.phone == '' - ? '绑定' - : state.user!.user.phone!, - style: TextStyle( - color: - customColors.weakTextColor?.withAlpha(200), - fontSize: 13, + return buildSettingsList( + context, + [ + SettingsSection( + title: const Text('基础信息'), + tiles: [ + SettingsTile( + title: const Text('昵称'), + trailing: Row( + children: [ + Text( + state.user!.user.name == null || + state.user!.user.name == '' + ? '未设置' + : state.user!.user.name!, + style: TextStyle( + color: customColors.weakTextColor + ?.withAlpha(200), + fontSize: 13, + ), ), - ), - if (state.user!.user.phone == null || - state.user!.user.phone == '') - const SizedBox(width: 5), - if (state.user!.user.phone == null || - state.user!.user.phone == '') const Icon( CupertinoIcons.chevron_forward, size: 18, color: Colors.grey, ), - ], + ], + ), + onPressed: (context) { + openTextFieldDialog( + context, + title: '设置昵称', + hint: '请输入你的昵称', + maxLine: 1, + maxLength: 30, + defaultValue: state.user?.user.name, + onSubmit: (value) { + context + .read() + .add(AccountUpdateEvent(realname: value)); + return true; + }, + ); + }, ), - onPressed: (context) { - if (state.user!.user.phone == null || - state.user!.user.phone == '') { - context - .push('/bind-phone?is_signin=false') - .then((value) => Logger.instance.d(value)); - } - }, - ), - if (Ability().enableWechatSignin && wechatInstalled) SettingsTile( - title: const Text('微信账号'), + title: const Text('手机号'), trailing: Row( children: [ Text( - state.user!.user.unionId == null || - state.user!.user.unionId == '' + state.user!.user.phone == null || + state.user!.user.phone == '' ? '绑定' - : '已绑定', + : state.user!.user.phone!, style: TextStyle( color: customColors.weakTextColor ?.withAlpha(200), fontSize: 13, ), ), - if (state.user!.user.unionId == null || - state.user!.user.unionId == '') + if (state.user!.user.phone == null || + state.user!.user.phone == '') const SizedBox(width: 5), - if (state.user!.user.unionId == null || - state.user!.user.unionId == '') + if (state.user!.user.phone == null || + state.user!.user.phone == '') const Icon( CupertinoIcons.chevron_forward, size: 18, @@ -230,59 +195,97 @@ class _AccountSecurityScreenState extends State { ), ], ), - onPressed: (context) async { - if (state.user!.user.unionId == null || - state.user!.user.unionId == '') { - final ok = await sendWeChatAuth( - scope: "snsapi_userinfo", - state: "wechat_sdk_demo_test"); - if (!ok) { - showErrorMessage('请先安装微信后再使用该功能'); - } + onPressed: (context) { + if (state.user!.user.phone == null || + state.user!.user.phone == '') { + context + .push('/bind-phone?is_signin=false') + .then((value) => Logger.instance.d(value)); } }, ), - SettingsTile( - title: Text(state.user!.control.isSetPassword - ? '修改密码' - : '设置密码'), - trailing: const Icon( - CupertinoIcons.chevron_forward, - size: 18, - color: Colors.grey, + if (Ability().enableWechatSignin && wechatInstalled) + SettingsTile( + title: const Text('微信账号'), + trailing: Row( + children: [ + Text( + state.user!.user.unionId == null || + state.user!.user.unionId == '' + ? '绑定' + : '已绑定', + style: TextStyle( + color: customColors.weakTextColor + ?.withAlpha(200), + fontSize: 13, + ), + ), + if (state.user!.user.unionId == null || + state.user!.user.unionId == '') + const SizedBox(width: 5), + if (state.user!.user.unionId == null || + state.user!.user.unionId == '') + const Icon( + CupertinoIcons.chevron_forward, + size: 18, + color: Colors.grey, + ), + ], + ), + onPressed: (context) async { + if (state.user!.user.unionId == null || + state.user!.user.unionId == '') { + final ok = await sendWeChatAuth( + scope: "snsapi_userinfo", + state: "wechat_sdk_demo_test"); + if (!ok) { + showErrorMessage('请先安装微信后再使用该功能'); + } + } + }, + ), + SettingsTile( + title: Text(state.user!.control.isSetPassword + ? '修改密码' + : '设置密码'), + trailing: const Icon( + CupertinoIcons.chevron_forward, + size: 18, + color: Colors.grey, + ), + onPressed: (context) { + context.push('/user/change-password'); + }, ), - onPressed: (context) { - context.push('/user/change-password'); - }, - ), - ], - ), - SettingsSection( - tiles: [ - SettingsTile( - title: Text(AppLocale.signOut.getString(context)), - trailing: const Icon( - Icons.logout, - size: 18, - color: Colors.grey, + ], + ), + SettingsSection( + tiles: [ + SettingsTile( + title: Text(AppLocale.signOut.getString(context)), + trailing: const Icon( + Icons.logout, + size: 18, + color: Colors.grey, + ), + onPressed: (_) { + openConfirmDialog( + context, + AppLocale.confirmSignOut.getString(context), + () { + context + .read() + .add(AccountSignOutEvent()); + context.go('/login'); + }, + danger: true, + ); + }, ), - onPressed: (_) { - openConfirmDialog( - context, - AppLocale.confirmSignOut.getString(context), - () { - context - .read() - .add(AccountSignOutEvent()); - context.go('/login'); - }, - danger: true, - ); - }, - ), - ], - ), - ]); + ], + ), + ], + ); } return const Center(child: CircularProgressIndicator()); @@ -294,19 +297,30 @@ class _AccountSecurityScreenState extends State { } } -SettingsList buildSettingsList(List sections) { - return SettingsList( - platform: DevicePlatform.iOS, - lightTheme: const SettingsThemeData( - settingsListBackground: Colors.transparent, - settingsSectionBackground: Color.fromARGB(255, 255, 255, 255), - ), - darkTheme: const SettingsThemeData( - settingsListBackground: Colors.transparent, - settingsSectionBackground: Color.fromARGB(255, 27, 27, 27), - titleTextColor: Color.fromARGB(255, 239, 239, 239), +Widget buildSettingsList( + BuildContext context, + List sections, +) { + final customColors = Theme.of(context).extension()!; + return RefreshIndicator( + color: customColors.linkColor, + displacement: 20, + onRefresh: () async { + context.read().add(AccountLoadEvent()); + }, + child: SettingsList( + platform: DevicePlatform.iOS, + lightTheme: const SettingsThemeData( + settingsListBackground: Colors.transparent, + settingsSectionBackground: Color.fromARGB(255, 255, 255, 255), + ), + darkTheme: const SettingsThemeData( + settingsListBackground: Colors.transparent, + settingsSectionBackground: Color.fromARGB(255, 27, 27, 27), + titleTextColor: Color.fromARGB(255, 239, 239, 239), + ), + sections: sections, + contentPadding: const EdgeInsets.all(0), ), - sections: sections, - contentPadding: const EdgeInsets.all(0), ); } diff --git a/lib/page/setting/setting_screen.dart b/lib/page/setting/setting_screen.dart index 339a9c6d..8bdcd2d0 100644 --- a/lib/page/setting/setting_screen.dart +++ b/lib/page/setting/setting_screen.dart @@ -95,276 +95,279 @@ class _SettingScreenState extends State { ], child: BlocBuilder( builder: (_, state) { - return buildSettingsList([ - // 智慧果信息、充值入口 - _buildAccountQuotaCard(context, state), - - // 账号信息 - SettingsSection( - title: Text(AppLocale.accountInfo.getString(context)), - tiles: _buildAccountSetting(state, customColors), - ), + return buildSettingsList( + context, + [ + // 智慧果信息、充值入口 + _buildAccountQuotaCard(context, state), - // 邀请卡片 - if (state is AccountLoaded && state.user != null) - _buildInviteCard(context, state), - - // 自定义设置 - SettingsSection( - title: Text(AppLocale.custom.getString(context)), - tiles: [ - // 主题设置 - _buildCommonThemeSetting(customColors), - // 语言设置 - _buildCommonLanguageSetting(), - // 常用模型 - if (Ability().isUserLogon()) - _buildCustomHomeModelsSetting(customColors), - // OpenAI 自定义配置 - if (Ability().enableOpenAI) - _buildOpenAISelfHostedSetting(customColors), - // 用户 API Keys 配置 - if (state is AccountLoaded && - state.user != null && - Ability().supportAPIKeys) - _buildUserAPIKeySetting(customColors), - ], - ), + // 账号信息 + SettingsSection( + title: Text(AppLocale.accountInfo.getString(context)), + tiles: _buildAccountSetting(state, customColors), + ), + + // 邀请卡片 + if (state is AccountLoaded && state.user != null) + _buildInviteCard(context, state), + + // 自定义设置 + SettingsSection( + title: Text(AppLocale.custom.getString(context)), + tiles: [ + // 主题设置 + _buildCommonThemeSetting(customColors), + // 语言设置 + _buildCommonLanguageSetting(), + // 常用模型 + if (Ability().isUserLogon()) + _buildCustomHomeModelsSetting(customColors), + // OpenAI 自定义配置 + if (Ability().enableOpenAI) + _buildOpenAISelfHostedSetting(customColors), + // 用户 API Keys 配置 + if (state is AccountLoaded && + state.user != null && + Ability().supportAPIKeys) + _buildUserAPIKeySetting(customColors), + ], + ), - // 系统信息 - SettingsSection( - title: Text(AppLocale.systemInfo.getString(context)), - tiles: [ - // 只有 Web 端才展示 App 下载 - if (PlatformTool.isWeb()) + // 系统信息 + SettingsSection( + title: Text(AppLocale.systemInfo.getString(context)), + tiles: [ + // 只有 Web 端才展示 App 下载 + if (PlatformTool.isWeb()) + SettingsTile( + title: const Text('APP 下载'), + trailing: const Icon( + Icons.download, + size: 18, + color: Colors.grey, + ), + onPressed: (context) { + launchUrlString( + 'https://aidea.aicode.cc', + mode: LaunchMode.externalApplication, + ); + }, + ), + // 服务状态 + if (Ability().serviceStatusPage != '') + SettingsTile( + title: const Text('服务状态'), + trailing: const Icon( + CupertinoIcons.chevron_forward, + size: 18, + color: Colors.grey, + ), + onPressed: (_) { + launchUrlString(Ability().serviceStatusPage); + }, + ), + // 清空缓存 SettingsTile( - title: const Text('APP 下载'), + title: Text(AppLocale.clearCache.getString(context)), trailing: const Icon( - Icons.download, + CupertinoIcons.refresh, size: 18, color: Colors.grey, ), - onPressed: (context) { - launchUrlString( - 'https://aidea.aicode.cc', - mode: LaunchMode.externalApplication, + onPressed: (_) { + openConfirmDialog( + context, + AppLocale.confirmClearCache.getString(context), + () async { + await Cache().clearAll(); + await HttpClient.cleanCache(); + + showSuccessMessage( + // ignore: use_build_context_synchronously + AppLocale.operateSuccess.getString(context), + ); + + if (context.mounted) { + Phoenix.rebirth(context); + } + }, + danger: true, ); }, ), - // 服务状态 - if (Ability().serviceStatusPage != '') + + // 检查更新 + if (!PlatformTool.isIOS()) + SettingsTile( + title: Text(AppLocale.updateCheck.getString(context)), + trailing: const Icon( + CupertinoIcons.chevron_forward, + size: 18, + color: Colors.grey, + ), + onPressed: (_) { + APIServer().versionCheck(cache: false).then((resp) { + if (resp.hasUpdate) { + showBeautyDialog( + context, + type: QuickAlertType.success, + text: resp.message, + confirmBtnText: '去更新', + onConfirmBtnTap: () { + launchUrlString( + resp.url, + mode: LaunchMode.externalApplication, + ); + }, + cancelBtnText: '暂不更新', + showCancelBtn: true, + ); + } else { + showSuccessMessage( + AppLocale.latestVersion.getString(context)); + } + }); + }, + ), + // 用户协议 SettingsTile( - title: const Text('服务状态'), + title: Text(AppLocale.userTerms.getString(context)), trailing: const Icon( CupertinoIcons.chevron_forward, size: 18, color: Colors.grey, ), onPressed: (_) { - launchUrlString(Ability().serviceStatusPage); + launchUrl(Uri.parse( + 'https://ai.aicode.cc/terms-user.html')); }, ), - // 清空缓存 - SettingsTile( - title: Text(AppLocale.clearCache.getString(context)), - trailing: const Icon( - CupertinoIcons.refresh, - size: 18, - color: Colors.grey, - ), - onPressed: (_) { - openConfirmDialog( - context, - AppLocale.confirmClearCache.getString(context), - () async { - await Cache().clearAll(); - await HttpClient.cleanCache(); - - showSuccessMessage( - // ignore: use_build_context_synchronously - AppLocale.operateSuccess.getString(context), - ); - - if (context.mounted) { - Phoenix.rebirth(context); - } - }, - danger: true, - ); - }, - ), - - // 检查更新 - if (!PlatformTool.isIOS()) + // 隐私政策 SettingsTile( - title: Text(AppLocale.updateCheck.getString(context)), + title: Text(AppLocale.privacyPolicy.getString(context)), trailing: const Icon( CupertinoIcons.chevron_forward, size: 18, color: Colors.grey, ), onPressed: (_) { - APIServer().versionCheck(cache: false).then((resp) { - if (resp.hasUpdate) { - showBeautyDialog( - context, - type: QuickAlertType.success, - text: resp.message, - confirmBtnText: '去更新', - onConfirmBtnTap: () { - launchUrlString( - resp.url, - mode: LaunchMode.externalApplication, - ); - }, - cancelBtnText: '暂不更新', - showCancelBtn: true, - ); - } else { - showSuccessMessage( - AppLocale.latestVersion.getString(context)); - } - }); + launchUrl(Uri.parse( + 'https://ai.aicode.cc/privacy-policy.html')); }, ), - // 用户协议 - SettingsTile( - title: Text(AppLocale.userTerms.getString(context)), - trailing: const Icon( - CupertinoIcons.chevron_forward, - size: 18, - color: Colors.grey, - ), - onPressed: (_) { - launchUrl( - Uri.parse('https://ai.aicode.cc/terms-user.html')); - }, - ), - // 隐私政策 - SettingsTile( - title: Text(AppLocale.privacyPolicy.getString(context)), - trailing: const Icon( - CupertinoIcons.chevron_forward, - size: 18, - color: Colors.grey, - ), - onPressed: (_) { - launchUrl(Uri.parse( - 'https://ai.aicode.cc/privacy-policy.html')); - }, - ), - // 关于 - SettingsTile( - title: Text(AppLocale.about.getString(context)), - trailing: const Icon( - CupertinoIcons.chevron_forward, - size: 18, - color: Colors.grey, + // 关于 + SettingsTile( + title: Text(AppLocale.about.getString(context)), + trailing: const Icon( + CupertinoIcons.chevron_forward, + size: 18, + color: Colors.grey, + ), + onPressed: (_) { + var tapCount = 0; + showAboutDialog( + context: context, + applicationName: 'AIdea', + applicationIcon: GestureDetector( + onTap: () { + if (userHasLabPermission(state)) { + return; + } + + tapCount++; + + if (tapCount > 5) { + tapCount = 0; + + final showLab = forceShowLab(); + widget.settings.set(settingForceShowLab, + showLab ? 'false' : 'true'); + + showSuccessMessage( + showLab ? '已关闭实验室功能' : '已启用实验室功能'); + + setState(() {}); + } + }, + child: Image.asset('assets/app.png', width: 40), + ), + applicationVersion: clientVersion, + children: [ + Text(AppLocale.aIdeaApp.getString(context)), + ], + ); + }, ), - onPressed: (_) { - var tapCount = 0; - showAboutDialog( - context: context, - applicationName: 'AIdea', - applicationIcon: GestureDetector( - onTap: () { - if (userHasLabPermission(state)) { - return; - } - - tapCount++; - - if (tapCount > 5) { - tapCount = 0; - - final showLab = forceShowLab(); - widget.settings.set(settingForceShowLab, - showLab ? 'false' : 'true'); - - showSuccessMessage( - showLab ? '已关闭实验室功能' : '已启用实验室功能'); + ], + ), - setState(() {}); - } + if (userHasLabPermission(state) || forceShowLab()) + SettingsSection( + title: const Text('实验室'), + tiles: [ + if (userHasLabPermission(state)) + SettingsTile( + title: const Text('画板'), + trailing: const Icon( + CupertinoIcons.chevron_forward, + size: 18, + color: Colors.grey, + ), + onPressed: (context) { + context.push('/lab/draw-board'); }, - child: Image.asset('assets/app.png', width: 40), ), - applicationVersion: clientVersion, - children: [ - Text(AppLocale.aIdeaApp.getString(context)), - ], - ); - }, - ), - ], - ), - if (userHasLabPermission(state) || forceShowLab()) - SettingsSection( - title: const Text('实验室'), - tiles: [ - if (userHasLabPermission(state)) + // 自定义服务器 + _buildServerSelfHostedSetting(customColors), + // 诊断 SettingsTile( - title: const Text('画板'), + title: Text(AppLocale.diagnostic.getString(context)), trailing: const Icon( CupertinoIcons.chevron_forward, size: 18, color: Colors.grey, ), onPressed: (context) { - context.push('/lab/draw-board'); + context.push('/diagnosis'); }, ), - - // 自定义服务器 - _buildServerSelfHostedSetting(customColors), - // 诊断 - SettingsTile( - title: Text(AppLocale.diagnostic.getString(context)), - trailing: const Icon( - CupertinoIcons.chevron_forward, - size: 18, - color: Colors.grey, - ), - onPressed: (context) { - context.push('/diagnosis'); - }, - ), - ], - ), - // 社交媒体图标 - _buildSocialIcons(context), - // 版权信息 - CustomSettingsSection( - child: Column( - children: [ - Text( - 'Copyright © 2023-${DateTime.now().year}', - style: TextStyle( - color: customColors.weakTextColor, - ), - ), - GestureDetector( - onTap: () { - launchUrlString( - 'https://aidea.aicode.cc', - mode: LaunchMode.externalApplication, - ); - }, - child: Text( - 'Gulu Artificial Intelligence Technology Co., Ltd.', + ], + ), + // 社交媒体图标 + _buildSocialIcons(context), + // 版权信息 + CustomSettingsSection( + child: Column( + children: [ + Text( + 'Copyright © 2023-${DateTime.now().year}', style: TextStyle( color: customColors.weakTextColor, - fontSize: 12, ), ), - ), - const SizedBox(height: 15), - ], + GestureDetector( + onTap: () { + launchUrlString( + 'https://aidea.aicode.cc', + mode: LaunchMode.externalApplication, + ); + }, + child: Text( + 'Gulu Artificial Intelligence Technology Co., Ltd.', + style: TextStyle( + color: customColors.weakTextColor, + fontSize: 12, + ), + ), + ), + const SizedBox(height: 15), + ], + ), ), - ), - ]); + ], + ); }, ), ), diff --git a/lib/repo/model/misc.dart b/lib/repo/model/misc.dart index 3ca21710..768eda02 100644 --- a/lib/repo/model/misc.dart +++ b/lib/repo/model/misc.dart @@ -555,14 +555,22 @@ class UploadInitResponse { String key; String token; String url; - - UploadInitResponse(this.key, this.bucket, this.token, this.url); + bool uploaded; + + UploadInitResponse( + this.key, + this.bucket, + this.token, + this.url, { + this.uploaded = false, + }); toJson() => { 'bucket': bucket, 'key': key, 'token': token, 'url': url, + 'uploaded': uploaded, }; static fromJson(Map json) { @@ -571,6 +579,7 @@ class UploadInitResponse { json['bucket'], json['token'], json['url'], + uploaded: json['uploaded'] ?? false, ); } } From e2b6cbd93341c4beb61fbf09f5c6a2859d4f12f1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=AE=A1=E5=AE=9C=E5=B0=A7?= Date: Fri, 11 Oct 2024 18:12:09 +0800 Subject: [PATCH 05/26] update --- lib/bloc/chat_message_bloc.dart | 48 +- lib/helper/cache.dart | 18 + lib/main.dart | 1745 ++++++++++--------- lib/page/chat/component/model_switcher.dart | 30 +- lib/page/chat/home_chat.dart | 191 +- lib/page/chat/room_chat.dart | 166 +- lib/page/component/account_quota_card.dart | 24 +- lib/page/component/chat/chat_input.dart | 1 - lib/page/component/chat/role_avatar.dart | 73 + lib/page/component/select_mode_toolbar.dart | 139 ++ lib/page/component/sliver_component.dart | 2 +- lib/page/component/theme/custom_size.dart | 7 + lib/page/custom_scaffold.dart | 62 + lib/page/drawer.dart | 142 ++ lib/page/home.dart | 721 ++++++++ lib/page/setting/setting_screen.dart | 2 +- lib/repo/api/info.dart | 5 +- 17 files changed, 2214 insertions(+), 1162 deletions(-) create mode 100644 lib/page/component/chat/role_avatar.dart create mode 100644 lib/page/component/select_mode_toolbar.dart create mode 100644 lib/page/custom_scaffold.dart create mode 100644 lib/page/drawer.dart create mode 100644 lib/page/home.dart diff --git a/lib/bloc/chat_message_bloc.dart b/lib/bloc/chat_message_bloc.dart index 54cd9db2..9b97cd1f 100644 --- a/lib/bloc/chat_message_bloc.dart +++ b/lib/bloc/chat_message_bloc.dart @@ -170,14 +170,18 @@ class ChatMessageBloc extends BlocExt { his = await chatMsgRepo.getChatHistory(event.chatHistoryId!); } - emit(ChatMessagesLoaded( - await chatMsgRepo.getRecentMessages( - roomId, - userId: APIServer().localUserID(), - chatHistoryId: event.chatHistoryId, - ), - chatHistory: his, - )); + if (roomId == chatAnywhereRoomId && his == null) { + emit(ChatMessagesLoaded(const [])); + } else { + emit(ChatMessagesLoaded( + await chatMsgRepo.getRecentMessages( + roomId, + userId: APIServer().localUserID(), + chatHistoryId: event.chatHistoryId, + ), + chatHistory: his, + )); + } } /// 停止输出事件处理 @@ -195,10 +199,11 @@ class ChatMessageBloc extends BlocExt { Message message = event.message as Message; ChatHistory? localChatHistory; + int? localChatHistoryId = message.chatHistoryId; // 如果是聊一聊,自动创建聊天记录历史 if (roomId == chatAnywhereRoomId) { - if (message.chatHistoryId == null || message.chatHistoryId! <= 0) { + if (localChatHistoryId == null || localChatHistoryId <= 0) { final chatHistory = await chatMsgRepo.createChatHistory( title: event.message.text, userId: APIServer().localUserID(), @@ -208,13 +213,17 @@ class ChatMessageBloc extends BlocExt { ); localChatHistory = chatHistory; + localChatHistoryId = chatHistory.id; message.chatHistoryId = chatHistory.id; emit(ChatAnywhereInited(chatHistory.id!)); + } else { + if (localChatHistoryId > 0) { + localChatHistory = + await chatMsgRepo.getChatHistory(localChatHistoryId); + } } } - int? localChatHistoryId = message.chatHistoryId; - // 查询当前 Room 信息 final room = await queryRoomById(chatMsgRepo, roomId); if (room == null) { @@ -230,14 +239,8 @@ class ChatMessageBloc extends BlocExt { return; } - if (roomId == chatAnywhereRoomId && - localChatHistoryId != null && - localChatHistoryId > 0) { - final chatHistory = await chatMsgRepo.getChatHistory(localChatHistoryId); - if (chatHistory != null && chatHistory.model != null) { - room.model = chatHistory.model!; - localChatHistory = chatHistory; - } + if (localChatHistory != null && localChatHistory.model != null) { + room.model = localChatHistory.model!; } // 查询最后一条消息 @@ -334,8 +337,11 @@ class ChatMessageBloc extends BlocExt { messages.add(waitMessage); - emit(ChatMessagesLoaded(messages, - processing: true, chatHistory: localChatHistory)); + emit(ChatMessagesLoaded( + messages, + processing: true, + chatHistory: localChatHistory, + )); emit(ChatMessageUpdated(waitMessage, processing: true)); // 等待监听机器人应答消息 diff --git a/lib/helper/cache.dart b/lib/helper/cache.dart index de773e3d..7841c0fe 100644 --- a/lib/helper/cache.dart +++ b/lib/helper/cache.dart @@ -54,4 +54,22 @@ class Cache { Future stringGet({required String key}) async { return await cacheRepo.get(key); } + + Future setInt({ + required String key, + required int value, + Duration duration = const Duration(days: 1), + }) async { + await cacheRepo.set(key, value.toString(), duration); + return value; + } + + Future intGet({required String key}) async { + var value = await cacheRepo.get(key); + if (value == null || value.isEmpty) { + return 0; + } + + return int.parse(value); + } } diff --git a/lib/main.dart b/lib/main.dart index 9bd38ce4..d086a6c1 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -20,6 +20,7 @@ import 'package:askaide/page/admin/users.dart'; import 'package:askaide/page/balance/web_payment_proxy.dart'; import 'package:askaide/page/balance/web_payment_result.dart'; import 'package:askaide/page/creative_island/draw/artistic_wordart.dart'; +import 'package:askaide/page/home.dart'; import 'package:flutter_phoenix/flutter_phoenix.dart'; import 'package:path/path.dart'; @@ -44,7 +45,6 @@ import 'package:askaide/data/migrate.dart'; import 'package:askaide/page/balance/quota_usage_details.dart'; import 'package:askaide/page/creative_island/draw/artistic_qr.dart'; import 'package:askaide/page/setting/account_security.dart'; -import 'package:askaide/page/app_scaffold.dart'; import 'package:askaide/page/lab/avatar_selector.dart'; import 'package:askaide/page/setting/article.dart'; import 'package:askaide/page/setting/background_selector.dart'; @@ -230,7 +230,7 @@ void main() async { mailEnabled: true, openaiEnabled: true, homeModels: [], - homeRoute: '/chat-chat', + homeRoute: '/', showHomeModelDescription: true, supportWebsocket: false, ), @@ -272,7 +272,6 @@ class MyApp extends StatefulWidget { late final FreeCountBloc freeCountBloc; final _rootNavigatorKey = GlobalKey(); - final _shellNavigatorKey = GlobalKey(); final FlutterLocalization localization = FlutterLocalization.instance; final MessageStateManager messageStateManager; @@ -315,966 +314,980 @@ class MyApp extends StatefulWidget { ], navigatorKey: _rootNavigatorKey, routes: [ - StatefulShellRoute.indexedStack( - // navigatorKey: _shellNavigatorKey, - //parentNavigatorKey: _rootNavigatorKey, - builder: ( - BuildContext context, - GoRouterState state, - StatefulNavigationShell navigationShell, - ) { - return AppScaffold( - settingRepo: settingRepo, - navigationShell: navigationShell, - ); + ShellRoute( + builder: (context, state, child) { + return child; }, - branches: [ - StatefulShellBranch(navigatorKey: _shellNavigatorKey, routes: [ - GoRoute( - name: 'chat_chat', - path: '/chat-chat', - pageBuilder: (context, state) => transitionResolver( + routes: [ + GoRoute( + path: '/', + name: 'home', + pageBuilder: (context, state) { + return transitionResolver( MultiBlocProvider( providers: [ + BlocProvider.value( + value: ChatBlocManager().getBloc( + chatAnywhereRoomId, + chatHistoryId: int.tryParse( + state.queryParameters['chat_id'] ?? ''), + ), + ), BlocProvider( - create: (context) => ChatChatBloc(chatMsgRepo)), + create: (context) => ChatChatBloc(chatMsgRepo), + ), + BlocProvider.value(value: chatRoomBloc), + BlocProvider.value(value: galleryBloc), + BlocProvider.value(value: accountBloc), + BlocProvider.value(value: versionBloc), BlocProvider.value(value: freeCountBloc), ], - child: HomePage( - setting: settingRepo, - showInitialDialog: - state.queryParameters['show_initial_dialog'] == - 'true', - reward: - int.tryParse(state.queryParameters['reward'] ?? '0'), + child: NewHomePage( + settings: settingRepo, + stateManager: messageStateManager, ), ), + ); + }, + ), + GoRoute( + name: 'setting', + path: '/setting', + pageBuilder: (context, state) => transitionResolver( + MultiBlocProvider( + providers: [ + BlocProvider.value(value: accountBloc), + ], + child: SettingScreen( + settings: context.read()), ), ), - ]), - StatefulShellBranch(routes: [ - GoRoute( - name: 'characters', - path: '/', - pageBuilder: (context, state) => transitionResolver( - MultiBlocProvider( - providers: [BlocProvider.value(value: chatRoomBloc)], - child: RoomsPage(setting: settingRepo), + ), + GoRoute( + name: 'creative-draw', + path: '/creative-draw', + pageBuilder: (context, state) => transitionResolver( + MultiBlocProvider( + providers: [ + BlocProvider( + create: (context) => + CreativeIslandBloc(creativeIslandRepo)), + ], + child: DrawListScreen( + setting: settingRepo, ), ), ), - ]), - StatefulShellBranch(routes: [ - GoRoute( - name: 'creative-gallery', - path: '/creative-gallery', - pageBuilder: (context, state) => transitionResolver( - MultiBlocProvider( - providers: [ - BlocProvider.value(value: galleryBloc), - ], - child: GalleryScreen(setting: settingRepo), + ), + GoRoute( + name: 'creative-gallery', + path: '/creative-gallery', + pageBuilder: (context, state) => transitionResolver( + MultiBlocProvider( + providers: [ + BlocProvider.value(value: galleryBloc), + ], + child: GalleryScreen(setting: settingRepo), + ), + ), + ), + GoRoute( + name: 'characters', + path: '/characters', + pageBuilder: (context, state) => transitionResolver( + MultiBlocProvider( + providers: [BlocProvider.value(value: chatRoomBloc)], + child: RoomsPage(setting: settingRepo), + ), + ), + ), + GoRoute( + name: 'chat_chat', + path: '/chat-chat', + pageBuilder: (context, state) => transitionResolver( + MultiBlocProvider( + providers: [ + BlocProvider( + create: (context) => ChatChatBloc(chatMsgRepo)), + BlocProvider.value(value: freeCountBloc), + ], + child: HomePage( + setting: settingRepo, + showInitialDialog: + state.queryParameters['show_initial_dialog'] == 'true', + reward: + int.tryParse(state.queryParameters['reward'] ?? '0'), ), ), ), - ]), - StatefulShellBranch(routes: [ - GoRoute( - name: 'creative-draw', - path: '/creative-draw', - pageBuilder: (context, state) => transitionResolver( - MultiBlocProvider( - providers: [ - BlocProvider( - create: (context) => - CreativeIslandBloc(creativeIslandRepo)), - ], - child: DrawListScreen( - setting: settingRepo, - ), + ), + GoRoute( + path: '/login', + pageBuilder: (context, state) => transitionResolver( + MultiBlocProvider( + providers: [ + BlocProvider.value(value: versionBloc), + ], + child: SignInScreen( + settings: settingRepo, + username: state.queryParameters['username'], ), ), ), - ]), - StatefulShellBranch(routes: [ - GoRoute( - name: 'setting', - path: '/setting', - pageBuilder: (context, state) => transitionResolver( - MultiBlocProvider( - providers: [ - BlocProvider.value(value: accountBloc), - ], - child: SettingScreen( - settings: context.read()), + ), + GoRoute( + path: '/signin-or-signup', + pageBuilder: (context, state) => transitionResolver( + MultiBlocProvider( + providers: [ + BlocProvider.value(value: versionBloc), + ], + child: SigninOrSignupScreen( + settings: settingRepo, + username: state.queryParameters['username']!, + isSignup: state.queryParameters['is_signup'] == 'true', + signInMethod: state.queryParameters['sign_in_method']!, + wechatBindToken: state.queryParameters['wechat_bind_token'], ), ), ), - ]), - ], - ), - GoRoute( - path: '/login', - pageBuilder: (context, state) => transitionResolver( - MultiBlocProvider( - providers: [ - BlocProvider.value(value: versionBloc), - ], - child: SignInScreen( - settings: settingRepo, - username: state.queryParameters['username'], + ), + GoRoute( + path: '/user/change-password', + pageBuilder: (context, state) => transitionResolver( + ChangePasswordScreen(setting: settingRepo), ), ), - ), - ), - GoRoute( - path: '/signin-or-signup', - pageBuilder: (context, state) => transitionResolver( - MultiBlocProvider( - providers: [ - BlocProvider.value(value: versionBloc), - ], - child: SigninOrSignupScreen( - settings: settingRepo, - username: state.queryParameters['username']!, - isSignup: state.queryParameters['is_signup'] == 'true', - signInMethod: state.queryParameters['sign_in_method']!, - wechatBindToken: state.queryParameters['wechat_bind_token'], + GoRoute( + path: '/user/destroy', + pageBuilder: (context, state) => transitionResolver( + DestroyAccountScreen(setting: settingRepo), ), ), - ), - ), - GoRoute( - path: '/user/change-password', - pageBuilder: (context, state) => transitionResolver( - ChangePasswordScreen(setting: settingRepo), - ), - ), - GoRoute( - path: '/user/destroy', - pageBuilder: (context, state) => transitionResolver( - DestroyAccountScreen(setting: settingRepo), - ), - ), - GoRoute( - path: '/signup', - pageBuilder: (context, state) => transitionResolver( - SignupScreen( - settings: settingRepo, - username: state.queryParameters['username'], - ), - ), - ), - GoRoute( - path: '/retrieve-password', - pageBuilder: (context, state) => transitionResolver( - RetrievePasswordScreen( - username: state.queryParameters['username'], - setting: settingRepo, - ), - ), - ), - GoRoute( - name: 'chat_anywhere', - path: '/chat-anywhere', - pageBuilder: (context, state) => transitionResolver( - MultiBlocProvider( - providers: [ - BlocProvider.value( - value: ChatBlocManager().getBloc( - chatAnywhereRoomId, - chatHistoryId: - int.tryParse(state.queryParameters['chat_id'] ?? ''), - ), + GoRoute( + path: '/signup', + pageBuilder: (context, state) => transitionResolver( + SignupScreen( + settings: settingRepo, + username: state.queryParameters['username'], ), - BlocProvider.value(value: chatRoomBloc), - BlocProvider(create: (context) => NotifyBloc()), - BlocProvider.value(value: freeCountBloc), - ], - child: HomeChatPage( - stateManager: messageStateManager, - setting: settingRepo, - chatId: int.tryParse(state.queryParameters['chat_id'] ?? '0'), - initialMessage: state.queryParameters['init_message'], - model: state.queryParameters['model'] == '' - ? null - : state.queryParameters['model'], - title: state.queryParameters['title'] == '' - ? null - : state.queryParameters['title'], ), ), - ), - ), - - GoRoute( - name: 'chat_chat_history', - path: '/chat-chat/history', - pageBuilder: (context, state) => transitionResolver( - MultiBlocProvider( - providers: [ - BlocProvider(create: (context) => ChatChatBloc(chatMsgRepo)), - ], - child: HomeChatHistoryPage( - setting: settingRepo, - chatMessageRepo: chatMsgRepo, + GoRoute( + path: '/retrieve-password', + pageBuilder: (context, state) => transitionResolver( + RetrievePasswordScreen( + username: state.queryParameters['username'], + setting: settingRepo, + ), ), ), - ), - ), - GoRoute( - path: '/lab/avatar-selector', - pageBuilder: (context, state) => transitionResolver( - const AvatarSelectorScreen(usage: AvatarUsage.room), - ), - ), - GoRoute( - path: '/lab/draw-board', - pageBuilder: (context, state) => transitionResolver( - const DrawboardScreen(), - ), - ), - - GoRoute( - name: 'create-room', - path: '/create-room', - pageBuilder: (context, state) => transitionResolver( - MultiBlocProvider( - providers: [BlocProvider.value(value: chatRoomBloc)], - child: RoomCreatePage(setting: settingRepo), - ), - ), - ), - GoRoute( - name: 'chat', - path: '/room/:room_id/chat', - pageBuilder: (context, state) { - final roomId = int.parse(state.pathParameters['room_id']!); - return transitionResolver( - MultiBlocProvider( - providers: [ - BlocProvider.value( - value: ChatBlocManager().getBloc(roomId), + GoRoute( + name: 'chat_anywhere', + path: '/chat-anywhere', + pageBuilder: (context, state) => transitionResolver( + MultiBlocProvider( + providers: [ + BlocProvider.value( + value: ChatBlocManager().getBloc( + chatAnywhereRoomId, + chatHistoryId: int.tryParse( + state.queryParameters['chat_id'] ?? ''), + ), + ), + BlocProvider.value(value: chatRoomBloc), + BlocProvider(create: (context) => NotifyBloc()), + BlocProvider.value(value: freeCountBloc), + ], + child: HomeChatPage( + stateManager: messageStateManager, + setting: settingRepo, + chatId: + int.tryParse(state.queryParameters['chat_id'] ?? '0'), + initialMessage: state.queryParameters['init_message'], + model: state.queryParameters['model'] == '' + ? null + : state.queryParameters['model'], + title: state.queryParameters['title'] == '' + ? null + : state.queryParameters['title'], ), - BlocProvider.value(value: chatRoomBloc), - BlocProvider(create: (context) => NotifyBloc()), - BlocProvider.value(value: freeCountBloc), - ], - child: RoomChatPage( - roomId: roomId, - stateManager: messageStateManager, - setting: settingRepo, ), ), - ); - }, - ), - GoRoute( - name: 'room_setting', - path: '/room/:room_id/setting', - pageBuilder: (context, state) { - final roomId = int.parse(state.pathParameters['room_id']!); - return transitionResolver( - MultiBlocProvider( - providers: [ - BlocProvider.value(value: chatRoomBloc), - BlocProvider.value( - value: ChatBlocManager().getBloc(roomId), + ), + + GoRoute( + name: 'chat_chat_history', + path: '/chat-chat/history', + pageBuilder: (context, state) => transitionResolver( + MultiBlocProvider( + providers: [ + BlocProvider( + create: (context) => ChatChatBloc(chatMsgRepo)), + ], + child: HomeChatHistoryPage( + setting: settingRepo, + chatMessageRepo: chatMsgRepo, ), - ], - child: RoomEditPage(roomId: roomId, setting: settingRepo), + ), ), - ); - }, - ), - GoRoute( - name: 'account-security-setting', - path: '/setting/account-security', - pageBuilder: (context, state) => transitionResolver( - MultiBlocProvider( - providers: [ - BlocProvider.value(value: accountBloc), - ], - child: AccountSecurityScreen( - settings: context.read(), + ), + GoRoute( + path: '/lab/avatar-selector', + pageBuilder: (context, state) => transitionResolver( + const AvatarSelectorScreen(usage: AvatarUsage.room), ), ), - ), - ), - GoRoute( - name: 'lab-user-center', - path: '/lab/user-center', - pageBuilder: (context, state) => transitionResolver( - MultiBlocProvider( - providers: [ - BlocProvider.value(value: accountBloc), - BlocProvider( - create: (context) => - CreativeIslandBloc(creativeIslandRepo)), - ], - child: - UserCenterScreen(settings: context.read()), + GoRoute( + path: '/lab/draw-board', + pageBuilder: (context, state) => transitionResolver( + const DrawboardScreen(), + ), ), - ), - ), - - GoRoute( - name: 'setting-background-selector', - path: '/setting/background-selector', - pageBuilder: (context, state) => transitionResolver( - BlocProvider( - create: (context) => BackgroundImageBloc(), - child: BackgroundSelectorScreen(setting: settingRepo), - ), - ), - ), - GoRoute( - name: 'setting-openai-custom', - path: '/setting/openai-custom', - pageBuilder: (context, state) => transitionResolver( - OpenAISettingScreen( - settings: settingRepo, - source: state.queryParameters['source'], - ), - ), - ), - GoRoute( - name: 'creative-upscale', - path: '/creative-draw/create-upscale', - pageBuilder: (context, state) => transitionResolver( - MultiBlocProvider( - providers: [ - BlocProvider( - create: (context) => - CreativeIslandBloc(creativeIslandRepo)), - ], - child: ImageEditDirectScreen( - setting: settingRepo, - title: AppLocale.superResolution.getString(context), - apiEndpoint: 'upscale', - note: state.queryParameters['note'], - initWaitDuration: 15, - initImage: state.queryParameters['init_image'], + GoRoute( + name: 'create-room', + path: '/create-room', + pageBuilder: (context, state) => transitionResolver( + MultiBlocProvider( + providers: [BlocProvider.value(value: chatRoomBloc)], + child: RoomCreatePage(setting: settingRepo), + ), ), ), - ), - ), - GoRoute( - name: 'creative-colorize', - path: '/creative-draw/create-colorize', - pageBuilder: (context, state) => transitionResolver( - MultiBlocProvider( - providers: [ - BlocProvider( - create: (context) => - CreativeIslandBloc(creativeIslandRepo)), - ], - child: ImageEditDirectScreen( - setting: settingRepo, - title: AppLocale.colorizeImage.getString(context), - apiEndpoint: 'colorize', - note: state.queryParameters['note'], - initWaitDuration: 15, - initImage: state.queryParameters['init_image'], - ), + GoRoute( + name: 'chat', + path: '/room/:room_id/chat', + pageBuilder: (context, state) { + final roomId = int.parse(state.pathParameters['room_id']!); + return transitionResolver( + MultiBlocProvider( + providers: [ + BlocProvider.value( + value: ChatBlocManager().getBloc(roomId), + ), + BlocProvider.value(value: chatRoomBloc), + BlocProvider(create: (context) => NotifyBloc()), + BlocProvider.value(value: freeCountBloc), + ], + child: RoomChatPage( + roomId: roomId, + stateManager: messageStateManager, + setting: settingRepo, + ), + ), + ); + }, ), - ), - ), - GoRoute( - name: 'creative-video', - path: '/creative-draw/create-video', - pageBuilder: (context, state) => transitionResolver( - MultiBlocProvider( - providers: [ - BlocProvider( - create: (context) => - CreativeIslandBloc(creativeIslandRepo)), - ], - child: ImageEditDirectScreen( - setting: settingRepo, - title: '图生视频', - apiEndpoint: 'image-to-video', - note: state.queryParameters['note'], - initWaitDuration: 60, - initImage: state.queryParameters['init_image'], - ), + GoRoute( + name: 'room_setting', + path: '/room/:room_id/setting', + pageBuilder: (context, state) { + final roomId = int.parse(state.pathParameters['room_id']!); + return transitionResolver( + MultiBlocProvider( + providers: [ + BlocProvider.value(value: chatRoomBloc), + BlocProvider.value( + value: ChatBlocManager().getBloc(roomId), + ), + ], + child: RoomEditPage(roomId: roomId, setting: settingRepo), + ), + ); + }, ), - ), - ), - GoRoute( - name: 'creative-draw-gallery-preview', - path: '/creative-draw/gallery/:id', - pageBuilder: (context, state) => transitionResolver( - MultiBlocProvider( - providers: [ - BlocProvider.value(value: galleryBloc), - ], - child: GalleryItemScreen( - setting: settingRepo, - galleryId: int.parse(state.pathParameters['id']!), + GoRoute( + name: 'account-security-setting', + path: '/setting/account-security', + pageBuilder: (context, state) => transitionResolver( + MultiBlocProvider( + providers: [ + BlocProvider.value(value: accountBloc), + ], + child: AccountSecurityScreen( + settings: context.read(), + ), + ), ), ), - ), - ), - GoRoute( - name: 'creative-draw-create', - path: '/creative-draw/create', - pageBuilder: (context, state) => transitionResolver( - MultiBlocProvider( - providers: [ - BlocProvider.value(value: galleryBloc), - ], - child: DrawCreateScreen( - setting: settingRepo, - galleryCopyId: int.tryParse( - state.queryParameters['gallery_copy_id'] ?? '', + GoRoute( + name: 'lab-user-center', + path: '/lab/user-center', + pageBuilder: (context, state) => transitionResolver( + MultiBlocProvider( + providers: [ + BlocProvider.value(value: accountBloc), + BlocProvider( + create: (context) => + CreativeIslandBloc(creativeIslandRepo)), + ], + child: UserCenterScreen( + settings: context.read()), ), - mode: state.queryParameters['mode']!, - id: state.queryParameters['id']!, - note: state.queryParameters['note'], - initImage: state.queryParameters['init_image'], ), ), - ), - ), - GoRoute( - name: 'creative-artistic-text', - path: '/creative-draw/artistic-text', - pageBuilder: (context, state) => transitionResolver( - MultiBlocProvider( - providers: [ - BlocProvider.value(value: galleryBloc), - ], - child: ArtisticQRScreen( - setting: settingRepo, - galleryCopyId: int.tryParse( - state.queryParameters['gallery_copy_id'] ?? '', + + GoRoute( + name: 'setting-background-selector', + path: '/setting/background-selector', + pageBuilder: (context, state) => transitionResolver( + BlocProvider( + create: (context) => BackgroundImageBloc(), + child: BackgroundSelectorScreen(setting: settingRepo), ), - type: state.queryParameters['type']!, - id: state.queryParameters['id']!, - note: state.queryParameters['note'], ), ), - ), - ), - GoRoute( - name: 'creative-artistic-wordart', - path: '/creative-draw/artistic-wordart', - pageBuilder: (context, state) => transitionResolver( - MultiBlocProvider( - providers: [ - BlocProvider.value(value: galleryBloc), - ], - child: ArtisticWordArtScreen( - setting: settingRepo, - galleryCopyId: int.tryParse( - state.queryParameters['gallery_copy_id'] ?? '', + GoRoute( + name: 'setting-openai-custom', + path: '/setting/openai-custom', + pageBuilder: (context, state) => transitionResolver( + OpenAISettingScreen( + settings: settingRepo, + source: state.queryParameters['source'], ), - id: state.queryParameters['id']!, - note: state.queryParameters['note'], ), ), - ), - ), - GoRoute( - name: 'creative-island-history-all', - path: '/creative-island/history', - pageBuilder: (context, state) { - return transitionResolver( - MultiBlocProvider( - providers: [ - BlocProvider( - create: (context) => - CreativeIslandBloc(creativeIslandRepo)), - ], - child: MyCreationScreen( - setting: settingRepo, - mode: state.queryParameters['mode'] ?? '', + + GoRoute( + name: 'creative-upscale', + path: '/creative-draw/create-upscale', + pageBuilder: (context, state) => transitionResolver( + MultiBlocProvider( + providers: [ + BlocProvider( + create: (context) => + CreativeIslandBloc(creativeIslandRepo)), + ], + child: ImageEditDirectScreen( + setting: settingRepo, + title: AppLocale.superResolution.getString(context), + apiEndpoint: 'upscale', + note: state.queryParameters['note'], + initWaitDuration: 15, + initImage: state.queryParameters['init_image'], + ), ), ), - ); - }, - ), - GoRoute( - name: 'creative-island-models', - path: '/creative-island/models', - pageBuilder: (context, state) { - return transitionResolver( - MultiBlocProvider( - providers: [ - BlocProvider( - create: (context) => - CreativeIslandBloc(creativeIslandRepo)), - ], - child: CreativeModelScreen(setting: settingRepo), + ), + GoRoute( + name: 'creative-colorize', + path: '/creative-draw/create-colorize', + pageBuilder: (context, state) => transitionResolver( + MultiBlocProvider( + providers: [ + BlocProvider( + create: (context) => + CreativeIslandBloc(creativeIslandRepo)), + ], + child: ImageEditDirectScreen( + setting: settingRepo, + title: AppLocale.colorizeImage.getString(context), + apiEndpoint: 'colorize', + note: state.queryParameters['note'], + initWaitDuration: 15, + initImage: state.queryParameters['init_image'], + ), + ), ), - ); - }, - ), - GoRoute( - name: 'creative-island-history-item', - path: '/creative-island/:id/history/:item_id', - pageBuilder: (context, state) { - final id = state.pathParameters['id']!; - final itemId = int.tryParse(state.pathParameters['item_id']!); - final showErrorMessage = - state.queryParameters['show_error'] == 'true'; - return transitionResolver( - MultiBlocProvider( - providers: [ - BlocProvider( - create: (context) => - CreativeIslandBloc(creativeIslandRepo)), - ], - child: MyCreationItemPage( - setting: settingRepo, - islandId: id, - itemId: itemId!, - showErrorMessage: showErrorMessage, + ), + GoRoute( + name: 'creative-video', + path: '/creative-draw/create-video', + pageBuilder: (context, state) => transitionResolver( + MultiBlocProvider( + providers: [ + BlocProvider( + create: (context) => + CreativeIslandBloc(creativeIslandRepo)), + ], + child: ImageEditDirectScreen( + setting: settingRepo, + title: '图生视频', + apiEndpoint: 'image-to-video', + note: state.queryParameters['note'], + initWaitDuration: 60, + initImage: state.queryParameters['init_image'], + ), ), ), - ); - }, - ), - GoRoute( - name: 'quota-details', - path: '/quota-details', - pageBuilder: (context, state) => transitionResolver( - PaymentHistoryScreen(setting: settingRepo), - ), - ), - GoRoute( - name: 'quota-usage-statistics', - path: '/quota-usage-statistics', - pageBuilder: (context, state) => transitionResolver( - QuotaUsageStatisticsScreen(setting: settingRepo), - ), - ), - GoRoute( - name: 'quota-usage-daily-details', - path: '/quota-usage-daily-details', - pageBuilder: (context, state) => transitionResolver( - QuotaUsageDetailScreen( - setting: settingRepo, - date: state.queryParameters['date'] ?? - DateFormat('yyyy-MM-dd').format(DateTime.now()), - ), - ), - ), - GoRoute( - name: 'prompt-editor', - path: '/prompt-editor', - pageBuilder: (context, state) { - var prompt = state.queryParameters['prompt'] ?? ''; - return transitionResolver(PromptScreen(prompt: prompt)); - }, - ), - GoRoute( - name: 'payment', - path: '/payment', - pageBuilder: (context, state) { - return transitionResolver( - MultiBlocProvider( - providers: [ - BlocProvider(create: ((context) => PaymentBloc())), - ], - child: PaymentScreen(setting: settingRepo), + ), + GoRoute( + name: 'creative-draw-gallery-preview', + path: '/creative-draw/gallery/:id', + pageBuilder: (context, state) => transitionResolver( + MultiBlocProvider( + providers: [ + BlocProvider.value(value: galleryBloc), + ], + child: GalleryItemScreen( + setting: settingRepo, + galleryId: int.parse(state.pathParameters['id']!), + ), + ), ), - ); - }, - ), - GoRoute( - name: 'bind-phone', - path: '/bind-phone', - pageBuilder: (context, state) { - return transitionResolver( - MultiBlocProvider( - providers: [ - BlocProvider.value(value: accountBloc), - ], - child: BindPhoneScreen( - setting: settingRepo, - isSignIn: state.queryParameters['is_signin'] != 'false', + ), + GoRoute( + name: 'creative-draw-create', + path: '/creative-draw/create', + pageBuilder: (context, state) => transitionResolver( + MultiBlocProvider( + providers: [ + BlocProvider.value(value: galleryBloc), + ], + child: DrawCreateScreen( + setting: settingRepo, + galleryCopyId: int.tryParse( + state.queryParameters['gallery_copy_id'] ?? '', + ), + mode: state.queryParameters['mode']!, + id: state.queryParameters['id']!, + note: state.queryParameters['note'], + initImage: state.queryParameters['init_image'], + ), ), ), - ); - }, - ), - GoRoute( - name: 'diagnosis', - path: '/diagnosis', - pageBuilder: (context, state) => transitionResolver( - DiagnosisScreen(setting: settingRepo), - ), - ), - GoRoute( - name: 'free-statistics', - path: '/free-statistics', - pageBuilder: (context, state) => transitionResolver( - MultiBlocProvider( - providers: [BlocProvider.value(value: freeCountBloc)], - child: FreeStatisticsPage(setting: settingRepo), - ), - ), - ), - GoRoute( - name: 'custom-home-models', - path: '/setting/custom-home-models', - pageBuilder: (context, state) => transitionResolver( - CustomHomeModelsPage(setting: settingRepo), - ), - ), - GoRoute( - name: 'group-chat-chat', - path: '/group-chat/:group_id/chat', - pageBuilder: (context, state) { - final groupId = int.tryParse(state.pathParameters['group_id']!); - - return transitionResolver( - MultiBlocProvider( - providers: [ - BlocProvider( - create: ((context) => - GroupChatBloc(stateManager: messageStateManager)), + ), + GoRoute( + name: 'creative-artistic-text', + path: '/creative-draw/artistic-text', + pageBuilder: (context, state) => transitionResolver( + MultiBlocProvider( + providers: [ + BlocProvider.value(value: galleryBloc), + ], + child: ArtisticQRScreen( + setting: settingRepo, + galleryCopyId: int.tryParse( + state.queryParameters['gallery_copy_id'] ?? '', + ), + type: state.queryParameters['type']!, + id: state.queryParameters['id']!, + note: state.queryParameters['note'], ), - BlocProvider.value(value: chatRoomBloc), - ], - child: GroupChatPage( - setting: settingRepo, - stateManager: messageStateManager, - groupId: groupId!, ), ), - ); - }, - ), - GoRoute( - name: 'group-chat-create', - path: '/group-chat-create', - pageBuilder: (context, state) { - return transitionResolver( - MultiBlocProvider( - providers: [ - BlocProvider( - create: ((context) => - GroupChatBloc(stateManager: messageStateManager)), + ), + GoRoute( + name: 'creative-artistic-wordart', + path: '/creative-draw/artistic-wordart', + pageBuilder: (context, state) => transitionResolver( + MultiBlocProvider( + providers: [ + BlocProvider.value(value: galleryBloc), + ], + child: ArtisticWordArtScreen( + setting: settingRepo, + galleryCopyId: int.tryParse( + state.queryParameters['gallery_copy_id'] ?? '', + ), + id: state.queryParameters['id']!, + note: state.queryParameters['note'], ), - BlocProvider.value(value: chatRoomBloc), - ], - child: GroupCreatePage(setting: settingRepo), + ), ), - ); - }, - ), - GoRoute( - name: 'group-chat-edit', - path: '/group-chat/:group_id/edit', - pageBuilder: (context, state) { - return transitionResolver( - MultiBlocProvider( - providers: [ - BlocProvider( - create: ((context) => - GroupChatBloc(stateManager: messageStateManager)), + ), + GoRoute( + name: 'creative-island-history-all', + path: '/creative-island/history', + pageBuilder: (context, state) { + return transitionResolver( + MultiBlocProvider( + providers: [ + BlocProvider( + create: (context) => + CreativeIslandBloc(creativeIslandRepo)), + ], + child: MyCreationScreen( + setting: settingRepo, + mode: state.queryParameters['mode'] ?? '', + ), + ), + ); + }, + ), + GoRoute( + name: 'creative-island-models', + path: '/creative-island/models', + pageBuilder: (context, state) { + return transitionResolver( + MultiBlocProvider( + providers: [ + BlocProvider( + create: (context) => + CreativeIslandBloc(creativeIslandRepo)), + ], + child: CreativeModelScreen(setting: settingRepo), ), - BlocProvider.value(value: chatRoomBloc), - ], - child: GroupEditPage( + ); + }, + ), + GoRoute( + name: 'creative-island-history-item', + path: '/creative-island/:id/history/:item_id', + pageBuilder: (context, state) { + final id = state.pathParameters['id']!; + final itemId = int.tryParse(state.pathParameters['item_id']!); + final showErrorMessage = + state.queryParameters['show_error'] == 'true'; + return transitionResolver( + MultiBlocProvider( + providers: [ + BlocProvider( + create: (context) => + CreativeIslandBloc(creativeIslandRepo)), + ], + child: MyCreationItemPage( + setting: settingRepo, + islandId: id, + itemId: itemId!, + showErrorMessage: showErrorMessage, + ), + ), + ); + }, + ), + GoRoute( + name: 'quota-details', + path: '/quota-details', + pageBuilder: (context, state) => transitionResolver( + PaymentHistoryScreen(setting: settingRepo), + ), + ), + GoRoute( + name: 'quota-usage-statistics', + path: '/quota-usage-statistics', + pageBuilder: (context, state) => transitionResolver( + QuotaUsageStatisticsScreen(setting: settingRepo), + ), + ), + GoRoute( + name: 'quota-usage-daily-details', + path: '/quota-usage-daily-details', + pageBuilder: (context, state) => transitionResolver( + QuotaUsageDetailScreen( setting: settingRepo, - groupId: int.tryParse(state.pathParameters['group_id']!)!, + date: state.queryParameters['date'] ?? + DateFormat('yyyy-MM-dd').format(DateTime.now()), ), ), - ); - }, - ), - GoRoute( - name: 'user-api-keys', - path: '/setting/user-api-keys', - pageBuilder: (context, state) { - return transitionResolver( - MultiBlocProvider( - providers: [ - BlocProvider( - create: ((context) => UserApiKeysBloc()), + ), + GoRoute( + name: 'prompt-editor', + path: '/prompt-editor', + pageBuilder: (context, state) { + var prompt = state.queryParameters['prompt'] ?? ''; + return transitionResolver(PromptScreen(prompt: prompt)); + }, + ), + GoRoute( + name: 'payment', + path: '/payment', + pageBuilder: (context, state) { + return transitionResolver( + MultiBlocProvider( + providers: [ + BlocProvider(create: ((context) => PaymentBloc())), + ], + child: PaymentScreen(setting: settingRepo), ), - ], - child: UserAPIKeysScreen(setting: settingRepo), + ); + }, + ), + GoRoute( + name: 'bind-phone', + path: '/bind-phone', + pageBuilder: (context, state) { + return transitionResolver( + MultiBlocProvider( + providers: [ + BlocProvider.value(value: accountBloc), + ], + child: BindPhoneScreen( + setting: settingRepo, + isSignIn: state.queryParameters['is_signin'] != 'false', + ), + ), + ); + }, + ), + GoRoute( + name: 'diagnosis', + path: '/diagnosis', + pageBuilder: (context, state) => transitionResolver( + DiagnosisScreen(setting: settingRepo), ), - ); - }, - ), - GoRoute( - name: 'notifications', - path: '/notifications', - pageBuilder: (context, state) { - return transitionResolver( - NotificationScreen(setting: settingRepo), - ); - }, - ), - GoRoute( - name: 'articles', - path: '/article', - pageBuilder: (context, state) { - return transitionResolver( - ArticleScreen( - settings: settingRepo, - id: int.tryParse(state.queryParameters['id'] ?? '') ?? 0, + ), + GoRoute( + name: 'free-statistics', + path: '/free-statistics', + pageBuilder: (context, state) => transitionResolver( + MultiBlocProvider( + providers: [BlocProvider.value(value: freeCountBloc)], + child: FreeStatisticsPage(setting: settingRepo), + ), ), - ); - }, - ), - GoRoute( - name: 'web-payment-result', - path: '/payment/result', - pageBuilder: (context, state) { - return transitionResolver(WebPaymentResult( - paymentId: state.queryParameters['payment_id']!, - action: state.queryParameters['action'], - )); - }, - ), - GoRoute( - name: 'web-payment-proxy', - path: '/payment/proxy', - pageBuilder: (context, state) { - return transitionResolver(WebPaymentProxy( - setting: settingRepo, - paymentId: state.queryParameters['id']!, - paymentIntent: state.queryParameters['intent']!, - price: state.queryParameters['price']!, - publishableKey: state.queryParameters['key']!, - finishAction: state.queryParameters['finish_action'] ?? 'close', - )); - }, - ), + ), + GoRoute( + name: 'custom-home-models', + path: '/setting/custom-home-models', + pageBuilder: (context, state) => transitionResolver( + CustomHomeModelsPage(setting: settingRepo), + ), + ), + GoRoute( + name: 'group-chat-chat', + path: '/group-chat/:group_id/chat', + pageBuilder: (context, state) { + final groupId = int.tryParse(state.pathParameters['group_id']!); - /// 管理员接口 - GoRoute( - name: 'admin-dashboard', - path: '/admin/dashboard', - pageBuilder: (context, state) { - return transitionResolver( - AdminDashboardPage(setting: settingRepo), - ); - }, - ), - GoRoute( - name: 'admin-models', - path: '/admin/models', - pageBuilder: (context, state) { - return transitionResolver( - MultiBlocProvider( - providers: [ - BlocProvider( - create: (context) => ModelBloc(), + return transitionResolver( + MultiBlocProvider( + providers: [ + BlocProvider( + create: ((context) => + GroupChatBloc(stateManager: messageStateManager)), + ), + BlocProvider.value(value: chatRoomBloc), + ], + child: GroupChatPage( + setting: settingRepo, + stateManager: messageStateManager, + groupId: groupId!, + ), ), - ], - child: AdminModelsPage(setting: settingRepo), - ), - ); - }, - ), - GoRoute( - name: 'admin-models-create', - path: '/admin/models/create', - pageBuilder: (context, state) { - return transitionResolver( - MultiBlocProvider( - providers: [ - BlocProvider( - create: (context) => ModelBloc(), + ); + }, + ), + GoRoute( + name: 'group-chat-create', + path: '/group-chat-create', + pageBuilder: (context, state) { + return transitionResolver( + MultiBlocProvider( + providers: [ + BlocProvider( + create: ((context) => + GroupChatBloc(stateManager: messageStateManager)), + ), + BlocProvider.value(value: chatRoomBloc), + ], + child: GroupCreatePage(setting: settingRepo), ), - ], - child: AdminModelCreatePage(setting: settingRepo), - ), - ); - }, - ), - GoRoute( - name: 'admin-models-edit', - path: '/admin/models/edit/:id', - pageBuilder: (context, state) { - return transitionResolver( - MultiBlocProvider( - providers: [ - BlocProvider( - create: (context) => ModelBloc(), + ); + }, + ), + GoRoute( + name: 'group-chat-edit', + path: '/group-chat/:group_id/edit', + pageBuilder: (context, state) { + return transitionResolver( + MultiBlocProvider( + providers: [ + BlocProvider( + create: ((context) => + GroupChatBloc(stateManager: messageStateManager)), + ), + BlocProvider.value(value: chatRoomBloc), + ], + child: GroupEditPage( + setting: settingRepo, + groupId: int.tryParse(state.pathParameters['group_id']!)!, + ), + ), + ); + }, + ), + GoRoute( + name: 'user-api-keys', + path: '/setting/user-api-keys', + pageBuilder: (context, state) { + return transitionResolver( + MultiBlocProvider( + providers: [ + BlocProvider( + create: ((context) => UserApiKeysBloc()), + ), + ], + child: UserAPIKeysScreen(setting: settingRepo), + ), + ); + }, + ), + GoRoute( + name: 'notifications', + path: '/notifications', + pageBuilder: (context, state) { + return transitionResolver( + NotificationScreen(setting: settingRepo), + ); + }, + ), + GoRoute( + name: 'articles', + path: '/article', + pageBuilder: (context, state) { + return transitionResolver( + ArticleScreen( + settings: settingRepo, + id: int.tryParse(state.queryParameters['id'] ?? '') ?? 0, ), - ], - child: AdminModelEditPage( + ); + }, + ), + GoRoute( + name: 'web-payment-result', + path: '/payment/result', + pageBuilder: (context, state) { + return transitionResolver(WebPaymentResult( + paymentId: state.queryParameters['payment_id']!, + action: state.queryParameters['action'], + )); + }, + ), + GoRoute( + name: 'web-payment-proxy', + path: '/payment/proxy', + pageBuilder: (context, state) { + return transitionResolver(WebPaymentProxy( setting: settingRepo, - modelId: state.pathParameters['id']!, - ), - ), - ); - }, - ), - GoRoute( - name: 'admin-channels', - path: '/admin/channels', - pageBuilder: (context, state) { - return transitionResolver( - MultiBlocProvider( - providers: [ - BlocProvider( - create: (context) => ChannelBloc(), + paymentId: state.queryParameters['id']!, + paymentIntent: state.queryParameters['intent']!, + price: state.queryParameters['price']!, + publishableKey: state.queryParameters['key']!, + finishAction: + state.queryParameters['finish_action'] ?? 'close', + )); + }, + ), + + /// 管理员接口 + GoRoute( + name: 'admin-dashboard', + path: '/admin/dashboard', + pageBuilder: (context, state) { + return transitionResolver( + AdminDashboardPage(setting: settingRepo), + ); + }, + ), + GoRoute( + name: 'admin-models', + path: '/admin/models', + pageBuilder: (context, state) { + return transitionResolver( + MultiBlocProvider( + providers: [ + BlocProvider( + create: (context) => ModelBloc(), + ), + ], + child: AdminModelsPage(setting: settingRepo), ), - ], - child: ChannelsPage(setting: settingRepo), - ), - ); - }, - ), - GoRoute( - name: 'admin-channels-create', - path: '/admin/channels/create', - pageBuilder: (context, state) { - return transitionResolver( - MultiBlocProvider( - providers: [ - BlocProvider( - create: (context) => ChannelBloc(), + ); + }, + ), + GoRoute( + name: 'admin-models-create', + path: '/admin/models/create', + pageBuilder: (context, state) { + return transitionResolver( + MultiBlocProvider( + providers: [ + BlocProvider( + create: (context) => ModelBloc(), + ), + ], + child: AdminModelCreatePage(setting: settingRepo), ), - ], - child: ChannelAddPage(setting: settingRepo), - ), - ); - }, - ), - GoRoute( - name: 'admin-channels-edit', - path: '/admin/channels/edit/:id', - pageBuilder: (context, state) { - final channelId = int.parse(state.pathParameters['id']!); + ); + }, + ), + GoRoute( + name: 'admin-models-edit', + path: '/admin/models/edit/:id', + pageBuilder: (context, state) { + return transitionResolver( + MultiBlocProvider( + providers: [ + BlocProvider( + create: (context) => ModelBloc(), + ), + ], + child: AdminModelEditPage( + setting: settingRepo, + modelId: state.pathParameters['id']!, + ), + ), + ); + }, + ), + GoRoute( + name: 'admin-channels', + path: '/admin/channels', + pageBuilder: (context, state) { + return transitionResolver( + MultiBlocProvider( + providers: [ + BlocProvider( + create: (context) => ChannelBloc(), + ), + ], + child: ChannelsPage(setting: settingRepo), + ), + ); + }, + ), + GoRoute( + name: 'admin-channels-create', + path: '/admin/channels/create', + pageBuilder: (context, state) { + return transitionResolver( + MultiBlocProvider( + providers: [ + BlocProvider( + create: (context) => ChannelBloc(), + ), + ], + child: ChannelAddPage(setting: settingRepo), + ), + ); + }, + ), + GoRoute( + name: 'admin-channels-edit', + path: '/admin/channels/edit/:id', + pageBuilder: (context, state) { + final channelId = int.parse(state.pathParameters['id']!); - return transitionResolver( - MultiBlocProvider( - providers: [ - BlocProvider( - create: (context) => ChannelBloc(), + return transitionResolver( + MultiBlocProvider( + providers: [ + BlocProvider( + create: (context) => ChannelBloc(), + ), + ], + child: ChannelEditPage( + setting: settingRepo, + channelId: channelId, + ), ), - ], - child: ChannelEditPage( - setting: settingRepo, - channelId: channelId, - ), - ), - ); - }, - ), - GoRoute( - name: 'admin-users', - path: '/admin/users', - pageBuilder: (context, state) { - return transitionResolver( - MultiBlocProvider( - providers: [ - BlocProvider( - create: (context) => UserBloc(), + ); + }, + ), + GoRoute( + name: 'admin-users', + path: '/admin/users', + pageBuilder: (context, state) { + return transitionResolver( + MultiBlocProvider( + providers: [ + BlocProvider( + create: (context) => UserBloc(), + ), + ], + child: AdminUsersPage(setting: settingRepo), ), - ], - child: AdminUsersPage(setting: settingRepo), - ), - ); - }, - ), - GoRoute( - name: 'admin-users-detail', - path: '/admin/users/:id', - pageBuilder: (context, state) { - final userId = int.parse(state.pathParameters['id']!); + ); + }, + ), + GoRoute( + name: 'admin-users-detail', + path: '/admin/users/:id', + pageBuilder: (context, state) { + final userId = int.parse(state.pathParameters['id']!); - return transitionResolver( - MultiBlocProvider( - providers: [ - BlocProvider( - create: (context) => UserBloc(), + return transitionResolver( + MultiBlocProvider( + providers: [ + BlocProvider( + create: (context) => UserBloc(), + ), + ], + child: AdminUserPage(setting: settingRepo, userId: userId), ), - ], - child: AdminUserPage(setting: settingRepo, userId: userId), - ), - ); - }, - ), + ); + }, + ), - GoRoute( - name: 'admin-payment-histories', - path: '/admin/payment/histories', - pageBuilder: (context, state) { - return transitionResolver( - MultiBlocProvider( - providers: [ - BlocProvider( - create: (context) => AdminPaymentBloc(), + GoRoute( + name: 'admin-payment-histories', + path: '/admin/payment/histories', + pageBuilder: (context, state) { + return transitionResolver( + MultiBlocProvider( + providers: [ + BlocProvider( + create: (context) => AdminPaymentBloc(), + ), + ], + child: PaymentHistoriesPage(setting: settingRepo), ), - ], - child: PaymentHistoriesPage(setting: settingRepo), - ), - ); - }, - ), + ); + }, + ), - GoRoute( - name: 'admin-user-rooms', - path: '/admin/users/:id/rooms', - pageBuilder: (context, state) { - final userId = int.parse(state.pathParameters['id']!); + GoRoute( + name: 'admin-user-rooms', + path: '/admin/users/:id/rooms', + pageBuilder: (context, state) { + final userId = int.parse(state.pathParameters['id']!); - return transitionResolver( - MultiBlocProvider( - providers: [ - BlocProvider( - create: (context) => AdminRoomBloc(), + return transitionResolver( + MultiBlocProvider( + providers: [ + BlocProvider( + create: (context) => AdminRoomBloc(), + ), + ], + child: AdminRoomsPage(setting: settingRepo, userId: userId), ), - ], - child: AdminRoomsPage(setting: settingRepo, userId: userId), - ), - ); - }, - ), + ); + }, + ), - GoRoute( - name: 'admin-user-rooms-messages', - path: '/admin/users/:id/rooms/:room_id/messages', - pageBuilder: (context, state) { - final userId = int.parse(state.pathParameters['id']!); - final roomId = int.parse(state.pathParameters['room_id']!); + GoRoute( + name: 'admin-user-rooms-messages', + path: '/admin/users/:id/rooms/:room_id/messages', + pageBuilder: (context, state) { + final userId = int.parse(state.pathParameters['id']!); + final roomId = int.parse(state.pathParameters['room_id']!); - return transitionResolver( - MultiBlocProvider( - providers: [ - BlocProvider( - create: (context) => AdminRoomBloc(), + return transitionResolver( + MultiBlocProvider( + providers: [ + BlocProvider( + create: (context) => AdminRoomBloc(), + ), + ], + child: AdminRoomMessagesPage( + setting: settingRepo, + userId: userId, + roomId: roomId, + roomType: int.parse(state.queryParameters['room_type']!), + ), ), - ], - child: AdminRoomMessagesPage( - setting: settingRepo, - userId: userId, - roomId: roomId, - roomType: int.parse(state.queryParameters['room_type']!), - ), - ), - ); - }, - ), + ); + }, + ), - GoRoute( - name: 'admin-recently-messages', - path: '/admin/recently-messages', - pageBuilder: (context, state) { - return transitionResolver( - MultiBlocProvider( - providers: [ - BlocProvider( - create: (context) => AdminRoomBloc(), + GoRoute( + name: 'admin-recently-messages', + path: '/admin/recently-messages', + pageBuilder: (context, state) { + return transitionResolver( + MultiBlocProvider( + providers: [ + BlocProvider( + create: (context) => AdminRoomBloc(), + ), + ], + child: AdminRecentlyMessagesPage(setting: settingRepo), ), - ], - child: AdminRecentlyMessagesPage(setting: settingRepo), - ), - ); - }, + ); + }, + ), + ], ), ], ); diff --git a/lib/page/chat/component/model_switcher.dart b/lib/page/chat/component/model_switcher.dart index d9baf1c0..24d6f2a7 100644 --- a/lib/page/chat/component/model_switcher.dart +++ b/lib/page/chat/component/model_switcher.dart @@ -18,21 +18,33 @@ class ModelSwitcher extends StatelessWidget { this.value, }); + static void openActionDialog({ + required BuildContext context, + required Function(mm.Model? selected) onSelected, + mm.Model? initValue, + }) { + HapticFeedbackHelper.mediumImpact(); + openSelectModelDialog( + context, + (selected) { + onSelected(selected); + }, + initValue: initValue?.uid(), + enableClear: true, + title: AppLocale.switchModelTitle.getString(context), + ); + } + @override Widget build(BuildContext context) { final customColors = Theme.of(context).extension()!; return IconButton( onPressed: () async { - HapticFeedbackHelper.mediumImpact(); - openSelectModelDialog( - context, - (selected) { - onSelected(selected); - }, - initValue: value?.uid(), - enableClear: true, - title: AppLocale.switchModelTitle.getString(context), + openActionDialog( + context: context, + onSelected: onSelected, + initValue: value, ); }, icon: value == null diff --git a/lib/page/chat/home_chat.dart b/lib/page/chat/home_chat.dart index 47d555c6..c74ef47a 100644 --- a/lib/page/chat/home_chat.dart +++ b/lib/page/chat/home_chat.dart @@ -3,6 +3,7 @@ import 'package:askaide/bloc/free_count_bloc.dart'; import 'package:askaide/bloc/notify_bloc.dart'; import 'package:askaide/bloc/room_bloc.dart'; import 'package:askaide/helper/ability.dart'; +import 'package:askaide/helper/cache.dart'; import 'package:askaide/helper/constant.dart'; import 'package:askaide/helper/global_store.dart'; import 'package:askaide/helper/haptic_feedback.dart'; @@ -20,19 +21,18 @@ import 'package:askaide/page/component/chat/empty.dart'; import 'package:askaide/page/component/chat/file_upload.dart'; import 'package:askaide/page/component/chat/help_tips.dart'; import 'package:askaide/page/component/chat/message_state_manager.dart'; +import 'package:askaide/page/component/chat/role_avatar.dart'; import 'package:askaide/page/component/enhanced_error.dart'; import 'package:askaide/page/component/global_alert.dart'; import 'package:askaide/page/component/loading.dart'; -import 'package:askaide/page/component/random_avatar.dart'; import 'package:askaide/page/component/dialog.dart'; +import 'package:askaide/page/component/select_mode_toolbar.dart'; import 'package:askaide/page/component/theme/custom_size.dart'; import 'package:askaide/page/component/theme/custom_theme.dart'; import 'package:askaide/repo/api/model.dart'; import 'package:askaide/repo/api_server.dart'; -import 'package:askaide/repo/model/chat_history.dart'; import 'package:askaide/repo/model/message.dart'; import 'package:askaide/repo/model/misc.dart'; -import 'package:askaide/repo/model/room.dart'; import 'package:askaide/repo/settings_repo.dart'; import 'package:bot_toast/bot_toast.dart'; import 'package:flutter/material.dart'; @@ -104,7 +104,7 @@ class _HomeChatPageState extends State { HomeModelV2? currentModelV2; /// 当前选择的模型 - mm.Model? tempModel; + mm.Model? selectedModel; @override void initState() { @@ -146,6 +146,23 @@ class _HomeChatPageState extends State { setState(() { supportModels = value; }); + + if (widget.model != null) { + selectedModel = + supportModels.where((e) => e.id == widget.model).firstOrNull; + } + + if (selectedModel == null) { + Cache().stringGet(key: 'last_selected_model').then((value) { + final selected = + supportModels.where((e) => e.id == value).firstOrNull; + if (selected != null) { + setState(() { + selectedModel = selected; + }); + } + }); + } }); if (widget.model != null) { @@ -206,21 +223,19 @@ class _HomeChatPageState extends State { if (state.room.model.startsWith('v2@')) { if (currentModelV2 != null && currentModelV2!.modelId != null) { // 加载免费使用次数 - if (tempModel == null) { - // ignore: use_build_context_synchronously - context.read().add(FreeCountReloadEvent( - model: currentModelV2!.modelId!, - )); - } - } - } else { - // 加载免费使用次数 - if (tempModel == null) { // ignore: use_build_context_synchronously context.read().add(FreeCountReloadEvent( - model: widget.model ?? state.room.model, + model: currentModelV2!.modelId!, )); } + } else { + // 加载免费使用次数 + // ignore: use_build_context_synchronously + context.read().add(FreeCountReloadEvent( + model: selectedModel?.name ?? + widget.model ?? + state.room.model, + )); } } }, @@ -274,34 +289,51 @@ class _HomeChatPageState extends State { buildWhen: (previous, current) => current is ChatMessagesLoaded, builder: (context, state) { if (state is ChatMessagesLoaded) { - return Column( - children: [ - Container( - width: MediaQuery.of(context).size.width / 2, - alignment: Alignment.center, - child: Text( - widget.title ?? AppLocale.chatAnywhere.getString(context), - overflow: TextOverflow.ellipsis, - maxLines: 1, - style: - const TextStyle(fontSize: CustomSize.appBarTitleSize), - ), - ), - if (state.chatHistory?.model != null || currentModelV2 != null) - Text( - currentModelV2 != null - ? currentModelV2!.name - : (supportModels - .where((e) => e.id == state.chatHistory!.model!) - .firstOrNull - ?.shortName ?? - ''), - style: TextStyle( - color: customColors.weakTextColor, - fontSize: 10, + return GestureDetector( + onTap: () { + ModelSwitcher.openActionDialog( + context: context, + onSelected: (selected) { + setState(() { + selectedModel = selected; + }); + }, + initValue: selectedModel, + ); + }, + child: Column( + children: [ + Container( + width: MediaQuery.of(context).size.width / 2, + alignment: Alignment.center, + child: Text( + widget.title ?? AppLocale.chatAnywhere.getString(context), + overflow: TextOverflow.ellipsis, + maxLines: 1, + style: + const TextStyle(fontSize: CustomSize.appBarTitleSize), ), - ) - ], + ), + if (selectedModel != null) + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text( + selectedModel!.name, + style: TextStyle( + color: customColors.weakTextColor, + fontSize: 10, + ), + ), + Icon( + Icons.unfold_more, + color: customColors.backgroundInvertedColor, + size: CustomSize.appBarTitleSize * 0.6, + ), + ], + ) + ], + ), ); } @@ -379,11 +411,11 @@ class _HomeChatPageState extends State { enableInput.value = false; }); } else if (!state.processing && !enableInput.value) { - if (tempModel == null) { - // 更新免费使用次数 - context.read().add(FreeCountReloadEvent( - model: widget.model ?? room.room.model)); - } + // 更新免费使用次数 + context.read().add(FreeCountReloadEvent( + model: selectedModel?.name ?? + widget.model ?? + room.room.model)); // 聊天回复完成时,取消输入框的禁止编辑状态 setState(() { @@ -409,7 +441,7 @@ class _HomeChatPageState extends State { if (!enableInput.value) Positioned( bottom: 10, - width: maxWindowWidth(context), + width: CustomSize.adaptiveMaxWindowWidth(context), child: Center( child: StopButton( label: '停止输出', @@ -439,9 +471,9 @@ class _HomeChatPageState extends State { child: BlocBuilder( builder: (context, freeState) { var hintText = '有问题尽管问我'; - if (freeState is FreeCountLoadedState && tempModel == null) { - final matched = - freeState.model(widget.model ?? room.room.model); + if (freeState is FreeCountLoadedState) { + final matched = freeState.model( + selectedModel?.name ?? widget.model ?? room.room.model); if (matched != null && matched.leftCount > 0 && matched.maxCount > 0) { @@ -473,9 +505,9 @@ class _HomeChatPageState extends State { handleSubmit(value); FocusManager.instance.primaryFocus?.unfocus(); }, - enableImageUpload: tempModel == null + enableImageUpload: selectedModel == null ? enableImageUpload - : (tempModel?.supportVision ?? false), + : (selectedModel?.supportVision ?? false), onImageSelected: (files) { setState(() { selectedImageFiles = files; @@ -492,18 +524,6 @@ class _HomeChatPageState extends State { .read() .add(ChatMessageStopEvent()); }, - leftSideToolsBuilder: () { - return [ - ModelSwitcher( - onSelected: (selected) { - setState(() { - tempModel = selected; - }); - }, - value: tempModel, - ), - ]; - }, ); }, ); @@ -513,11 +533,7 @@ class _HomeChatPageState extends State { // 选择模式工具栏 if (chatPreviewController.selectMode) - buildSelectModeToolbars( - context, - chatPreviewController, - customColors, - ), + SelectModeToolbar(chatPreviewController: chatPreviewController), ], ); } @@ -594,7 +610,11 @@ class _HomeChatPageState extends State { stateManager: widget.stateManager, robotAvatar: selectMode ? null - : buildAvatar(room.room, his: loadedState.chatHistory), + : RoleAvatar( + avatarUrl: room.room.avatarUrl, + his: loadedState.chatHistory, + alternativeAvatarUrl: currentModelV2?.avatarUrl, + ), onDeleteMessage: (id) { handleDeleteMessage(context, id, chatHistoryId: chatId); }, @@ -679,7 +699,7 @@ class _HomeChatPageState extends State { text, user: 'me', ts: DateTime.now(), - model: widget.model, + model: selectedModel?.id ?? widget.model, type: messagetType, chatHistoryId: chatId, images: selectedImageFiles @@ -689,7 +709,6 @@ class _HomeChatPageState extends State { ), index: index, isResent: isResent, - tempModel: tempModel?.id, ), ); @@ -700,30 +719,4 @@ class _HomeChatPageState extends State { .read() .add(RoomLoadEvent(chatAnywhereRoomId, cascading: false)); } - - Widget buildAvatar(Room room, {ChatHistory? his}) { - if (room.avatarUrl != null && room.avatarUrl!.startsWith('http')) { - return RemoteAvatar(avatarUrl: room.avatarUrl!, size: 30); - } - - if (currentModelV2 != null && currentModelV2!.avatarUrl != null) { - return RemoteAvatar(avatarUrl: currentModelV2!.avatarUrl!, size: 30); - } - - if (his != null && his.model != null) { - var mod = supportModels.where((e) => e.id == his.model!).firstOrNull; - if (mod != null && mod.avatarUrl != null && mod.avatarUrl != '') { - return RemoteAvatar(avatarUrl: mod.avatarUrl!, size: 30); - } - } - - return const LocalAvatar(assetName: 'assets/app.png', size: 30); - } - - double maxWindowWidth(BuildContext context) { - final windowSize = MediaQuery.of(context).size.width; - return windowSize > CustomSize.maxWindowSize - ? CustomSize.maxWindowSize - : windowSize; - } } diff --git a/lib/page/chat/room_chat.dart b/lib/page/chat/room_chat.dart index 3abcc9ee..ff0fbb31 100644 --- a/lib/page/chat/room_chat.dart +++ b/lib/page/chat/room_chat.dart @@ -2,9 +2,7 @@ import 'dart:convert'; import 'package:askaide/bloc/free_count_bloc.dart'; import 'package:askaide/helper/ability.dart'; -import 'package:askaide/helper/constant.dart'; import 'package:askaide/helper/haptic_feedback.dart'; -import 'package:askaide/helper/image.dart'; import 'package:askaide/helper/model.dart'; import 'package:askaide/helper/upload.dart'; import 'package:askaide/lang/lang.dart'; @@ -12,17 +10,17 @@ import 'package:askaide/page/chat/component/model_switcher.dart'; import 'package:askaide/page/chat/component/stop_button.dart'; import 'package:askaide/page/component/audio_player.dart'; import 'package:askaide/page/component/background_container.dart'; -import 'package:askaide/page/component/chat/chat_share.dart'; import 'package:askaide/page/component/chat/empty.dart'; import 'package:askaide/page/component/chat/file_upload.dart'; import 'package:askaide/page/component/chat/help_tips.dart'; import 'package:askaide/page/component/chat/message_state_manager.dart'; +import 'package:askaide/page/component/chat/role_avatar.dart'; import 'package:askaide/page/component/effect/glass.dart'; import 'package:askaide/page/component/enhanced_popup_menu.dart'; import 'package:askaide/page/component/enhanced_textfield.dart'; import 'package:askaide/page/component/global_alert.dart'; import 'package:askaide/page/component/loading.dart'; -import 'package:askaide/page/component/random_avatar.dart'; +import 'package:askaide/page/component/select_mode_toolbar.dart'; import 'package:askaide/page/component/theme/custom_size.dart'; import 'package:askaide/page/component/theme/custom_theme.dart'; import 'package:askaide/bloc/chat_message_bloc.dart'; @@ -37,7 +35,6 @@ import 'package:askaide/repo/settings_repo.dart'; import 'package:bot_toast/bot_toast.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; -import 'package:flutter_initicon/flutter_initicon.dart'; import 'package:flutter_localization/flutter_localization.dart'; import 'package:go_router/go_router.dart'; import 'package:askaide/repo/model/model.dart' as mm; @@ -189,7 +186,7 @@ class _RoomChatPageState extends State { if (!_inputEnabled.value) Positioned( bottom: 10, - width: maxWindowWidth(context), + width: CustomSize.adaptiveMaxWindowWidth(context), child: Center( child: StopButton( label: '停止输出', @@ -233,10 +230,8 @@ class _RoomChatPageState extends State { : (tempModel?.supportVision ?? false); return _chatPreviewController.selectMode - ? buildSelectModeToolbars( - context, - _chatPreviewController, - customColors, + ? SelectModeToolbar( + chatPreviewController: _chatPreviewController, ) : ChatInput( enableNotifier: _inputEnabled, @@ -399,7 +394,12 @@ class _RoomChatPageState extends State { scrollController: _scrollController, controller: _chatPreviewController, stateManager: widget.stateManager, - robotAvatar: selectMode ? null : _buildAvatar(room.room), + robotAvatar: selectMode + ? null + : RoleAvatar( + avatarUrl: room.room.avatarUrl, + name: room.room.name, + ), onDeleteMessage: (id) { handleDeleteMessage(context, id); }, @@ -510,22 +510,6 @@ class _RoomChatPageState extends State { ); } - Widget _buildAvatar(Room room) { - if (room.avatarUrl != null && room.avatarUrl!.startsWith('http')) { - return RemoteAvatar( - avatarUrl: imageURL(room.avatarUrl!, qiniuImageTypeAvatar), - size: 30, - ); - } - - return Initicon( - text: room.name.split('、').join(' '), - size: 30, - backgroundColor: Colors.grey.withAlpha(100), - borderRadius: BorderRadius.circular(8), - ); - } - /// 提交新消息 void _handleSubmit( String text, { @@ -653,13 +637,6 @@ class _RoomChatPageState extends State { .read() .add(RoomLoadEvent(widget.roomId, cascading: false)); } - - double maxWindowWidth(BuildContext context) { - final windowSize = MediaQuery.of(context).size.width; - return windowSize > CustomSize.maxWindowSize - ? CustomSize.maxWindowSize - : windowSize; - } } /// 处理消息删除事件 @@ -808,127 +785,6 @@ void handleOpenExampleQuestion( ); } -/// 构建聊天内容窗口 -Widget buildSelectModeToolbars( - BuildContext context, - ChatPreviewController chatPreviewController, - CustomColors customColors, -) { - return Container( - padding: const EdgeInsets.all(10), - decoration: BoxDecoration( - borderRadius: const BorderRadius.only( - topLeft: Radius.circular(10), - topRight: Radius.circular(10), - ), - color: customColors.backgroundColor, - ), - child: SafeArea( - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceAround, - children: [ - TextButton.icon( - onPressed: () { - var messages = chatPreviewController.selectedMessages(); - if (messages.isEmpty) { - showErrorMessageEnhanced( - context, AppLocale.noMessageSelected.getString(context)); - return; - } - - Navigator.push( - context, - MaterialPageRoute( - fullscreenDialog: true, - builder: (context) => ChatShareScreen( - messages: messages - .map((e) => ChatShareMessage( - content: e.message.text, - username: e.message.senderName, - avatarURL: e.message.avatarUrl, - leftSide: e.message.role == Role.receiver, - images: e.message.images, - )) - .toList(), - ), - ), - ); - // var messages = chatPreviewController.selectedMessages(); - // if (messages.isEmpty) { - // showErrorMessageEnhanced( - // context, AppLocale.noMessageSelected.getString(context)); - // return; - // } - // var shareText = messages.map((e) { - // if (e.message.role == Role.sender) { - // return '我:\n${e.message.text}'; - // } - - // return '助理:\n${e.message.text}'; - // }).join('\n\n'); - - // shareTo( - // context, - // content: shareText, - // title: AppLocale.chatHistory.getString(context), - // ); - }, - icon: Icon(Icons.share, color: customColors.linkColor), - label: Text( - AppLocale.share.getString(context), - style: TextStyle(color: customColors.linkColor), - ), - ), - TextButton.icon( - onPressed: () { - chatPreviewController.selectAllMessage(); - }, - icon: - Icon(Icons.select_all_outlined, color: customColors.linkColor), - label: Text( - AppLocale.selectAll.getString(context), - style: TextStyle(color: customColors.linkColor), - ), - ), - TextButton.icon( - onPressed: () { - if (chatPreviewController.selectedMessageIds.isEmpty) { - showErrorMessageEnhanced( - context, AppLocale.noMessageSelected.getString(context)); - return; - } - - openConfirmDialog( - context, - AppLocale.confirmDelete.getString(context), - () { - final ids = chatPreviewController.selectedMessageIds.toList(); - if (ids.isNotEmpty) { - context - .read() - .add(ChatMessageDeleteEvent(ids)); - - showErrorMessageEnhanced( - context, AppLocale.operateSuccess.getString(context)); - - chatPreviewController.exitSelectMode(); - } - }, - danger: true, - ); - }, - icon: Icon(Icons.delete, color: customColors.linkColor), - label: Text( - AppLocale.delete.getString(context), - style: TextStyle(color: customColors.linkColor), - ), - ), - ], - ), - ), - ); -} - /// 构建聊天设置下拉菜单 Widget buildChatMoreMenu( BuildContext context, diff --git a/lib/page/component/account_quota_card.dart b/lib/page/component/account_quota_card.dart index 54aea4b4..273ff57e 100644 --- a/lib/page/component/account_quota_card.dart +++ b/lib/page/component/account_quota_card.dart @@ -13,17 +13,21 @@ import 'package:url_launcher/url_launcher.dart'; class AccountQuotaCard extends StatelessWidget { final UserInfo? userInfo; final VoidCallback? onPaymentReturn; - const AccountQuotaCard({super.key, this.userInfo, this.onPaymentReturn}); + final bool noBorder; + const AccountQuotaCard( + {super.key, this.userInfo, this.onPaymentReturn, this.noBorder = false}); @override Widget build(BuildContext context) { final customColors = Theme.of(context).extension()!; return Container( - margin: const EdgeInsets.symmetric(horizontal: 15, vertical: 10), + margin: noBorder + ? null + : const EdgeInsets.symmetric(horizontal: 15, vertical: 10), decoration: BoxDecoration( color: Colors.white, - borderRadius: BorderRadius.circular(15), + borderRadius: BorderRadius.circular(noBorder ? 0 : 15), boxShadow: [ BoxShadow( color: Colors.white.withOpacity(0.1), @@ -59,10 +63,16 @@ class AccountQuotaCard extends StatelessWidget { ), height: 140, child: Container( - padding: const EdgeInsets.symmetric( - horizontal: 20, - vertical: 30, - ), + padding: noBorder + ? const EdgeInsets.only( + top: 35, + left: 20, + right: 20, + ) + : const EdgeInsets.symmetric( + horizontal: 20, + vertical: 30, + ), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ diff --git a/lib/page/component/chat/chat_input.dart b/lib/page/component/chat/chat_input.dart index f844e073..6e2088e6 100644 --- a/lib/page/component/chat/chat_input.dart +++ b/lib/page/component/chat/chat_input.dart @@ -14,7 +14,6 @@ import 'package:askaide/repo/settings_repo.dart'; import 'package:file_picker/file_picker.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; -import 'package:flutter/widgets.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_localization/flutter_localization.dart'; import 'package:flutter_slidable/flutter_slidable.dart'; diff --git a/lib/page/component/chat/role_avatar.dart b/lib/page/component/chat/role_avatar.dart new file mode 100644 index 00000000..2e3b3142 --- /dev/null +++ b/lib/page/component/chat/role_avatar.dart @@ -0,0 +1,73 @@ +import 'package:askaide/helper/constant.dart'; +import 'package:askaide/helper/image.dart'; +import 'package:askaide/helper/model.dart'; +import 'package:askaide/page/component/random_avatar.dart'; +import 'package:askaide/repo/model/chat_history.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_initicon/flutter_initicon.dart'; + +class RoleAvatar extends StatefulWidget { + final String? avatarUrl; + final String? alternativeAvatarUrl; + final String? name; + final ChatHistory? his; + + const RoleAvatar({ + super.key, + this.avatarUrl, + this.alternativeAvatarUrl, + this.his, + this.name, + }); + + @override + State createState() => _RoleAvatarState(); +} + +class _RoleAvatarState extends State { + @override + Widget build(BuildContext context) { + if (widget.avatarUrl != null && widget.avatarUrl!.startsWith('http')) { + return RemoteAvatar( + avatarUrl: imageURL(widget.avatarUrl!, qiniuImageTypeAvatar), + size: 30, + ); + } + + if (widget.alternativeAvatarUrl != null) { + return RemoteAvatar( + avatarUrl: imageURL(widget.alternativeAvatarUrl!, qiniuImageTypeAvatar), + size: 30, + ); + } + + if (widget.his != null && widget.his!.model != null) { + return FutureBuilder( + future: ModelAggregate.models(), + builder: (context, snapshot) { + if (!snapshot.hasError) { + var mod = snapshot.data! + .where((e) => e.id == widget.his!.model!) + .firstOrNull; + if (mod != null && mod.avatarUrl != null && mod.avatarUrl != '') { + return RemoteAvatar(avatarUrl: mod.avatarUrl!, size: 30); + } + } + + return const LocalAvatar(assetName: 'assets/app.png', size: 30); + }, + ); + } + + if (widget.name != null && widget.name!.isNotEmpty) { + return Initicon( + text: widget.name!.split('、').join(' '), + size: 30, + backgroundColor: Colors.grey.withAlpha(100), + borderRadius: BorderRadius.circular(8), + ); + } + + return const LocalAvatar(assetName: 'assets/app.png', size: 30); + } +} diff --git a/lib/page/component/select_mode_toolbar.dart b/lib/page/component/select_mode_toolbar.dart new file mode 100644 index 00000000..0e63d154 --- /dev/null +++ b/lib/page/component/select_mode_toolbar.dart @@ -0,0 +1,139 @@ +import 'package:askaide/bloc/chat_message_bloc.dart'; +import 'package:askaide/lang/lang.dart'; +import 'package:askaide/page/component/chat/chat_preview.dart'; +import 'package:askaide/page/component/chat/chat_share.dart'; +import 'package:askaide/page/component/dialog.dart'; +import 'package:askaide/page/component/theme/custom_theme.dart'; +import 'package:askaide/repo/model/message.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_localization/flutter_localization.dart'; +import 'package:provider/provider.dart'; + +class SelectModeToolbar extends StatefulWidget { + final ChatPreviewController chatPreviewController; + const SelectModeToolbar({super.key, required this.chatPreviewController}); + + @override + State createState() => _SelectModeToolbarState(); +} + +class _SelectModeToolbarState extends State { + @override + Widget build(BuildContext context) { + final customColors = Theme.of(context).extension()!; + return Container( + padding: const EdgeInsets.all(10), + decoration: BoxDecoration( + borderRadius: const BorderRadius.only( + topLeft: Radius.circular(10), + topRight: Radius.circular(10), + ), + color: customColors.backgroundColor, + ), + child: SafeArea( + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceAround, + children: [ + TextButton.icon( + onPressed: () { + var messages = widget.chatPreviewController.selectedMessages(); + if (messages.isEmpty) { + showErrorMessageEnhanced( + context, AppLocale.noMessageSelected.getString(context)); + return; + } + + Navigator.push( + context, + MaterialPageRoute( + fullscreenDialog: true, + builder: (context) => ChatShareScreen( + messages: messages + .map((e) => ChatShareMessage( + content: e.message.text, + username: e.message.senderName, + avatarURL: e.message.avatarUrl, + leftSide: e.message.role == Role.receiver, + images: e.message.images, + )) + .toList(), + ), + ), + ); + // var messages = chatPreviewController.selectedMessages(); + // if (messages.isEmpty) { + // showErrorMessageEnhanced( + // context, AppLocale.noMessageSelected.getString(context)); + // return; + // } + // var shareText = messages.map((e) { + // if (e.message.role == Role.sender) { + // return '我:\n${e.message.text}'; + // } + + // return '助理:\n${e.message.text}'; + // }).join('\n\n'); + + // shareTo( + // context, + // content: shareText, + // title: AppLocale.chatHistory.getString(context), + // ); + }, + icon: Icon(Icons.share, color: customColors.linkColor), + label: Text( + AppLocale.share.getString(context), + style: TextStyle(color: customColors.linkColor), + ), + ), + TextButton.icon( + onPressed: () { + widget.chatPreviewController.selectAllMessage(); + }, + icon: Icon(Icons.select_all_outlined, + color: customColors.linkColor), + label: Text( + AppLocale.selectAll.getString(context), + style: TextStyle(color: customColors.linkColor), + ), + ), + TextButton.icon( + onPressed: () { + if (widget.chatPreviewController.selectedMessageIds.isEmpty) { + showErrorMessageEnhanced( + context, AppLocale.noMessageSelected.getString(context)); + return; + } + + openConfirmDialog( + context, + AppLocale.confirmDelete.getString(context), + () { + final ids = widget.chatPreviewController.selectedMessageIds + .toList(); + if (ids.isNotEmpty) { + context + .read() + .add(ChatMessageDeleteEvent(ids)); + + showErrorMessageEnhanced( + context, AppLocale.operateSuccess.getString(context)); + + widget.chatPreviewController.exitSelectMode(); + } + }, + danger: true, + ); + }, + icon: Icon(Icons.delete, color: customColors.linkColor), + label: Text( + AppLocale.delete.getString(context), + style: TextStyle(color: customColors.linkColor), + ), + ), + ], + ), + ), + ); + } +} diff --git a/lib/page/component/sliver_component.dart b/lib/page/component/sliver_component.dart index 459d58ac..f6dca541 100644 --- a/lib/page/component/sliver_component.dart +++ b/lib/page/component/sliver_component.dart @@ -91,7 +91,7 @@ class SliverComponent extends StatelessWidget { headerSliverBuilder: (context, innerBoxIsScrolled) { return [ SliverAppBar( - automaticallyImplyLeading: false, + automaticallyImplyLeading: true, toolbarHeight: CustomSize.toolbarHeight, expandedHeight: expendedHeight, floating: false, diff --git a/lib/page/component/theme/custom_size.dart b/lib/page/component/theme/custom_size.dart index db384851..617c19a4 100644 --- a/lib/page/component/theme/custom_size.dart +++ b/lib/page/component/theme/custom_size.dart @@ -14,4 +14,11 @@ class CustomSize { return kToolbarHeight; } + + static double adaptiveMaxWindowWidth(BuildContext context) { + final windowSize = MediaQuery.of(context).size.width; + return windowSize > CustomSize.maxWindowSize + ? CustomSize.maxWindowSize + : windowSize; + } } diff --git a/lib/page/custom_scaffold.dart b/lib/page/custom_scaffold.dart new file mode 100644 index 00000000..fd44993e --- /dev/null +++ b/lib/page/custom_scaffold.dart @@ -0,0 +1,62 @@ +import 'package:askaide/page/component/background_container.dart'; +import 'package:askaide/page/component/theme/custom_size.dart'; +import 'package:askaide/page/component/theme/custom_theme.dart'; +import 'package:askaide/repo/settings_repo.dart'; +import 'package:flutter/material.dart'; + +class CustomScaffold extends StatefulWidget { + final SettingRepository settings; + final Widget title; + final List? actions; + final Widget body; + final Widget? drawer; + final Widget? appBarBackground; + const CustomScaffold({ + super.key, + required this.settings, + required this.title, + this.actions, + required this.body, + this.drawer, + this.appBarBackground, + }); + + @override + State createState() => _CustomScaffoldState(); +} + +class _CustomScaffoldState extends State { + @override + Widget build(BuildContext context) { + return BackgroundContainer( + setting: widget.settings, + maxWidth: double.infinity, + child: Scaffold( + appBar: AppBar( + title: widget.title, + centerTitle: true, + toolbarHeight: CustomSize.toolbarHeight, + elevation: 0, + actions: widget.actions, + flexibleSpace: SizedBox( + width: double.infinity, + child: ShaderMask( + shaderCallback: (rect) { + return const LinearGradient( + begin: Alignment.topCenter, + end: Alignment.bottomCenter, + colors: [Colors.black, Colors.transparent], + ).createShader(Rect.fromLTRB(0, 0, rect.width, rect.height)); + }, + blendMode: BlendMode.dstIn, + child: widget.appBarBackground, + ), + ), + ), + backgroundColor: Colors.transparent, + body: widget.body, + drawer: widget.drawer, + ), + ); + } +} diff --git a/lib/page/drawer.dart b/lib/page/drawer.dart new file mode 100644 index 00000000..ff48235e --- /dev/null +++ b/lib/page/drawer.dart @@ -0,0 +1,142 @@ +import 'package:askaide/bloc/account_bloc.dart'; +import 'package:askaide/bloc/chat_chat_bloc.dart'; +import 'package:askaide/lang/lang.dart'; +import 'package:askaide/page/component/account_quota_card.dart'; +import 'package:askaide/repo/api/user.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:flutter_localization/flutter_localization.dart'; +import 'package:go_router/go_router.dart'; +import 'package:url_launcher/url_launcher_string.dart'; + +class LeftDrawer extends StatefulWidget { + const LeftDrawer({super.key}); + + @override + State createState() => _LeftDrawerState(); +} + +class _LeftDrawerState extends State { + @override + Widget build(BuildContext context) { + return Drawer( + shape: const RoundedRectangleBorder( + borderRadius: BorderRadius.zero, + ), + child: Column( + children: [ + Expanded( + child: SingleChildScrollView( + child: Column( + children: [ + DrawerHeader( + padding: const EdgeInsets.all(0), + child: BlocBuilder( + builder: (_, state) { + UserInfo? userInfo; + if (state is AccountLoaded) { + userInfo = state.user; + } + + return AccountQuotaCard( + userInfo: userInfo, + noBorder: true, + onPaymentReturn: () { + if (userInfo != null) { + context + .read() + .add(AccountLoadEvent(cache: false)); + } + }, + ); + }, + ), + ), + const SizedBox(height: 15), + ListTile( + leading: const Icon(Icons.history), + title: Text(AppLocale.histories.getString(context)), + onTap: () { + context.push('/chat-chat/history').whenComplete(() { + context + .read() + .add(ChatChatLoadRecentHistories()); + }); + }, + ), + ListTile( + leading: const Icon(Icons.group_outlined), + title: Text(AppLocale.homeTitle.getString(context)), + onTap: () { + context.push('/characters'); + }, + ), + ListTile( + leading: const Icon(Icons.auto_awesome_outlined), + title: Text(AppLocale.discover.getString(context)), + onTap: () { + context.push('/creative-gallery'); + }, + ), + ListTile( + leading: const Icon(Icons.palette_outlined), + title: Text(AppLocale.creativeIsland.getString(context)), + onTap: () { + context.push('/creative-draw'); + }, + ), + ListTile( + leading: const Icon(Icons.settings_outlined), + title: const Text('设置'), + onTap: () { + context.push('/setting'); + }, + ), + ], + ), + ), + ), + SizedBox( + height: 70, + child: Column( + children: [ + GestureDetector( + onTap: () { + launchUrlString( + 'https://weibo.com/code404', + mode: LaunchMode.externalApplication, + ); + }, + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + const Text('关注我们:'), + const SizedBox(width: 10), + Image.asset('assets/weibo.png', width: 25), + ], + ), + ), + GestureDetector( + onTap: () { + launchUrlString( + 'https://ai.aicode.cc/social/github', + mode: LaunchMode.externalApplication, + ); + }, + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + const Text('本项目开源,欢迎贡献:'), + const SizedBox(width: 10), + Image.asset('assets/github.png', width: 25), + ], + ), + ), + ], + ), + ), + ], + ), + ); + } +} diff --git a/lib/page/home.dart b/lib/page/home.dart new file mode 100644 index 00000000..2b647b1f --- /dev/null +++ b/lib/page/home.dart @@ -0,0 +1,721 @@ +import 'package:askaide/bloc/account_bloc.dart'; +import 'package:askaide/bloc/chat_chat_bloc.dart'; +import 'package:askaide/bloc/chat_message_bloc.dart'; +import 'package:askaide/bloc/free_count_bloc.dart'; +import 'package:askaide/bloc/notify_bloc.dart'; +import 'package:askaide/bloc/room_bloc.dart'; +import 'package:askaide/helper/ability.dart'; +import 'package:askaide/helper/cache.dart'; +import 'package:askaide/helper/constant.dart'; +import 'package:askaide/helper/haptic_feedback.dart'; +import 'package:askaide/helper/model.dart'; +import 'package:askaide/helper/upload.dart'; +import 'package:askaide/lang/lang.dart'; +import 'package:askaide/page/chat/component/model_switcher.dart'; +import 'package:askaide/page/chat/component/stop_button.dart'; +import 'package:askaide/page/chat/room_chat.dart'; +import 'package:askaide/page/component/audio_player.dart'; +import 'package:askaide/page/component/chat/chat_input.dart'; +import 'package:askaide/page/component/chat/chat_preview.dart'; +import 'package:askaide/page/component/chat/empty.dart'; +import 'package:askaide/page/component/chat/file_upload.dart'; +import 'package:askaide/page/component/chat/help_tips.dart'; +import 'package:askaide/page/component/chat/message_state_manager.dart'; +import 'package:askaide/page/component/chat/role_avatar.dart'; +import 'package:askaide/page/component/dialog.dart'; +import 'package:askaide/page/component/enhanced_error.dart'; +import 'package:askaide/page/component/global_alert.dart'; +import 'package:askaide/page/component/loading.dart'; +import 'package:askaide/page/component/select_mode_toolbar.dart'; +import 'package:askaide/page/component/theme/custom_size.dart'; +import 'package:askaide/page/component/theme/custom_theme.dart'; +import 'package:askaide/page/custom_scaffold.dart'; +import 'package:askaide/page/drawer.dart'; +import 'package:askaide/repo/api/model.dart'; +import 'package:askaide/repo/api_server.dart'; +import 'package:askaide/repo/model/message.dart'; +import 'package:askaide/repo/model/misc.dart'; +import 'package:askaide/repo/settings_repo.dart'; +import 'package:bot_toast/bot_toast.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:flutter_localization/flutter_localization.dart'; +import 'package:askaide/repo/model/model.dart' as mm; + +class NewHomePage extends StatefulWidget { + final SettingRepository settings; + + /// 聊天内容窗口状态管理器 + final MessageStateManager stateManager; + const NewHomePage({ + super.key, + required this.settings, + required this.stateManager, + }); + + @override + State createState() => _NewHomePageState(); +} + +class _NewHomePageState extends State { + // 聊天内容界面控制器 + final ChatPreviewController chatPreviewController = ChatPreviewController(); + // 聊天内容滚动控制器 + final ScrollController scrollController = ScrollController(); + // 输入框是否可编辑 + final ValueNotifier enableInput = ValueNotifier(true); + // 音频播放器控制器 + final AudioPlayerController audioPlayerController = + AudioPlayerController(useRemoteAPI: true); + + // 聊天室 ID,当没有值时,会在第一个聊天消息发送后自动设置新值 + int? chatId; + // The selected image files for image upload + List selectedImageFiles = []; + // The selected file for file upload + FileUpload? selectedFile; + + // 是否显示音频播放器 + bool showAudioPlayer = false; + // 是否显示音频播放器加载中 + bool audioLoadding = false; + + /// 当前选择的模型 + mm.Model? selectedModel; + // 全量模型列表 + List supportModels = []; + // 当前聊天所使用的模型(v2) + HomeModelV2? currentModelV2; + + @override + void initState() { + super.initState(); + + Cache().intGet(key: 'last_chat_id').then((value) { + chatId = value; + reloadPage(); + }); + + reloadModels(); + initListeners(); + } + + /// 重新加载页面 + void reloadPage() { + // 加载当前用户信息 + context.read().add(AccountLoadEvent()); + + // 加载当前聊天室信息 + context.read().add(RoomLoadEvent( + chatAnywhereRoomId, + chatHistoryId: chatId, + cascading: true, + )); + + // 查询最近聊天记录 + context + .read() + .add(ChatMessageGetRecentEvent(chatHistoryId: chatId)); + } + + /// 加载模型列表,用于查询模型名称 + void reloadModels() { + ModelAggregate.models().then((value) { + setState(() { + supportModels = value; + }); + + Cache().stringGet(key: 'last_selected_model').then((value) { + final selected = supportModels.where((e) => e.id == value).firstOrNull; + if (selected != null) { + setState(() { + selectedModel = selected; + }); + } + + if (selectedModel == null) { + setState(() { + selectedModel = supportModels.first; + }); + } + + loadCurrentModel(selectedModel!.id); + }); + }); + } + + void initListeners() { + chatPreviewController.addListener(() { + setState(() {}); + }); + + audioPlayerController.onPlayStopped = () { + setState(() { + showAudioPlayer = false; + }); + }; + audioPlayerController.onPlayAudioStarted = () { + setState(() { + showAudioPlayer = true; + }); + }; + audioPlayerController.onPlayAudioLoading = (loading) { + setState(() { + audioLoadding = loading; + }); + }; + } + + /// 创建新的聊天 + void createNewChat() { + Cache().setInt( + key: 'last_chat_id', + value: 0, + duration: const Duration(days: 3650), + ); + setState(() { + chatId = null; + }); + + reloadPage(); + } + + /// 更新当前聊天 + void updateCurrentChat(int chatId) { + Cache().setInt( + key: 'last_chat_id', + value: chatId, + duration: const Duration(days: 3650), + ); + if (this.chatId == chatId) { + return; + } + + setState(() { + this.chatId = chatId; + }); + reloadPage(); + } + + @override + void dispose() { + scrollController.dispose(); + chatPreviewController.dispose(); + audioPlayerController.dispose(); + + super.dispose(); + } + + Future loadCurrentModel(String model) async { + if (!model.startsWith('v2@') || currentModelV2 != null) { + return; + } + + currentModelV2 = await APIServer().customHomeModelsItemV2( + uniqueKey: model.split('v2@').last, + ); + + setState(() {}); + } + + @override + Widget build(BuildContext context) { + final customColors = Theme.of(context).extension()!; + return CustomScaffold( + settings: widget.settings, + appBarBackground: Image.asset( + customColors.appBarBackgroundImage!, + fit: BoxFit.cover, + ), + // 标题,点击后弹出模型选择对话框 + title: GestureDetector( + onTap: () { + ModelSwitcher.openActionDialog( + context: context, + onSelected: (selected) { + setState(() { + selectedModel = selected; + }); + + if (selected != null) { + Cache().setString( + key: 'last_selected_model', + value: selected.id, + duration: const Duration(days: 3650), + ); + } + }, + initValue: selectedModel, + ); + }, + child: Column( + children: [ + Container( + width: MediaQuery.of(context).size.width / 2, + alignment: Alignment.center, + child: BlocBuilder( + buildWhen: (previous, current) => current is ChatMessagesLoaded, + builder: (context, state) { + if (state is ChatMessagesLoaded) { + return Text( + state.chatHistory == null || + state.chatHistory!.title == null + ? AppLocale.chatAnywhere.getString(context) + : state.chatHistory!.title!, + overflow: TextOverflow.ellipsis, + maxLines: 1, + style: const TextStyle( + fontSize: CustomSize.appBarTitleSize, + ), + ); + } + + return Text( + AppLocale.chatAnywhere.getString(context), + overflow: TextOverflow.ellipsis, + maxLines: 1, + style: + const TextStyle(fontSize: CustomSize.appBarTitleSize), + ); + }, + ), + ), + if (selectedModel != null) + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text( + selectedModel!.name, + style: TextStyle( + fontSize: CustomSize.appBarTitleSize * 0.6, + color: customColors.backgroundInvertedColor, + ), + ), + Icon( + Icons.unfold_more, + color: customColors.backgroundInvertedColor, + size: CustomSize.appBarTitleSize * 0.6, + ), + ], + ), + ], + ), + ), + actions: [ + IconButton( + icon: const Icon(Icons.post_add), + onPressed: createNewChat, + ), + ], + body: BlocConsumer( + listenWhen: (previous, current) => current is RoomLoaded, + listener: (context, state) async { + if (state is RoomLoaded && currentModelV2 == null) { + await loadCurrentModel(state.room.model); + } + + if (state is RoomLoaded && state.cascading) { + if (state.room.model.startsWith('v2@')) { + if (currentModelV2 != null && currentModelV2!.modelId != null) { + // 加载免费使用次数 + // ignore: use_build_context_synchronously + context.read().add(FreeCountReloadEvent( + model: currentModelV2!.modelId!, + )); + } + } else { + // 加载免费使用次数 + // ignore: use_build_context_synchronously + context.read().add(FreeCountReloadEvent( + model: selectedModel?.id ?? state.room.model, + )); + } + } + }, + buildWhen: (previous, current) => current is RoomLoaded, + builder: (context, room) { + // 加载聊天室 + if (room is RoomLoaded) { + if (room.error != null) { + return EnhancedErrorWidget(error: room.error); + } + + return buildChatComponents( + customColors, + context, + room, + ); + } else { + return Container(); + } + }, + ), + drawer: MultiBlocProvider( + providers: [ + BlocProvider.value( + value: context.read(), + ), + BlocProvider.value( + value: context.read(), + ), + ], + child: const LeftDrawer(), + ), + ); + } + + /// 构建聊天室窗口 + Widget buildChatComponents( + CustomColors customColors, + BuildContext context, + RoomLoaded room, + ) { + return Column( + children: [ + if (Ability().showGlobalAlert) const GlobalAlert(pageKey: 'chat'), + if (showAudioPlayer) + EnhancedAudioPlayer( + controller: audioPlayerController, + loading: audioLoadding, + ), + // 聊天内容窗口 + Expanded( + child: Stack( + fit: StackFit.expand, + children: [ + BlocConsumer( + listener: (context, state) { + if (state is ChatAnywhereInited) { + updateCurrentChat(state.chatId); + } + + if (state is ChatMessagesLoaded && state.error == null) { + setState(() { + selectedImageFiles = []; + }); + } + // 显示错误提示 + else if (state is ChatMessagesLoaded && state.error != null) { + showErrorMessageEnhanced(context, state.error); + } else if (state is ChatMessageUpdated) { + // 聊天内容窗口滚动到底部 + if (!state.processing && scrollController.hasClients) { + scrollController.animateTo( + 0, + duration: const Duration(milliseconds: 500), + curve: Curves.easeOut, + ); + } + + if (state.processing && enableInput.value) { + // 聊天回复中时,禁止输入框编辑 + setState(() { + enableInput.value = false; + }); + } else if (!state.processing && !enableInput.value) { + // 更新免费使用次数 + context.read().add(FreeCountReloadEvent( + model: selectedModel?.id ?? room.room.model)); + + // 聊天回复完成时,取消输入框的禁止编辑状态 + setState(() { + enableInput.value = true; + }); + } + } + }, + buildWhen: (prv, cur) => cur is ChatMessagesLoaded, + builder: (context, state) { + if (state is ChatMessagesLoaded) { + return buildChatPreviewArea( + state, + room.examples ?? [], + room, + customColors, + chatPreviewController.selectMode, + ); + } + return const Center(child: CircularProgressIndicator()); + }, + ), + if (!enableInput.value) + Positioned( + bottom: 10, + width: CustomSize.adaptiveMaxWindowWidth(context), + child: Center( + child: StopButton( + label: '停止输出', + onPressed: () { + HapticFeedbackHelper.mediumImpact(); + context + .read() + .add(ChatMessageStopEvent()); + }, + ), + ), + ), + ], + ), + ), + + // 聊天输入窗口 + if (!chatPreviewController.selectMode) + Container( + decoration: BoxDecoration( + borderRadius: const BorderRadius.only( + topLeft: Radius.circular(10), + topRight: Radius.circular(10), + ), + color: customColors.chatInputPanelBackground, + ), + child: BlocBuilder( + builder: (context, freeState) { + var hintText = '有问题尽管问我'; + if (freeState is FreeCountLoadedState) { + final matched = freeState.model(room.room.model); + if (matched != null && + matched.leftCount > 0 && + matched.maxCount > 0) { + hintText += '(今日还可免费${matched.leftCount}次)'; + } + } + + return BlocBuilder( + buildWhen: (previous, current) => + current is ChatMessagesLoaded, + builder: (context, state) { + var enableImageUpload = false; + if (state is ChatMessagesLoaded) { + if (currentModelV2 != null) { + enableImageUpload = + currentModelV2?.supportVision ?? false; + } else { + var model = state.chatHistory?.model ?? room.room.model; + final cur = supportModels + .where((e) => e.id == model) + .firstOrNull; + enableImageUpload = cur?.supportVision ?? false; + } + } + + return ChatInput( + enableNotifier: enableInput, + onSubmit: (value) { + handleSubmit(value); + FocusManager.instance.primaryFocus?.unfocus(); + }, + enableImageUpload: selectedModel == null + ? enableImageUpload + : (selectedModel?.supportVision ?? false), + onImageSelected: (files) { + setState(() { + selectedImageFiles = files; + }); + }, + selectedImageFiles: + enableImageUpload ? selectedImageFiles : [], + hintText: hintText, + onVoiceRecordTappedEvent: () { + audioPlayerController.stop(); + }, + onStopGenerate: () { + context + .read() + .add(ChatMessageStopEvent()); + }, + ); + }, + ); + }, + ), + ), + + // 选择模式工具栏 + if (chatPreviewController.selectMode) + SelectModeToolbar(chatPreviewController: chatPreviewController), + ], + ); + } + + /// 构建聊天内容窗口 + Widget buildChatPreviewArea( + ChatMessagesLoaded loadedState, + List examples, + RoomLoaded room, + CustomColors customColors, + bool selectMode, + ) { + final loadedMessages = loadedState.messages as List; + if (room.room.initMessage != null && + room.room.initMessage != '' && + loadedMessages.isEmpty) { + loadedMessages.add( + Message( + Role.receiver, + room.room.initMessage!, + type: MessageType.initMessage, + ), + ); + } + + // 聊天内容为空时,显示示例页面 + if (loadedMessages.isEmpty) { + return EmptyPreview( + examples: examples, + onSubmit: handleSubmit, + ); + } + + final messages = loadedMessages.map((e) { + if (e.model != null && !e.model!.startsWith('v2@')) { + final mod = supportModels.where((m) => m.id == e.model).firstOrNull; + if (mod != null) { + e.senderName = mod.shortName; + e.avatarUrl = mod.avatarUrl; + } + } + + if (e.avatarUrl == null || e.senderName == null) { + if (loadedState.chatHistory != null && + loadedState.chatHistory!.model != null) { + if (currentModelV2 != null) { + e.senderName = currentModelV2!.name; + e.avatarUrl = currentModelV2!.avatarUrl; + } else { + final mod = supportModels + .where((e) => e.id == loadedState.chatHistory!.model!) + .firstOrNull; + if (mod != null) { + e.senderName = mod.shortName; + e.avatarUrl = mod.avatarUrl; + } + } + } + } + + final stateMessage = + room.states[widget.stateManager.getKey(e.roomId ?? 0, e.id ?? 0)] ?? + MessageState(); + return MessageWithState(e, stateMessage); + }).toList(); + + chatPreviewController.setAllMessageIds(messages); + + return ChatPreview( + padding: enableInput.value ? null : const EdgeInsets.only(bottom: 35), + messages: messages, + scrollController: scrollController, + controller: chatPreviewController, + stateManager: widget.stateManager, + robotAvatar: selectMode + ? null + : RoleAvatar( + avatarUrl: room.room.avatarUrl, + his: loadedState.chatHistory, + alternativeAvatarUrl: currentModelV2?.avatarUrl, + ), + onDeleteMessage: (id) { + handleDeleteMessage(context, id, chatHistoryId: chatId); + }, + onResetContext: () => handleResetContext(context), + onResentEvent: (message, index) { + scrollController.animateTo(0, + duration: const Duration(milliseconds: 500), curve: Curves.easeOut); + + handleSubmit(message.text, + messagetType: message.type, index: index, isResent: true); + }, + onSpeakEvent: (message) { + audioPlayerController.playAudio(message.text); + }, + helpWidgets: loadedState.processing || loadedMessages.last.isInitMessage() + ? null + : [HelpTips(onSubmitMessage: handleSubmit)], + ); + } + + /// 提交新消息 + void handleSubmit( + String text, { + messagetType = MessageType.text, + int? index, + bool isResent = false, + }) async { + setState(() { + enableInput.value = false; + }); + + if (selectedImageFiles.isNotEmpty) { + final cancel = BotToast.showCustomLoading( + toastBuilder: (cancel) { + return const LoadingIndicator( + message: '正在上传图片,请稍后...', + ); + }, + allowClick: false, + ); + + try { + final uploader = ImageUploader(widget.settings); + + for (var file in selectedImageFiles) { + if (file.uploaded) { + continue; + } + + if (file.file.bytes != null) { + final res = await uploader.base64( + imageData: file.file.bytes, + maxSize: 1024 * 1024, + compressWidth: 512, + compressHeight: 512, + ); + file.setUrl(res); + } else { + final res = await uploader.base64( + path: file.file.path!, + maxSize: 1024 * 1024, + compressWidth: 512, + compressHeight: 512, + ); + file.setUrl(res); + } + } + } catch (e) { + // ignore: use_build_context_synchronously + showErrorMessageEnhanced(context, e); + return; + } finally { + cancel(); + } + } + + // ignore: use_build_context_synchronously + context.read().add( + ChatMessageSendEvent( + Message( + Role.sender, + text, + user: 'me', + ts: DateTime.now(), + model: selectedModel!.id, + type: messagetType, + chatHistoryId: chatId, + images: selectedImageFiles + .where((e) => e.uploaded) + .map((e) => e.url!) + .toList(), + ), + index: index, + isResent: isResent, + ), + ); + + // ignore: use_build_context_synchronously + context.read().add(NotifyResetEvent()); + // ignore: use_build_context_synchronously + context + .read() + .add(RoomLoadEvent(chatAnywhereRoomId, cascading: false)); + } +} diff --git a/lib/page/setting/setting_screen.dart b/lib/page/setting/setting_screen.dart index 8bdcd2d0..cf6fa91b 100644 --- a/lib/page/setting/setting_screen.dart +++ b/lib/page/setting/setting_screen.dart @@ -99,7 +99,7 @@ class _SettingScreenState extends State { context, [ // 智慧果信息、充值入口 - _buildAccountQuotaCard(context, state), + // _buildAccountQuotaCard(context, state), // 账号信息 SettingsSection( diff --git a/lib/repo/api/info.dart b/lib/repo/api/info.dart index 25335f0c..3a6158da 100644 --- a/lib/repo/api/info.dart +++ b/lib/repo/api/info.dart @@ -63,7 +63,7 @@ class Capabilities { required this.mailEnabled, required this.openaiEnabled, required this.homeModels, - this.homeRoute = '/chat-chat', + this.homeRoute = '/', this.showHomeModelDescription = true, this.supportWebsocket = false, this.supportAPIKeys = false, @@ -89,7 +89,8 @@ class Capabilities { homeModels: ((json['home_models_v2'] ?? []) as List) .map((e) => HomeModelV2.fromJson(e)) .toList(), - homeRoute: json['home_route'] ?? '/chat-chat', + // homeRoute: json['home_route'] ?? '/', + homeRoute: '/', showHomeModelDescription: json['show_home_model_description'] ?? true, supportWebsocket: json['support_websocket'] ?? false, supportAPIKeys: json['support_api_keys'] ?? false, From c9574bcd6e7822c113a60ac8858080129dc2c1a4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=AE=A1=E5=AE=9C=E5=B0=A7?= Date: Mon, 14 Oct 2024 15:57:56 +0800 Subject: [PATCH 06/26] update --- ios/Podfile.lock | 2 +- ios/Runner.xcodeproj/project.pbxproj | 3 + lib/bloc/room_state.dart | 6 +- lib/page/component/account_quota_card.dart | 39 +-- lib/page/component/chat/empty.dart | 9 +- lib/page/component/icon_box.dart | 31 ++ lib/page/custom_scaffold.dart | 1 - lib/page/drawer.dart | 221 ++++++++------- lib/page/home.dart | 38 +-- pubspec.lock | 312 ++++++++++----------- 10 files changed, 340 insertions(+), 322 deletions(-) create mode 100644 lib/page/component/icon_box.dart diff --git a/ios/Podfile.lock b/ios/Podfile.lock index 8c932700..988715fc 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -282,7 +282,7 @@ SPEC CHECKSUMS: media_kit_video: 5da63f157170e5bf303bf85453b7ef6971218a2e package_info_plus: 58f0028419748fad15bf008b270aaa8e54380b1c path_provider_foundation: 2b6b4c569c0fb62ec74538f866245ac84301af46 - record_darwin: 1f6619f2abac4d1ca91d3eeab038c980d76f1517 + record_darwin: df0a677188e5fed18472550298e675f19ddaffbe screen_brightness_ios: 715ca807df953bf676d339f11464e438143ee625 SDWebImage: 066c47b573f408f18caa467d71deace7c0f8280d SDWebImageWebPCoder: e38c0a70396191361d60c092933e22c20d5b1380 diff --git a/ios/Runner.xcodeproj/project.pbxproj b/ios/Runner.xcodeproj/project.pbxproj index 09204d39..4888ed3a 100644 --- a/ios/Runner.xcodeproj/project.pbxproj +++ b/ios/Runner.xcodeproj/project.pbxproj @@ -387,6 +387,7 @@ ENABLE_BITCODE = NO; "EXCLUDED_ARCHS[sdk=iphonesimulator*]" = "i386 arm64"; INFOPLIST_FILE = Runner/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -612,6 +613,7 @@ ENABLE_BITCODE = NO; "EXCLUDED_ARCHS[sdk=iphonesimulator*]" = "i386 arm64"; INFOPLIST_FILE = Runner/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -731,6 +733,7 @@ ENABLE_BITCODE = NO; "EXCLUDED_ARCHS[sdk=iphonesimulator*]" = "i386 arm64"; INFOPLIST_FILE = Runner/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", diff --git a/lib/bloc/room_state.dart b/lib/bloc/room_state.dart index d695d64a..4d4ff061 100644 --- a/lib/bloc/room_state.dart +++ b/lib/bloc/room_state.dart @@ -28,7 +28,11 @@ class RoomLoaded extends RoomState { this.error, this.examples, required this.cascading, - }); + }) { + if (examples != null) { + examples!.shuffle(); + } + } } class RoomCreateError extends RoomState { diff --git a/lib/page/component/account_quota_card.dart b/lib/page/component/account_quota_card.dart index 273ff57e..22188b5e 100644 --- a/lib/page/component/account_quota_card.dart +++ b/lib/page/component/account_quota_card.dart @@ -2,7 +2,6 @@ import 'package:askaide/helper/ability.dart'; import 'package:askaide/lang/lang.dart'; import 'package:askaide/page/component/coin.dart'; import 'package:askaide/page/component/enhanced_button.dart'; -import 'package:askaide/page/component/image.dart'; import 'package:askaide/page/component/theme/custom_theme.dart'; import 'package:askaide/repo/api/user.dart'; import 'package:flutter/material.dart'; @@ -25,47 +24,11 @@ class AccountQuotaCard extends StatelessWidget { margin: noBorder ? null : const EdgeInsets.symmetric(horizontal: 15, vertical: 10), - decoration: BoxDecoration( - color: Colors.white, - borderRadius: BorderRadius.circular(noBorder ? 0 : 15), - boxShadow: [ - BoxShadow( - color: Colors.white.withOpacity(0.1), - blurRadius: 10, - offset: const Offset(0, 5), - ), - ], - image: userInfo != null && userInfo!.control.userCardBg != null - ? DecorationImage( - // opacity: 0.83, - image: CachedNetworkImageProviderEnhanced( - userInfo!.control.userCardBg!), - fit: BoxFit.cover, - ) - : DecorationImage( - image: CachedNetworkImageProviderEnhanced( - "https://ssl.aicode.cc/ai-server/assets/quota-card-bg.webp-thumb1000", - ), - fit: BoxFit.cover, - ), - gradient: userInfo == null || userInfo!.control.userCardBg == null - ? const LinearGradient( - begin: Alignment.topLeft, - end: Alignment.bottomRight, - colors: [ - Color.fromARGB(255, 90, 218, 196), - // Color.fromARGB(255, 230, 153, 38), - Color.fromARGB(255, 242, 7, 213), - ], - transform: GradientRotation(0.5), - ) - : null, - ), height: 140, child: Container( padding: noBorder ? const EdgeInsets.only( - top: 35, + top: 5, left: 20, right: 20, ) diff --git a/lib/page/component/chat/empty.dart b/lib/page/component/chat/empty.dart index 25666e22..04b6acd7 100644 --- a/lib/page/component/chat/empty.dart +++ b/lib/page/component/chat/empty.dart @@ -6,16 +6,11 @@ class EmptyPreview extends StatefulWidget { final List examples; final Function(String message) onSubmit; - EmptyPreview({ + const EmptyPreview({ super.key, required this.examples, required this.onSubmit, - }) { - // 示例问题随机排序 - if (examples.isNotEmpty) { - examples.shuffle(); - } - } + }); @override State createState() => _EmptyPreviewState(); diff --git a/lib/page/component/icon_box.dart b/lib/page/component/icon_box.dart new file mode 100644 index 00000000..4421583a --- /dev/null +++ b/lib/page/component/icon_box.dart @@ -0,0 +1,31 @@ +import 'package:flutter/material.dart'; + +class IconBox extends StatelessWidget { + final Icon icon; + final Widget title; + final Function()? onTap; + const IconBox({ + super.key, + required this.icon, + required this.title, + this.onTap, + }); + + @override + Widget build(BuildContext context) { + return MaterialButton( + padding: const EdgeInsets.symmetric(vertical: 10, horizontal: 10), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(8), + ), + onPressed: onTap, + child: Column( + children: [ + icon, + const SizedBox(height: 10), + title, + ], + ), + ); + } +} diff --git a/lib/page/custom_scaffold.dart b/lib/page/custom_scaffold.dart index fd44993e..0889e5ef 100644 --- a/lib/page/custom_scaffold.dart +++ b/lib/page/custom_scaffold.dart @@ -1,6 +1,5 @@ import 'package:askaide/page/component/background_container.dart'; import 'package:askaide/page/component/theme/custom_size.dart'; -import 'package:askaide/page/component/theme/custom_theme.dart'; import 'package:askaide/repo/settings_repo.dart'; import 'package:flutter/material.dart'; diff --git a/lib/page/drawer.dart b/lib/page/drawer.dart index ff48235e..cf29f27b 100644 --- a/lib/page/drawer.dart +++ b/lib/page/drawer.dart @@ -2,6 +2,8 @@ import 'package:askaide/bloc/account_bloc.dart'; import 'package:askaide/bloc/chat_chat_bloc.dart'; import 'package:askaide/lang/lang.dart'; import 'package:askaide/page/component/account_quota_card.dart'; +import 'package:askaide/page/component/icon_box.dart'; +import 'package:askaide/page/component/image.dart'; import 'package:askaide/repo/api/user.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; @@ -23,119 +25,138 @@ class _LeftDrawerState extends State { shape: const RoundedRectangleBorder( borderRadius: BorderRadius.zero, ), - child: Column( - children: [ - Expanded( - child: SingleChildScrollView( - child: Column( - children: [ - DrawerHeader( - padding: const EdgeInsets.all(0), - child: BlocBuilder( - builder: (_, state) { - UserInfo? userInfo; - if (state is AccountLoaded) { - userInfo = state.user; - } + child: SafeArea( + top: false, + child: Column( + children: [ + Expanded( + child: SingleChildScrollView( + child: Column( + children: [ + DrawerHeader( + padding: const EdgeInsets.all(0), + decoration: BoxDecoration( + color: Colors.white, + image: DecorationImage( + image: CachedNetworkImageProviderEnhanced( + "https://ssl.aicode.cc/ai-server/assets/quota-card-bg.webp-thumb1000", + ), + fit: BoxFit.cover, + ), + ), + child: BlocBuilder( + builder: (_, state) { + UserInfo? userInfo; + if (state is AccountLoaded) { + userInfo = state.user; + } - return AccountQuotaCard( - userInfo: userInfo, - noBorder: true, - onPaymentReturn: () { - if (userInfo != null) { - context - .read() - .add(AccountLoadEvent(cache: false)); - } + return AccountQuotaCard( + userInfo: userInfo, + noBorder: true, + onPaymentReturn: () { + if (userInfo != null) { + context + .read() + .add(AccountLoadEvent(cache: false)); + } + }, + ); + }, + ), + ), + const SizedBox(height: 15), + Row( + mainAxisAlignment: MainAxisAlignment.spaceAround, + children: [ + IconBox( + icon: const Icon(Icons.group_outlined), + title: Text(AppLocale.homeTitle.getString(context)), + onTap: () { + context.push('/characters'); + }, + ), + IconBox( + icon: const Icon(Icons.auto_awesome_outlined), + title: Text(AppLocale.discover.getString(context)), + onTap: () { + context.push('/creative-gallery'); }, - ); + ), + IconBox( + icon: const Icon(Icons.palette_outlined), + title: + Text(AppLocale.creativeIsland.getString(context)), + onTap: () { + context.push('/creative-draw'); + }, + ), + ], + ), + const SizedBox(height: 15), + ListTile( + leading: const Icon(Icons.history), + title: Text(AppLocale.histories.getString(context)), + onTap: () { + context.push('/chat-chat/history').whenComplete(() { + context + .read() + .add(ChatChatLoadRecentHistories()); + }); }, ), - ), - const SizedBox(height: 15), - ListTile( - leading: const Icon(Icons.history), - title: Text(AppLocale.histories.getString(context)), - onTap: () { - context.push('/chat-chat/history').whenComplete(() { - context - .read() - .add(ChatChatLoadRecentHistories()); - }); - }, - ), - ListTile( - leading: const Icon(Icons.group_outlined), - title: Text(AppLocale.homeTitle.getString(context)), - onTap: () { - context.push('/characters'); - }, - ), - ListTile( - leading: const Icon(Icons.auto_awesome_outlined), - title: Text(AppLocale.discover.getString(context)), - onTap: () { - context.push('/creative-gallery'); - }, - ), - ListTile( - leading: const Icon(Icons.palette_outlined), - title: Text(AppLocale.creativeIsland.getString(context)), + ListTile( + leading: const Icon(Icons.settings_outlined), + title: const Text('设置'), + onTap: () { + context.push('/setting'); + }, + ), + ], + ), + ), + ), + SizedBox( + height: 70, + child: Column( + children: [ + GestureDetector( onTap: () { - context.push('/creative-draw'); + launchUrlString( + 'https://weibo.com/code404', + mode: LaunchMode.externalApplication, + ); }, + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + const Text('关注我们:'), + const SizedBox(width: 10), + Image.asset('assets/weibo.png', width: 25), + ], + ), ), - ListTile( - leading: const Icon(Icons.settings_outlined), - title: const Text('设置'), + GestureDetector( onTap: () { - context.push('/setting'); + launchUrlString( + 'https://ai.aicode.cc/social/github', + mode: LaunchMode.externalApplication, + ); }, + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + const Text('本项目开源,欢迎贡献:'), + const SizedBox(width: 10), + Image.asset('assets/github.png', width: 25), + ], + ), ), ], ), ), - ), - SizedBox( - height: 70, - child: Column( - children: [ - GestureDetector( - onTap: () { - launchUrlString( - 'https://weibo.com/code404', - mode: LaunchMode.externalApplication, - ); - }, - child: Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - const Text('关注我们:'), - const SizedBox(width: 10), - Image.asset('assets/weibo.png', width: 25), - ], - ), - ), - GestureDetector( - onTap: () { - launchUrlString( - 'https://ai.aicode.cc/social/github', - mode: LaunchMode.externalApplication, - ); - }, - child: Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - const Text('本项目开源,欢迎贡献:'), - const SizedBox(width: 10), - Image.asset('assets/github.png', width: 25), - ], - ), - ), - ], - ), - ), - ], + ], + ), ), ); } diff --git a/lib/page/home.dart b/lib/page/home.dart index 2b647b1f..d21285f2 100644 --- a/lib/page/home.dart +++ b/lib/page/home.dart @@ -93,7 +93,7 @@ class _NewHomePageState extends State { Cache().intGet(key: 'last_chat_id').then((value) { chatId = value; - reloadPage(); + reloadPage(loadAll: true); }); reloadModels(); @@ -101,21 +101,23 @@ class _NewHomePageState extends State { } /// 重新加载页面 - void reloadPage() { + void reloadPage({bool loadAll = false}) { // 加载当前用户信息 context.read().add(AccountLoadEvent()); - // 加载当前聊天室信息 - context.read().add(RoomLoadEvent( - chatAnywhereRoomId, - chatHistoryId: chatId, - cascading: true, - )); - - // 查询最近聊天记录 - context - .read() - .add(ChatMessageGetRecentEvent(chatHistoryId: chatId)); + if (loadAll) { + // 加载当前聊天室信息 + context.read().add(RoomLoadEvent( + chatAnywhereRoomId, + chatHistoryId: chatId, + cascading: true, + )); + + // 查询最近聊天记录 + context + .read() + .add(ChatMessageGetRecentEvent(chatHistoryId: chatId)); + } } /// 加载模型列表,用于查询模型名称 @@ -133,13 +135,15 @@ class _NewHomePageState extends State { }); } - if (selectedModel == null) { + if (selectedModel == null && supportModels.isNotEmpty) { setState(() { selectedModel = supportModels.first; }); } - loadCurrentModel(selectedModel!.id); + if (selectedModel != null) { + loadCurrentModel(selectedModel!.id); + } }); }); } @@ -177,7 +181,7 @@ class _NewHomePageState extends State { chatId = null; }); - reloadPage(); + reloadPage(loadAll: true); } /// 更新当前聊天 @@ -711,8 +715,6 @@ class _NewHomePageState extends State { ), ); - // ignore: use_build_context_synchronously - context.read().add(NotifyResetEvent()); // ignore: use_build_context_synchronously context .read() diff --git a/pubspec.lock b/pubspec.lock index 05db4fae..47741420 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -45,10 +45,10 @@ packages: dependency: transitive description: name: ansicolor - sha256: "8bf17a8ff6ea17499e40a2d2542c2f481cd7615760c6d34065cb22bfd22e6880" + sha256: "50e982d500bc863e1d703448afdbf9e5a72eb48840a4f766fa361ffd6877055f" url: "https://pub.dev" source: hosted - version: "2.0.2" + version: "2.0.3" archive: dependency: transitive description: @@ -190,10 +190,10 @@ packages: dependency: transitive description: name: build_daemon - sha256: "0343061a33da9c5810b2d6cee51945127d8f4c060b7fbdd9d54917f0a3feaaa1" + sha256: "79b2aef6ac2ed00046867ed354c88778c9c0f029df8a20fe10b5436826721ef9" url: "https://pub.dev" source: hosted - version: "4.0.1" + version: "4.0.2" build_resolvers: dependency: transitive description: @@ -206,18 +206,18 @@ packages: dependency: "direct dev" description: name: build_runner - sha256: "3ac61a79bfb6f6cc11f693591063a7f19a7af628dc52f141743edac5c16e8c22" + sha256: "644dc98a0f179b872f612d3eb627924b578897c629788e858157fa5e704ca0c7" url: "https://pub.dev" source: hosted - version: "2.4.9" + version: "2.4.11" build_runner_core: dependency: transitive description: name: build_runner_core - sha256: "4ae8ffe5ac758da294ecf1802f2aff01558d8b1b00616aa7538ea9a8a5d50799" + sha256: e3c79f69a64bdfcd8a776a3c28db4eb6e3fb5356d013ae5eb2e52007706d5dbe url: "https://pub.dev" source: hosted - version: "7.3.0" + version: "7.3.1" built_collection: dependency: transitive description: @@ -238,26 +238,26 @@ packages: dependency: "direct main" description: name: cached_network_image - sha256: "28ea9690a8207179c319965c13cd8df184d5ee721ae2ce60f398ced1219cea1f" + sha256: "4a5d8d2c728b0f3d0245f69f921d7be90cae4c2fd5288f773088672c0893f819" url: "https://pub.dev" source: hosted - version: "3.3.1" + version: "3.4.0" cached_network_image_platform_interface: dependency: transitive description: name: cached_network_image_platform_interface - sha256: "9e90e78ae72caa874a323d78fa6301b3fb8fa7ea76a8f96dc5b5bf79f283bf2f" + sha256: "35814b016e37fbdc91f7ae18c8caf49ba5c88501813f73ce8a07027a395e2829" url: "https://pub.dev" source: hosted - version: "4.0.0" + version: "4.1.1" cached_network_image_web: dependency: transitive description: name: cached_network_image_web - sha256: "205d6a9f1862de34b93184f22b9d2d94586b2f05c581d546695e3d8f6a805cd7" + sha256: "6322dde7a5ad92202e64df659241104a43db20ed594c41ca18de1014598d7996" url: "https://pub.dev" source: hosted - version: "1.2.0" + version: "1.3.0" characters: dependency: transitive description: @@ -278,10 +278,10 @@ packages: dependency: "direct main" description: name: circular_countdown_timer - sha256: "9ba5fbc076cedbcbf6190ed86762e679f43d7c67cdd903ea34df059dabdc08d4" + sha256: "608d166c8c659af5740ffec8859e1429a4849b706675a529dc381ef40784697b" url: "https://pub.dev" source: hosted - version: "0.2.3" + version: "0.2.4" cli_util: dependency: transitive description: @@ -342,18 +342,18 @@ packages: dependency: transitive description: name: cross_file - sha256: "55d7b444feb71301ef6b8838dbc1ae02e63dd48c8773f3810ff53bb1e2945b32" + sha256: "7caf6a750a0c04effbb52a676dce9a4a592e10ad35c34d6d2d0e4811160d5670" url: "https://pub.dev" source: hosted - version: "0.3.4+1" + version: "0.3.4+2" crypto: dependency: "direct main" description: name: crypto - sha256: ff625774173754681d66daaf4a448684fb04b78f902da9cb3d308c19cc5e8bab + sha256: ec30d999af904f33454ba22ed9a86162b35e52b44ac4807d1d93c288041d7d27 url: "https://pub.dev" source: hosted - version: "3.0.3" + version: "3.0.5" csslib: dependency: transitive description: @@ -407,18 +407,18 @@ packages: dependency: transitive description: name: dev_build - sha256: "863842423cffd927af831b6b576cd391b5f5de2f3f62b83c25ca970af96ee708" + sha256: f526d1fbe68875f6119ffc333f114dfe6aa93ad04439276d53968f7977cc410e url: "https://pub.dev" source: hosted - version: "0.16.7+3" + version: "1.0.0+11" dio: dependency: "direct main" description: name: dio - sha256: e17f6b3097b8c51b72c74c9f071a605c47bcc8893839bd66732457a5ebe73714 + sha256: "5598aa796bbf4699afd5c67c0f5f6e2ed542afc956884b9cd58c306966efc260" url: "https://pub.dev" source: hosted - version: "5.5.0+1" + version: "5.7.0" dio_cache_interceptor: dependency: "direct main" description: @@ -439,18 +439,18 @@ packages: dependency: transitive description: name: dio_web_adapter - sha256: "36c5b2d79eb17cdae41e974b7a8284fec631651d2a6f39a8a2ff22327e90aeac" + sha256: "33259a9276d6cea88774a0000cfae0d861003497755969c92faa223108620dc8" url: "https://pub.dev" source: hosted - version: "1.0.1" + version: "2.0.0" extended_list: dependency: transitive description: name: extended_list - sha256: b27a2f0f55dadbf5b273bdaaf9307a7e0098a9fc0c4b8eb60ae98c319af596bc + sha256: fa7bcb2645b7d6849918d499fda6ea917cda85e43b2e06dfec2a29b649722974 url: "https://pub.dev" source: hosted - version: "3.0.1" + version: "3.0.2" extended_list_library: dependency: transitive description: @@ -487,18 +487,18 @@ packages: dependency: transitive description: name: ffi - sha256: "493f37e7df1804778ff3a53bd691d8692ddf69702cf4c1c1096a2e41b4779e21" + sha256: "16ed7b077ef01ad6170a3d0c57caa4a112a38d7a2ed5602e0aca9ca6f3d98da6" url: "https://pub.dev" source: hosted - version: "2.1.2" + version: "2.1.3" file: dependency: transitive description: name: file - sha256: "5fc22d7c25582e38ad9a8515372cd9a93834027aacf1801cf01164dac0ffa08c" + sha256: a3b4f84adafef897088c160faf7dfffb7696046cb13ae90b508c2cbc95d3b8d4 url: "https://pub.dev" source: hosted - version: "7.0.0" + version: "7.0.1" file_picker: dependency: "direct main" description: @@ -549,10 +549,10 @@ packages: dependency: "direct main" description: name: flutter_cache_manager - sha256: "8207f27539deb83732fdda03e259349046a39a4c767269285f449ade355d54ba" + sha256: "400b6592f16a4409a7f2bb929a9a7e38c72cceb8ffb99ee57bbf2cb2cecf8386" url: "https://pub.dev" source: hosted - version: "3.3.1" + version: "3.4.1" flutter_colorpicker: dependency: "direct main" description: @@ -606,10 +606,10 @@ packages: dependency: "direct main" description: name: flutter_initicon - sha256: "5aeda6b16150cb54a34a048a85f9cfddbf36a92135f769ea998d1394dcb54a0f" + sha256: "1cd11dc9a32c222ce3e3d3a74c4f0d121909698f72a664e0ba371b4ee7bfe462" url: "https://pub.dev" source: hosted - version: "3.0.0+1" + version: "3.0.1" flutter_launcher_icons: dependency: "direct dev" description: @@ -638,18 +638,18 @@ packages: dependency: transitive description: name: flutter_local_notifications_linux - sha256: "33f741ef47b5f63cc7f78fe75eeeac7e19f171ff3c3df054d84c1e38bedb6a03" + sha256: c49bd06165cad9beeb79090b18cd1eb0296f4bf4b23b84426e37dd7c027fc3af url: "https://pub.dev" source: hosted - version: "4.0.0+1" + version: "4.0.1" flutter_local_notifications_platform_interface: dependency: transitive description: name: flutter_local_notifications_platform_interface - sha256: "340abf67df238f7f0ef58f4a26d2a83e1ab74c77ab03cd2b2d5018ac64db30b7" + sha256: "85f8d07fe708c1bdcf45037f2c0109753b26ae077e9d9e899d55971711a4ea66" url: "https://pub.dev" source: hosted - version: "7.1.0" + version: "7.2.0" flutter_localization: dependency: "direct main" description: @@ -699,18 +699,18 @@ packages: dependency: transitive description: name: flutter_plugin_android_lifecycle - sha256: "8cf40eebf5dec866a6d1956ad7b4f7016e6c0cc69847ab946833b7d43743809f" + sha256: "9ee02950848f61c4129af3d6ec84a1cfc0e47931abc746b03e7a3bc3e8ff6eda" url: "https://pub.dev" source: hosted - version: "2.0.19" + version: "2.0.22" flutter_slidable: dependency: "direct main" description: name: flutter_slidable - sha256: "673403d2eeef1f9e8483bd6d8d92aae73b1d8bd71f382bc3930f699c731bc27c" + sha256: "2c5611c0b44e20d180e4342318e1bbc28b0a44ad2c442f5df16962606fd3e8e3" url: "https://pub.dev" source: hosted - version: "3.1.0" + version: "3.1.1" flutter_sticky_header: dependency: "direct main" description: @@ -723,10 +723,10 @@ packages: dependency: "direct main" description: name: flutter_stripe - sha256: "28527923373720fcd39eade306f4acc007df8a3f2c0aeea545f1521dec9399c2" + sha256: acbefd9503d9c2e69a544ba84b0924dde372d9a016cc4f796a816c9df1b5fb6f url: "https://pub.dev" source: hosted - version: "11.0.0" + version: "11.2.0" flutter_stripe_web: dependency: "direct main" description: @@ -781,10 +781,10 @@ packages: dependency: transitive description: name: freezed_annotation - sha256: c3fd9336eb55a38cc1bbd79ab17573113a8deccd0ecbbf926cca3c62803b5c2d + sha256: c2e2d632dd9b8a2b7751117abcfc2b4888ecfe181bd9fca7170d9ef02e595fe2 url: "https://pub.dev" source: hosted - version: "2.4.1" + version: "2.4.4" frontend_server_client: dependency: transitive description: @@ -821,10 +821,10 @@ packages: dependency: transitive description: name: graphs - sha256: aedc5a15e78fc65a6e23bcd927f24c64dd995062bcd1ca6eda65a3cff92a4d19 + sha256: "741bbf84165310a68ff28fe9e727332eef1407342fca52759cb21ad8177bb8d0" url: "https://pub.dev" source: hosted - version: "2.3.1" + version: "2.3.2" highlight: dependency: transitive description: @@ -845,10 +845,10 @@ packages: dependency: "direct main" description: name: http - sha256: "761a297c042deedc1ffbb156d6e2af13886bb305c2a343a4d972504cd67dd938" + sha256: b9c29a161230ee03d3ccf545097fccd9b87a5264228c5d348202e0f0c28f9010 url: "https://pub.dev" source: hosted - version: "1.2.1" + version: "1.2.2" http_multi_server: dependency: transitive description: @@ -893,10 +893,10 @@ packages: dependency: transitive description: name: in_app_purchase_android - sha256: "3d84d1a001fa138bed09e0cd95e9dbce268dce8b6f63bda7cf69cbf9135fbfbb" + sha256: b5ec117e3483fbca0fc5c546e9ed420c126e97fc5952aa891d01f9cf87661d44 url: "https://pub.dev" source: hosted - version: "0.3.6" + version: "0.3.6+7" in_app_purchase_platform_interface: dependency: transitive description: @@ -909,10 +909,10 @@ packages: dependency: transitive description: name: in_app_purchase_storekit - sha256: "3eea5e173fca0a59ab2fcec5201bea14bb808dfad01004d2b1d7bfdb9244c5e6" + sha256: e9a408da11d055f9af9849859e1251b2b0a2f84f256fa3bf1285c5614a397079 url: "https://pub.dev" source: hosted - version: "0.3.16" + version: "0.3.18+1" intl: dependency: "direct main" description: @@ -997,10 +997,10 @@ packages: dependency: "direct main" description: name: loading_animation_widget - sha256: ee3659035528d19145d50cf0107632bf647e7306c88b6a32f35f3bed63f6d728 + sha256: "9fe23381f3096e902f39e87e487648ff7f74925e86234353fa885bb9f6c98004" url: "https://pub.dev" source: hosted - version: "1.2.1" + version: "1.3.0" loading_more_list: dependency: "direct main" description: @@ -1022,10 +1022,10 @@ packages: description: path: "." ref: HEAD - resolved-ref: "4d8d5959064f40d8d8c3fd8e1a379e68360c0431" + resolved-ref: "0a13268fd5fc068b97f09bb7ccf7319883b66b48" url: "https://github.com/Bungeefan/logger.git" source: git - version: "2.3.0" + version: "2.4.0" logging: dependency: transitive description: @@ -1070,10 +1070,10 @@ packages: dependency: "direct main" description: name: media_kit - sha256: "3289062540e3b8b9746e5c50d95bd78a9289826b7227e253dff806d002b9e67a" + sha256: "1f1deee148533d75129a6f38251ff8388e33ee05fc2d20a6a80e57d6051b7b62" url: "https://pub.dev" source: hosted - version: "1.1.10+1" + version: "1.1.11" media_kit_libs_android_video: dependency: transitive description: @@ -1110,34 +1110,34 @@ packages: dependency: "direct main" description: name: media_kit_libs_video - sha256: "3688e0c31482074578652bf038ce6301a5d21e1eda6b54fc3117ffeb4bdba067" + sha256: "20bb4aefa8fece282b59580e1cd8528117297083a6640c98c2e98cfc96b93288" url: "https://pub.dev" source: hosted - version: "1.0.4" + version: "1.0.5" media_kit_libs_windows_video: dependency: transitive description: name: media_kit_libs_windows_video - sha256: "7bace5f35d9afcc7f9b5cdadb7541d2191a66bb3fc71bfa11c1395b3360f6122" + sha256: "32654572167825c42c55466f5d08eee23ea11061c84aa91b09d0e0f69bdd0887" url: "https://pub.dev" source: hosted - version: "1.0.9" + version: "1.0.10" media_kit_native_event_loop: dependency: transitive description: name: media_kit_native_event_loop - sha256: a605cf185499d14d58935b8784955a92a4bf0ff4e19a23de3d17a9106303930e + sha256: "7d82e3b3e9ded5c35c3146c5ba1da3118d1dd8ac3435bac7f29f458181471b40" url: "https://pub.dev" source: hosted - version: "1.0.8" + version: "1.0.9" media_kit_video: dependency: "direct main" description: name: media_kit_video - sha256: c048d11a19e379aebbe810647636e3fc6d18374637e2ae12def4ff8a4b99a882 + sha256: "2cc3b966679963ba25a4ce5b771e532a521ebde7c6aa20e9802bec95d9916c8f" url: "https://pub.dev" source: hosted - version: "1.2.4" + version: "1.2.5" meta: dependency: transitive description: @@ -1150,10 +1150,10 @@ packages: dependency: transitive description: name: mime - sha256: "2e123074287cc9fd6c09de8336dae606d1ddb88d9ac47358826db698c176a1f2" + sha256: "801fd0b26f14a4a58ccb09d5892c3fbdeff209594300a542492cf13fba9d247a" url: "https://pub.dev" source: hosted - version: "1.0.5" + version: "1.0.6" msix: dependency: "direct dev" description: @@ -1174,10 +1174,10 @@ packages: dependency: transitive description: name: octo_image - sha256: "45b40f99622f11901238e18d48f5f12ea36426d8eced9f4cbf58479c7aa2430d" + sha256: "34faa6639a78c7e3cbe79be6f9f96535867e879748ade7d17c9b1ae7536293bd" url: "https://pub.dev" source: hosted - version: "2.0.0" + version: "2.1.0" package_config: dependency: transitive description: @@ -1190,18 +1190,18 @@ packages: dependency: transitive description: name: package_info_plus - sha256: b93d8b4d624b4ea19b0a5a208b2d6eff06004bc3ce74c06040b120eeadd00ce0 + sha256: "894f37107424311bdae3e476552229476777b8752c5a2a2369c0cb9a2d5442ef" url: "https://pub.dev" source: hosted - version: "8.0.0" + version: "8.0.3" package_info_plus_platform_interface: dependency: transitive description: name: package_info_plus_platform_interface - sha256: f49918f3433a3146047372f9d4f1f847511f2acd5cd030e1f44fe5a50036b70e + sha256: ac1f4a4847f1ade8e6a87d1f39f5d7c67490738642e2542f559ec38c37489a66 url: "https://pub.dev" source: hosted - version: "3.0.0" + version: "3.0.1" path: dependency: "direct main" description: @@ -1222,18 +1222,18 @@ packages: dependency: "direct main" description: name: path_provider - sha256: c9e7d3a4cd1410877472158bee69963a4579f78b68c65a2b7d40d1a7a88bb161 + sha256: fec0d61223fba3154d87759e3cc27fe2c8dc498f6386c6d6fc80d1afdd1bf378 url: "https://pub.dev" source: hosted - version: "2.1.3" + version: "2.1.4" path_provider_android: dependency: transitive description: name: path_provider_android - sha256: a248d8146ee5983446bf03ed5ea8f6533129a12b11f12057ad1b4a67a2b3b41d + sha256: "6f01f8e37ec30b07bc424b4deabac37cacb1bc7e2e515ad74486039918a37eb7" url: "https://pub.dev" source: hosted - version: "2.2.4" + version: "2.2.10" path_provider_foundation: dependency: transitive description: @@ -1262,10 +1262,10 @@ packages: dependency: transitive description: name: path_provider_windows - sha256: "8bc9f22eee8690981c22aa7fc602f5c85b497a6fb2ceb35ee5a5e5ed85ad8170" + sha256: bd6f00dbd873bfb70d0761682da2b3a2c2fccc2b9e84c495821639601d81afe7 url: "https://pub.dev" source: hosted - version: "2.2.1" + version: "2.3.0" petitparser: dependency: transitive description: @@ -1310,10 +1310,10 @@ packages: dependency: transitive description: name: process_run - sha256: "8d9c6198b98fbbfb511edd42e7364e24d85c163e47398919871b952dc86a423e" + sha256: c917dfb5f7afad4c7485bc00a4df038621248fce046105020cea276d1a87c820 url: "https://pub.dev" source: hosted - version: "0.14.2" + version: "1.1.0" provider: dependency: "direct main" description: @@ -1342,26 +1342,26 @@ packages: dependency: "direct main" description: name: qiniu_flutter_sdk - sha256: "6a61ea7b2acfa0f4ac2550096b14843ea887db1af9ea533a1da27b8df2c04810" + sha256: "76ebf0877f9bc91c98ccaa1a9d38617c71b0286bb8a2023558f8a8ce90dd00cf" url: "https://pub.dev" source: hosted - version: "0.6.0" + version: "0.6.1" qiniu_sdk_base: dependency: transitive description: name: qiniu_sdk_base - sha256: "01cc53929d03fda7fa816660f9cfa1ccac697dbf1949066d39dadbdce1ff2ebf" + sha256: c99a6150e4d83a9ad65a591e220262225a112d2ec6a23c77ea68e637ee44c8fe url: "https://pub.dev" source: hosted - version: "0.6.0" + version: "0.6.3" qr: dependency: transitive description: name: qr - sha256: "64957a3930367bf97cc211a5af99551d630f2f4625e38af10edd6b19131b64b3" + sha256: "5a1d2586170e172b8a8c8470bbbffd5eb0cd38a66c0d77155ea138d3af3a4445" url: "https://pub.dev" source: hosted - version: "3.0.1" + version: "3.0.2" qr_flutter: dependency: "direct main" description: @@ -1391,34 +1391,34 @@ packages: dependency: "direct main" description: name: record - sha256: "78353d3d55fa145ffe1db1f63232ad0a0cd4c773e9f7d161210ce796ba1c94f9" + sha256: "4a5cf4d083d1ee49e0878823c4397d073f8eb0a775f31215d388e2bc47a9e867" url: "https://pub.dev" source: hosted - version: "5.1.1" + version: "5.1.2" record_android: dependency: transitive description: name: record_android - sha256: fe83beefc8ac81b9dd02ca9365e8685755e3f12be1d442964082f1d5b618183d + sha256: d7af0b3119725a0f561817c72b5f5eca4d7a76d441deef519ae04e4824c0734c url: "https://pub.dev" source: hosted - version: "1.2.2" + version: "1.2.6" record_darwin: dependency: transitive description: name: record_darwin - sha256: "2210da0fde7c86b4048cccfe2cd19b25fc7adf1ada7d50ec4a5ab4af2a863739" + sha256: fe90d302acb1f3cee1ade5df9c150ca5cee33b48d8cdf1cf433bf577d7f00134 url: "https://pub.dev" source: hosted - version: "1.1.0" + version: "1.1.2" record_linux: dependency: transitive description: name: record_linux - sha256: "7d0e70cd51635128fe9d37d89bafd6011d7cbba9af8dc323079ae60f23546aef" + sha256: "74d41a9ebb1eb498a38e9a813dd524e8f0b4fdd627270bda9756f437b110a3e3" url: "https://pub.dev" source: hosted - version: "0.7.1" + version: "0.7.2" record_platform_interface: dependency: transitive description: @@ -1431,10 +1431,10 @@ packages: dependency: transitive description: name: record_web - sha256: "0ef370d1e6553ad33c39dd03103b374e7861f3518b0533e64c94d73f988a5ffa" + sha256: "656b7a865f90651fab997c2a563364f5fd60a0b527d5dadbb915d62d84fc3867" url: "https://pub.dev" source: hosted - version: "1.1.0" + version: "1.1.3" record_windows: dependency: transitive description: @@ -1447,10 +1447,10 @@ packages: dependency: transitive description: name: rxdart - sha256: "0c7c0cedd93788d996e33041ffecda924cc54389199cde4e6a34b440f50044cb" + sha256: "5c3004a4a8dbb94bd4bf5412a4def4acdaa12e12f269737a5751369e12d1a962" url: "https://pub.dev" source: hosted - version: "0.27.7" + version: "0.28.0" safe_local_storage: dependency: transitive description: @@ -1543,58 +1543,58 @@ packages: dependency: transitive description: name: shared_preferences - sha256: d3bbe5553a986e83980916ded2f0b435ef2e1893dfaa29d5a7a790d0eca12180 + sha256: "746e5369a43170c25816cc472ee016d3a66bc13fcf430c0bc41ad7b4b2922051" url: "https://pub.dev" source: hosted - version: "2.2.3" + version: "2.3.2" shared_preferences_android: dependency: transitive description: name: shared_preferences_android - sha256: "1ee8bf911094a1b592de7ab29add6f826a7331fb854273d55918693d5364a1f2" + sha256: "480ba4345773f56acda9abf5f50bd966f581dac5d514e5fc4a18c62976bbba7e" url: "https://pub.dev" source: hosted - version: "2.2.2" + version: "2.3.2" shared_preferences_foundation: dependency: transitive description: name: shared_preferences_foundation - sha256: "0a8a893bf4fd1152f93fec03a415d11c27c74454d96e2318a7ac38dd18683ab7" + sha256: "07e050c7cd39bad516f8d64c455f04508d09df104be326d8c02551590a0d513d" url: "https://pub.dev" source: hosted - version: "2.4.0" + version: "2.5.3" shared_preferences_linux: dependency: transitive description: name: shared_preferences_linux - sha256: "9f2cbcf46d4270ea8be39fa156d86379077c8a5228d9dfdb1164ae0bb93f1faa" + sha256: "580abfd40f415611503cae30adf626e6656dfb2f0cee8f465ece7b6defb40f2f" url: "https://pub.dev" source: hosted - version: "2.3.2" + version: "2.4.1" shared_preferences_platform_interface: dependency: transitive description: name: shared_preferences_platform_interface - sha256: "22e2ecac9419b4246d7c22bfbbda589e3acf5c0351137d87dd2939d984d37c3b" + sha256: "57cbf196c486bc2cf1f02b85784932c6094376284b3ad5779d1b1c6c6a816b80" url: "https://pub.dev" source: hosted - version: "2.3.2" + version: "2.4.1" shared_preferences_web: dependency: transitive description: name: shared_preferences_web - sha256: "9aee1089b36bd2aafe06582b7d7817fd317ef05fc30e6ba14bff247d0933042a" + sha256: d2ca4132d3946fec2184261726b355836a82c33d7d5b67af32692aff18a4684e url: "https://pub.dev" source: hosted - version: "2.3.0" + version: "2.4.2" shared_preferences_windows: dependency: transitive description: name: shared_preferences_windows - sha256: "841ad54f3c8381c480d0c9b508b89a34036f512482c407e6df7a9c4aa2ef8f59" + sha256: "94ef0f72b2d71bc3e700e025db3710911bd51a71cefb65cc609dd0d9a982e3c1" url: "https://pub.dev" source: hosted - version: "2.3.2" + version: "2.4.1" shelf: dependency: transitive description: @@ -1607,10 +1607,10 @@ packages: dependency: transitive description: name: shelf_web_socket - sha256: "9ca081be41c60190ebcb4766b2486a7d50261db7bd0f5d9615f2d653637a84c1" + sha256: "073c147238594ecd0d193f3456a5fe91c4b0abbcc68bf5cd95b36c4e194ac611" url: "https://pub.dev" source: hosted - version: "1.0.4" + version: "2.0.0" sign_in_button: dependency: "direct main" description: @@ -1716,10 +1716,10 @@ packages: dependency: transitive description: name: sqlite3 - sha256: b384f598b813b347c5a7e5ffad82cbaff1bec3d1561af267041e66f6f0899295 + sha256: fde692580bee3379374af1f624eb3e113ab2865ecb161dbe2d8ac2de9735dbdb url: "https://pub.dev" source: hosted - version: "2.4.3" + version: "2.4.5" stack_trace: dependency: transitive description: @@ -1756,18 +1756,18 @@ packages: dependency: transitive description: name: stripe_android - sha256: "189b8d5c79dfb363540a77e813ba350c9058559673f3df80a2cb87e0983a316e" + sha256: "6d65446ac95b5e66535f38606b51e30961b1a3823cd19253902bc0992d6f3dcc" url: "https://pub.dev" source: hosted - version: "11.0.0" + version: "11.2.1" stripe_ios: dependency: transitive description: name: stripe_ios - sha256: "680d442b2bb920dd91bec4aba9286de050a76a789e9bc128044f2b3732369be6" + sha256: "87444df75265c5e5056c1043a68a27945eb10f9909ea532da8d4d894bedf0c51" url: "https://pub.dev" source: hosted - version: "11.0.0" + version: "11.2.0" stripe_js: dependency: "direct main" description: @@ -1780,10 +1780,10 @@ packages: dependency: transitive description: name: stripe_platform_interface - sha256: "3a4e22f0ad461dc47147601d1215f2a72715c6c67f56fb4b8a3cab4b857b9a41" + sha256: b88542fa430aa716f120bab40ea93f81249f6b5a9fa6aa2176a57e1f03df86bc url: "https://pub.dev" source: hosted - version: "11.0.0" + version: "11.2.0" synchronized: dependency: transitive description: @@ -1820,10 +1820,10 @@ packages: dependency: transitive description: name: timezone - sha256: a6ccda4a69a442098b602c44e61a1e2b4bf6f5516e875bbf0f427d5df14745d5 + sha256: "2236ec079a174ce07434e89fcd3fcda430025eb7692244139a9cf54fdcf1fc7d" url: "https://pub.dev" source: hosted - version: "0.9.3" + version: "0.9.4" timing: dependency: transitive description: @@ -1884,42 +1884,42 @@ packages: dependency: "direct main" description: name: url_launcher - sha256: "21b704ce5fa560ea9f3b525b43601c678728ba46725bab9b01187b4831377ed3" + sha256: "9d06212b1362abc2f0f0d78e6f09f726608c74e3b9462e8368bb03314aa8d603" url: "https://pub.dev" source: hosted - version: "6.3.0" + version: "6.3.1" url_launcher_android: dependency: transitive description: name: url_launcher_android - sha256: "17cd5e205ea615e2c6ea7a77323a11712dffa0720a8a90540db57a01347f9ad9" + sha256: f0c73347dfcfa5b3db8bc06e1502668265d39c08f310c29bff4e28eea9699f79 url: "https://pub.dev" source: hosted - version: "6.3.2" + version: "6.3.9" url_launcher_ios: dependency: transitive description: name: url_launcher_ios - sha256: "7068716403343f6ba4969b4173cbf3b84fc768042124bc2c011e5d782b24fe89" + sha256: e43b677296fadce447e987a2f519dcf5f6d1e527dc35d01ffab4fff5b8a7063e url: "https://pub.dev" source: hosted - version: "6.3.0" + version: "6.3.1" url_launcher_linux: dependency: transitive description: name: url_launcher_linux - sha256: ab360eb661f8879369acac07b6bb3ff09d9471155357da8443fd5d3cf7363811 + sha256: e2b9622b4007f97f504cd64c0128309dfb978ae66adbe944125ed9e1750f06af url: "https://pub.dev" source: hosted - version: "3.1.1" + version: "3.2.0" url_launcher_macos: dependency: transitive description: name: url_launcher_macos - sha256: "9a1a42d5d2d95400c795b2914c36fdcb525870c752569438e4ebb09a2b5d90de" + sha256: "769549c999acdb42b8bcfa7c43d72bf79a382ca7441ab18a808e101149daf672" url: "https://pub.dev" source: hosted - version: "3.2.0" + version: "3.2.1" url_launcher_platform_interface: dependency: transitive description: @@ -1932,26 +1932,26 @@ packages: dependency: transitive description: name: url_launcher_web - sha256: "8d9e750d8c9338601e709cd0885f95825086bd8b642547f26bda435aade95d8a" + sha256: "772638d3b34c779ede05ba3d38af34657a05ac55b06279ea6edd409e323dca8e" url: "https://pub.dev" source: hosted - version: "2.3.1" + version: "2.3.3" url_launcher_windows: dependency: transitive description: name: url_launcher_windows - sha256: ecf9725510600aa2bb6d7ddabe16357691b6d2805f66216a97d1b881e21beff7 + sha256: "49c10f879746271804767cb45551ec5592cdab00ee105c06dddde1a98f73b185" url: "https://pub.dev" source: hosted - version: "3.1.1" + version: "3.1.2" uuid: dependency: "direct main" description: name: uuid - sha256: "83d37c7ad7aaf9aa8e275490669535c8080377cfa7a7004c24dfac53afffaa90" + sha256: a5be9ef6618a7ac1e964353ef476418026db906c4facdedaa299b7a2e71690ff url: "https://pub.dev" source: hosted - version: "4.4.2" + version: "4.5.1" value_layout_builder: dependency: transitive description: @@ -2012,18 +2012,18 @@ packages: dependency: transitive description: name: volume_controller - sha256: "189bdc7a554f476b412e4c8b2f474562b09d74bc458c23667356bce3ca1d48c9" + sha256: c71d4c62631305df63b72da79089e078af2659649301807fa746088f365cb48e url: "https://pub.dev" source: hosted - version: "2.0.7" + version: "2.0.8" wakelock_plus: dependency: transitive description: name: wakelock_plus - sha256: "14758533319a462ffb5aa3b7ddb198e59b29ac3b02da14173a1715d65d4e6e68" + sha256: bf4ee6f17a2fa373ed3753ad0e602b7603f8c75af006d5b9bdade263928c0484 url: "https://pub.dev" source: hosted - version: "1.2.5" + version: "1.2.8" wakelock_plus_platform_interface: dependency: transitive description: @@ -2076,18 +2076,18 @@ packages: dependency: transitive description: name: win32 - sha256: "0eaf06e3446824099858367950a813472af675116bf63f008a4c2a75ae13e9cb" + sha256: "68d1e89a91ed61ad9c370f9f8b6effed9ae5e0ede22a270bdfa6daf79fc2290a" url: "https://pub.dev" source: hosted - version: "5.5.0" + version: "5.5.4" xdg_directories: dependency: transitive description: name: xdg_directories - sha256: faea9dee56b520b55a566385b84f2e8de55e7496104adada9962e0bd11bcff1d + sha256: "7a3f37b05d989967cdddcbb571f1ea834867ae2faa29725fd085180e0883aa15" url: "https://pub.dev" source: hosted - version: "1.0.4" + version: "1.1.0" xml: dependency: transitive description: @@ -2105,5 +2105,5 @@ packages: source: hosted version: "3.1.2" sdks: - dart: ">=3.3.0 <4.0.0" - flutter: ">=3.19.0" + dart: ">=3.4.4 <4.0.0" + flutter: ">=3.22.0" From 740d7297df5f8f200962d2854b91fd63bd11bfcc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=AE=A1=E5=AE=9C=E5=B0=A7?= Date: Tue, 15 Oct 2024 12:17:41 +0800 Subject: [PATCH 07/26] update --- lib/helper/http.dart | 2 +- lib/page/chat/home_chat.dart | 17 +++++++ lib/page/chat/room_chat.dart | 17 +++++++ lib/page/component/chat/chat_preview.dart | 24 +++++++--- lib/page/component/chat/chat_share.dart | 44 ++++++++++-------- lib/page/custom_scaffold.dart | 55 ++++++++++++++--------- lib/page/drawer.dart | 5 ++- lib/page/home.dart | 45 +++++++++++++++++-- macos/Podfile.lock | 6 +-- 9 files changed, 161 insertions(+), 54 deletions(-) diff --git a/lib/helper/http.dart b/lib/helper/http.dart index 66da66ab..126b0620 100644 --- a/lib/helper/http.dart +++ b/lib/helper/http.dart @@ -68,7 +68,7 @@ class HttpClient { .toExtra()), ); // print("======================="); - Logger.instance.d("request: $url [${resp.statusCode}]"); + // Logger.instance.d("request: $url [${resp.statusCode}]"); // print("response: ${resp.data}"); return resp; diff --git a/lib/page/chat/home_chat.dart b/lib/page/chat/home_chat.dart index c74ef47a..182d7220 100644 --- a/lib/page/chat/home_chat.dart +++ b/lib/page/chat/home_chat.dart @@ -615,6 +615,23 @@ class _HomeChatPageState extends State { his: loadedState.chatHistory, alternativeAvatarUrl: currentModelV2?.avatarUrl, ), + senderNameBuilder: (message) { + if (message.senderName == null) { + return null; + } + + return Container( + margin: const EdgeInsets.fromLTRB(0, 0, 10, 7), + padding: const EdgeInsets.symmetric(horizontal: 13), + child: Text( + message.senderName!, + style: TextStyle( + color: customColors.weakTextColor, + fontSize: 12, + ), + ), + ); + }, onDeleteMessage: (id) { handleDeleteMessage(context, id, chatHistoryId: chatId); }, diff --git a/lib/page/chat/room_chat.dart b/lib/page/chat/room_chat.dart index ff0fbb31..30638e61 100644 --- a/lib/page/chat/room_chat.dart +++ b/lib/page/chat/room_chat.dart @@ -400,6 +400,23 @@ class _RoomChatPageState extends State { avatarUrl: room.room.avatarUrl, name: room.room.name, ), + senderNameBuilder: (message) { + if (message.senderName == null) { + return null; + } + + return Container( + margin: const EdgeInsets.fromLTRB(0, 0, 10, 7), + padding: const EdgeInsets.symmetric(horizontal: 13), + child: Text( + message.senderName!, + style: TextStyle( + color: customColors.weakTextColor, + fontSize: 12, + ), + ), + ); + }, onDeleteMessage: (id) { handleDeleteMessage(context, id); }, diff --git a/lib/page/component/chat/chat_preview.dart b/lib/page/component/chat/chat_preview.dart index 1f97856b..a3d068e5 100644 --- a/lib/page/component/chat/chat_preview.dart +++ b/lib/page/component/chat/chat_preview.dart @@ -267,25 +267,35 @@ class _ChatPreviewState extends State { ), child: FileUploadPreview(images: message.images ?? []), ), - Row( + Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, children: [ // 消息头像 - buildAvatar(message), + Container( + margin: const EdgeInsets.only(left: 10), + child: Row( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.end, + children: [ + buildAvatar(message), + // 发送人名称 + if (message.role == Role.receiver && + widget.senderNameBuilder != null) + widget.senderNameBuilder!(message) ?? const SizedBox(), + ], + ), + ), + const SizedBox(height: 10), // 消息内容部分 ConstrainedBox( constraints: BoxConstraints( - maxWidth: _chatBoxMaxWidth(context) - 80, + maxWidth: _chatBoxMaxWidth(context) - 30, ), child: Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, children: [ - // 发送人名称 - if (message.role == Role.receiver && - widget.senderNameBuilder != null) - widget.senderNameBuilder!(message) ?? const SizedBox(), Wrap( crossAxisAlignment: WrapCrossAlignment.end, children: [ diff --git a/lib/page/component/chat/chat_share.dart b/lib/page/component/chat/chat_share.dart index b9e545bd..9561c8e2 100644 --- a/lib/page/component/chat/chat_share.dart +++ b/lib/page/component/chat/chat_share.dart @@ -300,6 +300,8 @@ class _ChatShareScreenState extends State { crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.end, children: [ if (message.avatarURL != null && message.leftSide) _buildAvatar(avatarUrl: message.avatarURL), @@ -378,15 +380,34 @@ class _ChatShareScreenState extends State { child: ConstrainedBox( constraints: BoxConstraints(maxWidth: _chatBoxMaxWidth(context)), - child: Row( + child: Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, children: [ - if (message.avatarURL != null && message.leftSide) - _buildAvatar(avatarUrl: message.avatarURL), + Row( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.end, + children: [ + if (message.avatarURL != null && message.leftSide) + _buildAvatar(avatarUrl: message.avatarURL), + if (message.username != null && message.leftSide) + Container( + margin: const EdgeInsets.fromLTRB(0, 0, 10, 7), + padding: const EdgeInsets.symmetric(horizontal: 13), + child: Text( + message.username!, + style: TextStyle( + color: customColors.weakTextColor, + fontSize: 12, + ), + ), + ), + ], + ), + const SizedBox(height: 10), ConstrainedBox( constraints: BoxConstraints( - maxWidth: _chatBoxMaxWidth(context) - 80, + maxWidth: _chatBoxMaxWidth(context) - 30, ), child: Column( mainAxisSize: MainAxisSize.min, @@ -406,22 +427,9 @@ class _ChatShareScreenState extends State { images: message.images ?? []), ), ), - if (message.username != null && message.leftSide) - Container( - margin: const EdgeInsets.fromLTRB(0, 0, 10, 7), - padding: - const EdgeInsets.symmetric(horizontal: 13), - child: Text( - message.username!, - style: TextStyle( - color: customColors.weakTextColor, - fontSize: 12, - ), - ), - ), Container( margin: message.leftSide - ? const EdgeInsets.fromLTRB(10, 0, 0, 7) + ? const EdgeInsets.fromLTRB(0, 0, 0, 7) : const EdgeInsets.fromLTRB(0, 0, 10, 7), decoration: BoxDecoration( borderRadius: BorderRadius.circular(10), diff --git a/lib/page/custom_scaffold.dart b/lib/page/custom_scaffold.dart index 0889e5ef..63b4db20 100644 --- a/lib/page/custom_scaffold.dart +++ b/lib/page/custom_scaffold.dart @@ -10,6 +10,9 @@ class CustomScaffold extends StatefulWidget { final Widget body; final Widget? drawer; final Widget? appBarBackground; + final AppBar? backAppBar; + final bool showBackAppBar; + const CustomScaffold({ super.key, required this.settings, @@ -18,6 +21,8 @@ class CustomScaffold extends StatefulWidget { required this.body, this.drawer, this.appBarBackground, + this.backAppBar, + this.showBackAppBar = false, }); @override @@ -31,31 +36,39 @@ class _CustomScaffoldState extends State { setting: widget.settings, maxWidth: double.infinity, child: Scaffold( - appBar: AppBar( - title: widget.title, - centerTitle: true, - toolbarHeight: CustomSize.toolbarHeight, - elevation: 0, - actions: widget.actions, - flexibleSpace: SizedBox( - width: double.infinity, - child: ShaderMask( - shaderCallback: (rect) { - return const LinearGradient( - begin: Alignment.topCenter, - end: Alignment.bottomCenter, - colors: [Colors.black, Colors.transparent], - ).createShader(Rect.fromLTRB(0, 0, rect.width, rect.height)); - }, - blendMode: BlendMode.dstIn, - child: widget.appBarBackground, - ), - ), - ), + appBar: buildAppBar(), backgroundColor: Colors.transparent, body: widget.body, drawer: widget.drawer, ), ); } + + AppBar? buildAppBar() { + if (widget.showBackAppBar && widget.backAppBar != null) { + return widget.backAppBar; + } + + return AppBar( + title: widget.title, + centerTitle: true, + toolbarHeight: CustomSize.toolbarHeight, + elevation: 0, + actions: widget.actions, + flexibleSpace: SizedBox( + width: double.infinity, + child: ShaderMask( + shaderCallback: (rect) { + return const LinearGradient( + begin: Alignment.topCenter, + end: Alignment.bottomCenter, + colors: [Colors.black, Colors.transparent], + ).createShader(Rect.fromLTRB(0, 0, rect.width, rect.height)); + }, + blendMode: BlendMode.dstIn, + child: widget.appBarBackground, + ), + ), + ); + } } diff --git a/lib/page/drawer.dart b/lib/page/drawer.dart index cf29f27b..960f8c03 100644 --- a/lib/page/drawer.dart +++ b/lib/page/drawer.dart @@ -1,5 +1,6 @@ import 'package:askaide/bloc/account_bloc.dart'; import 'package:askaide/bloc/chat_chat_bloc.dart'; +import 'package:askaide/helper/platform.dart'; import 'package:askaide/lang/lang.dart'; import 'package:askaide/page/component/account_quota_card.dart'; import 'package:askaide/page/component/icon_box.dart'; @@ -34,7 +35,9 @@ class _LeftDrawerState extends State { child: Column( children: [ DrawerHeader( - padding: const EdgeInsets.all(0), + padding: PlatformTool.isMacOS() + ? const EdgeInsets.only(top: kToolbarHeight) + : const EdgeInsets.all(0), decoration: BoxDecoration( color: Colors.white, image: DecorationImage( diff --git a/lib/page/home.dart b/lib/page/home.dart index d21285f2..7dd77787 100644 --- a/lib/page/home.dart +++ b/lib/page/home.dart @@ -231,6 +231,26 @@ class _NewHomePageState extends State { customColors.appBarBackgroundImage!, fit: BoxFit.cover, ), + showBackAppBar: chatPreviewController.selectMode, + backAppBar: AppBar( + title: Text( + AppLocale.select.getString(context), + style: const TextStyle(fontSize: CustomSize.appBarTitleSize), + ), + centerTitle: true, + elevation: 0, + leadingWidth: 80, + leading: TextButton( + onPressed: () { + chatPreviewController.exitSelectMode(); + }, + child: Text( + AppLocale.cancel.getString(context), + style: TextStyle(color: customColors.linkColor), + ), + ), + toolbarHeight: CustomSize.toolbarHeight, + ), // 标题,点击后弹出模型选择对话框 title: GestureDetector( onTap: () { @@ -502,15 +522,17 @@ class _NewHomePageState extends State { } } + enableImageUpload = selectedModel == null + ? enableImageUpload + : (selectedModel?.supportVision ?? false); + return ChatInput( enableNotifier: enableInput, onSubmit: (value) { handleSubmit(value); FocusManager.instance.primaryFocus?.unfocus(); }, - enableImageUpload: selectedModel == null - ? enableImageUpload - : (selectedModel?.supportVision ?? false), + enableImageUpload: enableImageUpload, onImageSelected: (files) { setState(() { selectedImageFiles = files; @@ -618,6 +640,23 @@ class _NewHomePageState extends State { his: loadedState.chatHistory, alternativeAvatarUrl: currentModelV2?.avatarUrl, ), + senderNameBuilder: (message) { + if (message.senderName == null) { + return null; + } + + return Container( + margin: const EdgeInsets.fromLTRB(0, 0, 10, 7), + padding: const EdgeInsets.symmetric(horizontal: 13), + child: Text( + message.senderName!, + style: TextStyle( + color: customColors.weakTextColor, + fontSize: 12, + ), + ), + ); + }, onDeleteMessage: (id) { handleDeleteMessage(context, id, chatHistoryId: chatId); }, diff --git a/macos/Podfile.lock b/macos/Podfile.lock index 4f1695ca..b8b33157 100644 --- a/macos/Podfile.lock +++ b/macos/Podfile.lock @@ -119,15 +119,15 @@ SPEC CHECKSUMS: media_kit_libs_macos_video: b3e2bbec2eef97c285f2b1baa7963c67c753fb82 media_kit_native_event_loop: 81fd5b45192b72f8b5b69eaf5b540f45777eb8d5 media_kit_video: c75b07f14d59706c775778e4dd47dd027de8d1e5 - package_info_plus: fa739dd842b393193c5ca93c26798dff6e3d0e0c + package_info_plus: d2f71247aab4b6521434f887276093acc70d214c path_provider_foundation: 2b6b4c569c0fb62ec74538f866245ac84301af46 - record_darwin: 1f6619f2abac4d1ca91d3eeab038c980d76f1517 + record_darwin: df0a677188e5fed18472550298e675f19ddaffbe screen_brightness_macos: 2d6d3af2165592d9a55ffcd95b7550970e41ebda share_plus: 76dd39142738f7a68dd57b05093b5e8193f220f7 shared_preferences_foundation: fcdcbc04712aee1108ac7fda236f363274528f78 sign_in_with_apple: a9e97e744e8edc36aefc2723111f652102a7a727 sqflite: 673a0e54cc04b7d6dba8d24fb8095b31c3a99eec - url_launcher_macos: 5f437abeda8c85500ceb03f5c1938a8c5a705399 + url_launcher_macos: c82c93949963e55b228a30115bd219499a6fe404 wakelock_plus: 4783562c9a43d209c458cb9b30692134af456269 PODFILE CHECKSUM: 38f6b47b8cb10a0771c5d71f5d300e8d2bb9b8d7 From 39769249bc850fd85b5201c385fc6fd223aa4254 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=AE=A1=E5=AE=9C=E5=B0=A7?= Date: Mon, 2 Dec 2024 14:06:46 +0800 Subject: [PATCH 08/26] Select model from digital humans on the homepage --- lib/helper/model.dart | 4 +- lib/page/component/chat/role_avatar.dart | 2 +- lib/page/home.dart | 51 ++++++++++++------------ lib/repo/api_server.dart | 5 ++- 4 files changed, 32 insertions(+), 30 deletions(-) diff --git a/lib/helper/model.dart b/lib/helper/model.dart index e899ef7d..178ab165 100644 --- a/lib/helper/model.dart +++ b/lib/helper/model.dart @@ -13,14 +13,14 @@ class ModelAggregate { } /// 支持的模型列表 - static Future> models() async { + static Future> models({bool cache = true}) async { final List models = []; final isAPIServerSet = settings.stringDefault(settingAPIServerToken, '') != ''; final selfHostOpenAI = settings.boolDefault(settingOpenAISelfHosted, false); if (isAPIServerSet) { - models.addAll((await APIServer().models()) + models.addAll((await APIServer().models(cache: cache)) .map( (e) => mm.Model( e.id.split(':').last, diff --git a/lib/page/component/chat/role_avatar.dart b/lib/page/component/chat/role_avatar.dart index 2e3b3142..69b0532e 100644 --- a/lib/page/component/chat/role_avatar.dart +++ b/lib/page/component/chat/role_avatar.dart @@ -45,7 +45,7 @@ class _RoleAvatarState extends State { return FutureBuilder( future: ModelAggregate.models(), builder: (context, snapshot) { - if (!snapshot.hasError) { + if (!snapshot.hasError && snapshot.hasData) { var mod = snapshot.data! .where((e) => e.id == widget.his!.model!) .firstOrNull; diff --git a/lib/page/home.dart b/lib/page/home.dart index 7dd77787..21a7bd0b 100644 --- a/lib/page/home.dart +++ b/lib/page/home.dart @@ -2,7 +2,6 @@ import 'package:askaide/bloc/account_bloc.dart'; import 'package:askaide/bloc/chat_chat_bloc.dart'; import 'package:askaide/bloc/chat_message_bloc.dart'; import 'package:askaide/bloc/free_count_bloc.dart'; -import 'package:askaide/bloc/notify_bloc.dart'; import 'package:askaide/bloc/room_bloc.dart'; import 'package:askaide/helper/ability.dart'; import 'package:askaide/helper/cache.dart'; @@ -96,7 +95,7 @@ class _NewHomePageState extends State { reloadPage(loadAll: true); }); - reloadModels(); + reloadModels(cache: false); initListeners(); } @@ -121,31 +120,30 @@ class _NewHomePageState extends State { } /// 加载模型列表,用于查询模型名称 - void reloadModels() { - ModelAggregate.models().then((value) { - setState(() { - supportModels = value; - }); + Future reloadModels({bool cache = true}) async { + var value = await ModelAggregate.models(cache: cache); + setState(() { + supportModels = value; + }); - Cache().stringGet(key: 'last_selected_model').then((value) { - final selected = supportModels.where((e) => e.id == value).firstOrNull; - if (selected != null) { - setState(() { - selectedModel = selected; - }); - } + var cacheValue = await Cache().stringGet(key: 'last_selected_model'); - if (selectedModel == null && supportModels.isNotEmpty) { - setState(() { - selectedModel = supportModels.first; - }); - } + final selected = supportModels.where((e) => e.id == cacheValue).firstOrNull; + if (selected != null) { + setState(() { + selectedModel = selected; + }); + } - if (selectedModel != null) { - loadCurrentModel(selectedModel!.id); - } + if (selectedModel == null && supportModels.isNotEmpty) { + setState(() { + selectedModel = supportModels.first; }); - }); + } + + if (selectedModel != null) { + loadCurrentModel(selectedModel!.id); + } } void initListeners() { @@ -253,8 +251,11 @@ class _NewHomePageState extends State { ), // 标题,点击后弹出模型选择对话框 title: GestureDetector( - onTap: () { + onTap: () async { + await reloadModels(cache: false); + ModelSwitcher.openActionDialog( + // ignore: use_build_context_synchronously context: context, onSelected: (selected) { setState(() { @@ -741,7 +742,7 @@ class _NewHomePageState extends State { text, user: 'me', ts: DateTime.now(), - model: selectedModel!.id, + model: selectedModel?.id, type: messagetType, chatHistoryId: chatId, images: selectedImageFiles diff --git a/lib/repo/api_server.dart b/lib/repo/api_server.dart index 3ab783d5..1695f426 100644 --- a/lib/repo/api_server.dart +++ b/lib/repo/api_server.dart @@ -496,9 +496,9 @@ class APIServer { } /// 获取模型列表 - Future> models() async { + Future> models({bool cache = true}) async { return sendCachedGetRequest( - '/v1/models', + '/v2/models', (resp) { var models = []; for (var model in resp.data) { @@ -508,6 +508,7 @@ class APIServer { return models; }, subKey: _cacheSubKey(), + forceRefresh: !cache, ); } From ad99eb8a5fd38ad50e1b4f3fa1d67d875d82fb99 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=AE=A1=E5=AE=9C=E5=B0=A7?= Date: Tue, 3 Dec 2024 18:17:46 +0800 Subject: [PATCH 09/26] Optimize English language and solve some interface bugs --- lib/helper/model.dart | 11 +- lib/helper/model_resolver.dart | 14 +- lib/lang/lang.dart | 172 +++++++++++--- lib/main.dart | 4 - lib/page/admin/models.dart | 44 ++-- lib/page/balance/free_statistics.dart | 59 ++--- lib/page/chat/component/room_item.dart | 37 +-- lib/page/chat/group/chat.dart | 80 ++----- lib/page/chat/group/edit.dart | 76 ++---- lib/page/chat/home.dart | 181 ++++---------- lib/page/chat/home_chat.dart | 220 ++++++------------ lib/page/chat/home_chat_history.dart | 167 +++++++------ lib/page/chat/room_chat.dart | 186 +++++---------- lib/page/chat/room_create.dart | 115 ++++----- lib/page/chat/room_edit.dart | 118 +++------- lib/page/chat/rooms.dart | 91 ++------ lib/page/component/avatar_selector.dart | 20 +- lib/page/component/chat/empty.dart | 19 +- lib/page/component/column_block.dart | 24 +- lib/page/component/model_item.dart | 86 +++---- lib/page/component/social_icon.dart | 8 +- .../creative_island/draw/draw_create.dart | 184 +++++---------- .../draw/image_edit_direct.dart | 4 +- lib/page/data/chat_history_datasource.dart | 6 +- lib/page/drawer.dart | 22 +- lib/page/home.dart | 218 ++++++----------- lib/page/setting/account_security.dart | 132 +++++------ lib/page/setting/article.dart | 15 +- lib/page/setting/background_selector.dart | 48 ++-- lib/page/setting/change_password.dart | 17 +- lib/page/setting/custom_home_models.dart | 33 +-- lib/page/setting/destroy_account.dart | 18 +- lib/page/setting/diagnosis.dart | 42 ++-- lib/page/setting/notification.dart | 29 ++- lib/page/setting/setting_screen.dart | 117 +++------- lib/page/setting/user_api_keys.dart | 5 +- lib/repo/chat_message_repo.dart | 8 +- lib/repo/data/chat_history.dart | 35 ++- lib/repo/model/misc.dart | 36 ++- lib/repo/model/model.dart | 5 + 40 files changed, 1063 insertions(+), 1643 deletions(-) diff --git a/lib/helper/model.dart b/lib/helper/model.dart index 178ab165..3d19674d 100644 --- a/lib/helper/model.dart +++ b/lib/helper/model.dart @@ -15,8 +15,7 @@ class ModelAggregate { /// 支持的模型列表 static Future> models({bool cache = true}) async { final List models = []; - final isAPIServerSet = - settings.stringDefault(settingAPIServerToken, '') != ''; + final isAPIServerSet = settings.stringDefault(settingAPIServerToken, '') != ''; final selfHostOpenAI = settings.boolDefault(settingOpenAISelfHosted, false); if (isAPIServerSet) { @@ -37,6 +36,7 @@ class ModelAggregate { tagTextColor: e.tagTextColor, tagBgColor: e.tagBgColor, isNew: e.isNew, + isDefault: e.isDefault, ), ) .toList()); @@ -45,9 +45,7 @@ class ModelAggregate { if (selfHostOpenAI) { return [ ...OpenAIRepository.supportModels(), - ...models - .where((element) => element.category != modelTypeOpenAI) - .toList() + ...models.where((element) => element.category != modelTypeOpenAI).toList() ]; } @@ -72,8 +70,7 @@ class ModelAggregate { final supportModels = await models(); return supportModels.firstWhere( (element) => element.uid() == uid || element.id == uid, - orElse: () => mm.Model(defaultChatModel, defaultChatModel, 'openai', - category: modelTypeOpenAI), + orElse: () => mm.Model(defaultChatModel, defaultChatModel, 'openai', category: modelTypeOpenAI), ); } } diff --git a/lib/helper/model_resolver.dart b/lib/helper/model_resolver.dart index 587bab63..3ae5ef54 100644 --- a/lib/helper/model_resolver.dart +++ b/lib/helper/model_resolver.dart @@ -118,10 +118,10 @@ class ModelResolver { onMessage('\n![image]($data)\n'); } } else if (res.status == 'failed') { - throw '响应失败: ${res.errors!.join("\n")}'; + throw 'Response failed: ${res.errors!.join("\n")}'; } else { if (retry > 10) { - throw '响应超时'; + throw 'Response timeout'; } await Future.delayed(const Duration(seconds: 5)); @@ -140,8 +140,7 @@ class ModelResolver { var res = await deepAIRepo.painting(room.modelName(), message.text); onMessage('\n![${res.id}](${res.url})\n'); } else { - var taskId = - await deepAIRepo.paintingAsync(room.modelName(), message.text); + var taskId = await deepAIRepo.paintingAsync(room.modelName(), message.text); await Future.delayed(const Duration(seconds: 10)); await _waitForTasks(taskId, onMessage); } @@ -186,8 +185,8 @@ class ModelResolver { // .where((e) => e.ts!.millisecondsSinceEpoch > lastAliveTime()) // .toList(); var recentMessages = messages.toList(); - int contextBreakIndex = recentMessages.lastIndexWhere((element) => - element.isSystem() && element.type == MessageType.contextBreak); + int contextBreakIndex = + recentMessages.lastIndexWhere((element) => element.isSystem() && element.type == MessageType.contextBreak); if (contextBreakIndex > -1) { recentMessages = recentMessages.sublist(contextBreakIndex + 1); @@ -212,8 +211,7 @@ class ModelResolver { .toList(); if (contextMessages.length > room.maxContext * 2) { - contextMessages = - contextMessages.sublist(contextMessages.length - room.maxContext * 2); + contextMessages = contextMessages.sublist(contextMessages.length - room.maxContext * 2); } if (room.systemPrompt != null && room.systemPrompt != '') { diff --git a/lib/lang/lang.dart b/lib/lang/lang.dart index 8bd52422..2396d8c6 100644 --- a/lib/lang/lang.dart +++ b/lib/lang/lang.dart @@ -80,6 +80,7 @@ mixin AppLocale { static const String model = "model"; static const String selectModel = "select-model"; static const String roomName = "room-name"; + static const String avatar = "avatar"; static const String iconName = "icon-name"; static const String prompt = "prompt"; static const String optional = "optional"; @@ -131,8 +132,7 @@ mixin AppLocale { static const String generateResult = 'generate-result'; static const String generateExitConfirm = 'generate-exit-confirm'; static const String tooManyRequests = 'too-many-requests'; - static const String tooManyRequestsOrPaymentRequired = - 'too-many-requests-or-payment-required'; + static const String tooManyRequestsOrPaymentRequired = 'too-many-requests-or-payment-required'; static const String promptHint = 'prompt-hint'; static const String confirmClearCache = 'confirm-clear-cache'; static const String confirmSignOut = 'confirm-sign-out'; @@ -151,8 +151,7 @@ mixin AppLocale { static const String referenceImage = 'reference-image'; static const String selectImage = 'select-image'; static const String imagination = 'imagination'; - static const String keywordsSeparatedByCommas = - 'keywords-separated-by-commas'; + static const String keywordsSeparatedByCommas = 'keywords-separated-by-commas'; static const String originalImage = 'original-image'; static const String superResolution = 'super-resolution'; static const String colorizeImage = 'colorize-image'; @@ -160,8 +159,7 @@ mixin AppLocale { static const String report = 'report'; static const String latestVersion = 'latest-version'; static const String aIdeaApp = 'aidea-app'; - static const String onceEnabledSmartOptimization = - 'once-enabled-smart-optimization'; + static const String onceEnabledSmartOptimization = 'once-enabled-smart-optimization'; static const String gotIt = 'got-it'; static const String referenceImageNote = 'reference-image-note'; static const String selectReferenceImage = 'select-reference-image'; @@ -203,6 +201,9 @@ mixin AppLocale { static const String passwordResetOK = 'password-reset-ok'; static const String resetPassword = 'reset-password'; static const String bindPhone = 'bind-phone'; + static const String bind = 'bind'; + static const String bound = 'bond'; + static const String unbind = 'unbind'; static const String inviteCode = 'invite-code'; static const String inviteCodeInputTips = 'invite-code-input-tips'; static const String inviteCodeFormatError = 'invite-code-format-error'; @@ -226,9 +227,45 @@ mixin AppLocale { static const String visionTag = 'vision-tag'; static const String newTag = 'new-tag'; + static const String imageUploading = 'image-uploading'; static const String uploadImageLimit4 = 'upload-image-limit-4'; static const String confirmStopOutput = 'confirm-stop-output'; + static const String stopOutput = 'stop-output'; + static const String opensource = 'opensource'; + static const String socialMedia = 'social-media'; + static const String unset = 'unset'; + static const String nickname = 'nickname'; + static const String setNickname = 'set-nickname'; + static const String inputYourNickname = 'input-your-nickname'; + static const String reset = 'reset'; + static const String deleteAccount = 'delete-account'; + static const String confirmDeleteAccount = 'confirm-delete-account'; + static const String wechatAccount = 'wechat-account'; + static const String modifyPassword = 'modify-password'; + static const String setPassword = 'set-password'; + static const String installWeChat = 'install-wechat'; + static const String freeQuota = 'free-quota'; + static const String serviceStatus = 'service-status'; + static const String lab = 'lab'; + static const String todayLeft = 'today-left'; + static const String freeModelNeedSignIn = 'free-model-need-sign-in'; + static const String noFreeModel = 'no-free-model'; + static const String freeModelInfo = 'free-model-info'; + static const String notification = 'notification'; + static const String selectMember = 'select-member'; + static const String members = 'members'; + static const String createGroupChat = 'create-group-chat'; + static const String advanced = 'advanced'; + static const String collapseOptions = 'collapse-options'; + static const String welcomeMessage = 'welcome-message'; + static const String welcomeMessageTips = 'welcome-message-tips'; + static const String memoryDepth = 'memory-depth'; + static const String robotRecommand = 'robot-recommand'; + static const String pickYourRobot = 'pick-your-robot'; + static const String viewMore = 'view-more'; + static const String using = 'using'; + static const Map zh = { required: '必填', systemInfo: '系统信息', @@ -296,6 +333,7 @@ mixin AppLocale { model: 'AI 模型', selectModel: '选择模型', roomName: '名称', + avatar: '头像', iconName: '图标', prompt: '角色设定', optional: '可选', @@ -355,8 +393,7 @@ mixin AppLocale { generateResult: '创作结果', generateExitConfirm: '创作中...\n退出后,可在历史记录中查看结果', tooManyRequests: '操作过于频繁,请稍后再试', - tooManyRequestsOrPaymentRequired: - '操作过于频繁(如果您使用了自定义的 OpenAI Keys,请登录 https://platform.openai.com 检查账户余额是否充足)', + tooManyRequestsOrPaymentRequired: '操作过于频繁(如果您使用了自定义的 OpenAI Keys,请登录 https://platform.openai.com 检查账户余额是否充足)', promptHint: '设定该数字人的角色和技能,以便为你提供更精准有效的信息。', confirmClearCache: '确定要清除缓存吗?', confirmSignOut: '确定要退出登录吗?', @@ -409,7 +446,7 @@ mixin AppLocale { passwordRequired: '请输入密码', passwordFormatError: '密码格式有误\n必须为8-20位字母、数字、特殊符号组合', accountCreated: '账号创建成功', - sendVerifyCode: '发送验证码', + sendVerifyCode: '发送', verifyCode: '验证码', verifyCodeInputTips: '输入验证码', retryInSeconds: '秒后重试', @@ -423,6 +460,9 @@ mixin AppLocale { passwordResetOK: '密码已重置,请重新登录', resetPassword: '重置密码', bindPhone: '绑定手机', + bind: '绑定', + bound: '已绑定', + unbind: '解绑', inviteCode: '邀请码', inviteCodeInputTips: '输入好友邀请码,获额外奖励(非必填)', inviteCodeFormatError: '邀请码格式有误', @@ -442,8 +482,43 @@ mixin AppLocale { recentlyUsed: '最近使用', visionTag: '视觉', newTag: '新', + imageUploading: '正在上传图片,请稍后...', uploadImageLimit4: '最多只能上传 4 张图片', confirmStopOutput: '确定要停止当前输出?', + stopOutput: '停止输出', + opensource: '本项目开源,欢迎贡献', + socialMedia: '关注我们', + unset: '未设置', + nickname: '昵称', + setNickname: '设置昵称', + inputYourNickname: '请输入你的昵称', + reset: '重置', + deleteAccount: '删除账号', + confirmDeleteAccount: '确定要删除账号', + wechatAccount: '微信账号', + modifyPassword: '修改密码', + setPassword: '设置密码', + installWeChat: '请先安装微信后再使用该功能', + freeQuota: '免费畅享额度', + serviceStatus: '服务状态', + lab: '实验室', + todayLeft: '今日可用', + freeModelNeedSignIn: '免费模型需登录账号后使用', + noFreeModel: '当前无可用的免费模型。', + freeModelInfo: '以下模型享有每日免费额度。', + notification: '通知', + selectMember: '选择本次对话成员', + members: '成员', + createGroupChat: '创建群聊', + advanced: '高级选项', + collapseOptions: '收起选项', + welcomeMessage: '引导语', + welcomeMessageTips: '每次开始新对话时,系统将会以 AI 的身份自动发送引导语。', + memoryDepth: '记忆深度', + robotRecommand: '热门推荐', + pickYourRobot: '挑选你的专属伙伴', + viewMore: '查看更多', + using: '使用中', }; static const Map en = { @@ -480,8 +555,7 @@ mixin AppLocale { text: 'Text', uploading: 'Uploading...', robotIsThinkingMessage: 'Thinking...', - robotHasSomeError: - 'There seems to be something wrong, Do you want to resend the message?', + robotHasSomeError: 'There seems to be something wrong, Do you want to resend the message?', appName: 'AIdea', chatAnywhere: 'Chat', homeTitle: 'Characters', @@ -514,6 +588,7 @@ mixin AppLocale { model: 'AI Model', selectModel: 'Select Model', roomName: 'Name', + avatar: 'Avatar', iconName: 'Icon', prompt: 'Prompt', optional: 'Optional', @@ -553,10 +628,8 @@ mixin AppLocale { modelNotValid: 'The current model is not open', signInRequired: 'You are not logged in, please log in first', accountNeedReSignin: 'Account exception, please log in again', - openAIAuthFailed: - 'You have enabled custom OpenAI service, please check if the API Key is correct', - modelNotFound: - 'The current model is not enabled yet, please try again later', + openAIAuthFailed: 'You have enabled custom OpenAI service, please check if the API Key is correct', + modelNotFound: 'The current model is not enabled yet, please try again later', confirmToDeleteRoom: 'Confirm to delete the character?', writeYourIdeas: 'Your ideas', describeYourImages: 'Your ideas', @@ -574,8 +647,7 @@ mixin AppLocale { generateTimeout: 'Generate timeout, please try again later', creativeIslandNeedSignIn: 'Unlock more features after login', generateResult: 'Generate result', - generateExitConfirm: - 'Generating...\nYou can view the result in the history', + generateExitConfirm: 'Generating...\nYou can view the result in the history', tooManyRequests: 'Too many requests, please try again later', tooManyRequestsOrPaymentRequired: 'Too many requests (If you are using your own OpenAI Keys, please log in to https://platform.openai.com to check if your account balance is sufficient)', @@ -583,7 +655,7 @@ mixin AppLocale { 'Set the role and skills of the character so that it can provide more accurate and effective information for you.', confirmClearCache: 'Confirm to clear cache?', confirmSignOut: 'Confirm to sign out?', - askMeAnyQuestion: 'Feel free to ask me any questions ~', + askMeAnyQuestion: 'Feel free to ask ~', askMeLikeThis: 'You can ask me like this:', refresh: 'Refresh', fastAndCostEffective: 'Fast & Cost-Effective', @@ -599,21 +671,17 @@ mixin AppLocale { referenceImage: 'Reference Image', selectImage: 'Select Image', imagination: 'Imagination', - keywordsSeparatedByCommas: - 'Keywords of the scene you imagine, separated by commas', + keywordsSeparatedByCommas: 'Keywords of the scene you imagine, separated by commas', originalImage: 'Original Image', superResolution: 'Super-Resolution', colorizeImage: 'Colorize Image', errorLog: 'Error Log', report: 'Report', latestVersion: 'You are currently on the latest version', - aIdeaApp: - 'AIdea is an app that allows you to converse with AI, and the app code is completely open source.', - onceEnabledSmartOptimization: - 'Smart Optimization\n\nOnce enabled, AI will further refine and optimize your ideas.', + aIdeaApp: 'AIdea is an app that allows you to converse with AI, and the app code is completely open source.', + onceEnabledSmartOptimization: 'Smart Optimization\n\nOnce enabled, AI will further refine and optimize your ideas.', gotIt: 'Got it', - referenceImageNote: - 'Reference Image\n\nAI will create based on the reference image provided.', + referenceImageNote: 'Reference Image\n\nAI will create based on the reference image provided.', selectReferenceImage: 'Please select a reference image', random: 'Random', followSystem: 'Follow System', @@ -628,19 +696,16 @@ mixin AppLocale { accountInputTips: 'Enter your phone number or email', phoneInputTips: 'Enter your phone number', passwordInputTips: 'Enter your password', - pleaseReadAgreeProtocol: - 'Please read and agree to the user agreement and privacy policy first', + pleaseReadAgreeProtocol: 'Please read and agree to the user agreement and privacy policy first', signInSuccess: 'Sign in success', signInFailed: 'Sign in failed', accountRequired: 'Please enter your account', - accountFormatError: - 'Account format error\nPlease enter your phone number or email', + accountFormatError: 'Account format error\nPlease enter your phone number or email', phoneNumberFormatError: 'Phone number format error', passwordRequired: 'Please enter your password', - passwordFormatError: - 'Password format error\nMust be 8-20 digits, letters, special characters', + passwordFormatError: 'Password format error\nMust be 8-20 digits, letters, special characters', accountCreated: 'Account created', - sendVerifyCode: 'Send verify code', + sendVerifyCode: 'Send', verifyCode: 'Verify code', verifyCodeInputTips: 'Enter verify code', retryInSeconds: 'Retry in', @@ -654,9 +719,11 @@ mixin AppLocale { passwordResetOK: 'Password has been reset, please log in again', resetPassword: 'Reset password', bindPhone: 'Bind phone', + bind: 'Bind', + bound: 'Bound', + unbind: 'Unbind', inviteCode: 'Invite code', - inviteCodeInputTips: - 'Enter friend invite code, get extra rewards (optional)', + inviteCodeInputTips: 'Enter friend invite code, get extra rewards (optional)', inviteCodeFormatError: 'Invite code format error', enableCustomOpenAI: 'Your custom OpenAI service will be used once enabled', me: 'Me', @@ -674,8 +741,43 @@ mixin AppLocale { recentlyUsed: 'Recently Used', visionTag: 'Vision', newTag: 'New', + imageUploading: 'Uploading image, please wait...', uploadImageLimit4: 'You can only upload up to 4 images', confirmStopOutput: 'Are you sure you want to stop current output?', + stopOutput: 'Stop Output', + opensource: 'Open Source', + socialMedia: 'Follow us', + unset: 'Unset', + nickname: 'Nickname', + setNickname: 'Set Nickname', + inputYourNickname: 'Input your nickname', + reset: 'Reset', + deleteAccount: 'Delete Account', + confirmDeleteAccount: 'Confirm to delete account', + wechatAccount: 'WeChat Account', + modifyPassword: 'Modify Password', + setPassword: 'Set Password', + installWeChat: 'Please install WeChat first', + freeQuota: 'Free Quota', + serviceStatus: 'Service Status', + lab: 'Lab', + todayLeft: 'Today Available', + freeModelNeedSignIn: 'Free model requires login to use', + noFreeModel: 'No free model available.', + freeModelInfo: 'The following models have daily free quotas.', + notification: 'Notification', + selectMember: 'Select member', + members: 'Members', + createGroupChat: 'Create group chat', + advanced: 'Advanced', + collapseOptions: 'Collapse', + welcomeMessage: 'Welcome Message', + welcomeMessageTips: 'The system will automatically send a welcome message each time a new chat is started.', + memoryDepth: 'Memory Depth', + robotRecommand: 'Recommand', + pickYourRobot: 'Pick your robot', + viewMore: 'More', + using: 'Using', }; } diff --git a/lib/main.dart b/lib/main.dart index d086a6c1..95ba0044 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -340,7 +340,6 @@ class MyApp extends StatefulWidget { BlocProvider.value(value: galleryBloc), BlocProvider.value(value: accountBloc), BlocProvider.value(value: versionBloc), - BlocProvider.value(value: freeCountBloc), ], child: NewHomePage( settings: settingRepo, @@ -409,7 +408,6 @@ class MyApp extends StatefulWidget { providers: [ BlocProvider( create: (context) => ChatChatBloc(chatMsgRepo)), - BlocProvider.value(value: freeCountBloc), ], child: HomePage( setting: settingRepo, @@ -497,7 +495,6 @@ class MyApp extends StatefulWidget { ), BlocProvider.value(value: chatRoomBloc), BlocProvider(create: (context) => NotifyBloc()), - BlocProvider.value(value: freeCountBloc), ], child: HomeChatPage( stateManager: messageStateManager, @@ -568,7 +565,6 @@ class MyApp extends StatefulWidget { ), BlocProvider.value(value: chatRoomBloc), BlocProvider(create: (context) => NotifyBloc()), - BlocProvider.value(value: freeCountBloc), ], child: RoomChatPage( roomId: roomId, diff --git a/lib/page/admin/models.dart b/lib/page/admin/models.dart index 55029a43..85605e10 100644 --- a/lib/page/admin/models.dart +++ b/lib/page/admin/models.dart @@ -130,8 +130,7 @@ class _AdminModelsPageState extends State { }, displacement: 20, child: BlocConsumer( - listenWhen: (previous, current) => - current is ModelOperationResult, + listenWhen: (previous, current) => current is ModelOperationResult, listener: (context, state) { if (state is ModelOperationResult) { if (state.success) { @@ -148,15 +147,9 @@ class _AdminModelsPageState extends State { final models = state.models .where((e) => keyword == '' || - e.name - .toLowerCase() - .contains(keyword.toLowerCase()) || - e.modelId - .toLowerCase() - .contains(keyword.toLowerCase()) || - (e.description ?? '') - .toLowerCase() - .contains(keyword.toLowerCase())) + e.name.toLowerCase().contains(keyword.toLowerCase()) || + e.modelId.toLowerCase().contains(keyword.toLowerCase()) || + (e.description ?? '').toLowerCase().contains(keyword.toLowerCase())) .toList(); return SafeArea( top: false, @@ -217,9 +210,7 @@ class _AdminModelsPageState extends State { openConfirmDialog( context, AppLocale.confirmToDeleteRoom.getString(context), - () => context - .read() - .add(ModelDeleteEvent(mod.modelId)), + () => context.read().add(ModelDeleteEvent(mod.modelId)), danger: true, ); }, @@ -227,17 +218,12 @@ class _AdminModelsPageState extends State { ], ), child: Material( - borderRadius: - BorderRadius.all(Radius.circular(customColors.borderRadius ?? 8)), + borderRadius: BorderRadius.all(Radius.circular(customColors.borderRadius ?? 8)), color: customColors.columnBlockBackgroundColor, child: InkWell( - borderRadius: BorderRadius.all( - Radius.circular(customColors.borderRadius ?? 8)), + borderRadius: BorderRadius.all(Radius.circular(customColors.borderRadius ?? 8)), onTap: () { - context - .push( - '/admin/models/edit/${Uri.encodeComponent(mod.modelId)}') - .then((value) { + context.push('/admin/models/edit/${Uri.encodeComponent(mod.modelId)}').then((value) { context.read().add(ModelsLoadEvent()); }); }, @@ -262,18 +248,18 @@ class _AdminModelsPageState extends State { padding: const EdgeInsets.all(3), width: 80, color: Colors.black.withAlpha(30), - child: const Row( + child: Row( mainAxisAlignment: MainAxisAlignment.center, children: [ - Icon( + const Icon( Icons.remove_red_eye_outlined, color: Colors.white, size: 12, ), - SizedBox(width: 3), + const SizedBox(width: 3), Text( - '视觉', - style: TextStyle( + AppLocale.visionTag.getString(context), + style: const TextStyle( color: Colors.white, fontSize: 12, ), @@ -324,9 +310,7 @@ class _AdminModelsPageState extends State { crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( - mod.providers - .map((e) => searchChannel(e).display) - .join('|'), + mod.providers.map((e) => searchChannel(e).display).join('|'), style: TextStyle( fontSize: 10, overflow: TextOverflow.ellipsis, diff --git a/lib/page/balance/free_statistics.dart b/lib/page/balance/free_statistics.dart index 6fb63366..41c28282 100644 --- a/lib/page/balance/free_statistics.dart +++ b/lib/page/balance/free_statistics.dart @@ -27,9 +27,7 @@ class _FreeStatisticsPageState extends State { @override void initState() { super.initState(); - context - .read() - .add(FreeCountReloadAllEvent(checkSigninStatus: true)); + context.read().add(FreeCountReloadAllEvent(checkSigninStatus: true)); } @override @@ -39,9 +37,9 @@ class _FreeStatisticsPageState extends State { return Scaffold( appBar: AppBar( toolbarHeight: CustomSize.toolbarHeight, - title: const Text( - '免费畅享额度', - style: TextStyle(fontSize: CustomSize.appBarTitleSize), + title: Text( + AppLocale.freeQuota.getString(context), + style: const TextStyle(fontSize: CustomSize.appBarTitleSize), ), centerTitle: true, ), @@ -61,16 +59,15 @@ class _FreeStatisticsPageState extends State { child: SingleChildScrollView( physics: const AlwaysScrollableScrollPhysics(), child: BlocConsumer( - listenWhen: (previous, current) => - current is FreeCountLoadedState, + listenWhen: (previous, current) => current is FreeCountLoadedState, listener: (BuildContext context, FreeCountState state) { if (state is FreeCountLoadedState) { if (state.needSignin) { showBeautyDialog( context, type: QuickAlertType.warning, - text: '免费模型需登录账号后使用', - confirmBtnText: '去登录', + text: AppLocale.freeModelNeedSignIn.getString(context), + confirmBtnText: AppLocale.signIn.getString(context), onConfirmBtnTap: () { context.pop(); context.go('/login'); @@ -83,11 +80,11 @@ class _FreeStatisticsPageState extends State { builder: (context, state) { if (state is FreeCountLoadedState) { if (state.counts.isEmpty) { - return const Padding( - padding: EdgeInsets.symmetric(horizontal: 10), + return Padding( + padding: const EdgeInsets.symmetric(horizontal: 10), child: Center( child: MessageBox( - message: '当前无可用的免费模型。', + message: AppLocale.noFreeModel.getString(context), type: MessageBoxType.warning, ), ), @@ -97,21 +94,21 @@ class _FreeStatisticsPageState extends State { padding: const EdgeInsets.symmetric(horizontal: 10), child: Column( children: [ - const MessageBox( - message: '以下模型享有每日免费额度。', + MessageBox( + message: AppLocale.freeModelInfo.getString(context), type: MessageBoxType.info, ), const SizedBox(height: 10), ColumnBlock( innerPanding: 5, children: [ - const Padding( - padding: EdgeInsets.symmetric(vertical: 5), + Padding( + padding: const EdgeInsets.symmetric(vertical: 5), child: Row( children: [ - Expanded( + const Expanded( child: Text( - '模型', + 'Model', style: TextStyle( fontWeight: FontWeight.bold, fontSize: 14, @@ -120,8 +117,8 @@ class _FreeStatisticsPageState extends State { Row( children: [ Text( - '今日可用', - style: TextStyle( + AppLocale.todayLeft.getString(context), + style: const TextStyle( fontWeight: FontWeight.bold, fontSize: 14, ), @@ -133,8 +130,7 @@ class _FreeStatisticsPageState extends State { ), ...state.counts.map((e) { return Padding( - padding: - const EdgeInsets.symmetric(vertical: 5), + padding: const EdgeInsets.symmetric(vertical: 5), child: Row( children: [ Expanded( @@ -146,29 +142,22 @@ class _FreeStatisticsPageState extends State { fontSize: 14, ), ), - if (e.info != null && - e.info != '') - const SizedBox(width: 5), - if (e.info != null && - e.info != '') + if (e.info != null && e.info != '') const SizedBox(width: 5), + if (e.info != null && e.info != '') InkWell( onTap: () { showBeautyDialog( context, type: QuickAlertType.info, text: e.info ?? '', - confirmBtnText: AppLocale - .gotIt - .getString(context), + confirmBtnText: AppLocale.gotIt.getString(context), showCancelBtn: false, ); }, child: Icon( Icons.help_outline, size: 16, - color: customColors - .weakLinkColor - ?.withAlpha(150), + color: customColors.weakLinkColor?.withAlpha(150), ), ), ], @@ -202,7 +191,7 @@ class _FreeStatisticsPageState extends State { Widget buildLeftCountWidget({required int leftCount, required int maxCount}) { return Text( - '$leftCount 次', + '$leftCount', style: const TextStyle( fontSize: 14, ), diff --git a/lib/page/chat/component/room_item.dart b/lib/page/chat/component/room_item.dart index c47736dd..59cf77ea 100644 --- a/lib/page/chat/component/room_item.dart +++ b/lib/page/chat/component/room_item.dart @@ -38,22 +38,18 @@ class RoomItem extends StatelessWidget { children: [ const SizedBox(width: 10), SlidableAction( - label: '设置', + label: AppLocale.settings.getString(context), backgroundColor: Colors.green, borderRadius: room.category == 'system' - ? BorderRadius.all( - Radius.circular(customColors.borderRadius ?? 8)) + ? BorderRadius.all(Radius.circular(customColors.borderRadius ?? 8)) : BorderRadius.only( topLeft: Radius.circular(customColors.borderRadius ?? 8), - bottomLeft: - Radius.circular(customColors.borderRadius ?? 8), + bottomLeft: Radius.circular(customColors.borderRadius ?? 8), ), icon: Icons.settings, onPressed: (_) { final chatRoomBloc = context.read(); - final redirectUrl = room.roomType == 4 - ? '/group-chat/${room.id}/edit' - : '/room/${room.id}/setting'; + final redirectUrl = room.roomType == 4 ? '/group-chat/${room.id}/edit' : '/room/${room.id}/setting'; context.push(redirectUrl).then((value) { chatRoomBloc.add(RoomsLoadEvent()); @@ -73,8 +69,7 @@ class RoomItem extends StatelessWidget { openConfirmDialog( context, AppLocale.confirmToDeleteRoom.getString(context), - () => - context.read().add(RoomDeleteEvent(room.id!)), + () => context.read().add(RoomDeleteEvent(room.id!)), danger: true, ); }, @@ -82,16 +77,12 @@ class RoomItem extends StatelessWidget { ], ), child: Material( - borderRadius: - BorderRadius.all(Radius.circular(customColors.borderRadius ?? 8)), + borderRadius: BorderRadius.all(Radius.circular(customColors.borderRadius ?? 8)), color: customColors.columnBlockBackgroundColor, child: InkWell( - borderRadius: BorderRadius.all( - Radius.circular(customColors.borderRadius ?? 8)), + borderRadius: BorderRadius.all(Radius.circular(customColors.borderRadius ?? 8)), onTap: () { - final redirectRoute = room.roomType == 4 - ? '/group-chat/${room.id}/chat' - : '/room/${room.id}/chat'; + final redirectRoute = room.roomType == 4 ? '/group-chat/${room.id}/chat' : '/room/${room.id}/chat'; HapticFeedbackHelper.lightImpact(); final chatRoomBloc = context.read(); context.push(redirectRoute).then((value) { @@ -122,8 +113,7 @@ class RoomItem extends StatelessWidget { Text( humanTime(room.lastActiveTime), style: TextStyle( - color: customColors.weakLinkColor - ?.withAlpha(65), + color: customColors.weakLinkColor?.withAlpha(65), fontSize: 10, ), ), @@ -149,8 +139,7 @@ class RoomItem extends StatelessWidget { bottomLeft: Radius.circular(8), ), ), - padding: const EdgeInsets.symmetric( - horizontal: 8, vertical: 2), + padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 2), child: Text( '群聊', style: TextStyle( @@ -172,8 +161,7 @@ class RoomItem extends StatelessWidget { bottomLeft: Radius.circular(8), ), ), - padding: const EdgeInsets.symmetric( - horizontal: 8, vertical: 2), + padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 2), child: Text( 'local', style: TextStyle( @@ -230,8 +218,7 @@ class RoomItem extends StatelessWidget { } Widget _buildAvatar(Room room) { - if (room.members.length == 1 && - (room.avatarUrl == null || room.avatarUrl == '')) { + if (room.members.length == 1 && (room.avatarUrl == null || room.avatarUrl == '')) { room.avatarUrl = room.members[0]; } diff --git a/lib/page/chat/group/chat.dart b/lib/page/chat/group/chat.dart index 3e38a1d4..142ac3d1 100644 --- a/lib/page/chat/group/chat.dart +++ b/lib/page/chat/group/chat.dart @@ -49,8 +49,7 @@ class _GroupChatPageState extends State { final ScrollController _scrollController = ScrollController(); final ValueNotifier _inputEnabled = ValueNotifier(true); final ChatPreviewController _chatPreviewController = ChatPreviewController(); - final AudioPlayerController _audioPlayerController = - AudioPlayerController(useRemoteAPI: true); + final AudioPlayerController _audioPlayerController = AudioPlayerController(useRemoteAPI: true); bool showAudioPlayer = false; bool audioLoadding = false; @@ -114,18 +113,15 @@ class _GroupChatPageState extends State { Widget _buildChatComponents(CustomColors customColors) { return BlocConsumer( - listenWhen: (previous, current) => - current is GroupChatLoaded || current is GroupDefaultMemberSelected, + listenWhen: (previous, current) => current is GroupChatLoaded || current is GroupDefaultMemberSelected, listener: (context, state) { if (state is GroupChatLoaded) { // 加载聊天记录列表 - context.read().add( - GroupChatMessagesLoadEvent(widget.groupId, isInitRequest: true)); + context.read().add(GroupChatMessagesLoadEvent(widget.groupId, isInitRequest: true)); // 选中默认的聊天成员 - selectedMembers = state.group.members - .where((e) => state.defaultChatMembers?.contains(e.id) ?? false) - .toList(); + selectedMembers = + state.group.members.where((e) => state.defaultChatMembers?.contains(e.id) ?? false).toList(); setState(() { group = state.group; @@ -135,9 +131,7 @@ class _GroupChatPageState extends State { if (state is GroupDefaultMemberSelected) { // 选中默认的聊天成员 if (group != null) { - selectedMembers = group?.members - .where((e) => state.members.contains(e.id)) - .toList(); + selectedMembers = group?.members.where((e) => state.members.contains(e.id)).toList(); } } }, @@ -149,8 +143,7 @@ class _GroupChatPageState extends State { bottom: false, child: Column( children: [ - if (Ability().showGlobalAlert) - const GlobalAlert(pageKey: 'chat'), + if (Ability().showGlobalAlert) const GlobalAlert(pageKey: 'chat'), // 语音输出中提示 if (showAudioPlayer) EnhancedAudioPlayer( @@ -190,7 +183,7 @@ class _GroupChatPageState extends State { FocusManager.instance.primaryFocus?.unfocus(); }, onNewChat: () => handleResetContext(context), - hintText: '有问题尽管问我', + hintText: AppLocale.askMeAnyQuestion.getString(context), onVoiceRecordTappedEvent: () { _audioPlayerController.stop(); }, @@ -212,23 +205,19 @@ class _GroupChatPageState extends State { }, child: Icon( Icons.alternate_email, - color: selectedMembers != null && - selectedMembers!.isNotEmpty + color: selectedMembers != null && selectedMembers!.isNotEmpty ? customColors.linkColor : customColors.chatInputPanelText, ), ), ), - if (selectedMembers != null && - selectedMembers!.isNotEmpty) + if (selectedMembers != null && selectedMembers!.isNotEmpty) Positioned( right: 2, top: 0, child: Container( - padding: const EdgeInsets.symmetric( - horizontal: 3, vertical: 3), - child: Text( - 'x${selectedMembers!.length}', + padding: const EdgeInsets.symmetric(horizontal: 3, vertical: 3), + child: Text('x${selectedMembers!.length}', style: TextStyle( fontSize: 7, color: customColors.linkColor, @@ -266,7 +255,7 @@ class _GroupChatPageState extends State { Container( padding: const EdgeInsets.only(top: 15, left: 20), child: Text( - '选择本次对话成员', + AppLocale.selectMember.getString(context), style: TextStyle( fontSize: 14, color: customColors.weakLinkColor, @@ -278,9 +267,7 @@ class _GroupChatPageState extends State { itemBuilder: (item) { return Text(item.modelName); }, - items: groupState.group.members - .where((e) => e.status != 2) - .toList(), + items: groupState.group.members.where((e) => e.status != 2).toList(), onChanged: (selected) { setState(() { selectedMembers = selected; @@ -341,9 +328,7 @@ class _GroupChatPageState extends State { // 启动定时器,定时刷新聊天记录 timer ??= Timer.periodic(const Duration(seconds: 3), (timer) { - context - .read() - .add(GroupChatUpdateMessageStatusEvent(widget.groupId)); + context.read().add(GroupChatUpdateMessageStatusEvent(widget.groupId)); }); } }, @@ -358,8 +343,7 @@ class _GroupChatPageState extends State { } final loadedMessages = state.messages.map((e) { - var member = - e.memberId != null ? group.group.findMember(e.memberId!) : null; + var member = e.memberId != null ? group.group.findMember(e.memberId!) : null; return Message( id: e.id, @@ -378,9 +362,7 @@ class _GroupChatPageState extends State { final messages = loadedMessages.map((e) { return MessageWithState( e, - group.states[ - widget.stateManager.getKey(e.roomId ?? 0, e.id ?? 0)] ?? - MessageState(), + group.states[widget.stateManager.getKey(e.roomId ?? 0, e.id ?? 0)] ?? MessageState(), ); }).toList(); @@ -432,9 +414,7 @@ class _GroupChatPageState extends State { _audioPlayerController.playAudio(message.text); }, onResentEvent: (message, index) { - _scrollController.animateTo(0, - duration: const Duration(milliseconds: 500), - curve: Curves.easeOut); + _scrollController.animateTo(0, duration: const Duration(milliseconds: 500), curve: Curves.easeOut); _handleSubmit(message.text, index: index, isResent: true); }, helpWidgets: state.hasWaitTasks || loadedMessages.isEmpty @@ -544,9 +524,7 @@ class _GroupChatPageState extends State { context, AppLocale.confirmDelete.getString(context), () { - context - .read() - .add(GroupChatDeleteEvent(widget.groupId, id)); + context.read().add(GroupChatDeleteEvent(widget.groupId, id)); HapticFeedbackHelper.mediumImpact(); }, danger: true, @@ -569,9 +547,7 @@ class _GroupChatPageState extends State { context, AppLocale.confirmClearMessages.getString(context), () { - context - .read() - .add(GroupChatDeleteAllEvent(widget.groupId)); + context.read().add(GroupChatDeleteAllEvent(widget.groupId)); HapticFeedbackHelper.mediumImpact(); }, danger: true, @@ -600,8 +576,7 @@ class _GroupChatPageState extends State { onPressed: () { var messages = chatPreviewController.selectedMessages(); if (messages.isEmpty) { - showErrorMessageEnhanced( - context, AppLocale.noMessageSelected.getString(context)); + showErrorMessageEnhanced(context, AppLocale.noMessageSelected.getString(context)); return; } @@ -646,8 +621,7 @@ class _GroupChatPageState extends State { onPressed: () { chatPreviewController.selectAllMessage(); }, - icon: - Icon(Icons.select_all_outlined, color: customColors.linkColor), + icon: Icon(Icons.select_all_outlined, color: customColors.linkColor), label: Text( AppLocale.selectAll.getString(context), style: TextStyle(color: customColors.linkColor), @@ -656,8 +630,7 @@ class _GroupChatPageState extends State { TextButton.icon( onPressed: () { if (chatPreviewController.selectedMessageIds.isEmpty) { - showErrorMessageEnhanced( - context, AppLocale.noMessageSelected.getString(context)); + showErrorMessageEnhanced(context, AppLocale.noMessageSelected.getString(context)); return; } @@ -671,8 +644,7 @@ class _GroupChatPageState extends State { // .read() // .add(ChatMessageDeleteEvent(ids)); - showErrorMessageEnhanced( - context, AppLocale.operateSuccess.getString(context)); + showErrorMessageEnhanced(context, AppLocale.operateSuccess.getString(context)); chatPreviewController.exitSelectMode(); } @@ -725,9 +697,7 @@ class _GroupChatPageState extends State { iconColor: customColors.linkColor, onTap: (_) { context.push('/group-chat/$chatRoomId/edit').whenComplete(() { - context - .read() - .add(GroupChatLoadEvent(widget.groupId, forceUpdate: true)); + context.read().add(GroupChatLoadEvent(widget.groupId, forceUpdate: true)); }); }, ), diff --git a/lib/page/chat/group/edit.dart b/lib/page/chat/group/edit.dart index b8eb3c8b..978760fd 100644 --- a/lib/page/chat/group/edit.dart +++ b/lib/page/chat/group/edit.dart @@ -90,9 +90,7 @@ class _GroupEditPageState extends State { setState(() { models = value; }); - context - .read() - .add(GroupChatLoadEvent(widget.groupId, forceUpdate: true)); + context.read().add(GroupChatLoadEvent(widget.groupId, forceUpdate: true)); }); } @@ -120,21 +118,17 @@ class _GroupEditPageState extends State { setting: widget.setting, enabled: false, child: BlocListener( - listenWhen: (previous, current) => - current is GroupRoomUpdateResultState, + listenWhen: (previous, current) => current is GroupRoomUpdateResultState, listener: (context, state) { if (state is GroupRoomUpdateResultState) { globalLoadingCancel?.call(); if (state.success) { showSuccessMessage(AppLocale.operateSuccess.getString(context)); } else { - showErrorMessageEnhanced(context, - state.error ?? AppLocale.operateFailed.getString(context)); + showErrorMessageEnhanced(context, state.error ?? AppLocale.operateFailed.getString(context)); } - context - .read() - .add(GroupChatLoadEvent(widget.groupId, forceUpdate: true)); + context.read().add(GroupChatLoadEvent(widget.groupId, forceUpdate: true)); } }, child: BlocConsumer( @@ -146,9 +140,7 @@ class _GroupEditPageState extends State { selectedModels = state.group.members .where((e) => e.status != 2) .map((e) { - final mod = models - .where((em) => e.modelId == em.realModelId) - .firstOrNull; + final mod = models.where((em) => e.modelId == em.realModelId).firstOrNull; if (mod == null) { return null; } @@ -159,13 +151,9 @@ class _GroupEditPageState extends State { .map((e) => e!) .toList(); - final selectedModelIds = - selectedModels.map((e) => e.model.realModelId).toList(); + final selectedModelIds = selectedModels.map((e) => e.model.realModelId).toList(); - models = models - .where((e) => - !e.disabled || selectedModelIds.contains(e.realModelId)) - .toList(); + models = models.where((e) => !e.disabled || selectedModelIds.contains(e.realModelId)).toList(); } }, builder: (context, state) { @@ -184,7 +172,7 @@ class _GroupEditPageState extends State { maxLength: 50, maxLines: 1, showCounter: false, - labelText: '名称', + labelText: AppLocale.roomName.getString(context), labelPosition: LabelPosition.left, hintText: AppLocale.required.getString(context), textDirection: TextDirection.rtl, @@ -192,7 +180,7 @@ class _GroupEditPageState extends State { EnhancedInput( padding: const EdgeInsets.only(top: 10, bottom: 5), title: Text( - '头像', + AppLocale.avatar.getString(context), style: TextStyle( color: customColors.textfieldLabelColor, fontSize: 16, @@ -211,10 +199,8 @@ class _GroupEditPageState extends State { ? null : DecorationImage( image: (_avatarUrl!.startsWith('http') - ? CachedNetworkImageProviderEnhanced( - _avatarUrl!) - : FileImage(File( - _avatarUrl!))) as ImageProvider, + ? CachedNetworkImageProviderEnhanced(_avatarUrl!) + : FileImage(File(_avatarUrl!))) as ImageProvider, fit: BoxFit.cover, ), ), @@ -259,7 +245,7 @@ class _GroupEditPageState extends State { EnhancedInput( padding: const EdgeInsets.only(top: 10, bottom: 5), title: Text( - '成员', + AppLocale.members.getString(context), style: TextStyle( color: customColors.textfieldLabelColor, fontSize: 16, @@ -272,8 +258,7 @@ class _GroupEditPageState extends State { Stack( children: [ Container( - width: resolveSelectedModelsPreviewWidth( - context), + width: resolveSelectedModelsPreviewWidth(context), height: 45, decoration: BoxDecoration( borderRadius: BorderRadius.circular(8), @@ -290,8 +275,7 @@ class _GroupEditPageState extends State { padding: const EdgeInsets.all(3), decoration: BoxDecoration( color: customColors.tagsBackground, - borderRadius: - BorderRadius.circular(8), + borderRadius: BorderRadius.circular(8), ), child: Text( 'x${selectedModels.length}', @@ -316,12 +300,7 @@ class _GroupEditPageState extends State { }, items: models .map((e) => ModelWithMemberId( - e, - selectedModels - .where( - (se) => se.model.id == e.id) - .firstOrNull - ?.memberId)) + e, selectedModels.where((se) => se.model.id == e.id).firstOrNull?.memberId)) .toList(), onChanged: (selected) { setState(() { @@ -349,11 +328,8 @@ class _GroupEditPageState extends State { Expanded( child: EnhancedButton( title: AppLocale.save.getString(context), - color: - canSubmit() ? null : customColors.weakTextColor, - backgroundColor: canSubmit() - ? null - : customColors.weakTextColor!.withAlpha(20), + color: canSubmit() ? null : customColors.weakTextColor, + backgroundColor: canSubmit() ? null : customColors.weakTextColor!.withAlpha(20), onPressed: () async { if (!canSubmit()) { return; @@ -362,8 +338,7 @@ class _GroupEditPageState extends State { globalLoadingCancel = BotToast.showCustomLoading( toastBuilder: (cancel) { return LoadingIndicator( - message: AppLocale.processingWait - .getString(context), + message: AppLocale.processingWait.getString(context), ); }, allowClick: false, @@ -379,23 +354,20 @@ class _GroupEditPageState extends State { try { if (_avatarUrl != null) { - if (!(_avatarUrl!.startsWith('http://') || - _avatarUrl!.startsWith('https://'))) { + if (!(_avatarUrl!.startsWith('http://') || _avatarUrl!.startsWith('https://'))) { // 上传文件,获取 URL final cancel = BotToast.showCustomLoading( toastBuilder: (cancel) { - return const LoadingIndicator( - message: "正在上传图片,请稍后...", + return LoadingIndicator( + message: AppLocale.imageUploading.getString(context), ); }, allowClick: false, ); - final uploadRes = - await ImageUploader(widget.setting) - .upload(_avatarUrl!, - usage: 'avatar') - .whenComplete(() => cancel()); + final uploadRes = await ImageUploader(widget.setting) + .upload(_avatarUrl!, usage: 'avatar') + .whenComplete(() => cancel()); _avatarUrl = uploadRes.url; } } diff --git a/lib/page/chat/home.dart b/lib/page/chat/home.dart index b3748c1b..ccceaadf 100644 --- a/lib/page/chat/home.dart +++ b/lib/page/chat/home.dart @@ -2,7 +2,6 @@ import 'dart:io'; import 'dart:math'; import 'package:askaide/bloc/chat_chat_bloc.dart'; -import 'package:askaide/bloc/free_count_bloc.dart'; import 'package:askaide/helper/ability.dart'; import 'package:askaide/helper/color.dart'; import 'package:askaide/helper/global_store.dart'; @@ -91,8 +90,7 @@ class _HomePageState extends State { id: 'openai:gpt-4', supportVision: false, name: 'Chat-4', - avatarUrl: - 'https://ssl.aicode.cc/ai-server/assets/avatar/gpt4-preview.png', + avatarUrl: 'https://ssl.aicode.cc/ai-server/assets/avatar/gpt4-preview.png', ), ]; @@ -110,8 +108,7 @@ class _HomePageState extends State { /// 用于监听键盘事件,实现回车发送消息,Shift+Enter换行 late final FocusNode _focusNode = FocusNode( onKeyEvent: (node, event) { - if (!HardwareKeyboard.instance.isShiftPressed && - event.logicalKey.keyLabel == 'Enter') { + if (!HardwareKeyboard.instance.isShiftPressed && event.logicalKey.keyLabel == 'Enter') { if (event is KeyDownEvent) { onSubmit(context, _textController.text.trim()); } @@ -144,13 +141,6 @@ class _HomePageState extends State { models = cap.homeModels; if (mounted) { - // 加载免费模型剩余使用次数 - if (currentModel != null && currentModel!.model.modelId != null) { - context - .read() - .add(FreeCountReloadEvent(model: currentModel!.model.modelId!)); - } - setState(() {}); } } @@ -160,14 +150,12 @@ class _HomePageState extends State { Cache().boolGet(key: 'show_home_free_model_message').then((show) async { if (show) { final promotions = await APIServer().notificationPromotionEvents(); - if (promotions['chat_page'] == null || - promotions['chat_page']!.isEmpty) { + if (promotions['chat_page'] == null || promotions['chat_page']!.isEmpty) { return; } // 多个促销事件,则随机选择一个 - promotionEvent = promotions['chat_page']![ - Random().nextInt(promotions['chat_page']!.length)]; + promotionEvent = promotions['chat_page']![Random().nextInt(promotions['chat_page']!.length)]; } setState(() { @@ -191,8 +179,7 @@ class _HomePageState extends State { showBeautyDialog( context, type: QuickAlertType.info, - text: - '恭喜您,账号创建成功!${(widget.reward != null && widget.reward! > 0) ? '\n\n为了庆祝这一时刻,我们向您的账户赠送了 ${widget.reward} 个智慧果。' : ''}', + text: '恭喜您,账号创建成功!${(widget.reward != null && widget.reward! > 0) ? '\n\n为了庆祝这一时刻,我们向您的账户赠送了 ${widget.reward} 个智慧果。' : ''}', confirmBtnText: '开始使用', onConfirmBtnTap: () { context.pop(); @@ -252,8 +239,7 @@ class _HomePageState extends State { child: Scaffold( backgroundColor: Colors.transparent, body: BlocBuilder( - buildWhen: (previous, current) => - current is ChatChatRecentHistoriesLoaded, + buildWhen: (previous, current) => current is ChatChatRecentHistoriesLoaded, builder: (context, state) { if (state is ChatChatRecentHistoriesLoaded) { return SliverSingleComponent( @@ -269,9 +255,7 @@ class _HomePageState extends State { icon: const Icon(Icons.history), onPressed: () { context.push('/chat-chat/history').whenComplete(() { - context - .read() - .add(ChatChatLoadRecentHistories()); + context.read().add(ChatChatLoadRecentHistories()); }); }, ), @@ -285,8 +269,7 @@ class _HomePageState extends State { SliverStickyHeader( header: SafeArea( top: false, - child: - buildChatComponents(customColors, context, state), + child: buildChatComponents(customColors, context, state), ), sliver: SliverList( delegate: SliverChildBuilderDelegate( @@ -296,13 +279,11 @@ class _HomePageState extends State { top: false, bottom: false, child: Container( - margin: - const EdgeInsets.only(top: 10, left: 15), + margin: const EdgeInsets.only(top: 10, left: 15), child: Text( AppLocale.histories.getString(context), style: TextStyle( - color: customColors.weakTextColor - ?.withAlpha(100), + color: customColors.weakTextColor?.withAlpha(100), fontSize: 13, ), ), @@ -316,41 +297,32 @@ class _HomePageState extends State { bottom: false, child: GestureDetector( onTap: () { - context - .push('/chat-chat/history') - .whenComplete(() { - context - .read() - .add(ChatChatLoadRecentHistories()); + context.push('/chat-chat/history').whenComplete(() { + context.read().add(ChatChatLoadRecentHistories()); }); }, child: Container( alignment: Alignment.center, - margin: const EdgeInsets.only( - top: 5, bottom: 15), + margin: const EdgeInsets.only(top: 5, bottom: 15), child: Row( - mainAxisAlignment: - MainAxisAlignment.center, + mainAxisAlignment: MainAxisAlignment.center, children: [ Icon( Icons.keyboard_double_arrow_left, size: 12, - color: customColors.weakTextColor! - .withAlpha(120), + color: customColors.weakTextColor!.withAlpha(120), ), Text( "查看更多", style: TextStyle( fontSize: 12, - color: customColors.weakTextColor! - .withAlpha(120), + color: customColors.weakTextColor!.withAlpha(120), ), ), Icon( Icons.keyboard_double_arrow_right, size: 12, - color: customColors.weakTextColor! - .withAlpha(120), + color: customColors.weakTextColor!.withAlpha(120), ), ], ), @@ -370,19 +342,14 @@ class _HomePageState extends State { .push( '/chat-anywhere?chat_id=${state.histories[index - 1].id}&model=${state.histories[index - 1].model}&title=${state.histories[index - 1].title}') .whenComplete(() { - FocusScope.of(context) - .requestFocus(FocusNode()); - context - .read() - .add(ChatChatLoadRecentHistories()); + FocusScope.of(context).requestFocus(FocusNode()); + context.read().add(ChatChatLoadRecentHistories()); }); }, ), ); }, - childCount: state.histories.isNotEmpty - ? state.histories.length + 1 - : 0, + childCount: state.histories.isNotEmpty ? state.histories.length + 1 : 0, ), ), ), @@ -441,16 +408,9 @@ class _HomePageState extends State { duration: const Duration(milliseconds: 300), curve: Curves.easeInToLinear, onValueChanged: (value) { - currentModel = indicators[value]; - - // 重新读取模型的免费使用次数 - if (currentModel != null && - currentModel!.model.modelId != null) { - context.read().add(FreeCountReloadEvent( - model: currentModel!.model.modelId!)); - } - - setState(() {}); + setState(() { + currentModel = indicators[value]; + }); }, ), ), @@ -506,8 +466,7 @@ class _HomePageState extends State { customColors: customColors, maxLines: inputMaxLines, minLines: 6, - hintText: - AppLocale.askMeAnyQuestion.getString(context), + hintText: AppLocale.askMeAnyQuestion.getString(context), maxLength: 150000, showCounter: false, hintColor: customColors.textfieldHintDeepColor, @@ -524,9 +483,7 @@ class _HomePageState extends State { customColors, ), ), - if (selectedImageFiles.isNotEmpty && - currentModel != null && - currentModel!.model.supportVision) + if (selectedImageFiles.isNotEmpty && currentModel != null && currentModel!.model.supportVision) SizedBox( height: 110, child: ListView( @@ -566,10 +523,8 @@ class _HomePageState extends State { child: Container( padding: const EdgeInsets.all(3), decoration: BoxDecoration( - borderRadius: - BorderRadius.circular(10), - color: customColors - .chatRoomBackground, + borderRadius: BorderRadius.circular(10), + color: customColors.chatRoomBackground, ), child: Icon( Icons.close, @@ -592,15 +547,12 @@ class _HomePageState extends State { ), ), // 问题示例 - if (state.examples != null && - state.examples!.isNotEmpty && - state.histories.isEmpty) + if (state.examples != null && state.examples!.isNotEmpty && state.histories.isEmpty) Container( decoration: BoxDecoration( borderRadius: BorderRadius.circular(10), ), - padding: - const EdgeInsets.only(top: 5, left: 10, right: 10, bottom: 3), + padding: const EdgeInsets.only(top: 5, left: 10, right: 10, bottom: 3), margin: const EdgeInsets.all(10), height: 260, child: Column( @@ -629,9 +581,7 @@ class _HomePageState extends State { Expanded( child: ListView.separated( padding: const EdgeInsets.all(0), - itemCount: state.examples!.length > 4 - ? 4 - : state.examples!.length, + itemCount: state.examples!.length > 4 ? 4 : state.examples!.length, physics: const NeverScrollableScrollPhysics(), itemBuilder: (context, index) { return ListTextItem( @@ -647,8 +597,7 @@ class _HomePageState extends State { }, separatorBuilder: (BuildContext context, int index) { return Divider( - color: - customColors.chatExampleItemText?.withAlpha(20), + color: customColors.chatExampleItemText?.withAlpha(20), ); }, ), @@ -657,8 +606,7 @@ class _HomePageState extends State { alignment: Alignment.centerRight, child: TextButton( style: ButtonStyle( - overlayColor: - WidgetStateProperty.all(Colors.transparent), + overlayColor: WidgetStateProperty.all(Colors.transparent), ), onPressed: () { setState(() { @@ -732,23 +680,19 @@ class _HomePageState extends State { maxLines: 2, ), ), - if (promotionEvent!.clickButtonType != - PromotionEventClickButtonType.none && + if (promotionEvent!.clickButtonType != PromotionEventClickButtonType.none && promotionEvent!.clickValue != null && promotionEvent!.clickValue!.isNotEmpty) InkWell( onTap: () { switch (promotionEvent!.clickButtonType) { case PromotionEventClickButtonType.url: - if (promotionEvent!.clickValue != null && - promotionEvent!.clickValue!.isNotEmpty) { - launchUrlString(promotionEvent!.clickValue!, - mode: LaunchMode.externalApplication); + if (promotionEvent!.clickValue != null && promotionEvent!.clickValue!.isNotEmpty) { + launchUrlString(promotionEvent!.clickValue!, mode: LaunchMode.externalApplication); } break; case PromotionEventClickButtonType.inAppRoute: - if (promotionEvent!.clickValue != null && - promotionEvent!.clickValue!.isNotEmpty) { + if (promotionEvent!.clickValue != null && promotionEvent!.clickValue!.isNotEmpty) { context.push(promotionEvent!.clickValue!); } @@ -761,8 +705,7 @@ class _HomePageState extends State { Text( '详情', style: TextStyle( - color: stringToColor( - promotionEvent!.clickButtonColor ?? 'FFFFFFFF'), + color: stringToColor(promotionEvent!.clickButtonColor ?? 'FFFFFFFF'), fontSize: 14, ), ), @@ -770,8 +713,7 @@ class _HomePageState extends State { Icon( Icons.keyboard_double_arrow_right, size: 16, - color: stringToColor( - promotionEvent!.clickButtonColor ?? 'FFFFFFFF'), + color: stringToColor(promotionEvent!.clickButtonColor ?? 'FFFFFFFF'), ), ], ), @@ -824,22 +766,19 @@ class _HomePageState extends State { // 上传图片 HapticFeedbackHelper.mediumImpact(); if (selectedImageFiles.length >= 4) { - showSuccessMessage('最多只能上传 4 张图片'); + showSuccessMessage(AppLocale.uploadImageLimit4.getString(context)); return; } - FilePickerResult? result = - await FilePicker.platform.pickFiles( + FilePickerResult? result = await FilePicker.platform.pickFiles( type: FileType.image, allowMultiple: true, ); if (result != null && result.files.isNotEmpty) { final files = selectedImageFiles; - files.addAll( - result.files.map((e) => FileUpload(file: e)).toList()); + files.addAll(result.files.map((e) => FileUpload(file: e)).toList()); setState(() { - selectedImageFiles = - files.sublist(0, files.length > 4 ? 4 : files.length); + selectedImageFiles = files.sublist(0, files.length > 4 ? 4 : files.length); }); } }, @@ -851,28 +790,7 @@ class _HomePageState extends State { ), ], ), - BlocBuilder( - buildWhen: (previous, current) => current is FreeCountLoadedState, - builder: (context, state) { - if (state is FreeCountLoadedState) { - if (currentModel != null && currentModel!.model.modelId != null) { - final matched = state.model(currentModel!.model.modelId!); - if (matched != null && - matched.leftCount > 0 && - matched.maxCount > 0) { - return Text( - '今日还可免费${matched.leftCount}次', - style: TextStyle( - color: customColors.weakTextColor?.withAlpha(120), - fontSize: 11, - ), - ); - } - } - } - return const SizedBox(); - }, - ), + const SizedBox(), InkWell( onTap: () { onSubmit(context, _textController.text.trim()); @@ -880,8 +798,7 @@ class _HomePageState extends State { child: Icon( Icons.send, color: _textController.text.trim().isNotEmpty - ? customColors.linkColor ?? - const Color.fromARGB(255, 70, 165, 73) + ? customColors.linkColor ?? const Color.fromARGB(255, 70, 165, 73) : customColors.chatInputPanelText, size: 26, ), @@ -953,9 +870,7 @@ class ChatHistoryItem extends StatelessWidget { context, AppLocale.confirmDelete.getString(context), () { - context - .read() - .add(ChatChatDeleteHistory(history.id!)); + context.read().add(ChatChatDeleteHistory(history.id!)); }, danger: true, ); @@ -970,11 +885,9 @@ class ChatHistoryItem extends StatelessWidget { ), child: InkWell( child: ListTile( - contentPadding: - const EdgeInsets.symmetric(horizontal: 10, vertical: 5), + contentPadding: const EdgeInsets.symmetric(horizontal: 10, vertical: 5), shape: RoundedRectangleBorder( - borderRadius: - BorderRadius.circular(customColors.borderRadius ?? 8), + borderRadius: BorderRadius.circular(customColors.borderRadius ?? 8), ), title: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, diff --git a/lib/page/chat/home_chat.dart b/lib/page/chat/home_chat.dart index 182d7220..a8e0e173 100644 --- a/lib/page/chat/home_chat.dart +++ b/lib/page/chat/home_chat.dart @@ -1,5 +1,4 @@ import 'package:askaide/bloc/chat_message_bloc.dart'; -import 'package:askaide/bloc/free_count_bloc.dart'; import 'package:askaide/bloc/notify_bloc.dart'; import 'package:askaide/bloc/room_bloc.dart'; import 'package:askaide/helper/ability.dart'; @@ -83,8 +82,7 @@ class _HomeChatPageState extends State { // 输入框是否可编辑 final ValueNotifier enableInput = ValueNotifier(true); // 音频播放器控制器 - final AudioPlayerController audioPlayerController = - AudioPlayerController(useRemoteAPI: true); + final AudioPlayerController audioPlayerController = AudioPlayerController(useRemoteAPI: true); // 聊天室 ID,当没有值时,会在第一个聊天消息发送后自动设置新值 int? chatId; @@ -117,9 +115,7 @@ class _HomeChatPageState extends State { cascading: true, )); // 查询最近聊天记录 - context - .read() - .add(ChatMessageGetRecentEvent(chatHistoryId: widget.chatId)); + context.read().add(ChatMessageGetRecentEvent(chatHistoryId: widget.chatId)); chatPreviewController.addListener(() { setState(() {}); @@ -148,14 +144,12 @@ class _HomeChatPageState extends State { }); if (widget.model != null) { - selectedModel = - supportModels.where((e) => e.id == widget.model).firstOrNull; + selectedModel = supportModels.where((e) => e.id == widget.model).firstOrNull; } if (selectedModel == null) { Cache().stringGet(key: 'last_selected_model').then((value) { - final selected = - supportModels.where((e) => e.id == value).firstOrNull; + final selected = supportModels.where((e) => e.id == value).firstOrNull; if (selected != null) { setState(() { selectedModel = selected; @@ -218,26 +212,6 @@ class _HomeChatPageState extends State { if (state is RoomLoaded && currentModelV2 == null) { await loadCurrentModel(state.room.model); } - - if (state is RoomLoaded && state.cascading) { - if (state.room.model.startsWith('v2@')) { - if (currentModelV2 != null && currentModelV2!.modelId != null) { - // 加载免费使用次数 - // ignore: use_build_context_synchronously - context.read().add(FreeCountReloadEvent( - model: currentModelV2!.modelId!, - )); - } - } else { - // 加载免费使用次数 - // ignore: use_build_context_synchronously - context.read().add(FreeCountReloadEvent( - model: selectedModel?.name ?? - widget.model ?? - state.room.model, - )); - } - } }, buildWhen: (previous, current) => current is RoomLoaded, builder: (context, room) { @@ -301,38 +275,36 @@ class _HomeChatPageState extends State { initValue: selectedModel, ); }, - child: Column( - children: [ - Container( - width: MediaQuery.of(context).size.width / 2, - alignment: Alignment.center, - child: Text( + child: SizedBox( + width: MediaQuery.of(context).size.width / 2, + child: Column( + children: [ + Text( widget.title ?? AppLocale.chatAnywhere.getString(context), overflow: TextOverflow.ellipsis, maxLines: 1, - style: - const TextStyle(fontSize: CustomSize.appBarTitleSize), + style: const TextStyle(fontSize: CustomSize.appBarTitleSize), ), - ), - if (selectedModel != null) - Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Text( - selectedModel!.name, - style: TextStyle( - color: customColors.weakTextColor, - fontSize: 10, + if (selectedModel != null) + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text( + selectedModel!.name, + style: TextStyle( + color: customColors.weakTextColor, + fontSize: 10, + ), ), - ), - Icon( - Icons.unfold_more, - color: customColors.backgroundInvertedColor, - size: CustomSize.appBarTitleSize * 0.6, - ), - ], - ) - ], + Icon( + Icons.unfold_more, + color: customColors.backgroundInvertedColor, + size: CustomSize.appBarTitleSize * 0.6, + ), + ], + ) + ], + ), ), ); } @@ -411,12 +383,6 @@ class _HomeChatPageState extends State { enableInput.value = false; }); } else if (!state.processing && !enableInput.value) { - // 更新免费使用次数 - context.read().add(FreeCountReloadEvent( - model: selectedModel?.name ?? - widget.model ?? - room.room.model)); - // 聊天回复完成时,取消输入框的禁止编辑状态 setState(() { enableInput.value = true; @@ -444,12 +410,10 @@ class _HomeChatPageState extends State { width: CustomSize.adaptiveMaxWindowWidth(context), child: Center( child: StopButton( - label: '停止输出', + label: AppLocale.stopOutput.getString(context), onPressed: () { HapticFeedbackHelper.mediumImpact(); - context - .read() - .add(ChatMessageStopEvent()); + context.read().add(ChatMessageStopEvent()); }, ), ), @@ -468,63 +432,40 @@ class _HomeChatPageState extends State { ), color: customColors.chatInputPanelBackground, ), - child: BlocBuilder( - builder: (context, freeState) { - var hintText = '有问题尽管问我'; - if (freeState is FreeCountLoadedState) { - final matched = freeState.model( - selectedModel?.name ?? widget.model ?? room.room.model); - if (matched != null && - matched.leftCount > 0 && - matched.maxCount > 0) { - hintText += '(今日还可免费${matched.leftCount}次)'; + child: BlocBuilder( + buildWhen: (previous, current) => current is ChatMessagesLoaded, + builder: (context, state) { + var enableImageUpload = false; + if (state is ChatMessagesLoaded) { + if (currentModelV2 != null) { + enableImageUpload = currentModelV2?.supportVision ?? false; + } else { + var model = state.chatHistory?.model ?? room.room.model; + final cur = supportModels.where((e) => e.id == model).firstOrNull; + enableImageUpload = cur?.supportVision ?? false; } } - return BlocBuilder( - buildWhen: (previous, current) => - current is ChatMessagesLoaded, - builder: (context, state) { - var enableImageUpload = false; - if (state is ChatMessagesLoaded) { - if (currentModelV2 != null) { - enableImageUpload = - currentModelV2?.supportVision ?? false; - } else { - var model = state.chatHistory?.model ?? room.room.model; - final cur = supportModels - .where((e) => e.id == model) - .firstOrNull; - enableImageUpload = cur?.supportVision ?? false; - } - } - - return ChatInput( - enableNotifier: enableInput, - onSubmit: (value) { - handleSubmit(value); - FocusManager.instance.primaryFocus?.unfocus(); - }, - enableImageUpload: selectedModel == null - ? enableImageUpload - : (selectedModel?.supportVision ?? false), - onImageSelected: (files) { - setState(() { - selectedImageFiles = files; - }); - }, - selectedImageFiles: - enableImageUpload ? selectedImageFiles : [], - hintText: hintText, - onVoiceRecordTappedEvent: () { - audioPlayerController.stop(); - }, - onStopGenerate: () { - context - .read() - .add(ChatMessageStopEvent()); - }, - ); + return ChatInput( + enableNotifier: enableInput, + onSubmit: (value) { + handleSubmit(value); + FocusManager.instance.primaryFocus?.unfocus(); + }, + enableImageUpload: + selectedModel == null ? enableImageUpload : (selectedModel?.supportVision ?? false), + onImageSelected: (files) { + setState(() { + selectedImageFiles = files; + }); + }, + selectedImageFiles: enableImageUpload ? selectedImageFiles : [], + hintText: AppLocale.askMeAnyQuestion.getString(context), + onVoiceRecordTappedEvent: () { + audioPlayerController.stop(); + }, + onStopGenerate: () { + context.read().add(ChatMessageStopEvent()); }, ); }, @@ -532,8 +473,7 @@ class _HomeChatPageState extends State { ), // 选择模式工具栏 - if (chatPreviewController.selectMode) - SelectModeToolbar(chatPreviewController: chatPreviewController), + if (chatPreviewController.selectMode) SelectModeToolbar(chatPreviewController: chatPreviewController), ], ); } @@ -547,9 +487,7 @@ class _HomeChatPageState extends State { bool selectMode, ) { final loadedMessages = loadedState.messages as List; - if (room.room.initMessage != null && - room.room.initMessage != '' && - loadedMessages.isEmpty) { + if (room.room.initMessage != null && room.room.initMessage != '' && loadedMessages.isEmpty) { loadedMessages.add( Message( Role.receiver, @@ -577,15 +515,12 @@ class _HomeChatPageState extends State { } if (e.avatarUrl == null || e.senderName == null) { - if (loadedState.chatHistory != null && - loadedState.chatHistory!.model != null) { + if (loadedState.chatHistory != null && loadedState.chatHistory!.model != null) { if (currentModelV2 != null) { e.senderName = currentModelV2!.name; e.avatarUrl = currentModelV2!.avatarUrl; } else { - final mod = supportModels - .where((e) => e.id == loadedState.chatHistory!.model!) - .firstOrNull; + final mod = supportModels.where((e) => e.id == loadedState.chatHistory!.model!).firstOrNull; if (mod != null) { e.senderName = mod.shortName; e.avatarUrl = mod.avatarUrl; @@ -594,9 +529,7 @@ class _HomeChatPageState extends State { } } - final stateMessage = - room.states[widget.stateManager.getKey(e.roomId ?? 0, e.id ?? 0)] ?? - MessageState(); + final stateMessage = room.states[widget.stateManager.getKey(e.roomId ?? 0, e.id ?? 0)] ?? MessageState(); return MessageWithState(e, stateMessage); }).toList(); @@ -637,11 +570,9 @@ class _HomeChatPageState extends State { }, onResetContext: () => handleResetContext(context), onResentEvent: (message, index) { - scrollController.animateTo(0, - duration: const Duration(milliseconds: 500), curve: Curves.easeOut); + scrollController.animateTo(0, duration: const Duration(milliseconds: 500), curve: Curves.easeOut); - handleSubmit(message.text, - messagetType: message.type, index: index, isResent: true); + handleSubmit(message.text, messagetType: message.type, index: index, isResent: true); }, onSpeakEvent: (message) { audioPlayerController.playAudio(message.text); @@ -666,8 +597,8 @@ class _HomeChatPageState extends State { if (selectedImageFiles.isNotEmpty) { final cancel = BotToast.showCustomLoading( toastBuilder: (cancel) { - return const LoadingIndicator( - message: '正在上传图片,请稍后...', + return LoadingIndicator( + message: AppLocale.imageUploading.getString(context), ); }, allowClick: false, @@ -719,10 +650,7 @@ class _HomeChatPageState extends State { model: selectedModel?.id ?? widget.model, type: messagetType, chatHistoryId: chatId, - images: selectedImageFiles - .where((e) => e.uploaded) - .map((e) => e.url!) - .toList(), + images: selectedImageFiles.where((e) => e.uploaded).map((e) => e.url!).toList(), ), index: index, isResent: isResent, @@ -732,8 +660,6 @@ class _HomeChatPageState extends State { // ignore: use_build_context_synchronously context.read().add(NotifyResetEvent()); // ignore: use_build_context_synchronously - context - .read() - .add(RoomLoadEvent(chatAnywhereRoomId, cascading: false)); + context.read().add(RoomLoadEvent(chatAnywhereRoomId, cascading: false)); } } diff --git a/lib/page/chat/home_chat_history.dart b/lib/page/chat/home_chat_history.dart index e2a3422a..b3e4bfb9 100644 --- a/lib/page/chat/home_chat_history.dart +++ b/lib/page/chat/home_chat_history.dart @@ -19,8 +19,7 @@ class HomeChatHistoryPage extends StatefulWidget { final SettingRepository setting; final ChatMessageRepository chatMessageRepo; - const HomeChatHistoryPage( - {super.key, required this.setting, required this.chatMessageRepo}); + const HomeChatHistoryPage({super.key, required this.setting, required this.chatMessageRepo}); @override State createState() => _HomeChatHistoryPageState(); @@ -29,6 +28,8 @@ class HomeChatHistoryPage extends StatefulWidget { class _HomeChatHistoryPageState extends State { late final ChatHistoryDatasource datasource; + String? keyword; + @override void initState() { datasource = ChatHistoryDatasource(widget.chatMessageRepo); @@ -57,79 +58,101 @@ class _HomeChatHistoryPageState extends State { top: false, left: false, right: false, - child: RefreshIndicator( - color: customColors.linkColor, - onRefresh: () async { - await datasource.refresh(); - }, - child: BlocListener( - listenWhen: (previous, current) => - current is ChatChatRecentHistoriesLoaded, - listener: (context, state) { - if (state is ChatChatRecentHistoriesLoaded) { - datasource.refresh(); - } - }, - child: RefreshIndicator( - color: customColors.linkColor, - displacement: 20, - onRefresh: () { - return datasource.refresh(); - }, - child: LoadingMoreList( - ListConfig( - itemBuilder: (context, item, index) { - return ChatHistoryItem( - history: item, - customColors: customColors, - onTap: () { - context - .push( - '/chat-anywhere?chat_id=${item.id}&model=${item.model}&title=${item.title}') - .whenComplete(() { - FocusScope.of(context).requestFocus(FocusNode()); - context - .read() - .add(ChatChatLoadRecentHistories()); - }); - }, - ); - }, - sourceList: datasource, - indicatorBuilder: (context, status) { - String msg = ''; - switch (status) { - case IndicatorStatus.noMoreLoad: - msg = '~ 没有更多了 ~'; - break; - case IndicatorStatus.loadingMoreBusying: - msg = '加载中...'; - break; - case IndicatorStatus.error: - msg = '加载失败,请稍后再试'; - break; - case IndicatorStatus.empty: - msg = '暂无数据'; - break; - default: - return const Center(child: LoadingIndicator()); - } - return Container( - padding: const EdgeInsets.all(15), - alignment: Alignment.center, - child: Text( - msg, - style: TextStyle( - color: customColors.weakTextColor, - fontSize: 14, - ), - ), - ); - }, + child: Column( + children: [ + Container( + padding: const EdgeInsets.only(left: 10, right: 10, bottom: 5), + child: TextField( + textAlignVertical: TextAlignVertical.center, + style: TextStyle(color: customColors.dialogDefaultTextColor), + decoration: InputDecoration( + hintText: AppLocale.search.getString(context), + hintStyle: TextStyle( + color: customColors.dialogDefaultTextColor, + ), + prefixIcon: Icon( + Icons.search, + color: customColors.dialogDefaultTextColor, + ), + isDense: true, + border: InputBorder.none, ), + onChanged: (value) { + setState(() { + keyword = value; + }); + + datasource.refresh(false, keyword); + }, ), ), - ), + Expanded( + child: BlocListener( + listenWhen: (previous, current) => current is ChatChatRecentHistoriesLoaded, + listener: (context, state) { + if (state is ChatChatRecentHistoriesLoaded) { + datasource.refresh(false, keyword); + } + }, + child: RefreshIndicator( + color: customColors.linkColor, + displacement: 20, + onRefresh: () { + return datasource.refresh(false, keyword); + }, + child: LoadingMoreList( + ListConfig( + itemBuilder: (context, item, index) { + return ChatHistoryItem( + history: item, + customColors: customColors, + onTap: () { + context + .push('/chat-anywhere?chat_id=${item.id}&model=${item.model}&title=${item.title}') + .whenComplete(() { + FocusScope.of(context).requestFocus(FocusNode()); + context.read().add(ChatChatLoadRecentHistories()); + }); + }, + ); + }, + sourceList: datasource, + indicatorBuilder: (context, status) { + String msg = ''; + switch (status) { + case IndicatorStatus.noMoreLoad: + msg = '~ No more left ~'; + break; + case IndicatorStatus.loadingMoreBusying: + msg = 'Loading...'; + break; + case IndicatorStatus.error: + msg = 'Failed to load, please try again later'; + break; + case IndicatorStatus.empty: + msg = 'No data'; + break; + default: + return const Center(child: LoadingIndicator()); + } + return Container( + padding: const EdgeInsets.all(15), + alignment: Alignment.center, + child: Text( + msg, + style: TextStyle( + color: customColors.weakTextColor, + fontSize: 14, + ), + ), + ); + }, + ), + ), + ), + ), + ) + ], ), ), ), diff --git a/lib/page/chat/room_chat.dart b/lib/page/chat/room_chat.dart index 30638e61..a30e6579 100644 --- a/lib/page/chat/room_chat.dart +++ b/lib/page/chat/room_chat.dart @@ -1,6 +1,5 @@ import 'dart:convert'; -import 'package:askaide/bloc/free_count_bloc.dart'; import 'package:askaide/helper/ability.dart'; import 'package:askaide/helper/haptic_feedback.dart'; import 'package:askaide/helper/model.dart'; @@ -61,8 +60,7 @@ class _RoomChatPageState extends State { final ScrollController _scrollController = ScrollController(); final ValueNotifier _inputEnabled = ValueNotifier(true); final ChatPreviewController _chatPreviewController = ChatPreviewController(); - final AudioPlayerController _audioPlayerController = - AudioPlayerController(useRemoteAPI: true); + final AudioPlayerController _audioPlayerController = AudioPlayerController(useRemoteAPI: true); bool showAudioPlayer = false; bool audioLoadding = false; @@ -140,15 +138,6 @@ class _RoomChatPageState extends State { return BlocConsumer( listenWhen: (previous, current) => current is RoomLoaded, listener: (context, state) { - if (state is RoomLoaded && state.cascading) { - // 加载免费使用次数 - if (tempModel == null) { - context - .read() - .add(FreeCountReloadEvent(model: state.room.model)); - } - } - if (state is RoomLoaded) { ModelAggregate.model(state.room.model).then((value) { setState(() { @@ -160,13 +149,14 @@ class _RoomChatPageState extends State { buildWhen: (previous, current) => current is RoomLoaded, builder: (context, room) { if (room is RoomLoaded) { + final enableImageUpload = + tempModel == null ? (roomModel != null && roomModel!.supportVision) : (tempModel?.supportVision ?? false); return SafeArea( top: false, bottom: false, child: Column( children: [ - if (Ability().showGlobalAlert) - const GlobalAlert(pageKey: 'chat'), + if (Ability().showGlobalAlert) const GlobalAlert(pageKey: 'chat'), // 语音输出中提示 if (showAudioPlayer) EnhancedAudioPlayer( @@ -189,12 +179,10 @@ class _RoomChatPageState extends State { width: CustomSize.adaptiveMaxWindowWidth(context), child: Center( child: StopButton( - label: '停止输出', + label: AppLocale.stopOutput.getString(context), onPressed: () { HapticFeedbackHelper.mediumImpact(); - context - .read() - .add(ChatMessageStopEvent()); + context.read().add(ChatMessageStopEvent()); }, ), ), @@ -212,73 +200,51 @@ class _RoomChatPageState extends State { ), color: customColors.chatInputPanelBackground, ), - child: BlocBuilder( - builder: (context, freeState) { - var hintText = '有问题尽管问我'; - if (freeState is FreeCountLoadedState && - tempModel == null) { - final matched = freeState.model(room.room.model); - if (matched != null && - matched.leftCount > 0 && - matched.maxCount > 0) { - hintText += '(今日还可免费${matched.leftCount}次)'; - } - } - - final enableImageUpload = tempModel == null - ? (roomModel != null && roomModel!.supportVision) - : (tempModel?.supportVision ?? false); - - return _chatPreviewController.selectMode - ? SelectModeToolbar( - chatPreviewController: _chatPreviewController, - ) - : ChatInput( - enableNotifier: _inputEnabled, - onSubmit: (value) { - _handleSubmit(value); - FocusManager.instance.primaryFocus?.unfocus(); - }, - enableImageUpload: - enableImageUpload && selectedFile == null, - onImageSelected: (files) { - setState(() { - selectedImageFiles = files; - }); - }, - selectedImageFiles: selectedImageFiles, - enableFileUpload: selectedImageFiles.isEmpty, - onFileSelected: (file) { - setState(() { - selectedFile = file; - }); - }, - selectedFile: selectedFile, - onNewChat: () => handleResetContext(context), - hintText: hintText, - onVoiceRecordTappedEvent: () { - _audioPlayerController.stop(); - }, - onStopGenerate: () { - context - .read() - .add(ChatMessageStopEvent()); - }, - leftSideToolsBuilder: () { - return [ - ModelSwitcher( - onSelected: (selected) { - setState(() { - tempModel = selected; - }); - }, - value: tempModel, - ), - ]; - }, - ); - }, - ), + child: _chatPreviewController.selectMode + ? SelectModeToolbar( + chatPreviewController: _chatPreviewController, + ) + : ChatInput( + enableNotifier: _inputEnabled, + onSubmit: (value) { + _handleSubmit(value); + FocusManager.instance.primaryFocus?.unfocus(); + }, + enableImageUpload: enableImageUpload && selectedFile == null, + onImageSelected: (files) { + setState(() { + selectedImageFiles = files; + }); + }, + selectedImageFiles: selectedImageFiles, + // enableFileUpload: selectedImageFiles.isEmpty, + onFileSelected: (file) { + setState(() { + selectedFile = file; + }); + }, + selectedFile: selectedFile, + onNewChat: () => handleResetContext(context), + hintText: AppLocale.askMeAnyQuestion.getString(context), + onVoiceRecordTappedEvent: () { + _audioPlayerController.stop(); + }, + onStopGenerate: () { + context.read().add(ChatMessageStopEvent()); + }, + leftSideToolsBuilder: () { + return [ + ModelSwitcher( + onSelected: (selected) { + setState(() { + tempModel = selected; + }); + }, + value: tempModel, + ), + ]; + }, + ), ), ], ), @@ -322,13 +288,6 @@ class _RoomChatPageState extends State { _inputEnabled.value = false; }); } else if (!state.processing && !_inputEnabled.value) { - // 更新免费使用次数 - if (tempModel == null) { - context - .read() - .add(FreeCountReloadEvent(model: room.room.model)); - } - // 聊天回复完成时,取消输入框的禁止编辑状态 setState(() { _inputEnabled.value = true; @@ -340,9 +299,7 @@ class _RoomChatPageState extends State { builder: (context, state) { if (state is ChatMessagesLoaded) { final loadedMessages = state.messages as List; - if (room.room.initMessage != null && - room.room.initMessage != '' && - loadedMessages.isEmpty) { + if (room.room.initMessage != null && room.room.initMessage != '' && loadedMessages.isEmpty) { loadedMessages.add( Message( Role.receiver, @@ -365,8 +322,7 @@ class _RoomChatPageState extends State { final messages = loadedMessages.map((e) { if (e.model != null && !e.model!.startsWith('v2@')) { - final mod = - supportModels.where((m) => m.id == e.model).firstOrNull; + final mod = supportModels.where((m) => m.id == e.model).firstOrNull; if (mod != null) { e.senderName = mod.shortName; e.avatarUrl = mod.avatarUrl; @@ -379,17 +335,14 @@ class _RoomChatPageState extends State { return MessageWithState( e, - room.states[ - widget.stateManager.getKey(e.roomId ?? 0, e.id ?? 0)] ?? - MessageState(), + room.states[widget.stateManager.getKey(e.roomId ?? 0, e.id ?? 0)] ?? MessageState(), ); }).toList(); _chatPreviewController.setAllMessageIds(messages); return ChatPreview( - padding: - _inputEnabled.value ? null : const EdgeInsets.only(bottom: 35), + padding: _inputEnabled.value ? null : const EdgeInsets.only(bottom: 35), messages: messages, scrollController: _scrollController, controller: _chatPreviewController, @@ -425,11 +378,8 @@ class _RoomChatPageState extends State { _audioPlayerController.playAudio(message.text); }, onResentEvent: (message, index) { - _scrollController.animateTo(0, - duration: const Duration(milliseconds: 500), - curve: Curves.easeOut); - _handleSubmit(message.text, - messagetType: message.type, index: index, isResent: true); + _scrollController.animateTo(0, duration: const Duration(milliseconds: 500), curve: Curves.easeOut); + _handleSubmit(message.text, messagetType: message.type, index: index, isResent: true); }, helpWidgets: state.processing || loadedMessages.last.isInitMessage() ? null @@ -554,11 +504,9 @@ class _RoomChatPageState extends State { if (!selectedFile!.uploaded) { final path = selectedFile!.file.path; if (path != null && path.isNotEmpty) { - final uploadRes = - await uploader.uploadFile(path, usage: 'document'); + final uploadRes = await uploader.uploadFile(path, usage: 'document'); selectedFile!.setUrl(uploadRes.url); - } else if (selectedFile!.file.bytes != null && - selectedFile!.file.bytes!.isNotEmpty) { + } else if (selectedFile!.file.bytes != null && selectedFile!.file.bytes!.isNotEmpty) { final uploadRes = await uploader.upload( 'file-${DateTime.now().millisecondsSinceEpoch}.${selectedFile!.file.name}', selectedFile!.file.bytes!, @@ -630,10 +578,7 @@ class _RoomChatPageState extends State { user: 'me', ts: DateTime.now(), type: messagetType, - images: selectedImageFiles - .where((e) => e.uploaded) - .map((e) => e.url!) - .toList(), + images: selectedImageFiles.where((e) => e.uploaded).map((e) => e.url!).toList(), file: selectedFile != null && selectedFile!.uploaded ? jsonEncode({ 'name': selectedFile!.file.name, @@ -650,9 +595,7 @@ class _RoomChatPageState extends State { // ignore: use_build_context_synchronously context.read().add(NotifyResetEvent()); // ignore: use_build_context_synchronously - context - .read() - .add(RoomLoadEvent(widget.roomId, cascading: false)); + context.read().add(RoomLoadEvent(widget.roomId, cascading: false)); } } @@ -661,9 +604,7 @@ void handleDeleteMessage(BuildContext context, int id, {int? chatHistoryId}) { openConfirmDialog( context, AppLocale.confirmDelete.getString(context), - () => context - .read() - .add(ChatMessageDeleteEvent([id], chatHistoryId: chatHistoryId)), + () => context.read().add(ChatMessageDeleteEvent([id], chatHistoryId: chatHistoryId)), danger: true, ); } @@ -746,8 +687,7 @@ void handleOpenExampleQuestion( color: customColors.chatExampleItemText, ), ), - if (examples[i].content != null) - const SizedBox(height: 5), + if (examples[i].content != null) const SizedBox(height: 5), if (examples[i].content != null) Text( examples[i].content!, diff --git a/lib/page/chat/room_create.dart b/lib/page/chat/room_create.dart index e0481b43..aa4657c3 100644 --- a/lib/page/chat/room_create.dart +++ b/lib/page/chat/room_create.dart @@ -58,10 +58,10 @@ class _RoomCreatePageState extends State { int maxContext = 3; List validMemories = [ - ChatMemory('无记忆', 1, description: '每次对话都是独立的,常用于一次性问答'), - ChatMemory('基础', 3, description: '记住最近的 3 次对话'), - ChatMemory('中等', 6, description: '记住最近的 6 次对话'), - ChatMemory('深度', 10, description: '记住最近的 10 次对话'), + ChatMemory('Ephemeral', 1, description: 'Each conversation is independent, often used for one-off Q&A'), + ChatMemory('Basic', 3, description: 'Remembers the last 3 conversations'), + ChatMemory('Medium', 6, description: 'Remembers the last 6 conversations'), + ChatMemory('Deep', 10, description: 'Remembers the last 10 conversations') ]; bool showAdvancedOptions = false; @@ -121,34 +121,29 @@ class _RoomCreatePageState extends State { children: [ Theme( data: Theme.of(context).copyWith( - colorScheme: Theme.of(context).colorScheme.copyWith( - surfaceContainerHighest: Colors.transparent), + colorScheme: + Theme.of(context).colorScheme.copyWith(surfaceContainerHighest: Colors.transparent), ), child: TabBar( tabs: [ for (var tag in tags) Tab(text: tag), - if (selectedSuggestions.isEmpty) - const Tab(text: '自定义'), + if (selectedSuggestions.isEmpty) Tab(text: AppLocale.custom.getString(context)), ], isScrollable: true, labelColor: customColors.linkColor, indicator: const BoxDecoration(), - labelPadding: - const EdgeInsets.only(right: 5, left: 10), - overlayColor: - WidgetStateProperty.all(Colors.transparent), + labelPadding: const EdgeInsets.only(right: 5, left: 10), + overlayColor: WidgetStateProperty.all(Colors.transparent), tabAlignment: TabAlignment.center, ), ), Expanded( child: BlocConsumer( - listenWhen: (previous, current) => - current is RoomGalleriesLoaded, + listenWhen: (previous, current) => current is RoomGalleriesLoaded, listener: (context, state) { if (state is RoomGalleriesLoaded) { if (state.error != null) { - showErrorMessageEnhanced( - context, state.error!); + showErrorMessageEnhanced(context, state.error!); } if (state.galleries.isNotEmpty) { @@ -158,8 +153,7 @@ class _RoomCreatePageState extends State { } } }, - buildWhen: (previous, current) => - current is RoomGalleriesLoaded, + buildWhen: (previous, current) => current is RoomGalleriesLoaded, builder: (context, state) { if (state is RoomGalleriesLoaded) { return TabBarView( @@ -168,13 +162,9 @@ class _RoomCreatePageState extends State { buildSuggestTab( customColors, context, - state.galleries - .where((element) => - element.tags.contains(tag)) - .toList(), + state.galleries.where((element) => element.tags.contains(tag)).toList(), ), - if (selectedSuggestions.isEmpty) - buildCustomTab(customColors, context), + if (selectedSuggestions.isEmpty) buildCustomTab(customColors, context), ], ); } @@ -215,10 +205,10 @@ class _RoomCreatePageState extends State { child: EnhancedButton( title: AppLocale.ok.getString(context), onPressed: () { - context.read().add(GalleryRoomCopyEvent( - selectedSuggestions.map((e) => e.id).toList())); - showSuccessMessage( - AppLocale.operateSuccess.getString(context)); + context + .read() + .add(GalleryRoomCopyEvent(selectedSuggestions.map((e) => e.id).toList())); + showSuccessMessage(AppLocale.operateSuccess.getString(context)); context.pop(); }, ), @@ -310,10 +300,8 @@ class _RoomCreatePageState extends State { ? null : DecorationImage( image: (_avatarUrl!.startsWith('http') - ? CachedNetworkImageProviderEnhanced( - _avatarUrl!) - : FileImage(File(_avatarUrl!))) - as ImageProvider, + ? CachedNetworkImageProviderEnhanced(_avatarUrl!) + : FileImage(File(_avatarUrl!))) as ImageProvider, fit: BoxFit.cover, ), ), @@ -366,13 +354,13 @@ class _RoomCreatePageState extends State { Container( margin: const EdgeInsets.only(top: 10), width: double.infinity, - padding: - const EdgeInsets.symmetric(horizontal: 5, vertical: 5), + padding: const EdgeInsets.symmetric(horizontal: 5, vertical: 5), child: Text( defaultModelNotChatDesc, - style: Theme.of(context).textTheme.bodySmall?.copyWith( - fontSize: 14, - color: const Color.fromARGB(255, 244, 155, 54)), + style: Theme.of(context) + .textTheme + .bodySmall + ?.copyWith(fontSize: 14, color: const Color.fromARGB(255, 244, 155, 54)), ), ), @@ -391,9 +379,7 @@ class _RoomCreatePageState extends State { initValue: _selectedModel?.uid(), ); }, - value: _selectedModel != null - ? _selectedModel!.name - : AppLocale.select.getString(context), + value: _selectedModel != null ? _selectedModel!.name : AppLocale.select.getString(context), ), // 提示语 if (_selectedModel != null && _selectedModel!.isChatModel) @@ -436,33 +422,28 @@ class _RoomCreatePageState extends State { if (showAdvancedOptions) ColumnBlock( innerPanding: 10, - padding: const EdgeInsets.only( - top: 15, left: 15, right: 15, bottom: 5), + padding: const EdgeInsets.only(top: 15, left: 15, right: 15, bottom: 5), children: [ EnhancedTextField( customColors: customColors, controller: _initMessageController, - labelText: '引导语', + labelText: AppLocale.welcomeMessage.getString(context), labelPosition: LabelPosition.top, - hintText: '每次开始新对话时,系统将会以 AI 的身份自动发送引导语。', + hintText: AppLocale.welcomeMessageTips.getString(context), maxLines: 3, showCounter: false, maxLength: 1000, ), EnhancedInput( title: Text( - '记忆深度', + AppLocale.memoryDepth.getString(context), style: TextStyle( color: customColors.textfieldLabelColor, fontSize: 16, ), ), value: Text( - validMemories - .where((element) => element.number == maxContext) - .firstOrNull - ?.name ?? - '', + validMemories.where((element) => element.number == maxContext).firstOrNull?.name ?? '', ), onPressed: () { openListSelectDialog( @@ -498,9 +479,7 @@ class _RoomCreatePageState extends State { return true; }, heightFactor: 0.5, - value: validMemories - .where((element) => element.number == maxContext) - .firstOrNull, + value: validMemories.where((element) => element.number == maxContext).firstOrNull, ); }, ), @@ -510,7 +489,9 @@ class _RoomCreatePageState extends State { Row( children: [ EnhancedButton( - title: showAdvancedOptions ? '收起选项' : '高级选项', + title: showAdvancedOptions + ? AppLocale.collapseOptions.getString(context) + : AppLocale.advanced.getString(context), width: 100, backgroundColor: Colors.transparent, color: customColors.weakLinkColor, @@ -532,31 +513,27 @@ class _RoomCreatePageState extends State { title: AppLocale.ok.getString(context), onPressed: () async { if (_nameController.text == '') { - showErrorMessage( - AppLocale.nameRequiredMessage.getString(context)); + showErrorMessage(AppLocale.nameRequiredMessage.getString(context)); return; } if (_promptController.text.length > 1000) { - showErrorMessage( - AppLocale.promptFormatError.getString(context)); + showErrorMessage(AppLocale.promptFormatError.getString(context)); return; } if (_selectedModel == null) { - showErrorMessage( - AppLocale.modelRequiredMessage.getString(context)); + showErrorMessage(AppLocale.modelRequiredMessage.getString(context)); return; } if (_avatarUrl != null) { - if (!(_avatarUrl!.startsWith('http://') || - _avatarUrl!.startsWith('https://'))) { + if (!(_avatarUrl!.startsWith('http://') || _avatarUrl!.startsWith('https://'))) { // 上传文件,获取 URL final cancel = BotToast.showCustomLoading( toastBuilder: (cancel) { - return const LoadingIndicator( - message: "正在上传图片,请稍后...", + return LoadingIndicator( + message: AppLocale.imageUploading.getString(context), ); }, allowClick: false, @@ -619,8 +596,7 @@ void openSelectModelDialog( if (priorityModelId != null) { // 将 models 中,id 与 priorityModelId 相同的元素排序到最前面 - final index = models.indexWhere( - (e) => e.id == priorityModelId || e.uid() == priorityModelId); + final index = models.indexWhere((e) => e.id == priorityModelId || e.uid() == priorityModelId); if (index != -1) { models.insert( 0, @@ -649,9 +625,7 @@ void openSelectModelDialog( return ModelItem( models: snapshot.data! - .where((e) => - !e.disabled || - (reservedModels != null && reservedModels.contains(e.id))) + .where((e) => !e.disabled || (reservedModels != null && reservedModels.contains(e.id))) .toList(), onSelected: (selected) { onSelected(selected); @@ -710,8 +684,7 @@ void openSystemPromptSelectDialog( ], ), e.content, - search: (keywrod) => - e.title.toLowerCase().contains(keywrod.toLowerCase()), + search: (keywrod) => e.title.toLowerCase().contains(keywrod.toLowerCase()), ), ) .toList(), diff --git a/lib/page/chat/room_edit.dart b/lib/page/chat/room_edit.dart index b88dbe65..86566657 100644 --- a/lib/page/chat/room_edit.dart +++ b/lib/page/chat/room_edit.dart @@ -71,8 +71,7 @@ class _RoomEditPageState extends State { void initState() { super.initState(); - BlocProvider.of(context) - .add(RoomLoadEvent(widget.roomId, cascading: false)); + BlocProvider.of(context).add(RoomLoadEvent(widget.roomId, cascading: false)); // 获取预设头像 if (Ability().isUserLogon()) { @@ -129,8 +128,7 @@ class _RoomEditPageState extends State { _originalAvatarUrl = state.room.avatarUrl; _originalAvatarId = null; }); - } else if (state.room.avatarId != null && - state.room.avatarId != 0) { + } else if (state.room.avatarId != null && state.room.avatarId != 0) { setState(() { _avatarId = state.room.avatarId; _avatarUrl = null; @@ -175,8 +173,7 @@ class _RoomEditPageState extends State { ), if (Ability().isUserLogon()) EnhancedInput( - padding: - const EdgeInsets.only(top: 10, bottom: 5), + padding: const EdgeInsets.only(top: 10, bottom: 5), title: Text( '头像', style: TextStyle( @@ -196,18 +193,13 @@ class _RoomEditPageState extends State { image: _avatarUrl == null ? null : DecorationImage( - image: (_avatarUrl! - .startsWith('http') - ? CachedNetworkImageProviderEnhanced( - _avatarUrl!) - : FileImage( - File(_avatarUrl!))) - as ImageProvider, + image: (_avatarUrl!.startsWith('http') + ? CachedNetworkImageProviderEnhanced(_avatarUrl!) + : FileImage(File(_avatarUrl!))) as ImageProvider, fit: BoxFit.cover, ), ), - child: _avatarUrl == null && - _avatarId == null + child: _avatarUrl == null && _avatarId == null ? const Center( child: Icon( Icons.interests, @@ -238,17 +230,10 @@ class _RoomEditPageState extends State { usage: AvatarUsage.room, defaultAvatarId: _avatarId, defaultAvatarUrl: _avatarUrl, - externalAvatarIds: - _originalAvatarId == null - ? [] - : [_originalAvatarId!], - externalAvatarUrls: - _originalAvatarUrl == null - ? [...avatarPresets] - : [ - _originalAvatarUrl!, - ...avatarPresets - ], + externalAvatarIds: _originalAvatarId == null ? [] : [_originalAvatarId!], + externalAvatarUrls: _originalAvatarUrl == null + ? [...avatarPresets] + : [_originalAvatarUrl!, ...avatarPresets], ); }, heightFactor: 0.8, @@ -274,19 +259,13 @@ class _RoomEditPageState extends State { }); }, initValue: _selectedModel?.uid(), - reservedModels: reservedModel != null - ? [reservedModel!] - : [], + reservedModels: reservedModel != null ? [reservedModel!] : [], ); }, - value: _selectedModel != null - ? _selectedModel!.name - : AppLocale.select.getString(context), + value: _selectedModel != null ? _selectedModel!.name : AppLocale.select.getString(context), ), // 提示语 - if ((_selectedModel != null && - _selectedModel!.isChatModel) || - _promptController.text != '') + if ((_selectedModel != null && _selectedModel!.isChatModel) || _promptController.text != '') EnhancedTextField( customColors: customColors, controller: _promptController, @@ -298,15 +277,13 @@ class _RoomEditPageState extends State { Icon( Icons.tips_and_updates_outlined, size: 13, - color: - customColors.linkColor?.withAlpha(150), + color: customColors.linkColor?.withAlpha(150), ), const SizedBox(width: 5), Text( '示例', style: TextStyle( - color: customColors.linkColor - ?.withAlpha(150), + color: customColors.linkColor?.withAlpha(150), fontSize: 13, ), ), @@ -328,34 +305,28 @@ class _RoomEditPageState extends State { if (showAdvancedOptions) ColumnBlock( innerPanding: 10, - padding: const EdgeInsets.only( - top: 15, left: 15, right: 15, bottom: 5), + padding: const EdgeInsets.only(top: 15, left: 15, right: 15, bottom: 5), children: [ EnhancedTextField( customColors: customColors, controller: _initMessageController, - labelText: '引导语', + labelText: AppLocale.welcomeMessage.getString(context), labelPosition: LabelPosition.top, - hintText: '每次开始新对话时,系统将会以 AI 的身份自动发送引导语。', + hintText: AppLocale.welcomeMessageTips.getString(context), maxLines: 3, showCounter: false, maxLength: 1000, ), EnhancedInput( title: Text( - '记忆深度', + AppLocale.memoryDepth.getString(context), style: TextStyle( color: customColors.textfieldLabelColor, fontSize: 16, ), ), value: Text( - validMemories - .where((element) => - element.number == maxContext) - .firstOrNull - ?.name ?? - '', + validMemories.where((element) => element.number == maxContext).firstOrNull?.name ?? '', ), onPressed: () { openListSelectDialog( @@ -374,8 +345,7 @@ class _RoomEditPageState extends State { e.description ?? '', textAlign: TextAlign.center, style: TextStyle( - color: customColors - .weakTextColor, + color: customColors.weakTextColor, fontSize: 12, ), ), @@ -392,10 +362,7 @@ class _RoomEditPageState extends State { return true; }, heightFactor: 0.5, - value: validMemories - .where((element) => - element.number == maxContext) - .firstOrNull, + value: validMemories.where((element) => element.number == maxContext).firstOrNull, ); }, ), @@ -405,15 +372,15 @@ class _RoomEditPageState extends State { Row( children: [ EnhancedButton( - title: showAdvancedOptions ? '收起选项' : '高级选项', + title: showAdvancedOptions + ? AppLocale.collapseOptions.getString(context) + : AppLocale.advanced.getString(context), width: 100, backgroundColor: Colors.transparent, color: customColors.weakLinkColor, fontSize: 15, icon: Icon( - showAdvancedOptions - ? Icons.unfold_less - : Icons.unfold_more, + showAdvancedOptions ? Icons.unfold_less : Icons.unfold_more, color: customColors.weakLinkColor, size: 15, ), @@ -429,42 +396,35 @@ class _RoomEditPageState extends State { title: AppLocale.save.getString(context), onPressed: () async { if (_nameController.text == '') { - showErrorMessage(AppLocale.nameRequiredMessage - .getString(context)); + showErrorMessage(AppLocale.nameRequiredMessage.getString(context)); return; } if (_selectedModel == null) { - showErrorMessage(AppLocale - .modelRequiredMessage - .getString(context)); + showErrorMessage(AppLocale.modelRequiredMessage.getString(context)); return; } if (_promptController.text.length > 1000) { - showErrorMessage(AppLocale.promptFormatError - .getString(context)); + showErrorMessage(AppLocale.promptFormatError.getString(context)); return; } if (_avatarUrl != null) { - if (!(_avatarUrl!.startsWith('http://') || - _avatarUrl!.startsWith('https://'))) { + if (!(_avatarUrl!.startsWith('http://') || _avatarUrl!.startsWith('https://'))) { // 上传文件,获取 URL final cancel = BotToast.showCustomLoading( toastBuilder: (cancel) { - return const LoadingIndicator( - message: "正在上传图片,请稍后...", + return LoadingIndicator( + message: AppLocale.imageUploading.getString(context), ); }, allowClick: false, ); - final uploadRes = - await ImageUploader(widget.setting) - .upload(_avatarUrl!, - usage: 'avatar') - .whenComplete(() => cancel()); + final uploadRes = await ImageUploader(widget.setting) + .upload(_avatarUrl!, usage: 'avatar') + .whenComplete(() => cancel()); _avatarUrl = uploadRes.url; } } @@ -479,13 +439,11 @@ class _RoomEditPageState extends State { avatarUrl: _avatarUrl, avatarId: _avatarId, maxContext: maxContext, - initMessage: - _initMessageController.text, + initMessage: _initMessageController.text, ), ); - showSuccessMessage(AppLocale.operateSuccess - .getString(context)); + showSuccessMessage(AppLocale.operateSuccess.getString(context)); } }, ), diff --git a/lib/page/chat/rooms.dart b/lib/page/chat/rooms.dart index 8dc4b258..70d9ae05 100644 --- a/lib/page/chat/rooms.dart +++ b/lib/page/chat/rooms.dart @@ -1,10 +1,7 @@ import 'package:askaide/helper/ability.dart'; -import 'package:askaide/helper/cache.dart'; -import 'package:askaide/helper/constant.dart'; import 'package:askaide/helper/event.dart'; import 'package:askaide/lang/lang.dart'; import 'package:askaide/bloc/room_bloc.dart'; -import 'package:askaide/page/chat/room_create.dart'; import 'package:askaide/page/component/background_container.dart'; import 'package:askaide/page/component/enhanced_button.dart'; import 'package:askaide/page/component/enhanced_error.dart'; @@ -72,12 +69,11 @@ class _RoomsPageState extends State { }); } } else { - showErrorMessageEnhanced(context, state.error ?? '操作失败'); + showErrorMessageEnhanced(context, state.error ?? AppLocale.operateFailed.getString(context)); } } }, - buildWhen: (previous, current) => - current is RoomsLoading || current is RoomsLoaded, + buildWhen: (previous, current) => current is RoomsLoading || current is RoomsLoaded, builder: (context, state) { if (state is RoomsLoaded) { if (state.error != null) { @@ -90,42 +86,8 @@ class _RoomsPageState extends State { if (selectedSuggestions.isEmpty) EnhancedPopupMenu( items: [ - if (Ability().isUserLogon() && - !Ability().enableLocalOpenAI) - EnhancedPopupMenuItem( - title: '快速开始', - icon: Icons.quickreply_outlined, - onTap: (p0) async { - final lastModel = await Cache() - .stringGet(key: cacheKeyLastModel); - openSelectModelDialog( - // ignore: use_build_context_synchronously - context, - (selected) { - if (selected == null) { - return; - } - // 缓存最后一次使用的模型 ID,下次创建时自动排在最前面 - Cache().setString( - key: cacheKeyLastModel, - value: selected.id, - ); - - context.read().add( - RoomCreateEvent( - selected.name, - selected.id, - null, - ), - ); - }, - title: '选择要对话的模型', - priorityModelId: lastModel, - ); - }, - ), EnhancedPopupMenuItem( - title: '创建数字人', + title: AppLocale.createRoom.getString(context), icon: Icons.person_add_alt_outlined, onTap: (p0) { context.push('/create-room').whenComplete(() { @@ -133,15 +95,12 @@ class _RoomsPageState extends State { }); }, ), - if (Ability().isUserLogon() && - !Ability().enableLocalOpenAI) + if (Ability().isUserLogon() && !Ability().enableLocalOpenAI) EnhancedPopupMenuItem( - title: '发起群聊', + title: AppLocale.createGroupChat.getString(context), icon: Icons.forum_outlined, onTap: (p0) { - context - .push('/group-chat-create') - .whenComplete(() { + context.push('/group-chat-create').whenComplete(() { context.read().add(RoomsLoadEvent()); }); }, @@ -150,10 +109,7 @@ class _RoomsPageState extends State { icon: Icons.add_circle_outline, ), ], - centerTitle: state.suggests.isEmpty, - titlePadding: state.suggests.isEmpty - ? null - : const EdgeInsets.only(left: 0), + centerTitle: true, title: state.suggests.isEmpty ? Text( AppLocale.homeTitle.getString(context), @@ -163,13 +119,13 @@ class _RoomsPageState extends State { ), ) : Column( - crossAxisAlignment: CrossAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.center, mainAxisSize: MainAxisSize.min, children: [ Container( margin: const EdgeInsets.only(top: 10, left: 10), child: Text( - '热门推荐', + AppLocale.robotRecommand.getString(context), style: TextStyle( fontWeight: FontWeight.w900, color: customColors.backgroundInvertedColor, @@ -178,10 +134,9 @@ class _RoomsPageState extends State { ), ), Container( - margin: const EdgeInsets.only( - top: 0, left: 10, bottom: 10), + margin: const EdgeInsets.only(top: 0, left: 10, bottom: 10), child: Text( - '挑选你的专属伙伴', + AppLocale.pickYourRobot.getString(context), style: TextStyle( color: customColors.weakTextColorPlus, fontSize: 11, @@ -197,15 +152,12 @@ class _RoomsPageState extends State { child: RefreshIndicator( color: customColors.linkColor, onRefresh: () async { - context - .read() - .add(RoomsLoadEvent(forceRefresh: true)); + context.read().add(RoomsLoadEvent(forceRefresh: true)); }, displacement: 20, child: Column( children: [ - if (Ability().showGlobalAlert) - const GlobalAlert(pageKey: 'rooms'), + if (Ability().showGlobalAlert) const GlobalAlert(pageKey: 'rooms'), Expanded(child: buildBody(customColors, state, context)), ], ), @@ -230,7 +182,7 @@ class _RoomsPageState extends State { child: Row( children: [ WeakTextButton( - title: '取消', + title: AppLocale.cancel.getString(context), onPressed: () { selectedSuggestions.clear(); setState(() {}); @@ -242,12 +194,10 @@ class _RoomsPageState extends State { child: EnhancedButton( title: AppLocale.ok.getString(context), onPressed: () { - context.read().add(GalleryRoomCopyEvent( - selectedSuggestions - .map((e) => e.id) - .toList())); - showSuccessMessage( - AppLocale.operateSuccess.getString(context)); + context + .read() + .add(GalleryRoomCopyEvent(selectedSuggestions.map((e) => e.id).toList())); + showSuccessMessage(AppLocale.operateSuccess.getString(context)); }), ) ], @@ -259,8 +209,7 @@ class _RoomsPageState extends State { ); } - Widget buildBody( - CustomColors customColors, RoomsLoaded state, BuildContext context) { + Widget buildBody(CustomColors customColors, RoomsLoaded state, BuildContext context) { if (state.rooms.isEmpty && state.suggests.isEmpty) { return Center( // 数字人列表为空 @@ -344,7 +293,7 @@ class _RoomsPageState extends State { ), const SizedBox(height: 5), Text( - '查看更多', + AppLocale.viewMore.getString(context), style: TextStyle( color: customColors.weakTextColor, fontSize: 13, diff --git a/lib/page/component/avatar_selector.dart b/lib/page/component/avatar_selector.dart index 8e2d4929..d7d2e22e 100644 --- a/lib/page/component/avatar_selector.dart +++ b/lib/page/component/avatar_selector.dart @@ -1,11 +1,13 @@ import 'dart:io'; import 'package:askaide/helper/haptic_feedback.dart'; +import 'package:askaide/lang/lang.dart'; import 'package:askaide/page/component/image.dart'; import 'package:askaide/page/component/random_avatar.dart'; import 'package:askaide/page/component/theme/custom_theme.dart'; import 'package:file_picker/file_picker.dart'; import 'package:flutter/material.dart'; +import 'package:flutter_localization/flutter_localization.dart'; enum AvatarType { localFile, @@ -115,16 +117,14 @@ class _AvatarSelectorState extends State { ), ], ), - if (_avatarId != null) - RandomAvatar(id: _avatarId ?? 0, size: 80, usage: widget.usage), + if (_avatarId != null) RandomAvatar(id: _avatarId ?? 0, size: 80, usage: widget.usage), Material( borderRadius: BorderRadius.circular(8), child: InkWell( borderRadius: BorderRadius.circular(8), onTap: () async { HapticFeedbackHelper.mediumImpact(); - FilePickerResult? result = - await FilePicker.platform.pickFiles(type: FileType.image); + FilePickerResult? result = await FilePicker.platform.pickFiles(type: FileType.image); if (result != null && result.files.isNotEmpty) { setState(() { _avatarUrl = result.files.first.path; @@ -132,9 +132,7 @@ class _AvatarSelectorState extends State { }); widget.onSelected(Avatar( - type: _avatarUrl!.startsWith('http') - ? AvatarType.network - : AvatarType.localFile, + type: _avatarUrl!.startsWith('http') ? AvatarType.network : AvatarType.localFile, url: _avatarUrl, )); } @@ -158,12 +156,11 @@ class _AvatarSelectorState extends State { ), const SizedBox(width: 10), Text( - '自定义', + AppLocale.custom.getString(context), style: TextStyle( fontSize: 14, fontWeight: FontWeight.w500, - color: customColors.chatInputPanelText - ?.withOpacity(0.8), + color: customColors.chatInputPanelText?.withOpacity(0.8), ), ), ], @@ -225,8 +222,7 @@ class _AvatarSelectorState extends State { decoration: BoxDecoration( borderRadius: BorderRadius.circular(11), border: Border.all( - color: (_avatarUrl != null && _avatarUrl == avatar.url) || - (_avatarId != null && _avatarId == avatar.id) + color: (_avatarUrl != null && _avatarUrl == avatar.url) || (_avatarId != null && _avatarId == avatar.id) ? customColors.linkColor ?? Colors.green : Colors.transparent, width: 4, diff --git a/lib/page/component/chat/empty.dart b/lib/page/component/chat/empty.dart index 04b6acd7..609a974a 100644 --- a/lib/page/component/chat/empty.dart +++ b/lib/page/component/chat/empty.dart @@ -1,6 +1,8 @@ +import 'package:askaide/lang/lang.dart'; import 'package:askaide/page/component/theme/custom_theme.dart'; import 'package:askaide/repo/model/misc.dart'; import 'package:flutter/material.dart'; +import 'package:flutter_localization/flutter_localization.dart'; class EmptyPreview extends StatefulWidget { final List examples; @@ -35,8 +37,7 @@ class _EmptyPreviewState extends State { color: customColors.backgroundColor?.withAlpha(200), borderRadius: BorderRadius.circular(10), ), - padding: - const EdgeInsets.only(top: 20, left: 15, right: 10, bottom: 3), + padding: const EdgeInsets.only(top: 20, left: 15, right: 10, bottom: 3), height: _resolveTipHeight(context), width: _resolveTipWidth(context), child: Column( @@ -45,12 +46,11 @@ class _EmptyPreviewState extends State { children: [ Row( children: [ - Image.asset('assets/app-256-transparent.png', - width: 20, height: 20), + Image.asset('assets/app-256-transparent.png', width: 20, height: 20), const SizedBox(width: 5), - const Text( - '可以这样问我:', - style: TextStyle( + Text( + AppLocale.askMeLikeThis.getString(context), + style: const TextStyle( fontSize: 16, fontWeight: FontWeight.bold, ), @@ -60,8 +60,7 @@ class _EmptyPreviewState extends State { const SizedBox(height: 20), Expanded( child: ListView.separated( - itemCount: - widget.examples.length > 4 ? 4 : widget.examples.length, + itemCount: widget.examples.length > 4 ? 4 : widget.examples.length, physics: const NeverScrollableScrollPhysics(), itemBuilder: (context, index) { return ListTextItem( @@ -100,7 +99,7 @@ class _EmptyPreviewState extends State { ), const SizedBox(width: 3), Text( - '换一换', + AppLocale.refresh.getString(context), style: TextStyle( color: customColors.chatExampleItemText, ), diff --git a/lib/page/component/column_block.dart b/lib/page/component/column_block.dart index ca43f717..1a377ba9 100644 --- a/lib/page/component/column_block.dart +++ b/lib/page/component/column_block.dart @@ -34,15 +34,15 @@ class ColumnBlock extends StatelessWidget { var items = []; for (var i = 0; i < children.length; i++) { items.add(children[i]); - if (i < children.length - 1 && showDivider) { - items.add(Container( - padding: EdgeInsets.symmetric(vertical: innerPanding ?? 0), - child: Divider( - color: customColors.columnBlockDividerColor, - height: 1, - ), - )); - } + items.add(Container( + padding: EdgeInsets.symmetric(vertical: innerPanding ?? 0), + child: (i < children.length - 1 && showDivider) + ? Divider( + color: customColors.columnBlockDividerColor, + height: 1, + ) + : Container(), + )); } return Container( @@ -50,8 +50,7 @@ class ColumnBlock extends StatelessWidget { decoration: BoxDecoration( color: backgroundColor ?? customColors.columnBlockBackgroundColor, border: border, - borderRadius: - BorderRadius.circular(borderRadius ?? customColors.borderRadius!), + borderRadius: BorderRadius.circular(borderRadius ?? customColors.borderRadius!), boxShadow: [ BoxShadow( color: customColors.boxShadowColor!, @@ -65,8 +64,7 @@ class ColumnBlock extends StatelessWidget { ), ], ), - padding: - padding ?? const EdgeInsets.symmetric(horizontal: 15, vertical: 5), + padding: padding ?? const EdgeInsets.symmetric(horizontal: 15, vertical: 5), margin: margin ?? const EdgeInsets.only(bottom: 10, left: 5, right: 5), child: Column( crossAxisAlignment: CrossAxisAlignment.start, diff --git a/lib/page/component/model_item.dart b/lib/page/component/model_item.dart index 29fe2e6e..c98f5cf2 100644 --- a/lib/page/component/model_item.dart +++ b/lib/page/component/model_item.dart @@ -15,6 +15,7 @@ class ModelItem extends StatefulWidget { final Function(Model? selected) onSelected; final String? initValue; final bool enableClear; + final bool showUsing; const ModelItem({ super.key, @@ -22,6 +23,7 @@ class ModelItem extends StatefulWidget { required this.onSelected, this.initValue, this.enableClear = false, + this.showUsing = false, }); @override @@ -35,13 +37,11 @@ class _ModelItemState extends State { Widget build(BuildContext context) { final customColors = Theme.of(context).extension()!; - if (widget.enableClear && widget.initValue != null) { + if (widget.enableClear && widget.initValue != null && widget.showUsing) { // 将当前选中的模型放在第一位 - var index = widget.models.indexWhere( - (e) => e.uid() == widget.initValue || e.id == widget.initValue); + var index = widget.models.indexWhere((e) => e.uid() == widget.initValue || e.id == widget.initValue); if (index != -1) { - widget.models - .insert(0, widget.models[index].copyWith(category: '正在使用')); + widget.models.insert(0, widget.models[index].copyWith(category: AppLocale.using.getString(context))); } } @@ -65,8 +65,7 @@ class _ModelItemState extends State { isDense: true, border: InputBorder.none, ), - onChanged: (value) => - setState(() => keyword = value.toLowerCase()), + onChanged: (value) => setState(() => keyword = value.toLowerCase()), ), ), Expanded( @@ -74,11 +73,8 @@ class _ModelItemState extends State { final models = keyword.isEmpty ? widget.models : widget.models.where((e) { - var matchText = e.name + - (e.description ?? '') + - (e.shortName ?? '') + - (e.tag ?? '') + - (e.category); + var matchText = + e.name + (e.description ?? '') + (e.shortName ?? '') + (e.tag ?? '') + (e.category); if (e.supportVision) { matchText += 'vision视觉看图'; } @@ -126,15 +122,11 @@ class _ModelItemState extends State { List separators = []; if (i == 0 && models[i].category != '') { - separators - .add(buildCategory(customColors, item.category)); - } else if (i > 0 && - models[i].category != models[i - 1].category) { + separators.add(buildCategory(customColors, item.category)); + } else if (i > 0 && models[i].category != models[i - 1].category) { separators.add(buildCategory( customColors, - item.category == '' - ? AppLocale.others.getString(context) - : item.category, + item.category == '' ? AppLocale.others.getString(context) : item.category, )); } @@ -146,70 +138,54 @@ class _ModelItemState extends State { title: Container( alignment: Alignment.center, padding: const EdgeInsets.symmetric(vertical: 5), + decoration: BoxDecoration( + color: widget.initValue == item.uid() ? customColors.dialogBackgroundColor : null, + borderRadius: BorderRadius.circular(5), + ), child: Row( - mainAxisAlignment: - MainAxisAlignment.spaceBetween, + mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisSize: MainAxisSize.min, children: [ if (item.avatarUrl != null) ...[ - _buildAvatar( - avatarUrl: item.avatarUrl, size: 50), + _buildAvatar(avatarUrl: item.avatarUrl, size: 50), const SizedBox(width: 10), ], Expanded( child: Column( - crossAxisAlignment: - CrossAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( children: [ Expanded( child: Container( alignment: - item.avatarUrl != null - ? Alignment.centerLeft - : Alignment.center, + item.avatarUrl != null ? Alignment.centerLeft : Alignment.center, child: Text( item.name, - overflow: - TextOverflow.ellipsis, - style: const TextStyle( - fontSize: 15), + overflow: TextOverflow.ellipsis, + style: const TextStyle(fontSize: 15), ), ), ), ...tags, if (item.avatarUrl != null) ...[ - if (widget.enableClear && i == 0) + if (widget.enableClear && i == 0 && widget.showUsing) SizedBox( - width: 50, - child: widget.initValue == - item.uid() + width: 60, + child: widget.initValue == item.uid() ? WeakTextButton( - title: '取消', + title: AppLocale.cancel.getString(context), fontSize: 10, onPressed: () { - widget.onSelected( - null); + widget.onSelected(null); }, ) : const SizedBox(), - ) - else if (widget.initValue == - item.uid()) - SizedBox( - width: 20, - child: Icon( - Icons.check, - color: - customColors.linkColor, - ), ), ], ], ), - if (item.description != null && - item.description != '') + if (item.description != null && item.description != '') Text( item.description!, maxLines: 2, @@ -292,9 +268,7 @@ class _ModelItemState extends State { }) { return Container( decoration: BoxDecoration( - color: tagBgColor != null - ? stringToColor(tagBgColor) - : customColors.tagsBackgroundHover, + color: tagBgColor != null ? stringToColor(tagBgColor) : customColors.tagsBackgroundHover, borderRadius: BorderRadius.circular(8), ), margin: const EdgeInsets.only(left: 5), @@ -306,9 +280,7 @@ class _ModelItemState extends State { tag, style: TextStyle( fontSize: 8, - color: tagTextColor != null - ? stringToColor(tagTextColor) - : customColors.tagsText, + color: tagTextColor != null ? stringToColor(tagTextColor) : customColors.tagsText, ), ), ); diff --git a/lib/page/component/social_icon.dart b/lib/page/component/social_icon.dart index 42e9b87f..d6c55b6b 100644 --- a/lib/page/component/social_icon.dart +++ b/lib/page/component/social_icon.dart @@ -1,6 +1,8 @@ import 'package:askaide/helper/platform.dart'; +import 'package:askaide/lang/lang.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; +import 'package:flutter_localization/flutter_localization.dart'; import 'package:settings_ui/settings_ui.dart'; import 'package:url_launcher/url_launcher_string.dart'; @@ -94,7 +96,7 @@ class SocialIconGroup extends StatelessWidget { Widget build(BuildContext context) { if (isSettingTiles) { return SettingsSection( - title: const Text('关注我们'), + title: Text(AppLocale.socialMedia.getString(context)), tiles: items .where((e) { if (e.nonIOS) { @@ -126,9 +128,7 @@ class SocialIconGroup extends StatelessWidget { return Row( mainAxisAlignment: MainAxisAlignment.spaceAround, - children: items - .map((e) => SocialIcon(image: e.image, name: e.name, onTap: e.onTap)) - .toList(), + children: items.map((e) => SocialIcon(image: e.image, name: e.name, onTap: e.onTap)).toList(), ); } } diff --git a/lib/page/creative_island/draw/draw_create.dart b/lib/page/creative_island/draw/draw_create.dart index 917938f9..0f0b2072 100644 --- a/lib/page/creative_island/draw/draw_create.dart +++ b/lib/page/creative_island/draw/draw_create.dart @@ -95,76 +95,60 @@ class _DrawCreateScreenState extends State { selectedImagePath = widget.initImage; } - APIServer() - .creativeIslandCapacity(mode: widget.mode, id: widget.id) - .then((cap) { + APIServer().creativeIslandCapacity(mode: widget.mode, id: widget.id).then((cap) { setState(() { capacity = cap; }); if (widget.galleryCopyId != null && widget.galleryCopyId! > 0) { - APIServer() - .creativeGalleryItem(id: widget.galleryCopyId!) - .then((response) { + APIServer().creativeGalleryItem(id: widget.galleryCopyId!).then((response) { final gallery = response.item; if (gallery.prompt != null && gallery.prompt!.isNotEmpty) { promptController.text = gallery.prompt!; } - if (gallery.negativePrompt != null && - gallery.negativePrompt!.isNotEmpty) { - if (gallery.negativePrompt != null && - gallery.negativePrompt!.isNotEmpty) { + if (gallery.negativePrompt != null && gallery.negativePrompt!.isNotEmpty) { + if (gallery.negativePrompt != null && gallery.negativePrompt!.isNotEmpty) { forceShowNegativePrompt = true; } negativePromptController.text = gallery.negativePrompt!; } - if (gallery.metaMap['model_id'] != null && - gallery.metaMap['model_id'] != '') { - final matchedModels = capacity!.vendorModels.where((e) => - e.id == gallery.metaMap['model_id'] || - e.id == 'model-${gallery.metaMap['model_id']}'); + if (gallery.metaMap['model_id'] != null && gallery.metaMap['model_id'] != '') { + final matchedModels = capacity!.vendorModels + .where((e) => e.id == gallery.metaMap['model_id'] || e.id == 'model-${gallery.metaMap['model_id']}'); if (matchedModels.isNotEmpty) { selectedModel = matchedModels.first; } } - if (gallery.metaMap['image_ratio'] != null && - gallery.metaMap['image_ratio'] != '') { + if (gallery.metaMap['image_ratio'] != null && gallery.metaMap['image_ratio'] != '') { selectedImageSize = gallery.metaMap['image_ratio']!; } - if (gallery.metaMap['filter_id'] != null && - gallery.metaMap['filter_id'] > 0) { - final matchedStyles = capacity!.filters - .where((e) => e.id == gallery.metaMap['filter_id']); + if (gallery.metaMap['filter_id'] != null && gallery.metaMap['filter_id'] > 0) { + final matchedStyles = capacity!.filters.where((e) => e.id == gallery.metaMap['filter_id']); if (matchedStyles.isNotEmpty) { selectedStyle = matchedStyles.first; } } - if (gallery.metaMap['real_prompt'] != null && - gallery.metaMap['real_prompt'] != '') { + if (gallery.metaMap['real_prompt'] != null && gallery.metaMap['real_prompt'] != '') { promptController.text = gallery.metaMap['real_prompt']!; } - if (gallery.metaMap['negative_prompt'] != null && - gallery.metaMap['negative_prompt'] != '') { + if (gallery.metaMap['negative_prompt'] != null && gallery.metaMap['negative_prompt'] != '') { negativePromptController.text = gallery.metaMap['negative_prompt']!; } - if (gallery.metaMap['real_negative_prompt'] != null && - gallery.metaMap['real_negative_prompt'] != '') { - negativePromptController.text = - gallery.metaMap['real_negative_prompt']!; + if (gallery.metaMap['real_negative_prompt'] != null && gallery.metaMap['real_negative_prompt'] != '') { + negativePromptController.text = gallery.metaMap['real_negative_prompt']!; } // 创建同款时,默认关闭 AI 优化,除非该同款包含 ai_rewrite 的设定 enableAIRewrite = false; - if ((gallery.metaMap['real_prompt'] == null || - gallery.metaMap['real_prompt'] == '') && + if ((gallery.metaMap['real_prompt'] == null || gallery.metaMap['real_prompt'] == '') && gallery.metaMap['ai_rewrite'] != null && gallery.metaMap['ai_rewrite']) { enableAIRewrite = gallery.metaMap['ai_rewrite']; @@ -176,9 +160,7 @@ class _DrawCreateScreenState extends State { }); if (widget.note != null) { - Cache() - .boolGet(key: 'creative:tutorials:${widget.mode}:dialog') - .then((show) { + Cache().boolGet(key: 'creative:tutorials:${widget.mode}:dialog').then((show) { if (!show) { return; } @@ -247,12 +229,10 @@ class _DrawCreateScreenState extends State { maxWidth: CustomSize.smallWindowSize, child: Column( children: [ - if (Ability().showGlobalAlert) - const GlobalAlert(pageKey: 'creative_create'), + if (Ability().showGlobalAlert) const GlobalAlert(pageKey: 'creative_create'), Expanded( child: Container( - padding: - const EdgeInsets.symmetric(horizontal: 10, vertical: 10), + padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 10), height: double.infinity, child: SingleChildScrollView( child: buildEditPanel(context, customColors), @@ -315,9 +295,7 @@ class _DrawCreateScreenState extends State { ), // 图片风格 - if (capacity != null && - capacity!.showStyle && - capacity!.filters.isNotEmpty) + if (capacity != null && capacity!.showStyle && capacity!.filters.isNotEmpty) ImageStyleSelector( styles: capacity!.filters, onSelected: (style) { @@ -334,12 +312,9 @@ class _DrawCreateScreenState extends State { padding: const EdgeInsets.symmetric(horizontal: 15, vertical: 15), children: [ // 生成内容 - if (widget.mode == 'text-to-image') - ...buildPromptField(customColors), + if (widget.mode == 'text-to-image') ...buildPromptField(customColors), // AI 优化配置 - if (capacity != null && - capacity!.showAIRewrite && - widget.mode != 'image-to-image') + if (capacity != null && capacity!.showAIRewrite && widget.mode != 'image-to-image') Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ @@ -355,10 +330,8 @@ class _DrawCreateScreenState extends State { showBeautyDialog( context, type: QuickAlertType.info, - text: AppLocale.onceEnabledSmartOptimization - .getString(context), - confirmBtnText: - AppLocale.gotIt.getString(context), + text: AppLocale.onceEnabledSmartOptimization.getString(context), + confirmBtnText: AppLocale.gotIt.getString(context), showCancelBtn: false, ); }, @@ -389,13 +362,10 @@ class _DrawCreateScreenState extends State { innerPanding: 10, padding: const EdgeInsets.symmetric(horizontal: 15, vertical: 15), children: [ - if (widget.mode == 'image-to-image' && - capacity != null && - capacity!.showPromptForImage2Image) + if (widget.mode == 'image-to-image' && capacity != null && capacity!.showPromptForImage2Image) ...buildPromptField(customColors), // 反向提示语 - if ((capacity != null && capacity!.showNegativeText) || - forceShowNegativePrompt) + if ((capacity != null && capacity!.showNegativeText) || forceShowNegativePrompt) EnhancedTextField( labelPosition: LabelPosition.top, labelText: AppLocale.excludeContents.getString(context), @@ -409,9 +379,7 @@ class _DrawCreateScreenState extends State { showCounter: false, ), // 原图相似度 - if (capacity != null && - capacity!.showImageStrength && - widget.mode == 'image-to-image') + if (capacity != null && capacity!.showImageStrength && widget.mode == 'image-to-image') Row( children: [ Row( @@ -423,10 +391,8 @@ class _DrawCreateScreenState extends State { showBeautyDialog( context, type: QuickAlertType.info, - text: - '想象力\n\n提高想象力,得到更有创造力的内容。降低想象力,效果与参考图更相似。', - confirmBtnText: - AppLocale.gotIt.getString(context), + text: '想象力\n\n提高想象力,得到更有创造力的内容。降低想象力,效果与参考图更相似。', + confirmBtnText: AppLocale.gotIt.getString(context), showCancelBtn: false, ); }, @@ -464,9 +430,7 @@ class _DrawCreateScreenState extends State { ], ), // 图片数量 - if (capacity != null && - capacity!.showImageCount && - widget.mode != 'image-to-image') + if (capacity != null && capacity!.showImageCount && widget.mode != 'image-to-image') EnhancedInput( title: Text( AppLocale.imageCount.getString(context), @@ -480,14 +444,10 @@ class _DrawCreateScreenState extends State { openListSelectDialog( context, [ - SelectorItem( - const Text('1', textAlign: TextAlign.center), 1), - SelectorItem( - const Text('2', textAlign: TextAlign.center), 2), - SelectorItem( - const Text('3', textAlign: TextAlign.center), 3), - SelectorItem( - const Text('4', textAlign: TextAlign.center), 4), + SelectorItem(const Text('1', textAlign: TextAlign.center), 1), + SelectorItem(const Text('2', textAlign: TextAlign.center), 2), + SelectorItem(const Text('3', textAlign: TextAlign.center), 3), + SelectorItem(const Text('4', textAlign: TextAlign.center), 4), ], (value) { setState(() { @@ -501,9 +461,7 @@ class _DrawCreateScreenState extends State { }, ), // 图片尺寸 - if (capacity != null && - capacity!.allowRatios.isNotEmpty && - widget.mode != 'image-to-image') + if (capacity != null && capacity!.allowRatios.isNotEmpty && widget.mode != 'image-to-image') EnhancedInput( title: Text( AppLocale.imageSize.getString(context), @@ -516,10 +474,7 @@ class _DrawCreateScreenState extends State { onPressed: () { openListSelectDialog( context, - capacity!.allowRatios - .map((e) => - SelectorItem(ImageSize(aspectRatio: e), e)) - .toList(), + capacity!.allowRatios.map((e) => SelectorItem(ImageSize(aspectRatio: e), e)).toList(), (value) { setState(() { selectedImageSize = value.value; @@ -530,9 +485,7 @@ class _DrawCreateScreenState extends State { value: selectedImageSize, heightFactor: 0.3, horizontal: true, - horizontalCount: capacity!.allowRatios.length > 3 - ? 4 - : capacity!.allowRatios.length, + horizontalCount: capacity!.allowRatios.length > 3 ? 4 : capacity!.allowRatios.length, ); }, ), @@ -570,34 +523,27 @@ class _DrawCreateScreenState extends State { Stack( children: [ Container( - padding: const EdgeInsets.only( - top: 25, bottom: 10), + padding: const EdgeInsets.only(top: 25, bottom: 10), alignment: Alignment.center, child: Text( e.name, textAlign: TextAlign.center, - style: - const TextStyle(fontSize: 14), - textWidthBasis: - TextWidthBasis.longestLine, + style: const TextStyle(fontSize: 14), + textWidthBasis: TextWidthBasis.longestLine, ), ), - if (e.vendor != null && - e.vendor!.isNotEmpty) + if (e.vendor != null && e.vendor!.isNotEmpty) Positioned( left: 0, top: 0, child: Container( - padding: - const EdgeInsets.symmetric( + padding: const EdgeInsets.symmetric( horizontal: 5, vertical: 3, ), decoration: BoxDecoration( - borderRadius: - BorderRadius.circular(5), - color: modelTypeTagColors[ - e.vendor!], + borderRadius: BorderRadius.circular(5), + color: modelTypeTagColors[e.vendor!], ), child: Text( e.vendor!, @@ -613,8 +559,7 @@ class _DrawCreateScreenState extends State { e.id, search: (keywrod) { return e.name.contains(keywrod) || - (e.vendor != null && - e.vendor!.contains(keywrod)); + (e.vendor != null && e.vendor!.contains(keywrod)); }, )) .toList(), @@ -626,8 +571,7 @@ class _DrawCreateScreenState extends State { return; } - selectedModel = capacity!.vendorModels - .firstWhere((e) => e.id == value.value); + selectedModel = capacity!.vendorModels.firstWhere((e) => e.id == value.value); }); return true; }, @@ -659,9 +603,7 @@ class _DrawCreateScreenState extends State { context, [ SelectorItem(const Text('自动'), null), - ...capacity!.allowUpscaleBy - .map((e) => SelectorItem(Text(e), e)) - .toList(), + ...capacity!.allowUpscaleBy.map((e) => SelectorItem(Text(e), e)).toList(), ], (value) { setState(() { @@ -837,9 +779,7 @@ class _DrawCreateScreenState extends State { return; } - if (widget.mode == 'image-to-image' && - selectedImagePath == null && - selectedImageData == null) { + if (widget.mode == 'image-to-image' && selectedImagePath == null && selectedImageData == null) { showErrorMessage(AppLocale.selectReferenceImage.getString(context)); return; } @@ -866,8 +806,7 @@ class _DrawCreateScreenState extends State { }; if (selectedImagePath != null && selectedImagePath!.isNotEmpty) { - params['image'] = - 'https://${selectedImagePath ?? 'demo'}'; // 仅用于测试消耗量,正式上传后会被替换为 URL + params['image'] = 'https://${selectedImagePath ?? 'demo'}'; // 仅用于测试消耗量,正式上传后会被替换为 URL } if (selectedImageData != null && selectedImageData!.isNotEmpty) { @@ -891,36 +830,31 @@ class _DrawCreateScreenState extends State { if (params['image'] != null && params['image'] != '') { final cancel = BotToast.showCustomLoading( toastBuilder: (cancel) { - return const LoadingIndicator( - message: '正在上传图片,请稍后...', + return LoadingIndicator( + message: AppLocale.imageUploading.getString(context), ); }, allowClick: false, ); if (selectedImagePath != null && - (selectedImagePath!.startsWith('http://') || - selectedImagePath!.startsWith('https://'))) { + (selectedImagePath!.startsWith('http://') || selectedImagePath!.startsWith('https://'))) { params['image'] = selectedImagePath; cancel(); } else { if (selectedImagePath != null && selectedImagePath!.isNotEmpty) { - final uploadRes = await ImageUploader(widget.setting) - .upload(selectedImagePath!) - .whenComplete(() => cancel()); + final uploadRes = + await ImageUploader(widget.setting).upload(selectedImagePath!).whenComplete(() => cancel()); params['image'] = uploadRes.url; - } else if (selectedImageData != null && - selectedImageData!.isNotEmpty) { - final uploadRes = await ImageUploader(widget.setting) - .uploadData(selectedImageData!) - .whenComplete(() => cancel()); + } else if (selectedImageData != null && selectedImageData!.isNotEmpty) { + final uploadRes = + await ImageUploader(widget.setting).uploadData(selectedImageData!).whenComplete(() => cancel()); params['image'] = uploadRes.url; } } } - final taskId = - await APIServer().creativeIslandCompletionsAsyncV2(params); + final taskId = await APIServer().creativeIslandCompletionsAsyncV2(params); stopPeriodQuery = false; @@ -1023,9 +957,7 @@ class _DrawCreateScreenState extends State { final resp = await APIServer().asyncTaskStatus(taskId); switch (resp.status) { case 'success': - if (params != null && - resp.originImage != null && - resp.originImage != '') { + if (params != null && resp.originImage != null && resp.originImage != '') { params['image'] = resp.originImage; } return IslandResult( diff --git a/lib/page/creative_island/draw/image_edit_direct.dart b/lib/page/creative_island/draw/image_edit_direct.dart index 751e92d7..663dd6b9 100644 --- a/lib/page/creative_island/draw/image_edit_direct.dart +++ b/lib/page/creative_island/draw/image_edit_direct.dart @@ -458,8 +458,8 @@ class _ImageEditDirectScreenState extends State { final cancel = BotToast.showCustomLoading( toastBuilder: (cancel) { - return const LoadingIndicator( - message: '正在上传图片,请稍后...', + return LoadingIndicator( + message: AppLocale.imageUploading.getString(context), ); }, allowClick: false, diff --git a/lib/page/data/chat_history_datasource.dart b/lib/page/data/chat_history_datasource.dart index 40a16414..25aa86d0 100644 --- a/lib/page/data/chat_history_datasource.dart +++ b/lib/page/data/chat_history_datasource.dart @@ -10,6 +10,8 @@ class ChatHistoryDatasource extends LoadingMoreBase { bool _hasMore = true; bool forceRefresh = false; + String? keyword; + final ChatMessageRepository repo; ChatHistoryDatasource(this.repo); @@ -22,6 +24,7 @@ class ChatHistoryDatasource extends LoadingMoreBase { final histories = await repo.recentChatHistories( chatAnywhereRoomId, 30, + keyword: keyword, offset: 30 * (pageindex - 1), userId: APIServer().localUserID(), ); @@ -47,7 +50,8 @@ class ChatHistoryDatasource extends LoadingMoreBase { } @override - Future refresh([bool notifyStateChanged = false]) async { + Future refresh([bool notifyStateChanged = false, String? keyword]) async { + this.keyword = keyword; _hasMore = true; pageindex = 1; //force to refresh list when you don't want clear list before request diff --git a/lib/page/drawer.dart b/lib/page/drawer.dart index 960f8c03..561d39c0 100644 --- a/lib/page/drawer.dart +++ b/lib/page/drawer.dart @@ -35,9 +35,8 @@ class _LeftDrawerState extends State { child: Column( children: [ DrawerHeader( - padding: PlatformTool.isMacOS() - ? const EdgeInsets.only(top: kToolbarHeight) - : const EdgeInsets.all(0), + padding: + PlatformTool.isMacOS() ? const EdgeInsets.only(top: kToolbarHeight) : const EdgeInsets.all(0), decoration: BoxDecoration( color: Colors.white, image: DecorationImage( @@ -59,9 +58,7 @@ class _LeftDrawerState extends State { noBorder: true, onPaymentReturn: () { if (userInfo != null) { - context - .read() - .add(AccountLoadEvent(cache: false)); + context.read().add(AccountLoadEvent(cache: false)); } }, ); @@ -88,8 +85,7 @@ class _LeftDrawerState extends State { ), IconBox( icon: const Icon(Icons.palette_outlined), - title: - Text(AppLocale.creativeIsland.getString(context)), + title: Text(AppLocale.creativeIsland.getString(context)), onTap: () { context.push('/creative-draw'); }, @@ -102,15 +98,13 @@ class _LeftDrawerState extends State { title: Text(AppLocale.histories.getString(context)), onTap: () { context.push('/chat-chat/history').whenComplete(() { - context - .read() - .add(ChatChatLoadRecentHistories()); + context.read().add(ChatChatLoadRecentHistories()); }); }, ), ListTile( leading: const Icon(Icons.settings_outlined), - title: const Text('设置'), + title: Text(AppLocale.settings.getString(context)), onTap: () { context.push('/setting'); }, @@ -133,7 +127,7 @@ class _LeftDrawerState extends State { child: Row( mainAxisAlignment: MainAxisAlignment.center, children: [ - const Text('关注我们:'), + Text(AppLocale.socialMedia.getString(context)), const SizedBox(width: 10), Image.asset('assets/weibo.png', width: 25), ], @@ -149,7 +143,7 @@ class _LeftDrawerState extends State { child: Row( mainAxisAlignment: MainAxisAlignment.center, children: [ - const Text('本项目开源,欢迎贡献:'), + Text(AppLocale.opensource.getString(context)), const SizedBox(width: 10), Image.asset('assets/github.png', width: 25), ], diff --git a/lib/page/home.dart b/lib/page/home.dart index 21a7bd0b..2f0f7154 100644 --- a/lib/page/home.dart +++ b/lib/page/home.dart @@ -1,7 +1,6 @@ import 'package:askaide/bloc/account_bloc.dart'; import 'package:askaide/bloc/chat_chat_bloc.dart'; import 'package:askaide/bloc/chat_message_bloc.dart'; -import 'package:askaide/bloc/free_count_bloc.dart'; import 'package:askaide/bloc/room_bloc.dart'; import 'package:askaide/helper/ability.dart'; import 'package:askaide/helper/cache.dart'; @@ -64,8 +63,7 @@ class _NewHomePageState extends State { // 输入框是否可编辑 final ValueNotifier enableInput = ValueNotifier(true); // 音频播放器控制器 - final AudioPlayerController audioPlayerController = - AudioPlayerController(useRemoteAPI: true); + final AudioPlayerController audioPlayerController = AudioPlayerController(useRemoteAPI: true); // 聊天室 ID,当没有值时,会在第一个聊天消息发送后自动设置新值 int? chatId; @@ -113,9 +111,7 @@ class _NewHomePageState extends State { )); // 查询最近聊天记录 - context - .read() - .add(ChatMessageGetRecentEvent(chatHistoryId: chatId)); + context.read().add(ChatMessageGetRecentEvent(chatHistoryId: chatId)); } } @@ -137,7 +133,8 @@ class _NewHomePageState extends State { if (selectedModel == null && supportModels.isNotEmpty) { setState(() { - selectedModel = supportModels.first; + selectedModel = supportModels.where((e) => e.isDefault).firstOrNull; + selectedModel ??= supportModels.firstOrNull; }); } @@ -251,8 +248,8 @@ class _NewHomePageState extends State { ), // 标题,点击后弹出模型选择对话框 title: GestureDetector( - onTap: () async { - await reloadModels(cache: false); + onTap: () { + reloadModels(cache: false); ModelSwitcher.openActionDialog( // ignore: use_build_context_synchronously @@ -273,18 +270,16 @@ class _NewHomePageState extends State { initValue: selectedModel, ); }, - child: Column( - children: [ - Container( - width: MediaQuery.of(context).size.width / 2, - alignment: Alignment.center, - child: BlocBuilder( + child: SizedBox( + width: MediaQuery.of(context).size.width / 2, + child: Column( + children: [ + BlocBuilder( buildWhen: (previous, current) => current is ChatMessagesLoaded, builder: (context, state) { if (state is ChatMessagesLoaded) { return Text( - state.chatHistory == null || - state.chatHistory!.title == null + state.chatHistory == null || state.chatHistory!.title == null ? AppLocale.chatAnywhere.getString(context) : state.chatHistory!.title!, overflow: TextOverflow.ellipsis, @@ -299,31 +294,30 @@ class _NewHomePageState extends State { AppLocale.chatAnywhere.getString(context), overflow: TextOverflow.ellipsis, maxLines: 1, - style: - const TextStyle(fontSize: CustomSize.appBarTitleSize), + style: const TextStyle(fontSize: CustomSize.appBarTitleSize), ); }, ), - ), - if (selectedModel != null) - Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Text( - selectedModel!.name, - style: TextStyle( - fontSize: CustomSize.appBarTitleSize * 0.6, + if (selectedModel != null) + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text( + selectedModel!.name, + style: TextStyle( + fontSize: CustomSize.appBarTitleSize * 0.6, + color: customColors.backgroundInvertedColor, + ), + ), + Icon( + Icons.unfold_more, color: customColors.backgroundInvertedColor, + size: CustomSize.appBarTitleSize * 0.6, ), - ), - Icon( - Icons.unfold_more, - color: customColors.backgroundInvertedColor, - size: CustomSize.appBarTitleSize * 0.6, - ), - ], - ), - ], + ], + ), + ], + ), ), ), actions: [ @@ -338,24 +332,6 @@ class _NewHomePageState extends State { if (state is RoomLoaded && currentModelV2 == null) { await loadCurrentModel(state.room.model); } - - if (state is RoomLoaded && state.cascading) { - if (state.room.model.startsWith('v2@')) { - if (currentModelV2 != null && currentModelV2!.modelId != null) { - // 加载免费使用次数 - // ignore: use_build_context_synchronously - context.read().add(FreeCountReloadEvent( - model: currentModelV2!.modelId!, - )); - } - } else { - // 加载免费使用次数 - // ignore: use_build_context_synchronously - context.read().add(FreeCountReloadEvent( - model: selectedModel?.id ?? state.room.model, - )); - } - } }, buildWhen: (previous, current) => current is RoomLoaded, builder: (context, room) { @@ -438,10 +414,6 @@ class _NewHomePageState extends State { enableInput.value = false; }); } else if (!state.processing && !enableInput.value) { - // 更新免费使用次数 - context.read().add(FreeCountReloadEvent( - model: selectedModel?.id ?? room.room.model)); - // 聊天回复完成时,取消输入框的禁止编辑状态 setState(() { enableInput.value = true; @@ -469,12 +441,10 @@ class _NewHomePageState extends State { width: CustomSize.adaptiveMaxWindowWidth(context), child: Center( child: StopButton( - label: '停止输出', + label: AppLocale.stopOutput.getString(context), onPressed: () { HapticFeedbackHelper.mediumImpact(); - context - .read() - .add(ChatMessageStopEvent()); + context.read().add(ChatMessageStopEvent()); }, ), ), @@ -493,64 +463,41 @@ class _NewHomePageState extends State { ), color: customColors.chatInputPanelBackground, ), - child: BlocBuilder( - builder: (context, freeState) { - var hintText = '有问题尽管问我'; - if (freeState is FreeCountLoadedState) { - final matched = freeState.model(room.room.model); - if (matched != null && - matched.leftCount > 0 && - matched.maxCount > 0) { - hintText += '(今日还可免费${matched.leftCount}次)'; + child: BlocBuilder( + buildWhen: (previous, current) => current is ChatMessagesLoaded, + builder: (context, state) { + var enableImageUpload = false; + if (state is ChatMessagesLoaded) { + if (currentModelV2 != null) { + enableImageUpload = currentModelV2?.supportVision ?? false; + } else { + var model = state.chatHistory?.model ?? room.room.model; + final cur = supportModels.where((e) => e.id == model).firstOrNull; + enableImageUpload = cur?.supportVision ?? false; } } - return BlocBuilder( - buildWhen: (previous, current) => - current is ChatMessagesLoaded, - builder: (context, state) { - var enableImageUpload = false; - if (state is ChatMessagesLoaded) { - if (currentModelV2 != null) { - enableImageUpload = - currentModelV2?.supportVision ?? false; - } else { - var model = state.chatHistory?.model ?? room.room.model; - final cur = supportModels - .where((e) => e.id == model) - .firstOrNull; - enableImageUpload = cur?.supportVision ?? false; - } - } - - enableImageUpload = selectedModel == null - ? enableImageUpload - : (selectedModel?.supportVision ?? false); + enableImageUpload = selectedModel == null ? enableImageUpload : (selectedModel?.supportVision ?? false); - return ChatInput( - enableNotifier: enableInput, - onSubmit: (value) { - handleSubmit(value); - FocusManager.instance.primaryFocus?.unfocus(); - }, - enableImageUpload: enableImageUpload, - onImageSelected: (files) { - setState(() { - selectedImageFiles = files; - }); - }, - selectedImageFiles: - enableImageUpload ? selectedImageFiles : [], - hintText: hintText, - onVoiceRecordTappedEvent: () { - audioPlayerController.stop(); - }, - onStopGenerate: () { - context - .read() - .add(ChatMessageStopEvent()); - }, - ); + return ChatInput( + enableNotifier: enableInput, + onSubmit: (value) { + handleSubmit(value); + FocusManager.instance.primaryFocus?.unfocus(); + }, + enableImageUpload: enableImageUpload, + onImageSelected: (files) { + setState(() { + selectedImageFiles = files; + }); + }, + selectedImageFiles: enableImageUpload ? selectedImageFiles : [], + hintText: AppLocale.askMeAnyQuestion.getString(context), + onVoiceRecordTappedEvent: () { + audioPlayerController.stop(); + }, + onStopGenerate: () { + context.read().add(ChatMessageStopEvent()); }, ); }, @@ -558,8 +505,7 @@ class _NewHomePageState extends State { ), // 选择模式工具栏 - if (chatPreviewController.selectMode) - SelectModeToolbar(chatPreviewController: chatPreviewController), + if (chatPreviewController.selectMode) SelectModeToolbar(chatPreviewController: chatPreviewController), ], ); } @@ -573,9 +519,7 @@ class _NewHomePageState extends State { bool selectMode, ) { final loadedMessages = loadedState.messages as List; - if (room.room.initMessage != null && - room.room.initMessage != '' && - loadedMessages.isEmpty) { + if (room.room.initMessage != null && room.room.initMessage != '' && loadedMessages.isEmpty) { loadedMessages.add( Message( Role.receiver, @@ -603,15 +547,12 @@ class _NewHomePageState extends State { } if (e.avatarUrl == null || e.senderName == null) { - if (loadedState.chatHistory != null && - loadedState.chatHistory!.model != null) { + if (loadedState.chatHistory != null && loadedState.chatHistory!.model != null) { if (currentModelV2 != null) { e.senderName = currentModelV2!.name; e.avatarUrl = currentModelV2!.avatarUrl; } else { - final mod = supportModels - .where((e) => e.id == loadedState.chatHistory!.model!) - .firstOrNull; + final mod = supportModels.where((e) => e.id == loadedState.chatHistory!.model!).firstOrNull; if (mod != null) { e.senderName = mod.shortName; e.avatarUrl = mod.avatarUrl; @@ -620,9 +561,7 @@ class _NewHomePageState extends State { } } - final stateMessage = - room.states[widget.stateManager.getKey(e.roomId ?? 0, e.id ?? 0)] ?? - MessageState(); + final stateMessage = room.states[widget.stateManager.getKey(e.roomId ?? 0, e.id ?? 0)] ?? MessageState(); return MessageWithState(e, stateMessage); }).toList(); @@ -663,11 +602,9 @@ class _NewHomePageState extends State { }, onResetContext: () => handleResetContext(context), onResentEvent: (message, index) { - scrollController.animateTo(0, - duration: const Duration(milliseconds: 500), curve: Curves.easeOut); + scrollController.animateTo(0, duration: const Duration(milliseconds: 500), curve: Curves.easeOut); - handleSubmit(message.text, - messagetType: message.type, index: index, isResent: true); + handleSubmit(message.text, messagetType: message.type, index: index, isResent: true); }, onSpeakEvent: (message) { audioPlayerController.playAudio(message.text); @@ -692,8 +629,8 @@ class _NewHomePageState extends State { if (selectedImageFiles.isNotEmpty) { final cancel = BotToast.showCustomLoading( toastBuilder: (cancel) { - return const LoadingIndicator( - message: '正在上传图片,请稍后...', + return LoadingIndicator( + message: AppLocale.imageUploading.getString(context), ); }, allowClick: false, @@ -745,10 +682,7 @@ class _NewHomePageState extends State { model: selectedModel?.id, type: messagetType, chatHistoryId: chatId, - images: selectedImageFiles - .where((e) => e.uploaded) - .map((e) => e.url!) - .toList(), + images: selectedImageFiles.where((e) => e.uploaded).map((e) => e.url!).toList(), ), index: index, isResent: isResent, @@ -756,8 +690,6 @@ class _NewHomePageState extends State { ); // ignore: use_build_context_synchronously - context - .read() - .add(RoomLoadEvent(chatAnywhereRoomId, cascading: false)); + context.read().add(RoomLoadEvent(chatAnywhereRoomId, cascading: false)); } } diff --git a/lib/page/setting/account_security.dart b/lib/page/setting/account_security.dart index 220c3ea1..2f3692fd 100644 --- a/lib/page/setting/account_security.dart +++ b/lib/page/setting/account_security.dart @@ -52,9 +52,7 @@ class _AccountSecurityScreenState extends State { return; } - _weChatResponse = weChatResponseEventHandler - .distinct((a, b) => a == b) - .listen((event) { + _weChatResponse = weChatResponseEventHandler.distinct((a, b) => a == b).listen((event) { if (event is WeChatAuthResponse) { if (event.errCode != 0) { showErrorMessage(event.errStr!); @@ -68,7 +66,7 @@ class _AccountSecurityScreenState extends State { APIServer().bindWechat(code: event.code!).then((_) { context.read().add(AccountLoadEvent()); - showSuccessMessage('绑定成功'); + showSuccessMessage(AppLocale.operateSuccess.getString(context)); }).onError((error, stackTrace) { showErrorMessageEnhanced(context, error!); }); @@ -89,16 +87,16 @@ class _AccountSecurityScreenState extends State { child: Scaffold( appBar: AppBar( toolbarHeight: CustomSize.toolbarHeight, - title: const Text( - '账号设置', - style: TextStyle(fontSize: CustomSize.appBarTitleSize), + title: Text( + AppLocale.accountSettings.getString(context), + style: const TextStyle(fontSize: CustomSize.appBarTitleSize), ), centerTitle: true, actions: [ EnhancedPopupMenu( items: [ EnhancedPopupMenuItem( - title: '注销账号', + title: AppLocale.deleteAccount.getString(context), icon: Icons.delete_forever, iconColor: Colors.red, onTap: (ctx) { @@ -127,20 +125,18 @@ class _AccountSecurityScreenState extends State { context, [ SettingsSection( - title: const Text('基础信息'), + title: Text(AppLocale.basicInfo.getString(context)), tiles: [ SettingsTile( - title: const Text('昵称'), + title: Text(AppLocale.nickname.getString(context)), trailing: Row( children: [ Text( - state.user!.user.name == null || - state.user!.user.name == '' - ? '未设置' + state.user!.user.name == null || state.user!.user.name == '' + ? AppLocale.unset.getString(context) : state.user!.user.name!, style: TextStyle( - color: customColors.weakTextColor - ?.withAlpha(200), + color: customColors.weakTextColor?.withAlpha(200), fontSize: 13, ), ), @@ -154,40 +150,34 @@ class _AccountSecurityScreenState extends State { onPressed: (context) { openTextFieldDialog( context, - title: '设置昵称', - hint: '请输入你的昵称', + title: AppLocale.setNickname.getString(context), + hint: AppLocale.inputYourNickname.getString(context), maxLine: 1, maxLength: 30, defaultValue: state.user?.user.name, onSubmit: (value) { - context - .read() - .add(AccountUpdateEvent(realname: value)); + context.read().add(AccountUpdateEvent(realname: value)); return true; }, ); }, ), SettingsTile( - title: const Text('手机号'), + title: Text(AppLocale.phone.getString(context)), trailing: Row( children: [ Text( - state.user!.user.phone == null || - state.user!.user.phone == '' - ? '绑定' + state.user!.user.phone == null || state.user!.user.phone == '' + ? AppLocale.bindPhone.getString(context) : state.user!.user.phone!, style: TextStyle( - color: customColors.weakTextColor - ?.withAlpha(200), + color: customColors.weakTextColor?.withAlpha(200), fontSize: 13, ), ), - if (state.user!.user.phone == null || - state.user!.user.phone == '') + if (state.user!.user.phone == null || state.user!.user.phone == '') const SizedBox(width: 5), - if (state.user!.user.phone == null || - state.user!.user.phone == '') + if (state.user!.user.phone == null || state.user!.user.phone == '') const Icon( CupertinoIcons.chevron_forward, size: 18, @@ -196,35 +186,28 @@ class _AccountSecurityScreenState extends State { ], ), onPressed: (context) { - if (state.user!.user.phone == null || - state.user!.user.phone == '') { - context - .push('/bind-phone?is_signin=false') - .then((value) => Logger.instance.d(value)); + if (state.user!.user.phone == null || state.user!.user.phone == '') { + context.push('/bind-phone?is_signin=false').then((value) => Logger.instance.d(value)); } }, ), if (Ability().enableWechatSignin && wechatInstalled) SettingsTile( - title: const Text('微信账号'), + title: Text(AppLocale.wechatAccount.getString(context)), trailing: Row( children: [ Text( - state.user!.user.unionId == null || - state.user!.user.unionId == '' - ? '绑定' - : '已绑定', + state.user!.user.unionId == null || state.user!.user.unionId == '' + ? AppLocale.bind.getString(context) + : AppLocale.bound.getString(context), style: TextStyle( - color: customColors.weakTextColor - ?.withAlpha(200), + color: customColors.weakTextColor?.withAlpha(200), fontSize: 13, ), ), - if (state.user!.user.unionId == null || - state.user!.user.unionId == '') + if (state.user!.user.unionId == null || state.user!.user.unionId == '') const SizedBox(width: 5), - if (state.user!.user.unionId == null || - state.user!.user.unionId == '') + if (state.user!.user.unionId == null || state.user!.user.unionId == '') const Icon( CupertinoIcons.chevron_forward, size: 18, @@ -233,21 +216,19 @@ class _AccountSecurityScreenState extends State { ], ), onPressed: (context) async { - if (state.user!.user.unionId == null || - state.user!.user.unionId == '') { - final ok = await sendWeChatAuth( - scope: "snsapi_userinfo", - state: "wechat_sdk_demo_test"); + if (state.user!.user.unionId == null || state.user!.user.unionId == '') { + final ok = + await sendWeChatAuth(scope: "snsapi_userinfo", state: "wechat_sdk_demo_test"); if (!ok) { - showErrorMessage('请先安装微信后再使用该功能'); + showErrorMessage(AppLocale.installWeChat.getString(context)); } } }, ), SettingsTile( title: Text(state.user!.control.isSetPassword - ? '修改密码' - : '设置密码'), + ? AppLocale.modifyPassword.getString(context) + : AppLocale.setPassword.getString(context)), trailing: const Icon( CupertinoIcons.chevron_forward, size: 18, @@ -273,9 +254,7 @@ class _AccountSecurityScreenState extends State { context, AppLocale.confirmSignOut.getString(context), () { - context - .read() - .add(AccountSignOutEvent()); + context.read().add(AccountSignOutEvent()); context.go('/login'); }, danger: true, @@ -302,25 +281,28 @@ Widget buildSettingsList( List sections, ) { final customColors = Theme.of(context).extension()!; - return RefreshIndicator( - color: customColors.linkColor, - displacement: 20, - onRefresh: () async { - context.read().add(AccountLoadEvent()); - }, - child: SettingsList( - platform: DevicePlatform.iOS, - lightTheme: const SettingsThemeData( - settingsListBackground: Colors.transparent, - settingsSectionBackground: Color.fromARGB(255, 255, 255, 255), - ), - darkTheme: const SettingsThemeData( - settingsListBackground: Colors.transparent, - settingsSectionBackground: Color.fromARGB(255, 27, 27, 27), - titleTextColor: Color.fromARGB(255, 239, 239, 239), + return SafeArea( + top: false, + child: RefreshIndicator( + color: customColors.linkColor, + displacement: 20, + onRefresh: () async { + context.read().add(AccountLoadEvent()); + }, + child: SettingsList( + platform: DevicePlatform.iOS, + lightTheme: const SettingsThemeData( + settingsListBackground: Colors.transparent, + settingsSectionBackground: Color.fromARGB(255, 255, 255, 255), + ), + darkTheme: const SettingsThemeData( + settingsListBackground: Colors.transparent, + settingsSectionBackground: Color.fromARGB(255, 27, 27, 27), + titleTextColor: Color.fromARGB(255, 239, 239, 239), + ), + sections: sections, + contentPadding: const EdgeInsets.all(0), ), - sections: sections, - contentPadding: const EdgeInsets.all(0), ), ); } diff --git a/lib/page/setting/article.dart b/lib/page/setting/article.dart index 6c46e237..f96dbd0f 100644 --- a/lib/page/setting/article.dart +++ b/lib/page/setting/article.dart @@ -24,8 +24,8 @@ class ArticleScreen extends StatefulWidget { class _ArticleScreenState extends State { Article article = Article( id: 0, - title: '标题', - content: '内容', + title: 'Title', + content: 'Content', ); @override @@ -73,8 +73,7 @@ class _ArticleScreenState extends State { padding: const EdgeInsets.symmetric(horizontal: 10), child: SingleChildScrollView( child: ColumnBlock( - padding: - const EdgeInsets.symmetric(horizontal: 15, vertical: 15), + padding: const EdgeInsets.symmetric(horizontal: 15, vertical: 15), children: [ Column( crossAxisAlignment: CrossAxisAlignment.start, @@ -83,7 +82,7 @@ class _ArticleScreenState extends State { mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text( - '作者:${article.author ?? '管理员'}', + 'Author: ${article.author ?? 'Admin'}', style: TextStyle( fontSize: 12, color: customColors.weakTextColor, @@ -91,12 +90,10 @@ class _ArticleScreenState extends State { ), if (article.createdAt != null) Text( - DateFormat('yyyy/MM/dd HH:mm') - .format(article.createdAt!.toLocal()), + DateFormat('yyyy/MM/dd HH:mm').format(article.createdAt!.toLocal()), style: TextStyle( fontSize: 12, - color: - customColors.weakTextColor?.withAlpha(100), + color: customColors.weakTextColor?.withAlpha(100), ), ), ], diff --git a/lib/page/setting/background_selector.dart b/lib/page/setting/background_selector.dart index 77f9ac79..c3052526 100644 --- a/lib/page/setting/background_selector.dart +++ b/lib/page/setting/background_selector.dart @@ -21,8 +21,7 @@ class BackgroundSelectorScreen extends StatefulWidget { const BackgroundSelectorScreen({super.key, required this.setting}); @override - State createState() => - _BackgroundSelectorScreenState(); + State createState() => _BackgroundSelectorScreenState(); } class _BackgroundSelectorScreenState extends State { @@ -66,8 +65,7 @@ class _BackgroundSelectorScreenState extends State { const Text('图片选择'), const SizedBox(height: 10), BlocBuilder( - buildWhen: (previous, current) => - current is BackgroundImageLoaded, + buildWhen: (previous, current) => current is BackgroundImageLoaded, builder: (context, state) { if (state is BackgroundImageLoaded) { return GridView.count( @@ -88,8 +86,7 @@ class _BackgroundSelectorScreenState extends State { children: [ ClipRRect( borderRadius: BorderRadius.circular(8), - child: - Image.asset('assets/light-dark-auto.png'), + child: Image.asset('assets/light-dark-auto.png'), ), Positioned( child: Container( @@ -99,8 +96,7 @@ class _BackgroundSelectorScreenState extends State { style: TextStyle( fontSize: 12, fontWeight: FontWeight.w500, - color: - Color.fromARGB(255, 146, 146, 146), + color: Color.fromARGB(255, 146, 146, 146), ), ), ), @@ -117,8 +113,7 @@ class _BackgroundSelectorScreenState extends State { }, child: ClipRRect( borderRadius: BorderRadius.circular(8), - child: CachedNetworkImageEnhanced( - imageUrl: img.preview), + child: CachedNetworkImageEnhanced(imageUrl: img.preview), ), ), Material( @@ -130,11 +125,9 @@ class _BackgroundSelectorScreenState extends State { selectDialogOpened = true; HapticFeedbackHelper.mediumImpact(); - FilePickerResult? result = await FilePicker - .platform + FilePickerResult? result = await FilePicker.platform .pickFiles(type: FileType.image) - .whenComplete( - () => selectDialogOpened = false); + .whenComplete(() => selectDialogOpened = false); if (result != null && result.files.isNotEmpty) { setState(() { _controller.text = result.files.first.path!; @@ -160,12 +153,11 @@ class _BackgroundSelectorScreenState extends State { color: customColors.chatInputPanelText, ), Text( - '自定义', + AppLocale.custom.getString(context), style: TextStyle( fontSize: 10, fontWeight: FontWeight.w500, - color: customColors.chatInputPanelText - ?.withOpacity(0.8), + color: customColors.chatInputPanelText?.withOpacity(0.8), ), ), ], @@ -219,8 +211,7 @@ class _BackgroundSelectorScreenState extends State { sigmaX: showOriginalImage ? 0 : blur, sigmaY: showOriginalImage ? 0 : blur, ), - child: - const SizedBox(width: double.infinity, height: 200), + child: const SizedBox(width: double.infinity, height: 200), ), ), ), @@ -249,30 +240,24 @@ class _BackgroundSelectorScreenState extends State { const SizedBox(height: 10), EnhancedButton( onPressed: () { - widget.setting - .set(settingBackgroundImageBlur, blur.toString()); + widget.setting.set(settingBackgroundImageBlur, blur.toString()); - final originalFilepath = - widget.setting.get(settingBackgroundImage); + final originalFilepath = widget.setting.get(settingBackgroundImage); if (originalFilepath != _controller.text) { // 移除原图 - if (originalFilepath != null && - originalFilepath != '' && - !originalFilepath.startsWith('http')) { + if (originalFilepath != null && originalFilepath != '' && !originalFilepath.startsWith('http')) { removeExternalFile(originalFilepath); } // 复制新图 if (_controller.text != '') { if (!_controller.text.startsWith('http')) { - copyExternalFileToAppDocs(_controller.text) - .then((value) { + copyExternalFileToAppDocs(_controller.text).then((value) { widget.setting.set(settingBackgroundImage, value); }); } else { - widget.setting - .set(settingBackgroundImage, _controller.text); + widget.setting.set(settingBackgroundImage, _controller.text); } } else { // 恢复为原图 @@ -280,8 +265,7 @@ class _BackgroundSelectorScreenState extends State { } } - showSuccessMessage( - AppLocale.operateSuccess.getString(context)); + showSuccessMessage(AppLocale.operateSuccess.getString(context)); // Navigator.pop(context); }, title: AppLocale.save.getString(context), diff --git a/lib/page/setting/change_password.dart b/lib/page/setting/change_password.dart index 5c99532d..68bb7632 100644 --- a/lib/page/setting/change_password.dart +++ b/lib/page/setting/change_password.dart @@ -26,8 +26,7 @@ class ChangePasswordScreen extends StatefulWidget { class _ChangePasswordScreenState extends State { final TextEditingController _passwordController = TextEditingController(); - final TextEditingController _verificationCodeController = - TextEditingController(); + final TextEditingController _verificationCodeController = TextEditingController(); String verifyCodeId = ''; @@ -45,9 +44,9 @@ class _ChangePasswordScreenState extends State { return Scaffold( appBar: AppBar( toolbarHeight: CustomSize.toolbarHeight, - title: const Text( - '修改密码', - style: TextStyle(fontSize: CustomSize.appBarTitleSize), + title: Text( + AppLocale.modifyPassword.getString(context), + style: const TextStyle(fontSize: CustomSize.appBarTitleSize), ), centerTitle: true, ), @@ -60,15 +59,18 @@ class _ChangePasswordScreenState extends State { child: Column( children: [ ColumnBlock( + innerPanding: 15, + backgroundColor: Colors.transparent, + showDivider: false, children: [ PasswordField( controller: _passwordController, labelText: AppLocale.newPassword.getString(context), hintText: AppLocale.passwordInputTips.getString(context), - inColumnBlock: true, + inColumnBlock: false, ), VerifyCodeInput( - inColumnBlock: true, + inColumnBlock: false, controller: _verificationCodeController, onVerifyCodeSent: (id) { verifyCodeId = id; @@ -82,7 +84,6 @@ class _ChangePasswordScreenState extends State { ), ], ), - const SizedBox(height: 15), Container( height: 45, width: double.infinity, diff --git a/lib/page/setting/custom_home_models.dart b/lib/page/setting/custom_home_models.dart index 240709ca..36918a77 100644 --- a/lib/page/setting/custom_home_models.dart +++ b/lib/page/setting/custom_home_models.dart @@ -48,7 +48,7 @@ class _CustomHomeModelsPageState extends State { type: 'model', id: '', supportVision: false, - name: '未设置', + name: 'Unset', ), ]; @@ -62,7 +62,7 @@ class _CustomHomeModelsPageState extends State { type: 'model', id: '', supportVision: false, - name: '未设置', + name: 'Unset', )); } } @@ -78,7 +78,7 @@ class _CustomHomeModelsPageState extends State { type: 'model', id: '', supportVision: false, - name: '未设置', + name: 'Unset', )); } @@ -120,8 +120,7 @@ class _CustomHomeModelsPageState extends State { const SizedBox(height: 10), ColumnBlock( innerPanding: 5, - padding: - const EdgeInsets.symmetric(horizontal: 15, vertical: 15), + padding: const EdgeInsets.symmetric(horizontal: 15, vertical: 15), children: [ for (var i = 0; i < models.length; i++) GestureDetector( @@ -147,11 +146,11 @@ class _CustomHomeModelsPageState extends State { type: 'model', id: '', supportVision: false, - name: '未设置', + name: 'Unset', ); }); }, - confirmText: '重置', + confirmText: AppLocale.reset.getString(context), ); } }, @@ -207,16 +206,10 @@ class _CustomHomeModelsPageState extends State { ); try { - final selectedModels = models - .where((e) => e.id != '') - .map((e) => e.uniqueKey) - .toList(); - await APIServer() - .updateCustomHomeModelsV2(models: selectedModels); + final selectedModels = models.where((e) => e.id != '').map((e) => e.uniqueKey).toList(); + await APIServer().updateCustomHomeModelsV2(models: selectedModels); - APIServer() - .capabilities(cache: false) - .then((value) => Ability().updateCapabilities(value)); + APIServer().capabilities(cache: false).then((value) => Ability().updateCapabilities(value)); showSuccessMessage( // ignore: use_build_context_synchronously @@ -326,9 +319,7 @@ class HomeModelItem extends StatelessWidget { buildTabView( context, customColors, - models - .where((e) => e.type == 'room_gallery') - .toList(), + models.where((e) => e.type == 'room_gallery').toList(), ), buildTabView( context, @@ -389,9 +380,7 @@ class HomeModelItem extends StatelessWidget { width: 10, child: Icon( Icons.check, - color: initValue == item.id - ? customColors.linkColor - : Colors.transparent, + color: initValue == item.id ? customColors.linkColor : Colors.transparent, ), ), ], diff --git a/lib/page/setting/destroy_account.dart b/lib/page/setting/destroy_account.dart index 6eee2df8..ef84e9c9 100644 --- a/lib/page/setting/destroy_account.dart +++ b/lib/page/setting/destroy_account.dart @@ -27,8 +27,7 @@ class DestroyAccountScreen extends StatefulWidget { class _DestroyAccountScreenState extends State { final TextEditingController _passwordController = TextEditingController(); - final TextEditingController _verificationCodeController = - TextEditingController(); + final TextEditingController _verificationCodeController = TextEditingController(); String verifyCodeId = ''; @@ -46,9 +45,9 @@ class _DestroyAccountScreenState extends State { return Scaffold( appBar: AppBar( toolbarHeight: CustomSize.toolbarHeight, - title: const Text( - '注销账号', - style: TextStyle(fontSize: CustomSize.appBarTitleSize), + title: Text( + AppLocale.deleteAccount.getString(context), + style: const TextStyle(fontSize: CustomSize.appBarTitleSize), ), centerTitle: true, ), @@ -67,9 +66,10 @@ class _DestroyAccountScreenState extends State { ), const SizedBox(height: 15), ColumnBlock( + backgroundColor: Colors.transparent, children: [ VerifyCodeInput( - inColumnBlock: true, + inColumnBlock: false, controller: _verificationCodeController, onVerifyCodeSent: (id) { verifyCodeId = id; @@ -93,9 +93,9 @@ class _DestroyAccountScreenState extends State { ), child: TextButton( onPressed: onDestroySubmit, - child: const Text( - '确认注销账号', - style: TextStyle(color: Colors.white, fontSize: 18), + child: Text( + AppLocale.confirmDeleteAccount.getString(context), + style: const TextStyle(color: Colors.white, fontSize: 18), ), ), ), diff --git a/lib/page/setting/diagnosis.dart b/lib/page/setting/diagnosis.dart index 6f670df7..7eeb109f 100644 --- a/lib/page/setting/diagnosis.dart +++ b/lib/page/setting/diagnosis.dart @@ -84,11 +84,9 @@ class _DiagnosisScreenState extends State { onPressed: () { openConfirmDialog( context, - '该操作将会清空所有设置和数据,是否继续?', + 'This action will erase all settings and data, do you want to proceed?', () async { - final databasePath = - (await databaseFactory.getDatabasesPath()) - .replaceAll('\\', '/'); + final databasePath = (await databaseFactory.getDatabasesPath()).replaceAll('\\', '/'); Logger.instance.d('databasePath: $databasePath'); @@ -103,15 +101,15 @@ class _DiagnosisScreenState extends State { AppLocale.operateSuccess.getString(context), ); - SystemChannels.platform - .invokeMethod('SystemNavigator.pop'); + SystemChannels.platform.invokeMethod('SystemNavigator.pop'); } catch (e) { Logger.instance.e(e); showBeautyDialog( // ignore: use_build_context_synchronously context, type: QuickAlertType.error, - text: '数据文件删除失败,请先关闭应用后,手动删除目录 $databasePath 之后再重启应用', + text: + 'Data file deletion failed. Please close the application first, manually delete the directory $databasePath, and then restart the application.', ); } }, @@ -121,9 +119,7 @@ class _DiagnosisScreenState extends State { child: Text( '重置系统', style: TextStyle( - color: isUploaded - ? customColors.weakTextColor?.withAlpha(100) - : customColors.weakLinkColor, + color: isUploaded ? customColors.weakTextColor?.withAlpha(100) : customColors.weakLinkColor, fontSize: 12, ), ), @@ -136,9 +132,7 @@ class _DiagnosisScreenState extends State { return; } - APIServer() - .diagnosisUpload(data: diagnosisInfo) - .then((value) { + APIServer().diagnosisUpload(data: diagnosisInfo).then((value) { showSuccessMessage('上报成功'); setState(() { isUploaded = true; @@ -150,9 +144,7 @@ class _DiagnosisScreenState extends State { child: Text( AppLocale.report.getString(context), style: TextStyle( - color: isUploaded - ? customColors.weakTextColor?.withAlpha(100) - : customColors.weakLinkColor, + color: isUploaded ? customColors.weakTextColor?.withAlpha(100) : customColors.weakLinkColor, fontSize: 12, ), ), @@ -170,31 +162,31 @@ class _DiagnosisScreenState extends State { padding: const EdgeInsets.all(10), children: [ Text( - '服务器: ${APIServer().url}', + 'Server: ${APIServer().url}', style: const TextStyle( fontSize: 10, ), ), Text( - '当前用户 ID: ${APIServer().localUserID()}', + 'User ID: ${APIServer().localUserID()}', style: const TextStyle( fontSize: 10, ), ), const Text( - '客户端版本: $clientVersion', + 'Client Version: $clientVersion', style: TextStyle( fontSize: 10, ), ), Text( - '操作系统: ${PlatformTool.operatingSystem()} | ${PlatformTool.operatingSystemVersion()}', + 'OS: ${PlatformTool.operatingSystem()} | ${PlatformTool.operatingSystemVersion()}', style: const TextStyle( fontSize: 10, ), ), Text( - 'OpenAI 自定义: ${Ability().enableLocalOpenAI}', + 'OpenAI Custom: ${Ability().enableLocalOpenAI}', style: const TextStyle( fontSize: 10, ), @@ -203,7 +195,7 @@ class _DiagnosisScreenState extends State { future: databaseFactory.getDatabasesPath(), builder: (context, snapshot) { return Text( - '本地数据库: ${snapshot.data?.replaceAll('\\', '/')}', + 'Local Database: ${snapshot.data?.replaceAll('\\', '/')}', style: const TextStyle( fontSize: 10, ), @@ -211,19 +203,19 @@ class _DiagnosisScreenState extends State { }, ), Text( - '日志文件: ${PathHelper().getLogfilePath}', + 'Log File: ${PathHelper().getLogfilePath}', style: const TextStyle( fontSize: 10, ), ), Text( - '缓存目录: ${PathHelper().getCachePath}', + 'Cache Directory: ${PathHelper().getCachePath}', style: const TextStyle( fontSize: 10, ), ), Text( - '主目录: ${PathHelper().getHomePath}', + 'Main Directory: ${PathHelper().getHomePath}', style: const TextStyle( fontSize: 10, ), diff --git a/lib/page/setting/notification.dart b/lib/page/setting/notification.dart index 3cfe246d..01e17e0e 100644 --- a/lib/page/setting/notification.dart +++ b/lib/page/setting/notification.dart @@ -1,5 +1,6 @@ import 'package:askaide/helper/haptic_feedback.dart'; import 'package:askaide/helper/helper.dart'; +import 'package:askaide/lang/lang.dart'; import 'package:askaide/page/component/background_container.dart'; import 'package:askaide/page/component/loading.dart'; import 'package:askaide/page/component/theme/custom_size.dart'; @@ -8,6 +9,7 @@ import 'package:askaide/page/data/notification_datasource.dart'; import 'package:askaide/repo/api/notification.dart'; import 'package:askaide/repo/settings_repo.dart'; import 'package:flutter/material.dart'; +import 'package:flutter_localization/flutter_localization.dart'; import 'package:flutter_slidable/flutter_slidable.dart'; import 'package:go_router/go_router.dart'; import 'package:loading_more_list/loading_more_list.dart'; @@ -35,9 +37,9 @@ class _NotificationScreenState extends State { return Scaffold( appBar: AppBar( - title: const Text( - '消息', - style: TextStyle(fontSize: CustomSize.appBarTitleSize), + title: Text( + AppLocale.notification.getString(context), + style: const TextStyle(fontSize: CustomSize.appBarTitleSize), ), toolbarHeight: CustomSize.toolbarHeight, centerTitle: true, @@ -63,9 +65,8 @@ class _NotificationScreenState extends State { message: item, customColors: customColors, onTap: () { - context.push(Uri(path: '/article', queryParameters: { - 'id': item.articleId.toString() - }).toString()); + context + .push(Uri(path: '/article', queryParameters: {'id': item.articleId.toString()}).toString()); }, ); }, @@ -74,16 +75,16 @@ class _NotificationScreenState extends State { String msg = ''; switch (status) { case IndicatorStatus.noMoreLoad: - msg = '~ 没有更多了 ~'; + msg = '~ No more left ~'; break; case IndicatorStatus.loadingMoreBusying: - msg = '加载中...'; + msg = 'Loading...'; break; case IndicatorStatus.error: - msg = '加载失败,请稍后再试'; + msg = 'Failed to load, please try again later.'; break; case IndicatorStatus.empty: - msg = '暂无数据'; + msg = 'No data'; break; default: return const Center(child: LoadingIndicator()); @@ -137,7 +138,7 @@ class NotifyMessageItem extends StatelessWidget { children: [ const SizedBox(width: 10), SlidableAction( - label: '详情', + label: 'Details', borderRadius: BorderRadius.circular(10), backgroundColor: Colors.green, icon: Icons.info_outline, @@ -155,11 +156,9 @@ class NotifyMessageItem extends StatelessWidget { ), child: InkWell( child: ListTile( - contentPadding: - const EdgeInsets.symmetric(horizontal: 10, vertical: 5), + contentPadding: const EdgeInsets.symmetric(horizontal: 10, vertical: 5), shape: RoundedRectangleBorder( - borderRadius: - BorderRadius.circular(customColors.borderRadius ?? 8), + borderRadius: BorderRadius.circular(customColors.borderRadius ?? 8), ), title: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, diff --git a/lib/page/setting/setting_screen.dart b/lib/page/setting/setting_screen.dart index cf6fa91b..ea68f3ff 100644 --- a/lib/page/setting/setting_screen.dart +++ b/lib/page/setting/setting_screen.dart @@ -62,7 +62,7 @@ class _SettingScreenState extends State { backgroundColor: Colors.transparent, body: SliverComponent( title: Text( - AppLocale.me.getString(context), + AppLocale.settings.getString(context), style: TextStyle( fontSize: CustomSize.appBarTitleSize, color: customColors.backgroundInvertedColor, @@ -78,7 +78,7 @@ class _SettingScreenState extends State { context.push('/admin/dashboard'); }, icon: const Icon(Icons.developer_board_outlined), - tooltip: '管理后台', + tooltip: 'Admin Dashboard', ); } @@ -90,7 +90,7 @@ class _SettingScreenState extends State { context.push('/notifications'); }, icon: const Icon(Icons.notifications_outlined), - tooltip: '消息通知', + tooltip: 'Notifications', ), ], child: BlocBuilder( @@ -108,8 +108,7 @@ class _SettingScreenState extends State { ), // 邀请卡片 - if (state is AccountLoaded && state.user != null) - _buildInviteCard(context, state), + if (state is AccountLoaded && state.user != null) _buildInviteCard(context, state), // 自定义设置 SettingsSection( @@ -119,16 +118,10 @@ class _SettingScreenState extends State { _buildCommonThemeSetting(customColors), // 语言设置 _buildCommonLanguageSetting(), - // 常用模型 - if (Ability().isUserLogon()) - _buildCustomHomeModelsSetting(customColors), // OpenAI 自定义配置 - if (Ability().enableOpenAI) - _buildOpenAISelfHostedSetting(customColors), + if (Ability().enableOpenAI) _buildOpenAISelfHostedSetting(customColors), // 用户 API Keys 配置 - if (state is AccountLoaded && - state.user != null && - Ability().supportAPIKeys) + if (state is AccountLoaded && state.user != null && Ability().supportAPIKeys) _buildUserAPIKeySetting(customColors), ], ), @@ -156,7 +149,7 @@ class _SettingScreenState extends State { // 服务状态 if (Ability().serviceStatusPage != '') SettingsTile( - title: const Text('服务状态'), + title: Text(AppLocale.serviceStatus.getString(context)), trailing: const Icon( CupertinoIcons.chevron_forward, size: 18, @@ -223,8 +216,7 @@ class _SettingScreenState extends State { showCancelBtn: true, ); } else { - showSuccessMessage( - AppLocale.latestVersion.getString(context)); + showSuccessMessage(AppLocale.latestVersion.getString(context)); } }); }, @@ -238,8 +230,7 @@ class _SettingScreenState extends State { color: Colors.grey, ), onPressed: (_) { - launchUrl(Uri.parse( - 'https://ai.aicode.cc/terms-user.html')); + launchUrl(Uri.parse('https://ai.aicode.cc/terms-user.html')); }, ), // 隐私政策 @@ -251,8 +242,7 @@ class _SettingScreenState extends State { color: Colors.grey, ), onPressed: (_) { - launchUrl(Uri.parse( - 'https://ai.aicode.cc/privacy-policy.html')); + launchUrl(Uri.parse('https://ai.aicode.cc/privacy-policy.html')); }, ), @@ -281,11 +271,9 @@ class _SettingScreenState extends State { tapCount = 0; final showLab = forceShowLab(); - widget.settings.set(settingForceShowLab, - showLab ? 'false' : 'true'); + widget.settings.set(settingForceShowLab, showLab ? 'false' : 'true'); - showSuccessMessage( - showLab ? '已关闭实验室功能' : '已启用实验室功能'); + showSuccessMessage(showLab ? 'Lab Feature Turned Off' : 'Labs features enabled'); setState(() {}); } @@ -304,11 +292,11 @@ class _SettingScreenState extends State { if (userHasLabPermission(state) || forceShowLab()) SettingsSection( - title: const Text('实验室'), + title: Text(AppLocale.lab.getString(context)), tiles: [ if (userHasLabPermission(state)) SettingsTile( - title: const Text('画板'), + title: const Text('Draw Board'), trailing: const Icon( CupertinoIcons.chevron_forward, size: 18, @@ -377,10 +365,7 @@ class _SettingScreenState extends State { /// 用户是否有实验室访问权限 bool userHasLabPermission(AccountState state) { - return state is AccountLoaded && - state.error == null && - state.user != null && - state.user!.control.withLab; + return state is AccountLoaded && state.error == null && state.user != null && state.user!.control.withLab; } /// 是否强制显示实验室功能 @@ -409,8 +394,7 @@ class _SettingScreenState extends State { ); } - CustomSettingsSection _buildInviteCard( - BuildContext context, AccountLoaded state) { + CustomSettingsSection _buildInviteCard(BuildContext context, AccountLoaded state) { if (state.error != null || !state.user!.showInviteMessage) { return CustomSettingsSection( child: Container(), @@ -443,15 +427,12 @@ class _SettingScreenState extends State { mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text(AppLocale.followSystem.getString(context)), - current == '' - ? const Icon(Icons.check, color: Colors.green) - : const SizedBox(), + current == '' ? const Icon(Icons.check, color: Colors.green) : const SizedBox(), ], ), onTap: () async { await widget.settings.set(settingLanguage, ''); - FlutterLocalization.instance - .translate(resolveSystemLanguage(Platform.localeName)); + FlutterLocalization.instance.translate(resolveSystemLanguage(Platform.localeName)); if (context.mounted) { context.pop(); } @@ -462,9 +443,7 @@ class _SettingScreenState extends State { mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ const Text('简体中文'), - current == 'zh-CHS' - ? const Icon(Icons.check, color: Colors.green) - : const SizedBox(), + current == 'zh-CHS' ? const Icon(Icons.check, color: Colors.green) : const SizedBox(), ], ), onTap: () async { @@ -480,9 +459,7 @@ class _SettingScreenState extends State { mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ const Text('English'), - current == 'en' - ? const Icon(Icons.check, color: Colors.green) - : const SizedBox(), + current == 'en' ? const Icon(Icons.check, color: Colors.green) : const SizedBox(), ], ), onTap: () async { @@ -511,8 +488,7 @@ class _SettingScreenState extends State { ]; } - List _buildAccountSetting( - AccountState state, CustomColors customColors) { + List _buildAccountSetting(AccountState state, CustomColors customColors) { if (state is AccountLoaded) { if (state.error != null && state.user == null) { return [ @@ -555,7 +531,7 @@ class _SettingScreenState extends State { }, ), SettingsTile( - title: const Text('免费畅享额度'), + title: Text(AppLocale.freeQuota.getString(context)), trailing: const Icon( CupertinoIcons.chevron_forward, size: 18, @@ -594,8 +570,7 @@ class _SettingScreenState extends State { return SettingsTile.navigation( title: Text(AppLocale.themeMode.getString(context)), onPressed: (context) { - final current = - widget.settings.stringDefault(settingThemeMode, 'system'); + final current = widget.settings.stringDefault(settingThemeMode, 'system'); openModalBottomSheet( context, @@ -608,15 +583,12 @@ class _SettingScreenState extends State { mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text(AppLocale.followSystem.getString(context)), - current == 'system' - ? const Icon(Icons.check, color: Colors.green) - : const SizedBox(), + current == 'system' ? const Icon(Icons.check, color: Colors.green) : const SizedBox(), ], ), onTap: () async { await widget.settings.set(settingThemeMode, 'system'); - AppTheme.instance.mode = - AppTheme.themeModeFormString('system'); + AppTheme.instance.mode = AppTheme.themeModeFormString('system'); if (context.mounted) { context.pop(); } @@ -627,15 +599,12 @@ class _SettingScreenState extends State { mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text(AppLocale.lightThemeMode.getString(context)), - current == 'light' - ? const Icon(Icons.check, color: Colors.green) - : const SizedBox(), + current == 'light' ? const Icon(Icons.check, color: Colors.green) : const SizedBox(), ], ), onTap: () async { await widget.settings.set(settingThemeMode, 'light'); - AppTheme.instance.mode = - AppTheme.themeModeFormString('light'); + AppTheme.instance.mode = AppTheme.themeModeFormString('light'); if (context.mounted) { context.pop(); } @@ -646,15 +615,12 @@ class _SettingScreenState extends State { mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text(AppLocale.darkThemeMode.getString(context)), - current == 'dark' - ? const Icon(Icons.check, color: Colors.green) - : const SizedBox(), + current == 'dark' ? const Icon(Icons.check, color: Colors.green) : const SizedBox(), ], ), onTap: () async { await widget.settings.set(settingThemeMode, 'dark'); - AppTheme.instance.mode = - AppTheme.themeModeFormString('dark'); + AppTheme.instance.mode = AppTheme.themeModeFormString('dark'); if (context.mounted) { context.pop(); } @@ -687,16 +653,6 @@ class _SettingScreenState extends State { ); } - /// 常用模型 - SettingsTile _buildCustomHomeModelsSetting(CustomColors customColors) { - return SettingsTile.navigation( - title: Text(AppLocale.customHomeModels.getString(context)), - onPressed: (context) { - context.push('/setting/custom-home-models'); - }, - ); - } - /// 用户 API Key 配置 SettingsTile _buildUserAPIKeySetting(CustomColors customColors) { return SettingsTile.navigation( @@ -709,7 +665,7 @@ class _SettingScreenState extends State { SettingsTile _buildServerSelfHostedSetting(CustomColors customColors) { return SettingsTile( - title: const Text('自定义服务器'), + title: const Text('Custom server'), trailing: const Icon( CupertinoIcons.chevron_forward, size: 18, @@ -718,9 +674,8 @@ class _SettingScreenState extends State { onPressed: (_) { openTextFieldDialog( context, - title: '服务器地址', - defaultValue: - widget.settings.stringDefault(settingServerURL, apiServerURL), + title: 'Server Address', + defaultValue: widget.settings.stringDefault(settingServerURL, apiServerURL), withSuffixIcon: true, enableSearch: false, futureDataSources: _defaultServerList(), @@ -728,18 +683,18 @@ class _SettingScreenState extends State { widget.settings.set(settingServerURL, value.trim()).then((value) { openConfirmDialog( context, - '设置成功,应用重启后生效', + 'Settings successful, will take effect after app restart', () { try { SystemChannels.platform.invokeMethod('SystemNavigator.pop'); } catch (e) { Logger.instance.e(e); - showErrorMessage('应用重启失败,请手动重启'); + showErrorMessage('Application restart failed, please restart manually'); } }, danger: true, - confirmText: '立即重启', - cancelText: '稍后我自己重启', + confirmText: 'Restart now', + cancelText: 'Restart later', ); }); return true; diff --git a/lib/page/setting/user_api_keys.dart b/lib/page/setting/user_api_keys.dart index d35b3008..c4b6c9f5 100644 --- a/lib/page/setting/user_api_keys.dart +++ b/lib/page/setting/user_api_keys.dart @@ -213,8 +213,9 @@ class _UserAPIKeysScreenState extends State { onTap: () { cancelDialog = BotToast.showCustomLoading( toastBuilder: (cancel) { - return const LoadingIndicator( - message: "正在上传图片,请稍后...", + return LoadingIndicator( + message: AppLocale.imageUploading + .getString(context), ); }, allowClick: false, diff --git a/lib/repo/chat_message_repo.dart b/lib/repo/chat_message_repo.dart index 8adbd9a6..1469eeca 100644 --- a/lib/repo/chat_message_repo.dart +++ b/lib/repo/chat_message_repo.dart @@ -103,10 +103,8 @@ class ChatMessageRepository { } /// 获取 room 中最后一条消息 - Future getLastMessage(int roomId, - {int? userId, int? chatHistoryId}) async { - return await _chatMsgDataProvider.getLastMessage(roomId, - userId: userId, chatHistoryId: chatHistoryId); + Future getLastMessage(int roomId, {int? userId, int? chatHistoryId}) async { + return await _chatMsgDataProvider.getLastMessage(roomId, userId: userId, chatHistoryId: chatHistoryId); } /// 获取 room @@ -148,12 +146,14 @@ class ChatMessageRepository { Future> recentChatHistories( int roomId, int count, { + String? keyword, int? userId, int? offset, }) async { return await _chatHistoryProvider.getChatHistories( roomId, count, + keyword: keyword, userId: userId, offset: offset, ); diff --git a/lib/repo/data/chat_history.dart b/lib/repo/data/chat_history.dart index 053fd2fa..4bf13221 100644 --- a/lib/repo/data/chat_history.dart +++ b/lib/repo/data/chat_history.dart @@ -5,14 +5,36 @@ class ChatHistoryProvider { Database conn; ChatHistoryProvider(this.conn); - Future> getChatHistories(int roomId, int count, - {int? userId, int? offset}) async { - final userConditon = - userId == null ? ' AND user_id IS NULL' : ' AND user_id = $userId'; + Future> getChatHistories( + int roomId, + int count, { + int? userId, + int? offset, + String? keyword, + }) async { + keyword ??= ''; + final userConditon = userId == null ? ' AND user_id IS NULL' : ' AND user_id = $userId'; + + var historyIds = []; + if (keyword != '') { + final histories = await conn.query( + 'chat_message', + where: 'chat_history_id IS NOT NULL AND text LIKE ? $userConditon', + whereArgs: ['%$keyword%'], + columns: ['chat_history_id'], + distinct: true, + ); + + historyIds = histories.map((h) => h['chat_history_id']).toList(); + if (historyIds.isEmpty) { + return []; + } + } + var keywordCondition = keyword != '' ? 'AND id in (${historyIds.join(',')})' : ''; List> histories = await conn.query( 'chat_history', - where: 'room_id = ? $userConditon', + where: 'room_id = ? $userConditon $keywordCondition', whereArgs: [roomId], orderBy: 'updated_at DESC', limit: count, @@ -63,8 +85,7 @@ class ChatHistoryProvider { } Future history(int id) async { - List> histories = await conn.query('chat_history', - where: 'id = ?', whereArgs: [id], limit: 1); + List> histories = await conn.query('chat_history', where: 'id = ?', whereArgs: [id], limit: 1); if (histories.isEmpty) { return null; } diff --git a/lib/repo/model/misc.dart b/lib/repo/model/misc.dart index 768eda02..bcace658 100644 --- a/lib/repo/model/misc.dart +++ b/lib/repo/model/misc.dart @@ -67,8 +67,7 @@ class PromotionEvent { return PromotionEvent( title: json['title'], content: json['content'], - clickButtonType: PromotionEventClickButtonType.fromName( - json['click_button_type'] ?? ''), + clickButtonType: PromotionEventClickButtonType.fromName(json['click_button_type'] ?? ''), clickValue: json['click_value'], clickButtonColor: json['click_button_color'], backgroundImage: json['background_image'], @@ -264,17 +263,10 @@ class RoomInServer { maxContext: json['max_context'] ?? 10, maxTokens: json['max_tokens'], roomType: json['room_type'], - lastActiveTime: json['last_active_time'] != null - ? DateTime.parse(json['last_active_time']) - : null, - createdAt: - json['CreatedAt'] != null ? DateTime.parse(json['CreatedAt']) : null, - updatedAt: - json['UpdatedAt'] != null ? DateTime.parse(json['UpdatedAt']) : null, - members: (json['members'] as List?) - ?.map((e) => e.toString()) - .toList() ?? - [], + lastActiveTime: json['last_active_time'] != null ? DateTime.parse(json['last_active_time']) : null, + createdAt: json['CreatedAt'] != null ? DateTime.parse(json['CreatedAt']) : null, + updatedAt: json['UpdatedAt'] != null ? DateTime.parse(json['UpdatedAt']) : null, + members: (json['members'] as List?)?.map((e) => e.toString()).toList() ?? [], ); } } @@ -455,8 +447,7 @@ class AsyncTaskResp { int? width; int? height; - AsyncTaskResp(this.status, - {this.errors, this.resources, this.originImage, this.width, this.height}); + AsyncTaskResp(this.status, {this.errors, this.resources, this.originImage, this.width, this.height}); toJson() => { 'status': status, @@ -470,14 +461,9 @@ class AsyncTaskResp { static AsyncTaskResp fromJson(Map json) { return AsyncTaskResp( json['status'], - errors: json['errors'] != null - ? (json['errors'] as List).map((e) => e.toString()).toList() - : null, - resources: json['resources'] != null - ? (json['resources'] as List) - .map((e) => e.toString()) - .toList() - : null, + errors: json['errors'] != null ? (json['errors'] as List).map((e) => e.toString()).toList() : null, + resources: + json['resources'] != null ? (json['resources'] as List).map((e) => e.toString()).toList() : null, originImage: json['origin_image'], width: json['width'], height: json['height'], @@ -622,6 +608,7 @@ class Model { String? tagTextColor; String? tagBgColor; bool isNew; + bool isDefault; String get realModelId { return id.split(':').last; @@ -642,6 +629,7 @@ class Model { this.tagBgColor, this.tagTextColor, this.isNew = false, + this.isDefault = false, }); toJson() => { @@ -659,6 +647,7 @@ class Model { 'tag_bg_color': tagBgColor, 'tag_text_color': tagTextColor, 'is_new': isNew, + 'is_default': isDefault, }; static Model fromJson(Map json) { @@ -677,6 +666,7 @@ class Model { tagBgColor: json['tag_bg_color'], tagTextColor: json['tag_text_color'], isNew: json['is_new'] ?? false, + isDefault: json['is_default'] ?? false, ); } } diff --git a/lib/repo/model/model.dart b/lib/repo/model/model.dart index 1d029986..4f23d541 100644 --- a/lib/repo/model/model.dart +++ b/lib/repo/model/model.dart @@ -15,6 +15,8 @@ class Model { bool isNew = false; String category; + bool isDefault; + Model( this.id, this.name, @@ -30,6 +32,7 @@ class Model { this.tagTextColor, this.tagBgColor, this.isNew = false, + this.isDefault = false, }); String uid() { @@ -51,6 +54,7 @@ class Model { String? tagBgColor, bool? isNew, String? category, + bool? isDefault, }) { return Model( id ?? this.id, @@ -67,6 +71,7 @@ class Model { tagBgColor: tagBgColor ?? this.tagBgColor, isNew: isNew ?? this.isNew, category: category ?? this.category, + isDefault: isDefault ?? false, ); } } From 507f2c67e99d7bf1237c761d23c4eeffa4414fb2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=AE=A1=E5=AE=9C=E5=B0=A7?= Date: Tue, 3 Dec 2024 18:29:40 +0800 Subject: [PATCH 10/26] Selected model is highlighted --- lib/page/component/model_item.dart | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/lib/page/component/model_item.dart b/lib/page/component/model_item.dart index c98f5cf2..ed3fb78a 100644 --- a/lib/page/component/model_item.dart +++ b/lib/page/component/model_item.dart @@ -163,7 +163,12 @@ class _ModelItemState extends State { child: Text( item.name, overflow: TextOverflow.ellipsis, - style: const TextStyle(fontSize: 15), + style: TextStyle( + fontSize: 15, + color: + widget.initValue == item.uid() ? customColors.linkColor : null, + fontWeight: widget.initValue == item.uid() ? FontWeight.bold : null, + ), ), ), ), @@ -192,7 +197,9 @@ class _ModelItemState extends State { overflow: TextOverflow.ellipsis, style: TextStyle( fontSize: 12, - color: customColors.weakTextColor, + color: widget.initValue == item.uid() + ? customColors.linkColor + : customColors.weakTextColor, ), ), ], From 04dea5d5c03f569fcb59733d81a8a3caa8645e81 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=AE=A1=E5=AE=9C=E5=B0=A7?= Date: Sat, 7 Dec 2024 02:05:09 +0800 Subject: [PATCH 11/26] Page Style Optimization --- lib/lang/lang.dart | 6 +- lib/main.dart | 160 ++++------ lib/page/admin/channels.dart | 38 +-- lib/page/admin/channels_add.dart | 20 +- lib/page/admin/channels_edit.dart | 36 +-- lib/page/admin/dashboard.dart | 7 +- lib/page/admin/messages.dart | 19 +- lib/page/admin/models.dart | 34 +-- lib/page/admin/models_add.dart | 81 ++---- lib/page/admin/models_edit.dart | 120 +++----- lib/page/admin/payments.dart | 53 +--- lib/page/admin/recently_messages.dart | 173 ++++------- lib/page/admin/rooms.dart | 72 ++--- lib/page/admin/user.dart | 81 ++---- lib/page/admin/users.dart | 53 ++-- lib/page/auth/signin_or_signup.dart | 60 ++-- lib/page/auth/signin_screen.dart | 61 ++-- lib/page/auth/signup_screen.dart | 108 +++---- lib/page/balance/free_statistics.dart | 2 +- lib/page/balance/payment.dart | 98 ++----- lib/page/balance/payment_history.dart | 21 +- lib/page/balance/price_block.dart | 17 +- lib/page/balance/quota_usage_details.dart | 9 +- lib/page/balance/quota_usage_statistics.dart | 20 +- lib/page/chat/component/group_empty.dart | 9 +- lib/page/chat/component/room_item.dart | 50 +--- lib/page/chat/group/chat.dart | 10 +- lib/page/chat/group/create.dart | 21 +- lib/page/chat/group/edit.dart | 10 +- lib/page/chat/home.dart | 31 +- lib/page/chat/home_chat.dart | 41 ++- lib/page/chat/home_chat_history.dart | 2 +- lib/page/chat/room_chat.dart | 7 +- lib/page/chat/room_create.dart | 7 +- lib/page/chat/room_edit.dart | 2 +- lib/page/chat/rooms.dart | 11 +- lib/page/component/attached_button_panel.dart | 3 +- lib/page/component/avatar_selector.dart | 17 +- lib/page/component/bottom_sheet_box.dart | 8 +- lib/page/component/chat/chat_bubble.dart | 29 +- lib/page/component/chat/chat_input.dart | 53 ++-- lib/page/component/chat/chat_preview.dart | 153 ++++------ lib/page/component/chat/chat_share.dart | 52 ++-- lib/page/component/chat/empty.dart | 5 +- lib/page/component/chat/file_upload.dart | 3 +- lib/page/component/chat/markdown.dart | 68 ++--- lib/page/component/chat/markdown/code.dart | 22 +- lib/page/component/chat/role_avatar.dart | 11 +- lib/page/component/chat_tools_button.dart | 7 +- lib/page/component/column_block.dart | 3 +- lib/page/component/dialog.dart | 40 +-- lib/page/component/enhanced_button.dart | 5 +- lib/page/component/enhanced_error.dart | 3 +- lib/page/component/enhanced_input.dart | 43 +-- lib/page/component/enhanced_popup_menu.dart | 8 +- lib/page/component/enhanced_textfield.dart | 35 +-- lib/page/component/gallery_item_share.dart | 30 +- lib/page/component/global_alert.dart | 10 +- lib/page/component/icon_box.dart | 5 +- lib/page/component/icon_box_button.dart | 5 +- lib/page/component/image_preview.dart | 58 ++-- lib/page/component/invite_card.dart | 10 +- lib/page/component/message_box.dart | 3 +- lib/page/component/model_item.dart | 5 +- lib/page/component/notify_message.dart | 3 +- lib/page/component/prompt_tags_selector.dart | 32 +- lib/page/component/random_avatar.dart | 16 +- lib/page/component/room_card.dart | 18 +- lib/page/component/select_mode_toolbar.dart | 25 +- lib/page/component/sliver_component.dart | 35 +-- lib/page/component/theme/custom_size.dart | 10 +- lib/page/component/theme/custom_theme.dart | 273 ++++++------------ lib/page/component/video_player.dart | 31 +- .../creative_island/draw/artistic_qr.dart | 66 ++--- .../draw/artistic_wordart.dart | 48 +-- .../components/artistic_style_selector.dart | 20 +- .../creative_island/draw/components/box.dart | 28 +- .../draw/components/content_preview.dart | 17 +- .../draw/components/creative_item.dart | 7 +- .../draw/components/image_selector.dart | 35 +-- .../draw/components/image_size.dart | 3 +- .../draw/components/image_style_selector.dart | 16 +- .../creative_island/draw/draw_create.dart | 8 +- lib/page/creative_island/draw/draw_list.dart | 36 +-- .../creative_island/draw/draw_result.dart | 5 +- .../draw/image_edit_direct.dart | 49 ++-- .../gallery/components/image_card.dart | 13 +- lib/page/creative_island/gallery/gallery.dart | 8 +- .../creative_island/gallery/gallery_item.dart | 65 ++--- lib/page/creative_island/my_creation.dart | 86 ++---- .../creative_island/my_creation_item.dart | 50 +--- lib/page/custom_scaffold.dart | 8 + lib/page/drawer.dart | 165 +++++++---- lib/page/home.dart | 105 ++++--- lib/page/lab/creative_models.dart | 101 +++---- lib/page/lab/user_center.dart | 23 +- lib/page/setting/account_security.dart | 4 +- lib/page/setting/article.dart | 3 +- lib/page/setting/background_selector.dart | 15 +- lib/page/setting/bind_phone_page.dart | 64 ++-- lib/page/setting/change_password.dart | 6 +- lib/page/setting/custom_home_models.dart | 2 +- lib/page/setting/destroy_account.dart | 9 +- lib/page/setting/notification.dart | 18 +- lib/page/setting/openai_setting.dart | 43 ++- .../setting/retrieve_password_screen.dart | 43 +-- lib/page/setting/setting_screen.dart | 2 +- lib/page/setting/user_api_keys.dart | 49 ++-- 108 files changed, 1409 insertions(+), 2498 deletions(-) diff --git a/lib/lang/lang.dart b/lib/lang/lang.dart index 2396d8c6..36b31e27 100644 --- a/lib/lang/lang.dart +++ b/lib/lang/lang.dart @@ -281,7 +281,7 @@ mixin AppLocale { selectAll: '全选', unselectAll: '取消全选', share: '分享', - histories: '历史记录', + histories: '最近的对话', enable: '启用', disable: '未启用', newChat: '新对话', @@ -399,7 +399,7 @@ mixin AppLocale { confirmSignOut: '确定要退出登录吗?', askMeAnyQuestion: '有问题尽管问我', askMeLikeThis: '可以这样问我:', - refresh: '换一换', + refresh: '换一批', fastAndCostEffective: '速度快,成本低', powerfulAndPrecise: '能力强,更精准', imageToImage: '图生图', @@ -536,7 +536,7 @@ mixin AppLocale { selectAll: 'Select all', unselectAll: 'Cancel', share: 'Share', - histories: 'Histories', + histories: 'Recents', enable: 'Enable', disable: 'Disable', newChat: 'New Chat', diff --git a/lib/main.dart b/lib/main.dart index 95ba0044..552234fd 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -19,6 +19,7 @@ import 'package:askaide/page/admin/user.dart'; import 'package:askaide/page/admin/users.dart'; import 'package:askaide/page/balance/web_payment_proxy.dart'; import 'package:askaide/page/balance/web_payment_result.dart'; +import 'package:askaide/page/component/theme/custom_size.dart'; import 'package:askaide/page/creative_island/draw/artistic_wordart.dart'; import 'package:askaide/page/home.dart'; import 'package:flutter_phoenix/flutter_phoenix.dart'; @@ -136,8 +137,7 @@ void main() async { await PathHelper().init(); FlutterError.onError = (FlutterErrorDetails details) { - if (details.library == 'rendering library' || - details.library == 'image resource service') { + if (details.library == 'rendering library' || details.library == 'image resource service') { return; } @@ -152,9 +152,7 @@ void main() async { if (kIsWeb) { databaseFactory = databaseFactoryFfiWeb; } else { - if (PlatformTool.isWindows() || - PlatformTool.isLinux() || - PlatformTool.isMacOS()) { + if (PlatformTool.isWindows() || PlatformTool.isLinux() || PlatformTool.isMacOS()) { sqfliteFfiInit(); databaseFactory = databaseFactoryFfi; var path = absolute(join(PathHelper().getHomePath, 'databases')); @@ -198,8 +196,7 @@ void main() async { ChatHistoryProvider(db), ); - final creativeIslandRepo = - CreativeIslandRepository(CreativeIslandDataProvider(db)); + final creativeIslandRepo = CreativeIslandRepository(CreativeIslandDataProvider(db)); // 聊天状态加载器 final stateManager = MessageStateManager(cacheRepo); @@ -284,8 +281,7 @@ class MyApp extends StatefulWidget { required this.creativeIslandRepo, required this.messageStateManager, }) { - chatRoomBloc = - RoomBloc(chatMsgRepo: chatMsgRepo, stateManager: messageStateManager); + chatRoomBloc = RoomBloc(chatMsgRepo: chatMsgRepo, stateManager: messageStateManager); accountBloc = AccountBloc(settingRepo); versionBloc = VersionBloc(); galleryBloc = GalleryBloc(); @@ -294,12 +290,9 @@ class MyApp extends StatefulWidget { var apiServerToken = settingRepo.get(settingAPIServerToken); var usingGuestMode = settingRepo.boolDefault(settingUsingGuestMode, false); - final openAISelfHosted = - settingRepo.boolDefault(settingOpenAISelfHosted, false); - final deepAISelfHosted = - settingRepo.boolDefault(settingDeepAISelfHosted, false); - final stabilityAISelfHosted = - settingRepo.boolDefault(settingStabilityAISelfHosted, false); + final openAISelfHosted = settingRepo.boolDefault(settingOpenAISelfHosted, false); + final deepAISelfHosted = settingRepo.boolDefault(settingDeepAISelfHosted, false); + final stabilityAISelfHosted = settingRepo.boolDefault(settingStabilityAISelfHosted, false); final shouldLogin = (apiServerToken == null || apiServerToken == '') && !usingGuestMode && @@ -329,8 +322,7 @@ class MyApp extends StatefulWidget { BlocProvider.value( value: ChatBlocManager().getBloc( chatAnywhereRoomId, - chatHistoryId: int.tryParse( - state.queryParameters['chat_id'] ?? ''), + chatHistoryId: int.tryParse(state.queryParameters['chat_id'] ?? ''), ), ), BlocProvider( @@ -357,8 +349,7 @@ class MyApp extends StatefulWidget { providers: [ BlocProvider.value(value: accountBloc), ], - child: SettingScreen( - settings: context.read()), + child: SettingScreen(settings: context.read()), ), ), ), @@ -368,9 +359,7 @@ class MyApp extends StatefulWidget { pageBuilder: (context, state) => transitionResolver( MultiBlocProvider( providers: [ - BlocProvider( - create: (context) => - CreativeIslandBloc(creativeIslandRepo)), + BlocProvider(create: (context) => CreativeIslandBloc(creativeIslandRepo)), ], child: DrawListScreen( setting: settingRepo, @@ -406,15 +395,12 @@ class MyApp extends StatefulWidget { pageBuilder: (context, state) => transitionResolver( MultiBlocProvider( providers: [ - BlocProvider( - create: (context) => ChatChatBloc(chatMsgRepo)), + BlocProvider(create: (context) => ChatChatBloc(chatMsgRepo)), ], child: HomePage( setting: settingRepo, - showInitialDialog: - state.queryParameters['show_initial_dialog'] == 'true', - reward: - int.tryParse(state.queryParameters['reward'] ?? '0'), + showInitialDialog: state.queryParameters['show_initial_dialog'] == 'true', + reward: int.tryParse(state.queryParameters['reward'] ?? '0'), ), ), ), @@ -489,8 +475,7 @@ class MyApp extends StatefulWidget { BlocProvider.value( value: ChatBlocManager().getBloc( chatAnywhereRoomId, - chatHistoryId: int.tryParse( - state.queryParameters['chat_id'] ?? ''), + chatHistoryId: int.tryParse(state.queryParameters['chat_id'] ?? ''), ), ), BlocProvider.value(value: chatRoomBloc), @@ -499,15 +484,10 @@ class MyApp extends StatefulWidget { child: HomeChatPage( stateManager: messageStateManager, setting: settingRepo, - chatId: - int.tryParse(state.queryParameters['chat_id'] ?? '0'), + chatId: int.tryParse(state.queryParameters['chat_id'] ?? '0'), initialMessage: state.queryParameters['init_message'], - model: state.queryParameters['model'] == '' - ? null - : state.queryParameters['model'], - title: state.queryParameters['title'] == '' - ? null - : state.queryParameters['title'], + model: state.queryParameters['model'] == '' ? null : state.queryParameters['model'], + title: state.queryParameters['title'] == '' ? null : state.queryParameters['title'], ), ), ), @@ -519,8 +499,7 @@ class MyApp extends StatefulWidget { pageBuilder: (context, state) => transitionResolver( MultiBlocProvider( providers: [ - BlocProvider( - create: (context) => ChatChatBloc(chatMsgRepo)), + BlocProvider(create: (context) => ChatChatBloc(chatMsgRepo)), ], child: HomeChatHistoryPage( setting: settingRepo, @@ -614,12 +593,9 @@ class MyApp extends StatefulWidget { MultiBlocProvider( providers: [ BlocProvider.value(value: accountBloc), - BlocProvider( - create: (context) => - CreativeIslandBloc(creativeIslandRepo)), + BlocProvider(create: (context) => CreativeIslandBloc(creativeIslandRepo)), ], - child: UserCenterScreen( - settings: context.read()), + child: UserCenterScreen(settings: context.read()), ), ), ), @@ -651,9 +627,7 @@ class MyApp extends StatefulWidget { pageBuilder: (context, state) => transitionResolver( MultiBlocProvider( providers: [ - BlocProvider( - create: (context) => - CreativeIslandBloc(creativeIslandRepo)), + BlocProvider(create: (context) => CreativeIslandBloc(creativeIslandRepo)), ], child: ImageEditDirectScreen( setting: settingRepo, @@ -672,9 +646,7 @@ class MyApp extends StatefulWidget { pageBuilder: (context, state) => transitionResolver( MultiBlocProvider( providers: [ - BlocProvider( - create: (context) => - CreativeIslandBloc(creativeIslandRepo)), + BlocProvider(create: (context) => CreativeIslandBloc(creativeIslandRepo)), ], child: ImageEditDirectScreen( setting: settingRepo, @@ -693,9 +665,7 @@ class MyApp extends StatefulWidget { pageBuilder: (context, state) => transitionResolver( MultiBlocProvider( providers: [ - BlocProvider( - create: (context) => - CreativeIslandBloc(creativeIslandRepo)), + BlocProvider(create: (context) => CreativeIslandBloc(creativeIslandRepo)), ], child: ImageEditDirectScreen( setting: settingRepo, @@ -790,9 +760,7 @@ class MyApp extends StatefulWidget { return transitionResolver( MultiBlocProvider( providers: [ - BlocProvider( - create: (context) => - CreativeIslandBloc(creativeIslandRepo)), + BlocProvider(create: (context) => CreativeIslandBloc(creativeIslandRepo)), ], child: MyCreationScreen( setting: settingRepo, @@ -809,9 +777,7 @@ class MyApp extends StatefulWidget { return transitionResolver( MultiBlocProvider( providers: [ - BlocProvider( - create: (context) => - CreativeIslandBloc(creativeIslandRepo)), + BlocProvider(create: (context) => CreativeIslandBloc(creativeIslandRepo)), ], child: CreativeModelScreen(setting: settingRepo), ), @@ -824,14 +790,11 @@ class MyApp extends StatefulWidget { pageBuilder: (context, state) { final id = state.pathParameters['id']!; final itemId = int.tryParse(state.pathParameters['item_id']!); - final showErrorMessage = - state.queryParameters['show_error'] == 'true'; + final showErrorMessage = state.queryParameters['show_error'] == 'true'; return transitionResolver( MultiBlocProvider( providers: [ - BlocProvider( - create: (context) => - CreativeIslandBloc(creativeIslandRepo)), + BlocProvider(create: (context) => CreativeIslandBloc(creativeIslandRepo)), ], child: MyCreationItemPage( setting: settingRepo, @@ -863,8 +826,7 @@ class MyApp extends StatefulWidget { pageBuilder: (context, state) => transitionResolver( QuotaUsageDetailScreen( setting: settingRepo, - date: state.queryParameters['date'] ?? - DateFormat('yyyy-MM-dd').format(DateTime.now()), + date: state.queryParameters['date'] ?? DateFormat('yyyy-MM-dd').format(DateTime.now()), ), ), ), @@ -941,8 +903,7 @@ class MyApp extends StatefulWidget { MultiBlocProvider( providers: [ BlocProvider( - create: ((context) => - GroupChatBloc(stateManager: messageStateManager)), + create: ((context) => GroupChatBloc(stateManager: messageStateManager)), ), BlocProvider.value(value: chatRoomBloc), ], @@ -963,8 +924,7 @@ class MyApp extends StatefulWidget { MultiBlocProvider( providers: [ BlocProvider( - create: ((context) => - GroupChatBloc(stateManager: messageStateManager)), + create: ((context) => GroupChatBloc(stateManager: messageStateManager)), ), BlocProvider.value(value: chatRoomBloc), ], @@ -981,8 +941,7 @@ class MyApp extends StatefulWidget { MultiBlocProvider( providers: [ BlocProvider( - create: ((context) => - GroupChatBloc(stateManager: messageStateManager)), + create: ((context) => GroupChatBloc(stateManager: messageStateManager)), ), BlocProvider.value(value: chatRoomBloc), ], @@ -1051,8 +1010,7 @@ class MyApp extends StatefulWidget { paymentIntent: state.queryParameters['intent']!, price: state.queryParameters['price']!, publishableKey: state.queryParameters['key']!, - finishAction: - state.queryParameters['finish_action'] ?? 'close', + finishAction: state.queryParameters['finish_action'] ?? 'close', )); }, ), @@ -1347,19 +1305,14 @@ class _MyAppState extends State { Widget build(BuildContext context) { return MultiRepositoryProvider( providers: [ - RepositoryProvider( - create: (context) => widget.chatMsgRepo), - RepositoryProvider( - create: (context) => widget.openAIRepo), - RepositoryProvider( - create: (context) => widget.settingRepo), - RepositoryProvider( - create: (context) => widget.cacheRepo), + RepositoryProvider(create: (context) => widget.chatMsgRepo), + RepositoryProvider(create: (context) => widget.openAIRepo), + RepositoryProvider(create: (context) => widget.settingRepo), + RepositoryProvider(create: (context) => widget.cacheRepo), ], child: ChangeNotifierProvider( create: (context) => AppTheme.get() - ..mode = AppTheme.themeModeFormString( - widget.settingRepo.stringDefault(settingThemeMode, 'system')), + ..mode = AppTheme.themeModeFormString(widget.settingRepo.stringDefault(settingThemeMode, 'system')), builder: (context, _) { final appTheme = context.watch(); return Sizer( @@ -1374,26 +1327,23 @@ class _MyAppState extends State { // 这里设置了全局字体固定大小,不随系统设置变更 // TODO 后面要增加一个设置项,允许用户自定义字体大小 return MediaQuery( - data: MediaQuery.of(context) - .copyWith(textScaler: TextScaler.noScaling), + data: MediaQuery.of(context).copyWith(textScaler: TextScaler.noScaling), child: BotToastInit()(context, child), ); }, routerConfig: widget._router, supportedLocales: widget.localization.supportedLocales, - localizationsDelegates: - widget.localization.localizationsDelegates, - scrollBehavior: - PlatformTool.isAndroid() || PlatformTool.isIOS() - ? null - : const MaterialScrollBehavior().copyWith( - dragDevices: { - PointerDeviceKind.touch, - PointerDeviceKind.mouse, - PointerDeviceKind.stylus, - PointerDeviceKind.trackpad, - }, - ), + localizationsDelegates: widget.localization.localizationsDelegates, + scrollBehavior: PlatformTool.isAndroid() || PlatformTool.isIOS() + ? null + : const MaterialScrollBehavior().copyWith( + dragDevices: { + PointerDeviceKind.touch, + PointerDeviceKind.mouse, + PointerDeviceKind.stylus, + PointerDeviceKind.trackpad, + }, + ), ); }, ); @@ -1421,14 +1371,13 @@ ThemeData createLightThemeData() { dialogBackgroundColor: Colors.white, dialogTheme: DialogTheme( shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(10), + borderRadius: CustomSize.borderRadius, ), elevation: 0, ), textButtonTheme: TextButtonThemeData( style: TextButton.styleFrom( - foregroundColor: const Color.fromARGB( - 255, 9, 185, 85), // This is a custom color variable + foregroundColor: const Color.fromARGB(255, 9, 185, 85), // This is a custom color variable ), ), ); @@ -1453,14 +1402,13 @@ ThemeData createDarkThemeData() { dialogBackgroundColor: const Color.fromARGB(255, 48, 48, 48), dialogTheme: DialogTheme( shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(10), + borderRadius: CustomSize.borderRadius, ), elevation: 0, ), textButtonTheme: TextButtonThemeData( style: TextButton.styleFrom( - foregroundColor: const Color.fromARGB( - 255, 9, 185, 85), // This is a custom color variable + foregroundColor: const Color.fromARGB(255, 9, 185, 85), // This is a custom color variable ), ), ); diff --git a/lib/page/admin/channels.dart b/lib/page/admin/channels.dart index 4e2b254b..741ca3f8 100644 --- a/lib/page/admin/channels.dart +++ b/lib/page/admin/channels.dart @@ -68,7 +68,7 @@ class _ChannelsPageState extends State { ), ], ), - backgroundColor: customColors.chatInputPanelBackground, + backgroundColor: customColors.backgroundColor, body: BackgroundContainer( setting: widget.setting, enabled: false, @@ -79,8 +79,7 @@ class _ChannelsPageState extends State { }, displacement: 20, child: BlocConsumer( - listenWhen: (previous, current) => - current is ChannelOperationResult, + listenWhen: (previous, current) => current is ChannelOperationResult, listener: (context, state) { if (state is ChannelOperationResult) { if (state.success) { @@ -128,9 +127,7 @@ class _ChannelsPageState extends State { horizontal: 10, vertical: 5, ), - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(customColors.borderRadius ?? 8), - ), + decoration: BoxDecoration(borderRadius: CustomSize.borderRadius), child: Slidable( endActionPane: ActionPane( motion: const ScrollMotion(), @@ -138,11 +135,11 @@ class _ChannelsPageState extends State { const SizedBox(width: 10), SlidableAction( label: AppLocale.delete.getString(context), - borderRadius: BorderRadius.only( - topLeft: Radius.circular(customColors.borderRadius ?? 8), - bottomLeft: Radius.circular(customColors.borderRadius ?? 8), - topRight: Radius.circular(customColors.borderRadius ?? 8), - bottomRight: Radius.circular(customColors.borderRadius ?? 8), + borderRadius: const BorderRadius.only( + topLeft: CustomSize.radius, + bottomLeft: CustomSize.radius, + topRight: CustomSize.radius, + bottomRight: CustomSize.radius, ), backgroundColor: Colors.red, icon: Icons.delete, @@ -150,9 +147,7 @@ class _ChannelsPageState extends State { openConfirmDialog( context, AppLocale.confirmToDeleteRoom.getString(context), - () => context - .read() - .add(ChannelDeleteEvent(channel.id!)), + () => context.read().add(ChannelDeleteEvent(channel.id!)), danger: true, ); }, @@ -160,12 +155,10 @@ class _ChannelsPageState extends State { ], ), child: Material( - borderRadius: - BorderRadius.all(Radius.circular(customColors.borderRadius ?? 8)), + borderRadius: CustomSize.borderRadius, color: customColors.columnBlockBackgroundColor, child: InkWell( - borderRadius: BorderRadius.all( - Radius.circular(customColors.borderRadius ?? 8)), + borderRadius: const BorderRadius.all(CustomSize.radius), onTap: () { context.push('/admin/channels/edit/${channel.id}').then((value) { context.read().add(ChannelsLoadEvent()); @@ -181,10 +174,7 @@ class _ChannelsPageState extends State { text: channel.name.split('、').join(' '), size: 50, backgroundColor: Colors.grey.withAlpha(100), - borderRadius: const BorderRadius.only( - topLeft: Radius.circular(8), - bottomLeft: Radius.circular(8), - ), + borderRadius: const BorderRadius.only(topLeft: CustomSize.radius, bottomLeft: CustomSize.radius), ), // 渠道名称 Expanded( @@ -214,9 +204,7 @@ class _ChannelsPageState extends State { crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( - channelTypes - .firstWhere((e) => e.name == channel.type) - .text, + channelTypes.firstWhere((e) => e.name == channel.type).text, style: TextStyle( fontSize: 10, overflow: TextOverflow.ellipsis, diff --git a/lib/page/admin/channels_add.dart b/lib/page/admin/channels_add.dart index 0e39d1aa..50e37a5b 100644 --- a/lib/page/admin/channels_add.dart +++ b/lib/page/admin/channels_add.dart @@ -51,8 +51,7 @@ class _ChannelAddPageState extends State { bool openaiAzure = false; /// OpenAI Azure API 版本 - final TextEditingController azureAPIVersionController = - TextEditingController(); + final TextEditingController azureAPIVersionController = TextEditingController(); @override void dispose() { @@ -92,7 +91,7 @@ class _ChannelAddPageState extends State { ), centerTitle: true, ), - backgroundColor: customColors.chatInputPanelBackground, + backgroundColor: customColors.backgroundColor, body: BackgroundContainer( setting: widget.setting, enabled: false, @@ -141,9 +140,7 @@ class _ChannelAddPageState extends State { onPressed: () { openListSelectDialog( context, - channelTypes - .map((e) => SelectorItem(Text(e.text), e.name)) - .toList(), + channelTypes.map((e) => SelectorItem(Text(e.text), e.name)).toList(), (value) { setState(() { selectedChannelType = value.value; @@ -240,9 +237,7 @@ class _ChannelAddPageState extends State { color: customColors.weakLinkColor, fontSize: 15, icon: Icon( - showAdvancedOptions - ? Icons.unfold_less - : Icons.unfold_more, + showAdvancedOptions ? Icons.unfold_less : Icons.unfold_more, color: customColors.weakLinkColor, size: 15, ), @@ -287,8 +282,7 @@ class _ChannelAddPageState extends State { return; } - if (!serverController.text.startsWith('http://') && - !serverController.text.startsWith('https://')) { + if (!serverController.text.startsWith('http://') && !serverController.text.startsWith('https://')) { showErrorMessage('服务器地址格式不正确'); return; } @@ -314,8 +308,6 @@ class _ChannelAddPageState extends State { return '请选择'; } - return channelTypes - .firstWhere((element) => element.name == selectedChannelType) - .text; + return channelTypes.firstWhere((element) => element.name == selectedChannelType).text; } } diff --git a/lib/page/admin/channels_edit.dart b/lib/page/admin/channels_edit.dart index bb0ca01d..3cc2264b 100644 --- a/lib/page/admin/channels_edit.dart +++ b/lib/page/admin/channels_edit.dart @@ -52,8 +52,7 @@ class _ChannelEditPageState extends State { bool openaiAzure = false; /// OpenAI Azure API 版本 - final TextEditingController azureAPIVersionController = - TextEditingController(); + final TextEditingController azureAPIVersionController = TextEditingController(); /// 是否锁定编辑 bool editLocked = true; @@ -99,20 +98,17 @@ class _ChannelEditPageState extends State { ), centerTitle: true, ), - backgroundColor: customColors.chatInputPanelBackground, + backgroundColor: customColors.backgroundColor, body: BackgroundContainer( setting: widget.setting, enabled: false, child: BlocListener( - listenWhen: (previous, current) => - current is ChannelOperationResult || current is ChannelLoaded, + listenWhen: (previous, current) => current is ChannelOperationResult || current is ChannelLoaded, listener: (context, state) { if (state is ChannelOperationResult) { if (state.success) { showSuccessMessage(state.message); - context - .read() - .add(ChannelLoadEvent(widget.channelId)); + context.read().add(ChannelLoadEvent(widget.channelId)); } else { showErrorMessage(state.message); } @@ -123,8 +119,7 @@ class _ChannelEditPageState extends State { secretController.text = state.channel.secret ?? ''; usingProxy = state.channel.meta?.usingProxy ?? false; openaiAzure = state.channel.meta?.openaiAzure ?? false; - azureAPIVersionController.text = - state.channel.meta?.openaiAzureAPIVersion ?? ''; + azureAPIVersionController.text = state.channel.meta?.openaiAzureAPIVersion ?? ''; setState(() { editLocked = false; @@ -164,9 +159,7 @@ class _ChannelEditPageState extends State { onPressed: () { openListSelectDialog( context, - channelTypes - .map((e) => SelectorItem(Text(e.text), e.name)) - .toList(), + channelTypes.map((e) => SelectorItem(Text(e.text), e.name)).toList(), (value) { setState(() { selectedChannelType = value.value; @@ -263,9 +256,7 @@ class _ChannelEditPageState extends State { color: customColors.weakLinkColor, fontSize: 15, icon: Icon( - showAdvancedOptions - ? Icons.unfold_less - : Icons.unfold_more, + showAdvancedOptions ? Icons.unfold_less : Icons.unfold_more, color: customColors.weakLinkColor, size: 15, ), @@ -282,10 +273,8 @@ class _ChannelEditPageState extends State { title: AppLocale.save.getString(context), onPressed: onSubmit, icon: editLocked - ? const Icon(Icons.lock, - color: Colors.white, size: 16) - : const Icon(Icons.lock_open, - color: Colors.white, size: 16), + ? const Icon(Icons.lock, color: Colors.white, size: 16) + : const Icon(Icons.lock_open, color: Colors.white, size: 16), ), ), ], @@ -319,8 +308,7 @@ class _ChannelEditPageState extends State { return; } - if (!serverController.text.startsWith('http://') && - !serverController.text.startsWith('https://')) { + if (!serverController.text.startsWith('http://') && !serverController.text.startsWith('https://')) { showErrorMessage('服务器地址格式不正确'); return; } @@ -346,8 +334,6 @@ class _ChannelEditPageState extends State { return '请选择'; } - return channelTypes - .firstWhere((element) => element.name == selectedChannelType) - .text; + return channelTypes.firstWhere((element) => element.name == selectedChannelType).text; } } diff --git a/lib/page/admin/dashboard.dart b/lib/page/admin/dashboard.dart index a22ec071..9207b754 100644 --- a/lib/page/admin/dashboard.dart +++ b/lib/page/admin/dashboard.dart @@ -1,6 +1,7 @@ import 'package:askaide/page/component/background_container.dart'; import 'package:askaide/page/component/dialog.dart'; import 'package:askaide/page/component/theme/custom_size.dart'; +import 'package:askaide/page/component/theme/custom_theme.dart'; import 'package:askaide/repo/api_server.dart'; import 'package:askaide/repo/settings_repo.dart'; import 'package:flutter/cupertino.dart'; @@ -19,7 +20,7 @@ class AdminDashboardPage extends StatefulWidget { class _AdminDashboardPageState extends State { @override Widget build(BuildContext context) { - // final customColors = Theme.of(context).extension()!; + final customColors = Theme.of(context).extension()!; return BackgroundContainer( setting: widget.setting, @@ -32,7 +33,7 @@ class _AdminDashboardPageState extends State { ), centerTitle: true, ), - backgroundColor: Colors.transparent, + backgroundColor: customColors.backgroundColor, body: Column( children: [ Expanded( @@ -44,7 +45,7 @@ class _AdminDashboardPageState extends State { ), darkTheme: const SettingsThemeData( settingsListBackground: Colors.transparent, - settingsSectionBackground: Color.fromARGB(255, 27, 27, 27), + settingsSectionBackground: Color.fromARGB(255, 44, 44, 46), titleTextColor: Color.fromARGB(255, 239, 239, 239), ), sections: [ diff --git a/lib/page/admin/messages.dart b/lib/page/admin/messages.dart index 25a9eb21..ad2dba9a 100644 --- a/lib/page/admin/messages.dart +++ b/lib/page/admin/messages.dart @@ -97,9 +97,7 @@ class _AdminRoomMessagesPageState extends State { child: RefreshIndicator( color: customColors.linkColor, onRefresh: () async { - context - .read() - .add(AdminRoomRecentlyMessagesLoadEvent( + context.read().add(AdminRoomRecentlyMessagesLoadEvent( userId: widget.userId, roomId: widget.roomId, roomType: widget.roomType, @@ -110,15 +108,13 @@ class _AdminRoomMessagesPageState extends State { listener: (context, state) { if (state is AdminRoomOperationResult) { if (state.success) { - showSuccessMessage( - AppLocale.operateSuccess.getString(context)); + showSuccessMessage(AppLocale.operateSuccess.getString(context)); } else { showErrorMessage(AppLocale.operateFailed.getString(context)); } } }, - buildWhen: (previous, current) => - current is AdminRoomRecentlyMessagesLoaded, + buildWhen: (previous, current) => current is AdminRoomRecentlyMessagesLoaded, builder: (context, state) { if (state is AdminRoomRecentlyMessagesLoaded) { return SafeArea( @@ -130,8 +126,7 @@ class _AdminRoomMessagesPageState extends State { if (e.model != null) { final model = models[e.model]; if (model != null) { - if (e.avatarUrl == null && - model.avatarUrl != null) { + if (e.avatarUrl == null && model.avatarUrl != null) { e.avatarUrl = model.avatarUrl; } @@ -144,8 +139,7 @@ class _AdminRoomMessagesPageState extends State { controller: controller, supportBloc: false, senderNameBuilder: (message) { - if (message.role == Role.sender || - message.senderName == null) { + if (message.role == Role.sender || message.senderName == null) { return null; } @@ -156,8 +150,7 @@ class _AdminRoomMessagesPageState extends State { right: 5, ), child: Row( - mainAxisAlignment: - MainAxisAlignment.spaceBetween, + mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text( message.senderName!, diff --git a/lib/page/admin/models.dart b/lib/page/admin/models.dart index 85605e10..c9e60a4c 100644 --- a/lib/page/admin/models.dart +++ b/lib/page/admin/models.dart @@ -96,7 +96,7 @@ class _AdminModelsPageState extends State { ), ], ), - backgroundColor: customColors.chatInputPanelBackground, + backgroundColor: customColors.backgroundColor, body: BackgroundContainer( setting: widget.setting, enabled: false, @@ -188,9 +188,7 @@ class _AdminModelsPageState extends State { horizontal: 10, vertical: 5, ), - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(customColors.borderRadius ?? 8), - ), + decoration: BoxDecoration(borderRadius: CustomSize.borderRadius), child: Slidable( endActionPane: ActionPane( motion: const ScrollMotion(), @@ -198,11 +196,11 @@ class _AdminModelsPageState extends State { const SizedBox(width: 10), SlidableAction( label: AppLocale.delete.getString(context), - borderRadius: BorderRadius.only( - topLeft: Radius.circular(customColors.borderRadius ?? 8), - bottomLeft: Radius.circular(customColors.borderRadius ?? 8), - topRight: Radius.circular(customColors.borderRadius ?? 8), - bottomRight: Radius.circular(customColors.borderRadius ?? 8), + borderRadius: const BorderRadius.only( + topLeft: CustomSize.radius, + bottomLeft: CustomSize.radius, + topRight: CustomSize.radius, + bottomRight: CustomSize.radius, ), backgroundColor: Colors.red, icon: Icons.delete, @@ -218,10 +216,10 @@ class _AdminModelsPageState extends State { ], ), child: Material( - borderRadius: BorderRadius.all(Radius.circular(customColors.borderRadius ?? 8)), + borderRadius: CustomSize.borderRadius, color: customColors.columnBlockBackgroundColor, child: InkWell( - borderRadius: BorderRadius.all(Radius.circular(customColors.borderRadius ?? 8)), + borderRadius: CustomSize.borderRadiusAll, onTap: () { context.push('/admin/models/edit/${Uri.encodeComponent(mod.modelId)}').then((value) { context.read().add(ModelsLoadEvent()); @@ -241,9 +239,7 @@ class _AdminModelsPageState extends State { left: 0, bottom: 0, child: ClipRRect( - borderRadius: const BorderRadius.only( - bottomLeft: Radius.circular(8), - ), + borderRadius: const BorderRadius.only(bottomLeft: CustomSize.radius), child: Container( padding: const EdgeInsets.all(3), width: 80, @@ -335,10 +331,7 @@ class _AdminModelsPageState extends State { width: 80, height: 80, child: ClipRRect( - borderRadius: const BorderRadius.only( - topLeft: Radius.circular(8), - bottomLeft: Radius.circular(8), - ), + borderRadius: const BorderRadius.only(topLeft: CustomSize.radius, bottomLeft: CustomSize.radius), child: CachedNetworkImage( imageUrl: imageURL(mod.avatarUrl!, qiniuImageTypeAvatar), fit: BoxFit.fill, @@ -351,10 +344,7 @@ class _AdminModelsPageState extends State { text: mod.name.split('、').join(' '), size: 80, backgroundColor: Colors.grey.withAlpha(100), - borderRadius: const BorderRadius.only( - topLeft: Radius.circular(8), - bottomLeft: Radius.circular(8), - ), + borderRadius: const BorderRadius.only(topLeft: CustomSize.radius, bottomLeft: CustomSize.radius), ); } diff --git a/lib/page/admin/models_add.dart b/lib/page/admin/models_add.dart index fa174e2a..1d595c8d 100644 --- a/lib/page/admin/models_add.dart +++ b/lib/page/admin/models_add.dart @@ -131,7 +131,7 @@ class _AdminModelCreatePageState extends State { ), centerTitle: true, ), - backgroundColor: customColors.chatInputPanelBackground, + backgroundColor: customColors.backgroundColor, body: BackgroundContainer( setting: widget.setting, enabled: false, @@ -149,8 +149,7 @@ class _AdminModelCreatePageState extends State { } }, child: Container( - padding: const EdgeInsets.only( - left: 10, right: 10, top: 10, bottom: 20), + padding: const EdgeInsets.only(left: 10, right: 10, top: 10, bottom: 20), child: Column( children: [ ColumnBlock( @@ -199,15 +198,13 @@ class _AdminModelCreatePageState extends State { width: 45, height: 45, decoration: BoxDecoration( - borderRadius: BorderRadius.circular(8), + borderRadius: CustomSize.borderRadius, image: avatarUrl == null ? null : DecorationImage( image: (avatarUrl!.startsWith('http') - ? CachedNetworkImageProviderEnhanced( - avatarUrl!) - : FileImage(File( - avatarUrl!))) as ImageProvider, + ? CachedNetworkImageProviderEnhanced(avatarUrl!) + : FileImage(File(avatarUrl!))) as ImageProvider, fit: BoxFit.cover, ), ), @@ -266,18 +263,14 @@ class _AdminModelCreatePageState extends State { hintText: '可选', showCounter: false, keyboardType: TextInputType.number, - inputFormatters: [ - FilteringTextInputFormatter.digitsOnly - ], + inputFormatters: [FilteringTextInputFormatter.digitsOnly], textDirection: TextDirection.rtl, suffixIcon: Container( width: 110, alignment: Alignment.center, child: Text( '智慧果/1K Token', - style: TextStyle( - color: customColors.weakTextColor, - fontSize: 12), + style: TextStyle(color: customColors.weakTextColor, fontSize: 12), ), ), ), @@ -289,18 +282,14 @@ class _AdminModelCreatePageState extends State { hintText: '可选', showCounter: false, keyboardType: TextInputType.number, - inputFormatters: [ - FilteringTextInputFormatter.digitsOnly - ], + inputFormatters: [FilteringTextInputFormatter.digitsOnly], textDirection: TextDirection.rtl, suffixIcon: Container( width: 110, alignment: Alignment.center, child: Text( '智慧果/1K Token', - style: TextStyle( - color: customColors.weakTextColor, - fontSize: 12), + style: TextStyle(color: customColors.weakTextColor, fontSize: 12), ), ), ), @@ -312,18 +301,14 @@ class _AdminModelCreatePageState extends State { hintText: '最大上下文减掉预期的输出长度', showCounter: false, keyboardType: TextInputType.number, - inputFormatters: [ - FilteringTextInputFormatter.digitsOnly - ], + inputFormatters: [FilteringTextInputFormatter.digitsOnly], textDirection: TextDirection.rtl, suffixIcon: Container( width: 50, alignment: Alignment.center, child: Text( 'Token', - style: TextStyle( - color: customColors.weakTextColor, - fontSize: 12), + style: TextStyle(color: customColors.weakTextColor, fontSize: 12), ), ), ), @@ -331,8 +316,7 @@ class _AdminModelCreatePageState extends State { ), ...providers.map((e) { return Container( - margin: - const EdgeInsets.only(bottom: 10, left: 5, right: 5), + margin: const EdgeInsets.only(bottom: 10, left: 5, right: 5), child: Slidable( endActionPane: ActionPane( motion: const ScrollMotion(), @@ -340,8 +324,7 @@ class _AdminModelCreatePageState extends State { const SizedBox(width: 10), SlidableAction( label: AppLocale.delete.getString(context), - borderRadius: BorderRadius.circular( - customColors.borderRadius ?? 8), + borderRadius: CustomSize.borderRadiusAll, backgroundColor: Colors.red, icon: Icons.delete, onPressed: (_) { @@ -352,12 +335,10 @@ class _AdminModelCreatePageState extends State { openConfirmDialog( context, - AppLocale.confirmToDeleteRoom - .getString(context), + AppLocale.confirmToDeleteRoom.getString(context), () { setState(() { - providers - .removeWhere((item) => item == e); + providers.removeWhere((item) => item == e); }); }, danger: true, @@ -391,8 +372,7 @@ class _AdminModelCreatePageState extends State { ...modelChannels .map( (e) => SelectorItem( - Text( - '${e.id == null ? '【系统】' : ''}${e.name}'), + Text('${e.id == null ? '【系统】' : ''}${e.name}'), e, ), ) @@ -478,16 +458,14 @@ class _AdminModelCreatePageState extends State { context, type: QuickAlertType.info, text: '当前模型是否支持视觉能力。', - confirmBtnText: - AppLocale.gotIt.getString(context), + confirmBtnText: AppLocale.gotIt.getString(context), showCancelBtn: false, ); }, child: Icon( Icons.help_outline, size: 16, - color: customColors.weakLinkColor - ?.withAlpha(150), + color: customColors.weakLinkColor?.withAlpha(150), ), ), ], @@ -519,16 +497,14 @@ class _AdminModelCreatePageState extends State { context, type: QuickAlertType.info, text: '是否在模型旁边展示“新”标识,告知用户这是一个新模型。', - confirmBtnText: - AppLocale.gotIt.getString(context), + confirmBtnText: AppLocale.gotIt.getString(context), showCancelBtn: false, ); }, child: Icon( Icons.help_outline, size: 16, - color: customColors.weakLinkColor - ?.withAlpha(150), + color: customColors.weakLinkColor?.withAlpha(150), ), ), ], @@ -560,16 +536,14 @@ class _AdminModelCreatePageState extends State { context, type: QuickAlertType.info, text: '受限模型是指因政策因素,不能在中国大陆地区使用的模型。', - confirmBtnText: - AppLocale.gotIt.getString(context), + confirmBtnText: AppLocale.gotIt.getString(context), showCancelBtn: false, ); }, child: Icon( Icons.help_outline, size: 16, - color: customColors.weakLinkColor - ?.withAlpha(150), + color: customColors.weakLinkColor?.withAlpha(150), ), ), ], @@ -627,9 +601,7 @@ class _AdminModelCreatePageState extends State { color: customColors.weakLinkColor, fontSize: 15, icon: Icon( - showAdvancedOptions - ? Icons.unfold_less - : Icons.unfold_more, + showAdvancedOptions ? Icons.unfold_less : Icons.unfold_more, color: customColors.weakLinkColor, size: 15, ), @@ -676,9 +648,7 @@ class _AdminModelCreatePageState extends State { return; } - if (avatarUrl != null && - (!avatarUrl!.startsWith('http://') && - !avatarUrl!.startsWith('https://'))) { + if (avatarUrl != null && (!avatarUrl!.startsWith('http://') && !avatarUrl!.startsWith('https://'))) { final cancel = BotToast.showCustomLoading( toastBuilder: (cancel) { return const LoadingIndicator( @@ -689,8 +659,7 @@ class _AdminModelCreatePageState extends State { ); try { - final res = await ImageUploader(widget.setting) - .upload(avatarUrl!, usage: 'avatar'); + final res = await ImageUploader(widget.setting).upload(avatarUrl!, usage: 'avatar'); avatarUrl = res.url; } catch (e) { showErrorMessage('上传头像失败'); diff --git a/lib/page/admin/models_edit.dart b/lib/page/admin/models_edit.dart index c4fa4154..bd994079 100644 --- a/lib/page/admin/models_edit.dart +++ b/lib/page/admin/models_edit.dart @@ -140,14 +140,13 @@ class _AdminModelEditPageState extends State { ), centerTitle: true, ), - backgroundColor: customColors.chatInputPanelBackground, + backgroundColor: customColors.backgroundColor, body: BackgroundContainer( setting: widget.setting, enabled: false, child: SingleChildScrollView( child: BlocListener( - listenWhen: (previous, current) => - current is ModelOperationResult || current is ModelLoaded, + listenWhen: (previous, current) => current is ModelOperationResult || current is ModelLoaded, listener: (context, state) { if (state is ModelOperationResult) { if (state.success) { @@ -159,12 +158,10 @@ class _AdminModelEditPageState extends State { } if (state is ModelLoaded) { - modelIdController.value = - TextEditingValue(text: state.model.modelId); + modelIdController.value = TextEditingValue(text: state.model.modelId); nameController.value = TextEditingValue(text: state.model.name); if (state.model.description != null) { - descriptionController.value = - TextEditingValue(text: state.model.description!); + descriptionController.value = TextEditingValue(text: state.model.description!); } if (state.model.avatarUrl != null) { @@ -179,31 +176,25 @@ class _AdminModelEditPageState extends State { if (state.model.meta != null) { if (state.model.meta!.maxContext != null) { - maxContextController.value = TextEditingValue( - text: state.model.meta!.maxContext.toString()); + maxContextController.value = TextEditingValue(text: state.model.meta!.maxContext.toString()); } if (state.model.meta!.inputPrice != null) { - inputPriceController.value = TextEditingValue( - text: state.model.meta!.inputPrice.toString()); + inputPriceController.value = TextEditingValue(text: state.model.meta!.inputPrice.toString()); } if (state.model.meta!.outputPrice != null) { - outputPriceController.value = TextEditingValue( - text: state.model.meta!.outputPrice.toString()); + outputPriceController.value = TextEditingValue(text: state.model.meta!.outputPrice.toString()); } - promptController.value = - TextEditingValue(text: state.model.meta!.prompt ?? ''); + promptController.value = TextEditingValue(text: state.model.meta!.prompt ?? ''); supportVision = state.model.meta!.vision ?? false; restricted = state.model.meta!.restricted ?? false; - tagController.value = - TextEditingValue(text: state.model.meta!.tag ?? ''); + tagController.value = TextEditingValue(text: state.model.meta!.tag ?? ''); tagTextColor = state.model.meta!.tagTextColor; tagBgColor = state.model.meta!.tagBgColor; isNew = state.model.meta!.isNew ?? false; - categoryController.value = - TextEditingValue(text: state.model.meta!.category ?? ''); + categoryController.value = TextEditingValue(text: state.model.meta!.category ?? ''); } } @@ -212,8 +203,7 @@ class _AdminModelEditPageState extends State { }); }, child: Container( - padding: const EdgeInsets.only( - left: 10, right: 10, top: 10, bottom: 20), + padding: const EdgeInsets.only(left: 10, right: 10, top: 10, bottom: 20), child: Column( children: [ ColumnBlock( @@ -263,15 +253,13 @@ class _AdminModelEditPageState extends State { width: 45, height: 45, decoration: BoxDecoration( - borderRadius: BorderRadius.circular(8), + borderRadius: CustomSize.borderRadius, image: avatarUrl == null ? null : DecorationImage( image: (avatarUrl!.startsWith('http') - ? CachedNetworkImageProviderEnhanced( - avatarUrl!) - : FileImage(File( - avatarUrl!))) as ImageProvider, + ? CachedNetworkImageProviderEnhanced(avatarUrl!) + : FileImage(File(avatarUrl!))) as ImageProvider, fit: BoxFit.cover, ), ), @@ -330,18 +318,14 @@ class _AdminModelEditPageState extends State { hintText: '可选', showCounter: false, keyboardType: TextInputType.number, - inputFormatters: [ - FilteringTextInputFormatter.digitsOnly - ], + inputFormatters: [FilteringTextInputFormatter.digitsOnly], textDirection: TextDirection.rtl, suffixIcon: Container( width: 110, alignment: Alignment.center, child: Text( '智慧果/1K Token', - style: TextStyle( - color: customColors.weakTextColor, - fontSize: 12), + style: TextStyle(color: customColors.weakTextColor, fontSize: 12), ), ), ), @@ -353,18 +337,14 @@ class _AdminModelEditPageState extends State { hintText: '可选', showCounter: false, keyboardType: TextInputType.number, - inputFormatters: [ - FilteringTextInputFormatter.digitsOnly - ], + inputFormatters: [FilteringTextInputFormatter.digitsOnly], textDirection: TextDirection.rtl, suffixIcon: Container( width: 110, alignment: Alignment.center, child: Text( '智慧果/1K Token', - style: TextStyle( - color: customColors.weakTextColor, - fontSize: 12), + style: TextStyle(color: customColors.weakTextColor, fontSize: 12), ), ), ), @@ -376,18 +356,14 @@ class _AdminModelEditPageState extends State { hintText: '最大上下文减掉预期的输出长度', showCounter: false, keyboardType: TextInputType.number, - inputFormatters: [ - FilteringTextInputFormatter.digitsOnly - ], + inputFormatters: [FilteringTextInputFormatter.digitsOnly], textDirection: TextDirection.rtl, suffixIcon: Container( width: 50, alignment: Alignment.center, child: Text( 'Token', - style: TextStyle( - color: customColors.weakTextColor, - fontSize: 12), + style: TextStyle(color: customColors.weakTextColor, fontSize: 12), ), ), ), @@ -395,8 +371,7 @@ class _AdminModelEditPageState extends State { ), for (var i = 0; i < providers.length; i++) Container( - margin: - const EdgeInsets.only(bottom: 10, left: 5, right: 5), + margin: const EdgeInsets.only(bottom: 10, left: 5, right: 5), child: Slidable( endActionPane: ActionPane( motion: const ScrollMotion(), @@ -404,8 +379,7 @@ class _AdminModelEditPageState extends State { const SizedBox(width: 10), SlidableAction( label: AppLocale.delete.getString(context), - borderRadius: BorderRadius.circular( - customColors.borderRadius ?? 8), + borderRadius: CustomSize.borderRadiusAll, backgroundColor: Colors.red, icon: Icons.delete, onPressed: (_) { @@ -416,8 +390,7 @@ class _AdminModelEditPageState extends State { openConfirmDialog( context, - AppLocale.confirmToDeleteRoom - .getString(context), + AppLocale.confirmToDeleteRoom.getString(context), () { setState(() { providers.removeAt(i); @@ -454,8 +427,7 @@ class _AdminModelEditPageState extends State { ...modelChannels .map( (e) => SelectorItem( - Text( - '${e.id == null ? '【系统】' : ''}${e.name}'), + Text('${e.id == null ? '【系统】' : ''}${e.name}'), e, ), ) @@ -494,18 +466,15 @@ class _AdminModelEditPageState extends State { showBeautyDialog( context, type: QuickAlertType.info, - text: - '渠道对应的模型标识和这里的 ID 不一致时,调用渠道接口时将会自动将模型替换为这里配置的值。', - confirmBtnText: - AppLocale.gotIt.getString(context), + text: '渠道对应的模型标识和这里的 ID 不一致时,调用渠道接口时将会自动将模型替换为这里配置的值。', + confirmBtnText: AppLocale.gotIt.getString(context), showCancelBtn: false, ); }, child: Icon( Icons.help_outline, size: 16, - color: customColors.weakLinkColor - ?.withAlpha(150), + color: customColors.weakLinkColor?.withAlpha(150), ), ), ), @@ -562,16 +531,14 @@ class _AdminModelEditPageState extends State { context, type: QuickAlertType.info, text: '当前模型是否支持视觉能力。', - confirmBtnText: - AppLocale.gotIt.getString(context), + confirmBtnText: AppLocale.gotIt.getString(context), showCancelBtn: false, ); }, child: Icon( Icons.help_outline, size: 16, - color: customColors.weakLinkColor - ?.withAlpha(150), + color: customColors.weakLinkColor?.withAlpha(150), ), ), ], @@ -603,16 +570,14 @@ class _AdminModelEditPageState extends State { context, type: QuickAlertType.info, text: '是否在模型旁边展示“新”标识,告知用户这是一个新模型。', - confirmBtnText: - AppLocale.gotIt.getString(context), + confirmBtnText: AppLocale.gotIt.getString(context), showCancelBtn: false, ); }, child: Icon( Icons.help_outline, size: 16, - color: customColors.weakLinkColor - ?.withAlpha(150), + color: customColors.weakLinkColor?.withAlpha(150), ), ), ], @@ -644,16 +609,14 @@ class _AdminModelEditPageState extends State { context, type: QuickAlertType.info, text: '受限模型是指因政策因素,不能在中国大陆地区使用的模型。', - confirmBtnText: - AppLocale.gotIt.getString(context), + confirmBtnText: AppLocale.gotIt.getString(context), showCancelBtn: false, ); }, child: Icon( Icons.help_outline, size: 16, - color: customColors.weakLinkColor - ?.withAlpha(150), + color: customColors.weakLinkColor?.withAlpha(150), ), ), ], @@ -711,9 +674,7 @@ class _AdminModelEditPageState extends State { color: customColors.weakLinkColor, fontSize: 15, icon: Icon( - showAdvancedOptions - ? Icons.unfold_less - : Icons.unfold_more, + showAdvancedOptions ? Icons.unfold_less : Icons.unfold_more, color: customColors.weakLinkColor, size: 15, ), @@ -730,10 +691,8 @@ class _AdminModelEditPageState extends State { title: AppLocale.save.getString(context), onPressed: onSubmit, icon: editLocked - ? const Icon(Icons.lock, - color: Colors.white, size: 16) - : const Icon(Icons.lock_open, - color: Colors.white, size: 16), + ? const Icon(Icons.lock, color: Colors.white, size: 16) + : const Icon(Icons.lock_open, color: Colors.white, size: 16), ), ), ], @@ -764,9 +723,7 @@ class _AdminModelEditPageState extends State { return; } - if (avatarUrl != null && - (!avatarUrl!.startsWith('http://') && - !avatarUrl!.startsWith('https://'))) { + if (avatarUrl != null && (!avatarUrl!.startsWith('http://') && !avatarUrl!.startsWith('https://'))) { final cancel = BotToast.showCustomLoading( toastBuilder: (cancel) { return const LoadingIndicator( @@ -777,8 +734,7 @@ class _AdminModelEditPageState extends State { ); try { - final res = await ImageUploader(widget.setting) - .upload(avatarUrl!, usage: 'avatar'); + final res = await ImageUploader(widget.setting).upload(avatarUrl!, usage: 'avatar'); avatarUrl = res.url; } catch (e) { showErrorMessage('上传头像失败'); diff --git a/lib/page/admin/payments.dart b/lib/page/admin/payments.dart index 3284211d..20d4c382 100644 --- a/lib/page/admin/payments.dart +++ b/lib/page/admin/payments.dart @@ -65,7 +65,7 @@ class _PaymentHistoriesPageState extends State { ), centerTitle: true, ), - backgroundColor: customColors.chatInputPanelBackground, + backgroundColor: customColors.backgroundColor, body: BackgroundContainer( setting: widget.setting, enabled: false, @@ -90,9 +90,7 @@ class _PaymentHistoriesPageState extends State { border: InputBorder.none, ), onEditingComplete: () { - context - .read() - .add(AdminPaymentHistoriesLoadEvent( + context.read().add(AdminPaymentHistoriesLoadEvent( perPage: perPage, page: page, keyword: keywordController.text, @@ -104,9 +102,7 @@ class _PaymentHistoriesPageState extends State { child: RefreshIndicator( color: customColors.linkColor, onRefresh: () async { - context - .read() - .add(AdminPaymentHistoriesLoadEvent( + context.read().add(AdminPaymentHistoriesLoadEvent( perPage: perPage, page: page, keyword: keywordController.text, @@ -131,8 +127,7 @@ class _PaymentHistoriesPageState extends State { }); } }, - buildWhen: (previous, current) => - current is AdminPaymentHistoriesLoaded, + buildWhen: (previous, current) => current is AdminPaymentHistoriesLoaded, builder: (context, state) { if (state is AdminPaymentHistoriesLoaded) { return SafeArea( @@ -152,8 +147,7 @@ class _PaymentHistoriesPageState extends State { }, ), ), - if (state.histories.lastPage != null && - state.histories.lastPage! > 1) + if (state.histories.lastPage != null && state.histories.lastPage! > 1) Container( padding: const EdgeInsets.all(10), child: Pagination( @@ -161,9 +155,7 @@ class _PaymentHistoriesPageState extends State { selectedPage: page, pagesVisible: 5, onPageChanged: (selected) { - context - .read() - .add(AdminPaymentHistoriesLoadEvent( + context.read().add(AdminPaymentHistoriesLoadEvent( perPage: perPage, page: selected, keyword: keywordController.text, @@ -199,17 +191,13 @@ class _PaymentHistoriesPageState extends State { horizontal: 10, vertical: 5, ), - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(customColors.borderRadius ?? 8), - ), + decoration: BoxDecoration(borderRadius: CustomSize.borderRadius), child: Slidable( child: Material( - borderRadius: - BorderRadius.all(Radius.circular(customColors.borderRadius ?? 8)), + borderRadius: CustomSize.borderRadius, color: customColors.columnBlockBackgroundColor, child: InkWell( - borderRadius: BorderRadius.all( - Radius.circular(customColors.borderRadius ?? 8)), + borderRadius: CustomSize.borderRadiusAll, onTap: () { context.push('/admin/users/${his.userId}'); }, @@ -219,7 +207,7 @@ class _PaymentHistoriesPageState extends State { mainAxisSize: MainAxisSize.min, children: [ // 头像 - buildAvatar(his, radius: BorderRadius.circular(15)), + buildAvatar(his, radius: CustomSize.borderRadiusAll), // 名称 Expanded( child: Container( @@ -269,9 +257,8 @@ class _PaymentHistoriesPageState extends State { fontSize: 10, overflow: TextOverflow.ellipsis, fontWeight: FontWeight.bold, - color: his.environment.toLowerCase() == 'production' - ? customColors.linkColor - : Colors.amber, + color: + his.environment.toLowerCase() == 'production' ? customColors.linkColor : Colors.amber, ), ), ], @@ -289,8 +276,7 @@ class _PaymentHistoriesPageState extends State { crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( - DateFormat('y-MM-dd HH:mm') - .format(his.purchaseAt.toLocal()), + DateFormat('y-MM-dd HH:mm').format(his.purchaseAt.toLocal()), style: TextStyle( fontSize: 10, overflow: TextOverflow.ellipsis, @@ -312,10 +298,7 @@ class _PaymentHistoriesPageState extends State { Widget buildAvatar( AdminPaymentHistory his, { - BorderRadius radius = const BorderRadius.only( - topLeft: Radius.circular(8), - bottomLeft: Radius.circular(8), - ), + BorderRadius radius = const BorderRadius.only(topLeft: CustomSize.radius, bottomLeft: CustomSize.radius), }) { final source = (his.source ?? '').toLowerCase(); @@ -342,8 +325,7 @@ Widget buildAvatar( ); } -Widget buildTags( - BuildContext context, CustomColors customColors, AdminPaymentHistory his) { +Widget buildTags(BuildContext context, CustomColors customColors, AdminPaymentHistory his) { final tags = []; if (his.source != null) { @@ -363,10 +345,7 @@ Widget buildTag(BuildContext context, CustomColors customColors, String s) { horizontal: 5, vertical: 2, ), - decoration: BoxDecoration( - color: customColors.tagsBackground, - borderRadius: BorderRadius.circular(5), - ), + decoration: BoxDecoration(color: customColors.tagsBackground, borderRadius: CustomSize.borderRadius), child: Text( s, style: TextStyle( diff --git a/lib/page/admin/recently_messages.dart b/lib/page/admin/recently_messages.dart index 54c2b1b1..d7f647e7 100644 --- a/lib/page/admin/recently_messages.dart +++ b/lib/page/admin/recently_messages.dart @@ -19,8 +19,7 @@ class AdminRecentlyMessagesPage extends StatefulWidget { const AdminRecentlyMessagesPage({super.key, required this.setting}); @override - State createState() => - _AdminRecentlyMessagesPageState(); + State createState() => _AdminRecentlyMessagesPageState(); } class _AdminRecentlyMessagesPageState extends State { @@ -62,7 +61,7 @@ class _AdminRecentlyMessagesPageState extends State { ), centerTitle: true, ), - backgroundColor: customColors.chatInputPanelBackground, + backgroundColor: customColors.backgroundColor, body: BackgroundContainer( setting: widget.setting, enabled: false, @@ -87,9 +86,7 @@ class _AdminRecentlyMessagesPageState extends State { border: InputBorder.none, ), onEditingComplete: () { - context - .read() - .add(AdminRecentlyMessagesLoadEvent( + context.read().add(AdminRecentlyMessagesLoadEvent( perPage: perPage, page: page, keyword: keywordController.text, @@ -101,9 +98,7 @@ class _AdminRecentlyMessagesPageState extends State { child: RefreshIndicator( color: customColors.linkColor, onRefresh: () async { - context - .read() - .add(AdminRecentlyMessagesLoadEvent( + context.read().add(AdminRecentlyMessagesLoadEvent( perPage: perPage, page: page, keyword: keywordController.text, @@ -114,11 +109,9 @@ class _AdminRecentlyMessagesPageState extends State { listener: (context, state) { if (state is AdminRoomOperationResult) { if (state.success) { - showSuccessMessage( - AppLocale.operateSuccess.getString(context)); + showSuccessMessage(AppLocale.operateSuccess.getString(context)); } else { - showErrorMessage( - AppLocale.operateFailed.getString(context)); + showErrorMessage(AppLocale.operateFailed.getString(context)); } } @@ -129,8 +122,7 @@ class _AdminRecentlyMessagesPageState extends State { }); } }, - buildWhen: (previous, current) => - current is AdminRecentlyMessagesLoaded, + buildWhen: (previous, current) => current is AdminRecentlyMessagesLoaded, builder: (context, state) { if (state is AdminRecentlyMessagesLoaded) { return SafeArea( @@ -148,83 +140,51 @@ class _AdminRecentlyMessagesPageState extends State { horizontal: 10, vertical: 5, ), - decoration: BoxDecoration( - borderRadius: BorderRadius.circular( - customColors.borderRadius ?? 8), - ), + decoration: BoxDecoration(borderRadius: CustomSize.borderRadius), child: Slidable( - startActionPane: ActionPane( - motion: const ScrollMotion(), - children: [ - SlidableAction( - label: '数字人', - borderRadius: BorderRadius.only( - topLeft: Radius.circular( - customColors.borderRadius ?? - 8), - bottomLeft: Radius.circular( - customColors.borderRadius ?? - 8), - topRight: Radius.circular( - customColors.borderRadius ?? - 8), - bottomRight: Radius.circular( - customColors.borderRadius ?? - 8), - ), - backgroundColor: Colors.blue, - icon: Icons.people, - foregroundColor: Colors.white, - onPressed: (_) { - context.push( - '/admin/users/${message.userId}/rooms'); - }, - ), - ]), + startActionPane: ActionPane(motion: const ScrollMotion(), children: [ + SlidableAction( + label: '数字人', + borderRadius: const BorderRadius.only( + topLeft: CustomSize.radius, + bottomLeft: CustomSize.radius, + topRight: CustomSize.radius, + bottomRight: CustomSize.radius, + ), + backgroundColor: Colors.blue, + icon: Icons.people, + foregroundColor: Colors.white, + onPressed: (_) { + context.push('/admin/users/${message.userId}/rooms'); + }, + ), + ]), endActionPane: ActionPane( motion: const ScrollMotion(), children: [ const SizedBox(width: 10), SlidableAction( label: '用户', - borderRadius: BorderRadius.only( - topLeft: Radius.circular( - customColors.borderRadius ?? - 8), - bottomLeft: Radius.circular( - customColors.borderRadius ?? - 8), - topRight: Radius.circular( - customColors.borderRadius ?? - 8), - bottomRight: Radius.circular( - customColors.borderRadius ?? - 8), + borderRadius: const BorderRadius.only( + topLeft: CustomSize.radius, + bottomLeft: CustomSize.radius, + topRight: CustomSize.radius, + bottomRight: CustomSize.radius, ), - backgroundColor: - customColors.linkColor ?? - Colors.green, + backgroundColor: customColors.linkColor ?? Colors.green, icon: Icons.person, foregroundColor: Colors.white, onPressed: (_) { - context.push( - '/admin/users/${message.userId}'); + context.push('/admin/users/${message.userId}'); }, ), ], ), child: Material( - borderRadius: BorderRadius.all( - Radius.circular( - customColors.borderRadius ?? - 8)), - color: customColors - .columnBlockBackgroundColor, + borderRadius: CustomSize.borderRadius, + color: customColors.columnBlockBackgroundColor, child: InkWell( - borderRadius: BorderRadius.all( - Radius.circular( - customColors.borderRadius ?? - 8)), + borderRadius: CustomSize.borderRadiusAll, onTap: () { context.push( '/admin/users/${message.userId}/rooms/${message.roomId}/messages?room_type=1'); @@ -235,83 +195,59 @@ class _AdminRecentlyMessagesPageState extends State { top: 0, right: 0, child: Container( - padding: const EdgeInsets - .symmetric( + padding: const EdgeInsets.symmetric( horizontal: 5, vertical: 2, ), decoration: BoxDecoration( - color: customColors - .columnBlockBackgroundColor - ?.withAlpha(100), - borderRadius: - BorderRadius.only( - topRight: Radius.circular( - customColors - .borderRadius ?? - 8), - bottomLeft: Radius - .circular(customColors - .borderRadius ?? - 8), + color: customColors.columnBlockBackgroundColor?.withAlpha(100), + borderRadius: const BorderRadius.only( + topRight: CustomSize.radius, + bottomLeft: CustomSize.radius, ), ), child: Text( '@ ${message.userId}', style: TextStyle( fontSize: 12, - color: customColors - .weakTextColor - ?.withAlpha(100), + color: customColors.weakTextColor?.withAlpha(100), ), ), ), ), Container( - padding: - const EdgeInsets.all(15), + padding: const EdgeInsets.all(15), child: Column( - crossAxisAlignment: - CrossAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( - state.messages.data[index] - .text, + state.messages.data[index].text, maxLines: 2, style: const TextStyle( - overflow: TextOverflow - .ellipsis, + overflow: TextOverflow.ellipsis, ), ), const SizedBox(height: 5), Row( - mainAxisAlignment: - MainAxisAlignment - .spaceBetween, + mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Expanded( child: Text( '${message.model}', - style: - const TextStyle( + style: const TextStyle( fontSize: 12, - color: - Colors.grey, + color: Colors.grey, ), - overflow: - TextOverflow - .ellipsis, + overflow: TextOverflow.ellipsis, maxLines: 1, ), ), if (message.ts != null) Text( ' ${DateFormat('MM/dd HH:mm').format(message.ts!.toLocal())}', - style: - const TextStyle( + style: const TextStyle( fontSize: 12, - color: - Colors.grey, + color: Colors.grey, ), ), ], @@ -328,8 +264,7 @@ class _AdminRecentlyMessagesPageState extends State { }, ), ), - if (state.messages.lastPage != null && - state.messages.lastPage! > 1) + if (state.messages.lastPage != null && state.messages.lastPage! > 1) Container( padding: const EdgeInsets.all(10), child: Pagination( @@ -337,9 +272,7 @@ class _AdminRecentlyMessagesPageState extends State { selectedPage: page, pagesVisible: 5, onPageChanged: (selected) { - context - .read() - .add(AdminRecentlyMessagesLoadEvent( + context.read().add(AdminRecentlyMessagesLoadEvent( perPage: perPage, page: selected, keyword: keywordController.text, diff --git a/lib/page/admin/rooms.dart b/lib/page/admin/rooms.dart index 0d3f2b16..7b0b5b88 100644 --- a/lib/page/admin/rooms.dart +++ b/lib/page/admin/rooms.dart @@ -33,9 +33,7 @@ class AdminRoomsPage extends StatefulWidget { class _AdminRoomsPageState extends State { @override void initState() { - context - .read() - .add(AdminRoomsLoadEvent(userId: widget.userId)); + context.read().add(AdminRoomsLoadEvent(userId: widget.userId)); super.initState(); } @@ -52,24 +50,21 @@ class _AdminRoomsPageState extends State { ), centerTitle: true, ), - backgroundColor: customColors.chatInputPanelBackground, + backgroundColor: customColors.backgroundColor, body: BackgroundContainer( setting: widget.setting, enabled: false, child: RefreshIndicator( color: customColors.linkColor, onRefresh: () async { - context - .read() - .add(AdminRoomsLoadEvent(userId: widget.userId)); + context.read().add(AdminRoomsLoadEvent(userId: widget.userId)); }, displacement: 20, child: BlocConsumer( listener: (context, state) { if (state is AdminRoomOperationResult) { if (state.success) { - showSuccessMessage( - AppLocale.operateSuccess.getString(context)); + showSuccessMessage(AppLocale.operateSuccess.getString(context)); } else { showErrorMessage(AppLocale.operateFailed.getString(context)); } @@ -90,17 +85,12 @@ class _AdminRoomsPageState extends State { horizontal: 10, vertical: 5, ), - decoration: BoxDecoration( - borderRadius: BorderRadius.circular( - customColors.borderRadius ?? 8), - ), + decoration: BoxDecoration(borderRadius: CustomSize.borderRadius), child: Material( - borderRadius: BorderRadius.all( - Radius.circular(customColors.borderRadius ?? 8)), + borderRadius: CustomSize.borderRadius, color: customColors.columnBlockBackgroundColor, child: InkWell( - borderRadius: BorderRadius.all(Radius.circular( - customColors.borderRadius ?? 8)), + borderRadius: CustomSize.borderRadiusAll, onTap: () { context.push( '/admin/users/${widget.userId}/rooms/${room.id}/messages?room_type=${room.roomType ?? 1}'); @@ -113,31 +103,23 @@ class _AdminRoomsPageState extends State { _buildAvatar(room), Expanded( child: Container( - padding: const EdgeInsets.symmetric( - horizontal: 10), + padding: const EdgeInsets.symmetric(horizontal: 10), child: Column( - crossAxisAlignment: - CrossAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( - mainAxisAlignment: - MainAxisAlignment - .spaceBetween, + mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Expanded( child: Text( room.name, - overflow: - TextOverflow.ellipsis, + overflow: TextOverflow.ellipsis, ), ), Text( - humanTime( - room.lastActiveTime), + humanTime(room.lastActiveTime), style: TextStyle( - color: customColors - .weakLinkColor - ?.withAlpha(65), + color: customColors.weakLinkColor?.withAlpha(65), fontSize: 10, ), ), @@ -155,15 +137,13 @@ class _AdminRoomsPageState extends State { top: 0, child: Container( decoration: BoxDecoration( - color: customColors - .backgroundContainerColor, + color: customColors.backgroundContainerColor, borderRadius: const BorderRadius.only( - topRight: Radius.circular(8), - bottomLeft: Radius.circular(8), + topRight: CustomSize.radius, + bottomLeft: CustomSize.radius, ), ), - padding: const EdgeInsets.symmetric( - horizontal: 8, vertical: 2), + padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 2), child: Text( '群聊', style: TextStyle( @@ -194,8 +174,7 @@ class _AdminRoomsPageState extends State { } Widget _buildAvatar(RoomInServer room) { - if (room.members.length == 1 && - (room.avatarUrl == null || room.avatarUrl == '')) { + if (room.members.length == 1 && (room.avatarUrl == null || room.avatarUrl == '')) { room.avatarUrl = room.members[0]; } @@ -204,10 +183,7 @@ class _AdminRoomsPageState extends State { width: 70, height: 70, child: ClipRRect( - borderRadius: const BorderRadius.only( - topLeft: Radius.circular(8), - bottomLeft: Radius.circular(8), - ), + borderRadius: const BorderRadius.only(topLeft: CustomSize.radius, bottomLeft: CustomSize.radius), child: CachedNetworkImageEnhanced( imageUrl: imageURL(room.avatarUrl!, qiniuImageTypeAvatar), fit: BoxFit.fill, @@ -218,10 +194,7 @@ class _AdminRoomsPageState extends State { if (room.members.isNotEmpty) { return ClipRRect( - borderRadius: const BorderRadius.only( - topLeft: Radius.circular(8), - bottomLeft: Radius.circular(8), - ), + borderRadius: const BorderRadius.only(topLeft: CustomSize.radius, bottomLeft: CustomSize.radius), child: GroupAvatar( size: 70, avatars: room.members, @@ -233,10 +206,7 @@ class _AdminRoomsPageState extends State { text: room.name.split('、').join(' '), size: 70, backgroundColor: Colors.grey.withAlpha(100), - borderRadius: const BorderRadius.only( - topLeft: Radius.circular(8), - bottomLeft: Radius.circular(8), - ), + borderRadius: const BorderRadius.only(topLeft: CustomSize.radius, bottomLeft: CustomSize.radius), ); } } diff --git a/lib/page/admin/user.dart b/lib/page/admin/user.dart index 78c37eb4..d30403d5 100644 --- a/lib/page/admin/user.dart +++ b/lib/page/admin/user.dart @@ -77,9 +77,7 @@ class _AdminUserPageState extends State { textAlignVertical: TextAlignVertical.top, showCounter: false, keyboardType: TextInputType.number, - inputFormatters: [ - FilteringTextInputFormatter.digitsOnly - ], + inputFormatters: [FilteringTextInputFormatter.digitsOnly], suffixIcon: Container( width: 110, alignment: Alignment.center, @@ -103,9 +101,7 @@ class _AdminUserPageState extends State { textAlignVertical: TextAlignVertical.top, showCounter: false, keyboardType: TextInputType.number, - inputFormatters: [ - FilteringTextInputFormatter.digitsOnly - ], + inputFormatters: [FilteringTextInputFormatter.digitsOnly], suffixIcon: Container( width: 110, alignment: Alignment.center, @@ -157,12 +153,9 @@ class _AdminUserPageState extends State { ) .then((value) { showSuccessMessage('赠送成功'); - context - .read() - .add(UserQuotaLoadEvent(widget.userId)); + context.read().add(UserQuotaLoadEvent(widget.userId)); }).onError( - (error, stackTrace) => - showErrorMessageEnhanced(context, error!), + (error, stackTrace) => showErrorMessageEnhanced(context, error!), ); return true; @@ -172,7 +165,7 @@ class _AdminUserPageState extends State { ), ], ), - backgroundColor: customColors.chatInputPanelBackground, + backgroundColor: customColors.backgroundColor, body: BackgroundContainer( setting: widget.setting, enabled: false, @@ -189,17 +182,14 @@ class _AdminUserPageState extends State { child: Column( children: [ BlocConsumer( - listenWhen: (previous, current) => - current is UserOperationResult, + listenWhen: (previous, current) => current is UserOperationResult, listener: (context, state) { if (state is UserOperationResult) { if (state.success) { - showSuccessMessage(state.message ?? - AppLocale.operateSuccess.getString(context)); + showSuccessMessage(state.message ?? AppLocale.operateSuccess.getString(context)); context.read().add(UserListLoadEvent()); } else { - showErrorMessage(state.message ?? - AppLocale.operateFailed.getString(context)); + showErrorMessage(state.message ?? AppLocale.operateFailed.getString(context)); } } }, @@ -215,14 +205,12 @@ class _AdminUserPageState extends State { children: [ Expanded( child: Column( - crossAxisAlignment: - CrossAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, children: [ SizedBox( width: double.infinity, child: Row( - crossAxisAlignment: - CrossAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.center, mainAxisSize: MainAxisSize.min, children: [ Text( @@ -230,8 +218,7 @@ class _AdminUserPageState extends State { style: TextStyle( fontSize: 14, fontWeight: FontWeight.bold, - color: - customColors.weakTextColor, + color: customColors.weakTextColor, ), ), const SizedBox(width: 10), @@ -239,8 +226,7 @@ class _AdminUserPageState extends State { '${state.user.id}', style: TextStyle( fontSize: 12, - color: - customColors.weakTextColor, + color: customColors.weakTextColor, ), maxLines: 5, overflow: TextOverflow.ellipsis, @@ -249,41 +235,33 @@ class _AdminUserPageState extends State { ), ), const SizedBox(height: 10), - buildTags( - context, customColors, state.user), + buildTags(context, customColors, state.user), ], ), ), - buildUserAvatar( - state.user, - radius: BorderRadius.circular(8), - ), + buildUserAvatar(state.user, radius: CustomSize.borderRadiusAll), ], ), TextItem( title: '类型', value: state.user.userType ?? '-', ), - if (state.user.phone != null && - state.user.phone!.isNotEmpty) + if (state.user.phone != null && state.user.phone!.isNotEmpty) TextItem( title: '手机号', value: state.user.phone!, ), - if (state.user.email != null && - state.user.email!.isNotEmpty) + if (state.user.email != null && state.user.email!.isNotEmpty) TextItem( title: '邮箱', value: state.user.email!, ), - if (state.user.realname != null && - state.user.realname!.isNotEmpty) + if (state.user.realname != null && state.user.realname!.isNotEmpty) TextItem( title: '昵称', value: state.user.realname!, ), - if (state.user.invitedBy != null && - state.user.invitedBy! > 0) + if (state.user.invitedBy != null && state.user.invitedBy! > 0) TextItem( title: '邀请人 ID', value: '${state.user.invitedBy}', @@ -291,8 +269,7 @@ class _AdminUserPageState extends State { if (state.user.createdAt != null) TextItem( title: '注册时间', - value: - state.user.createdAt!.toLocal().toString(), + value: state.user.createdAt!.toLocal().toString(), ), TextItem( title: '状态', @@ -310,8 +287,7 @@ class _AdminUserPageState extends State { }, ), BlocBuilder( - buildWhen: (previous, current) => - current is UserQuotaLoaded, + buildWhen: (previous, current) => current is UserQuotaLoaded, builder: (context, state) { if (state is UserQuotaLoaded) { return ColumnBlock( @@ -388,7 +364,7 @@ class _AdminUserPageState extends State { ), decoration: BoxDecoration( color: customColors.paymentItemBackgroundColor, - borderRadius: BorderRadius.circular(10), + borderRadius: CustomSize.borderRadius, ), child: Column( mainAxisSize: MainAxisSize.min, @@ -398,13 +374,10 @@ class _AdminUserPageState extends State { children: [ Expanded( child: Column( - crossAxisAlignment: - CrossAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( - (item.note == null || item.note == '') - ? '购买' - : item.note!, + (item.note == null || item.note == '') ? '购买' : item.note!, overflow: TextOverflow.ellipsis, ), const SizedBox(height: 5), @@ -412,8 +385,7 @@ class _AdminUserPageState extends State { DateFormat( 'yyyy/MM/dd HH:mm', ).format(item.createdAt.toLocal()), - textScaler: - const TextScaler.linear(0.8), + textScaler: const TextScaler.linear(0.8), style: TextStyle( color: Colors.grey[600], ), @@ -470,10 +442,7 @@ class _AdminUserPageState extends State { child: Container( decoration: BoxDecoration( color: color, - borderRadius: const BorderRadius.only( - topRight: Radius.circular(9), - bottomLeft: Radius.circular(9), - ), + borderRadius: const BorderRadius.only(topRight: CustomSize.radius, bottomLeft: CustomSize.radius), ), padding: const EdgeInsets.symmetric( horizontal: 5, diff --git a/lib/page/admin/users.dart b/lib/page/admin/users.dart index f3032c98..5d7a9bde 100644 --- a/lib/page/admin/users.dart +++ b/lib/page/admin/users.dart @@ -68,7 +68,7 @@ class _AdminUsersPageState extends State { ), centerTitle: true, ), - backgroundColor: customColors.chatInputPanelBackground, + backgroundColor: customColors.backgroundColor, body: BackgroundContainer( setting: widget.setting, enabled: false, @@ -116,12 +116,10 @@ class _AdminUsersPageState extends State { listener: (context, state) { if (state is UserOperationResult) { if (state.success) { - showSuccessMessage(state.message ?? - AppLocale.operateSuccess.getString(context)); + showSuccessMessage(state.message ?? AppLocale.operateSuccess.getString(context)); context.read().add(UserListLoadEvent()); } else { - showErrorMessage(state.message ?? - AppLocale.operateFailed.getString(context)); + showErrorMessage(state.message ?? AppLocale.operateFailed.getString(context)); } } @@ -152,8 +150,7 @@ class _AdminUsersPageState extends State { }, ), ), - if (state.users.lastPage != null && - state.users.lastPage! > 1) + if (state.users.lastPage != null && state.users.lastPage! > 1) Container( padding: const EdgeInsets.all(10), child: Pagination( @@ -161,9 +158,7 @@ class _AdminUsersPageState extends State { selectedPage: page, pagesVisible: 5, onPageChanged: (selected) { - context - .read() - .add(UserListLoadEvent( + context.read().add(UserListLoadEvent( perPage: perPage, page: selected, keyword: keywordController.text, @@ -199,9 +194,7 @@ class _AdminUsersPageState extends State { horizontal: 10, vertical: 5, ), - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(customColors.borderRadius ?? 8), - ), + decoration: BoxDecoration(borderRadius: CustomSize.borderRadius), child: Slidable( endActionPane: ActionPane( motion: const ScrollMotion(), @@ -209,11 +202,11 @@ class _AdminUsersPageState extends State { const SizedBox(width: 10), SlidableAction( label: '数字人列表', - borderRadius: BorderRadius.only( - topLeft: Radius.circular(customColors.borderRadius ?? 8), - bottomLeft: Radius.circular(customColors.borderRadius ?? 8), - topRight: Radius.circular(customColors.borderRadius ?? 8), - bottomRight: Radius.circular(customColors.borderRadius ?? 8), + borderRadius: const BorderRadius.only( + topLeft: CustomSize.radius, + bottomLeft: CustomSize.radius, + topRight: CustomSize.radius, + bottomRight: CustomSize.radius, ), backgroundColor: customColors.linkColor ?? Colors.green, icon: Icons.people, @@ -225,12 +218,10 @@ class _AdminUsersPageState extends State { ], ), child: Material( - borderRadius: - BorderRadius.all(Radius.circular(customColors.borderRadius ?? 8)), + borderRadius: CustomSize.borderRadius, color: customColors.columnBlockBackgroundColor, child: InkWell( - borderRadius: BorderRadius.all( - Radius.circular(customColors.borderRadius ?? 8)), + borderRadius: CustomSize.borderRadiusAll, onTap: () { context.push('/admin/users/${user.id}'); }, @@ -247,9 +238,7 @@ class _AdminUsersPageState extends State { bottom: 0, width: 70, child: ClipRRect( - borderRadius: const BorderRadius.only( - bottomLeft: Radius.circular(8), - ), + borderRadius: const BorderRadius.only(bottomLeft: CustomSize.radius), child: Container( color: Colors.black.withAlpha(100), padding: const EdgeInsets.symmetric(vertical: 2), @@ -322,9 +311,7 @@ class _AdminUsersPageState extends State { crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( - user.createdAt != null - ? humanTime(user.createdAt) - : '', + user.createdAt != null ? humanTime(user.createdAt) : '', style: TextStyle( fontSize: 10, overflow: TextOverflow.ellipsis, @@ -346,10 +333,7 @@ class _AdminUsersPageState extends State { Widget buildUserAvatar( AdminUser user, { - BorderRadius radius = const BorderRadius.only( - topLeft: Radius.circular(8), - bottomLeft: Radius.circular(8), - ), + BorderRadius radius = const BorderRadius.only(topLeft: CustomSize.radius, bottomLeft: CustomSize.radius), }) { if (user.avatar != null && user.avatar!.startsWith('http')) { return SizedBox( @@ -373,8 +357,7 @@ Widget buildUserAvatar( ); } -Widget buildTags( - BuildContext context, CustomColors customColors, AdminUser user) { +Widget buildTags(BuildContext context, CustomColors customColors, AdminUser user) { final tags = []; if (user.email != null && user.email!.isNotEmpty) { @@ -408,7 +391,7 @@ Widget buildTag(BuildContext context, CustomColors customColors, String s) { ), decoration: BoxDecoration( color: customColors.tagsBackground, - borderRadius: BorderRadius.circular(5), + borderRadius: CustomSize.borderRadius, ), child: Text( s, diff --git a/lib/page/auth/signin_or_signup.dart b/lib/page/auth/signin_or_signup.dart index 73d2d472..9754f563 100644 --- a/lib/page/auth/signin_or_signup.dart +++ b/lib/page/auth/signin_or_signup.dart @@ -41,8 +41,7 @@ class SigninOrSignupScreen extends StatefulWidget { class _SigninOrSignupScreenState extends State { final TextEditingController _inviteCodeController = TextEditingController(); - final TextEditingController _verificationCodeController = - TextEditingController(); + final TextEditingController _verificationCodeController = TextEditingController(); final TextEditingController _passwordController = TextEditingController(); String verifyCodeId = ''; @@ -84,7 +83,7 @@ class _SigninOrSignupScreenState extends State { }, ), ), - backgroundColor: customColors.backgroundColor, + backgroundColor: customColors.backgroundContainerColor, body: BackgroundContainer( setting: widget.settings, enabled: false, @@ -93,9 +92,7 @@ class _SigninOrSignupScreenState extends State { child: ConstrainedBox( constraints: const BoxConstraints(maxWidth: 400), child: SingleChildScrollView( - child: widget.isSignup || - signInMethod == 'sms_code' || - signInMethod == 'email_code' + child: widget.isSignup || signInMethod == 'sms_code' || signInMethod == 'email_code' ? signInOrSignUpWithSMSOrEmailCode(customColors, context) : signInWithPassword(customColors, context)), ), @@ -125,8 +122,7 @@ class _SigninOrSignupScreenState extends State { const SizedBox(height: 10), // 密码 Padding( - padding: const EdgeInsets.only( - left: 15.0, right: 15.0, top: 15, bottom: 0), + padding: const EdgeInsets.only(left: 15.0, right: 15.0, top: 15, bottom: 0), child: PasswordField( controller: _passwordController, labelText: AppLocale.password.getString(context), @@ -140,10 +136,7 @@ class _SigninOrSignupScreenState extends State { height: 45, width: double.infinity, margin: const EdgeInsets.symmetric(horizontal: 15), - decoration: BoxDecoration( - color: customColors.linkColor, - borderRadius: BorderRadius.circular(8), - ), + decoration: BoxDecoration(color: customColors.linkColor, borderRadius: CustomSize.borderRadius), child: TextButton( onPressed: () { FocusScope.of(context).requestFocus(FocusNode()); @@ -155,8 +148,7 @@ class _SigninOrSignupScreenState extends State { } if (password.length < 8 || password.length > 20) { - showErrorMessage( - AppLocale.passwordFormatError.getString(context)); + showErrorMessage(AppLocale.passwordFormatError.getString(context)); return; } @@ -193,10 +185,7 @@ class _SigninOrSignupScreenState extends State { TextButton( onPressed: () { setState(() { - signInMethod = - phoneNumberValidator.hasMatch(widget.username) - ? 'sms_code' - : 'email_code'; + signInMethod = phoneNumberValidator.hasMatch(widget.username) ? 'sms_code' : 'email_code'; }); }, child: Text( @@ -209,8 +198,7 @@ class _SigninOrSignupScreenState extends State { ), TextButton( onPressed: () { - context - .push('/retrieve-password?username=${widget.username}'); + context.push('/retrieve-password?username=${widget.username}'); }, child: Text( AppLocale.forgotPassword.getString(context), @@ -248,8 +236,7 @@ class _SigninOrSignupScreenState extends State { const SizedBox(height: 10), // 验证码 Padding( - padding: - const EdgeInsets.only(left: 15.0, right: 5.0, top: 15, bottom: 0), + padding: const EdgeInsets.only(left: 15.0, right: 5.0, top: 15, bottom: 0), child: VerifyCodeInput( controller: _verificationCodeController, onVerifyCodeSent: (id) { @@ -258,9 +245,7 @@ class _SigninOrSignupScreenState extends State { sendVerifyCode: () { return APIServer().sendSigninOrSignupVerifyCode( widget.username, - verifyType: phoneNumberValidator.hasMatch(widget.username) - ? 'sms' - : 'email', + verifyType: phoneNumberValidator.hasMatch(widget.username) ? 'sms' : 'email', isSignup: widget.isSignup, ); }, @@ -273,25 +258,19 @@ class _SigninOrSignupScreenState extends State { // 邀请码 if (widget.isSignup) Padding( - padding: const EdgeInsets.only( - left: 15.0, right: 15.0, top: 15, bottom: 0), + padding: const EdgeInsets.only(left: 15.0, right: 15.0, top: 15, bottom: 0), child: TextFormField( controller: _inviteCodeController, - inputFormatters: [ - FilteringTextInputFormatter.singleLineFormatter - ], + inputFormatters: [FilteringTextInputFormatter.singleLineFormatter], decoration: InputDecoration( border: const OutlineInputBorder(), enabledBorder: const OutlineInputBorder( - borderSide: - BorderSide(color: Color.fromARGB(200, 192, 192, 192)), + borderSide: BorderSide(color: Color.fromARGB(200, 192, 192, 192)), ), focusedBorder: OutlineInputBorder( - borderSide: - BorderSide(color: customColors.linkColor ?? Colors.green), + borderSide: BorderSide(color: customColors.linkColor ?? Colors.green), ), - floatingLabelStyle: - TextStyle(color: customColors.linkColor ?? Colors.green), + floatingLabelStyle: TextStyle(color: customColors.linkColor ?? Colors.green), isDense: true, floatingLabelBehavior: FloatingLabelBehavior.always, labelText: AppLocale.inviteCode.getString(context), @@ -311,16 +290,11 @@ class _SigninOrSignupScreenState extends State { height: 45, width: double.infinity, margin: const EdgeInsets.symmetric(horizontal: 15), - decoration: BoxDecoration( - color: customColors.linkColor, - borderRadius: BorderRadius.circular(8), - ), + decoration: BoxDecoration(color: customColors.linkColor, borderRadius: CustomSize.borderRadius), child: TextButton( onPressed: onCreateSubmit, child: Text( - widget.isSignup - ? AppLocale.createAccount.getString(context) - : AppLocale.signIn.getString(context), + widget.isSignup ? AppLocale.createAccount.getString(context) : AppLocale.signIn.getString(context), style: const TextStyle(color: Colors.white, fontSize: 18), ), ), diff --git a/lib/page/auth/signin_screen.dart b/lib/page/auth/signin_screen.dart index ac66432f..96fdd9ed 100644 --- a/lib/page/auth/signin_screen.dart +++ b/lib/page/auth/signin_screen.dart @@ -43,8 +43,7 @@ class _SignInScreenState extends State { final TextEditingController _usernameController = TextEditingController(); final phoneNumberValidator = RegExp(r"^1[3456789]\d{9}$"); - final emailValidator = RegExp( - r"^[a-zA-Z0-9.a-zA-Z0-9.!#$%&'*+-/=?^_`{|}~]+@[a-zA-Z0-9]+\.[a-zA-Z]+"); + final emailValidator = RegExp(r"^[a-zA-Z0-9.a-zA-Z0-9.!#$%&'*+-/=?^_`{|}~]+@[a-zA-Z0-9]+\.[a-zA-Z]+"); var agreeProtocol = false; @@ -61,8 +60,7 @@ class _SignInScreenState extends State { } if (Ability().enableWechatSignin) { - _weChatResponse = - weChatResponseEventHandler.distinct((a, b) => a == b).listen((event) { + _weChatResponse = weChatResponseEventHandler.distinct((a, b) => a == b).listen((event) { if (event is WeChatAuthResponse) { if (event.errCode != 0) { showErrorMessage(event.errStr!); @@ -76,9 +74,7 @@ class _SignInScreenState extends State { processing = true; - APIServer() - .trySignInWithWechat(code: event.code!) - .then((tryRes) async { + APIServer().trySignInWithWechat(code: event.code!).then((tryRes) async { if (tryRes.exist) { await confirmWeChatSignin(tryRes.token); } else { @@ -170,7 +166,7 @@ class _SignInScreenState extends State { }, ), ), - backgroundColor: customColors.backgroundColor, + backgroundColor: customColors.backgroundContainerColor, body: BackgroundContainer( setting: widget.settings, enabled: false, @@ -205,22 +201,17 @@ class _SignInScreenState extends State { ), const SizedBox(height: 30), Padding( - padding: const EdgeInsets.only( - left: 15.0, right: 15.0, top: 15, bottom: 0), + padding: const EdgeInsets.only(left: 15.0, right: 15.0, top: 15, bottom: 0), child: TextFormField( controller: _usernameController, - inputFormatters: [ - FilteringTextInputFormatter.singleLineFormatter - ], + inputFormatters: [FilteringTextInputFormatter.singleLineFormatter], decoration: InputDecoration( border: const OutlineInputBorder(), enabledBorder: const OutlineInputBorder( - borderSide: BorderSide( - color: Color.fromARGB(200, 192, 192, 192)), + borderSide: BorderSide(color: Color.fromARGB(200, 192, 192, 192)), ), focusedBorder: OutlineInputBorder( - borderSide: BorderSide( - color: customColors.linkColor ?? Colors.green), + borderSide: BorderSide(color: customColors.linkColor ?? Colors.green), ), floatingLabelStyle: TextStyle( color: customColors.linkColor ?? Colors.green, @@ -256,9 +247,7 @@ class _SignInScreenState extends State { height: 45, width: double.infinity, margin: const EdgeInsets.symmetric(horizontal: 15), - decoration: BoxDecoration( - color: customColors.linkColor, - borderRadius: BorderRadius.circular(8)), + decoration: BoxDecoration(color: customColors.linkColor, borderRadius: CustomSize.borderRadius), child: TextButton( onPressed: onSigninSubmit, child: const Text( @@ -318,8 +307,7 @@ class _SignInScreenState extends State { // 三方登录 BlocBuilder( builder: (context, state) { - return _buildThirdPartySignInButtons( - context, customColors); + return _buildThirdPartySignInButtons(context, customColors); }, ), const SizedBox(height: 10), @@ -332,8 +320,7 @@ class _SignInScreenState extends State { ); } - Row _buildUserTermsAndPrivicy( - CustomColors customColors, BuildContext context) { + Row _buildUserTermsAndPrivicy(CustomColors customColors, BuildContext context) { return Row( mainAxisAlignment: MainAxisAlignment.center, children: [ @@ -378,8 +365,7 @@ class _SignInScreenState extends State { ), recognizer: TapGestureRecognizer() ..onTap = () { - launchUrl( - Uri.parse('$apiServerURL/public/info/terms-of-user')); + launchUrl(Uri.parse('$apiServerURL/public/info/terms-of-user')); }, ), TextSpan( @@ -397,8 +383,7 @@ class _SignInScreenState extends State { ), recognizer: TapGestureRecognizer() ..onTap = () { - launchUrl( - Uri.parse('$apiServerURL/public/info/privacy-policy')); + launchUrl(Uri.parse('$apiServerURL/public/info/privacy-policy')); }, ), ], @@ -408,8 +393,7 @@ class _SignInScreenState extends State { ); } - Widget _buildThirdPartySignInButtons( - BuildContext context, CustomColors customColors) { + Widget _buildThirdPartySignInButtons(BuildContext context, CustomColors customColors) { return FutureBuilder( future: isWeChatInstalled, builder: (context, installed) { @@ -436,13 +420,11 @@ class _SignInScreenState extends State { } if (!agreeProtocol) { - showErrorMessage( - AppLocale.pleaseReadAgreeProtocol.getString(context)); + showErrorMessage(AppLocale.pleaseReadAgreeProtocol.getString(context)); return; } - final ok = await sendWeChatAuth( - scope: "snsapi_userinfo", state: "wechat_sdk_demo_test"); + final ok = await sendWeChatAuth(scope: "snsapi_userinfo", state: "wechat_sdk_demo_test"); if (!ok) { showErrorMessage('请先安装微信后再使用该功能'); } @@ -471,10 +453,7 @@ class _SignInScreenState extends State { Row( mainAxisAlignment: MainAxisAlignment.center, mainAxisSize: MainAxisSize.min, - children: signInItems - .map((e) => - Padding(padding: const EdgeInsets.all(10), child: e)) - .toList(), + children: signInItems.map((e) => Padding(padding: const EdgeInsets.all(10), child: e)).toList(), ), ], ); @@ -500,8 +479,7 @@ class _SignInScreenState extends State { final credential = await SignInWithApple.getAppleIDCredential( webAuthenticationOptions: WebAuthenticationOptions( clientId: 'cc.aicode.askaide', - redirectUri: Uri.parse( - 'https://ai-api.aicode.cc/v1/callback/auth/sign_in_with_apple'), + redirectUri: Uri.parse('https://ai-api.aicode.cc/v1/callback/auth/sign_in_with_apple'), ), scopes: [ AppleIDAuthorizationScopes.email, @@ -581,8 +559,7 @@ class _SignInScreenState extends State { return; } - if (!phoneNumberValidator.hasMatch(username) && - !emailValidator.hasMatch(username)) { + if (!phoneNumberValidator.hasMatch(username) && !emailValidator.hasMatch(username)) { showErrorMessage(AppLocale.accountFormatError.getString(context)); return; } diff --git a/lib/page/auth/signup_screen.dart b/lib/page/auth/signup_screen.dart index fb4fe518..a5b2b711 100644 --- a/lib/page/auth/signup_screen.dart +++ b/lib/page/auth/signup_screen.dart @@ -11,6 +11,7 @@ import 'package:askaide/page/component/background_container.dart'; import 'package:askaide/page/component/loading.dart'; import 'package:askaide/page/component/password_field.dart'; import 'package:askaide/page/component/dialog.dart'; +import 'package:askaide/page/component/theme/custom_size.dart'; import 'package:askaide/page/component/theme/custom_theme.dart'; import 'package:askaide/repo/api_server.dart'; import 'package:askaide/repo/settings_repo.dart'; @@ -36,13 +37,11 @@ class _SignupScreenState extends State { final TextEditingController _usernameController = TextEditingController(); final TextEditingController _inviteCodeController = TextEditingController(); final TextEditingController _passwordController = TextEditingController(); - final TextEditingController _verificationCodeController = - TextEditingController(); + final TextEditingController _verificationCodeController = TextEditingController(); String verifyCodeId = ''; - final emailValidator = RegExp( - r"^[a-zA-Z0-9.a-zA-Z0-9.!#$%&'*+-/=?^_`{|}~]+@[a-zA-Z0-9]+\.[a-zA-Z]+"); + final emailValidator = RegExp(r"^[a-zA-Z0-9.a-zA-Z0-9.!#$%&'*+-/=?^_`{|}~]+@[a-zA-Z0-9]+\.[a-zA-Z]+"); final phoneNumberValidator = RegExp(r"^1[3456789]\d{9}$"); var agreeProtocol = false; @@ -143,25 +142,19 @@ class _SignupScreenState extends State { const SizedBox(height: 30), // 用户名 Padding( - padding: const EdgeInsets.only( - left: 15.0, right: 15.0, top: 15, bottom: 0), + padding: const EdgeInsets.only(left: 15.0, right: 15.0, top: 15, bottom: 0), child: TextFormField( controller: _usernameController, - inputFormatters: [ - FilteringTextInputFormatter.singleLineFormatter - ], + inputFormatters: [FilteringTextInputFormatter.singleLineFormatter], decoration: InputDecoration( border: const OutlineInputBorder(), enabledBorder: const OutlineInputBorder( - borderSide: BorderSide( - color: Color.fromARGB(255, 192, 192, 192)), + borderSide: BorderSide(color: Color.fromARGB(255, 192, 192, 192)), ), focusedBorder: OutlineInputBorder( - borderSide: - BorderSide(color: customColors.linkColor!), + borderSide: BorderSide(color: customColors.linkColor!), ), - floatingLabelStyle: - TextStyle(color: customColors.linkColor!), + floatingLabelStyle: TextStyle(color: customColors.linkColor!), isDense: true, floatingLabelBehavior: FloatingLabelBehavior.always, labelText: AppLocale.account.getString(context), @@ -175,8 +168,7 @@ class _SignupScreenState extends State { ), // 密码 Padding( - padding: const EdgeInsets.only( - left: 15.0, right: 15.0, top: 15, bottom: 0), + padding: const EdgeInsets.only(left: 15.0, right: 15.0, top: 15, bottom: 0), child: PasswordField( controller: _passwordController, labelText: AppLocale.password.getString(context), @@ -185,30 +177,23 @@ class _SignupScreenState extends State { ), // 邀请码 Padding( - padding: const EdgeInsets.only( - left: 15.0, right: 15.0, top: 15, bottom: 0), + padding: const EdgeInsets.only(left: 15.0, right: 15.0, top: 15, bottom: 0), child: TextFormField( controller: _inviteCodeController, - inputFormatters: [ - FilteringTextInputFormatter.singleLineFormatter - ], + inputFormatters: [FilteringTextInputFormatter.singleLineFormatter], decoration: InputDecoration( border: const OutlineInputBorder(), enabledBorder: const OutlineInputBorder( - borderSide: BorderSide( - color: Color.fromARGB(255, 192, 192, 192)), + borderSide: BorderSide(color: Color.fromARGB(255, 192, 192, 192)), ), focusedBorder: OutlineInputBorder( - borderSide: - BorderSide(color: customColors.linkColor!), + borderSide: BorderSide(color: customColors.linkColor!), ), - floatingLabelStyle: - TextStyle(color: customColors.linkColor!), + floatingLabelStyle: TextStyle(color: customColors.linkColor!), isDense: true, floatingLabelBehavior: FloatingLabelBehavior.always, labelText: AppLocale.inviteCode.getString(context), - hintText: - AppLocale.inviteCodeInputTips.getString(context), + hintText: AppLocale.inviteCodeInputTips.getString(context), hintStyle: TextStyle( color: customColors.textfieldHintColor, fontSize: 15, @@ -218,8 +203,7 @@ class _SignupScreenState extends State { ), // 验证码 Padding( - padding: const EdgeInsets.only( - left: 15.0, right: 15.0, top: 15, bottom: 0), + padding: const EdgeInsets.only(left: 15.0, right: 15.0, top: 15, bottom: 0), child: Row(children: [ Expanded( child: TextFormField( @@ -234,20 +218,16 @@ class _SignupScreenState extends State { counterText: '', border: const OutlineInputBorder(), enabledBorder: const OutlineInputBorder( - borderSide: BorderSide( - color: Color.fromARGB(255, 192, 192, 192)), + borderSide: BorderSide(color: Color.fromARGB(255, 192, 192, 192)), ), focusedBorder: OutlineInputBorder( - borderSide: - BorderSide(color: customColors.linkColor!), + borderSide: BorderSide(color: customColors.linkColor!), ), - floatingLabelStyle: - TextStyle(color: customColors.linkColor!), + floatingLabelStyle: TextStyle(color: customColors.linkColor!), isDense: true, floatingLabelBehavior: FloatingLabelBehavior.always, labelText: AppLocale.verifyCode.getString(context), - hintText: AppLocale.verifyCodeInputTips - .getString(context), + hintText: AppLocale.verifyCodeInputTips.getString(context), hintStyle: TextStyle( color: customColors.textfieldHintColor, fontSize: 15, @@ -271,25 +251,19 @@ class _SignupScreenState extends State { ) : TextButton( onPressed: () { - final username = - _usernameController.text.trim(); + final username = _usernameController.text.trim(); - final isEmail = - emailValidator.hasMatch(username); + final isEmail = emailValidator.hasMatch(username); - final isPhoneNumber = - phoneNumberValidator.hasMatch(username); + final isPhoneNumber = phoneNumberValidator.hasMatch(username); if (_usernameController.text.trim() == '') { - showErrorMessage(AppLocale.accountRequired - .getString(context)); + showErrorMessage(AppLocale.accountRequired.getString(context)); return; } if (!isEmail && !isPhoneNumber) { - showErrorMessage(AppLocale - .accountFormatError - .getString(context)); + showErrorMessage(AppLocale.accountFormatError.getString(context)); return; } @@ -309,8 +283,7 @@ class _SignupScreenState extends State { timer = null; } - timer = Timer.periodic( - const Duration(seconds: 1), (timer) { + timer = Timer.periodic(const Duration(seconds: 1), (timer) { if (verifyCodeWaitSeconds <= 0) { timer.cancel(); return; @@ -329,8 +302,7 @@ class _SignupScreenState extends State { timer?.cancel(); }); - showErrorMessage( - resolveError(context, error!)); + showErrorMessage(resolveError(context, error!)); }); }, child: Text( @@ -350,16 +322,12 @@ class _SignupScreenState extends State { height: 50, width: double.infinity, margin: const EdgeInsets.symmetric(horizontal: 15), - decoration: BoxDecoration( - color: customColors.linkColor, - borderRadius: BorderRadius.circular(8), - ), + decoration: BoxDecoration(color: customColors.linkColor, borderRadius: CustomSize.borderRadius), child: TextButton( onPressed: onCreateSubmit, child: Text( AppLocale.createAccount.getString(context), - style: - const TextStyle(color: Colors.white, fontSize: 20), + style: const TextStyle(color: Colors.white, fontSize: 20), ), ), ), @@ -375,8 +343,7 @@ class _SignupScreenState extends State { if (context.canPop()) { context.pop(_usernameController.text.trim()); } else { - context.go( - '/login?username=${_usernameController.text.trim()}'); + context.go('/login?username=${_usernameController.text.trim()}'); } }, child: Text( @@ -402,8 +369,7 @@ class _SignupScreenState extends State { ); } - Row _buildUserTermsAndPrivicy( - CustomColors customColors, BuildContext context) { + Row _buildUserTermsAndPrivicy(CustomColors customColors, BuildContext context) { return Row( mainAxisAlignment: MainAxisAlignment.center, children: [ @@ -448,8 +414,7 @@ class _SignupScreenState extends State { ), recognizer: TapGestureRecognizer() ..onTap = () { - launchUrl( - Uri.parse('$apiServerURL/public/info/terms-of-user')); + launchUrl(Uri.parse('$apiServerURL/public/info/terms-of-user')); }, ), TextSpan( @@ -461,12 +426,10 @@ class _SignupScreenState extends State { ), TextSpan( text: '《${AppLocale.privacyPolicy.getString(context)}》', - style: - TextStyle(color: customColors.weakLinkColor, fontSize: 13), + style: TextStyle(color: customColors.weakLinkColor, fontSize: 13), recognizer: TapGestureRecognizer() ..onTap = () { - launchUrl( - Uri.parse('$apiServerURL/public/info/privacy-policy')); + launchUrl(Uri.parse('$apiServerURL/public/info/privacy-policy')); }, ), ], @@ -485,8 +448,7 @@ class _SignupScreenState extends State { return; } - if (!emailValidator.hasMatch(username) && - !phoneNumberValidator.hasMatch(username)) { + if (!emailValidator.hasMatch(username) && !phoneNumberValidator.hasMatch(username)) { showErrorMessage(AppLocale.accountFormatError.getString(context)); return; } diff --git a/lib/page/balance/free_statistics.dart b/lib/page/balance/free_statistics.dart index 41c28282..4f3220dc 100644 --- a/lib/page/balance/free_statistics.dart +++ b/lib/page/balance/free_statistics.dart @@ -43,7 +43,7 @@ class _FreeStatisticsPageState extends State { ), centerTitle: true, ), - backgroundColor: customColors.backgroundContainerColor, + backgroundColor: customColors.backgroundColor, body: BackgroundContainer( setting: widget.setting, enabled: false, diff --git a/lib/page/balance/payment.dart b/lib/page/balance/payment.dart index c249390f..097f0d5d 100644 --- a/lib/page/balance/payment.dart +++ b/lib/page/balance/payment.dart @@ -31,8 +31,7 @@ import 'package:tobias/tobias.dart'; import 'package:url_launcher/url_launcher_string.dart'; import 'package:fluwx/fluwx.dart' as fluwx; -import 'web/payment_element.dart' - if (dart.library.js) 'web/payment_element_web.dart'; +import 'web/payment_element.dart' if (dart.library.js) 'web/payment_element_web.dart'; class PaymentScreen extends StatefulWidget { final SettingRepository setting; @@ -97,10 +96,8 @@ class _PaymentScreenState extends State { await APIServer().updateApplePay( paymentId!, productId: purchaseDetails.productID, - localVerifyData: - purchaseDetails.verificationData.localVerificationData, - serverVerifyData: - purchaseDetails.verificationData.serverVerificationData, + localVerifyData: purchaseDetails.verificationData.localVerificationData, + serverVerifyData: purchaseDetails.verificationData.serverVerificationData, verifyDataSource: purchaseDetails.verificationData.source, ); @@ -125,10 +122,8 @@ class _PaymentScreenState extends State { productId: purchaseDetails.productID, purchaseId: purchaseDetails.purchaseID, transactionDate: purchaseDetails.transactionDate, - localVerifyData: - purchaseDetails.verificationData.localVerificationData, - serverVerifyData: - purchaseDetails.verificationData.serverVerificationData, + localVerifyData: purchaseDetails.verificationData.localVerificationData, + serverVerifyData: purchaseDetails.verificationData.serverVerificationData, verifyDataSource: purchaseDetails.verificationData.source, status: purchaseDetails.status.toString(), ) @@ -228,7 +223,7 @@ class _PaymentScreenState extends State { ), ], ), - backgroundColor: customColors.backgroundContainerColor, + backgroundColor: customColors.backgroundColor, body: BackgroundContainer( setting: widget.setting, enabled: false, @@ -244,21 +239,17 @@ class _PaymentScreenState extends State { if (state.localProducts.isEmpty) { showErrorMessage('暂无可购买的产品'); } else { - final recommends = state.localProducts - .where((e) => e.recommend) - .toList(); + final recommends = state.localProducts.where((e) => e.recommend).toList(); if (recommends.isNotEmpty && !state.loading) { setState(() { - selectedProduct = state.products - .firstWhere((e) => e.id == recommends.first.id); + selectedProduct = state.products.firstWhere((e) => e.id == recommends.first.id); }); } } } } }, - buildWhen: (previous, current) => - current is PaymentAppleProductsLoaded, + buildWhen: (previous, current) => current is PaymentAppleProductsLoaded, builder: (context, state) { if (state is! PaymentAppleProductsLoaded) { return const Center(child: LoadingIndicator()); @@ -288,8 +279,7 @@ class _PaymentScreenState extends State { customColors: customColors, detail: item, selectedProduct: selectedProduct, - product: state.localProducts - .firstWhere((e) => e.id == item.id), + product: state.localProducts.firstWhere((e) => e.id == item.id), loading: state.loading, ), ), @@ -300,10 +290,7 @@ class _PaymentScreenState extends State { Padding( padding: const EdgeInsets.symmetric(horizontal: 15), child: Text( - state.localProducts - .where((e) => e.id == selectedProduct!.id) - .first - .description!, + state.localProducts.where((e) => e.id == selectedProduct!.id).first.description!, style: TextStyle( fontSize: 12, color: customColors.weakTextColor, @@ -314,8 +301,7 @@ class _PaymentScreenState extends State { Padding( padding: const EdgeInsets.symmetric(horizontal: 10), child: EnhancedButton( - title: - '${AppLocale.toPay.getString(context)} ${selectedProduct?.price ?? ''}', + title: '${AppLocale.toPay.getString(context)} ${selectedProduct?.price ?? ''}', onPressed: () async { if (state.loading) { showErrorMessage('价格加载中,请稍后'); @@ -378,15 +364,13 @@ class _PaymentScreenState extends State { ' 购买说明:', style: TextStyle( fontSize: 12, - color: customColors.paymentItemTitleColor - ?.withOpacity(0.5), + color: customColors.paymentItemTitleColor?.withOpacity(0.5), ), ), Markdown( data: state.note!, textStyle: TextStyle( - color: customColors.paymentItemTitleColor - ?.withOpacity(0.5), + color: customColors.paymentItemTitleColor?.withOpacity(0.5), fontSize: 12, ), ), @@ -403,8 +387,7 @@ class _PaymentScreenState extends State { ); } - void handlePaymentForWeb(PaymentAppleProductsLoaded state, - BuildContext context, CustomColors customColors) { + void handlePaymentForWeb(PaymentAppleProductsLoaded state, BuildContext context, CustomColors customColors) { // openConfirmDialog( // context, // '当前终端在线支付暂不可用,预计最晚 2023 年 10 月 15 日恢复,如需充值,请使用移动端 APP(支持 Android 手机、Apple 手机)。', @@ -417,8 +400,7 @@ class _PaymentScreenState extends State { // confirmText: '前往下载移动端 APP', // ) - final localProduct = - state.localProducts.firstWhere((e) => e.id == selectedProduct!.id); + final localProduct = state.localProducts.firstWhere((e) => e.id == selectedProduct!.id); final enableStripe = Ability().enableStripe && localProduct.supportStripe; @@ -458,8 +440,7 @@ class _PaymentScreenState extends State { Text( '(${localProduct.retailPriceUSDText})', style: TextStyle( - color: - customColors.paymentItemTitleColor?.withOpacity(0.5), + color: customColors.paymentItemTitleColor?.withOpacity(0.5), fontSize: 12, ), ), @@ -477,8 +458,7 @@ class _PaymentScreenState extends State { } else if (value.value == 'wechat-pay') { createWechatPayment(localProduct); } else { - createWebOrWapAlipay(source: value.value) - .onError((error, stackTrace) { + createWebOrWapAlipay(source: value.value).onError((error, stackTrace) { _closePaymentLoading(); showErrorMessageEnhanced(context, error!); }); @@ -497,8 +477,7 @@ class _PaymentScreenState extends State { BuildContext context, CustomColors customColors, ) async { - final localProduct = - state.localProducts.firstWhere((e) => e.id == selectedProduct!.id); + final localProduct = state.localProducts.firstWhere((e) => e.id == selectedProduct!.id); final enableStripe = Ability().enableStripe && localProduct.supportStripe; openListSelectDialog( context, @@ -530,8 +509,7 @@ class _PaymentScreenState extends State { Text( '(${localProduct.retailPriceUSDText})', style: TextStyle( - color: - customColors.paymentItemTitleColor?.withOpacity(0.5), + color: customColors.paymentItemTitleColor?.withOpacity(0.5), fontSize: 12, ), ), @@ -568,8 +546,7 @@ class _PaymentScreenState extends State { BuildContext context, CustomColors customColors, ) { - final localProduct = - state.localProducts.firstWhere((e) => e.id == selectedProduct!.id); + final localProduct = state.localProducts.firstWhere((e) => e.id == selectedProduct!.id); final enableStripe = Ability().enableStripe && localProduct.supportStripe; openListSelectDialog( context, @@ -601,8 +578,7 @@ class _PaymentScreenState extends State { Text( '(${localProduct.retailPriceUSDText})', style: TextStyle( - color: - customColors.paymentItemTitleColor?.withOpacity(0.5), + color: customColors.paymentItemTitleColor?.withOpacity(0.5), fontSize: 12, ), ), @@ -661,8 +637,7 @@ class _PaymentScreenState extends State { () async { _startPaymentLoading(); try { - final resp = - await APIServer().queryPaymentStatus(created.paymentId); + final resp = await APIServer().queryPaymentStatus(created.paymentId); if (resp.success) { showSuccessMessage(resp.note ?? '支付成功'); _closePaymentLoading(); @@ -670,8 +645,7 @@ class _PaymentScreenState extends State { // 支付失败,延迟 5s 再次查询支付状态 await Future.delayed(const Duration(seconds: 5), () async { try { - final value = - await APIServer().queryPaymentStatus(created.paymentId); + final value = await APIServer().queryPaymentStatus(created.paymentId); if (value.success) { showSuccessMessage(value.note ?? '支付成功'); @@ -740,7 +714,7 @@ class _PaymentScreenState extends State { child: Column( children: [ ClipRRect( - borderRadius: BorderRadius.circular(8), + borderRadius: CustomSize.borderRadius, child: QrImageView( data: created.codeUrl!, version: QrVersions.auto, @@ -769,8 +743,7 @@ class _PaymentScreenState extends State { // 支付失败,延迟 5s 再次查询支付状态 Future.delayed(const Duration(seconds: 5), () async { try { - final value = - await APIServer().queryPaymentStatus(created.paymentId); + final value = await APIServer().queryPaymentStatus(created.paymentId); if (value.success) { showSuccessMessage(value.note ?? '支付成功'); @@ -810,9 +783,7 @@ class _PaymentScreenState extends State { ); paymentId = created.paymentId; - if (PlatformTool.isWeb() || - PlatformTool.isAndroid() || - PlatformTool.isIOS()) { + if (PlatformTool.isWeb() || PlatformTool.isAndroid() || PlatformTool.isIOS()) { Stripe.publishableKey = created.publishableKey; Stripe.urlScheme = 'flutterstripe'; @@ -851,8 +822,7 @@ class _PaymentScreenState extends State { final cancel = BotToast.showCustomLoading( toastBuilder: (cancel) { return LoadingIndicator( - message: AppLocale.processingWait - .getString(context), + message: AppLocale.processingWait.getString(context), ); }, allowClick: false, @@ -888,9 +858,7 @@ class _PaymentScreenState extends State { customerEphemeralKeySecret: created.ephemeralKey, returnURL: 'flutterstripe://redirect', // ignore: use_build_context_synchronously - style: Ability().themeMode == 'dark' - ? ThemeMode.dark - : ThemeMode.light, + style: Ability().themeMode == 'dark' ? ThemeMode.dark : ThemeMode.light, ), ); @@ -918,8 +886,7 @@ class _PaymentScreenState extends State { () async { _startPaymentLoading(); try { - final resp = - await APIServer().queryPaymentStatus(created.paymentId); + final resp = await APIServer().queryPaymentStatus(created.paymentId); if (resp.success) { showSuccessMessage(resp.note ?? '支付成功'); _closePaymentLoading(); @@ -927,8 +894,7 @@ class _PaymentScreenState extends State { // 支付失败,延迟 5s 再次查询支付状态 await Future.delayed(const Duration(seconds: 5), () async { try { - final value = await APIServer() - .queryPaymentStatus(created.paymentId); + final value = await APIServer().queryPaymentStatus(created.paymentId); if (value.success) { showSuccessMessage(value.note ?? '支付成功'); @@ -1032,7 +998,7 @@ class PaymentMethodItem extends StatelessWidget { children: [ if (image != null) ...[ ClipRRect( - borderRadius: BorderRadius.circular(5), + borderRadius: CustomSize.borderRadius, child: Image.asset( image!, width: 20, diff --git a/lib/page/balance/payment_history.dart b/lib/page/balance/payment_history.dart index ab7f2b78..20fe0dda 100644 --- a/lib/page/balance/payment_history.dart +++ b/lib/page/balance/payment_history.dart @@ -34,7 +34,7 @@ class _PaymentHistoryScreenState extends State { centerTitle: true, elevation: 0, ), - backgroundColor: customColors.backgroundContainerColor, + backgroundColor: customColors.backgroundColor, body: BackgroundContainer( setting: widget.setting, enabled: false, @@ -69,8 +69,7 @@ class _PaymentHistoryScreenState extends State { ); } - return _buildQuotaDetailPage( - context, snapshot.data!, customColors); + return _buildQuotaDetailPage(context, snapshot.data!, customColors); }, ), ), @@ -78,8 +77,7 @@ class _PaymentHistoryScreenState extends State { ); } - Widget _buildQuotaDetailPage( - BuildContext context, QuotaResp quota, CustomColors customColors) { + Widget _buildQuotaDetailPage(BuildContext context, QuotaResp quota, CustomColors customColors) { return Column( children: [ Expanded( @@ -98,8 +96,8 @@ class _PaymentHistoryScreenState extends State { right: 16, ), decoration: BoxDecoration( - color: customColors.paymentItemBackgroundColor, - borderRadius: BorderRadius.circular(10), + color: customColors.backgroundContainerColor, + borderRadius: CustomSize.borderRadius, ), child: Column( mainAxisSize: MainAxisSize.min, @@ -112,9 +110,7 @@ class _PaymentHistoryScreenState extends State { crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( - (item.note == null || item.note == '') - ? '购买' - : item.note!, + (item.note == null || item.note == '') ? '购买' : item.note!, overflow: TextOverflow.ellipsis, ), const SizedBox(height: 5), @@ -179,10 +175,7 @@ class _PaymentHistoryScreenState extends State { child: Container( decoration: BoxDecoration( color: color, - borderRadius: const BorderRadius.only( - topRight: Radius.circular(9), - bottomLeft: Radius.circular(9), - ), + borderRadius: const BorderRadius.only(topRight: CustomSize.radius, bottomLeft: CustomSize.radius), ), padding: const EdgeInsets.symmetric( horizontal: 5, diff --git a/lib/page/balance/price_block.dart b/lib/page/balance/price_block.dart index fefe4f03..05c7820b 100644 --- a/lib/page/balance/price_block.dart +++ b/lib/page/balance/price_block.dart @@ -1,4 +1,5 @@ import 'package:askaide/page/component/coin.dart'; +import 'package:askaide/page/component/theme/custom_size.dart'; import 'package:askaide/page/component/theme/custom_theme.dart'; import 'package:askaide/repo/api/payment.dart'; import 'package:flutter/material.dart'; @@ -30,14 +31,13 @@ class PriceBlock extends StatelessWidget { padding: const EdgeInsets.all(20), alignment: Alignment.center, decoration: BoxDecoration( - color: customColors.paymentItemBackgroundColor, + color: customColors.backgroundContainerColor, border: Border.all( - color: - (selectedProduct != null && selectedProduct!.id == detail.id) - ? customColors.linkColor ?? Colors.green - : customColors.paymentItemBackgroundColor!, + color: (selectedProduct != null && selectedProduct!.id == detail.id) + ? customColors.linkColor ?? Colors.green + : customColors.paymentItemBackgroundColor!, ), - borderRadius: BorderRadius.circular(10), + borderRadius: CustomSize.borderRadius, ), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, @@ -90,10 +90,7 @@ class PriceBlock extends StatelessWidget { child: Container( decoration: const BoxDecoration( color: Color.fromARGB(255, 224, 68, 7), - borderRadius: BorderRadius.only( - topRight: Radius.circular(8), - bottomLeft: Radius.circular(8), - ), + borderRadius: BorderRadius.only(topRight: CustomSize.radius, bottomLeft: CustomSize.radius), ), padding: const EdgeInsets.symmetric(horizontal: 5, vertical: 2), child: const Text( diff --git a/lib/page/balance/quota_usage_details.dart b/lib/page/balance/quota_usage_details.dart index 3112f2e8..82d9606d 100644 --- a/lib/page/balance/quota_usage_details.dart +++ b/lib/page/balance/quota_usage_details.dart @@ -51,7 +51,7 @@ class _QuotaUsageDetailScreenState extends State { centerTitle: true, elevation: 0, ), - backgroundColor: customColors.backgroundContainerColor, + backgroundColor: customColors.backgroundColor, body: BackgroundContainer( setting: widget.setting, enabled: false, @@ -103,14 +103,13 @@ class _QuotaUsageDetailScreenState extends State { margin: const EdgeInsets.symmetric(vertical: 6), padding: const EdgeInsets.all(16), decoration: BoxDecoration( - color: customColors.paymentItemBackgroundColor, - borderRadius: BorderRadius.circular(10), + color: customColors.backgroundContainerColor, + borderRadius: CustomSize.borderRadius, ), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - Text(item.createdAt, - style: const TextStyle(fontWeight: FontWeight.bold)), + Text(item.createdAt, style: const TextStyle(fontWeight: FontWeight.bold)), const SizedBox(width: 20), Expanded( child: Text('使用 ${item.type} 消耗 ${item.used} 个智慧果'), diff --git a/lib/page/balance/quota_usage_statistics.dart b/lib/page/balance/quota_usage_statistics.dart index 6ada45fb..aa8cbaa6 100644 --- a/lib/page/balance/quota_usage_statistics.dart +++ b/lib/page/balance/quota_usage_statistics.dart @@ -14,12 +14,10 @@ class QuotaUsageStatisticsScreen extends StatefulWidget { const QuotaUsageStatisticsScreen({super.key, required this.setting}); @override - State createState() => - _QuotaUsageStatisticsScreenState(); + State createState() => _QuotaUsageStatisticsScreenState(); } -class _QuotaUsageStatisticsScreenState - extends State { +class _QuotaUsageStatisticsScreenState extends State { List usages = []; bool loaded = false; @@ -49,7 +47,7 @@ class _QuotaUsageStatisticsScreenState centerTitle: true, elevation: 0, ), - backgroundColor: customColors.backgroundContainerColor, + backgroundColor: customColors.backgroundColor, body: BackgroundContainer( setting: widget.setting, enabled: false, @@ -112,13 +110,12 @@ class _QuotaUsageStatisticsScreenState margin: const EdgeInsets.symmetric(vertical: 6), padding: const EdgeInsets.all(16), decoration: BoxDecoration( - color: customColors.paymentItemBackgroundColor, - borderRadius: BorderRadius.circular(10), + color: customColors.backgroundContainerColor, + borderRadius: CustomSize.borderRadius, ), child: InkWell( onTap: () { - context - .push('/quota-usage-daily-details?date=${item.date}'); + context.push('/quota-usage-daily-details?date=${item.date}'); }, child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, @@ -129,10 +126,7 @@ class _QuotaUsageStatisticsScreenState fontWeight: FontWeight.bold, ), ), - if (item.used == -1) - const Text('未出账') - else - Text('${item.used > 0 ? "-" : ""}${item.used}'), + if (item.used == -1) const Text('未出账') else Text('${item.used > 0 ? "-" : ""}${item.used}'), ], ), ), diff --git a/lib/page/chat/component/group_empty.dart b/lib/page/chat/component/group_empty.dart index 6be860a5..bbf15219 100644 --- a/lib/page/chat/component/group_empty.dart +++ b/lib/page/chat/component/group_empty.dart @@ -1,3 +1,4 @@ +import 'package:askaide/page/component/theme/custom_size.dart'; import 'package:askaide/page/component/theme/custom_theme.dart'; import 'package:flutter/material.dart'; @@ -15,10 +16,9 @@ class GroupEmptyBoard extends StatelessWidget { Container( decoration: BoxDecoration( color: customColors.backgroundColor?.withAlpha(200), - borderRadius: BorderRadius.circular(10), + borderRadius: CustomSize.borderRadius, ), - padding: - const EdgeInsets.only(top: 20, left: 15, right: 10, bottom: 3), + padding: const EdgeInsets.only(top: 20, left: 15, right: 10, bottom: 3), width: _resolveTipWidth(context), child: Column( mainAxisSize: MainAxisSize.min, @@ -26,8 +26,7 @@ class GroupEmptyBoard extends StatelessWidget { children: [ Row( children: [ - Image.asset('assets/app-256-transparent.png', - width: 20, height: 20), + Image.asset('assets/app-256-transparent.png', width: 20, height: 20), const SizedBox(width: 5), const Text( '小提示', diff --git a/lib/page/chat/component/room_item.dart b/lib/page/chat/component/room_item.dart index 59cf77ea..1b3b8c02 100644 --- a/lib/page/chat/component/room_item.dart +++ b/lib/page/chat/component/room_item.dart @@ -8,6 +8,7 @@ import 'package:askaide/lang/lang.dart'; import 'package:askaide/page/chat/component/group_avatar.dart'; import 'package:askaide/page/component/image.dart'; import 'package:askaide/page/component/dialog.dart'; +import 'package:askaide/page/component/theme/custom_size.dart'; import 'package:askaide/page/component/theme/custom_theme.dart'; import 'package:askaide/repo/model/room.dart'; import 'package:flutter/material.dart'; @@ -29,9 +30,7 @@ class RoomItem extends StatelessWidget { horizontal: 10, vertical: 5, ), - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(customColors.borderRadius ?? 8), - ), + decoration: BoxDecoration(borderRadius: CustomSize.borderRadius), child: Slidable( endActionPane: ActionPane( motion: const ScrollMotion(), @@ -41,11 +40,8 @@ class RoomItem extends StatelessWidget { label: AppLocale.settings.getString(context), backgroundColor: Colors.green, borderRadius: room.category == 'system' - ? BorderRadius.all(Radius.circular(customColors.borderRadius ?? 8)) - : BorderRadius.only( - topLeft: Radius.circular(customColors.borderRadius ?? 8), - bottomLeft: Radius.circular(customColors.borderRadius ?? 8), - ), + ? CustomSize.borderRadiusAll + : const BorderRadius.only(topLeft: CustomSize.radius, bottomLeft: CustomSize.radius), icon: Icons.settings, onPressed: (_) { final chatRoomBloc = context.read(); @@ -59,10 +55,7 @@ class RoomItem extends StatelessWidget { if (room.category != 'system') SlidableAction( label: AppLocale.delete.getString(context), - borderRadius: BorderRadius.only( - topRight: Radius.circular(customColors.borderRadius ?? 8), - bottomRight: Radius.circular(customColors.borderRadius ?? 8), - ), + borderRadius: const BorderRadius.only(topRight: CustomSize.radius, bottomRight: CustomSize.radius), backgroundColor: Colors.red, icon: Icons.delete, onPressed: (_) { @@ -77,10 +70,10 @@ class RoomItem extends StatelessWidget { ], ), child: Material( - borderRadius: BorderRadius.all(Radius.circular(customColors.borderRadius ?? 8)), - color: customColors.columnBlockBackgroundColor, + borderRadius: CustomSize.borderRadius, + color: customColors.backgroundContainerColor, child: InkWell( - borderRadius: BorderRadius.all(Radius.circular(customColors.borderRadius ?? 8)), + borderRadius: CustomSize.borderRadiusAll, onTap: () { final redirectRoute = room.roomType == 4 ? '/group-chat/${room.id}/chat' : '/room/${room.id}/chat'; HapticFeedbackHelper.lightImpact(); @@ -134,10 +127,8 @@ class RoomItem extends StatelessWidget { child: Container( decoration: BoxDecoration( color: customColors.backgroundContainerColor, - borderRadius: const BorderRadius.only( - topRight: Radius.circular(8), - bottomLeft: Radius.circular(8), - ), + borderRadius: + const BorderRadius.only(topRight: CustomSize.radius, bottomLeft: CustomSize.radius), ), padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 2), child: Text( @@ -156,10 +147,8 @@ class RoomItem extends StatelessWidget { child: Container( decoration: BoxDecoration( color: customColors.backgroundContainerColor, - borderRadius: const BorderRadius.only( - topRight: Radius.circular(8), - bottomLeft: Radius.circular(8), - ), + borderRadius: + const BorderRadius.only(topRight: CustomSize.radius, bottomLeft: CustomSize.radius), ), padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 2), child: Text( @@ -227,10 +216,7 @@ class RoomItem extends StatelessWidget { width: 70, height: 70, child: ClipRRect( - borderRadius: const BorderRadius.only( - topLeft: Radius.circular(8), - bottomLeft: Radius.circular(8), - ), + borderRadius: const BorderRadius.only(topLeft: CustomSize.radius, bottomLeft: CustomSize.radius), child: CachedNetworkImageEnhanced( imageUrl: imageURL(room.avatarUrl!, qiniuImageTypeAvatar), fit: BoxFit.fill, @@ -241,10 +227,7 @@ class RoomItem extends StatelessWidget { if (room.members.isNotEmpty) { return ClipRRect( - borderRadius: const BorderRadius.only( - topLeft: Radius.circular(8), - bottomLeft: Radius.circular(8), - ), + borderRadius: const BorderRadius.only(topLeft: CustomSize.radius, bottomLeft: CustomSize.radius), child: GroupAvatar( size: 70, avatars: room.members, @@ -256,10 +239,7 @@ class RoomItem extends StatelessWidget { text: room.name.split('、').join(' '), size: 70, backgroundColor: Colors.grey.withAlpha(100), - borderRadius: const BorderRadius.only( - topLeft: Radius.circular(8), - bottomLeft: Radius.circular(8), - ), + borderRadius: const BorderRadius.only(topLeft: CustomSize.radius, bottomLeft: CustomSize.radius), ); } } diff --git a/lib/page/chat/group/chat.dart b/lib/page/chat/group/chat.dart index 142ac3d1..1b9de30b 100644 --- a/lib/page/chat/group/chat.dart +++ b/lib/page/chat/group/chat.dart @@ -162,10 +162,7 @@ class _GroupChatPageState extends State { // 聊天输入窗口 Container( decoration: BoxDecoration( - borderRadius: const BorderRadius.only( - topLeft: Radius.circular(10), - topRight: Radius.circular(10), - ), + borderRadius: const BorderRadius.only(topLeft: CustomSize.radius, topRight: CustomSize.radius), color: customColors.chatInputPanelBackground, ), child: SafeArea( @@ -563,10 +560,7 @@ class _GroupChatPageState extends State { return Container( padding: const EdgeInsets.all(10), decoration: BoxDecoration( - borderRadius: const BorderRadius.only( - topLeft: Radius.circular(10), - topRight: Radius.circular(10), - ), + borderRadius: const BorderRadius.only(topLeft: CustomSize.radius, topRight: CustomSize.radius), color: customColors.backgroundColor, ), child: Row( diff --git a/lib/page/chat/group/create.dart b/lib/page/chat/group/create.dart index 0fbe5a15..aacc7965 100644 --- a/lib/page/chat/group/create.dart +++ b/lib/page/chat/group/create.dart @@ -68,9 +68,7 @@ class _GroupCreatePageState extends State { fontSize: 14, title: AppLocale.ok.getString(context), color: selectedModels.isEmpty ? customColors.weakTextColor : null, - backgroundColor: selectedModels.isEmpty - ? customColors.weakTextColor!.withAlpha(20) - : null, + backgroundColor: selectedModels.isEmpty ? customColors.weakTextColor!.withAlpha(20) : null, onPressed: () { onSave(context); }, @@ -78,13 +76,12 @@ class _GroupCreatePageState extends State { ), ], ), - backgroundColor: customColors.backgroundContainerColor, + backgroundColor: customColors.backgroundColor, body: BackgroundContainer( setting: widget.setting, enabled: false, child: BlocListener( - listenWhen: (previous, current) => - current is GroupRoomUpdateResultState, + listenWhen: (previous, current) => current is GroupRoomUpdateResultState, listener: (context, state) { if (state is GroupRoomUpdateResultState) { globalLoadingCancel?.call(); @@ -92,8 +89,7 @@ class _GroupCreatePageState extends State { showSuccessMessage(AppLocale.operateSuccess.getString(context)); context.pop(); } else { - showErrorMessageEnhanced(context, - state.error ?? AppLocale.operateFailed.getString(context)); + showErrorMessageEnhanced(context, state.error ?? AppLocale.operateFailed.getString(context)); } } }, @@ -155,8 +151,7 @@ class _GroupCreatePageState extends State { separatorBuilder: (BuildContext context, int index) { return Divider( height: 1, - color: - customColors.columnBlockDividerColor?.withAlpha(200), + color: customColors.columnBlockDividerColor?.withAlpha(200), ); }, ), @@ -188,10 +183,8 @@ class _GroupCreatePageState extends State { context.read().add( GroupRoomCreateEvent( name: selectedModels.map((e) => e.shortName).take(3).join("、"), - members: selectedModels - .map((e) => GroupMember( - modelId: e.realModelId, modelName: e.shortName)) - .toList(), + members: + selectedModels.map((e) => GroupMember(modelId: e.realModelId, modelName: e.shortName)).toList(), ), ); } diff --git a/lib/page/chat/group/edit.dart b/lib/page/chat/group/edit.dart index 978760fd..dc9fb275 100644 --- a/lib/page/chat/group/edit.dart +++ b/lib/page/chat/group/edit.dart @@ -113,7 +113,7 @@ class _GroupEditPageState extends State { elevation: 0, toolbarHeight: CustomSize.toolbarHeight, ), - backgroundColor: customColors.backgroundContainerColor, + backgroundColor: customColors.backgroundColor, body: BackgroundContainer( setting: widget.setting, enabled: false, @@ -194,7 +194,7 @@ class _GroupEditPageState extends State { width: 45, height: 45, decoration: BoxDecoration( - borderRadius: BorderRadius.circular(8), + borderRadius: CustomSize.borderRadius, image: _avatarUrl == null ? null : DecorationImage( @@ -260,9 +260,7 @@ class _GroupEditPageState extends State { Container( width: resolveSelectedModelsPreviewWidth(context), height: 45, - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(8), - ), + decoration: BoxDecoration(borderRadius: CustomSize.borderRadius), alignment: Alignment.center, clipBehavior: Clip.hardEdge, child: buildSelectedModelsPreview(), @@ -275,7 +273,7 @@ class _GroupEditPageState extends State { padding: const EdgeInsets.all(3), decoration: BoxDecoration( color: customColors.tagsBackground, - borderRadius: BorderRadius.circular(8), + borderRadius: CustomSize.borderRadius, ), child: Text( 'x${selectedModels.length}', diff --git a/lib/page/chat/home.dart b/lib/page/chat/home.dart index ccceaadf..ba422126 100644 --- a/lib/page/chat/home.dart +++ b/lib/page/chat/home.dart @@ -179,7 +179,8 @@ class _HomePageState extends State { showBeautyDialog( context, type: QuickAlertType.info, - text: '恭喜您,账号创建成功!${(widget.reward != null && widget.reward! > 0) ? '\n\n为了庆祝这一时刻,我们向您的账户赠送了 ${widget.reward} 个智慧果。' : ''}', + text: + '恭喜您,账号创建成功!${(widget.reward != null && widget.reward! > 0) ? '\n\n为了庆祝这一时刻,我们向您的账户赠送了 ${widget.reward} 个智慧果。' : ''}', confirmBtnText: '开始使用', onConfirmBtnTap: () { context.pop(); @@ -399,11 +400,11 @@ class _HomePageState extends State { innerPadding: const EdgeInsets.all(0), decoration: BoxDecoration( color: customColors.columnBlockBackgroundColor?.withAlpha(150), - borderRadius: BorderRadius.circular(8), + borderRadius: CustomSize.borderRadius, ), thumbDecoration: BoxDecoration( color: currentModel?.iconAndColor.color, - borderRadius: BorderRadius.circular(6), + borderRadius: CustomSize.borderRadius, ), duration: const Duration(milliseconds: 300), curve: Curves.easeInToLinear, @@ -496,7 +497,7 @@ class _HomePageState extends State { child: Stack( children: [ ClipRRect( - borderRadius: BorderRadius.circular(5), + borderRadius: CustomSize.borderRadius, child: e.file.bytes != null ? Image.memory( e.file.bytes!, @@ -523,7 +524,7 @@ class _HomePageState extends State { child: Container( padding: const EdgeInsets.all(3), decoration: BoxDecoration( - borderRadius: BorderRadius.circular(10), + borderRadius: CustomSize.borderRadius, color: customColors.chatRoomBackground, ), child: Icon( @@ -549,9 +550,7 @@ class _HomePageState extends State { // 问题示例 if (state.examples != null && state.examples!.isNotEmpty && state.histories.isEmpty) Container( - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(10), - ), + decoration: BoxDecoration(borderRadius: CustomSize.borderRadius), padding: const EdgeInsets.only(top: 5, left: 10, right: 10, bottom: 3), margin: const EdgeInsets.all(10), height: 260, @@ -852,9 +851,7 @@ class ChatHistoryItem extends StatelessWidget { horizontal: 15, vertical: 5, ), - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(10), - ), + decoration: BoxDecoration(borderRadius: CustomSize.borderRadius), child: Slidable( endActionPane: ActionPane( motion: const ScrollMotion(), @@ -862,7 +859,7 @@ class ChatHistoryItem extends StatelessWidget { const SizedBox(width: 10), SlidableAction( label: AppLocale.delete.getString(context), - borderRadius: BorderRadius.circular(10), + borderRadius: CustomSize.borderRadiusAll, backgroundColor: Colors.red, icon: Icons.delete, onPressed: (_) { @@ -879,16 +876,12 @@ class ChatHistoryItem extends StatelessWidget { ], ), child: Material( - color: customColors.backgroundColor?.withAlpha(200), - borderRadius: BorderRadius.all( - Radius.circular(customColors.borderRadius ?? 8), - ), + color: customColors.backgroundContainerColor, + borderRadius: CustomSize.borderRadius, child: InkWell( child: ListTile( contentPadding: const EdgeInsets.symmetric(horizontal: 10, vertical: 5), - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(customColors.borderRadius ?? 8), - ), + shape: RoundedRectangleBorder(borderRadius: CustomSize.borderRadius), title: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ diff --git a/lib/page/chat/home_chat.dart b/lib/page/chat/home_chat.dart index a8e0e173..174ca1c3 100644 --- a/lib/page/chat/home_chat.dart +++ b/lib/page/chat/home_chat.dart @@ -312,23 +312,23 @@ class _HomeChatPageState extends State { return const SizedBox(); }, ), - flexibleSpace: SizedBox( - width: double.infinity, - child: ShaderMask( - shaderCallback: (rect) { - return const LinearGradient( - begin: Alignment.topCenter, - end: Alignment.bottomCenter, - colors: [Colors.black, Colors.transparent], - ).createShader(Rect.fromLTRB(0, 0, rect.width, rect.height)); - }, - blendMode: BlendMode.dstIn, - child: Image.asset( - customColors.appBarBackgroundImage!, - fit: BoxFit.cover, - ), - ), - ), + // flexibleSpace: SizedBox( + // width: double.infinity, + // child: ShaderMask( + // shaderCallback: (rect) { + // return const LinearGradient( + // begin: Alignment.topCenter, + // end: Alignment.bottomCenter, + // colors: [Colors.black, Colors.transparent], + // ).createShader(Rect.fromLTRB(0, 0, rect.width, rect.height)); + // }, + // blendMode: BlendMode.dstIn, + // child: Image.asset( + // customColors.appBarBackgroundImage!, + // fit: BoxFit.cover, + // ), + // ), + // ), ); } @@ -426,10 +426,7 @@ class _HomeChatPageState extends State { if (!chatPreviewController.selectMode) Container( decoration: BoxDecoration( - borderRadius: const BorderRadius.only( - topLeft: Radius.circular(10), - topRight: Radius.circular(10), - ), + borderRadius: const BorderRadius.only(topLeft: CustomSize.radius, topRight: CustomSize.radius), color: customColors.chatInputPanelBackground, ), child: BlocBuilder( @@ -555,7 +552,7 @@ class _HomeChatPageState extends State { return Container( margin: const EdgeInsets.fromLTRB(0, 0, 10, 7), - padding: const EdgeInsets.symmetric(horizontal: 13), + padding: const EdgeInsets.symmetric(horizontal: 10), child: Text( message.senderName!, style: TextStyle( diff --git a/lib/page/chat/home_chat_history.dart b/lib/page/chat/home_chat_history.dart index b3e4bfb9..8bdb59ab 100644 --- a/lib/page/chat/home_chat_history.dart +++ b/lib/page/chat/home_chat_history.dart @@ -50,7 +50,7 @@ class _HomeChatHistoryPageState extends State { toolbarHeight: CustomSize.toolbarHeight, centerTitle: true, ), - backgroundColor: customColors.backgroundContainerColor, + backgroundColor: customColors.backgroundColor, body: BackgroundContainer( setting: widget.setting, enabled: false, diff --git a/lib/page/chat/room_chat.dart b/lib/page/chat/room_chat.dart index a30e6579..8ded8892 100644 --- a/lib/page/chat/room_chat.dart +++ b/lib/page/chat/room_chat.dart @@ -194,10 +194,7 @@ class _RoomChatPageState extends State { // 聊天输入窗口 Container( decoration: BoxDecoration( - borderRadius: const BorderRadius.only( - topLeft: Radius.circular(10), - topRight: Radius.circular(10), - ), + borderRadius: const BorderRadius.only(topLeft: CustomSize.radius, topRight: CustomSize.radius), color: customColors.chatInputPanelBackground, ), child: _chatPreviewController.selectMode @@ -674,7 +671,7 @@ void handleOpenExampleQuestion( vertical: 10, ), decoration: BoxDecoration( - borderRadius: BorderRadius.circular(10), + borderRadius: CustomSize.borderRadius, color: customColors.chatExampleItemBackground, ), child: Column( diff --git a/lib/page/chat/room_create.dart b/lib/page/chat/room_create.dart index aa4657c3..63fc8934 100644 --- a/lib/page/chat/room_create.dart +++ b/lib/page/chat/room_create.dart @@ -102,12 +102,14 @@ class _RoomCreatePageState extends State { AppLocale.createRoom.getString(context), style: const TextStyle(fontSize: CustomSize.appBarTitleSize), ), - backgroundColor: customColors.backgroundContainerColor, + backgroundColor: customColors.backgroundColor, centerTitle: true, toolbarHeight: CustomSize.toolbarHeight, ), + backgroundColor: customColors.backgroundColor, body: BackgroundContainer( setting: widget.setting, + enabled: false, maxWidth: 0, child: Padding( padding: const EdgeInsets.symmetric(horizontal: 10), @@ -295,7 +297,7 @@ class _RoomCreatePageState extends State { width: 45, height: 45, decoration: BoxDecoration( - borderRadius: BorderRadius.circular(8), + borderRadius: CustomSize.borderRadius, image: _avatarUrl == null ? null : DecorationImage( @@ -566,6 +568,7 @@ class _RoomCreatePageState extends State { ), ], ), + const SizedBox(height: 15), ], ), ); diff --git a/lib/page/chat/room_edit.dart b/lib/page/chat/room_edit.dart index 86566657..90731d01 100644 --- a/lib/page/chat/room_edit.dart +++ b/lib/page/chat/room_edit.dart @@ -189,7 +189,7 @@ class _RoomEditPageState extends State { width: 45, height: 45, decoration: BoxDecoration( - borderRadius: BorderRadius.circular(8), + borderRadius: CustomSize.borderRadius, image: _avatarUrl == null ? null : DecorationImage( diff --git a/lib/page/chat/rooms.dart b/lib/page/chat/rooms.dart index 70d9ae05..d7eede1f 100644 --- a/lib/page/chat/rooms.dart +++ b/lib/page/chat/rooms.dart @@ -45,8 +45,9 @@ class _RoomsPageState extends State { var customColors = Theme.of(context).extension()!; return BackgroundContainer( setting: widget.setting, + enabled: false, child: Scaffold( - backgroundColor: Colors.transparent, + backgroundColor: customColors.backgroundColor, body: BlocConsumer( listener: (context, state) { if (state is RoomsLoaded) { @@ -266,18 +267,16 @@ class _RoomsPageState extends State { context.read().add(RoomsLoadEvent()); }); }, - borderRadius: BorderRadius.circular(8), + borderRadius: CustomSize.borderRadiusAll, child: Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center, children: [ Container( - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(10), - ), + decoration: BoxDecoration(borderRadius: CustomSize.borderRadius), child: ClipRRect( - borderRadius: BorderRadius.circular(8), + borderRadius: CustomSize.borderRadius, child: Stack( alignment: Alignment.center, children: [ diff --git a/lib/page/component/attached_button_panel.dart b/lib/page/component/attached_button_panel.dart index 95c16426..dbc49583 100644 --- a/lib/page/component/attached_button_panel.dart +++ b/lib/page/component/attached_button_panel.dart @@ -1,3 +1,4 @@ +import 'package:askaide/page/component/theme/custom_size.dart'; import 'package:flutter/material.dart'; class AttachedButtonPanel extends StatelessWidget { @@ -29,7 +30,7 @@ class AttachedButtonPanel extends StatelessWidget { child: Container( padding: const EdgeInsets.symmetric(horizontal: 5, vertical: 10), decoration: BoxDecoration( - borderRadius: BorderRadius.circular(5), + borderRadius: CustomSize.borderRadius, color: const Color.fromARGB(223, 0, 0, 0), ), child: Column( diff --git a/lib/page/component/avatar_selector.dart b/lib/page/component/avatar_selector.dart index d7d2e22e..31df0b30 100644 --- a/lib/page/component/avatar_selector.dart +++ b/lib/page/component/avatar_selector.dart @@ -4,6 +4,7 @@ import 'package:askaide/helper/haptic_feedback.dart'; import 'package:askaide/lang/lang.dart'; import 'package:askaide/page/component/image.dart'; import 'package:askaide/page/component/random_avatar.dart'; +import 'package:askaide/page/component/theme/custom_size.dart'; import 'package:askaide/page/component/theme/custom_theme.dart'; import 'package:file_picker/file_picker.dart'; import 'package:flutter/material.dart'; @@ -79,7 +80,7 @@ class _AvatarSelectorState extends State { width: 100, height: 100, child: ClipRRect( - borderRadius: BorderRadius.circular(8), + borderRadius: CustomSize.borderRadius, child: _avatarUrl!.startsWith('http') ? CachedNetworkImageEnhanced( imageUrl: _avatarUrl!, @@ -104,7 +105,7 @@ class _AvatarSelectorState extends State { child: Container( padding: const EdgeInsets.all(3), decoration: BoxDecoration( - borderRadius: BorderRadius.circular(10), + borderRadius: CustomSize.borderRadius, color: customColors.chatRoomBackground, ), child: Icon( @@ -119,9 +120,9 @@ class _AvatarSelectorState extends State { ), if (_avatarId != null) RandomAvatar(id: _avatarId ?? 0, size: 80, usage: widget.usage), Material( - borderRadius: BorderRadius.circular(8), + borderRadius: CustomSize.borderRadius, child: InkWell( - borderRadius: BorderRadius.circular(8), + borderRadius: CustomSize.borderRadiusAll, onTap: () async { HapticFeedbackHelper.mediumImpact(); FilePickerResult? result = await FilePicker.platform.pickFiles(type: FileType.image); @@ -139,10 +140,10 @@ class _AvatarSelectorState extends State { }, child: Container( decoration: BoxDecoration( - borderRadius: BorderRadius.circular(8), + borderRadius: CustomSize.borderRadius, ), child: ClipRRect( - borderRadius: BorderRadius.circular(8), + borderRadius: CustomSize.borderRadius, child: SizedBox( height: 100, width: 160, @@ -220,7 +221,7 @@ class _AvatarSelectorState extends State { Widget _buildAvatarButton(CustomColors customColors, Avatar avatar) { return Container( decoration: BoxDecoration( - borderRadius: BorderRadius.circular(11), + borderRadius: CustomSize.borderRadius, border: Border.all( color: (_avatarUrl != null && _avatarUrl == avatar.url) || (_avatarId != null && _avatarId == avatar.id) ? customColors.linkColor ?? Colors.green @@ -240,7 +241,7 @@ class _AvatarSelectorState extends State { child: avatar.type == AvatarType.random ? RandomAvatar(id: avatar.id ?? 0, size: 80, usage: widget.usage) : ClipRRect( - borderRadius: BorderRadius.circular(8), + borderRadius: CustomSize.borderRadius, child: CachedNetworkImageEnhanced( imageUrl: avatar.url!, fit: BoxFit.fill, diff --git a/lib/page/component/bottom_sheet_box.dart b/lib/page/component/bottom_sheet_box.dart index ddc4c446..1117d6b2 100644 --- a/lib/page/component/bottom_sheet_box.dart +++ b/lib/page/component/bottom_sheet_box.dart @@ -1,3 +1,4 @@ +import 'package:askaide/page/component/theme/custom_size.dart'; import 'package:askaide/page/component/theme/custom_theme.dart'; import 'package:flutter/material.dart'; @@ -15,11 +16,8 @@ class BottomSheetBox extends StatelessWidget { child: Container( width: double.infinity, decoration: BoxDecoration( - borderRadius: const BorderRadius.vertical( - top: Radius.circular(10), - bottom: Radius.circular(10), - ), - color: customColors.backgroundColor, + borderRadius: const BorderRadius.vertical(top: CustomSize.radius, bottom: CustomSize.radius), + color: customColors.backgroundContainerColor, ), padding: const EdgeInsets.only(top: 0, left: 10, right: 10), child: child, diff --git a/lib/page/component/chat/chat_bubble.dart b/lib/page/component/chat/chat_bubble.dart index 671d6255..8dea1b51 100644 --- a/lib/page/component/chat/chat_bubble.dart +++ b/lib/page/component/chat/chat_bubble.dart @@ -1,3 +1,4 @@ +import 'package:askaide/page/component/theme/custom_size.dart'; import 'package:flutter/material.dart'; /// 来源于 https://github.com/prahack/chat_bubbles/blob/master/lib/bubbles/bubble_special_one.dart @@ -12,7 +13,7 @@ class SpecialChatBubbleOne extends CustomPainter { required this.tail, }); - final double _radius = 10.0; + final double _radius = CustomSize.radiusValue; final double _x = 5.0; @override @@ -181,9 +182,7 @@ class BubbleSpecialThree extends StatelessWidget { padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 2), child: CustomPaint( painter: SpecialChatBubbleThree( - color: color, - alignment: isSender ? Alignment.topRight : Alignment.topLeft, - tail: tail), + color: color, alignment: isSender ? Alignment.topRight : Alignment.topLeft, tail: tail), child: Container( constraints: BoxConstraints( maxWidth: MediaQuery.of(context).size.width * .7, @@ -196,9 +195,8 @@ class BubbleSpecialThree extends StatelessWidget { child: Stack( children: [ Padding( - padding: stateTick - ? const EdgeInsets.only(left: 4, right: 20) - : const EdgeInsets.only(left: 4, right: 4), + padding: + stateTick ? const EdgeInsets.only(left: 4, right: 20) : const EdgeInsets.only(left: 4, right: 4), child: Text( text, style: textStyle, @@ -237,7 +235,7 @@ class SpecialChatBubbleThree extends CustomPainter { required this.tail, }); - final double _radius = 10.0; + final double _radius = CustomSize.radiusValue; @override void paint(Canvas canvas, Size size) { @@ -263,15 +261,13 @@ class SpecialChatBubbleThree extends CustomPainter { path.lineTo(w - _radius * 3, h); /// bottom-right bubble curve - path.quadraticBezierTo( - w - _radius * 1.5, h, w - _radius * 1.5, h - _radius * 0.6); + path.quadraticBezierTo(w - _radius * 1.5, h, w - _radius * 1.5, h - _radius * 0.6); /// bottom-right tail curve 1 path.quadraticBezierTo(w - _radius * 1, h, w, h); /// bottom-right tail curve 2 - path.quadraticBezierTo( - w - _radius * 0.8, h, w - _radius, h - _radius * 1.5); + path.quadraticBezierTo(w - _radius * 0.8, h, w - _radius, h - _radius * 1.5); /// right line path.lineTo(w - _radius, _radius * 1.5); @@ -335,8 +331,7 @@ class SpecialChatBubbleThree extends CustomPainter { path.quadraticBezierTo(_radius * .8, h, 0, h); /// bottom-right tail curve 2 - path.quadraticBezierTo( - _radius * 1, h, _radius * 1.5, h - _radius * 0.6); + path.quadraticBezierTo(_radius * 1, h, _radius * 1.5, h - _radius * 0.6); /// bottom-left bubble curve path.quadraticBezierTo(_radius * 1.5, h, _radius * 3, h); @@ -461,9 +456,7 @@ class BubbleSpecialTwo extends StatelessWidget { padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 2), child: CustomPaint( painter: SpecialChatBubbleTwo( - color: color, - alignment: isSender ? Alignment.topRight : Alignment.topLeft, - tail: tail), + color: color, alignment: isSender ? Alignment.topRight : Alignment.topLeft, tail: tail), child: Container( constraints: BoxConstraints( maxWidth: MediaQuery.of(context).size.width * .8, @@ -517,7 +510,7 @@ class SpecialChatBubbleTwo extends CustomPainter { required this.tail, }); - final double _radius = 10.0; + final double _radius = CustomSize.radiusValue; final double _x = 10.0; @override diff --git a/lib/page/component/chat/chat_input.dart b/lib/page/component/chat/chat_input.dart index 6e2088e6..1decdb01 100644 --- a/lib/page/component/chat/chat_input.dart +++ b/lib/page/component/chat/chat_input.dart @@ -69,8 +69,7 @@ class _ChatInputState extends State with TickerProviderStateMixin { /// 用于监听键盘事件,实现回车发送消息,Shift+Enter换行 late final FocusNode _focusNode = FocusNode( onKeyEvent: (node, event) { - if (!HardwareKeyboard.instance.isShiftPressed && - event.logicalKey.keyLabel == 'Enter') { + if (!HardwareKeyboard.instance.isShiftPressed && event.logicalKey.keyLabel == 'Enter') { if (event is KeyDownEvent && widget.enableNotifier.value) { _handleSubmited(_textController.text.trim()); } @@ -119,29 +118,24 @@ class _ChatInputState extends State with TickerProviderStateMixin { Ability().supportWebSocket; // Whether the user can upload files - bool get canUploadFile => - widget.enableFileUpload && Ability().supportWebSocket; + bool get canUploadFile => widget.enableFileUpload && Ability().supportWebSocket; @override Widget build(BuildContext context) { final customColors = Theme.of(context).extension()!; return Container( padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 8), - decoration: BoxDecoration( - color: customColors.backgroundColor, - ), + decoration: BoxDecoration(color: customColors.chatInputPanelBackground), child: Builder(builder: (context) { final setting = context.read(); return SafeArea( child: Column( children: [ // 选中的图片预览 - if (widget.selectedImageFiles != null && - widget.selectedImageFiles!.isNotEmpty) + if (widget.selectedImageFiles != null && widget.selectedImageFiles!.isNotEmpty) buildSelectedImagePreview(customColors), // 选中文件预览 - if (widget.selectedFile != null) - buildSelectedFilePreview(customColors), + if (widget.selectedFile != null) buildSelectedFilePreview(customColors), // 工具栏 if (widget.toolbar != null) widget.toolbar!, // if (widget.toolbar != null) @@ -158,8 +152,7 @@ class _ChatInputState extends State with TickerProviderStateMixin { autoClose: true, label: AppLocale.newChat.getString(context), backgroundColor: Colors.blue, - borderRadius: - const BorderRadius.all(Radius.circular(20)), + borderRadius: CustomSize.borderRadiusAll, onPressed: (_) { widget.onNewChat!(); }, @@ -173,10 +166,8 @@ class _ChatInputState extends State with TickerProviderStateMixin { // 聊天功能按钮 Row( children: [ - if (widget.leftSideToolsBuilder != null) - ...widget.leftSideToolsBuilder!(), - if (widget.enableNotifier.value && - (canUploadImage || canUploadFile)) + if (widget.leftSideToolsBuilder != null) ...widget.leftSideToolsBuilder!(), + if (widget.enableNotifier.value && (canUploadImage || canUploadFile)) buildUploadButtons(context, setting, customColors), ], ), @@ -184,9 +175,7 @@ class _ChatInputState extends State with TickerProviderStateMixin { Expanded( child: Container( decoration: BoxDecoration( - color: customColors.chatInputAreaBackground, - borderRadius: BorderRadius.circular(20), - ), + color: customColors.chatInputAreaBackground, borderRadius: CustomSize.borderRadius), padding: const EdgeInsets.symmetric(horizontal: 10), child: Row( children: [ @@ -214,8 +203,7 @@ class _ChatInputState extends State with TickerProviderStateMixin { decoration: InputDecoration( hintText: widget.hintText, hintStyle: const TextStyle( - fontSize: - CustomSize.defaultHintTextSize, + fontSize: CustomSize.defaultHintTextSize, ), border: InputBorder.none, counterText: '', @@ -274,7 +262,7 @@ class _ChatInputState extends State with TickerProviderStateMixin { child: Container( padding: const EdgeInsets.all(3), decoration: BoxDecoration( - borderRadius: BorderRadius.circular(10), + borderRadius: CustomSize.borderRadius, color: customColors.chatRoomBackground, ), child: Icon( @@ -306,7 +294,7 @@ class _ChatInputState extends State with TickerProviderStateMixin { child: Stack( children: [ ClipRRect( - borderRadius: BorderRadius.circular(5), + borderRadius: CustomSize.borderRadius, child: e.file.bytes != null ? Image.memory( e.file.bytes!, @@ -329,14 +317,13 @@ class _ChatInputState extends State with TickerProviderStateMixin { onTap: () { setState(() { widget.selectedImageFiles!.remove(e); - widget.onImageSelected - ?.call(widget.selectedImageFiles!); + widget.onImageSelected?.call(widget.selectedImageFiles!); }); }, child: Container( padding: const EdgeInsets.all(3), decoration: BoxDecoration( - borderRadius: BorderRadius.circular(10), + borderRadius: CustomSize.borderRadius, color: customColors.chatRoomBackground, ), child: Icon( @@ -406,7 +393,7 @@ class _ChatInputState extends State with TickerProviderStateMixin { ); }, child: Icon( - Icons.mic, + Icons.mic_none, color: customColors.chatInputPanelText, ), ) @@ -414,9 +401,7 @@ class _ChatInputState extends State with TickerProviderStateMixin { onPressed: () => _handleSubmited(_textController.text.trim()), icon: Icon( Icons.send, - color: _textController.text.trim().isNotEmpty - ? const Color.fromARGB(255, 70, 165, 73) - : null, + color: _textController.text.trim().isNotEmpty ? const Color.fromARGB(255, 70, 165, 73) : null, ), splashRadius: 20, tooltip: AppLocale.send.getString(context), @@ -427,8 +412,7 @@ class _ChatInputState extends State with TickerProviderStateMixin { // Image upload button event void onImageUploadButtonPressed() async { HapticFeedbackHelper.mediumImpact(); - if (widget.selectedImageFiles != null && - widget.selectedImageFiles!.length >= 4) { + if (widget.selectedImageFiles != null && widget.selectedImageFiles!.length >= 4) { showSuccessMessage(AppLocale.uploadImageLimit4.getString(context)); return; } @@ -441,8 +425,7 @@ class _ChatInputState extends State with TickerProviderStateMixin { if (result != null && result.files.isNotEmpty) { final files = widget.selectedImageFiles ?? []; files.addAll(result.files.map((e) => FileUpload(file: e)).toList()); - widget.onImageSelected - ?.call(files.sublist(0, files.length > 4 ? 4 : files.length)); + widget.onImageSelected?.call(files.sublist(0, files.length > 4 ? 4 : files.length)); } } diff --git a/lib/page/component/chat/chat_preview.dart b/lib/page/component/chat/chat_preview.dart index a3d068e5..15fefa72 100644 --- a/lib/page/component/chat/chat_preview.dart +++ b/lib/page/component/chat/chat_preview.dart @@ -107,18 +107,15 @@ class _ChatPreviewState extends State { crossAxisAlignment: CrossAxisAlignment.center, children: [ // 消息选择模式,显示选择框 - if (widget.controller.selectMode && - !message.message.isSystem()) + if (widget.controller.selectMode && !message.message.isSystem()) Checkbox( - value: widget.controller - .isMessageSelected(message.message.id!), + value: widget.controller.isMessageSelected(message.message.id!), activeColor: customColors.linkColor, onChanged: (value) { if (value != null && value) { widget.controller.selectMessage(message.message.id!); } else { - widget.controller - .unSelectMessage(message.message.id!); + widget.controller.unSelectMessage(message.message.id!); } }, ), @@ -128,14 +125,10 @@ class _ChatPreviewState extends State { child: widget.supportBloc ? BlocBuilder( buildWhen: (previous, current) => - (current is ChatMessageUpdated && - current.message.id == message.message.id), + (current is ChatMessageUpdated && current.message.id == message.message.id), builder: (context, state) { return Container( - padding: const EdgeInsets.symmetric( - horizontal: 10, - vertical: 10, - ), + padding: const EdgeInsets.all(5), child: _buildMessageBox( context, customColors, @@ -147,10 +140,7 @@ class _ChatPreviewState extends State { }, ) : Container( - padding: const EdgeInsets.symmetric( - horizontal: 10, - vertical: 10, - ), + padding: const EdgeInsets.all(5), child: _buildMessageBox( context, customColors, @@ -163,9 +153,7 @@ class _ChatPreviewState extends State { ], ), - if (index == 0 && - widget.helpWidgets != null && - !message.message.isSystem()) + if (index == 0 && widget.helpWidgets != null && !message.message.isSystem()) for (var widget in widget.helpWidgets!) widget, ], ); @@ -199,26 +187,21 @@ class _ChatPreviewState extends State { vertical: 5, ), child: Text( - message.isTimeline() - ? message.friendlyTime() - : message.text.getString(context), + message.isTimeline() ? message.friendlyTime() : message.text.getString(context), style: Theme.of(context).textTheme.bodySmall, ), ), ); } - final showTranslate = state.showTranslate && - state.translateText != null && - state.translateText != ''; + final showTranslate = state.showTranslate && state.translateText != null && state.translateText != ''; final extra = index == 0 ? message.decodeExtra() : null; final extraInfo = extra != null ? extra['info'] ?? '' : ''; // 普通消息 return Align( - alignment: - message.role == Role.sender ? Alignment.topRight : Alignment.topLeft, + alignment: message.role == Role.sender ? Alignment.topRight : Alignment.topLeft, child: ConstrainedBox( constraints: BoxConstraints(maxWidth: _chatBoxMaxWidth(context)), child: Column( @@ -280,8 +263,7 @@ class _ChatPreviewState extends State { children: [ buildAvatar(message), // 发送人名称 - if (message.role == Role.receiver && - widget.senderNameBuilder != null) + if (message.role == Role.receiver && widget.senderNameBuilder != null) widget.senderNameBuilder!(message) ?? const SizedBox(), ], ), @@ -300,8 +282,7 @@ class _ChatPreviewState extends State { crossAxisAlignment: WrapCrossAlignment.end, children: [ // 错误指示器 - if (message.role == Role.sender && - message.statusIsFailed()) + if (message.role == Role.sender && message.statusIsFailed()) buildErrorIndicator(message, state, context, index), // 消息主体 GestureDetector( @@ -309,8 +290,7 @@ class _ChatPreviewState extends State { // 非选择模式下,单击隐藏键盘 onTap: () { if (widget.controller.selectMode) { - widget.controller - .toggleMessageSelected(message.id!); + widget.controller.toggleMessageSelected(message.id!); } FocusScope.of(context).requestFocus(FocusNode()); }, @@ -349,7 +329,7 @@ class _ChatPreviewState extends State { ? const EdgeInsets.fromLTRB(0, 0, 10, 7) : const EdgeInsets.fromLTRB(10, 0, 0, 7), decoration: BoxDecoration( - borderRadius: BorderRadius.circular(10), + borderRadius: CustomSize.borderRadius, color: message.role == Role.receiver ? customColors.chatRoomReplyBackground : customColors.chatRoomSenderBackground, @@ -360,9 +340,7 @@ class _ChatPreviewState extends State { ), child: Builder( builder: (context) { - if ((message.statusPending() || - !message.isReady) && - message.text.isEmpty) { + if ((message.statusPending() || !message.isReady) && message.text.isEmpty) { return LoadingAnimationWidget.waveDots( color: customColors.weakLinkColor!, size: 25, @@ -375,37 +353,30 @@ class _ChatPreviewState extends State { } return Column( mainAxisSize: MainAxisSize.min, - crossAxisAlignment: - CrossAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, children: [ state.showMarkdown ? Markdown( data: text.trim(), - onUrlTap: (value) => - onMarkdownUrlTap(value), + onUrlTap: (value) => onMarkdownUrlTap(value), ) : SelectableText( text, style: TextStyle( - color: customColors - .chatRoomSenderText, + color: customColors.chatRoomSenderText, ), ), - if (message.quotaConsumed != null && - message.quotaConsumed! > 0) + if (message.quotaConsumed != null && message.quotaConsumed! > 0) Row( children: [ - const Icon(Icons.check_circle, - size: 12, - color: Colors.green), + const Icon(Icons.check_circle, size: 12, color: Colors.green), const SizedBox(width: 5), Expanded( child: Text( '共 ${message.tokenConsumed} 个 Token, 消耗 ${message.quotaConsumed} 个智慧果', style: TextStyle( fontSize: 14, - color: customColors - .weakTextColor, + color: customColors.weakTextColor, ), ), ), @@ -425,11 +396,9 @@ class _ChatPreviewState extends State { showCustomBeautyDialog( context, type: QuickAlertType.warning, - confirmBtnText: AppLocale.gotIt - .getString(context), + confirmBtnText: AppLocale.gotIt.getString(context), showCancelBtn: false, - title: AppLocale.goodTips - .getString(context), + title: AppLocale.goodTips.getString(context), child: Markdown( data: extraInfo, onUrlTap: (value) { @@ -438,8 +407,7 @@ class _ChatPreviewState extends State { }, textStyle: TextStyle( fontSize: 14, - color: customColors - .dialogDefaultTextColor, + color: customColors.dialogDefaultTextColor, ), ), ); @@ -447,8 +415,7 @@ class _ChatPreviewState extends State { child: Icon( Icons.info_outline, size: 16, - color: customColors.weakLinkColor - ?.withAlpha(50), + color: customColors.weakLinkColor?.withAlpha(50), ), ), ), @@ -463,14 +430,12 @@ class _ChatPreviewState extends State { ? const EdgeInsets.fromLTRB(7, 10, 14, 7) : const EdgeInsets.fromLTRB(10, 10, 0, 7), decoration: BoxDecoration( - borderRadius: BorderRadius.circular(10), + borderRadius: CustomSize.borderRadius, color: message.role == Role.receiver ? customColors.chatRoomReplyBackgroundSecondary - : customColors - .chatRoomSenderBackgroundSecondary, + : customColors.chatRoomSenderBackgroundSecondary, ), - padding: const EdgeInsets.symmetric( - horizontal: 10, vertical: 5), + padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 5), child: Builder( builder: (context) { return Column( @@ -481,8 +446,7 @@ class _ChatPreviewState extends State { : SelectableText( state.translateText!, style: TextStyle( - color: - customColors.chatRoomSenderText, + color: customColors.chatRoomSenderText, ), ), Row( @@ -495,12 +459,10 @@ class _ChatPreviewState extends State { ), const SizedBox(width: 5), Text( - AppLocale.translateFinished - .getString(context), + AppLocale.translateFinished.getString(context), style: const TextStyle( fontSize: 12, - color: Color.fromARGB( - 255, 145, 145, 145), + color: Color.fromARGB(255, 145, 145, 145), ), ), ], @@ -510,8 +472,7 @@ class _ChatPreviewState extends State { }, ), ), - if (widget.messageFooterBuilder != null) - widget.messageFooterBuilder!(message), + if (widget.messageFooterBuilder != null) widget.messageFooterBuilder!(message), ], ), ), @@ -588,24 +549,28 @@ class _ChatPreviewState extends State { } } + Widget avatarWrap(Widget avatar) { + return avatar; + } + Widget buildAvatar(Message message) { if (widget.avatarBuilder != null) { final avatar = widget.avatarBuilder!(message); if (avatar != null) { - return avatar; + return avatarWrap(avatar); } } if (widget.robotAvatar != null) { if (message.role == Role.receiver && message.avatarUrl != null) { - return RemoteAvatar( + return avatarWrap(RemoteAvatar( avatarUrl: message.avatarUrl!, size: 30, - ); + )); } if (message.role == Role.receiver) { - return widget.robotAvatar!; + return avatarWrap(widget.robotAvatar!); } } @@ -626,9 +591,7 @@ class _ChatPreviewState extends State { HapticFeedbackHelper.mediumImpact(); - final showTranslate = state.showTranslate && - state.translateText != null && - state.translateText != ''; + final showTranslate = state.showTranslate && state.translateText != null && state.translateText != ''; BotToast.showAttachedWidget( target: offset, @@ -703,23 +666,18 @@ class _ChatPreviewState extends State { if (showTranslate) { widget.stateManager! - .setState(message.roomId!, message.id!, - state..showTranslate = false) + .setState(message.roomId!, message.id!, state..showTranslate = false) .then((value) { setState(() {}); - context.read().add( - RoomLoadEvent(message.roomId!, cascading: false)); + context.read().add(RoomLoadEvent(message.roomId!, cascading: false)); }); } else { - if (state.translateText != null && - state.translateText != '') { + if (state.translateText != null && state.translateText != '') { widget.stateManager! - .setState(message.roomId!, message.id!, - state..showTranslate = true) + .setState(message.roomId!, message.id!, state..showTranslate = true) .then((value) { setState(() {}); - context.read().add( - RoomLoadEvent(message.roomId!, cascading: false)); + context.read().add(RoomLoadEvent(message.roomId!, cascading: false)); }); return; } @@ -735,8 +693,7 @@ class _ChatPreviewState extends State { ) .then((value) { setState(() {}); - context.read().add( - RoomLoadEvent(message.roomId!, cascading: false)); + context.read().add(RoomLoadEvent(message.roomId!, cascading: false)); }); }).onError((error, stackTrace) { showErrorMessage(resolveError(context, error!)); @@ -753,9 +710,7 @@ class _ChatPreviewState extends State { size: 14, ), Text( - showTranslate - ? AppLocale.hide.getString(context) - : AppLocale.translate.getString(context), + showTranslate ? AppLocale.hide.getString(context) : AppLocale.translate.getString(context), style: const TextStyle(fontSize: 12, color: Colors.white), ) ], @@ -766,9 +721,7 @@ class _ChatPreviewState extends State { var messages = []; if (message.role == Role.receiver) { - final questions = widget.messages - .where((e) => e.message.id == message.refId) - .toList(); + final questions = widget.messages.where((e) => e.message.id == message.refId).toList(); if (questions.isNotEmpty) { var q = questions.first; messages.add(ChatShareMessage( @@ -788,9 +741,7 @@ class _ChatPreviewState extends State { )); if (message.role == Role.sender) { - final answers = widget.messages - .where((e) => e.message.refId == message.id) - .toList(); + final answers = widget.messages.where((e) => e.message.refId == message.id).toList(); if (answers.isNotEmpty) { for (var a in answers) { messages.add(ChatShareMessage( @@ -968,9 +919,7 @@ class ChatPreviewController extends ChangeNotifier { return []; } - return _allMessages! - .where((element) => _selectedMessageIds.contains(element.message.id)) - .toList(); + return _allMessages!.where((element) => _selectedMessageIds.contains(element.message.id)).toList(); } /// 设置所有消息 diff --git a/lib/page/component/chat/chat_share.dart b/lib/page/component/chat/chat_share.dart index 9561c8e2..5694c53c 100644 --- a/lib/page/component/chat/chat_share.dart +++ b/lib/page/component/chat/chat_share.dart @@ -99,13 +99,11 @@ class _ChatShareScreenState extends State { }, child: Row( children: [ - Icon(Icons.share, - size: 14, color: customColors.weakLinkColor), + Icon(Icons.share, size: 14, color: customColors.weakLinkColor), const SizedBox(width: 5), Text( '分享', - style: TextStyle( - color: customColors.weakLinkColor, fontSize: 14), + style: TextStyle(color: customColors.weakLinkColor, fontSize: 14), ), ], ), @@ -229,8 +227,7 @@ class _ChatShareScreenState extends State { ); } - Widget buildShareWindow(CustomColors customColors, BuildContext context, - AsyncSnapshot snapshot) { + Widget buildShareWindow(CustomColors customColors, BuildContext context, AsyncSnapshot snapshot) { return WidgetsToImage( controller: controller, child: Container( @@ -239,9 +236,7 @@ class _ChatShareScreenState extends State { mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.center, children: [ - usingChatStyle - ? buildChatPreview(context, customColors) - : buildListPreview(context, customColors), + usingChatStyle ? buildChatPreview(context, customColors) : buildListPreview(context, customColors), if (showQRCode) buildQRCodePanel(customColors, snapshot), ], ), @@ -249,8 +244,7 @@ class _ChatShareScreenState extends State { ); } - Widget buildQRCodePanel( - CustomColors customColors, AsyncSnapshot snapshot) { + Widget buildQRCodePanel(CustomColors customColors, AsyncSnapshot snapshot) { return Container( color: customColors.backgroundColor, child: Padding( @@ -261,7 +255,7 @@ class _ChatShareScreenState extends State { child: Row( children: [ ClipRRect( - borderRadius: BorderRadius.circular(8), + borderRadius: CustomSize.borderRadius, child: CachedNetworkImageEnhanced( imageUrl: snapshot.data!.qrCode, width: 100, @@ -303,8 +297,7 @@ class _ChatShareScreenState extends State { mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.end, children: [ - if (message.avatarURL != null && message.leftSide) - _buildAvatar(avatarUrl: message.avatarURL), + if (message.avatarURL != null && message.leftSide) _buildAvatar(avatarUrl: message.avatarURL), if (message.username != null && message.leftSide) Container( margin: const EdgeInsets.fromLTRB(0, 0, 10, 7), @@ -323,9 +316,8 @@ class _ChatShareScreenState extends State { Container( margin: const EdgeInsets.fromLTRB(0, 10, 10, 0), child: ConstrainedBox( - constraints: BoxConstraints( - maxWidth: _chatBoxImagePreviewWidth( - context, (message.images ?? []).length)), + constraints: + BoxConstraints(maxWidth: _chatBoxImagePreviewWidth(context, (message.images ?? []).length)), child: FileUploadPreview(images: message.images ?? []), ), ), @@ -336,7 +328,7 @@ class _ChatShareScreenState extends State { child: Container( margin: const EdgeInsets.fromLTRB(0, 10, 10, 7), decoration: BoxDecoration( - borderRadius: BorderRadius.circular(10), + borderRadius: CustomSize.borderRadius, color: message.leftSide ? customColors.chatRoomReplyBackground : customColors.chatRoomSenderBackground, @@ -375,11 +367,9 @@ class _ChatShareScreenState extends State { vertical: 10, ), child: Align( - alignment: - message.leftSide ? Alignment.topLeft : Alignment.topRight, + alignment: message.leftSide ? Alignment.topLeft : Alignment.topRight, child: ConstrainedBox( - constraints: - BoxConstraints(maxWidth: _chatBoxMaxWidth(context)), + constraints: BoxConstraints(maxWidth: _chatBoxMaxWidth(context)), child: Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, @@ -388,8 +378,7 @@ class _ChatShareScreenState extends State { mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.end, children: [ - if (message.avatarURL != null && message.leftSide) - _buildAvatar(avatarUrl: message.avatarURL), + if (message.avatarURL != null && message.leftSide) _buildAvatar(avatarUrl: message.avatarURL), if (message.username != null && message.leftSide) Container( margin: const EdgeInsets.fromLTRB(0, 0, 10, 7), @@ -411,20 +400,15 @@ class _ChatShareScreenState extends State { ), child: Column( mainAxisSize: MainAxisSize.min, - crossAxisAlignment: message.leftSide - ? CrossAxisAlignment.start - : CrossAxisAlignment.end, + crossAxisAlignment: message.leftSide ? CrossAxisAlignment.start : CrossAxisAlignment.end, children: [ - if (message.images != null && - message.images!.isNotEmpty) + if (message.images != null && message.images!.isNotEmpty) Container( margin: const EdgeInsets.fromLTRB(0, 0, 10, 7), child: ConstrainedBox( constraints: BoxConstraints( - maxWidth: _chatBoxImagePreviewWidth(context, - (message.images ?? []).length)), - child: FileUploadPreview( - images: message.images ?? []), + maxWidth: _chatBoxImagePreviewWidth(context, (message.images ?? []).length)), + child: FileUploadPreview(images: message.images ?? []), ), ), Container( @@ -432,7 +416,7 @@ class _ChatShareScreenState extends State { ? const EdgeInsets.fromLTRB(0, 0, 0, 7) : const EdgeInsets.fromLTRB(0, 0, 10, 7), decoration: BoxDecoration( - borderRadius: BorderRadius.circular(10), + borderRadius: CustomSize.borderRadius, color: message.leftSide ? customColors.chatRoomReplyBackground : customColors.chatRoomSenderBackground, diff --git a/lib/page/component/chat/empty.dart b/lib/page/component/chat/empty.dart index 609a974a..9817c977 100644 --- a/lib/page/component/chat/empty.dart +++ b/lib/page/component/chat/empty.dart @@ -1,4 +1,5 @@ import 'package:askaide/lang/lang.dart'; +import 'package:askaide/page/component/theme/custom_size.dart'; import 'package:askaide/page/component/theme/custom_theme.dart'; import 'package:askaide/repo/model/misc.dart'; import 'package:flutter/material.dart'; @@ -34,8 +35,8 @@ class _EmptyPreviewState extends State { // 示例内容区域 Container( decoration: BoxDecoration( - color: customColors.backgroundColor?.withAlpha(200), - borderRadius: BorderRadius.circular(10), + // color: customColors.backgroundColor?.withAlpha(200), + borderRadius: CustomSize.borderRadius, ), padding: const EdgeInsets.only(top: 20, left: 15, right: 10, bottom: 3), height: _resolveTipHeight(context), diff --git a/lib/page/component/chat/file_upload.dart b/lib/page/component/chat/file_upload.dart index c0dba018..a287bd15 100644 --- a/lib/page/component/chat/file_upload.dart +++ b/lib/page/component/chat/file_upload.dart @@ -1,6 +1,7 @@ import 'dart:convert'; import 'package:askaide/page/component/image_preview.dart'; +import 'package:askaide/page/component/theme/custom_size.dart'; import 'package:file_picker/file_picker.dart'; import 'package:flutter/widgets.dart'; @@ -34,7 +35,7 @@ class FileUploadPreview extends StatelessWidget { if (e.startsWith('data:')) { return ImageProviderPreviewer( - borderRadius: BorderRadius.circular(5), + borderRadius: CustomSize.borderRadiusAll, imageProvider: MemoryImage( const Base64Decoder().convert(e.split(',')[1]), ), diff --git a/lib/page/component/chat/markdown.dart b/lib/page/component/chat/markdown.dart index 911dcca6..3f37697f 100644 --- a/lib/page/component/chat/markdown.dart +++ b/lib/page/component/chat/markdown.dart @@ -5,11 +5,11 @@ import 'package:askaide/page/component/chat/markdown/code.dart'; import 'package:askaide/page/component/chat/markdown/latex.dart'; import 'package:askaide/page/component/dialog.dart'; import 'package:askaide/page/component/image_preview.dart'; +import 'package:askaide/page/component/theme/custom_size.dart'; import 'package:askaide/page/component/theme/custom_theme.dart'; import 'package:clipboard/clipboard.dart'; import 'package:flutter/material.dart'; import 'package:flutter_cache_manager/flutter_cache_manager.dart'; -import 'package:flutter_highlight/themes/default.dart'; import 'package:flutter_markdown/flutter_markdown.dart' as md; import 'package:markdown/markdown.dart' as mm; import 'package:markdown_widget/config/all.dart'; @@ -52,19 +52,14 @@ class Markdown extends StatelessWidget { color: customColors.markdownCodeColor, backgroundColor: Colors.transparent, ), - codeblockPadding: - const EdgeInsets.symmetric(horizontal: 10, vertical: 10), - codeblockDecoration: BoxDecoration( - color: customColors.markdownPreColor, - borderRadius: BorderRadius.circular(5), - ), + codeblockPadding: const EdgeInsets.symmetric(horizontal: 10, vertical: 10), + codeblockDecoration: const BoxDecoration(borderRadius: CustomSize.borderRadiusAll), tableBorder: TableBorder.all( color: customColors.weakTextColor!.withOpacity(0.5), width: 1, ), tableColumnWidth: const FlexColumnWidth(), - blockquotePadding: - const EdgeInsets.symmetric(horizontal: 12, vertical: 2), + blockquotePadding: const EdgeInsets.symmetric(horizontal: 12, vertical: 2), blockquoteDecoration: BoxDecoration( border: Border( left: BorderSide( @@ -85,10 +80,7 @@ class Markdown extends StatelessWidget { ); } - return ClipRRect( - borderRadius: BorderRadius.circular(5), - child: Image.network(uri.toString()), - ); + return ClipRRect(borderRadius: CustomSize.borderRadiusAll, child: Image.network(uri.toString())); }, extensionSet: mm.ExtensionSet( mm.ExtensionSet.gitHubFlavored.blockSyntaxes, @@ -136,15 +128,11 @@ class MarkdownPlus extends StatelessWidget { ), // 代码块配置 PreConfig( - theme: defaultTheme, - decoration: BoxDecoration( - color: customColors.markdownPreColor, - borderRadius: BorderRadius.circular(5), - ), + theme: codeTheme(), + decoration: const BoxDecoration(borderRadius: CustomSize.borderRadiusAll), margin: const EdgeInsets.symmetric(vertical: 0.0), - padding: - const EdgeInsets.only(top: 35, left: 10, right: 10, bottom: 10), - textStyle: const TextStyle(fontSize: 14), + padding: const EdgeInsets.only(top: 28, left: 10, right: 10, bottom: 10), + textStyle: const TextStyle(fontSize: 13), wrapper: (child, code, language) { return Stack( children: [ @@ -153,27 +141,15 @@ class MarkdownPlus extends StatelessWidget { right: 0, top: 0, child: IconButton( - tooltip: '复制代码', - icon: Row( - children: [ - Icon( - Icons.copy, - size: 12, - color: customColors.weakLinkColor, - ), - const SizedBox(width: 5), - Text( - 'Copy', - style: TextStyle( - fontSize: 12, - color: customColors.weakLinkColor, - ), - ), - ], + tooltip: 'Copy code', + icon: Icon( + Icons.copy, + size: 10, + color: customColors.weakLinkColor, ), onPressed: () { FlutterClipboard.copy(code).then((value) { - showSuccessMessage('已复制到剪贴板'); + showSuccessMessage('Copied to clipboard'); }); }, ), @@ -185,7 +161,7 @@ class MarkdownPlus extends StatelessWidget { // 代码配置 CodeConfig( style: TextStyle( - fontSize: 14, + fontSize: 13, color: customColors.markdownCodeColor, ), ), @@ -198,7 +174,7 @@ class MarkdownPlus extends StatelessWidget { if (url.startsWith('data:')) { return ClipRRect( - borderRadius: BorderRadius.circular(5), + borderRadius: CustomSize.borderRadiusAll, child: Image.memory( const Base64Decoder().convert(url.split(',')[1]), fit: BoxFit.cover, @@ -219,11 +195,12 @@ class MarkdownPlus extends StatelessWidget { @override Widget build(BuildContext context) { final customColors = Theme.of(context).extension()!; + final markdownGenerator = MarkdownGenerator( + generators: [latexGenerator], + inlineSyntaxList: [LatexSyntax()], + ); + if (compact) { - final markdownGenerator = MarkdownGenerator( - generators: [latexGenerator], - inlineSyntaxList: [LatexSyntax()], - ); return Column( mainAxisSize: MainAxisSize.min, mainAxisAlignment: MainAxisAlignment.start, @@ -240,6 +217,7 @@ class MarkdownPlus extends StatelessWidget { data: data, shrinkWrap: true, config: _buildMarkdownConfig(customColors), + markdownGenerator: markdownGenerator, ); } } diff --git a/lib/page/component/chat/markdown/code.dart b/lib/page/component/chat/markdown/code.dart index 3894ffed..9b286a95 100644 --- a/lib/page/component/chat/markdown/code.dart +++ b/lib/page/component/chat/markdown/code.dart @@ -3,11 +3,21 @@ import 'package:askaide/page/component/dialog.dart'; import 'package:clipboard/clipboard.dart'; import 'package:flutter/material.dart'; import 'package:flutter_highlight/flutter_highlight.dart'; -import 'package:flutter_highlight/themes/default.dart'; -import 'package:flutter_highlight/themes/monokai.dart'; +import 'package:flutter_highlight/themes/tomorrow-night.dart'; +import 'package:flutter_highlight/themes/tomorrow.dart'; import 'package:flutter_markdown/flutter_markdown.dart'; import 'package:markdown/markdown.dart' as md; +Map codeTheme() { + var theme = Map.from(Ability().themeMode != 'dark' ? tomorrowTheme : tomorrowNightTheme); + theme['root'] = TextStyle( + backgroundColor: Colors.transparent, + color: theme['root']?.color, + ); + + return theme; +} + class CodeElementBuilder extends MarkdownElementBuilder { @override Widget? visitElementAfter(md.Element element, TextStyle? preferredStyle) { @@ -31,7 +41,7 @@ class CodeElementBuilder extends MarkdownElementBuilder { // Specify highlight theme // All available themes are listed in `themes` folder - theme: Ability().themeMode != 'dark' ? defaultTheme : monokaiTheme, + theme: codeTheme(), // Specify padding padding: multiLine @@ -44,7 +54,7 @@ class CodeElementBuilder extends MarkdownElementBuilder { : const EdgeInsets.symmetric(horizontal: 5, vertical: 2), textStyle: const TextStyle( - fontSize: 14, + fontSize: 13, height: 1.5, wordSpacing: 3, ), @@ -59,11 +69,11 @@ class CodeElementBuilder extends MarkdownElementBuilder { right: 0, top: 0, child: IconButton( - tooltip: '复制代码', + tooltip: 'Copy code', icon: const Icon(Icons.copy, size: 12), onPressed: () { FlutterClipboard.copy(element.textContent).then((value) { - showSuccessMessage('已复制到剪贴板'); + showSuccessMessage('Copied to clipboard'); }); }, ), diff --git a/lib/page/component/chat/role_avatar.dart b/lib/page/component/chat/role_avatar.dart index 69b0532e..785dc8f6 100644 --- a/lib/page/component/chat/role_avatar.dart +++ b/lib/page/component/chat/role_avatar.dart @@ -2,6 +2,7 @@ import 'package:askaide/helper/constant.dart'; import 'package:askaide/helper/image.dart'; import 'package:askaide/helper/model.dart'; import 'package:askaide/page/component/random_avatar.dart'; +import 'package:askaide/page/component/theme/custom_size.dart'; import 'package:askaide/repo/model/chat_history.dart'; import 'package:flutter/material.dart'; import 'package:flutter_initicon/flutter_initicon.dart'; @@ -27,6 +28,10 @@ class RoleAvatar extends StatefulWidget { class _RoleAvatarState extends State { @override Widget build(BuildContext context) { + return _buildAvatar(context); + } + + Widget _buildAvatar(BuildContext context) { if (widget.avatarUrl != null && widget.avatarUrl!.startsWith('http')) { return RemoteAvatar( avatarUrl: imageURL(widget.avatarUrl!, qiniuImageTypeAvatar), @@ -46,9 +51,7 @@ class _RoleAvatarState extends State { future: ModelAggregate.models(), builder: (context, snapshot) { if (!snapshot.hasError && snapshot.hasData) { - var mod = snapshot.data! - .where((e) => e.id == widget.his!.model!) - .firstOrNull; + var mod = snapshot.data!.where((e) => e.id == widget.his!.model!).firstOrNull; if (mod != null && mod.avatarUrl != null && mod.avatarUrl != '') { return RemoteAvatar(avatarUrl: mod.avatarUrl!, size: 30); } @@ -64,7 +67,7 @@ class _RoleAvatarState extends State { text: widget.name!.split('、').join(' '), size: 30, backgroundColor: Colors.grey.withAlpha(100), - borderRadius: BorderRadius.circular(8), + borderRadius: CustomSize.borderRadiusAll, ); } diff --git a/lib/page/component/chat_tools_button.dart b/lib/page/component/chat_tools_button.dart index 1dd352a5..c1b5ac7d 100644 --- a/lib/page/component/chat_tools_button.dart +++ b/lib/page/component/chat_tools_button.dart @@ -1,3 +1,4 @@ +import 'package:askaide/page/component/theme/custom_size.dart'; import 'package:flutter/material.dart'; import 'package:askaide/page/component/theme/custom_theme.dart'; @@ -41,10 +42,8 @@ class _ChatToolsButtonState extends State { }, child: Container( decoration: BoxDecoration( - borderRadius: BorderRadius.circular(10), - color: _mouseHover - ? customColors.tagsBackgroundHover - : customColors.tagsBackground, + borderRadius: CustomSize.borderRadius, + color: _mouseHover ? customColors.tagsBackgroundHover : customColors.tagsBackground, ), padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 3), child: Row( diff --git a/lib/page/component/column_block.dart b/lib/page/component/column_block.dart index 1a377ba9..06140cc7 100644 --- a/lib/page/component/column_block.dart +++ b/lib/page/component/column_block.dart @@ -1,3 +1,4 @@ +import 'package:askaide/page/component/theme/custom_size.dart'; import 'package:askaide/page/component/theme/custom_theme.dart'; import 'package:flutter/material.dart'; @@ -50,7 +51,7 @@ class ColumnBlock extends StatelessWidget { decoration: BoxDecoration( color: backgroundColor ?? customColors.columnBlockBackgroundColor, border: border, - borderRadius: BorderRadius.circular(borderRadius ?? customColors.borderRadius!), + borderRadius: BorderRadius.circular(borderRadius ?? CustomSize.radiusValue), boxShadow: [ BoxShadow( color: customColors.boxShadowColor!, diff --git a/lib/page/component/dialog.dart b/lib/page/component/dialog.dart index 0d67929f..04094816 100644 --- a/lib/page/component/dialog.dart +++ b/lib/page/component/dialog.dart @@ -97,8 +97,8 @@ showCustomBeautyDialog( confirmBtnText: confirmBtnText, cancelBtnText: cancelBtnText ?? AppLocale.cancel.getString(context), confirmBtnColor: customColors.linkColor!, - borderRadius: 10, - buttonBorderRadius: 10, + borderRadius: CustomSize.radiusValue, + buttonBorderRadius: CustomSize.radiusValue, backgroundColor: customColors.dialogBackgroundColor!, confirmBtnTextStyle: const TextStyle( color: Colors.white, @@ -144,8 +144,8 @@ Future showBeautyDialog( confirmBtnText: confirmBtnText, cancelBtnText: cancelBtnText ?? AppLocale.cancel.getString(context), confirmBtnColor: customColors.linkColor!, - borderRadius: 10, - buttonBorderRadius: 10, + borderRadius: CustomSize.radiusValue, + buttonBorderRadius: CustomSize.radiusValue, backgroundColor: customColors.dialogBackgroundColor!, confirmBtnTextStyle: const TextStyle( color: Colors.white, @@ -163,8 +163,7 @@ Future showBeautyDialog( ); } -showErrorMessage(String message, - {Duration duration = const Duration(seconds: 3)}) { +showErrorMessage(String message, {Duration duration = const Duration(seconds: 3)}) { HapticFeedbackHelper.mediumImpact(); Logger.instance.e(message); @@ -179,8 +178,7 @@ showErrorMessage(String message, ); } -showSuccessMessage(String message, - {Duration duration = const Duration(seconds: 3)}) async { +showSuccessMessage(String message, {Duration duration = const Duration(seconds: 3)}) async { BotToast.showText( text: message, duration: duration, @@ -230,9 +228,7 @@ Future openModalBottomSheet( useSafeArea: useSafeArea, isScrollControlled: isScrollControlled, shape: const RoundedRectangleBorder( - borderRadius: BorderRadius.vertical( - top: Radius.circular(10), - ), + borderRadius: BorderRadius.vertical(top: CustomSize.radius), ), elevation: 0, backgroundColor: Colors.transparent, @@ -317,9 +313,7 @@ openConfirmDialog( context.pop(); }, size: const ButtonSize.full(), - color: danger - ? const Color.fromARGB(255, 255, 17, 0) - : customColors.linkColor, + color: danger ? const Color.fromARGB(255, 255, 17, 0) : customColors.linkColor, backgroundColor: const Color.fromARGB(36, 222, 222, 222), ), const SizedBox(height: 10), @@ -391,7 +385,7 @@ Center buildBottomSheetTopBar(CustomColors customColors) { height: 4, decoration: BoxDecoration( color: const Color.fromARGB(255, 192, 192, 192), - borderRadius: BorderRadius.circular(2), + borderRadius: CustomSize.borderRadius, border: Border.all( color: Colors.black12, width: 0.5, @@ -595,8 +589,7 @@ openTextFieldDialog( builder: (context, snapshot) { if (snapshot.hasData) { return ItemSearchSelector( - items: - snapshot.data as List, + items: snapshot.data as List, onSelected: (value) { controller.text = value.value; return true; @@ -741,23 +734,14 @@ class CustomDialog extends StatelessWidget { final dialog = AlertDialog( title: title, elevation: 0, - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(10), - ), - // actionsPadding: EdgeInsetsGeometry.lerp( - // const EdgeInsets.all(10), - // const EdgeInsets.all(10), - // 10, - // ), + shape: RoundedRectangleBorder(borderRadius: CustomSize.borderRadius), titleTextStyle: TextStyle( color: customColors.dialogDefaultTextColor, fontSize: 20, fontWeight: FontWeight.bold, ), backgroundColor: backgroundColor ?? - (glassEffect - ? customColors.dialogBackgroundColor!.withAlpha(50) - : customColors.dialogBackgroundColor), + (glassEffect ? customColors.dialogBackgroundColor!.withAlpha(50) : customColors.dialogBackgroundColor), content: content, actions: actions, actionsAlignment: MainAxisAlignment.spaceAround, diff --git a/lib/page/component/enhanced_button.dart b/lib/page/component/enhanced_button.dart index 7966a9d6..1ef97180 100644 --- a/lib/page/component/enhanced_button.dart +++ b/lib/page/component/enhanced_button.dart @@ -1,3 +1,4 @@ +import 'package:askaide/page/component/theme/custom_size.dart'; import 'package:askaide/page/component/theme/custom_theme.dart'; import 'package:flutter/material.dart'; @@ -29,9 +30,9 @@ class EnhancedButton extends StatelessWidget { return Material( color: backgroundColor ?? customColors.linkColor, - borderRadius: BorderRadius.circular(10), + borderRadius: CustomSize.borderRadius, child: InkWell( - borderRadius: BorderRadius.circular(10), + borderRadius: CustomSize.borderRadiusAll, onTap: onPressed, child: Container( height: height ?? 42, diff --git a/lib/page/component/enhanced_error.dart b/lib/page/component/enhanced_error.dart index 3de8ae44..45746966 100644 --- a/lib/page/component/enhanced_error.dart +++ b/lib/page/component/enhanced_error.dart @@ -1,4 +1,5 @@ import 'package:askaide/helper/helper.dart'; +import 'package:askaide/page/component/theme/custom_size.dart'; import 'package:flutter/material.dart'; import 'package:go_router/go_router.dart'; @@ -33,7 +34,7 @@ class EnhancedErrorWidget extends StatelessWidget { onTap: () { context.go('/login'); }, - borderRadius: BorderRadius.circular(10), + borderRadius: CustomSize.borderRadiusAll, child: const Padding( padding: EdgeInsets.symmetric(horizontal: 10, vertical: 5), child: Text( diff --git a/lib/page/component/enhanced_input.dart b/lib/page/component/enhanced_input.dart index 67d70845..811c525e 100644 --- a/lib/page/component/enhanced_input.dart +++ b/lib/page/component/enhanced_input.dart @@ -1,3 +1,4 @@ +import 'package:askaide/page/component/theme/custom_size.dart'; import 'package:askaide/page/component/theme/custom_theme.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; @@ -71,7 +72,7 @@ class EnhancedInput extends StatelessWidget { Widget build(BuildContext context) { return InkWell( onTap: onPressed, - borderRadius: BorderRadius.circular(8), + borderRadius: CustomSize.borderRadiusAll, child: Container( padding: padding ?? const EdgeInsets.symmetric(vertical: 12), child: Column( @@ -112,45 +113,5 @@ class EnhancedInput extends StatelessWidget { ), ), ); - - // return Material( - // borderRadius: BorderRadius.circular(8), - // // color: customColors.dialogBackgroundColor, - // child: InkWell( - // onTap: onPressed, - // borderRadius: BorderRadius.circular(8), - // child: Container( - // padding: const EdgeInsets.symmetric( - // // horizontal: 10, - // vertical: 12, - // ), - // child: Row( - // mainAxisAlignment: MainAxisAlignment.spaceBetween, - // children: [ - // SizedBox( - // width: 80, - // child: title, - // ), - // Expanded( - // child: Row( - // mainAxisAlignment: MainAxisAlignment.end, - // mainAxisSize: MainAxisSize.min, - // children: [ - // value ?? Container(), - // const SizedBox(width: 10), - // icon ?? - // Icon( - // CupertinoIcons.chevron_forward, - // size: MediaQuery.of(context).textScaleFactor * 18, - // color: Colors.grey, - // ), - // ], - // ), - // ), - // ], - // ), - // ), - // ), - // ); } } diff --git a/lib/page/component/enhanced_popup_menu.dart b/lib/page/component/enhanced_popup_menu.dart index cc710905..90199c0a 100644 --- a/lib/page/component/enhanced_popup_menu.dart +++ b/lib/page/component/enhanced_popup_menu.dart @@ -1,3 +1,4 @@ +import 'package:askaide/page/component/theme/custom_size.dart'; import 'package:flutter/material.dart'; class EnhancedPopupMenuItem { @@ -34,9 +35,7 @@ class EnhancedPopupMenu extends StatelessWidget { tooltip: tooltip, splashRadius: 20, elevation: 0, - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(10), - ), + shape: RoundedRectangleBorder(borderRadius: CustomSize.borderRadius), position: PopupMenuPosition.under, onSelected: (value) { if (value.onTap != null) { @@ -53,8 +52,7 @@ class EnhancedPopupMenu extends StatelessWidget { value: item, child: Row( children: [ - if (item.icon != null) - Icon(item.icon!, size: 15, color: item.iconColor), + if (item.icon != null) Icon(item.icon!, size: 15, color: item.iconColor), if (item.icon != null) const SizedBox(width: 10), Text( item.title, diff --git a/lib/page/component/enhanced_textfield.dart b/lib/page/component/enhanced_textfield.dart index a7a76f09..455dd420 100644 --- a/lib/page/component/enhanced_textfield.dart +++ b/lib/page/component/enhanced_textfield.dart @@ -144,8 +144,7 @@ class _EnhancedTextFieldState extends State { @override Widget build(BuildContext context) { - if ((widget.labelText != null || widget.labelWidget != null) && - widget.labelPosition != LabelPosition.inner) { + if ((widget.labelText != null || widget.labelWidget != null) && widget.labelPosition != LabelPosition.inner) { // 上下结构 if (widget.labelPosition == LabelPosition.top) { return Column( @@ -168,8 +167,7 @@ class _EnhancedTextFieldState extends State { ), ), const SizedBox(width: 5), - if (widget.labelHelpWidget != null) - widget.labelHelpWidget!, + if (widget.labelHelpWidget != null) widget.labelHelpWidget!, ], ), if (widget.inputSelector != null) widget.inputSelector!, @@ -255,10 +253,8 @@ class _EnhancedTextFieldState extends State { fillColor: widget.customColors.textfieldBackgroundColor, hintText: widget.hintText, hintStyle: TextStyle( - fontSize: widget.hintTextSize ?? - CustomSize.defaultHintTextSize, - color: widget.hintColor ?? - widget.customColors.textfieldHintColor, + fontSize: widget.hintTextSize ?? CustomSize.defaultHintTextSize, + color: widget.hintColor ?? widget.customColors.textfieldHintColor, ), hintTextDirection: widget.textDirection, counterText: "", @@ -270,24 +266,18 @@ class _EnhancedTextFieldState extends State { top: widget.labelPosition == LabelPosition.top ? 0 : 10, left: widget.enableBackground ? 15 : 0, right: widget.enableBackground ? 15 : 0, - bottom: (widget.showCounter || - widget.bottomButton != null) && - widget.middleWidget == null + bottom: (widget.showCounter || widget.bottomButton != null) && widget.middleWidget == null ? 30 : 10, ), - labelText: widget.labelPosition == LabelPosition.inner - ? widget.labelText - : null, + labelText: widget.labelPosition == LabelPosition.inner ? widget.labelText : null, labelStyle: TextStyle( color: widget.customColors.textfieldLabelColor, ), suffixIcon: widget.suffixIcon ?? - (widget.labelPosition == LabelPosition.left - ? widget.inputSelector - : null), + (widget.labelPosition == LabelPosition.left ? widget.inputSelector : null), ), - cursorRadius: const Radius.circular(10), + cursorRadius: CustomSize.radius, keyboardType: widget.keyboardType, autofocus: widget.autofocus ?? false, maxLength: widget.maxLength, @@ -337,9 +327,7 @@ class _EnhancedTextFieldState extends State { highlightColor: Colors.transparent, padding: const EdgeInsets.all(0), minWidth: 60, - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(10), - ), + shape: RoundedRectangleBorder(borderRadius: CustomSize.borderRadius), onPressed: widget.bottomButtonOnPressed, child: widget.bottomButton!, ), @@ -352,10 +340,7 @@ class _EnhancedTextFieldState extends State { InputBorder resolveInputBorder() { if (widget.enableBackground) { - return OutlineInputBorder( - borderRadius: BorderRadius.circular(10), - borderSide: BorderSide.none, - ); + return const OutlineInputBorder(borderRadius: CustomSize.borderRadiusAll, borderSide: BorderSide.none); } return InputBorder.none; diff --git a/lib/page/component/gallery_item_share.dart b/lib/page/component/gallery_item_share.dart index 95a8324c..386380e7 100644 --- a/lib/page/component/gallery_item_share.dart +++ b/lib/page/component/gallery_item_share.dart @@ -85,13 +85,11 @@ class _GalleryItemShareScreenState extends State { }, child: Row( children: [ - Icon(Icons.share, - size: 14, color: customColors.weakLinkColor), + Icon(Icons.share, size: 14, color: customColors.weakLinkColor), const SizedBox(width: 5), Text( '分享', - style: TextStyle( - color: customColors.weakLinkColor, fontSize: 14), + style: TextStyle(color: customColors.weakLinkColor, fontSize: 14), ), ], ), @@ -174,7 +172,7 @@ class _GalleryItemShareScreenState extends State { ) ], ), - backgroundColor: customColors.backgroundContainerColor, + backgroundColor: customColors.backgroundColor, body: Align( alignment: Alignment.topCenter, child: ConstrainedBox( @@ -205,33 +203,28 @@ class _GalleryItemShareScreenState extends State { children: [ for (var img in widget.images) Container( - decoration: BoxDecoration( - color: customColors.backgroundColor, - borderRadius: BorderRadius.circular(8), - ), + decoration: BoxDecoration(color: customColors.backgroundColor), child: NetworkImagePreviewer( url: img, - preview: - imageURL(img, qiniuImageTypeThumb), + preview: imageURL(img, qiniuImageTypeThumb), hidePreviewButton: true, notClickable: true, - borderRadius: BorderRadius.circular(0), + borderRadius: BorderRadius.zero, ), ), ColumnBlock( + backgroundColor: customColors.backgroundContainerColor, innerPanding: 10, padding: const EdgeInsets.all(15), margin: const EdgeInsets.all(0), borderRadius: 0, children: [ - if (widget.prompt != null && - widget.prompt!.isNotEmpty) + if (widget.prompt != null && widget.prompt!.isNotEmpty) TextItem( title: 'Prompt', value: widget.prompt!, ), - if (widget.negativePrompt != null && - widget.negativePrompt!.isNotEmpty) + if (widget.negativePrompt != null && widget.negativePrompt!.isNotEmpty) TextItem( title: 'Negative Prompt', value: widget.negativePrompt!, @@ -240,7 +233,7 @@ class _GalleryItemShareScreenState extends State { ), if (showQRCode) Container( - color: customColors.backgroundColor, + color: customColors.backgroundContainerColor, child: Padding( padding: const EdgeInsets.symmetric( horizontal: 15, @@ -249,8 +242,7 @@ class _GalleryItemShareScreenState extends State { child: Row( children: [ ClipRRect( - borderRadius: - BorderRadius.circular(8), + borderRadius: CustomSize.borderRadius, child: CachedNetworkImageEnhanced( imageUrl: snapshot.data!.qrCode, width: 100, diff --git a/lib/page/component/global_alert.dart b/lib/page/component/global_alert.dart index fac9ec03..d80086b0 100644 --- a/lib/page/component/global_alert.dart +++ b/lib/page/component/global_alert.dart @@ -1,5 +1,6 @@ import 'package:askaide/helper/event.dart'; import 'package:askaide/page/component/chat/markdown.dart'; +import 'package:askaide/page/component/theme/custom_size.dart'; import 'package:askaide/repo/api_server.dart'; import 'package:flutter/material.dart'; import 'package:go_router/go_router.dart'; @@ -69,9 +70,7 @@ class _GlobalAlertState extends State { @override Widget build(BuildContext context) { - if (alertEvent.id == '' || - alertEvent.message == null || - alertEvent.message == '') { + if (alertEvent.id == '' || alertEvent.message == null || alertEvent.message == '') { return const SizedBox(); } @@ -79,10 +78,7 @@ class _GlobalAlertState extends State { margin: const EdgeInsets.symmetric(horizontal: 15, vertical: 5), padding: const EdgeInsets.symmetric(horizontal: 10), width: double.infinity, - decoration: BoxDecoration( - color: resolveBackgroundColor(), - borderRadius: BorderRadius.circular(5), - ), + decoration: BoxDecoration(color: resolveBackgroundColor(), borderRadius: CustomSize.borderRadius), child: Markdown( data: alertEvent.message!, textStyle: const TextStyle( diff --git a/lib/page/component/icon_box.dart b/lib/page/component/icon_box.dart index 4421583a..f7b7cfa9 100644 --- a/lib/page/component/icon_box.dart +++ b/lib/page/component/icon_box.dart @@ -1,3 +1,4 @@ +import 'package:askaide/page/component/theme/custom_size.dart'; import 'package:flutter/material.dart'; class IconBox extends StatelessWidget { @@ -15,9 +16,7 @@ class IconBox extends StatelessWidget { Widget build(BuildContext context) { return MaterialButton( padding: const EdgeInsets.symmetric(vertical: 10, horizontal: 10), - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(8), - ), + shape: RoundedRectangleBorder(borderRadius: CustomSize.borderRadius), onPressed: onTap, child: Column( children: [ diff --git a/lib/page/component/icon_box_button.dart b/lib/page/component/icon_box_button.dart index 04ac4120..7c9152eb 100644 --- a/lib/page/component/icon_box_button.dart +++ b/lib/page/component/icon_box_button.dart @@ -1,3 +1,4 @@ +import 'package:askaide/page/component/theme/custom_size.dart'; import 'package:flutter/material.dart'; class IconBoxButton extends StatelessWidget { @@ -19,7 +20,7 @@ class IconBoxButton extends StatelessWidget { @override Widget build(BuildContext context) { return InkWell( - borderRadius: BorderRadius.circular(8), + borderRadius: CustomSize.borderRadiusAll, onTap: () => onTap?.call(), child: Container( height: 75, @@ -30,7 +31,7 @@ class IconBoxButton extends StatelessWidget { border: Border.all( color: Colors.grey.withAlpha(50), ), - borderRadius: BorderRadius.circular(8), + borderRadius: CustomSize.borderRadius, ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, diff --git a/lib/page/component/image_preview.dart b/lib/page/component/image_preview.dart index 4719c3ef..478c357d 100644 --- a/lib/page/component/image_preview.dart +++ b/lib/page/component/image_preview.dart @@ -53,48 +53,36 @@ class _NetworkImagePreviewerState extends State { if (widget.hidePreviewButton) { return ClipRRect( - borderRadius: widget.borderRadius ?? BorderRadius.circular(8), + borderRadius: widget.borderRadius ?? CustomSize.borderRadius, child: widget.original == null ? _buildImage(widget.borderRadius) : BeforeAfter( - beforeImage: Image( - image: CachedNetworkImageProviderEnhanced( - imageURL(widget.original!, qiniuImageTypeThumb))), + beforeImage: + Image(image: CachedNetworkImageProviderEnhanced(imageURL(widget.original!, qiniuImageTypeThumb))), afterImage: Image( - image: CachedNetworkImageProviderEnhanced(imageURL( - widget.preview ?? widget.url, qiniuImageTypeThumb))), + image: CachedNetworkImageProviderEnhanced( + imageURL(widget.preview ?? widget.url, qiniuImageTypeThumb))), thumbWidth: 1.0, ), ); } return Container( - decoration: BoxDecoration( - color: customColors.columnBlockBackgroundColor, - borderRadius: BorderRadius.circular(8), - ), + decoration: BoxDecoration(color: customColors.columnBlockBackgroundColor, borderRadius: CustomSize.borderRadius), child: Column( crossAxisAlignment: CrossAxisAlignment.end, children: [ widget.original == null - ? _buildImage(const BorderRadius.only( - topLeft: Radius.circular(8), - topRight: Radius.circular(8), - )) + ? _buildImage(const BorderRadius.only(topLeft: CustomSize.radius, topRight: CustomSize.radius)) : ClipRRect( - borderRadius: const BorderRadius.only( - topLeft: Radius.circular(8), - topRight: Radius.circular(8), - ), + borderRadius: const BorderRadius.only(topLeft: CustomSize.radius, topRight: CustomSize.radius), child: BeforeAfter( imageCornerRadius: 0, beforeImage: Image( - image: CachedNetworkImageProviderEnhanced( - imageURL(widget.original!, qiniuImageTypeThumb))), + image: CachedNetworkImageProviderEnhanced(imageURL(widget.original!, qiniuImageTypeThumb))), afterImage: Image( - image: CachedNetworkImageProviderEnhanced(imageURL( - widget.preview ?? widget.url, - qiniuImageTypeThumb))), + image: CachedNetworkImageProviderEnhanced( + imageURL(widget.preview ?? widget.url, qiniuImageTypeThumb))), thumbWidth: 0.5, thumbRadius: 3, ), @@ -195,8 +183,7 @@ class _NetworkImagePreviewerState extends State { openImagePreviewDialog( context, customColors, - imageProvider: - CachedNetworkImageProviderEnhanced(widget.url), + imageProvider: CachedNetworkImageProviderEnhanced(widget.url), imageUrl: widget.url, ); } catch (e) { @@ -278,9 +265,9 @@ class ImageFilePreviewer extends StatelessWidget { Widget build(BuildContext context) { final customColors = Theme.of(context).extension()!; return ClipRRect( - borderRadius: borderRadius ?? BorderRadius.circular(8), + borderRadius: borderRadius ?? CustomSize.borderRadius, child: InkWell( - borderRadius: borderRadius ?? BorderRadius.circular(8), + borderRadius: borderRadius ?? CustomSize.borderRadiusAll, child: Image(image: imageProvider, fit: BoxFit.cover), onTap: () { openImagePreviewDialog( @@ -353,8 +340,7 @@ void openImagePreviewDialog( ); try { - final saveFile = - await DefaultCacheManager().getSingleFile(downloadUrl); + final saveFile = await DefaultCacheManager().getSingleFile(downloadUrl); if (PlatformTool.isIOS() || PlatformTool.isAndroid()) { await ImageGallerySaver.saveImage( @@ -383,7 +369,7 @@ void openImagePreviewDialog( if (PlatformTool.isWindows()) { FileSaver.instance - .saveAs( + .saveAs( name: filenameWithoutExt(saveFile.path.split('/').last), filePath: saveFile.path, ext: '.$ext', @@ -391,7 +377,7 @@ void openImagePreviewDialog( ) .then((value) async { if (value == null) { - return ; + return; } await File(value).writeAsBytes(await saveFile.readAsBytes()); @@ -401,7 +387,7 @@ void openImagePreviewDialog( }); } else { FileSaver.instance - .saveFile( + .saveFile( name: filenameWithoutExt(saveFile.path.split('/').last), filePath: saveFile.path, ext: ext, @@ -412,7 +398,6 @@ void openImagePreviewDialog( showSuccessMessage('文件保存成功'); }); } - } } catch (e) { // ignore: use_build_context_synchronously @@ -428,8 +413,7 @@ void openImagePreviewDialog( color: customColors.weakLinkColor, ), ), - if (downloadUrl == null && - (PlatformTool.isIOS() || PlatformTool.isAndroid())) + if (downloadUrl == null && (PlatformTool.isIOS() || PlatformTool.isAndroid())) IconButton( onPressed: () async { final cancel = BotToast.showCustomLoading( @@ -491,9 +475,9 @@ class ImageProviderPreviewer extends StatelessWidget { Widget build(BuildContext context) { final customColors = Theme.of(context).extension()!; return ClipRRect( - borderRadius: borderRadius ?? BorderRadius.circular(8), + borderRadius: borderRadius ?? CustomSize.borderRadius, child: InkWell( - borderRadius: borderRadius ?? BorderRadius.circular(8), + borderRadius: borderRadius ?? CustomSize.borderRadiusAll, child: Image(image: imageProvider, fit: BoxFit.cover), onTap: () { openImagePreviewDialog( diff --git a/lib/page/component/invite_card.dart b/lib/page/component/invite_card.dart index 67016427..1dc0bd89 100644 --- a/lib/page/component/invite_card.dart +++ b/lib/page/component/invite_card.dart @@ -14,7 +14,7 @@ class InviteCard extends StatelessWidget { return Container( margin: const EdgeInsets.symmetric(horizontal: 15, vertical: 10), decoration: BoxDecoration( - borderRadius: BorderRadius.circular(15), + borderRadius: BorderRadius.circular(15), // Maintain consistency with Settings Card boxShadow: [ BoxShadow( color: Colors.white.withOpacity(0.1), @@ -24,9 +24,8 @@ class InviteCard extends StatelessWidget { ], image: DecorationImage( // opacity: 0.83, - image: CachedNetworkImageProviderEnhanced(userInfo - .control.inviteCardBg ?? - 'https://ssl.aicode.cc/ai-server/assets/invite-card-bg.webp-thumb1000'), + image: CachedNetworkImageProviderEnhanced( + userInfo.control.inviteCardBg ?? 'https://ssl.aicode.cc/ai-server/assets/invite-card-bg.webp-thumb1000'), fit: BoxFit.cover, ), // gradient: const LinearGradient( @@ -88,8 +87,7 @@ class InviteCard extends StatelessWidget { onPressed: () { shareTo( context, - content: userInfo.control.inviteMessage ?? - '邀请码 ${userInfo.user.inviteCode}', + content: userInfo.control.inviteMessage ?? '邀请码 ${userInfo.user.inviteCode}', title: '邀请码分享', ); }, diff --git a/lib/page/component/message_box.dart b/lib/page/component/message_box.dart index 02d1e21c..88895430 100644 --- a/lib/page/component/message_box.dart +++ b/lib/page/component/message_box.dart @@ -1,3 +1,4 @@ +import 'package:askaide/page/component/theme/custom_size.dart'; import 'package:flutter/material.dart'; class MessageBox extends StatelessWidget { @@ -10,7 +11,7 @@ class MessageBox extends StatelessWidget { return Container( decoration: BoxDecoration( color: type.backgroundColor, - borderRadius: BorderRadius.circular(8), + borderRadius: CustomSize.borderRadius, border: Border.all( color: type.borderColor, width: 1, diff --git a/lib/page/component/model_item.dart b/lib/page/component/model_item.dart index ed3fb78a..e35baa74 100644 --- a/lib/page/component/model_item.dart +++ b/lib/page/component/model_item.dart @@ -4,6 +4,7 @@ import 'package:askaide/helper/constant.dart'; import 'package:askaide/helper/image.dart'; import 'package:askaide/lang/lang.dart'; import 'package:askaide/page/component/random_avatar.dart'; +import 'package:askaide/page/component/theme/custom_size.dart'; import 'package:askaide/page/component/theme/custom_theme.dart'; import 'package:askaide/page/component/weak_text_button.dart'; import 'package:askaide/repo/model/model.dart'; @@ -140,7 +141,7 @@ class _ModelItemState extends State { padding: const EdgeInsets.symmetric(vertical: 5), decoration: BoxDecoration( color: widget.initValue == item.uid() ? customColors.dialogBackgroundColor : null, - borderRadius: BorderRadius.circular(5), + borderRadius: CustomSize.borderRadius, ), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, @@ -276,7 +277,7 @@ class _ModelItemState extends State { return Container( decoration: BoxDecoration( color: tagBgColor != null ? stringToColor(tagBgColor) : customColors.tagsBackgroundHover, - borderRadius: BorderRadius.circular(8), + borderRadius: CustomSize.borderRadius, ), margin: const EdgeInsets.only(left: 5), padding: const EdgeInsets.symmetric( diff --git a/lib/page/component/notify_message.dart b/lib/page/component/notify_message.dart index 3acb3a70..e0223eb5 100644 --- a/lib/page/component/notify_message.dart +++ b/lib/page/component/notify_message.dart @@ -1,4 +1,5 @@ import 'package:askaide/page/component/gradient_style.dart'; +import 'package:askaide/page/component/theme/custom_size.dart'; import 'package:cached_network_image/cached_network_image.dart'; import 'package:flutter/material.dart'; @@ -38,7 +39,7 @@ class NotifyMessageWidget extends StatelessWidget { height: height, padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 10), decoration: BoxDecoration( - borderRadius: BorderRadius.circular(8), + borderRadius: CustomSize.borderRadius, // gradient: buildGradientStyle(), color: backgroundColor, image: backgroundImageUrl != null diff --git a/lib/page/component/prompt_tags_selector.dart b/lib/page/component/prompt_tags_selector.dart index 273c34d4..a5c0e0a6 100644 --- a/lib/page/component/prompt_tags_selector.dart +++ b/lib/page/component/prompt_tags_selector.dart @@ -1,4 +1,5 @@ import 'package:askaide/page/component/enhanced_button.dart'; +import 'package:askaide/page/component/theme/custom_size.dart'; import 'package:askaide/page/component/weak_text_button.dart'; import 'package:askaide/page/component/theme/custom_theme.dart'; import 'package:askaide/repo/api_server.dart'; @@ -57,23 +58,18 @@ class _PromptTagsSelectorState extends State { children: [ Expanded( child: Container( - decoration: BoxDecoration( - color: customColors.backgroundColor, - borderRadius: BorderRadius.circular(5), - ), + decoration: BoxDecoration(borderRadius: CustomSize.borderRadius), child: DefaultTabController( length: promptCategories.length, child: Column( children: [ Theme( data: Theme.of(context).copyWith( - colorScheme: Theme.of(context).colorScheme.copyWith( - surfaceContainerHighest: Colors.transparent), + colorScheme: + Theme.of(context).colorScheme.copyWith(surfaceContainerHighest: Colors.transparent), ), child: TabBar( - tabs: [ - for (var cat in promptCategories) Tab(text: cat.name) - ], + tabs: [for (var cat in promptCategories) Tab(text: cat.name)], isScrollable: true, labelPadding: const EdgeInsets.only(left: 0, right: 20), labelColor: customColors.linkColor, @@ -83,15 +79,13 @@ class _PromptTagsSelectorState extends State { fontWeight: FontWeight.bold, ), indicator: const BoxDecoration(), - overlayColor: - WidgetStateProperty.all(Colors.transparent), + overlayColor: WidgetStateProperty.all(Colors.transparent), ), ), Expanded( child: TabBarView( children: [ - for (var cat in promptCategories) - buildTabBarView(customColors, cat.children), + for (var cat in promptCategories) buildTabBarView(customColors, cat.children), ], ), ), @@ -174,14 +168,13 @@ class _PromptTagsSelectorState extends State { ); } - Widget buildTabBarView( - CustomColors customColors, List subCategories) { + Widget buildTabBarView(CustomColors customColors, List subCategories) { return Container( margin: const EdgeInsets.symmetric(horizontal: 5), padding: const EdgeInsets.only(left: 10, right: 10, top: 0, bottom: 10), decoration: BoxDecoration( color: customColors.backgroundContainerColor?.withAlpha(50), - borderRadius: BorderRadius.circular(5), + borderRadius: CustomSize.borderRadius, ), child: ListView.builder( itemCount: subCategories.length, @@ -230,9 +223,7 @@ class _PromptTagsSelectorState extends State { curve: Curves.easeOut, ); }, - textColor: selectedTags.containsKey(tag.value) - ? Colors.white - : customColors.weakTextColorPlusPlus, + textColor: selectedTags.containsKey(tag.value) ? Colors.white : customColors.weakTextColorPlusPlus, backgroundColor: selectedTags.containsKey(tag.value) ? customColors.linkColor : customColors.backgroundContainerColor?.withAlpha(200), @@ -268,8 +259,7 @@ class Tag extends StatelessWidget { side: BorderSide.none, visualDensity: const VisualDensity(horizontal: -4.0, vertical: -4.0), padding: const EdgeInsets.all(0), - labelPadding: - EdgeInsets.only(left: 5, right: onDeleted == null ? 5 : 0), + labelPadding: EdgeInsets.only(left: 5, right: onDeleted == null ? 5 : 0), elevation: 0, label: Text( name, diff --git a/lib/page/component/random_avatar.dart b/lib/page/component/random_avatar.dart index 7a354ba3..8949efe1 100644 --- a/lib/page/component/random_avatar.dart +++ b/lib/page/component/random_avatar.dart @@ -1,3 +1,4 @@ +import 'package:askaide/page/component/theme/custom_size.dart'; import 'package:cached_network_image/cached_network_image.dart'; import 'package:flutter/material.dart'; import 'package:random_avatar/random_avatar.dart' as ava; @@ -37,10 +38,9 @@ class RandomAvatar extends StatelessWidget { maxHeight: size?.toDouble() ?? 500, ), child: ClipRRect( - borderRadius: borderRadius ?? BorderRadius.circular(8), + borderRadius: borderRadius ?? CustomSize.borderRadius, child: CachedNetworkImage( - imageUrl: - 'https://ai-api.aicode.cc/v1/images/random-avatar/${usage.name}/$id/${size ?? 500}', + imageUrl: 'https://ai-api.aicode.cc/v1/images/random-avatar/${usage.name}/$id/${size ?? 500}', fit: BoxFit.cover, ), ), @@ -52,8 +52,7 @@ class RemoteAvatar extends StatelessWidget { final String avatarUrl; final int? size; final double? radius; - const RemoteAvatar( - {super.key, required this.avatarUrl, this.size, this.radius}); + const RemoteAvatar({super.key, required this.avatarUrl, this.size, this.radius}); @override Widget build(BuildContext context) { @@ -61,7 +60,7 @@ class RemoteAvatar extends StatelessWidget { width: size?.toDouble() ?? 60, height: size?.toDouble() ?? 60, child: ClipRRect( - borderRadius: BorderRadius.circular(radius ?? 8), + borderRadius: BorderRadius.circular(radius ?? CustomSize.radiusValue), child: CachedNetworkImage( imageUrl: avatarUrl, fit: BoxFit.fill, @@ -81,10 +80,7 @@ class LocalAvatar extends StatelessWidget { return SizedBox( width: size?.toDouble() ?? 60, height: size?.toDouble() ?? 60, - child: ClipRRect( - borderRadius: BorderRadius.circular(8), - child: Image.asset(assetName, fit: BoxFit.fill), - ), + child: ClipRRect(borderRadius: CustomSize.borderRadius, child: Image.asset(assetName, fit: BoxFit.fill)), ); } } diff --git a/lib/page/component/room_card.dart b/lib/page/component/room_card.dart index f39c0159..22e349fc 100644 --- a/lib/page/component/room_card.dart +++ b/lib/page/component/room_card.dart @@ -2,6 +2,7 @@ import 'package:askaide/helper/constant.dart'; import 'package:askaide/helper/image.dart'; import 'package:askaide/page/component/enhanced_button.dart'; import 'package:askaide/page/component/image.dart'; +import 'package:askaide/page/component/theme/custom_size.dart'; import 'package:askaide/page/component/weak_text_button.dart'; import 'package:askaide/page/component/dialog.dart'; import 'package:askaide/page/component/theme/custom_theme.dart'; @@ -58,7 +59,7 @@ class RoomCard extends StatelessWidget { onTap: () { onItemSelected(item); }, - borderRadius: BorderRadius.circular(8), + borderRadius: CustomSize.borderRadiusAll, child: Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.center, @@ -66,7 +67,7 @@ class RoomCard extends StatelessWidget { children: [ Container( decoration: BoxDecoration( - borderRadius: BorderRadius.circular(11), + borderRadius: CustomSize.borderRadius, border: selected ? Border.all( width: 2, @@ -77,7 +78,7 @@ class RoomCard extends StatelessWidget { child: Stack( children: [ ClipRRect( - borderRadius: BorderRadius.circular(8), + borderRadius: CustomSize.borderRadius, child: CachedNetworkImageEnhanced( imageUrl: imageURL(item.avatarUrl, qiniuImageTypeAvatar), fit: BoxFit.cover, @@ -91,10 +92,8 @@ class RoomCard extends StatelessWidget { padding: const EdgeInsets.all(4), decoration: BoxDecoration( color: customColors.linkColor ?? Colors.green, - borderRadius: const BorderRadius.only( - topLeft: Radius.circular(8), - bottomRight: Radius.circular(8), - ), + borderRadius: + const BorderRadius.only(topLeft: CustomSize.radius, bottomRight: CustomSize.radius), ), child: const Icon( Icons.check, @@ -125,8 +124,7 @@ class GalleryRoomCard extends StatelessWidget { final RoomGallery item; final Function()? onConfirm; final bool selected; - const GalleryRoomCard( - {super.key, required this.item, this.onConfirm, this.selected = false}); + const GalleryRoomCard({super.key, required this.item, this.onConfirm, this.selected = false}); @override Widget build(BuildContext context) { @@ -146,7 +144,7 @@ class GalleryRoomCard extends StatelessWidget { borderRadius: BorderRadius.circular(30), ), child: ClipRRect( - borderRadius: BorderRadius.circular(8), + borderRadius: CustomSize.borderRadius, child: CachedNetworkImageEnhanced( imageUrl: item.avatarUrl, fit: BoxFit.cover, diff --git a/lib/page/component/select_mode_toolbar.dart b/lib/page/component/select_mode_toolbar.dart index 0e63d154..91acef8e 100644 --- a/lib/page/component/select_mode_toolbar.dart +++ b/lib/page/component/select_mode_toolbar.dart @@ -3,6 +3,7 @@ import 'package:askaide/lang/lang.dart'; import 'package:askaide/page/component/chat/chat_preview.dart'; import 'package:askaide/page/component/chat/chat_share.dart'; import 'package:askaide/page/component/dialog.dart'; +import 'package:askaide/page/component/theme/custom_size.dart'; import 'package:askaide/page/component/theme/custom_theme.dart'; import 'package:askaide/repo/model/message.dart'; import 'package:flutter/material.dart'; @@ -24,10 +25,7 @@ class _SelectModeToolbarState extends State { return Container( padding: const EdgeInsets.all(10), decoration: BoxDecoration( - borderRadius: const BorderRadius.only( - topLeft: Radius.circular(10), - topRight: Radius.circular(10), - ), + borderRadius: const BorderRadius.only(topLeft: CustomSize.radius, topRight: CustomSize.radius), color: customColors.backgroundColor, ), child: SafeArea( @@ -38,8 +36,7 @@ class _SelectModeToolbarState extends State { onPressed: () { var messages = widget.chatPreviewController.selectedMessages(); if (messages.isEmpty) { - showErrorMessageEnhanced( - context, AppLocale.noMessageSelected.getString(context)); + showErrorMessageEnhanced(context, AppLocale.noMessageSelected.getString(context)); return; } @@ -90,8 +87,7 @@ class _SelectModeToolbarState extends State { onPressed: () { widget.chatPreviewController.selectAllMessage(); }, - icon: Icon(Icons.select_all_outlined, - color: customColors.linkColor), + icon: Icon(Icons.select_all_outlined, color: customColors.linkColor), label: Text( AppLocale.selectAll.getString(context), style: TextStyle(color: customColors.linkColor), @@ -100,8 +96,7 @@ class _SelectModeToolbarState extends State { TextButton.icon( onPressed: () { if (widget.chatPreviewController.selectedMessageIds.isEmpty) { - showErrorMessageEnhanced( - context, AppLocale.noMessageSelected.getString(context)); + showErrorMessageEnhanced(context, AppLocale.noMessageSelected.getString(context)); return; } @@ -109,15 +104,11 @@ class _SelectModeToolbarState extends State { context, AppLocale.confirmDelete.getString(context), () { - final ids = widget.chatPreviewController.selectedMessageIds - .toList(); + final ids = widget.chatPreviewController.selectedMessageIds.toList(); if (ids.isNotEmpty) { - context - .read() - .add(ChatMessageDeleteEvent(ids)); + context.read().add(ChatMessageDeleteEvent(ids)); - showErrorMessageEnhanced( - context, AppLocale.operateSuccess.getString(context)); + showErrorMessageEnhanced(context, AppLocale.operateSuccess.getString(context)); widget.chatPreviewController.exitSelectMode(); } diff --git a/lib/page/component/sliver_component.dart b/lib/page/component/sliver_component.dart index f6dca541..2d65bf45 100644 --- a/lib/page/component/sliver_component.dart +++ b/lib/page/component/sliver_component.dart @@ -35,9 +35,7 @@ class SliverSingleComponent extends StatelessWidget { pinned: true, snap: false, primary: true, - actions: (actions ?? []).isEmpty - ? null - : [...actions!, const SizedBox(width: 8)], + actions: (actions ?? []).isEmpty ? null : [...actions!, const SizedBox(width: 8)], backgroundColor: customColors.backgroundContainerColor, flexibleSpace: FlexibleSpaceBar( title: title, @@ -98,10 +96,8 @@ class SliverComponent extends StatelessWidget { pinned: true, snap: false, primary: true, - actions: (actions ?? []).isEmpty - ? null - : [...actions!, const SizedBox(width: 8)], - backgroundColor: customColors.backgroundContainerColor, + actions: (actions ?? []).isEmpty ? null : [...actions!, const SizedBox(width: 8)], + backgroundColor: backgroundImage != null ? Colors.transparent : customColors.backgroundColor, flexibleSpace: FlexibleSpaceBar( title: title, centerTitle: centerTitle, @@ -120,8 +116,7 @@ class SliverComponent extends StatelessWidget { expandedTitleScale: 1.1, ), ), - if (appBarExtraWidgets != null) - ...appBarExtraWidgets!(innerBoxIsScrolled), + if (appBarExtraWidgets != null) ...appBarExtraWidgets!(innerBoxIsScrolled), ]; }, body: child, @@ -138,8 +133,7 @@ class SliverTabComponent extends StatelessWidget { final double childAspectRatio; final double expendedHeight; - final List Function(BuildContext context, String tabName) - itemsBuilder; + final List Function(BuildContext context, String tabName) itemsBuilder; const SliverTabComponent({ super.key, @@ -163,17 +157,13 @@ class SliverTabComponent extends StatelessWidget { return [ Theme( data: Theme.of(context).copyWith( - colorScheme: Theme.of(context) - .colorScheme - .copyWith(surfaceContainerHighest: Colors.transparent), + colorScheme: Theme.of(context).colorScheme.copyWith(surfaceContainerHighest: Colors.transparent), ), child: SliverOverlapAbsorber( - handle: - NestedScrollView.sliverOverlapAbsorberHandleFor(context), + handle: NestedScrollView.sliverOverlapAbsorberHandleFor(context), sliver: SliverAppBar( title: title, - backgroundColor: - innerBoxIsScrolled ? customColors.backgroundColor : null, + backgroundColor: innerBoxIsScrolled ? customColors.backgroundColor : null, centerTitle: true, pinned: true, floating: true, @@ -189,12 +179,10 @@ class SliverTabComponent extends StatelessWidget { begin: Alignment.topCenter, end: Alignment.bottomCenter, colors: [Colors.black, Colors.transparent], - ).createShader( - Rect.fromLTRB(0, 0, rect.width, rect.height)); + ).createShader(Rect.fromLTRB(0, 0, rect.width, rect.height)); }, blendMode: BlendMode.dstIn, - child: backgroundImageUrl != null && - backgroundImageUrl!.isNotEmpty + child: backgroundImageUrl != null && backgroundImageUrl!.isNotEmpty ? CachedNetworkImageEnhanced( imageUrl: backgroundImageUrl!, fit: BoxFit.cover, @@ -242,8 +230,7 @@ class SliverTabComponent extends StatelessWidget { }, childCount: items.length, //内部控件数量 ), - gridDelegate: - SliverGridDelegateWithFixedCrossAxisCount( + gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( crossAxisCount: crossAxisCount, crossAxisSpacing: 5, mainAxisSpacing: 10, diff --git a/lib/page/component/theme/custom_size.dart b/lib/page/component/theme/custom_size.dart index 617c19a4..50c1ea12 100644 --- a/lib/page/component/theme/custom_size.dart +++ b/lib/page/component/theme/custom_size.dart @@ -7,6 +7,12 @@ class CustomSize { static const double maxWindowSize = 1000; static const double smallWindowSize = 500; + static const double radiusValue = 8.0; + + static BorderRadiusGeometry borderRadius = BorderRadius.circular(radiusValue); + static const Radius radius = Radius.circular(radiusValue); + static const BorderRadius borderRadiusAll = BorderRadius.all(radius); + static double get toolbarHeight { if (PlatformTool.isMacOS()) { return kToolbarHeight + 30; @@ -17,8 +23,6 @@ class CustomSize { static double adaptiveMaxWindowWidth(BuildContext context) { final windowSize = MediaQuery.of(context).size.width; - return windowSize > CustomSize.maxWindowSize - ? CustomSize.maxWindowSize - : windowSize; + return windowSize > CustomSize.maxWindowSize ? CustomSize.maxWindowSize : windowSize; } } diff --git a/lib/page/component/theme/custom_theme.dart b/lib/page/component/theme/custom_theme.dart index 0d8e5c9d..6fe64d99 100644 --- a/lib/page/component/theme/custom_theme.dart +++ b/lib/page/component/theme/custom_theme.dart @@ -4,7 +4,6 @@ import 'package:flutter/material.dart'; class CustomColors extends ThemeExtension { const CustomColors({ - this.borderRadius, this.appBarBackgroundImage, this.appBarBackgroundImageForRoom, this.appBarBackgroundImageForCreativeIsland, @@ -60,8 +59,6 @@ class CustomColors extends ThemeExtension { this.paymentItemDescriptionColor, }); - final double? borderRadius; - final String? appBarBackgroundImage; final String? appBarBackgroundImageForRoom; final String? appBarBackgroundImageForCreativeIsland; @@ -138,146 +135,100 @@ class CustomColors extends ThemeExtension { } return CustomColors( - borderRadius: lerpDouble(borderRadius, other.borderRadius, t), appBarBackgroundImage: appBarBackgroundImage, appBarBackgroundImageForRoom: appBarBackgroundImageForRoom, - appBarBackgroundImageForCreativeIsland: - appBarBackgroundImageForCreativeIsland, + appBarBackgroundImageForCreativeIsland: appBarBackgroundImageForCreativeIsland, appBarBackgroundImageDiscovery: appBarBackgroundImageDiscovery, - chatRoomBackground: - Color.lerp(chatRoomBackground, other.chatRoomBackground, t), - chatRoomReplyBackground: - Color.lerp(chatRoomReplyBackground, other.chatRoomReplyBackground, t), - chatRoomReplyBackgroundSecondary: Color.lerp( - chatRoomReplyBackgroundSecondary, - other.chatRoomReplyBackgroundSecondary, - t), - chatRoomReplyText: - Color.lerp(chatRoomReplyText, other.chatRoomReplyText, t), - chatRoomSenderBackground: Color.lerp( - chatRoomSenderBackground, other.chatRoomSenderBackground, t), - chatRoomSenderBackgroundSecondary: Color.lerp( - chatRoomSenderBackgroundSecondary, - other.chatRoomSenderBackgroundSecondary, - t), - chatRoomSenderBackgroundWarning: Color.lerp( - chatRoomSenderBackgroundWarning, - other.chatRoomSenderBackgroundWarning, - t), - chatRoomSenderText: - Color.lerp(chatRoomSenderText, other.chatRoomSenderText, t), + chatRoomBackground: Color.lerp(chatRoomBackground, other.chatRoomBackground, t), + chatRoomReplyBackground: Color.lerp(chatRoomReplyBackground, other.chatRoomReplyBackground, t), + chatRoomReplyBackgroundSecondary: + Color.lerp(chatRoomReplyBackgroundSecondary, other.chatRoomReplyBackgroundSecondary, t), + chatRoomReplyText: Color.lerp(chatRoomReplyText, other.chatRoomReplyText, t), + chatRoomSenderBackground: Color.lerp(chatRoomSenderBackground, other.chatRoomSenderBackground, t), + chatRoomSenderBackgroundSecondary: + Color.lerp(chatRoomSenderBackgroundSecondary, other.chatRoomSenderBackgroundSecondary, t), + chatRoomSenderBackgroundWarning: + Color.lerp(chatRoomSenderBackgroundWarning, other.chatRoomSenderBackgroundWarning, t), + chatRoomSenderText: Color.lerp(chatRoomSenderText, other.chatRoomSenderText, t), tagsBackground: Color.lerp(tagsBackground, other.tagsBackground, t), - tagsBackgroundHover: - Color.lerp(tagsBackgroundHover, other.tagsBackgroundHover, t), + tagsBackgroundHover: Color.lerp(tagsBackgroundHover, other.tagsBackgroundHover, t), tagsText: Color.lerp(tagsText, other.tagsText, t), - chatInputPanelBackground: Color.lerp( - chatInputPanelBackground, other.chatInputPanelBackground, t), - chatInputPanelText: - Color.lerp(chatInputPanelText, other.chatInputPanelText, t), - chatInputAreaBackground: - Color.lerp(chatInputAreaBackground, other.chatInputAreaBackground, t), - chatExampleItemBackground: Color.lerp( - chatExampleItemBackground, other.chatExampleItemBackground, t), - chatExampleItemBackgroundHover: Color.lerp(chatExampleItemBackgroundHover, - other.chatExampleItemBackgroundHover, t), - chatExampleItemText: - Color.lerp(chatExampleItemText, other.chatExampleItemText, t), - chatExampleTitleText: - Color.lerp(chatExampleTitleText, other.chatExampleTitleText, t), - markdownLinkColor: - Color.lerp(markdownLinkColor, other.markdownLinkColor, t), + chatInputPanelBackground: Color.lerp(chatInputPanelBackground, other.chatInputPanelBackground, t), + chatInputPanelText: Color.lerp(chatInputPanelText, other.chatInputPanelText, t), + chatInputAreaBackground: Color.lerp(chatInputAreaBackground, other.chatInputAreaBackground, t), + chatExampleItemBackground: Color.lerp(chatExampleItemBackground, other.chatExampleItemBackground, t), + chatExampleItemBackgroundHover: + Color.lerp(chatExampleItemBackgroundHover, other.chatExampleItemBackgroundHover, t), + chatExampleItemText: Color.lerp(chatExampleItemText, other.chatExampleItemText, t), + chatExampleTitleText: Color.lerp(chatExampleTitleText, other.chatExampleTitleText, t), + markdownLinkColor: Color.lerp(markdownLinkColor, other.markdownLinkColor, t), markdownPreColor: Color.lerp(markdownPreColor, other.markdownPreColor, t), - markdownCodeColor: - Color.lerp(markdownCodeColor, other.markdownCodeColor, t), + markdownCodeColor: Color.lerp(markdownCodeColor, other.markdownCodeColor, t), boxShadowColor: Color.lerp(boxShadowColor, other.boxShadowColor, t), backgroundColor: Color.lerp(backgroundColor, other.backgroundColor, t), - backgroundInvertedColor: - Color.lerp(backgroundInvertedColor, other.backgroundInvertedColor, t), - backgroundContainerColor: Color.lerp( - backgroundContainerColor, other.backgroundContainerColor, t), - textFieldBorderColor: - Color.lerp(textFieldBorderColor, other.textFieldBorderColor, t), + backgroundInvertedColor: Color.lerp(backgroundInvertedColor, other.backgroundInvertedColor, t), + backgroundContainerColor: Color.lerp(backgroundContainerColor, other.backgroundContainerColor, t), + textFieldBorderColor: Color.lerp(textFieldBorderColor, other.textFieldBorderColor, t), iconButtonColor: Color.lerp(iconButtonColor, other.iconButtonColor, t), weakLinkColor: Color.lerp(weakLinkColor, other.weakLinkColor, t), weakTextColor: Color.lerp(weakTextColor, other.weakTextColor, t), - weakTextColorPlus: - Color.lerp(weakTextColorPlus, other.weakTextColorPlus, t), - weakTextColorPlusPlus: - Color.lerp(weakTextColorPlusPlus, other.weakTextColorPlusPlus, t), - dialogDefaultTextColor: - Color.lerp(dialogDefaultTextColor, other.dialogDefaultTextColor, t), - dialogBackgroundColor: - Color.lerp(dialogBackgroundColor, other.dialogBackgroundColor, t), - columnBlockBorderColor: - Color.lerp(columnBlockBorderColor, other.columnBlockBorderColor, t), - columnBlockBackgroundColor: Color.lerp( - columnBlockBackgroundColor, other.columnBlockBackgroundColor, t), - columnBlockDividerColor: - Color.lerp(columnBlockDividerColor, other.columnBlockDividerColor, t), - textfieldHintColor: - Color.lerp(textfieldHintColor, other.textfieldHintColor, t), - textfieldHintDeepColor: - Color.lerp(textfieldHintDeepColor, other.textfieldHintDeepColor, t), - textfieldLabelColor: - Color.lerp(textfieldLabelColor, other.textfieldLabelColor, t), - textfieldValueColor: - Color.lerp(textfieldValueColor, other.textfieldValueColor, t), - textfieldBackgroundColor: Color.lerp( - textfieldBackgroundColor, other.textfieldBackgroundColor, t), - textfieldSelectorColor: - Color.lerp(textfieldSelectorColor, other.textfieldSelectorColor, t), - paymentItemBorderColor: - Color.lerp(paymentItemBorderColor, other.paymentItemBorderColor, t), - paymentItemBackgroundColor: Color.lerp( - paymentItemBackgroundColor, other.paymentItemBackgroundColor, t), - paymentItemTitleColor: - Color.lerp(paymentItemTitleColor, other.paymentItemTitleColor, t), - paymentItemPriceColor: - Color.lerp(paymentItemPriceColor, other.paymentItemPriceColor, t), - paymentItemDateColor: - Color.lerp(paymentItemDateColor, other.paymentItemDateColor, t), - paymentItemDescriptionColor: Color.lerp( - paymentItemDescriptionColor, other.paymentItemDescriptionColor, t), + weakTextColorPlus: Color.lerp(weakTextColorPlus, other.weakTextColorPlus, t), + weakTextColorPlusPlus: Color.lerp(weakTextColorPlusPlus, other.weakTextColorPlusPlus, t), + dialogDefaultTextColor: Color.lerp(dialogDefaultTextColor, other.dialogDefaultTextColor, t), + dialogBackgroundColor: Color.lerp(dialogBackgroundColor, other.dialogBackgroundColor, t), + columnBlockBorderColor: Color.lerp(columnBlockBorderColor, other.columnBlockBorderColor, t), + columnBlockBackgroundColor: Color.lerp(columnBlockBackgroundColor, other.columnBlockBackgroundColor, t), + columnBlockDividerColor: Color.lerp(columnBlockDividerColor, other.columnBlockDividerColor, t), + textfieldHintColor: Color.lerp(textfieldHintColor, other.textfieldHintColor, t), + textfieldHintDeepColor: Color.lerp(textfieldHintDeepColor, other.textfieldHintDeepColor, t), + textfieldLabelColor: Color.lerp(textfieldLabelColor, other.textfieldLabelColor, t), + textfieldValueColor: Color.lerp(textfieldValueColor, other.textfieldValueColor, t), + textfieldBackgroundColor: Color.lerp(textfieldBackgroundColor, other.textfieldBackgroundColor, t), + textfieldSelectorColor: Color.lerp(textfieldSelectorColor, other.textfieldSelectorColor, t), + paymentItemBorderColor: Color.lerp(paymentItemBorderColor, other.paymentItemBorderColor, t), + paymentItemBackgroundColor: Color.lerp(paymentItemBackgroundColor, other.paymentItemBackgroundColor, t), + paymentItemTitleColor: Color.lerp(paymentItemTitleColor, other.paymentItemTitleColor, t), + paymentItemPriceColor: Color.lerp(paymentItemPriceColor, other.paymentItemPriceColor, t), + paymentItemDateColor: Color.lerp(paymentItemDateColor, other.paymentItemDateColor, t), + paymentItemDescriptionColor: Color.lerp(paymentItemDescriptionColor, other.paymentItemDescriptionColor, t), ); } static const light = CustomColors( - borderRadius: 8, appBarBackgroundImage: 'assets/background.jpg', appBarBackgroundImageForRoom: 'assets/background-team.jpg', - appBarBackgroundImageForCreativeIsland: - 'assets/background-creative-island.jpg', + appBarBackgroundImageForCreativeIsland: 'assets/background-creative-island.jpg', appBarBackgroundImageDiscovery: 'assets/background-light-s1.jpg', chatRoomBackground: Color.fromARGB(255, 239, 239, 239), - chatRoomReplyBackground: Colors.white, + chatRoomReplyBackground: Colors.transparent, chatRoomReplyBackgroundSecondary: Color.fromARGB(200, 255, 255, 255), chatRoomReplyText: Color(0xFF000000), - chatRoomSenderBackground: Color.fromARGB(255, 133, 238, 94), + chatRoomSenderBackground: Color.fromARGB(255, 242, 242, 242), chatRoomSenderBackgroundSecondary: Color.fromARGB(255, 133, 238, 94), chatRoomSenderBackgroundWarning: Color.fromARGB(255, 255, 176, 131), chatRoomSenderText: Color(0xFF000000), tagsBackground: Color.fromARGB(255, 238, 238, 238), tagsBackgroundHover: Color.fromARGB(255, 237, 237, 237), tagsText: Colors.black, - chatInputPanelBackground: Color.fromARGB(255, 250, 250, 250), - chatInputPanelText: Color.fromARGB(255, 151, 151, 151), - chatInputAreaBackground: Color.fromARGB(255, 244, 244, 244), + chatInputPanelBackground: Colors.transparent, + chatInputPanelText: Color.fromARGB(255, 0, 0, 0), + chatInputAreaBackground: Color.fromARGB(255, 245, 245, 245), chatExampleItemBackground: Color.fromARGB(194, 221, 221, 221), chatExampleItemBackgroundHover: Color.fromARGB(255, 223, 223, 223), chatExampleItemText: Color.fromARGB(255, 66, 66, 66), chatExampleTitleText: Color.fromARGB(255, 66, 66, 66), markdownLinkColor: Colors.blue, markdownPreColor: Color.fromARGB(255, 247, 247, 247), - markdownCodeColor: Color.fromARGB(255, 136, 0, 0), + markdownCodeColor: Color.fromARGB(255, 167, 100, 153), boxShadowColor: Color.fromARGB(149, 232, 232, 232), - backgroundColor: Colors.white, + backgroundColor: Color.fromARGB(255, 242, 242, 246), backgroundInvertedColor: Color.fromARGB(255, 72, 72, 72), - backgroundContainerColor: Color.fromARGB(255, 234, 234, 234), + backgroundContainerColor: Color.fromARGB(255, 255, 255, 255), textFieldBorderColor: Color.fromARGB(255, 228, 228, 228), iconButtonColor: Color.fromARGB(255, 117, 117, 117), linkColor: Color.fromARGB(255, 9, 185, 85), - weakLinkColor: Color.fromARGB(255, 117, 117, 117), - weakTextColor: Color.fromARGB(255, 117, 117, 117), + weakLinkColor: Color.fromARGB(255, 75, 75, 75), + weakTextColor: Color.fromARGB(255, 75, 75, 75), weakTextColorPlus: Color.fromARGB(255, 146, 146, 146), weakTextColorPlusPlus: Color.fromARGB(255, 29, 29, 29), dialogDefaultTextColor: Color.fromARGB(195, 0, 0, 0), @@ -300,36 +251,35 @@ class CustomColors extends ThemeExtension { ); static const dark = CustomColors( - borderRadius: 8, appBarBackgroundImage: 'assets/background-dark.jpg', appBarBackgroundImageForRoom: 'assets/background-discovery-dark.jpg', appBarBackgroundImageForCreativeIsland: 'assets/background-dark-s3.jpg', appBarBackgroundImageDiscovery: 'assets/background-dark-s1.jpg', - chatRoomBackground: Color.fromARGB(255, 53, 53, 53), - chatRoomReplyBackground: Color.fromARGB(255, 22, 22, 22), + chatRoomBackground: Color.fromARGB(255, 0, 0, 0), + chatRoomReplyBackground: Colors.transparent, chatRoomReplyBackgroundSecondary: Color.fromARGB(200, 39, 39, 39), chatRoomReplyText: Color(0xFFECEFF1), - chatRoomSenderBackground: Color.fromARGB(255, 36, 172, 86), + chatRoomSenderBackground: Color.fromARGB(255, 33, 33, 33), chatRoomSenderBackgroundSecondary: Color.fromARGB(181, 36, 172, 86), chatRoomSenderBackgroundWarning: Color.fromARGB(255, 255, 176, 131), chatRoomSenderText: Color(0xFFECEFF1), tagsBackground: Color.fromARGB(255, 69, 69, 69), tagsBackgroundHover: Color.fromARGB(255, 106, 106, 106), tagsText: Color.fromARGB(255, 218, 218, 218), - chatInputPanelBackground: Color.fromARGB(255, 48, 48, 48), - chatInputPanelText: Color.fromARGB(255, 187, 187, 187), - chatInputAreaBackground: Color.fromARGB(255, 88, 88, 88), + chatInputPanelBackground: Color.fromARGB(255, 0, 0, 0), + chatInputPanelText: Color.fromARGB(255, 255, 255, 255), + chatInputAreaBackground: Color.fromARGB(255, 32, 32, 32), chatExampleItemBackground: Color.fromARGB(255, 80, 80, 80), chatExampleItemBackgroundHover: Color.fromARGB(255, 69, 69, 69), chatExampleItemText: Color.fromARGB(255, 218, 218, 218), chatExampleTitleText: Color.fromARGB(255, 150, 150, 150), markdownLinkColor: Color.fromARGB(255, 0, 122, 255), - markdownPreColor: Color.fromARGB(255, 69, 69, 69), - markdownCodeColor: Color.fromARGB(255, 244, 54, 111), + markdownPreColor: Color.fromARGB(255, 16, 16, 16), + markdownCodeColor: Color.fromARGB(255, 179, 148, 173), boxShadowColor: Color.fromARGB(70, 37, 37, 37), - backgroundColor: Color.fromARGB(255, 48, 48, 48), + backgroundColor: Color.fromARGB(255, 30, 30, 30), backgroundInvertedColor: Color.fromARGB(255, 233, 233, 233), - backgroundContainerColor: Color.fromARGB(255, 41, 41, 41), + backgroundContainerColor: Color.fromARGB(255, 0, 0, 0), textFieldBorderColor: Color.fromARGB(106, 107, 107, 107), iconButtonColor: Color.fromARGB(255, 218, 218, 218), linkColor: Color.fromARGB(255, 9, 185, 85), @@ -340,7 +290,7 @@ class CustomColors extends ThemeExtension { dialogDefaultTextColor: Color.fromARGB(195, 255, 255, 255), dialogBackgroundColor: Color.fromARGB(255, 48, 48, 48), columnBlockBorderColor: Color.fromARGB(255, 72, 72, 72), - columnBlockBackgroundColor: Color.fromARGB(255, 52, 52, 52), + columnBlockBackgroundColor: Color.fromARGB(255, 44, 44, 46), columnBlockDividerColor: Color.fromARGB(160, 60, 60, 60), textfieldHintColor: Color.fromARGB(255, 105, 105, 105), textfieldHintDeepColor: Color.fromARGB(255, 170, 170, 170), @@ -358,7 +308,6 @@ class CustomColors extends ThemeExtension { @override ThemeExtension copyWith({ - double? borderRadius, String? appBarBackgroundImage, String? appBarBackgroundImageForRoom, String? appBarBackgroundImageForCreativeIsland, @@ -414,41 +363,27 @@ class CustomColors extends ThemeExtension { Color? paymentItemDescriptionColor, }) { return CustomColors( - borderRadius: borderRadius ?? this.borderRadius, - appBarBackgroundImage: - appBarBackgroundImage ?? this.appBarBackgroundImage, - appBarBackgroundImageForRoom: - appBarBackgroundImageForRoom ?? this.appBarBackgroundImageForRoom, + appBarBackgroundImage: appBarBackgroundImage ?? this.appBarBackgroundImage, + appBarBackgroundImageForRoom: appBarBackgroundImageForRoom ?? this.appBarBackgroundImageForRoom, appBarBackgroundImageForCreativeIsland: - appBarBackgroundImageForCreativeIsland ?? - this.appBarBackgroundImageForCreativeIsland, - appBarBackgroundImageDiscovery: - appBarBackgroundImageDiscovery ?? this.appBarBackgroundImageDiscovery, + appBarBackgroundImageForCreativeIsland ?? this.appBarBackgroundImageForCreativeIsland, + appBarBackgroundImageDiscovery: appBarBackgroundImageDiscovery ?? this.appBarBackgroundImageDiscovery, chatRoomBackground: chatRoomBackground ?? this.chatRoomBackground, - chatRoomReplyBackground: - chatRoomReplyBackground ?? this.chatRoomReplyBackground, - chatRoomReplyBackgroundSecondary: chatRoomReplyBackgroundSecondary ?? - this.chatRoomReplyBackgroundSecondary, + chatRoomReplyBackground: chatRoomReplyBackground ?? this.chatRoomReplyBackground, + chatRoomReplyBackgroundSecondary: chatRoomReplyBackgroundSecondary ?? this.chatRoomReplyBackgroundSecondary, chatRoomReplyText: chatRoomReplyText ?? this.chatRoomReplyText, - chatRoomSenderBackground: - chatRoomSenderBackground ?? this.chatRoomSenderBackground, - chatRoomSenderBackgroundSecondary: chatRoomSenderBackgroundSecondary ?? - this.chatRoomSenderBackgroundSecondary, - chatRoomSenderBackgroundWarning: chatRoomSenderBackgroundWarning ?? - this.chatRoomSenderBackgroundWarning, + chatRoomSenderBackground: chatRoomSenderBackground ?? this.chatRoomSenderBackground, + chatRoomSenderBackgroundSecondary: chatRoomSenderBackgroundSecondary ?? this.chatRoomSenderBackgroundSecondary, + chatRoomSenderBackgroundWarning: chatRoomSenderBackgroundWarning ?? this.chatRoomSenderBackgroundWarning, chatRoomSenderText: chatRoomSenderText ?? this.chatRoomSenderText, tagsBackground: tagsBackground ?? this.tagsBackground, tagsBackgroundHover: tagsBackgroundHover ?? this.tagsBackgroundHover, tagsText: tagsText ?? this.tagsText, - chatInputPanelBackground: - chatInputPanelBackground ?? this.chatInputPanelBackground, + chatInputPanelBackground: chatInputPanelBackground ?? this.chatInputPanelBackground, chatInputPanelText: chatInputPanelText ?? this.chatInputPanelText, - chatInputAreaBackground: - chatInputAreaBackground ?? this.chatInputAreaBackground, - chatExampleItemBackground: - chatExampleItemBackground ?? this.chatExampleItemBackground, - chatExampleItemBackgroundHover: - chatExampleItemBackgroundHover ?? this.chatExampleItemBackgroundHover, + chatInputAreaBackground: chatInputAreaBackground ?? this.chatInputAreaBackground, + chatExampleItemBackground: chatExampleItemBackground ?? this.chatExampleItemBackground, + chatExampleItemBackgroundHover: chatExampleItemBackgroundHover ?? this.chatExampleItemBackgroundHover, chatExampleItemText: chatExampleItemText ?? this.chatExampleItemText, chatExampleTitleText: chatExampleTitleText ?? this.chatExampleTitleText, markdownLinkColor: markdownLinkColor ?? this.markdownLinkColor, @@ -456,48 +391,32 @@ class CustomColors extends ThemeExtension { markdownCodeColor: markdownCodeColor ?? this.markdownCodeColor, boxShadowColor: boxShadowColor ?? this.boxShadowColor, backgroundColor: backgroundColor ?? this.backgroundColor, - backgroundInvertedColor: - backgroundInvertedColor ?? this.backgroundInvertedColor, - backgroundContainerColor: - backgroundContainerColor ?? this.backgroundContainerColor, + backgroundInvertedColor: backgroundInvertedColor ?? this.backgroundInvertedColor, + backgroundContainerColor: backgroundContainerColor ?? this.backgroundContainerColor, textFieldBorderColor: textFieldBorderColor ?? this.textFieldBorderColor, iconButtonColor: iconButtonColor ?? this.iconButtonColor, linkColor: linkColor ?? this.linkColor, weakLinkColor: weakLinkColor ?? this.weakLinkColor, weakTextColor: weakTextColor ?? this.weakTextColor, weakTextColorPlus: weakTextColorPlus ?? this.weakTextColorPlus, - weakTextColorPlusPlus: - weakTextColorPlusPlus ?? this.weakTextColorPlusPlus, - dialogDefaultTextColor: - dialogDefaultTextColor ?? this.dialogDefaultTextColor, - dialogBackgroundColor: - dialogBackgroundColor ?? this.dialogBackgroundColor, - columnBlockBorderColor: - columnBlockBorderColor ?? this.columnBlockBorderColor, - columnBlockBackgroundColor: - columnBlockBackgroundColor ?? this.columnBlockBackgroundColor, - columnBlockDividerColor: - columnBlockDividerColor ?? this.columnBlockDividerColor, + weakTextColorPlusPlus: weakTextColorPlusPlus ?? this.weakTextColorPlusPlus, + dialogDefaultTextColor: dialogDefaultTextColor ?? this.dialogDefaultTextColor, + dialogBackgroundColor: dialogBackgroundColor ?? this.dialogBackgroundColor, + columnBlockBorderColor: columnBlockBorderColor ?? this.columnBlockBorderColor, + columnBlockBackgroundColor: columnBlockBackgroundColor ?? this.columnBlockBackgroundColor, + columnBlockDividerColor: columnBlockDividerColor ?? this.columnBlockDividerColor, textfieldHintColor: textfieldHintColor ?? this.textfieldHintColor, - textfieldHintDeepColor: - textfieldHintDeepColor ?? this.textfieldHintDeepColor, + textfieldHintDeepColor: textfieldHintDeepColor ?? this.textfieldHintDeepColor, textfieldLabelColor: textfieldLabelColor ?? this.textfieldLabelColor, textfieldValueColor: textfieldValueColor ?? this.textfieldValueColor, - textfieldBackgroundColor: - textfieldBackgroundColor ?? this.textfieldBackgroundColor, - textfieldSelectorColor: - textfieldSelectorColor ?? this.textfieldSelectorColor, - paymentItemBorderColor: - paymentItemBorderColor ?? this.paymentItemBorderColor, - paymentItemBackgroundColor: - paymentItemBackgroundColor ?? this.paymentItemBackgroundColor, - paymentItemTitleColor: - paymentItemTitleColor ?? this.paymentItemTitleColor, - paymentItemPriceColor: - paymentItemPriceColor ?? this.paymentItemPriceColor, + textfieldBackgroundColor: textfieldBackgroundColor ?? this.textfieldBackgroundColor, + textfieldSelectorColor: textfieldSelectorColor ?? this.textfieldSelectorColor, + paymentItemBorderColor: paymentItemBorderColor ?? this.paymentItemBorderColor, + paymentItemBackgroundColor: paymentItemBackgroundColor ?? this.paymentItemBackgroundColor, + paymentItemTitleColor: paymentItemTitleColor ?? this.paymentItemTitleColor, + paymentItemPriceColor: paymentItemPriceColor ?? this.paymentItemPriceColor, paymentItemDateColor: paymentItemDateColor ?? this.paymentItemDateColor, - paymentItemDescriptionColor: - paymentItemDescriptionColor ?? this.paymentItemDescriptionColor, + paymentItemDescriptionColor: paymentItemDescriptionColor ?? this.paymentItemDescriptionColor, ); } } diff --git a/lib/page/component/video_player.dart b/lib/page/component/video_player.dart index 8ddff3e4..d8758ad6 100644 --- a/lib/page/component/video_player.dart +++ b/lib/page/component/video_player.dart @@ -5,6 +5,7 @@ import 'package:askaide/helper/logger.dart'; import 'package:askaide/helper/platform.dart'; import 'package:askaide/page/component/dialog.dart'; import 'package:askaide/page/component/loading.dart'; +import 'package:askaide/page/component/theme/custom_size.dart'; import 'package:askaide/page/component/theme/custom_theme.dart'; import 'package:bot_toast/bot_toast.dart'; import 'package:file_saver/file_saver.dart'; @@ -20,12 +21,7 @@ class VideoPlayer extends StatefulWidget { final int? width; final int? height; - const VideoPlayer( - {super.key, - required this.url, - this.width, - this.height, - this.aspectRatio}); + const VideoPlayer({super.key, required this.url, this.width, this.height, this.aspectRatio}); @override State createState() => _VideoPlayerState(); @@ -55,23 +51,18 @@ class _VideoPlayerState extends State { return Container( decoration: BoxDecoration( color: customColors.columnBlockBackgroundColor, - borderRadius: BorderRadius.circular(8), + borderRadius: CustomSize.borderRadius, ), child: Column( crossAxisAlignment: CrossAxisAlignment.end, children: [ ClipRRect( - borderRadius: const BorderRadius.only( - topLeft: Radius.circular(8), - topRight: Radius.circular(8), - ), + borderRadius: const BorderRadius.only(topLeft: CustomSize.radius, topRight: CustomSize.radius), child: Center( child: SizedBox( width: MediaQuery.of(context).size.width, height: widget.width != null && widget.height != null - ? MediaQuery.of(context).size.width * - widget.height! / - widget.width! + ? MediaQuery.of(context).size.width * widget.height! / widget.width! : MediaQuery.of(context).size.width, child: Video( controller: controller, @@ -121,8 +112,7 @@ class _VideoPlayerState extends State { ); try { - final saveFile = - await DefaultCacheManager().getSingleFile(widget.url); + final saveFile = await DefaultCacheManager().getSingleFile(widget.url); if (PlatformTool.isIOS() || PlatformTool.isAndroid()) { await ImageGallerySaver.saveFile(saveFile.path); @@ -133,7 +123,7 @@ class _VideoPlayerState extends State { if (PlatformTool.isWindows()) { FileSaver.instance - .saveAs( + .saveAs( name: filenameWithoutExt(saveFile.path.split('/').last), filePath: saveFile.path, ext: '.$ext', @@ -141,7 +131,7 @@ class _VideoPlayerState extends State { ) .then((value) async { if (value == null) { - return ; + return; } await File(value).writeAsBytes(await saveFile.readAsBytes()); @@ -152,8 +142,7 @@ class _VideoPlayerState extends State { } else { FileSaver.instance .saveFile( - name: - filenameWithoutExt(saveFile.path.split('/').last), + name: filenameWithoutExt(saveFile.path.split('/').last), filePath: saveFile.path, ext: ext, mimeType: MimeType.mpeg, @@ -161,7 +150,7 @@ class _VideoPlayerState extends State { .then((value) { showSuccessMessage('文件保存成功'); }); - } + } } } catch (e) { // ignore: use_build_context_synchronously diff --git a/lib/page/creative_island/draw/artistic_qr.dart b/lib/page/creative_island/draw/artistic_qr.dart index bd3c1e04..bc5e8fb9 100644 --- a/lib/page/creative_island/draw/artistic_qr.dart +++ b/lib/page/creative_island/draw/artistic_qr.dart @@ -81,42 +81,33 @@ class _ArtisticQRScreenState extends State { @override void initState() { - APIServer() - .creativeIslandCapacity(mode: widget.type, id: widget.id) - .then((cap) { + APIServer().creativeIslandCapacity(mode: widget.type, id: widget.id).then((cap) { setState(() { capacity = cap; }); if (widget.galleryCopyId != null && widget.galleryCopyId! > 0) { - APIServer() - .creativeGalleryItem(id: widget.galleryCopyId!) - .then((response) { + APIServer().creativeGalleryItem(id: widget.galleryCopyId!).then((response) { final gallery = response.item; if (gallery.prompt != null && gallery.prompt!.isNotEmpty) { promptController.text = gallery.prompt!; } - if (gallery.metaMap['real_prompt'] != null && - gallery.metaMap['real_prompt'] != '') { + if (gallery.metaMap['real_prompt'] != null && gallery.metaMap['real_prompt'] != '') { promptController.text = gallery.metaMap['real_prompt']!; } - if (gallery.metaMap['negative_prompt'] != null && - gallery.metaMap['negative_prompt'] != '') { + if (gallery.metaMap['negative_prompt'] != null && gallery.metaMap['negative_prompt'] != '') { negativePromptController.text = gallery.metaMap['negative_prompt']!; } - if (gallery.metaMap['real_negative_prompt'] != null && - gallery.metaMap['real_negative_prompt'] != '') { - negativePromptController.text = - gallery.metaMap['real_negative_prompt']!; + if (gallery.metaMap['real_negative_prompt'] != null && gallery.metaMap['real_negative_prompt'] != '') { + negativePromptController.text = gallery.metaMap['real_negative_prompt']!; } // 创建同款时,默认关闭 AI 优化,除非该同款包含 ai_rewrite 的设定 enableAIRewrite = false; - if ((gallery.metaMap['real_prompt'] == null || - gallery.metaMap['real_prompt'] == '') && + if ((gallery.metaMap['real_prompt'] == null || gallery.metaMap['real_prompt'] == '') && gallery.metaMap['ai_rewrite'] != null && gallery.metaMap['ai_rewrite']) { enableAIRewrite = gallery.metaMap['ai_rewrite']; @@ -128,9 +119,7 @@ class _ArtisticQRScreenState extends State { }); if (widget.note != null) { - Cache() - .boolGet(key: 'creative:tutorials:${widget.type}:dialog') - .then((show) { + Cache().boolGet(key: 'creative:tutorials:${widget.type}:dialog').then((show) { if (!show) { return; } @@ -165,7 +154,7 @@ class _ArtisticQRScreenState extends State { icon: const Icon(Icons.arrow_back_ios), ), toolbarHeight: CustomSize.toolbarHeight, - backgroundColor: customColors.backgroundContainerColor, + backgroundColor: customColors.backgroundColor, actions: [ if (widget.note != null) IconButton( @@ -176,19 +165,17 @@ class _ArtisticQRScreenState extends State { ) ], ), - backgroundColor: customColors.backgroundContainerColor, + backgroundColor: customColors.backgroundColor, body: BackgroundContainer( setting: widget.setting, - enabled: true, + enabled: false, maxWidth: CustomSize.smallWindowSize, child: Column( children: [ - if (Ability().showGlobalAlert) - const GlobalAlert(pageKey: 'creative_create'), + if (Ability().showGlobalAlert) const GlobalAlert(pageKey: 'creative_create'), Expanded( child: Container( - padding: - const EdgeInsets.symmetric(horizontal: 10, vertical: 10), + padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 10), height: double.infinity, child: SingleChildScrollView( child: buildEditPanel(context, customColors), @@ -222,7 +209,7 @@ class _ArtisticQRScreenState extends State { children: [ ColumnBlock( innerPanding: 10, - padding: const EdgeInsets.symmetric(horizontal: 15, vertical: 15), + padding: const EdgeInsets.only(left: 15, right: 15, top: 15, bottom: 0), children: [ if (capacity != null && capacity!.artisticStyles.isNotEmpty) ArtisticStyleSelector( @@ -270,8 +257,7 @@ class _ArtisticQRScreenState extends State { showBeautyDialog( context, type: QuickAlertType.info, - text: AppLocale.onceEnabledSmartOptimization - .getString(context), + text: AppLocale.onceEnabledSmartOptimization.getString(context), confirmBtnText: AppLocale.gotIt.getString(context), showCancelBtn: false, ); @@ -329,8 +315,7 @@ class _ArtisticQRScreenState extends State { context, type: QuickAlertType.info, text: '文本权重\n\n权重越高,图像中出现的文本痕迹越明显。', - confirmBtnText: - AppLocale.gotIt.getString(context), + confirmBtnText: AppLocale.gotIt.getString(context), showCancelBtn: false, ); }, @@ -380,14 +365,10 @@ class _ArtisticQRScreenState extends State { openListSelectDialog( context, [ - SelectorItem( - const Text('1', textAlign: TextAlign.center), 1), - SelectorItem( - const Text('2', textAlign: TextAlign.center), 2), - SelectorItem( - const Text('3', textAlign: TextAlign.center), 3), - SelectorItem( - const Text('4', textAlign: TextAlign.center), 4), + SelectorItem(const Text('1', textAlign: TextAlign.center), 1), + SelectorItem(const Text('2', textAlign: TextAlign.center), 2), + SelectorItem(const Text('3', textAlign: TextAlign.center), 3), + SelectorItem(const Text('4', textAlign: TextAlign.center), 4), ], (value) { setState(() { @@ -597,8 +578,7 @@ class _ArtisticQRScreenState extends State { request(int waitDuration) async { try { - final taskId = await APIServer() - .creativeIslandArtisticTextCompletionsAsyncV2(params); + final taskId = await APIServer().creativeIslandArtisticTextCompletionsAsyncV2(params); stopPeriodQuery = false; @@ -654,9 +634,7 @@ class _ArtisticQRScreenState extends State { final resp = await APIServer().asyncTaskStatus(taskId); switch (resp.status) { case 'success': - if (params != null && - resp.originImage != null && - resp.originImage != '') { + if (params != null && resp.originImage != null && resp.originImage != '') { params['image'] = resp.originImage; } return IslandResult( diff --git a/lib/page/creative_island/draw/artistic_wordart.dart b/lib/page/creative_island/draw/artistic_wordart.dart index 2d8c4803..d3d5496d 100644 --- a/lib/page/creative_island/draw/artistic_wordart.dart +++ b/lib/page/creative_island/draw/artistic_wordart.dart @@ -72,24 +72,19 @@ class _ArtisticWordArtScreenState extends State { @override void initState() { - APIServer() - .creativeIslandCapacity(mode: 'artistic-text', id: widget.id) - .then((cap) { + APIServer().creativeIslandCapacity(mode: 'artistic-text', id: widget.id).then((cap) { setState(() { capacity = cap; }); if (widget.galleryCopyId != null && widget.galleryCopyId! > 0) { - APIServer() - .creativeGalleryItem(id: widget.galleryCopyId!) - .then((response) { + APIServer().creativeGalleryItem(id: widget.galleryCopyId!).then((response) { final gallery = response.item; if (gallery.prompt != null && gallery.prompt!.isNotEmpty) { promptController.text = gallery.prompt!; } - if (gallery.metaMap['real_prompt'] != null && - gallery.metaMap['real_prompt'] != '') { + if (gallery.metaMap['real_prompt'] != null && gallery.metaMap['real_prompt'] != '') { promptController.text = gallery.metaMap['real_prompt']!; } @@ -99,9 +94,7 @@ class _ArtisticWordArtScreenState extends State { }); if (widget.note != null) { - Cache() - .boolGet(key: 'creative:tutorials:artistic-text:dialog') - .then((show) { + Cache().boolGet(key: 'creative:tutorials:artistic-text:dialog').then((show) { if (!show) { return; } @@ -136,7 +129,7 @@ class _ArtisticWordArtScreenState extends State { icon: const Icon(Icons.arrow_back_ios), ), toolbarHeight: CustomSize.toolbarHeight, - backgroundColor: customColors.backgroundContainerColor, + backgroundColor: customColors.backgroundColor, actions: [ if (widget.note != null) IconButton( @@ -147,19 +140,17 @@ class _ArtisticWordArtScreenState extends State { ) ], ), - backgroundColor: customColors.backgroundContainerColor, + backgroundColor: customColors.backgroundColor, body: BackgroundContainer( setting: widget.setting, - enabled: true, + enabled: false, maxWidth: CustomSize.smallWindowSize, child: Column( children: [ - if (Ability().showGlobalAlert) - const GlobalAlert(pageKey: 'creative_create'), + if (Ability().showGlobalAlert) const GlobalAlert(pageKey: 'creative_create'), Expanded( child: Container( - padding: - const EdgeInsets.symmetric(horizontal: 10, vertical: 10), + padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 10), height: double.infinity, child: SingleChildScrollView( child: buildEditPanel(context, customColors), @@ -193,7 +184,7 @@ class _ArtisticWordArtScreenState extends State { children: [ ColumnBlock( innerPanding: 10, - padding: const EdgeInsets.symmetric(horizontal: 15, vertical: 15), + padding: const EdgeInsets.only(top: 15, left: 15, right: 15, bottom: 0), children: [ if (capacity != null && capacity!.artisticTextStyles.isNotEmpty) ArtisticStyleSelector( @@ -259,14 +250,10 @@ class _ArtisticWordArtScreenState extends State { openListSelectDialog( context, [ - SelectorItem( - const Text('1', textAlign: TextAlign.center), 1), - SelectorItem( - const Text('2', textAlign: TextAlign.center), 2), - SelectorItem( - const Text('3', textAlign: TextAlign.center), 3), - SelectorItem( - const Text('4', textAlign: TextAlign.center), 4), + SelectorItem(const Text('1', textAlign: TextAlign.center), 1), + SelectorItem(const Text('2', textAlign: TextAlign.center), 2), + SelectorItem(const Text('3', textAlign: TextAlign.center), 3), + SelectorItem(const Text('4', textAlign: TextAlign.center), 4), ], (value) { setState(() { @@ -453,8 +440,7 @@ class _ArtisticWordArtScreenState extends State { request(int waitDuration) async { try { - final taskId = await APIServer() - .creativeIslandArtisticTextCompletionsAsyncV2(params); + final taskId = await APIServer().creativeIslandArtisticTextCompletionsAsyncV2(params); stopPeriodQuery = false; @@ -509,9 +495,7 @@ class _ArtisticWordArtScreenState extends State { final resp = await APIServer().asyncTaskStatus(taskId); switch (resp.status) { case 'success': - if (params != null && - resp.originImage != null && - resp.originImage != '') { + if (params != null && resp.originImage != null && resp.originImage != '') { params['image'] = resp.originImage; } return IslandResult( diff --git a/lib/page/creative_island/draw/components/artistic_style_selector.dart b/lib/page/creative_island/draw/components/artistic_style_selector.dart index 6d158537..52ea1f89 100644 --- a/lib/page/creative_island/draw/components/artistic_style_selector.dart +++ b/lib/page/creative_island/draw/components/artistic_style_selector.dart @@ -2,6 +2,7 @@ import 'package:askaide/lang/lang.dart'; import 'package:askaide/page/component/enhanced_input.dart'; import 'package:askaide/page/component/image.dart'; import 'package:askaide/page/component/dialog.dart'; +import 'package:askaide/page/component/theme/custom_size.dart'; import 'package:askaide/page/component/theme/custom_theme.dart'; import 'package:askaide/repo/api/creative.dart'; import 'package:flutter/material.dart'; @@ -42,10 +43,7 @@ class ArtisticStyleSelector extends StatelessWidget { // const SizedBox(width: 10), _buildImageStyleItemPreview( customColors, - selectedStyle == null - ? CreativeIslandArtisticStyle( - id: '', name: '', previewImage: '') - : selectedStyle!, + selectedStyle == null ? CreativeIslandArtisticStyle(id: '', name: '', previewImage: '') : selectedStyle!, size: 50, ), ], @@ -60,11 +58,7 @@ class ArtisticStyleSelector extends StatelessWidget { mainAxisSpacing: 20, padding: const EdgeInsets.only(top: 20, bottom: 20), children: [ - for (var item in [ - CreativeIslandArtisticStyle( - id: '', name: '自动', previewImage: ''), - ...styles - ]) + for (var item in [CreativeIslandArtisticStyle(id: '', name: '自动', previewImage: ''), ...styles]) InkWell( onTap: () { onSelected(item); @@ -110,9 +104,8 @@ class ArtisticStyleSelector extends StatelessWidget { width: size, height: size, decoration: BoxDecoration( - borderRadius: BorderRadius.circular(5), - border: showSelected && - (selectedStyle != null && style.id == selectedStyle!.id) + borderRadius: CustomSize.borderRadius, + border: showSelected && (selectedStyle != null && style.id == selectedStyle!.id) ? Border.all( color: customColors.linkColor ?? Colors.green, width: 1, @@ -120,8 +113,7 @@ class ArtisticStyleSelector extends StatelessWidget { : null, image: style.previewImage != null && style.previewImage != '' ? DecorationImage( - image: - CachedNetworkImageProviderEnhanced(style.previewImage!), + image: CachedNetworkImageProviderEnhanced(style.previewImage!), fit: BoxFit.cover, ) : null, diff --git a/lib/page/creative_island/draw/components/box.dart b/lib/page/creative_island/draw/components/box.dart index 559b55ec..715db127 100644 --- a/lib/page/creative_island/draw/components/box.dart +++ b/lib/page/creative_island/draw/components/box.dart @@ -3,6 +3,7 @@ import 'dart:ui'; import 'package:askaide/helper/color.dart'; import 'package:askaide/helper/haptic_feedback.dart'; import 'package:askaide/page/component/image.dart'; +import 'package:askaide/page/component/theme/custom_size.dart'; import 'package:askaide/repo/api/creative.dart'; import 'package:flutter/material.dart'; import 'package:go_router/go_router.dart'; @@ -11,8 +12,7 @@ import 'package:go_router/go_router.dart'; class CreativeIslandBox extends StatelessWidget { final CreativeIslandItem item; final Color? backgroundColor; - const CreativeIslandBox( - {super.key, required this.item, this.backgroundColor}); + const CreativeIslandBox({super.key, required this.item, this.backgroundColor}); @override Widget build(BuildContext context) { @@ -25,7 +25,7 @@ class CreativeIslandBox extends StatelessWidget { // height: 80, decoration: BoxDecoration( color: backgroundColor, - borderRadius: const BorderRadius.all(Radius.circular(10)), + borderRadius: CustomSize.borderRadius, image: item.bgImage != null ? DecorationImage( image: CachedNetworkImageProviderEnhanced(item.bgImage!), @@ -36,7 +36,7 @@ class CreativeIslandBox extends StatelessWidget { child: Material( color: Colors.transparent, child: InkWell( - borderRadius: BorderRadius.circular(10), + borderRadius: CustomSize.borderRadiusAll, onTap: () { HapticFeedbackHelper.lightImpact(); context.push('/creative-island/${item.id}/create'); @@ -49,9 +49,7 @@ class CreativeIslandBox extends StatelessWidget { decoration: BoxDecoration( color: Colors.white.withAlpha(60), borderRadius: const BorderRadius.only( - bottomLeft: Radius.circular(10), - bottomRight: Radius.circular(10), - ), + bottomLeft: CustomSize.radius, bottomRight: CustomSize.radius), ), child: ClipRect( child: BackdropFilter( @@ -67,14 +65,10 @@ class CreativeIslandBox extends StatelessWidget { style: TextStyle( fontSize: 18, fontWeight: FontWeight.bold, - color: item.titleColor != null - ? stringToColor(item.titleColor!) - : Colors.white, + color: item.titleColor != null ? stringToColor(item.titleColor!) : Colors.white, shadows: [ Shadow( - color: const Color.fromARGB( - 255, 161, 161, 161) - .withOpacity(0.5), + color: const Color.fromARGB(255, 161, 161, 161).withOpacity(0.5), offset: const Offset(2, 2), blurRadius: 5, ), @@ -106,13 +100,9 @@ class CreativeIslandBox extends StatelessWidget { top: 0, right: 0, child: Container( - padding: - const EdgeInsets.symmetric(horizontal: 10, vertical: 5), + padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 5), decoration: BoxDecoration( - borderRadius: const BorderRadius.only( - topRight: Radius.circular(10), - bottomLeft: Radius.circular(10), - ), + borderRadius: const BorderRadius.only(topRight: CustomSize.radius, bottomLeft: CustomSize.radius), color: item.labelColor != null ? stringToColor(item.labelColor!) : const Color.fromARGB(255, 230, 173, 58), diff --git a/lib/page/creative_island/draw/components/content_preview.dart b/lib/page/creative_island/draw/components/content_preview.dart index fd75ccca..a47b6fdd 100644 --- a/lib/page/creative_island/draw/components/content_preview.dart +++ b/lib/page/creative_island/draw/components/content_preview.dart @@ -22,20 +22,17 @@ class CreativeIslandContentPreview extends StatefulWidget { }); @override - State createState() => - _CreativeIslandContentPreviewState(); + State createState() => _CreativeIslandContentPreviewState(); } -class _CreativeIslandContentPreviewState - extends State { +class _CreativeIslandContentPreviewState extends State { var currentTime = DateTime.now().add(const Duration(days: 7)); @override Widget build(BuildContext context) { var customColors = Theme.of(context).extension()!; final expireTime = widget.item != null - ? DateFormat('y-MM-dd').format( - widget.item!.createdAt!.add(const Duration(days: 7)).toLocal()) + ? DateFormat('y-MM-dd').format(widget.item!.createdAt!.add(const Duration(days: 7)).toLocal()) : DateFormat('y-MM-dd').format(currentTime); return widget.result.text == '' ? const Center( @@ -81,9 +78,7 @@ class _CreativeIslandContentPreviewState vertical: 5, horizontal: 10, ), - child: (widget.item != null && - (widget.item!.isVideoType)) || - e.endsWith('.mp4') + child: (widget.item != null && (widget.item!.isVideoType)) || e.endsWith('.mp4') ? _buildVideoPreviewer( widget.result.params ?? {}, e, @@ -121,9 +116,7 @@ class _CreativeIslandContentPreviewState return NetworkImagePreviewer( url: e, preview: imageURL(e, qiniuImageTypeThumb), - original: params['image'] == null || params['image'] == '' - ? null - : params['image'] as String, + original: params['image'] == null || params['image'] == '' ? null : params['image'] as String, description: widget.prompt ?? widget.item?.prompt ?? '', ); } diff --git a/lib/page/creative_island/draw/components/creative_item.dart b/lib/page/creative_island/draw/components/creative_item.dart index 93f79141..e141c11b 100644 --- a/lib/page/creative_island/draw/components/creative_item.dart +++ b/lib/page/creative_island/draw/components/creative_item.dart @@ -1,5 +1,6 @@ import 'package:askaide/page/component/image.dart'; import 'package:askaide/page/component/prompt_tags_selector.dart'; +import 'package:askaide/page/component/theme/custom_size.dart'; import 'package:askaide/page/component/theme/custom_theme.dart'; import 'package:flutter/material.dart'; @@ -24,9 +25,9 @@ class CreativeItem extends StatelessWidget { Widget build(BuildContext context) { final customColors = Theme.of(context).extension()!; return Material( - borderRadius: BorderRadius.circular(10), + borderRadius: CustomSize.borderRadius, child: InkWell( - borderRadius: BorderRadius.circular(10), + borderRadius: CustomSize.borderRadiusAll, onTap: onTap, child: Stack( children: [ @@ -34,7 +35,7 @@ class CreativeItem extends StatelessWidget { width: double.infinity, height: double.infinity, child: ClipRRect( - borderRadius: BorderRadius.circular(10), + borderRadius: CustomSize.borderRadius, child: CachedNetworkImageEnhanced( imageUrl: imageURL, fit: BoxFit.cover, diff --git a/lib/page/creative_island/draw/components/image_selector.dart b/lib/page/creative_island/draw/components/image_selector.dart index 0ad61f34..99b2a622 100644 --- a/lib/page/creative_island/draw/components/image_selector.dart +++ b/lib/page/creative_island/draw/components/image_selector.dart @@ -4,6 +4,7 @@ import 'package:askaide/helper/haptic_feedback.dart'; import 'package:askaide/helper/platform.dart'; import 'package:askaide/lang/lang.dart'; import 'package:askaide/page/component/image.dart'; +import 'package:askaide/page/component/theme/custom_size.dart'; import 'package:askaide/page/component/theme/custom_theme.dart'; import 'package:file_picker/file_picker.dart'; import 'package:flutter/material.dart'; @@ -53,14 +54,13 @@ class ImageSelector extends StatelessWidget { ), if (title != null) const SizedBox(height: 10), Material( - borderRadius: BorderRadius.circular(8), + borderRadius: CustomSize.borderRadius, color: customColors.backgroundColor, child: InkWell( - borderRadius: BorderRadius.circular(8), + borderRadius: CustomSize.borderRadiusAll, onTap: () async { HapticFeedbackHelper.mediumImpact(); - FilePickerResult? result = - await FilePicker.platform.pickFiles(type: FileType.image); + FilePickerResult? result = await FilePicker.platform.pickFiles(type: FileType.image); if (result != null && result.files.isNotEmpty) { if (PlatformTool.isWeb()) { onImageSelected(data: result.files.first.bytes!); @@ -70,30 +70,23 @@ class ImageSelector extends StatelessWidget { } }, child: Container( - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(8), - ), + decoration: BoxDecoration(borderRadius: CustomSize.borderRadius), child: ClipRRect( - borderRadius: BorderRadius.circular(8), + borderRadius: CustomSize.borderRadius, child: Stack( children: [ Container( - decoration: (selectedImagePath != null && - selectedImagePath!.isNotEmpty) || - (selectedImageData != null && - selectedImageData!.isNotEmpty) + decoration: (selectedImagePath != null && selectedImagePath!.isNotEmpty) || + (selectedImageData != null && selectedImageData!.isNotEmpty) ? BoxDecoration( image: DecorationImage( image: (selectedImagePath != null ? resolveImageProvider(selectedImagePath!) - : (selectedImageData != null - ? MemoryImage(selectedImageData!) - : null))!, + : (selectedImageData != null ? MemoryImage(selectedImageData!) : null))!, fit: BoxFit.cover, ), - color: customColors.backgroundContainerColor - ?.withAlpha(100), - borderRadius: BorderRadius.circular(8), + color: customColors.backgroundContainerColor?.withAlpha(100), + borderRadius: CustomSize.borderRadius, ) : null, child: SizedBox( @@ -118,8 +111,7 @@ class ImageSelector extends StatelessWidget { style: TextStyle( fontSize: 14, fontWeight: FontWeight.w500, - color: customColors.chatInputPanelText - ?.withOpacity(0.8), + color: customColors.chatInputPanelText?.withOpacity(0.8), ), ), ], @@ -146,8 +138,7 @@ class ImageSelector extends StatelessWidget { style: TextStyle( fontSize: 14, fontWeight: FontWeight.w500, - color: - Color.fromARGB(147, 255, 255, 255), + color: Color.fromARGB(147, 255, 255, 255), ), ), ], diff --git a/lib/page/creative_island/draw/components/image_size.dart b/lib/page/creative_island/draw/components/image_size.dart index fdc27cac..25368410 100644 --- a/lib/page/creative_island/draw/components/image_size.dart +++ b/lib/page/creative_island/draw/components/image_size.dart @@ -1,3 +1,4 @@ +import 'package:askaide/page/component/theme/custom_size.dart'; import 'package:askaide/page/component/theme/custom_theme.dart'; import 'package:flutter/material.dart'; @@ -27,7 +28,7 @@ class ImageSize extends StatelessWidget { width: width, height: height, decoration: BoxDecoration( - borderRadius: BorderRadius.circular(2), + borderRadius: CustomSize.borderRadius, color: customColors.backgroundContainerColor, ), alignment: Alignment.center, diff --git a/lib/page/creative_island/draw/components/image_style_selector.dart b/lib/page/creative_island/draw/components/image_style_selector.dart index c17e6e4f..93840063 100644 --- a/lib/page/creative_island/draw/components/image_style_selector.dart +++ b/lib/page/creative_island/draw/components/image_style_selector.dart @@ -2,6 +2,7 @@ import 'package:askaide/lang/lang.dart'; import 'package:askaide/page/component/enhanced_input.dart'; import 'package:askaide/page/component/image.dart'; import 'package:askaide/page/component/dialog.dart'; +import 'package:askaide/page/component/theme/custom_size.dart'; import 'package:askaide/page/component/theme/custom_theme.dart'; import 'package:askaide/repo/api/creative.dart'; import 'package:flutter/material.dart'; @@ -40,9 +41,7 @@ class ImageStyleSelector extends StatelessWidget { // const SizedBox(width: 10), _buildImageStyleItemPreview( customColors, - selectedStyle == null - ? CreativeIslandImageFilter(id: 0, name: '', previewImage: '') - : selectedStyle!, + selectedStyle == null ? CreativeIslandImageFilter(id: 0, name: '', previewImage: '') : selectedStyle!, size: 50, ), ], @@ -57,11 +56,7 @@ class ImageStyleSelector extends StatelessWidget { mainAxisSpacing: 20, padding: const EdgeInsets.only(top: 20, bottom: 20), children: [ - for (var item in [ - CreativeIslandImageFilter( - id: 0, name: '自动', previewImage: ''), - ...styles - ]) + for (var item in [CreativeIslandImageFilter(id: 0, name: '自动', previewImage: ''), ...styles]) InkWell( onTap: () { onSelected(item); @@ -107,9 +102,8 @@ class ImageStyleSelector extends StatelessWidget { width: size, height: size, decoration: BoxDecoration( - borderRadius: BorderRadius.circular(5), - border: showSelected && - (selectedStyle != null && style.id == selectedStyle!.id) + borderRadius: CustomSize.borderRadius, + border: showSelected && (selectedStyle != null && style.id == selectedStyle!.id) ? Border.all( color: customColors.linkColor ?? Colors.green, width: 1, diff --git a/lib/page/creative_island/draw/draw_create.dart b/lib/page/creative_island/draw/draw_create.dart index 0f0b2072..60ae0230 100644 --- a/lib/page/creative_island/draw/draw_create.dart +++ b/lib/page/creative_island/draw/draw_create.dart @@ -211,7 +211,7 @@ class _DrawCreateScreenState extends State { icon: const Icon(Icons.arrow_back_ios), ), toolbarHeight: CustomSize.toolbarHeight, - backgroundColor: customColors.backgroundContainerColor, + backgroundColor: customColors.backgroundColor, actions: [ if (widget.note != null) IconButton( @@ -222,10 +222,10 @@ class _DrawCreateScreenState extends State { ) ], ), - backgroundColor: customColors.backgroundContainerColor, + backgroundColor: customColors.backgroundColor, body: BackgroundContainer( setting: widget.setting, - enabled: true, + enabled: false, maxWidth: CustomSize.smallWindowSize, child: Column( children: [ @@ -542,7 +542,7 @@ class _DrawCreateScreenState extends State { vertical: 3, ), decoration: BoxDecoration( - borderRadius: BorderRadius.circular(5), + borderRadius: CustomSize.borderRadius, color: modelTypeTagColors[e.vendor!], ), child: Text( diff --git a/lib/page/creative_island/draw/draw_list.dart b/lib/page/creative_island/draw/draw_list.dart index a5a5ba14..551ec645 100644 --- a/lib/page/creative_island/draw/draw_list.dart +++ b/lib/page/creative_island/draw/draw_list.dart @@ -28,9 +28,7 @@ class _DrawListScreenState extends State { userSignedIn = true; } - context - .read() - .add(CreativeIslandItemsV2LoadEvent(forceRefresh: false)); + context.read().add(CreativeIslandItemsV2LoadEvent(forceRefresh: false)); super.initState(); } @@ -47,8 +45,9 @@ class _DrawListScreenState extends State { final customColors = Theme.of(context).extension()!; return BackgroundContainer( setting: widget.setting, + enabled: false, child: Scaffold( - backgroundColor: Colors.transparent, + backgroundColor: customColors.backgroundColor, body: _buildIslandItems(customColors), ), ); @@ -83,8 +82,7 @@ class _DrawListScreenState extends State { fit: BoxFit.cover, ), child: BlocBuilder( - buildWhen: (previous, current) => - current is CreativeIslandItemsV2Loaded, + buildWhen: (previous, current) => current is CreativeIslandItemsV2Loaded, builder: (context, state) { if (state is CreativeIslandItemsV2Loaded) { final items = state.items @@ -109,15 +107,11 @@ class _DrawListScreenState extends State { .toList(); final largeItems = items.where((e) => e.size == 'large').toList(); final mediumItems = items.where((e) => e.size == 'medium').toList(); - final otherItems = items - .where((e) => e.size != 'large' && e.size != 'medium') - .toList(); + final otherItems = items.where((e) => e.size != 'large' && e.size != 'medium').toList(); return RefreshIndicator( onRefresh: () async { - context - .read() - .add(CreativeIslandItemsV2LoadEvent(forceRefresh: true)); + context.read().add(CreativeIslandItemsV2LoadEvent(forceRefresh: true)); }, color: customColors.linkColor, displacement: 20, @@ -126,45 +120,39 @@ class _DrawListScreenState extends State { children: [ GridView.count( physics: const NeverScrollableScrollPhysics(), - padding: - const EdgeInsets.only(top: 0, left: 10, right: 10), + padding: const EdgeInsets.only(top: 0, left: 10, right: 10), crossAxisCount: _calCrossAxisCount(context), childAspectRatio: 2, shrinkWrap: true, children: largeItems .map((e) => Container( - padding: const EdgeInsets.symmetric( - horizontal: 5, vertical: 10), + padding: const EdgeInsets.symmetric(horizontal: 5, vertical: 10), child: e, )) .toList(), ), GridView.count( physics: const NeverScrollableScrollPhysics(), - padding: - const EdgeInsets.only(top: 5, left: 10, right: 10), + padding: const EdgeInsets.only(top: 5, left: 10, right: 10), crossAxisCount: _calCrossAxisCount(context) * 2, childAspectRatio: 1, shrinkWrap: true, children: mediumItems .map((e) => Container( - padding: const EdgeInsets.symmetric( - horizontal: 5, vertical: 5), + padding: const EdgeInsets.symmetric(horizontal: 5, vertical: 5), child: e, )) .toList(), ), GridView.count( physics: const NeverScrollableScrollPhysics(), - padding: - const EdgeInsets.only(top: 5, left: 10, right: 10), + padding: const EdgeInsets.only(top: 5, left: 10, right: 10), crossAxisCount: _calCrossAxisCount(context) * 2, childAspectRatio: 2, shrinkWrap: true, children: otherItems .map((e) => Container( - padding: const EdgeInsets.symmetric( - horizontal: 5, vertical: 5), + padding: const EdgeInsets.symmetric(horizontal: 5, vertical: 5), child: e, )) .toList(), diff --git a/lib/page/creative_island/draw/draw_result.dart b/lib/page/creative_island/draw/draw_result.dart index 8e415bf0..808fe5fa 100644 --- a/lib/page/creative_island/draw/draw_result.dart +++ b/lib/page/creative_island/draw/draw_result.dart @@ -61,7 +61,7 @@ class _DrawResultPageState extends State { }, ), ), - backgroundColor: customColors.backgroundContainerColor, + backgroundColor: customColors.backgroundColor, body: FutureBuilder( future: widget.future, builder: (context, snapshot) { @@ -150,8 +150,7 @@ class _DrawResultPageState extends State { Text( '如队列太长,将会有数分钟等待时间', style: TextStyle( - color: customColors.backgroundInvertedColor - ?.withAlpha(150), + color: customColors.backgroundInvertedColor?.withAlpha(150), fontSize: 10, ), ) diff --git a/lib/page/creative_island/draw/image_edit_direct.dart b/lib/page/creative_island/draw/image_edit_direct.dart index 663dd6b9..6fab1a4e 100644 --- a/lib/page/creative_island/draw/image_edit_direct.dart +++ b/lib/page/creative_island/draw/image_edit_direct.dart @@ -67,9 +67,7 @@ class _ImageEditDirectScreenState extends State { if (widget.note != null) { if (widget.apiEndpoint == 'image-to-video') { - Cache() - .boolGet(key: 'creative:tutorials:${widget.apiEndpoint}:dialog') - .then((show) { + Cache().boolGet(key: 'creative:tutorials:${widget.apiEndpoint}:dialog').then((show) { if (!show) { return; } @@ -83,9 +81,7 @@ class _ImageEditDirectScreenState extends State { }); }); } else { - Cache() - .boolGet(key: 'creative:tutorials:${widget.apiEndpoint}:dialog') - .then((show) { + Cache().boolGet(key: 'creative:tutorials:${widget.apiEndpoint}:dialog').then((show) { if (!show) { return; } @@ -156,7 +152,7 @@ class _ImageEditDirectScreenState extends State { icon: const Icon(Icons.arrow_back_ios), ), toolbarHeight: CustomSize.toolbarHeight, - backgroundColor: customColors.backgroundContainerColor, + backgroundColor: customColors.backgroundColor, actions: [ if (widget.note != null && widget.apiEndpoint == 'image-to-video') IconButton( @@ -174,19 +170,17 @@ class _ImageEditDirectScreenState extends State { ), ], ), - backgroundColor: customColors.backgroundContainerColor, + backgroundColor: customColors.backgroundColor, body: BackgroundContainer( setting: widget.setting, - enabled: true, + enabled: false, maxWidth: CustomSize.smallWindowSize, child: Column( children: [ - if (Ability().showGlobalAlert) - const GlobalAlert(pageKey: 'creative_create'), + if (Ability().showGlobalAlert) const GlobalAlert(pageKey: 'creative_create'), Expanded( child: Container( - padding: - const EdgeInsets.symmetric(horizontal: 10, vertical: 10), + padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 10), height: double.infinity, child: SingleChildScrollView( child: buildEditPanel(context, customColors), @@ -251,8 +245,7 @@ class _ImageEditDirectScreenState extends State { type: QuickAlertType.info, text: 'How strongly the video sticks to the original image. \nUse lower values to allow the model more freedom to make changes and higher values to correct motion distortions', - confirmBtnText: - AppLocale.gotIt.getString(context), + confirmBtnText: AppLocale.gotIt.getString(context), showCancelBtn: false, ); }, @@ -311,8 +304,7 @@ class _ImageEditDirectScreenState extends State { type: QuickAlertType.info, text: 'Lower values generally result in less motion in the output video, \nwhile higher values generally result in more motion', - confirmBtnText: - AppLocale.gotIt.getString(context), + confirmBtnText: AppLocale.gotIt.getString(context), showCancelBtn: false, ); }, @@ -396,8 +388,7 @@ class _ImageEditDirectScreenState extends State { }); }, ), - if (widget.apiEndpoint == 'image-to-video') - const SizedBox(width: 10), + if (widget.apiEndpoint == 'image-to-video') const SizedBox(width: 10), Expanded( flex: 1, child: EnhancedButton( @@ -466,21 +457,17 @@ class _ImageEditDirectScreenState extends State { ); if (selectedImagePath != null && - (selectedImagePath!.startsWith('http://') || - selectedImagePath!.startsWith('https://'))) { + (selectedImagePath!.startsWith('http://') || selectedImagePath!.startsWith('https://'))) { params['image'] = selectedImagePath; cancel(); } else { if (selectedImagePath != null && selectedImagePath!.isNotEmpty) { - final uploadRes = await ImageUploader(widget.setting) - .upload(selectedImagePath!) - .whenComplete(() => cancel()); + final uploadRes = + await ImageUploader(widget.setting).upload(selectedImagePath!).whenComplete(() => cancel()); params['image'] = uploadRes.url; - } else if (selectedImageData != null && - selectedImageData!.isNotEmpty) { - final uploadRes = await ImageUploader(widget.setting) - .uploadData(selectedImageData!) - .whenComplete(() => cancel()); + } else if (selectedImageData != null && selectedImageData!.isNotEmpty) { + final uploadRes = + await ImageUploader(widget.setting).uploadData(selectedImageData!).whenComplete(() => cancel()); params['image'] = uploadRes.url; } } @@ -541,9 +528,7 @@ class _ImageEditDirectScreenState extends State { final resp = await APIServer().asyncTaskStatus(taskId); switch (resp.status) { case 'success': - if (params != null && - resp.originImage != null && - resp.originImage != '') { + if (params != null && resp.originImage != null && resp.originImage != '') { params['image'] = resp.originImage; } if (params != null && resp.width != null) { diff --git a/lib/page/creative_island/gallery/components/image_card.dart b/lib/page/creative_island/gallery/components/image_card.dart index da4a6669..f2daa7b7 100644 --- a/lib/page/creative_island/gallery/components/image_card.dart +++ b/lib/page/creative_island/gallery/components/image_card.dart @@ -2,6 +2,7 @@ import 'package:askaide/helper/constant.dart'; import 'package:askaide/helper/image.dart'; import 'package:askaide/page/component/image.dart'; import 'package:askaide/page/component/random_avatar.dart'; +import 'package:askaide/page/component/theme/custom_size.dart'; import 'package:askaide/page/component/theme/custom_theme.dart'; import 'package:flutter/material.dart'; @@ -27,8 +28,8 @@ class ImageCard extends StatelessWidget { onTap: onTap, child: Container( decoration: BoxDecoration( - color: customColors.columnBlockBackgroundColor, - borderRadius: BorderRadius.circular(8), + color: customColors.backgroundContainerColor, + borderRadius: CustomSize.borderRadius, ), child: Column( mainAxisSize: MainAxisSize.min, @@ -38,15 +39,11 @@ class ImageCard extends StatelessWidget { minHeight: 50, ), child: ClipRRect( - borderRadius: const BorderRadius.only( - topLeft: Radius.circular(8), - topRight: Radius.circular(8), - ), + borderRadius: const BorderRadius.only(topLeft: CustomSize.radius, topRight: CustomSize.radius), child: images.isEmpty ? Image.asset('assets/image-broken.png') : CachedNetworkImageEnhanced( - imageUrl: - imageURL(images.first, qiniuImageTypeThumbMedium), + imageUrl: imageURL(images.first, qiniuImageTypeThumbMedium), fit: BoxFit.cover, ), ), diff --git a/lib/page/creative_island/gallery/gallery.dart b/lib/page/creative_island/gallery/gallery.dart index 490d21d5..1ebe1abe 100644 --- a/lib/page/creative_island/gallery/gallery.dart +++ b/lib/page/creative_island/gallery/gallery.dart @@ -40,7 +40,7 @@ class _GalleryScreenState extends State { return BackgroundContainer( setting: widget.setting, child: Scaffold( - backgroundColor: Colors.transparent, + backgroundColor: customColors.backgroundColor, body: _buildIslandItems(customColors), ), ); @@ -85,14 +85,12 @@ class _GalleryScreenState extends State { username: item.username, userId: item.userId, hotValue: item.hotValue, - onTap: () => - context.push('/creative-draw/gallery/${item.id}'), + onTap: () => context.push('/creative-draw/gallery/${item.id}'), ); }, sourceList: datasource, padding: const EdgeInsets.all(10), - extendedListDelegate: - SliverWaterfallFlowDelegateWithFixedCrossAxisCount( + extendedListDelegate: SliverWaterfallFlowDelegateWithFixedCrossAxisCount( crossAxisCount: _calCrossAxisCount(context), crossAxisSpacing: 10, mainAxisSpacing: 10, diff --git a/lib/page/creative_island/gallery/gallery_item.dart b/lib/page/creative_island/gallery/gallery_item.dart index 538cb085..bb692331 100644 --- a/lib/page/creative_island/gallery/gallery_item.dart +++ b/lib/page/creative_island/gallery/gallery_item.dart @@ -55,29 +55,24 @@ class _GalleryItemScreenState extends State { icon: const Icon(Icons.close), onPressed: () => Navigator.of(context).pop(), ), - backgroundColor: customColors.backgroundContainerColor?.withAlpha(200), + backgroundColor: customColors.backgroundColor, toolbarHeight: CustomSize.toolbarHeight, actions: [ BlocBuilder( buildWhen: (previous, current) => current is GalleryItemLoaded, builder: (context, state) { - if (state is GalleryItemLoaded && - state.isInternalUser && - state.item.status == 1) { + if (state is GalleryItemLoaded && state.isInternalUser && state.item.status == 1) { return TextButton( onPressed: () { openConfirmDialog( context, '确认取消?', () => APIServer() - .cancelShareCreativeHistoryToGallery( - historyId: state.item.creativeHistoryId!) + .cancelShareCreativeHistoryToGallery(historyId: state.item.creativeHistoryId!) .then((value) { - showSuccessMessage( - AppLocale.operateSuccess.getString(context)); + showSuccessMessage(AppLocale.operateSuccess.getString(context)); - context.read().add(GalleryItemLoadEvent( - id: widget.galleryId, forceRefresh: true)); + context.read().add(GalleryItemLoadEvent(id: widget.galleryId, forceRefresh: true)); }), ); }, @@ -97,7 +92,7 @@ class _GalleryItemScreenState extends State { ], ), extendBodyBehindAppBar: true, - backgroundColor: customColors.backgroundContainerColor, + backgroundColor: customColors.backgroundColor, body: BackgroundContainer( setting: widget.setting, enabled: false, @@ -114,8 +109,7 @@ class _GalleryItemScreenState extends State { child: SingleChildScrollView( child: SafeArea( child: Container( - padding: const EdgeInsets.symmetric( - horizontal: 5, vertical: 10), + padding: const EdgeInsets.symmetric(horizontal: 5, vertical: 10), child: Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.center, @@ -124,14 +118,13 @@ class _GalleryItemScreenState extends State { Container( decoration: BoxDecoration( color: customColors.backgroundColor, - borderRadius: BorderRadius.circular(8), + borderRadius: CustomSize.borderRadius, ), // padding: const EdgeInsets.symmetric( // horizontal: 10, // vertical: 10, // ), - margin: const EdgeInsets.symmetric( - horizontal: 5, vertical: 5), + margin: const EdgeInsets.symmetric(horizontal: 5, vertical: 5), child: NetworkImagePreviewer( url: img, preview: imageURL(img, qiniuImageTypeThumb), @@ -144,8 +137,7 @@ class _GalleryItemScreenState extends State { vertical: 8, ), child: Row( - mainAxisAlignment: - MainAxisAlignment.spaceBetween, + mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Row( children: [ @@ -188,14 +180,12 @@ class _GalleryItemScreenState extends State { innerPanding: 10, padding: const EdgeInsets.all(15), children: [ - if (state.item.prompt != null && - state.item.prompt!.isNotEmpty) + if (state.item.prompt != null && state.item.prompt!.isNotEmpty) TextItem( title: 'Prompt', value: state.item.prompt!, ), - if (state.item.negativePrompt != null && - state.item.negativePrompt!.isNotEmpty) + if (state.item.negativePrompt != null && state.item.negativePrompt!.isNotEmpty) TextItem( title: 'Negative Prompt', value: state.item.negativePrompt!, @@ -215,19 +205,16 @@ class _GalleryItemScreenState extends State { icon: const Icon(Icons.share, size: 14), width: 80, color: customColors.backgroundInvertedColor, - backgroundColor: - customColors.backgroundColor, + backgroundColor: customColors.backgroundColor, onPressed: () { Navigator.push( context, MaterialPageRoute( fullscreenDialog: true, - builder: (context) => - GalleryItemShareScreen( + builder: (context) => GalleryItemShareScreen( images: state.item.images, prompt: state.item.prompt, - negativePrompt: - state.item.negativePrompt, + negativePrompt: state.item.negativePrompt, ), ), ); @@ -239,21 +226,17 @@ class _GalleryItemScreenState extends State { icon: const Icon(Icons.webhook, size: 14), width: 80, color: customColors.backgroundInvertedColor, - backgroundColor: - customColors.backgroundColor, + backgroundColor: customColors.backgroundColor, onPressed: () { if (state.item.images.length > 1) { List> items = []; - for (var i = 0; - i < state.item.images.length; - i++) { + for (var i = 0; i < state.item.images.length; i++) { items.add(SelectorItem( NetworkImagePreviewer( url: state.item.images[i], notClickable: true, hidePreviewButton: true, - borderRadius: - BorderRadius.circular(8), + borderRadius: CustomSize.borderRadiusAll, ), state.item.images[i], )); @@ -275,8 +258,7 @@ class _GalleryItemScreenState extends State { horizontal: true, horizontalCount: 2, heightFactor: 0.8, - innerPadding: - const EdgeInsets.symmetric( + innerPadding: const EdgeInsets.symmetric( vertical: 10, ), title: '选择要执行动作的图片', @@ -470,8 +452,7 @@ class _TextItemState extends State { target: details.globalPosition, duration: const Duration(seconds: 8), animationDuration: const Duration(milliseconds: 200), - animationReverseDuration: - const Duration(milliseconds: 200), + animationReverseDuration: const Duration(milliseconds: 200), preferDirection: PreferDirection.topCenter, ignoreContentClick: false, onlyOne: true, @@ -481,8 +462,7 @@ class _TextItemState extends State { buttons: [ TextButton.icon( onPressed: () { - FlutterClipboard.copy(valueTranslated) - .then((value) { + FlutterClipboard.copy(valueTranslated).then((value) { showSuccessMessage('已复制到剪贴板'); }); cancel(); @@ -498,8 +478,7 @@ class _TextItemState extends State { ), Text( "复制", - style: TextStyle( - fontSize: 12, color: Colors.white), + style: TextStyle(fontSize: 12, color: Colors.white), ), ], ), diff --git a/lib/page/creative_island/my_creation.dart b/lib/page/creative_island/my_creation.dart index f7975124..39cf6364 100644 --- a/lib/page/creative_island/my_creation.dart +++ b/lib/page/creative_island/my_creation.dart @@ -23,8 +23,7 @@ import 'package:loading_more_list/loading_more_list.dart'; class MyCreationScreen extends StatefulWidget { final SettingRepository setting; final String mode; - const MyCreationScreen( - {super.key, required this.setting, required this.mode}); + const MyCreationScreen({super.key, required this.setting, required this.mode}); @override State createState() => _MyCreationScreenState(); @@ -52,7 +51,7 @@ class _MyCreationScreenState extends State { centerTitle: true, toolbarHeight: CustomSize.toolbarHeight, ), - backgroundColor: customColors.backgroundContainerColor, + backgroundColor: customColors.backgroundColor, body: BackgroundContainer( setting: widget.setting, enabled: false, @@ -61,30 +60,25 @@ class _MyCreationScreenState extends State { child: RefreshIndicator( color: customColors.linkColor, onRefresh: () async { - context.read().add( - CreativeIslandHistoriesAllLoadEvent( - forceRefresh: true, mode: widget.mode)); + context + .read() + .add(CreativeIslandHistoriesAllLoadEvent(forceRefresh: true, mode: widget.mode)); }, child: LoadingMoreList( ListConfig( - extendedListDelegate: - SliverWaterfallFlowDelegateWithFixedCrossAxisCount( + extendedListDelegate: SliverWaterfallFlowDelegateWithFixedCrossAxisCount( crossAxisCount: _calCrossAxisCount(context), crossAxisSpacing: 10, mainAxisSpacing: 10, ), itemBuilder: (context, item, index) { return Material( - // color: customColors.chatExampleItemBackground, - borderRadius: const BorderRadius.all( - Radius.circular(10), - ), - // color: Colors.transparent, + color: customColors.backgroundContainerColor, + borderRadius: CustomSize.borderRadius, child: InkWell( - borderRadius: BorderRadius.circular(10), + borderRadius: CustomSize.borderRadiusAll, onTap: () { - context.push( - '/creative-island/${item.islandId}/history/${item.id}?show_error=true'); + context.push('/creative-island/${item.islandId}/history/${item.id}?show_error=true'); }, onLongPress: () { openModalBottomSheet( @@ -107,8 +101,7 @@ class _MyCreationScreenState extends State { }, size: const ButtonSize.full(), color: customColors.weakLinkColor, - backgroundColor: const Color.fromARGB( - 36, 222, 222, 222), + backgroundColor: const Color.fromARGB(36, 222, 222, 222), ), const SizedBox(height: 10), Button( @@ -125,17 +118,13 @@ class _MyCreationScreenState extends State { }, size: const ButtonSize.full(), color: customColors.weakLinkColor, - backgroundColor: const Color.fromARGB( - 36, 222, 222, 222), + backgroundColor: const Color.fromARGB(36, 222, 222, 222), ), const SizedBox(height: 10), Button( - title: - AppLocale.cancel.getString(context), - backgroundColor: const Color.fromARGB( - 36, 222, 222, 222), - color: customColors.dialogDefaultTextColor - ?.withAlpha(150), + title: AppLocale.cancel.getString(context), + backgroundColor: const Color.fromARGB(36, 222, 222, 222), + color: customColors.dialogDefaultTextColor?.withAlpha(150), onPressed: () { context.pop(); }, @@ -156,8 +145,7 @@ class _MyCreationScreenState extends State { children: [ _buildAnswerImagePreview(context, item), // TODO 风格名称,测试阶段使用 - if (item.filterName != null && - item.filterName!.isNotEmpty) + if (item.filterName != null && item.filterName!.isNotEmpty) Positioned( bottom: 0, child: Container( @@ -168,9 +156,7 @@ class _MyCreationScreenState extends State { decoration: BoxDecoration( color: Colors.black.withOpacity(0.5), borderRadius: const BorderRadius.only( - topRight: Radius.circular(8), - bottomLeft: Radius.circular(8), - ), + topRight: CustomSize.radius, bottomLeft: CustomSize.radius), ), child: Text( item.filterName!, @@ -193,8 +179,8 @@ class _MyCreationScreenState extends State { decoration: BoxDecoration( color: Colors.black.withOpacity(0.5), borderRadius: const BorderRadius.only( - topRight: Radius.circular(8), - bottomLeft: Radius.circular(8), + topRight: CustomSize.radius, + bottomLeft: CustomSize.radius, ), ), child: const Text( @@ -218,8 +204,7 @@ class _MyCreationScreenState extends State { humanTime(item.createdAt, withTime: true), style: TextStyle( fontSize: 12, - color: customColors.weakTextColor - ?.withAlpha(150), + color: customColors.weakTextColor?.withAlpha(150), ), ), ], @@ -270,8 +255,7 @@ class _MyCreationScreenState extends State { ); } - Widget buildIslandTypeText( - CustomColors customColors, CreativeItemInServer item) { + Widget buildIslandTypeText(CustomColors customColors, CreativeItemInServer item) { return Text( item.islandTitle ?? '', style: TextStyle( @@ -281,12 +265,9 @@ class _MyCreationScreenState extends State { ); } - void onItemDelete(BuildContext context, CreativeItemInServer item, int index, - {Function? onFinished}) { + void onItemDelete(BuildContext context, CreativeItemInServer item, int index, {Function? onFinished}) { openConfirmDialog(context, AppLocale.confirmDelete.getString(context), () { - APIServer() - .deleteCreativeHistoryItem(item.islandId, hisId: item.id) - .then((value) { + APIServer().deleteCreativeHistoryItem(item.islandId, hisId: item.id).then((value) { // datasource.refresh(true); datasource.removeAt(index); setState(() {}); @@ -308,13 +289,9 @@ class _MyCreationScreenState extends State { child: Stack( children: [ ClipRRect( - borderRadius: const BorderRadius.only( - topLeft: Radius.circular(8), - topRight: Radius.circular(8), - ), + borderRadius: const BorderRadius.only(topLeft: CustomSize.radius, topRight: CustomSize.radius), child: CachedNetworkImageEnhanced( - imageUrl: - imageURL(item.originalImage!, qiniuImageTypeThumbMedium), + imageUrl: imageURL(item.originalImage!, qiniuImageTypeThumbMedium), fit: BoxFit.cover, ), ), @@ -338,13 +315,9 @@ class _MyCreationScreenState extends State { child: Stack( children: [ ClipRRect( - borderRadius: const BorderRadius.only( - topLeft: Radius.circular(8), - topRight: Radius.circular(8), - ), + borderRadius: const BorderRadius.only(topLeft: CustomSize.radius, topRight: CustomSize.radius), child: CachedNetworkImageEnhanced( - imageUrl: - imageURL(item.images.first, qiniuImageTypeThumbMedium), + imageUrl: imageURL(item.images.first, qiniuImageTypeThumbMedium), fit: BoxFit.cover, ), ), @@ -356,10 +329,9 @@ class _MyCreationScreenState extends State { width: 60, height: 60, child: ClipRRect( - borderRadius: BorderRadius.circular(8), + borderRadius: CustomSize.borderRadius, child: CachedNetworkImageEnhanced( - imageUrl: - imageURL(item.params['image'], qiniuImageTypeAvatar), + imageUrl: imageURL(item.params['image'], qiniuImageTypeAvatar), fit: BoxFit.cover, ), ), diff --git a/lib/page/creative_island/my_creation_item.dart b/lib/page/creative_island/my_creation_item.dart index 7833dabf..ff34b3e5 100644 --- a/lib/page/creative_island/my_creation_item.dart +++ b/lib/page/creative_island/my_creation_item.dart @@ -31,8 +31,7 @@ class MyCreationItemPage extends StatefulWidget { State createState() => _MyCreationItemPageState(); } -class _MyCreationItemPageState extends State - with SingleTickerProviderStateMixin { +class _MyCreationItemPageState extends State with SingleTickerProviderStateMixin { late final TabController _tabController; @override @@ -42,9 +41,7 @@ class _MyCreationItemPageState extends State _tabController = TabController(length: 2, vsync: this); _tabController.animateTo(1); - context - .read() - .add(CreativeIslandHistoryItemLoadEvent(widget.itemId)); + context.read().add(CreativeIslandHistoryItemLoadEvent(widget.itemId)); } @override @@ -59,8 +56,7 @@ class _MyCreationItemPageState extends State return BlocBuilder( buildWhen: (previous, current) => - current is CreativeIslandHistoryItemLoaded || - current is CreativeIslandHistoryItemLoading, + current is CreativeIslandHistoryItemLoaded || current is CreativeIslandHistoryItemLoading, builder: (context, state) { if (state is CreativeIslandHistoryItemLoaded) { return Scaffold( @@ -78,7 +74,7 @@ class _MyCreationItemPageState extends State ), actions: buildActions(state, context, customColors), ), - backgroundColor: customColors.backgroundContainerColor, + backgroundColor: customColors.backgroundColor, body: BackgroundContainer( setting: widget.setting, enabled: false, @@ -108,8 +104,7 @@ class _MyCreationItemPageState extends State horizontal: 10, ), children: [ - if (state.item!.prompt != null && - state.item!.prompt != '') + if (state.item!.prompt != null && state.item!.prompt != '') Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ @@ -157,20 +152,15 @@ class _MyCreationItemPageState extends State BuildContext context, CustomColors customColors, ) { - if (state.item!.userId != APIServer().localUserID() && - state.item!.isSuccessful) { + if (state.item!.userId != APIServer().localUserID() && state.item!.isSuccessful) { return [ TextButton( onPressed: () { openConfirmDialog(context, '确定封禁该项目?', () { - APIServer() - .forbidCreativeHistoryItem(historyId: state.item!.id) - .then((value) { + APIServer().forbidCreativeHistoryItem(historyId: state.item!.id).then((value) { showSuccessMessage(AppLocale.operateSuccess.getString(context)); - context - .read() - .add(CreativeIslandHistoryItemLoadEvent( + context.read().add(CreativeIslandHistoryItemLoadEvent( widget.itemId, forceRefresh: true, )); @@ -203,28 +193,19 @@ class _MyCreationItemPageState extends State TextButton( onPressed: () { if (state.item!.isShared) { - APIServer() - .cancelShareCreativeHistoryToGallery( - historyId: state.item!.id) - .then((value) { + APIServer().cancelShareCreativeHistoryToGallery(historyId: state.item!.id).then((value) { showSuccessMessage(AppLocale.operateSuccess.getString(context)); - context - .read() - .add(CreativeIslandHistoryItemLoadEvent( + context.read().add(CreativeIslandHistoryItemLoadEvent( widget.itemId, forceRefresh: true, )); }); } else { - APIServer() - .shareCreativeHistoryToGallery(historyId: state.item!.id) - .then((value) { + APIServer().shareCreativeHistoryToGallery(historyId: state.item!.id).then((value) { showSuccessMessage(AppLocale.operateSuccess.getString(context)); - context - .read() - .add(CreativeIslandHistoryItemLoadEvent( + context.read().add(CreativeIslandHistoryItemLoadEvent( widget.itemId, forceRefresh: true, )); @@ -264,9 +245,7 @@ class _MyCreationItemPageState extends State ), const SizedBox(height: 10), SelectableText( - widget.showErrorMessage - ? '${state.item!.answer}' - : '错误代码:${state.item!.errorCode}', + widget.showErrorMessage ? '${state.item!.answer}' : '错误代码:${state.item!.errorCode}', textAlign: TextAlign.center, style: TextStyle( fontSize: 10, @@ -300,8 +279,7 @@ class _MyCreationItemPageState extends State ); } - List _buildItemArguments( - CreativeItemArguments arg, CustomColors customColors) { + List _buildItemArguments(CreativeItemArguments arg, CustomColors customColors) { final children = []; if (arg.negativePrompt != null && arg.negativePrompt != '') { diff --git a/lib/page/custom_scaffold.dart b/lib/page/custom_scaffold.dart index 63b4db20..14408642 100644 --- a/lib/page/custom_scaffold.dart +++ b/lib/page/custom_scaffold.dart @@ -69,6 +69,14 @@ class _CustomScaffoldState extends State { child: widget.appBarBackground, ), ), + leading: widget.drawer != null + ? Builder( + builder: (context) => IconButton( + icon: const Icon(Icons.sort), + onPressed: () => Scaffold.of(context).openDrawer(), + ), + ) + : null, ); } } diff --git a/lib/page/drawer.dart b/lib/page/drawer.dart index 561d39c0..6b686113 100644 --- a/lib/page/drawer.dart +++ b/lib/page/drawer.dart @@ -3,8 +3,8 @@ import 'package:askaide/bloc/chat_chat_bloc.dart'; import 'package:askaide/helper/platform.dart'; import 'package:askaide/lang/lang.dart'; import 'package:askaide/page/component/account_quota_card.dart'; -import 'package:askaide/page/component/icon_box.dart'; import 'package:askaide/page/component/image.dart'; +import 'package:askaide/page/component/theme/custom_theme.dart'; import 'package:askaide/repo/api/user.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; @@ -22,10 +22,12 @@ class LeftDrawer extends StatefulWidget { class _LeftDrawerState extends State { @override Widget build(BuildContext context) { + final customColors = Theme.of(context).extension()!; return Drawer( shape: const RoundedRectangleBorder( borderRadius: BorderRadius.zero, ), + backgroundColor: customColors.backgroundColor, child: SafeArea( top: false, child: Column( @@ -66,33 +68,6 @@ class _LeftDrawerState extends State { ), ), const SizedBox(height: 15), - Row( - mainAxisAlignment: MainAxisAlignment.spaceAround, - children: [ - IconBox( - icon: const Icon(Icons.group_outlined), - title: Text(AppLocale.homeTitle.getString(context)), - onTap: () { - context.push('/characters'); - }, - ), - IconBox( - icon: const Icon(Icons.auto_awesome_outlined), - title: Text(AppLocale.discover.getString(context)), - onTap: () { - context.push('/creative-gallery'); - }, - ), - IconBox( - icon: const Icon(Icons.palette_outlined), - title: Text(AppLocale.creativeIsland.getString(context)), - onTap: () { - context.push('/creative-draw'); - }, - ), - ], - ), - const SizedBox(height: 15), ListTile( leading: const Icon(Icons.history), title: Text(AppLocale.histories.getString(context)), @@ -102,6 +77,39 @@ class _LeftDrawerState extends State { }); }, ), + Divider( + color: customColors.weakTextColor?.withAlpha(50), + height: 10, + indent: 10, + endIndent: 10, + ), + ListTile( + leading: const Icon(Icons.group_outlined), + title: Text(AppLocale.homeTitle.getString(context)), + onTap: () { + context.push('/characters'); + }, + ), + ListTile( + leading: const Icon(Icons.auto_awesome_outlined), + title: Text(AppLocale.discover.getString(context)), + onTap: () { + context.push('/creative-gallery'); + }, + ), + ListTile( + leading: const Icon(Icons.palette_outlined), + title: Text(AppLocale.creativeIsland.getString(context)), + onTap: () { + context.push('/creative-draw'); + }, + ), + Divider( + color: customColors.weakTextColor?.withAlpha(50), + height: 10, + indent: 10, + endIndent: 10, + ), ListTile( leading: const Icon(Icons.settings_outlined), title: Text(AppLocale.settings.getString(context)), @@ -113,42 +121,81 @@ class _LeftDrawerState extends State { ), ), ), - SizedBox( - height: 70, + Container( + height: 90, + padding: const EdgeInsets.only(left: 20, right: 20), child: Column( + crossAxisAlignment: CrossAxisAlignment.start, children: [ - GestureDetector( - onTap: () { - launchUrlString( - 'https://weibo.com/code404', - mode: LaunchMode.externalApplication, - ); - }, - child: Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Text(AppLocale.socialMedia.getString(context)), - const SizedBox(width: 10), - Image.asset('assets/weibo.png', width: 25), - ], + Text( + "${AppLocale.socialMedia.getString(context)} :", + style: TextStyle( + color: customColors.weakTextColor, + fontSize: 16, + fontWeight: FontWeight.bold, ), ), - GestureDetector( - onTap: () { - launchUrlString( - 'https://ai.aicode.cc/social/github', - mode: LaunchMode.externalApplication, - ); - }, - child: Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Text(AppLocale.opensource.getString(context)), - const SizedBox(width: 10), - Image.asset('assets/github.png', width: 25), - ], - ), + const SizedBox(height: 10), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + GestureDetector( + onTap: () { + launchUrlString( + 'https://ai.aicode.cc/social/home', + mode: LaunchMode.externalApplication, + ); + }, + child: Image.asset('assets/app-256-transparent.png', width: 25), + ), + GestureDetector( + onTap: () { + launchUrlString( + 'https://weibo.com/code404', + mode: LaunchMode.externalApplication, + ); + }, + child: Image.asset('assets/weibo.png', width: 25), + ), + GestureDetector( + onTap: () { + launchUrlString( + 'https://ai.aicode.cc/social/github', + mode: LaunchMode.externalApplication, + ); + }, + child: Image.asset('assets/github.png', width: 25), + ), + GestureDetector( + onTap: () { + launchUrlString( + 'https://ai.aicode.cc/social/wechat-platform', + mode: LaunchMode.externalApplication, + ); + }, + child: Image.asset('assets/wechat.png', width: 25), + ), + GestureDetector( + onTap: () { + launchUrlString( + 'https://ai.aicode.cc/social/x', + mode: LaunchMode.externalApplication, + ); + }, + child: Image.asset('assets/x.png', width: 25), + ), + GestureDetector( + onTap: () { + launchUrlString( + 'https://ai.aicode.cc/social/xiaohongshu', + mode: LaunchMode.externalApplication, + ); + }, + child: Image.asset('assets/xiaohongshu.png', width: 25), + ), + ], ), + const SizedBox(height: 15), ], ), ), diff --git a/lib/page/home.dart b/lib/page/home.dart index 2f0f7154..5f42fe68 100644 --- a/lib/page/home.dart +++ b/lib/page/home.dart @@ -222,10 +222,10 @@ class _NewHomePageState extends State { final customColors = Theme.of(context).extension()!; return CustomScaffold( settings: widget.settings, - appBarBackground: Image.asset( - customColors.appBarBackgroundImage!, - fit: BoxFit.cover, - ), + // appBarBackground: Image.asset( + // customColors.appBarBackgroundImage!, + // fit: BoxFit.cover, + // ), showBackAppBar: chatPreviewController.selectMode, backAppBar: AppBar( title: Text( @@ -274,55 +274,57 @@ class _NewHomePageState extends State { width: MediaQuery.of(context).size.width / 2, child: Column( children: [ - BlocBuilder( - buildWhen: (previous, current) => current is ChatMessagesLoaded, - builder: (context, state) { - if (state is ChatMessagesLoaded) { - return Text( - state.chatHistory == null || state.chatHistory!.title == null - ? AppLocale.chatAnywhere.getString(context) - : state.chatHistory!.title!, - overflow: TextOverflow.ellipsis, - maxLines: 1, - style: const TextStyle( - fontSize: CustomSize.appBarTitleSize, - ), - ); - } - - return Text( - AppLocale.chatAnywhere.getString(context), - overflow: TextOverflow.ellipsis, - maxLines: 1, - style: const TextStyle(fontSize: CustomSize.appBarTitleSize), - ); - }, - ), - if (selectedModel != null) - Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Text( - selectedModel!.name, - style: TextStyle( - fontSize: CustomSize.appBarTitleSize * 0.6, - color: customColors.backgroundInvertedColor, - ), - ), - Icon( - Icons.unfold_more, + // BlocBuilder( + // buildWhen: (previous, current) => current is ChatMessagesLoaded, + // builder: (context, state) { + // if (state is ChatMessagesLoaded) { + // return Text( + // state.chatHistory == null || state.chatHistory!.title == null + // ? AppLocale.chatAnywhere.getString(context) + // : state.chatHistory!.title!, + // overflow: TextOverflow.ellipsis, + // maxLines: 1, + // style: const TextStyle( + // fontSize: CustomSize.appBarTitleSize, + // ), + // ); + // } + + // return Text( + // AppLocale.chatAnywhere.getString(context), + // overflow: TextOverflow.ellipsis, + // maxLines: 1, + // style: const TextStyle(fontSize: CustomSize.appBarTitleSize), + // ); + // }, + // ), + + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text( + selectedModel != null ? selectedModel!.name : AppLocale.selectModel.getString(context), + style: TextStyle( + fontSize: CustomSize.appBarTitleSize, color: customColors.backgroundInvertedColor, - size: CustomSize.appBarTitleSize * 0.6, + fontWeight: FontWeight.bold, ), - ], - ), + ), + const SizedBox(width: 3), + Icon( + Icons.arrow_forward_ios, + color: customColors.backgroundInvertedColor!.withAlpha(150), + size: CustomSize.appBarTitleSize * 0.8, + ), + ], + ), ], ), ), ), actions: [ IconButton( - icon: const Icon(Icons.post_add), + icon: const Icon(Icons.maps_ugc_outlined), onPressed: createNewChat, ), ], @@ -458,8 +460,8 @@ class _NewHomePageState extends State { Container( decoration: BoxDecoration( borderRadius: const BorderRadius.only( - topLeft: Radius.circular(10), - topRight: Radius.circular(10), + topLeft: CustomSize.radius, + topRight: CustomSize.radius, ), color: customColors.chatInputPanelBackground, ), @@ -519,15 +521,6 @@ class _NewHomePageState extends State { bool selectMode, ) { final loadedMessages = loadedState.messages as List; - if (room.room.initMessage != null && room.room.initMessage != '' && loadedMessages.isEmpty) { - loadedMessages.add( - Message( - Role.receiver, - room.room.initMessage!, - type: MessageType.initMessage, - ), - ); - } // 聊天内容为空时,显示示例页面 if (loadedMessages.isEmpty) { diff --git a/lib/page/lab/creative_models.dart b/lib/page/lab/creative_models.dart index 2c2bad1a..66b8ec19 100644 --- a/lib/page/lab/creative_models.dart +++ b/lib/page/lab/creative_models.dart @@ -45,9 +45,7 @@ class _CreativeModelScreenState extends State { }); }); - context - .read() - .add(CreativeIslandGalleryLoadEvent(mode: "all")); + context.read().add(CreativeIslandGalleryLoadEvent(mode: "all")); super.initState(); } @@ -67,7 +65,7 @@ class _CreativeModelScreenState extends State { ), centerTitle: true, ), - backgroundColor: customColors.chatInputPanelBackground, + backgroundColor: customColors.backgroundColor, body: BackgroundContainer( setting: widget.setting, enabled: false, @@ -104,15 +102,13 @@ class _CreativeModelScreenState extends State { Stack( children: [ Container( - padding: const EdgeInsets.only( - top: 25, bottom: 10), + padding: const EdgeInsets.only(top: 25, bottom: 10), alignment: Alignment.center, child: Text( e.modelName, textAlign: TextAlign.center, style: const TextStyle(fontSize: 14), - textWidthBasis: - TextWidthBasis.longestLine, + textWidthBasis: TextWidthBasis.longestLine, ), ), Positioned( @@ -124,8 +120,7 @@ class _CreativeModelScreenState extends State { vertical: 3, ), decoration: BoxDecoration( - borderRadius: - BorderRadius.circular(5), + borderRadius: CustomSize.borderRadius, color: modelTypeTagColors[e.vendor], ), child: Text( @@ -141,9 +136,7 @@ class _CreativeModelScreenState extends State { ), e.id, search: (keywrod) { - return e.modelName - .toLowerCase() - .contains(keywrod.toLowerCase()) || + return e.modelName.toLowerCase().contains(keywrod.toLowerCase()) || e.vendor.contains(keywrod.toLowerCase()); }, ), @@ -155,30 +148,22 @@ class _CreativeModelScreenState extends State { if (value.value == null) { selectedModel = null; selectedFilter = null; - context.read().add( - CreativeIslandGalleryLoadEvent(mode: "all")); + context.read().add(CreativeIslandGalleryLoadEvent(mode: "all")); return; } - selectedModel = imageModels - .firstWhere((e) => e.id == value.value); + selectedModel = imageModels.firstWhere((e) => e.id == value.value); if (selectedModel != null) { - final matchedFilters = imageModelFilters - .where( - (e) => e.modelId == selectedModel!.modelId) - .toList(); - selectedFilter = matchedFilters.isNotEmpty - ? matchedFilters.first - : null; - context.read().add( - CreativeIslandGalleryLoadEvent( - mode: "all", - model: selectedModel!.realModel)); + final matchedFilters = + imageModelFilters.where((e) => e.modelId == selectedModel!.modelId).toList(); + selectedFilter = matchedFilters.isNotEmpty ? matchedFilters.first : null; + context + .read() + .add(CreativeIslandGalleryLoadEvent(mode: "all", model: selectedModel!.realModel)); } else { selectedFilter = null; - context.read().add( - CreativeIslandGalleryLoadEvent(mode: "all")); + context.read().add(CreativeIslandGalleryLoadEvent(mode: "all")); } }); return true; @@ -196,8 +181,7 @@ class _CreativeModelScreenState extends State { if (selectedFilter != null) Row( children: [ - if (selectedFilter!.previewImage != null && - selectedFilter!.previewImage!.isNotEmpty) + if (selectedFilter!.previewImage != null && selectedFilter!.previewImage!.isNotEmpty) SizedBox( width: 70, height: 70, @@ -216,19 +200,15 @@ class _CreativeModelScreenState extends State { child: RefreshIndicator( color: customColors.linkColor, onRefresh: () async { - context - .read() - .add(CreativeIslandGalleryLoadEvent( + context.read().add(CreativeIslandGalleryLoadEvent( forceRefresh: true, mode: "all", model: selectedModel?.realModel, )); }, child: BlocConsumer( - listenWhen: (previous, current) => - current is CreativeIslandGalleryLoaded, - buildWhen: (previous, current) => - current is CreativeIslandGalleryLoaded, + listenWhen: (previous, current) => current is CreativeIslandGalleryLoaded, + buildWhen: (previous, current) => current is CreativeIslandGalleryLoaded, listener: (context, state) { if (state is CreativeIslandHistoriesAllLoaded) { if (state.error != null) { @@ -247,26 +227,19 @@ class _CreativeModelScreenState extends State { (e) { return GestureDetector( onTap: () { - context.push( - '/creative-island/${e.islandId}/history/${e.id}?show_error=true'); + context.push('/creative-island/${e.islandId}/history/${e.id}?show_error=true'); }, child: Container( - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(10), - ), + decoration: BoxDecoration(borderRadius: CustomSize.borderRadius), child: Stack( children: [ - if (e.firstImagePreview - .startsWith('http://') || - e.firstImagePreview - .startsWith('https://')) + if (e.firstImagePreview.startsWith('http://') || + e.firstImagePreview.startsWith('https://')) ClipRRect( - borderRadius: BorderRadius.circular(10), - child: e.firstImagePreview - .endsWith('.mp4') + borderRadius: CustomSize.borderRadius, + child: e.firstImagePreview.endsWith('.mp4') ? CachedNetworkImageEnhanced( - imageUrl: e.params['image'] ?? - e.firstImagePreview, + imageUrl: e.params['image'] ?? e.firstImagePreview, fit: BoxFit.cover, height: double.infinity, ) @@ -279,10 +252,8 @@ class _CreativeModelScreenState extends State { Container( padding: const EdgeInsets.all(5), decoration: BoxDecoration( - borderRadius: - BorderRadius.circular(8), - color: const Color.fromARGB( - 255, 148, 124, 245), + borderRadius: CustomSize.borderRadius, + color: const Color.fromARGB(255, 148, 124, 245), ), child: const Center( child: Text( @@ -301,8 +272,7 @@ class _CreativeModelScreenState extends State { Container( padding: const EdgeInsets.all(5), decoration: BoxDecoration( - borderRadius: - BorderRadius.circular(8), + borderRadius: CustomSize.borderRadius, color: Colors.amber, ), child: Center( @@ -327,10 +297,8 @@ class _CreativeModelScreenState extends State { vertical: 3, ), decoration: BoxDecoration( - color: customColors.backgroundColor - ?.withAlpha(200), - borderRadius: - BorderRadius.circular(8), + color: customColors.backgroundColor?.withAlpha(200), + borderRadius: CustomSize.borderRadius, ), child: Text( '${DateFormat('HH:mm').format(e.createdAt!.toLocal())}@${e.userId}#${e.id}', @@ -351,10 +319,9 @@ class _CreativeModelScreenState extends State { vertical: 5, ), decoration: BoxDecoration( - borderRadius: - const BorderRadius.only( - topLeft: Radius.circular(5), - bottomRight: Radius.circular(5), + borderRadius: const BorderRadius.only( + topLeft: CustomSize.radius, + bottomRight: CustomSize.radius, ), color: customColors.linkColor, ), diff --git a/lib/page/lab/user_center.dart b/lib/page/lab/user_center.dart index 5edff271..a72828a1 100644 --- a/lib/page/lab/user_center.dart +++ b/lib/page/lab/user_center.dart @@ -23,9 +23,7 @@ class _UserCenterScreenState extends State { @override void initState() { context.read().add(AccountLoadEvent()); - context - .read() - .add(CreativeIslandGalleryLoadEvent(mode: "default")); + context.read().add(CreativeIslandGalleryLoadEvent(mode: "default")); super.initState(); } @@ -51,10 +49,8 @@ class _UserCenterScreenState extends State { builder: (_, state) { if (state is AccountLoaded) { return BlocConsumer( - listenWhen: (previous, current) => - current is CreativeIslandGalleryLoaded, - buildWhen: (previous, current) => - current is CreativeIslandGalleryLoaded, + listenWhen: (previous, current) => current is CreativeIslandGalleryLoaded, + buildWhen: (previous, current) => current is CreativeIslandGalleryLoaded, listener: (context, state) { if (state is CreativeIslandHistoriesAllLoaded) { if (state.error != null) { @@ -71,9 +67,7 @@ class _UserCenterScreenState extends State { AccountQuotaCard( userInfo: state.user!, onPaymentReturn: () { - context - .read() - .add(AccountLoadEvent(cache: false)); + context.read().add(AccountLoadEvent(cache: false)); }, ), InviteCard(userInfo: state.user!), @@ -85,17 +79,14 @@ class _UserCenterScreenState extends State { shrinkWrap: true, physics: const NeverScrollableScrollPhysics(), cacheExtent: 100, - children: state2.items - .where((e) => e.images.isNotEmpty) - .map( + children: state2.items.where((e) => e.images.isNotEmpty).map( (e) { return GestureDetector( onTap: () { - context.push( - '/creative-island/${e.islandId}/history/${e.id}'); + context.push('/creative-island/${e.islandId}/history/${e.id}'); }, child: ClipRRect( - borderRadius: BorderRadius.circular(10), + borderRadius: CustomSize.borderRadius, child: CachedNetworkImageEnhanced( imageUrl: e.firstImagePreview, fit: BoxFit.cover, diff --git a/lib/page/setting/account_security.dart b/lib/page/setting/account_security.dart index 2f3692fd..1f202dfa 100644 --- a/lib/page/setting/account_security.dart +++ b/lib/page/setting/account_security.dart @@ -107,7 +107,7 @@ class _AccountSecurityScreenState extends State { ) ], ), - backgroundColor: Colors.transparent, + backgroundColor: customColors.backgroundColor, body: SafeArea( child: BlocConsumer( listenWhen: (previous, current) => current is AccountLoaded, @@ -297,7 +297,7 @@ Widget buildSettingsList( ), darkTheme: const SettingsThemeData( settingsListBackground: Colors.transparent, - settingsSectionBackground: Color.fromARGB(255, 27, 27, 27), + settingsSectionBackground: Color.fromARGB(255, 44, 44, 46), titleTextColor: Color.fromARGB(255, 239, 239, 239), ), sections: sections, diff --git a/lib/page/setting/article.dart b/lib/page/setting/article.dart index f96dbd0f..ba386e42 100644 --- a/lib/page/setting/article.dart +++ b/lib/page/setting/article.dart @@ -64,9 +64,10 @@ class _ArticleScreenState extends State { }, ), ), - backgroundColor: customColors.backgroundContainerColor, + backgroundColor: customColors.backgroundColor, body: BackgroundContainer( setting: widget.settings, + enabled: false, child: SafeArea( top: false, child: Container( diff --git a/lib/page/setting/background_selector.dart b/lib/page/setting/background_selector.dart index c3052526..8c27088f 100644 --- a/lib/page/setting/background_selector.dart +++ b/lib/page/setting/background_selector.dart @@ -8,6 +8,7 @@ import 'package:askaide/lang/lang.dart'; import 'package:askaide/page/component/enhanced_button.dart'; import 'package:askaide/page/component/image.dart'; import 'package:askaide/page/component/dialog.dart'; +import 'package:askaide/page/component/theme/custom_size.dart'; import 'package:askaide/page/component/theme/custom_theme.dart'; import 'package:askaide/repo/settings_repo.dart'; import 'package:file_picker/file_picker.dart'; @@ -85,7 +86,7 @@ class _BackgroundSelectorScreenState extends State { child: Stack( children: [ ClipRRect( - borderRadius: BorderRadius.circular(8), + borderRadius: CustomSize.borderRadius, child: Image.asset('assets/light-dark-auto.png'), ), Positioned( @@ -112,14 +113,14 @@ class _BackgroundSelectorScreenState extends State { }); }, child: ClipRRect( - borderRadius: BorderRadius.circular(8), + borderRadius: CustomSize.borderRadius, child: CachedNetworkImageEnhanced(imageUrl: img.preview), ), ), Material( - borderRadius: BorderRadius.circular(8), + borderRadius: CustomSize.borderRadius, child: InkWell( - borderRadius: BorderRadius.circular(8), + borderRadius: CustomSize.borderRadiusAll, onTap: () async { if (selectDialogOpened) return; @@ -141,7 +142,7 @@ class _BackgroundSelectorScreenState extends State { color: customColors.textFieldBorderColor!, style: BorderStyle.solid, ), - borderRadius: BorderRadius.circular(8), + borderRadius: CustomSize.borderRadius, ), padding: const EdgeInsets.all(10), child: Column( @@ -179,7 +180,7 @@ class _BackgroundSelectorScreenState extends State { const Text('图片预览'), const SizedBox(height: 10), ClipRRect( - borderRadius: BorderRadius.circular(8), + borderRadius: CustomSize.borderRadius, child: GestureDetector( onLongPressStart: (details) { setState(() { @@ -200,7 +201,7 @@ class _BackgroundSelectorScreenState extends State { ) : null, color: customColors.backgroundContainerColor, - borderRadius: BorderRadius.circular(8), + borderRadius: CustomSize.borderRadius, border: Border.all( color: customColors.textFieldBorderColor!, style: BorderStyle.solid, diff --git a/lib/page/setting/bind_phone_page.dart b/lib/page/setting/bind_phone_page.dart index 47a349a5..cbd52bd1 100644 --- a/lib/page/setting/bind_phone_page.dart +++ b/lib/page/setting/bind_phone_page.dart @@ -23,8 +23,7 @@ import 'package:go_router/go_router.dart'; class BindPhoneScreen extends StatefulWidget { final SettingRepository setting; final bool isSignIn; - const BindPhoneScreen( - {super.key, required this.setting, this.isSignIn = true}); + const BindPhoneScreen({super.key, required this.setting, this.isSignIn = true}); @override State createState() => _BindPhoneScreenState(); @@ -33,8 +32,7 @@ class BindPhoneScreen extends StatefulWidget { class _BindPhoneScreenState extends State { final TextEditingController _usernameController = TextEditingController(); final TextEditingController _inviteCodeController = TextEditingController(); - final TextEditingController _verificationCodeController = - TextEditingController(); + final TextEditingController _verificationCodeController = TextEditingController(); String verifyCodeId = ''; final phoneNumberValidator = RegExp(r"^1[3456789]\d{9}$"); @@ -87,8 +85,7 @@ class _BindPhoneScreenState extends State { leading: IconButton( onPressed: () { if (widget.isSignIn) { - context.go( - '${Ability().homeRoute}?show_initial_dialog=false&reward=0'); + context.go('${Ability().homeRoute}?show_initial_dialog=false&reward=0'); } else { context.pop(); } @@ -114,26 +111,20 @@ class _BindPhoneScreenState extends State { return Column( children: [ Padding( - padding: const EdgeInsets.only( - left: 15.0, right: 15.0, top: 15, bottom: 0), + padding: const EdgeInsets.only(left: 15.0, right: 15.0, top: 15, bottom: 0), child: TextFormField( controller: _usernameController, keyboardType: TextInputType.number, - inputFormatters: [ - FilteringTextInputFormatter.singleLineFormatter - ], + inputFormatters: [FilteringTextInputFormatter.singleLineFormatter], decoration: InputDecoration( border: const OutlineInputBorder(), enabledBorder: const OutlineInputBorder( - borderSide: BorderSide( - color: Color.fromARGB(200, 192, 192, 192)), + borderSide: BorderSide(color: Color.fromARGB(200, 192, 192, 192)), ), focusedBorder: OutlineInputBorder( - borderSide: BorderSide( - color: customColors.linkColor ?? Colors.green), + borderSide: BorderSide(color: customColors.linkColor ?? Colors.green), ), - floatingLabelStyle: - TextStyle(color: customColors.linkColor!), + floatingLabelStyle: TextStyle(color: customColors.linkColor!), isDense: true, floatingLabelBehavior: FloatingLabelBehavior.always, labelText: AppLocale.account.getString(context), @@ -147,25 +138,21 @@ class _BindPhoneScreenState extends State { ), ), Padding( - padding: const EdgeInsets.only( - left: 15.0, right: 5.0, top: 15, bottom: 0), + padding: const EdgeInsets.only(left: 15.0, right: 5.0, top: 15, bottom: 0), child: VerifyCodeInput( controller: _verificationCodeController, onVerifyCodeSent: (id) { verifyCodeId = id; }, sendVerifyCode: () { - return APIServer() - .sendBindPhoneCode(_usernameController.text.trim()); + return APIServer().sendBindPhoneCode(_usernameController.text.trim()); }, sendCheck: () { final username = _usernameController.text.trim(); - final isPhoneNumber = - phoneNumberValidator.hasMatch(username); + final isPhoneNumber = phoneNumberValidator.hasMatch(username); if (!isPhoneNumber) { - showErrorMessage(AppLocale.phoneNumberFormatError - .getString(context)); + showErrorMessage(AppLocale.phoneNumberFormatError.getString(context)); return false; } @@ -173,34 +160,26 @@ class _BindPhoneScreenState extends State { }, ), ), - if (state.user!.user.invitedBy == null || - state.user!.user.invitedBy == 0) + if (state.user!.user.invitedBy == null || state.user!.user.invitedBy == 0) Padding( - padding: const EdgeInsets.only( - left: 15.0, right: 15.0, top: 15, bottom: 0), + padding: const EdgeInsets.only(left: 15.0, right: 15.0, top: 15, bottom: 0), child: TextFormField( controller: _inviteCodeController, - inputFormatters: [ - FilteringTextInputFormatter.singleLineFormatter - ], + inputFormatters: [FilteringTextInputFormatter.singleLineFormatter], decoration: InputDecoration( border: const OutlineInputBorder(), enabledBorder: const OutlineInputBorder( - borderSide: BorderSide( - color: Color.fromARGB(200, 192, 192, 192)), + borderSide: BorderSide(color: Color.fromARGB(200, 192, 192, 192)), ), focusedBorder: OutlineInputBorder( - borderSide: - BorderSide(color: customColors.linkColor!), + borderSide: BorderSide(color: customColors.linkColor!), ), - floatingLabelStyle: - TextStyle(color: customColors.linkColor!), + floatingLabelStyle: TextStyle(color: customColors.linkColor!), isDense: true, floatingLabelBehavior: FloatingLabelBehavior.always, labelText: AppLocale.inviteCode.getString(context), labelStyle: const TextStyle(fontSize: 17), - hintText: - AppLocale.inviteCodeInputTips.getString(context), + hintText: AppLocale.inviteCodeInputTips.getString(context), hintStyle: TextStyle( color: customColors.textfieldHintColor, fontSize: 15, @@ -215,14 +194,13 @@ class _BindPhoneScreenState extends State { margin: const EdgeInsets.symmetric(horizontal: 15), decoration: BoxDecoration( color: customColors.linkColor, - borderRadius: BorderRadius.circular(8), + borderRadius: CustomSize.borderRadius, ), child: TextButton( onPressed: onSubmit, child: Text( AppLocale.ok.getString(context), - style: - const TextStyle(color: Colors.white, fontSize: 18), + style: const TextStyle(color: Colors.white, fontSize: 18), ), ), ), diff --git a/lib/page/setting/change_password.dart b/lib/page/setting/change_password.dart index 68bb7632..0092b13c 100644 --- a/lib/page/setting/change_password.dart +++ b/lib/page/setting/change_password.dart @@ -50,7 +50,7 @@ class _ChangePasswordScreenState extends State { ), centerTitle: true, ), - backgroundColor: customColors.backgroundContainerColor, + backgroundColor: customColors.backgroundColor, body: BackgroundContainer( setting: widget.setting, enabled: false, @@ -60,7 +60,7 @@ class _ChangePasswordScreenState extends State { children: [ ColumnBlock( innerPanding: 15, - backgroundColor: Colors.transparent, + padding: const EdgeInsets.only(top: 20, left: 10, right: 10), showDivider: false, children: [ PasswordField( @@ -89,7 +89,7 @@ class _ChangePasswordScreenState extends State { width: double.infinity, decoration: BoxDecoration( color: customColors.linkColor, - borderRadius: BorderRadius.circular(8), + borderRadius: CustomSize.borderRadius, ), child: TextButton( onPressed: onResetSubmit, diff --git a/lib/page/setting/custom_home_models.dart b/lib/page/setting/custom_home_models.dart index 36918a77..72200238 100644 --- a/lib/page/setting/custom_home_models.dart +++ b/lib/page/setting/custom_home_models.dart @@ -161,7 +161,7 @@ class _CustomHomeModelsPageState extends State { width: double.infinity, decoration: BoxDecoration( color: iconAndColors[i].color, - borderRadius: BorderRadius.circular(10), + borderRadius: CustomSize.borderRadius, ), child: ModelIndicator( model: models[i], diff --git a/lib/page/setting/destroy_account.dart b/lib/page/setting/destroy_account.dart index ef84e9c9..2191e94b 100644 --- a/lib/page/setting/destroy_account.dart +++ b/lib/page/setting/destroy_account.dart @@ -51,7 +51,7 @@ class _DestroyAccountScreenState extends State { ), centerTitle: true, ), - backgroundColor: customColors.backgroundContainerColor, + backgroundColor: customColors.backgroundColor, body: BackgroundContainer( setting: widget.setting, enabled: false, @@ -66,7 +66,7 @@ class _DestroyAccountScreenState extends State { ), const SizedBox(height: 15), ColumnBlock( - backgroundColor: Colors.transparent, + padding: const EdgeInsets.only(top: 20, left: 10, right: 10, bottom: 20), children: [ VerifyCodeInput( inColumnBlock: false, @@ -87,10 +87,7 @@ class _DestroyAccountScreenState extends State { Container( height: 45, width: double.infinity, - decoration: BoxDecoration( - color: Colors.red, - borderRadius: BorderRadius.circular(8), - ), + decoration: BoxDecoration(color: Colors.red, borderRadius: CustomSize.borderRadius), child: TextButton( onPressed: onDestroySubmit, child: Text( diff --git a/lib/page/setting/notification.dart b/lib/page/setting/notification.dart index 01e17e0e..3bd45e78 100644 --- a/lib/page/setting/notification.dart +++ b/lib/page/setting/notification.dart @@ -44,7 +44,7 @@ class _NotificationScreenState extends State { toolbarHeight: CustomSize.toolbarHeight, centerTitle: true, ), - backgroundColor: customColors.backgroundContainerColor, + backgroundColor: customColors.backgroundColor, body: BackgroundContainer( setting: widget.setting, enabled: false, @@ -129,9 +129,7 @@ class NotifyMessageItem extends StatelessWidget { horizontal: 15, vertical: 5, ), - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(10), - ), + decoration: BoxDecoration(borderRadius: CustomSize.borderRadius), child: Slidable( endActionPane: ActionPane( motion: const ScrollMotion(), @@ -139,7 +137,7 @@ class NotifyMessageItem extends StatelessWidget { const SizedBox(width: 10), SlidableAction( label: 'Details', - borderRadius: BorderRadius.circular(10), + borderRadius: CustomSize.borderRadiusAll, backgroundColor: Colors.green, icon: Icons.info_outline, onPressed: (_) { @@ -150,16 +148,12 @@ class NotifyMessageItem extends StatelessWidget { ], ), child: Material( - color: customColors.backgroundColor?.withAlpha(200), - borderRadius: BorderRadius.all( - Radius.circular(customColors.borderRadius ?? 8), - ), + color: customColors.backgroundContainerColor, + borderRadius: CustomSize.borderRadius, child: InkWell( child: ListTile( contentPadding: const EdgeInsets.symmetric(horizontal: 10, vertical: 5), - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(customColors.borderRadius ?? 8), - ), + shape: RoundedRectangleBorder(borderRadius: CustomSize.borderRadius), title: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ diff --git a/lib/page/setting/openai_setting.dart b/lib/page/setting/openai_setting.dart index f8c1a9f3..64e11b6f 100644 --- a/lib/page/setting/openai_setting.dart +++ b/lib/page/setting/openai_setting.dart @@ -42,14 +42,11 @@ class _OpenAISettingScreenState extends State { @override void initState() { super.initState(); - _apiKeyController.text = - widget.settings.stringDefault(settingOpenAIAPIToken, ''); - _organizationController.text = - widget.settings.stringDefault(settingOpenAIOrganization, ''); + _apiKeyController.text = widget.settings.stringDefault(settingOpenAIAPIToken, ''); + _organizationController.text = widget.settings.stringDefault(settingOpenAIOrganization, ''); _urlController.text = widget.settings.stringDefault(settingOpenAIURL, ''); if (widget.source == 'setting') { - enableOpenAISelfHosted = - widget.settings.boolDefault(settingOpenAISelfHosted, false); + enableOpenAISelfHosted = widget.settings.boolDefault(settingOpenAISelfHosted, false); } } @@ -68,7 +65,7 @@ class _OpenAISettingScreenState extends State { appBar: AppBar( toolbarHeight: CustomSize.toolbarHeight, title: const Text( - 'OpenAI 设置', + 'OpenAI Setting', style: TextStyle( fontSize: CustomSize.appBarTitleSize, ), @@ -82,7 +79,7 @@ class _OpenAISettingScreenState extends State { context.go(Ability().homeRoute); }, child: Text( - '暂不设置', + 'Do not set', style: TextStyle( color: customColors.weakLinkColor, fontSize: 13, @@ -91,9 +88,10 @@ class _OpenAISettingScreenState extends State { ), ], ), - backgroundColor: customColors.backgroundContainerColor, + backgroundColor: customColors.backgroundColor, body: BackgroundContainer( setting: widget.settings, + enabled: false, child: SizedBox( height: double.infinity, child: SingleChildScrollView( @@ -168,9 +166,7 @@ class _OpenAISettingScreenState extends State { ), const SizedBox(height: 10), EnhancedButton( - title: widget.source == 'setting' - ? AppLocale.save.getString(context) - : '启用', + title: widget.source == 'setting' ? AppLocale.save.getString(context) : 'Enable', onPressed: () { var url = _urlController.text; var apiKey = _apiKeyController.text; @@ -180,10 +176,8 @@ class _OpenAISettingScreenState extends State { url = 'https://api.openai.com'; } - if (!url.startsWith('http://') && - !url.startsWith('https://')) { - showErrorMessageEnhanced( - context, 'URL 必须以 http:// 或 https:// 开头'); + if (!url.startsWith('http://') && !url.startsWith('https://')) { + showErrorMessageEnhanced(context, 'The URL must begin with http:// or https://.'); return; } @@ -193,7 +187,7 @@ class _OpenAISettingScreenState extends State { } if (enableOpenAISelfHosted && apiKey == '') { - showErrorMessageEnhanced(context, 'API Key 不能为空'); + showErrorMessageEnhanced(context, 'API Key cannot be empty'); return; } @@ -212,8 +206,7 @@ class _OpenAISettingScreenState extends State { ); } - void onSaveAndEnter(String apiKey, String organization, String url, - BuildContext context) async { + void onSaveAndEnter(String apiKey, String organization, String url, BuildContext context) async { await widget.settings.set(settingOpenAIAPIToken, apiKey); await widget.settings.set(settingOpenAIOrganization, organization); await widget.settings.set(settingOpenAIURL, url); @@ -243,11 +236,11 @@ class _OpenAISettingScreenState extends State { } if (!url.startsWith('http://') && !url.startsWith('https://')) { - return Future.error('URL 必须以 http:// 或 https:// 开头'); + return Future.error('The URL must begin with http:// or https://.'); } if (apiKey == '') { - return Future.error('API Key 不能为空'); + return Future.error('API Key cannot be empty'); } final headers = { @@ -289,7 +282,7 @@ class _OpenAISettingScreenState extends State { setState(() { verifySuccess = false; }); - return Future.error('验证失败,请检查 API Key:${resp.data}'); + return Future.error('Verification failed, please check the API Key: ${resp.data}'); } cancelLoading(); @@ -305,12 +298,12 @@ class _OpenAISettingScreenState extends State { if (e is DioException) { if (e.response != null && e.response!.data != null) { return Future.error( - '验证失败,请检查网络 或 API Key:${e.response!.data["error"]["message"]}'); + 'Verification failed, please check the network or API Key: ${e.response!.data["error"]["message"]}'); } else { - return Future.error('验证失败,请检查网络 或 API Key:${e.error}'); + return Future.error('Verification failed, please check the network or API Key: ${e.error}'); } } else { - return Future.error('验证失败,请检查网络 或 API Key:${e.toString()}'); + return Future.error('Verification failed, please check the network or API Key: ${e.toString()}'); } } } diff --git a/lib/page/setting/retrieve_password_screen.dart b/lib/page/setting/retrieve_password_screen.dart index 723a2e8b..a3aa562d 100644 --- a/lib/page/setting/retrieve_password_screen.dart +++ b/lib/page/setting/retrieve_password_screen.dart @@ -18,8 +18,7 @@ import 'package:go_router/go_router.dart'; class RetrievePasswordScreen extends StatefulWidget { final String? username; final SettingRepository setting; - const RetrievePasswordScreen( - {super.key, this.username, required this.setting}); + const RetrievePasswordScreen({super.key, this.username, required this.setting}); @override State createState() => _RetrievePasswordScreenState(); @@ -28,14 +27,12 @@ class RetrievePasswordScreen extends StatefulWidget { class _RetrievePasswordScreenState extends State { final TextEditingController _usernameController = TextEditingController(); final TextEditingController _passwordController = TextEditingController(); - final TextEditingController _verificationCodeController = - TextEditingController(); + final TextEditingController _verificationCodeController = TextEditingController(); String verifyCodeId = ''; final phoneNumberValidator = RegExp(r"^1[3456789]\d{9}$"); - final emailValidator = RegExp( - r"^[a-zA-Z0-9.a-zA-Z0-9.!#$%&'*+-/=?^_`{|}~]+@[a-zA-Z0-9]+\.[a-zA-Z]+"); + final emailValidator = RegExp(r"^[a-zA-Z0-9.a-zA-Z0-9.!#$%&'*+-/=?^_`{|}~]+@[a-zA-Z0-9]+\.[a-zA-Z]+"); @override void initState() { @@ -68,26 +65,22 @@ class _RetrievePasswordScreenState extends State { ), centerTitle: true, ), - backgroundColor: customColors.backgroundColor, + backgroundColor: customColors.backgroundContainerColor, body: BackgroundContainer( setting: widget.setting, enabled: false, child: Column( children: [ Padding( - padding: const EdgeInsets.only( - left: 15.0, right: 15.0, top: 15, bottom: 0), + padding: const EdgeInsets.only(left: 15.0, right: 15.0, top: 15, bottom: 0), child: TextFormField( controller: _usernameController, - inputFormatters: [ - FilteringTextInputFormatter.singleLineFormatter - ], + inputFormatters: [FilteringTextInputFormatter.singleLineFormatter], keyboardType: TextInputType.phone, decoration: InputDecoration( border: const OutlineInputBorder(), enabledBorder: const OutlineInputBorder( - borderSide: - BorderSide(color: Color.fromARGB(200, 192, 192, 192)), + borderSide: BorderSide(color: Color.fromARGB(200, 192, 192, 192)), ), focusedBorder: OutlineInputBorder( borderSide: BorderSide(color: customColors.linkColor!), @@ -105,8 +98,7 @@ class _RetrievePasswordScreenState extends State { ), ), Padding( - padding: const EdgeInsets.only( - left: 15.0, right: 15.0, top: 15, bottom: 0), + padding: const EdgeInsets.only(left: 15.0, right: 15.0, top: 15, bottom: 0), child: PasswordField( controller: _passwordController, labelText: AppLocale.newPassword.getString(context), @@ -114,8 +106,7 @@ class _RetrievePasswordScreenState extends State { ), ), Padding( - padding: const EdgeInsets.only( - left: 15.0, right: 10.0, top: 15, bottom: 0), + padding: const EdgeInsets.only(left: 15.0, right: 10.0, top: 15, bottom: 0), child: VerifyCodeInput( controller: _verificationCodeController, onVerifyCodeSent: (id) { @@ -124,10 +115,7 @@ class _RetrievePasswordScreenState extends State { sendVerifyCode: () { return APIServer().sendResetPasswordCode( _usernameController.text.trim(), - verifyType: - phoneNumberValidator.hasMatch(_usernameController.text) - ? 'sms' - : 'email', + verifyType: phoneNumberValidator.hasMatch(_usernameController.text) ? 'sms' : 'email', ); }, sendCheck: () { @@ -136,14 +124,12 @@ class _RetrievePasswordScreenState extends State { final isEmail = emailValidator.hasMatch(username); if (username == '') { - showErrorMessage( - AppLocale.accountRequired.getString(context)); + showErrorMessage(AppLocale.accountRequired.getString(context)); return false; } if (!isPhoneNumber && !isEmail) { - showErrorMessage( - AppLocale.accountFormatError.getString(context)); + showErrorMessage(AppLocale.accountFormatError.getString(context)); return false; } @@ -158,7 +144,7 @@ class _RetrievePasswordScreenState extends State { margin: const EdgeInsets.symmetric(horizontal: 15), decoration: BoxDecoration( color: customColors.linkColor, - borderRadius: BorderRadius.circular(8), + borderRadius: CustomSize.borderRadius, ), child: TextButton( onPressed: onResetSubmit, @@ -181,8 +167,7 @@ class _RetrievePasswordScreenState extends State { return; } - if (!phoneNumberValidator.hasMatch(username) && - !emailValidator.hasMatch(username)) { + if (!phoneNumberValidator.hasMatch(username) && !emailValidator.hasMatch(username)) { showErrorMessage(AppLocale.accountFormatError.getString(context)); return; } diff --git a/lib/page/setting/setting_screen.dart b/lib/page/setting/setting_screen.dart index ea68f3ff..0697b80a 100644 --- a/lib/page/setting/setting_screen.dart +++ b/lib/page/setting/setting_screen.dart @@ -59,7 +59,7 @@ class _SettingScreenState extends State { return BackgroundContainer( setting: widget.settings, child: Scaffold( - backgroundColor: Colors.transparent, + backgroundColor: customColors.backgroundColor, body: SliverComponent( title: Text( AppLocale.settings.getString(context), diff --git a/lib/page/setting/user_api_keys.dart b/lib/page/setting/user_api_keys.dart index c4b6c9f5..a5868eac 100644 --- a/lib/page/setting/user_api_keys.dart +++ b/lib/page/setting/user_api_keys.dart @@ -74,7 +74,8 @@ class _UserAPIKeysScreenState extends State { child: Column( children: [ const MessageBox( - message: '你可以在其它应用中使用 API Key 访问你的数据,接口协议全面兼容 OpenAI 官方 API。', + message: + 'You can use API Key to access your data in other applications, and the protocol is fully compatible with OpenAI\'s official API.', type: MessageBoxType.info, ), const SizedBox(height: 10), @@ -86,10 +87,10 @@ class _UserAPIKeysScreenState extends State { type: QuickAlertType.success, title: 'API Key', text: state.key.token, - confirmBtnText: '复制到剪切板', + confirmBtnText: 'Copy to clipboard', onConfirmBtnTap: () { FlutterClipboard.copy(state.key.token).then((value) { - showSuccessMessage('已复制到剪贴板'); + showSuccessMessage('Copied to clipboard'); context.pop(); }); @@ -110,7 +111,7 @@ class _UserAPIKeysScreenState extends State { alignment: Alignment.center, child: Center( child: Text( - '你还没有创建任何 API Key', + 'You haven\'t created any API Key yet.', style: TextStyle( fontSize: 14, color: customColors.weakTextColor, @@ -127,9 +128,7 @@ class _UserAPIKeysScreenState extends State { final item = state.keys[index]; return Container( margin: const EdgeInsets.symmetric(vertical: 5), - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(10), - ), + decoration: BoxDecoration(borderRadius: CustomSize.borderRadius), child: Slidable( endActionPane: ActionPane( motion: const ScrollMotion(), @@ -137,18 +136,15 @@ class _UserAPIKeysScreenState extends State { const SizedBox(width: 10), SlidableAction( label: AppLocale.delete.getString(context), - borderRadius: BorderRadius.circular(10), + borderRadius: CustomSize.borderRadiusAll, backgroundColor: Colors.red, icon: Icons.delete, onPressed: (_) { openConfirmDialog( context, - AppLocale.confirmDelete - .getString(context), + AppLocale.confirmDelete.getString(context), () { - context - .read() - .add(UserApiKeyDelete(item.id)); + context.read().add(UserApiKeyDelete(item.id)); }, danger: true, ); @@ -157,22 +153,14 @@ class _UserAPIKeysScreenState extends State { ], ), child: Material( - color: - customColors.backgroundColor?.withAlpha(200), - borderRadius: BorderRadius.all( - Radius.circular(customColors.borderRadius ?? 8), - ), + color: customColors.backgroundColor?.withAlpha(200), + borderRadius: const BorderRadius.all(CustomSize.radius), child: InkWell( child: ListTile( - contentPadding: const EdgeInsets.symmetric( - horizontal: 10, vertical: 5), - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular( - customColors.borderRadius ?? 8), - ), + contentPadding: const EdgeInsets.symmetric(horizontal: 10, vertical: 5), + shape: RoundedRectangleBorder(borderRadius: CustomSize.borderRadius), title: Row( - mainAxisAlignment: - MainAxisAlignment.spaceBetween, + mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Expanded( child: Text( @@ -188,8 +176,7 @@ class _UserAPIKeysScreenState extends State { Text( humanTime(DateTime.now()), style: TextStyle( - color: customColors.weakTextColor - ?.withAlpha(65), + color: customColors.weakTextColor?.withAlpha(65), fontSize: 12, ), ), @@ -202,8 +189,7 @@ class _UserAPIKeysScreenState extends State { maxLines: 1, overflow: TextOverflow.ellipsis, style: TextStyle( - color: customColors.weakTextColor - ?.withAlpha(150), + color: customColors.weakTextColor?.withAlpha(150), fontSize: 12, overflow: TextOverflow.ellipsis, ), @@ -214,8 +200,7 @@ class _UserAPIKeysScreenState extends State { cancelDialog = BotToast.showCustomLoading( toastBuilder: (cancel) { return LoadingIndicator( - message: AppLocale.imageUploading - .getString(context), + message: AppLocale.imageUploading.getString(context), ); }, allowClick: false, From d8bdf162cc65f5bda94c2825bcc91b207b666426 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=AE=A1=E5=AE=9C=E5=B0=A7?= Date: Sat, 7 Dec 2024 15:29:38 +0800 Subject: [PATCH 12/26] update --- android/app/src/main/AndroidManifest.xml | 2 + ios/Podfile.lock | 16 ++ ios/Runner/Info.plist | 2 + lib/helper/helper.dart | 9 +- lib/helper/platform.dart | 4 + lib/lang/lang.dart | 92 ++++++++- lib/page/balance/quota_usage_statistics.dart | 19 +- lib/page/component/chat/chat_input.dart | 161 +++++++++++++--- lib/page/component/chat/chat_preview.dart | 4 +- lib/page/component/chat/chat_share.dart | 16 +- lib/page/component/chat/help_tips.dart | 19 +- lib/page/component/chat/markdown.dart | 8 +- lib/page/component/gallery_item_share.dart | 18 +- lib/page/component/image_action.dart | 32 ++-- lib/page/component/image_preview.dart | 32 ++-- lib/page/component/invite_card.dart | 11 +- lib/page/component/share.dart | 23 +-- lib/page/component/take_photo.dart | 175 ++++++++++++++++++ lib/page/component/video_player.dart | 18 +- .../draw/components/content_preview.dart | 3 +- .../draw/components/image_selector.dart | 10 +- .../creative_island/gallery/gallery_item.dart | 10 +- lib/page/drawer.dart | 26 ++- lib/page/lab/draw_board.dart | 31 ++-- pubspec.lock | 80 ++++++++ pubspec.yaml | 3 + 26 files changed, 657 insertions(+), 167 deletions(-) create mode 100644 lib/page/component/take_photo.dart diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index 8962cfb5..d69c7abb 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -84,4 +84,6 @@ + + diff --git a/ios/Podfile.lock b/ios/Podfile.lock index 988715fc..39474bf5 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -1,6 +1,11 @@ PODS: - audioplayers_darwin (0.0.1): - Flutter + - camera_avfoundation (0.0.1): + - Flutter + - camerawesome (0.0.1): + - Flutter + - JPSVolumeButtonHandler - DKImagePickerController/Core (4.3.9): - DKImagePickerController/ImageDataManager - DKImagePickerController/Resource @@ -59,6 +64,7 @@ PODS: - in_app_purchase_storekit (0.0.1): - Flutter - FlutterMacOS + - JPSVolumeButtonHandler (1.0.5) - libwebp (1.3.2): - libwebp/demux (= 1.3.2) - libwebp/mux (= 1.3.2) @@ -155,6 +161,8 @@ PODS: DEPENDENCIES: - audioplayers_darwin (from `.symlinks/plugins/audioplayers_darwin/ios`) + - camera_avfoundation (from `.symlinks/plugins/camera_avfoundation/ios`) + - camerawesome (from `.symlinks/plugins/camerawesome/ios`) - file_picker (from `.symlinks/plugins/file_picker/ios`) - file_saver (from `.symlinks/plugins/file_saver/ios`) - Flutter (from `Flutter`) @@ -187,6 +195,7 @@ SPEC REPOS: trunk: - DKImagePickerController - DKPhotoGallery + - JPSVolumeButtonHandler - libwebp - Mantle - SDWebImage @@ -205,6 +214,10 @@ SPEC REPOS: EXTERNAL SOURCES: audioplayers_darwin: :path: ".symlinks/plugins/audioplayers_darwin/ios" + camera_avfoundation: + :path: ".symlinks/plugins/camera_avfoundation/ios" + camerawesome: + :path: ".symlinks/plugins/camerawesome/ios" file_picker: :path: ".symlinks/plugins/file_picker/ios" file_saver: @@ -262,6 +275,8 @@ EXTERNAL SOURCES: SPEC CHECKSUMS: audioplayers_darwin: 877d9a4d06331c5c374595e46e16453ac7eafa40 + camera_avfoundation: dd002b0330f4981e1bbcb46ae9b62829237459a4 + camerawesome: 1e06540f60158809bc70f398ed1ac2cf93fe4188 DKImagePickerController: 946cec48c7873164274ecc4624d19e3da4c1ef3c DKPhotoGallery: b3834fecb755ee09a593d7c9e389d8b5d6deed60 file_picker: ce3938a0df3cc1ef404671531facef740d03f920 @@ -275,6 +290,7 @@ SPEC CHECKSUMS: fluwx: e9e728cfdb80e82dac5f4ff974b1901a7939dcd0 image_gallery_saver: cb43cc43141711190510e92c460eb1655cd343cb in_app_purchase_storekit: 8c3b0b3eb1b0f04efbff401c3de6266d4258d433 + JPSVolumeButtonHandler: 53110330c9168ed325def93eabff39f0fe3e8082 libwebp: 1786c9f4ff8a279e4dac1e8f385004d5fc253009 Mantle: c5aa8794a29a022dfbbfc9799af95f477a69b62d media_kit_libs_ios_video: a5fe24bc7875ccd6378a0978c13185e1344651c1 diff --git a/ios/Runner/Info.plist b/ios/Runner/Info.plist index eeba3426..ce808c20 100644 --- a/ios/Runner/Info.plist +++ b/ios/Runner/Info.plist @@ -51,6 +51,8 @@ We need to access to the microphone to record audio file NSPhotoLibraryUsageDescription Used to demonstrate image picker plugin + NSLocationWhenInUseUsageDescription + To enable GPS location access for Exif data UIApplicationSupportsIndirectInputEvents UIFileSharingEnabled diff --git a/lib/helper/helper.dart b/lib/helper/helper.dart index 057f094b..e2c5f175 100644 --- a/lib/helper/helper.dart +++ b/lib/helper/helper.dart @@ -47,8 +47,7 @@ Future readTempFile(String path) async { return await file.readAsBytes(); } -Future writeStringFileToDocumentsDirectory( - String path, String content) async { +Future writeStringFileToDocumentsDirectory(String path, String content) async { try { final directory = await getApplicationDocumentsDirectory(); final file = File('${directory.path}/$path'); @@ -126,14 +125,14 @@ String humanTime(DateTime? ts, {bool withTime = false}) { } if (diff.inHours > 0) { - return '${diff.inHours}小时前'; + return '${diff.inHours} hours ago'; } if (diff.inMinutes > 0) { - return '${diff.inMinutes}分钟前'; + return '${diff.inMinutes} minutes ago'; } - return '刚刚'; + return 'Just now'; } /// 解析错误信息 diff --git a/lib/helper/platform.dart b/lib/helper/platform.dart index d4bd52b1..27ce1812 100644 --- a/lib/helper/platform.dart +++ b/lib/helper/platform.dart @@ -3,6 +3,10 @@ import 'dart:io'; import 'package:flutter/foundation.dart'; class PlatformTool { + static bool isDesktop() { + return isWindows() || isLinux() || isMacOS(); + } + static bool isIOS() { try { return Platform.isIOS; diff --git a/lib/lang/lang.dart b/lib/lang/lang.dart index 36b31e27..3a90872a 100644 --- a/lib/lang/lang.dart +++ b/lib/lang/lang.dart @@ -41,6 +41,7 @@ mixin AppLocale { static const String selectAll = 'select-all'; static const String unselectAll = 'unselect-all'; static const String share = 'share'; + static const String cancelShare = 'cancel-share'; static const String histories = 'histories'; static const String enable = 'enable'; static const String disable = 'disable'; @@ -50,8 +51,9 @@ mixin AppLocale { static const String examples = 'examples'; static const String continueMessage = 'continue'; static const String messageInputTips = 'message-input-tips'; - static const String uploadImage = 'upload-image'; - static const String uploadDocument = 'upload-document'; + static const String takePhoto = 'take-photo'; + static const String photoLibrary = 'photo-library'; + static const String fileLibrary = 'file-library'; static const String upload = 'upload'; static const String longPressSpeak = 'long-press-speak'; static const String send = 'send'; @@ -142,7 +144,9 @@ mixin AppLocale { static const String fastAndCostEffective = 'fast-and-cost-effective'; static const String powerfulAndPrecise = 'powerful-and-precise'; static const String imageToImage = 'image-to-image'; + static const String imageToVideo = 'image-to-video'; static const String textToImage = 'text-to-image'; + static const String hdRestoration = 'hd-restoration'; static const String yourIdeas = 'your-ideas'; static const String smartOptimization = 'smart-optimization'; static const String professionalMode = 'professional-mode'; @@ -211,6 +215,7 @@ mixin AppLocale { static const String me = 'me'; static const String coinsUsage = 'coins-usage'; + static const String coinUsageTips = 'coin-usage-tips'; static const String updateCheck = 'update-check'; static const String buy = 'buy'; static const String paymentHistory = 'payment-history'; @@ -266,6 +271,29 @@ mixin AppLocale { static const String viewMore = 'view-more'; static const String using = 'using'; + static const String inviteCodeShare = 'invite-code-share'; + static const String shareToWechatQ = 'share-to-wechat-q'; + static const String shareToWechat = 'share-to-wechat'; + static const String shareToOtherApps = 'share-to-other-apps'; + static const String clickToShareWithExpire = 'click-to-share-with-expire'; + + static const String shortcut = 'shortcut'; + static const String selectImageToShortcut = 'select-image-to-shortcut'; + static const String selectShortcutAction = 'select-shortcut-action'; + static const String makeSameStyle = 'make-same-style'; + static const String saveToLocal = 'save-to-local'; + static const String showInviteCode = 'show-invite-code'; + static const String dontShowInviteCode = 'dont-show-invite-code'; + static const String inviteNow = 'invite-now'; + static const String inviteSlogan = 'invite-slogan'; + static const String preview = 'preview'; + static const String download = 'download'; + static const String clickSwitchImage = 'click-switch-image'; + + static const String startNewChatTips = 'start-new-chat-tips'; + static const String wantMoreContentTips = 'want-more-content-tips'; + static const String unbilled = 'unbilled'; + static const Map zh = { required: '必填', systemInfo: '系统信息', @@ -281,6 +309,7 @@ mixin AppLocale { selectAll: '全选', unselectAll: '取消全选', share: '分享', + cancelShare: '取消分享', histories: '最近的对话', enable: '启用', disable: '未启用', @@ -289,8 +318,9 @@ mixin AppLocale { examples: '示例', continueMessage: '继续', messageInputTips: '有问题尽管问我', - uploadImage: '上传图片', - uploadDocument: '上传文档', + takePhoto: '拍照', + photoLibrary: '照片图库', + fileLibrary: '文档', upload: '上传', longPressSpeak: '长按说话', send: '发送', @@ -403,7 +433,9 @@ mixin AppLocale { fastAndCostEffective: '速度快,成本低', powerfulAndPrecise: '能力强,更精准', imageToImage: '图生图', + imageToVideo: '图生视频', textToImage: '文生图', + hdRestoration: '高清修复', yourIdeas: '你的想法', smartOptimization: '智能优化', professionalMode: '专业模式', @@ -469,6 +501,7 @@ mixin AppLocale { enableCustomOpenAI: '启用后将使用您自己配置的 OpenAI 服务', me: '我的', coinsUsage: '使用明细', + coinUsageTips: '使用明细将在次日更新,显示近 30 天的使用量。', updateCheck: '检测更新', buy: '购买', paymentHistory: '购买历史', @@ -519,6 +552,26 @@ mixin AppLocale { pickYourRobot: '挑选你的专属伙伴', viewMore: '查看更多', using: '使用中', + inviteCodeShare: '邀请码分享', + shareToWechatQ: '分享到朋友圈', + shareToWechat: '分享到微信', + shareToOtherApps: '分享到其它应用', + clickToShareWithExpire: '点击图片可分享、保存;有效期至', + shortcut: '动作', + selectImageToShortcut: '选择要执行动作的图片', + selectShortcutAction: '选择要执行的操作', + makeSameStyle: '制作同款', + saveToLocal: '保存到本地', + showInviteCode: '显示邀请信息', + dontShowInviteCode: '不显示邀请信息', + inviteNow: '立即邀请', + inviteSlogan: '邀请好友注册,双方都可获得奖励', + preview: '预览', + download: '下载', + clickSwitchImage: '点击此处更换图片', + startNewChatTips: '想要开启新的聊天?试试', + wantMoreContentTips: '想要更多内容?试着对我说', + unbilled: '未出账', }; static const Map en = { @@ -536,6 +589,7 @@ mixin AppLocale { selectAll: 'Select all', unselectAll: 'Cancel', share: 'Share', + cancelShare: 'Cancel share', histories: 'Recents', enable: 'Enable', disable: 'Disable', @@ -544,8 +598,9 @@ mixin AppLocale { examples: 'Examples', continueMessage: 'Continue', messageInputTips: 'Ask me something...', - uploadImage: 'Upload Image', - uploadDocument: 'Upload Document', + takePhoto: 'Take Photo', + photoLibrary: 'Attach Photos', + fileLibrary: 'Attach Files', upload: 'Upload', longPressSpeak: 'Long press to speak', send: 'Send', @@ -609,7 +664,7 @@ mixin AppLocale { readByVoice: 'Voice', unknownFile: 'Unknown File', switchModel: 'Switch Model', - switchModelTitle: 'Select a model to switch', + switchModelTitle: 'Switch model', noMessageSelected: 'No message selected', modelUsage: 'The model is used to set the type of AI character used', promptUsage: 'Prompt is used to set the behavior of the AI character', @@ -661,7 +716,9 @@ mixin AppLocale { fastAndCostEffective: 'Fast & Cost-Effective', powerfulAndPrecise: 'Powerful & Precise', imageToImage: 'Image to Image', + imageToVideo: 'Image to Video', textToImage: 'Text to Image', + hdRestoration: 'HD Restoration', yourIdeas: 'Your Ideas', smartOptimization: 'Smart Optimization', professionalMode: 'Pro Mode', @@ -728,6 +785,7 @@ mixin AppLocale { enableCustomOpenAI: 'Your custom OpenAI service will be used once enabled', me: 'Me', coinsUsage: 'Usage', + coinUsageTips: 'Usage details will be updated the next day, showing usage in the last 30 days.', updateCheck: 'Check Update', buy: 'Buy', paymentHistory: 'Histories', @@ -778,6 +836,26 @@ mixin AppLocale { pickYourRobot: 'Pick your robot', viewMore: 'More', using: 'Using', + inviteCodeShare: 'Invite Code Share', + shareToWechatQ: 'Share to WeChat Moments', + shareToWechat: 'Share to WeChat', + shareToOtherApps: 'Share to Other Apps', + clickToShareWithExpire: 'Click to share, valid until', + shortcut: 'Action', + selectImageToShortcut: 'Select image to perform action', + selectShortcutAction: 'Select the action to perform', + makeSameStyle: 'Make the Same', + saveToLocal: 'Save to Local', + showInviteCode: 'Show Invite Code', + dontShowInviteCode: 'Don\'t Show Invite Code', + inviteNow: 'Invite Now', + inviteSlogan: 'Invite friends to register, both parties will receive rewards', + preview: 'Preview', + download: 'Download', + clickSwitchImage: 'Click to switch image', + startNewChatTips: 'Want to start a new chat? Try', + wantMoreContentTips: 'Want more content? Try', + unbilled: 'Pending', }; } diff --git a/lib/page/balance/quota_usage_statistics.dart b/lib/page/balance/quota_usage_statistics.dart index aa8cbaa6..8385893d 100644 --- a/lib/page/balance/quota_usage_statistics.dart +++ b/lib/page/balance/quota_usage_statistics.dart @@ -1,3 +1,4 @@ +import 'package:askaide/lang/lang.dart'; import 'package:askaide/page/component/background_container.dart'; import 'package:askaide/page/component/loading.dart'; import 'package:askaide/page/component/message_box.dart'; @@ -7,6 +8,7 @@ import 'package:askaide/repo/api_server.dart'; import 'package:askaide/repo/settings_repo.dart'; import 'package:flutter/material.dart'; import 'package:askaide/repo/model/misc.dart'; +import 'package:flutter_localization/flutter_localization.dart'; import 'package:go_router/go_router.dart'; class QuotaUsageStatisticsScreen extends StatefulWidget { @@ -40,9 +42,9 @@ class _QuotaUsageStatisticsScreenState extends State return Scaffold( appBar: AppBar( toolbarHeight: CustomSize.toolbarHeight, - title: const Text( - '使用明细', - style: TextStyle(fontSize: CustomSize.appBarTitleSize), + title: Text( + AppLocale.coinsUsage.getString(context), + style: const TextStyle(fontSize: CustomSize.appBarTitleSize), ), centerTitle: true, elevation: 0, @@ -55,8 +57,8 @@ class _QuotaUsageStatisticsScreenState extends State padding: const EdgeInsets.all(16), child: Column( children: [ - const MessageBox( - message: '使用明细将在次日更新,显示近 30 天的使用量。', + MessageBox( + message: AppLocale.coinUsageTips.getString(context), type: MessageBoxType.info, ), const SizedBox(height: 10), @@ -92,7 +94,7 @@ class _QuotaUsageStatisticsScreenState extends State ), SizedBox(height: 10), Text( - '暂无使用记录', + 'No records yet', ), ], ), @@ -126,7 +128,10 @@ class _QuotaUsageStatisticsScreenState extends State fontWeight: FontWeight.bold, ), ), - if (item.used == -1) const Text('未出账') else Text('${item.used > 0 ? "-" : ""}${item.used}'), + if (item.used == -1) + Text(AppLocale.unbilled.getString(context)) + else + Text('${item.used > 0 ? "-" : ""}${item.used}'), ], ), ), diff --git a/lib/page/component/chat/chat_input.dart b/lib/page/component/chat/chat_input.dart index 1decdb01..cc60bf46 100644 --- a/lib/page/component/chat/chat_input.dart +++ b/lib/page/component/chat/chat_input.dart @@ -7,10 +7,12 @@ import 'package:askaide/lang/lang.dart'; import 'package:askaide/page/component/chat/file_upload.dart'; import 'package:askaide/page/component/chat/voice_record.dart'; import 'package:askaide/page/component/dialog.dart'; -import 'package:askaide/page/component/enhanced_popup_menu.dart'; import 'package:askaide/page/component/file_preview.dart'; +import 'package:askaide/page/component/item_selector_search.dart'; import 'package:askaide/page/component/theme/custom_size.dart'; import 'package:askaide/repo/settings_repo.dart'; +import 'package:camera/camera.dart'; +import 'package:camerawesome/camerawesome_plugin.dart'; import 'package:file_picker/file_picker.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; @@ -18,6 +20,7 @@ import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_localization/flutter_localization.dart'; import 'package:flutter_slidable/flutter_slidable.dart'; import 'package:askaide/page/component/theme/custom_theme.dart'; +import 'package:go_router/go_router.dart'; import 'package:loading_animation_widget/loading_animation_widget.dart'; class ChatInput extends StatefulWidget { @@ -85,11 +88,21 @@ class _ChatInputState extends State with TickerProviderStateMixin { /// Maximum height of the chat input box var maxLines = 5; + var minLines = 1; + var hasCamera = false; @override void initState() { super.initState(); + if (!PlatformTool.isDesktop()) { + availableCameras().then((cameras) { + setState(() { + hasCamera = cameras.isNotEmpty; + }); + }); + } + _textController.addListener(() { setState(() {}); }); @@ -185,8 +198,10 @@ class _ChatInputState extends State with TickerProviderStateMixin { setState(() { if (hasFocus) { maxLines = 10; + minLines = 3; } else { - maxLines = 5; + maxLines = 3; + minLines = 1; } }); @@ -196,10 +211,11 @@ class _ChatInputState extends State with TickerProviderStateMixin { keyboardType: TextInputType.multiline, textInputAction: TextInputAction.newline, maxLines: maxLines, - minLines: 1, + minLines: minLines, maxLength: maxLength, focusNode: _focusNode, controller: _textController, + style: const TextStyle(fontSize: CustomSize.defaultHintTextSize), decoration: InputDecoration( hintText: widget.hintText, hintStyle: const TextStyle( @@ -451,46 +467,141 @@ class _ChatInputState extends State with TickerProviderStateMixin { SettingRepository setting, CustomColors customColors, ) { - if (canUploadImage && !canUploadFile) { + if (canUploadImage && !canUploadFile && !hasCamera) { return IconButton( onPressed: onImageUploadButtonPressed, icon: const Icon(Icons.camera_alt), color: customColors.chatInputPanelText, splashRadius: 20, - tooltip: AppLocale.uploadImage.getString(context), + tooltip: AppLocale.photoLibrary.getString(context), ); } - if (!canUploadImage && canUploadFile) { + if (!canUploadImage && canUploadFile && !hasCamera) { return IconButton( onPressed: onFileUploadButtonPressed, icon: const Icon(Icons.upload_file), color: customColors.chatInputPanelText, splashRadius: 20, - tooltip: AppLocale.uploadDocument.getString(context), + tooltip: AppLocale.fileLibrary.getString(context), ); } - return EnhancedPopupMenu( - icon: Icons.upload_file, + return IconButton( + icon: const Icon(Icons.add), color: customColors.chatInputPanelText, - tooltip: AppLocale.upload.getString(context), - items: [ - EnhancedPopupMenuItem( - title: AppLocale.uploadImage.getString(context), - icon: Icons.camera_alt, - onTap: (ctx) async { - onImageUploadButtonPressed(); - }, - ), - EnhancedPopupMenuItem( - title: AppLocale.uploadDocument.getString(context), - icon: Icons.attach_file, - onTap: (ctx) { - onFileUploadButtonPressed(); + splashRadius: 20, + tooltip: 'Tools', + onPressed: () { + openListSelectDialog( + context, + >[ + if (hasCamera) + SelectorItem( + Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + const Icon(Icons.camera_alt, size: 50), + const SizedBox(height: 5), + Text( + AppLocale.takePhoto.getString(context), + style: const TextStyle(fontSize: 12), + maxLines: 1, + overflow: TextOverflow.ellipsis, + ), + ], + ), + 'take-photo', + ), + SelectorItem( + Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + const Icon(Icons.photo_library, size: 50), + const SizedBox(height: 5), + Text( + AppLocale.photoLibrary.getString(context), + style: const TextStyle(fontSize: 12), + maxLines: 1, + overflow: TextOverflow.ellipsis, + ), + ], + ), + 'select-image', + ), + if (canUploadFile) + SelectorItem( + Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + const Icon(Icons.upload_file_sharp, size: 50), + const SizedBox(height: 5), + Text( + AppLocale.fileLibrary.getString(context), + style: const TextStyle(fontSize: 12), + maxLines: 1, + overflow: TextOverflow.ellipsis, + ), + ], + ), + 'select-file', + ), + ], + (value) { + switch (value.value) { + case 'select-image': + onImageUploadButtonPressed(); + break; + case 'select-file': + onFileUploadButtonPressed(); + break; + case 'take-photo': + HapticFeedbackHelper.mediumImpact(); + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => Scaffold( + appBar: AppBar( + title: Text(AppLocale.takePhoto.getString(context)), + backgroundColor: customColors.backgroundColor, + ), + body: CameraAwesomeBuilder.awesome( + saveConfig: SaveConfig.photo(), + enablePhysicalButton: true, + onMediaCaptureEvent: (mediaCapture) async { + if (mediaCapture.status == MediaCaptureStatus.success) { + final file = FileUpload( + file: PlatformFile( + path: mediaCapture.captureRequest.path!, + name: mediaCapture.captureRequest.path!.split('/').last, + size: await File(mediaCapture.captureRequest.path!).length(), + )); + + final files = widget.selectedImageFiles ?? []; + files.add(file); + widget.onImageSelected?.call(files.sublist(0, files.length > 4 ? 4 : files.length)); + + if (context.mounted) { + context.pop(); + } + } + }, + ), + ), + ), + ).whenComplete(() { + context.pop(); + }); + return false; + default: + } + return true; }, - ), - ], + heightFactor: 0.3, + horizontal: true, + horizontalCount: canUploadFile ? 3 : 2, + ); + }, ); } diff --git a/lib/page/component/chat/chat_preview.dart b/lib/page/component/chat/chat_preview.dart index 15fefa72..da909615 100644 --- a/lib/page/component/chat/chat_preview.dart +++ b/lib/page/component/chat/chat_preview.dart @@ -389,8 +389,8 @@ class _ChatPreviewState extends State { ), if (extraInfo.isNotEmpty) Positioned( - top: 5, - right: 5, + top: 0, + right: 0, child: InkWell( onTap: () { showCustomBeautyDialog( diff --git a/lib/page/component/chat/chat_share.dart b/lib/page/component/chat/chat_share.dart index 5694c53c..768d65c5 100644 --- a/lib/page/component/chat/chat_share.dart +++ b/lib/page/component/chat/chat_share.dart @@ -102,7 +102,7 @@ class _ChatShareScreenState extends State { Icon(Icons.share, size: 14, color: customColors.weakLinkColor), const SizedBox(width: 5), Text( - '分享', + AppLocale.share.getString(context), style: TextStyle(color: customColors.weakLinkColor, fontSize: 14), ), ], @@ -111,7 +111,7 @@ class _ChatShareScreenState extends State { EnhancedPopupMenu( items: [ EnhancedPopupMenuItem( - title: '保存到本地', + title: AppLocale.saveToLocal.getString(context), icon: Icons.save, onTap: (ctx) async { final cancel = BotToast.showCustomLoading( @@ -133,7 +133,7 @@ class _ChatShareScreenState extends State { if (PlatformTool.isIOS() || PlatformTool.isAndroid()) { await ImageGallerySaver.saveImage(data, quality: 100); - showSuccessMessage('图片保存成功'); + showSuccessMessage(AppLocale.operateSuccess.getString(context)); } else { if (PlatformTool.isWindows()) { FileSaver.instance @@ -150,8 +150,8 @@ class _ChatShareScreenState extends State { await File(value).writeAsBytes(data); - Logger.instance.d('文件保存成功: $value'); - showSuccessMessage('文件保存成功'); + Logger.instance.d('File saved successfully: $value'); + showSuccessMessage(AppLocale.operateSuccess.getString(context)); }); } else { FileSaver.instance @@ -162,7 +162,7 @@ class _ChatShareScreenState extends State { mimeType: MimeType.png, ) .then((value) { - showSuccessMessage('文件保存成功'); + showSuccessMessage(AppLocale.operateSuccess.getString(context)); }); } } @@ -173,7 +173,9 @@ class _ChatShareScreenState extends State { }, ), EnhancedPopupMenuItem( - title: showQRCode ? '不显示邀请信息' : '显示邀请信息', + title: showQRCode + ? AppLocale.dontShowInviteCode.getString(context) + : AppLocale.showInviteCode.getString(context), icon: showQRCode ? Icons.visibility_off : Icons.visibility, onTap: (ctx) { setState(() { diff --git a/lib/page/component/chat/help_tips.dart b/lib/page/component/chat/help_tips.dart index f7f51728..b1182ad6 100644 --- a/lib/page/component/chat/help_tips.dart +++ b/lib/page/component/chat/help_tips.dart @@ -18,13 +18,10 @@ class HelpTips extends StatelessWidget { List children = [ if (onNewChat != null && onSubmitMessage != null) Builder( - builder: (context) => _buildNewChatActionTip( - customColors, context, (text) => onSubmitMessage!(text)), + builder: (context) => _buildNewChatActionTip(customColors, context, (text) => onSubmitMessage!(text)), ), if (onSubmitMessage != null) - Builder( - builder: (context) => _buildContinueActionTip( - customColors, context, onSubmitMessage!)) + Builder(builder: (context) => _buildContinueActionTip(customColors, context, onSubmitMessage!)) ]; // 随机取一个 builder @@ -34,18 +31,18 @@ class HelpTips extends StatelessWidget { ); } - RichText _buildNewChatActionTip(CustomColors customColors, - BuildContext context, Function(String text) onSubmit) { + RichText _buildNewChatActionTip(CustomColors customColors, BuildContext context, Function(String text) onSubmit) { return RichText( text: TextSpan( children: [ TextSpan( - text: '想要开启新的聊天?试试 ', + text: AppLocale.startNewChatTips.getString(context), style: TextStyle( color: customColors.dialogDefaultTextColor, fontSize: 12, ), ), + const TextSpan(text: ' '), TextSpan( text: AppLocale.newChat.getString(context), style: TextStyle( @@ -57,18 +54,18 @@ class HelpTips extends StatelessWidget { )); } - RichText _buildContinueActionTip(CustomColors customColors, - BuildContext context, Function(String text) onSubmit) { + RichText _buildContinueActionTip(CustomColors customColors, BuildContext context, Function(String text) onSubmit) { return RichText( text: TextSpan( children: [ TextSpan( - text: '想要更多内容?试着对我说 ', + text: AppLocale.wantMoreContentTips.getString(context), style: TextStyle( color: customColors.dialogDefaultTextColor, fontSize: 12, ), ), + const TextSpan(text: ' '), TextSpan( text: AppLocale.continueMessage.getString(context), style: TextStyle( diff --git a/lib/page/component/chat/markdown.dart b/lib/page/component/chat/markdown.dart index 3f37697f..d8dae04a 100644 --- a/lib/page/component/chat/markdown.dart +++ b/lib/page/component/chat/markdown.dart @@ -11,6 +11,7 @@ import 'package:clipboard/clipboard.dart'; import 'package:flutter/material.dart'; import 'package:flutter_cache_manager/flutter_cache_manager.dart'; import 'package:flutter_markdown/flutter_markdown.dart' as md; +import 'package:flutter_markdown_latex/flutter_markdown_latex.dart'; import 'package:markdown/markdown.dart' as mm; import 'package:markdown_widget/config/all.dart'; import 'package:markdown_widget/widget/all.dart'; @@ -83,15 +84,20 @@ class Markdown extends StatelessWidget { return ClipRRect(borderRadius: CustomSize.borderRadiusAll, child: Image.network(uri.toString())); }, extensionSet: mm.ExtensionSet( - mm.ExtensionSet.gitHubFlavored.blockSyntaxes, + [ + ...mm.ExtensionSet.gitHubFlavored.blockSyntaxes, + LatexBlockSyntax(), + ], [ mm.EmojiSyntax(), ...mm.ExtensionSet.gitHubFlavored.inlineSyntaxes, + LatexInlineSyntax(), ], ), data: data, builders: { 'code': CodeElementBuilder(), + 'latex': LatexElementBuilder(), }, ); } diff --git a/lib/page/component/gallery_item_share.dart b/lib/page/component/gallery_item_share.dart index 386380e7..9eb4ad23 100644 --- a/lib/page/component/gallery_item_share.dart +++ b/lib/page/component/gallery_item_share.dart @@ -88,7 +88,7 @@ class _GalleryItemShareScreenState extends State { Icon(Icons.share, size: 14, color: customColors.weakLinkColor), const SizedBox(width: 5), Text( - '分享', + AppLocale.share.getString(context), style: TextStyle(color: customColors.weakLinkColor, fontSize: 14), ), ], @@ -97,7 +97,7 @@ class _GalleryItemShareScreenState extends State { EnhancedPopupMenu( items: [ EnhancedPopupMenuItem( - title: '保存到本地', + title: AppLocale.saveToLocal.getString(context), icon: Icons.save, onTap: (ctx) async { final cancel = BotToast.showCustomLoading( @@ -119,7 +119,7 @@ class _GalleryItemShareScreenState extends State { if (PlatformTool.isIOS() || PlatformTool.isAndroid()) { await ImageGallerySaver.saveImage(data, quality: 100); - showSuccessMessage('图片保存成功'); + showSuccessMessage(AppLocale.operateSuccess.getString(context)); } else { if (PlatformTool.isWindows()) { FileSaver.instance @@ -136,8 +136,8 @@ class _GalleryItemShareScreenState extends State { await File(value).writeAsBytes(data); - Logger.instance.d('文件保存成功: $value'); - showSuccessMessage('文件保存成功'); + Logger.instance.d('File saved successfully: $value'); + showSuccessMessage(AppLocale.operateSuccess.getString(context)); }); } else { FileSaver.instance @@ -148,8 +148,8 @@ class _GalleryItemShareScreenState extends State { mimeType: MimeType.png, ) .then((value) { - Logger.instance.d('文件保存成功: $value'); - showSuccessMessage('文件保存成功'); + Logger.instance.d('File saved successfully: $value'); + showSuccessMessage(AppLocale.operateSuccess.getString(context)); }); } } @@ -160,7 +160,9 @@ class _GalleryItemShareScreenState extends State { }, ), EnhancedPopupMenuItem( - title: showQRCode ? '不显示邀请信息' : '显示邀请信息', + title: showQRCode + ? AppLocale.dontShowInviteCode.getString(context) + : AppLocale.showInviteCode.getString(context), icon: showQRCode ? Icons.visibility_off : Icons.visibility, onTap: (ctx) { setState(() { diff --git a/lib/page/component/image_action.dart b/lib/page/component/image_action.dart index 323315c2..e0913396 100644 --- a/lib/page/component/image_action.dart +++ b/lib/page/component/image_action.dart @@ -19,10 +19,12 @@ Future openImageWorkflowActionDialog( (context) { return Column( children: [ - Text('选择要执行的操作', - style: TextStyle( - color: customColors.weakTextColorPlus, - )), + Text( + AppLocale.selectShortcutAction.getString(context), + style: TextStyle( + color: customColors.weakTextColorPlus, + ), + ), Expanded( child: FutureBuilder( future: APIServer().creativeIslandItemsV2(), @@ -33,12 +35,11 @@ Future openImageWorkflowActionDialog( itemsMap[item.id] = item; } - return actionsBuilder( - itemsMap, customColors, context, imageUrl); + return actionsBuilder(itemsMap, customColors, context, imageUrl); } return const LoadingIndicator( - message: '加载中,请稍候...', + message: 'Loading, please wait...', ); }, ), @@ -67,7 +68,7 @@ Widget actionsBuilder( children: [ if (itemsMap.containsKey('image-to-image')) Button( - title: '图生图', + title: AppLocale.imageToImage.getString(context), icon: Icon( Icons.collections_outlined, size: 16, @@ -90,11 +91,10 @@ Widget actionsBuilder( color: customColors.weakLinkColor, backgroundColor: const Color.fromARGB(34, 183, 183, 183), ), - if (itemsMap.containsKey('image-to-image')) - const SizedBox(height: 10), + if (itemsMap.containsKey('image-to-image')) const SizedBox(height: 10), if (itemsMap.containsKey('image-to-video')) Button( - title: '图生视频', + title: AppLocale.imageToVideo.getString(context), icon: Icon( Icons.video_camera_back_outlined, size: 16, @@ -115,11 +115,10 @@ Widget actionsBuilder( color: customColors.weakLinkColor, backgroundColor: const Color.fromARGB(34, 183, 183, 183), ), - if (itemsMap.containsKey('image-to-video')) - const SizedBox(height: 10), + if (itemsMap.containsKey('image-to-video')) const SizedBox(height: 10), if (itemsMap.containsKey('image-upscale')) Button( - title: '高清修复', + title: AppLocale.hdRestoration.getString(context), icon: Icon( Icons.hd_outlined, size: 16, @@ -140,11 +139,10 @@ Widget actionsBuilder( color: customColors.weakLinkColor, backgroundColor: const Color.fromARGB(34, 183, 183, 183), ), - if (itemsMap.containsKey('image-upscale')) - const SizedBox(height: 10), + if (itemsMap.containsKey('image-upscale')) const SizedBox(height: 10), if (itemsMap.containsKey('image-colorize')) Button( - title: '旧照片上色', + title: AppLocale.colorizeImage.getString(context), icon: Icon( Icons.palette_outlined, size: 16, diff --git a/lib/page/component/image_preview.dart b/lib/page/component/image_preview.dart index 478c357d..d2c93487 100644 --- a/lib/page/component/image_preview.dart +++ b/lib/page/component/image_preview.dart @@ -106,7 +106,7 @@ class _NetworkImagePreviewerState extends State { ), const SizedBox(width: 5), Text( - '分享', + AppLocale.share.getString(context), style: TextStyle( fontSize: 12, color: customColors.weakLinkColor, @@ -140,7 +140,7 @@ class _NetworkImagePreviewerState extends State { ), const SizedBox(width: 5), Text( - '动作', + AppLocale.shortcut.getString(context), style: TextStyle( fontSize: 12, color: customColors.weakLinkColor, @@ -170,7 +170,7 @@ class _NetworkImagePreviewerState extends State { ), const SizedBox(width: 5), Text( - '预览', + AppLocale.preview.getString(context), style: TextStyle( fontSize: 12, color: customColors.weakLinkColor, @@ -187,7 +187,7 @@ class _NetworkImagePreviewerState extends State { imageUrl: widget.url, ); } catch (e) { - showErrorMessageEnhanced(context, '图片加载失败,请稍后再试'); + showErrorMessageEnhanced(context, 'Image loading failed, please try again later'); } }, ), @@ -332,7 +332,7 @@ void openImagePreviewDialog( final cancel = BotToast.showCustomLoading( toastBuilder: (cancel) { return const LoadingIndicator( - message: '下载中,请稍候...', + message: 'Downloading, please wait...', ); }, allowClick: false, @@ -348,7 +348,7 @@ void openImagePreviewDialog( quality: 100, ); - showSuccessMessage('图片保存成功'); + showSuccessMessage(AppLocale.operateSuccess.getString(context)); } else { var ext = saveFile.path.toLowerCase().split('.').last; MimeType mimeType; @@ -382,8 +382,8 @@ void openImagePreviewDialog( await File(value).writeAsBytes(await saveFile.readAsBytes()); - Logger.instance.d('文件保存成功: $value'); - showSuccessMessage('文件保存成功'); + Logger.instance.d('File saved successfully: $value'); + showSuccessMessage(AppLocale.operateSuccess.getString(context)); }); } else { FileSaver.instance @@ -394,15 +394,15 @@ void openImagePreviewDialog( mimeType: mimeType, ) .then((value) { - Logger.instance.d('文件保存成功: $value'); - showSuccessMessage('文件保存成功'); + Logger.instance.d('File saved successfully: $value'); + showSuccessMessage(AppLocale.operateSuccess.getString(context)); }); } } } catch (e) { // ignore: use_build_context_synchronously - showErrorMessageEnhanced(context, '图片保存失败,请稍后再试'); - Logger.instance.e('下载图片原图失败', error: e); + showErrorMessageEnhanced(context, 'Image save failed, please try again later'); + Logger.instance.e('Failed to download the original image', error: e); } finally { cancel(); } @@ -419,7 +419,7 @@ void openImagePreviewDialog( final cancel = BotToast.showCustomLoading( toastBuilder: (cancel) { return const LoadingIndicator( - message: '下载中,请稍候...', + message: 'Downloading, please wait...', ); }, allowClick: false, @@ -432,11 +432,11 @@ void openImagePreviewDialog( quality: 100, ); - showSuccessMessage('图片保存成功'); + showSuccessMessage(AppLocale.operateSuccess.getString(context)); } catch (e) { // ignore: use_build_context_synchronously - showErrorMessageEnhanced(context, '图片保存失败,请稍后再试'); - Logger.instance.e('下载图片原图失败', error: e); + showErrorMessageEnhanced(context, 'Image save failed, please try again later.'); + Logger.instance.e('Failed to download the original image', error: e); } finally { cancel(); } diff --git a/lib/page/component/invite_card.dart b/lib/page/component/invite_card.dart index 1dc0bd89..73b8f570 100644 --- a/lib/page/component/invite_card.dart +++ b/lib/page/component/invite_card.dart @@ -1,9 +1,11 @@ import 'package:askaide/helper/color.dart'; +import 'package:askaide/lang/lang.dart'; import 'package:askaide/page/component/enhanced_button.dart'; import 'package:askaide/page/component/image.dart'; import 'package:askaide/page/component/share.dart'; import 'package:askaide/repo/api/user.dart'; import 'package:flutter/material.dart'; +import 'package:flutter_localization/flutter_localization.dart'; class InviteCard extends StatelessWidget { final UserInfo userInfo; @@ -64,7 +66,7 @@ class InviteCard extends StatelessWidget { ), const SizedBox(height: 10), Text( - userInfo.control.inviteCardSlogan ?? '邀请好友注册,双方都可获得奖励', + userInfo.control.inviteCardSlogan ?? AppLocale.inviteSlogan.getString(context), strutStyle: const StrutStyle(height: 1.3), overflow: TextOverflow.ellipsis, maxLines: 4, @@ -79,7 +81,7 @@ class InviteCard extends StatelessWidget { ), ), EnhancedButton( - title: '立即邀请', + title: AppLocale.inviteNow.getString(context), fontSize: 14, height: 35, width: 80, @@ -87,8 +89,9 @@ class InviteCard extends StatelessWidget { onPressed: () { shareTo( context, - content: userInfo.control.inviteMessage ?? '邀请码 ${userInfo.user.inviteCode}', - title: '邀请码分享', + content: userInfo.control.inviteMessage ?? + '${AppLocale.inviteCode.getString(context)} ${userInfo.user.inviteCode}', + title: AppLocale.inviteCodeShare.getString(context), ); }, ), diff --git a/lib/page/component/share.dart b/lib/page/component/share.dart index 74be8730..a952651b 100644 --- a/lib/page/component/share.dart +++ b/lib/page/component/share.dart @@ -1,8 +1,10 @@ import 'dart:io'; import 'package:askaide/helper/platform.dart'; +import 'package:askaide/lang/lang.dart'; import 'package:askaide/page/component/dialog.dart'; import 'package:flutter/material.dart'; +import 'package:flutter_localization/flutter_localization.dart'; import 'package:fluwx/fluwx.dart'; import 'package:go_router/go_router.dart'; import 'package:share_plus/share_plus.dart'; @@ -22,8 +24,7 @@ Future shareTo( // ignore: empty_catches } catch (ignored) {} - if ((PlatformTool.isIOS() || PlatformTool.isAndroid()) && - await isWeChatInstalled) { + if ((PlatformTool.isIOS() || PlatformTool.isAndroid()) && await isWeChatInstalled) { openModalBottomSheet( // ignore: use_build_context_synchronously context, @@ -59,9 +60,9 @@ Future shareTo( children: [ Image.asset('assets/friendroom.png', width: 40), const SizedBox(height: 10), - const Text( - '分享到朋友圈', - style: TextStyle(fontSize: 12), + Text( + AppLocale.shareToWechatQ.getString(context), + style: const TextStyle(fontSize: 12), ), ], ), @@ -88,9 +89,9 @@ Future shareTo( children: [ Image.asset('assets/wechat.png', width: 40), const SizedBox(height: 10), - const Text( - '分享到微信', - style: TextStyle(fontSize: 12), + Text( + AppLocale.shareToWechat.getString(context), + style: const TextStyle(fontSize: 12), ), ], ), @@ -116,9 +117,9 @@ Future shareTo( children: [ Image.asset('assets/share.png', width: 40), const SizedBox(height: 10), - const Text( - '分享到其它应用', - style: TextStyle(fontSize: 12), + Text( + AppLocale.shareToOtherApps.getString(context), + style: const TextStyle(fontSize: 12), ), ], ), diff --git a/lib/page/component/take_photo.dart b/lib/page/component/take_photo.dart new file mode 100644 index 00000000..9477a6fb --- /dev/null +++ b/lib/page/component/take_photo.dart @@ -0,0 +1,175 @@ +import 'dart:io'; + +import 'package:askaide/helper/platform.dart'; +import 'package:askaide/page/component/dialog.dart'; +import 'package:flutter/material.dart'; +import 'package:camera/camera.dart'; + +class TakePhoto extends StatefulWidget { + const TakePhoto({super.key}); + + @override + State createState() => _TakePhotoState(); +} + +class _TakePhotoState extends State with WidgetsBindingObserver { + CameraController? _cameraController; + late Future _initializeControllerFuture; + + @override + void didChangeAppLifecycleState(AppLifecycleState state) { + final CameraController? cameraController = _cameraController; + + // App state changed before we got the chance to initialize. + if (cameraController == null || !cameraController.value.isInitialized) { + return; + } + + if (state == AppLifecycleState.inactive) { + cameraController.dispose(); + } else if (state == AppLifecycleState.resumed) { + _initializeCameraController(cameraController.description); + } + } + + Future _initializeCameraController(CameraDescription cameraDescription) async { + final CameraController cameraController = CameraController( + cameraDescription, + PlatformTool.isWeb() ? ResolutionPreset.max : ResolutionPreset.medium, + enableAudio: false, + imageFormatGroup: ImageFormatGroup.jpeg, + ); + + _cameraController = cameraController; + + // If the controller is updated then update the UI. + cameraController.addListener(() { + if (mounted) { + setState(() {}); + } + if (cameraController.value.hasError) { + showErrorMessage('Camera error ${cameraController.value.errorDescription}'); + } + }); + + try { + _initializeControllerFuture = cameraController.initialize(); + await _initializeControllerFuture; + } on CameraException catch (e) { + switch (e.code) { + case 'CameraAccessDenied': + showErrorMessage('You have denied camera access.'); + case 'CameraAccessDeniedWithoutPrompt': + // iOS only + showErrorMessage('Please go to Settings app to enable camera access.'); + case 'CameraAccessRestricted': + // iOS only + showErrorMessage('Camera access is restricted.'); + case 'AudioAccessDenied': + showErrorMessage('You have denied audio access.'); + case 'AudioAccessDeniedWithoutPrompt': + // iOS only + showErrorMessage('Please go to Settings app to enable audio access.'); + case 'AudioAccessRestricted': + // iOS only + showErrorMessage('Audio access is restricted.'); + default: + showErrorMessage('Camera error: ${e.code} ${e.description}'); + } + } + + if (mounted) { + setState(() {}); + } + } + + @override + void initState() { + super.initState(); + WidgetsBinding.instance.addObserver(this); + + availableCameras().then((cameras) { + if (cameras.isNotEmpty) { + _initializeCameraController(cameras.first); + } + }); + } + + @override + void dispose() { + WidgetsBinding.instance.removeObserver(this); + _cameraController?.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar(title: const Text('Take a picture')), + // You must wait until the controller is initialized before displaying the + // camera preview. Use a FutureBuilder to display a loading spinner until the + // controller has finished initializing. + body: FutureBuilder( + future: _initializeControllerFuture, + builder: (context, snapshot) { + if (snapshot.connectionState == ConnectionState.done) { + // If the Future is complete, display the preview. + return CameraPreview(_cameraController!); + } else { + // Otherwise, display a loading indicator. + return const Center(child: CircularProgressIndicator()); + } + }, + ), + floatingActionButton: FloatingActionButton( + // Provide an onPressed callback. + onPressed: () async { + // Take the Picture in a try / catch block. If anything goes wrong, + // catch the error. + try { + // Ensure that the camera is initialized. + await _initializeControllerFuture; + + // Attempt to take a picture and get the file `image` + // where it was saved. + final image = await _cameraController!.takePicture(); + + if (!context.mounted) return; + + // If the picture was taken, display it on a new screen. + await Navigator.of(context).push( + MaterialPageRoute( + builder: (context) => DisplayPictureScreen( + // Pass the automatically generated path to + // the DisplayPictureScreen widget. + imagePath: image.path, + ), + ), + ); + } catch (e) { + // If an error occurs, log the error to the console. + print(e); + } + }, + child: const Icon(Icons.camera_alt), + ), + ); + } +} + +// A widget that displays the picture taken by the user. +class DisplayPictureScreen extends StatelessWidget { + final String imagePath; + + const DisplayPictureScreen({super.key, required this.imagePath}); + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar(title: const Text('Display the Picture')), + // The image is stored as a file on the device. Use the `Image.file` + // constructor with the given path to display the image. + body: Image.file(File(imagePath)), + ); + } +} diff --git a/lib/page/component/video_player.dart b/lib/page/component/video_player.dart index d8758ad6..f42e2d7c 100644 --- a/lib/page/component/video_player.dart +++ b/lib/page/component/video_player.dart @@ -3,6 +3,7 @@ import 'dart:io'; import 'package:askaide/helper/helper.dart'; import 'package:askaide/helper/logger.dart'; import 'package:askaide/helper/platform.dart'; +import 'package:askaide/lang/lang.dart'; import 'package:askaide/page/component/dialog.dart'; import 'package:askaide/page/component/loading.dart'; import 'package:askaide/page/component/theme/custom_size.dart'; @@ -11,6 +12,7 @@ import 'package:bot_toast/bot_toast.dart'; import 'package:file_saver/file_saver.dart'; import 'package:flutter/material.dart'; import 'package:flutter_cache_manager/flutter_cache_manager.dart'; +import 'package:flutter_localization/flutter_localization.dart'; import 'package:image_gallery_saver/image_gallery_saver.dart'; import 'package:media_kit/media_kit.dart'; import 'package:media_kit_video/media_kit_video.dart'; @@ -92,7 +94,7 @@ class _VideoPlayerState extends State { ), const SizedBox(width: 5), Text( - '下载', + AppLocale.download.getString(context), style: TextStyle( fontSize: 12, color: customColors.weakLinkColor, @@ -104,7 +106,7 @@ class _VideoPlayerState extends State { final cancel = BotToast.showCustomLoading( toastBuilder: (cancel) { return const LoadingIndicator( - message: '下载中,请稍候...', + message: 'Downloading, please wait...', ); }, allowClick: false, @@ -117,7 +119,7 @@ class _VideoPlayerState extends State { if (PlatformTool.isIOS() || PlatformTool.isAndroid()) { await ImageGallerySaver.saveFile(saveFile.path); - showSuccessMessage('保存成功'); + showSuccessMessage(AppLocale.operateSuccess.getString(context)); } else { var ext = saveFile.path.toLowerCase().split('.').last; @@ -136,8 +138,8 @@ class _VideoPlayerState extends State { await File(value).writeAsBytes(await saveFile.readAsBytes()); - Logger.instance.d('文件保存成功: $value'); - showSuccessMessage('文件保存成功'); + Logger.instance.d('File saved successfully: $value'); + showSuccessMessage(AppLocale.operateSuccess.getString(context)); }); } else { FileSaver.instance @@ -148,14 +150,14 @@ class _VideoPlayerState extends State { mimeType: MimeType.mpeg, ) .then((value) { - showSuccessMessage('文件保存成功'); + showSuccessMessage(AppLocale.operateSuccess.getString(context)); }); } } } catch (e) { // ignore: use_build_context_synchronously - showErrorMessageEnhanced(context, '保存失败,请稍后再试'); - Logger.instance.e('下载失败', error: e); + showErrorMessageEnhanced(context, 'Image save failed, please try again later'); + Logger.instance.e('Download failed', error: e); } finally { cancel(); } diff --git a/lib/page/creative_island/draw/components/content_preview.dart b/lib/page/creative_island/draw/components/content_preview.dart index a47b6fdd..3eeaf0f5 100644 --- a/lib/page/creative_island/draw/components/content_preview.dart +++ b/lib/page/creative_island/draw/components/content_preview.dart @@ -1,5 +1,6 @@ import 'package:askaide/helper/constant.dart'; import 'package:askaide/helper/image.dart'; +import 'package:askaide/lang/lang.dart'; import 'package:askaide/page/component/image_preview.dart'; import 'package:askaide/page/component/theme/custom_theme.dart'; import 'package:askaide/page/component/video_player.dart'; @@ -61,7 +62,7 @@ class _CreativeIslandContentPreviewState extends State { ); }, child: Text( - '取消共享', + AppLocale.cancelShare.getString(context), style: TextStyle( color: customColors.weakLinkColor, fontSize: 12, @@ -201,7 +201,7 @@ class _GalleryItemScreenState extends State { mainAxisSize: MainAxisSize.min, children: [ EnhancedButton( - title: '分享', + title: AppLocale.share.getString(context), icon: const Icon(Icons.share, size: 14), width: 80, color: customColors.backgroundInvertedColor, @@ -222,7 +222,7 @@ class _GalleryItemScreenState extends State { ), const SizedBox(width: 10), EnhancedButton( - title: '动作', + title: AppLocale.shortcut.getString(context), icon: const Icon(Icons.webhook, size: 14), width: 80, color: customColors.backgroundInvertedColor, @@ -261,7 +261,7 @@ class _GalleryItemScreenState extends State { innerPadding: const EdgeInsets.symmetric( vertical: 10, ), - title: '选择要执行动作的图片', + title: AppLocale.selectImageToShortcut.getString(context), ); } else { openImageWorkflowActionDialog( @@ -275,7 +275,7 @@ class _GalleryItemScreenState extends State { const SizedBox(width: 10), Expanded( child: EnhancedButton( - title: '制作同款', + title: AppLocale.makeSameStyle.getString(context), onPressed: () { context.push( '/creative-draw/create?mode=text-to-image&id=${state.item.creativeId}&gallery_copy_id=${state.item.id}'); diff --git a/lib/page/drawer.dart b/lib/page/drawer.dart index 6b686113..d9a290db 100644 --- a/lib/page/drawer.dart +++ b/lib/page/drawer.dart @@ -127,13 +127,25 @@ class _LeftDrawerState extends State { child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - Text( - "${AppLocale.socialMedia.getString(context)} :", - style: TextStyle( - color: customColors.weakTextColor, - fontSize: 16, - fontWeight: FontWeight.bold, - ), + Row( + children: [ + Text( + "${AppLocale.socialMedia.getString(context)} ", + style: TextStyle( + color: customColors.weakTextColor, + fontSize: 16, + fontWeight: FontWeight.bold, + ), + ), + Transform.rotate( + angle: 90 * 3.1415926535897932 / 180, + child: Icon( + Icons.turn_right, + color: customColors.weakTextColor, + size: 16, + ), + ), + ], ), const SizedBox(height: 10), Row( diff --git a/lib/page/lab/draw_board.dart b/lib/page/lab/draw_board.dart index 59f4a2b9..51917737 100644 --- a/lib/page/lab/draw_board.dart +++ b/lib/page/lab/draw_board.dart @@ -58,18 +58,17 @@ class _DrawboardScreenState extends State { } // save imageData to file - if (PlatformTool.isIOS() || - PlatformTool.isAndroid()) { + if (PlatformTool.isIOS() || PlatformTool.isAndroid()) { await ImageGallerySaver.saveImage( imageData.buffer.asUint8List(), quality: 100, ); - showSuccessMessage('图片保存成功'); + showSuccessMessage(AppLocale.operateSuccess.getString(context)); } else { if (PlatformTool.isWindows()) { FileSaver.instance - .saveAs( + .saveAs( name: randomId(), ext: '.png', bytes: imageData.buffer.asUint8List(), @@ -77,13 +76,13 @@ class _DrawboardScreenState extends State { ) .then((value) async { if (value == null) { - return ; + return; } await File(value).writeAsBytes(imageData.buffer.asUint8List()); - Logger.instance.d('文件保存成功: $value'); - showSuccessMessage('文件保存成功'); + Logger.instance.d('File saved successfully: $value'); + showSuccessMessage(AppLocale.operateSuccess.getString(context)); }); } else { FileSaver.instance @@ -94,10 +93,9 @@ class _DrawboardScreenState extends State { mimeType: MimeType.png, ) .then((value) { - showSuccessMessage('文件保存成功'); + showSuccessMessage(AppLocale.operateSuccess.getString(context)); }); } - } }, icon: const Icon(Icons.save), @@ -213,9 +211,7 @@ class _DrawMaskBoardState extends State { }, icon: Icon( Icons.edit, - color: selectedToolbar == 'draw' - ? customColors.linkColor - : customColors.weakLinkColor, + color: selectedToolbar == 'draw' ? customColors.linkColor : customColors.weakLinkColor, ), ), IconButton( @@ -227,9 +223,7 @@ class _DrawMaskBoardState extends State { }, icon: Icon( CupertinoIcons.bandage, - color: selectedToolbar == 'eraser' - ? customColors.linkColor - : customColors.weakLinkColor, + color: selectedToolbar == 'eraser' ? customColors.linkColor : customColors.weakLinkColor, ), ), IconButton( @@ -303,8 +297,7 @@ class _DrawMaskBoardState extends State { ), Expanded( child: FutureBuilder( - future: - decodeImageFromList(widget.backgroundImage.readAsBytesSync()), + future: decodeImageFromList(widget.backgroundImage.readAsBytesSync()), builder: (context, snapshot) { if (!snapshot.hasData) { return const Center(child: CircularProgressIndicator()); @@ -318,9 +311,7 @@ class _DrawMaskBoardState extends State { snapshot.data!.width.toDouble() * snapshot.data!.height.toDouble(), color: Colors.black, - child: showBackground - ? Image.file(widget.backgroundImage, fit: BoxFit.fitWidth) - : null, + child: showBackground ? Image.file(widget.backgroundImage, fit: BoxFit.fitWidth) : null, ), showDefaultActions: false, ); diff --git a/pubspec.lock b/pubspec.lock index 47741420..facaad2a 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -258,6 +258,62 @@ packages: url: "https://pub.dev" source: hosted version: "1.3.0" + camera: + dependency: "direct main" + description: + name: camera + sha256: "26ff41045772153f222ffffecba711a206f670f5834d40ebf5eed3811692f167" + url: "https://pub.dev" + source: hosted + version: "0.11.0+2" + camera_android_camerax: + dependency: transitive + description: + name: camera_android_camerax + sha256: "011be2ab0e5b3e3aa8094413fa890f8c5c5afd7cfdaef353a992047d4dab5780" + url: "https://pub.dev" + source: hosted + version: "0.6.8+2" + camera_avfoundation: + dependency: transitive + description: + name: camera_avfoundation + sha256: "2e4c568f70e406ccb87376bc06b53d2f5bebaab71e2fbcc1a950e31449381bcf" + url: "https://pub.dev" + source: hosted + version: "0.9.17+5" + camera_platform_interface: + dependency: transitive + description: + name: camera_platform_interface + sha256: b3ede1f171532e0d83111fe0980b46d17f1aa9788a07a2fbed07366bbdbb9061 + url: "https://pub.dev" + source: hosted + version: "2.8.0" + camera_web: + dependency: transitive + description: + name: camera_web + sha256: "595f28c89d1fb62d77c73c633193755b781c6d2e0ebcd8dc25b763b514e6ba8f" + url: "https://pub.dev" + source: hosted + version: "0.3.5" + camerawesome: + dependency: "direct main" + description: + name: camerawesome + sha256: "3619d5605fb14ab72c815532c1d9f635512c75df07b5a742b60a9a4b03b6081e" + url: "https://pub.dev" + source: hosted + version: "2.1.0" + carousel_slider: + dependency: transitive + description: + name: carousel_slider + sha256: "7b006ec356205054af5beaef62e2221160ea36b90fb70a35e4deacd49d0349ae" + url: "https://pub.dev" + source: hosted + version: "5.0.0" characters: dependency: transitive description: @@ -322,6 +378,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.18.0" + colorfilter_generator: + dependency: transitive + description: + name: colorfilter_generator + sha256: ccc2995e440b1d828d55d99150e7cad64624f3cb4a1e235000de3f93cf10d35c + url: "https://pub.dev" + source: hosted + version: "0.0.8" console: dependency: transitive description: @@ -671,6 +735,14 @@ packages: url: "https://pub.dev" source: hosted version: "0.6.23" + flutter_markdown_latex: + dependency: "direct main" + description: + name: flutter_markdown_latex + sha256: "839e76a84abb3632ffcebbd450cf93c7e9894af65622527d23f0084cee1bfd04" + url: "https://pub.dev" + source: hosted + version: "0.3.4" flutter_math_fork: dependency: "direct main" description: @@ -1066,6 +1138,14 @@ packages: url: "https://pub.dev" source: hosted version: "0.8.0" + matrix2d: + dependency: transitive + description: + name: matrix2d + sha256: "188718dd3bc2a31e372cfd0791b0f77f4f13ea76164147342cc378d9132949e7" + url: "https://pub.dev" + source: hosted + version: "1.0.4" media_kit: dependency: "direct main" description: diff --git a/pubspec.yaml b/pubspec.yaml index 427b2397..54c97905 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -131,6 +131,9 @@ dependencies: qr_flutter: ^4.1.0 flutter_phoenix: ^1.1.1 dio_cache_interceptor: ^3.5.0 + camera: ^0.11.0+2 + camerawesome: ^2.1.0 + flutter_markdown_latex: ^0.3.4 dev_dependencies: flutter_test: From 43a5f0294417d8232e6b3c33a41d573fd510119e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=AE=A1=E5=AE=9C=E5=B0=A7?= Date: Sat, 7 Dec 2024 18:49:53 +0800 Subject: [PATCH 13/26] update --- lib/lang/lang.dart | 38 +++++++- lib/page/admin/channels.dart | 2 +- lib/page/admin/channels_add.dart | 30 +++--- lib/page/admin/channels_edit.dart | 30 +++--- lib/page/admin/dashboard.dart | 26 ++--- lib/page/admin/models.dart | 12 ++- lib/page/admin/models_add.dart | 94 ++++++++++--------- lib/page/admin/models_edit.dart | 94 ++++++++++--------- lib/page/admin/payments.dart | 12 ++- lib/page/admin/recently_messages.dart | 6 +- lib/page/admin/rooms.dart | 2 +- lib/page/admin/user.dart | 52 +++++----- lib/page/admin/users.dart | 10 +- lib/page/balance/payment.dart | 6 +- lib/page/balance/price_block.dart | 4 +- lib/page/chat/component/room_item.dart | 2 +- lib/page/chat/group/create.dart | 8 +- lib/page/chat/room_create.dart | 4 +- lib/page/chat/room_edit.dart | 12 +-- lib/page/component/dialog.dart | 16 ++-- .../creative_island/my_creation_item.dart | 20 ++-- lib/page/lab/avatar_selector.dart | 4 +- lib/page/lab/creative_models.dart | 8 +- lib/repo/api/admin/models.dart | 5 +- 24 files changed, 277 insertions(+), 220 deletions(-) diff --git a/lib/lang/lang.dart b/lib/lang/lang.dart index 3a90872a..1cd110a5 100644 --- a/lib/lang/lang.dart +++ b/lib/lang/lang.dart @@ -77,7 +77,7 @@ mixin AppLocale { static const String switchModel = 'switch-model'; static const String switchModelTitle = 'switch-model-title'; - static const String room = "room"; + static const String character = "character"; static const String createRoom = "create-room"; static const String model = "model"; static const String selectModel = "select-model"; @@ -132,6 +132,8 @@ mixin AppLocale { static const String generateTimeout = 'generate-timeout'; static const String creativeIslandNeedSignIn = 'creative-island-need-sign-in'; static const String generateResult = 'generate-result'; + static const String generateFailed = 'generate-failed'; + static const String generating = 'generating'; static const String generateExitConfirm = 'generate-exit-confirm'; static const String tooManyRequests = 'too-many-requests'; static const String tooManyRequestsOrPaymentRequired = 'too-many-requests-or-payment-required'; @@ -293,6 +295,14 @@ mixin AppLocale { static const String startNewChatTips = 'start-new-chat-tips'; static const String wantMoreContentTips = 'want-more-content-tips'; static const String unbilled = 'unbilled'; + static const String signinNow = 'signin-now'; + static const String needSigninToUse = 'need-signin-to-use'; + static const String reSignIn = 're-sign-in'; + static const String ideaPrompt = 'idea-prompt'; + static const String groupChat = 'group-chat'; + static const String selectGroupMembers = 'select-group-members'; + static const String selectPaymentMethod = 'select-payment-method'; + static const String validDays = 'valid-days'; static const Map zh = { required: '必填', @@ -358,7 +368,7 @@ mixin AppLocale { account: '账号', usedUp: '已用完', expired: '已过期', - room: '数字人', + character: '数字人', createRoom: '创建数字人', model: 'AI 模型', selectModel: '选择模型', @@ -421,6 +431,8 @@ mixin AppLocale { generateTimeout: '创作超时,请稍后再试', creativeIslandNeedSignIn: '登录后解锁更多玩法~', generateResult: '创作结果', + generateFailed: '创作失败', + generating: '创作中...', generateExitConfirm: '创作中...\n退出后,可在历史记录中查看结果', tooManyRequests: '操作过于频繁,请稍后再试', tooManyRequestsOrPaymentRequired: '操作过于频繁(如果您使用了自定义的 OpenAI Keys,请登录 https://platform.openai.com 检查账户余额是否充足)', @@ -572,6 +584,14 @@ mixin AppLocale { startNewChatTips: '想要开启新的聊天?试试', wantMoreContentTips: '想要更多内容?试着对我说', unbilled: '未出账', + signinNow: '立即登录', + needSigninToUse: '该功能需要登录账号后使用', + reSignIn: '重新登录', + ideaPrompt: '想法', + groupChat: '群聊', + selectGroupMembers: '选择参与群聊的成员', + selectPaymentMethod: '请选择支付方式', + validDays: '内有效', }; static const Map en = { @@ -638,7 +658,7 @@ mixin AppLocale { account: 'Account', usedUp: 'Used Up', expired: 'Expired', - room: 'Character', + character: 'Character', createRoom: 'Create Character', model: 'AI Model', selectModel: 'Select Model', @@ -702,6 +722,8 @@ mixin AppLocale { generateTimeout: 'Generate timeout, please try again later', creativeIslandNeedSignIn: 'Unlock more features after login', generateResult: 'Generate result', + generateFailed: 'Creation failed', + generating: 'Generating...', generateExitConfirm: 'Generating...\nYou can view the result in the history', tooManyRequests: 'Too many requests, please try again later', tooManyRequestsOrPaymentRequired: @@ -791,7 +813,7 @@ mixin AppLocale { paymentHistory: 'Histories', buyCoins: 'Buy coins', coinUnit: '', - toPay: 'To pay', + toPay: 'Create Order', discover: 'Discover', customHomeModels: 'Favorite Models', userApiKeys: 'API Keys', @@ -856,6 +878,14 @@ mixin AppLocale { startNewChatTips: 'Want to start a new chat? Try', wantMoreContentTips: 'Want more content? Try', unbilled: 'Pending', + signinNow: 'Sign in now', + needSigninToUse: 'Please login first', + reSignIn: 'Re-login', + ideaPrompt: 'Prompt', + groupChat: 'Group', + selectGroupMembers: 'Select group members', + selectPaymentMethod: 'Select payment method', + validDays: 'expiration', }; } diff --git a/lib/page/admin/channels.dart b/lib/page/admin/channels.dart index 741ca3f8..698864f9 100644 --- a/lib/page/admin/channels.dart +++ b/lib/page/admin/channels.dart @@ -53,7 +53,7 @@ class _ChannelsPageState extends State { appBar: AppBar( toolbarHeight: CustomSize.toolbarHeight, title: const Text( - '渠道管理', + 'Channel', style: TextStyle(fontSize: CustomSize.appBarTitleSize), ), centerTitle: true, diff --git a/lib/page/admin/channels_add.dart b/lib/page/admin/channels_add.dart index 50e37a5b..a6d83e23 100644 --- a/lib/page/admin/channels_add.dart +++ b/lib/page/admin/channels_add.dart @@ -86,7 +86,7 @@ class _ChannelAddPageState extends State { appBar: AppBar( toolbarHeight: CustomSize.toolbarHeight, title: const Text( - '新增渠道', + 'New Channel', style: TextStyle(fontSize: CustomSize.appBarTitleSize), ), centerTitle: true, @@ -114,17 +114,17 @@ class _ChannelAddPageState extends State { ColumnBlock( children: [ EnhancedTextField( - labelText: '渠道名称', + labelText: 'Name', customColors: customColors, controller: nameController, textAlignVertical: TextAlignVertical.top, - hintText: '请输入渠道名称', + hintText: 'Enter channel name', maxLength: 100, showCounter: false, ), EnhancedInput( title: Text( - '类型', + 'Type', style: TextStyle( color: customColors.textfieldLabelColor, fontSize: 16, @@ -153,7 +153,7 @@ class _ChannelAddPageState extends State { }, ), EnhancedTextField( - labelText: '服务器', + labelText: 'Server', customColors: customColors, controller: serverController, textAlignVertical: TextAlignVertical.top, @@ -162,11 +162,11 @@ class _ChannelAddPageState extends State { showCounter: false, ), EnhancedTextField( - labelText: '鉴权密钥', + labelText: 'API Key', customColors: customColors, controller: secretController, textAlignVertical: TextAlignVertical.top, - hintText: '请输入鉴权密钥', + hintText: 'Enter API Key', maxLength: 255, obscureText: true, showCounter: false, @@ -182,7 +182,7 @@ class _ChannelAddPageState extends State { mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ const Text( - '使用代理', + 'Use Proxy', style: TextStyle(fontSize: 16), ), CupertinoSwitch( @@ -200,7 +200,7 @@ class _ChannelAddPageState extends State { mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ const Text( - 'Azure 模式', + 'Azure Mode', style: TextStyle(fontSize: 16), ), CupertinoSwitch( @@ -215,7 +215,7 @@ class _ChannelAddPageState extends State { ], ), EnhancedTextField( - labelText: 'API 版本', + labelText: 'Version', customColors: customColors, controller: azureAPIVersionController, textAlignVertical: TextAlignVertical.top, @@ -268,22 +268,22 @@ class _ChannelAddPageState extends State { /// 提交 void onSubmit() { if (nameController.text.isEmpty) { - showErrorMessage('请输入渠道名称'); + showErrorMessage('Please enter a channel name'); return; } if (selectedChannelType == null) { - showErrorMessage('请选择渠道类型'); + showErrorMessage('Please select channel type'); return; } if (serverController.text.isEmpty) { - showErrorMessage('请输入服务器地址'); + showErrorMessage('Please enter the server address'); return; } if (!serverController.text.startsWith('http://') && !serverController.text.startsWith('https://')) { - showErrorMessage('服务器地址格式不正确'); + showErrorMessage('The server address format is incorrect'); return; } @@ -305,7 +305,7 @@ class _ChannelAddPageState extends State { /// 生成选中的渠道类型文本 String buildSelectedChannelTypeText() { if (selectedChannelType == null) { - return '请选择'; + return 'Select'; } return channelTypes.firstWhere((element) => element.name == selectedChannelType).text; diff --git a/lib/page/admin/channels_edit.dart b/lib/page/admin/channels_edit.dart index 3cc2264b..289b61ab 100644 --- a/lib/page/admin/channels_edit.dart +++ b/lib/page/admin/channels_edit.dart @@ -93,7 +93,7 @@ class _ChannelEditPageState extends State { appBar: AppBar( toolbarHeight: CustomSize.toolbarHeight, title: const Text( - '编辑渠道', + 'Edit Channel', style: TextStyle(fontSize: CustomSize.appBarTitleSize), ), centerTitle: true, @@ -133,17 +133,17 @@ class _ChannelEditPageState extends State { ColumnBlock( children: [ EnhancedTextField( - labelText: '渠道名称', + labelText: 'Name', customColors: customColors, controller: nameController, textAlignVertical: TextAlignVertical.top, - hintText: '请输入渠道名称', + hintText: 'Enter channel name', maxLength: 100, showCounter: false, ), EnhancedInput( title: Text( - '类型', + 'Type', style: TextStyle( color: customColors.textfieldLabelColor, fontSize: 16, @@ -172,7 +172,7 @@ class _ChannelEditPageState extends State { }, ), EnhancedTextField( - labelText: '服务器', + labelText: 'Server', customColors: customColors, controller: serverController, textAlignVertical: TextAlignVertical.top, @@ -181,11 +181,11 @@ class _ChannelEditPageState extends State { showCounter: false, ), EnhancedTextField( - labelText: '鉴权密钥', + labelText: 'API Key', customColors: customColors, controller: secretController, textAlignVertical: TextAlignVertical.top, - hintText: '请输入鉴权密钥', + hintText: 'Enter API Key', maxLength: 255, obscureText: true, showCounter: false, @@ -201,7 +201,7 @@ class _ChannelEditPageState extends State { mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ const Text( - '使用代理', + 'Use Proxy', style: TextStyle(fontSize: 16), ), CupertinoSwitch( @@ -219,7 +219,7 @@ class _ChannelEditPageState extends State { mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ const Text( - 'Azure 模式', + 'Azure Mode', style: TextStyle(fontSize: 16), ), CupertinoSwitch( @@ -234,7 +234,7 @@ class _ChannelEditPageState extends State { ], ), EnhancedTextField( - labelText: 'API 版本', + labelText: 'Version', customColors: customColors, controller: azureAPIVersionController, textAlignVertical: TextAlignVertical.top, @@ -294,22 +294,22 @@ class _ChannelEditPageState extends State { } if (nameController.text.isEmpty) { - showErrorMessage('请输入渠道名称'); + showErrorMessage('Please enter a channel name'); return; } if (selectedChannelType == null) { - showErrorMessage('请选择渠道类型'); + showErrorMessage('Please select channel type'); return; } if (serverController.text.isEmpty) { - showErrorMessage('请输入服务器地址'); + showErrorMessage('Please enter the server address'); return; } if (!serverController.text.startsWith('http://') && !serverController.text.startsWith('https://')) { - showErrorMessage('服务器地址格式不正确'); + showErrorMessage('The server address format is incorrect'); return; } @@ -331,7 +331,7 @@ class _ChannelEditPageState extends State { /// 生成选中的渠道类型文本 String buildSelectedChannelTypeText() { if (selectedChannelType == null) { - return '请选择'; + return 'Select'; } return channelTypes.firstWhere((element) => element.name == selectedChannelType).text; diff --git a/lib/page/admin/dashboard.dart b/lib/page/admin/dashboard.dart index 9207b754..01d82fdb 100644 --- a/lib/page/admin/dashboard.dart +++ b/lib/page/admin/dashboard.dart @@ -50,10 +50,10 @@ class _AdminDashboardPageState extends State { ), sections: [ SettingsSection( - title: const Text('使用记录'), + title: const Text('Usage'), tiles: [ SettingsTile( - title: const Text('创作岛历史记录'), + title: const Text('Creation Island History'), trailing: const Icon( CupertinoIcons.chevron_forward, size: 18, @@ -64,7 +64,7 @@ class _AdminDashboardPageState extends State { }, ), SettingsTile( - title: const Text('普通聊天历史记录'), + title: const Text('Chat History'), trailing: const Icon( CupertinoIcons.chevron_forward, size: 18, @@ -77,10 +77,10 @@ class _AdminDashboardPageState extends State { ], ), SettingsSection( - title: const Text('用户 & 收入'), + title: const Text('Users & Revenue'), tiles: [ SettingsTile( - title: const Text('用户管理'), + title: const Text('User Management'), trailing: const Icon( CupertinoIcons.chevron_forward, size: 18, @@ -91,7 +91,7 @@ class _AdminDashboardPageState extends State { }, ), SettingsTile( - title: const Text('支付订单历史'), + title: const Text('Payment Order History'), trailing: const Icon( CupertinoIcons.chevron_forward, size: 18, @@ -104,10 +104,10 @@ class _AdminDashboardPageState extends State { ], ), SettingsSection( - title: const Text('模型管理'), + title: const Text('Model management'), tiles: [ SettingsTile( - title: const Text('渠道'), + title: const Text('Channel'), trailing: const Icon( CupertinoIcons.chevron_forward, size: 18, @@ -118,7 +118,7 @@ class _AdminDashboardPageState extends State { }, ), SettingsTile( - title: const Text('大语言模型'), + title: const Text('Large Language Model'), trailing: const Icon( CupertinoIcons.chevron_forward, size: 18, @@ -131,10 +131,10 @@ class _AdminDashboardPageState extends State { ], ), SettingsSection( - title: const Text('系统设置'), + title: const Text('System settings'), tiles: [ SettingsTile( - title: const Text('更新配置缓存'), + title: const Text('Refresh Config Cache'), trailing: const Icon( CupertinoIcons.chevron_forward, size: 18, @@ -143,10 +143,10 @@ class _AdminDashboardPageState extends State { onPressed: (context) { openConfirmDialog( context, - '该操作将重新加载全部系统配置,确定继续?', + 'Reload all system configurations.\n Are you sure you want to proceed?', () { APIServer().adminSettingsReload().then((value) { - showSuccessMessage('更新成功'); + showSuccessMessage('Update successful'); }).onError((error, stackTrace) { showErrorMessageEnhanced(context, error!); }); diff --git a/lib/page/admin/models.dart b/lib/page/admin/models.dart index c9e60a4c..6c6fb530 100644 --- a/lib/page/admin/models.dart +++ b/lib/page/admin/models.dart @@ -81,7 +81,7 @@ class _AdminModelsPageState extends State { appBar: AppBar( toolbarHeight: CustomSize.toolbarHeight, title: const Text( - '大语言模型管理', + 'Large Language Model', style: TextStyle(fontSize: CustomSize.appBarTitleSize), ), centerTitle: true, @@ -276,10 +276,14 @@ class _AdminModelsPageState extends State { children: [ Text( mod.name, - style: const TextStyle( + style: TextStyle( overflow: TextOverflow.ellipsis, + fontWeight: mod.enabled ? FontWeight.bold : FontWeight.normal, + color: mod.enabled ? null : customColors.weakLinkColor?.withAlpha(100), + decoration: mod.enabled ? null : TextDecoration.lineThrough, ), ), + const SizedBox(height: 5), Text( buildModelDescription(mod), style: TextStyle( @@ -353,9 +357,9 @@ class _AdminModelsPageState extends State { if (mod.inputPrice > 0 || mod.outputPrice > 0) { desc += '💰 '; if (mod.inputPrice == mod.outputPrice) { - desc += '${mod.inputPrice} 智慧果/1K Token'; + desc += '${mod.inputPrice} Coins/1K Token'; } else { - desc += '${mod.inputPrice} / ${mod.outputPrice} 智慧果/1K Token'; + desc += '${mod.inputPrice} / ${mod.outputPrice} Coins/1K Token'; } } diff --git a/lib/page/admin/models_add.dart b/lib/page/admin/models_add.dart index 1d595c8d..a1f5757b 100644 --- a/lib/page/admin/models_add.dart +++ b/lib/page/admin/models_add.dart @@ -126,7 +126,7 @@ class _AdminModelCreatePageState extends State { appBar: AppBar( toolbarHeight: CustomSize.toolbarHeight, title: const Text( - '新增模型', + 'New Model', style: TextStyle(fontSize: CustomSize.appBarTitleSize), ), centerTitle: true, @@ -155,36 +155,36 @@ class _AdminModelCreatePageState extends State { ColumnBlock( children: [ EnhancedTextField( - labelText: '唯一标识', + labelText: 'ID', customColors: customColors, controller: modelIdController, textAlignVertical: TextAlignVertical.top, - hintText: '请输入模型唯一标识', + hintText: 'Enter a unique ID', maxLength: 100, showCounter: false, ), EnhancedTextField( - labelText: '厂商', + labelText: 'Vendor', customColors: customColors, controller: categoryController, textAlignVertical: TextAlignVertical.top, - hintText: '请输入厂商名称(可选)', + hintText: 'Enter a vendor name (Optional)', maxLength: 100, showCounter: false, ), EnhancedTextField( - labelText: '名称', + labelText: 'Name', customColors: customColors, controller: nameController, textAlignVertical: TextAlignVertical.top, - hintText: '请输入模型名称', + hintText: 'Enter a model name', maxLength: 100, showCounter: false, ), EnhancedInput( padding: const EdgeInsets.only(top: 10, bottom: 5), title: Text( - '头像', + 'Avatar', style: TextStyle( color: customColors.textfieldLabelColor, fontSize: 16, @@ -242,11 +242,11 @@ class _AdminModelCreatePageState extends State { }, ), EnhancedTextField( - labelText: '描述', + labelText: 'Description', customColors: customColors, controller: descriptionController, textAlignVertical: TextAlignVertical.top, - hintText: '可选', + hintText: 'Optional', maxLength: 255, showCounter: false, maxLines: 3, @@ -256,11 +256,12 @@ class _AdminModelCreatePageState extends State { ColumnBlock( children: [ EnhancedTextField( - labelText: '输入价格', + labelWidth: 120, + labelText: 'Input Price', customColors: customColors, controller: inputPriceController, textAlignVertical: TextAlignVertical.top, - hintText: '可选', + hintText: 'Optional', showCounter: false, keyboardType: TextInputType.number, inputFormatters: [FilteringTextInputFormatter.digitsOnly], @@ -269,17 +270,18 @@ class _AdminModelCreatePageState extends State { width: 110, alignment: Alignment.center, child: Text( - '智慧果/1K Token', + 'Coins/1K Token', style: TextStyle(color: customColors.weakTextColor, fontSize: 12), ), ), ), EnhancedTextField( - labelText: '输出价格', + labelWidth: 120, + labelText: 'Output Price', customColors: customColors, controller: outputPriceController, textAlignVertical: TextAlignVertical.top, - hintText: '可选', + hintText: 'Optional', showCounter: false, keyboardType: TextInputType.number, inputFormatters: [FilteringTextInputFormatter.digitsOnly], @@ -288,17 +290,18 @@ class _AdminModelCreatePageState extends State { width: 110, alignment: Alignment.center, child: Text( - '智慧果/1K Token', + 'Coins/1K Token', style: TextStyle(color: customColors.weakTextColor, fontSize: 12), ), ), ), EnhancedTextField( - labelText: '输入限制', + labelWidth: 120, + labelText: 'Context Length', customColors: customColors, controller: maxContextController, textAlignVertical: TextAlignVertical.top, - hintText: '最大上下文减掉预期的输出长度', + hintText: 'Subtract the expected output length from the maximum context.', showCounter: false, keyboardType: TextInputType.number, inputFormatters: [FilteringTextInputFormatter.digitsOnly], @@ -329,7 +332,7 @@ class _AdminModelCreatePageState extends State { icon: Icons.delete, onPressed: (_) { if (providers.length == 1) { - showErrorMessage('至少需要一个渠道'); + showErrorMessage('At least one channel is needed'); return; } @@ -352,7 +355,7 @@ class _AdminModelCreatePageState extends State { children: [ EnhancedInput( title: Text( - '渠道', + 'Channel', style: TextStyle( color: customColors.textfieldLabelColor, fontSize: 16, @@ -372,7 +375,7 @@ class _AdminModelCreatePageState extends State { ...modelChannels .map( (e) => SelectorItem( - Text('${e.id == null ? '【系统】' : ''}${e.name}'), + Text('${e.id == null ? '【System】' : ''}${e.name}'), e, ), ) @@ -393,10 +396,11 @@ class _AdminModelCreatePageState extends State { }, ), EnhancedTextField( - labelText: '模型重写', + labelWidth: 120, + labelText: 'Model Rewrite', customColors: customColors, textAlignVertical: TextAlignVertical.top, - hintText: '可选', + hintText: 'Optional', maxLength: 100, showCounter: false, initValue: e.modelRewrite, @@ -411,7 +415,7 @@ class _AdminModelCreatePageState extends State { }).toList(), const SizedBox(width: 10), WeakTextButton( - title: '添加渠道', + title: 'Add Channel', icon: Icons.add, onPressed: () { setState(() { @@ -425,20 +429,20 @@ class _AdminModelCreatePageState extends State { innerPanding: 5, children: [ EnhancedTextField( - labelText: '简称', + labelText: 'Abbr.', customColors: customColors, controller: shortNameController, textAlignVertical: TextAlignVertical.top, - hintText: '请输入模型简称', + hintText: 'Enter model shorthand', maxLength: 100, showCounter: false, ), EnhancedTextField( - labelText: '标签', + labelText: 'Tag', customColors: customColors, controller: tagController, textAlignVertical: TextAlignVertical.top, - hintText: '请输入标签', + hintText: 'Enter tags', maxLength: 100, showCounter: false, ), @@ -448,7 +452,7 @@ class _AdminModelCreatePageState extends State { Row( children: [ const Text( - '视觉', + 'Vision', style: TextStyle(fontSize: 16), ), const SizedBox(width: 5), @@ -457,7 +461,7 @@ class _AdminModelCreatePageState extends State { showBeautyDialog( context, type: QuickAlertType.info, - text: '当前模型是否支持视觉能力。', + text: 'Whether the current model supports visual capabilities.', confirmBtnText: AppLocale.gotIt.getString(context), showCancelBtn: false, ); @@ -487,7 +491,7 @@ class _AdminModelCreatePageState extends State { Row( children: [ const Text( - '上新', + 'New', style: TextStyle(fontSize: 16), ), const SizedBox(width: 5), @@ -496,7 +500,8 @@ class _AdminModelCreatePageState extends State { showBeautyDialog( context, type: QuickAlertType.info, - text: '是否在模型旁边展示“新”标识,告知用户这是一个新模型。', + text: + 'Whether to display a "New" icon next to the model to inform users that this is a new model.', confirmBtnText: AppLocale.gotIt.getString(context), showCancelBtn: false, ); @@ -526,7 +531,7 @@ class _AdminModelCreatePageState extends State { Row( children: [ const Text( - '受限模型', + 'Restricted', style: TextStyle(fontSize: 16), ), const SizedBox(width: 5), @@ -535,7 +540,8 @@ class _AdminModelCreatePageState extends State { showBeautyDialog( context, type: QuickAlertType.info, - text: '受限模型是指因政策因素,不能在中国大陆地区使用的模型。', + text: + 'Restricted models refer to models that cannot be used in Chinese Mainland due to policy factors.', confirmBtnText: AppLocale.gotIt.getString(context), showCancelBtn: false, ); @@ -563,7 +569,7 @@ class _AdminModelCreatePageState extends State { mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ const Text( - '启用', + 'Enabled', style: TextStyle(fontSize: 16), ), CupertinoSwitch( @@ -579,11 +585,11 @@ class _AdminModelCreatePageState extends State { ), EnhancedTextField( labelPosition: LabelPosition.top, - labelText: '系统提示语', + labelText: 'System prompt', customColors: customColors, controller: promptController, textAlignVertical: TextAlignVertical.top, - hintText: '全局系统提示语', + hintText: 'Global system prompt', maxLength: 2000, maxLines: 3, ), @@ -633,18 +639,18 @@ class _AdminModelCreatePageState extends State { /// 提交 void onSubmit() async { if (nameController.text.isEmpty) { - showErrorMessage('请输入模型名称'); + showErrorMessage('Please enter a model name'); return; } if (modelIdController.text.isEmpty) { - showErrorMessage('请输入模型唯一标识'); + showErrorMessage('Please enter a model ID'); return; } final ps = providers.where((e) => e.id != null || e.name != null).toList(); if (ps.isEmpty) { - showErrorMessage('至少需要一个渠道'); + showErrorMessage('At least one channel is required'); return; } @@ -652,7 +658,7 @@ class _AdminModelCreatePageState extends State { final cancel = BotToast.showCustomLoading( toastBuilder: (cancel) { return const LoadingIndicator( - message: '正在上传头像,请稍后...', + message: 'Uploading avatar, please wait...', ); }, allowClick: false, @@ -662,7 +668,7 @@ class _AdminModelCreatePageState extends State { final res = await ImageUploader(widget.setting).upload(avatarUrl!, usage: 'avatar'); avatarUrl = res.url; } catch (e) { - showErrorMessage('上传头像失败'); + showErrorMessage('Failed to upload avatar'); cancel(); return; } finally { @@ -707,11 +713,11 @@ class _AdminModelCreatePageState extends State { return modelChannels .firstWhere( (e) => e.type == provider.name! && e.id == null, - orElse: () => AdminChannel(name: '未知', type: ''), + orElse: () => AdminChannel(name: 'Unknown', type: ''), ) .display; } - return '请选择'; + return 'Select'; } } diff --git a/lib/page/admin/models_edit.dart b/lib/page/admin/models_edit.dart index bd994079..97592658 100644 --- a/lib/page/admin/models_edit.dart +++ b/lib/page/admin/models_edit.dart @@ -135,7 +135,7 @@ class _AdminModelEditPageState extends State { appBar: AppBar( toolbarHeight: CustomSize.toolbarHeight, title: const Text( - '编辑模型', + 'Edit Model', style: TextStyle(fontSize: CustomSize.appBarTitleSize), ), centerTitle: true, @@ -209,37 +209,37 @@ class _AdminModelEditPageState extends State { ColumnBlock( children: [ EnhancedTextField( - labelText: '唯一标识', + labelText: 'ID', customColors: customColors, controller: modelIdController, textAlignVertical: TextAlignVertical.top, - hintText: '请输入模型唯一标识', + hintText: 'Enter a unique ID', maxLength: 100, showCounter: false, readOnly: true, ), EnhancedTextField( - labelText: '厂商', + labelText: 'Vendor', customColors: customColors, controller: categoryController, textAlignVertical: TextAlignVertical.top, - hintText: '请输入厂商名称(可选)', + hintText: 'Enter a vendor name (Optional)', maxLength: 100, showCounter: false, ), EnhancedTextField( - labelText: '名称', + labelText: 'Name', customColors: customColors, controller: nameController, textAlignVertical: TextAlignVertical.top, - hintText: '请输入模型名称', + hintText: 'Enter a model name', maxLength: 100, showCounter: false, ), EnhancedInput( padding: const EdgeInsets.only(top: 10, bottom: 5), title: Text( - '头像', + 'Avatar', style: TextStyle( color: customColors.textfieldLabelColor, fontSize: 16, @@ -297,11 +297,11 @@ class _AdminModelEditPageState extends State { }, ), EnhancedTextField( - labelText: '描述', + labelText: 'Description', customColors: customColors, controller: descriptionController, textAlignVertical: TextAlignVertical.top, - hintText: '可选', + hintText: 'Optional', maxLength: 255, showCounter: false, maxLines: 3, @@ -311,11 +311,12 @@ class _AdminModelEditPageState extends State { ColumnBlock( children: [ EnhancedTextField( - labelText: '输入价格', + labelWidth: 120, + labelText: 'Input Price', customColors: customColors, controller: inputPriceController, textAlignVertical: TextAlignVertical.top, - hintText: '可选', + hintText: 'Optional', showCounter: false, keyboardType: TextInputType.number, inputFormatters: [FilteringTextInputFormatter.digitsOnly], @@ -324,17 +325,18 @@ class _AdminModelEditPageState extends State { width: 110, alignment: Alignment.center, child: Text( - '智慧果/1K Token', + 'Coins/1K Token', style: TextStyle(color: customColors.weakTextColor, fontSize: 12), ), ), ), EnhancedTextField( - labelText: '输出价格', + labelWidth: 120, + labelText: 'Output Price', customColors: customColors, controller: outputPriceController, textAlignVertical: TextAlignVertical.top, - hintText: '可选', + hintText: 'Optional', showCounter: false, keyboardType: TextInputType.number, inputFormatters: [FilteringTextInputFormatter.digitsOnly], @@ -343,17 +345,17 @@ class _AdminModelEditPageState extends State { width: 110, alignment: Alignment.center, child: Text( - '智慧果/1K Token', + 'Coins/1K Token', style: TextStyle(color: customColors.weakTextColor, fontSize: 12), ), ), ), EnhancedTextField( - labelText: '输入限制', + labelText: 'Context Length', customColors: customColors, controller: maxContextController, textAlignVertical: TextAlignVertical.top, - hintText: '最大上下文减掉预期的输出长度', + hintText: 'Subtract the expected output length from the maximum context.', showCounter: false, keyboardType: TextInputType.number, inputFormatters: [FilteringTextInputFormatter.digitsOnly], @@ -384,7 +386,7 @@ class _AdminModelEditPageState extends State { icon: Icons.delete, onPressed: (_) { if (providers.length == 1) { - showErrorMessage('至少需要一个渠道'); + showErrorMessage('At least one channel is needed'); return; } @@ -407,7 +409,7 @@ class _AdminModelEditPageState extends State { children: [ EnhancedInput( title: Text( - '渠道', + 'Channel', style: TextStyle( color: customColors.textfieldLabelColor, fontSize: 16, @@ -427,7 +429,7 @@ class _AdminModelEditPageState extends State { ...modelChannels .map( (e) => SelectorItem( - Text('${e.id == null ? '【系统】' : ''}${e.name}'), + Text('${e.id == null ? '【System】' : ''}${e.name}'), e, ), ) @@ -448,11 +450,12 @@ class _AdminModelEditPageState extends State { }, ), EnhancedTextField( - labelText: '模型重写', + labelWidth: 120, + labelText: 'Model Rewrite', labelFontSize: 12, customColors: customColors, textAlignVertical: TextAlignVertical.top, - hintText: '可选', + hintText: 'Optional', maxLength: 100, showCounter: false, initValue: providers[i].modelRewrite, @@ -466,7 +469,8 @@ class _AdminModelEditPageState extends State { showBeautyDialog( context, type: QuickAlertType.info, - text: '渠道对应的模型标识和这里的 ID 不一致时,调用渠道接口时将会自动将模型替换为这里配置的值。', + text: + 'When the model identifier corresponding to the channel does not match the ID here, calling the channel interface will automatically replace the model with the value configured here.', confirmBtnText: AppLocale.gotIt.getString(context), showCancelBtn: false, ); @@ -484,7 +488,7 @@ class _AdminModelEditPageState extends State { ), const SizedBox(width: 10), WeakTextButton( - title: '添加渠道', + title: 'Add Channel', icon: Icons.add, onPressed: () { setState(() { @@ -498,20 +502,20 @@ class _AdminModelEditPageState extends State { innerPanding: 5, children: [ EnhancedTextField( - labelText: '简称', + labelText: 'Abbr.', customColors: customColors, controller: shortNameController, textAlignVertical: TextAlignVertical.top, - hintText: '请输入模型简称', + hintText: 'Enter model shorthand', maxLength: 100, showCounter: false, ), EnhancedTextField( - labelText: '标签', + labelText: 'Tag', customColors: customColors, controller: tagController, textAlignVertical: TextAlignVertical.top, - hintText: '请输入标签', + hintText: 'Enter tags', maxLength: 100, showCounter: false, ), @@ -521,7 +525,7 @@ class _AdminModelEditPageState extends State { Row( children: [ const Text( - '视觉', + 'Vision', style: TextStyle(fontSize: 16), ), const SizedBox(width: 5), @@ -530,7 +534,7 @@ class _AdminModelEditPageState extends State { showBeautyDialog( context, type: QuickAlertType.info, - text: '当前模型是否支持视觉能力。', + text: 'Whether the current model supports visual capabilities.', confirmBtnText: AppLocale.gotIt.getString(context), showCancelBtn: false, ); @@ -560,7 +564,7 @@ class _AdminModelEditPageState extends State { Row( children: [ const Text( - '上新', + 'New', style: TextStyle(fontSize: 16), ), const SizedBox(width: 5), @@ -569,7 +573,8 @@ class _AdminModelEditPageState extends State { showBeautyDialog( context, type: QuickAlertType.info, - text: '是否在模型旁边展示“新”标识,告知用户这是一个新模型。', + text: + 'Whether to display a "New" icon next to the model to inform users that this is a new model.', confirmBtnText: AppLocale.gotIt.getString(context), showCancelBtn: false, ); @@ -599,7 +604,7 @@ class _AdminModelEditPageState extends State { Row( children: [ const Text( - '受限模型', + 'Restricted', style: TextStyle(fontSize: 16), ), const SizedBox(width: 5), @@ -608,7 +613,8 @@ class _AdminModelEditPageState extends State { showBeautyDialog( context, type: QuickAlertType.info, - text: '受限模型是指因政策因素,不能在中国大陆地区使用的模型。', + text: + 'Restricted models refer to models that cannot be used in Chinese Mainland due to policy factors.', confirmBtnText: AppLocale.gotIt.getString(context), showCancelBtn: false, ); @@ -636,7 +642,7 @@ class _AdminModelEditPageState extends State { mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ const Text( - '启用', + 'Enabled', style: TextStyle(fontSize: 16), ), CupertinoSwitch( @@ -652,11 +658,11 @@ class _AdminModelEditPageState extends State { ), EnhancedTextField( labelPosition: LabelPosition.top, - labelText: '系统提示语', + labelText: 'System prompt', customColors: customColors, controller: promptController, textAlignVertical: TextAlignVertical.top, - hintText: '全局系统提示语', + hintText: 'Global system prompt', maxLength: 2000, maxLines: 3, ), @@ -713,13 +719,13 @@ class _AdminModelEditPageState extends State { } if (nameController.text.isEmpty) { - showErrorMessage('请输入模型名称'); + showErrorMessage('Please enter a model name'); return; } final ps = providers.where((e) => e.id != null || e.name != null).toList(); if (ps.isEmpty) { - showErrorMessage('至少需要一个渠道'); + showErrorMessage('At least one channel is required'); return; } @@ -727,7 +733,7 @@ class _AdminModelEditPageState extends State { final cancel = BotToast.showCustomLoading( toastBuilder: (cancel) { return const LoadingIndicator( - message: '正在上传头像,请稍后...', + message: 'Uploading avatar, please wait...', ); }, allowClick: false, @@ -737,7 +743,7 @@ class _AdminModelEditPageState extends State { final res = await ImageUploader(widget.setting).upload(avatarUrl!, usage: 'avatar'); avatarUrl = res.url; } catch (e) { - showErrorMessage('上传头像失败'); + showErrorMessage('Failed to upload avatar'); cancel(); return; } finally { @@ -785,11 +791,11 @@ class _AdminModelEditPageState extends State { return modelChannels .firstWhere( (e) => e.type == provider.name! && e.id == null, - orElse: () => AdminChannel(name: '未知', type: ''), + orElse: () => AdminChannel(name: 'Unknown', type: ''), ) .display; } - return '请选择'; + return 'Select'; } } diff --git a/lib/page/admin/payments.dart b/lib/page/admin/payments.dart index 20d4c382..3a1a7a36 100644 --- a/lib/page/admin/payments.dart +++ b/lib/page/admin/payments.dart @@ -60,7 +60,7 @@ class _PaymentHistoriesPageState extends State { appBar: AppBar( toolbarHeight: CustomSize.toolbarHeight, title: const Text( - '支付订单历史', + 'Payment Order History', style: TextStyle(fontSize: CustomSize.appBarTitleSize), ), centerTitle: true, @@ -218,11 +218,19 @@ class _PaymentHistoriesPageState extends State { Row( children: [ Text( - '用户 ${his.userId} 充值 ${(his.retailPrice / 100).ceil()} 元', + '@${his.userId} Charge ', style: const TextStyle( overflow: TextOverflow.ellipsis, ), ), + Text( + '¥${(his.retailPrice / 100).ceil()}', + style: const TextStyle( + fontSize: 16, + fontWeight: FontWeight.bold, + color: Colors.red, + ), + ), const SizedBox(width: 5), Text( '#${his.id}', diff --git a/lib/page/admin/recently_messages.dart b/lib/page/admin/recently_messages.dart index d7f647e7..9490ac54 100644 --- a/lib/page/admin/recently_messages.dart +++ b/lib/page/admin/recently_messages.dart @@ -56,7 +56,7 @@ class _AdminRecentlyMessagesPageState extends State { appBar: AppBar( toolbarHeight: CustomSize.toolbarHeight, title: const Text( - '最近聊天历史记录', + 'Chat History', style: TextStyle(fontSize: CustomSize.appBarTitleSize), ), centerTitle: true, @@ -144,7 +144,7 @@ class _AdminRecentlyMessagesPageState extends State { child: Slidable( startActionPane: ActionPane(motion: const ScrollMotion(), children: [ SlidableAction( - label: '数字人', + label: AppLocale.character.getString(context), borderRadius: const BorderRadius.only( topLeft: CustomSize.radius, bottomLeft: CustomSize.radius, @@ -164,7 +164,7 @@ class _AdminRecentlyMessagesPageState extends State { children: [ const SizedBox(width: 10), SlidableAction( - label: '用户', + label: 'User', borderRadius: const BorderRadius.only( topLeft: CustomSize.radius, bottomLeft: CustomSize.radius, diff --git a/lib/page/admin/rooms.dart b/lib/page/admin/rooms.dart index 7b0b5b88..8acfeb61 100644 --- a/lib/page/admin/rooms.dart +++ b/lib/page/admin/rooms.dart @@ -145,7 +145,7 @@ class _AdminRoomsPageState extends State { ), padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 2), child: Text( - '群聊', + AppLocale.groupChat.getString(context), style: TextStyle( color: customColors.weakTextColor, fontSize: 8, diff --git a/lib/page/admin/user.dart b/lib/page/admin/user.dart index d30403d5..faa5e0da 100644 --- a/lib/page/admin/user.dart +++ b/lib/page/admin/user.dart @@ -47,18 +47,18 @@ class _AdminUserPageState extends State { appBar: AppBar( toolbarHeight: CustomSize.toolbarHeight, title: const Text( - '用户详情', + 'User Info', style: TextStyle(fontSize: CustomSize.appBarTitleSize), ), centerTitle: true, actions: [ IconButton( icon: const Icon(Icons.card_giftcard_outlined), - tooltip: '赠送智慧果', + tooltip: 'Give Coins', onPressed: () { - int sendCount = 1000; + int sendCount = 600; String? note; - int validDays = 365; + int validDays = 30; openDialog( context, @@ -67,12 +67,12 @@ class _AdminUserPageState extends State { mainAxisSize: MainAxisSize.min, children: [ const Text( - '赠送智慧果', + 'Give Coins', style: TextStyle(fontSize: 18), ), const SizedBox(height: 10), EnhancedTextField( - labelText: '数量', + labelText: 'Quantity', customColors: customColors, textAlignVertical: TextAlignVertical.top, showCounter: false, @@ -82,7 +82,7 @@ class _AdminUserPageState extends State { width: 110, alignment: Alignment.center, child: Text( - '个智慧果', + 'Coins', style: TextStyle( color: customColors.weakTextColor, fontSize: 12, @@ -96,7 +96,7 @@ class _AdminUserPageState extends State { ), const SizedBox(height: 10), EnhancedTextField( - labelText: '有效期', + labelText: 'Expiration', customColors: customColors, textAlignVertical: TextAlignVertical.top, showCounter: false, @@ -106,7 +106,7 @@ class _AdminUserPageState extends State { width: 110, alignment: Alignment.center, child: Text( - '天', + 'Days', style: TextStyle( color: customColors.weakTextColor, fontSize: 12, @@ -120,11 +120,11 @@ class _AdminUserPageState extends State { ), const SizedBox(height: 10), EnhancedTextField( - labelText: '备注', + labelText: 'Note', customColors: customColors, textAlignVertical: TextAlignVertical.top, showCounter: false, - hintText: '可选', + hintText: 'Optional', onChanged: (value) { note = value; }, @@ -135,12 +135,12 @@ class _AdminUserPageState extends State { }), onSubmit: () { if (sendCount <= 0) { - showErrorMessage('数量必须大于 0'); + showErrorMessage('Quantity must be greater than 0'); return false; } if (validDays <= 0) { - showErrorMessage('有效期必须大于 0'); + showErrorMessage('Expiration date must be greater than 0'); return false; } @@ -152,7 +152,7 @@ class _AdminUserPageState extends State { note: note, ) .then((value) { - showSuccessMessage('赠送成功'); + showSuccessMessage('Gift sent successfully'); context.read().add(UserQuotaLoadEvent(widget.userId)); }).onError( (error, stackTrace) => showErrorMessageEnhanced(context, error!), @@ -243,36 +243,36 @@ class _AdminUserPageState extends State { ], ), TextItem( - title: '类型', + title: 'Type', value: state.user.userType ?? '-', ), if (state.user.phone != null && state.user.phone!.isNotEmpty) TextItem( - title: '手机号', + title: 'Photo', value: state.user.phone!, ), if (state.user.email != null && state.user.email!.isNotEmpty) TextItem( - title: '邮箱', + title: 'Email', value: state.user.email!, ), if (state.user.realname != null && state.user.realname!.isNotEmpty) TextItem( - title: '昵称', + title: 'Nickname', value: state.user.realname!, ), if (state.user.invitedBy != null && state.user.invitedBy! > 0) TextItem( - title: '邀请人 ID', + title: 'Inviter ID', value: '${state.user.invitedBy}', ), if (state.user.createdAt != null) TextItem( - title: '注册时间', + title: 'Creation time', value: state.user.createdAt!.toLocal().toString(), ), TextItem( - title: '状态', + title: 'Status', value: state.user.status ?? '-', ), ], @@ -300,7 +300,7 @@ class _AdminUserPageState extends State { ), children: [ TextItem( - title: '剩余智慧果', + title: 'Remaining coins', value: state.quota.total.toString(), ), buildPaymentDetails(customColors, state) @@ -336,7 +336,7 @@ class _AdminUserPageState extends State { mainAxisSize: MainAxisSize.min, children: [ Text( - '充值历史', + 'Recharge History', style: TextStyle( fontSize: 14, fontWeight: FontWeight.bold, @@ -345,7 +345,7 @@ class _AdminUserPageState extends State { ), const SizedBox(height: 10), if (state.quota.details.isEmpty) - const Text('无充值记录') + const Text('No recharge record') else ListView( shrinkWrap: true, @@ -377,7 +377,7 @@ class _AdminUserPageState extends State { crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( - (item.note == null || item.note == '') ? '购买' : item.note!, + (item.note == null || item.note == '') ? 'Buy' : item.note!, overflow: TextOverflow.ellipsis, ), const SizedBox(height: 5), @@ -403,7 +403,7 @@ class _AdminUserPageState extends State { fontWeight: FontWeight.w500, ), Text( - '${DateFormat('yyyy/MM/dd').format(item.periodEndAt.toLocal())} 过期', + '${DateFormat('yyyy/MM/dd').format(item.periodEndAt.toLocal())} expired', textScaler: const TextScaler.linear(0.7), ), ], diff --git a/lib/page/admin/users.dart b/lib/page/admin/users.dart index 5d7a9bde..72b1a87e 100644 --- a/lib/page/admin/users.dart +++ b/lib/page/admin/users.dart @@ -63,7 +63,7 @@ class _AdminUsersPageState extends State { appBar: AppBar( toolbarHeight: CustomSize.toolbarHeight, title: const Text( - '用户管理', + 'User Management', style: TextStyle(fontSize: CustomSize.appBarTitleSize), ), centerTitle: true, @@ -201,7 +201,7 @@ class _AdminUsersPageState extends State { children: [ const SizedBox(width: 10), SlidableAction( - label: '数字人列表', + label: AppLocale.character.getString(context), borderRadius: const BorderRadius.only( topLeft: CustomSize.radius, bottomLeft: CustomSize.radius, @@ -361,15 +361,15 @@ Widget buildTags(BuildContext context, CustomColors customColors, AdminUser user final tags = []; if (user.email != null && user.email!.isNotEmpty) { - tags.add(buildTag(context, customColors, '邮箱')); + tags.add(buildTag(context, customColors, 'Email')); } if (user.phone != null && user.phone!.isNotEmpty) { - tags.add(buildTag(context, customColors, '手机')); + tags.add(buildTag(context, customColors, 'Phone')); } if (user.unionId != null && user.unionId!.isNotEmpty) { - tags.add(buildTag(context, customColors, '微信')); + tags.add(buildTag(context, customColors, 'WeChat')); } if (user.appleUid != null && user.appleUid!.isNotEmpty) { diff --git a/lib/page/balance/payment.dart b/lib/page/balance/payment.dart index 097f0d5d..bd5fed40 100644 --- a/lib/page/balance/payment.dart +++ b/lib/page/balance/payment.dart @@ -466,7 +466,7 @@ class _PaymentScreenState extends State { return true; }, - title: '请选择支付方式', + title: AppLocale.selectPaymentMethod.getString(context), heightFactor: 0.4, ); } @@ -536,7 +536,7 @@ class _PaymentScreenState extends State { return true; }, - title: '请选择支付方式', + title: AppLocale.selectPaymentMethod.getString(context), heightFactor: 0.4, ); } @@ -605,7 +605,7 @@ class _PaymentScreenState extends State { return true; }, - title: '请选择支付方式', + title: AppLocale.selectPaymentMethod.getString(context), heightFactor: 0.3, ); } diff --git a/lib/page/balance/price_block.dart b/lib/page/balance/price_block.dart index 05c7820b..65015de3 100644 --- a/lib/page/balance/price_block.dart +++ b/lib/page/balance/price_block.dart @@ -1,8 +1,10 @@ +import 'package:askaide/lang/lang.dart'; import 'package:askaide/page/component/coin.dart'; import 'package:askaide/page/component/theme/custom_size.dart'; import 'package:askaide/page/component/theme/custom_theme.dart'; import 'package:askaide/repo/api/payment.dart'; import 'package:flutter/material.dart'; +import 'package:flutter_localization/flutter_localization.dart'; import 'package:in_app_purchase/in_app_purchase.dart'; class PriceBlock extends StatelessWidget { @@ -59,7 +61,7 @@ class PriceBlock extends StatelessWidget { ), const SizedBox(width: 1), Text( - '${product.expirePolicyText}内有效', + '${product.expirePolicyText} ${AppLocale.validDays.getString(context)}', style: const TextStyle( fontSize: 11, color: Color.fromARGB(255, 224, 170, 7), diff --git a/lib/page/chat/component/room_item.dart b/lib/page/chat/component/room_item.dart index 1b3b8c02..e39279a4 100644 --- a/lib/page/chat/component/room_item.dart +++ b/lib/page/chat/component/room_item.dart @@ -132,7 +132,7 @@ class RoomItem extends StatelessWidget { ), padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 2), child: Text( - '群聊', + AppLocale.groupChat.getString(context), style: TextStyle( color: customColors.weakTextColor, fontSize: 8, diff --git a/lib/page/chat/group/create.dart b/lib/page/chat/group/create.dart index aacc7965..19bfc8a0 100644 --- a/lib/page/chat/group/create.dart +++ b/lib/page/chat/group/create.dart @@ -52,9 +52,9 @@ class _GroupCreatePageState extends State { final customColors = Theme.of(context).extension()!; return Scaffold( appBar: AppBar( - title: const Text( - '发起群聊', - style: TextStyle(fontSize: CustomSize.appBarTitleSize), + title: Text( + AppLocale.createGroupChat.getString(context), + style: const TextStyle(fontSize: CustomSize.appBarTitleSize), ), centerTitle: true, elevation: 0, @@ -99,7 +99,7 @@ class _GroupCreatePageState extends State { Container( padding: const EdgeInsets.only(top: 15, left: 20, bottom: 15), child: Text( - '选择参与群聊的成员', + AppLocale.selectGroupMembers.getString(context), style: TextStyle( fontSize: 14, color: customColors.weakLinkColor, diff --git a/lib/page/chat/room_create.dart b/lib/page/chat/room_create.dart index 63fc8934..353621e2 100644 --- a/lib/page/chat/room_create.dart +++ b/lib/page/chat/room_create.dart @@ -283,7 +283,7 @@ class _RoomCreatePageState extends State { EnhancedInput( padding: const EdgeInsets.only(top: 10, bottom: 5), title: Text( - '头像', + AppLocale.avatar.getString(context), style: TextStyle( color: customColors.textfieldLabelColor, fontSize: 16, @@ -400,7 +400,7 @@ class _RoomCreatePageState extends State { ), const SizedBox(width: 5), Text( - '示例', + AppLocale.examples.getString(context), style: TextStyle( color: customColors.linkColor?.withAlpha(150), fontSize: 13, diff --git a/lib/page/chat/room_edit.dart b/lib/page/chat/room_edit.dart index 90731d01..42a6105d 100644 --- a/lib/page/chat/room_edit.dart +++ b/lib/page/chat/room_edit.dart @@ -56,10 +56,10 @@ class _RoomEditPageState extends State { int maxContext = 5; List validMemories = [ - ChatMemory('无记忆', 1, description: '每次对话都是独立的,常用于一次性问答'), - ChatMemory('基础', 3, description: '记住最近的 3 次对话'), - ChatMemory('中等', 6, description: '记住最近的 6 次对话'), - ChatMemory('深度', 10, description: '记住最近的 10 次对话'), + ChatMemory('Ephemeral', 1, description: 'Each conversation is independent, often used for one-off Q&A'), + ChatMemory('Basic', 3, description: 'Remembers the last 3 conversations'), + ChatMemory('Medium', 6, description: 'Remembers the last 6 conversations'), + ChatMemory('Deep', 10, description: 'Remembers the last 10 conversations') ]; bool showAdvancedOptions = false; @@ -175,7 +175,7 @@ class _RoomEditPageState extends State { EnhancedInput( padding: const EdgeInsets.only(top: 10, bottom: 5), title: Text( - '头像', + AppLocale.avatar.getString(context), style: TextStyle( color: customColors.textfieldLabelColor, fontSize: 16, @@ -281,7 +281,7 @@ class _RoomEditPageState extends State { ), const SizedBox(width: 5), Text( - '示例', + AppLocale.examples.getString(context), style: TextStyle( color: customColors.linkColor?.withAlpha(150), fontSize: 13, diff --git a/lib/page/component/dialog.dart b/lib/page/component/dialog.dart index 04094816..6fa31f3c 100644 --- a/lib/page/component/dialog.dart +++ b/lib/page/component/dialog.dart @@ -29,7 +29,7 @@ showErrorMessageEnhanced( context, type: QuickAlertType.warning, text: message.message.getString(context), - confirmBtnText: '立即购买', + confirmBtnText: AppLocale.buy.getString(context), showCancelBtn: true, onConfirmBtnTap: () { context.pop(); @@ -43,7 +43,7 @@ showErrorMessageEnhanced( context, type: QuickAlertType.warning, text: message.message.getString(context), - confirmBtnText: '重新登录', + confirmBtnText: AppLocale.reSignIn.getString(context), showCancelBtn: true, onConfirmBtnTap: () { context.pop(); @@ -56,13 +56,13 @@ showErrorMessageEnhanced( showBeautyDialog( context, type: QuickAlertType.warning, - text: '该功能需要登录账号后使用', + text: AppLocale.needSigninToUse.getString(context), onConfirmBtnTap: () { context.pop(); context.push('/login'); }, showCancelBtn: true, - confirmBtnText: '立即登录', + confirmBtnText: AppLocale.signinNow.getString(context), ); return; } @@ -78,7 +78,7 @@ showCustomBeautyDialog( BuildContext context, { required QuickAlertType type, required Widget child, - String confirmBtnText = '确定', + String confirmBtnText = '', String? cancelBtnText, Function()? onConfirmBtnTap, Function()? onCancelBtnTap, @@ -94,7 +94,7 @@ showCustomBeautyDialog( width: MediaQuery.of(context).size.width > 600 ? 400 : null, barrierDismissible: false, // 禁止点击外部关闭 showCancelBtn: showCancelBtn, - confirmBtnText: confirmBtnText, + confirmBtnText: confirmBtnText == '' ? AppLocale.ok.getString(context) : confirmBtnText, cancelBtnText: cancelBtnText ?? AppLocale.cancel.getString(context), confirmBtnColor: customColors.linkColor!, borderRadius: CustomSize.radiusValue, @@ -123,7 +123,7 @@ Future showBeautyDialog( String? title, String? customAsset, Widget? widget, - String confirmBtnText = '确定', + String confirmBtnText = '', String? cancelBtnText, Function()? onConfirmBtnTap, Function()? onCancelBtnTap, @@ -141,7 +141,7 @@ Future showBeautyDialog( width: MediaQuery.of(context).size.width > 600 ? 400 : null, barrierDismissible: barrierDismissible, showCancelBtn: showCancelBtn, - confirmBtnText: confirmBtnText, + confirmBtnText: confirmBtnText == '' ? AppLocale.ok.getString(context) : confirmBtnText, cancelBtnText: cancelBtnText ?? AppLocale.cancel.getString(context), confirmBtnColor: customColors.linkColor!, borderRadius: CustomSize.radiusValue, diff --git a/lib/page/creative_island/my_creation_item.dart b/lib/page/creative_island/my_creation_item.dart index ff34b3e5..153b508f 100644 --- a/lib/page/creative_island/my_creation_item.dart +++ b/lib/page/creative_island/my_creation_item.dart @@ -109,7 +109,7 @@ class _MyCreationItemPageState extends State with SingleTick crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( - '想法', + AppLocale.ideaPrompt.getString(context), style: TextStyle( fontSize: 15, fontWeight: FontWeight.bold, @@ -156,7 +156,7 @@ class _MyCreationItemPageState extends State with SingleTick return [ TextButton( onPressed: () { - openConfirmDialog(context, '确定封禁该项目?', () { + openConfirmDialog(context, 'Are you sure to ban this project?', () { APIServer().forbidCreativeHistoryItem(historyId: state.item!.id).then((value) { showSuccessMessage(AppLocale.operateSuccess.getString(context)); @@ -176,7 +176,7 @@ class _MyCreationItemPageState extends State with SingleTick ), const SizedBox(width: 5), Text( - '封禁', + 'Ban', style: TextStyle( color: customColors.weakLinkColor, fontSize: 12, @@ -213,7 +213,7 @@ class _MyCreationItemPageState extends State with SingleTick } }, child: Text( - state.item!.isShared ? '设为私有' : '设为公开', + state.item!.isShared ? 'Set Private' : 'Set Public', style: TextStyle( color: customColors.weakLinkColor, fontSize: 12, @@ -238,14 +238,14 @@ class _MyCreationItemPageState extends State with SingleTick color: Colors.red, ), const SizedBox(height: 10), - const Text( - '创作失败', - style: TextStyle(color: Colors.red), + Text( + AppLocale.generateFailed.getString(context), + style: const TextStyle(color: Colors.red), textAlign: TextAlign.center, ), const SizedBox(height: 10), SelectableText( - widget.showErrorMessage ? '${state.item!.answer}' : '错误代码:${state.item!.errorCode}', + widget.showErrorMessage ? '${state.item!.answer}' : 'Error Code:${state.item!.errorCode}', textAlign: TextAlign.center, style: TextStyle( fontSize: 10, @@ -269,7 +269,7 @@ class _MyCreationItemPageState extends State with SingleTick ), const SizedBox(height: 10), Text( - '创作中,请稍后...', + AppLocale.generate.getString(context), style: TextStyle( color: customColors.backgroundInvertedColor, ), @@ -338,7 +338,7 @@ class _MyCreationItemPageState extends State with SingleTick crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( - '风格', + AppLocale.style.getString(context), style: TextStyle( fontSize: 15, fontWeight: FontWeight.bold, diff --git a/lib/page/lab/avatar_selector.dart b/lib/page/lab/avatar_selector.dart index eff96420..85186ac3 100644 --- a/lib/page/lab/avatar_selector.dart +++ b/lib/page/lab/avatar_selector.dart @@ -1,5 +1,7 @@ +import 'package:askaide/lang/lang.dart'; import 'package:askaide/page/component/random_avatar.dart'; import 'package:flutter/material.dart'; +import 'package:flutter_localization/flutter_localization.dart'; class AvatarSelectorScreen extends StatefulWidget { final AvatarUsage usage; @@ -14,7 +16,7 @@ class _AvatarSelectorScreenState extends State { Widget build(BuildContext context) { return Scaffold( appBar: AppBar( - title: const Text('选择头像'), + title: Text(AppLocale.avatar.getString(context)), centerTitle: true, ), body: GridView.count( diff --git a/lib/page/lab/creative_models.dart b/lib/page/lab/creative_models.dart index 66b8ec19..34b8fff2 100644 --- a/lib/page/lab/creative_models.dart +++ b/lib/page/lab/creative_models.dart @@ -60,7 +60,7 @@ class _CreativeModelScreenState extends State { appBar: AppBar( toolbarHeight: CustomSize.toolbarHeight, title: const Text( - '模型 Gallery', + 'Creation Island History', style: TextStyle(fontSize: CustomSize.appBarTitleSize), ), centerTitle: true, @@ -87,7 +87,7 @@ class _CreativeModelScreenState extends State { alignment: Alignment.centerRight, width: MediaQuery.of(context).size.width - 200, child: Text( - selectedModel?.modelName ?? '自动', + selectedModel?.modelName ?? 'Auto', overflow: TextOverflow.ellipsis, ), ), @@ -95,7 +95,7 @@ class _CreativeModelScreenState extends State { openListSelectDialog( context, [ - SelectorItem(const Text('自动'), null), + SelectorItem(const Text('Auto'), null), ...imageModels .map( (e) => SelectorItem( @@ -257,7 +257,7 @@ class _CreativeModelScreenState extends State { ), child: const Center( child: Text( - '正在处理中...', + 'Processing...', textAlign: TextAlign.center, maxLines: 4, style: TextStyle( diff --git a/lib/repo/api/admin/models.dart b/lib/repo/api/admin/models.dart index 94d76a35..537d4af2 100644 --- a/lib/repo/api/admin/models.dart +++ b/lib/repo/api/admin/models.dart @@ -12,6 +12,7 @@ class AdminModel { int get inputPrice => meta?.inputPrice ?? 0; int get outputPrice => meta?.outputPrice ?? 0; int get maxContext => meta?.maxContext ?? 0; + bool get enabled => status == 1; AdminModel({ required this.modelId, @@ -33,9 +34,7 @@ class AdminModel { avatarUrl: json['avatar_url'], status: json['status'], meta: json['meta'] != null ? AdminModelMeta.fromJson(json['meta']) : null, - providers: ((json['providers'] ?? []) as List) - .map((e) => AdminModelProvider.fromJson(e)) - .toList(), + providers: ((json['providers'] ?? []) as List).map((e) => AdminModelProvider.fromJson(e)).toList(), ); } From c3d07264de6f7b13f3235650292f1e3acfa6abb4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=AE=A1=E5=AE=9C=E5=B0=A7?= Date: Sun, 8 Dec 2024 00:37:36 +0800 Subject: [PATCH 14/26] update --- android/gradle.properties | 1 + .../gradle/wrapper/gradle-wrapper.properties | 4 +- ios/Podfile.lock | 6 -- ios/Runner/AppDelegate.swift | 2 +- macos/Flutter/GeneratedPluginRegistrant.swift | 2 - pubspec.lock | 56 ++++--------------- pubspec.yaml | 1 - 7 files changed, 17 insertions(+), 55 deletions(-) diff --git a/android/gradle.properties b/android/gradle.properties index 00485990..c24abd67 100644 --- a/android/gradle.properties +++ b/android/gradle.properties @@ -1,3 +1,4 @@ org.gradle.jvmargs=-Xms1024m -Xmx4096m android.useAndroidX=true android.enableJetifier=true +kotlin.jvm.target.validation.mode=IGNORE \ No newline at end of file diff --git a/android/gradle/wrapper/gradle-wrapper.properties b/android/gradle/wrapper/gradle-wrapper.properties index 3c472b99..09523c0e 100644 --- a/android/gradle/wrapper/gradle-wrapper.properties +++ b/android/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,7 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-8.9-bin.zip +networkTimeout=10000 +validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-7.5-all.zip diff --git a/ios/Podfile.lock b/ios/Podfile.lock index 39474bf5..7377389c 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -48,8 +48,6 @@ PODS: - Mantle - SDWebImage - SDWebImageWebPCoder - - flutter_local_notifications (0.0.1): - - Flutter - flutter_localization (0.0.1): - Flutter - flutter_native_splash (0.0.1): @@ -167,7 +165,6 @@ DEPENDENCIES: - file_saver (from `.symlinks/plugins/file_saver/ios`) - Flutter (from `Flutter`) - flutter_image_compress (from `.symlinks/plugins/flutter_image_compress/ios`) - - flutter_local_notifications (from `.symlinks/plugins/flutter_local_notifications/ios`) - flutter_localization (from `.symlinks/plugins/flutter_localization/ios`) - flutter_native_splash (from `.symlinks/plugins/flutter_native_splash/ios`) - flutter_tts (from `.symlinks/plugins/flutter_tts/ios`) @@ -226,8 +223,6 @@ EXTERNAL SOURCES: :path: Flutter flutter_image_compress: :path: ".symlinks/plugins/flutter_image_compress/ios" - flutter_local_notifications: - :path: ".symlinks/plugins/flutter_local_notifications/ios" flutter_localization: :path: ".symlinks/plugins/flutter_localization/ios" flutter_native_splash: @@ -283,7 +278,6 @@ SPEC CHECKSUMS: file_saver: 503e386464dbe118f630e17b4c2e1190fa0cf808 Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7 flutter_image_compress: 5a5e9aee05b6553048b8df1c3bc456d0afaac433 - flutter_local_notifications: 0c0b1ae97e741e1521e4c1629a459d04b9aec743 flutter_localization: f43b18844a2b3d2c71fd64f04ffd6b1e64dd54d4 flutter_native_splash: edf599c81f74d093a4daf8e17bd7a018854bc778 flutter_tts: 0f492aab6accf87059b72354fcb4ba934304771d diff --git a/ios/Runner/AppDelegate.swift b/ios/Runner/AppDelegate.swift index 70693e4a..b6363034 100644 --- a/ios/Runner/AppDelegate.swift +++ b/ios/Runner/AppDelegate.swift @@ -1,7 +1,7 @@ import UIKit import Flutter -@UIApplicationMain +@main @objc class AppDelegate: FlutterAppDelegate { override func application( _ application: UIApplication, diff --git a/macos/Flutter/GeneratedPluginRegistrant.swift b/macos/Flutter/GeneratedPluginRegistrant.swift index 13a0619f..078da95c 100644 --- a/macos/Flutter/GeneratedPluginRegistrant.swift +++ b/macos/Flutter/GeneratedPluginRegistrant.swift @@ -7,7 +7,6 @@ import Foundation import audioplayers_darwin import file_saver -import flutter_local_notifications import flutter_localization import flutter_tts import in_app_purchase_storekit @@ -27,7 +26,6 @@ import wakelock_plus func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { AudioplayersDarwinPlugin.register(with: registry.registrar(forPlugin: "AudioplayersDarwinPlugin")) FileSaverPlugin.register(with: registry.registrar(forPlugin: "FileSaverPlugin")) - FlutterLocalNotificationsPlugin.register(with: registry.registrar(forPlugin: "FlutterLocalNotificationsPlugin")) FlutterLocalizationPlugin.register(with: registry.registrar(forPlugin: "FlutterLocalizationPlugin")) FlutterTtsPlugin.register(with: registry.registrar(forPlugin: "FlutterTtsPlugin")) InAppPurchasePlugin.register(with: registry.registrar(forPlugin: "InAppPurchasePlugin")) diff --git a/pubspec.lock b/pubspec.lock index facaad2a..b9ce62e1 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -690,30 +690,6 @@ packages: url: "https://pub.dev" source: hosted version: "2.0.3" - flutter_local_notifications: - dependency: "direct main" - description: - name: flutter_local_notifications - sha256: "5f79a1be5e9fef9ddd7f494532d31851399099f9defc21ebcb1ae4539e8a37f1" - url: "https://pub.dev" - source: hosted - version: "14.1.5" - flutter_local_notifications_linux: - dependency: transitive - description: - name: flutter_local_notifications_linux - sha256: c49bd06165cad9beeb79090b18cd1eb0296f4bf4b23b84426e37dd7c027fc3af - url: "https://pub.dev" - source: hosted - version: "4.0.1" - flutter_local_notifications_platform_interface: - dependency: transitive - description: - name: flutter_local_notifications_platform_interface - sha256: "85f8d07fe708c1bdcf45037f2c0109753b26ae077e9d9e899d55971711a4ea66" - url: "https://pub.dev" - source: hosted - version: "7.2.0" flutter_localization: dependency: "direct main" description: @@ -1037,18 +1013,18 @@ packages: dependency: transitive description: name: leak_tracker - sha256: "7f0df31977cb2c0b88585095d168e689669a2cc9b97c309665e3386f3e9d341a" + sha256: "3f87a60e8c63aecc975dda1ceedbc8f24de75f09e4856ea27daf8958f2f0ce05" url: "https://pub.dev" source: hosted - version: "10.0.4" + version: "10.0.5" leak_tracker_flutter_testing: dependency: transitive description: name: leak_tracker_flutter_testing - sha256: "06e98f569d004c1315b991ded39924b21af84cf14cc94791b8aea337d25b57f8" + sha256: "932549fb305594d82d7183ecd9fa93463e9914e1b67cacc34bc40906594a1806" url: "https://pub.dev" source: hosted - version: "3.0.3" + version: "3.0.5" leak_tracker_testing: dependency: transitive description: @@ -1134,10 +1110,10 @@ packages: dependency: transitive description: name: material_color_utilities - sha256: "0e0a020085b65b6083975e499759762399b4475f766c21668c4ecca34ea74e5a" + sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec url: "https://pub.dev" source: hosted - version: "0.8.0" + version: "0.11.1" matrix2d: dependency: transitive description: @@ -1222,10 +1198,10 @@ packages: dependency: transitive description: name: meta - sha256: "7687075e408b093f36e6bbf6c91878cc0d4cd10f409506f7bc996f68220b9136" + sha256: bdb68674043280c3428e9ec998512fb681678676b3c54e773629ffe74419f8c7 url: "https://pub.dev" source: hosted - version: "1.12.0" + version: "1.15.0" mime: dependency: transitive description: @@ -1884,10 +1860,10 @@ packages: dependency: transitive description: name: test_api - sha256: "9955ae474176f7ac8ee4e989dadfb411a58c30415bcfb648fa04b2b8a03afa7f" + sha256: "5b8a98dafc4d5c4c9c72d8b31ab2b23fc13422348d2997120294d3bac86b4ddb" url: "https://pub.dev" source: hosted - version: "0.7.0" + version: "0.7.2" tiktoken: dependency: "direct main" description: @@ -1896,14 +1872,6 @@ packages: url: "https://pub.dev" source: hosted version: "1.0.3" - timezone: - dependency: transitive - description: - name: timezone - sha256: "2236ec079a174ce07434e89fcd3fcda430025eb7692244139a9cf54fdcf1fc7d" - url: "https://pub.dev" - source: hosted - version: "0.9.4" timing: dependency: transitive description: @@ -2084,10 +2052,10 @@ packages: dependency: transitive description: name: vm_service - sha256: "3923c89304b715fb1eb6423f017651664a03bf5f4b29983627c4da791f74a4ec" + sha256: "5c5f338a667b4c644744b661f309fb8080bb94b18a7e91ef1dbd343bed00ed6d" url: "https://pub.dev" source: hosted - version: "14.2.1" + version: "14.2.5" volume_controller: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 54c97905..04c25f0a 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -102,7 +102,6 @@ dependencies: git: url: https://github.com/mylxsw/before_after.git flutter_native_splash: ^2.2.19 - flutter_local_notifications: ^14.1.1 flutter_drawing_board: ^0.4.4+2 loading_more_list: ^5.0.3 tobias: ^2.4.2 From 9ef4a15e964d7dc1333f7e3c8ed54b939940d756 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=AE=A1=E5=AE=9C=E5=B0=A7?= Date: Mon, 9 Dec 2024 21:29:37 +0800 Subject: [PATCH 15/26] fix android build failed problem --- android/app/build.gradle | 12 +++-- android/build.gradle | 19 ++++++++ android/gradle.properties | 4 +- .../gradle/wrapper/gradle-wrapper.properties | 2 +- android/settings.gradle | 2 +- ios/Runner.xcodeproj/project.pbxproj | 6 --- lib/helper/constant.dart | 2 +- lib/lang/lang.dart | 28 ++++++------ lib/page/admin/models.dart | 4 +- lib/page/admin/models_add.dart | 4 +- lib/page/admin/models_edit.dart | 4 +- lib/page/admin/user.dart | 12 ++--- lib/page/balance/payment.dart | 2 +- lib/page/balance/payment_history.dart | 4 +- lib/page/balance/price_block.dart | 4 +- lib/page/balance/quota_usage_statistics.dart | 4 +- lib/page/component/account_quota_card.dart | 13 +++--- lib/page/component/{coin.dart => credit.dart} | 7 ++- macos/Flutter/GeneratedPluginRegistrant.swift | 2 + pubspec.lock | 44 ++++++++++++++++++- pubspec.yaml | 2 +- 21 files changed, 119 insertions(+), 62 deletions(-) rename lib/page/component/{coin.dart => credit.dart} (88%) diff --git a/android/app/build.gradle b/android/app/build.gradle index 2c6465e0..6786a5fd 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -30,23 +30,29 @@ if (keystorePropertiesFile.exists()) { } android { + namespace 'cc.aicode.flutter.askaide.askaide' compileSdkVersion flutter.compileSdkVersion - ndkVersion flutter.ndkVersion + // ndkVersion flutter.ndkVersion + ndkVersion "27.0.12077973" compileOptions { sourceCompatibility JavaVersion.VERSION_1_8 targetCompatibility JavaVersion.VERSION_1_8 } + kotlinOptions { + jvmTarget = JavaVersion.VERSION_1_8 + } + defaultConfig { // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). applicationId "cc.aicode.flutter.askaide.askaide" // You can update the following values to match your application needs. // For more information, see: https://docs.flutter.dev/deployment/android#reviewing-the-gradle-build-configuration. // minSdkVersion flutter.minSdkVersion - minSdkVersion 24 + minSdkVersion 23 targetSdkVersion flutter.targetSdkVersion - versionCode flutterVersionCode.toInteger() + versionCode flutter.versionCode versionName flutterVersionName } diff --git a/android/build.gradle b/android/build.gradle index d2ffbffa..257ac3cc 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -3,6 +3,24 @@ allprojects { google() mavenCentral() } + + subprojects { + afterEvaluate { project -> + if (project.hasProperty('android')) { + project.android { + if (namespace == null) { + namespace project.group + } + } + } + + if (project.hasProperty("android")) { + project.android { + compileSdkVersion = 34 + } + } + } + } } rootProject.buildDir = "../build" @@ -16,3 +34,4 @@ subprojects { tasks.register("clean", Delete) { delete rootProject.buildDir } + diff --git a/android/gradle.properties b/android/gradle.properties index c24abd67..4e7e81f2 100644 --- a/android/gradle.properties +++ b/android/gradle.properties @@ -1,4 +1,4 @@ -org.gradle.jvmargs=-Xms1024m -Xmx4096m +org.gradle.jvmargs=-Xmx4G -XX:MaxMetaspaceSize=2G -XX:+HeapDumpOnOutOfMemoryError android.useAndroidX=true android.enableJetifier=true -kotlin.jvm.target.validation.mode=IGNORE \ No newline at end of file +kotlin.jvm.target.validation.mode = IGNORE \ No newline at end of file diff --git a/android/gradle/wrapper/gradle-wrapper.properties b/android/gradle/wrapper/gradle-wrapper.properties index 09523c0e..9355b415 100644 --- a/android/gradle/wrapper/gradle-wrapper.properties +++ b/android/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.9-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.10-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME diff --git a/android/settings.gradle b/android/settings.gradle index 684b763e..bb551eaa 100644 --- a/android/settings.gradle +++ b/android/settings.gradle @@ -18,7 +18,7 @@ pluginManagement { plugins { id "dev.flutter.flutter-plugin-loader" version "1.0.0" - id "com.android.application" version "7.4.0" apply false + id "com.android.application" version '8.7.2' apply false id "org.jetbrains.kotlin.android" version "1.9.20" apply false } diff --git a/ios/Runner.xcodeproj/project.pbxproj b/ios/Runner.xcodeproj/project.pbxproj index 4888ed3a..15ab8d37 100644 --- a/ios/Runner.xcodeproj/project.pbxproj +++ b/ios/Runner.xcodeproj/project.pbxproj @@ -453,8 +453,6 @@ "-framework", "\"flutter_image_compress\"", "-framework", - "\"flutter_local_notifications\"", - "-framework", "\"flutter_localization\"", "-framework", "\"flutter_native_splash\"", @@ -679,8 +677,6 @@ "-framework", "\"flutter_image_compress\"", "-framework", - "\"flutter_local_notifications\"", - "-framework", "\"flutter_localization\"", "-framework", "\"flutter_native_splash\"", @@ -799,8 +795,6 @@ "-framework", "\"flutter_image_compress\"", "-framework", - "\"flutter_local_notifications\"", - "-framework", "\"flutter_localization\"", "-framework", "\"flutter_native_splash\"", diff --git a/lib/helper/constant.dart b/lib/helper/constant.dart index bf1ecb57..747fc6a6 100644 --- a/lib/helper/constant.dart +++ b/lib/helper/constant.dart @@ -6,7 +6,7 @@ const clientVersion = '1.0.15'; const databaseVersion = 27; const maxRoomNumForNonVIP = 50; -const coinSign = '个'; +const creditSign = '个'; const settingAPIServerToken = 'api-token'; const settingUserInfo = 'user-info'; diff --git a/lib/lang/lang.dart b/lib/lang/lang.dart index 1cd110a5..3f5fbfdc 100644 --- a/lib/lang/lang.dart +++ b/lib/lang/lang.dart @@ -216,13 +216,13 @@ mixin AppLocale { static const String enableCustomOpenAI = 'enable-custom-openai'; static const String me = 'me'; - static const String coinsUsage = 'coins-usage'; - static const String coinUsageTips = 'coin-usage-tips'; + static const String creditsUsage = 'credits-usage'; + static const String creditUsageTips = 'credit-usage-tips'; static const String updateCheck = 'update-check'; static const String buy = 'buy'; static const String paymentHistory = 'payment-history'; - static const String buyCoins = 'buy-coins'; - static const String coinUnit = 'coin-unit'; + static const String buyCredits = 'buy-credits'; + static const String creditUnit = 'credit-unit'; static const String toPay = 'to-pay'; static const String discover = 'discover'; @@ -512,13 +512,13 @@ mixin AppLocale { inviteCodeFormatError: '邀请码格式有误', enableCustomOpenAI: '启用后将使用您自己配置的 OpenAI 服务', me: '我的', - coinsUsage: '使用明细', - coinUsageTips: '使用明细将在次日更新,显示近 30 天的使用量。', + creditsUsage: '使用明细', + creditUsageTips: '使用明细将在次日更新,显示近 30 天的使用量。', updateCheck: '检测更新', buy: '购买', paymentHistory: '购买历史', - buyCoins: '购买智慧果', - coinUnit: '个', + buyCredits: '购买智慧果', + creditUnit: '个', toPay: '立即支付', discover: '绘玩', customHomeModels: '常用模型', @@ -640,7 +640,7 @@ mixin AppLocale { themeMode: 'Theme', accountInfo: 'Account Info', accountSettings: 'Account Settings', - usage: 'Coins', + usage: 'Credits', validBefore: 'Valid Before', custom: 'Custom', clearCache: 'Clear Cache', @@ -696,7 +696,7 @@ mixin AppLocale { confirmDelete: 'Confirm to delete?', confirmStartNewChat: 'Confirm to start a new chat?', confirmClearMessages: 'Confirm to clear chat histories?', - quotaExceeded: 'Insufficient coins, please purchase first', + quotaExceeded: 'Insufficient credits, please purchase first', internalServerError: 'Internal server error, please try again later', badGateway: 'Sorry, our server is currently unable to process your request. We are working to resolve the issue, please try again later', @@ -806,13 +806,13 @@ mixin AppLocale { inviteCodeFormatError: 'Invite code format error', enableCustomOpenAI: 'Your custom OpenAI service will be used once enabled', me: 'Me', - coinsUsage: 'Usage', - coinUsageTips: 'Usage details will be updated the next day, showing usage in the last 30 days.', + creditsUsage: 'Usage', + creditUsageTips: 'Usage details will be updated the next day, showing usage in the last 30 days.', updateCheck: 'Check Update', buy: 'Buy', paymentHistory: 'Histories', - buyCoins: 'Buy coins', - coinUnit: '', + buyCredits: 'Buy Credits', + creditUnit: '', toPay: 'Create Order', discover: 'Discover', customHomeModels: 'Favorite Models', diff --git a/lib/page/admin/models.dart b/lib/page/admin/models.dart index 6c6fb530..63d23b5e 100644 --- a/lib/page/admin/models.dart +++ b/lib/page/admin/models.dart @@ -357,9 +357,9 @@ class _AdminModelsPageState extends State { if (mod.inputPrice > 0 || mod.outputPrice > 0) { desc += '💰 '; if (mod.inputPrice == mod.outputPrice) { - desc += '${mod.inputPrice} Coins/1K Token'; + desc += '${mod.inputPrice} Credits/1K Token'; } else { - desc += '${mod.inputPrice} / ${mod.outputPrice} Coins/1K Token'; + desc += '${mod.inputPrice} / ${mod.outputPrice} Credits/1K Token'; } } diff --git a/lib/page/admin/models_add.dart b/lib/page/admin/models_add.dart index a1f5757b..0ab7e9e1 100644 --- a/lib/page/admin/models_add.dart +++ b/lib/page/admin/models_add.dart @@ -270,7 +270,7 @@ class _AdminModelCreatePageState extends State { width: 110, alignment: Alignment.center, child: Text( - 'Coins/1K Token', + 'Credits/1K Token', style: TextStyle(color: customColors.weakTextColor, fontSize: 12), ), ), @@ -290,7 +290,7 @@ class _AdminModelCreatePageState extends State { width: 110, alignment: Alignment.center, child: Text( - 'Coins/1K Token', + 'Credits/1K Token', style: TextStyle(color: customColors.weakTextColor, fontSize: 12), ), ), diff --git a/lib/page/admin/models_edit.dart b/lib/page/admin/models_edit.dart index 97592658..97f9467b 100644 --- a/lib/page/admin/models_edit.dart +++ b/lib/page/admin/models_edit.dart @@ -325,7 +325,7 @@ class _AdminModelEditPageState extends State { width: 110, alignment: Alignment.center, child: Text( - 'Coins/1K Token', + 'Credits/1K Token', style: TextStyle(color: customColors.weakTextColor, fontSize: 12), ), ), @@ -345,7 +345,7 @@ class _AdminModelEditPageState extends State { width: 110, alignment: Alignment.center, child: Text( - 'Coins/1K Token', + 'Credits/1K Token', style: TextStyle(color: customColors.weakTextColor, fontSize: 12), ), ), diff --git a/lib/page/admin/user.dart b/lib/page/admin/user.dart index faa5e0da..90f933dc 100644 --- a/lib/page/admin/user.dart +++ b/lib/page/admin/user.dart @@ -2,7 +2,7 @@ import 'package:askaide/bloc/user_bloc.dart'; import 'package:askaide/lang/lang.dart'; import 'package:askaide/page/admin/users.dart'; import 'package:askaide/page/component/background_container.dart'; -import 'package:askaide/page/component/coin.dart'; +import 'package:askaide/page/component/credit.dart'; import 'package:askaide/page/component/column_block.dart'; import 'package:askaide/page/component/dialog.dart'; import 'package:askaide/page/component/enhanced_textfield.dart'; @@ -54,7 +54,7 @@ class _AdminUserPageState extends State { actions: [ IconButton( icon: const Icon(Icons.card_giftcard_outlined), - tooltip: 'Give Coins', + tooltip: 'Give Credits', onPressed: () { int sendCount = 600; String? note; @@ -67,7 +67,7 @@ class _AdminUserPageState extends State { mainAxisSize: MainAxisSize.min, children: [ const Text( - 'Give Coins', + 'Give Credits', style: TextStyle(fontSize: 18), ), const SizedBox(height: 10), @@ -82,7 +82,7 @@ class _AdminUserPageState extends State { width: 110, alignment: Alignment.center, child: Text( - 'Coins', + 'Credits', style: TextStyle( color: customColors.weakTextColor, fontSize: 12, @@ -300,7 +300,7 @@ class _AdminUserPageState extends State { ), children: [ TextItem( - title: 'Remaining coins', + title: 'Remaining credits', value: state.quota.total.toString(), ), buildPaymentDetails(customColors, state) @@ -396,7 +396,7 @@ class _AdminUserPageState extends State { Column( crossAxisAlignment: CrossAxisAlignment.end, children: [ - Coin( + Credit( count: item.quota, color: Colors.amber, withAddPrefix: true, diff --git a/lib/page/balance/payment.dart b/lib/page/balance/payment.dart index bd5fed40..d88ce7e5 100644 --- a/lib/page/balance/payment.dart +++ b/lib/page/balance/payment.dart @@ -188,7 +188,7 @@ class _PaymentScreenState extends State { toolbarHeight: CustomSize.toolbarHeight, elevation: 0, title: Text( - AppLocale.buyCoins.getString(context), + AppLocale.buyCredits.getString(context), style: const TextStyle( fontSize: CustomSize.appBarTitleSize, ), diff --git a/lib/page/balance/payment_history.dart b/lib/page/balance/payment_history.dart index 20fe0dda..4a1c951b 100644 --- a/lib/page/balance/payment_history.dart +++ b/lib/page/balance/payment_history.dart @@ -1,7 +1,7 @@ import 'package:askaide/helper/helper.dart'; import 'package:askaide/lang/lang.dart'; import 'package:askaide/page/component/background_container.dart'; -import 'package:askaide/page/component/coin.dart'; +import 'package:askaide/page/component/credit.dart'; import 'package:askaide/page/component/theme/custom_size.dart'; import 'package:askaide/page/component/theme/custom_theme.dart'; import 'package:askaide/repo/api/quota.dart'; @@ -129,7 +129,7 @@ class _PaymentHistoryScreenState extends State { Column( crossAxisAlignment: CrossAxisAlignment.end, children: [ - Coin( + Credit( count: item.quota, color: Colors.amber, withAddPrefix: true, diff --git a/lib/page/balance/price_block.dart b/lib/page/balance/price_block.dart index 65015de3..3b029d14 100644 --- a/lib/page/balance/price_block.dart +++ b/lib/page/balance/price_block.dart @@ -1,5 +1,5 @@ import 'package:askaide/lang/lang.dart'; -import 'package:askaide/page/component/coin.dart'; +import 'package:askaide/page/component/credit.dart'; import 'package:askaide/page/component/theme/custom_size.dart'; import 'package:askaide/page/component/theme/custom_theme.dart'; import 'package:askaide/repo/api/payment.dart'; @@ -47,7 +47,7 @@ class PriceBlock extends StatelessWidget { Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - Coin( + Credit( count: product.quota, color: customColors.paymentItemTitleColor, ), diff --git a/lib/page/balance/quota_usage_statistics.dart b/lib/page/balance/quota_usage_statistics.dart index 8385893d..4d15cede 100644 --- a/lib/page/balance/quota_usage_statistics.dart +++ b/lib/page/balance/quota_usage_statistics.dart @@ -43,7 +43,7 @@ class _QuotaUsageStatisticsScreenState extends State appBar: AppBar( toolbarHeight: CustomSize.toolbarHeight, title: Text( - AppLocale.coinsUsage.getString(context), + AppLocale.creditsUsage.getString(context), style: const TextStyle(fontSize: CustomSize.appBarTitleSize), ), centerTitle: true, @@ -58,7 +58,7 @@ class _QuotaUsageStatisticsScreenState extends State child: Column( children: [ MessageBox( - message: AppLocale.coinUsageTips.getString(context), + message: AppLocale.creditUsageTips.getString(context), type: MessageBoxType.info, ), const SizedBox(height: 10), diff --git a/lib/page/component/account_quota_card.dart b/lib/page/component/account_quota_card.dart index 22188b5e..f516b20a 100644 --- a/lib/page/component/account_quota_card.dart +++ b/lib/page/component/account_quota_card.dart @@ -1,6 +1,6 @@ import 'package:askaide/helper/ability.dart'; import 'package:askaide/lang/lang.dart'; -import 'package:askaide/page/component/coin.dart'; +import 'package:askaide/page/component/credit.dart'; import 'package:askaide/page/component/enhanced_button.dart'; import 'package:askaide/page/component/theme/custom_theme.dart'; import 'package:askaide/repo/api/user.dart'; @@ -13,17 +13,14 @@ class AccountQuotaCard extends StatelessWidget { final UserInfo? userInfo; final VoidCallback? onPaymentReturn; final bool noBorder; - const AccountQuotaCard( - {super.key, this.userInfo, this.onPaymentReturn, this.noBorder = false}); + const AccountQuotaCard({super.key, this.userInfo, this.onPaymentReturn, this.noBorder = false}); @override Widget build(BuildContext context) { final customColors = Theme.of(context).extension()!; return Container( - margin: noBorder - ? null - : const EdgeInsets.symmetric(horizontal: 15, vertical: 10), + margin: noBorder ? null : const EdgeInsets.symmetric(horizontal: 15, vertical: 10), height: 140, child: Container( padding: noBorder @@ -72,7 +69,7 @@ class AccountQuotaCard extends StatelessWidget { crossAxisAlignment: CrossAxisAlignment.end, children: [ if (userInfo != null) - Coin( + Credit( count: userInfo!.quota.quotaRemain(), ) else @@ -84,7 +81,7 @@ class AccountQuotaCard extends StatelessWidget { context.push('/quota-usage-statistics'); }, child: Text( - AppLocale.coinsUsage.getString(context), + AppLocale.creditsUsage.getString(context), style: const TextStyle( fontSize: 12, color: Color.fromARGB(129, 220, 220, 220), diff --git a/lib/page/component/coin.dart b/lib/page/component/credit.dart similarity index 88% rename from lib/page/component/coin.dart rename to lib/page/component/credit.dart index 5e93f153..36557510 100644 --- a/lib/page/component/coin.dart +++ b/lib/page/component/credit.dart @@ -2,13 +2,13 @@ import 'package:askaide/lang/lang.dart'; import 'package:flutter/material.dart'; import 'package:flutter_localization/flutter_localization.dart'; -class Coin extends StatelessWidget { +class Credit extends StatelessWidget { final int count; final Color? color; final FontWeight? fontWeight; final double? fontSize; final bool withAddPrefix; - const Coin({ + const Credit({ super.key, required this.count, this.color, @@ -32,8 +32,7 @@ class Coin extends StatelessWidget { ), ), TextSpan( - text: - '${AppLocale.coinUnit.getString(context)}${count >= maxShowCount ? " +" : ""}', + text: '${AppLocale.creditUnit.getString(context)}${count >= maxShowCount ? " +" : ""}', style: TextStyle( fontSize: fontSize != null ? (fontSize! - 7) : 12, color: color ?? Colors.white.withAlpha(200), diff --git a/macos/Flutter/GeneratedPluginRegistrant.swift b/macos/Flutter/GeneratedPluginRegistrant.swift index 078da95c..1f9f3acd 100644 --- a/macos/Flutter/GeneratedPluginRegistrant.swift +++ b/macos/Flutter/GeneratedPluginRegistrant.swift @@ -7,6 +7,7 @@ import Foundation import audioplayers_darwin import file_saver +import flutter_image_compress_macos import flutter_localization import flutter_tts import in_app_purchase_storekit @@ -26,6 +27,7 @@ import wakelock_plus func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { AudioplayersDarwinPlugin.register(with: registry.registrar(forPlugin: "AudioplayersDarwinPlugin")) FileSaverPlugin.register(with: registry.registrar(forPlugin: "FileSaverPlugin")) + FlutterImageCompressMacosPlugin.register(with: registry.registrar(forPlugin: "FlutterImageCompressMacosPlugin")) FlutterLocalizationPlugin.register(with: registry.registrar(forPlugin: "FlutterLocalizationPlugin")) FlutterTtsPlugin.register(with: registry.registrar(forPlugin: "FlutterTtsPlugin")) InAppPurchasePlugin.register(with: registry.registrar(forPlugin: "InAppPurchasePlugin")) diff --git a/pubspec.lock b/pubspec.lock index b9ce62e1..f6bcad4d 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -662,10 +662,50 @@ packages: dependency: "direct main" description: name: flutter_image_compress - sha256: "37f1b26399098e5f97b74c1483f534855e7dff68ead6ddaccf747029fb03f29f" + sha256: "45a3071868092a61b11044c70422b04d39d4d9f2ef536f3c5b11fb65a1e7dd90" url: "https://pub.dev" source: hosted - version: "1.1.3" + version: "2.3.0" + flutter_image_compress_common: + dependency: transitive + description: + name: flutter_image_compress_common + sha256: "7f79bc6c8a363063620b4e372fa86bc691e1cb28e58048cd38e030692fbd99ee" + url: "https://pub.dev" + source: hosted + version: "1.0.5" + flutter_image_compress_macos: + dependency: transitive + description: + name: flutter_image_compress_macos + sha256: "26df6385512e92b3789dc76b613b54b55c457a7f1532e59078b04bf189782d47" + url: "https://pub.dev" + source: hosted + version: "1.0.2" + flutter_image_compress_ohos: + dependency: transitive + description: + name: flutter_image_compress_ohos + sha256: e76b92bbc830ee08f5b05962fc78a532011fcd2041f620b5400a593e96da3f51 + url: "https://pub.dev" + source: hosted + version: "0.0.3" + flutter_image_compress_platform_interface: + dependency: transitive + description: + name: flutter_image_compress_platform_interface + sha256: "579cb3947fd4309103afe6442a01ca01e1e6f93dc53bb4cbd090e8ce34a41889" + url: "https://pub.dev" + source: hosted + version: "1.0.5" + flutter_image_compress_web: + dependency: transitive + description: + name: flutter_image_compress_web + sha256: f02fe352b17f82b72f481de45add240db062a2585850bea1667e82cc4cd6c311 + url: "https://pub.dev" + source: hosted + version: "0.1.4+1" flutter_initicon: dependency: "direct main" description: diff --git a/pubspec.yaml b/pubspec.yaml index 04c25f0a..dde8f163 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -62,7 +62,7 @@ dependencies: http: ^1.1.0 cached_network_image: ^3.2.3 photo_view: ^0.14.0 - flutter_image_compress: ^1.1.3 + flutter_image_compress: ^2.3.0 isolate_image_compress: ^2.0.0 image: 4.1.7 flutter_cache_manager: ^3.3.0 From 195583ef815354f5559cad8f14a62b8ef6a07155 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=AE=A1=E5=AE=9C=E5=B0=A7?= Date: Tue, 10 Dec 2024 16:21:18 +0800 Subject: [PATCH 16/26] Page Style Optimization --- lib/bloc/room_bloc.dart | 159 +++++++++--------- lib/helper/constant.dart | 5 +- lib/helper/http.dart | 7 +- lib/helper/model.dart | 5 +- lib/lang/lang.dart | 19 ++- lib/page/admin/messages.dart | 2 +- lib/page/admin/models.dart | 9 +- lib/page/balance/quota_usage_statistics.dart | 2 +- lib/page/chat/component/model_switcher.dart | 1 + lib/page/chat/home_chat.dart | 2 +- lib/page/chat/room_chat.dart | 2 +- lib/page/chat/room_create.dart | 161 ++++++++++--------- lib/page/chat/room_edit.dart | 21 ++- lib/page/chat/rooms.dart | 8 +- lib/page/component/chat/role_avatar.dart | 2 +- lib/page/component/credit.dart | 4 +- lib/page/component/enhanced_error.dart | 63 ++++---- lib/page/component/model_item.dart | 89 ++++++++-- lib/page/component/theme/custom_theme.dart | 2 +- lib/page/drawer.dart | 7 + lib/page/home.dart | 2 +- lib/repo/api_server.dart | 137 +++++----------- lib/repo/model/misc.dart | 4 + lib/repo/model/model.dart | 39 +++++ macos/Podfile.lock | 10 +- macos/Runner/AppDelegate.swift | 2 +- pubspec.yaml | 2 +- 27 files changed, 424 insertions(+), 342 deletions(-) diff --git a/lib/bloc/room_bloc.dart b/lib/bloc/room_bloc.dart index c977baa9..4f75ad69 100644 --- a/lib/bloc/room_bloc.dart +++ b/lib/bloc/room_bloc.dart @@ -41,8 +41,7 @@ class RoomBloc extends BlocExt { if (Ability().isUserLogon()) { final room = await APIServer().room(roomId: event.roomId); if (event.chatHistoryId != null && event.chatHistoryId! > 0) { - final chatHistory = - await chatMsgRepo.getChatHistory(event.chatHistoryId!); + final chatHistory = await chatMsgRepo.getChatHistory(event.chatHistoryId!); if (chatHistory != null && chatHistory.model != null) { room.model = chatHistory.model!; } @@ -58,9 +57,7 @@ class RoomBloc extends BlocExt { lastActiveTime: room.lastActiveTime, systemPrompt: room.systemPrompt, priority: room.priority ?? 0, - model: room.model.startsWith('v2@') - ? room.model - : '${room.vendor}:${room.model}', + model: room.model.startsWith('v2@') ? room.model : '${room.vendor}:${room.model}', initMessage: room.initMessage, maxContext: room.maxContext, avatarId: room.avatarId, @@ -83,9 +80,7 @@ class RoomBloc extends BlocExt { lastActiveTime: room.lastActiveTime, systemPrompt: room.systemPrompt, priority: room.priority ?? 0, - model: room.model.startsWith('v2@') - ? room.model - : '${room.vendor}:${room.model}', + model: room.model.startsWith('v2@') ? room.model : '${room.vendor}:${room.model}', initMessage: room.initMessage, maxContext: room.maxContext, avatarId: room.avatarId, @@ -94,9 +89,7 @@ class RoomBloc extends BlocExt { ), states, examples: await APIServer().example( - room.model.startsWith('v2@') - ? room.model - : '${room.vendor}:${room.model}', + room.model.startsWith('v2@') ? room.model : '${room.vendor}:${room.model}', ), cascading: event.cascading, )); @@ -142,12 +135,8 @@ class RoomBloc extends BlocExt { id = await APIServer().createRoom( name: event.name, - vendor: event.model.startsWith('v2@') - ? '' - : (segs.length > 1 ? segs.first : ''), - model: event.model.startsWith('v2@') - ? event.model - : (segs.length > 1 ? segs.last : event.model), + vendor: event.model.startsWith('v2@') ? '' : (segs.length > 1 ? segs.first : ''), + model: event.model.startsWith('v2@') ? event.model : (segs.length > 1 ? segs.last : event.model), systemPrompt: event.prompt, avatarId: event.avatarId, avatarUrl: event.avatarUrl, @@ -170,7 +159,8 @@ class RoomBloc extends BlocExt { emit(RoomOperationResult(true, redirect: '/room/$id/chat')); emit(await createRoomsLoadedState(cache: false)); } catch (e) { - emit(RoomsLoaded(const [], error: e.toString())); + emit(RoomOperationResult(false, error: e.toString())); + // emit(RoomsLoaded(const [], error: e.toString())); } }); @@ -198,78 +188,76 @@ class RoomBloc extends BlocExt { // 更新聊天室信息 on((event, emit) async { - if (Ability().isUserLogon()) { - final room = await APIServer().updateRoom( - roomId: event.roomId, - name: event.name!, - model: event.model!.startsWith('v2@') - ? event.model! - : event.model!.split(':').last, - vendor: event.model!.startsWith('v2@') - ? '' - : event.model!.split(':').first, - systemPrompt: event.prompt!, - avatarId: event.avatarId, - avatarUrl: event.avatarUrl, - maxContext: event.maxContext, - initMessage: event.initMessage, - ); + try { + if (Ability().isUserLogon()) { + final room = await APIServer().updateRoom( + roomId: event.roomId, + name: event.name!, + model: event.model!.startsWith('v2@') ? event.model! : event.model!.split(':').last, + vendor: event.model!.startsWith('v2@') ? '' : event.model!.split(':').first, + systemPrompt: event.prompt!, + avatarId: event.avatarId, + avatarUrl: event.avatarUrl, + maxContext: event.maxContext, + initMessage: event.initMessage, + ); - final states = await stateManager.loadRoomStates(event.roomId); - emit( - RoomLoaded( - Room( - room.name, - 'chat', - description: room.description, - id: room.id, - userId: room.userId, - createdAt: room.createdAt, - lastActiveTime: room.lastActiveTime, - systemPrompt: room.systemPrompt, - priority: room.priority ?? 0, - model: room.model.startsWith('v2@') - ? room.model - : '${room.vendor}:${room.model}', - avatarId: room.avatarId, - avatarUrl: room.avatarUrl, - initMessage: room.initMessage, - roomType: room.roomType, + final states = await stateManager.loadRoomStates(event.roomId); + emit( + RoomLoaded( + Room( + room.name, + 'chat', + description: room.description, + id: room.id, + userId: room.userId, + createdAt: room.createdAt, + lastActiveTime: room.lastActiveTime, + systemPrompt: room.systemPrompt, + priority: room.priority ?? 0, + model: room.model.startsWith('v2@') ? room.model : '${room.vendor}:${room.model}', + avatarId: room.avatarId, + avatarUrl: room.avatarUrl, + initMessage: room.initMessage, + roomType: room.roomType, + ), + states, + examples: await APIServer().example(room.model), + cascading: false, ), - states, - examples: await APIServer().example(room.model), - cascading: false, - ), - ); - } else { - final room = await chatMsgRepo.room(event.roomId); + ); + } else { + final room = await chatMsgRepo.room(event.roomId); - if (room != null) { - if (event.name != null && event.name != '') { - room.name = event.name!; - } + if (room != null) { + if (event.name != null && event.name != '') { + room.name = event.name!; + } - if (event.model != null && event.model != '') { - room.model = event.model!; - } + if (event.model != null && event.model != '') { + room.model = event.model!; + } - if (event.prompt != null && event.prompt != '') { - room.systemPrompt = event.prompt!; - } + if (event.prompt != null && event.prompt != '') { + room.systemPrompt = event.prompt!; + } - if (event.maxContext != null) { - room.maxContext = event.maxContext!; - } + if (event.maxContext != null) { + room.maxContext = event.maxContext!; + } - await chatMsgRepo.updateRoom(room); - final states = await stateManager.loadRoomStates(event.roomId); - emit(RoomLoaded( - room, - states, - examples: await APIServer().examples(), - cascading: false, - )); + await chatMsgRepo.updateRoom(room); + final states = await stateManager.loadRoomStates(event.roomId); + emit(RoomLoaded( + room, + states, + examples: await APIServer().examples(), + cascading: false, + )); + } } + } catch (e) { + emit(RoomOperationResult(false, error: e.toString())); } }); @@ -349,9 +337,7 @@ class RoomBloc extends BlocExt { lastActiveTime: room.lastActiveTime, systemPrompt: room.systemPrompt, priority: room.priority ?? 0, - model: room.model.startsWith('v2@') - ? room.model - : '${room.vendor}:${room.model}', + model: room.model.startsWith('v2@') ? room.model : '${room.vendor}:${room.model}', avatarId: room.avatarId, avatarUrl: room.avatarUrl, roomType: room.roomType, @@ -364,8 +350,7 @@ class RoomBloc extends BlocExt { final rooms = await chatMsgRepo.rooms( userId: APIServer().localUserID(), ); - rooms.removeWhere((element) => - element.id == chatAnywhereRoomId && element.category == 'system'); + rooms.removeWhere((element) => element.id == chatAnywhereRoomId && element.category == 'system'); return RoomsLoaded(rooms); } } catch (e) { diff --git a/lib/helper/constant.dart b/lib/helper/constant.dart index 747fc6a6..9943637c 100644 --- a/lib/helper/constant.dart +++ b/lib/helper/constant.dart @@ -1,13 +1,10 @@ import 'package:flutter/material.dart'; // 客户端应用版本号 -const clientVersion = '1.0.15'; +const clientVersion = '2.0.0'; // 本地数据库版本号 const databaseVersion = 27; -const maxRoomNumForNonVIP = 50; -const creditSign = '个'; - const settingAPIServerToken = 'api-token'; const settingUserInfo = 'user-info'; const settingUsingGuestMode = 'using-guest-mode'; diff --git a/lib/helper/http.dart b/lib/helper/http.dart index 126b0620..b3a7d00b 100644 --- a/lib/helper/http.dart +++ b/lib/helper/http.dart @@ -40,8 +40,7 @@ class HttpClient { Map? queryParameters, Options? options, }) async { - return await dio.get(url, - queryParameters: queryParameters, options: options); + return await dio.get(url, queryParameters: queryParameters, options: options); } static Future getCached( @@ -61,9 +60,7 @@ class HttpClient { extra: cacheOptions .copyWith( maxStale: Nullable(duration), - policy: forceRefresh - ? CachePolicy.refreshForceCache - : CachePolicy.forceCache, + policy: forceRefresh ? CachePolicy.refreshForceCache : CachePolicy.forceCache, ) .toExtra()), ); diff --git a/lib/helper/model.dart b/lib/helper/model.dart index 3d19674d..722b1d1a 100644 --- a/lib/helper/model.dart +++ b/lib/helper/model.dart @@ -13,13 +13,13 @@ class ModelAggregate { } /// 支持的模型列表 - static Future> models({bool cache = true}) async { + static Future> models({bool cache = true, bool withCustom = false}) async { final List models = []; final isAPIServerSet = settings.stringDefault(settingAPIServerToken, '') != ''; final selfHostOpenAI = settings.boolDefault(settingOpenAISelfHosted, false); if (isAPIServerSet) { - models.addAll((await APIServer().models(cache: cache)) + models.addAll((await APIServer().models(cache: cache, withCustom: withCustom)) .map( (e) => mm.Model( e.id.split(':').last, @@ -27,6 +27,7 @@ class ModelAggregate { e.category, shortName: e.shortName, description: e.description, + priceInfo: e.priceInfo, isChatModel: e.isChat, disabled: e.disabled, category: e.category, diff --git a/lib/lang/lang.dart b/lib/lang/lang.dart index 3f5fbfdc..eeeef3dc 100644 --- a/lib/lang/lang.dart +++ b/lib/lang/lang.dart @@ -113,7 +113,6 @@ mixin AppLocale { static const String modelNotFound = 'model-not-found'; static const String nameRequiredMessage = 'name-required-message'; - static const String promptFormatError = 'prompt-format-error'; static const String modelRequiredMessage = 'model-required-message'; static const String writeYourIdeas = 'write-your-ideas'; @@ -303,6 +302,10 @@ mixin AppLocale { static const String selectGroupMembers = 'select-group-members'; static const String selectPaymentMethod = 'select-payment-method'; static const String validDays = 'valid-days'; + static const String clickToReSignin = 'click-to-resignin'; + static const String free = 'free'; + static const String input = 'input'; + static const String output = 'output'; static const Map zh = { required: '必填', @@ -399,7 +402,6 @@ mixin AppLocale { modelUsage: '模型用于设置采用的 AI 数字人类型', promptUsage: '领域设定用于设置 AI 数字人的行为', nameRequiredMessage: '请输入数字人名称', - promptFormatError: '角色设定不能超过1000字', modelRequiredMessage: '请选择 AI 模型', operateSuccess: '操作成功', operateFailed: '操作失败', @@ -518,7 +520,7 @@ mixin AppLocale { buy: '购买', paymentHistory: '购买历史', buyCredits: '购买智慧果', - creditUnit: '个', + creditUnit: '¢', toPay: '立即支付', discover: '绘玩', customHomeModels: '常用模型', @@ -592,6 +594,10 @@ mixin AppLocale { selectGroupMembers: '选择参与群聊的成员', selectPaymentMethod: '请选择支付方式', validDays: '内有效', + clickToReSignin: '点击此处重新登录', + free: '限免', + input: '输入', + output: '输出', }; static const Map en = { @@ -689,7 +695,6 @@ mixin AppLocale { modelUsage: 'The model is used to set the type of AI character used', promptUsage: 'Prompt is used to set the behavior of the AI character', nameRequiredMessage: 'Please enter the name of the character', - promptFormatError: 'Prompt cannot exceed 1000 words', modelRequiredMessage: 'Please select AI model', operateSuccess: 'Success', operateFailed: 'Failed', @@ -812,7 +817,7 @@ mixin AppLocale { buy: 'Buy', paymentHistory: 'Histories', buyCredits: 'Buy Credits', - creditUnit: '', + creditUnit: '¢', toPay: 'Create Order', discover: 'Discover', customHomeModels: 'Favorite Models', @@ -886,6 +891,10 @@ mixin AppLocale { selectGroupMembers: 'Select group members', selectPaymentMethod: 'Select payment method', validDays: 'expiration', + clickToReSignin: 'Click here to sign in again', + free: 'Free', + input: 'Input', + output: 'Output', }; } diff --git a/lib/page/admin/messages.dart b/lib/page/admin/messages.dart index ad2dba9a..03811bd2 100644 --- a/lib/page/admin/messages.dart +++ b/lib/page/admin/messages.dart @@ -55,7 +55,7 @@ class _AdminRoomMessagesPageState extends State { roomType: widget.roomType, )); - ModelAggregate.models().then((value) { + ModelAggregate.models(withCustom: true).then((value) { setState(() { for (var element in value) { models[element.id] = element; diff --git a/lib/page/admin/models.dart b/lib/page/admin/models.dart index 63d23b5e..ee25ffaf 100644 --- a/lib/page/admin/models.dart +++ b/lib/page/admin/models.dart @@ -357,9 +357,10 @@ class _AdminModelsPageState extends State { if (mod.inputPrice > 0 || mod.outputPrice > 0) { desc += '💰 '; if (mod.inputPrice == mod.outputPrice) { - desc += '${mod.inputPrice} Credits/1K Token'; + desc += 'IO${AppLocale.creditUnit.getString(context)}${mod.inputPrice}'; } else { - desc += '${mod.inputPrice} / ${mod.outputPrice} Credits/1K Token'; + desc += + 'I${AppLocale.creditUnit.getString(context)}${mod.inputPrice}/O${AppLocale.creditUnit.getString(context)}${mod.outputPrice}'; } } @@ -371,6 +372,10 @@ class _AdminModelsPageState extends State { desc += '🎞️ ${mod.maxContext} Tokens'; } + if (mod.meta != null && mod.meta!.tag != null && mod.meta!.tag != '') { + desc += ' | ${mod.meta!.tag}'; + } + if (desc != '') { desc += '\n'; } diff --git a/lib/page/balance/quota_usage_statistics.dart b/lib/page/balance/quota_usage_statistics.dart index 4d15cede..59c1b10e 100644 --- a/lib/page/balance/quota_usage_statistics.dart +++ b/lib/page/balance/quota_usage_statistics.dart @@ -131,7 +131,7 @@ class _QuotaUsageStatisticsScreenState extends State if (item.used == -1) Text(AppLocale.unbilled.getString(context)) else - Text('${item.used > 0 ? "-" : ""}${item.used}'), + Text('${item.used > 0 ? "-" : ""}${AppLocale.creditUnit.getString(context)}${item.used}'), ], ), ), diff --git a/lib/page/chat/component/model_switcher.dart b/lib/page/chat/component/model_switcher.dart index 24d6f2a7..e7e27be7 100644 --- a/lib/page/chat/component/model_switcher.dart +++ b/lib/page/chat/component/model_switcher.dart @@ -32,6 +32,7 @@ class ModelSwitcher extends StatelessWidget { initValue: initValue?.uid(), enableClear: true, title: AppLocale.switchModelTitle.getString(context), + withCustom: true, ); } diff --git a/lib/page/chat/home_chat.dart b/lib/page/chat/home_chat.dart index 174ca1c3..a4d55cfb 100644 --- a/lib/page/chat/home_chat.dart +++ b/lib/page/chat/home_chat.dart @@ -138,7 +138,7 @@ class _HomeChatPageState extends State { }; // 加载模型列表,用于查询模型名称 - ModelAggregate.models().then((value) { + ModelAggregate.models(withCustom: true).then((value) { setState(() { supportModels = value; }); diff --git a/lib/page/chat/room_chat.dart b/lib/page/chat/room_chat.dart index 8ded8892..5d8ba763 100644 --- a/lib/page/chat/room_chat.dart +++ b/lib/page/chat/room_chat.dart @@ -103,7 +103,7 @@ class _RoomChatPageState extends State { }; // 加载模型列表,用于查询模型名称 - ModelAggregate.models().then((value) { + ModelAggregate.models(withCustom: true).then((value) { setState(() { supportModels = value; }); diff --git a/lib/page/chat/room_create.dart b/lib/page/chat/room_create.dart index 353621e2..dec2e897 100644 --- a/lib/page/chat/room_create.dart +++ b/lib/page/chat/room_create.dart @@ -111,77 +111,93 @@ class _RoomCreatePageState extends State { setting: widget.setting, enabled: false, maxWidth: 0, - child: Padding( - padding: const EdgeInsets.symmetric(horizontal: 10), - child: Ability().isUserLogon() - ? SafeArea( - top: false, - child: DefaultTabController( - length: tags.length + (selectedSuggestions.isEmpty ? 1 : 0), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Theme( - data: Theme.of(context).copyWith( - colorScheme: - Theme.of(context).colorScheme.copyWith(surfaceContainerHighest: Colors.transparent), - ), - child: TabBar( - tabs: [ - for (var tag in tags) Tab(text: tag), - if (selectedSuggestions.isEmpty) Tab(text: AppLocale.custom.getString(context)), - ], - isScrollable: true, - labelColor: customColors.linkColor, - indicator: const BoxDecoration(), - labelPadding: const EdgeInsets.only(right: 5, left: 10), - overlayColor: WidgetStateProperty.all(Colors.transparent), - tabAlignment: TabAlignment.center, + child: BlocListener( + listenWhen: (previous, current) => current is RoomOperationResult, + listener: (context, state) { + if (state is RoomOperationResult) { + if (state.success) { + if (state.redirect != null) { + context.push(state.redirect!).then((value) { + context.read().add(RoomsLoadEvent()); + }); + } + } else { + showErrorMessageEnhanced(context, state.error ?? AppLocale.operateFailed.getString(context)); + } + } + }, + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 10), + child: Ability().isUserLogon() + ? SafeArea( + top: false, + child: DefaultTabController( + length: tags.length + (selectedSuggestions.isEmpty ? 1 : 0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Theme( + data: Theme.of(context).copyWith( + colorScheme: + Theme.of(context).colorScheme.copyWith(surfaceContainerHighest: Colors.transparent), + ), + child: TabBar( + tabs: [ + for (var tag in tags) Tab(text: tag), + if (selectedSuggestions.isEmpty) Tab(text: AppLocale.custom.getString(context)), + ], + isScrollable: true, + labelColor: customColors.linkColor, + indicator: const BoxDecoration(), + labelPadding: const EdgeInsets.only(right: 5, left: 10), + overlayColor: WidgetStateProperty.all(Colors.transparent), + tabAlignment: TabAlignment.center, + ), ), - ), - Expanded( - child: BlocConsumer( - listenWhen: (previous, current) => current is RoomGalleriesLoaded, - listener: (context, state) { - if (state is RoomGalleriesLoaded) { - if (state.error != null) { - showErrorMessageEnhanced(context, state.error!); + Expanded( + child: BlocConsumer( + listenWhen: (previous, current) => current is RoomGalleriesLoaded, + listener: (context, state) { + if (state is RoomGalleriesLoaded) { + if (state.error != null) { + showErrorMessageEnhanced(context, state.error!); + } + + if (state.galleries.isNotEmpty) { + tags = state.tags; + + setState(() {}); + } } - - if (state.galleries.isNotEmpty) { - tags = state.tags; - - setState(() {}); + }, + buildWhen: (previous, current) => current is RoomGalleriesLoaded, + builder: (context, state) { + if (state is RoomGalleriesLoaded) { + return TabBarView( + children: [ + for (var tag in tags) + buildSuggestTab( + customColors, + context, + state.galleries.where((element) => element.tags.contains(tag)).toList(), + ), + if (selectedSuggestions.isEmpty) buildCustomTab(customColors, context), + ], + ); } - } - }, - buildWhen: (previous, current) => current is RoomGalleriesLoaded, - builder: (context, state) { - if (state is RoomGalleriesLoaded) { - return TabBarView( - children: [ - for (var tag in tags) - buildSuggestTab( - customColors, - context, - state.galleries.where((element) => element.tags.contains(tag)).toList(), - ), - if (selectedSuggestions.isEmpty) buildCustomTab(customColors, context), - ], - ); - } - return const Center( - child: CircularProgressIndicator(), - ); - }, - ), - ) - ], + return const Center( + child: CircularProgressIndicator(), + ); + }, + ), + ) + ], + ), ), - ), - ) - : buildCustomTab(customColors, context), + ) + : buildCustomTab(customColors, context), + ), ), ), bottomNavigationBar: selectedSuggestions.isNotEmpty @@ -386,6 +402,7 @@ class _RoomCreatePageState extends State { // 提示语 if (_selectedModel != null && _selectedModel!.isChatModel) EnhancedTextField( + fontSize: 12, customColors: customColors, controller: _promptController, labelText: AppLocale.prompt.getString(context), @@ -416,7 +433,7 @@ class _RoomCreatePageState extends State { ); }, minLines: 4, - maxLines: 8, + maxLines: 20, showCounter: false, ), ], @@ -519,11 +536,6 @@ class _RoomCreatePageState extends State { return; } - if (_promptController.text.length > 1000) { - showErrorMessage(AppLocale.promptFormatError.getString(context)); - return; - } - if (_selectedModel == null) { showErrorMessage(AppLocale.modelRequiredMessage.getString(context)); return; @@ -560,8 +572,6 @@ class _RoomCreatePageState extends State { initMessage: _initMessageController.text, ), ); - - context.pop(); } }, ), @@ -593,9 +603,10 @@ void openSelectModelDialog( bool enableClear = false, String? title, String? priorityModelId, + bool withCustom = false, }) { future() async { - final models = await ModelAggregate.models(); + final models = await ModelAggregate.models(cache: false, withCustom: withCustom); if (priorityModelId != null) { // 将 models 中,id 与 priorityModelId 相同的元素排序到最前面 diff --git a/lib/page/chat/room_edit.dart b/lib/page/chat/room_edit.dart index 42a6105d..0bad179a 100644 --- a/lib/page/chat/room_edit.dart +++ b/lib/page/chat/room_edit.dart @@ -146,7 +146,20 @@ class _RoomEditPageState extends State { }); } } + + if (state is RoomOperationResult) { + if (state.success) { + if (state.redirect != null) { + context.push(state.redirect!).then((value) { + context.read().add(RoomsLoadEvent()); + }); + } + } else { + showErrorMessageEnhanced(context, state.error ?? AppLocale.operateFailed.getString(context)); + } + } }, + buildWhen: (previous, current) => current is RoomLoaded, builder: (context, state) { if (state is RoomLoaded) { return SingleChildScrollView( @@ -267,6 +280,7 @@ class _RoomEditPageState extends State { // 提示语 if ((_selectedModel != null && _selectedModel!.isChatModel) || _promptController.text != '') EnhancedTextField( + fontSize: 12, customColors: customColors, controller: _promptController, labelText: AppLocale.prompt.getString(context), @@ -297,7 +311,7 @@ class _RoomEditPageState extends State { ); }, minLines: 4, - maxLines: 8, + maxLines: 20, showCounter: false, ), ], @@ -405,11 +419,6 @@ class _RoomEditPageState extends State { return; } - if (_promptController.text.length > 1000) { - showErrorMessage(AppLocale.promptFormatError.getString(context)); - return; - } - if (_avatarUrl != null) { if (!(_avatarUrl!.startsWith('http://') || _avatarUrl!.startsWith('https://'))) { // 上传文件,获取 URL diff --git a/lib/page/chat/rooms.dart b/lib/page/chat/rooms.dart index d7eede1f..6f245420 100644 --- a/lib/page/chat/rooms.dart +++ b/lib/page/chat/rooms.dart @@ -63,13 +63,7 @@ class _RoomsPageState extends State { } if (state is RoomOperationResult) { - if (state.success) { - if (state.redirect != null) { - context.push(state.redirect!).then((value) { - context.read().add(RoomsLoadEvent()); - }); - } - } else { + if (!state.success) { showErrorMessageEnhanced(context, state.error ?? AppLocale.operateFailed.getString(context)); } } diff --git a/lib/page/component/chat/role_avatar.dart b/lib/page/component/chat/role_avatar.dart index 785dc8f6..2916703e 100644 --- a/lib/page/component/chat/role_avatar.dart +++ b/lib/page/component/chat/role_avatar.dart @@ -48,7 +48,7 @@ class _RoleAvatarState extends State { if (widget.his != null && widget.his!.model != null) { return FutureBuilder( - future: ModelAggregate.models(), + future: ModelAggregate.models(withCustom: true), builder: (context, snapshot) { if (!snapshot.hasError && snapshot.hasData) { var mod = snapshot.data!.where((e) => e.id == widget.his!.model!).firstOrNull; diff --git a/lib/page/component/credit.dart b/lib/page/component/credit.dart index 36557510..1b27fa96 100644 --- a/lib/page/component/credit.dart +++ b/lib/page/component/credit.dart @@ -23,7 +23,7 @@ class Credit extends StatelessWidget { text: TextSpan( children: [ TextSpan( - text: '${withAddPrefix ? "+ " : ""}${formatCount()}', + text: '${withAddPrefix ? "+ " : ""}${AppLocale.creditUnit.getString(context)}${formatCount()}', style: TextStyle( fontSize: fontSize ?? 20, color: color ?? Colors.white, @@ -32,7 +32,7 @@ class Credit extends StatelessWidget { ), ), TextSpan( - text: '${AppLocale.creditUnit.getString(context)}${count >= maxShowCount ? " +" : ""}', + text: count >= maxShowCount ? " +" : "", style: TextStyle( fontSize: fontSize != null ? (fontSize! - 7) : 12, color: color ?? Colors.white.withAlpha(200), diff --git a/lib/page/component/enhanced_error.dart b/lib/page/component/enhanced_error.dart index 45746966..a058c6c3 100644 --- a/lib/page/component/enhanced_error.dart +++ b/lib/page/component/enhanced_error.dart @@ -1,6 +1,8 @@ import 'package:askaide/helper/helper.dart'; +import 'package:askaide/lang/lang.dart'; import 'package:askaide/page/component/theme/custom_size.dart'; import 'package:flutter/material.dart'; +import 'package:flutter_localization/flutter_localization.dart'; import 'package:go_router/go_router.dart'; class EnhancedErrorWidget extends StatelessWidget { @@ -13,40 +15,45 @@ class EnhancedErrorWidget extends StatelessWidget { return Container(); } - return Center( - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - RichText( - text: TextSpan( - children: [ - TextSpan( - text: resolveError(context, error!), + return Scaffold( + appBar: AppBar( + toolbarHeight: CustomSize.toolbarHeight, + ), + body: Center( + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + RichText( + text: TextSpan( + children: [ + TextSpan( + text: resolveError(context, error!), + style: const TextStyle( + color: Colors.red, + fontSize: 17, + ), + ), + ], + ), + ), + InkWell( + onTap: () { + context.go('/login'); + }, + borderRadius: CustomSize.borderRadiusAll, + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 5), + child: Text( + AppLocale.clickToReSignin.getString(context), + textScaler: const TextScaler.linear(0.8), style: const TextStyle( color: Colors.red, - fontSize: 17, ), ), - ], - ), - ), - InkWell( - onTap: () { - context.go('/login'); - }, - borderRadius: CustomSize.borderRadiusAll, - child: const Padding( - padding: EdgeInsets.symmetric(horizontal: 10, vertical: 5), - child: Text( - '点击此处重新登录', - textScaler: TextScaler.linear(0.8), - style: TextStyle( - color: Colors.red, - ), ), ), - ), - ], + ], + ), ), ); } diff --git a/lib/page/component/model_item.dart b/lib/page/component/model_item.dart index e35baa74..664d7d36 100644 --- a/lib/page/component/model_item.dart +++ b/lib/page/component/model_item.dart @@ -3,6 +3,7 @@ import 'package:askaide/helper/color.dart'; import 'package:askaide/helper/constant.dart'; import 'package:askaide/helper/image.dart'; import 'package:askaide/lang/lang.dart'; +import 'package:askaide/page/component/dialog.dart'; import 'package:askaide/page/component/random_avatar.dart'; import 'package:askaide/page/component/theme/custom_size.dart'; import 'package:askaide/page/component/theme/custom_theme.dart'; @@ -10,6 +11,7 @@ import 'package:askaide/page/component/weak_text_button.dart'; import 'package:askaide/repo/model/model.dart'; import 'package:flutter/material.dart'; import 'package:flutter_localization/flutter_localization.dart'; +import 'package:quickalert/models/quickalert_type.dart'; class ModelItem extends StatefulWidget { final List models; @@ -86,8 +88,17 @@ class _ModelItemState extends State { itemCount: models.length, itemBuilder: (context, i) { var item = models[i]; + final modelPrice = item.modelPrice; var tags = []; + if (modelPrice.isFree) { + tags.add(buildTag( + customColors, + AppLocale.free.getString(context), + tagTextColor: colorToString(Colors.white), + tagBgColor: colorToString(customColors.markdownLinkColor!), + )); + } if (item.tag != null) { item.tag!.split(",").forEach((tag) { if (tag.isEmpty) return; @@ -191,18 +202,19 @@ class _ModelItemState extends State { ], ], ), - if (item.description != null && item.description != '') - Text( - item.description!, - maxLines: 2, - overflow: TextOverflow.ellipsis, - style: TextStyle( - fontSize: 12, - color: widget.initValue == item.uid() - ? customColors.linkColor - : customColors.weakTextColor, - ), - ), + if (!modelPrice.isFree) buildPriceBlock(customColors, item, modelPrice), + // if (item.description != null && item.description != '') + // Text( + // item.description!, + // maxLines: 2, + // overflow: TextOverflow.ellipsis, + // style: TextStyle( + // fontSize: 12, + // color: widget.initValue == item.uid() + // ? customColors.linkColor + // : customColors.weakTextColor, + // ), + // ), ], ), ), @@ -212,6 +224,19 @@ class _ModelItemState extends State { onTap: () { widget.onSelected(item); }, + onLongPress: () { + if (item.description == null || item.description == '') { + return; + } + + showBeautyDialog( + context, + type: QuickAlertType.info, + text: item.description, + confirmBtnText: AppLocale.gotIt.getString(context), + showCancelBtn: false, + ); + }, ), ], ); @@ -239,6 +264,46 @@ class _ModelItemState extends State { ); } + Widget buildPriceBlock(CustomColors customColors, Model model, ModelPrice item) { + if (item.isFree) { + return const SizedBox(); + } + + return Row( + children: [ + Text( + '${AppLocale.input.getString(context)} ¢${item.input}, ${AppLocale.output.getString(context)} ¢${item.output}', + maxLines: 2, + overflow: TextOverflow.ellipsis, + style: TextStyle( + fontSize: 10, + color: + widget.initValue == model.uid() ? customColors.linkColor : customColors.weakTextColor?.withAlpha(150), + ), + ), + if (item.hasNote) ...[ + const SizedBox(width: 5), + InkWell( + onTap: () { + showBeautyDialog( + context, + type: QuickAlertType.info, + text: item.note, + confirmBtnText: AppLocale.gotIt.getString(context), + showCancelBtn: false, + ); + }, + child: Icon( + Icons.help_outline, + size: 12, + color: customColors.weakLinkColor?.withAlpha(50), + ), + ), + ], + ], + ); + } + Widget buildCategory(CustomColors customColors, String category) { return Container( padding: const EdgeInsets.only(left: 10, right: 10), diff --git a/lib/page/component/theme/custom_theme.dart b/lib/page/component/theme/custom_theme.dart index 6fe64d99..d8cc315c 100644 --- a/lib/page/component/theme/custom_theme.dart +++ b/lib/page/component/theme/custom_theme.dart @@ -288,7 +288,7 @@ class CustomColors extends ThemeExtension { weakTextColorPlus: Color.fromARGB(255, 137, 137, 137), weakTextColorPlusPlus: Color.fromARGB(255, 173, 173, 173), dialogDefaultTextColor: Color.fromARGB(195, 255, 255, 255), - dialogBackgroundColor: Color.fromARGB(255, 48, 48, 48), + dialogBackgroundColor: Colors.black, columnBlockBorderColor: Color.fromARGB(255, 72, 72, 72), columnBlockBackgroundColor: Color.fromARGB(255, 44, 44, 46), columnBlockDividerColor: Color.fromARGB(160, 60, 60, 60), diff --git a/lib/page/drawer.dart b/lib/page/drawer.dart index d9a290db..efa3c2f8 100644 --- a/lib/page/drawer.dart +++ b/lib/page/drawer.dart @@ -20,6 +20,13 @@ class LeftDrawer extends StatefulWidget { } class _LeftDrawerState extends State { + @override + void initState() { + super.initState(); + + context.read().add(AccountLoadEvent(cache: false)); + } + @override Widget build(BuildContext context) { final customColors = Theme.of(context).extension()!; diff --git a/lib/page/home.dart b/lib/page/home.dart index 5f42fe68..c7c76f90 100644 --- a/lib/page/home.dart +++ b/lib/page/home.dart @@ -117,7 +117,7 @@ class _NewHomePageState extends State { /// 加载模型列表,用于查询模型名称 Future reloadModels({bool cache = true}) async { - var value = await ModelAggregate.models(cache: cache); + var value = await ModelAggregate.models(cache: cache, withCustom: true); setState(() { supportModels = value; }); diff --git a/lib/repo/api_server.dart b/lib/repo/api_server.dart index 1695f426..02842248 100644 --- a/lib/repo/api_server.dart +++ b/lib/repo/api_server.dart @@ -39,8 +39,7 @@ class APIServer { return _instance; } - GlobalAlertEvent _globalAlertEvent = - GlobalAlertEvent(id: '', type: 'info', pages: [], message: ''); + GlobalAlertEvent _globalAlertEvent = GlobalAlertEvent(id: '', type: 'info', pages: [], message: ''); GlobalAlertEvent get globalAlertEvent => _globalAlertEvent; @@ -85,10 +84,7 @@ class APIServer { if (e.response != null) { final resp = e.response!; - if (resp.data is Map && - resp.data['error'] != null && - resp.statusCode != 402 && - resp.statusCode != 401) { + if (resp.data is Map && resp.data['error'] != null && resp.statusCode != 402 && resp.statusCode != 401) { return resp.data['error'] ?? e.toString(); } @@ -114,12 +110,8 @@ class APIServer { return Options( headers: _buildAuthHeaders(), receiveDataWhenStatusError: true, - sendTimeout: requestTimeout != null - ? Duration(milliseconds: requestTimeout) - : null, - receiveTimeout: requestTimeout != null - ? Duration(milliseconds: requestTimeout) - : null, + sendTimeout: requestTimeout != null ? Duration(milliseconds: requestTimeout) : null, + receiveTimeout: requestTimeout != null ? Duration(milliseconds: requestTimeout) : null, ); } @@ -321,15 +313,11 @@ class APIServer { final globalAlertEvent = GlobalAlertEvent( id: resp.headers.value('aidea-global-alert-id') ?? '', type: resp.headers.value('aidea-global-alert-type') ?? 'info', - pages: (resp.headers.value('aidea-global-alert-pages') ?? '') - .split(',') - .where((e) => e != '') - .toList(), + pages: (resp.headers.value('aidea-global-alert-pages') ?? '').split(',').where((e) => e != '').toList(), message: msg, ); - if (globalAlertEvent.id != '' && - globalAlertEvent.id != _globalAlertEvent.id) { + if (globalAlertEvent.id != '' && globalAlertEvent.id != _globalAlertEvent.id) { _globalAlertEvent = globalAlertEvent; GlobalEvent().emit('global-alert', _globalAlertEvent); } @@ -489,14 +477,13 @@ class APIServer { Future> proxyServers(String service) async { return sendCachedGetRequest( '/v1/proxy/servers', - (resp) => - (resp['servers'][service] as List).map((e) => e.toString()).toList(), + (resp) => (resp['servers'][service] as List).map((e) => e.toString()).toList(), subKey: _cacheSubKey(), ); } /// 获取模型列表 - Future> models({bool cache = true}) async { + Future> models({bool cache = true, bool withCustom = false}) async { return sendCachedGetRequest( '/v2/models', (resp) { @@ -507,6 +494,9 @@ class APIServer { return models; }, + queryParameters: { + "with-custom": withCustom, + }, subKey: _cacheSubKey(), forceRefresh: !cache, ); @@ -553,9 +543,7 @@ class APIServer { return sendCachedGetRequest( '/v1/images/avatar', (resp) { - return (resp.data['avatars'] as List) - .map((e) => e.toString()) - .toList(); + return (resp.data['avatars'] as List).map((e) => e.toString()).toList(); }, ); } @@ -616,9 +604,7 @@ class APIServer { examples.add(ChatExample( example['title'], content: example['content'], - models: ((example['models'] ?? []) as List) - .map((e) => e.toString()) - .toList(), + models: ((example['models'] ?? []) as List).map((e) => e.toString()).toList(), )); } return examples; @@ -655,9 +641,7 @@ class APIServer { examples.add(ChatExample( example['title'], content: example['content'], - models: ((example['models'] ?? []) as List) - .map((e) => e.toString()) - .toList(), + models: ((example['models'] ?? []) as List).map((e) => e.toString()).toList(), )); } return examples; @@ -693,9 +677,7 @@ class APIServer { for (var item in resp.data['items']) { items.add(CreativeIslandItem.fromJson(item)); } - final categories = (resp.data['categories'] as List) - .map((e) => e.toString()) - .toList(); + final categories = (resp.data['categories'] as List).map((e) => e.toString()).toList(); return CreativeIslandItems( items, categories, @@ -719,8 +701,7 @@ class APIServer { } /// 创作岛生成消耗量预估 - Future creativeIslandCompletionsEvaluate( - String id, Map params) async { + Future creativeIslandCompletionsEvaluate(String id, Map params) async { return sendPostRequest( '/v1/creative-island/completions/$id/evaluate', (resp) => QuotaEvaluated.fromJson(resp.data), @@ -729,8 +710,7 @@ class APIServer { } /// 创意岛项目生成数据 - Future> creativeIslandCompletions( - String id, Map params) async { + Future> creativeIslandCompletions(String id, Map params) async { return sendPostRequest( '/v1/creative-island/completions/$id', (resp) { @@ -747,8 +727,7 @@ class APIServer { } /// 创意岛项目生成数据 - Future creativeIslandCompletionsAsync( - String id, Map params) async { + Future creativeIslandCompletionsAsync(String id, Map params) async { params["mode"] = 'async'; return sendPostRequest( @@ -761,8 +740,7 @@ class APIServer { ); } - Future creativeIslandCompletionsEvaluateV2( - Map params) async { + Future creativeIslandCompletionsEvaluateV2(Map params) async { return sendPostRequest( '/v2/creative-island/completions/evaluate', (resp) => QuotaEvaluated.fromJson(resp.data), @@ -770,8 +748,7 @@ class APIServer { ); } - Future creativeIslandCompletionsAsyncV2( - Map params) async { + Future creativeIslandCompletionsAsyncV2(Map params) async { return sendPostRequest( '/v2/creative-island/completions', (resp) { @@ -782,8 +759,7 @@ class APIServer { ); } - Future creativeIslandArtisticTextCompletionsAsyncV2( - Map params) async { + Future creativeIslandArtisticTextCompletionsAsyncV2(Map params) async { return sendPostRequest( '/v2/creative-island/completions/artistic-text', (resp) { @@ -794,8 +770,7 @@ class APIServer { ); } - Future creativeIslandImageToVideoCompletionsAsyncV2( - Map params) async { + Future creativeIslandImageToVideoCompletionsAsyncV2(Map params) async { return sendPostRequest( '/v2/creative-island/completions/image-to-video', (resp) { @@ -836,8 +811,7 @@ class APIServer { } /// 创作岛能力 - Future creativeIslandCapacity( - {required String mode, required String id}) async { + Future creativeIslandCapacity({required String mode, required String id}) async { return sendCachedGetRequest( '/v2/creative-island/capacity', (resp) { @@ -1063,8 +1037,7 @@ class APIServer { } /// 发起支付 - Future createOtherPay(String productId, - {required String source}) async { + Future createOtherPay(String productId, {required String source}) async { return sendPostRequest( '/v1/payment/others', (resp) => OtherPayCreatedReponse.fromJson(resp.data), @@ -1411,8 +1384,7 @@ class APIServer { } /// 创作岛历史记录 - Future> creativeItemHistories(String islandId, - {bool cache = true}) async { + Future> creativeItemHistories(String islandId, {bool cache = true}) async { return sendCachedGetRequest( '/v1/creative-island/items/$islandId/histories', (resp) { @@ -1444,8 +1416,7 @@ class APIServer { } /// 删除创作岛项目历史记录 - Future deleteCreativeHistoryItem(String islandId, - {required hisId}) async { + Future deleteCreativeHistoryItem(String islandId, {required hisId}) async { return sendDeleteRequest( '/v1/creative-island/items/$islandId/histories/$hisId', (resp) {}, @@ -1471,8 +1442,7 @@ class APIServer { } /// 获取用户智慧果消耗历史记录详情 - Future> quotaUsedDetails( - {required String date}) async { + Future> quotaUsedDetails({required String date}) async { return sendGetRequest( '/v1/users/quota/usage-stat/$date', (resp) { @@ -1533,9 +1503,7 @@ class APIServer { return sendPostRequest( '/v1/voice/text2voice', formData: {'text': text}, - (resp) => (resp.data['results'] as List) - .map((e) => e.toString()) - .toList(), + (resp) => (resp.data['results'] as List).map((e) => e.toString()).toList(), ); } @@ -1574,8 +1542,7 @@ class APIServer { ); } - Future roomGalleryItem( - {required int id, bool cache = true}) async { + Future roomGalleryItem({required int id, bool cache = true}) async { return sendCachedGetRequest( '/v1/room-galleries/$id', (resp) => RoomGallery.fromJson(resp.data), @@ -1592,8 +1559,7 @@ class APIServer { ); } - Future> creativeIslandItemsV2( - {bool cache = true}) async { + Future> creativeIslandItemsV2({bool cache = true}) async { return sendCachedGetRequest( '/v2/creative/items', (resp) { @@ -1686,8 +1652,7 @@ class APIServer { } /// 用户免费聊天次数统计(单个模型) - Future userFreeStatisticsForModel( - {required String model}) async { + Future userFreeStatisticsForModel({required String model}) async { return sendGetRequest( '/v1/users/stat/free-chat-counts/${Uri.encodeComponent(model)}', (resp) => FreeModelCount.fromJson(resp.data), @@ -1695,8 +1660,7 @@ class APIServer { } /// 通知信息(促销事件) - Future>> notificationPromotionEvents( - {bool cache = true}) async { + Future>> notificationPromotionEvents({bool cache = true}) async { return sendCachedGetRequest( '/v1/notifications/promotions', (value) { @@ -1830,8 +1794,7 @@ class APIServer { } /// 发起群聊消息 - Future chatGroupSendMessage( - int groupId, GroupChatSendRequest req) async { + Future chatGroupSendMessage(int groupId, GroupChatSendRequest req) async { return sendPostJSONRequest( '/v1/group-chat/$groupId/chat', (resp) { @@ -1858,8 +1821,7 @@ class APIServer { } /// 群组聊天消息状态 - Future> chatGroupMessageStatus( - int groupId, List messageIds) async { + Future> chatGroupMessageStatus(int groupId, List messageIds) async { return sendGetRequest( '/v1/group-chat/$groupId/chat-messages', (resp) { @@ -1883,17 +1845,14 @@ class APIServer { /// 删除群组聊天消息 Future chatGroupDeleteMessage(int groupId, int messageId) async { - return sendDeleteRequest( - '/v1/group-chat/$groupId/chat/$messageId', (resp) {}); + return sendDeleteRequest('/v1/group-chat/$groupId/chat/$messageId', (resp) {}); } /// API 模式 //////////////////////////////////////////////////////////////////// /// 查询用户所有的 API Keys Future> userAPIKeys() async { return sendGetRequest('/v1/api-keys', (data) { - return ((data.data['data'] ?? []) as List) - .map((e) => UserAPIKey.fromJson(e)) - .toList(); + return ((data.data['data'] ?? []) as List).map((e) => UserAPIKey.fromJson(e)).toList(); }); } @@ -2021,8 +1980,7 @@ class APIServer { }); final channelTypes = await adminChannelTypes(); - channels.addAll( - channelTypes.map((e) => AdminChannel(name: e.text, type: e.name))); + channels.addAll(channelTypes.map((e) => AdminChannel(name: e.text, type: e.name))); return channels; } @@ -2056,8 +2014,7 @@ class APIServer { } /// 管理员接口:更新渠道 - Future adminUpdateChannel( - {required int id, required AdminChannelUpdateReq req}) { + Future adminUpdateChannel({required int id, required AdminChannelUpdateReq req}) { return sendPutJSONRequest( '/v1/admin/channels/$id', (resp) {}, @@ -2090,8 +2047,7 @@ class APIServer { /// 管理员接口:返回指定模型 Future adminModel({required String modelId}) async { - return sendGetRequest('/v1/admin/models/${Uri.encodeComponent(modelId)}', - (resp) { + return sendGetRequest('/v1/admin/models/${Uri.encodeComponent(modelId)}', (resp) { return AdminModel.fromJson(resp.data['data']); }); } @@ -2106,8 +2062,7 @@ class APIServer { } /// 管理员接口:更新模型 - Future adminUpdateModel( - {required String modelId, required AdminModelUpdateReq req}) { + Future adminUpdateModel({required String modelId, required AdminModelUpdateReq req}) { return sendPutJSONRequest( '/v1/admin/models/${Uri.encodeComponent(modelId)}', (resp) {}, @@ -2117,8 +2072,7 @@ class APIServer { /// 管理员接口:删除模型 Future adminDeleteModel({required String modelId}) { - return sendDeleteRequest( - '/v1/admin/models/${Uri.encodeComponent(modelId)}', (resp) {}); + return sendDeleteRequest('/v1/admin/models/${Uri.encodeComponent(modelId)}', (resp) {}); } /// 管理员接口:查询用户列表 @@ -2237,18 +2191,15 @@ class APIServer { } /// 管理员接口:查询用户指定的数字人 - Future adminUserRoom( - {required int userId, required int roomId}) async { + Future adminUserRoom({required int userId, required int roomId}) async { return sendGetRequest('/v1/admin/messages/$userId/rooms/$roomId', (resp) { return RoomInServer.fromJson(resp.data['data']); }); } /// 管理员接口:查询用户指定数字人最近聊天历史记录 - Future> adminUserRoomMessages( - {required int userId, required int roomId}) async { - return sendGetRequest('/v1/admin/messages/$userId/rooms/$roomId/messages', - (resp) { + Future> adminUserRoomMessages({required int userId, required int roomId}) async { + return sendGetRequest('/v1/admin/messages/$userId/rooms/$roomId/messages', (resp) { var res = []; for (var item in resp.data['data']) { res.add(MessageInServer.fromJson(item)); diff --git a/lib/repo/model/misc.dart b/lib/repo/model/misc.dart index bcace658..01cad102 100644 --- a/lib/repo/model/misc.dart +++ b/lib/repo/model/misc.dart @@ -597,6 +597,7 @@ class Model { String name; String shortName; String? description; + String? priceInfo; bool isChat; bool isImage; bool disabled; @@ -622,6 +623,7 @@ class Model { required this.isChat, required this.isImage, this.description, + this.priceInfo, this.disabled = false, this.tag, this.avatarUrl, @@ -637,6 +639,7 @@ class Model { 'name': name, 'short_name': shortName, 'description': description, + 'price_info': priceInfo, 'category': category, 'is_chat': isChat, 'is_image': isImage, @@ -656,6 +659,7 @@ class Model { name: json['name'], shortName: json['short_name'] ?? json['name'], description: json['description'], + priceInfo: json['price_info'], category: json['category'], isChat: json['is_chat'], isImage: json['is_image'], diff --git a/lib/repo/model/model.dart b/lib/repo/model/model.dart index 4f23d541..b0d7d969 100644 --- a/lib/repo/model/model.dart +++ b/lib/repo/model/model.dart @@ -1,9 +1,12 @@ +import 'dart:convert'; + class Model { final String id; final String name; final String? shortName; final String ownedBy; String? description; + String? priceInfo; bool isChatModel = false; bool disabled; String? avatarUrl; @@ -24,6 +27,7 @@ class Model { this.shortName, required this.category, this.description, + this.priceInfo, this.isChatModel = false, this.disabled = false, this.tag, @@ -45,6 +49,7 @@ class Model { String? shortName, String? ownedBy, String? description, + String? priceInfo, bool? isChatModel, bool? disabled, String? avatarUrl, @@ -62,6 +67,7 @@ class Model { ownedBy ?? this.ownedBy, shortName: shortName ?? this.shortName, description: description ?? this.description, + priceInfo: priceInfo ?? this.priceInfo, isChatModel: isChatModel ?? this.isChatModel, disabled: disabled ?? this.disabled, avatarUrl: avatarUrl ?? this.avatarUrl, @@ -74,4 +80,37 @@ class Model { isDefault: isDefault ?? false, ); } + + ModelPrice get modelPrice { + if (priceInfo == null || priceInfo == '') { + return ModelPrice(input: 0, output: 0, note: ''); + } + + return ModelPrice.fromMap(jsonDecode(priceInfo!) as Map); + } +} + +class ModelPrice { + final int input; + final int output; + final String note; + + bool get isFree { + return input == output && input == 0; + } + + bool get hasNote { + return note != ''; + } + + ModelPrice({ + required this.input, + required this.output, + required this.note, + }); + + ModelPrice.fromMap(Map map) + : input = map['input'] ?? 0, + output = map['output'] ?? 0, + note = map['note'] ?? ''; } diff --git a/macos/Podfile.lock b/macos/Podfile.lock index b8b33157..9efc2f55 100644 --- a/macos/Podfile.lock +++ b/macos/Podfile.lock @@ -3,7 +3,7 @@ PODS: - FlutterMacOS - file_saver (0.0.1): - FlutterMacOS - - flutter_local_notifications (0.0.1): + - flutter_image_compress_macos (1.0.0): - FlutterMacOS - flutter_localization (0.0.1): - FlutterMacOS @@ -47,7 +47,7 @@ PODS: DEPENDENCIES: - audioplayers_darwin (from `Flutter/ephemeral/.symlinks/plugins/audioplayers_darwin/macos`) - file_saver (from `Flutter/ephemeral/.symlinks/plugins/file_saver/macos`) - - flutter_local_notifications (from `Flutter/ephemeral/.symlinks/plugins/flutter_local_notifications/macos`) + - flutter_image_compress_macos (from `Flutter/ephemeral/.symlinks/plugins/flutter_image_compress_macos/macos`) - flutter_localization (from `Flutter/ephemeral/.symlinks/plugins/flutter_localization/macos`) - flutter_tts (from `Flutter/ephemeral/.symlinks/plugins/flutter_tts/macos`) - FlutterMacOS (from `Flutter/ephemeral`) @@ -71,8 +71,8 @@ EXTERNAL SOURCES: :path: Flutter/ephemeral/.symlinks/plugins/audioplayers_darwin/macos file_saver: :path: Flutter/ephemeral/.symlinks/plugins/file_saver/macos - flutter_local_notifications: - :path: Flutter/ephemeral/.symlinks/plugins/flutter_local_notifications/macos + flutter_image_compress_macos: + :path: Flutter/ephemeral/.symlinks/plugins/flutter_image_compress_macos/macos flutter_localization: :path: Flutter/ephemeral/.symlinks/plugins/flutter_localization/macos flutter_tts: @@ -111,7 +111,7 @@ EXTERNAL SOURCES: SPEC CHECKSUMS: audioplayers_darwin: dcad41de4fbd0099cb3749f7ab3b0cb8f70b810c file_saver: 44e6fbf666677faf097302460e214e977fdd977b - flutter_local_notifications: 3805ca215b2fb7f397d78b66db91f6a747af52e4 + flutter_image_compress_macos: c26c3c13ea0f28ae6dea4e139b3292e7729f99f1 flutter_localization: 4035848ae2ed142875d5fd3dde328250a5e81f42 flutter_tts: 64651204e5d276ffea5a910f942d5e9785a96085 FlutterMacOS: 8f6f14fa908a6fb3fba0cd85dbd81ec4b251fb24 diff --git a/macos/Runner/AppDelegate.swift b/macos/Runner/AppDelegate.swift index d53ef643..8e02df28 100644 --- a/macos/Runner/AppDelegate.swift +++ b/macos/Runner/AppDelegate.swift @@ -1,7 +1,7 @@ import Cocoa import FlutterMacOS -@NSApplicationMain +@main class AppDelegate: FlutterAppDelegate { override func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool { return true diff --git a/pubspec.yaml b/pubspec.yaml index dde8f163..95dff37d 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -17,7 +17,7 @@ publish_to: 'none' # Remove this line if you wish to publish to pub.dev # In Windows, build-name is used as the major, minor, and patch parts # of the product and file versions while build-number is used as the build suffix. # 应用正式发布时,需要同步修改 lib/helper/constant.dart 中的 VERSION 值 -version: 1.0.15 +version: 2.0.0 environment: sdk: '>=3.0.0 <4.0.0' From d99439cff91120c4dfe43583f486b5e60587bb15 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=AE=A1=E5=AE=9C=E5=B0=A7?= Date: Tue, 10 Dec 2024 17:34:09 +0800 Subject: [PATCH 17/26] update --- ios/Podfile.lock | 10 +- ios/Runner.xcodeproj/project.pbxproj | 6 -- lib/bloc/chat_chat_bloc.dart | 8 +- lib/lang/lang.dart | 6 ++ lib/page/chat/room_create.dart | 5 +- lib/page/component/account_quota_card.dart | 102 ++++++++++----------- lib/page/component/chat/chat_preview.dart | 48 ++++++---- lib/page/drawer.dart | 88 ++++++++++++------ 8 files changed, 155 insertions(+), 118 deletions(-) diff --git a/ios/Podfile.lock b/ios/Podfile.lock index 7377389c..cf509552 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -43,7 +43,7 @@ PODS: - file_saver (0.0.1): - Flutter - Flutter (1.0.0) - - flutter_image_compress (1.0.0): + - flutter_image_compress_common (1.0.0): - Flutter - Mantle - SDWebImage @@ -164,7 +164,7 @@ DEPENDENCIES: - file_picker (from `.symlinks/plugins/file_picker/ios`) - file_saver (from `.symlinks/plugins/file_saver/ios`) - Flutter (from `Flutter`) - - flutter_image_compress (from `.symlinks/plugins/flutter_image_compress/ios`) + - flutter_image_compress_common (from `.symlinks/plugins/flutter_image_compress_common/ios`) - flutter_localization (from `.symlinks/plugins/flutter_localization/ios`) - flutter_native_splash (from `.symlinks/plugins/flutter_native_splash/ios`) - flutter_tts (from `.symlinks/plugins/flutter_tts/ios`) @@ -221,8 +221,8 @@ EXTERNAL SOURCES: :path: ".symlinks/plugins/file_saver/ios" Flutter: :path: Flutter - flutter_image_compress: - :path: ".symlinks/plugins/flutter_image_compress/ios" + flutter_image_compress_common: + :path: ".symlinks/plugins/flutter_image_compress_common/ios" flutter_localization: :path: ".symlinks/plugins/flutter_localization/ios" flutter_native_splash: @@ -277,7 +277,7 @@ SPEC CHECKSUMS: file_picker: ce3938a0df3cc1ef404671531facef740d03f920 file_saver: 503e386464dbe118f630e17b4c2e1190fa0cf808 Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7 - flutter_image_compress: 5a5e9aee05b6553048b8df1c3bc456d0afaac433 + flutter_image_compress_common: ec1d45c362c9d30a3f6a0426c297f47c52007e3e flutter_localization: f43b18844a2b3d2c71fd64f04ffd6b1e64dd54d4 flutter_native_splash: edf599c81f74d093a4daf8e17bd7a018854bc778 flutter_tts: 0f492aab6accf87059b72354fcb4ba934304771d diff --git a/ios/Runner.xcodeproj/project.pbxproj b/ios/Runner.xcodeproj/project.pbxproj index 15ab8d37..9a2b4f09 100644 --- a/ios/Runner.xcodeproj/project.pbxproj +++ b/ios/Runner.xcodeproj/project.pbxproj @@ -451,8 +451,6 @@ "-framework", "\"file_saver\"", "-framework", - "\"flutter_image_compress\"", - "-framework", "\"flutter_localization\"", "-framework", "\"flutter_native_splash\"", @@ -675,8 +673,6 @@ "-framework", "\"file_saver\"", "-framework", - "\"flutter_image_compress\"", - "-framework", "\"flutter_localization\"", "-framework", "\"flutter_native_splash\"", @@ -793,8 +789,6 @@ "-framework", "\"file_saver\"", "-framework", - "\"flutter_image_compress\"", - "-framework", "\"flutter_localization\"", "-framework", "\"flutter_native_splash\"", diff --git a/lib/bloc/chat_chat_bloc.dart b/lib/bloc/chat_chat_bloc.dart index c21f95b3..517bc996 100644 --- a/lib/bloc/chat_chat_bloc.dart +++ b/lib/bloc/chat_chat_bloc.dart @@ -16,17 +16,13 @@ class ChatChatBloc extends Bloc { on((event, emit) async { final histories = await _chatMessageRepository.recentChatHistories( chatAnywhereRoomId, - 4, + 3, userId: APIServer().localUserID(), ); - var examples = await APIServer().example('openai:$defaultChatModel'); - // examples 随机排序 - examples.shuffle(); - emit(ChatChatRecentHistoriesLoaded( histories: histories, - examples: examples, + examples: const [], )); }); diff --git a/lib/lang/lang.dart b/lib/lang/lang.dart index eeeef3dc..54a8c662 100644 --- a/lib/lang/lang.dart +++ b/lib/lang/lang.dart @@ -43,6 +43,7 @@ mixin AppLocale { static const String share = 'share'; static const String cancelShare = 'cancel-share'; static const String histories = 'histories'; + static const String moreHistories = 'more-histories'; static const String enable = 'enable'; static const String disable = 'disable'; @@ -306,6 +307,7 @@ mixin AppLocale { static const String free = 'free'; static const String input = 'input'; static const String output = 'output'; + static const String info = 'info'; static const Map zh = { required: '必填', @@ -324,6 +326,7 @@ mixin AppLocale { share: '分享', cancelShare: '取消分享', histories: '最近的对话', + moreHistories: '更多历史对话', enable: '启用', disable: '未启用', newChat: '新对话', @@ -598,6 +601,7 @@ mixin AppLocale { free: '限免', input: '输入', output: '输出', + info: '详情', }; static const Map en = { @@ -617,6 +621,7 @@ mixin AppLocale { share: 'Share', cancelShare: 'Cancel share', histories: 'Recents', + moreHistories: 'More Histories', enable: 'Enable', disable: 'Disable', newChat: 'New Chat', @@ -895,6 +900,7 @@ mixin AppLocale { free: 'Free', input: 'Input', output: 'Output', + info: 'Detail', }; } diff --git a/lib/page/chat/room_create.dart b/lib/page/chat/room_create.dart index dec2e897..d50c1c90 100644 --- a/lib/page/chat/room_create.dart +++ b/lib/page/chat/room_create.dart @@ -606,7 +606,7 @@ void openSelectModelDialog( bool withCustom = false, }) { future() async { - final models = await ModelAggregate.models(cache: false, withCustom: withCustom); + final models = await ModelAggregate.models(cache: true, withCustom: withCustom); if (priorityModelId != null) { // 将 models 中,id 与 priorityModelId 相同的元素排序到最前面 @@ -620,6 +620,9 @@ void openSelectModelDialog( } } + // 再请求一次,用于异步更新 Cache,下次打开时将显示最新数据 + ModelAggregate.models(cache: false, withCustom: withCustom); + return models; } diff --git a/lib/page/component/account_quota_card.dart b/lib/page/component/account_quota_card.dart index f516b20a..11d01561 100644 --- a/lib/page/component/account_quota_card.dart +++ b/lib/page/component/account_quota_card.dart @@ -21,50 +21,51 @@ class AccountQuotaCard extends StatelessWidget { return Container( margin: noBorder ? null : const EdgeInsets.symmetric(horizontal: 15, vertical: 10), - height: 140, + height: 120, child: Container( padding: noBorder ? const EdgeInsets.only( top: 5, left: 20, right: 20, + bottom: 0, ) : const EdgeInsets.symmetric( horizontal: 20, vertical: 30, ), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, + child: Column( + crossAxisAlignment: CrossAxisAlignment.end, children: [ - Column( - crossAxisAlignment: CrossAxisAlignment.start, + Row( + crossAxisAlignment: CrossAxisAlignment.center, children: [ - Row( - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - Text( - AppLocale.usage.getString(context), - style: const TextStyle( - fontSize: 22, - color: Colors.white, - ), - ), - const SizedBox(width: 5), - InkWell( - onTap: () { - launchUrl( - Uri.parse('https://ai.aicode.cc/zhihuiguo.html'), - ); - }, - child: const Icon( - Icons.help, - size: 16, - color: Color.fromARGB(129, 220, 220, 220), - ), - ) - ], + Text( + AppLocale.usage.getString(context), + style: const TextStyle( + fontSize: 22, + color: Colors.white, + ), + ), + const SizedBox(width: 5), + InkWell( + onTap: () { + launchUrl( + Uri.parse('https://ai.aicode.cc/zhihuiguo.html'), + ); + }, + child: const Icon( + Icons.help, + size: 16, + color: Color.fromARGB(129, 220, 220, 220), + ), ), - const SizedBox(height: 15), + ], + ), + const SizedBox(height: 15), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ Row( crossAxisAlignment: CrossAxisAlignment.end, children: [ @@ -89,33 +90,24 @@ class AccountQuotaCard extends StatelessWidget { ), ), ], - ) + ), + if (Ability().enablePayment) + EnhancedButton( + onPressed: () { + context.push('/payment').whenComplete(() { + if (onPaymentReturn != null) { + onPaymentReturn!(); + } + }); + }, + title: AppLocale.buy.getString(context), + backgroundColor: customColors.linkColor, + width: 70, + height: 35, + fontSize: 14, + ), ], ), - if (Ability().enablePayment) - EnhancedButton( - onPressed: () { - // if (PlatformTool.isWeb() || PlatformTool.isMacOS()) { - // showBeautyDialog( - // context, - // type: QuickAlertType.info, - // text: 'Web、桌面端购买功能暂未推出,敬请期待', - // ); - // return; - // } - - context.push('/payment').whenComplete(() { - if (onPaymentReturn != null) { - onPaymentReturn!(); - } - }); - }, - title: AppLocale.buy.getString(context), - backgroundColor: customColors.linkColor, - width: 70, - height: 35, - fontSize: 14, - ), ], ), ), diff --git a/lib/page/component/chat/chat_preview.dart b/lib/page/component/chat/chat_preview.dart index da909615..88486dbc 100644 --- a/lib/page/component/chat/chat_preview.dart +++ b/lib/page/component/chat/chat_preview.dart @@ -366,22 +366,6 @@ class _ChatPreviewState extends State { color: customColors.chatRoomSenderText, ), ), - if (message.quotaConsumed != null && message.quotaConsumed! > 0) - Row( - children: [ - const Icon(Icons.check_circle, size: 12, color: Colors.green), - const SizedBox(width: 5), - Expanded( - child: Text( - '共 ${message.tokenConsumed} 个 Token, 消耗 ${message.quotaConsumed} 个智慧果', - style: TextStyle( - fontSize: 14, - color: customColors.weakTextColor, - ), - ), - ), - ], - ), ], ); }, @@ -811,7 +795,7 @@ class _ChatPreviewState extends State { mainAxisSize: MainAxisSize.min, children: [ const Icon( - Icons.delete, + Icons.delete_outline, color: Color.fromARGB(255, 255, 255, 255), size: 14, ), @@ -833,7 +817,7 @@ class _ChatPreviewState extends State { mainAxisSize: MainAxisSize.min, children: [ const Icon( - Icons.record_voice_over, + Icons.record_voice_over_outlined, color: Color.fromARGB(255, 255, 255, 255), size: 14, ), @@ -866,6 +850,34 @@ class _ChatPreviewState extends State { ], ), ), + if (message.quotaConsumed != null && message.quotaConsumed! > 0) + TextButton.icon( + onPressed: () { + showBeautyDialog( + context, + type: QuickAlertType.info, + text: '本轮对话共 ${message.tokenConsumed} 个 Token, 消耗 ${message.quotaConsumed} 个智慧果。', + confirmBtnText: AppLocale.gotIt.getString(context), + showCancelBtn: false, + ); + cancel(); + }, + label: const Text(''), + icon: Column( + mainAxisSize: MainAxisSize.min, + children: [ + const Icon( + Icons.info_outline, + color: Color.fromARGB(255, 255, 255, 255), + size: 14, + ), + Text( + AppLocale.info.getString(context), + style: const TextStyle(fontSize: 12, color: Colors.white), + ), + ], + ), + ) ], ), ); diff --git a/lib/page/drawer.dart b/lib/page/drawer.dart index efa3c2f8..bbd3d106 100644 --- a/lib/page/drawer.dart +++ b/lib/page/drawer.dart @@ -25,6 +25,7 @@ class _LeftDrawerState extends State { super.initState(); context.read().add(AccountLoadEvent(cache: false)); + context.read().add(ChatChatLoadRecentHistories()); } @override @@ -43,41 +44,74 @@ class _LeftDrawerState extends State { child: SingleChildScrollView( child: Column( children: [ - DrawerHeader( - padding: - PlatformTool.isMacOS() ? const EdgeInsets.only(top: kToolbarHeight) : const EdgeInsets.all(0), - decoration: BoxDecoration( - color: Colors.white, - image: DecorationImage( - image: CachedNetworkImageProviderEnhanced( - "https://ssl.aicode.cc/ai-server/assets/quota-card-bg.webp-thumb1000", + SizedBox( + height: 170, + child: DrawerHeader( + padding: PlatformTool.isMacOS() + ? const EdgeInsets.only(top: kToolbarHeight) + : const EdgeInsets.all(0), + margin: const EdgeInsets.all(0), + decoration: BoxDecoration( + color: Colors.white, + image: DecorationImage( + image: CachedNetworkImageProviderEnhanced( + "https://ssl.aicode.cc/ai-server/assets/quota-card-bg.webp-thumb1000", + ), + fit: BoxFit.cover, ), - fit: BoxFit.cover, ), - ), - child: BlocBuilder( - builder: (_, state) { - UserInfo? userInfo; - if (state is AccountLoaded) { - userInfo = state.user; - } + child: BlocBuilder( + builder: (_, state) { + UserInfo? userInfo; + if (state is AccountLoaded) { + userInfo = state.user; + } - return AccountQuotaCard( - userInfo: userInfo, - noBorder: true, - onPaymentReturn: () { - if (userInfo != null) { - context.read().add(AccountLoadEvent(cache: false)); - } - }, - ); - }, + return AccountQuotaCard( + userInfo: userInfo, + noBorder: true, + onPaymentReturn: () { + if (userInfo != null) { + context.read().add(AccountLoadEvent(cache: false)); + } + }, + ); + }, + ), ), ), const SizedBox(height: 15), + BlocBuilder( + buildWhen: (previous, current) => current is ChatChatRecentHistoriesLoaded, + builder: (_, state) { + if (state is ChatChatRecentHistoriesLoaded) { + return ListView.builder( + shrinkWrap: true, + itemCount: state.histories.length, + itemBuilder: (context, index) { + final item = state.histories[index]; + return ListTile( + leading: const Icon(Icons.question_answer_outlined), + title: Text( + item.title ?? 'Unknown', + maxLines: 1, + overflow: TextOverflow.ellipsis, + ), + onTap: () { + context.push( + '/chat-anywhere?chat_id=${item.id}&model=${item.model}&title=${item.title}'); + }, + ); + }, + ); + } + + return const SizedBox(); + }, + ), ListTile( leading: const Icon(Icons.history), - title: Text(AppLocale.histories.getString(context)), + title: Text(AppLocale.moreHistories.getString(context)), onTap: () { context.push('/chat-chat/history').whenComplete(() { context.read().add(ChatChatLoadRecentHistories()); From 47a527eee15e7640163e8546931753c9e5469262 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=AE=A1=E5=AE=9C=E5=B0=A7?= Date: Tue, 10 Dec 2024 17:57:40 +0800 Subject: [PATCH 18/26] update --- lib/page/drawer.dart | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/page/drawer.dart b/lib/page/drawer.dart index bbd3d106..c6dd5fe5 100644 --- a/lib/page/drawer.dart +++ b/lib/page/drawer.dart @@ -87,6 +87,8 @@ class _LeftDrawerState extends State { if (state is ChatChatRecentHistoriesLoaded) { return ListView.builder( shrinkWrap: true, + padding: const EdgeInsets.all(0), + physics: const NeverScrollableScrollPhysics(), itemCount: state.histories.length, itemBuilder: (context, index) { final item = state.histories[index]; From 14ddaf2ed00ad69be92560c99730512f6ac7620d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=AE=A1=E5=AE=9C=E5=B0=A7?= Date: Wed, 11 Dec 2024 14:54:45 +0800 Subject: [PATCH 19/26] update --- lib/helper/color.dart | 12 ++- lib/page/chat/component/model_switcher.dart | 1 - lib/page/chat/room_create.dart | 2 - lib/page/component/model_item.dart | 102 +++++++++++--------- 4 files changed, 63 insertions(+), 54 deletions(-) diff --git a/lib/helper/color.dart b/lib/helper/color.dart index af76a998..32510046 100644 --- a/lib/helper/color.dart +++ b/lib/helper/color.dart @@ -1,19 +1,23 @@ import 'package:flutter/material.dart'; /// 将颜色转换为字符串 -String colorToString(Color color) { +String colorToString(Color color, {String defaultColor = 'FF000000'}) { try { return color.toString().split('(0x')[1].split(')')[0]; } catch (e) { - return '000000'; + return defaultColor; } } /// 将字符串转换为颜色 -Color stringToColor(String colorString) { +Color stringToColor(String colorString, {Color defaultColor = Colors.black}) { try { + if (colorString.length == 6) { + colorString = 'FF$colorString'; + } + return Color(int.parse(colorString, radix: 16)); } catch (e) { - return Colors.black; + return defaultColor; } } diff --git a/lib/page/chat/component/model_switcher.dart b/lib/page/chat/component/model_switcher.dart index e7e27be7..baead961 100644 --- a/lib/page/chat/component/model_switcher.dart +++ b/lib/page/chat/component/model_switcher.dart @@ -30,7 +30,6 @@ class ModelSwitcher extends StatelessWidget { onSelected(selected); }, initValue: initValue?.uid(), - enableClear: true, title: AppLocale.switchModelTitle.getString(context), withCustom: true, ); diff --git a/lib/page/chat/room_create.dart b/lib/page/chat/room_create.dart index d50c1c90..f9f8b745 100644 --- a/lib/page/chat/room_create.dart +++ b/lib/page/chat/room_create.dart @@ -600,7 +600,6 @@ void openSelectModelDialog( Function(mm.Model? selected) onSelected, { String? initValue, List? reservedModels, - bool enableClear = false, String? title, String? priorityModelId, bool withCustom = false, @@ -649,7 +648,6 @@ void openSelectModelDialog( context.pop(); }, initValue: initValue, - enableClear: enableClear, ); }); }, diff --git a/lib/page/component/model_item.dart b/lib/page/component/model_item.dart index 664d7d36..525c8426 100644 --- a/lib/page/component/model_item.dart +++ b/lib/page/component/model_item.dart @@ -7,7 +7,6 @@ import 'package:askaide/page/component/dialog.dart'; import 'package:askaide/page/component/random_avatar.dart'; import 'package:askaide/page/component/theme/custom_size.dart'; import 'package:askaide/page/component/theme/custom_theme.dart'; -import 'package:askaide/page/component/weak_text_button.dart'; import 'package:askaide/repo/model/model.dart'; import 'package:flutter/material.dart'; import 'package:flutter_localization/flutter_localization.dart'; @@ -17,7 +16,6 @@ class ModelItem extends StatefulWidget { final List models; final Function(Model? selected) onSelected; final String? initValue; - final bool enableClear; final bool showUsing; const ModelItem({ @@ -25,7 +23,6 @@ class ModelItem extends StatefulWidget { required this.models, required this.onSelected, this.initValue, - this.enableClear = false, this.showUsing = false, }); @@ -39,15 +36,6 @@ class _ModelItemState extends State { @override Widget build(BuildContext context) { final customColors = Theme.of(context).extension()!; - - if (widget.enableClear && widget.initValue != null && widget.showUsing) { - // 将当前选中的模型放在第一位 - var index = widget.models.indexWhere((e) => e.uid() == widget.initValue || e.id == widget.initValue); - if (index != -1) { - widget.models.insert(0, widget.models[index].copyWith(category: AppLocale.using.getString(context))); - } - } - return widget.models.isNotEmpty ? Column( children: [ @@ -95,40 +83,37 @@ class _ModelItemState extends State { tags.add(buildTag( customColors, AppLocale.free.getString(context), - tagTextColor: colorToString(Colors.white), - tagBgColor: colorToString(customColors.markdownLinkColor!), + tagTextColor: 'FFFFFFFF', + tagBgColor: '2196F3', )); } if (item.tag != null) { - item.tag!.split(",").forEach((tag) { - if (tag.isEmpty) return; - + var tt = item.tag!.split(",").where((e) => e.isNotEmpty).toList(); + for (var i = 0; i < tt.length; i++) { tags.add(buildTag( customColors, - tag, - tagTextColor: item.tagTextColor, - tagBgColor: item.tagBgColor, + tt[i], + tagTextColor: i == 0 ? item.tagTextColor : 'FFFFFFFF', + tagBgColor: i == 0 ? item.tagBgColor : modelTagColorSeq(i), )); - }); + } } if (item.supportVision) { tags.add(buildTag( customColors, AppLocale.visionTag.getString(context), - tagTextColor: colorToString(Colors.white), - tagBgColor: colorToString( - customColors.linkColor ?? Colors.green, - ), + tagTextColor: 'FFFFFFFF', + tagBgColor: '4CAF50', )); } - if (item.isNew && widget.initValue != item.uid()) { + if (item.isNew) { tags.add(buildTag( customColors, AppLocale.newTag.getString(context), - tagTextColor: colorToString(Colors.white), - tagBgColor: colorToString(Colors.red), + tagTextColor: 'FFFFFFFF', + tagBgColor: 'F44336', )); } @@ -184,25 +169,19 @@ class _ModelItemState extends State { ), ), ), - ...tags, - if (item.avatarUrl != null) ...[ - if (widget.enableClear && i == 0 && widget.showUsing) - SizedBox( - width: 60, - child: widget.initValue == item.uid() - ? WeakTextButton( - title: AppLocale.cancel.getString(context), - fontSize: 10, - onPressed: () { - widget.onSelected(null); - }, - ) - : const SizedBox(), - ), - ], + if (tags.length <= 3) ...formatTags(tags), ], ), + if (tags.length > 3) + SingleChildScrollView( + scrollDirection: Axis.horizontal, + child: Container( + margin: const EdgeInsets.symmetric(vertical: 5), + child: Row(children: formatTags(tags)), + ), + ), if (!modelPrice.isFree) buildPriceBlock(customColors, item, modelPrice), + // if (item.description != null && item.description != '') // Text( // item.description!, @@ -341,10 +320,11 @@ class _ModelItemState extends State { }) { return Container( decoration: BoxDecoration( - color: tagBgColor != null ? stringToColor(tagBgColor) : customColors.tagsBackgroundHover, + color: tagBgColor != null + ? stringToColor(tagBgColor, defaultColor: customColors.tagsBackgroundHover ?? Colors.grey) + : customColors.tagsBackgroundHover, borderRadius: CustomSize.borderRadius, ), - margin: const EdgeInsets.only(left: 5), padding: const EdgeInsets.symmetric( horizontal: 5, vertical: 2, @@ -353,9 +333,37 @@ class _ModelItemState extends State { tag, style: TextStyle( fontSize: 8, - color: tagTextColor != null ? stringToColor(tagTextColor) : customColors.tagsText, + color: tagTextColor != null + ? stringToColor(tagTextColor, defaultColor: customColors.tagsText ?? Colors.white) + : customColors.tagsText, ), ), ); } } + +String modelTagColorSeq(int index) { + var colors = { + Colors.grey, + Colors.purple, + Colors.orange, + Colors.pink, + Colors.deepPurple, + Colors.indigo, + Colors.cyan, + }; + return colorToString(colors.elementAt(index % colors.length)); +} + +List formatTags(List tags) { + var widgets = []; + + for (var i = 0; i < tags.length; i++) { + widgets.add(tags[i]); + if (i < tags.length - 1) { + widgets.add(const SizedBox(width: 5)); + } + } + + return widgets; +} From 467d5d60744008981451aa0955de0e1a6a7270ef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=AE=A1=E5=AE=9C=E5=B0=A7?= Date: Wed, 11 Dec 2024 22:21:44 +0800 Subject: [PATCH 20/26] Introducing a pay-per-use billing model, the daily free model will only provide valid results. --- lib/bloc/model_bloc.dart | 6 +++--- lib/lang/lang.dart | 3 +++ lib/page/admin/models.dart | 18 ++++++++++++------ lib/page/admin/models_add.dart | 24 ++++++++++++++++++++++++ lib/page/admin/models_edit.dart | 28 ++++++++++++++++++++++++++++ lib/page/component/model_item.dart | 12 +++++++++++- lib/repo/api/admin/models.dart | 15 ++++++++++----- lib/repo/model/model.dart | 7 +++++-- 8 files changed, 96 insertions(+), 17 deletions(-) diff --git a/lib/bloc/model_bloc.dart b/lib/bloc/model_bloc.dart index e0cf4bd4..f831616c 100644 --- a/lib/bloc/model_bloc.dart +++ b/lib/bloc/model_bloc.dart @@ -24,7 +24,7 @@ class ModelBloc extends Bloc { on((event, emit) async { try { await APIServer().adminCreateModel(event.req); - emit(ModelOperationResult(true, '创建成功')); + emit(ModelOperationResult(true, 'Creation successful')); } catch (e) { emit(ModelOperationResult(false, e.toString())); } @@ -37,7 +37,7 @@ class ModelBloc extends Bloc { modelId: event.modelId, req: event.req, ); - emit(ModelOperationResult(true, '更新成功')); + emit(ModelOperationResult(true, 'Update successful')); } catch (e) { emit(ModelOperationResult(false, e.toString())); } @@ -47,7 +47,7 @@ class ModelBloc extends Bloc { on((event, emit) async { try { await APIServer().adminDeleteModel(modelId: event.modelId); - emit(ModelOperationResult(true, '删除成功')); + emit(ModelOperationResult(true, 'Delete successful')); } catch (e) { emit(ModelOperationResult(false, e.toString())); } diff --git a/lib/lang/lang.dart b/lib/lang/lang.dart index 54a8c662..06356d25 100644 --- a/lib/lang/lang.dart +++ b/lib/lang/lang.dart @@ -307,6 +307,7 @@ mixin AppLocale { static const String free = 'free'; static const String input = 'input'; static const String output = 'output'; + static const String perRequest = 'per-request'; static const String info = 'info'; static const Map zh = { @@ -601,6 +602,7 @@ mixin AppLocale { free: '限免', input: '输入', output: '输出', + perRequest: '每次', info: '详情', }; @@ -900,6 +902,7 @@ mixin AppLocale { free: 'Free', input: 'Input', output: 'Output', + perRequest: 'Per', info: 'Detail', }; } diff --git a/lib/page/admin/models.dart b/lib/page/admin/models.dart index ee25ffaf..b5a3d4f8 100644 --- a/lib/page/admin/models.dart +++ b/lib/page/admin/models.dart @@ -354,13 +354,19 @@ class _AdminModelsPageState extends State { String buildModelDescription(AdminModel mod) { String desc = ''; - if (mod.inputPrice > 0 || mod.outputPrice > 0) { + if (mod.inputPrice > 0 || mod.outputPrice > 0 || mod.perReqPrice > 0) { desc += '💰 '; - if (mod.inputPrice == mod.outputPrice) { - desc += 'IO${AppLocale.creditUnit.getString(context)}${mod.inputPrice}'; - } else { - desc += - 'I${AppLocale.creditUnit.getString(context)}${mod.inputPrice}/O${AppLocale.creditUnit.getString(context)}${mod.outputPrice}'; + if (mod.inputPrice > 0 || mod.outputPrice > 0) { + if (mod.inputPrice == mod.outputPrice) { + desc += 'IO${AppLocale.creditUnit.getString(context)}${mod.inputPrice} '; + } else { + desc += + 'I${AppLocale.creditUnit.getString(context)}${mod.inputPrice} O${AppLocale.creditUnit.getString(context)}${mod.outputPrice} '; + } + } + + if (mod.perReqPrice > 0) { + desc += 'R${AppLocale.creditUnit.getString(context)}${mod.perReqPrice}'; } } diff --git a/lib/page/admin/models_add.dart b/lib/page/admin/models_add.dart index 0ab7e9e1..12c655e9 100644 --- a/lib/page/admin/models_add.dart +++ b/lib/page/admin/models_add.dart @@ -50,6 +50,7 @@ class _AdminModelCreatePageState extends State { final TextEditingController maxContextController = TextEditingController(); final TextEditingController inputPriceController = TextEditingController(); final TextEditingController outputPriceController = TextEditingController(); + final TextEditingController perReqPriceController = TextEditingController(); final TextEditingController promptController = TextEditingController(); final TextEditingController categoryController = TextEditingController(); @@ -91,6 +92,7 @@ class _AdminModelCreatePageState extends State { maxContextController.dispose(); inputPriceController.dispose(); outputPriceController.dispose(); + perReqPriceController.dispose(); promptController.dispose(); categoryController.dispose(); tagController.dispose(); @@ -113,6 +115,7 @@ class _AdminModelCreatePageState extends State { maxContextController.value = const TextEditingValue(text: '7500'); inputPriceController.value = const TextEditingValue(text: '0'); outputPriceController.value = const TextEditingValue(text: '0'); + perReqPriceController.value = const TextEditingValue(text: '0'); providers.add(AdminModelProvider()); super.initState(); @@ -295,6 +298,26 @@ class _AdminModelCreatePageState extends State { ), ), ), + EnhancedTextField( + labelWidth: 120, + labelText: 'Request Price', + customColors: customColors, + controller: perReqPriceController, + textAlignVertical: TextAlignVertical.top, + hintText: 'Optional', + showCounter: false, + keyboardType: TextInputType.number, + inputFormatters: [FilteringTextInputFormatter.digitsOnly], + textDirection: TextDirection.rtl, + suffixIcon: Container( + width: 110, + alignment: Alignment.center, + child: Text( + 'Credits/Request', + style: TextStyle(color: customColors.weakTextColor, fontSize: 12), + ), + ), + ), EnhancedTextField( labelWidth: 120, labelText: 'Context Length', @@ -685,6 +708,7 @@ class _AdminModelCreatePageState extends State { maxContext: int.parse(maxContextController.text), inputPrice: int.parse(inputPriceController.text), outputPrice: int.parse(outputPriceController.text), + perReqPrice: int.parse(perReqPriceController.text), prompt: promptController.text, vision: supportVision, restricted: restricted, diff --git a/lib/page/admin/models_edit.dart b/lib/page/admin/models_edit.dart index 97f9467b..fc4f9414 100644 --- a/lib/page/admin/models_edit.dart +++ b/lib/page/admin/models_edit.dart @@ -52,6 +52,7 @@ class _AdminModelEditPageState extends State { final TextEditingController maxContextController = TextEditingController(); final TextEditingController inputPriceController = TextEditingController(); final TextEditingController outputPriceController = TextEditingController(); + final TextEditingController perRequestPriceController = TextEditingController(); final TextEditingController promptController = TextEditingController(); final TextEditingController categoryController = TextEditingController(); @@ -96,6 +97,7 @@ class _AdminModelEditPageState extends State { maxContextController.dispose(); inputPriceController.dispose(); outputPriceController.dispose(); + perRequestPriceController.dispose(); promptController.dispose(); categoryController.dispose(); tagController.dispose(); @@ -123,6 +125,7 @@ class _AdminModelEditPageState extends State { maxContextController.value = const TextEditingValue(text: '7500'); inputPriceController.value = const TextEditingValue(text: '0'); outputPriceController.value = const TextEditingValue(text: '0'); + perRequestPriceController.value = const TextEditingValue(text: '0'); super.initState(); } @@ -187,6 +190,10 @@ class _AdminModelEditPageState extends State { outputPriceController.value = TextEditingValue(text: state.model.meta!.outputPrice.toString()); } + if (state.model.meta!.perReqPrice != null) { + perRequestPriceController.value = TextEditingValue(text: state.model.meta!.perReqPrice.toString()); + } + promptController.value = TextEditingValue(text: state.model.meta!.prompt ?? ''); supportVision = state.model.meta!.vision ?? false; restricted = state.model.meta!.restricted ?? false; @@ -350,6 +357,26 @@ class _AdminModelEditPageState extends State { ), ), ), + EnhancedTextField( + labelWidth: 120, + labelText: 'Request Price', + customColors: customColors, + controller: perRequestPriceController, + textAlignVertical: TextAlignVertical.top, + hintText: 'Optional', + showCounter: false, + keyboardType: TextInputType.number, + inputFormatters: [FilteringTextInputFormatter.digitsOnly], + textDirection: TextDirection.rtl, + suffixIcon: Container( + width: 110, + alignment: Alignment.center, + child: Text( + 'Credits/Request', + style: TextStyle(color: customColors.weakTextColor, fontSize: 12), + ), + ), + ), EnhancedTextField( labelText: 'Context Length', customColors: customColors, @@ -759,6 +786,7 @@ class _AdminModelEditPageState extends State { maxContext: int.parse(maxContextController.text), inputPrice: int.parse(inputPriceController.text), outputPrice: int.parse(outputPriceController.text), + perReqPrice: int.parse(perRequestPriceController.text), prompt: promptController.text, vision: supportVision, restricted: restricted, diff --git a/lib/page/component/model_item.dart b/lib/page/component/model_item.dart index 525c8426..516d6eb3 100644 --- a/lib/page/component/model_item.dart +++ b/lib/page/component/model_item.dart @@ -248,10 +248,20 @@ class _ModelItemState extends State { return const SizedBox(); } + var priceText = ''; + if (item.input > 0 || item.output > 0) { + priceText += + '${AppLocale.input.getString(context)} ¢${item.input}, ${AppLocale.output.getString(context)} ¢${item.output}'; + } + + if (item.request > 0) { + priceText += ', ${AppLocale.perRequest.getString(context)} ¢${item.request}'; + } + return Row( children: [ Text( - '${AppLocale.input.getString(context)} ¢${item.input}, ${AppLocale.output.getString(context)} ¢${item.output}', + priceText, maxLines: 2, overflow: TextOverflow.ellipsis, style: TextStyle( diff --git a/lib/repo/api/admin/models.dart b/lib/repo/api/admin/models.dart index 537d4af2..938b76e3 100644 --- a/lib/repo/api/admin/models.dart +++ b/lib/repo/api/admin/models.dart @@ -11,6 +11,7 @@ class AdminModel { bool get isVision => meta?.vision ?? false; int get inputPrice => meta?.inputPrice ?? 0; int get outputPrice => meta?.outputPrice ?? 0; + int get perReqPrice => meta?.perReqPrice ?? 0; int get maxContext => meta?.maxContext ?? 0; bool get enabled => status == 1; @@ -58,6 +59,7 @@ class AdminModelMeta { int? maxContext; int? inputPrice; int? outputPrice; + int? perReqPrice; String? prompt; String? tag; @@ -73,6 +75,7 @@ class AdminModelMeta { this.maxContext, this.inputPrice, this.outputPrice, + this.perReqPrice, this.prompt, this.tag, this.tagTextColor, @@ -83,16 +86,17 @@ class AdminModelMeta { factory AdminModelMeta.fromJson(Map json) { return AdminModelMeta( - vision: json['vision'], - restricted: json['restricted'], + vision: json['vision'] ?? false, + restricted: json['restricted'] ?? false, maxContext: json['max_context'], - inputPrice: json['input_price'], - outputPrice: json['output_price'], + inputPrice: json['input_price'] ?? 0, + outputPrice: json['output_price'] ?? 0, + perReqPrice: json['per_req_price'] ?? 0, prompt: json['prompt'], tag: json['tag'], tagTextColor: json['tag_text_color'], tagBgColor: json['tag_bg_color'], - isNew: json['is_new'], + isNew: json['is_new'] ?? false, category: json['category'], ); } @@ -104,6 +108,7 @@ class AdminModelMeta { 'max_context': maxContext, 'input_price': inputPrice, 'output_price': outputPrice, + 'per_req_price': perReqPrice, 'prompt': prompt, 'tag': tag, 'tag_text_color': tagTextColor, diff --git a/lib/repo/model/model.dart b/lib/repo/model/model.dart index b0d7d969..f94e2323 100644 --- a/lib/repo/model/model.dart +++ b/lib/repo/model/model.dart @@ -83,7 +83,7 @@ class Model { ModelPrice get modelPrice { if (priceInfo == null || priceInfo == '') { - return ModelPrice(input: 0, output: 0, note: ''); + return ModelPrice(input: 0, output: 0, request: 0, note: ''); } return ModelPrice.fromMap(jsonDecode(priceInfo!) as Map); @@ -93,10 +93,11 @@ class Model { class ModelPrice { final int input; final int output; + final int request; final String note; bool get isFree { - return input == output && input == 0; + return input == output && input == 0 && request == 0; } bool get hasNote { @@ -106,11 +107,13 @@ class ModelPrice { ModelPrice({ required this.input, required this.output, + required this.request, required this.note, }); ModelPrice.fromMap(Map map) : input = map['input'] ?? 0, output = map['output'] ?? 0, + request = map['request'] ?? 0, note = map['note'] ?? ''; } From feb40cbcc4a697bbfbf87fa1718126bf08da7b6f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=AE=A1=E5=AE=9C=E5=B0=A7?= Date: Wed, 11 Dec 2024 22:55:43 +0800 Subject: [PATCH 21/26] bugfix shortName is not displayed in admin model management --- lib/page/admin/models_edit.dart | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/page/admin/models_edit.dart b/lib/page/admin/models_edit.dart index fc4f9414..f3954522 100644 --- a/lib/page/admin/models_edit.dart +++ b/lib/page/admin/models_edit.dart @@ -194,6 +194,7 @@ class _AdminModelEditPageState extends State { perRequestPriceController.value = TextEditingValue(text: state.model.meta!.perReqPrice.toString()); } + shortNameController.value = TextEditingValue(text: state.model.shortName ?? ''); promptController.value = TextEditingValue(text: state.model.meta!.prompt ?? ''); supportVision = state.model.meta!.vision ?? false; restricted = state.model.meta!.restricted ?? false; From 7b3d670e487e48ad75199d80c5f0971c7fb41f24 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=AE=A1=E5=AE=9C=E5=B0=A7?= Date: Fri, 13 Dec 2024 23:25:44 +0800 Subject: [PATCH 22/26] bugfix: change max length for API Key field in admin/channel management --- lib/page/admin/channels_add.dart | 2 +- lib/page/admin/channels_edit.dart | 2 +- lib/page/component/model_item.dart | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/page/admin/channels_add.dart b/lib/page/admin/channels_add.dart index a6d83e23..e101f2d3 100644 --- a/lib/page/admin/channels_add.dart +++ b/lib/page/admin/channels_add.dart @@ -167,7 +167,7 @@ class _ChannelAddPageState extends State { controller: secretController, textAlignVertical: TextAlignVertical.top, hintText: 'Enter API Key', - maxLength: 255, + maxLength: 2048, obscureText: true, showCounter: false, ), diff --git a/lib/page/admin/channels_edit.dart b/lib/page/admin/channels_edit.dart index 289b61ab..7e7921e4 100644 --- a/lib/page/admin/channels_edit.dart +++ b/lib/page/admin/channels_edit.dart @@ -186,7 +186,7 @@ class _ChannelEditPageState extends State { controller: secretController, textAlignVertical: TextAlignVertical.top, hintText: 'Enter API Key', - maxLength: 255, + maxLength: 2048, obscureText: true, showCounter: false, ), diff --git a/lib/page/component/model_item.dart b/lib/page/component/model_item.dart index 516d6eb3..292276f2 100644 --- a/lib/page/component/model_item.dart +++ b/lib/page/component/model_item.dart @@ -255,7 +255,7 @@ class _ModelItemState extends State { } if (item.request > 0) { - priceText += ', ${AppLocale.perRequest.getString(context)} ¢${item.request}'; + priceText += '${priceText == '' ? '' : ', '}${AppLocale.perRequest.getString(context)} ¢${item.request}'; } return Row( From ef006325e9cd27298eb563c1293337ec576084a6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=AE=A1=E5=AE=9C=E5=B0=A7?= Date: Sun, 15 Dec 2024 01:32:55 +0800 Subject: [PATCH 23/26] update --- lib/page/component/model_item.dart | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/page/component/model_item.dart b/lib/page/component/model_item.dart index 292276f2..3d9b5f33 100644 --- a/lib/page/component/model_item.dart +++ b/lib/page/component/model_item.dart @@ -84,7 +84,7 @@ class _ModelItemState extends State { customColors, AppLocale.free.getString(context), tagTextColor: 'FFFFFFFF', - tagBgColor: '2196F3', + tagBgColor: 'FF2196F3', )); } if (item.tag != null) { @@ -104,7 +104,7 @@ class _ModelItemState extends State { customColors, AppLocale.visionTag.getString(context), tagTextColor: 'FFFFFFFF', - tagBgColor: '4CAF50', + tagBgColor: 'FF4CAF50', )); } @@ -113,7 +113,7 @@ class _ModelItemState extends State { customColors, AppLocale.newTag.getString(context), tagTextColor: 'FFFFFFFF', - tagBgColor: 'F44336', + tagBgColor: 'FFF44336', )); } From 405f54a20a9bd2a27b7886bde908b0a479cd8ffd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=AE=A1=E5=AE=9C=E5=B0=A7?= Date: Mon, 16 Dec 2024 00:43:25 +0800 Subject: [PATCH 24/26] Optimize the style of the image selection button --- lib/page/component/chat/chat_input.dart | 205 ++++++++++---------- lib/page/component/enhanced_popup_menu.dart | 12 ++ 2 files changed, 117 insertions(+), 100 deletions(-) diff --git a/lib/page/component/chat/chat_input.dart b/lib/page/component/chat/chat_input.dart index cc60bf46..587f6912 100644 --- a/lib/page/component/chat/chat_input.dart +++ b/lib/page/component/chat/chat_input.dart @@ -8,7 +8,6 @@ import 'package:askaide/page/component/chat/file_upload.dart'; import 'package:askaide/page/component/chat/voice_record.dart'; import 'package:askaide/page/component/dialog.dart'; import 'package:askaide/page/component/file_preview.dart'; -import 'package:askaide/page/component/item_selector_search.dart'; import 'package:askaide/page/component/theme/custom_size.dart'; import 'package:askaide/repo/settings_repo.dart'; import 'package:camera/camera.dart'; @@ -91,6 +90,9 @@ class _ChatInputState extends State with TickerProviderStateMixin { var minLines = 1; var hasCamera = false; + // Whether to display the bottom tool bar + var showBottomTools = false; + @override void initState() { super.initState(); @@ -386,7 +388,7 @@ class _ChatInputState extends State with TickerProviderStateMixin { ); } - return _textController.text == '' + return _textController.text == '' && !PlatformTool.isWeb() ? InkWell( onTap: () { HapticFeedbackHelper.mediumImpact(); @@ -461,6 +463,8 @@ class _ChatInputState extends State with TickerProviderStateMixin { } } + final _menuKey = GlobalKey(); + /// Build image or file upload button Widget buildUploadButtons( BuildContext context, @@ -487,121 +491,122 @@ class _ChatInputState extends State with TickerProviderStateMixin { ); } - return IconButton( - icon: const Icon(Icons.add), - color: customColors.chatInputPanelText, - splashRadius: 20, - tooltip: 'Tools', - onPressed: () { - openListSelectDialog( - context, - >[ - if (hasCamera) - SelectorItem( - Column( - mainAxisAlignment: MainAxisAlignment.center, + return Listener( + onPointerDown: (_) async { + if (FocusScope.of(context).hasFocus) { + FocusScope.of(context).unfocus(); + await Future.delayed(const Duration(milliseconds: 200)); + } + _menuKey.currentState?.showButtonMenu(); + }, + child: PopupMenuButton( + key: _menuKey, + enabled: false, + icon: Icon(Icons.add, color: customColors.chatInputPanelText), + splashRadius: 20, + elevation: 0, + enableFeedback: true, + color: customColors.backgroundColor, + shape: RoundedRectangleBorder(borderRadius: CustomSize.borderRadius), + position: PopupMenuPosition.under, + onSelected: (value) { + switch (value) { + case 'take_photo': + onTakePhotoButtonPressed(context, customColors); + break; + case 'photo_library': + onImageUploadButtonPressed(); + break; + case 'file_library': + onFileUploadButtonPressed(); + break; + } + }, + itemBuilder: (context) { + return [ + if (canUploadImage) + PopupMenuItem( + value: 'take_photo', + child: Row( children: [ - const Icon(Icons.camera_alt, size: 50), - const SizedBox(height: 5), + const Icon(Icons.camera_alt), + const SizedBox(width: 10), Text( AppLocale.takePhoto.getString(context), - style: const TextStyle(fontSize: 12), - maxLines: 1, - overflow: TextOverflow.ellipsis, + style: const TextStyle(fontSize: 14), ), ], ), - 'take-photo', ), - SelectorItem( - Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - const Icon(Icons.photo_library, size: 50), - const SizedBox(height: 5), - Text( - AppLocale.photoLibrary.getString(context), - style: const TextStyle(fontSize: 12), - maxLines: 1, - overflow: TextOverflow.ellipsis, - ), - ], + if (canUploadImage) + PopupMenuItem( + value: 'photo_library', + child: Row( + children: [ + const Icon(Icons.photo_library), + const SizedBox(width: 10), + Text( + AppLocale.photoLibrary.getString(context), + style: const TextStyle(fontSize: 14), + ), + ], + ), ), - 'select-image', - ), if (canUploadFile) - SelectorItem( - Column( - mainAxisAlignment: MainAxisAlignment.center, + PopupMenuItem( + value: 'file_library', + child: Row( children: [ - const Icon(Icons.upload_file_sharp, size: 50), - const SizedBox(height: 5), + const Icon(Icons.upload_file_sharp), + const SizedBox(width: 10), Text( AppLocale.fileLibrary.getString(context), - style: const TextStyle(fontSize: 12), - maxLines: 1, - overflow: TextOverflow.ellipsis, + style: const TextStyle(fontSize: 14), ), ], ), - 'select-file', ), - ], - (value) { - switch (value.value) { - case 'select-image': - onImageUploadButtonPressed(); - break; - case 'select-file': - onFileUploadButtonPressed(); - break; - case 'take-photo': - HapticFeedbackHelper.mediumImpact(); - Navigator.push( - context, - MaterialPageRoute( - builder: (context) => Scaffold( - appBar: AppBar( - title: Text(AppLocale.takePhoto.getString(context)), - backgroundColor: customColors.backgroundColor, - ), - body: CameraAwesomeBuilder.awesome( - saveConfig: SaveConfig.photo(), - enablePhysicalButton: true, - onMediaCaptureEvent: (mediaCapture) async { - if (mediaCapture.status == MediaCaptureStatus.success) { - final file = FileUpload( - file: PlatformFile( - path: mediaCapture.captureRequest.path!, - name: mediaCapture.captureRequest.path!.split('/').last, - size: await File(mediaCapture.captureRequest.path!).length(), - )); - - final files = widget.selectedImageFiles ?? []; - files.add(file); - widget.onImageSelected?.call(files.sublist(0, files.length > 4 ? 4 : files.length)); - - if (context.mounted) { - context.pop(); - } - } - }, - ), - ), - ), - ).whenComplete(() { + ]; + }, + ), + ); + } + + // Take a photo + void onTakePhotoButtonPressed(BuildContext context, CustomColors customColors) { + HapticFeedbackHelper.mediumImpact(); + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => Scaffold( + appBar: AppBar( + title: Text(AppLocale.takePhoto.getString(context)), + backgroundColor: customColors.backgroundColor, + ), + body: CameraAwesomeBuilder.awesome( + saveConfig: SaveConfig.photo(), + enablePhysicalButton: true, + onMediaCaptureEvent: (mediaCapture) async { + if (mediaCapture.status == MediaCaptureStatus.success) { + final file = FileUpload( + file: PlatformFile( + path: mediaCapture.captureRequest.path!, + name: mediaCapture.captureRequest.path!.split('/').last, + size: await File(mediaCapture.captureRequest.path!).length(), + )); + + final files = widget.selectedImageFiles ?? []; + files.add(file); + widget.onImageSelected?.call(files.sublist(0, files.length > 4 ? 4 : files.length)); + + if (context.mounted) { context.pop(); - }); - return false; - default: - } - return true; - }, - heightFactor: 0.3, - horizontal: true, - horizontalCount: canUploadFile ? 3 : 2, - ); - }, + } + } + }, + ), + ), + ), ); } diff --git a/lib/page/component/enhanced_popup_menu.dart b/lib/page/component/enhanced_popup_menu.dart index 90199c0a..c5c40250 100644 --- a/lib/page/component/enhanced_popup_menu.dart +++ b/lib/page/component/enhanced_popup_menu.dart @@ -1,4 +1,5 @@ import 'package:askaide/page/component/theme/custom_size.dart'; +import 'package:askaide/page/component/theme/custom_theme.dart'; import 'package:flutter/material.dart'; class EnhancedPopupMenuItem { @@ -20,23 +21,34 @@ class EnhancedPopupMenu extends StatelessWidget { final IconData? icon; final Color? color; final String? tooltip; + final void Function()? onOpened; + final void Function()? onCanceled; + const EnhancedPopupMenu({ super.key, required this.items, this.icon, this.color, this.tooltip, + this.onOpened, + this.onCanceled, }); @override Widget build(BuildContext context) { + final customColors = Theme.of(context).extension()!; + return PopupMenuButton( icon: Icon(icon ?? Icons.more_horiz, color: color), tooltip: tooltip, splashRadius: 20, elevation: 0, + enableFeedback: true, + color: customColors.backgroundColor, shape: RoundedRectangleBorder(borderRadius: CustomSize.borderRadius), position: PopupMenuPosition.under, + onOpened: onOpened, + onCanceled: onCanceled, onSelected: (value) { if (value.onTap != null) { value.onTap!(context); From 8d878cb834af763bdd809f3df646916c23e12819 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=AE=A1=E5=AE=9C=E5=B0=A7?= Date: Fri, 20 Dec 2024 16:40:42 +0800 Subject: [PATCH 25/26] Some code optimization --- devtools_options.yaml | 3 + lib/bloc/chat_chat_bloc.dart | 2 +- lib/bloc/chat_chat_event.dart | 5 +- lib/lang/lang.dart | 78 ++++--- lib/page/admin/messages.dart | 21 +- lib/page/admin/models_add.dart | 137 +++++++++--- lib/page/admin/models_edit.dart | 185 +++++++++++++---- lib/page/admin/rooms.dart | 54 +++-- lib/page/chat/home.dart | 135 ++++++++---- lib/page/chat/home_chat_history.dart | 16 +- lib/page/component/account_quota_card.dart | 137 ++++++------ lib/page/component/model_item.dart | 230 +++++++++++++++++---- lib/page/drawer.dart | 38 ++-- lib/page/home.dart | 97 +++++---- lib/page/setting/destroy_account.dart | 11 +- lib/repo/api/admin/models.dart | 8 +- lib/repo/model/model.dart | 4 + macos/Runner/MainFlutterWindow.swift | 2 +- pubspec.lock | 8 + pubspec.yaml | 1 + windows/runner/main.cpp | 2 +- 21 files changed, 826 insertions(+), 348 deletions(-) create mode 100644 devtools_options.yaml diff --git a/devtools_options.yaml b/devtools_options.yaml new file mode 100644 index 00000000..fa0b357c --- /dev/null +++ b/devtools_options.yaml @@ -0,0 +1,3 @@ +description: This file stores settings for Dart & Flutter DevTools. +documentation: https://docs.flutter.dev/tools/devtools/extensions#configure-extension-enablement-states +extensions: diff --git a/lib/bloc/chat_chat_bloc.dart b/lib/bloc/chat_chat_bloc.dart index 517bc996..8f890b79 100644 --- a/lib/bloc/chat_chat_bloc.dart +++ b/lib/bloc/chat_chat_bloc.dart @@ -16,7 +16,7 @@ class ChatChatBloc extends Bloc { on((event, emit) async { final histories = await _chatMessageRepository.recentChatHistories( chatAnywhereRoomId, - 3, + event.count, userId: APIServer().localUserID(), ); diff --git a/lib/bloc/chat_chat_event.dart b/lib/bloc/chat_chat_event.dart index 89fbc098..de2c8204 100644 --- a/lib/bloc/chat_chat_event.dart +++ b/lib/bloc/chat_chat_event.dart @@ -3,7 +3,10 @@ part of 'chat_chat_bloc.dart'; @immutable abstract class ChatChatEvent {} -class ChatChatLoadRecentHistories extends ChatChatEvent {} +class ChatChatLoadRecentHistories extends ChatChatEvent { + final int count; + ChatChatLoadRecentHistories({this.count = 4}); +} class ChatChatNewChat extends ChatChatEvent { final String text; diff --git a/lib/lang/lang.dart b/lib/lang/lang.dart index 06356d25..276e63b0 100644 --- a/lib/lang/lang.dart +++ b/lib/lang/lang.dart @@ -136,7 +136,8 @@ mixin AppLocale { static const String generating = 'generating'; static const String generateExitConfirm = 'generate-exit-confirm'; static const String tooManyRequests = 'too-many-requests'; - static const String tooManyRequestsOrPaymentRequired = 'too-many-requests-or-payment-required'; + static const String tooManyRequestsOrPaymentRequired = + 'too-many-requests-or-payment-required'; static const String promptHint = 'prompt-hint'; static const String confirmClearCache = 'confirm-clear-cache'; static const String confirmSignOut = 'confirm-sign-out'; @@ -157,7 +158,8 @@ mixin AppLocale { static const String referenceImage = 'reference-image'; static const String selectImage = 'select-image'; static const String imagination = 'imagination'; - static const String keywordsSeparatedByCommas = 'keywords-separated-by-commas'; + static const String keywordsSeparatedByCommas = + 'keywords-separated-by-commas'; static const String originalImage = 'original-image'; static const String superResolution = 'super-resolution'; static const String colorizeImage = 'colorize-image'; @@ -165,7 +167,8 @@ mixin AppLocale { static const String report = 'report'; static const String latestVersion = 'latest-version'; static const String aIdeaApp = 'aidea-app'; - static const String onceEnabledSmartOptimization = 'once-enabled-smart-optimization'; + static const String onceEnabledSmartOptimization = + 'once-enabled-smart-optimization'; static const String gotIt = 'got-it'; static const String referenceImageNote = 'reference-image-note'; static const String selectReferenceImage = 'select-reference-image'; @@ -233,6 +236,7 @@ mixin AppLocale { static const String recentlyUsed = 'recently-used'; static const String visionTag = 'vision-tag'; static const String newTag = 'new-tag'; + static const String recommendTag = 'recommend-tag'; static const String imageUploading = 'image-uploading'; static const String uploadImageLimit4 = 'upload-image-limit-4'; @@ -346,11 +350,11 @@ mixin AppLocale { selectText: '选择文本', text: '文本', uploading: '上传中...', - robotIsThinkingMessage: '数字人正在思考中...', + robotIsThinkingMessage: '正在思考中...', robotHasSomeError: '发送失败,重发该消息?', appName: 'AIdea', chatAnywhere: '聊一聊', - homeTitle: '数字人', + homeTitle: '自定义角色', creativeIsland: '创作岛', settings: '设置', language: '语言', @@ -375,8 +379,8 @@ mixin AppLocale { account: '账号', usedUp: '已用完', expired: '已过期', - character: '数字人', - createRoom: '创建数字人', + character: '角色', + createRoom: '创建角色', model: 'AI 模型', selectModel: '选择模型', roomName: '名称', @@ -403,9 +407,9 @@ mixin AppLocale { switchModel: '切换对话模型', switchModelTitle: '选择要切换的对话模型', noMessageSelected: '没有选择任何消息', - modelUsage: '模型用于设置采用的 AI 数字人类型', - promptUsage: '领域设定用于设置 AI 数字人的行为', - nameRequiredMessage: '请输入数字人名称', + modelUsage: '模型用于设置采用的 AI 角色类型', + promptUsage: '领域设定用于设置 AI 角色的行为', + nameRequiredMessage: '请输入名称', modelRequiredMessage: '请选择 AI 模型', operateSuccess: '操作成功', operateFailed: '操作失败', @@ -441,8 +445,9 @@ mixin AppLocale { generating: '创作中...', generateExitConfirm: '创作中...\n退出后,可在历史记录中查看结果', tooManyRequests: '操作过于频繁,请稍后再试', - tooManyRequestsOrPaymentRequired: '操作过于频繁(如果您使用了自定义的 OpenAI Keys,请登录 https://platform.openai.com 检查账户余额是否充足)', - promptHint: '设定该数字人的角色和技能,以便为你提供更精准有效的信息。', + tooManyRequestsOrPaymentRequired: + '操作过于频繁(如果您使用了自定义的 OpenAI Keys,请登录 https://platform.openai.com 检查账户余额是否充足)', + promptHint: '设定角色和技能,以便为你提供更精准有效的信息。', confirmClearCache: '确定要清除缓存吗?', confirmSignOut: '确定要退出登录吗?', askMeAnyQuestion: '有问题尽管问我', @@ -532,7 +537,8 @@ mixin AppLocale { others: '其它', recentlyUsed: '最近使用', visionTag: '视觉', - newTag: '新', + newTag: '上新', + recommendTag: '推荐', imageUploading: '正在上传图片,请稍后...', uploadImageLimit4: '最多只能上传 4 张图片', confirmStopOutput: '确定要停止当前输出?', @@ -643,7 +649,8 @@ mixin AppLocale { text: 'Text', uploading: 'Uploading...', robotIsThinkingMessage: 'Thinking...', - robotHasSomeError: 'There seems to be something wrong, Do you want to resend the message?', + robotHasSomeError: + 'There seems to be something wrong, Do you want to resend the message?', appName: 'AIdea', chatAnywhere: 'Chat', homeTitle: 'Characters', @@ -715,8 +722,10 @@ mixin AppLocale { modelNotValid: 'The current model is not open', signInRequired: 'You are not logged in, please log in first', accountNeedReSignin: 'Account exception, please log in again', - openAIAuthFailed: 'You have enabled custom OpenAI service, please check if the API Key is correct', - modelNotFound: 'The current model is not enabled yet, please try again later', + openAIAuthFailed: + 'You have enabled custom OpenAI service, please check if the API Key is correct', + modelNotFound: + 'The current model is not enabled yet, please try again later', confirmToDeleteRoom: 'Confirm to delete the character?', writeYourIdeas: 'Your ideas', describeYourImages: 'Your ideas', @@ -736,7 +745,8 @@ mixin AppLocale { generateResult: 'Generate result', generateFailed: 'Creation failed', generating: 'Generating...', - generateExitConfirm: 'Generating...\nYou can view the result in the history', + generateExitConfirm: + 'Generating...\nYou can view the result in the history', tooManyRequests: 'Too many requests, please try again later', tooManyRequestsOrPaymentRequired: 'Too many requests (If you are using your own OpenAI Keys, please log in to https://platform.openai.com to check if your account balance is sufficient)', @@ -762,17 +772,21 @@ mixin AppLocale { referenceImage: 'Reference Image', selectImage: 'Select Image', imagination: 'Imagination', - keywordsSeparatedByCommas: 'Keywords of the scene you imagine, separated by commas', + keywordsSeparatedByCommas: + 'Keywords of the scene you imagine, separated by commas', originalImage: 'Original Image', superResolution: 'Super-Resolution', colorizeImage: 'Colorize Image', errorLog: 'Error Log', report: 'Report', latestVersion: 'You are currently on the latest version', - aIdeaApp: 'AIdea is an app that allows you to converse with AI, and the app code is completely open source.', - onceEnabledSmartOptimization: 'Smart Optimization\n\nOnce enabled, AI will further refine and optimize your ideas.', + aIdeaApp: + 'AIdea is an app that allows you to converse with AI, and the app code is completely open source.', + onceEnabledSmartOptimization: + 'Smart Optimization\n\nOnce enabled, AI will further refine and optimize your ideas.', gotIt: 'Got it', - referenceImageNote: 'Reference Image\n\nAI will create based on the reference image provided.', + referenceImageNote: + 'Reference Image\n\nAI will create based on the reference image provided.', selectReferenceImage: 'Please select a reference image', random: 'Random', followSystem: 'Follow System', @@ -787,14 +801,17 @@ mixin AppLocale { accountInputTips: 'Enter your phone number or email', phoneInputTips: 'Enter your phone number', passwordInputTips: 'Enter your password', - pleaseReadAgreeProtocol: 'Please read and agree to the user agreement and privacy policy first', + pleaseReadAgreeProtocol: + 'Please read and agree to the user agreement and privacy policy first', signInSuccess: 'Sign in success', signInFailed: 'Sign in failed', accountRequired: 'Please enter your account', - accountFormatError: 'Account format error\nPlease enter your phone number or email', + accountFormatError: + 'Account format error\nPlease enter your phone number or email', phoneNumberFormatError: 'Phone number format error', passwordRequired: 'Please enter your password', - passwordFormatError: 'Password format error\nMust be 8-20 digits, letters, special characters', + passwordFormatError: + 'Password format error\nMust be 8-20 digits, letters, special characters', accountCreated: 'Account created', sendVerifyCode: 'Send', verifyCode: 'Verify code', @@ -814,12 +831,14 @@ mixin AppLocale { bound: 'Bound', unbind: 'Unbind', inviteCode: 'Invite code', - inviteCodeInputTips: 'Enter friend invite code, get extra rewards (optional)', + inviteCodeInputTips: + 'Enter friend invite code, get extra rewards (optional)', inviteCodeFormatError: 'Invite code format error', enableCustomOpenAI: 'Your custom OpenAI service will be used once enabled', me: 'Me', creditsUsage: 'Usage', - creditUsageTips: 'Usage details will be updated the next day, showing usage in the last 30 days.', + creditUsageTips: + 'Usage details will be updated the next day, showing usage in the last 30 days.', updateCheck: 'Check Update', buy: 'Buy', paymentHistory: 'Histories', @@ -833,6 +852,7 @@ mixin AppLocale { recentlyUsed: 'Recently Used', visionTag: 'Vision', newTag: 'New', + recommendTag: 'Recommend', imageUploading: 'Uploading image, please wait...', uploadImageLimit4: 'You can only upload up to 4 images', confirmStopOutput: 'Are you sure you want to stop current output?', @@ -864,7 +884,8 @@ mixin AppLocale { advanced: 'Advanced', collapseOptions: 'Collapse', welcomeMessage: 'Welcome Message', - welcomeMessageTips: 'The system will automatically send a welcome message each time a new chat is started.', + welcomeMessageTips: + 'The system will automatically send a welcome message each time a new chat is started.', memoryDepth: 'Memory Depth', robotRecommand: 'Recommand', pickYourRobot: 'Pick your robot', @@ -883,7 +904,8 @@ mixin AppLocale { showInviteCode: 'Show Invite Code', dontShowInviteCode: 'Don\'t Show Invite Code', inviteNow: 'Invite Now', - inviteSlogan: 'Invite friends to register, both parties will receive rewards', + inviteSlogan: + 'Invite friends to register, both parties will receive rewards', preview: 'Preview', download: 'Download', clickSwitchImage: 'Click to switch image', diff --git a/lib/page/admin/messages.dart b/lib/page/admin/messages.dart index 03811bd2..14e0079e 100644 --- a/lib/page/admin/messages.dart +++ b/lib/page/admin/messages.dart @@ -84,7 +84,7 @@ class _AdminRoomMessagesPageState extends State { } return const Text( - '用户数字人', + 'Character', style: TextStyle(fontSize: CustomSize.appBarTitleSize), ); }, @@ -97,7 +97,9 @@ class _AdminRoomMessagesPageState extends State { child: RefreshIndicator( color: customColors.linkColor, onRefresh: () async { - context.read().add(AdminRoomRecentlyMessagesLoadEvent( + context + .read() + .add(AdminRoomRecentlyMessagesLoadEvent( userId: widget.userId, roomId: widget.roomId, roomType: widget.roomType, @@ -108,13 +110,15 @@ class _AdminRoomMessagesPageState extends State { listener: (context, state) { if (state is AdminRoomOperationResult) { if (state.success) { - showSuccessMessage(AppLocale.operateSuccess.getString(context)); + showSuccessMessage( + AppLocale.operateSuccess.getString(context)); } else { showErrorMessage(AppLocale.operateFailed.getString(context)); } } }, - buildWhen: (previous, current) => current is AdminRoomRecentlyMessagesLoaded, + buildWhen: (previous, current) => + current is AdminRoomRecentlyMessagesLoaded, builder: (context, state) { if (state is AdminRoomRecentlyMessagesLoaded) { return SafeArea( @@ -126,7 +130,8 @@ class _AdminRoomMessagesPageState extends State { if (e.model != null) { final model = models[e.model]; if (model != null) { - if (e.avatarUrl == null && model.avatarUrl != null) { + if (e.avatarUrl == null && + model.avatarUrl != null) { e.avatarUrl = model.avatarUrl; } @@ -139,7 +144,8 @@ class _AdminRoomMessagesPageState extends State { controller: controller, supportBloc: false, senderNameBuilder: (message) { - if (message.role == Role.sender || message.senderName == null) { + if (message.role == Role.sender || + message.senderName == null) { return null; } @@ -150,7 +156,8 @@ class _AdminRoomMessagesPageState extends State { right: 5, ), child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, + mainAxisAlignment: + MainAxisAlignment.spaceBetween, children: [ Text( message.senderName!, diff --git a/lib/page/admin/models_add.dart b/lib/page/admin/models_add.dart index 12c655e9..d27a730b 100644 --- a/lib/page/admin/models_add.dart +++ b/lib/page/admin/models_add.dart @@ -73,6 +73,9 @@ class _AdminModelCreatePageState extends State { /// 是否是上新 bool isNew = false; + /// 是否是推荐模型 + bool isRecommended = false; + /// Tag final TextEditingController tagController = TextEditingController(); String? tagTextColor; @@ -152,7 +155,8 @@ class _AdminModelCreatePageState extends State { } }, child: Container( - padding: const EdgeInsets.only(left: 10, right: 10, top: 10, bottom: 20), + padding: const EdgeInsets.only( + left: 10, right: 10, top: 10, bottom: 20), child: Column( children: [ ColumnBlock( @@ -206,8 +210,10 @@ class _AdminModelCreatePageState extends State { ? null : DecorationImage( image: (avatarUrl!.startsWith('http') - ? CachedNetworkImageProviderEnhanced(avatarUrl!) - : FileImage(File(avatarUrl!))) as ImageProvider, + ? CachedNetworkImageProviderEnhanced( + avatarUrl!) + : FileImage(File( + avatarUrl!))) as ImageProvider, fit: BoxFit.cover, ), ), @@ -267,14 +273,18 @@ class _AdminModelCreatePageState extends State { hintText: 'Optional', showCounter: false, keyboardType: TextInputType.number, - inputFormatters: [FilteringTextInputFormatter.digitsOnly], + inputFormatters: [ + FilteringTextInputFormatter.digitsOnly + ], textDirection: TextDirection.rtl, suffixIcon: Container( width: 110, alignment: Alignment.center, child: Text( 'Credits/1K Token', - style: TextStyle(color: customColors.weakTextColor, fontSize: 12), + style: TextStyle( + color: customColors.weakTextColor, + fontSize: 12), ), ), ), @@ -287,14 +297,18 @@ class _AdminModelCreatePageState extends State { hintText: 'Optional', showCounter: false, keyboardType: TextInputType.number, - inputFormatters: [FilteringTextInputFormatter.digitsOnly], + inputFormatters: [ + FilteringTextInputFormatter.digitsOnly + ], textDirection: TextDirection.rtl, suffixIcon: Container( width: 110, alignment: Alignment.center, child: Text( 'Credits/1K Token', - style: TextStyle(color: customColors.weakTextColor, fontSize: 12), + style: TextStyle( + color: customColors.weakTextColor, + fontSize: 12), ), ), ), @@ -307,14 +321,18 @@ class _AdminModelCreatePageState extends State { hintText: 'Optional', showCounter: false, keyboardType: TextInputType.number, - inputFormatters: [FilteringTextInputFormatter.digitsOnly], + inputFormatters: [ + FilteringTextInputFormatter.digitsOnly + ], textDirection: TextDirection.rtl, suffixIcon: Container( width: 110, alignment: Alignment.center, child: Text( 'Credits/Request', - style: TextStyle(color: customColors.weakTextColor, fontSize: 12), + style: TextStyle( + color: customColors.weakTextColor, + fontSize: 12), ), ), ), @@ -324,17 +342,22 @@ class _AdminModelCreatePageState extends State { customColors: customColors, controller: maxContextController, textAlignVertical: TextAlignVertical.top, - hintText: 'Subtract the expected output length from the maximum context.', + hintText: + 'Subtract the expected output length from the maximum context.', showCounter: false, keyboardType: TextInputType.number, - inputFormatters: [FilteringTextInputFormatter.digitsOnly], + inputFormatters: [ + FilteringTextInputFormatter.digitsOnly + ], textDirection: TextDirection.rtl, suffixIcon: Container( width: 50, alignment: Alignment.center, child: Text( 'Token', - style: TextStyle(color: customColors.weakTextColor, fontSize: 12), + style: TextStyle( + color: customColors.weakTextColor, + fontSize: 12), ), ), ), @@ -342,7 +365,8 @@ class _AdminModelCreatePageState extends State { ), ...providers.map((e) { return Container( - margin: const EdgeInsets.only(bottom: 10, left: 5, right: 5), + margin: + const EdgeInsets.only(bottom: 10, left: 5, right: 5), child: Slidable( endActionPane: ActionPane( motion: const ScrollMotion(), @@ -355,16 +379,19 @@ class _AdminModelCreatePageState extends State { icon: Icons.delete, onPressed: (_) { if (providers.length == 1) { - showErrorMessage('At least one channel is needed'); + showErrorMessage( + 'At least one channel is needed'); return; } openConfirmDialog( context, - AppLocale.confirmToDeleteRoom.getString(context), + AppLocale.confirmToDeleteRoom + .getString(context), () { setState(() { - providers.removeWhere((item) => item == e); + providers + .removeWhere((item) => item == e); }); }, danger: true, @@ -398,7 +425,8 @@ class _AdminModelCreatePageState extends State { ...modelChannels .map( (e) => SelectorItem( - Text('${e.id == null ? '【System】' : ''}${e.name}'), + Text( + '${e.id == null ? '【System】' : ''}${e.name}'), e, ), ) @@ -484,15 +512,18 @@ class _AdminModelCreatePageState extends State { showBeautyDialog( context, type: QuickAlertType.info, - text: 'Whether the current model supports visual capabilities.', - confirmBtnText: AppLocale.gotIt.getString(context), + text: + 'Whether the current model supports visual capabilities.', + confirmBtnText: + AppLocale.gotIt.getString(context), showCancelBtn: false, ); }, child: Icon( Icons.help_outline, size: 16, - color: customColors.weakLinkColor?.withAlpha(150), + color: customColors.weakLinkColor + ?.withAlpha(150), ), ), ], @@ -525,14 +556,16 @@ class _AdminModelCreatePageState extends State { type: QuickAlertType.info, text: 'Whether to display a "New" icon next to the model to inform users that this is a new model.', - confirmBtnText: AppLocale.gotIt.getString(context), + confirmBtnText: + AppLocale.gotIt.getString(context), showCancelBtn: false, ); }, child: Icon( Icons.help_outline, size: 16, - color: customColors.weakLinkColor?.withAlpha(150), + color: customColors.weakLinkColor + ?.withAlpha(150), ), ), ], @@ -548,6 +581,48 @@ class _AdminModelCreatePageState extends State { ), ], ), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Row( + children: [ + const Text( + 'Recommended', + style: TextStyle(fontSize: 16), + ), + const SizedBox(width: 5), + InkWell( + onTap: () { + showBeautyDialog( + context, + type: QuickAlertType.info, + text: + 'Whether to display a "Recommended" icon next to the model to inform users that this is a recommended model.', + confirmBtnText: + AppLocale.gotIt.getString(context), + showCancelBtn: false, + ); + }, + child: Icon( + Icons.help_outline, + size: 16, + color: customColors.weakLinkColor + ?.withAlpha(150), + ), + ), + ], + ), + CupertinoSwitch( + activeColor: customColors.linkColor, + value: isRecommended, + onChanged: (value) { + setState(() { + isRecommended = value; + }); + }, + ), + ], + ), Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ @@ -565,14 +640,16 @@ class _AdminModelCreatePageState extends State { type: QuickAlertType.info, text: 'Restricted models refer to models that cannot be used in Chinese Mainland due to policy factors.', - confirmBtnText: AppLocale.gotIt.getString(context), + confirmBtnText: + AppLocale.gotIt.getString(context), showCancelBtn: false, ); }, child: Icon( Icons.help_outline, size: 16, - color: customColors.weakLinkColor?.withAlpha(150), + color: customColors.weakLinkColor + ?.withAlpha(150), ), ), ], @@ -630,7 +707,9 @@ class _AdminModelCreatePageState extends State { color: customColors.weakLinkColor, fontSize: 15, icon: Icon( - showAdvancedOptions ? Icons.unfold_less : Icons.unfold_more, + showAdvancedOptions + ? Icons.unfold_less + : Icons.unfold_more, color: customColors.weakLinkColor, size: 15, ), @@ -677,7 +756,9 @@ class _AdminModelCreatePageState extends State { return; } - if (avatarUrl != null && (!avatarUrl!.startsWith('http://') && !avatarUrl!.startsWith('https://'))) { + if (avatarUrl != null && + (!avatarUrl!.startsWith('http://') && + !avatarUrl!.startsWith('https://'))) { final cancel = BotToast.showCustomLoading( toastBuilder: (cancel) { return const LoadingIndicator( @@ -688,7 +769,8 @@ class _AdminModelCreatePageState extends State { ); try { - final res = await ImageUploader(widget.setting).upload(avatarUrl!, usage: 'avatar'); + final res = await ImageUploader(widget.setting) + .upload(avatarUrl!, usage: 'avatar'); avatarUrl = res.url; } catch (e) { showErrorMessage('Failed to upload avatar'); @@ -717,6 +799,7 @@ class _AdminModelCreatePageState extends State { tagBgColor: tagBgColor, category: categoryController.text, isNew: isNew, + isRecommend: isRecommended, ), status: modelEnabled ? 1 : 2, providers: ps, diff --git a/lib/page/admin/models_edit.dart b/lib/page/admin/models_edit.dart index f3954522..602dbac2 100644 --- a/lib/page/admin/models_edit.dart +++ b/lib/page/admin/models_edit.dart @@ -52,7 +52,8 @@ class _AdminModelEditPageState extends State { final TextEditingController maxContextController = TextEditingController(); final TextEditingController inputPriceController = TextEditingController(); final TextEditingController outputPriceController = TextEditingController(); - final TextEditingController perRequestPriceController = TextEditingController(); + final TextEditingController perRequestPriceController = + TextEditingController(); final TextEditingController promptController = TextEditingController(); final TextEditingController categoryController = TextEditingController(); @@ -71,6 +72,9 @@ class _AdminModelEditPageState extends State { /// 是否是上新 bool isNew = false; + /// 是否是推荐模型 + bool isRecommended = false; + /// Tag final TextEditingController tagController = TextEditingController(); String? tagTextColor; @@ -149,7 +153,8 @@ class _AdminModelEditPageState extends State { enabled: false, child: SingleChildScrollView( child: BlocListener( - listenWhen: (previous, current) => current is ModelOperationResult || current is ModelLoaded, + listenWhen: (previous, current) => + current is ModelOperationResult || current is ModelLoaded, listener: (context, state) { if (state is ModelOperationResult) { if (state.success) { @@ -161,10 +166,12 @@ class _AdminModelEditPageState extends State { } if (state is ModelLoaded) { - modelIdController.value = TextEditingValue(text: state.model.modelId); + modelIdController.value = + TextEditingValue(text: state.model.modelId); nameController.value = TextEditingValue(text: state.model.name); if (state.model.description != null) { - descriptionController.value = TextEditingValue(text: state.model.description!); + descriptionController.value = + TextEditingValue(text: state.model.description!); } if (state.model.avatarUrl != null) { @@ -179,30 +186,41 @@ class _AdminModelEditPageState extends State { if (state.model.meta != null) { if (state.model.meta!.maxContext != null) { - maxContextController.value = TextEditingValue(text: state.model.meta!.maxContext.toString()); + maxContextController.value = TextEditingValue( + text: state.model.meta!.maxContext.toString()); } if (state.model.meta!.inputPrice != null) { - inputPriceController.value = TextEditingValue(text: state.model.meta!.inputPrice.toString()); + inputPriceController.value = TextEditingValue( + text: state.model.meta!.inputPrice.toString()); } if (state.model.meta!.outputPrice != null) { - outputPriceController.value = TextEditingValue(text: state.model.meta!.outputPrice.toString()); + outputPriceController.value = TextEditingValue( + text: state.model.meta!.outputPrice.toString()); } if (state.model.meta!.perReqPrice != null) { - perRequestPriceController.value = TextEditingValue(text: state.model.meta!.perReqPrice.toString()); + perRequestPriceController.value = TextEditingValue( + text: state.model.meta!.perReqPrice.toString()); } - shortNameController.value = TextEditingValue(text: state.model.shortName ?? ''); - promptController.value = TextEditingValue(text: state.model.meta!.prompt ?? ''); + shortNameController.value = + TextEditingValue(text: state.model.shortName ?? ''); + promptController.value = + TextEditingValue(text: state.model.meta!.prompt ?? ''); supportVision = state.model.meta!.vision ?? false; restricted = state.model.meta!.restricted ?? false; - tagController.value = TextEditingValue(text: state.model.meta!.tag ?? ''); + tagController.value = + TextEditingValue(text: state.model.meta!.tag ?? ''); tagTextColor = state.model.meta!.tagTextColor; tagBgColor = state.model.meta!.tagBgColor; isNew = state.model.meta!.isNew ?? false; - categoryController.value = TextEditingValue(text: state.model.meta!.category ?? ''); + isRecommended = state.model.meta!.isRecommend ?? false; + categoryController.value = + TextEditingValue(text: state.model.meta!.category ?? ''); + + setState(() {}); } } @@ -211,7 +229,8 @@ class _AdminModelEditPageState extends State { }); }, child: Container( - padding: const EdgeInsets.only(left: 10, right: 10, top: 10, bottom: 20), + padding: const EdgeInsets.only( + left: 10, right: 10, top: 10, bottom: 20), child: Column( children: [ ColumnBlock( @@ -266,8 +285,10 @@ class _AdminModelEditPageState extends State { ? null : DecorationImage( image: (avatarUrl!.startsWith('http') - ? CachedNetworkImageProviderEnhanced(avatarUrl!) - : FileImage(File(avatarUrl!))) as ImageProvider, + ? CachedNetworkImageProviderEnhanced( + avatarUrl!) + : FileImage(File( + avatarUrl!))) as ImageProvider, fit: BoxFit.cover, ), ), @@ -327,14 +348,18 @@ class _AdminModelEditPageState extends State { hintText: 'Optional', showCounter: false, keyboardType: TextInputType.number, - inputFormatters: [FilteringTextInputFormatter.digitsOnly], + inputFormatters: [ + FilteringTextInputFormatter.digitsOnly + ], textDirection: TextDirection.rtl, suffixIcon: Container( width: 110, alignment: Alignment.center, child: Text( 'Credits/1K Token', - style: TextStyle(color: customColors.weakTextColor, fontSize: 12), + style: TextStyle( + color: customColors.weakTextColor, + fontSize: 12), ), ), ), @@ -347,14 +372,18 @@ class _AdminModelEditPageState extends State { hintText: 'Optional', showCounter: false, keyboardType: TextInputType.number, - inputFormatters: [FilteringTextInputFormatter.digitsOnly], + inputFormatters: [ + FilteringTextInputFormatter.digitsOnly + ], textDirection: TextDirection.rtl, suffixIcon: Container( width: 110, alignment: Alignment.center, child: Text( 'Credits/1K Token', - style: TextStyle(color: customColors.weakTextColor, fontSize: 12), + style: TextStyle( + color: customColors.weakTextColor, + fontSize: 12), ), ), ), @@ -367,14 +396,18 @@ class _AdminModelEditPageState extends State { hintText: 'Optional', showCounter: false, keyboardType: TextInputType.number, - inputFormatters: [FilteringTextInputFormatter.digitsOnly], + inputFormatters: [ + FilteringTextInputFormatter.digitsOnly + ], textDirection: TextDirection.rtl, suffixIcon: Container( width: 110, alignment: Alignment.center, child: Text( 'Credits/Request', - style: TextStyle(color: customColors.weakTextColor, fontSize: 12), + style: TextStyle( + color: customColors.weakTextColor, + fontSize: 12), ), ), ), @@ -383,17 +416,22 @@ class _AdminModelEditPageState extends State { customColors: customColors, controller: maxContextController, textAlignVertical: TextAlignVertical.top, - hintText: 'Subtract the expected output length from the maximum context.', + hintText: + 'Subtract the expected output length from the maximum context.', showCounter: false, keyboardType: TextInputType.number, - inputFormatters: [FilteringTextInputFormatter.digitsOnly], + inputFormatters: [ + FilteringTextInputFormatter.digitsOnly + ], textDirection: TextDirection.rtl, suffixIcon: Container( width: 50, alignment: Alignment.center, child: Text( 'Token', - style: TextStyle(color: customColors.weakTextColor, fontSize: 12), + style: TextStyle( + color: customColors.weakTextColor, + fontSize: 12), ), ), ), @@ -401,7 +439,8 @@ class _AdminModelEditPageState extends State { ), for (var i = 0; i < providers.length; i++) Container( - margin: const EdgeInsets.only(bottom: 10, left: 5, right: 5), + margin: + const EdgeInsets.only(bottom: 10, left: 5, right: 5), child: Slidable( endActionPane: ActionPane( motion: const ScrollMotion(), @@ -414,13 +453,15 @@ class _AdminModelEditPageState extends State { icon: Icons.delete, onPressed: (_) { if (providers.length == 1) { - showErrorMessage('At least one channel is needed'); + showErrorMessage( + 'At least one channel is needed'); return; } openConfirmDialog( context, - AppLocale.confirmToDeleteRoom.getString(context), + AppLocale.confirmToDeleteRoom + .getString(context), () { setState(() { providers.removeAt(i); @@ -457,7 +498,8 @@ class _AdminModelEditPageState extends State { ...modelChannels .map( (e) => SelectorItem( - Text('${e.id == null ? '【System】' : ''}${e.name}'), + Text( + '${e.id == null ? '【System】' : ''}${e.name}'), e, ), ) @@ -499,14 +541,16 @@ class _AdminModelEditPageState extends State { type: QuickAlertType.info, text: 'When the model identifier corresponding to the channel does not match the ID here, calling the channel interface will automatically replace the model with the value configured here.', - confirmBtnText: AppLocale.gotIt.getString(context), + confirmBtnText: + AppLocale.gotIt.getString(context), showCancelBtn: false, ); }, child: Icon( Icons.help_outline, size: 16, - color: customColors.weakLinkColor?.withAlpha(150), + color: customColors.weakLinkColor + ?.withAlpha(150), ), ), ), @@ -562,15 +606,18 @@ class _AdminModelEditPageState extends State { showBeautyDialog( context, type: QuickAlertType.info, - text: 'Whether the current model supports visual capabilities.', - confirmBtnText: AppLocale.gotIt.getString(context), + text: + 'Whether the current model supports visual capabilities.', + confirmBtnText: + AppLocale.gotIt.getString(context), showCancelBtn: false, ); }, child: Icon( Icons.help_outline, size: 16, - color: customColors.weakLinkColor?.withAlpha(150), + color: customColors.weakLinkColor + ?.withAlpha(150), ), ), ], @@ -603,14 +650,16 @@ class _AdminModelEditPageState extends State { type: QuickAlertType.info, text: 'Whether to display a "New" icon next to the model to inform users that this is a new model.', - confirmBtnText: AppLocale.gotIt.getString(context), + confirmBtnText: + AppLocale.gotIt.getString(context), showCancelBtn: false, ); }, child: Icon( Icons.help_outline, size: 16, - color: customColors.weakLinkColor?.withAlpha(150), + color: customColors.weakLinkColor + ?.withAlpha(150), ), ), ], @@ -626,6 +675,48 @@ class _AdminModelEditPageState extends State { ), ], ), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Row( + children: [ + const Text( + 'Recommended', + style: TextStyle(fontSize: 16), + ), + const SizedBox(width: 5), + InkWell( + onTap: () { + showBeautyDialog( + context, + type: QuickAlertType.info, + text: + 'Whether to display a "Recommended" icon next to the model to inform users that this is a recommended model.', + confirmBtnText: + AppLocale.gotIt.getString(context), + showCancelBtn: false, + ); + }, + child: Icon( + Icons.help_outline, + size: 16, + color: customColors.weakLinkColor + ?.withAlpha(150), + ), + ), + ], + ), + CupertinoSwitch( + activeColor: customColors.linkColor, + value: isRecommended, + onChanged: (value) { + setState(() { + isRecommended = value; + }); + }, + ), + ], + ), Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ @@ -643,14 +734,16 @@ class _AdminModelEditPageState extends State { type: QuickAlertType.info, text: 'Restricted models refer to models that cannot be used in Chinese Mainland due to policy factors.', - confirmBtnText: AppLocale.gotIt.getString(context), + confirmBtnText: + AppLocale.gotIt.getString(context), showCancelBtn: false, ); }, child: Icon( Icons.help_outline, size: 16, - color: customColors.weakLinkColor?.withAlpha(150), + color: customColors.weakLinkColor + ?.withAlpha(150), ), ), ], @@ -708,7 +801,9 @@ class _AdminModelEditPageState extends State { color: customColors.weakLinkColor, fontSize: 15, icon: Icon( - showAdvancedOptions ? Icons.unfold_less : Icons.unfold_more, + showAdvancedOptions + ? Icons.unfold_less + : Icons.unfold_more, color: customColors.weakLinkColor, size: 15, ), @@ -725,8 +820,10 @@ class _AdminModelEditPageState extends State { title: AppLocale.save.getString(context), onPressed: onSubmit, icon: editLocked - ? const Icon(Icons.lock, color: Colors.white, size: 16) - : const Icon(Icons.lock_open, color: Colors.white, size: 16), + ? const Icon(Icons.lock, + color: Colors.white, size: 16) + : const Icon(Icons.lock_open, + color: Colors.white, size: 16), ), ), ], @@ -757,7 +854,9 @@ class _AdminModelEditPageState extends State { return; } - if (avatarUrl != null && (!avatarUrl!.startsWith('http://') && !avatarUrl!.startsWith('https://'))) { + if (avatarUrl != null && + (!avatarUrl!.startsWith('http://') && + !avatarUrl!.startsWith('https://'))) { final cancel = BotToast.showCustomLoading( toastBuilder: (cancel) { return const LoadingIndicator( @@ -768,7 +867,8 @@ class _AdminModelEditPageState extends State { ); try { - final res = await ImageUploader(widget.setting).upload(avatarUrl!, usage: 'avatar'); + final res = await ImageUploader(widget.setting) + .upload(avatarUrl!, usage: 'avatar'); avatarUrl = res.url; } catch (e) { showErrorMessage('Failed to upload avatar'); @@ -796,6 +896,7 @@ class _AdminModelEditPageState extends State { tagTextColor: tagTextColor, tagBgColor: tagBgColor, isNew: isNew, + isRecommend: isRecommended, ), status: modelEnabled ? 1 : 2, providers: ps, diff --git a/lib/page/admin/rooms.dart b/lib/page/admin/rooms.dart index 8acfeb61..1cfa907d 100644 --- a/lib/page/admin/rooms.dart +++ b/lib/page/admin/rooms.dart @@ -33,7 +33,9 @@ class AdminRoomsPage extends StatefulWidget { class _AdminRoomsPageState extends State { @override void initState() { - context.read().add(AdminRoomsLoadEvent(userId: widget.userId)); + context + .read() + .add(AdminRoomsLoadEvent(userId: widget.userId)); super.initState(); } @@ -45,7 +47,7 @@ class _AdminRoomsPageState extends State { appBar: AppBar( toolbarHeight: CustomSize.toolbarHeight, title: const Text( - '用户数字人列表', + 'Characters', style: TextStyle(fontSize: CustomSize.appBarTitleSize), ), centerTitle: true, @@ -57,14 +59,17 @@ class _AdminRoomsPageState extends State { child: RefreshIndicator( color: customColors.linkColor, onRefresh: () async { - context.read().add(AdminRoomsLoadEvent(userId: widget.userId)); + context + .read() + .add(AdminRoomsLoadEvent(userId: widget.userId)); }, displacement: 20, child: BlocConsumer( listener: (context, state) { if (state is AdminRoomOperationResult) { if (state.success) { - showSuccessMessage(AppLocale.operateSuccess.getString(context)); + showSuccessMessage( + AppLocale.operateSuccess.getString(context)); } else { showErrorMessage(AppLocale.operateFailed.getString(context)); } @@ -85,7 +90,8 @@ class _AdminRoomsPageState extends State { horizontal: 10, vertical: 5, ), - decoration: BoxDecoration(borderRadius: CustomSize.borderRadius), + decoration: BoxDecoration( + borderRadius: CustomSize.borderRadius), child: Material( borderRadius: CustomSize.borderRadius, color: customColors.columnBlockBackgroundColor, @@ -103,23 +109,31 @@ class _AdminRoomsPageState extends State { _buildAvatar(room), Expanded( child: Container( - padding: const EdgeInsets.symmetric(horizontal: 10), + padding: const EdgeInsets.symmetric( + horizontal: 10), child: Column( - crossAxisAlignment: CrossAxisAlignment.start, + crossAxisAlignment: + CrossAxisAlignment.start, children: [ Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, + mainAxisAlignment: + MainAxisAlignment + .spaceBetween, children: [ Expanded( child: Text( room.name, - overflow: TextOverflow.ellipsis, + overflow: + TextOverflow.ellipsis, ), ), Text( - humanTime(room.lastActiveTime), + humanTime( + room.lastActiveTime), style: TextStyle( - color: customColors.weakLinkColor?.withAlpha(65), + color: customColors + .weakLinkColor + ?.withAlpha(65), fontSize: 10, ), ), @@ -137,13 +151,15 @@ class _AdminRoomsPageState extends State { top: 0, child: Container( decoration: BoxDecoration( - color: customColors.backgroundContainerColor, + color: customColors + .backgroundContainerColor, borderRadius: const BorderRadius.only( topRight: CustomSize.radius, bottomLeft: CustomSize.radius, ), ), - padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 2), + padding: const EdgeInsets.symmetric( + horizontal: 8, vertical: 2), child: Text( AppLocale.groupChat.getString(context), style: TextStyle( @@ -174,7 +190,8 @@ class _AdminRoomsPageState extends State { } Widget _buildAvatar(RoomInServer room) { - if (room.members.length == 1 && (room.avatarUrl == null || room.avatarUrl == '')) { + if (room.members.length == 1 && + (room.avatarUrl == null || room.avatarUrl == '')) { room.avatarUrl = room.members[0]; } @@ -183,7 +200,8 @@ class _AdminRoomsPageState extends State { width: 70, height: 70, child: ClipRRect( - borderRadius: const BorderRadius.only(topLeft: CustomSize.radius, bottomLeft: CustomSize.radius), + borderRadius: const BorderRadius.only( + topLeft: CustomSize.radius, bottomLeft: CustomSize.radius), child: CachedNetworkImageEnhanced( imageUrl: imageURL(room.avatarUrl!, qiniuImageTypeAvatar), fit: BoxFit.fill, @@ -194,7 +212,8 @@ class _AdminRoomsPageState extends State { if (room.members.isNotEmpty) { return ClipRRect( - borderRadius: const BorderRadius.only(topLeft: CustomSize.radius, bottomLeft: CustomSize.radius), + borderRadius: const BorderRadius.only( + topLeft: CustomSize.radius, bottomLeft: CustomSize.radius), child: GroupAvatar( size: 70, avatars: room.members, @@ -206,7 +225,8 @@ class _AdminRoomsPageState extends State { text: room.name.split('、').join(' '), size: 70, backgroundColor: Colors.grey.withAlpha(100), - borderRadius: const BorderRadius.only(topLeft: CustomSize.radius, bottomLeft: CustomSize.radius), + borderRadius: const BorderRadius.only( + topLeft: CustomSize.radius, bottomLeft: CustomSize.radius), ); } } diff --git a/lib/page/chat/home.dart b/lib/page/chat/home.dart index ba422126..b9dfbdbc 100644 --- a/lib/page/chat/home.dart +++ b/lib/page/chat/home.dart @@ -90,7 +90,8 @@ class _HomePageState extends State { id: 'openai:gpt-4', supportVision: false, name: 'Chat-4', - avatarUrl: 'https://ssl.aicode.cc/ai-server/assets/avatar/gpt4-preview.png', + avatarUrl: + 'https://ssl.aicode.cc/ai-server/assets/avatar/gpt4-preview.png', ), ]; @@ -108,7 +109,8 @@ class _HomePageState extends State { /// 用于监听键盘事件,实现回车发送消息,Shift+Enter换行 late final FocusNode _focusNode = FocusNode( onKeyEvent: (node, event) { - if (!HardwareKeyboard.instance.isShiftPressed && event.logicalKey.keyLabel == 'Enter') { + if (!HardwareKeyboard.instance.isShiftPressed && + event.logicalKey.keyLabel == 'Enter') { if (event is KeyDownEvent) { onSubmit(context, _textController.text.trim()); } @@ -150,12 +152,14 @@ class _HomePageState extends State { Cache().boolGet(key: 'show_home_free_model_message').then((show) async { if (show) { final promotions = await APIServer().notificationPromotionEvents(); - if (promotions['chat_page'] == null || promotions['chat_page']!.isEmpty) { + if (promotions['chat_page'] == null || + promotions['chat_page']!.isEmpty) { return; } // 多个促销事件,则随机选择一个 - promotionEvent = promotions['chat_page']![Random().nextInt(promotions['chat_page']!.length)]; + promotionEvent = promotions['chat_page']![ + Random().nextInt(promotions['chat_page']!.length)]; } setState(() { @@ -240,7 +244,8 @@ class _HomePageState extends State { child: Scaffold( backgroundColor: Colors.transparent, body: BlocBuilder( - buildWhen: (previous, current) => current is ChatChatRecentHistoriesLoaded, + buildWhen: (previous, current) => + current is ChatChatRecentHistoriesLoaded, builder: (context, state) { if (state is ChatChatRecentHistoriesLoaded) { return SliverSingleComponent( @@ -256,7 +261,9 @@ class _HomePageState extends State { icon: const Icon(Icons.history), onPressed: () { context.push('/chat-chat/history').whenComplete(() { - context.read().add(ChatChatLoadRecentHistories()); + context + .read() + .add(ChatChatLoadRecentHistories()); }); }, ), @@ -270,7 +277,8 @@ class _HomePageState extends State { SliverStickyHeader( header: SafeArea( top: false, - child: buildChatComponents(customColors, context, state), + child: + buildChatComponents(customColors, context, state), ), sliver: SliverList( delegate: SliverChildBuilderDelegate( @@ -280,11 +288,13 @@ class _HomePageState extends State { top: false, bottom: false, child: Container( - margin: const EdgeInsets.only(top: 10, left: 15), + margin: + const EdgeInsets.only(top: 10, left: 15), child: Text( AppLocale.histories.getString(context), style: TextStyle( - color: customColors.weakTextColor?.withAlpha(100), + color: customColors.weakTextColor + ?.withAlpha(100), fontSize: 13, ), ), @@ -298,32 +308,41 @@ class _HomePageState extends State { bottom: false, child: GestureDetector( onTap: () { - context.push('/chat-chat/history').whenComplete(() { - context.read().add(ChatChatLoadRecentHistories()); + context + .push('/chat-chat/history') + .whenComplete(() { + context + .read() + .add(ChatChatLoadRecentHistories()); }); }, child: Container( alignment: Alignment.center, - margin: const EdgeInsets.only(top: 5, bottom: 15), + margin: const EdgeInsets.only( + top: 5, bottom: 15), child: Row( - mainAxisAlignment: MainAxisAlignment.center, + mainAxisAlignment: + MainAxisAlignment.center, children: [ Icon( Icons.keyboard_double_arrow_left, size: 12, - color: customColors.weakTextColor!.withAlpha(120), + color: customColors.weakTextColor! + .withAlpha(120), ), Text( "查看更多", style: TextStyle( fontSize: 12, - color: customColors.weakTextColor!.withAlpha(120), + color: customColors.weakTextColor! + .withAlpha(120), ), ), Icon( Icons.keyboard_double_arrow_right, size: 12, - color: customColors.weakTextColor!.withAlpha(120), + color: customColors.weakTextColor! + .withAlpha(120), ), ], ), @@ -343,14 +362,19 @@ class _HomePageState extends State { .push( '/chat-anywhere?chat_id=${state.histories[index - 1].id}&model=${state.histories[index - 1].model}&title=${state.histories[index - 1].title}') .whenComplete(() { - FocusScope.of(context).requestFocus(FocusNode()); - context.read().add(ChatChatLoadRecentHistories()); + FocusScope.of(context) + .requestFocus(FocusNode()); + context + .read() + .add(ChatChatLoadRecentHistories()); }); }, ), ); }, - childCount: state.histories.isNotEmpty ? state.histories.length + 1 : 0, + childCount: state.histories.isNotEmpty + ? state.histories.length + 1 + : 0, ), ), ), @@ -467,7 +491,8 @@ class _HomePageState extends State { customColors: customColors, maxLines: inputMaxLines, minLines: 6, - hintText: AppLocale.askMeAnyQuestion.getString(context), + hintText: + AppLocale.askMeAnyQuestion.getString(context), maxLength: 150000, showCounter: false, hintColor: customColors.textfieldHintDeepColor, @@ -484,7 +509,9 @@ class _HomePageState extends State { customColors, ), ), - if (selectedImageFiles.isNotEmpty && currentModel != null && currentModel!.model.supportVision) + if (selectedImageFiles.isNotEmpty && + currentModel != null && + currentModel!.model.supportVision) SizedBox( height: 110, child: ListView( @@ -524,8 +551,10 @@ class _HomePageState extends State { child: Container( padding: const EdgeInsets.all(3), decoration: BoxDecoration( - borderRadius: CustomSize.borderRadius, - color: customColors.chatRoomBackground, + borderRadius: + CustomSize.borderRadius, + color: customColors + .chatRoomBackground, ), child: Icon( Icons.close, @@ -548,10 +577,13 @@ class _HomePageState extends State { ), ), // 问题示例 - if (state.examples != null && state.examples!.isNotEmpty && state.histories.isEmpty) + if (state.examples != null && + state.examples!.isNotEmpty && + state.histories.isEmpty) Container( decoration: BoxDecoration(borderRadius: CustomSize.borderRadius), - padding: const EdgeInsets.only(top: 5, left: 10, right: 10, bottom: 3), + padding: + const EdgeInsets.only(top: 5, left: 10, right: 10, bottom: 3), margin: const EdgeInsets.all(10), height: 260, child: Column( @@ -580,7 +612,9 @@ class _HomePageState extends State { Expanded( child: ListView.separated( padding: const EdgeInsets.all(0), - itemCount: state.examples!.length > 4 ? 4 : state.examples!.length, + itemCount: state.examples!.length > 4 + ? 4 + : state.examples!.length, physics: const NeverScrollableScrollPhysics(), itemBuilder: (context, index) { return ListTextItem( @@ -596,7 +630,8 @@ class _HomePageState extends State { }, separatorBuilder: (BuildContext context, int index) { return Divider( - color: customColors.chatExampleItemText?.withAlpha(20), + color: + customColors.chatExampleItemText?.withAlpha(20), ); }, ), @@ -605,7 +640,8 @@ class _HomePageState extends State { alignment: Alignment.centerRight, child: TextButton( style: ButtonStyle( - overlayColor: WidgetStateProperty.all(Colors.transparent), + overlayColor: + WidgetStateProperty.all(Colors.transparent), ), onPressed: () { setState(() { @@ -679,19 +715,23 @@ class _HomePageState extends State { maxLines: 2, ), ), - if (promotionEvent!.clickButtonType != PromotionEventClickButtonType.none && + if (promotionEvent!.clickButtonType != + PromotionEventClickButtonType.none && promotionEvent!.clickValue != null && promotionEvent!.clickValue!.isNotEmpty) InkWell( onTap: () { switch (promotionEvent!.clickButtonType) { case PromotionEventClickButtonType.url: - if (promotionEvent!.clickValue != null && promotionEvent!.clickValue!.isNotEmpty) { - launchUrlString(promotionEvent!.clickValue!, mode: LaunchMode.externalApplication); + if (promotionEvent!.clickValue != null && + promotionEvent!.clickValue!.isNotEmpty) { + launchUrlString(promotionEvent!.clickValue!, + mode: LaunchMode.externalApplication); } break; case PromotionEventClickButtonType.inAppRoute: - if (promotionEvent!.clickValue != null && promotionEvent!.clickValue!.isNotEmpty) { + if (promotionEvent!.clickValue != null && + promotionEvent!.clickValue!.isNotEmpty) { context.push(promotionEvent!.clickValue!); } @@ -704,7 +744,8 @@ class _HomePageState extends State { Text( '详情', style: TextStyle( - color: stringToColor(promotionEvent!.clickButtonColor ?? 'FFFFFFFF'), + color: stringToColor( + promotionEvent!.clickButtonColor ?? 'FFFFFFFF'), fontSize: 14, ), ), @@ -712,7 +753,8 @@ class _HomePageState extends State { Icon( Icons.keyboard_double_arrow_right, size: 16, - color: stringToColor(promotionEvent!.clickButtonColor ?? 'FFFFFFFF'), + color: stringToColor( + promotionEvent!.clickButtonColor ?? 'FFFFFFFF'), ), ], ), @@ -765,19 +807,23 @@ class _HomePageState extends State { // 上传图片 HapticFeedbackHelper.mediumImpact(); if (selectedImageFiles.length >= 4) { - showSuccessMessage(AppLocale.uploadImageLimit4.getString(context)); + showSuccessMessage( + AppLocale.uploadImageLimit4.getString(context)); return; } - FilePickerResult? result = await FilePicker.platform.pickFiles( + FilePickerResult? result = + await FilePicker.platform.pickFiles( type: FileType.image, allowMultiple: true, ); if (result != null && result.files.isNotEmpty) { final files = selectedImageFiles; - files.addAll(result.files.map((e) => FileUpload(file: e)).toList()); + files.addAll( + result.files.map((e) => FileUpload(file: e)).toList()); setState(() { - selectedImageFiles = files.sublist(0, files.length > 4 ? 4 : files.length); + selectedImageFiles = + files.sublist(0, files.length > 4 ? 4 : files.length); }); } }, @@ -797,7 +843,8 @@ class _HomePageState extends State { child: Icon( Icons.send, color: _textController.text.trim().isNotEmpty - ? customColors.linkColor ?? const Color.fromARGB(255, 70, 165, 73) + ? customColors.linkColor ?? + const Color.fromARGB(255, 70, 165, 73) : customColors.chatInputPanelText, size: 26, ), @@ -867,7 +914,9 @@ class ChatHistoryItem extends StatelessWidget { context, AppLocale.confirmDelete.getString(context), () { - context.read().add(ChatChatDeleteHistory(history.id!)); + context + .read() + .add(ChatChatDeleteHistory(history.id!)); }, danger: true, ); @@ -880,8 +929,10 @@ class ChatHistoryItem extends StatelessWidget { borderRadius: CustomSize.borderRadius, child: InkWell( child: ListTile( - contentPadding: const EdgeInsets.symmetric(horizontal: 10, vertical: 5), - shape: RoundedRectangleBorder(borderRadius: CustomSize.borderRadius), + contentPadding: + const EdgeInsets.symmetric(horizontal: 10, vertical: 5), + shape: + RoundedRectangleBorder(borderRadius: CustomSize.borderRadius), title: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ diff --git a/lib/page/chat/home_chat_history.dart b/lib/page/chat/home_chat_history.dart index 8bdb59ab..33b1a8ab 100644 --- a/lib/page/chat/home_chat_history.dart +++ b/lib/page/chat/home_chat_history.dart @@ -19,7 +19,8 @@ class HomeChatHistoryPage extends StatefulWidget { final SettingRepository setting; final ChatMessageRepository chatMessageRepo; - const HomeChatHistoryPage({super.key, required this.setting, required this.chatMessageRepo}); + const HomeChatHistoryPage( + {super.key, required this.setting, required this.chatMessageRepo}); @override State createState() => _HomeChatHistoryPageState(); @@ -88,7 +89,8 @@ class _HomeChatHistoryPageState extends State { ), Expanded( child: BlocListener( - listenWhen: (previous, current) => current is ChatChatRecentHistoriesLoaded, + listenWhen: (previous, current) => + current is ChatChatRecentHistoriesLoaded, listener: (context, state) { if (state is ChatChatRecentHistoriesLoaded) { datasource.refresh(false, keyword); @@ -108,10 +110,14 @@ class _HomeChatHistoryPageState extends State { customColors: customColors, onTap: () { context - .push('/chat-anywhere?chat_id=${item.id}&model=${item.model}&title=${item.title}') + .push( + '/chat-anywhere?chat_id=${item.id}&model=${item.model}&title=${item.title}') .whenComplete(() { - FocusScope.of(context).requestFocus(FocusNode()); - context.read().add(ChatChatLoadRecentHistories()); + FocusScope.of(context) + .requestFocus(FocusNode()); + context + .read() + .add(ChatChatLoadRecentHistories()); }); }, ); diff --git a/lib/page/component/account_quota_card.dart b/lib/page/component/account_quota_card.dart index 11d01561..42a8c43d 100644 --- a/lib/page/component/account_quota_card.dart +++ b/lib/page/component/account_quota_card.dart @@ -2,6 +2,7 @@ import 'package:askaide/helper/ability.dart'; import 'package:askaide/lang/lang.dart'; import 'package:askaide/page/component/credit.dart'; import 'package:askaide/page/component/enhanced_button.dart'; +import 'package:askaide/page/component/theme/custom_size.dart'; import 'package:askaide/page/component/theme/custom_theme.dart'; import 'package:askaide/repo/api/user.dart'; import 'package:flutter/material.dart'; @@ -13,14 +14,17 @@ class AccountQuotaCard extends StatelessWidget { final UserInfo? userInfo; final VoidCallback? onPaymentReturn; final bool noBorder; - const AccountQuotaCard({super.key, this.userInfo, this.onPaymentReturn, this.noBorder = false}); + const AccountQuotaCard( + {super.key, this.userInfo, this.onPaymentReturn, this.noBorder = false}); @override Widget build(BuildContext context) { final customColors = Theme.of(context).extension()!; return Container( - margin: noBorder ? null : const EdgeInsets.symmetric(horizontal: 15, vertical: 10), + margin: noBorder + ? null + : const EdgeInsets.symmetric(horizontal: 15, vertical: 10), height: 120, child: Container( padding: noBorder @@ -34,80 +38,77 @@ class AccountQuotaCard extends StatelessWidget { horizontal: 20, vertical: 30, ), - child: Column( - crossAxisAlignment: CrossAxisAlignment.end, + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + crossAxisAlignment: CrossAxisAlignment.start, children: [ - Row( - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - Text( - AppLocale.usage.getString(context), - style: const TextStyle( - fontSize: 22, - color: Colors.white, - ), - ), - const SizedBox(width: 5), - InkWell( - onTap: () { - launchUrl( - Uri.parse('https://ai.aicode.cc/zhihuiguo.html'), - ); - }, - child: const Icon( - Icons.help, - size: 16, - color: Color.fromARGB(129, 220, 220, 220), - ), - ), - ], - ), - const SizedBox(height: 15), - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Row( - crossAxisAlignment: CrossAxisAlignment.end, - children: [ - if (userInfo != null) - Credit( - count: userInfo!.quota.quotaRemain(), - ) - else - const Text('-'), - const SizedBox(width: 5), - if (userInfo != null) + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Text( + AppLocale.usage.getString(context), + style: TextStyle( + fontSize: 18, + color: customColors.backgroundInvertedColor, + ), + ), + const SizedBox(width: 5), InkWell( onTap: () { - context.push('/quota-usage-statistics'); + launchUrl( + Uri.parse('https://ai.aicode.cc/zhihuiguo.html'), + ); }, - child: Text( - AppLocale.creditsUsage.getString(context), - style: const TextStyle( - fontSize: 12, - color: Color.fromARGB(129, 220, 220, 220), - ), + child: Icon( + Icons.help, + size: 14, + color: customColors.weakTextColor?.withAlpha(150), ), ), - ], - ), - if (Ability().enablePayment) - EnhancedButton( - onPressed: () { - context.push('/payment').whenComplete(() { - if (onPaymentReturn != null) { - onPaymentReturn!(); - } - }); - }, - title: AppLocale.buy.getString(context), - backgroundColor: customColors.linkColor, - width: 70, - height: 35, - fontSize: 14, + ], + ), + const SizedBox(height: 15), + Row( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + if (userInfo != null) + InkWell( + onTap: () { + context.push('/quota-usage-statistics'); + }, + borderRadius: CustomSize.borderRadiusAll, + child: Credit( + count: userInfo!.quota.quotaRemain(), + color: customColors.backgroundInvertedColor, + ), + ) + else + const Text('-'), + const SizedBox(width: 5), + ], ), - ], + ], + ), ), + if (Ability().enablePayment) + EnhancedButton( + onPressed: () { + context.push('/payment').whenComplete(() { + if (onPaymentReturn != null) { + onPaymentReturn!(); + } + }); + }, + title: AppLocale.buy.getString(context), + backgroundColor: customColors.linkColor, + width: 70, + height: 35, + fontSize: 14, + ), ], ), ), diff --git a/lib/page/component/model_item.dart b/lib/page/component/model_item.dart index 3d9b5f33..d68b6326 100644 --- a/lib/page/component/model_item.dart +++ b/lib/page/component/model_item.dart @@ -8,6 +8,7 @@ import 'package:askaide/page/component/random_avatar.dart'; import 'package:askaide/page/component/theme/custom_size.dart'; import 'package:askaide/page/component/theme/custom_theme.dart'; import 'package:askaide/repo/model/model.dart'; +import 'package:auto_size_text/auto_size_text.dart'; import 'package:flutter/material.dart'; import 'package:flutter_localization/flutter_localization.dart'; import 'package:quickalert/models/quickalert_type.dart'; @@ -36,6 +37,56 @@ class _ModelItemState extends State { @override Widget build(BuildContext context) { final customColors = Theme.of(context).extension()!; + + var tags = []; + + // Collect all unique tags from models + var uniqueTags = {}; + for (var model in widget.models) { + if (model.tag != null) { + uniqueTags.addAll(model.tag!.split(',').where((e) => e.isNotEmpty)); + } + + if (model.isRecommend) { + uniqueTags.add(AppLocale.recommendTag.getString(context)); + } + + if (model.isNew) { + uniqueTags.add(AppLocale.newTag.getString(context)); + } + + if (model.supportVision) { + uniqueTags.add(AppLocale.visionTag.getString(context)); + } + + if (model.modelPrice.isFree) { + uniqueTags.add(AppLocale.free.getString(context)); + } + } + + // Create tag widgets + tags = uniqueTags.map((tag) { + return InkWell( + onTap: () { + setState(() { + selectedTag = selectedTag == tag ? '' : tag; + }); + }, + child: Container( + padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 5), + child: buildTag( + customColors, + tag, + tagBgColor: selectedTag == tag + ? colorToString(customColors.linkColor ?? Colors.green) + : colorToString(customColors.tagsBackground ?? Colors.grey), + tagTextColor: selectedTag == tag ? 'FFFFFFFF' : null, + tagFontSize: 12, + ), + ), + ); + }).toList(); + return widget.models.isNotEmpty ? Column( children: [ @@ -56,22 +107,21 @@ class _ModelItemState extends State { isDense: true, border: InputBorder.none, ), - onChanged: (value) => setState(() => keyword = value.toLowerCase()), + onChanged: (value) => + setState(() => keyword = value.toLowerCase()), ), ), + + // Tags + if (tags.isNotEmpty) + Container( + padding: const EdgeInsets.only(left: 5, right: 5, bottom: 5), + child: Row(children: tags), + ), + Expanded( child: Builder(builder: (context) { - final models = keyword.isEmpty - ? widget.models - : widget.models.where((e) { - var matchText = - e.name + (e.description ?? '') + (e.shortName ?? '') + (e.tag ?? '') + (e.category); - if (e.supportVision) { - matchText += 'vision视觉看图'; - } - - return matchText.toLowerCase().contains(keyword); - }).toList(); + final models = searchModels(); return ListView.separated( itemCount: models.length, itemBuilder: (context, i) { @@ -88,13 +138,18 @@ class _ModelItemState extends State { )); } if (item.tag != null) { - var tt = item.tag!.split(",").where((e) => e.isNotEmpty).toList(); + var tt = item.tag! + .split(",") + .where((e) => e.isNotEmpty) + .toList(); for (var i = 0; i < tt.length; i++) { tags.add(buildTag( customColors, tt[i], - tagTextColor: i == 0 ? item.tagTextColor : 'FFFFFFFF', - tagBgColor: i == 0 ? item.tagBgColor : modelTagColorSeq(i), + tagTextColor: + i == 0 ? item.tagTextColor : 'FFFFFFFF', + tagBgColor: + i == 0 ? item.tagBgColor : modelTagColorSeq(i), )); } } @@ -119,11 +174,15 @@ class _ModelItemState extends State { List separators = []; if (i == 0 && models[i].category != '') { - separators.add(buildCategory(customColors, item.category)); - } else if (i > 0 && models[i].category != models[i - 1].category) { + separators + .add(buildCategory(customColors, item.category)); + } else if (i > 0 && + models[i].category != models[i - 1].category) { separators.add(buildCategory( customColors, - item.category == '' ? AppLocale.others.getString(context) : item.category, + item.category == '' + ? AppLocale.others.getString(context) + : item.category, )); } @@ -136,51 +195,71 @@ class _ModelItemState extends State { alignment: Alignment.center, padding: const EdgeInsets.symmetric(vertical: 5), decoration: BoxDecoration( - color: widget.initValue == item.uid() ? customColors.dialogBackgroundColor : null, + color: widget.initValue == item.uid() + ? customColors.dialogBackgroundColor + : null, borderRadius: CustomSize.borderRadius, ), child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, + mainAxisAlignment: + MainAxisAlignment.spaceBetween, mainAxisSize: MainAxisSize.min, children: [ if (item.avatarUrl != null) ...[ - _buildAvatar(avatarUrl: item.avatarUrl, size: 50), + _buildAvatar( + avatarUrl: item.avatarUrl, size: 50), const SizedBox(width: 10), ], Expanded( child: Column( - crossAxisAlignment: CrossAxisAlignment.start, + crossAxisAlignment: + CrossAxisAlignment.start, children: [ Row( children: [ Expanded( child: Container( alignment: - item.avatarUrl != null ? Alignment.centerLeft : Alignment.center, - child: Text( + item.avatarUrl != null + ? Alignment.centerLeft + : Alignment.center, + child: AutoSizeText( item.name, - overflow: TextOverflow.ellipsis, + minFontSize: 10, + maxFontSize: 15, + maxLines: 1, style: TextStyle( - fontSize: 15, - color: - widget.initValue == item.uid() ? customColors.linkColor : null, - fontWeight: widget.initValue == item.uid() ? FontWeight.bold : null, + color: widget.initValue == + item.uid() + ? customColors.linkColor + : null, + fontWeight: + widget.initValue == + item.uid() + ? FontWeight.bold + : null, ), ), ), ), - if (tags.length <= 3) ...formatTags(tags), + if (tags.length <= 3) + ...formatTags(tags), ], ), if (tags.length > 3) SingleChildScrollView( scrollDirection: Axis.horizontal, child: Container( - margin: const EdgeInsets.symmetric(vertical: 5), - child: Row(children: formatTags(tags)), + margin: + const EdgeInsets.symmetric( + vertical: 5), + child: Row( + children: formatTags(tags)), ), ), - if (!modelPrice.isFree) buildPriceBlock(customColors, item, modelPrice), + if (!modelPrice.isFree) + buildPriceBlock( + customColors, item, modelPrice), // if (item.description != null && item.description != '') // Text( @@ -204,7 +283,8 @@ class _ModelItemState extends State { widget.onSelected(item); }, onLongPress: () { - if (item.description == null || item.description == '') { + if (item.description == null || + item.description == '') { return; } @@ -212,7 +292,8 @@ class _ModelItemState extends State { context, type: QuickAlertType.info, text: item.description, - confirmBtnText: AppLocale.gotIt.getString(context), + confirmBtnText: + AppLocale.gotIt.getString(context), showCancelBtn: false, ); }, @@ -243,7 +324,67 @@ class _ModelItemState extends State { ); } - Widget buildPriceBlock(CustomColors customColors, Model model, ModelPrice item) { + String selectedTag = ''; + + List searchModels() { + var models = keyword.isEmpty + ? widget.models + : widget.models.where((e) { + var matchText = e.name + + (e.description ?? '') + + (e.shortName ?? '') + + (e.tag ?? '') + + (e.category); + if (e.supportVision) { + matchText += 'vision视觉看图'; + } + if (e.isNew) { + matchText += 'new新'; + } + + if (e.isRecommend) { + matchText += 'recommend推荐'; + } + + if (e.modelPrice.isFree) { + matchText += 'free免费'; + } + + return matchText.toLowerCase().contains(keyword); + }).toList(); + + if (selectedTag.isNotEmpty) { + models = models.where((e) { + var tags = []; + if (e.tag != null) { + tags = e.tag!.split(',').where((e) => e.isNotEmpty).toList(); + } + + if (e.isRecommend) { + tags.add(AppLocale.recommendTag.getString(context)); + } + + if (e.isNew) { + tags.add(AppLocale.newTag.getString(context)); + } + + if (e.supportVision) { + tags.add(AppLocale.visionTag.getString(context)); + } + + if (e.modelPrice.isFree) { + tags.add(AppLocale.free.getString(context)); + } + + return tags.contains(selectedTag); + }).toList(); + } + + return models; + } + + Widget buildPriceBlock( + CustomColors customColors, Model model, ModelPrice item) { if (item.isFree) { return const SizedBox(); } @@ -255,7 +396,8 @@ class _ModelItemState extends State { } if (item.request > 0) { - priceText += '${priceText == '' ? '' : ', '}${AppLocale.perRequest.getString(context)} ¢${item.request}'; + priceText += + '${priceText == '' ? '' : ', '}${AppLocale.perRequest.getString(context)} ¢${item.request}'; } return Row( @@ -266,8 +408,9 @@ class _ModelItemState extends State { overflow: TextOverflow.ellipsis, style: TextStyle( fontSize: 10, - color: - widget.initValue == model.uid() ? customColors.linkColor : customColors.weakTextColor?.withAlpha(150), + color: widget.initValue == model.uid() + ? customColors.linkColor + : customColors.weakTextColor?.withAlpha(150), ), ), if (item.hasNote) ...[ @@ -327,11 +470,13 @@ class _ModelItemState extends State { String tag, { String? tagTextColor, String? tagBgColor, + double? tagFontSize, }) { return Container( decoration: BoxDecoration( color: tagBgColor != null - ? stringToColor(tagBgColor, defaultColor: customColors.tagsBackgroundHover ?? Colors.grey) + ? stringToColor(tagBgColor, + defaultColor: customColors.tagsBackgroundHover ?? Colors.grey) : customColors.tagsBackgroundHover, borderRadius: CustomSize.borderRadius, ), @@ -342,9 +487,10 @@ class _ModelItemState extends State { child: Text( tag, style: TextStyle( - fontSize: 8, + fontSize: tagFontSize ?? 8, color: tagTextColor != null - ? stringToColor(tagTextColor, defaultColor: customColors.tagsText ?? Colors.white) + ? stringToColor(tagTextColor, + defaultColor: customColors.tagsText ?? Colors.white) : customColors.tagsText, ), ), diff --git a/lib/page/drawer.dart b/lib/page/drawer.dart index c6dd5fe5..3c4cc704 100644 --- a/lib/page/drawer.dart +++ b/lib/page/drawer.dart @@ -3,7 +3,6 @@ import 'package:askaide/bloc/chat_chat_bloc.dart'; import 'package:askaide/helper/platform.dart'; import 'package:askaide/lang/lang.dart'; import 'package:askaide/page/component/account_quota_card.dart'; -import 'package:askaide/page/component/image.dart'; import 'package:askaide/page/component/theme/custom_theme.dart'; import 'package:askaide/repo/api/user.dart'; import 'package:flutter/material.dart'; @@ -45,21 +44,21 @@ class _LeftDrawerState extends State { child: Column( children: [ SizedBox( - height: 170, + height: 150, child: DrawerHeader( padding: PlatformTool.isMacOS() ? const EdgeInsets.only(top: kToolbarHeight) : const EdgeInsets.all(0), margin: const EdgeInsets.all(0), - decoration: BoxDecoration( - color: Colors.white, - image: DecorationImage( - image: CachedNetworkImageProviderEnhanced( - "https://ssl.aicode.cc/ai-server/assets/quota-card-bg.webp-thumb1000", - ), - fit: BoxFit.cover, - ), - ), + // decoration: BoxDecoration( + // color: Colors.white, + // image: DecorationImage( + // image: CachedNetworkImageProviderEnhanced( + // "https://ssl.aicode.cc/ai-server/assets/quota-card-bg.webp-thumb1000", + // ), + // fit: BoxFit.cover, + // ), + // ), child: BlocBuilder( builder: (_, state) { UserInfo? userInfo; @@ -72,7 +71,9 @@ class _LeftDrawerState extends State { noBorder: true, onPaymentReturn: () { if (userInfo != null) { - context.read().add(AccountLoadEvent(cache: false)); + context + .read() + .add(AccountLoadEvent(cache: false)); } }, ); @@ -82,7 +83,8 @@ class _LeftDrawerState extends State { ), const SizedBox(height: 15), BlocBuilder( - buildWhen: (previous, current) => current is ChatChatRecentHistoriesLoaded, + buildWhen: (previous, current) => + current is ChatChatRecentHistoriesLoaded, builder: (_, state) { if (state is ChatChatRecentHistoriesLoaded) { return ListView.builder( @@ -93,7 +95,8 @@ class _LeftDrawerState extends State { itemBuilder: (context, index) { final item = state.histories[index]; return ListTile( - leading: const Icon(Icons.question_answer_outlined), + leading: + const Icon(Icons.question_answer_outlined), title: Text( item.title ?? 'Unknown', maxLines: 1, @@ -116,7 +119,9 @@ class _LeftDrawerState extends State { title: Text(AppLocale.moreHistories.getString(context)), onTap: () { context.push('/chat-chat/history').whenComplete(() { - context.read().add(ChatChatLoadRecentHistories()); + context + .read() + .add(ChatChatLoadRecentHistories()); }); }, ), @@ -201,7 +206,8 @@ class _LeftDrawerState extends State { mode: LaunchMode.externalApplication, ); }, - child: Image.asset('assets/app-256-transparent.png', width: 25), + child: Image.asset('assets/app-256-transparent.png', + width: 25), ), GestureDetector( onTap: () { diff --git a/lib/page/home.dart b/lib/page/home.dart index c7c76f90..348cddde 100644 --- a/lib/page/home.dart +++ b/lib/page/home.dart @@ -34,6 +34,7 @@ import 'package:askaide/repo/api_server.dart'; import 'package:askaide/repo/model/message.dart'; import 'package:askaide/repo/model/misc.dart'; import 'package:askaide/repo/settings_repo.dart'; +import 'package:auto_size_text/auto_size_text.dart'; import 'package:bot_toast/bot_toast.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; @@ -63,7 +64,8 @@ class _NewHomePageState extends State { // 输入框是否可编辑 final ValueNotifier enableInput = ValueNotifier(true); // 音频播放器控制器 - final AudioPlayerController audioPlayerController = AudioPlayerController(useRemoteAPI: true); + final AudioPlayerController audioPlayerController = + AudioPlayerController(useRemoteAPI: true); // 聊天室 ID,当没有值时,会在第一个聊天消息发送后自动设置新值 int? chatId; @@ -111,7 +113,9 @@ class _NewHomePageState extends State { )); // 查询最近聊天记录 - context.read().add(ChatMessageGetRecentEvent(chatHistoryId: chatId)); + context + .read() + .add(ChatMessageGetRecentEvent(chatHistoryId: chatId)); } } @@ -274,40 +278,24 @@ class _NewHomePageState extends State { width: MediaQuery.of(context).size.width / 2, child: Column( children: [ - // BlocBuilder( - // buildWhen: (previous, current) => current is ChatMessagesLoaded, - // builder: (context, state) { - // if (state is ChatMessagesLoaded) { - // return Text( - // state.chatHistory == null || state.chatHistory!.title == null - // ? AppLocale.chatAnywhere.getString(context) - // : state.chatHistory!.title!, - // overflow: TextOverflow.ellipsis, - // maxLines: 1, - // style: const TextStyle( - // fontSize: CustomSize.appBarTitleSize, - // ), - // ); - // } - - // return Text( - // AppLocale.chatAnywhere.getString(context), - // overflow: TextOverflow.ellipsis, - // maxLines: 1, - // style: const TextStyle(fontSize: CustomSize.appBarTitleSize), - // ); - // }, - // ), - Row( mainAxisAlignment: MainAxisAlignment.center, + mainAxisSize: MainAxisSize.min, children: [ - Text( - selectedModel != null ? selectedModel!.name : AppLocale.selectModel.getString(context), - style: TextStyle( - fontSize: CustomSize.appBarTitleSize, - color: customColors.backgroundInvertedColor, - fontWeight: FontWeight.bold, + Flexible( + child: AutoSizeText( + selectedModel != null + ? selectedModel!.name + : AppLocale.selectModel.getString(context), + maxFontSize: 15, + minFontSize: 12, + maxLines: 1, + overflow: TextOverflow.ellipsis, + style: TextStyle( + fontSize: CustomSize.appBarTitleSize, + color: customColors.backgroundInvertedColor, + fontWeight: FontWeight.bold, + ), ), ), const SizedBox(width: 3), @@ -446,7 +434,9 @@ class _NewHomePageState extends State { label: AppLocale.stopOutput.getString(context), onPressed: () { HapticFeedbackHelper.mediumImpact(); - context.read().add(ChatMessageStopEvent()); + context + .read() + .add(ChatMessageStopEvent()); }, ), ), @@ -474,12 +464,15 @@ class _NewHomePageState extends State { enableImageUpload = currentModelV2?.supportVision ?? false; } else { var model = state.chatHistory?.model ?? room.room.model; - final cur = supportModels.where((e) => e.id == model).firstOrNull; + final cur = + supportModels.where((e) => e.id == model).firstOrNull; enableImageUpload = cur?.supportVision ?? false; } } - enableImageUpload = selectedModel == null ? enableImageUpload : (selectedModel?.supportVision ?? false); + enableImageUpload = selectedModel == null + ? enableImageUpload + : (selectedModel?.supportVision ?? false); return ChatInput( enableNotifier: enableInput, @@ -493,7 +486,8 @@ class _NewHomePageState extends State { selectedImageFiles = files; }); }, - selectedImageFiles: enableImageUpload ? selectedImageFiles : [], + selectedImageFiles: + enableImageUpload ? selectedImageFiles : [], hintText: AppLocale.askMeAnyQuestion.getString(context), onVoiceRecordTappedEvent: () { audioPlayerController.stop(); @@ -507,7 +501,8 @@ class _NewHomePageState extends State { ), // 选择模式工具栏 - if (chatPreviewController.selectMode) SelectModeToolbar(chatPreviewController: chatPreviewController), + if (chatPreviewController.selectMode) + SelectModeToolbar(chatPreviewController: chatPreviewController), ], ); } @@ -540,12 +535,15 @@ class _NewHomePageState extends State { } if (e.avatarUrl == null || e.senderName == null) { - if (loadedState.chatHistory != null && loadedState.chatHistory!.model != null) { + if (loadedState.chatHistory != null && + loadedState.chatHistory!.model != null) { if (currentModelV2 != null) { e.senderName = currentModelV2!.name; e.avatarUrl = currentModelV2!.avatarUrl; } else { - final mod = supportModels.where((e) => e.id == loadedState.chatHistory!.model!).firstOrNull; + final mod = supportModels + .where((e) => e.id == loadedState.chatHistory!.model!) + .firstOrNull; if (mod != null) { e.senderName = mod.shortName; e.avatarUrl = mod.avatarUrl; @@ -554,7 +552,9 @@ class _NewHomePageState extends State { } } - final stateMessage = room.states[widget.stateManager.getKey(e.roomId ?? 0, e.id ?? 0)] ?? MessageState(); + final stateMessage = + room.states[widget.stateManager.getKey(e.roomId ?? 0, e.id ?? 0)] ?? + MessageState(); return MessageWithState(e, stateMessage); }).toList(); @@ -595,9 +595,11 @@ class _NewHomePageState extends State { }, onResetContext: () => handleResetContext(context), onResentEvent: (message, index) { - scrollController.animateTo(0, duration: const Duration(milliseconds: 500), curve: Curves.easeOut); + scrollController.animateTo(0, + duration: const Duration(milliseconds: 500), curve: Curves.easeOut); - handleSubmit(message.text, messagetType: message.type, index: index, isResent: true); + handleSubmit(message.text, + messagetType: message.type, index: index, isResent: true); }, onSpeakEvent: (message) { audioPlayerController.playAudio(message.text); @@ -675,7 +677,10 @@ class _NewHomePageState extends State { model: selectedModel?.id, type: messagetType, chatHistoryId: chatId, - images: selectedImageFiles.where((e) => e.uploaded).map((e) => e.url!).toList(), + images: selectedImageFiles + .where((e) => e.uploaded) + .map((e) => e.url!) + .toList(), ), index: index, isResent: isResent, @@ -683,6 +688,8 @@ class _NewHomePageState extends State { ); // ignore: use_build_context_synchronously - context.read().add(RoomLoadEvent(chatAnywhereRoomId, cascading: false)); + context + .read() + .add(RoomLoadEvent(chatAnywhereRoomId, cascading: false)); } } diff --git a/lib/page/setting/destroy_account.dart b/lib/page/setting/destroy_account.dart index 2191e94b..3da5deec 100644 --- a/lib/page/setting/destroy_account.dart +++ b/lib/page/setting/destroy_account.dart @@ -27,7 +27,8 @@ class DestroyAccountScreen extends StatefulWidget { class _DestroyAccountScreenState extends State { final TextEditingController _passwordController = TextEditingController(); - final TextEditingController _verificationCodeController = TextEditingController(); + final TextEditingController _verificationCodeController = + TextEditingController(); String verifyCodeId = ''; @@ -61,12 +62,13 @@ class _DestroyAccountScreenState extends State { children: [ const MessageBox( message: - '请注意,注销账号后:\n1. 您的数据将被清空,包括数字人、创作岛历史纪录、充值数据、智慧果使用明细等全部数据;\n2. 您未使用完的智慧果将会被销毁,无法继续使用,无法退回;\n3. 注销操作不可逆,一旦账号注销,所有被删除数据均无法恢复。', + '请注意,注销账号后:\n1. 您的数据将被清空,包括角色、创作岛历史纪录、充值数据、智慧果使用明细等全部数据;\n2. 您未使用完的智慧果将会被销毁,无法继续使用,无法退回;\n3. 注销操作不可逆,一旦账号注销,所有被删除数据均无法恢复。', type: MessageBoxType.warning, ), const SizedBox(height: 15), ColumnBlock( - padding: const EdgeInsets.only(top: 20, left: 10, right: 10, bottom: 20), + padding: const EdgeInsets.only( + top: 20, left: 10, right: 10, bottom: 20), children: [ VerifyCodeInput( inColumnBlock: false, @@ -87,7 +89,8 @@ class _DestroyAccountScreenState extends State { Container( height: 45, width: double.infinity, - decoration: BoxDecoration(color: Colors.red, borderRadius: CustomSize.borderRadius), + decoration: BoxDecoration( + color: Colors.red, borderRadius: CustomSize.borderRadius), child: TextButton( onPressed: onDestroySubmit, child: Text( diff --git a/lib/repo/api/admin/models.dart b/lib/repo/api/admin/models.dart index 938b76e3..258cd1c0 100644 --- a/lib/repo/api/admin/models.dart +++ b/lib/repo/api/admin/models.dart @@ -35,7 +35,9 @@ class AdminModel { avatarUrl: json['avatar_url'], status: json['status'], meta: json['meta'] != null ? AdminModelMeta.fromJson(json['meta']) : null, - providers: ((json['providers'] ?? []) as List).map((e) => AdminModelProvider.fromJson(e)).toList(), + providers: ((json['providers'] ?? []) as List) + .map((e) => AdminModelProvider.fromJson(e)) + .toList(), ); } @@ -67,6 +69,7 @@ class AdminModelMeta { String? tagBgColor; bool? isNew; + bool? isRecommend; String? category; AdminModelMeta({ @@ -81,6 +84,7 @@ class AdminModelMeta { this.tagTextColor, this.tagBgColor, this.isNew, + this.isRecommend, this.category, }); @@ -97,6 +101,7 @@ class AdminModelMeta { tagTextColor: json['tag_text_color'], tagBgColor: json['tag_bg_color'], isNew: json['is_new'] ?? false, + isRecommend: json['is_recommend'] ?? false, category: json['category'], ); } @@ -114,6 +119,7 @@ class AdminModelMeta { 'tag_text_color': tagTextColor, 'tag_bg_color': tagBgColor, 'is_new': isNew, + 'is_recommend': isRecommend, 'category': category, }; } diff --git a/lib/repo/model/model.dart b/lib/repo/model/model.dart index f94e2323..3b23fdfe 100644 --- a/lib/repo/model/model.dart +++ b/lib/repo/model/model.dart @@ -16,6 +16,7 @@ class Model { String? tagTextColor; String? tagBgColor; bool isNew = false; + bool isRecommend = false; String category; bool isDefault; @@ -36,6 +37,7 @@ class Model { this.tagTextColor, this.tagBgColor, this.isNew = false, + this.isRecommend = false, this.isDefault = false, }); @@ -58,6 +60,7 @@ class Model { String? tagTextColor, String? tagBgColor, bool? isNew, + bool? isRecommend, String? category, bool? isDefault, }) { @@ -76,6 +79,7 @@ class Model { tagTextColor: tagTextColor ?? this.tagTextColor, tagBgColor: tagBgColor ?? this.tagBgColor, isNew: isNew ?? this.isNew, + isRecommend: isRecommend ?? this.isRecommend, category: category ?? this.category, isDefault: isDefault ?? false, ); diff --git a/macos/Runner/MainFlutterWindow.swift b/macos/Runner/MainFlutterWindow.swift index 2ac77f5c..a8b21e7d 100644 --- a/macos/Runner/MainFlutterWindow.swift +++ b/macos/Runner/MainFlutterWindow.swift @@ -9,7 +9,7 @@ class MainFlutterWindow: NSWindow { self.setFrame(windowFrame, display: true) // 设置窗口大小 - self.setContentSize(NSSize(width: 400, height: 800)) + self.setContentSize(NSSize(width: 850, height: 750)) // 设置窗口禁止缩放 // let window: NSWindow! = self.contentView?.window diff --git a/pubspec.lock b/pubspec.lock index f6bcad4d..7be64cd1 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -129,6 +129,14 @@ packages: url: "https://pub.dev" source: hosted version: "3.1.0" + auto_size_text: + dependency: "direct main" + description: + name: auto_size_text + sha256: "3f5261cd3fb5f2a9ab4e2fc3fba84fd9fcaac8821f20a1d4e71f557521b22599" + url: "https://pub.dev" + source: hosted + version: "3.0.0" autoscale_tabbarview: dependency: "direct main" description: diff --git a/pubspec.yaml b/pubspec.yaml index 95dff37d..d368f3f9 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -133,6 +133,7 @@ dependencies: camera: ^0.11.0+2 camerawesome: ^2.1.0 flutter_markdown_latex: ^0.3.4 + auto_size_text: ^3.0.0 dev_dependencies: flutter_test: diff --git a/windows/runner/main.cpp b/windows/runner/main.cpp index d85164e5..95eeb04a 100644 --- a/windows/runner/main.cpp +++ b/windows/runner/main.cpp @@ -26,7 +26,7 @@ int APIENTRY wWinMain(_In_ HINSTANCE instance, _In_opt_ HINSTANCE prev, FlutterWindow window(project); Win32Window::Point origin(10, 10); - Win32Window::Size size(400, 800); + Win32Window::Size size(850, 750); if (!window.Create(L"AIdea", origin, size)) { return EXIT_FAILURE; } From 4e21f755910f2869e49533db021f7172646c6f0e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=AE=A1=E5=AE=9C=E5=B0=A7?= Date: Fri, 20 Dec 2024 16:58:17 +0800 Subject: [PATCH 26/26] update doc --- README.md | 22 ++++++++-------------- 1 file changed, 8 insertions(+), 14 deletions(-) diff --git a/README.md b/README.md index 8abaa506..5ad6e181 100644 --- a/README.md +++ b/README.md @@ -6,17 +6,7 @@ mylxsw%2Faidea | Trendshift -一款集成了主流大语言模型以及绘图模型的 APP, 采用 Flutter 开发,代码完全开源,支持以下功能: - -- 支持 OpenAI 的 GPT-3.5,GPT-4 大语言模型 -- 支持 Anthropic 的 Claude instant,Claude 2.1 大语言模型 -- 支持 Google 的 Gemini Pro 以及视觉大语言模型 -- 支持国产模型:通义千问,文心一言,讯飞星火,商汤日日新,腾讯混元,百川53B,360智脑,天工,智谱,月之暗面等 -- 支持开源大模型:Yi 34B,Llama2,ChatGLM2,AquilaChat 7B,Bloomz 7B,轩辕 70B,ChatLaw,Mixtral 等,后续还将开放更多 -- 支持文生图、图生图、超分辨率、黑白图片上色、艺术字、艺术二维码等功能,支持 SDXL 1.0、Dall·E 3 等 - -![image](https://github.com/mylxsw/aidea/assets/2330911/297bfe8e-8b26-45b3-bc03-26bc81823211) - +一款集成了主流大语言模型以及绘图模型的 APP, 采用 Flutter 开发,代码完全开源。 下载体验地址: @@ -30,6 +20,12 @@ https://aidea.aicode.cc ## 开发、编译运行环境 +默认分支 `main` 是 v2 版本,当前正在开发中,如需自己部署,请切换到 [v1.x](https://github.com/mylxsw/aidea/tree/v1.x) 分支。 + +```bash +git checkout v1.x +``` + 搭建开发环境,用来编译和打包 APP,可以参考下面的文章,更多文章后面有时间了会持续更新。 - [AIdea 项目开发环境部署教程(一)前端 Flutter 环境搭建](https://mp.weixin.qq.com/s/bgAIH6s7t5IREusK_WtpRg) @@ -44,7 +40,7 @@ https://aidea.aicode.cc - 微信技术交流群: - + 如果无法加入,请添加微信号 `x-prometheus` 为好友,拉你进群。 @@ -52,8 +48,6 @@ https://aidea.aicode.cc -- 电报群:[点此加入](https://t.me/aideachat) - ## APP 截图