From 12013c7cfe3993ae125cfc8f76f7b53f0974c238 Mon Sep 17 00:00:00 2001 From: My-Responsitories <107370289+My-Responsitories@users.noreply.github.com> Date: Mon, 8 Jun 2026 16:34:00 +0800 Subject: [PATCH] opt: level img --- assets/images/lv/lv0.png | Bin 915 -> 0 bytes assets/images/lv/lv1.png | Bin 876 -> 0 bytes assets/images/lv/lv2.png | Bin 1049 -> 0 bytes assets/images/lv/lv3.png | Bin 991 -> 0 bytes assets/images/lv/lv4.png | Bin 912 -> 0 bytes assets/images/lv/lv5.png | Bin 1077 -> 0 bytes assets/images/lv/lv6.png | Bin 1092 -> 0 bytes assets/images/lv/lv6_s.png | Bin 1426 -> 0 bytes lib/common/widgets/svg/level_icon.dart | 409 ++++++++++++++++++ lib/pages/member/widget/user_info_card.dart | 10 +- lib/pages/mine/view.dart | 9 +- lib/pages/pgc_review/child/view.dart | 6 +- lib/pages/search_panel/user/widgets/item.dart | 11 +- lib/pages/video/member/view.dart | 10 +- lib/pages/video/note/view.dart | 10 +- .../video/reply/widgets/reply_item_grpc.dart | 9 +- lib/utils/bili_utils.dart | 13 +- pubspec.yaml | 1 - 18 files changed, 439 insertions(+), 49 deletions(-) delete mode 100644 assets/images/lv/lv0.png delete mode 100644 assets/images/lv/lv1.png delete mode 100644 assets/images/lv/lv2.png delete mode 100644 assets/images/lv/lv3.png delete mode 100644 assets/images/lv/lv4.png delete mode 100644 assets/images/lv/lv5.png delete mode 100644 assets/images/lv/lv6.png delete mode 100644 assets/images/lv/lv6_s.png create mode 100644 lib/common/widgets/svg/level_icon.dart diff --git a/assets/images/lv/lv0.png b/assets/images/lv/lv0.png deleted file mode 100644 index f9ed49b620d04f29610aa42834b6946d2eafb3b8..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 915 zcmV;E18n?>P)Lda*%`NsvxmkA-?$%4ewVr{|tby3&#_k@sduh7}daLzaUF-j?;lsXBt zh~qdpe_Rv<;RomZUD7096HU|5Xfz0=R8DfgX_{!YTAmmI@J|dL04kLVD5Z(-FO^CV zMR715d+Td}2T`1V%_f9Ado=wGDwPUWS62Z5mSw>(jF~!+Bng|Fo5^7Gc#mh|$wR4T!;ELfHmTd-QKVq@d)+NCH84i68fwkL#OcXt;nD=Pp1 z+qQ9ZbOhIRWA%4+bp_kD194fFQLEKJNccnUF@O-l0u5$-?EG^y>i&E_|7Y+3psFeg zg#vDGZzKD9czD41`T5lAs;XjreLYko^EE&agjlgjI8OL(80RU<+<>fE8jy7YS+g`C z>jbi9X+YKqWX;lmMRy>IV(1Aj?n}?2NZkN`jZu;$G#ZVGCMb%6dc7WqN4?>tN$Lie zrU}NpM~dHt437z#reSYy5BYrF)AsW668HD_X{R-n)D0L6252^$p(d2eW$f(iAeZx2 zs0_ov#l=PJzM_gFaf&CAM?Fo`LWOMG#^d9oC)V%x(QG!cy}j)@j%u2Q{r!C?iW1Oi zx7#>7JBv(soI;_1TCFy*-XO~|*4CoSoRJ1ptJTo!uIu99;2r zFN(RR0HqYNJh?{sVuF78x9z7xm!dLvCCYS)e`~q&S9+Jos3s?XE002ovPDHLkV1nAwr^WyP diff --git a/assets/images/lv/lv1.png b/assets/images/lv/lv1.png deleted file mode 100644 index ad8b70ce0ad220d08090a6aef98cd19513865534..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 876 zcmV-y1C#uTP)?f?J)8FWQhbW?9;ba!ELWdL_~cP?peYja~^aAhuUa%Y?FJQ@H10{KZq zK~z|U?U_$*+CUV>zZs?`Muf^rB}haPK@_r!L@v;KRJ}&GRj<$^^d$MSV}V#yjQ~rj zvM4b^q{NIA3Ghr8QE1E$=eO!T{_iq%ToUPEKH&rV{V!y!253yt?2eC8QrZnav?J?*1r zvx#oEt5wW2O_a;!>1WHb(C_!>8cL_rs8*}fz7;~CR;yt&8ikJIc^)|D(}%V!v*tM= zB=HeDB$SmNXfgoxk0npj8jr5%9i%I*tQJ-*xA|f z#VeHx2q6$c1R8f72kmw{ZaJYunm0{zx<8*qN(t9>mFaHVHV7fg6h1sW46Nfg4qC02 zu4Vua=Kzc`W!Fh5g=JZgQerq9!t=bjH6qRD^ZC#%qwBh8G#X0$@mq}Q;bn~P?d_pd zDg|CM3E(lL;8pUk3++L1a#=)xvl@#@5!ue-O8~x462xT2Sg=CX65+?8Xywnp@>GK#%O$PNPNab7sj0%KY$CiM)xLu0bP=4 zQWK5OM1w|R41p+$i3>G6N`z8CD=pA=dhc~XK!)jKT4quj{Ox+?%sJ=&+jDO3C7g3? zR6?PUIMzPb4bCqBkPQwWC!Alm7#!44!fmlmaB|tC@NQ1X4Z)<~hQLL##pD#ka(n3R zAtvA^II2xfRtkQz6Id8QH#nNjPGkfgs~e{qPQWcWi+-1JhVOHu=#31h>=vLh77qX& zzE(6igC)OzaLXRNkA6&K7F<`DxV-HgJXSXV;M<=OJP!9{^&Oo33WvoGm(CgBvAPhn=if+F z0?0&~lSQj-wJjifbtPNIzs5gc^X2t#bk(mc9st@rO|XkH7HLZ3n`R2$O!n!nW$DYv zFF@I603q7$$YwK(vI|gd{tHm<0?N&XJ`AlTAbWAXu@g>AgkO!BwJwl~nO5y3BeaH;7mT#`d& z=bR%=jX(9}!f*k;#(v;!q+es-8)(PL`ePs<@W~!rYC5A?Jmm~e#$O|voHe?voR~5L z2BwD~5&=hR52)gx$3?7GjFdQE@qokY^6rjV`iVk0E&gWB$Gq^we zOmkV$7ySf);AriBm3^nTMH53|3LcKWM0kEuXW2_gf`qQ!S9SaUm>a{Ru~!`UH+k3H z0=4#P1nt=`l>4HCcsKPS@B1m5MrhUW$1zs^){tyfee_9aRz}Dr^~#ukoWo0Ob}J=kWdy78hMV{R7`)KO#VED zP*?^T##ARMa%t8>(Rlm)pYC8m?Vn&V2Mew8Vv5nz)M697z?yNWUr6##M#n^S}daMJY`Fn%I( TxZEDJ00000NkvXXu0mjfCQjCw diff --git a/assets/images/lv/lv3.png b/assets/images/lv/lv3.png deleted file mode 100644 index 80a0db3fad5977e2f421f7b4b7a68cf786a55f66..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 991 zcmV<510ei~P)41$G6fkp^g`jFbRdDMyRdl*U~PMpMP?KV+ADa-Nkx#xU7=eySqF*8~f z7oI}dJ1{rQ;Ijb2K8Mc|<2JQ20XN^xpCZU-0WL8s0u1g0>}0?Tkb3Lg%rSuO0Mrxk zVoigX5+J8d6UZxqipz;tv^XvSwcMrxVi>@{va<{l6~*t4tGF}}iwZ(?IpHQ3Y~1^t zmCX3p`2ww4ybJncDg-t5tA$M+3PMD=a$AdQKp=3azFK*;OB0-mDiQz;n`w0363y-t z{V`QWL&TDSbk-7v%`13r|1o7qKf*ZR6a*sU+P+R*S4dg1y%<}->q+mLbEsRxx$TjE zo%JJU;r>XjrvC;_;Od?jHfjP4;A6(bqtU#p?}wQZG9`)5kT*Ve#8ezj);~Tt2+$Rp z*A>#sZOIM=%^;~e{d*hSKhUXSqqfKw0XWbVLPRALEcd?qk+VGKqInREHP-;Q9DSJ= zuj{YkS}lonPe9c!(CX;b70)7AsYh;TD+2a+g>Z3a)K`sYs}V>=6yF81tq2GLZAHMZ zf(^gFUbK}4e9D-(`!S2SM-ki=58?QxCQgDufd2k!J-KFQdfKotZFuwDpfzrA-$$Ub zP+cLM-rC_>Xpb&%VvAm7_p*KY1>XsnTCl5ZN0Q+x5_)ujE4yRp3^`Mf3>59v`%dXQ z0q67{&)MFGdo-wG z@eQ!DcvCVSrwjNpXV#6~A5#(YD1xbo4=2Hc2@MAmTFvX@1se}Wawth>+rFAuRH{l6 z23=y6BxCq{9;4a%ueyT?4d?qKjZ$t3D;|Nqh=Nqqo#|H-240TWmu$mk41H}}JOCth z|Bo%cj6l7#YIOnt-j|=Pbw- zY!D^Muo)fYHoEzC))6wHi*&ChetHc$Q{v52hP}s&Z&DXu6Jar7O47#i*1n?TxYu=- zu-rdJR6#|-Eayy4m6r$8zia>i N002ovPDHLkV1g`kwR8Xg diff --git a/assets/images/lv/lv4.png b/assets/images/lv/lv4.png deleted file mode 100644 index 5441967b204259d9699d593bd3c7165ba28e76f4..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 912 zcmV;B18@9^P)$ zzd%TPWM*s@!^naN3z>C5@kKPu45l>)fIXi=)s^*@;#>wgso_@e7tI|=&(S1eBmo#7 zLSNo2te5zqB_wN zP;ygKW)jYR&tCzci3URU9>nXig9R&&>^SJJTc1X@?f1%;Z>J+f=mhAmQLnJA$4f$e z8tFamO=nfG=fe)GAi~dp@gel>J+Hj22ibWjUlR3A{*!ld=N9ye{~0{-K9Z0Mp)>)l-AHS9;qmEj=##6ETX4&V2T|~n(Awp- zky}7t+<>1Wr6Ud*m6t%nru^BPABG3fcMpm`Z)eeG167$kgjE8?UUh{s0bqU(eSST< znh)^T%bfu^JApdoJIc-$ANR|I>;o0jmp>1~6Xj mD5{7tL*NvvlY}qgaQZhB^5AsYu5vm60000bZ_c9w#f%qi=v4Fkj2;4`DS%A@<9nTT-40v&_lqq4vi%Wq(vs?;*E_$J(ScdIh_=p0_=|wgn?hf|Nr`lZSxQ)g=L8#hnpe_6K}oQ(qLq|gfJn@!?>il* zy7W|^l};al@#6{4F{0fsBDwv*RA*UNe?#ujYqQ#mObgN-Ot4w@}It0wRj+$;TX&jaCuQ_oH+^rj!pmo&W$4X*;|mqmjX${69~5J z$kqXDmgA6?KZnfV`~SL{%+v{L><)5AcK9mZy7DWaq`&+sAa7p=mrJQijz-^Zw_V(# z>CtOiOLo|+Q+4-Ni~MJ3gBM$^IeSGktsG&?h|24Kw6T$|_Znvk^ZR3D8I-JS*D;3x vHHI+(BKQ9NAA^890t&loQUu1|4BGo2Z<#nq*B25500000NkvXXu0mjfgA(nr diff --git a/assets/images/lv/lv6.png b/assets/images/lv/lv6.png deleted file mode 100644 index 399e585a70e9ba064cd0f9952a24fb120931e4a2..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1092 zcmV-K1iSl*P)_2k}W`Fo?kz9^l19Atok-2NNi= z{XwZnOf>Ot;MMp_`~yIv38=KTIt>d^DCkIR zbIyGC{+hcar9{(m?3lK&ymS@7Q2?o!qZcG7L(MF};^9MYNup~BrkH0529JZM5{^&$ z;*qxxNQqGZ-3ceF9b#QT*$8b*xuhGVdcw(KaVvz-gEn0PR{=a%cae#rzP!GF5ccq> z4NbG_iN>>8ES>&DK+69{K7(x5mk*=Av=`8{y8COpcLQyyrJ$|Z*MQg|Ny@O&%N@)& z-sJlJK@I@iaaifQZ*|nLQ;=oJw&N#!>Pky^9#+4!&h%^8&4!#cV{0aZpUn1u)-#J3&yGM;j&)VLXQTXtK z${jQf9bbP#ES+8(+fu?EItzdDcJ*YO!@F@kT47SI`FdV^(5#LC!zh7BF_C3OOYq3Q zUG=ZlpDY_JoemfXY*#N3K>+EkqIl(UP4$)2a%(_2{|nIQ0vb(d7uZxV4f?);Xug7j z&(1=+u43NQ0+4CK9-oNpFCNaJaOxugAp$4C7J~Kc1evLVVrx_}%|goyu&LJoxw?wt z_*lhueP95o_uk(yM)CrfCa7~8GfP*$;)s6=aIQR(X~Lek z4C>q#a@F>{0Q=&la17szy{t;aE`ayr523wQ>+1`Npue&Y_QdC)En5{|k;}pR`KP$c zsz~et@IPkYUKoO0Qtlm~_4T3Siz}e6E*P&KfIU14YEwSUARf-aJ%0`|zy5Sdf~sqP z*5424mEHKAI}m^WrP>?^?&vV=v2l?7`1XoDy9*s(eg)Ru4KP)Ee)lfi%z4Q9`N+i+ z(_p-IuyXBtp066CssPniQFtB-AD@KCn zsLj816UC8CXwgg(Nm9A-Q3Q~sk`3RlJu2H+M?U>K+>s1q;qfutyKx=v_!#8MO5`;D z?cYM%Y%TC?7Fa2D1Z|W*d`P+RWIa2xthe`GU_w(3@k&rECMecoNP&WI zffxd%7(xLz6`^P?Y}b~yyPcigotfjsBHigucV}j|bQk;F zo$q|#Id9&XIcMe!VT>Uc2-Va)trD#jK*eNhv4Niu)<7p)#eZmE+mN3|0)US((wsA9 zGQx@`TSJD-$r&?2Sng=C2ju}^pt^c4N2za8r4ZuPwK)!4X(>Rhd8`Hg0UH|3SPq>r z6J*vbsK5Pcx`qtZtlY?eBLGOo3bKf6Qv;mKS6HrNl7!%f^SED-8<>?Zry@<>!z(aXvu7dAAael|4_5d?@E>Ocz%0RZatUm%}8Wjtv`1%x+N0UU*LwG)xH zGik?^9`! zVH$_STnqsCgg7ApZ(*JZfL~ex;muV^an0XnZJcZg%2&wr>2Pn}62GWpV6j+yS05W6 zk-&n(N%8-;nS{XIum!|B&9K{5_px!Esv@*$Bh=fsb>X}*Q(XT6#M*Uf^Oz(-YT66+ z#_wr!g;j5XU$WG)78Z$sg+fX3yfKpq0w4(K^9l;!c;UsgW^!d^5Z|k{r6qj)7_|QW zqw{nK@eBHUZI+1mJ`m)a5g@seTRqiJRu#JYOWIo3b?v^%|!j~^SiKW`rM z$k{e1omUL|gw<=nKeNcxICRELIA2?4U4WuMI?xOj4v(8Mp=J`JHI5Jv=Q!`5DbRX) zpj_)R>~pTHHb2flKSp-%0ylqtvNS<}@b((RI=S^^+I4tB&2;bBiILBCLHoPs{%kzl z4VytrW>4g_X;H3rLb=+R6z57y4b4=pbs=)*o3!I*Z_PBg{i&}Q1 zd*FDbQlF$KKs1&$8JR$elF!H4&=90U2LTg59hM;`PF_Z$DTd z06K4;zDQLO{_-f8JpMZLFrmINW-^BGkruFl0aNuU&7_4zaBtr+Zaw z(oEvC0$R|%&tIt@0!C18bdZ gA&&rmQjKl?7lU`QDV}MAX#fBK07*qoM6N<$f{H+!)c^nh diff --git a/lib/common/widgets/svg/level_icon.dart b/lib/common/widgets/svg/level_icon.dart new file mode 100644 index 000000000..e0379816d --- /dev/null +++ b/lib/common/widgets/svg/level_icon.dart @@ -0,0 +1,409 @@ +import 'dart:ui'; + +import 'package:flutter/material.dart'; +import 'package:flutter/semantics.dart'; + +class UserLevel extends LeafRenderObjectWidget { + const UserLevel( + this.level, { + super.key, + this.height = 11, + this.flash = false, + }); + + final double height; + final int level; + final bool flash; + + @override + RenderObject createRenderObject(BuildContext context) { + return RenderLevel(height, level, flash); + } + + @override + void updateRenderObject( + BuildContext context, + RenderLevel renderObject, + ) { + renderObject + ..height = height + ..level = level + ..flash = flash; + } +} + +class RenderLevel extends RenderBox { + RenderLevel(this._height, this._level, this._flash); + + double _height; + set height(double value) { + if (_height == value) return; + _height = value; + markNeedsLayout(); + } + + int _level; + set level(int value) { + if (_level == value) return; + _level = value; + markNeedsPaint(); + markNeedsSemanticsUpdate(); + } + + bool _flash; + set flash(bool value) { + if (_flash == value) return; + _flash = value; + markNeedsLayout(); + } + + @override + Size computeDryLayout(covariant BoxConstraints constraints) { + return Size( + (_flash ? LevelCanvas._extendR : LevelCanvas._totalR) * + _height / + LevelCanvas._totalB, + _height, + ); + } + + @override + void performLayout() { + size = computeDryLayout(constraints); + } + + @override + void paint(PaintingContext context, Offset offset) { + final paint = Paint()..color = lookupBackgroundColor(_level); + LevelCanvas(context.canvas) + ..save() + ..translate(offset.dx, offset.dy) + ..scale(_height / LevelCanvas._totalB) + ..drawLevelBack(paint, bolt: _flash) + ..drawLevelLv(paint..color = Colors.white) + ..drawLEDigit(_level, paint) + ..restore(); + } + + @override + void describeSemanticsConfiguration(SemanticsConfiguration config) { + super.describeSemanticsConfiguration(config); + config.label = '${_flash ? "硬核" : ""}$_level级'; + } + + static Color lookupBackgroundColor(int level) { + return switch (level) { + 0 || 1 => const Color(0xFFC0C0C0), + 2 => const Color(0xFF8BD29B), + 3 => const Color(0xFF7BCDEF), + 4 => const Color(0xFFFEBB8B), + 5 => const Color(0xFFEE672A), + _ => const Color(0xFFFF0000), + }; + } +} + +extension type LevelCanvas(Canvas _) implements Canvas { + // ========== 布局常量 ========== + static const _r = Radius.circular(20); + + static const double _left = 629; + static const double _right = 877; + static const double _colW = 68; // 竖段宽度 + static const double _lColR = _left + _colW; // 697 + static const double _rColL = _right - _colW; // 810 + + // 三条横线的边界 + static const double _rowH = 68; + static const double _rowSp = 146; + static const double _topY = 55; + static const double _topYB = _topY + _rowH; // 123 + static const double _midY = _topY + _rowSp; // 201 + static const double _midYB = _midY + _rowH; // 269 + static const double _botY = _midY + _rowSp; // 347 + static const double _botYB = _botY + _rowH; // 415 + + // 竖段拼接用的中心线 + static const double _midMid = (_midY + _midYB) / 2; // 235 + + static final _boltIcon = + (ParagraphBuilder( + ParagraphStyle( + fontSize: 460, + fontFamily: Icons.bolt_rounded.fontFamily, + height: 1, + fontWeight: FontWeight.w900, + textDirection: TextDirection.ltr, + ), + )..addText(.fromCharCode(Icons.bolt_rounded.codePoint))).build() + ..layout(const ParagraphConstraints(width: double.infinity)); + void drawBolt() => drawParagraph(_boltIcon, const Offset(840, 5)); + + void _draw1(Paint paint) { + drawRRect(const .fromLTRBXY(673, _botY, 833, _botYB, 20, 20), paint); + drawRRect( + .fromLTRBAndCorners( + 673, + _topY, + 787, + _topYB, + topLeft: _r, + bottomLeft: _r, + topRight: _r, + ), + paint, + ); + drawRect(const .fromLTRB(719, _topYB, 787, _botY), paint); + } + + void drawLEDigit(int digit, Paint paint) { + if (digit == 1) return _draw1(paint); + final bits = switch (digit) { + 0 => 0x7E, + 2 => 0x6D, + 3 => 0x79, + 4 => 0x33, + 5 => 0x5B, + 6 => 0x5F, + 7 => 0x70, + 8 => 0x7F, + 9 => 0x7B, + // _ => throw ArgumentError('Unsupported digit: $digit'), + _ => 0x4F, // `E` + }; + + _drawSegments( + bits & 0x40 != 0, + bits & 0x20 != 0, + bits & 0x10 != 0, + bits & 0x08 != 0, + bits & 0x04 != 0, + bits & 0x02 != 0, + bits & 0x01 != 0, + paint, + ); + } + + void _drawSegments( + bool a, + bool b, + bool c, + bool d, + bool e, + bool f, + bool g, + Paint paint, + ) { + // 横段 + if (a) { + _drawRRect( + _left, + _topY, + _right, + _topYB, + _r, + _r, + f ? .zero : _r, + b ? .zero : _r, + paint, + ); + } + if (g) { + _drawRRect( + _left, + _midY, + _right, + _midYB, + f ? .zero : _r, + b ? .zero : _r, + e ? .zero : _r, + c ? .zero : _r, + paint, + ); + } + if (d) { + _drawRRect( + _left, + _botY, + _right, + _botYB, + e ? .zero : _r, + c ? .zero : _r, + _r, + _r, + paint, + ); + } + + // 竖段 + // 左上竖段 f + if (f) { + final top = a ? _topYB : _topY; // 有上横则齐底,否则到顶 + final bottom = g ? _midY : (e ? _midMid : _midYB); + final rTop = a ? Radius.zero : _r; + final rBot = g || e ? Radius.zero : _r; + _drawRRect(_left, top, _lColR, bottom, rTop, rTop, rBot, rBot, paint); + } + + // 右上竖段 b + if (b) { + final top = a ? _topYB : _topY; + final bottom = g ? _midY : (c ? _midMid : _midYB); + final rTop = a ? Radius.zero : _r; + final rBot = g || c ? Radius.zero : _r; + _drawRRect(_rColL, top, _right, bottom, rTop, rTop, rBot, rBot, paint); + } + + // 左下竖段 e + if (e) { + final top = g ? _midYB : (f ? _midMid : _midY); + final bottom = d ? _botY : _botYB; + final rTop = g || f ? Radius.zero : _r; + final rBot = d ? Radius.zero : _r; + _drawRRect(_left, top, _lColR, bottom, rTop, rTop, rBot, rBot, paint); + } + + // 右下竖段 c + if (c) { + final top = g ? _midYB : (b ? _midMid : _midY); + final bottom = d ? _botY : _botYB; + final rTop = g || b ? Radius.zero : _r; + final rBot = d ? Radius.zero : _r; + _drawRRect(_rColL, top, _right, bottom, rTop, rTop, rBot, rBot, paint); + } + } + + /// 绘制圆角矩形,四角全零时退化为矩形 + void _drawRRect( + double l, + double t, + double r, + double b, + Radius tl, + Radius tr, + Radius bl, + Radius br, + Paint paint, + ) { + if (tl == .zero && tr == .zero && bl == .zero && br == .zero) { + drawRect(.fromLTRB(l, t, r, b), paint); + } else { + drawRRect( + .fromLTRBAndCorners( + l, + t, + r, + b, + topLeft: tl, + topRight: tr, + bottomLeft: bl, + bottomRight: br, + ), + paint, + ); + } + } + + static const double _vLeft = 296; + static final vV = Path() + ..moveTo(_vLeft, 282) + ..lineTo(_vLeft, 292) + ..arcToPoint( + const Offset(300, 313), + radius: const .circular(50), + clockwise: false, + ) + ..lineTo(395, 408) + ..arcToPoint( + const Offset(420, 415.5), // 避免拼接裂缝, 实为419.5, 415 + radius: const .circular(50), + clockwise: false, + ) + ..lineTo(420, 340.5) // 419.5, 340 + ..lineTo(363, 282) + ..close(); + + void drawLevelLv(Paint paint) { + const double lvTop = 106; + + drawRRect( + .fromLTRBAndCorners( + 56, + lvTop, + 123, + _botYB, + topLeft: _r, + topRight: _r, + bottomLeft: _r, + ), + paint, + ); + drawRRect( + .fromLTRBAndCorners( + 123, + _botY, + 256, + _botYB, + topRight: _r, + bottomRight: _r, + ), + paint, + ); + + final vL = RRect.fromLTRBAndCorners( + _vLeft, + lvTop, + 363, + 282, + topLeft: _r, + topRight: _r, + ); + + drawRRect(vL, paint); + + const double vSymmetryX = 419.5; + + drawPath(vV, paint); + save(); + translate(vSymmetryX, 0); + scale(-1, 1); + translate(-vSymmetryX, 0); + drawRRect(vL, paint); + drawPath(vV, paint); + restore(); + } + + static const double _totalR = 930; + static const double _extendR = 1250; + static const double _totalB = 466; + + void drawLevelBack(Paint paint, {bool bolt = false}) { + const radius = Radius.circular(27); + final double right = bolt ? _extendR : _totalR; + drawRRect( + RRect.fromLTRBAndCorners( + 0, + 48, + right, + _totalB, + topLeft: radius, + bottomLeft: radius, + bottomRight: radius, + ), + paint, + ); + drawRRect( + RRect.fromLTRBAndCorners( + 576, + 0, + right, + 48, + topLeft: radius, + topRight: radius, + ), + paint, + ); + + if (bolt) drawBolt(); + } +} diff --git a/lib/pages/member/widget/user_info_card.dart b/lib/pages/member/widget/user_info_card.dart index 4e064ce27..9a887b204 100644 --- a/lib/pages/member/widget/user_info_card.dart +++ b/lib/pages/member/widget/user_info_card.dart @@ -217,14 +217,10 @@ class UserInfoCard extends StatelessWidget { ), ), ), - Image.asset( - BiliUtils.levelName( - card.levelInfo!.currentLevel!, - isSeniorMember: card.levelInfo?.identity == 2, - ), + BiliUtils.levelPicture( + card.levelInfo!.currentLevel!, + isSeniorMember: card.levelInfo?.identity == 2, height: 11, - cacheHeight: 11.cacheSize(context), - semanticLabel: '等级${card.levelInfo?.currentLevel}', ), if (card.vip?.status == 1) Container( diff --git a/lib/pages/mine/view.dart b/lib/pages/mine/view.dart index 4a86bceed..7712abb5c 100644 --- a/lib/pages/mine/view.dart +++ b/lib/pages/mine/view.dart @@ -315,13 +315,10 @@ class _MediaPageState extends CommonPageState overflow: .ellipsis, ), ), - Image.asset( - BiliUtils.levelName( - levelInfo?.currentLevel ?? 0, - isSeniorMember: userInfo.isSeniorMember == 1, - ), + BiliUtils.levelPicture( + levelInfo?.currentLevel ?? 0, + isSeniorMember: userInfo.isSeniorMember == 1, height: 10, - cacheHeight: 10.cacheSize(context), ), ], ), diff --git a/lib/pages/pgc_review/child/view.dart b/lib/pages/pgc_review/child/view.dart index a227d299b..6718180e1 100644 --- a/lib/pages/pgc_review/child/view.dart +++ b/lib/pages/pgc_review/child/view.dart @@ -14,7 +14,6 @@ import 'package:PiliPlus/pages/pgc_review/child/controller.dart'; import 'package:PiliPlus/pages/pgc_review/post/view.dart'; import 'package:PiliPlus/utils/accounts.dart'; import 'package:PiliPlus/utils/bili_utils.dart'; -import 'package:PiliPlus/utils/extension/num_ext.dart'; import 'package:PiliPlus/utils/extension/theme_ext.dart'; import 'package:PiliPlus/utils/num_utils.dart'; import 'package:PiliPlus/utils/platform_utils.dart'; @@ -244,10 +243,9 @@ class _PgcReviewChildPageState extends State fontSize: 13, ), ), - Image.asset( - BiliUtils.levelName(item.author!.level!), + BiliUtils.levelPicture( + item.author!.level!, height: 11, - cacheHeight: 11.cacheSize(context), ), ], ), diff --git a/lib/pages/search_panel/user/widgets/item.dart b/lib/pages/search_panel/user/widgets/item.dart index fdb45feeb..4fc1d7ac9 100644 --- a/lib/pages/search_panel/user/widgets/item.dart +++ b/lib/pages/search_panel/user/widgets/item.dart @@ -1,7 +1,6 @@ import 'package:PiliPlus/common/widgets/pendant_avatar.dart'; import 'package:PiliPlus/models/search/result.dart'; import 'package:PiliPlus/utils/bili_utils.dart'; -import 'package:PiliPlus/utils/extension/num_ext.dart'; import 'package:PiliPlus/utils/num_utils.dart'; import 'package:flutter/material.dart'; import 'package:get/get_core/src/get_main.dart'; @@ -52,14 +51,10 @@ class SearchUserItem extends StatelessWidget { ), ), const SizedBox(width: 6), - Image.asset( - BiliUtils.levelName( - item.level!, - isSeniorMember: item.isSeniorMember == 1, - ), + BiliUtils.levelPicture( + item.level!, + isSeniorMember: item.isSeniorMember == 1, height: 11, - cacheHeight: 11.cacheSize(context), - semanticLabel: '等级${item.level}', ), ], ), diff --git a/lib/pages/video/member/view.dart b/lib/pages/video/member/view.dart index 0893c101e..4fe42183c 100644 --- a/lib/pages/video/member/view.dart +++ b/lib/pages/video/member/view.dart @@ -20,7 +20,6 @@ import 'package:PiliPlus/pages/video/introduction/ugc/controller.dart'; import 'package:PiliPlus/pages/video/member/controller.dart'; import 'package:PiliPlus/utils/accounts.dart'; import 'package:PiliPlus/utils/bili_utils.dart'; -import 'package:PiliPlus/utils/extension/num_ext.dart'; import 'package:PiliPlus/utils/extension/theme_ext.dart'; import 'package:PiliPlus/utils/num_utils.dart'; import 'package:PiliPlus/utils/page_utils.dart'; @@ -257,13 +256,10 @@ class _HorizontalMemberPageState extends State { ), ), const SizedBox(width: 8), - Image.asset( - BiliUtils.levelName( - memberInfoModel.level!, - isSeniorMember: memberInfoModel.isSeniorMember == 1, - ), + BiliUtils.levelPicture( + memberInfoModel.level!, + isSeniorMember: memberInfoModel.isSeniorMember == 1, height: 11, - cacheHeight: 11.cacheSize(context), ), ], ), diff --git a/lib/pages/video/note/view.dart b/lib/pages/video/note/view.dart index cdd3c97ca..ca8bdab95 100644 --- a/lib/pages/video/note/view.dart +++ b/lib/pages/video/note/view.dart @@ -10,7 +10,6 @@ import 'package:PiliPlus/pages/video/note/controller.dart'; import 'package:PiliPlus/pages/webview/view.dart'; import 'package:PiliPlus/utils/accounts.dart'; import 'package:PiliPlus/utils/bili_utils.dart'; -import 'package:PiliPlus/utils/extension/num_ext.dart'; import 'package:PiliPlus/utils/extension/theme_ext.dart'; import 'package:extended_nested_scroll_view/extended_nested_scroll_view.dart'; import 'package:flutter/material.dart'; @@ -263,13 +262,10 @@ class _NoteListPageState extends State ), ), const SizedBox(width: 6), - Image.asset( - BiliUtils.levelName( - item.author!.level!, - isSeniorMember: item.author!.isSeniorMember == 1, - ), + BiliUtils.levelPicture( + item.author!.level!, + isSeniorMember: item.author!.isSeniorMember == 1, height: 11, - cacheHeight: 11.cacheSize(context), ), ], ), diff --git a/lib/pages/video/reply/widgets/reply_item_grpc.dart b/lib/pages/video/reply/widgets/reply_item_grpc.dart index c4e1c3c89..7da217e0c 100644 --- a/lib/pages/video/reply/widgets/reply_item_grpc.dart +++ b/lib/pages/video/reply/widgets/reply_item_grpc.dart @@ -184,13 +184,10 @@ class ReplyItemGrpc extends StatelessWidget { ), ), ), - Image.asset( - BiliUtils.levelName( - member.level, - isSeniorMember: member.isSeniorMember == 1, - ), + BiliUtils.levelPicture( + member.level.toInt(), + isSeniorMember: member.isSeniorMember == 1, height: 11, - cacheHeight: 11.cacheSize(context), ), if (replyItem.mid == upMid) const PBadge( diff --git a/lib/utils/bili_utils.dart b/lib/utils/bili_utils.dart index 96779e859..1555079c1 100644 --- a/lib/utils/bili_utils.dart +++ b/lib/utils/bili_utils.dart @@ -1,3 +1,6 @@ +import 'package:PiliPlus/common/widgets/svg/level_icon.dart'; +import 'package:flutter/material.dart'; + abstract final class BiliUtils { static bool isDefaultFav(int? attr) { if (attr == null) { @@ -21,8 +24,12 @@ abstract final class BiliUtils { return tagid != null && tagid != 0 && tagid != -10 && tagid != -2; } - static String levelName( - Object level, { + // https://s1.hdslb.com/bfs/svg-next/font/2025-10-27/freshspace-zpjpp3aqht.css + static Widget levelPicture( + int level, { bool isSeniorMember = false, - }) => 'assets/images/lv/lv${isSeniorMember ? '6_s' : level}.png'; + double height = 11, + }) { + return UserLevel(level, height: height, flash: isSeniorMember); + } } diff --git a/pubspec.yaml b/pubspec.yaml index c8c3e8ae0..79f61bb12 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -270,7 +270,6 @@ flutter: assets: - path: assets/images/ - - path: assets/images/lv/ - path: assets/images/logo/ - path: assets/images/logo/ico/ platforms: [windows]