From c8ee2c3033cd0ab362f4679622f97c671b4f41aa Mon Sep 17 00:00:00 2001 From: 22 <60903333+nini22P@users.noreply.github.com> Date: Wed, 16 Jul 2025 14:30:33 +0800 Subject: [PATCH 01/31] fix: add error handling for storage directory retrieval on Android --- lib/models/storages/local.dart | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/lib/models/storages/local.dart b/lib/models/storages/local.dart index e5b81e2..8753425 100644 --- a/lib/models/storages/local.dart +++ b/lib/models/storages/local.dart @@ -84,9 +84,22 @@ Future> getLocalStorages( return storages; } else if (isAndroid) { final androidXStorage = AndroidXStorage(); - final external = await androidXStorage.getExternalStorageDirectory(); - final sdcard = await androidXStorage.getSDCardStorageDirectory(); - final usbs = await androidXStorage.getUSBStorageDirectories(); + final external = + await androidXStorage.getExternalStorageDirectory().catchError((error) { + logger('Error getting external storage: $error'); + return null; + }); + final sdcard = + await androidXStorage.getSDCardStorageDirectory().catchError((error) { + logger('Error getting SD card: $error'); + return null; + }); + final usbs = + await androidXStorage.getUSBStorageDirectories().catchError((error) { + logger('Error getting USB storages: $error'); + return []; + }); + List storages = []; if (external != null) { From bb2f036c062d6c9ad73616a6bff79cb1ab22758f Mon Sep 17 00:00:00 2001 From: 22 <60903333+nini22P@users.noreply.github.com> Date: Wed, 16 Jul 2025 14:37:57 +0800 Subject: [PATCH 02/31] feat: add system UI hide timer for video playback --- lib/pages/player/iris_player.dart | 28 ++++++++++++++++++++++------ 1 file changed, 22 insertions(+), 6 deletions(-) diff --git a/lib/pages/player/iris_player.dart b/lib/pages/player/iris_player.dart index 5fcdc51..cdd4f8a 100644 --- a/lib/pages/player/iris_player.dart +++ b/lib/pages/player/iris_player.dart @@ -75,6 +75,7 @@ class IrisPlayer extends HookWidget { final controlHideTimer = useRef(null); final progressHideTimer = useRef(null); + final systemUiHideTimer = useRef(null); final brightness = useBrightness(isLeftGesture.value); final volume = useVolume(isRightGesture.value); @@ -189,6 +190,17 @@ class IrisPlayer extends HookWidget { ); } + void startSystemUiHideTimer() { + systemUiHideTimer.value = Timer( + const Duration(seconds: 3), + () { + if (!isShowControl.value && mediaType == MediaType.video) { + SystemChrome.setEnabledSystemUIMode(SystemUiMode.immersive); + } + }, + ); + } + void resetControlHideTimer() { controlHideTimer.value?.cancel(); startControlHideTimer(); @@ -199,6 +211,11 @@ class IrisPlayer extends HookWidget { startProgressHideTimer(); } + void resetSystemUiHideTimer() { + systemUiHideTimer.value?.cancel(); + startSystemUiHideTimer(); + } + void showControl() { isShowControl.value = true; isHover.value = false; @@ -247,21 +264,20 @@ class IrisPlayer extends HookWidget { useEffect(() { if (isShowControl.value || mediaType != MediaType.video) { SystemChrome.setEnabledSystemUIMode(SystemUiMode.edgeToEdge); + systemUiHideTimer.value?.cancel(); } else { SystemChrome.setEnabledSystemUIMode(SystemUiMode.immersive); } return; - }, [isShowControl.value, currentPlay?.file.type]); + }, [isShowControl.value, mediaType]); useEffect(() { SystemChrome.setSystemUIChangeCallback((value) async { if (value) { - showControl(); + resetSystemUiHideTimer(); } }); - return () { - SystemChrome.setSystemUIChangeCallback(null); - }; + return null; }, []); void onKeyEvent(KeyEvent event) async { @@ -1052,7 +1068,7 @@ class IrisPlayer extends HookWidget { curve: Curves.easeInOutCubicEmphasized, bottom: isShowControl.value || mediaType != MediaType.video ? 0 - : -96, + : -128, left: 0, right: 0, child: Align( From b4b28153768be60c38c7a85909ef3a334c547be4 Mon Sep 17 00:00:00 2001 From: 22 <60903333+nini22P@users.noreply.github.com> Date: Mon, 28 Jul 2025 18:00:16 +0800 Subject: [PATCH 03/31] update app icon --- README.md | 2 +- README_CN.md | 2 +- .../app/src/main/ic_launcher-playstore.png | Bin 15918 -> 31919 bytes .../src/main/res/mipmap-hdpi/ic_launcher.png | Bin 1887 -> 0 bytes .../src/main/res/mipmap-hdpi/ic_launcher.webp | Bin 0 -> 1412 bytes .../mipmap-hdpi/ic_launcher_foreground.png | Bin 3028 -> 0 bytes .../mipmap-hdpi/ic_launcher_foreground.webp | Bin 0 -> 1684 bytes .../res/mipmap-hdpi/ic_launcher_round.png | Bin 2457 -> 0 bytes .../res/mipmap-hdpi/ic_launcher_round.webp | Bin 0 -> 2728 bytes .../src/main/res/mipmap-mdpi/ic_launcher.png | Bin 1080 -> 0 bytes .../src/main/res/mipmap-mdpi/ic_launcher.webp | Bin 0 -> 934 bytes .../mipmap-mdpi/ic_launcher_foreground.png | Bin 1804 -> 0 bytes .../mipmap-mdpi/ic_launcher_foreground.webp | Bin 0 -> 1126 bytes .../res/mipmap-mdpi/ic_launcher_round.png | Bin 1406 -> 0 bytes .../res/mipmap-mdpi/ic_launcher_round.webp | Bin 0 -> 1730 bytes .../src/main/res/mipmap-xhdpi/ic_launcher.png | Bin 2659 -> 0 bytes .../main/res/mipmap-xhdpi/ic_launcher.webp | Bin 0 -> 1882 bytes .../mipmap-xhdpi/ic_launcher_foreground.png | Bin 4835 -> 0 bytes .../mipmap-xhdpi/ic_launcher_foreground.webp | Bin 0 -> 2588 bytes .../res/mipmap-xhdpi/ic_launcher_round.png | Bin 3774 -> 0 bytes .../res/mipmap-xhdpi/ic_launcher_round.webp | Bin 0 -> 3644 bytes .../main/res/mipmap-xxhdpi/ic_launcher.png | Bin 4905 -> 0 bytes .../main/res/mipmap-xxhdpi/ic_launcher.webp | Bin 0 -> 2846 bytes .../mipmap-xxhdpi/ic_launcher_foreground.png | Bin 9247 -> 0 bytes .../mipmap-xxhdpi/ic_launcher_foreground.webp | Bin 0 -> 4372 bytes .../res/mipmap-xxhdpi/ic_launcher_round.png | Bin 6773 -> 0 bytes .../res/mipmap-xxhdpi/ic_launcher_round.webp | Bin 0 -> 5974 bytes .../main/res/mipmap-xxxhdpi/ic_launcher.png | Bin 7636 -> 0 bytes .../main/res/mipmap-xxxhdpi/ic_launcher.webp | Bin 0 -> 3966 bytes .../mipmap-xxxhdpi/ic_launcher_foreground.png | Bin 14651 -> 0 bytes .../ic_launcher_foreground.webp | Bin 0 -> 6620 bytes .../res/mipmap-xxxhdpi/ic_launcher_round.png | Bin 10557 -> 0 bytes .../res/mipmap-xxxhdpi/ic_launcher_round.webp | Bin 0 -> 8384 bytes .../res/values/ic_launcher_background.xml | 4 ++-- assets/images/icon.png | Bin 25033 -> 26338 bytes assets/images/icon.svg | 19 ------------------ assets/images/icon_transparent.png | Bin 0 -> 32839 bytes inno.iss | 2 +- lib/widgets/title_bar.dart | 6 +++++- pubspec.yaml | 4 ++-- windows/runner/resources/app_icon.ico | Bin 25055 -> 14626 bytes 41 files changed, 12 insertions(+), 27 deletions(-) delete mode 100644 android/app/src/main/res/mipmap-hdpi/ic_launcher.png create mode 100644 android/app/src/main/res/mipmap-hdpi/ic_launcher.webp delete mode 100644 android/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png create mode 100644 android/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.webp delete mode 100644 android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png create mode 100644 android/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp delete mode 100644 android/app/src/main/res/mipmap-mdpi/ic_launcher.png create mode 100644 android/app/src/main/res/mipmap-mdpi/ic_launcher.webp delete mode 100644 android/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png create mode 100644 android/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.webp delete mode 100644 android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png create mode 100644 android/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp delete mode 100644 android/app/src/main/res/mipmap-xhdpi/ic_launcher.png create mode 100644 android/app/src/main/res/mipmap-xhdpi/ic_launcher.webp delete mode 100644 android/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png create mode 100644 android/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.webp delete mode 100644 android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png create mode 100644 android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp delete mode 100644 android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png create mode 100644 android/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp delete mode 100644 android/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png create mode 100644 android/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.webp delete mode 100644 android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png create mode 100644 android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp delete mode 100644 android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png create mode 100644 android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp delete mode 100644 android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png create mode 100644 android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.webp delete mode 100644 android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png create mode 100644 android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp delete mode 100644 assets/images/icon.svg create mode 100644 assets/images/icon_transparent.png diff --git a/README.md b/README.md index 2eb4a01..c1214ad 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -icon +icon # IRIS - A lightweight video player diff --git a/README_CN.md b/README_CN.md index 977567c..b2593cc 100644 --- a/README_CN.md +++ b/README_CN.md @@ -1,4 +1,4 @@ -icon +icon # IRIS - 轻量级视频播放器 diff --git a/android/app/src/main/ic_launcher-playstore.png b/android/app/src/main/ic_launcher-playstore.png index ba9959f76d551ecd05bd5ef9153d2f0ff7cd2d79..0e3c86734b3802121d7a0ac79dba7088bfe42568 100644 GIT binary patch literal 31919 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 b@UCIcb)i;&2Ljn4;E#%ehJ2-*McDrXWkUml literal 15918 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 diff --git a/android/app/src/main/res/mipmap-hdpi/ic_launcher.png b/android/app/src/main/res/mipmap-hdpi/ic_launcher.png deleted file mode 100644 index 729ec34a338d4dcd75b4619ae258b8e2cac9cdd7..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1887 zcmV-l2cYPx+8A(JzRCr$Pn`>+oRTRhncek&kQV1rLS|iU`A?O!G2%r`V>BEKul2RT4N=2X* zQ3SN8;D=ITNhFrkTK&*Kf=!HR0*NHG5^YgP#6qRg#zG<57<|MKwG}U&&&WGpvM868qL-VjJ=o;Lc@sR!2|R(fR6<_i73+6)`nxpjzLjIymQf_ zMOd+7h23Ukm6r>sudfevb#);)`oDeq_8~JfQKB-RFD=JwXU$R5Np<~al_D9 z+mH)r5o?JlX<>leoiy9vc21A24b2aVn$;ruRZfz=H_DCwr#N(8%rIzfYz^HZwg3MNi2V&>B8j6lVdU3P93>` z)~;O}0mv>Csw4)8e$sTHrKJTaDJgc0wo_IvAT}e3#2hUMUU>@W`0?X+BY%<$X!YvV zVFl53Oh8qHKIZU%=+84H&YL$6)z#H@6v8Vj7tqR;D^dGE7AB=60imUsf7}ZH3S069HjXMlK+{zDtFp?z5?ILXbxM1bYX5!ucCFx#<8?6)(`|(-&NveGUn$$(znVGKxB4|6xBrYnE_jf(yO z1XYEJ25+V+qfGtq;lr3UYnCV|$$)mh#0Ny9H}$v>l#%#!^w;ZX`uR6QMe$tB2r8}9 z+1UxdpWd;uJR}3E+O8U|0$?#ntDq=AcVKPpA3*nwo2tN~H{GlhW(1O^9aU9TR)i!2 zdgaAvKv8h?3W`({<+tS9OSl;bxT2`DXU`%%Jv~llv6o*k0K^BPbvKqjogW-R<>en@ z-W_1Uo2CO@U0rbkXqUPQ54%=$yoroIdiChj17Q2r0et!UpsAX8TPk#!(%s!1FMyuE z6M(}(INF7b6l0WJve}N#a&yo3Vp!c@;z7~IDjKXaGE5&tksO0|ZVdst76{^MAc*@2 zFx5viGnN4?btba{#L|PIKoIl0dM%SoZGu@`T#SPU55`fD-tinN2LHvdx|V(%ybtRI_mgM87-G)6*leO(Iu8M=H1Ai(A8?E6mne z>~kCx92%PjqAp_jiqV2(Inx~l&iJZ3B7ifuYHVyoUS1wA|6%9l0-~Gt6bOT21ZsAY z54$qb4DuyC5KS&+kX&xP^gTYk{zur|^7HdiUtcc@#v4FvPudKS_V?{$(y%ZsS!nGx z;*yc^tK{Nkd^0!{y2sYu-fkC+H-O5@$|4k`cQYeJeK27x#;~UZY@`P>&-LKKg$tl} zLmV7(U5xEX>%o})&w$7_aT+z5B$sOHaIS0%X6G$-z$IK&E+D!mZKM*bn52bw zG;2IElYFMRRABmp@V`;x1{iN#Y}Kk&mI0clBntPlNfV;tO-MLR%^=R)$q1}m1(lYT zS}MpaCTV~Fe9A=lGy(Zm?IEOOWx1&uZCi2ym6Vi3C&Z^3No6_D7d$3YcY4h0@-kAe{y3GT|FDQj#(U9_~>lB`F5QbV7yUKQBjf5EOt+F zGESU4>GY+D!hb=8(B^54Ng57)exM`G|Z8-w;#O(*=W1e^EjOFmb0yjaJwdDv9 zeeBfsYm73iap{l}KyC(U4=$kRT_zNiP>?ebCKSZs Z_aB^Ume9%WuulL0002ovPDHLkV1mQib4~yN diff --git a/android/app/src/main/res/mipmap-hdpi/ic_launcher.webp b/android/app/src/main/res/mipmap-hdpi/ic_launcher.webp new file mode 100644 index 0000000000000000000000000000000000000000..22ca9617848d2e49a9d03cc3d5c9675021b0964b GIT binary patch literal 1412 zcmV-~1$+8ZNk&F|1pok7MM6+kP&iC*1pojqN5Byf6^DR?;Qw)15D`goqru7c&yn&I zaLGUpn|Ci6IRCN&GyBY+RQ?s9;9rxsxm4~_5oWLfbRGN9wrv}#2V_ihf?R9cj@daR+qP}nwvE`f zZ603!|C`xga0GVRf8~wcshrf4RC!mN%-U5@8LgelPU=h>^eOk+UF=l0&L9fgwn6&; z!!T$WNRpH`e1a2K5}f`!awAER9K8&uhIs=YzXLiYNU}KqW1?1h<`|X+jX`hF8FYd&=ZN*k)$%C?-3rXiG_!V9%*|bR}gJ5XYR>_Et zzg($-NXB%6)xSnOq2sM};wc?(V}J}$v9wCIC;=S&Bmt`BkXx%wXs*;ksfI)3Zd#R6 zfWd6F+xreK134J=V#}UO17h&<`=vKKvg@MyRuaf01vHFu|H=Dj+gPO%Wv0=cRghF) z_S}6GN&v9hA-=IwZ?=9M{W(M(Rbp>f1ktpo@&qI|fTNZ_tqK4Q$TmvIs6Tt9d6mEv z=|^@|MP4;=GLDuk$bqu=?x(>T3A+pE@i^a)ohNMVZ2Rtv0yzBk4$8==gUUpDf^Ow_ z!!9a;zseU^>qchn-Cb>vz8drB@%?20J0I}eeuFA0z-sg8>J$2~BVy!j^gMZteAR4ZD$`!|ZBL#~*)S8ab4P-$MmoV`{w zZ%1YF%xmf7^j^GL8VGdQz5R%Ow}98rD; zA{Dl8MTp4$@&}Hu+qpYH#gG2%SlYP%Qb0O9e=fCKF?YNJI_`){^ZW8w!!!3bi53GT zBVO_4RsD(Sd-~tv_|7WUJZ7X$!*$NxzHGdRD*0U| zU62C&bBK%CeTBzQ7b%kiR-ca}`w)1`#nn2Ise86{;Sl??kC6Zx-j{T$9+GzN8Fl6# z36g-jkC0KuUVBXMZ`xIxSx>nZPQJIuf&dJoyR)(mVAC4<*QN8E-|zqZrzhL{ynP(w zF9HB4f)2G(nuCkFs+P=qOE5UDlg08S<&q-!;u-c%C<{^sYgKAih@=brU2oIu?ZZZg z-M=Op(CJNeysDf#3hq3GR_%X6ssIHJH@v%ZlAQNKaBo@FJ(OC`e92|s*5F78~0XBHc+CbKn SvQe^(x4qouyz~&n$EX-H{l&Wg literal 0 HcmV?d00001 diff --git a/android/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png b/android/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png deleted file mode 100644 index 9bf06eee52421f2e53252171f88ef57c59aa650a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3028 zcmds3=|2>T8lJ(7AF;M1w+>WY5?tTMfo`G#ZjMyRsY$ z|0)QW-P<-!OR@*>u8pY?;3ZvZ5db*x$Q<*BU4-juA>QA1M7k?=6`6pM=g+#7clFZ3 zmq6eAh9u>K3a7u5s(65D5aF*0;W7dCPeTG4Z+RTx2q8-sCEdk`Y@lYjXRoJ+REpO< zPN;sr^g%vB`}NKI-^xCTTEGOQiWkSJomA?`&hEH*G;8nr!-DVBXy(q*?v6e)oK9)b zXq^Fa$h;DI1yp>;$p_=$I4J`lDtN{S0fhJ-uK~~sa-7KkqFYP?HvrBf?#6}Y;#4pI zK+Qh_bgyy^xJ?!kF|WRyhfNnxFSB=M%05&SuqdmjP~X&i5;EYjF9HdKuTV>v)mC42}<% z^UoWnZ&RY z5Vls8U~xw&315mT!XkjxW1zawEjtV+82kz)ALj}q|bne%Oka^ zO4ANN%eo-(x5e$V4xh%y$JJ!7C)GRfLPu(xe8fMPRbrNsO-Ra}ot*_|t&6tKD5e_V zEr8$he#f;jBvsO`Xh)qWIJl3;MjnzJo(&7xW#4TGU(jNGt(je$X;J_guG#3UBXqlDxyrU8(>n2UZ+{u!b#rr`4$8(K z^6aGeRrK@yT8OEsijuy-#~*Y$-DYz%Ef;SF{Pt(zz*-P;0^Yk#6 zJ>)ZEs8Vuf*3{ILsn#i-xohS?_;E*|*H!;WNN5A?{;Hz>ZJdKI%pL4rGC2Hmhq<%0 zb!dGW#sI7{2VT6j=QFvCdb3}u;yMce37^{!p;{p^*>Qo_NWD z($q|!TaW0};xW62u2Xm5NfUDlPp-U}?IPvrk@5cIQ{U&mzwMq@PxpiWJj)y?R_hZM zV@v6fHs`+)Cjg38C(T##`dpp*V&tVCs zWh?Jq4=x_mSkpXdG(81_W~-~+6}7c$tD%+$;G93i%zSu>iSZ(ZN+@|T5chdW?qB`j zAIZ4_zBZt5WQU1tgo`S4NA^$*!Bs`{8Lzj5^tXRaxT_DS9dS7YwlJB zZ!Xd0PE6Cmm>cATJudS`9pNm*C2sK`btLc=jW0^s&Pg~WLAijFlk--s$G8sq`*3GC z$^$Btvj4#3%5&@h*Elk&yF(N_y1ky1nT-c?a}WQA?tlvHt3_c?P&C~!_@4g~w~Abk zk(b%!9s!r12ePhwqi(DOO)|B-Yp(m>uBmUhSabU#mp93jQflyhJ4~1+ofUOPKX6d* zTy>oMA~Xv-vPg+~P;)+)_Q+P@_`~vc$+lkP1ICq|(1}%s=N_DOXMjy{WPttR4@ADkSbFkpi;EWM$_70eW2{HK;!BorNE$CS$fiTk4ChOE zUsxTPv@Gt?)_a5^^Jc4HM&tXL1<4D{t)b1zk>!rIQ4FY}nYQnsH|SeARjaK}qU6-B zJtc5I9V;1MVBIVewgZw`;;K3TRwWS^2U26Q4%>K(0GqjH;VOEN{4X8>=2SlJ_X@ z?~fHVYI(6vT4$fsNy58X&d|R6^Uyw0VNmDGL=vrul86^T;uk&3Or_@b>!LU6kBp(}HAAAn)&r+TDIea-cGySH*sM^x7LKt@K z>eY@&it!tvEBM33+Ni8TYG?p`4p9-GTHK)`CujzA_ww?ZB#H%Hfpe@B&rdaO=?5KA z5OM<^K6^bnz1fKT^v?xB9@1S_;V9r=={qsPLbn%($~C6R?sZ!h3LMHWZZ$Wzte%g& z9Js-iNQZ%l7TK!iju_QY&$G5NN+T3+?%vC3*MSUnn7Sb>5{%XCo_sUF2Xygg1KHwuW z=(8koq5q9)h#qZk|KKM<8^JpR-zf7gFDIQwXr??g4m(gGtS&?V{Vi$SWklk0C&J

*jojk?0i!8PvH$=8 diff --git a/android/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.webp b/android/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.webp new file mode 100644 index 0000000000000000000000000000000000000000..865c83161b3aaac223b5a8d659b277f907aa2d3f GIT binary patch literal 1684 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 X#l67LCeLsG00000NkvXXu0mjf3)6&u diff --git a/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp b/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp new file mode 100644 index 0000000000000000000000000000000000000000..a6f9f1b1be8debea3f7e814d1a02d4a05d43b0a2 GIT binary patch literal 2728 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 literal 0 HcmV?d00001 diff --git a/android/app/src/main/res/mipmap-mdpi/ic_launcher.png b/android/app/src/main/res/mipmap-mdpi/ic_launcher.png deleted file mode 100644 index 03a1a7e0f6fe7d91a8bb54ae1485d53ca1967dcf..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1080 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 diff --git a/android/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.webp b/android/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.webp new file mode 100644 index 0000000000000000000000000000000000000000..298e78982069485b4b1bb36270cea41a4cb59be6 GIT binary patch literal 1126 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 M07*qoM6N<$g6S8U*Z=?k diff --git a/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp b/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp new file mode 100644 index 0000000000000000000000000000000000000000..c830339ff295e33ec875190c799896dc1bde6ce6 GIT binary patch literal 1730 zcmV;z20i&wNk&Gx1^@t8MM6+kP&iDj1^@srFTe{B6^DYf4Vcxx?L7$*F#+-bA1lY% zHbruNGc&V?XSQpq3>Vl-`2KgULYvrDVa%*<>@OrbMA z0CVRXT0Vd`8nrXdHi>{6006-zwrx9;opiHp+uCd%V7o8a)*j8ZZ3b@J#*us9?Y4Ap zkZbM#IXPFe@{FKUJ@bD`{%lBp~;P~1=+b*b*R{kXYw{+YPEL=T+A&0ioY%jn0e0A2qyBtR6v^40kiy7--p>nj%8e5 z4@~vqM^&mHK3roDsvYKzATVDS*RtRah`05o!S5g_|9 zWHA6WP=GQ?;$_b&(=DRBW&}|KIJLz?P#la9IWdqGyx8FkMH#c`#Lj0XX4_0Hn5DYN zRN~wg%m2D+7?h!`RBR9B_Ts+`^;kTo)tDMWn5)bj=B3Bx>7u>iXP$vj&^sNF1}J!if?$!(1=RobiKyxyUyGD4b509?9VQ=i>Jq!))wS?m*Fu}2V~S$H)3mDJIP zYwl}H*}V~7AV`^H7kq^dB*_KWg)uCg@_6GBuzbUbK$a~p^mj137u-NUPl-Jc(j`IS z^Io@N@#UQgC}_D{*>s;yxHBm(7)KG8BLzTOEblZBs*-Jns{7IuRM;CKeG())&paKQ zXQ;A|pcUXL?1K<(%icZMNvQ+hm5I;68A2Ziazp*@K@F74t3OPYISYZ|Y9Psz zmpUq-fMsTk{YXf636h-mq!XJQUj0`Cm`W17CoyPsJ&Yh4;%b-}=t0z*0Jv1pJ0_&P z1R-A76ycQ-YJhu{b?ZGHiyR07L2uHP1`u5AIN8;j8H*i_L+BR@`((f461?A~Q8*wJ z>LCQN#+}4J%Q^!kcQ#wp1@0GZSo~p0>gwaaM0-ziGXynZlr7~T(8%u{Q#!KAnpOHl z4VB64M4oZXf%=ORHBmSfBQ)xf4{K~^K@)d7eIWDQU_Q*^X^7dju!OPJ*w?7OFLqUy znHuP`j=rP(CgBmm{7;p|dbXr5F3@Yb-|vPsQ1;`C2@F8eBWnH|+YWT%3b$?tQe7lF zS$ro}bCO;Vy3*7jB^%wsEw6E%r=vhKG=>m~+PKkg z03jUN^wa2ejF{GmR=sSDRBl@qbA3a+P{fC>(=fjd{gQys`7`2xz`9wj42~>*YDIdG zQhPci{7G}dzoZ6u_+Dl^5ndAW#OJ`cN(ktV2#Jn(_F@3WqBMz`G5f>ZnN9h<-PRWY z8MEkXcb(XHmeD_BcJY5?&o|>@=c~lZ%qjs?fOpt9Ls5tdQCdt*lEv&4%U2Yc$NmfUjWzawFV80|D8YO|Z-;Z@8I#qqwAkJf#xgz%~;BqY&E z-YaB8Luw&^aH;Oxx>>*T(hSq|ixe#{!!rL|2uiq?CtKU;y889-|32l-)+a67UzrOi YQ;|7-{MAcB6ZY}Km#>b8rt~BL0A|rc`v3p{ literal 0 HcmV?d00001 diff --git a/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png deleted file mode 100644 index f13eac4b070c4c2af6e5e707ec200eaee6b9c596..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2659 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 U@2l{?mV+}(0Z<*KIsgCw literal 0 HcmV?d00001 diff --git a/android/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png b/android/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png deleted file mode 100644 index 11d69e6f01f20c255424d97fa2a86226177e57d4..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4835 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 diff --git a/android/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.webp b/android/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.webp new file mode 100644 index 0000000000000000000000000000000000000000..3c3bc1e515f30d6b60a761aef3daf0415a4f20c5 GIT binary patch literal 2588 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 literal 0 HcmV?d00001 diff --git a/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png b/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png deleted file mode 100644 index cf8317d0aa878b3d0f898ccce4fe03621895f19f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3774 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 diff --git a/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp b/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp new file mode 100644 index 0000000000000000000000000000000000000000..694b0faab600b01962968863e4d16089a80904b6 GIT binary patch literal 2846 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 literal 0 HcmV?d00001 diff --git a/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png b/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png deleted file mode 100644 index 39d798535dd0aae0327e13b83e3ce895570433b8..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 9247 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 Nnu?Zk8SLfj{{k3~WYquw diff --git a/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.webp b/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.webp new file mode 100644 index 0000000000000000000000000000000000000000..e881a19ca9ff530162120fa010f260c240a6b97d GIT binary patch literal 4372 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 diff --git a/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp b/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp new file mode 100644 index 0000000000000000000000000000000000000000..c6cbaa1707c9314beb5fb98d64bfeeef416bc3c9 GIT binary patch literal 3966 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 Y{B#zHD*I4XL?Qx;h${P63CLS^5j=jorvLx| literal 0 HcmV?d00001 diff --git a/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png b/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png deleted file mode 100644 index 5f2efc8f1a7f491007158ec7b80e0f8abe9965ff..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 14651 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 literal 0 HcmV?d00001 diff --git a/android/app/src/main/res/values/ic_launcher_background.xml b/android/app/src/main/res/values/ic_launcher_background.xml index ab200ff..21fa9fa 100644 --- a/android/app/src/main/res/values/ic_launcher_background.xml +++ b/android/app/src/main/res/values/ic_launcher_background.xml @@ -1,4 +1,4 @@ - - #ffffff + + #2F2F2F \ No newline at end of file diff --git a/assets/images/icon.png b/assets/images/icon.png index d8b3a4543c83be0234099af5b7af2d6969456c72..6ee70c5fb745a2b7e8fdcdf8750d511079808b67 100644 GIT binary patch literal 26338 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 So`wrD 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 literal 25033 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 diff --git a/assets/images/icon.svg b/assets/images/icon.svg deleted file mode 100644 index 486af68..0000000 --- a/assets/images/icon.svg +++ /dev/null @@ -1,19 +0,0 @@ - - - - - - - - - - - - - - - - - - - diff --git a/assets/images/icon_transparent.png b/assets/images/icon_transparent.png new file mode 100644 index 0000000000000000000000000000000000000000..0a8303aecb11b366c8815861a611f82dc0782707 GIT binary patch literal 32839 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 %)j2G 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ʶ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 u2wcqh=w0kJ{{K%U_`g{OS$B_ffl&A*ZS5 Date: Mon, 28 Jul 2025 18:36:40 +0800 Subject: [PATCH 04/31] fix trim_logo and rename logo --- README.md | 2 +- README_CN.md | 2 +- assets/images/icon.png | Bin 26338 -> 0 bytes assets/images/icon_transparent.png | Bin 32839 -> 0 bytes assets/images/logo.png | Bin 0 -> 67077 bytes assets/images/logo_transparent.png | Bin 0 -> 94824 bytes lib/pages/settings/about.dart | 2 +- lib/widgets/title_bar.dart | 2 +- pubspec.lock | 8 ++++---- pubspec.yaml | 6 +++--- 10 files changed, 11 insertions(+), 11 deletions(-) delete mode 100644 assets/images/icon.png delete mode 100644 assets/images/icon_transparent.png create mode 100644 assets/images/logo.png create mode 100644 assets/images/logo_transparent.png diff --git a/README.md b/README.md index c1214ad..cfffbeb 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -icon +logo # IRIS - A lightweight video player diff --git a/README_CN.md b/README_CN.md index b2593cc..de90e2d 100644 --- a/README_CN.md +++ b/README_CN.md @@ -1,4 +1,4 @@ -icon +logo # IRIS - 轻量级视频播放器 diff --git a/assets/images/icon.png b/assets/images/icon.png deleted file mode 100644 index 6ee70c5fb745a2b7e8fdcdf8750d511079808b67..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 26338 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 So`wrD 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 diff --git a/assets/images/icon_transparent.png b/assets/images/icon_transparent.png deleted file mode 100644 index 0a8303aecb11b366c8815861a611f82dc0782707..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 32839 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 %)j2G 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

s(7WX#;|q^S8VdNh2?4AH98!`OKE}!~K_zyl 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 Z@DJcagDIxmHo?L+Ib(J@*T^mE{{i9ZODO;V literal 0 HcmV?d00001 diff --git a/assets/images/logo_transparent.png b/assets/images/logo_transparent.png new file mode 100644 index 0000000000000000000000000000000000000000..74a0db03bc5082f7bcfb8cbe4a7890d1104c24ea GIT binary patch literal 94824 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 Date: Mon, 28 Jul 2025 20:18:10 +0800 Subject: [PATCH 05/31] update dependencies --- pubspec.lock | 88 ++++++++++++++++++++++++++-------------------------- pubspec.yaml | 50 ++++++++++++++--------------- 2 files changed, 69 insertions(+), 69 deletions(-) diff --git a/pubspec.lock b/pubspec.lock index b1a6154..458298d 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -5,18 +5,18 @@ packages: dependency: transitive description: name: _fe_analyzer_shared - sha256: e55636ed79578b9abca5fecf9437947798f5ef7456308b5cb85720b793eac92f + sha256: da0d9209ca76bde579f2da330aeb9df62b6319c834fa7baae052021b0462401f url: "https://pub.dev" source: hosted - version: "82.0.0" + version: "85.0.0" analyzer: dependency: transitive description: name: analyzer - sha256: "904ae5bb474d32c38fb9482e2d925d5454cda04ddd0e55d2e6826bc72f6ba8c0" + sha256: "974859dc0ff5f37bc4313244b3218c791810d03ab3470a579580279ba971a48d" url: "https://pub.dev" source: hosted - version: "7.4.5" + version: "7.7.1" android_x_storage: dependency: "direct main" description: @@ -101,10 +101,10 @@ packages: dependency: transitive description: name: build - sha256: cef23f1eda9b57566c81e2133d196f8e3df48f244b317368d65c5943d91148f0 + sha256: "7d95cbbb1526ab5ae977df9b4cc660963b9b27f6d1075c0b34653868911385e4" url: "https://pub.dev" source: hosted - version: "2.4.2" + version: "3.0.0" build_config: dependency: transitive description: @@ -125,26 +125,26 @@ packages: dependency: transitive description: name: build_resolvers - sha256: b9e4fda21d846e192628e7a4f6deda6888c36b5b69ba02ff291a01fd529140f0 + sha256: "38c9c339333a09b090a638849a4c56e70a404c6bdd3b511493addfbc113b60c2" url: "https://pub.dev" source: hosted - version: "2.4.4" + version: "3.0.0" build_runner: dependency: "direct dev" description: name: build_runner - sha256: "058fe9dce1de7d69c4b84fada934df3e0153dd000758c4d65964d0166779aa99" + sha256: b971d4a1c789eba7be3e6fe6ce5e5b50fd3719e3cb485b3fad6d04358304351d url: "https://pub.dev" source: hosted - version: "2.4.15" + version: "2.6.0" build_runner_core: dependency: transitive description: name: build_runner_core - sha256: "22e3aa1c80e0ada3722fe5b63fd43d9c8990759d0a2cf489c8c5d7b2bdebc021" + sha256: c04e612ca801cd0928ccdb891c263a2b1391cb27940a5ea5afcf9ba894de5d62 url: "https://pub.dev" source: hosted - version: "8.0.0" + version: "9.2.0" built_collection: dependency: transitive description: @@ -301,26 +301,26 @@ packages: dependency: "direct main" description: name: desktop_drop - sha256: bd21017e0415632c85f6b813c846bc8c9811742507776dcf6abf91a14d946e98 + sha256: "927511f590ce01ee90d0d80f79bc71b9c919d8522d01e495e89a00c6f4a4fb5b" url: "https://pub.dev" source: hosted - version: "0.6.0" + version: "0.6.1" device_info_plus: dependency: "direct main" description: name: device_info_plus - sha256: "0c6396126421b590089447154c5f98a5de423b70cfb15b1578fd018843ee6f53" + sha256: "98f28b42168cc509abc92f88518882fd58061ea372d7999aecc424345c7bff6a" url: "https://pub.dev" source: hosted - version: "11.4.0" + version: "11.5.0" device_info_plus_platform_interface: dependency: transitive description: name: device_info_plus_platform_interface - sha256: "0b04e02b30791224b31969eb1b50d723498f402971bff3630bca2ba839bd1ed2" + sha256: e1ea89119e34903dca74b883d0dd78eb762814f97fb6c76f35e9ff74d261a18f url: "https://pub.dev" source: hosted - version: "7.0.2" + version: "7.0.3" dio: dependency: transitive description: @@ -398,10 +398,10 @@ packages: dependency: "direct main" description: name: file_picker - sha256: "77f8e81d22d2a07d0dee2c62e1dda71dc1da73bf43bb2d45af09727406167964" + sha256: ef9908739bdd9c476353d6adff72e88fd00c625f5b959ae23f7567bd5137db0a url: "https://pub.dev" source: hosted - version: "10.1.9" + version: "10.2.0" fixnum: dependency: transitive description: @@ -546,18 +546,18 @@ packages: dependency: "direct dev" description: name: freezed - sha256: "6022db4c7bfa626841b2a10f34dd1e1b68e8f8f9650db6112dcdeeca45ca793c" + sha256: da32f8ba8cfcd4ec71d9decc8cbf28bd2c31b5283d9887eb51eb4a0659d8110c url: "https://pub.dev" source: hosted - version: "3.0.6" + version: "3.2.0" freezed_annotation: dependency: "direct main" description: name: freezed_annotation - sha256: c87ff004c8aa6af2d531668b46a4ea379f7191dc6dfa066acd53d506da6e044b + sha256: "7294967ff0a6d98638e7acb774aac3af2550777accd8149c90af5b014e6d44d8" url: "https://pub.dev" source: hosted - version: "3.0.0" + version: "3.1.0" frontend_server_client: dependency: transitive description: @@ -570,10 +570,10 @@ packages: dependency: "direct main" description: name: fvp - sha256: a2b6f305a5e559abc21b1be06ca0ffb5bb6b5b523d6d45eb8e78d53f3b89e9a2 + sha256: cb867b293ee684fbc5de188040f88bae8603d967d825b9407ecb96e191dcadc9 url: "https://pub.dev" source: hosted - version: "0.32.1" + version: "0.33.1" get_it: dependency: transitive description: @@ -698,10 +698,10 @@ packages: dependency: "direct dev" description: name: json_serializable - sha256: c50ef5fc083d5b5e12eef489503ba3bf5ccc899e487d691584699b4bdefeea8c + sha256: ce2cf974ccdee13be2a510832d7fba0b94b364e0b0395dee42abaa51b855be27 url: "https://pub.dev" source: hosted - version: "6.9.5" + version: "6.10.0" leak_tracker: dependency: transitive description: @@ -971,10 +971,10 @@ packages: dependency: "direct main" description: name: permission_handler - sha256: "2d070d8684b68efb580a5997eb62f675e8a885ef0be6e754fb9ef489c177470f" + sha256: bc917da36261b00137bbc8896bf1482169cd76f866282368948f032c8c1caae1 url: "https://pub.dev" source: hosted - version: "12.0.0+1" + version: "12.0.1" permission_handler_android: dependency: transitive description: @@ -1107,10 +1107,10 @@ packages: dependency: "direct main" description: name: saf_util - sha256: b18e57677fc704918b2a67d7b5aa1c9c61dc5f3f2e7e70480576db77d18bec4a + sha256: "219f983e5f17b28998335158cdc97add9d52af9884e38b5a43f10dcc070510ec" url: "https://pub.dev" source: hosted - version: "0.10.0" + version: "0.11.0" safe_local_storage: dependency: transitive description: @@ -1123,18 +1123,18 @@ packages: dependency: "direct main" description: name: screen_brightness - sha256: "20b43489fbb12316d64633d5abb731f8d3e2c49871f65c8e434c6225d0f58fcf" + sha256: f9bcfd7029d81aa2ba960f8c67a51427c721796f7f4eea02bc2bb84c41205ad7 url: "https://pub.dev" source: hosted - version: "2.1.4" + version: "2.1.5" screen_brightness_android: dependency: transitive description: name: screen_brightness_android - sha256: "6ba1b5812f66c64e9e4892be2d36ecd34210f4e0da8bdec6a2ea34f1aa42683e" + sha256: fb5fa43cb89d0c9b8534556c427db1e97e46594ac5d66ebdcf16063b773d54ed url: "https://pub.dev" source: hosted - version: "2.1.1" + version: "2.1.2" screen_brightness_ios: dependency: transitive description: @@ -1264,18 +1264,18 @@ packages: dependency: transitive description: name: source_gen - sha256: "35c8150ece9e8c8d263337a265153c3329667640850b9304861faea59fc98f6b" + sha256: fc787b1f89ceac9580c3616f899c9a447413cbdac1df071302127764c023a134 url: "https://pub.dev" source: hosted - version: "2.0.0" + version: "3.0.0" source_helper: dependency: transitive description: name: source_helper - sha256: "86d247119aedce8e63f4751bd9626fc9613255935558447569ad42f9f5b48b3c" + sha256: "4f81479fe5194a622cdd1713fe1ecb683a6e6c85cd8cec8e2e35ee5ab3fdf2a1" url: "https://pub.dev" source: hosted - version: "1.3.5" + version: "1.3.6" source_span: dependency: transitive description: @@ -1384,10 +1384,10 @@ packages: dependency: "direct main" description: name: url_launcher - sha256: "9d06212b1362abc2f0f0d78e6f09f726608c74e3b9462e8368bb03314aa8d603" + sha256: f6a7e5c4835bb4e3026a04793a4199ca2d14c739ec378fdfe23fc8075d0439f8 url: "https://pub.dev" source: hosted - version: "6.3.1" + version: "6.3.2" url_launcher_android: dependency: transitive description: @@ -1592,10 +1592,10 @@ packages: dependency: "direct main" description: name: window_manager - sha256: "51d50168ab267d344b975b15390426b1243600d436770d3f13de67e55b05ec16" + sha256: "7eb6d6c4164ec08e1bf978d6e733f3cebe792e2a23fb07cbca25c2872bfdbdcd" url: "https://pub.dev" source: hosted - version: "0.5.0" + version: "0.5.1" window_size: dependency: "direct main" description: diff --git a/pubspec.yaml b/pubspec.yaml index 4b3a4dd..b6f43b9 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -13,43 +13,43 @@ dependencies: flutter_localizations: sdk: flutter intl: any - file_picker: ^10.1.9 + file_picker: ^10.2.0 flutter_breadcrumb: ^1.0.1 flutter_hooks: ^0.21.2 flutter_secure_storage: ^8.1.0 flutter_zustand: ^0.0.5 - media_kit: ^1.1.11 - media_kit_video: ^1.2.5 - media_kit_libs_video: ^1.0.5 - path: ^1.9.0 + media_kit: ^1.2.0 + media_kit_video: ^1.3.0 + media_kit_libs_video: ^1.0.6 + path: ^1.9.1 path_provider: ^2.1.5 - package_info_plus: ^8.1.1 - provider: ^6.1.2 + package_info_plus: ^8.3.0 + provider: ^6.1.5 webdav_client: ^1.2.2 - window_manager: ^0.5.0 - url_launcher: ^6.3.1 + window_manager: ^0.5.1 + url_launcher: ^6.3.2 scrollable_positioned_list: ^0.3.8 google_fonts: ^6.2.1 dynamic_color: ^1.7.0 window_size: ^0.1.0 uuid: ^4.5.1 - flutter_markdown: ^0.7.5 - http: ^1.2.2 - collection: ^1.19.0 + flutter_markdown: ^0.7.7+1 + http: ^1.4.0 + collection: ^1.19.1 json_annotation: ^4.9.0 - freezed_annotation: ^3.0.0 + freezed_annotation: ^3.1.0 disks_desktop: ^1.0.1 android_x_storage: ^1.0.2 - permission_handler: ^12.0.0+1 - desktop_drop: ^0.6.0 - app_links: ^6.3.3 - device_info_plus: ^11.2.1 - saf_util: ^0.10.0 - screen_brightness: ^2.1.4 + permission_handler: ^12.0.1 + desktop_drop: ^0.6.1 + app_links: ^6.4.0 + device_info_plus: ^11.5.0 + saf_util: ^0.11.0 + screen_brightness: ^2.1.5 flutter_volume_controller: ^1.3.3 - fvp: ^0.32.1 - video_player: ^2.9.2 - wakelock_plus: ^1.2.10 + fvp: ^0.33.1 + video_player: ^2.10.0 + wakelock_plus: ^1.3.2 popover: ^0.3.1 pure_ftp: ^0.7.5 drives_windows: @@ -66,9 +66,9 @@ dev_dependencies: sdk: flutter flutter_lints: ^6.0.0 flutter_oss_licenses: ^3.0.4 - build_runner: ^2.4.14 - freezed: ^3.0.6 - json_serializable: ^6.9.3 + build_runner: ^2.6.0 + freezed: ^3.2.0 + json_serializable: ^6.10.0 msix: ^3.16.10 flutter: From d40523a948dcf185fd8724806885581e9f2d780e Mon Sep 17 00:00:00 2001 From: 22 <60903333+nini22P@users.noreply.github.com> Date: Mon, 28 Jul 2025 21:47:01 +0800 Subject: [PATCH 06/31] refactor: remove redundant System UI overlay style setting and centralize it in Home widget --- lib/main.dart | 8 -------- lib/pages/home/home.dart | 10 +++++++++- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/lib/main.dart b/lib/main.dart index 1dc5245..8d107f3 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -85,14 +85,6 @@ class MyApp extends HookWidget { @override Widget build(BuildContext context) { - useEffect(() { - SystemChrome.setSystemUIOverlayStyle(const SystemUiOverlayStyle( - statusBarColor: Colors.transparent, - systemNavigationBarColor: Colors.transparent, - )); - return null; - }, []); - useEffect(() { () async { globals.storagePermissionStatus = Platform.isAndroid diff --git a/lib/pages/home/home.dart b/lib/pages/home/home.dart index 50c2dc5..823768b 100644 --- a/lib/pages/home/home.dart +++ b/lib/pages/home/home.dart @@ -1,4 +1,5 @@ import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:flutter_zustand/flutter_zustand.dart'; import 'package:iris/hooks/use_fvp_player.dart'; @@ -30,6 +31,13 @@ class Home extends HookWidget { } }(); - return Scaffold(body: player); + return AnnotatedRegion( + value: const SystemUiOverlayStyle( + statusBarIconBrightness: Brightness.light, + statusBarColor: Colors.transparent, + systemNavigationBarColor: Colors.transparent, + ), + child: Scaffold(body: player), + ); } } From 889d65b0b5ccf127d9ade6d346744b8b9662a215 Mon Sep 17 00:00:00 2001 From: 22 <60903333+nini22P@users.noreply.github.com> Date: Tue, 29 Jul 2025 22:41:52 +0800 Subject: [PATCH 07/31] feat: refactor player handling in Home widget and update IrisPlayer to accept MediaPlayer directly --- lib/main.dart | 1 - lib/pages/home/home.dart | 71 ++++++++++++++++++++++++------- lib/pages/player/iris_player.dart | 12 +++--- 3 files changed, 62 insertions(+), 22 deletions(-) diff --git a/lib/main.dart b/lib/main.dart index 8d107f3..198f414 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -2,7 +2,6 @@ import 'dart:io'; import 'package:app_links/app_links.dart'; import 'package:fvp/fvp.dart' as fvp; import 'package:flutter/material.dart'; -import 'package:flutter/services.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:flutter_zustand/flutter_zustand.dart'; import 'package:iris/info.dart'; diff --git a/lib/pages/home/home.dart b/lib/pages/home/home.dart index 823768b..49ac4a0 100644 --- a/lib/pages/home/home.dart +++ b/lib/pages/home/home.dart @@ -4,6 +4,7 @@ import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:flutter_zustand/flutter_zustand.dart'; import 'package:iris/hooks/use_fvp_player.dart'; import 'package:iris/hooks/use_media_kit_player.dart'; +import 'package:iris/models/player.dart'; import 'package:iris/models/store/app_state.dart'; import 'package:iris/pages/player/iris_player.dart'; import 'package:iris/store/use_app_store.dart'; @@ -16,20 +17,25 @@ class Home extends HookWidget { final playerBackend = useAppStore().select(context, (state) => state.playerBackend); - final player = () { - switch (playerBackend) { - case PlayerBackend.mediaKit: - return IrisPlayer( - key: const ValueKey('media-kit'), - playerHooks: useMediaKitPlayer, - ); - case PlayerBackend.fvp: - return IrisPlayer( - key: const ValueKey('fvp'), - playerHooks: useFvpPlayer, - ); - } - }(); + final playerState = useState(null); + + void handlePlayerCreated(MediaPlayer player) { + WidgetsBinding.instance.addPostFrameCallback((_) { + if (context.mounted) { + playerState.value = player; + } + }); + } + + final Widget playerHost; + switch (playerBackend) { + case PlayerBackend.mediaKit: + playerHost = _MediaKitPlayerHost(onPlayerCreated: handlePlayerCreated); + break; + case PlayerBackend.fvp: + playerHost = _FvpPlayerHost(onPlayerCreated: handlePlayerCreated); + break; + } return AnnotatedRegion( value: const SystemUiOverlayStyle( @@ -37,7 +43,42 @@ class Home extends HookWidget { statusBarColor: Colors.transparent, systemNavigationBarColor: Colors.transparent, ), - child: Scaffold(body: player), + child: Scaffold( + backgroundColor: Color(0xFF2f2f2f), + body: Stack( + children: [ + playerHost, + if (playerState.value != null) + IrisPlayer(player: playerState.value!) + ], + ), + ), ); } } + +class _MediaKitPlayerHost extends HookWidget { + final ValueChanged onPlayerCreated; + + const _MediaKitPlayerHost({required this.onPlayerCreated}); + + @override + Widget build(BuildContext context) { + final player = useMediaKitPlayer(context); + onPlayerCreated(player); + return Container(); // Doesn't build any UI itself. + } +} + +class _FvpPlayerHost extends HookWidget { + final ValueChanged onPlayerCreated; + + const _FvpPlayerHost({required this.onPlayerCreated}); + + @override + Widget build(BuildContext context) { + final player = useFvpPlayer(context); + onPlayerCreated(player); + return Container(); // Doesn't build any UI itself. + } +} diff --git a/lib/pages/player/iris_player.dart b/lib/pages/player/iris_player.dart index cdd4f8a..f4fb943 100644 --- a/lib/pages/player/iris_player.dart +++ b/lib/pages/player/iris_player.dart @@ -48,14 +48,12 @@ enum MediaType { } class IrisPlayer extends HookWidget { - const IrisPlayer({super.key, required this.playerHooks}); + const IrisPlayer({super.key, required this.player}); - final MediaPlayer Function(BuildContext) playerHooks; + final MediaPlayer player; @override Widget build(BuildContext context) { - final MediaPlayer player = playerHooks(context); - useAppLifecycle(player); useFullScreen(context); useOrientation(context, player); @@ -796,13 +794,15 @@ class IrisPlayer extends HookWidget { child: SizedBox( width: player.width, height: player.height, - child: VideoPlayer(player.controller), + child: VideoPlayer( + (player as FvpPlayer).controller), ), ) : player is MediaKitPlayer ? Video( key: ValueKey(currentPlay?.file.uri), - controller: player.controller, + controller: + (player as MediaKitPlayer).controller, controls: NoVideoControls, fit: fit == BoxFit.none ? BoxFit.contain From b0ab5890def82762e39df02adf7b73a34e04f179 Mon Sep 17 00:00:00 2001 From: 22 <60903333+nini22P@users.noreply.github.com> Date: Sun, 17 Aug 2025 13:32:57 +0800 Subject: [PATCH 08/31] refactor: split keyboard and touch events into hooks --- inno.iss | 2 +- lib/hooks/{ => player}/use_fvp_player.dart | 12 +- .../{ => player}/use_media_kit_player.dart | 31 +- lib/hooks/player/use_player.dart | 18 + lib/hooks/use_gesture.dart | 298 +++++ lib/hooks/use_keyboard.dart | 250 ++++ lib/pages/home/home.dart | 61 +- lib/pages/player/control_bar/control_bar.dart | 2 + .../control_bar/control_bar_slider.dart | 32 +- lib/pages/player/iris_player.dart | 1065 ++++++----------- .../show_open_link_bottom_sheet.dart | 2 + .../dialogs/show_open_link_dialog.dart | 2 + pubspec.lock | 160 ++- pubspec.yaml | 36 +- 14 files changed, 1038 insertions(+), 933 deletions(-) rename lib/hooks/{ => player}/use_fvp_player.dart (98%) rename lib/hooks/{ => player}/use_media_kit_player.dart (93%) create mode 100644 lib/hooks/player/use_player.dart create mode 100644 lib/hooks/use_gesture.dart create mode 100644 lib/hooks/use_keyboard.dart diff --git a/inno.iss b/inno.iss index 9884829..f0be132 100644 --- a/inno.iss +++ b/inno.iss @@ -2,7 +2,7 @@ ; SEE THE DOCUMENTATION FOR DETAILS ON CREATING INNO SETUP SCRIPT FILES! #define MyAppName "IRIS" -#define MyAppVersion "1.4.3" +#define MyAppVersion "1.5.0" #define MyAppPublisher "nini22P" #define MyAppURL "https://github.com/nini22P/iris" #define MyAppExeName "iris.exe" diff --git a/lib/hooks/use_fvp_player.dart b/lib/hooks/player/use_fvp_player.dart similarity index 98% rename from lib/hooks/use_fvp_player.dart rename to lib/hooks/player/use_fvp_player.dart index 20359ae..60b9612 100644 --- a/lib/hooks/use_fvp_player.dart +++ b/lib/hooks/player/use_fvp_player.dart @@ -21,7 +21,11 @@ import 'package:saf_util/saf_util.dart'; import 'package:video_player/video_player.dart'; import 'package:wakelock_plus/wakelock_plus.dart'; -FvpPlayer useFvpPlayer(BuildContext context) { +FvpPlayer? useFvpPlayer(BuildContext context, bool isActive) { + if (!isActive) { + return null; + } + final autoPlay = useAppStore().select(context, (state) => state.autoPlay); final rate = useAppStore().select(context, (state) => state.rate); final volume = useAppStore().select(context, (state) => state.volume); @@ -117,8 +121,6 @@ FvpPlayer useFvpPlayer(BuildContext context) { }; }, [controller, initValue.value]); - useEffect(() => controller.dispose, []); - final isPlaying = useListenableSelector(controller, () => controller.value.isPlaying); final duration = @@ -277,7 +279,6 @@ FvpPlayer useFvpPlayer(BuildContext context) { }, [isPlaying]); Future play() async { - await useAppStore().updateAutoPlay(true); if (!controller.value.isInitialized && !isInitializing.value) { init(); } @@ -285,7 +286,6 @@ FvpPlayer useFvpPlayer(BuildContext context) { } Future pause() async { - await useAppStore().updateAutoPlay(false); controller.pause(); } @@ -335,6 +335,8 @@ FvpPlayer useFvpPlayer(BuildContext context) { useEffect(() => saveProgress, []); + useEffect(() => controller.dispose, []); + return FvpPlayer( controller: controller, isInitializing: isInitializing.value, diff --git a/lib/hooks/use_media_kit_player.dart b/lib/hooks/player/use_media_kit_player.dart similarity index 93% rename from lib/hooks/use_media_kit_player.dart rename to lib/hooks/player/use_media_kit_player.dart index 87648a0..2428669 100644 --- a/lib/hooks/use_media_kit_player.dart +++ b/lib/hooks/player/use_media_kit_player.dart @@ -20,7 +20,11 @@ import 'package:media_kit_video/media_kit_video.dart'; import 'package:media_stream/media_stream.dart'; import 'package:path_provider/path_provider.dart'; -MediaKitPlayer useMediaKitPlayer(BuildContext context) { +MediaKitPlayer? useMediaKitPlayer(BuildContext context, bool isActive) { + if (!isActive) { + return null; + } + final player = useMemoized( () => Player( configuration: const PlayerConfiguration( @@ -294,7 +298,6 @@ MediaKitPlayer useMediaKitPlayer(BuildContext context) { useEffect(() => saveProgress, []); Future play() async { - await useAppStore().updateAutoPlay(true); if (duration == Duration.zero && file != null && !isInitializing.value) { await init(file); } @@ -302,31 +305,27 @@ MediaKitPlayer useMediaKitPlayer(BuildContext context) { } Future pause() async { - await useAppStore().updateAutoPlay(false); await player.pause(); } - Future seekTo(Duration newPosition) async => newPosition.inSeconds < 0 - ? await player.seek(Duration.zero) - : newPosition.inSeconds > duration.inSeconds - ? await player.seek(duration) - : await player.seek(newPosition); + Future seekTo(Duration newPosition) async => + newPosition.inMilliseconds < 0 + ? await player.seek(Duration.zero) + : newPosition.inMilliseconds > duration.inMilliseconds + ? await player.seek(duration) + : await player.seek(newPosition); Future backward(int seconds) async { - if (file?.type == ContentType.video) { - await seekTo(Duration(seconds: position.value.inSeconds - seconds)); - } + await seekTo(Duration(seconds: position.value.inSeconds - seconds)); } Future forward(int seconds) async { - if (file?.type == ContentType.video) { - await seekTo(Duration(seconds: position.value.inSeconds + seconds)); - } + await seekTo(Duration(seconds: position.value.inSeconds + seconds)); } Future stepBackward() async { final nativePlayer = player.platform; - if (nativePlayer is NativePlayer) { + if (nativePlayer is NativePlayer && file?.type == ContentType.video) { await nativePlayer.command(['frame-back-step']); logger('Step backward'); } @@ -334,7 +333,7 @@ MediaKitPlayer useMediaKitPlayer(BuildContext context) { Future stepForward() async { final nativePlayer = player.platform; - if (nativePlayer is NativePlayer) { + if (nativePlayer is NativePlayer && file?.type == ContentType.video) { await nativePlayer.command(['frame-step']); logger('Step forward'); } diff --git a/lib/hooks/player/use_player.dart b/lib/hooks/player/use_player.dart new file mode 100644 index 0000000..ee48b34 --- /dev/null +++ b/lib/hooks/player/use_player.dart @@ -0,0 +1,18 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_zustand/flutter_zustand.dart'; +import 'package:iris/hooks/player/use_fvp_player.dart'; +import 'package:iris/hooks/player/use_media_kit_player.dart'; +import 'package:iris/models/player.dart'; +import 'package:iris/models/store/app_state.dart'; +import 'package:iris/store/use_app_store.dart'; + +MediaPlayer usePlayer(BuildContext context) { + final playerBackend = + useAppStore().select(context, (state) => state.playerBackend); + + final mediaKitPlayer = + useMediaKitPlayer(context, playerBackend == PlayerBackend.mediaKit); + final fvpPlayer = useFvpPlayer(context, playerBackend == PlayerBackend.fvp); + + return fvpPlayer ?? mediaKitPlayer!; +} diff --git a/lib/hooks/use_gesture.dart b/lib/hooks/use_gesture.dart new file mode 100644 index 0000000..2bb0ed5 --- /dev/null +++ b/lib/hooks/use_gesture.dart @@ -0,0 +1,298 @@ +import 'package:flutter/gestures.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_hooks/flutter_hooks.dart'; +import 'package:iris/hooks/use_brightness.dart'; +import 'package:iris/hooks/use_volume.dart'; +import 'package:iris/models/player.dart'; +import 'package:iris/store/use_app_store.dart'; +import 'package:iris/store/use_ui_store.dart'; +import 'package:iris/utils/platform.dart'; +import 'package:iris/utils/resize_window.dart'; +import 'package:window_manager/window_manager.dart'; + +class Gesture { + final void Function(TapDownDetails) onTapDown; + final void Function() onTap; + final void Function(TapDownDetails) onDoubleTapDown; + final void Function(LongPressStartDetails) onLongPressStart; + final void Function(LongPressMoveUpdateDetails) onLongPressMoveUpdate; + final void Function(LongPressEndDetails) onLongPressEnd; + final void Function() onLongPressCancel; + final void Function(DragStartDetails) onPanStart; + final void Function(DragUpdateDetails) onPanUpdate; + final void Function(DragEndDetails) onPanEnd; + final void Function() onPanCancel; + final void Function(PointerHoverEvent) onHover; + + final bool isLongPress; + final bool isLeftGesture; + final bool isRightGesture; + final double? brightness; + final double? volume; + final MouseCursor cursor; + + Gesture({ + required this.onTapDown, + required this.onTap, + required this.onDoubleTapDown, + required this.onLongPressStart, + required this.onLongPressMoveUpdate, + required this.onLongPressEnd, + required this.onLongPressCancel, + required this.onPanStart, + required this.onPanUpdate, + required this.onPanEnd, + required this.onPanCancel, + required this.onHover, + required this.isLongPress, + required this.isLeftGesture, + required this.isRightGesture, + required this.brightness, + required this.volume, + required this.cursor, + }); +} + +Gesture useGesture({ + required BuildContext context, + required MediaPlayer player, + required bool isFullScreen, + required void Function() showControl, + required void Function() hideControl, + required void Function() showProgress, + required ValueNotifier isHover, + required ValueNotifier isShowControl, +}) { + final isTouch = useState(false); + final isLongPress = useState(false); + final startPosition = useState(null); + final isHorizontalGesture = useState(false); + final isVerticalGesture = useState(false); + final isLeftGesture = useState(false); + final isRightGesture = useState(false); + + final brightness = useBrightness(isLeftGesture.value); + final volume = useVolume(isRightGesture.value); + + void onTapDown(TapDownDetails details) { + if (details.kind == PointerDeviceKind.touch) { + isTouch.value = true; + } + } + + void onTap() { + if (isShowControl.value) { + hideControl(); + } else { + showControl(); + } + } + + void onDoubleTapDown(TapDownDetails details) async { + if (details.kind == PointerDeviceKind.touch) { + double position = + details.globalPosition.dx / MediaQuery.of(context).size.width; + if (position > 0.75) { + if (isShowControl.value) { + showControl(); + } else { + showProgress(); + } + await player.forward(10); + } else if (position < 0.25) { + if (isShowControl.value) { + showControl(); + } else { + showProgress(); + } + player.backward(10); + } else { + if (player.isPlaying == true) { + await useAppStore().updateAutoPlay(false); + player.pause(); + showControl(); + } else { + await useAppStore().updateAutoPlay(true); + player.play(); + } + } + } else { + if (isDesktop) { + if (isFullScreen) { + await resizeWindow(player.aspect); + } + useUiStore().updateFullScreen(!isFullScreen); + } + } + } + + void onLongPressStart(LongPressStartDetails details) { + if (isTouch.value && player.isPlaying == true) { + isLongPress.value = true; + useAppStore().updateRate(2.0); + } + } + + void onLongPressMoveUpdate(LongPressMoveUpdateDetails details) { + int fast = (details.offsetFromOrigin.dx / 50).toInt(); + if (fast >= 1) { + useAppStore().updateRate(fast > 4 ? 5.0 : (1 + fast).toDouble()); + } else if (fast <= -1) { + useAppStore() + .updateRate(fast < -3 ? 0.25 : (1 - 0.25 * fast.abs()).toDouble()); + } + } + + void onLongPressEnd(LongPressEndDetails details) { + if (isLongPress.value) { + useAppStore().updateRate(1.0); + } + isLongPress.value = false; + isTouch.value = false; + } + + void onLongPressCancel() { + if (isLongPress.value) { + useAppStore().updateRate(1.0); + } + isLongPress.value = false; + isTouch.value = false; + } + + void onPanStart(DragStartDetails details) async { + if (isDesktop && details.kind != PointerDeviceKind.touch) { + windowManager.startDragging(); + } else if (details.kind == PointerDeviceKind.touch) { + isTouch.value = true; + startPosition.value = details.globalPosition; + } + } + + void onPanUpdate(DragUpdateDetails details) async { + if (isTouch.value && startPosition.value != null) { + // 判断滑动方向 + double dx = (details.globalPosition.dx - startPosition.value!.dx).abs(); + double dy = (details.globalPosition.dy - startPosition.value!.dy).abs(); + if (!isHorizontalGesture.value && !isVerticalGesture.value) { + if (dx > dy) { + isHorizontalGesture.value = true; + player.updateSeeking(true); + } else { + isVerticalGesture.value = true; + } + } + + // 水平滑动 + if (isHorizontalGesture.value && player.seeking) { + double dx = details.delta.dx; + int seconds = (dx * 2 + player.position.inSeconds).toInt(); + Duration position = Duration( + seconds: seconds < 0 + ? 0 + : seconds > player.duration.inSeconds + ? player.duration.inSeconds + : seconds); + player.updatePosition(position); + if (isShowControl.value) { + showControl(); + } else { + showProgress(); + } + } + + // 垂直滑动 + final startDX = startPosition.value?.dx; + if (isVerticalGesture.value && startDX != null) { + if (!isLeftGesture.value && !isRightGesture.value) { + if (startDX < (MediaQuery.of(context).size.width / 2)) { + isLeftGesture.value = true; + } else { + isRightGesture.value = true; + } + } + + double dy = details.delta.dy; + + // 屏幕亮度 + if (isLeftGesture.value && brightness.value != null) { + final newBrightness = brightness.value! - dy / 200; + brightness.value = newBrightness > 1 + ? 1 + : newBrightness < 0 + ? 0 + : newBrightness; + } + + // 音量 + if (isRightGesture.value && volume.value != null) { + final newVolume = volume.value! - dy / 200; + volume.value = newVolume > 1 + ? 1 + : newVolume < 0 + ? 0 + : newVolume; + } + } + } + } + + void onPanEnd(DragEndDetails details) async { + isTouch.value = false; + isHorizontalGesture.value = false; + isVerticalGesture.value = false; + isLeftGesture.value = false; + isRightGesture.value = false; + startPosition.value = null; + if (player.seeking) { + await player.seekTo(player.position); + player.updateSeeking(false); + } + } + + void onPanCancel() async { + isHorizontalGesture.value = false; + isVerticalGesture.value = false; + isLeftGesture.value = false; + isRightGesture.value = false; + startPosition.value = null; + if (player.seeking) { + isTouch.value = false; + await player.seekTo(player.position); + player.updateSeeking(false); + } + } + + void onHover(PointerHoverEvent event) { + if (event.kind != PointerDeviceKind.touch) { + isHover.value = true; + showControl(); + } + } + + final cursor = useMemoized(() { + return player.isPlaying == false + ? SystemMouseCursors.basic + : SystemMouseCursors.none; + }, [player.isPlaying]); + + return Gesture( + onTapDown: onTapDown, + onTap: onTap, + onDoubleTapDown: onDoubleTapDown, + onLongPressStart: onLongPressStart, + onLongPressMoveUpdate: onLongPressMoveUpdate, + onLongPressEnd: onLongPressEnd, + onLongPressCancel: onLongPressCancel, + onPanStart: onPanStart, + onPanUpdate: onPanUpdate, + onPanEnd: onPanEnd, + onPanCancel: onPanCancel, + onHover: onHover, + isLongPress: isLongPress.value, + isLeftGesture: isLeftGesture.value, + isRightGesture: isRightGesture.value, + brightness: brightness.value, + volume: volume.value, + cursor: cursor, + ); +} diff --git a/lib/hooks/use_keyboard.dart b/lib/hooks/use_keyboard.dart new file mode 100644 index 0000000..610ba69 --- /dev/null +++ b/lib/hooks/use_keyboard.dart @@ -0,0 +1,250 @@ +import 'dart:io'; + +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:iris/globals.dart'; +import 'package:iris/models/player.dart'; +import 'package:iris/models/storages/local.dart'; +import 'package:iris/pages/home/history.dart'; +import 'package:iris/pages/player/play_queue.dart'; +import 'package:iris/pages/player/subtitle_and_audio_track.dart'; +import 'package:iris/pages/settings/settings.dart'; +import 'package:iris/store/use_app_store.dart'; +import 'package:iris/store/use_play_queue_store.dart'; +import 'package:iris/store/use_ui_store.dart'; +import 'package:iris/utils/platform.dart'; +import 'package:iris/widgets/bottom_sheets/show_open_link_bottom_sheet.dart'; +import 'package:iris/widgets/dialogs/show_open_link_dialog.dart'; +import 'package:iris/widgets/popup.dart'; + +typedef KeyboardEvent = void Function(KeyEvent event); + +KeyboardEvent useKeyboard({ + required BuildContext context, + required MediaPlayer player, + required bool isFullScreen, + required ValueNotifier isShowControl, + required void Function() showControl, + required Future Function(Future) showControlForHover, + required void Function() showProgress, + required bool shuffle, +}) { + void onKeyEvent(KeyEvent event) async { + if (event.runtimeType == KeyDownEvent) { + if (HardwareKeyboard.instance.isAltPressed) { + switch (event.logicalKey) { + // 退出 + case LogicalKeyboardKey.keyX: + showControl(); + await player.saveProgress(); + exit(0); + } + return; + } + + if (HardwareKeyboard.instance.isControlPressed) { + switch (event.logicalKey) { + // 上一个 + case LogicalKeyboardKey.arrowLeft: + showControl(); + usePlayQueueStore().previous(); + break; + // 下一个 + case LogicalKeyboardKey.arrowRight: + showControl(); + usePlayQueueStore().next(); + break; + // 设置 + case LogicalKeyboardKey.keyP: + showControlForHover( + showPopup( + context: context, + child: const Settings(), + direction: PopupDirection.right, + ), + ); + break; + // 打开文件 + case LogicalKeyboardKey.keyO: + showControl(); + await pickLocalFile(); + showControl(); + break; + // 随机 + case LogicalKeyboardKey.keyX: + showControl(); + shuffle + ? usePlayQueueStore().sort() + : usePlayQueueStore().shuffle(); + useAppStore().updateShuffle(!shuffle); + break; + // 循环 + case LogicalKeyboardKey.keyR: + showControl(); + useAppStore().toggleRepeat(); + break; + // 视频缩放 + case LogicalKeyboardKey.keyV: + showControl(); + useAppStore().toggleFit(); + break; + // 历史 + case LogicalKeyboardKey.keyH: + showControlForHover( + showPopup( + context: context, + child: const History(), + direction: PopupDirection.right, + ), + ); + break; + // 打开链接 + case LogicalKeyboardKey.keyL: + showControl(); + isDesktop + ? await showOpenLinkDialog(context) + : await showOpenLinkBottomSheet(context); + showControl(); + break; + // 关闭当前播放媒体文件 + case LogicalKeyboardKey.keyC: + showControl(); + player.pause(); + usePlayQueueStore().updateCurrentIndex(-1); + break; + // 静音 + case LogicalKeyboardKey.keyM: + showControl(); + useAppStore().toggleMute(); + break; + default: + break; + } + return; + } + + switch (event.logicalKey) { + // 播放 | 暂停 + case LogicalKeyboardKey.space: + case LogicalKeyboardKey.mediaPlayPause: + showControl(); + if (player.isPlaying) { + player.pause(); + } else { + player.play(); + } + break; + // 上一个 + case LogicalKeyboardKey.mediaTrackPrevious: + usePlayQueueStore().previous(); + showControl(); + break; + // 下一个 + case LogicalKeyboardKey.mediaTrackNext: + showControl(); + usePlayQueueStore().next(); + break; + // 存储 + // case LogicalKeyboardKey.keyF: + // showControlForHover( + // showPopup( + // context: context, + // child: const Storages(), + // direction: PopupDirection.right, + // ), + // ); + // break; + // 播放队列 + case LogicalKeyboardKey.keyP: + showControlForHover( + showPopup( + context: context, + child: const PlayQueue(), + direction: PopupDirection.right, + ), + ); + break; + // 字幕和音轨 + case LogicalKeyboardKey.keyS: + showControlForHover( + showPopup( + context: context, + child: SubtitleAndAudioTrack(player: player), + direction: PopupDirection.right, + ), + ); + break; + // 退出全屏 + case LogicalKeyboardKey.escape: + if (isDesktop && isFullScreen) { + useUiStore().updateFullScreen(false); + } + break; + // 全屏 + case LogicalKeyboardKey.enter: + case LogicalKeyboardKey.f11: + if (isDesktop) { + useUiStore().updateFullScreen(!isFullScreen); + } + break; + case LogicalKeyboardKey.tab: + showControl(); + break; + case LogicalKeyboardKey.f10: + showControl(); + await useUiStore().toggleIsAlwaysOnTop(); + break; + case LogicalKeyboardKey.equal: + await player.stepForward(); + break; + case LogicalKeyboardKey.minus: + await player.stepBackward(); + break; + case LogicalKeyboardKey.contextMenu: + showControl(); + moreMenuKey.currentState?.showButtonMenu(); + break; + default: + break; + } + } + + if (event.runtimeType == KeyDownEvent || + event.runtimeType == KeyRepeatEvent) { + switch (event.logicalKey) { + // 快退 + case LogicalKeyboardKey.arrowLeft: + if (isShowControl.value) { + showControl(); + } else { + showProgress(); + } + player.backward(10); + break; + // 快进 + case LogicalKeyboardKey.arrowRight: + if (isShowControl.value) { + showControl(); + } else { + showProgress(); + } + player.forward(10); + break; + // 提升音量 + case LogicalKeyboardKey.arrowUp: + showControl(); + await useAppStore().updateVolume(useAppStore().state.volume + 1); + break; + // 降低音量 + case LogicalKeyboardKey.arrowDown: + showControl(); + await useAppStore().updateVolume(useAppStore().state.volume - 1); + break; + default: + break; + } + } + } + + return onKeyEvent; +} diff --git a/lib/pages/home/home.dart b/lib/pages/home/home.dart index 49ac4a0..3bc785b 100644 --- a/lib/pages/home/home.dart +++ b/lib/pages/home/home.dart @@ -1,42 +1,13 @@ import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; -import 'package:flutter_zustand/flutter_zustand.dart'; -import 'package:iris/hooks/use_fvp_player.dart'; -import 'package:iris/hooks/use_media_kit_player.dart'; -import 'package:iris/models/player.dart'; -import 'package:iris/models/store/app_state.dart'; import 'package:iris/pages/player/iris_player.dart'; -import 'package:iris/store/use_app_store.dart'; class Home extends HookWidget { const Home({super.key}); @override Widget build(BuildContext context) { - final playerBackend = - useAppStore().select(context, (state) => state.playerBackend); - - final playerState = useState(null); - - void handlePlayerCreated(MediaPlayer player) { - WidgetsBinding.instance.addPostFrameCallback((_) { - if (context.mounted) { - playerState.value = player; - } - }); - } - - final Widget playerHost; - switch (playerBackend) { - case PlayerBackend.mediaKit: - playerHost = _MediaKitPlayerHost(onPlayerCreated: handlePlayerCreated); - break; - case PlayerBackend.fvp: - playerHost = _FvpPlayerHost(onPlayerCreated: handlePlayerCreated); - break; - } - return AnnotatedRegion( value: const SystemUiOverlayStyle( statusBarIconBrightness: Brightness.light, @@ -46,39 +17,9 @@ class Home extends HookWidget { child: Scaffold( backgroundColor: Color(0xFF2f2f2f), body: Stack( - children: [ - playerHost, - if (playerState.value != null) - IrisPlayer(player: playerState.value!) - ], + children: [IrisPlayer()], ), ), ); } } - -class _MediaKitPlayerHost extends HookWidget { - final ValueChanged onPlayerCreated; - - const _MediaKitPlayerHost({required this.onPlayerCreated}); - - @override - Widget build(BuildContext context) { - final player = useMediaKitPlayer(context); - onPlayerCreated(player); - return Container(); // Doesn't build any UI itself. - } -} - -class _FvpPlayerHost extends HookWidget { - final ValueChanged onPlayerCreated; - - const _FvpPlayerHost({required this.onPlayerCreated}); - - @override - Widget build(BuildContext context) { - final player = useFvpPlayer(context); - onPlayerCreated(player); - return Container(); // Doesn't build any UI itself. - } -} diff --git a/lib/pages/player/control_bar/control_bar.dart b/lib/pages/player/control_bar/control_bar.dart index 4a4ba5b..95d5259 100644 --- a/lib/pages/player/control_bar/control_bar.dart +++ b/lib/pages/player/control_bar/control_bar.dart @@ -129,8 +129,10 @@ class ControlBar extends HookWidget { onPressed: () { showControl(); if (player.isPlaying == true) { + useAppStore().updateAutoPlay(false); player.pause(); } else { + useAppStore().updateAutoPlay(true); player.play(); } }, diff --git a/lib/pages/player/control_bar/control_bar_slider.dart b/lib/pages/player/control_bar/control_bar_slider.dart index c1cadc2..3fa7db3 100644 --- a/lib/pages/player/control_bar/control_bar_slider.dart +++ b/lib/pages/player/control_bar/control_bar_slider.dart @@ -1,6 +1,8 @@ import 'package:flutter/material.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; +import 'package:flutter_zustand/flutter_zustand.dart'; import 'package:iris/models/player.dart'; +import 'package:iris/store/use_app_store.dart'; import 'package:iris/utils/format_duration_to_minutes.dart'; class ControlBarSlider extends HookWidget { @@ -20,6 +22,8 @@ class ControlBarSlider extends HookWidget { @override Widget build(BuildContext context) { + final autoPlay = useAppStore().select(context, (state) => state.autoPlay); + return ExcludeFocus( child: Container( padding: const EdgeInsets.fromLTRB(12, 0, 12, 0), @@ -53,12 +57,12 @@ class ControlBarSlider extends HookWidget { trackHeight: 3, ), child: Slider( - value: player.buffer.inSeconds.toDouble() > - player.duration.inSeconds.toDouble() + value: player.buffer.inMilliseconds.toDouble() > + player.duration.inMilliseconds.toDouble() ? 0 - : player.buffer.inSeconds.toDouble(), + : player.buffer.inMilliseconds.toDouble(), min: 0, - max: player.duration.inSeconds.toDouble(), + max: player.duration.inMilliseconds.toDouble(), onChanged: null, ), ), @@ -78,27 +82,27 @@ class ControlBarSlider extends HookWidget { trackHeight: 4, ), child: Slider( - value: player.position.inSeconds.toDouble() > - player.duration.inSeconds.toDouble() + value: player.position.inMilliseconds.toDouble() > + player.duration.inMilliseconds.toDouble() ? 0 - : player.position.inSeconds.toDouble(), + : player.position.inMilliseconds.toDouble(), min: 0, - max: player.duration.inSeconds.toDouble(), + max: player.duration.inMilliseconds.toDouble(), onChangeStart: (value) { player.updateSeeking(true); + player.pause(); }, onChanged: (value) { showControl(); if (player is MediaKitPlayer) { - player - .updatePosition(Duration(seconds: value.toInt())); - } else if (player is FvpPlayer) { - player.seekTo(Duration(seconds: value.toInt())); + player.updatePosition( + Duration(milliseconds: value.toInt())); } + player.seekTo(Duration(milliseconds: value.toInt())); }, onChangeEnd: (value) async { - if (player is MediaKitPlayer) { - await player.seekTo(Duration(seconds: value.toInt())); + if (autoPlay) { + player.play(); } player.updateSeeking(false); }, diff --git a/lib/pages/player/iris_player.dart b/lib/pages/player/iris_player.dart index f4fb943..b401c3d 100644 --- a/lib/pages/player/iris_player.dart +++ b/lib/pages/player/iris_player.dart @@ -1,36 +1,27 @@ import 'dart:async'; import 'dart:io'; -import 'dart:ui'; import 'package:desktop_drop/desktop_drop.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:flutter_zustand/flutter_zustand.dart'; -import 'package:iris/globals.dart'; import 'package:iris/hooks/use_app_lifecycle.dart'; -import 'package:iris/hooks/use_brightness.dart'; import 'package:iris/hooks/use_cover.dart'; import 'package:iris/hooks/use_full_screen.dart'; +import 'package:iris/hooks/use_gesture.dart'; +import 'package:iris/hooks/use_keyboard.dart'; import 'package:iris/hooks/use_orientation.dart'; -import 'package:iris/hooks/use_volume.dart'; +import 'package:iris/hooks/player/use_player.dart'; import 'package:iris/info.dart'; import 'package:iris/models/file.dart'; import 'package:iris/models/player.dart'; import 'package:iris/models/storages/local.dart'; -import 'package:iris/widgets/dialogs/show_open_link_dialog.dart'; -import 'package:iris/pages/home/history.dart'; -import 'package:iris/pages/player/play_queue.dart'; import 'package:iris/pages/player/audio.dart'; import 'package:iris/pages/player/control_bar/control_bar_slider.dart'; -import 'package:iris/widgets/bottom_sheets/show_open_link_bottom_sheet.dart'; -import 'package:iris/pages/settings/settings.dart'; -import 'package:iris/pages/player/subtitle_and_audio_track.dart'; import 'package:iris/store/use_ui_store.dart'; import 'package:iris/utils/check_content_type.dart'; import 'package:iris/utils/logger.dart'; import 'package:iris/utils/platform.dart'; -import 'package:iris/widgets/popup.dart'; -import 'package:iris/pages/storages/storages.dart'; import 'package:iris/store/use_app_store.dart'; import 'package:iris/store/use_play_queue_store.dart'; import 'package:iris/utils/format_duration_to_minutes.dart'; @@ -42,32 +33,19 @@ import 'package:media_kit_video/media_kit_video.dart'; import 'package:video_player/video_player.dart'; import 'package:window_manager/window_manager.dart'; -enum MediaType { - video, - audio, -} - class IrisPlayer extends HookWidget { - const IrisPlayer({super.key, required this.player}); - - final MediaPlayer player; + const IrisPlayer({super.key}); @override Widget build(BuildContext context) { + final player = usePlayer(context); + useAppLifecycle(player); useFullScreen(context); useOrientation(context, player); final cover = useCover(context, player); final isHover = useState(false); - final isTouch = useState(false); - final isLongPress = useState(false); - final startPosition = useState(null); - final isHorizontalGesture = useState(false); - final isVerticalGesture = useState(false); - final isLeftGesture = useState(false); - final isRightGesture = useState(false); - final isShowControl = useState(true); final isShowProgress = useState(false); @@ -75,9 +53,6 @@ class IrisPlayer extends HookWidget { final progressHideTimer = useRef(null); final systemUiHideTimer = useRef(null); - final brightness = useBrightness(isLeftGesture.value); - final volume = useVolume(isRightGesture.value); - final t = getLocalizations(context); final rate = useAppStore().select(context, (state) => state.rate); final shuffle = useAppStore().select(context, (state) => state.shuffle); @@ -111,18 +86,6 @@ class IrisPlayer extends HookWidget { : INFO.title, [currentPlay, currentPlayIndex, playQueue]); - final mediaType = useMemoized( - () => player.width == null || - player.height == null || - player.width == 0 || - player.height == 0 || - (currentPlay != null && - checkContentType(currentPlay.file.name) == - ContentType.audio) - ? MediaType.audio - : MediaType.video, - [player]); - final contentColor = useMemoized( () => Theme.of(context).brightness == Brightness.dark ? Theme.of(context).colorScheme.onSurface @@ -192,7 +155,8 @@ class IrisPlayer extends HookWidget { systemUiHideTimer.value = Timer( const Duration(seconds: 3), () { - if (!isShowControl.value && mediaType == MediaType.video) { + if (!isShowControl.value && + currentPlay?.file.type == ContentType.video) { SystemChrome.setEnabledSystemUIMode(SystemUiMode.immersive); } }, @@ -243,6 +207,28 @@ class IrisPlayer extends HookWidget { resetBottomProgressTimer(); } + final gesture = useGesture( + context: context, + player: player, + isFullScreen: isFullScreen, + showControl: showControl, + hideControl: hideControl, + showProgress: showProgress, + isHover: isHover, + isShowControl: isShowControl, + ); + + final onKeyEvent = useKeyboard( + context: context, + player: player, + isFullScreen: isFullScreen, + isShowControl: isShowControl, + showControl: showControl, + showControlForHover: showControlForHover, + showProgress: showProgress, + shuffle: shuffle, + ); + useEffect(() { startControlHideTimer(); return () => controlHideTimer.value?.cancel(); @@ -260,14 +246,14 @@ class IrisPlayer extends HookWidget { }, [title, player.isPlaying]); useEffect(() { - if (isShowControl.value || mediaType != MediaType.video) { + if (isShowControl.value || currentPlay?.file.type == ContentType.video) { SystemChrome.setEnabledSystemUIMode(SystemUiMode.edgeToEdge); systemUiHideTimer.value?.cancel(); } else { SystemChrome.setEnabledSystemUIMode(SystemUiMode.immersive); } return; - }, [isShowControl.value, mediaType]); + }, [isShowControl.value, currentPlay?.file.type]); useEffect(() { SystemChrome.setSystemUIChangeCallback((value) async { @@ -278,223 +264,6 @@ class IrisPlayer extends HookWidget { return null; }, []); - void onKeyEvent(KeyEvent event) async { - if (event.runtimeType == KeyDownEvent) { - if (HardwareKeyboard.instance.isAltPressed) { - switch (event.logicalKey) { - // 退出 - case LogicalKeyboardKey.keyX: - showControl(); - await player.saveProgress(); - exit(0); - } - return; - } - - if (HardwareKeyboard.instance.isControlPressed) { - switch (event.logicalKey) { - // 上一个 - case LogicalKeyboardKey.arrowLeft: - showControl(); - usePlayQueueStore().previous(); - break; - // 下一个 - case LogicalKeyboardKey.arrowRight: - showControl(); - usePlayQueueStore().next(); - break; - // 设置 - case LogicalKeyboardKey.keyP: - showControlForHover( - showPopup( - context: context, - child: const Settings(), - direction: PopupDirection.right, - ), - ); - break; - // 打开文件 - case LogicalKeyboardKey.keyO: - showControl(); - await pickLocalFile(); - showControl(); - break; - // 随机 - case LogicalKeyboardKey.keyX: - showControl(); - shuffle - ? usePlayQueueStore().sort() - : usePlayQueueStore().shuffle(); - useAppStore().updateShuffle(!shuffle); - break; - // 循环 - case LogicalKeyboardKey.keyR: - showControl(); - useAppStore().toggleRepeat(); - break; - // 视频缩放 - case LogicalKeyboardKey.keyV: - showControl(); - useAppStore().toggleFit(); - break; - // 历史 - case LogicalKeyboardKey.keyH: - showControlForHover( - showPopup( - context: context, - child: const History(), - direction: PopupDirection.right, - ), - ); - break; - // 打开链接 - case LogicalKeyboardKey.keyL: - showControl(); - isDesktop - ? await showOpenLinkDialog(context) - : await showOpenLinkBottomSheet(context); - showControl(); - break; - // 关闭当前播放媒体文件 - case LogicalKeyboardKey.keyC: - showControl(); - player.pause(); - usePlayQueueStore().updateCurrentIndex(-1); - break; - // 静音 - case LogicalKeyboardKey.keyM: - showControl(); - useAppStore().toggleMute(); - break; - default: - break; - } - return; - } - - switch (event.logicalKey) { - // 播放 | 暂停 - case LogicalKeyboardKey.space: - case LogicalKeyboardKey.mediaPlayPause: - showControl(); - if (player.isPlaying) { - player.pause(); - } else { - player.play(); - } - break; - // 上一个 - case LogicalKeyboardKey.mediaTrackPrevious: - usePlayQueueStore().previous(); - showControl(); - break; - // 下一个 - case LogicalKeyboardKey.mediaTrackNext: - showControl(); - usePlayQueueStore().next(); - break; - // 存储 - case LogicalKeyboardKey.keyF: - showControlForHover( - showPopup( - context: context, - child: const Storages(), - direction: PopupDirection.right, - ), - ); - break; - // 播放队列 - case LogicalKeyboardKey.keyP: - showControlForHover( - showPopup( - context: context, - child: const PlayQueue(), - direction: PopupDirection.right, - ), - ); - break; - // 字幕和音轨 - case LogicalKeyboardKey.keyS: - showControlForHover( - showPopup( - context: context, - child: SubtitleAndAudioTrack(player: player), - direction: PopupDirection.right, - ), - ); - break; - // 退出全屏 - case LogicalKeyboardKey.escape: - if (isDesktop && isFullScreen) { - useUiStore().updateFullScreen(false); - } - break; - // 全屏 - case LogicalKeyboardKey.enter: - case LogicalKeyboardKey.f11: - if (isDesktop) { - useUiStore().updateFullScreen(!isFullScreen); - } - break; - case LogicalKeyboardKey.tab: - showControl(); - break; - case LogicalKeyboardKey.f10: - showControl(); - await useUiStore().toggleIsAlwaysOnTop(); - break; - case LogicalKeyboardKey.equal: - await player.stepForward(); - break; - case LogicalKeyboardKey.minus: - await player.stepBackward(); - break; - case LogicalKeyboardKey.contextMenu: - showControl(); - moreMenuKey.currentState?.showButtonMenu(); - break; - default: - break; - } - } - - if (event.runtimeType == KeyDownEvent || - event.runtimeType == KeyRepeatEvent) { - switch (event.logicalKey) { - // 快退 - case LogicalKeyboardKey.arrowLeft: - if (isShowControl.value) { - showControl(); - } else { - showProgress(); - } - player.backward(10); - break; - // 快进 - case LogicalKeyboardKey.arrowRight: - if (isShowControl.value) { - showControl(); - } else { - showProgress(); - } - player.forward(10); - break; - // 提升音量 - case LogicalKeyboardKey.arrowUp: - showControl(); - await useAppStore().updateVolume(useAppStore().state.volume + 1); - break; - // 降低音量 - case LogicalKeyboardKey.arrowDown: - showControl(); - await useAppStore().updateVolume(useAppStore().state.volume - 1); - break; - default: - break; - } - } - } - final scaleFactor = useMemoized( () => View.of(context).physicalSize.width / @@ -584,480 +353,326 @@ class IrisPlayer extends HookWidget { cursor: isShowControl.value || player.isPlaying == false ? SystemMouseCursors.basic : SystemMouseCursors.none, - onHover: (event) { - if (event.kind != PointerDeviceKind.touch) { - showControl(); - } - }, + onHover: gesture.onHover, child: GestureDetector( - onTap: () { - if (isShowControl.value) { - hideControl(); - } else { - showControl(); - } - }, - onTapDown: (details) { - if (details.kind == PointerDeviceKind.touch) { - isTouch.value = true; - } - }, - onDoubleTapDown: (details) async { - if (details.kind == PointerDeviceKind.touch) { - double position = details.globalPosition.dx / - MediaQuery.of(context).size.width; - if (position > 0.75) { - if (isShowControl.value) { - showControl(); - } else { - showProgress(); - } - await player.forward(10); - } else if (position < 0.25) { - if (isShowControl.value) { - showControl(); - } else { - showProgress(); - } - player.backward(10); - } else { - if (player.isPlaying == true) { - player.pause(); - showControl(); - } else { - player.play(); - } - } - } else { - if (isDesktop) { - if (isFullScreen) { - await resizeWindow(player.aspect); - } - useUiStore().updateFullScreen(!isFullScreen); - } - } - }, - onLongPressStart: (details) { - if (isTouch.value && player.isPlaying == true) { - isLongPress.value = true; - useAppStore().updateRate(2.0); - } - }, - onLongPressMoveUpdate: (details) { - int fast = (details.offsetFromOrigin.dx / 50).toInt(); - if (fast >= 1) { - useAppStore() - .updateRate(fast > 4 ? 5.0 : (1 + fast).toDouble()); - } else if (fast <= -1) { - useAppStore().updateRate(fast < -3 - ? 0.25 - : (1 - 0.25 * fast.abs()).toDouble()); - } - }, - onLongPressEnd: (details) { - if (isLongPress.value) { - useAppStore().updateRate(1.0); - } - isLongPress.value = false; - isTouch.value = false; - }, - onLongPressCancel: () { - if (isLongPress.value) { - useAppStore().updateRate(1.0); - } - isLongPress.value = false; - isTouch.value = false; - }, - onPanStart: (details) async { - if (isDesktop && - details.kind != PointerDeviceKind.touch) { - windowManager.startDragging(); - } else if (details.kind == PointerDeviceKind.touch) { - isTouch.value = true; - startPosition.value = details.globalPosition; - } - }, - onPanUpdate: (details) async { - if (isTouch.value && startPosition.value != null) { - double dx = (details.globalPosition.dx - - startPosition.value!.dx) - .abs(); - double dy = (details.globalPosition.dy - - startPosition.value!.dy) - .abs(); - if (!isHorizontalGesture.value && - !isVerticalGesture.value) { - if (dx > dy) { - isHorizontalGesture.value = true; - player.updateSeeking(true); - } else { - isVerticalGesture.value = true; - } - } - - // 调整进度 - if (isHorizontalGesture.value && player.seeking) { - double dx = details.delta.dx; - int seconds = - (dx * 2 + player.position.inSeconds).toInt(); - Duration position = Duration( - seconds: seconds < 0 - ? 0 - : seconds > player.duration.inSeconds - ? player.duration.inSeconds - : seconds); - player.updatePosition(position); - if (isShowControl.value) { - showControl(); - } else { - showProgress(); - } - } - - // 亮度和音量 - final startDX = startPosition.value?.dx; - if (isVerticalGesture.value && startDX != null) { - if (!isLeftGesture.value && !isRightGesture.value) { - if (startDX < - (MediaQuery.of(context).size.width / 2)) { - isLeftGesture.value = true; - } else { - isRightGesture.value = true; - } - } - - double dy = details.delta.dy; - - if (isLeftGesture.value && brightness.value != null) { - final newBrightness = brightness.value! - dy / 200; - brightness.value = newBrightness > 1 - ? 1 - : newBrightness < 0 - ? 0 - : newBrightness; - } - - if (isRightGesture.value && volume.value != null) { - final newVolume = volume.value! - dy / 200; - volume.value = newVolume > 1 - ? 1 - : newVolume < 0 - ? 0 - : newVolume; - } - } - } - }, - onPanEnd: (details) async { - isTouch.value = false; - isHorizontalGesture.value = false; - isVerticalGesture.value = false; - isLeftGesture.value = false; - isRightGesture.value = false; - startPosition.value = null; - if (player.seeking) { - await player.seekTo(player.position); - player.updateSeeking(false); - } - }, - onPanCancel: () async { - isHorizontalGesture.value = false; - isVerticalGesture.value = false; - isLeftGesture.value = false; - isRightGesture.value = false; - startPosition.value = null; - if (player.seeking) { - isTouch.value = false; - await player.seekTo(player.position); - player.updateSeeking(false); - } - }, - child: Stack( - children: [ - Positioned( - left: 0, - top: 0, - right: 0, - bottom: 0, - child: Container( - color: Colors.black, - ), - ), - Positioned( - left: videoViewOffset.dx, - top: videoViewOffset.dy, - width: videoViewSize.width, - height: videoViewSize.height, - child: player is FvpPlayer - ? FittedBox( - fit: fit, - child: SizedBox( - width: player.width, - height: player.height, - child: VideoPlayer( - (player as FvpPlayer).controller), - ), - ) - : player is MediaKitPlayer - ? Video( - key: ValueKey(currentPlay?.file.uri), - controller: - (player as MediaKitPlayer).controller, - controls: NoVideoControls, - fit: fit == BoxFit.none - ? BoxFit.contain - : fit, - // wakelock: mediaType == 'video', - ) - : Container(), - ), - ], - ), - ), - ), - ), - // Audio - if (mediaType == MediaType.audio) - Positioned( - left: 0, - top: 0, - right: 0, - bottom: 0, - child: Audio(cover: cover)), - // 播放速度 - if (rate != 1.0 && isLongPress.value) - Positioned( - left: 0, - top: 0, - right: 0, - bottom: 0, - child: Center( - child: Container( - padding: const EdgeInsets.fromLTRB(12, 12, 18, 12), - decoration: BoxDecoration( - color: Colors.black54, - borderRadius: BorderRadius.circular(8), - ), - child: Row( - mainAxisSize: MainAxisSize.min, - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Transform.translate( - offset: const Offset(0, 1.5), - child: Icon( - Icons.speed_rounded, - color: Colors.white, - size: 24, - ), - ), - const SizedBox(width: 10), - Text( - rate.toString(), - style: const TextStyle( - color: Colors.white, - fontSize: 20, - height: 1, - ), - ), - ], - ), - ), - ), - ), - // 屏幕亮度 - if (isLeftGesture.value && brightness.value != null) - Positioned( - left: 0, - top: 0, - right: 0, - bottom: 0, - child: Center( - child: Container( - padding: const EdgeInsets.fromLTRB(12, 12, 18, 12), - decoration: BoxDecoration( - color: Colors.black54, - borderRadius: BorderRadius.circular(8), - ), - child: Row( - mainAxisSize: MainAxisSize.min, - children: [ - Icon( - brightness.value == 0 - ? Icons.brightness_low_rounded - : brightness.value! < 1 - ? Icons.brightness_medium_rounded - : Icons.brightness_high_rounded, - color: Colors.white, - size: 24, - ), - const SizedBox(width: 12), - SizedBox( - width: 100, - child: LinearProgressIndicator( - value: brightness.value, - borderRadius: BorderRadius.circular(4), - backgroundColor: Colors.grey, - valueColor: - AlwaysStoppedAnimation(Colors.white), - ), - ), - ], - ), - ), - ), - ), - // 音量 - if (isRightGesture.value && volume.value != null) - Positioned( - left: 0, - top: 0, - right: 0, - bottom: 0, - child: Center( - child: Container( - padding: const EdgeInsets.fromLTRB(12, 12, 18, 12), - decoration: BoxDecoration( - color: Colors.black54, - borderRadius: BorderRadius.circular(8), - ), - child: Row( - mainAxisSize: MainAxisSize.min, - children: [ - Icon( - volume.value == 0 - ? Icons.volume_mute_rounded - : volume.value! < 0.5 - ? Icons.volume_down_rounded - : Icons.volume_up_rounded, - color: Colors.white, - size: 24, - ), - const SizedBox(width: 12), - SizedBox( - width: 100, - child: LinearProgressIndicator( - value: volume.value, - borderRadius: BorderRadius.circular(4), - backgroundColor: Colors.grey, - valueColor: AlwaysStoppedAnimation( - Colors.white, + onTap: gesture.onTap, + onTapDown: gesture.onTapDown, + onDoubleTapDown: gesture.onDoubleTapDown, + onLongPressStart: gesture.onLongPressStart, + onLongPressMoveUpdate: gesture.onLongPressMoveUpdate, + onLongPressEnd: gesture.onLongPressEnd, + onLongPressCancel: gesture.onLongPressCancel, + onPanStart: gesture.onPanStart, + onPanUpdate: gesture.onPanUpdate, + onPanEnd: gesture.onPanEnd, + onPanCancel: gesture.onPanCancel, + child: InkWell( + onTap: showControl, + child: Card( + color: Colors.transparent, + clipBehavior: Clip.hardEdge, + elevation: 0, + margin: EdgeInsets.zero, + child: Stack( + children: [ + Positioned( + left: 0, + top: 0, + right: 0, + bottom: 0, + child: Container( + color: Colors.black, ), ), - ), - ], - ), - ), - ), - ), - if (isShowProgress.value && - !isShowControl.value && - mediaType != MediaType.audio) - Positioned( - left: -28, - right: -28, - bottom: -16, - height: 32, - child: ControlBarSlider( - player: player, - showControl: showControl, - disabled: true, - color: contentColor, - ), - ), - if (isShowProgress.value && - !isShowControl.value && - mediaType != MediaType.audio) - Positioned( - left: 12, - top: 12, - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - currentPlay != null ? title : '', - style: TextStyle( - color: Colors.white, - fontSize: 20, - height: 1, - decoration: TextDecoration.none, - shadows: const [ - Shadow( - color: Colors.black, - offset: Offset(0, 0), - blurRadius: 1, - ), - ], - ), - ), - ], - ), - ), - if (isShowProgress.value && - !isShowControl.value && - mediaType != MediaType.audio) - Positioned( - left: 12, - bottom: 6, - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - '${formatDurationToMinutes(player.position)} / ${formatDurationToMinutes(player.duration)}', - style: TextStyle( - color: Colors.white, - fontSize: 16, - height: 2, - decoration: TextDecoration.none, - shadows: const [ - Shadow( - color: Colors.black, - offset: Offset(0, 0), - blurRadius: 1, + Positioned( + left: videoViewOffset.dx, + top: videoViewOffset.dy, + width: videoViewSize.width, + height: videoViewSize.height, + child: player is FvpPlayer + ? FittedBox( + fit: fit, + child: SizedBox( + width: player.width, + height: player.height, + child: VideoPlayer(player.controller), + ), + ) + : player is MediaKitPlayer + ? Video( + key: ValueKey(currentPlay?.file.uri), + controller: player.controller, + controls: NoVideoControls, + fit: fit == BoxFit.none + ? BoxFit.contain + : fit, + // wakelock: mediaType == 'video', + ) + : Container(), ), + // Audio + if (currentPlay?.file.type == ContentType.audio) + Positioned( + left: 0, + top: 0, + right: 0, + bottom: 0, + child: Audio(cover: cover), + ), + // 播放速度 + if (rate != 1.0 && gesture.isLongPress) + Positioned( + left: 0, + top: 0, + right: 0, + bottom: 0, + child: Center( + child: Container( + padding: const EdgeInsets.fromLTRB( + 12, 12, 18, 12), + decoration: BoxDecoration( + color: Colors.black54, + borderRadius: BorderRadius.circular(8), + ), + child: Row( + mainAxisSize: MainAxisSize.min, + mainAxisAlignment: + MainAxisAlignment.center, + children: [ + Transform.translate( + offset: const Offset(0, 1.5), + child: Icon( + Icons.speed_rounded, + color: Colors.white, + size: 24, + ), + ), + const SizedBox(width: 10), + Text( + rate.toString(), + style: const TextStyle( + color: Colors.white, + fontSize: 20, + height: 1, + ), + ), + ], + ), + ), + ), + ), + // 屏幕亮度 + if (gesture.isLeftGesture && + gesture.brightness != null) + Positioned( + left: 0, + top: 0, + right: 0, + bottom: 0, + child: Center( + child: Container( + padding: const EdgeInsets.fromLTRB( + 12, 12, 18, 12), + decoration: BoxDecoration( + color: Colors.black54, + borderRadius: BorderRadius.circular(8), + ), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Icon( + gesture.brightness == 0 + ? Icons.brightness_low_rounded + : gesture.brightness! < 1 + ? Icons + .brightness_medium_rounded + : Icons + .brightness_high_rounded, + color: Colors.white, + size: 24, + ), + const SizedBox(width: 12), + SizedBox( + width: 100, + child: LinearProgressIndicator( + value: gesture.brightness, + borderRadius: + BorderRadius.circular(4), + backgroundColor: Colors.grey, + valueColor: + AlwaysStoppedAnimation( + Colors.white), + ), + ), + ], + ), + ), + ), + ), + // 音量 + if (gesture.isRightGesture && + gesture.volume != null) + Positioned( + left: 0, + top: 0, + right: 0, + bottom: 0, + child: Center( + child: Container( + padding: const EdgeInsets.fromLTRB( + 12, 12, 18, 12), + decoration: BoxDecoration( + color: Colors.black54, + borderRadius: BorderRadius.circular(8), + ), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Icon( + gesture.volume == 0 + ? Icons.volume_mute_rounded + : gesture.volume! < 0.5 + ? Icons.volume_down_rounded + : Icons.volume_up_rounded, + color: Colors.white, + size: 24, + ), + const SizedBox(width: 12), + SizedBox( + width: 100, + child: LinearProgressIndicator( + value: gesture.volume, + borderRadius: + BorderRadius.circular(4), + backgroundColor: Colors.grey, + valueColor: + AlwaysStoppedAnimation( + Colors.white, + ), + ), + ), + ], + ), + ), + ), + ), + if (isShowProgress.value && + !isShowControl.value && + currentPlay?.file.type == ContentType.video) + Positioned( + left: -28, + right: -28, + bottom: -16, + height: 32, + child: ControlBarSlider( + player: player, + showControl: showControl, + disabled: true, + ), + ), + if (isShowProgress.value && + !isShowControl.value && + currentPlay?.file.type == ContentType.video) + Positioned( + left: 12, + top: 12, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + currentPlay != null ? title : '', + style: TextStyle( + color: Colors.white, + fontSize: 20, + height: 1, + decoration: TextDecoration.none, + shadows: const [ + Shadow( + color: Colors.black, + offset: Offset(0, 0), + blurRadius: 1, + ), + ], + ), + ), + ], + ), + ), + if (isShowProgress.value && + !isShowControl.value && + currentPlay?.file.type == ContentType.video) + Positioned( + left: 12, + bottom: 6, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + '${formatDurationToMinutes(player.position)} / ${formatDurationToMinutes(player.duration)}', + style: TextStyle( + color: Colors.white, + fontSize: 16, + height: 2, + decoration: TextDecoration.none, + shadows: const [ + Shadow( + color: Colors.black, + offset: Offset(0, 0), + blurRadius: 1, + ), + ], + ), + ), + ], + ), + ), ], ), ), - ], + ), ), ), + ), // 标题栏 AnimatedPositioned( duration: const Duration(milliseconds: 200), curve: Curves.easeInOutCubicEmphasized, - top: isShowControl.value || mediaType != MediaType.video + top: isShowControl.value || + currentPlay?.file.type != ContentType.video ? 0 : -72, left: 0, right: 0, - child: MouseRegion( - onHover: (event) { - if (event.kind != PointerDeviceKind.touch) { - isHover.value = true; - showControl(); - } - }, - child: GestureDetector( - onTap: () => showControl(), - onDoubleTap: () async { - if (isDesktop && await windowManager.isMaximized()) { - await windowManager.unmaximize(); - await resizeWindow(player.aspect); - } else { - await windowManager.maximize(); - } - }, - onPanStart: (details) async { - if (isDesktop) { - windowManager.startDragging(); - } - }, - child: TitleBar( - title: title, - actions: [const SizedBox(width: 8)], - color: contentColor, - overlayColor: overlayColor, - saveProgress: () => player.saveProgress(), - resizeWindow: () => resizeWindow(player.aspect), + child: SafeArea( + child: MouseRegion( + onHover: gesture.onHover, + child: GestureDetector( + onTap: () => showControl(), + onDoubleTap: () async { + if (isFullScreen) { + await useUiStore().updateFullScreen(false); + } else { + if (isDesktop && await windowManager.isMaximized()) { + await windowManager.unmaximize(); + await resizeWindow(player.aspect); + } else { + await windowManager.maximize(); + } + } + }, + onPanStart: (details) async { + if (isDesktop) { + windowManager.startDragging(); + } + }, + child: TitleBar( + title: title, + actions: [const SizedBox(width: 8)], + color: contentColor, + overlayColor: overlayColor, + saveProgress: () => player.saveProgress(), + resizeWindow: () => resizeWindow(player.aspect), + ), ), ), ), @@ -1066,7 +681,8 @@ class IrisPlayer extends HookWidget { AnimatedPositioned( duration: const Duration(milliseconds: 200), curve: Curves.easeInOutCubicEmphasized, - bottom: isShowControl.value || mediaType != MediaType.video + bottom: isShowControl.value || + currentPlay?.file.type != ContentType.video ? 0 : -128, left: 0, @@ -1076,12 +692,7 @@ class IrisPlayer extends HookWidget { child: SizedBox( width: MediaQuery.of(context).size.width, child: MouseRegion( - onHover: (event) { - if (event.kind != PointerDeviceKind.touch) { - isHover.value = true; - showControl(); - } - }, + onHover: gesture.onHover, child: GestureDetector( onTap: () => showControl(), child: ControlBar( diff --git a/lib/widgets/bottom_sheets/show_open_link_bottom_sheet.dart b/lib/widgets/bottom_sheets/show_open_link_bottom_sheet.dart index c06dc1e..2007988 100644 --- a/lib/widgets/bottom_sheets/show_open_link_bottom_sheet.dart +++ b/lib/widgets/bottom_sheets/show_open_link_bottom_sheet.dart @@ -1,6 +1,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:iris/models/file.dart'; +import 'package:iris/store/use_app_store.dart'; import 'package:iris/store/use_play_queue_store.dart'; import 'package:iris/utils/get_localizations.dart'; @@ -28,6 +29,7 @@ class OpenLinkBottomSheet extends HookWidget { void play() { if (url.value.isNotEmpty && RegExp(r'^(http://|https://)').hasMatch(url.value)) { + useAppStore().updateAutoPlay(true); usePlayQueueStore().update( playQueue: [ PlayQueueItem( diff --git a/lib/widgets/dialogs/show_open_link_dialog.dart b/lib/widgets/dialogs/show_open_link_dialog.dart index c4829f2..bf2dafd 100644 --- a/lib/widgets/dialogs/show_open_link_dialog.dart +++ b/lib/widgets/dialogs/show_open_link_dialog.dart @@ -1,6 +1,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:iris/models/file.dart'; +import 'package:iris/store/use_app_store.dart'; import 'package:iris/store/use_play_queue_store.dart'; import 'package:iris/utils/get_localizations.dart'; @@ -21,6 +22,7 @@ class OpenLinkDialog extends HookWidget { void play() { if (url.value.isNotEmpty && RegExp(r'^(http://|https://)').hasMatch(url.value)) { + useAppStore().updateAutoPlay(true); usePlayQueueStore().update( playQueue: [ PlayQueueItem( diff --git a/pubspec.lock b/pubspec.lock index 458298d..f749c08 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -5,18 +5,18 @@ packages: dependency: transitive description: name: _fe_analyzer_shared - sha256: da0d9209ca76bde579f2da330aeb9df62b6319c834fa7baae052021b0462401f + sha256: f0bb5d1648339c8308cc0b9838d8456b3cfe5c91f9dc1a735b4d003269e5da9a url: "https://pub.dev" source: hosted - version: "85.0.0" + version: "88.0.0" analyzer: dependency: transitive description: name: analyzer - sha256: "974859dc0ff5f37bc4313244b3218c791810d03ab3470a579580279ba971a48d" + sha256: "0b7b9c329d2879f8f05d6c05b32ee9ec025f39b077864bdb5ac9a7b63418a98f" url: "https://pub.dev" source: hosted - version: "7.7.1" + version: "8.1.1" android_x_storage: dependency: "direct main" description: @@ -29,10 +29,10 @@ packages: dependency: "direct main" description: name: app_links - sha256: "85ed8fc1d25a76475914fff28cc994653bd900bc2c26e4b57a49e097febb54ba" + sha256: "5f88447519add627fe1cbcab4fd1da3d4fed15b9baf29f28b22535c95ecee3e8" url: "https://pub.dev" source: hosted - version: "6.4.0" + version: "6.4.1" app_links_linux: dependency: transitive description: @@ -101,18 +101,18 @@ packages: dependency: transitive description: name: build - sha256: "7d95cbbb1526ab5ae977df9b4cc660963b9b27f6d1075c0b34653868911385e4" + sha256: "5b887c55a0f734b433b3b2d89f9cd1f99eb636b17e268a5b4259258bc916504b" url: "https://pub.dev" source: hosted - version: "3.0.0" + version: "4.0.0" build_config: dependency: transitive description: name: build_config - sha256: "4ae2de3e1e67ea270081eaee972e1bd8f027d459f249e0f1186730784c2e7e33" + sha256: "4f64382b97504dc2fcdf487d5aae33418e08b4703fc21249e4db6d804a4d0187" url: "https://pub.dev" source: hosted - version: "1.1.2" + version: "1.2.0" build_daemon: dependency: transitive description: @@ -121,30 +121,14 @@ packages: url: "https://pub.dev" source: hosted version: "4.0.4" - build_resolvers: - dependency: transitive - description: - name: build_resolvers - sha256: "38c9c339333a09b090a638849a4c56e70a404c6bdd3b511493addfbc113b60c2" - url: "https://pub.dev" - source: hosted - version: "3.0.0" build_runner: dependency: "direct dev" description: name: build_runner - sha256: b971d4a1c789eba7be3e6fe6ce5e5b50fd3719e3cb485b3fad6d04358304351d + sha256: "804c47c936df75e1911c19a4fb8c46fa8ff2b3099b9f2b2aa4726af3774f734b" url: "https://pub.dev" source: hosted - version: "2.6.0" - build_runner_core: - dependency: transitive - description: - name: build_runner_core - sha256: c04e612ca801cd0928ccdb891c263a2b1391cb27940a5ea5afcf9ba894de5d62 - url: "https://pub.dev" - source: hosted - version: "9.2.0" + version: "2.8.0" built_collection: dependency: transitive description: @@ -277,18 +261,18 @@ packages: dependency: transitive description: name: dart_pubspec_licenses - sha256: "23ddb78ff9204d08e3109ced67cd3c6c6a066f581b0edf5ee092fc3e1127f4ea" + sha256: "3e6545923039e35719415dfd2132574083a8bb5c31db074e7b63493cb6c28447" url: "https://pub.dev" source: hosted - version: "3.0.4" + version: "3.0.8" dart_style: dependency: transitive description: name: dart_style - sha256: "5b236382b47ee411741447c1f1e111459c941ea1b3f2b540dde54c210a3662af" + sha256: c87dfe3d56f183ffe9106a18aebc6db431fc7c98c31a54b952a77f3d54a85697 url: "https://pub.dev" source: hosted - version: "3.1.0" + version: "3.1.2" dbus: dependency: transitive description: @@ -309,10 +293,10 @@ packages: dependency: "direct main" description: name: device_info_plus - sha256: "98f28b42168cc509abc92f88518882fd58061ea372d7999aecc424345c7bff6a" + sha256: a7e611f8475a124caca8dac394c76c48218a244daa498c3584f97934267d1d4e url: "https://pub.dev" source: hosted - version: "11.5.0" + version: "12.0.0" device_info_plus_platform_interface: dependency: transitive description: @@ -358,10 +342,10 @@ packages: dependency: "direct main" description: name: dynamic_color - sha256: eae98052fa6e2826bdac3dd2e921c6ce2903be15c6b7f8b6d8a5d49b5086298d + sha256: "43a5a6679649a7731ab860334a5812f2067c2d9ce6452cf069c5e0c25336c17c" url: "https://pub.dev" source: hosted - version: "1.7.0" + version: "1.8.1" equatable: dependency: transitive description: @@ -398,10 +382,10 @@ packages: dependency: "direct main" description: name: file_picker - sha256: ef9908739bdd9c476353d6adff72e88fd00c625f5b959ae23f7567bd5137db0a + sha256: f2d9f173c2c14635cc0e9b14c143c49ef30b4934e8d1d274d6206fcb0086a06f url: "https://pub.dev" source: hosted - version: "10.2.0" + version: "10.3.3" fixnum: dependency: transitive description: @@ -427,10 +411,10 @@ packages: dependency: "direct main" description: name: flutter_hooks - sha256: b772e710d16d7a20c0740c4f855095026b31c7eb5ba3ab67d2bd52021cd9461d + sha256: "8ae1f090e5f4ef5cfa6670ce1ab5dddadd33f3533a7f9ba19d9f958aa2a89f42" url: "https://pub.dev" source: hosted - version: "0.21.2" + version: "0.21.3+1" flutter_lints: dependency: "direct dev" description: @@ -456,10 +440,10 @@ packages: dependency: "direct dev" description: name: flutter_oss_licenses - sha256: e4bbaeb00bc768e8430ee0c95ad304d2f256fb15194d30b912cea269871c8885 + sha256: "57c1e38b44ed8815d0303c278238b54796c4c4f57b0c4b89fa7f3c5359ac815e" url: "https://pub.dev" source: hosted - version: "3.0.4" + version: "3.0.6" flutter_plugin_android_lifecycle: dependency: transitive description: @@ -546,10 +530,10 @@ packages: dependency: "direct dev" description: name: freezed - sha256: da32f8ba8cfcd4ec71d9decc8cbf28bd2c31b5283d9887eb51eb4a0659d8110c + sha256: "13065f10e135263a4f5a4391b79a8efc5fb8106f8dd555a9e49b750b45393d77" url: "https://pub.dev" source: hosted - version: "3.2.0" + version: "3.2.3" freezed_annotation: dependency: "direct main" description: @@ -570,10 +554,10 @@ packages: dependency: "direct main" description: name: fvp - sha256: cb867b293ee684fbc5de188040f88bae8603d967d825b9407ecb96e191dcadc9 + sha256: a2850b856e177cb48f3e49940613f2180f3375bcceab2f75e3d7b915009a2840 url: "https://pub.dev" source: hosted - version: "0.33.1" + version: "0.34.0" get_it: dependency: transitive description: @@ -594,10 +578,10 @@ packages: dependency: "direct main" description: name: google_fonts - sha256: b1ac0fe2832c9cc95e5e88b57d627c5e68c223b9657f4b96e1487aa9098c7b82 + sha256: ebc94ed30fd13cefd397cb1658b593f21571f014b7d1197eeb41fb95f05d899a url: "https://pub.dev" source: hosted - version: "6.2.1" + version: "6.3.1" graphs: dependency: transitive description: @@ -626,10 +610,10 @@ packages: dependency: "direct main" description: name: http - sha256: "2c11f3f94c687ee9bad77c171151672986360b2b001d109814ee7140b2cf261b" + sha256: bb2ce4590bc2667c96f318d68cac1b5a7987ec819351d32b1c987239a815e007 url: "https://pub.dev" source: hosted - version: "1.4.0" + version: "1.5.0" http_methods: dependency: transitive description: @@ -698,34 +682,34 @@ packages: dependency: "direct dev" description: name: json_serializable - sha256: ce2cf974ccdee13be2a510832d7fba0b94b364e0b0395dee42abaa51b855be27 + sha256: "33a040668b31b320aafa4822b7b1e177e163fc3c1e835c6750319d4ab23aa6fe" url: "https://pub.dev" source: hosted - version: "6.10.0" + version: "6.11.1" leak_tracker: dependency: transitive description: name: leak_tracker - sha256: "6bb818ecbdffe216e81182c2f0714a2e62b593f4a4f13098713ff1685dfb6ab0" + sha256: "33e2e26bdd85a0112ec15400c8cbffea70d0f9c3407491f672a2fad47915e2de" url: "https://pub.dev" source: hosted - version: "10.0.9" + version: "11.0.2" leak_tracker_flutter_testing: dependency: transitive description: name: leak_tracker_flutter_testing - sha256: f8b613e7e6a13ec79cfdc0e97638fddb3ab848452eff057653abd3edba760573 + sha256: "1dbc140bb5a23c75ea9c4811222756104fbcd1a27173f0c34ca01e16bea473c1" url: "https://pub.dev" source: hosted - version: "3.0.9" + version: "3.0.10" leak_tracker_testing: dependency: transitive description: name: leak_tracker_testing - sha256: "6ba465d5d76e67ddf503e1161d1f4a6bc42306f9d66ca1e8f079a47290fb06d3" + sha256: "8d5a2d49f4a66b49744b23b018848400d23e54caf9463f4eb20df3eb8acb2eb1" url: "https://pub.dev" source: hosted - version: "3.0.1" + version: "3.0.2" lints: dependency: transitive description: @@ -859,10 +843,10 @@ packages: dependency: "direct dev" description: name: msix - sha256: bbb9b3ff4a9f8e7e7507b2a22dc0517fd1fe3db44e72de7ab052cb6b362406ee + sha256: f88033fcb9e0dd8de5b18897cbebbd28ea30596810f4a7c86b12b0c03ace87e5 url: "https://pub.dev" source: hosted - version: "3.16.10" + version: "3.16.12" mutex: dependency: transitive description: @@ -891,18 +875,18 @@ packages: dependency: "direct main" description: name: package_info_plus - sha256: "7976bfe4c583170d6cdc7077e3237560b364149fcd268b5f53d95a991963b191" + sha256: f69da0d3189a4b4ceaeb1a3defb0f329b3b352517f52bed4290f83d4f06bc08d url: "https://pub.dev" source: hosted - version: "8.3.0" + version: "9.0.0" package_info_plus_platform_interface: dependency: transitive description: name: package_info_plus_platform_interface - sha256: "6c935fb612dff8e3cc9632c2b301720c77450a126114126ffaafe28d2e87956c" + sha256: "202a487f08836a592a6bd4f901ac69b3a8f146af552bbd14407b6b41e1c3f086" url: "https://pub.dev" source: hosted - version: "3.2.0" + version: "3.2.1" path: dependency: "direct main" description: @@ -1123,18 +1107,18 @@ packages: dependency: "direct main" description: name: screen_brightness - sha256: f9bcfd7029d81aa2ba960f8c67a51427c721796f7f4eea02bc2bb84c41205ad7 + sha256: "5f70754028f169f059fdc61112a19dcbee152f8b293c42c848317854d650cba3" url: "https://pub.dev" source: hosted - version: "2.1.5" + version: "2.1.7" screen_brightness_android: dependency: transitive description: name: screen_brightness_android - sha256: fb5fa43cb89d0c9b8534556c427db1e97e46594ac5d66ebdcf16063b773d54ed + sha256: d34f5321abd03bc3474f4c381f53d189117eba0b039eac1916aa92cca5fd0a96 url: "https://pub.dev" source: hosted - version: "2.1.2" + version: "2.1.3" screen_brightness_ios: dependency: transitive description: @@ -1155,10 +1139,10 @@ packages: dependency: transitive description: name: screen_brightness_ohos - sha256: "61e313e46eaee3f83dd4e85a2a91f8a81be02c154bc9e60830a7c0fd76dac286" + sha256: a93a263dcd39b5c56e589eb495bcd001ce65cdd96ff12ab1350683559d5c5bb7 url: "https://pub.dev" source: hosted - version: "2.1.0" + version: "2.1.2" screen_brightness_platform_interface: dependency: transitive description: @@ -1264,18 +1248,18 @@ packages: dependency: transitive description: name: source_gen - sha256: fc787b1f89ceac9580c3616f899c9a447413cbdac1df071302127764c023a134 + sha256: ccf30b0c9fbcd79d8b6f5bfac23199fb354938436f62475e14aea0f29ee0f800 url: "https://pub.dev" source: hosted - version: "3.0.0" + version: "4.0.1" source_helper: dependency: transitive description: name: source_helper - sha256: "4f81479fe5194a622cdd1713fe1ecb683a6e6c85cd8cec8e2e35ee5ab3fdf2a1" + sha256: "6a3c6cc82073a8797f8c4dc4572146114a39652851c157db37e964d9c7038723" url: "https://pub.dev" source: hosted - version: "1.3.6" + version: "1.3.8" source_span: dependency: transitive description: @@ -1344,18 +1328,10 @@ packages: dependency: transitive description: name: test_api - sha256: fb31f383e2ee25fbbfe06b40fe21e1e458d14080e3c67e7ba0acfde4df4e0bbd + sha256: "522f00f556e73044315fa4585ec3270f1808a4b186c936e612cab0b565ff1e00" url: "https://pub.dev" source: hosted - version: "0.7.4" - timing: - dependency: transitive - description: - name: timing - sha256: "62ee18aca144e4a9f29d212f5a4c6a053be252b895ab14b5821996cff4ed90fe" - url: "https://pub.dev" - source: hosted - version: "1.0.2" + version: "0.7.6" typed_data: dependency: transitive description: @@ -1456,10 +1432,10 @@ packages: dependency: transitive description: name: vector_math - sha256: "80b3257d1492ce4d091729e3a67a60407d227c27241d6927be0130c98e741803" + sha256: d530bd74fea330e6e364cda7a85019c434070188383e1cd8d9777ee586914c5b url: "https://pub.dev" source: hosted - version: "2.1.4" + version: "2.2.0" video_player: dependency: "direct main" description: @@ -1520,18 +1496,18 @@ packages: dependency: "direct main" description: name: wakelock_plus - sha256: a474e314c3e8fb5adef1f9ae2d247e57467ad557fa7483a2b895bc1b421c5678 + sha256: "9296d40c9adbedaba95d1e704f4e0b434be446e2792948d0e4aa977048104228" url: "https://pub.dev" source: hosted - version: "1.3.2" + version: "1.4.0" wakelock_plus_platform_interface: dependency: transitive description: name: wakelock_plus_platform_interface - sha256: e10444072e50dbc4999d7316fd303f7ea53d31c824aa5eb05d7ccbdd98985207 + sha256: "036deb14cd62f558ca3b73006d52ce049fabcdcb2eddfe0bf0fe4e8a943b5cf2" url: "https://pub.dev" source: hosted - version: "1.2.3" + version: "1.3.0" watcher: dependency: transitive description: @@ -1637,5 +1613,5 @@ packages: source: hosted version: "0.0.5" sdks: - dart: ">=3.8.0 <4.0.0" - flutter: ">=3.29.0" + dart: ">=3.9.0 <4.0.0" + flutter: ">=3.32.0" diff --git a/pubspec.yaml b/pubspec.yaml index b6f43b9..c539288 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,7 +1,7 @@ name: iris description: "A lightweight video player" publish_to: 'none' -version: 1.4.3+3 +version: 1.5.0+3 environment: sdk: ^3.5.4 @@ -13,9 +13,9 @@ dependencies: flutter_localizations: sdk: flutter intl: any - file_picker: ^10.2.0 + file_picker: ^10.3.3 flutter_breadcrumb: ^1.0.1 - flutter_hooks: ^0.21.2 + flutter_hooks: ^0.21.3+1 flutter_secure_storage: ^8.1.0 flutter_zustand: ^0.0.5 media_kit: ^1.2.0 @@ -23,18 +23,18 @@ dependencies: media_kit_libs_video: ^1.0.6 path: ^1.9.1 path_provider: ^2.1.5 - package_info_plus: ^8.3.0 + package_info_plus: ^9.0.0 provider: ^6.1.5 webdav_client: ^1.2.2 window_manager: ^0.5.1 url_launcher: ^6.3.2 scrollable_positioned_list: ^0.3.8 - google_fonts: ^6.2.1 - dynamic_color: ^1.7.0 + google_fonts: ^6.3.1 + dynamic_color: ^1.8.1 window_size: ^0.1.0 uuid: ^4.5.1 flutter_markdown: ^0.7.7+1 - http: ^1.4.0 + http: ^1.5.0 collection: ^1.19.1 json_annotation: ^4.9.0 freezed_annotation: ^3.1.0 @@ -42,14 +42,14 @@ dependencies: android_x_storage: ^1.0.2 permission_handler: ^12.0.1 desktop_drop: ^0.6.1 - app_links: ^6.4.0 - device_info_plus: ^11.5.0 + app_links: ^6.4.1 + device_info_plus: ^12.0.0 saf_util: ^0.11.0 - screen_brightness: ^2.1.5 + screen_brightness: ^2.1.7 flutter_volume_controller: ^1.3.3 - fvp: ^0.33.1 + fvp: ^0.34.0 video_player: ^2.10.0 - wakelock_plus: ^1.3.2 + wakelock_plus: ^1.4.0 popover: ^0.3.1 pure_ftp: ^0.7.5 drives_windows: @@ -65,11 +65,11 @@ dev_dependencies: flutter_test: sdk: flutter flutter_lints: ^6.0.0 - flutter_oss_licenses: ^3.0.4 - build_runner: ^2.6.0 - freezed: ^3.2.0 - json_serializable: ^6.10.0 - msix: ^3.16.10 + flutter_oss_licenses: ^3.0.6 + build_runner: ^2.8.0 + freezed: ^3.2.3 + json_serializable: ^6.11.1 + msix: ^3.16.12 flutter: generate: true @@ -82,7 +82,7 @@ msix_config: identity_name: 22P.IRISplayer publisher_display_name: 22P publisher: CN=9740B6B2-E777-4F52-8ECD-C4A577A73010 - msix_version: 1.4.3.0 + msix_version: 1.5.0.0 logo_path: assets/images/logo.png trim_logo: false languages: en-us, zh-cn From e103c5edcb890ca19c99edb957128c939dcb4086 Mon Sep 17 00:00:00 2001 From: 22 <60903333+nini22P@users.noreply.github.com> Date: Sat, 13 Sep 2025 10:39:22 +0800 Subject: [PATCH 09/31] feat: improve uri and find subtitle --- .github/workflows/ci.yml | 2 +- lib/models/storages/ftp.dart | 4 +- lib/models/storages/local.dart | 108 ++++++++++------- lib/models/storages/webdav.dart | 30 +++-- lib/pages/player/control_bar/control_bar.dart | 13 ++- lib/utils/find_subtitle.dart | 109 +++++++----------- pubspec.lock | 98 ++++++++-------- 7 files changed, 196 insertions(+), 168 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 811494d..5f0562c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -16,7 +16,7 @@ on: jobs: build-windows: - runs-on: windows-latest + runs-on: windows-2022 steps: - name: Clone repository uses: actions/checkout@v4 diff --git a/lib/models/storages/ftp.dart b/lib/models/storages/ftp.dart index 578ddbf..727d18f 100644 --- a/lib/models/storages/ftp.dart +++ b/lib/models/storages/ftp.dart @@ -35,6 +35,8 @@ Future> getFTPFiles( final baseUri = 'ftp?host=${storage.host}&port=${storage.port}&path=${path.join('/').replaceFirst('//', '/')}'; + final allFileNames = files.map((file) => file.name).toList(); + return await Future.wait(files.map( (file) async => FileItem( storageId: storage.id, @@ -49,7 +51,7 @@ Future> getFTPFiles( : null, type: file.isDirectory ? ContentType.dir : checkContentType(file.name), subtitles: await findSubtitle( - files.map((file) => file.name).toList(), + allFileNames, file.name, baseUri, ), diff --git a/lib/models/storages/local.dart b/lib/models/storages/local.dart index 8753425..7dc23db 100644 --- a/lib/models/storages/local.dart +++ b/lib/models/storages/local.dart @@ -195,64 +195,84 @@ Future pickLocalFile() async { Future> getLocalFiles( LocalStorage storage, List path) async { - final directory = Directory(p.normalize(path.join('/'))); + final directoryPath = p.joinAll(path); + final directory = Directory(directoryPath); - List files = []; - try { - final entities = directory.list(); + if (!await directory.exists()) { + logger('Error: Directory does not exist at $directoryPath'); + return []; + } - await for (final entity in entities) { - final isDir = entity is Directory; - int size = 0; - DateTime? lastModified; - if (!isDir) { - final file = File(entity.path); - try { - size = await file.length(); - lastModified = await file.lastModified(); - } on PathAccessException catch (e) { - logger( - 'PathAccessException when getting file info for ${entity.path}: $e'); - } catch (e) { - logger('Error getting file info for ${entity.path}: $e'); - } - } + final Map> groupedEntities = {}; + final allEntities = await directory.list().toList(); + + for (final entity in allEntities) { + final baseName = p.basenameWithoutExtension(entity.path); + groupedEntities.putIfAbsent(baseName, () => []).add(entity); + } - if (isDir) { - final dir = Directory(entity.path); - try { - final stat = await dir.stat(); - lastModified = stat.modified; - } on PathAccessException catch (e) { - logger( - 'PathAccessException when getting directory info for ${entity.path}: $e'); - } catch (e) { - logger('Error getting directory info for ${entity.path}: $e'); + final List fileItems = []; + final subtitleExtensions = {'ass', 'srt', 'vtt', 'sub'}; + + for (final group in groupedEntities.values) { + final videos = group + .where((e) => + e is! Directory && checkContentType(e.path) == ContentType.video) + .toList(); + final subtitles = group.where((e) { + final ext = p.extension(e.path).replaceFirst('.', ''); + return e is! Directory && subtitleExtensions.contains(ext); + }).toList(); + final others = group + .where((e) => !videos.contains(e) && !subtitles.contains(e)) + .toList(); + + for (final video in videos) { + final videoStat = await video.stat(); + final associatedSubtitles = subtitles.map((sub) { + final baseName = p.basenameWithoutExtension(video.path); + String subTitleName = p.basename(sub.path); // Default name + final regex = RegExp(r'^' + RegExp.escape(baseName) + r'\.(.+?)\.'); + final match = regex.firstMatch(subTitleName); + if (match != null) { + subTitleName = match.group(1) ?? subTitleName; } - } + return Subtitle(name: subTitleName, uri: sub.path); + }).toList(); - final subtitles = await findLocalSubtitle(entity.path); + fileItems.add(FileItem( + storageId: storage.id, + storageType: storage.type, + name: p.basename(video.path), + uri: video.path, + path: [...path, p.basename(video.path)], + isDir: false, + size: videoStat.size, + lastModified: videoStat.modified, + type: ContentType.video, + subtitles: associatedSubtitles, + )); + } - files.add(FileItem( + for (final entity in others) { + final stat = await entity.stat(); + final isDir = entity is Directory; + fileItems.add(FileItem( storageId: storage.id, storageType: storage.type, name: p.basename(entity.path), uri: entity.path, path: [...path, p.basename(entity.path)], isDir: isDir, - size: size, - lastModified: lastModified, - type: - isDir ? ContentType.dir : checkContentType(p.basename(entity.path)), - subtitles: subtitles, + size: stat.size, + lastModified: stat.modified, + type: isDir ? ContentType.dir : checkContentType(entity.path), + subtitles: [], )); } - } catch (e) { - logger('Error reading directory $path : $e'); - return []; } - return files; + return fileItems; } Future pickContentFile() async { @@ -278,6 +298,8 @@ Future pickContentFile() async { Future> getContentFiles(String uri) async { final list = await SafUtil().list(uri); + final allFileNames = list.map((e) => e.name).toList(); + return await Future.wait(list .map((file) async => FileItem( name: file.name, @@ -289,7 +311,7 @@ Future> getContentFiles(String uri) async { DateTime.fromMillisecondsSinceEpoch(file.lastModified), type: file.isDir ? ContentType.dir : checkContentType(file.name), subtitles: await findSubtitle( - list.map((e) => e.name).toList(), + allFileNames, file.name, uri, encodeUri: false, diff --git a/lib/models/storages/webdav.dart b/lib/models/storages/webdav.dart index 4159a31..df59ce9 100644 --- a/lib/models/storages/webdav.dart +++ b/lib/models/storages/webdav.dart @@ -37,7 +37,9 @@ Future testWebDAV(WebDAVStorage storage) async { } Future> getWebDAVFiles( - WebDAVStorage storage, List path) async { + WebDAVStorage storage, + List path, +) async { final id = storage.id; final host = storage.host; final port = storage.port; @@ -62,11 +64,22 @@ Future> getWebDAVFiles( final String baseUri = 'http${https ? 's' : ''}://$host:$port${path.join('/')}'; - return await Future.wait(files.map((file) async => FileItem( + final allFileNames = files.map((f) => f.name as String).toList(); + + return await Future.wait( + files.map((file) async { + final fileUri = Uri( + scheme: https ? 'https' : 'http', + host: host, + port: int.tryParse(port), + pathSegments: [...path, file.name!], + ); + + return FileItem( storageId: id, storageType: StorageType.webdav, name: '${file.name}', - uri: Uri.encodeFull('$baseUri/${file.name}'), + uri: fileUri.toString(), path: [...path, '${file.name}'], isDir: file.isDir ?? false, size: file.size ?? 0, @@ -75,10 +88,13 @@ Future> getWebDAVFiles( ? ContentType.dir : checkContentType(file.name!), subtitles: await findSubtitle( - files.map((file) => file.name as String).toList(), - file.name as String, - baseUri), - ))); + allFileNames, + file.name as String, + baseUri, + ), + ); + }), + ); } String getWebDAVAuth(WebDAVStorage storage) => diff --git a/lib/pages/player/control_bar/control_bar.dart b/lib/pages/player/control_bar/control_bar.dart index 95d5259..d1fe299 100644 --- a/lib/pages/player/control_bar/control_bar.dart +++ b/lib/pages/player/control_bar/control_bar.dart @@ -65,6 +65,15 @@ class ControlBar extends HookWidget { useAppStore().select(context, (state) => state.repeat); final BoxFit fit = useAppStore().select(context, (state) => state.fit); + final displayIsPlaying = useState(player.isPlaying); + + useEffect(() { + if (!player.seeking) { + displayIsPlaying.value = player.isPlaying; + } + return null; + }, [player.isPlaying]); + return Container( padding: const EdgeInsets.all(8), decoration: BoxDecoration( @@ -118,9 +127,9 @@ class ControlBar extends HookWidget { children: [ IconButton( tooltip: - '${player.isPlaying == true ? t.pause : t.play} ( Space )', + '${displayIsPlaying.value ? t.pause : t.play} ( Space )', icon: Icon( - player.isPlaying == true + displayIsPlaying.value ? Icons.pause_rounded : Icons.play_arrow_rounded, size: 36, diff --git a/lib/utils/find_subtitle.dart b/lib/utils/find_subtitle.dart index 78e9e75..c8544ef 100644 --- a/lib/utils/find_subtitle.dart +++ b/lib/utils/find_subtitle.dart @@ -1,77 +1,56 @@ -import 'dart:io'; import 'package:iris/utils/platform.dart'; -import 'package:path/path.dart' as p; import 'package:iris/models/file.dart'; import 'package:iris/utils/check_content_type.dart'; +import 'package:path/path.dart' as path; Future> findSubtitle( - List fileNames, String name, String baseUri, - {bool encodeUri = true}) async { - if (checkContentType(name) == ContentType.video) { - List foundSubTitles = []; - - String baseName = - name.split('.').sublist(0, name.split('.').length - 1).join('.'); - - List subtitleExtensions = ['ass', 'srt', 'vtt', 'sub']; - - for (String fileName in fileNames) { - if (fileName.startsWith(baseName) && - subtitleExtensions.any((ext) => fileName.endsWith(ext))) { - String subTitleName = fileName - .replaceAll(baseName, '') - .split('.') - .where((e) => e.isNotEmpty && !subtitleExtensions.contains(e)) - .join('.'); - - foundSubTitles.add(Subtitle( - name: subTitleName.isEmpty ? fileName : subTitleName, - uri: isAndroid && baseUri.startsWith('content://') - ? '$baseUri${Uri.encodeComponent('/$fileName')}' - : encodeUri - ? Uri.encodeFull('$baseUri/$fileName') - : '$baseUri/$fileName', - )); - } - } - return foundSubTitles; - } else { + List fileNames, + String videoName, + String baseUri, { + bool encodeUri = true, +}) async { + if (checkContentType(videoName) != ContentType.video) { return []; } -} - -Future> findLocalSubtitle(String uri) async { - if (checkContentType(uri) == ContentType.video) { - final baseUri = - uri.split('.').sublist(0, uri.split('.').length - 1).join('.'); - final directory = Directory(p.dirname(uri)); - - List foundSubTitles = []; - - List subtitleExtensions = ['ass', 'srt', 'vtt', 'sub']; - - final entities = directory.list(); - await for (final entity in entities) { - if (entity.path.startsWith(baseUri) && - subtitleExtensions.any((ext) => entity.path.endsWith(ext))) { - String subTitleName = entity.path - .replaceAll(baseUri, '') - .split('.') - .where((e) => e.isNotEmpty && !subtitleExtensions.contains(e)) - .join('.'); - - final fileName = p.basename(entity.path); + final baseName = path.basenameWithoutExtension(videoName); + + final subtitleExtensions = {'ass', 'srt', 'vtt', 'sub'}; + + final regex = RegExp( + r'^' + + RegExp.escape(baseName) + + r'\.(.+?)\.(' + + subtitleExtensions.join('|') + + r')$', + caseSensitive: false, + ); + + return fileNames.where((fileName) { + final fileExt = path.extension(fileName).replaceFirst('.', ''); + return fileName.startsWith(baseName) && + subtitleExtensions.contains(fileExt); + }).map((fileName) { + String subTitleName = fileName; + final match = regex.firstMatch(fileName); + + if (match != null && match.groupCount >= 1) { + subTitleName = match.group(1) ?? fileName; + } - foundSubTitles.add(Subtitle( - name: subTitleName.isEmpty ? fileName : subTitleName, - uri: entity.path, - )); - } + final Uri fileUri; + if (isAndroid && baseUri.startsWith('content://')) { + fileUri = Uri.parse('$baseUri/${Uri.encodeComponent(fileName)}'); + } else { + final uriParts = Uri.parse(baseUri); + fileUri = uriParts.replace( + pathSegments: [...uriParts.pathSegments, fileName], + ); } - return foundSubTitles; - } else { - return []; - } + return Subtitle( + name: subTitleName, + uri: encodeUri ? fileUri.toString() : '$baseUri/$fileName', + ); + }).toList(); } diff --git a/pubspec.lock b/pubspec.lock index f749c08..81924dd 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -77,10 +77,10 @@ packages: dependency: transitive description: name: asn1lib - sha256: "0511d6be23b007e95105ae023db599aea731df604608978dada7f9faf2637623" + sha256: "9a8f69025044eb466b9b60ef3bc3ac99b4dc6c158ae9c56d25eeccf5bc56d024" url: "https://pub.dev" source: hosted - version: "1.6.4" + version: "1.6.5" async: dependency: transitive description: @@ -141,10 +141,10 @@ packages: dependency: transitive description: name: built_value - sha256: "082001b5c3dc495d4a42f1d5789990505df20d8547d42507c29050af6933ee27" + sha256: a30f0a0e38671e89a492c44d005b5545b830a961575bbd8336d42869ff71066d url: "https://pub.dev" source: hosted - version: "8.10.1" + version: "8.12.0" characters: dependency: transitive description: @@ -189,10 +189,10 @@ packages: dependency: transitive description: name: code_builder - sha256: "0ec10bf4a89e4c613960bf1e8b42c64127021740fb21640c29c909826a5eea3e" + sha256: "11654819532ba94c34de52ff5feb52bd81cba1de00ef2ed622fd50295f9d4243" url: "https://pub.dev" source: hosted - version: "4.10.1" + version: "4.11.0" collection: dependency: "direct main" description: @@ -293,10 +293,10 @@ packages: dependency: "direct main" description: name: device_info_plus - sha256: a7e611f8475a124caca8dac394c76c48218a244daa498c3584f97934267d1d4e + sha256: "49413c8ca514dea7633e8def233b25efdf83ec8522955cc2c0e3ad802927e7c6" url: "https://pub.dev" source: hosted - version: "12.0.0" + version: "12.1.0" device_info_plus_platform_interface: dependency: transitive description: @@ -309,10 +309,10 @@ packages: dependency: transitive description: name: dio - sha256: "253a18bbd4851fecba42f7343a1df3a9a4c1d31a2c1b37e221086b4fa8c8dbc9" + sha256: d90ee57923d1828ac14e492ca49440f65477f4bb1263575900be731a3dac66a9 url: "https://pub.dev" source: hosted - version: "5.8.0+1" + version: "5.9.0" dio_web_adapter: dependency: transitive description: @@ -448,10 +448,10 @@ packages: dependency: transitive description: name: flutter_plugin_android_lifecycle - sha256: f948e346c12f8d5480d2825e03de228d0eb8c3a737e4cdaa122267b89c022b5e + sha256: b0694b7fb1689b0e6cc193b3f1fcac6423c4f93c74fb20b806c6b6f196db0c31 url: "https://pub.dev" source: hosted - version: "2.0.28" + version: "2.0.30" flutter_secure_storage: dependency: "direct main" description: @@ -562,10 +562,10 @@ packages: dependency: transitive description: name: get_it - sha256: f126a3e286b7f5b578bf436d5592968706c4c1de28a228b870ce375d9f743103 + sha256: a4292e7cf67193f8e7c1258203104eb2a51ec8b3a04baa14695f4064c144297b url: "https://pub.dev" source: hosted - version: "8.0.3" + version: "8.2.0" glob: dependency: transitive description: @@ -907,18 +907,18 @@ packages: dependency: transitive description: name: path_provider_android - sha256: d0d310befe2c8ab9e7f393288ccbb11b60c019c6b5afc21973eeee4dda2b35e9 + sha256: "993381400e94d18469750e5b9dcb8206f15bc09f9da86b9e44a9b0092a0066db" url: "https://pub.dev" source: hosted - version: "2.2.17" + version: "2.2.18" path_provider_foundation: dependency: transitive description: name: path_provider_foundation - sha256: "4843174df4d288f5e29185bd6e72a6fbdf5a4a4602717eed565497429f179942" + sha256: "16eef174aacb07e09c351502740fa6254c165757638eba1e9116b0a781201bbd" url: "https://pub.dev" source: hosted - version: "2.4.1" + version: "2.4.2" path_provider_linux: dependency: transitive description: @@ -1003,10 +1003,10 @@ packages: dependency: transitive description: name: petitparser - sha256: "07c8f0b1913bcde1ff0d26e57ace2f3012ccbf2b204e070290dad3bb22797646" + sha256: "1a97266a94f7350d30ae522c0af07890c70b8e62c71e8e3920d1db4d23c057d1" url: "https://pub.dev" source: hosted - version: "6.1.0" + version: "7.0.1" platform: dependency: transitive description: @@ -1051,18 +1051,18 @@ packages: dependency: transitive description: name: posix - sha256: f0d7856b6ca1887cfa6d1d394056a296ae33489db914e365e2044fdada449e62 + sha256: "6323a5b0fa688b6a010df4905a56b00181479e6d10534cecfecede2aa55add61" url: "https://pub.dev" source: hosted - version: "6.0.2" + version: "6.0.3" provider: dependency: "direct main" description: name: provider - sha256: "4abbd070a04e9ddc287673bf5a030c7ca8b685ff70218720abab8b092f53dd84" + sha256: "4e82183fa20e5ca25703ead7e05de9e4cceed1fbd1eadc1ac3cb6f565a09f272" url: "https://pub.dev" source: hosted - version: "6.1.5" + version: "6.1.5+1" pub_semver: dependency: transitive description: @@ -1312,10 +1312,10 @@ packages: dependency: transitive description: name: synchronized - sha256: "0669c70faae6270521ee4f05bffd2919892d42d1276e6c495be80174b6bc0ef6" + sha256: c254ade258ec8282947a0acbbc90b9575b4f19673533ee46f2f6e9b3aeefd7c0 url: "https://pub.dev" source: hosted - version: "3.3.1" + version: "3.4.0" term_glyph: dependency: transitive description: @@ -1368,18 +1368,18 @@ packages: dependency: transitive description: name: url_launcher_android - sha256: "8582d7f6fe14d2652b4c45c9b6c14c0b678c2af2d083a11b604caeba51930d79" + sha256: "07cffecb7d68cbc6437cd803d5f11a86fe06736735c3dfe46ff73bcb0f958eed" url: "https://pub.dev" source: hosted - version: "6.3.16" + version: "6.3.21" url_launcher_ios: dependency: transitive description: name: url_launcher_ios - sha256: "7f2022359d4c099eea7df3fdf739f7d3d3b9faf3166fb1dd390775176e0b76cb" + sha256: d80b3f567a617cb923546034cc94bfe44eb15f989fe670b37f26abdb9d939cb7 url: "https://pub.dev" source: hosted - version: "6.3.3" + version: "6.3.4" url_launcher_linux: dependency: transitive description: @@ -1392,10 +1392,10 @@ packages: dependency: transitive description: name: url_launcher_macos - sha256: "17ba2000b847f334f16626a574c702b196723af2a289e7a93ffcb79acff855c2" + sha256: c043a77d6600ac9c38300567f33ef12b0ef4f4783a2c1f00231d2b1941fea13f url: "https://pub.dev" source: hosted - version: "3.2.2" + version: "3.2.3" url_launcher_platform_interface: dependency: transitive description: @@ -1448,42 +1448,42 @@ packages: dependency: transitive description: name: video_player_android - sha256: "4a5135754a62dbc827a64a42ef1f8ed72c962e191c97e2d48744225c2b9ebb73" + sha256: "59e5a457ddcc1688f39e9aef0efb62aa845cf0cbbac47e44ac9730dc079a2385" url: "https://pub.dev" source: hosted - version: "2.8.7" + version: "2.8.13" video_player_avfoundation: dependency: transitive description: name: video_player_avfoundation - sha256: "9ee764e5cd2fc1e10911ae8ad588e1a19db3b6aa9a6eb53c127c42d3a3c3f22f" + sha256: f9a780aac57802b2892f93787e5ea53b5f43cc57dc107bee9436458365be71cd url: "https://pub.dev" source: hosted - version: "2.7.1" + version: "2.8.4" video_player_platform_interface: dependency: transitive description: name: video_player_platform_interface - sha256: df534476c341ab2c6a835078066fc681b8265048addd853a1e3c78740316a844 + sha256: cf2a1d29a284db648fd66cbd18aacc157f9862d77d2cc790f6f9678a46c1db5a url: "https://pub.dev" source: hosted - version: "6.3.0" + version: "6.4.0" video_player_web: dependency: transitive description: name: video_player_web - sha256: e8bba2e5d1e159d5048c9a491bb2a7b29c535c612bb7d10c1e21107f5bd365ba + sha256: "9f3c00be2ef9b76a95d94ac5119fb843dca6f2c69e6c9968f6f2b6c9e7afbdeb" url: "https://pub.dev" source: hosted - version: "2.3.5" + version: "2.4.0" vm_service: dependency: transitive description: name: vm_service - sha256: ddfa8d30d89985b96407efce8acbdd124701f96741f2d981ca860662f1c0dc02 + sha256: "45caa6c5917fa127b5dbcfbd1fa60b14e583afdc08bfc96dda38886ca252eb60" url: "https://pub.dev" source: hosted - version: "15.0.0" + version: "15.0.2" volume_controller: dependency: transitive description: @@ -1512,10 +1512,10 @@ packages: dependency: transitive description: name: watcher - sha256: "69da27e49efa56a15f8afe8f4438c4ec02eff0a117df1b22ea4aad194fe1c104" + sha256: "5bf046f41320ac97a469d506261797f35254fa61c641741ef32dacda98b7d39c" url: "https://pub.dev" source: hosted - version: "1.1.1" + version: "1.1.3" web: dependency: transitive description: @@ -1552,10 +1552,10 @@ packages: dependency: transitive description: name: win32 - sha256: "329edf97fdd893e0f1e3b9e88d6a0e627128cc17cc316a8d67fda8f1451178ba" + sha256: "66814138c3562338d05613a6e368ed8cfb237ad6d64a9e9334be3f309acfca03" url: "https://pub.dev" source: hosted - version: "5.13.0" + version: "5.14.0" win32_registry: dependency: transitive description: @@ -1592,10 +1592,10 @@ packages: dependency: transitive description: name: xml - sha256: b015a8ad1c488f66851d762d3090a21c600e479dc75e68328c52774040cf9226 + sha256: "971043b3a0d3da28727e40ed3e0b5d18b742fa5a68665cca88e74b7876d5e025" url: "https://pub.dev" source: hosted - version: "6.5.0" + version: "6.6.1" yaml: dependency: transitive description: @@ -1614,4 +1614,4 @@ packages: version: "0.0.5" sdks: dart: ">=3.9.0 <4.0.0" - flutter: ">=3.32.0" + flutter: ">=3.35.0" From 5935b7789ba1547b5e05cd8686419676400c9be9 Mon Sep 17 00:00:00 2001 From: 22 <60903333+nini22P@users.noreply.github.com> Date: Sat, 13 Sep 2025 11:31:53 +0800 Subject: [PATCH 10/31] restore PlayerHost --- android/.gitignore | 5 +- android/app/build.gradle | 9 ++- .../gradle/wrapper/gradle-wrapper.properties | 2 +- android/settings.gradle | 4 +- lib/hooks/player/use_fvp_player.dart | 16 ++--- lib/hooks/player/use_media_kit_player.dart | 10 ++- lib/hooks/player/use_player.dart | 18 ------ lib/pages/home/home.dart | 61 ++++++++++++++++++- lib/pages/player/iris_player.dart | 13 ++-- 9 files changed, 91 insertions(+), 47 deletions(-) delete mode 100644 lib/hooks/player/use_player.dart diff --git a/android/.gitignore b/android/.gitignore index 2a7ed28..8d1b247 100644 --- a/android/.gitignore +++ b/android/.gitignore @@ -14,4 +14,7 @@ key.properties **/*.keystore **/*.jks -/app/src/main/assets/flutter_assets \ No newline at end of file +/app/src/main/assets/flutter_assets + +.kotlin +build \ No newline at end of file diff --git a/android/app/build.gradle b/android/app/build.gradle index d2b9f78..c9c8bdc 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -1,4 +1,3 @@ -import java.io.File import java.nio.file.Files import java.security.MessageDigest @@ -21,12 +20,12 @@ android { ndkVersion = "27.0.12077973" compileOptions { - sourceCompatibility = JavaVersion.VERSION_1_8 - targetCompatibility = JavaVersion.VERSION_1_8 + sourceCompatibility = JavaVersion.VERSION_17 + targetCompatibility = JavaVersion.VERSION_17 } kotlinOptions { - jvmTarget = JavaVersion.VERSION_1_8 + jvmTarget = JavaVersion.VERSION_17 } defaultConfig { @@ -93,7 +92,7 @@ task downloadFiles(type: Exec) { def calculatedMD5 = MessageDigest.getInstance("MD5").digest(Files.readAllBytes(destFile.toPath())).encodeHex().toString() if (calculatedMD5 != fileInfo.md5) { - throw new GradleException("MD5 verification failed for ${destFile}") + throw new FileNotFoundException("MD5 verification failed for ${destFile}") } } } diff --git a/android/gradle/wrapper/gradle-wrapper.properties b/android/gradle/wrapper/gradle-wrapper.properties index 348c409..02767eb 100644 --- a/android/gradle/wrapper/gradle-wrapper.properties +++ b/android/gradle/wrapper/gradle-wrapper.properties @@ -2,4 +2,4 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.9-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.13-all.zip diff --git a/android/settings.gradle b/android/settings.gradle index 663f60a..7cffcd0 100644 --- a/android/settings.gradle +++ b/android/settings.gradle @@ -18,8 +18,8 @@ pluginManagement { plugins { id "dev.flutter.flutter-plugin-loader" version "1.0.0" - id "com.android.application" version "8.7.1" apply false - id "org.jetbrains.kotlin.android" version "1.8.22" apply false + id "com.android.application" version "8.13.0" apply false + id "org.jetbrains.kotlin.android" version "2.2.20" apply false } include ":app" diff --git a/lib/hooks/player/use_fvp_player.dart b/lib/hooks/player/use_fvp_player.dart index 60b9612..919dd0c 100644 --- a/lib/hooks/player/use_fvp_player.dart +++ b/lib/hooks/player/use_fvp_player.dart @@ -21,11 +21,7 @@ import 'package:saf_util/saf_util.dart'; import 'package:video_player/video_player.dart'; import 'package:wakelock_plus/wakelock_plus.dart'; -FvpPlayer? useFvpPlayer(BuildContext context, bool isActive) { - if (!isActive) { - return null; - } - +FvpPlayer useFvpPlayer(BuildContext context) { final autoPlay = useAppStore().select(context, (state) => state.autoPlay); final rate = useAppStore().select(context, (state) => state.rate); final volume = useAppStore().select(context, (state) => state.volume); @@ -116,11 +112,19 @@ FvpPlayer? useFvpPlayer(BuildContext context, bool isActive) { }(); return () { + if (controller.dataSource.isEmpty) return; controller.dispose(); externalSubtitle.value = null; }; }, [controller, initValue.value]); + useEffect(() { + return () { + if (controller.dataSource.isEmpty) return; + controller.dispose(); + }; + }, []); + final isPlaying = useListenableSelector(controller, () => controller.value.isPlaying); final duration = @@ -335,8 +339,6 @@ FvpPlayer? useFvpPlayer(BuildContext context, bool isActive) { useEffect(() => saveProgress, []); - useEffect(() => controller.dispose, []); - return FvpPlayer( controller: controller, isInitializing: isInitializing.value, diff --git a/lib/hooks/player/use_media_kit_player.dart b/lib/hooks/player/use_media_kit_player.dart index 2428669..82122e4 100644 --- a/lib/hooks/player/use_media_kit_player.dart +++ b/lib/hooks/player/use_media_kit_player.dart @@ -20,11 +20,7 @@ import 'package:media_kit_video/media_kit_video.dart'; import 'package:media_stream/media_stream.dart'; import 'package:path_provider/path_provider.dart'; -MediaKitPlayer? useMediaKitPlayer(BuildContext context, bool isActive) { - if (!isActive) { - return null; - } - +MediaKitPlayer useMediaKitPlayer(BuildContext context) { final player = useMemoized( () => Player( configuration: const PlayerConfiguration( @@ -71,7 +67,9 @@ MediaKitPlayer? useMediaKitPlayer(BuildContext context, bool isActive) { await nativePlayer.setProperty("sub-font", "NotoSansCJKsc-Medium"); } }(); - return player.dispose; + return () { + player.dispose(); + }; }, []); final List playQueue = diff --git a/lib/hooks/player/use_player.dart b/lib/hooks/player/use_player.dart deleted file mode 100644 index ee48b34..0000000 --- a/lib/hooks/player/use_player.dart +++ /dev/null @@ -1,18 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:flutter_zustand/flutter_zustand.dart'; -import 'package:iris/hooks/player/use_fvp_player.dart'; -import 'package:iris/hooks/player/use_media_kit_player.dart'; -import 'package:iris/models/player.dart'; -import 'package:iris/models/store/app_state.dart'; -import 'package:iris/store/use_app_store.dart'; - -MediaPlayer usePlayer(BuildContext context) { - final playerBackend = - useAppStore().select(context, (state) => state.playerBackend); - - final mediaKitPlayer = - useMediaKitPlayer(context, playerBackend == PlayerBackend.mediaKit); - final fvpPlayer = useFvpPlayer(context, playerBackend == PlayerBackend.fvp); - - return fvpPlayer ?? mediaKitPlayer!; -} diff --git a/lib/pages/home/home.dart b/lib/pages/home/home.dart index 3bc785b..47cfe12 100644 --- a/lib/pages/home/home.dart +++ b/lib/pages/home/home.dart @@ -1,13 +1,42 @@ import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; +import 'package:flutter_zustand/flutter_zustand.dart'; +import 'package:iris/hooks/player/use_fvp_player.dart'; +import 'package:iris/hooks/player/use_media_kit_player.dart'; +import 'package:iris/models/player.dart'; +import 'package:iris/models/store/app_state.dart'; import 'package:iris/pages/player/iris_player.dart'; +import 'package:iris/store/use_app_store.dart'; class Home extends HookWidget { const Home({super.key}); @override Widget build(BuildContext context) { + final playerBackend = + useAppStore().select(context, (state) => state.playerBackend); + + final playerState = useState(null); + + void handlePlayerCreated(MediaPlayer player) { + WidgetsBinding.instance.addPostFrameCallback((_) { + if (context.mounted) { + playerState.value = player; + } + }); + } + + final Widget playerHost; + switch (playerBackend) { + case PlayerBackend.mediaKit: + playerHost = _MediaKitPlayerHost(onPlayerCreated: handlePlayerCreated); + break; + case PlayerBackend.fvp: + playerHost = _FvpPlayerHost(onPlayerCreated: handlePlayerCreated); + break; + } + return AnnotatedRegion( value: const SystemUiOverlayStyle( statusBarIconBrightness: Brightness.light, @@ -17,9 +46,39 @@ class Home extends HookWidget { child: Scaffold( backgroundColor: Color(0xFF2f2f2f), body: Stack( - children: [IrisPlayer()], + children: [ + playerHost, + if (playerState.value != null) + IrisPlayer(player: playerState.value!) + ], ), ), ); } } + +class _MediaKitPlayerHost extends HookWidget { + final ValueChanged onPlayerCreated; + + const _MediaKitPlayerHost({required this.onPlayerCreated}); + + @override + Widget build(BuildContext context) { + final player = useMediaKitPlayer(context); + onPlayerCreated(player); + return Container(); // Doesn't build any UI itself. + } +} + +class _FvpPlayerHost extends HookWidget { + final ValueChanged onPlayerCreated; + + const _FvpPlayerHost({required this.onPlayerCreated}); + + @override + Widget build(BuildContext context) { + final player = useFvpPlayer(context); + onPlayerCreated(player); + return Container(); // Doesn't build any UI itself. + } +} diff --git a/lib/pages/player/iris_player.dart b/lib/pages/player/iris_player.dart index b401c3d..f4f7742 100644 --- a/lib/pages/player/iris_player.dart +++ b/lib/pages/player/iris_player.dart @@ -11,7 +11,6 @@ import 'package:iris/hooks/use_full_screen.dart'; import 'package:iris/hooks/use_gesture.dart'; import 'package:iris/hooks/use_keyboard.dart'; import 'package:iris/hooks/use_orientation.dart'; -import 'package:iris/hooks/player/use_player.dart'; import 'package:iris/info.dart'; import 'package:iris/models/file.dart'; import 'package:iris/models/player.dart'; @@ -34,12 +33,12 @@ import 'package:video_player/video_player.dart'; import 'package:window_manager/window_manager.dart'; class IrisPlayer extends HookWidget { - const IrisPlayer({super.key}); + const IrisPlayer({super.key, required this.player}); + + final MediaPlayer player; @override Widget build(BuildContext context) { - final player = usePlayer(context); - useAppLifecycle(player); useFullScreen(context); useOrientation(context, player); @@ -395,13 +394,15 @@ class IrisPlayer extends HookWidget { child: SizedBox( width: player.width, height: player.height, - child: VideoPlayer(player.controller), + child: VideoPlayer( + (player as FvpPlayer).controller), ), ) : player is MediaKitPlayer ? Video( key: ValueKey(currentPlay?.file.uri), - controller: player.controller, + controller: (player as MediaKitPlayer) + .controller, controls: NoVideoControls, fit: fit == BoxFit.none ? BoxFit.contain From 0cd9fd74a6eb240978216dab7294521aa5b2af0a Mon Sep 17 00:00:00 2001 From: 22 <60903333+nini22P@users.noreply.github.com> Date: Sat, 13 Sep 2025 13:05:48 +0800 Subject: [PATCH 11/31] fix: update gesture and keyboardEvent --- lib/hooks/player/use_fvp_player.dart | 2 +- lib/hooks/use_gesture.dart | 4 +- lib/hooks/use_keyboard.dart | 23 +- lib/pages/player/iris_player.dart | 474 +++++++++++++-------------- 4 files changed, 244 insertions(+), 259 deletions(-) diff --git a/lib/hooks/player/use_fvp_player.dart b/lib/hooks/player/use_fvp_player.dart index 919dd0c..96fe92b 100644 --- a/lib/hooks/player/use_fvp_player.dart +++ b/lib/hooks/player/use_fvp_player.dart @@ -66,7 +66,7 @@ FvpPlayer useFvpPlayer(BuildContext context) { final streamUrl = mediaStream.url; final controllerFuture = useMemoized(() async { - if (file == null) return VideoPlayerController.networkUrl(Uri.parse('')); + if (file == null) return null; isInitializing.value = true; final storage = useStorageStore().findById(file.storageId); final auth = storage?.getAuth(); diff --git a/lib/hooks/use_gesture.dart b/lib/hooks/use_gesture.dart index 2bb0ed5..21004f2 100644 --- a/lib/hooks/use_gesture.dart +++ b/lib/hooks/use_gesture.dart @@ -98,14 +98,14 @@ Gesture useGesture({ } else { showProgress(); } - await player.forward(10); + await player.forward(5); } else if (position < 0.25) { if (isShowControl.value) { showControl(); } else { showProgress(); } - player.backward(10); + player.backward(5); } else { if (player.isPlaying == true) { await useAppStore().updateAutoPlay(false); diff --git a/lib/hooks/use_keyboard.dart b/lib/hooks/use_keyboard.dart index 610ba69..cbf536a 100644 --- a/lib/hooks/use_keyboard.dart +++ b/lib/hooks/use_keyboard.dart @@ -9,6 +9,7 @@ import 'package:iris/pages/home/history.dart'; import 'package:iris/pages/player/play_queue.dart'; import 'package:iris/pages/player/subtitle_and_audio_track.dart'; import 'package:iris/pages/settings/settings.dart'; +import 'package:iris/pages/storages/storages.dart'; import 'package:iris/store/use_app_store.dart'; import 'package:iris/store/use_play_queue_store.dart'; import 'package:iris/store/use_ui_store.dart'; @@ -145,15 +146,15 @@ KeyboardEvent useKeyboard({ usePlayQueueStore().next(); break; // 存储 - // case LogicalKeyboardKey.keyF: - // showControlForHover( - // showPopup( - // context: context, - // child: const Storages(), - // direction: PopupDirection.right, - // ), - // ); - // break; + case LogicalKeyboardKey.keyF: + showControlForHover( + showPopup( + context: context, + child: const Storages(), + direction: PopupDirection.right, + ), + ); + break; // 播放队列 case LogicalKeyboardKey.keyP: showControlForHover( @@ -219,7 +220,7 @@ KeyboardEvent useKeyboard({ } else { showProgress(); } - player.backward(10); + player.backward(5); break; // 快进 case LogicalKeyboardKey.arrowRight: @@ -228,7 +229,7 @@ KeyboardEvent useKeyboard({ } else { showProgress(); } - player.forward(10); + player.forward(5); break; // 提升音量 case LogicalKeyboardKey.arrowUp: diff --git a/lib/pages/player/iris_player.dart b/lib/pages/player/iris_player.dart index f4f7742..dd81cf0 100644 --- a/lib/pages/player/iris_player.dart +++ b/lib/pages/player/iris_player.dart @@ -365,271 +365,255 @@ class IrisPlayer extends HookWidget { onPanUpdate: gesture.onPanUpdate, onPanEnd: gesture.onPanEnd, onPanCancel: gesture.onPanCancel, - child: InkWell( - onTap: showControl, - child: Card( - color: Colors.transparent, - clipBehavior: Clip.hardEdge, - elevation: 0, - margin: EdgeInsets.zero, - child: Stack( - children: [ - Positioned( - left: 0, - top: 0, - right: 0, - bottom: 0, + child: Stack( + children: [ + Positioned( + left: 0, + top: 0, + right: 0, + bottom: 0, + child: Container( + color: Colors.black, + ), + ), + Positioned( + left: videoViewOffset.dx, + top: videoViewOffset.dy, + width: videoViewSize.width, + height: videoViewSize.height, + child: player is FvpPlayer + ? FittedBox( + fit: fit, + child: SizedBox( + width: player.width, + height: player.height, + child: VideoPlayer( + (player as FvpPlayer).controller), + ), + ) + : player is MediaKitPlayer + ? Video( + key: ValueKey(currentPlay?.file.uri), + controller: + (player as MediaKitPlayer).controller, + controls: NoVideoControls, + fit: fit == BoxFit.none + ? BoxFit.contain + : fit, + // wakelock: mediaType == 'video', + ) + : Container(), + ), + // Audio + if (currentPlay?.file.type == ContentType.audio) + Positioned( + left: 0, + top: 0, + right: 0, + bottom: 0, + child: Audio(cover: cover), + ), + // 播放速度 + if (rate != 1.0 && gesture.isLongPress) + Positioned( + left: 0, + top: 0, + right: 0, + bottom: 0, + child: Center( child: Container( - color: Colors.black, - ), - ), - Positioned( - left: videoViewOffset.dx, - top: videoViewOffset.dy, - width: videoViewSize.width, - height: videoViewSize.height, - child: player is FvpPlayer - ? FittedBox( - fit: fit, - child: SizedBox( - width: player.width, - height: player.height, - child: VideoPlayer( - (player as FvpPlayer).controller), + padding: + const EdgeInsets.fromLTRB(12, 12, 18, 12), + decoration: BoxDecoration( + color: Colors.black54, + borderRadius: BorderRadius.circular(8), + ), + child: Row( + mainAxisSize: MainAxisSize.min, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Transform.translate( + offset: const Offset(0, 1.5), + child: Icon( + Icons.speed_rounded, + color: Colors.white, + size: 24, ), - ) - : player is MediaKitPlayer - ? Video( - key: ValueKey(currentPlay?.file.uri), - controller: (player as MediaKitPlayer) - .controller, - controls: NoVideoControls, - fit: fit == BoxFit.none - ? BoxFit.contain - : fit, - // wakelock: mediaType == 'video', - ) - : Container(), - ), - // Audio - if (currentPlay?.file.type == ContentType.audio) - Positioned( - left: 0, - top: 0, - right: 0, - bottom: 0, - child: Audio(cover: cover), - ), - // 播放速度 - if (rate != 1.0 && gesture.isLongPress) - Positioned( - left: 0, - top: 0, - right: 0, - bottom: 0, - child: Center( - child: Container( - padding: const EdgeInsets.fromLTRB( - 12, 12, 18, 12), - decoration: BoxDecoration( - color: Colors.black54, - borderRadius: BorderRadius.circular(8), ), - child: Row( - mainAxisSize: MainAxisSize.min, - mainAxisAlignment: - MainAxisAlignment.center, - children: [ - Transform.translate( - offset: const Offset(0, 1.5), - child: Icon( - Icons.speed_rounded, - color: Colors.white, - size: 24, - ), - ), - const SizedBox(width: 10), - Text( - rate.toString(), - style: const TextStyle( - color: Colors.white, - fontSize: 20, - height: 1, - ), - ), - ], + const SizedBox(width: 10), + Text( + rate.toString(), + style: const TextStyle( + color: Colors.white, + fontSize: 20, + height: 1, + ), ), - ), + ], ), ), - // 屏幕亮度 - if (gesture.isLeftGesture && - gesture.brightness != null) - Positioned( - left: 0, - top: 0, - right: 0, - bottom: 0, - child: Center( - child: Container( - padding: const EdgeInsets.fromLTRB( - 12, 12, 18, 12), - decoration: BoxDecoration( - color: Colors.black54, - borderRadius: BorderRadius.circular(8), - ), - child: Row( - mainAxisSize: MainAxisSize.min, - children: [ - Icon( - gesture.brightness == 0 - ? Icons.brightness_low_rounded - : gesture.brightness! < 1 - ? Icons - .brightness_medium_rounded - : Icons - .brightness_high_rounded, - color: Colors.white, - size: 24, - ), - const SizedBox(width: 12), - SizedBox( - width: 100, - child: LinearProgressIndicator( - value: gesture.brightness, - borderRadius: - BorderRadius.circular(4), - backgroundColor: Colors.grey, - valueColor: - AlwaysStoppedAnimation( - Colors.white), - ), - ), - ], - ), - ), + ), + ), + // 屏幕亮度 + if (gesture.isLeftGesture && gesture.brightness != null) + Positioned( + left: 0, + top: 0, + right: 0, + bottom: 0, + child: Center( + child: Container( + padding: + const EdgeInsets.fromLTRB(12, 12, 18, 12), + decoration: BoxDecoration( + color: Colors.black54, + borderRadius: BorderRadius.circular(8), ), - ), - // 音量 - if (gesture.isRightGesture && - gesture.volume != null) - Positioned( - left: 0, - top: 0, - right: 0, - bottom: 0, - child: Center( - child: Container( - padding: const EdgeInsets.fromLTRB( - 12, 12, 18, 12), - decoration: BoxDecoration( - color: Colors.black54, - borderRadius: BorderRadius.circular(8), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Icon( + gesture.brightness == 0 + ? Icons.brightness_low_rounded + : gesture.brightness! < 1 + ? Icons.brightness_medium_rounded + : Icons.brightness_high_rounded, + color: Colors.white, + size: 24, ), - child: Row( - mainAxisSize: MainAxisSize.min, - children: [ - Icon( - gesture.volume == 0 - ? Icons.volume_mute_rounded - : gesture.volume! < 0.5 - ? Icons.volume_down_rounded - : Icons.volume_up_rounded, - color: Colors.white, - size: 24, - ), - const SizedBox(width: 12), - SizedBox( - width: 100, - child: LinearProgressIndicator( - value: gesture.volume, - borderRadius: - BorderRadius.circular(4), - backgroundColor: Colors.grey, - valueColor: - AlwaysStoppedAnimation( - Colors.white, - ), - ), - ), - ], + const SizedBox(width: 12), + SizedBox( + width: 100, + child: LinearProgressIndicator( + value: gesture.brightness, + borderRadius: BorderRadius.circular(4), + backgroundColor: Colors.grey, + valueColor: + AlwaysStoppedAnimation( + Colors.white), + ), ), - ), + ], ), ), - if (isShowProgress.value && - !isShowControl.value && - currentPlay?.file.type == ContentType.video) - Positioned( - left: -28, - right: -28, - bottom: -16, - height: 32, - child: ControlBarSlider( - player: player, - showControl: showControl, - disabled: true, + ), + ), + // 音量 + if (gesture.isRightGesture && gesture.volume != null) + Positioned( + left: 0, + top: 0, + right: 0, + bottom: 0, + child: Center( + child: Container( + padding: + const EdgeInsets.fromLTRB(12, 12, 18, 12), + decoration: BoxDecoration( + color: Colors.black54, + borderRadius: BorderRadius.circular(8), ), - ), - if (isShowProgress.value && - !isShowControl.value && - currentPlay?.file.type == ContentType.video) - Positioned( - left: 12, - top: 12, - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, + child: Row( + mainAxisSize: MainAxisSize.min, children: [ - Text( - currentPlay != null ? title : '', - style: TextStyle( - color: Colors.white, - fontSize: 20, - height: 1, - decoration: TextDecoration.none, - shadows: const [ - Shadow( - color: Colors.black, - offset: Offset(0, 0), - blurRadius: 1, - ), - ], + Icon( + gesture.volume == 0 + ? Icons.volume_mute_rounded + : gesture.volume! < 0.5 + ? Icons.volume_down_rounded + : Icons.volume_up_rounded, + color: Colors.white, + size: 24, + ), + const SizedBox(width: 12), + SizedBox( + width: 100, + child: LinearProgressIndicator( + value: gesture.volume, + borderRadius: BorderRadius.circular(4), + backgroundColor: Colors.grey, + valueColor: + AlwaysStoppedAnimation( + Colors.white, + ), ), ), ], ), ), - if (isShowProgress.value && - !isShowControl.value && - currentPlay?.file.type == ContentType.video) - Positioned( - left: 12, - bottom: 6, - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - '${formatDurationToMinutes(player.position)} / ${formatDurationToMinutes(player.duration)}', - style: TextStyle( - color: Colors.white, - fontSize: 16, - height: 2, - decoration: TextDecoration.none, - shadows: const [ - Shadow( - color: Colors.black, - offset: Offset(0, 0), - blurRadius: 1, - ), - ], + ), + ), + if (isShowProgress.value && + !isShowControl.value && + currentPlay?.file.type == ContentType.video) + Positioned( + left: -28, + right: -28, + bottom: -16, + height: 32, + child: ControlBarSlider( + player: player, + showControl: showControl, + disabled: true, + ), + ), + if (isShowProgress.value && + !isShowControl.value && + currentPlay?.file.type == ContentType.video) + Positioned( + left: 12, + top: 12, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + currentPlay != null ? title : '', + style: TextStyle( + color: Colors.white, + fontSize: 20, + height: 1, + decoration: TextDecoration.none, + shadows: const [ + Shadow( + color: Colors.black, + offset: Offset(0, 0), + blurRadius: 1, ), - ), - ], + ], + ), ), - ), - ], - ), - ), + ], + ), + ), + if (isShowProgress.value && + !isShowControl.value && + currentPlay?.file.type == ContentType.video) + Positioned( + left: 12, + bottom: 6, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + '${formatDurationToMinutes(player.position)} / ${formatDurationToMinutes(player.duration)}', + style: TextStyle( + color: Colors.white, + fontSize: 16, + height: 2, + decoration: TextDecoration.none, + shadows: const [ + Shadow( + color: Colors.black, + offset: Offset(0, 0), + blurRadius: 1, + ), + ], + ), + ), + ], + ), + ), + ], ), ), ), From c8fcb7fde870419de489df733552cf9846295751 Mon Sep 17 00:00:00 2001 From: 22 <60903333+nini22P@users.noreply.github.com> Date: Sat, 13 Sep 2025 20:18:53 +0800 Subject: [PATCH 12/31] feat: improve fvp player --- lib/hooks/player/use_fvp_player.dart | 282 ++++++++++++++------------- lib/pages/player/iris_player.dart | 39 ++-- 2 files changed, 163 insertions(+), 158 deletions(-) diff --git a/lib/hooks/player/use_fvp_player.dart b/lib/hooks/player/use_fvp_player.dart index 96fe92b..0cdffe8 100644 --- a/lib/hooks/player/use_fvp_player.dart +++ b/lib/hooks/player/use_fvp_player.dart @@ -56,127 +56,103 @@ FvpPlayer useFvpPlayer(BuildContext context) { final List externalSubtitles = useMemoized( () => currentPlay?.file.subtitles ?? [], [currentPlay?.file.subtitles]); - final initValue = useState(false); - final isInitializing = useState(false); - Future init() async => initValue.value = true; - MediaStream mediaStream = MediaStream(); final streamUrl = mediaStream.url; - final controllerFuture = useMemoized(() async { - if (file == null) return null; + final controller = useState(VideoPlayerController.networkUrl(Uri.parse(''))); + + Future init() async { isInitializing.value = true; - final storage = useStorageStore().findById(file.storageId); - final auth = storage?.getAuth(); - logger('Open file: $file'); - switch (checkDataSourceType(file)) { - case DataSourceType.file: - return VideoPlayerController.file( - File(file.uri), - httpHeaders: auth != null ? {'authorization': auth} : {}, - ); - case DataSourceType.contentUri: - final isExists = await SafUtil().exists(file.uri, false); - return VideoPlayerController.contentUri( - isExists ? Uri.parse(file.uri) : Uri.parse(''), - ); - default: - return VideoPlayerController.networkUrl( - Uri.parse(file.storageType == StorageType.ftp - ? '$streamUrl/${file.uri}' - : file.uri), - httpHeaders: auth != null ? {'authorization': auth} : {}, - ); + + try { + await controller.value.initialize(); + await controller.value.setLooping(repeat == Repeat.one ? true : false); + await controller.value.setPlaybackSpeed(rate); + await controller.value.setVolume(isMuted ? 0 : volume / 100); + } catch (e) { + logger('Error initializing player: $e'); } - }, [file, initValue.value]); - final controller = useFuture(controllerFuture).data ?? - VideoPlayerController.networkUrl(Uri.parse('')); + isInitializing.value = false; + } useEffect(() { () async { - if (controller.dataSource.isEmpty) return; - - try { - await controller.initialize(); - await controller.setLooping(repeat == Repeat.one ? true : false); - await controller.setPlaybackSpeed(rate); - await controller.setVolume(isMuted ? 0 : volume / 100); - } catch (e) { - logger('Error initializing player: $e'); + if (controller.value.value.isInitialized) { + logger('Dispose player'); + controller.value.dispose(); } - isInitializing.value = false; + if (file == null || file.uri.isEmpty) { + controller.value = VideoPlayerController.networkUrl(Uri.parse('')); + } else { + final storage = useStorageStore().findById(file.storageId); + final auth = storage?.getAuth(); + + logger('Open file: $file'); + + switch (checkDataSourceType(file)) { + case DataSourceType.file: + controller.value = VideoPlayerController.file( + File(file.uri), + httpHeaders: auth != null ? {'authorization': auth} : {}, + ); + case DataSourceType.contentUri: + final isExists = await SafUtil().exists(file.uri, false); + controller.value = VideoPlayerController.contentUri( + isExists ? Uri.parse(file.uri) : Uri.parse(''), + ); + default: + controller.value = VideoPlayerController.networkUrl( + Uri.parse(file.storageType == StorageType.ftp + ? '$streamUrl/${file.uri}' + : file.uri), + httpHeaders: auth != null ? {'authorization': auth} : {}, + ); + } + await init(); + } }(); - return () { - if (controller.dataSource.isEmpty) return; - controller.dispose(); - externalSubtitle.value = null; - }; - }, [controller, initValue.value]); + return; + }, [file?.uri]); useEffect(() { return () { - if (controller.dataSource.isEmpty) return; - controller.dispose(); + if (controller.value.value.isInitialized) { + controller.value.dispose(); + } }; }, []); - final isPlaying = - useListenableSelector(controller, () => controller.value.isPlaying); - final duration = - useListenableSelector(controller, () => controller.value.duration); - final position = - useListenableSelector(controller, () => controller.value.position); - final buffered = - useListenableSelector(controller, () => controller.value.buffered); - final size = useListenableSelector(controller, () => controller.value.size); - final isCompleted = - useListenableSelector(controller, () => controller.value.isCompleted); - - final double aspect = useMemoized( - () => size.width != 0 && size.height != 0 ? size.width / size.height : 0, - [size.width, size.height]); + final double aspect = useMemoized(() { + if (file?.type != ContentType.video) { + return 0; + } - final seeking = useState(false); + final width = controller.value.value.size.width; + final height = controller.value.value.size.height; - useEffect(() { - () async { - if (duration != Duration.zero && - currentPlay != null && - currentPlay.file.type == ContentType.video) { - Progress? progress = history[currentPlay.file.getID()]; - if (progress != null) { - if (!alwaysPlayFromBeginning && - (progress.duration.inMilliseconds - - progress.position.inMilliseconds) > - 5000) { - logger( - 'Resume progress: ${currentPlay.file.name} position: ${progress.position} duration: ${progress.duration}'); - await controller.seekTo(progress.position); - } - } - } - - if (autoPlay) { - controller.play(); - } + if (width != 0 && height != 0) { + return width / height; + } else { + return 0; + } + }, [ + file?.type, + controller.value.value.size.width, + controller.value.value.size.height + ]); - if (externalSubtitles.isNotEmpty) { - externalSubtitle.value = 0; - } - }(); - return; - }, [duration]); + final seeking = useState(false); useEffect(() { () async { final currentExternalSubtitle = externalSubtitle.value; if (currentExternalSubtitle == null || externalSubtitles.isEmpty) { - controller.setExternalSubtitle(''); + controller.value.setExternalSubtitle(''); } else if (externalSubtitle.value! < externalSubtitles.length) { bool isExists = true; @@ -194,7 +170,7 @@ FvpPlayer useFvpPlayer(BuildContext context) { } if (isExists) { - controller.setExternalSubtitle(uri); + controller.value.setExternalSubtitle(uri); } else { externalSubtitle.value = null; } @@ -207,9 +183,9 @@ FvpPlayer useFvpPlayer(BuildContext context) { useEffect(() { () async { if (currentPlay != null && - isCompleted && - controller.value.position != Duration.zero && - controller.value.duration != Duration.zero) { + controller.value.value.isCompleted && + controller.value.value.position != Duration.zero && + controller.value.value.duration != Duration.zero) { logger('Completed: ${currentPlay.file.name}'); if (repeat == Repeat.one) return; if (currentPlayIndex == playQueue.length - 1) { @@ -223,30 +199,59 @@ FvpPlayer useFvpPlayer(BuildContext context) { } }(); return; - }, [isCompleted]); + }, [controller.value.value.isCompleted]); useEffect(() { - if (controller.value.isInitialized) { - controller.setPlaybackSpeed(rate); + if (controller.value.value.isInitialized) { + controller.value.setPlaybackSpeed(rate); } return; }, [rate]); useEffect(() { - if (controller.value.isInitialized) { - controller.setVolume(isMuted ? 0 : volume / 100); + if (controller.value.value.isInitialized) { + controller.value.setVolume(isMuted ? 0 : volume / 100); } return; }, [volume, isMuted]); useEffect(() { - if (controller.value.isInitialized) { + if (controller.value.value.isInitialized) { logger('Set looping: $looping'); - controller.setLooping(repeat == Repeat.one ? true : false); + controller.value.setLooping(repeat == Repeat.one ? true : false); } return; }, [looping]); + useEffect(() { + () async { + if (controller.value.value.duration != Duration.zero && + file != null && + file.type == ContentType.video) { + Progress? progress = history[file.getID()]; + if (progress != null) { + if (!alwaysPlayFromBeginning && + (progress.duration.inMilliseconds - + progress.position.inMilliseconds) > + 5000) { + logger( + 'Resume progress: ${file.name} position: ${progress.position} duration: ${progress.duration}'); + await controller.value.seekTo(progress.position); + } + } + } + + if (autoPlay) { + controller.value.play(); + } + + if (externalSubtitles.isNotEmpty) { + externalSubtitle.value = 0; + } + }(); + return; + }, [controller.value.value.duration]); + useEffect(() { return () { if (isAndroid && @@ -257,14 +262,14 @@ FvpPlayer useFvpPlayer(BuildContext context) { } if (file != null && - controller.value.isInitialized && - controller.value.duration.inSeconds != 0) { + controller.value.value.isInitialized && + controller.value.value.duration.inSeconds != 0) { logger( - 'Save progress: ${file.name}, position: ${controller.value.position}, duration: ${controller.value.duration}'); + 'Save progress: ${file.name}, position: ${controller.value.value.position}, duration: ${controller.value.value.duration}'); useHistoryStore().add(Progress( dateTime: DateTime.now().toUtc(), - position: controller.value.position, - duration: controller.value.duration, + position: controller.value.value.position, + duration: controller.value.value.duration, file: file, )); } @@ -272,7 +277,7 @@ FvpPlayer useFvpPlayer(BuildContext context) { }, [currentPlay?.file]); useEffect(() { - if (isPlaying) { + if (controller.value.value.isPlaying) { logger('Enable wakelock'); WakelockPlus.enable(); } else { @@ -280,39 +285,39 @@ FvpPlayer useFvpPlayer(BuildContext context) { WakelockPlus.disable(); } return; - }, [isPlaying]); + }, [controller.value.value.isPlaying]); Future play() async { - if (!controller.value.isInitialized && !isInitializing.value) { + if (!controller.value.value.isInitialized && !isInitializing.value) { init(); } - controller.play(); + controller.value.play(); } Future pause() async { - controller.pause(); + controller.value.pause(); } Future seekTo(Duration newPosition) async { logger('Seek to: $newPosition'); - if (duration == Duration.zero) return; + if (controller.value.value.duration == Duration.zero) return; newPosition.inSeconds < 0 - ? await controller.seekTo(Duration.zero) - : newPosition.inSeconds > duration.inSeconds - ? await controller.seekTo(duration) - : await controller.seekTo(newPosition); + ? await controller.value.seekTo(Duration.zero) + : newPosition.inSeconds > controller.value.value.duration.inSeconds + ? await controller.value.seekTo(controller.value.value.duration) + : await controller.value.seekTo(newPosition); } Future stepBackward() async { if (file?.type == ContentType.video) { - await controller.step(frames: -1); + await controller.value.step(frames: -1); logger('Step backward'); } } Future stepForward() async { if (file?.type == ContentType.video) { - await controller.step(frames: 1); + await controller.value.step(frames: 1); logger('Step forward'); } } @@ -325,13 +330,13 @@ FvpPlayer useFvpPlayer(BuildContext context) { return; } - if (file != null && duration != Duration.zero) { + if (file != null && controller.value.value.duration != Duration.zero) { logger( - 'Save progress: ${file.name}, position: $position, duration: $duration'); + 'Save progress: ${file.name}, position: ${controller.value.value.position}, duration: ${controller.value.value.duration}'); useHistoryStore().add(Progress( dateTime: DateTime.now().toUtc(), - position: position, - duration: duration, + position: controller.value.value.position, + duration: controller.value.value.duration, file: file, )); } @@ -340,25 +345,30 @@ FvpPlayer useFvpPlayer(BuildContext context) { useEffect(() => saveProgress, []); return FvpPlayer( - controller: controller, + controller: controller.value, isInitializing: isInitializing.value, - isPlaying: isPlaying, + isPlaying: controller.value.value.isPlaying, externalSubtitle: externalSubtitle, externalSubtitles: externalSubtitles, - position: duration == Duration.zero ? Duration.zero : position, - duration: duration, - buffer: buffered.isEmpty || duration == Duration.zero + position: controller.value.value.duration == Duration.zero + ? Duration.zero + : controller.value.value.position, + duration: controller.value.value.duration, + buffer: controller.value.value.buffered.isEmpty || + controller.value.value.duration == Duration.zero ? Duration.zero - : buffered.reduce((max, curr) => curr.end > max.end ? curr : max).end, + : controller.value.value.buffered + .reduce((max, curr) => curr.end > max.end ? curr : max) + .end, aspect: aspect, - width: size.width, - height: size.height, + width: controller.value.value.size.width, + height: controller.value.value.size.height, play: play, pause: pause, - backward: (seconds) => - seekTo(Duration(seconds: position.inSeconds - seconds)), - forward: (seconds) => - seekTo(Duration(seconds: position.inSeconds + seconds)), + backward: (seconds) => seekTo( + Duration(seconds: controller.value.value.position.inSeconds - seconds)), + forward: (seconds) => seekTo( + Duration(seconds: controller.value.value.position.inSeconds + seconds)), stepBackward: stepBackward, stepForward: stepForward, seekTo: seekTo, diff --git a/lib/pages/player/iris_player.dart b/lib/pages/player/iris_player.dart index dd81cf0..ef632a6 100644 --- a/lib/pages/player/iris_player.dart +++ b/lib/pages/player/iris_player.dart @@ -377,33 +377,28 @@ class IrisPlayer extends HookWidget { ), ), Positioned( - left: videoViewOffset.dx, - top: videoViewOffset.dy, - width: videoViewSize.width, - height: videoViewSize.height, - child: player is FvpPlayer - ? FittedBox( + left: videoViewOffset.dx, + top: videoViewOffset.dy, + width: videoViewSize.width, + height: videoViewSize.height, + child: switch (player) { + MediaKitPlayer player => Video( + key: ValueKey(currentPlay?.file.uri), + controller: player.controller, + controls: NoVideoControls, + fit: + fit == BoxFit.none ? BoxFit.contain : fit, + ), + FvpPlayer player => FittedBox( fit: fit, child: SizedBox( width: player.width, height: player.height, - child: VideoPlayer( - (player as FvpPlayer).controller), + child: VideoPlayer(player.controller), ), - ) - : player is MediaKitPlayer - ? Video( - key: ValueKey(currentPlay?.file.uri), - controller: - (player as MediaKitPlayer).controller, - controls: NoVideoControls, - fit: fit == BoxFit.none - ? BoxFit.contain - : fit, - // wakelock: mediaType == 'video', - ) - : Container(), - ), + ), + _ => Container(), + }), // Audio if (currentPlay?.file.type == ContentType.audio) Positioned( From 2722aef9fbaf3e36c823dca6e7f42be7dcfa9822 Mon Sep 17 00:00:00 2001 From: 22 <60903333+nini22P@users.noreply.github.com> Date: Sun, 14 Sep 2025 17:32:53 +0800 Subject: [PATCH 13/31] refactor: overhaul player architecture and state management --- analysis_options.yaml | 2 + lib/hooks/player/use_fvp_player.dart | 158 +- lib/hooks/player/use_media_kit_player.dart | 147 +- lib/hooks/{ => ui}/use_full_screen.dart | 9 +- lib/hooks/{ => ui}/use_orientation.dart | 16 +- lib/hooks/ui/use_resize_window.dart | 21 + lib/hooks/use_app_lifecycle.dart | 8 +- lib/hooks/use_cover.dart | 52 +- lib/hooks/use_gesture.dart | 56 +- lib/hooks/use_keyboard.dart | 27 +- lib/l10n/app_en.arb | 3 +- lib/l10n/app_zh.arb | 3 +- lib/models/player.dart | 24 +- lib/models/storages/local.dart | 2 +- lib/models/store/player_ui_state.dart | 21 + lib/models/store/ui_state.dart | 16 - lib/oss_licenses.dart | 4736 ++++++++++++----- lib/pages/home/home.dart | 67 +- lib/pages/player/control_bar/control_bar.dart | 93 +- .../control_bar/control_bar_slider.dart | 11 +- lib/pages/player/controls_overlay.dart | 360 ++ lib/pages/player/iris_player.dart | 696 --- lib/pages/player/player.dart | 348 ++ lib/pages/player/player_view.dart | 42 + lib/pages/player/video_view.dart | 36 + .../{libraries.dart => dependencies.dart} | 4 +- lib/pages/settings/play.dart | 3 +- lib/pages/settings/settings.dart | 4 +- lib/store/use_play_queue_store.dart | 14 +- lib/store/use_player_ui_store.dart | 44 + lib/store/use_ui_store.dart | 24 - lib/widgets/drag_aria.dart | 43 + lib/widgets/title_bar.dart | 12 +- pubspec.lock | 93 +- pubspec.yaml | 10 +- 35 files changed, 4756 insertions(+), 2449 deletions(-) rename lib/hooks/{ => ui}/use_full_screen.dart (66%) rename lib/hooks/{ => ui}/use_orientation.dart (77%) create mode 100644 lib/hooks/ui/use_resize_window.dart create mode 100644 lib/models/store/player_ui_state.dart delete mode 100644 lib/models/store/ui_state.dart create mode 100644 lib/pages/player/controls_overlay.dart delete mode 100644 lib/pages/player/iris_player.dart create mode 100644 lib/pages/player/player.dart create mode 100644 lib/pages/player/player_view.dart create mode 100644 lib/pages/player/video_view.dart rename lib/pages/settings/{libraries.dart => dependencies.dart} (96%) create mode 100644 lib/store/use_player_ui_store.dart delete mode 100644 lib/store/use_ui_store.dart create mode 100644 lib/widgets/drag_aria.dart diff --git a/analysis_options.yaml b/analysis_options.yaml index d54b87e..4b246ae 100644 --- a/analysis_options.yaml +++ b/analysis_options.yaml @@ -27,5 +27,7 @@ linter: # Additional information about this file can be found at # https://dart.dev/guides/language/analysis-options analyzer: + plugins: + - custom_lint errors: invalid_annotation_target: ignore \ No newline at end of file diff --git a/lib/hooks/player/use_fvp_player.dart b/lib/hooks/player/use_fvp_player.dart index 0cdffe8..d76eb5e 100644 --- a/lib/hooks/player/use_fvp_player.dart +++ b/lib/hooks/player/use_fvp_player.dart @@ -12,6 +12,7 @@ import 'package:iris/models/store/app_state.dart'; import 'package:iris/store/use_app_store.dart'; import 'package:iris/store/use_history_store.dart'; import 'package:iris/store/use_play_queue_store.dart'; +import 'package:iris/store/use_player_ui_store.dart'; import 'package:iris/store/use_storage_store.dart'; import 'package:iris/utils/check_data_source_type.dart'; import 'package:iris/utils/logger.dart'; @@ -43,26 +44,51 @@ FvpPlayer useFvpPlayer(BuildContext context) { () => playQueue.indexWhere((element) => element.index == currentIndex), [playQueue, currentIndex]); - final PlayQueueItem? currentPlay = useMemoized( + final FileItem? file = useMemoized( () => playQueue.isEmpty || currentPlayIndex < 0 ? null - : playQueue[currentPlayIndex], + : playQueue[currentPlayIndex].file, [playQueue, currentPlayIndex]); - final file = useMemoized(() => currentPlay?.file, [currentPlay]); - final externalSubtitle = useState(null); - final List externalSubtitles = useMemoized( - () => currentPlay?.file.subtitles ?? [], [currentPlay?.file.subtitles]); + final List externalSubtitles = + useMemoized(() => file?.subtitles ?? [], [file?.subtitles]); final isInitializing = useState(false); - MediaStream mediaStream = MediaStream(); - final streamUrl = mediaStream.url; + MediaStream mediaStream = useMemoized(() => MediaStream()); + final streamUrl = useMemoized(() => mediaStream.url); final controller = useState(VideoPlayerController.networkUrl(Uri.parse(''))); + final isPlaying = useListenableSelector( + controller.value, () => controller.value.value.isPlaying); + final position = useListenableSelector( + controller.value, () => controller.value.value.position); + final duration = useListenableSelector( + controller.value, () => controller.value.value.duration); + final buffered = useListenableSelector( + controller.value, () => controller.value.value.buffered); + final width = useListenableSelector( + controller.value, () => controller.value.value.size.width); + final height = useListenableSelector( + controller.value, () => controller.value.value.size.height); + + useEffect(() { + if (file?.type != ContentType.video) { + usePlayerUiStore().updateAspectRatio(0); + return; + } + + if (width != 0 && height != 0) { + usePlayerUiStore().updateAspectRatio(width / height); + } else { + usePlayerUiStore().updateAspectRatio(0); + } + return; + }, [file?.type, width, height]); + Future init() async { isInitializing.value = true; @@ -127,27 +153,6 @@ FvpPlayer useFvpPlayer(BuildContext context) { }; }, []); - final double aspect = useMemoized(() { - if (file?.type != ContentType.video) { - return 0; - } - - final width = controller.value.value.size.width; - final height = controller.value.value.size.height; - - if (width != 0 && height != 0) { - return width / height; - } else { - return 0; - } - }, [ - file?.type, - controller.value.value.size.width, - controller.value.value.size.height - ]); - - final seeking = useState(false); - useEffect(() { () async { final currentExternalSubtitle = externalSubtitle.value; @@ -182,11 +187,11 @@ FvpPlayer useFvpPlayer(BuildContext context) { useEffect(() { () async { - if (currentPlay != null && + if (file != null && controller.value.value.isCompleted && controller.value.value.position != Duration.zero && controller.value.value.duration != Duration.zero) { - logger('Completed: ${currentPlay.file.name}'); + logger('Completed: ${file.name}'); if (repeat == Repeat.one) return; if (currentPlayIndex == playQueue.length - 1) { if (repeat == Repeat.all) { @@ -274,7 +279,7 @@ FvpPlayer useFvpPlayer(BuildContext context) { )); } }; - }, [currentPlay?.file]); + }, [file]); useEffect(() { if (controller.value.value.isPlaying) { @@ -298,7 +303,7 @@ FvpPlayer useFvpPlayer(BuildContext context) { controller.value.pause(); } - Future seekTo(Duration newPosition) async { + Future seek(Duration newPosition) async { logger('Seek to: $newPosition'); if (controller.value.value.duration == Duration.zero) return; newPosition.inSeconds < 0 @@ -308,6 +313,12 @@ FvpPlayer useFvpPlayer(BuildContext context) { : await controller.value.seekTo(newPosition); } + Future backward(int seconds) async => await seek( + Duration(seconds: controller.value.value.position.inSeconds - seconds)); + + Future forward(int seconds) async => await seek( + Duration(seconds: controller.value.value.position.inSeconds + seconds)); + Future stepBackward() async { if (file?.type == ContentType.video) { await controller.value.step(frames: -1); @@ -344,37 +355,54 @@ FvpPlayer useFvpPlayer(BuildContext context) { useEffect(() => saveProgress, []); - return FvpPlayer( - controller: controller.value, - isInitializing: isInitializing.value, - isPlaying: controller.value.value.isPlaying, - externalSubtitle: externalSubtitle, - externalSubtitles: externalSubtitles, - position: controller.value.value.duration == Duration.zero - ? Duration.zero - : controller.value.value.position, - duration: controller.value.value.duration, - buffer: controller.value.value.buffered.isEmpty || - controller.value.value.duration == Duration.zero - ? Duration.zero - : controller.value.value.buffered - .reduce((max, curr) => curr.end > max.end ? curr : max) - .end, - aspect: aspect, - width: controller.value.value.size.width, - height: controller.value.value.size.height, - play: play, - pause: pause, - backward: (seconds) => seekTo( - Duration(seconds: controller.value.value.position.inSeconds - seconds)), - forward: (seconds) => seekTo( - Duration(seconds: controller.value.value.position.inSeconds + seconds)), - stepBackward: stepBackward, - stepForward: stepForward, - seekTo: seekTo, - saveProgress: saveProgress, - seeking: seeking.value, - updatePosition: seekTo, - updateSeeking: (value) => seeking.value = value, + final fvpPlayer = useMemoized( + () => FvpPlayer( + controller: controller.value, + isInitializing: isInitializing.value, + isPlaying: isPlaying, + externalSubtitle: externalSubtitle, + externalSubtitles: externalSubtitles, + position: + !controller.value.value.isInitialized || duration == Duration.zero + ? Duration.zero + : position, + duration: controller.value.value.isInitialized ? duration : Duration.zero, + buffer: buffered.isEmpty || duration == Duration.zero + ? Duration.zero + : buffered.reduce((max, curr) => curr.end > max.end ? curr : max).end, + width: width, + height: height, + play: play, + pause: pause, + backward: backward, + forward: forward, + stepBackward: stepBackward, + stepForward: stepForward, + seek: seek, + saveProgress: saveProgress, + ), + [ + controller.value, + controller.value.value.isInitialized, + isInitializing.value, + isPlaying, + externalSubtitle.value, + externalSubtitles, + position, + duration, + buffered, + width, + height, + play, + pause, + seek, + backward, + forward, + stepBackward, + stepForward, + saveProgress, + ], ); + + return fvpPlayer; } diff --git a/lib/hooks/player/use_media_kit_player.dart b/lib/hooks/player/use_media_kit_player.dart index 82122e4..87121b8 100644 --- a/lib/hooks/player/use_media_kit_player.dart +++ b/lib/hooks/player/use_media_kit_player.dart @@ -12,6 +12,7 @@ import 'package:iris/models/store/app_state.dart'; import 'package:iris/store/use_app_store.dart'; import 'package:iris/store/use_history_store.dart'; import 'package:iris/store/use_play_queue_store.dart'; +import 'package:iris/store/use_player_ui_store.dart'; import 'package:iris/store/use_storage_store.dart'; import 'package:iris/utils/logger.dart'; import 'package:iris/utils/platform.dart'; @@ -94,24 +95,37 @@ MediaKitPlayer useMediaKitPlayer(BuildContext context) { : playQueue[currentPlayIndex].file, [playQueue, currentPlayIndex]); - ValueNotifier seeking = useState(false); + final playingStream = useMemoized(() => player.stream.playing, []); + bool playing = useStream(playingStream).data ?? false; + + final videoParamsStream = useMemoized(() => player.stream.videoParams, []); + VideoParams? videoParams = useStream(videoParamsStream).data; - bool playing = useStream(player.stream.playing).data ?? false; - VideoParams? videoParams = useStream(player.stream.videoParams).data; // AudioParams? audioParams = useStream(player.stream.audioParams).data; - ValueNotifier position = useState(Duration.zero); - Duration duration = useStream(player.stream.duration).data ?? Duration.zero; - Duration buffer = useStream(player.stream.buffer).data ?? Duration.zero; - bool completed = useStream(player.stream.completed).data ?? false; + + final positionStream = useMemoized(() => player.stream.position, []); + final position = useStream(positionStream).data ?? Duration.zero; + + final durationStream = useMemoized(() => player.stream.duration, []); + Duration duration = useStream(durationStream).data ?? Duration.zero; + + final bufferStream = useMemoized(() => player.stream.buffer, []); + Duration buffer = useStream(bufferStream).data ?? Duration.zero; + + final completedStream = useMemoized(() => player.stream.completed, []); + bool completed = useStream(completedStream).data ?? false; + // double rate = useStream(player.stream.rate).data ?? 1.0; - Track? track = useStream(player.stream.track).data; + final trackStream = useMemoized(() => player.stream.track, []); + Track? track = useStream(trackStream).data; AudioTrack audio = useMemoized(() => track?.audio ?? AudioTrack.no(), [track?.audio]); SubtitleTrack subtitle = useMemoized( () => track?.subtitle ?? SubtitleTrack.no(), [track?.subtitle]); - Tracks? tracks = useStream(player.stream.tracks).data; + final tracksStream = useMemoized(() => player.stream.tracks, []); + Tracks? tracks = useStream(tracksStream).data; List audios = useMemoized(() => (tracks?.audio ?? []), [tracks?.audio]); List subtitles = useMemoized( @@ -124,18 +138,9 @@ MediaKitPlayer useMediaKitPlayer(BuildContext context) { (subtitle) => subtitles.any((item) => item.title == subtitle.name)), [file?.subtitles, subtitles]); - final positionStream = useStream(player.stream.position); - - if (positionStream.hasData) { - if (!seeking.value) { - position.value = positionStream.data!; - } - } - final isInitializing = useState(false); - MediaStream mediaStream = MediaStream(); - final streamUrl = mediaStream.url; + MediaStream mediaStream = useMemoized(() => MediaStream(), []); Future init(FileItem file) async { if (file.uri == '') return; @@ -148,7 +153,7 @@ MediaKitPlayer useMediaKitPlayer(BuildContext context) { await player.open( Media( file.storageType == StorageType.ftp - ? '$streamUrl/${file.uri}' + ? '${mediaStream.url}/${file.uri}' : file.uri, httpHeaders: auth != null ? {'authorization': auth} : {}, ), @@ -212,7 +217,7 @@ MediaKitPlayer useMediaKitPlayer(BuildContext context) { if (externalSubtitles!.isNotEmpty) { logger('Set external subtitle: ${externalSubtitles[0]}'); final uri = file?.storageType == StorageType.ftp - ? '$streamUrl/${externalSubtitles[0].uri}' + ? '${mediaStream.url}/${externalSubtitles[0].uri}' : externalSubtitles[0].uri; logger('External subtitle uri: $uri'); await player.setSubtitleTrack( @@ -269,9 +274,21 @@ MediaKitPlayer useMediaKitPlayer(BuildContext context) { return; }, [repeat]); - void updatePosition(Duration newPosition) => position.value = newPosition; + useEffect(() { + if (file?.type != ContentType.video) { + usePlayerUiStore().updateAspectRatio(0); + return; + } - void updateSeeking(bool value) => seeking.value = value; + final width = videoParams?.w ?? 0; + final height = videoParams?.h ?? 0; + if (width == 0 || height == 0) { + usePlayerUiStore().updateAspectRatio(0); + } else { + usePlayerUiStore().updateAspectRatio(width / height); + } + return; + }, [file?.type, videoParams?.w, videoParams?.h]); Future saveProgress() async { if (isAndroid && @@ -306,7 +323,7 @@ MediaKitPlayer useMediaKitPlayer(BuildContext context) { await player.pause(); } - Future seekTo(Duration newPosition) async => + Future seek(Duration newPosition) async => newPosition.inMilliseconds < 0 ? await player.seek(Duration.zero) : newPosition.inMilliseconds > duration.inMilliseconds @@ -314,11 +331,11 @@ MediaKitPlayer useMediaKitPlayer(BuildContext context) { : await player.seek(newPosition); Future backward(int seconds) async { - await seekTo(Duration(seconds: position.value.inSeconds - seconds)); + await seek(Duration(seconds: position.inSeconds - seconds)); } Future forward(int seconds) async { - await seekTo(Duration(seconds: position.value.inSeconds + seconds)); + await seek(Duration(seconds: position.inSeconds + seconds)); } Future stepBackward() async { @@ -337,32 +354,56 @@ MediaKitPlayer useMediaKitPlayer(BuildContext context) { } } - return MediaKitPlayer( - player: player, - controller: controller, - subtitle: subtitle, - subtitles: subtitles, - externalSubtitles: externalSubtitles ?? [], - audio: audio, - audios: audios, - isInitializing: isInitializing.value, - isPlaying: playing, - position: duration == Duration.zero ? Duration.zero : position.value, - duration: duration, - buffer: duration == Duration.zero ? Duration.zero : buffer, - seeking: seeking.value, - aspect: videoParams?.aspect, - width: videoParams?.w?.toDouble(), - height: videoParams?.h?.toDouble(), - updatePosition: updatePosition, - updateSeeking: updateSeeking, - saveProgress: saveProgress, - play: play, - pause: pause, - backward: backward, - forward: forward, - stepBackward: stepBackward, - stepForward: stepForward, - seekTo: seekTo, + final mediaKitPlayer = useMemoized( + () => MediaKitPlayer( + player: player, + controller: controller, + subtitle: subtitle, + subtitles: subtitles, + externalSubtitles: externalSubtitles ?? [], + audio: audio, + audios: audios, + isInitializing: isInitializing.value, + isPlaying: playing, + position: duration == Duration.zero ? Duration.zero : position, + duration: duration, + buffer: duration == Duration.zero ? Duration.zero : buffer, + width: videoParams?.w?.toDouble(), + height: videoParams?.h?.toDouble(), + saveProgress: saveProgress, + play: play, + pause: pause, + backward: backward, + forward: forward, + stepBackward: stepBackward, + stepForward: stepForward, + seek: seek, + ), + [ + player, + controller, + subtitle, + subtitles, + externalSubtitles, + audio, + audios, + isInitializing.value, + playing, + position, + duration, + buffer, + videoParams?.w, + videoParams?.h, + saveProgress, + play, + pause, + backward, + forward, + stepBackward, + stepForward, + seek, + ], ); + + return mediaKitPlayer; } diff --git a/lib/hooks/use_full_screen.dart b/lib/hooks/ui/use_full_screen.dart similarity index 66% rename from lib/hooks/use_full_screen.dart rename to lib/hooks/ui/use_full_screen.dart index e03bda6..3f9f6fc 100644 --- a/lib/hooks/use_full_screen.dart +++ b/lib/hooks/ui/use_full_screen.dart @@ -1,13 +1,14 @@ -import 'package:flutter/material.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:flutter_zustand/flutter_zustand.dart'; -import 'package:iris/store/use_ui_store.dart'; +import 'package:iris/store/use_player_ui_store.dart'; import 'package:iris/utils/platform.dart'; import 'package:window_manager/window_manager.dart'; -void useFullScreen(BuildContext context) { +void useFullScreen() { + final context = useContext(); + final isFullScreen = - useUiStore().select(context, (state) => state.isFullScreen); + usePlayerUiStore().select(context, (state) => state.isFullScreen); useEffect(() { () async { diff --git a/lib/hooks/use_orientation.dart b/lib/hooks/ui/use_orientation.dart similarity index 77% rename from lib/hooks/use_orientation.dart rename to lib/hooks/ui/use_orientation.dart index c1b3e62..60df571 100644 --- a/lib/hooks/use_orientation.dart +++ b/lib/hooks/ui/use_orientation.dart @@ -1,17 +1,19 @@ import 'dart:io'; - -import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:flutter_zustand/flutter_zustand.dart'; -import 'package:iris/models/player.dart'; import 'package:iris/models/store/app_state.dart'; import 'package:iris/store/use_app_store.dart'; +import 'package:iris/store/use_player_ui_store.dart'; -void useOrientation(BuildContext context, MediaPlayer player) { +void useOrientation() { + final context = useContext(); final orientation = useAppStore().select(context, (state) => state.orientation); + final aspectRatio = + usePlayerUiStore().select(context, (state) => state.aspectRatio); + setOrientation(ScreenOrientation orientation, double? aspect) { if (Platform.isAndroid || Platform.isIOS) { switch (orientation) { @@ -35,12 +37,12 @@ void useOrientation(BuildContext context, MediaPlayer player) { } useEffect(() { - setOrientation(orientation, player.aspect); + setOrientation(orientation, aspectRatio); return () => SystemChrome.setPreferredOrientations([]); }, []); useEffect(() { - setOrientation(orientation, player.aspect); + setOrientation(orientation, aspectRatio); return; - }, [orientation, player.aspect]); + }, [orientation, aspectRatio]); } diff --git a/lib/hooks/ui/use_resize_window.dart b/lib/hooks/ui/use_resize_window.dart new file mode 100644 index 0000000..7daa871 --- /dev/null +++ b/lib/hooks/ui/use_resize_window.dart @@ -0,0 +1,21 @@ +import 'package:flutter_hooks/flutter_hooks.dart'; +import 'package:flutter_zustand/flutter_zustand.dart'; +import 'package:iris/store/use_app_store.dart'; +import 'package:iris/store/use_player_ui_store.dart'; +import 'package:iris/utils/platform.dart'; +import 'package:iris/utils/resize_window.dart'; + +void useResizeWindow() { + final context = useContext(); + + final autoResize = useAppStore().select(context, (state) => state.autoResize); + final aspectRatio = + usePlayerUiStore().select(context, (state) => state.aspectRatio); + + useEffect(() { + if (isDesktop) { + resizeWindow(!autoResize ? 0 : aspectRatio); + } + return; + }, [aspectRatio, autoResize]); +} diff --git a/lib/hooks/use_app_lifecycle.dart b/lib/hooks/use_app_lifecycle.dart index cf8b4aa..4337531 100644 --- a/lib/hooks/use_app_lifecycle.dart +++ b/lib/hooks/use_app_lifecycle.dart @@ -1,17 +1,17 @@ import 'dart:ui'; - import 'package:flutter_hooks/flutter_hooks.dart'; -import 'package:iris/models/player.dart'; import 'package:iris/utils/logger.dart'; -void useAppLifecycle(MediaPlayer player) { +void useAppLifecycle( + Future Function() saveProgress, +) { AppLifecycleState? appLifecycleState = useAppLifecycleState(); useEffect(() { try { if (appLifecycleState == AppLifecycleState.paused) { logger('App lifecycle state: paused'); - player.saveProgress(); + saveProgress(); } } catch (e) { logger('App lifecycle state error: $e'); diff --git a/lib/hooks/use_cover.dart b/lib/hooks/use_cover.dart index 2d45a30..712ff14 100644 --- a/lib/hooks/use_cover.dart +++ b/lib/hooks/use_cover.dart @@ -1,19 +1,16 @@ import 'package:collection/collection.dart'; -import 'package:flutter/widgets.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:flutter_zustand/flutter_zustand.dart'; import 'package:iris/models/file.dart'; -import 'package:iris/models/player.dart'; import 'package:iris/models/storages/local.dart'; import 'package:iris/models/storages/storage.dart'; import 'package:iris/store/use_play_queue_store.dart'; import 'package:iris/store/use_storage_store.dart'; import 'package:iris/utils/files_filter.dart'; -FileItem? useCover( - BuildContext context, - MediaPlayer player, -) { +FileItem? useCover(bool isPlaying) { + final context = useContext(); + final playQueue = usePlayQueueStore().select(context, (state) => state.playQueue); final currentIndex = @@ -35,13 +32,6 @@ FileItem? useCover( final storages = useStorageStore().select(context, (state) => state.storages); - final List dir = useMemoized( - () => currentPlay?.file == null || currentPlay!.file.path.isEmpty - ? [] - : ([...currentPlay.file.path]..removeLast()), - [currentPlay?.file], - ); - final Storage? storage = useMemoized( () => currentPlay?.file == null ? null @@ -49,24 +39,32 @@ FileItem? useCover( (storage) => storage.id == currentPlay?.file.storageId), [currentPlay?.file, localStorages, storages]); - final getCover = useMemoized(() async { - if (currentPlay?.file.type != ContentType.audio) return null; + final cover = useState(null); - final files = await storage?.getFiles(dir); + useEffect(() { + () async { + final dir = currentPlay?.file == null || currentPlay!.file.path.isEmpty + ? [] + : ([...currentPlay.file.path]..removeLast()); - if (files == null) return null; + if (storage == null || currentPlay?.file.type != ContentType.audio) { + cover.value = null; + return; + } - final images = filesFilter(files, [ContentType.image]); + final files = await storage.getFiles(dir); - return images.firstWhereOrNull( - (image) => image.name.split('.').first.toLowerCase() == 'cover') ?? - images.firstWhereOrNull((image) => - image.name.toLowerCase().startsWith('cover') || - image.name.toLowerCase().startsWith('folder')) ?? - images.firstOrNull; - }, [currentPlay?.file, dir, player.isPlaying]); + final images = filesFilter(files, [ContentType.image]); - final cover = useFuture(getCover).data; + cover.value = images.firstWhereOrNull((image) => + image.name.split('.').first.toLowerCase() == 'cover') ?? + images.firstWhereOrNull((image) => + image.name.toLowerCase().startsWith('cover') || + image.name.toLowerCase().startsWith('folder')) ?? + images.firstOrNull; + }(); + return null; + }, [storage, isPlaying]); - return cover; + return cover.value; } diff --git a/lib/hooks/use_gesture.dart b/lib/hooks/use_gesture.dart index 21004f2..8a9741c 100644 --- a/lib/hooks/use_gesture.dart +++ b/lib/hooks/use_gesture.dart @@ -1,11 +1,12 @@ import 'package:flutter/gestures.dart'; import 'package:flutter/material.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; +import 'package:flutter_zustand/flutter_zustand.dart'; import 'package:iris/hooks/use_brightness.dart'; import 'package:iris/hooks/use_volume.dart'; import 'package:iris/models/player.dart'; import 'package:iris/store/use_app_store.dart'; -import 'package:iris/store/use_ui_store.dart'; +import 'package:iris/store/use_player_ui_store.dart'; import 'package:iris/utils/platform.dart'; import 'package:iris/utils/resize_window.dart'; import 'package:window_manager/window_manager.dart'; @@ -54,15 +55,28 @@ class Gesture { } Gesture useGesture({ - required BuildContext context, required MediaPlayer player, - required bool isFullScreen, required void Function() showControl, required void Function() hideControl, required void Function() showProgress, - required ValueNotifier isHover, - required ValueNotifier isShowControl, }) { + final context = useContext(); + + final aspectRatio = + usePlayerUiStore().select(context, (state) => state.aspectRatio); + + final isFullScreen = + usePlayerUiStore().select(context, (state) => state.isFullScreen); + + final isShowControl = + usePlayerUiStore().select(context, (state) => state.isShowControl); + final isSeeking = + usePlayerUiStore().select(context, (state) => state.isSeeking); + + final updateIsHovering = useCallback((bool value) { + usePlayerUiStore().updateIsHovering(value); + }, [usePlayerUiStore().updateIsHovering]); + final isTouch = useState(false); final isLongPress = useState(false); final startPosition = useState(null); @@ -81,7 +95,7 @@ Gesture useGesture({ } void onTap() { - if (isShowControl.value) { + if (isShowControl) { hideControl(); } else { showControl(); @@ -93,14 +107,14 @@ Gesture useGesture({ double position = details.globalPosition.dx / MediaQuery.of(context).size.width; if (position > 0.75) { - if (isShowControl.value) { + if (isShowControl) { showControl(); } else { showProgress(); } await player.forward(5); } else if (position < 0.25) { - if (isShowControl.value) { + if (isShowControl) { showControl(); } else { showProgress(); @@ -119,9 +133,9 @@ Gesture useGesture({ } else { if (isDesktop) { if (isFullScreen) { - await resizeWindow(player.aspect); + await resizeWindow(aspectRatio); } - useUiStore().updateFullScreen(!isFullScreen); + usePlayerUiStore().updateFullScreen(!isFullScreen); } } } @@ -176,14 +190,14 @@ Gesture useGesture({ if (!isHorizontalGesture.value && !isVerticalGesture.value) { if (dx > dy) { isHorizontalGesture.value = true; - player.updateSeeking(true); + usePlayerUiStore().updateIsSeeking(true); } else { isVerticalGesture.value = true; } } // 水平滑动 - if (isHorizontalGesture.value && player.seeking) { + if (isHorizontalGesture.value && isSeeking) { double dx = details.delta.dx; int seconds = (dx * 2 + player.position.inSeconds).toInt(); Duration position = Duration( @@ -192,8 +206,8 @@ Gesture useGesture({ : seconds > player.duration.inSeconds ? player.duration.inSeconds : seconds); - player.updatePosition(position); - if (isShowControl.value) { + player.seek(position); + if (isShowControl) { showControl(); } else { showProgress(); @@ -243,9 +257,9 @@ Gesture useGesture({ isLeftGesture.value = false; isRightGesture.value = false; startPosition.value = null; - if (player.seeking) { - await player.seekTo(player.position); - player.updateSeeking(false); + if (isSeeking) { + await player.seek(player.position); + usePlayerUiStore().updateIsSeeking(false); } } @@ -255,16 +269,16 @@ Gesture useGesture({ isLeftGesture.value = false; isRightGesture.value = false; startPosition.value = null; - if (player.seeking) { + if (isSeeking) { isTouch.value = false; - await player.seekTo(player.position); - player.updateSeeking(false); + await player.seek(player.position); + usePlayerUiStore().updateIsSeeking(false); } } void onHover(PointerHoverEvent event) { if (event.kind != PointerDeviceKind.touch) { - isHover.value = true; + updateIsHovering(true); showControl(); } } diff --git a/lib/hooks/use_keyboard.dart b/lib/hooks/use_keyboard.dart index cbf536a..eb9b0c7 100644 --- a/lib/hooks/use_keyboard.dart +++ b/lib/hooks/use_keyboard.dart @@ -1,7 +1,6 @@ import 'dart:io'; - -import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; +import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:iris/globals.dart'; import 'package:iris/models/player.dart'; import 'package:iris/models/storages/local.dart'; @@ -12,7 +11,7 @@ import 'package:iris/pages/settings/settings.dart'; import 'package:iris/pages/storages/storages.dart'; import 'package:iris/store/use_app_store.dart'; import 'package:iris/store/use_play_queue_store.dart'; -import 'package:iris/store/use_ui_store.dart'; +import 'package:iris/store/use_player_ui_store.dart'; import 'package:iris/utils/platform.dart'; import 'package:iris/widgets/bottom_sheets/show_open_link_bottom_sheet.dart'; import 'package:iris/widgets/dialogs/show_open_link_dialog.dart'; @@ -21,15 +20,17 @@ import 'package:iris/widgets/popup.dart'; typedef KeyboardEvent = void Function(KeyEvent event); KeyboardEvent useKeyboard({ - required BuildContext context, required MediaPlayer player, - required bool isFullScreen, - required ValueNotifier isShowControl, required void Function() showControl, required Future Function(Future) showControlForHover, required void Function() showProgress, - required bool shuffle, }) { + final context = useContext(); + + final shuffle = useAppStore().state.shuffle; + final isFullScreen = usePlayerUiStore().state.isFullScreen; + final isShowControl = usePlayerUiStore().state.isShowControl; + void onKeyEvent(KeyEvent event) async { if (event.runtimeType == KeyDownEvent) { if (HardwareKeyboard.instance.isAltPressed) { @@ -130,8 +131,10 @@ KeyboardEvent useKeyboard({ case LogicalKeyboardKey.mediaPlayPause: showControl(); if (player.isPlaying) { + useAppStore().updateAutoPlay(false); player.pause(); } else { + useAppStore().updateAutoPlay(true); player.play(); } break; @@ -178,14 +181,14 @@ KeyboardEvent useKeyboard({ // 退出全屏 case LogicalKeyboardKey.escape: if (isDesktop && isFullScreen) { - useUiStore().updateFullScreen(false); + usePlayerUiStore().updateFullScreen(false); } break; // 全屏 case LogicalKeyboardKey.enter: case LogicalKeyboardKey.f11: if (isDesktop) { - useUiStore().updateFullScreen(!isFullScreen); + usePlayerUiStore().updateFullScreen(!isFullScreen); } break; case LogicalKeyboardKey.tab: @@ -193,7 +196,7 @@ KeyboardEvent useKeyboard({ break; case LogicalKeyboardKey.f10: showControl(); - await useUiStore().toggleIsAlwaysOnTop(); + await usePlayerUiStore().toggleIsAlwaysOnTop(); break; case LogicalKeyboardKey.equal: await player.stepForward(); @@ -215,7 +218,7 @@ KeyboardEvent useKeyboard({ switch (event.logicalKey) { // 快退 case LogicalKeyboardKey.arrowLeft: - if (isShowControl.value) { + if (isShowControl) { showControl(); } else { showProgress(); @@ -224,7 +227,7 @@ KeyboardEvent useKeyboard({ break; // 快进 case LogicalKeyboardKey.arrowRight: - if (isShowControl.value) { + if (isShowControl) { showControl(); } else { showProgress(); diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index a57b3c2..2c765c8 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -27,6 +27,7 @@ "confirmUpdate": "Confirm update", "crop": "Crop", "dark": "Datk", + "dependencies": "Dependencies", "device": "Device", "download": "Download", "download_and_update": "Download and update", @@ -52,7 +53,6 @@ "landscape": "Landscape", "language": "Language", "last_modified": "Last modified", - "libraries": "Libraries", "light": "Light", "local_storage": "Local storage", "media_file_does_not_exist": "Media file does not exist", @@ -95,6 +95,7 @@ "size": "Size", "sort": "Sort", "source_code": "Source code", + "stop": "Stop", "storage": "Storage", "stretch": "Stretch", "subtitle": "Subtitle", diff --git a/lib/l10n/app_zh.arb b/lib/l10n/app_zh.arb index 9a22871..5c3ac4e 100644 --- a/lib/l10n/app_zh.arb +++ b/lib/l10n/app_zh.arb @@ -27,6 +27,7 @@ "confirmUpdate": "确认更新", "crop": "裁切", "dark": "暗色", + "dependencies": "开源库", "device": "设备", "download": "下载", "download_and_update": "下载并更新", @@ -52,7 +53,6 @@ "landscape": "横向", "language": "语言", "last_modified": "最后修改", - "libraries": "开源库", "light": "亮色", "local_storage": "本地存储", "media_file_does_not_exist": "媒体文件不存在", @@ -95,6 +95,7 @@ "size": "大小", "sort": "排序", "source_code": "源码", + "stop": "停止", "storage": "存储", "stretch": "拉伸", "subtitle": "字幕", diff --git a/lib/models/player.dart b/lib/models/player.dart index 5067ff5..4316193 100644 --- a/lib/models/player.dart +++ b/lib/models/player.dart @@ -11,12 +11,8 @@ class MediaPlayer { final Duration position; final Duration duration; final Duration buffer; - final bool seeking; - final double? aspect; final double? width; final double? height; - final void Function(Duration) updatePosition; - final void Function(bool) updateSeeking; final Future Function() saveProgress; final Future Function() play; final Future Function() pause; @@ -24,7 +20,7 @@ class MediaPlayer { final Future Function(int) forward; final Future Function() stepBackward; final Future Function() stepForward; - final Future Function(Duration) seekTo; + final Future Function(Duration) seek; MediaPlayer({ required this.isInitializing, @@ -33,12 +29,8 @@ class MediaPlayer { required this.position, required this.duration, required this.buffer, - required this.seeking, - required this.aspect, required this.width, required this.height, - required this.updatePosition, - required this.updateSeeking, required this.saveProgress, required this.play, required this.pause, @@ -46,7 +38,7 @@ class MediaPlayer { required this.forward, required this.stepBackward, required this.stepForward, - required this.seekTo, + required this.seek, }); } @@ -71,12 +63,8 @@ class MediaKitPlayer extends MediaPlayer { required super.position, required super.duration, required super.buffer, - required super.seeking, - required super.aspect, required super.width, required super.height, - required super.updatePosition, - required super.updateSeeking, required super.saveProgress, required super.play, required super.pause, @@ -84,7 +72,7 @@ class MediaKitPlayer extends MediaPlayer { required super.forward, required super.stepBackward, required super.stepForward, - required super.seekTo, + required super.seek, }); } @@ -101,12 +89,8 @@ class FvpPlayer extends MediaPlayer { required super.position, required super.duration, required super.buffer, - required super.seeking, - required super.aspect, required super.width, required super.height, - required super.updatePosition, - required super.updateSeeking, required super.saveProgress, required super.play, required super.pause, @@ -114,6 +98,6 @@ class FvpPlayer extends MediaPlayer { required super.forward, required super.stepBackward, required super.stepForward, - required super.seekTo, + required super.seek, }); } diff --git a/lib/models/storages/local.dart b/lib/models/storages/local.dart index 7dc23db..a5ce30c 100644 --- a/lib/models/storages/local.dart +++ b/lib/models/storages/local.dart @@ -264,7 +264,7 @@ Future> getLocalFiles( uri: entity.path, path: [...path, p.basename(entity.path)], isDir: isDir, - size: stat.size, + size: isDir ? 0 : stat.size, lastModified: stat.modified, type: isDir ? ContentType.dir : checkContentType(entity.path), subtitles: [], diff --git a/lib/models/store/player_ui_state.dart b/lib/models/store/player_ui_state.dart new file mode 100644 index 0000000..7c9ce5d --- /dev/null +++ b/lib/models/store/player_ui_state.dart @@ -0,0 +1,21 @@ +import 'package:freezed_annotation/freezed_annotation.dart'; +import 'package:flutter/foundation.dart'; + +part 'player_ui_state.freezed.dart'; +part 'player_ui_state.g.dart'; + +@freezed +abstract class PlayerUiState with _$PlayerUiState { + const factory PlayerUiState({ + @Default(0) double aspectRatio, + @Default(false) bool isAlwaysOnTop, + @Default(false) bool isFullScreen, + @Default(false) bool isSeeking, + @Default(false) bool isHovering, + @Default(true) bool isShowControl, + @Default(false) bool isShowProgress, + }) = _PlayerUiState; + + factory PlayerUiState.fromJson(Map json) => + _$PlayerUiStateFromJson(json); +} diff --git a/lib/models/store/ui_state.dart b/lib/models/store/ui_state.dart deleted file mode 100644 index 2c89b99..0000000 --- a/lib/models/store/ui_state.dart +++ /dev/null @@ -1,16 +0,0 @@ -import 'package:freezed_annotation/freezed_annotation.dart'; -import 'package:flutter/foundation.dart'; - -part 'ui_state.freezed.dart'; -part 'ui_state.g.dart'; - -@freezed -abstract class UiState with _$UiState { - const factory UiState({ - @Default(false) bool isAlwaysOnTop, - @Default(false) bool isFullScreen, - }) = _UiState; - - factory UiState.fromJson(Map json) => - _$UiStateFromJson(json); -} diff --git a/lib/oss_licenses.dart b/lib/oss_licenses.dart index fe3eb5e..6674294 100644 --- a/lib/oss_licenses.dart +++ b/lib/oss_licenses.dart @@ -1,15 +1,17 @@ +// dart format off // cSpell:disable // ignore_for_file: always_put_required_named_parameters_first // ignore_for_file: constant_identifier_names // ignore_for_file: sort_constructors_first -// This code was generated by flutter_oss_licenses -// https://pub.dev/packages/flutter_oss_licenses +// This code was generated by dart_pubspec_licenses +// https://pub.dev/packages/dart_pubspec_licenses /// All dependencies including transitive dependencies. const allDependencies = [ __fe_analyzer_shared, _analyzer, + _analyzer_plugin, _android_x_storage, _app_links, _app_links_linux, @@ -23,14 +25,13 @@ const allDependencies = [ _build, _build_config, _build_daemon, - _build_resolvers, _build_runner, - _build_runner_core, _built_collection, _built_value, _characters, _charset, _checked_yaml, + _ci, _cli_util, _clock, _code_builder, @@ -42,6 +43,10 @@ const allDependencies = [ _cryptography, _csslib, _cupertino_icons, + _custom_lint, + _custom_lint_builder, + _custom_lint_core, + _custom_lint_visitor, _dart_pubspec_licenses, _dart_style, _dbus, @@ -62,9 +67,9 @@ const allDependencies = [ _flutter, _flutter_breadcrumb, _flutter_hooks, + _flutter_hooks_lint, _flutter_lints, _flutter_markdown, - _flutter_oss_licenses, _flutter_plugin_android_lifecycle, _flutter_secure_storage, _flutter_secure_storage_linux, @@ -73,6 +78,7 @@ const allDependencies = [ _flutter_secure_storage_web, _flutter_secure_storage_windows, _flutter_volume_controller, + _flutter_web_plugins, _flutter_zustand, _freezed, _freezed_annotation, @@ -83,6 +89,7 @@ const allDependencies = [ _google_fonts, _graphs, _gtk, + _hotreloader, _html, _http, _http_methods, @@ -144,6 +151,7 @@ const allDependencies = [ _pub_semver, _pubspec_parse, _pure_ftp, + _rxdart, _saf_util, _safe_local_storage, _screen_brightness, @@ -174,7 +182,6 @@ const allDependencies = [ _synchronized, _term_glyph, _test_api, - _timing, _typed_data, _universal_platform, _uri_parser, @@ -214,96 +221,107 @@ const allDependencies = [ /// Direct `dependencies`. const dependencies = [ - _android_x_storage, - _app_links, - _collection, + _flutter, _cupertino_icons, - _desktop_drop, - _device_info_plus, - _disks_desktop, - _drives_windows, - _dynamic_color, + _intl, _file_picker, - _flutter, _flutter_breadcrumb, _flutter_hooks, - _flutter_markdown, _flutter_secure_storage, - _flutter_volume_controller, _flutter_zustand, - _freezed_annotation, - _fvp, - _google_fonts, - _http, - _intl, - _json_annotation, _media_kit, - _media_kit_libs_video, _media_kit_video, - _media_stream, - _package_info_plus, + _media_kit_libs_video, _path, _path_provider, - _permission_handler, - _popover, + _package_info_plus, _provider, - _pure_ftp, - _saf_util, - _screen_brightness, - _scrollable_positioned_list, + _webdav_client, + _window_manager, _url_launcher, + _scrollable_positioned_list, + _google_fonts, + _dynamic_color, + _window_size, _uuid, + _flutter_markdown, + _http, + _collection, + _json_annotation, + _freezed_annotation, + _disks_desktop, + _android_x_storage, + _permission_handler, + _desktop_drop, + _app_links, + _device_info_plus, + _saf_util, + _screen_brightness, + _flutter_volume_controller, + _fvp, _video_player, _wakelock_plus, - _webdav_client, - _window_manager, - _window_size + _popover, + _pure_ftp, + _drives_windows, + _media_stream ]; /// Direct `dev_dependencies`. const devDependencies = [ - _build_runner, _flutter_lints, - _flutter_oss_licenses, + _dart_pubspec_licenses, + _build_runner, _freezed, _json_serializable, - _msix + _msix, + _flutter_hooks_lint, + _custom_lint ]; /// Package license definition. class Package { /// Package name final String name; + /// Description final String description; - /// Website URL - final String? homepage; - /// Repository URL - final String? repository; + /// Authors final List authors; - /// Version - final String version; - /// License - final String? license; + /// Whether the license is in markdown format or not (plain text). final bool isMarkdown; + /// Whether the package is included in the SDK or not. final bool isSdk; + /// Direct dependencies final List dependencies; + /// Website URL + final String? homepage; + + /// Repository URL + final String? repository; + + /// Version + final String? version; + + /// License + final String? license; + const Package({ required this.name, required this.description, - this.homepage, - this.repository, required this.authors, - required this.version, - this.license, required this.isMarkdown, required this.isSdk, required this.dependencies, + this.homepage, + this.repository, + this.version, + this.license, }); } @@ -315,13 +333,15 @@ class PackageRef { Package resolve() => allDependencies.firstWhere((d) => d.name == name); } -/// _fe_analyzer_shared 82.0.0 +/// _fe_analyzer_shared 88.0.0 const __fe_analyzer_shared = Package( name: '_fe_analyzer_shared', - description: 'Logic that is shared between the front_end and analyzer packages.', - repository: 'https://github.com/dart-lang/sdk/tree/main/pkg/_fe_analyzer_shared', + description: + 'Logic that is shared between the front_end and analyzer packages.', + repository: + 'https://github.com/dart-lang/sdk/tree/main/pkg/_fe_analyzer_shared', authors: [], - version: '82.0.0', + version: '88.0.0', license: '''Copyright 2019, the Dart project authors. Redistribution and use in source and binary forms, with or without @@ -351,16 +371,16 @@ THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.''', isMarkdown: false, isSdk: false, - dependencies: [PackageRef('meta')] - ); + dependencies: [PackageRef('meta')]); -/// analyzer 7.4.5 +/// analyzer 8.1.1 const _analyzer = Package( name: 'analyzer', - description: 'This package provides a library that performs static analysis of Dart code.', + description: + 'This package provides a library that performs static analysis of Dart code.', repository: 'https://github.com/dart-lang/sdk/tree/main/pkg/analyzer', authors: [], - version: '7.4.5', + version: '8.1.1', license: '''Copyright 2013, the Dart project authors. Redistribution and use in source and binary forms, with or without @@ -390,13 +410,73 @@ THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.''', isMarkdown: false, isSdk: false, - dependencies: [PackageRef('_fe_analyzer_shared'), PackageRef('collection'), PackageRef('convert'), PackageRef('crypto'), PackageRef('glob'), PackageRef('meta'), PackageRef('package_config'), PackageRef('path'), PackageRef('pub_semver'), PackageRef('source_span'), PackageRef('watcher'), PackageRef('yaml')] - ); + dependencies: [ + PackageRef('_fe_analyzer_shared'), + PackageRef('collection'), + PackageRef('convert'), + PackageRef('crypto'), + PackageRef('glob'), + PackageRef('meta'), + PackageRef('package_config'), + PackageRef('path'), + PackageRef('pub_semver'), + PackageRef('source_span'), + PackageRef('watcher'), + PackageRef('yaml') + ]); + +/// analyzer_plugin 0.13.7 +const _analyzer_plugin = Package( + name: 'analyzer_plugin', + description: + 'A framework and support code for building plugins for the analysis server.', + repository: + 'https://github.com/dart-lang/sdk/tree/main/pkg/analyzer_plugin', + authors: [], + version: '0.13.7', + license: '''Copyright 2017, the Dart project authors. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following + disclaimer in the documentation and/or other materials provided + with the distribution. + * Neither the name of Google LLC nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.''', + isMarkdown: false, + isSdk: false, + dependencies: [ + PackageRef('analyzer'), + PackageRef('collection'), + PackageRef('dart_style'), + PackageRef('pub_semver'), + PackageRef('yaml'), + PackageRef('path') + ]); /// android_x_storage 1.0.2 const _android_x_storage = Package( name: 'android_x_storage', - description: 'A new Flutter plugin for Android to get the external storage directories.', + description: + 'A new Flutter plugin for Android to get the external storage directories.', homepage: 'https://github.com/djeddi-yacine/android_x_storage', repository: 'https://github.com/djeddi-yacine/android_x_storage', authors: [], @@ -432,16 +512,19 @@ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.''', isMarkdown: false, isSdk: false, - dependencies: [PackageRef('flutter'), PackageRef('plugin_platform_interface')] - ); + dependencies: [ + PackageRef('flutter'), + PackageRef('plugin_platform_interface') + ]); -/// app_links 6.4.0 +/// app_links 6.4.1 const _app_links = Package( name: 'app_links', - description: 'Android App Links, Deep Links, iOs Universal Links and Custom URL schemes handler for Flutter (desktop included).', + description: + 'Android App Links, Deep Links, iOs Universal Links and Custom URL schemes handler for Flutter (desktop included).', homepage: 'https://github.com/llfbandit/app_links', authors: [], - version: '6.4.0', + version: '6.4.1', license: '''Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ @@ -645,14 +728,19 @@ const _app_links = Package( limitations under the License.''', isMarkdown: false, isSdk: false, - dependencies: [PackageRef('flutter'), PackageRef('app_links_linux'), PackageRef('app_links_platform_interface'), PackageRef('app_links_web')] - ); + dependencies: [ + PackageRef('flutter'), + PackageRef('app_links_linux'), + PackageRef('app_links_platform_interface'), + PackageRef('app_links_web') + ]); /// app_links_linux 1.0.3 const _app_links_linux = Package( name: 'app_links_linux', description: 'Linux platform implementation of app_links plugin.', - homepage: 'https://github.com/llfbandit/app_links/tree/master/app_links_linux', + homepage: + 'https://github.com/llfbandit/app_links/tree/master/app_links_linux', authors: [], version: '1.0.3', license: '''Apache License @@ -858,14 +946,18 @@ const _app_links_linux = Package( limitations under the License.''', isMarkdown: false, isSdk: false, - dependencies: [PackageRef('flutter'), PackageRef('app_links_platform_interface'), PackageRef('gtk')] - ); + dependencies: [ + PackageRef('flutter'), + PackageRef('app_links_platform_interface'), + PackageRef('gtk') + ]); /// app_links_platform_interface 2.0.2 const _app_links_platform_interface = Package( name: 'app_links_platform_interface', description: 'A common platform interface for the app_links plugin.', - homepage: 'https://github.com/llfbandit/app_links/tree/master/app_links_platform_interface', + homepage: + 'https://github.com/llfbandit/app_links/tree/master/app_links_platform_interface', authors: [], version: '2.0.2', license: '''Apache License @@ -1071,14 +1163,17 @@ const _app_links_platform_interface = Package( limitations under the License.''', isMarkdown: false, isSdk: false, - dependencies: [PackageRef('flutter'), PackageRef('plugin_platform_interface')] - ); + dependencies: [ + PackageRef('flutter'), + PackageRef('plugin_platform_interface') + ]); /// app_links_web 1.0.4 const _app_links_web = Package( name: 'app_links_web', description: 'Web platform implementation of app_links plugin.', - homepage: 'https://github.com/llfbandit/app_links/tree/master/app_links_web', + homepage: + 'https://github.com/llfbandit/app_links/tree/master/app_links_web', authors: [], version: '1.0.4', license: '''Apache License @@ -1284,13 +1379,18 @@ const _app_links_web = Package( limitations under the License.''', isMarkdown: false, isSdk: false, - dependencies: [PackageRef('flutter'), PackageRef('app_links_platform_interface'), PackageRef('web')] - ); + dependencies: [ + PackageRef('flutter'), + PackageRef('flutter_web_plugins'), + PackageRef('app_links_platform_interface'), + PackageRef('web') + ]); /// archive 4.0.7 const _archive = Package( name: 'archive', - description: 'Provides encoders and decoders for various archive and compression formats such as zip, tar, bzip2, gzip, and zlib.', + description: + 'Provides encoders and decoders for various archive and compression formats such as zip, tar, bzip2, gzip, and zlib.', repository: 'https://github.com/brendan-duncan/archive', authors: [], version: '4.0.7', @@ -1318,13 +1418,17 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.''', isMarkdown: false, isSdk: false, - dependencies: [PackageRef('crypto'), PackageRef('path'), PackageRef('posix')] - ); + dependencies: [ + PackageRef('crypto'), + PackageRef('path'), + PackageRef('posix') + ]); /// args 2.7.0 const _args = Package( name: 'args', - description: 'Library for defining parsers for parsing raw command-line arguments into a set of options and values using GNU and POSIX style options.', + description: + 'Library for defining parsers for parsing raw command-line arguments into a set of options and values using GNU and POSIX style options.', repository: 'https://github.com/dart-lang/core/tree/main/pkgs/args', authors: [], version: '2.7.0', @@ -1357,16 +1461,16 @@ THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.''', isMarkdown: false, isSdk: false, - dependencies: [] - ); + dependencies: []); -/// asn1lib 1.6.4 +/// asn1lib 1.6.5 const _asn1lib = Package( name: 'asn1lib', - description: 'An ASN1 parser library for Dart. Encodes / decodes from ASN1 Objects to BER bytes', + description: + 'An ASN1 parser library for Dart. Encodes / decodes from ASN1 Objects to BER bytes', homepage: 'https://github.com/wstrange/asn1lib', authors: [], - version: '1.6.4', + version: '1.6.5', license: '''http://opensource.org/licenses/BSD-3-Clause Copyright (c) 2015, Warren Strange All rights reserved. @@ -1393,13 +1497,13 @@ ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.''', isMarkdown: false, isSdk: false, - dependencies: [] - ); + dependencies: []); /// async 2.13.0 const _async = Package( name: 'async', - description: "Utility functions and classes related to the 'dart:async' library.", + description: + "Utility functions and classes related to the 'dart:async' library.", repository: 'https://github.com/dart-lang/core/tree/main/pkgs/async', authors: [], version: '2.13.0', @@ -1432,14 +1536,15 @@ THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.''', isMarkdown: false, isSdk: false, - dependencies: [PackageRef('collection'), PackageRef('meta')] - ); + dependencies: [PackageRef('collection'), PackageRef('meta')]); /// boolean_selector 2.1.2 const _boolean_selector = Package( name: 'boolean_selector', - description: "A flexible syntax for boolean expressions, based on a simplified version of Dart's expression syntax.", - repository: 'https://github.com/dart-lang/tools/tree/main/pkgs/boolean_selector', + description: + "A flexible syntax for boolean expressions, based on a simplified version of Dart's expression syntax.", + repository: + 'https://github.com/dart-lang/tools/tree/main/pkgs/boolean_selector', authors: [], version: '2.1.2', license: '''Copyright 2016, the Dart project authors. @@ -1471,16 +1576,16 @@ THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.''', isMarkdown: false, isSdk: false, - dependencies: [PackageRef('source_span'), PackageRef('string_scanner')] - ); + dependencies: [PackageRef('source_span'), PackageRef('string_scanner')]); -/// build 2.4.2 +/// build 4.0.0 const _build = Package( name: 'build', - description: 'A package for authoring build_runner compatible code generators.', + description: + 'A package for authoring build_runner compatible code generators.', repository: 'https://github.com/dart-lang/build/tree/master/build', authors: [], - version: '2.4.2', + version: '4.0.0', license: '''Copyright 2016, the Dart project authors. Redistribution and use in source and binary forms, with or without @@ -1510,16 +1615,23 @@ THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.''', isMarkdown: false, isSdk: false, - dependencies: [PackageRef('analyzer'), PackageRef('async'), PackageRef('convert'), PackageRef('crypto'), PackageRef('glob'), PackageRef('logging'), PackageRef('meta'), PackageRef('package_config'), PackageRef('path')] - ); + dependencies: [ + PackageRef('analyzer'), + PackageRef('crypto'), + PackageRef('glob'), + PackageRef('logging'), + PackageRef('package_config'), + PackageRef('path') + ]); -/// build_config 1.1.2 +/// build_config 1.2.0 const _build_config = Package( name: 'build_config', - description: 'Format definition and support for parsing `build.yaml` configuration.', + description: + 'Format definition and support for parsing `build.yaml` configuration.', repository: 'https://github.com/dart-lang/build/tree/master/build_config', authors: [], - version: '1.1.2', + version: '1.2.0', license: '''Copyright 2017, the Dart project authors. Redistribution and use in source and binary forms, with or without @@ -1549,8 +1661,12 @@ THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.''', isMarkdown: false, isSdk: false, - dependencies: [PackageRef('checked_yaml'), PackageRef('json_annotation'), PackageRef('path'), PackageRef('pubspec_parse'), PackageRef('yaml')] - ); + dependencies: [ + PackageRef('checked_yaml'), + PackageRef('json_annotation'), + PackageRef('path'), + PackageRef('pubspec_parse') + ]); /// build_daemon 4.0.4 const _build_daemon = Package( @@ -1588,55 +1704,29 @@ THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.''', isMarkdown: false, isSdk: false, - dependencies: [PackageRef('built_collection'), PackageRef('built_value'), PackageRef('crypto'), PackageRef('http_multi_server'), PackageRef('logging'), PackageRef('path'), PackageRef('pool'), PackageRef('shelf'), PackageRef('shelf_web_socket'), PackageRef('stream_transform'), PackageRef('watcher'), PackageRef('web_socket_channel')] - ); - -/// build_resolvers 2.4.4 -const _build_resolvers = Package( - name: 'build_resolvers', - description: 'Resolve Dart code in a Builder', - repository: 'https://github.com/dart-lang/build/tree/master/build_resolvers', - authors: [], - version: '2.4.4', - license: '''Copyright 2018, the Dart project authors. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are -met: - - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above - copyright notice, this list of conditions and the following - disclaimer in the documentation and/or other materials provided - with the distribution. - * Neither the name of Google LLC nor the names of its - contributors may be used to endorse or promote products derived - from this software without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.''', - isMarkdown: false, - isSdk: false, - dependencies: [PackageRef('analyzer'), PackageRef('async'), PackageRef('build'), PackageRef('collection'), PackageRef('convert'), PackageRef('crypto'), PackageRef('graphs'), PackageRef('logging'), PackageRef('package_config'), PackageRef('path'), PackageRef('pool'), PackageRef('pub_semver'), PackageRef('stream_transform'), PackageRef('yaml')] - ); - -/// build_runner 2.4.15 + dependencies: [ + PackageRef('built_collection'), + PackageRef('built_value'), + PackageRef('crypto'), + PackageRef('http_multi_server'), + PackageRef('logging'), + PackageRef('path'), + PackageRef('pool'), + PackageRef('shelf'), + PackageRef('shelf_web_socket'), + PackageRef('stream_transform'), + PackageRef('watcher'), + PackageRef('web_socket_channel') + ]); + +/// build_runner 2.8.0 const _build_runner = Package( name: 'build_runner', - description: 'A build system for Dart code generation and modular compilation.', + description: + 'A build system for Dart code generation and modular compilation.', repository: 'https://github.com/dart-lang/build/tree/master/build_runner', authors: [], - version: '2.4.15', + version: '2.8.0', license: '''Copyright 2016, the Dart project authors. Redistribution and use in source and binary forms, with or without @@ -1666,52 +1756,47 @@ THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.''', isMarkdown: false, isSdk: false, - dependencies: [PackageRef('analyzer'), PackageRef('args'), PackageRef('async'), PackageRef('build'), PackageRef('build_config'), PackageRef('build_daemon'), PackageRef('build_resolvers'), PackageRef('build_runner_core'), PackageRef('code_builder'), PackageRef('collection'), PackageRef('crypto'), PackageRef('dart_style'), PackageRef('frontend_server_client'), PackageRef('glob'), PackageRef('graphs'), PackageRef('http'), PackageRef('http_multi_server'), PackageRef('io'), PackageRef('js'), PackageRef('logging'), PackageRef('meta'), PackageRef('mime'), PackageRef('package_config'), PackageRef('path'), PackageRef('pool'), PackageRef('pub_semver'), PackageRef('pubspec_parse'), PackageRef('shelf'), PackageRef('shelf_web_socket'), PackageRef('stack_trace'), PackageRef('stream_transform'), PackageRef('timing'), PackageRef('watcher'), PackageRef('web'), PackageRef('web_socket_channel'), PackageRef('yaml')] - ); - -/// build_runner_core 8.0.0 -const _build_runner_core = Package( - name: 'build_runner_core', - description: 'Core tools to organize the structure of a build and run Builders.', - repository: 'https://github.com/dart-lang/build/tree/master/build_runner_core', - authors: [], - version: '8.0.0', - license: '''Copyright 2018, the Dart project authors. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are -met: - - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above - copyright notice, this list of conditions and the following - disclaimer in the documentation and/or other materials provided - with the distribution. - * Neither the name of Google LLC nor the names of its - contributors may be used to endorse or promote products derived - from this software without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.''', - isMarkdown: false, - isSdk: false, - dependencies: [PackageRef('async'), PackageRef('build'), PackageRef('build_config'), PackageRef('build_resolvers'), PackageRef('collection'), PackageRef('convert'), PackageRef('crypto'), PackageRef('glob'), PackageRef('graphs'), PackageRef('json_annotation'), PackageRef('logging'), PackageRef('meta'), PackageRef('package_config'), PackageRef('path'), PackageRef('pool'), PackageRef('timing'), PackageRef('watcher'), PackageRef('yaml')] - ); + dependencies: [ + PackageRef('analyzer'), + PackageRef('args'), + PackageRef('async'), + PackageRef('build'), + PackageRef('build_config'), + PackageRef('build_daemon'), + PackageRef('built_collection'), + PackageRef('built_value'), + PackageRef('code_builder'), + PackageRef('collection'), + PackageRef('convert'), + PackageRef('crypto'), + PackageRef('dart_style'), + PackageRef('frontend_server_client'), + PackageRef('glob'), + PackageRef('graphs'), + PackageRef('http_multi_server'), + PackageRef('io'), + PackageRef('json_annotation'), + PackageRef('logging'), + PackageRef('meta'), + PackageRef('mime'), + PackageRef('package_config'), + PackageRef('path'), + PackageRef('pool'), + PackageRef('pub_semver'), + PackageRef('shelf'), + PackageRef('shelf_web_socket'), + PackageRef('stack_trace'), + PackageRef('stream_transform'), + PackageRef('watcher'), + PackageRef('web_socket_channel'), + PackageRef('yaml') + ]); /// built_collection 5.1.1 const _built_collection = Package( name: 'built_collection', - description: '''Immutable collections based on the SDK collections. Each SDK collection class is split into a new immutable collection class and a corresponding mutable builder class. + description: + '''Immutable collections based on the SDK collections. Each SDK collection class is split into a new immutable collection class and a corresponding mutable builder class. ''', homepage: 'https://github.com/google/built_collection.dart', authors: [], @@ -1746,17 +1831,18 @@ THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.''', isMarkdown: false, isSdk: false, - dependencies: [] - ); + dependencies: []); -/// built_value 8.10.1 +/// built_value 8.12.0 const _built_value = Package( name: 'built_value', - description: '''Value types with builders, Dart classes as enums, and serialization. This library is the runtime dependency. + description: + '''Value types with builders, Dart classes as enums, and serialization. This library is the runtime dependency. ''', - repository: 'https://github.com/google/built_value.dart/tree/master/built_value', + repository: + 'https://github.com/google/built_value.dart/tree/master/built_value', authors: [], - version: '8.10.1', + version: '8.12.0', license: '''Copyright 2015, Google Inc. All rights reserved. Redistribution and use in source and binary forms, with or without @@ -1787,13 +1873,18 @@ THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.''', isMarkdown: false, isSdk: false, - dependencies: [PackageRef('built_collection'), PackageRef('collection'), PackageRef('fixnum'), PackageRef('meta')] - ); + dependencies: [ + PackageRef('built_collection'), + PackageRef('collection'), + PackageRef('fixnum'), + PackageRef('meta') + ]); /// characters 1.4.0 const _characters = Package( name: 'characters', - description: 'String replacement with operations that are Unicode/grapheme cluster aware.', + description: + 'String replacement with operations that are Unicode/grapheme cluster aware.', repository: 'https://github.com/dart-lang/core/tree/main/pkgs/characters', authors: [], version: '1.4.0', @@ -1826,13 +1917,13 @@ THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.''', isMarkdown: false, isSdk: false, - dependencies: [] - ); + dependencies: []); /// charset 2.0.1 const _charset = Package( name: 'charset', - description: 'Charset encoding and decoding Library, include iso-(2-15), windows series, gbk, euc-jp, euc-kr, shift-jis. And supportted charset detect, canEncode, canDecode.', + description: + 'Charset encoding and decoding Library, include iso-(2-15), windows series, gbk, euc-jp, euc-kr, shift-jis. And supportted charset detect, canEncode, canDecode.', repository: 'https://github.com/shirne/charset-dart', authors: [], version: '2.0.1', @@ -2039,14 +2130,15 @@ const _charset = Package( limitations under the License.''', isMarkdown: false, isSdk: false, - dependencies: [] - ); + dependencies: []); /// checked_yaml 2.0.4 const _checked_yaml = Package( name: 'checked_yaml', - description: 'Generate more helpful exceptions when decoding YAML documents using package:json_serializable and package:yaml.', - repository: 'https://github.com/google/json_serializable.dart/tree/master/checked_yaml', + description: + 'Generate more helpful exceptions when decoding YAML documents using package:json_serializable and package:yaml.', + repository: + 'https://github.com/google/json_serializable.dart/tree/master/checked_yaml', authors: [], version: '2.0.4', license: '''Copyright 2019, the Dart project authors. All rights reserved. @@ -2077,55 +2169,21 @@ THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.''', isMarkdown: false, isSdk: false, - dependencies: [PackageRef('json_annotation'), PackageRef('source_span'), PackageRef('yaml')] - ); - -/// cli_util 0.4.2 -const _cli_util = Package( - name: 'cli_util', - description: 'A library to help in building Dart command-line apps.', - repository: 'https://github.com/dart-lang/tools/tree/main/pkgs/cli_util', - authors: [], - version: '0.4.2', - license: '''Copyright 2015, the Dart project authors. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are -met: - - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above - copyright notice, this list of conditions and the following - disclaimer in the documentation and/or other materials provided - with the distribution. - * Neither the name of Google LLC nor the names of its - contributors may be used to endorse or promote products derived - from this software without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.''', - isMarkdown: false, - isSdk: false, - dependencies: [PackageRef('meta'), PackageRef('path')] - ); + dependencies: [ + PackageRef('json_annotation'), + PackageRef('source_span'), + PackageRef('yaml') + ]); -/// clock 1.1.2 -const _clock = Package( - name: 'clock', - description: 'A fakeable wrapper for dart:core clock APIs.', - repository: 'https://github.com/dart-lang/tools/tree/main/pkgs/clock', +/// ci 0.1.0 +const _ci = Package( + name: 'ci', + description: + "Detect whether you're running in a CI environment and information about the CI vendor.", + repository: + 'https://github.com/invertase/dart-cli-utilities/tree/main/packages/ci', authors: [], - version: '1.1.2', + version: '0.1.0', license: '''Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ @@ -2314,7 +2372,7 @@ const _clock = Package( same "printed page" as the copyright notice for easier identification within third-party archives. - Copyright [yyyy] [name of copyright owner] + Copyright 2021 Invertase Limited Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -2329,17 +2387,16 @@ const _clock = Package( limitations under the License.''', isMarkdown: false, isSdk: false, - dependencies: [] - ); + dependencies: []); -/// code_builder 4.10.1 -const _code_builder = Package( - name: 'code_builder', - description: 'A fluent, builder-based library for generating valid Dart code.', - repository: 'https://github.com/dart-lang/tools/tree/main/pkgs/code_builder', +/// cli_util 0.4.2 +const _cli_util = Package( + name: 'cli_util', + description: 'A library to help in building Dart command-line apps.', + repository: 'https://github.com/dart-lang/tools/tree/main/pkgs/cli_util', authors: [], - version: '4.10.1', - license: '''Copyright 2016, the Dart project authors. + version: '0.4.2', + license: '''Copyright 2015, the Dart project authors. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are @@ -2368,19 +2425,277 @@ THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.''', isMarkdown: false, isSdk: false, - dependencies: [PackageRef('built_collection'), PackageRef('built_value'), PackageRef('collection'), PackageRef('matcher'), PackageRef('meta')] - ); + dependencies: [PackageRef('meta'), PackageRef('path')]); -/// collection 1.19.1 -const _collection = Package( - name: 'collection', - description: 'Collections and utilities functions and classes related to collections.', - repository: 'https://github.com/dart-lang/core/tree/main/pkgs/collection', +/// clock 1.1.2 +const _clock = Package( + name: 'clock', + description: 'A fakeable wrapper for dart:core clock APIs.', + repository: 'https://github.com/dart-lang/tools/tree/main/pkgs/clock', authors: [], - version: '1.19.1', - license: '''Copyright 2015, the Dart project authors. + version: '1.1.2', + license: '''Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ -Redistribution and use in source and binary forms, with or without + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License.''', + isMarkdown: false, + isSdk: false, + dependencies: []); + +/// code_builder 4.11.0 +const _code_builder = Package( + name: 'code_builder', + description: + 'A fluent, builder-based library for generating valid Dart code.', + repository: + 'https://github.com/dart-lang/tools/tree/main/pkgs/code_builder', + authors: [], + version: '4.11.0', + license: '''Copyright 2016, the Dart project authors. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following + disclaimer in the documentation and/or other materials provided + with the distribution. + * Neither the name of Google LLC nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.''', + isMarkdown: false, + isSdk: false, + dependencies: [ + PackageRef('built_collection'), + PackageRef('built_value'), + PackageRef('collection'), + PackageRef('matcher'), + PackageRef('meta') + ]); + +/// collection 1.19.1 +const _collection = Package( + name: 'collection', + description: + 'Collections and utilities functions and classes related to collections.', + repository: 'https://github.com/dart-lang/core/tree/main/pkgs/collection', + authors: [], + version: '1.19.1', + license: '''Copyright 2015, the Dart project authors. + +Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: @@ -2407,13 +2722,13 @@ THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.''', isMarkdown: false, isSdk: false, - dependencies: [] - ); + dependencies: []); /// console 4.1.0 const _console = Package( name: 'console', - description: 'A library for common features required by console applications, including color formatting, keyboard input, and progress bars.', + description: + 'A library for common features required by console applications, including color formatting, keyboard input, and progress bars.', homepage: 'https://github.com/DirectMyFile/console.dart', authors: ['Kenneth Endfinger '], version: '4.1.0', @@ -2442,13 +2757,13 @@ SOFTWARE. ```''', isMarkdown: true, isSdk: false, - dependencies: [PackageRef('vector_math')] - ); + dependencies: [PackageRef('vector_math')]); /// convert 3.1.2 const _convert = Package( name: 'convert', - description: 'Utilities for converting between data representations. Provides a number of Sink, Codec, Decoder, and Encoder types.', + description: + 'Utilities for converting between data representations. Provides a number of Sink, Codec, Decoder, and Encoder types.', repository: 'https://github.com/dart-lang/core/tree/main/pkgs/convert', authors: [], version: '3.1.2', @@ -2468,105 +2783,1056 @@ met: contributors may be used to endorse or promote products derived from this software without specific prior written permission. -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.''', - isMarkdown: false, - isSdk: false, - dependencies: [PackageRef('typed_data')] - ); +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.''', + isMarkdown: false, + isSdk: false, + dependencies: [PackageRef('typed_data')]); + +/// cross_file 0.3.4+2 +const _cross_file = Package( + name: 'cross_file', + description: + 'An abstraction to allow working with files across multiple platforms.', + repository: + 'https://github.com/flutter/packages/tree/main/packages/cross_file', + authors: [], + version: '0.3.4+2', + license: '''Copyright 2013 The Flutter Authors. All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following + disclaimer in the documentation and/or other materials provided + with the distribution. + * Neither the name of Google Inc. nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON +ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.''', + isMarkdown: false, + isSdk: false, + dependencies: [PackageRef('meta'), PackageRef('web')]); + +/// crypto 3.0.6 +const _crypto = Package( + name: 'crypto', + description: + 'Implementations of SHA, MD5, and HMAC cryptographic functions.', + repository: 'https://github.com/dart-lang/core/tree/main/pkgs/crypto', + authors: [], + version: '3.0.6', + license: '''Copyright 2015, the Dart project authors. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following + disclaimer in the documentation and/or other materials provided + with the distribution. + * Neither the name of Google LLC nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.''', + isMarkdown: false, + isSdk: false, + dependencies: [PackageRef('typed_data')]); + +/// cryptography 2.7.0 +const _cryptography = Package( + name: 'cryptography', + description: + 'Cryptographic algorithms for encryption, digital signatures, key agreement, authentication, and hashing. AES, Chacha20, ED25519, X25519, Argon2, and more. Good cross-platform support.', + homepage: 'https://github.com/dint-dev/cryptography', + authors: [], + version: '2.7.0', + license: '''Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS''', + isMarkdown: false, + isSdk: false, + dependencies: [ + PackageRef('collection'), + PackageRef('crypto'), + PackageRef('ffi'), + PackageRef('js'), + PackageRef('meta'), + PackageRef('typed_data') + ]); + +/// csslib 1.0.2 +const _csslib = Package( + name: 'csslib', + description: + 'A library for parsing and analyzing CSS (Cascading Style Sheets).', + repository: 'https://github.com/dart-lang/tools/tree/main/pkgs/csslib', + authors: [], + version: '1.0.2', + license: '''Copyright 2013, the Dart project authors. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following + disclaimer in the documentation and/or other materials provided + with the distribution. + * Neither the name of Google LLC nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.''', + isMarkdown: false, + isSdk: false, + dependencies: [PackageRef('source_span')]); + +/// cupertino_icons 1.0.8 +const _cupertino_icons = Package( + name: 'cupertino_icons', + description: + 'Default icons asset for Cupertino widgets based on Apple styled icons', + repository: + 'https://github.com/flutter/packages/tree/main/third_party/packages/cupertino_icons', + authors: [], + version: '1.0.8', + license: '''The MIT License (MIT) + +Copyright (c) 2016 Vladimir Kharlampidi + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.''', + isMarkdown: false, + isSdk: false, + dependencies: []); + +/// custom_lint 0.8.1 +const _custom_lint = Package( + name: 'custom_lint', + description: + 'Lint rules are a powerful way to improve the maintainability of a project. Custom Lint allows package authors and developers to easily write custom lint rules.', + repository: 'https://github.com/invertase/dart_custom_lint', + authors: [], + version: '0.8.1', + license: '''Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright 2020 Invertase Limited + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License.''', + isMarkdown: false, + isSdk: false, + dependencies: [ + PackageRef('analyzer'), + PackageRef('analyzer_plugin'), + PackageRef('args'), + PackageRef('async'), + PackageRef('ci'), + PackageRef('cli_util'), + PackageRef('collection'), + PackageRef('custom_lint_core'), + PackageRef('freezed_annotation'), + PackageRef('json_annotation'), + PackageRef('meta'), + PackageRef('package_config'), + PackageRef('path'), + PackageRef('pub_semver'), + PackageRef('pubspec_parse'), + PackageRef('rxdart'), + PackageRef('uuid'), + PackageRef('yaml') + ]); + +/// custom_lint_builder 0.8.1 +const _custom_lint_builder = Package( + name: 'custom_lint_builder', + description: 'A package to help writing custom linters', + repository: 'https://github.com/invertase/dart_custom_lint', + authors: [], + version: '0.8.1', + license: '''Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright 2020 Invertase Limited + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License.''', + isMarkdown: false, + isSdk: false, + dependencies: [ + PackageRef('analyzer'), + PackageRef('analyzer_plugin'), + PackageRef('collection'), + PackageRef('custom_lint'), + PackageRef('custom_lint_core'), + PackageRef('custom_lint_visitor'), + PackageRef('glob'), + PackageRef('hotreloader'), + PackageRef('meta'), + PackageRef('package_config'), + PackageRef('path'), + PackageRef('pubspec_parse'), + PackageRef('rxdart') + ]); + +/// custom_lint_core 0.8.1 +const _custom_lint_core = Package( + name: 'custom_lint_core', + description: 'A package to help writing custom linters', + repository: 'https://github.com/invertase/dart_custom_lint', + authors: [], + version: '0.8.1', + license: '''Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. -/// cross_file 0.3.4+2 -const _cross_file = Package( - name: 'cross_file', - description: 'An abstraction to allow working with files across multiple platforms.', - repository: 'https://github.com/flutter/packages/tree/main/packages/cross_file', - authors: [], - version: '0.3.4+2', - license: '''Copyright 2013 The Flutter Authors. All rights reserved. + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. -Redistribution and use in source and binary forms, with or without modification, -are permitted provided that the following conditions are met: + END OF TERMS AND CONDITIONS - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above - copyright notice, this list of conditions and the following - disclaimer in the documentation and/or other materials provided - with the distribution. - * Neither the name of Google Inc. nor the names of its - contributors may be used to endorse or promote products derived - from this software without specific prior written permission. + APPENDIX: How to apply the Apache License to your work. -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND -ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR -ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES -(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; -LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON -ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS -SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.''', - isMarkdown: false, - isSdk: false, - dependencies: [PackageRef('meta'), PackageRef('web')] - ); + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. -/// crypto 3.0.6 -const _crypto = Package( - name: 'crypto', - description: 'Implementations of SHA, MD5, and HMAC cryptographic functions.', - repository: 'https://github.com/dart-lang/core/tree/main/pkgs/crypto', - authors: [], - version: '3.0.6', - license: '''Copyright 2015, the Dart project authors. + Copyright 2020 Invertase Limited -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are -met: + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above - copyright notice, this list of conditions and the following - disclaimer in the documentation and/or other materials provided - with the distribution. - * Neither the name of Google LLC nor the names of its - contributors may be used to endorse or promote products derived - from this software without specific prior written permission. + http://www.apache.org/licenses/LICENSE-2.0 -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.''', + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License.''', isMarkdown: false, isSdk: false, - dependencies: [PackageRef('typed_data')] - ); - -/// cryptography 2.7.0 -const _cryptography = Package( - name: 'cryptography', - description: 'Cryptographic algorithms for encryption, digital signatures, key agreement, authentication, and hashing. AES, Chacha20, ED25519, X25519, Argon2, and more. Good cross-platform support.', - homepage: 'https://github.com/dint-dev/cryptography', - authors: [], - version: '2.7.0', + dependencies: [ + PackageRef('analyzer'), + PackageRef('analyzer_plugin'), + PackageRef('collection'), + PackageRef('custom_lint_visitor'), + PackageRef('glob'), + PackageRef('matcher'), + PackageRef('meta'), + PackageRef('package_config'), + PackageRef('path'), + PackageRef('pubspec_parse'), + PackageRef('source_span'), + PackageRef('uuid'), + PackageRef('yaml') + ]); + +/// custom_lint_visitor 1.0.0+8.1.1 +const _custom_lint_visitor = Package( + name: 'custom_lint_visitor', + description: 'A package that exports visitors for CustomLint.', + repository: 'https://github.com/invertase/dart_custom_lint', + authors: [], + version: '1.0.0+8.1.1', license: '''Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ @@ -2742,91 +4008,46 @@ const _cryptography = Package( incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. - END OF TERMS AND CONDITIONS''', - isMarkdown: false, - isSdk: false, - dependencies: [PackageRef('collection'), PackageRef('crypto'), PackageRef('ffi'), PackageRef('js'), PackageRef('meta'), PackageRef('typed_data')] - ); - -/// csslib 1.0.2 -const _csslib = Package( - name: 'csslib', - description: 'A library for parsing and analyzing CSS (Cascading Style Sheets).', - repository: 'https://github.com/dart-lang/tools/tree/main/pkgs/csslib', - authors: [], - version: '1.0.2', - license: '''Copyright 2013, the Dart project authors. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are -met: - - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above - copyright notice, this list of conditions and the following - disclaimer in the documentation and/or other materials provided - with the distribution. - * Neither the name of Google LLC nor the names of its - contributors may be used to endorse or promote products derived - from this software without specific prior written permission. + END OF TERMS AND CONDITIONS -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.''', - isMarkdown: false, - isSdk: false, - dependencies: [PackageRef('source_span')] - ); + APPENDIX: How to apply the Apache License to your work. -/// cupertino_icons 1.0.8 -const _cupertino_icons = Package( - name: 'cupertino_icons', - description: 'Default icons asset for Cupertino widgets based on Apple styled icons', - repository: 'https://github.com/flutter/packages/tree/main/third_party/packages/cupertino_icons', - authors: [], - version: '1.0.8', - license: '''The MIT License (MIT) + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. -Copyright (c) 2016 Vladimir Kharlampidi + Copyright 2020 Invertase Limited -Permission is hereby granted, free of charge, to any person obtaining a copy of -this software and associated documentation files (the "Software"), to deal in -the Software without restriction, including without limitation the rights to -use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of -the Software, and to permit persons to whom the Software is furnished to do so, -subject to the following conditions: + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. + http://www.apache.org/licenses/LICENSE-2.0 -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS -FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR -COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER -IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN -CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.''', + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License.''', isMarkdown: false, isSdk: false, - dependencies: [] - ); + dependencies: [PackageRef('analyzer')]); -/// dart_pubspec_licenses 3.0.4 +/// dart_pubspec_licenses 3.0.8 const _dart_pubspec_licenses = Package( name: 'dart_pubspec_licenses', - description: 'A library to make it easy to extract OSS license information from Dart packages using pubspec.yaml', - homepage: 'https://github.com/espresso3389/flutter_oss_licenses/tree/master/packages/dart_pubspec_licenses', + description: + 'A library to make it easy to extract OSS license information from Dart packages using pubspec.yaml', + homepage: + 'https://github.com/espresso3389/flutter_oss_licenses/tree/master/packages/dart_pubspec_licenses', repository: 'https://github.com/espresso3389/flutter_oss_licenses', authors: [], - version: '3.0.4', + version: '3.0.8', license: '''MIT License Copyright (c) 2019 Takashi Kawasaki @@ -2850,16 +4071,21 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.''', isMarkdown: false, isSdk: false, - dependencies: [PackageRef('yaml'), PackageRef('path'), PackageRef('json_annotation')] - ); + dependencies: [ + PackageRef('yaml'), + PackageRef('path'), + PackageRef('json_annotation'), + PackageRef('args') + ]); -/// dart_style 3.1.0 +/// dart_style 3.1.2 const _dart_style = Package( name: 'dart_style', - description: 'Opinionated, automatic Dart source code formatter. Provides an API and a CLI tool.', + description: + 'Opinionated, automatic Dart source code formatter. Provides an API and a CLI tool.', repository: 'https://github.com/dart-lang/dart_style', authors: [], - version: '3.1.0', + version: '3.1.2', license: '''Copyright 2014, the Dart project authors. Redistribution and use in source and binary forms, with or without @@ -2889,13 +4115,22 @@ THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.''', isMarkdown: false, isSdk: false, - dependencies: [PackageRef('analyzer'), PackageRef('args'), PackageRef('collection'), PackageRef('package_config'), PackageRef('path'), PackageRef('pub_semver'), PackageRef('source_span'), PackageRef('yaml')] - ); + dependencies: [ + PackageRef('analyzer'), + PackageRef('args'), + PackageRef('collection'), + PackageRef('package_config'), + PackageRef('path'), + PackageRef('pub_semver'), + PackageRef('source_span'), + PackageRef('yaml') + ]); /// dbus 0.7.11 const _dbus = Package( name: 'dbus', - description: 'A native Dart implementation of the D-Bus message bus client. This package allows Dart applications to directly access services on the Linux desktop.', + description: + 'A native Dart implementation of the D-Bus message bus client. This package allows Dart applications to directly access services on the Linux desktop.', homepage: 'https://github.com/canonical/dbus.dart', authors: [], version: '0.7.11', @@ -3274,16 +4509,22 @@ Exhibit B - "Incompatible With Secondary Licenses" Notice defined by the Mozilla Public License, v. 2.0.''', isMarkdown: false, isSdk: false, - dependencies: [PackageRef('args'), PackageRef('ffi'), PackageRef('meta'), PackageRef('xml')] - ); + dependencies: [ + PackageRef('args'), + PackageRef('ffi'), + PackageRef('meta'), + PackageRef('xml') + ]); -/// desktop_drop 0.6.0 +/// desktop_drop 0.6.1 const _desktop_drop = Package( name: 'desktop_drop', - description: 'A plugin which allows user dragging files to your flutter desktop applications.', - homepage: 'https://github.com/MixinNetwork/flutter-plugins/tree/main/packages/desktop_drop', + description: + 'A plugin which allows user dragging files to your flutter desktop applications.', + homepage: + 'https://github.com/MixinNetwork/flutter-plugins/tree/main/packages/desktop_drop', authors: [], - version: '0.6.0', + version: '0.6.1', license: '''Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ @@ -3487,17 +4728,24 @@ const _desktop_drop = Package( limitations under the License.''', isMarkdown: false, isSdk: false, - dependencies: [PackageRef('flutter'), PackageRef('cross_file'), PackageRef('web'), PackageRef('universal_platform')] - ); + dependencies: [ + PackageRef('flutter'), + PackageRef('flutter_web_plugins'), + PackageRef('cross_file'), + PackageRef('web'), + PackageRef('universal_platform') + ]); -/// device_info_plus 11.4.0 +/// device_info_plus 12.1.0 const _device_info_plus = Package( name: 'device_info_plus', - description: 'Flutter plugin providing detailed information about the device (make, model, etc.), and Android or iOS version the app is running on.', + description: + 'Flutter plugin providing detailed information about the device (make, model, etc.), and Android or iOS version the app is running on.', homepage: 'https://github.com/fluttercommunity/plus_plugins', - repository: 'https://github.com/fluttercommunity/plus_plugins/tree/main/packages/device_info_plus/device_info_plus', + repository: + 'https://github.com/fluttercommunity/plus_plugins/tree/main/packages/device_info_plus/device_info_plus', authors: [], - version: '11.4.0', + version: '12.1.0', license: '''Copyright 2017 The Chromium Authors. All rights reserved. Redistribution and use in source and binary forms, with or without @@ -3527,17 +4775,27 @@ THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.''', isMarkdown: false, isSdk: false, - dependencies: [PackageRef('device_info_plus_platform_interface'), PackageRef('ffi'), PackageRef('file'), PackageRef('flutter'), PackageRef('meta'), PackageRef('web'), PackageRef('win32'), PackageRef('win32_registry')] - ); + dependencies: [ + PackageRef('device_info_plus_platform_interface'), + PackageRef('ffi'), + PackageRef('file'), + PackageRef('flutter'), + PackageRef('flutter_web_plugins'), + PackageRef('meta'), + PackageRef('web'), + PackageRef('win32'), + PackageRef('win32_registry') + ]); -/// device_info_plus_platform_interface 7.0.2 +/// device_info_plus_platform_interface 7.0.3 const _device_info_plus_platform_interface = Package( name: 'device_info_plus_platform_interface', description: 'A common platform interface for the device_info_plus plugin.', homepage: 'https://github.com/fluttercommunity/plus_plugins', - repository: 'https://github.com/fluttercommunity/plus_plugins/tree/main/packages/', + repository: + 'https://github.com/fluttercommunity/plus_plugins/tree/main/packages/', authors: [], - version: '7.0.2', + version: '7.0.3', license: '''Copyright 2017 The Chromium Authors. All rights reserved. Redistribution and use in source and binary forms, with or without @@ -3567,10 +4825,13 @@ THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.''', isMarkdown: false, isSdk: false, - dependencies: [PackageRef('flutter'), PackageRef('meta'), PackageRef('plugin_platform_interface')] - ); + dependencies: [ + PackageRef('flutter'), + PackageRef('meta'), + PackageRef('plugin_platform_interface') + ]); -/// dio 5.8.0+1 +/// dio 5.9.0 const _dio = Package( name: 'dio', description: '''A powerful HTTP networking package, @@ -3581,7 +4842,7 @@ Custom adapters, Transformers, etc. homepage: 'https://github.com/cfug/dio', repository: 'https://github.com/cfug/dio/blob/main/dio', authors: [], - version: '5.8.0+1', + version: '5.9.0', license: '''MIT License Copyright (c) 2018 Wen Du (wendux) @@ -3606,8 +4867,15 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.''', isMarkdown: false, isSdk: false, - dependencies: [PackageRef('async'), PackageRef('collection'), PackageRef('http_parser'), PackageRef('meta'), PackageRef('path'), PackageRef('dio_web_adapter')] - ); + dependencies: [ + PackageRef('async'), + PackageRef('collection'), + PackageRef('http_parser'), + PackageRef('meta'), + PackageRef('mime'), + PackageRef('path'), + PackageRef('dio_web_adapter') + ]); /// dio_web_adapter 2.1.1 const _dio_web_adapter = Package( @@ -3641,13 +4909,18 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.''', isMarkdown: false, isSdk: false, - dependencies: [PackageRef('dio'), PackageRef('http_parser'), PackageRef('meta'), PackageRef('web')] - ); + dependencies: [ + PackageRef('dio'), + PackageRef('http_parser'), + PackageRef('meta'), + PackageRef('web') + ]); /// disks_desktop 1.0.1 const _disks_desktop = Package( name: 'disks_desktop', - description: 'A Flutter desktop library able to retrieve the installed devices information', + description: + 'A Flutter desktop library able to retrieve the installed devices information', homepage: 'https://www.angelocassano.it', repository: 'https://github.com/AngeloAvv/disks', authors: [], @@ -3676,8 +4949,11 @@ FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.''', isMarkdown: false, isSdk: false, - dependencies: [PackageRef('flutter'), PackageRef('equatable'), PackageRef('path')] - ); + dependencies: [ + PackageRef('flutter'), + PackageRef('equatable'), + PackageRef('path') + ]); /// drives_windows 0.0.1 const _drives_windows = Package( @@ -3710,16 +4986,22 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.''', isMarkdown: false, isSdk: false, - dependencies: [PackageRef('ffi'), PackageRef('flutter'), PackageRef('path'), PackageRef('win32')] - ); + dependencies: [ + PackageRef('ffi'), + PackageRef('flutter'), + PackageRef('path'), + PackageRef('win32') + ]); -/// dynamic_color 1.7.0 +/// dynamic_color 1.8.1 const _dynamic_color = Package( name: 'dynamic_color', - description: "A Flutter package to create Material color schemes based on a platform's implementation of dynamic color.", - repository: 'https://github.com/material-foundation/flutter-packages/tree/main/packages/dynamic_color', + description: + "A Flutter package to create Material color schemes based on a platform's implementation of dynamic color.", + repository: + 'https://github.com/material-foundation/flutter-packages/tree/main/packages/dynamic_color', authors: [], - version: '1.7.0', + version: '1.8.1', license: '''Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ @@ -3923,13 +5205,16 @@ const _dynamic_color = Package( limitations under the License.''', isMarkdown: false, isSdk: false, - dependencies: [PackageRef('flutter'), PackageRef('material_color_utilities')] - ); + dependencies: [ + PackageRef('flutter'), + PackageRef('material_color_utilities') + ]); /// equatable 2.0.7 const _equatable = Package( name: 'equatable', - description: 'A Dart package that helps to implement value based equality without needing to explicitly override == and hashCode.', + description: + 'A Dart package that helps to implement value based equality without needing to explicitly override == and hashCode.', homepage: 'https://github.com/felangel/equatable', repository: 'https://github.com/felangel/equatable', authors: [], @@ -3957,13 +5242,13 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.''', isMarkdown: false, isSdk: false, - dependencies: [PackageRef('collection'), PackageRef('meta')] - ); + dependencies: [PackageRef('collection'), PackageRef('meta')]); /// fake_async 1.3.3 const _fake_async = Package( name: 'fake_async', - description: 'Fake asynchronous events such as timers and microtasks for deterministic testing.', + description: + 'Fake asynchronous events such as timers and microtasks for deterministic testing.', repository: 'https://github.com/dart-lang/test/tree/master/pkgs/fake_async', authors: [], version: '1.3.3', @@ -4170,13 +5455,13 @@ const _fake_async = Package( limitations under the License.''', isMarkdown: false, isSdk: false, - dependencies: [] - ); + dependencies: [PackageRef('clock'), PackageRef('collection')]); /// ffi 2.1.4 const _ffi = Package( name: 'ffi', - description: 'Utilities for working with Foreign Function Interface (FFI) code.', + description: + 'Utilities for working with Foreign Function Interface (FFI) code.', repository: 'https://github.com/dart-lang/native/tree/main/pkgs/ffi', authors: [], version: '2.1.4', @@ -4209,13 +5494,13 @@ THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.''', isMarkdown: false, isSdk: false, - dependencies: [] - ); + dependencies: []); /// file 7.0.1 const _file = Package( name: 'file', - description: 'A pluggable, mockable file system abstraction for Dart. Supports local file system access, as well as in-memory file systems, record-replay file systems, and chroot file systems.', + description: + 'A pluggable, mockable file system abstraction for Dart. Supports local file system access, as well as in-memory file systems, record-replay file systems, and chroot file systems.', repository: 'https://github.com/dart-lang/tools/tree/main/pkgs/file', authors: [], version: '7.0.1', @@ -4247,17 +5532,17 @@ THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.''', isMarkdown: false, isSdk: false, - dependencies: [PackageRef('meta'), PackageRef('path')] - ); + dependencies: [PackageRef('meta'), PackageRef('path')]); -/// file_picker 10.1.9 +/// file_picker 10.3.3 const _file_picker = Package( name: 'file_picker', - description: 'A package that allows you to use a native file explorer to pick single or multiple absolute file paths, with extension filtering support.', + description: + 'A package that allows you to use a native file explorer to pick single or multiple absolute file paths, with extension filtering support.', homepage: 'https://github.com/miguelpruivo/plugins_flutter_file_picker', repository: 'https://github.com/miguelpruivo/flutter_file_picker', authors: [], - version: '10.1.9', + version: '10.3.3', license: '''MIT License Copyright (c) 2018 Miguel Ruivo @@ -4281,13 +5566,24 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.''', isMarkdown: false, isSdk: false, - dependencies: [PackageRef('flutter'), PackageRef('flutter_plugin_android_lifecycle'), PackageRef('plugin_platform_interface'), PackageRef('ffi'), PackageRef('path'), PackageRef('win32'), PackageRef('cross_file'), PackageRef('web')] - ); + dependencies: [ + PackageRef('flutter'), + PackageRef('flutter_web_plugins'), + PackageRef('flutter_plugin_android_lifecycle'), + PackageRef('plugin_platform_interface'), + PackageRef('ffi'), + PackageRef('path'), + PackageRef('win32'), + PackageRef('cross_file'), + PackageRef('web'), + PackageRef('dbus') + ]); /// fixnum 1.1.1 const _fixnum = Package( name: 'fixnum', - description: 'Library for 32- and 64-bit signed fixed-width integers with consistent behavior between native and JS runtimes.', + description: + 'Library for 32- and 64-bit signed fixed-width integers with consistent behavior between native and JS runtimes.', repository: 'https://github.com/dart-lang/core/tree/main/pkgs/fixnum', authors: [], version: '1.1.1', @@ -4320,16 +5616,15 @@ THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.''', isMarkdown: false, isSdk: false, - dependencies: [] - ); + dependencies: []); -/// flutter 3.32.2 +/// flutter 3.35.3 const _flutter = Package( name: 'flutter', description: 'A framework for writing Flutter applications', homepage: 'https://flutter.dev', authors: [], - version: '3.32.2', + version: '3.35.3', license: '''Copyright 2014 The Flutter Authors. All rights reserved. Redistribution and use in source and binary forms, with or without modification, @@ -4357,8 +5652,13 @@ ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.''', isMarkdown: false, isSdk: true, - dependencies: [PackageRef('characters'), PackageRef('collection'), PackageRef('material_color_utilities'), PackageRef('meta'), PackageRef('vector_math')] - ); + dependencies: [ + PackageRef('characters'), + PackageRef('collection'), + PackageRef('material_color_utilities'), + PackageRef('meta'), + PackageRef('vector_math') + ]); /// flutter_breadcrumb 1.0.1 const _flutter_breadcrumb = Package( @@ -4399,17 +5699,18 @@ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.''', isMarkdown: false, isSdk: false, - dependencies: [PackageRef('flutter'), PackageRef('pedantic')] - ); + dependencies: [PackageRef('flutter'), PackageRef('pedantic')]); -/// flutter_hooks 0.21.2 +/// flutter_hooks 0.21.3+1 const _flutter_hooks = Package( name: 'flutter_hooks', - description: 'A flutter implementation of React hooks. It adds a new kind of widget with enhanced code reuse.', + description: + 'A flutter implementation of React hooks. It adds a new kind of widget with enhanced code reuse.', homepage: 'https://github.com/rrousselGit/flutter_hooks', - repository: 'https://github.com/rrousselGit/flutter_hooks/tree/master/packages/flutter_hooks', + repository: + 'https://github.com/rrousselGit/flutter_hooks/tree/master/packages/flutter_hooks', authors: [], - version: '0.21.2', + version: '0.21.3+1', license: '''MIT License Copyright (c) 2018 Remi Rousselet @@ -4433,14 +5734,49 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.''', isMarkdown: false, isSdk: false, - dependencies: [PackageRef('flutter')] - ); + dependencies: [PackageRef('flutter')]); + +/// flutter_hooks_lint 1.4.0 +const _flutter_hooks_lint = Package( + name: 'flutter_hooks_lint', + description: + 'A lint package providing guidelines for using flutter_hooks in your Flutter widget!', + homepage: 'https://github.com/nikaera/flutter_hooks_lint', + repository: 'https://github.com/nikaera/flutter_hooks_lint', + authors: [], + version: '1.4.0', + license: '''MIT License + +Copyright (c) 2024 nikaera + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE.''', + isMarkdown: false, + isSdk: false, + dependencies: [PackageRef('analyzer'), PackageRef('custom_lint_builder')]); /// flutter_lints 6.0.0 const _flutter_lints = Package( name: 'flutter_lints', - description: 'Recommended lints for Flutter apps, packages, and plugins to encourage good coding practices.', - repository: 'https://github.com/flutter/packages/tree/main/packages/flutter_lints', + description: + 'Recommended lints for Flutter apps, packages, and plugins to encourage good coding practices.', + repository: + 'https://github.com/flutter/packages/tree/main/packages/flutter_lints', authors: [], version: '6.0.0', license: '''Copyright 2013 The Flutter Authors. All rights reserved. @@ -4470,14 +5806,15 @@ ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.''', isMarkdown: false, isSdk: false, - dependencies: [PackageRef('lints')] - ); + dependencies: [PackageRef('lints')]); /// flutter_markdown 0.7.7+1 const _flutter_markdown = Package( name: 'flutter_markdown', - description: 'A Markdown renderer for Flutter. Create rich text output, including text styles, tables, links, and more, from plain text data formatted with simple Markdown tags.', - repository: 'https://github.com/flutter/packages/tree/main/packages/flutter_markdown', + description: + 'A Markdown renderer for Flutter. Create rich text output, including text styles, tables, links, and more, from plain text data formatted with simple Markdown tags.', + repository: + 'https://github.com/flutter/packages/tree/main/packages/flutter_markdown', authors: [], version: '0.7.7+1', license: '''Copyright 2013 The Flutter Authors. All rights reserved. @@ -4507,50 +5844,22 @@ ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.''', isMarkdown: false, isSdk: false, - dependencies: [PackageRef('flutter'), PackageRef('markdown'), PackageRef('meta'), PackageRef('path')] - ); - -/// flutter_oss_licenses 3.0.4 -const _flutter_oss_licenses = Package( - name: 'flutter_oss_licenses', - description: 'A tool to generate detail and better OSS license list using pubspec.yaml/lock files.', - homepage: 'https://github.com/espresso3389/flutter_oss_licenses/tree/master/packages/flutter_oss_licenses', - repository: 'https://github.com/espresso3389/flutter_oss_licenses', - authors: [], - version: '3.0.4', - license: '''MIT License - -Copyright (c) 2019 Takashi Kawasaki - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE.''', - isMarkdown: false, - isSdk: false, - dependencies: [PackageRef('path'), PackageRef('meta'), PackageRef('yaml'), PackageRef('dart_pubspec_licenses'), PackageRef('args')] - ); + dependencies: [ + PackageRef('flutter'), + PackageRef('markdown'), + PackageRef('meta'), + PackageRef('path') + ]); -/// flutter_plugin_android_lifecycle 2.0.28 +/// flutter_plugin_android_lifecycle 2.0.30 const _flutter_plugin_android_lifecycle = Package( name: 'flutter_plugin_android_lifecycle', - description: 'Flutter plugin for accessing an Android Lifecycle within other plugins.', - repository: 'https://github.com/flutter/packages/tree/main/packages/flutter_plugin_android_lifecycle', + description: + 'Flutter plugin for accessing an Android Lifecycle within other plugins.', + repository: + 'https://github.com/flutter/packages/tree/main/packages/flutter_plugin_android_lifecycle', authors: [], - version: '2.0.28', + version: '2.0.30', license: '''Copyright 2013 The Flutter Authors. All rights reserved. Redistribution and use in source and binary forms, with or without modification, @@ -4578,14 +5887,15 @@ ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.''', isMarkdown: false, isSdk: false, - dependencies: [PackageRef('flutter')] - ); + dependencies: [PackageRef('flutter')]); /// flutter_secure_storage 8.1.0 const _flutter_secure_storage = Package( name: 'flutter_secure_storage', - description: 'Flutter Secure Storage provides API to store data in secure storage. Keychain is used in iOS, KeyStore based solution is used in Android.', - repository: 'https://github.com/mogol/flutter_secure_storage/tree/develop/flutter_secure_storage', + description: + 'Flutter Secure Storage provides API to store data in secure storage. Keychain is used in iOS, KeyStore based solution is used in Android.', + repository: + 'https://github.com/mogol/flutter_secure_storage/tree/develop/flutter_secure_storage', authors: [], version: '8.1.0', license: '''BSD 3-Clause License @@ -4619,8 +5929,15 @@ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.''', isMarkdown: false, isSdk: false, - dependencies: [PackageRef('flutter'), PackageRef('flutter_secure_storage_linux'), PackageRef('flutter_secure_storage_macos'), PackageRef('flutter_secure_storage_platform_interface'), PackageRef('flutter_secure_storage_web'), PackageRef('flutter_secure_storage_windows'), PackageRef('meta')] - ); + dependencies: [ + PackageRef('flutter'), + PackageRef('flutter_secure_storage_linux'), + PackageRef('flutter_secure_storage_macos'), + PackageRef('flutter_secure_storage_platform_interface'), + PackageRef('flutter_secure_storage_web'), + PackageRef('flutter_secure_storage_windows'), + PackageRef('meta') + ]); /// flutter_secure_storage_linux 1.2.3 const _flutter_secure_storage_linux = Package( @@ -4660,8 +5977,10 @@ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.''', isMarkdown: false, isSdk: false, - dependencies: [PackageRef('flutter'), PackageRef('flutter_secure_storage_platform_interface')] - ); + dependencies: [ + PackageRef('flutter'), + PackageRef('flutter_secure_storage_platform_interface') + ]); /// flutter_secure_storage_macos 3.1.3 const _flutter_secure_storage_macos = Package( @@ -4701,13 +6020,16 @@ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.''', isMarkdown: false, isSdk: false, - dependencies: [PackageRef('flutter'), PackageRef('flutter_secure_storage_platform_interface')] - ); + dependencies: [ + PackageRef('flutter'), + PackageRef('flutter_secure_storage_platform_interface') + ]); /// flutter_secure_storage_platform_interface 1.1.2 const _flutter_secure_storage_platform_interface = Package( name: 'flutter_secure_storage_platform_interface', - description: 'A common platform interface for the flutter_secure_storage plugin.', + description: + 'A common platform interface for the flutter_secure_storage plugin.', homepage: 'https://github.com/mogol/flutter_secure_storage', authors: [], version: '1.1.2', @@ -4742,13 +6064,16 @@ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.''', isMarkdown: false, isSdk: false, - dependencies: [PackageRef('flutter'), PackageRef('plugin_platform_interface')] - ); + dependencies: [ + PackageRef('flutter'), + PackageRef('plugin_platform_interface') + ]); /// flutter_secure_storage_web 1.2.1 const _flutter_secure_storage_web = Package( name: 'flutter_secure_storage_web', - description: 'Web implementation of flutter_secure_storage. Use flutter_secure_storage for the full flutter package.', + description: + 'Web implementation of flutter_secure_storage. Use flutter_secure_storage for the full flutter package.', repository: 'https://github.com/mogol/flutter_secure_storage', authors: [], version: '1.2.1', @@ -4783,13 +6108,18 @@ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.''', isMarkdown: false, isSdk: false, - dependencies: [PackageRef('flutter'), PackageRef('flutter_secure_storage_platform_interface'), PackageRef('js')] - ); + dependencies: [ + PackageRef('flutter'), + PackageRef('flutter_secure_storage_platform_interface'), + PackageRef('flutter_web_plugins'), + PackageRef('js') + ]); /// flutter_secure_storage_windows 2.1.1 const _flutter_secure_storage_windows = Package( name: 'flutter_secure_storage_windows', - description: 'Windows implementation of flutter_secure_storage. Please use flutter_secure_storage instead of this package.', + description: + 'Windows implementation of flutter_secure_storage. Please use flutter_secure_storage instead of this package.', repository: 'https://github.com/mogol/flutter_secure_storage', authors: [], version: '2.1.1', @@ -4824,13 +6154,16 @@ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.''', isMarkdown: false, isSdk: false, - dependencies: [PackageRef('flutter'), PackageRef('flutter_secure_storage_platform_interface')] - ); + dependencies: [ + PackageRef('flutter'), + PackageRef('flutter_secure_storage_platform_interface') + ]); /// flutter_volume_controller 1.3.3 const _flutter_volume_controller = Package( name: 'flutter_volume_controller', - description: 'A Flutter plugin to control system volume and listen for volume changes on different platforms.', + description: + 'A Flutter plugin to control system volume and listen for volume changes on different platforms.', homepage: 'https://github.com/yosemiteyss/flutter_volume_controller', authors: [], version: '1.3.3', @@ -4856,16 +6189,31 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.''', isMarkdown: false, - isSdk: false, - dependencies: [PackageRef('flutter'), PackageRef('flutter_plugin_android_lifecycle')] - ); + isSdk: false, + dependencies: [ + PackageRef('flutter'), + PackageRef('flutter_plugin_android_lifecycle') + ]); + +/// flutter_web_plugins null +const _flutter_web_plugins = Package( + name: 'flutter_web_plugins', + description: 'Library to register Flutter Web plugins', + homepage: 'https://flutter.dev', + authors: [], + isMarkdown: false, + isSdk: true, + dependencies: [PackageRef('flutter')]); /// flutter_zustand 0.0.5 const _flutter_zustand = Package( name: 'flutter_zustand', - description: "Brings zustand's bear necessities for state management to Flutter", - homepage: 'https://github.com/josiahsrc/flutter_zustand/tree/main/packages/flutter_zustand', - repository: 'https://github.com/josiahsrc/flutter_zustand/tree/main/packages/flutter_zustand', + description: + "Brings zustand's bear necessities for state management to Flutter", + homepage: + 'https://github.com/josiahsrc/flutter_zustand/tree/main/packages/flutter_zustand', + repository: + 'https://github.com/josiahsrc/flutter_zustand/tree/main/packages/flutter_zustand', authors: [], version: '0.0.5', license: '''MIT License @@ -4891,17 +6239,21 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.''', isMarkdown: false, isSdk: false, - dependencies: [PackageRef('flutter'), PackageRef('provider'), PackageRef('zustand')] - ); + dependencies: [ + PackageRef('flutter'), + PackageRef('provider'), + PackageRef('zustand') + ]); -/// freezed 3.0.6 +/// freezed 3.2.3 const _freezed = Package( name: 'freezed', - description: '''Code generation for immutable classes that has a simple syntax/API without compromising on the features. + description: + '''Code generation for immutable classes that has a simple syntax/API without compromising on the features. ''', repository: 'https://github.com/rrousselGit/freezed', authors: [], - version: '3.0.6', + version: '3.2.3', license: '''MIT License Copyright (c) 2020 Remi Rousselet @@ -4925,17 +6277,28 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.''', isMarkdown: false, isSdk: false, - dependencies: [PackageRef('analyzer'), PackageRef('build'), PackageRef('build_config'), PackageRef('collection'), PackageRef('meta'), PackageRef('source_gen'), PackageRef('freezed_annotation'), PackageRef('json_annotation')] - ); - -/// freezed_annotation 3.0.0 + dependencies: [ + PackageRef('analyzer'), + PackageRef('build'), + PackageRef('build_config'), + PackageRef('collection'), + PackageRef('meta'), + PackageRef('source_gen'), + PackageRef('freezed_annotation'), + PackageRef('json_annotation'), + PackageRef('dart_style'), + PackageRef('pub_semver') + ]); + +/// freezed_annotation 3.1.0 const _freezed_annotation = Package( name: 'freezed_annotation', - description: '''Annotations for the freezed code-generator. This package does nothing without freezed too. + description: + '''Annotations for the freezed code-generator. This package does nothing without freezed too. ''', repository: 'https://github.com/rrousselGit/freezed', authors: [], - version: '3.0.0', + version: '3.1.0', license: '''MIT License Copyright (c) 2020 Remi Rousselet @@ -4959,14 +6322,19 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.''', isMarkdown: false, isSdk: false, - dependencies: [PackageRef('collection'), PackageRef('json_annotation'), PackageRef('meta')] - ); + dependencies: [ + PackageRef('collection'), + PackageRef('json_annotation'), + PackageRef('meta') + ]); /// frontend_server_client 4.0.0 const _frontend_server_client = Package( name: 'frontend_server_client', - description: 'Client code to start and interact with the frontend_server compiler from the Dart SDK.', - repository: 'https://github.com/dart-lang/webdev/tree/master/frontend_server_client', + description: + 'Client code to start and interact with the frontend_server compiler from the Dart SDK.', + repository: + 'https://github.com/dart-lang/webdev/tree/master/frontend_server_client', authors: [], version: '4.0.0', license: '''Copyright 2020, the Dart project authors. @@ -4998,16 +6366,16 @@ THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.''', isMarkdown: false, isSdk: false, - dependencies: [PackageRef('async'), PackageRef('path')] - ); + dependencies: [PackageRef('async'), PackageRef('path')]); -/// fvp 0.32.1 +/// fvp 0.34.0 const _fvp = Package( name: 'fvp', - description: 'video_player plugin and backend APIs. Support all desktop/mobile platforms with hardware decoders, optimal renders. Supports most formats via FFmpeg', + description: + 'video_player plugin and backend APIs. Support all desktop/mobile platforms with hardware decoders, optimal renders. Supports most formats via FFmpeg', homepage: 'https://github.com/wang-bin/fvp', authors: [], - version: '0.32.1', + version: '0.34.0', license: '''BSD-3-Clause License Copyright 2022 Wang Bin. All rights reserved. @@ -5037,16 +6405,26 @@ ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.''', isMarkdown: false, isSdk: false, - dependencies: [PackageRef('ffi'), PackageRef('flutter'), PackageRef('logging'), PackageRef('path'), PackageRef('plugin_platform_interface'), PackageRef('video_player'), PackageRef('video_player_platform_interface'), PackageRef('path_provider'), PackageRef('http')] - ); + dependencies: [ + PackageRef('ffi'), + PackageRef('flutter'), + PackageRef('logging'), + PackageRef('path'), + PackageRef('plugin_platform_interface'), + PackageRef('video_player'), + PackageRef('video_player_platform_interface'), + PackageRef('path_provider'), + PackageRef('http') + ]); -/// get_it 7.7.0 +/// get_it 8.2.0 const _get_it = Package( name: 'get_it', - description: 'Simple direct Service Locator that allows to decouple the interface from a concrete implementation and to access the concrete implementation from everywhere in your App"', - homepage: 'https://github.com/fluttercommunity/get_it', + description: + 'Simple direct Service Locator that allows to decouple the interface from a concrete implementation and to access the concrete implementation from everywhere in your App"', + homepage: 'https://github.com/flutter-it/get_it', authors: [], - version: '7.7.0', + version: '8.2.0', license: '''MIT License Copyright (c) 2018 Thomas Burkhart @@ -5070,8 +6448,11 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.''', isMarkdown: false, isSdk: false, - dependencies: [PackageRef('async'), PackageRef('collection'), PackageRef('meta')] - ); + dependencies: [ + PackageRef('async'), + PackageRef('collection'), + PackageRef('meta') + ]); /// glob 2.1.3 const _glob = Package( @@ -5109,226 +6490,62 @@ THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.''', isMarkdown: false, isSdk: false, - dependencies: [PackageRef('async'), PackageRef('collection'), PackageRef('file'), PackageRef('path'), PackageRef('string_scanner')] - ); + dependencies: [ + PackageRef('async'), + PackageRef('collection'), + PackageRef('file'), + PackageRef('path'), + PackageRef('string_scanner') + ]); -/// google_fonts 6.2.1 +/// google_fonts 6.3.1 const _google_fonts = Package( name: 'google_fonts', - description: 'A Flutter package to use fonts from fonts.google.com. Supports HTTP fetching, caching, and asset bundling.', - repository: 'https://github.com/material-foundation/flutter-packages/tree/main/packages/google_fonts', + description: + 'A Flutter package to use fonts from fonts.google.com. Supports HTTP fetching, caching, and asset bundling.', + repository: + 'https://github.com/flutter/packages/tree/main/packages/google_fonts', authors: [], - version: '6.2.1', - license: '''Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright [yyyy] [name of copyright owner] + version: '6.3.1', + license: '''Copyright 2013 The Flutter Authors. All rights reserved. - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: - http://www.apache.org/licenses/LICENSE-2.0 + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following + disclaimer in the documentation and/or other materials provided + with the distribution. + * Neither the name of Google Inc. nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License.''', +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON +ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.''', isMarkdown: false, isSdk: false, - dependencies: [PackageRef('flutter'), PackageRef('http'), PackageRef('path_provider'), PackageRef('crypto')] - ); + dependencies: [ + PackageRef('crypto'), + PackageRef('flutter'), + PackageRef('http'), + PackageRef('path_provider') + ]); /// graphs 2.3.2 const _graphs = Package( name: 'graphs', - description: 'Graph algorithms that operate on graphs in any representation.', + description: + 'Graph algorithms that operate on graphs in any representation.', repository: 'https://github.com/dart-lang/tools/tree/main/pkgs/graphs', authors: [], version: '2.3.2', @@ -5361,8 +6578,7 @@ THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.''', isMarkdown: false, isSdk: false, - dependencies: [PackageRef('collection')] - ); + dependencies: [PackageRef('collection')]); /// gtk 2.1.0 const _gtk = Package( @@ -5747,13 +6963,239 @@ Exhibit B - "Incompatible With Secondary Licenses" Notice defined by the Mozilla Public License, v. 2.0.''', isMarkdown: false, isSdk: false, - dependencies: [PackageRef('ffi'), PackageRef('flutter'), PackageRef('meta')] - ); + dependencies: [ + PackageRef('ffi'), + PackageRef('flutter'), + PackageRef('meta') + ]); + +/// hotreloader 4.3.0 +const _hotreloader = Package( + name: 'hotreloader', + description: + '''Automatic hot code reloader for Dart projects that monitors the source files of a Dart project for changes and automatically applies them to the running Dart process. +''', + homepage: 'https://github.com/vegardit/dart-hotreloader', + repository: 'https://github.com/vegardit/dart-hotreloader.git', + authors: [], + version: '4.3.0', + license: '''Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + +2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + +3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + +4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + +5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + +6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + +8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + +9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + +END OF TERMS AND CONDITIONS + +APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + +Copyright [yyyy] [name of copyright owner] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License.''', + isMarkdown: false, + isSdk: false, + dependencies: [ + PackageRef('collection'), + PackageRef('logging'), + PackageRef('path'), + PackageRef('stream_transform'), + PackageRef('vm_service'), + PackageRef('watcher') + ]); /// html 0.15.6 const _html = Package( name: 'html', - description: 'APIs for parsing and manipulating HTML content outside the browser.', + description: + 'APIs for parsing and manipulating HTML content outside the browser.', repository: 'https://github.com/dart-lang/tools/tree/main/pkgs/html', authors: [], version: '0.15.6', @@ -5782,16 +7224,16 @@ The above copyright notice and this permission notice shall be included in all c THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.''', isMarkdown: false, isSdk: false, - dependencies: [PackageRef('csslib'), PackageRef('source_span')] - ); + dependencies: [PackageRef('csslib'), PackageRef('source_span')]); -/// http 1.4.0 +/// http 1.5.0 const _http = Package( name: 'http', - description: 'A composable, multi-platform, Future-based API for HTTP requests.', + description: + 'A composable, multi-platform, Future-based API for HTTP requests.', repository: 'https://github.com/dart-lang/http/tree/master/pkgs/http', authors: [], - version: '1.4.0', + version: '1.5.0', license: '''Copyright 2014, the Dart project authors. Redistribution and use in source and binary forms, with or without @@ -5821,13 +7263,18 @@ THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.''', isMarkdown: false, isSdk: false, - dependencies: [PackageRef('async'), PackageRef('http_parser'), PackageRef('meta'), PackageRef('web')] - ); + dependencies: [ + PackageRef('async'), + PackageRef('http_parser'), + PackageRef('meta'), + PackageRef('web') + ]); /// http_methods 1.1.1 const _http_methods = Package( name: 'http_methods', - description: '''List of all HTTP methods registered with IANA as list of strings, and metadata + description: + '''List of all HTTP methods registered with IANA as list of strings, and metadata such as whether a method idempotent. ''', homepage: 'https://github.com/google/dart-neats/tree/master/http_methods', @@ -6037,14 +7484,15 @@ such as whether a method idempotent. limitations under the License.''', isMarkdown: false, isSdk: false, - dependencies: [] - ); + dependencies: []); /// http_multi_server 3.2.2 const _http_multi_server = Package( name: 'http_multi_server', - description: 'A dart:io HttpServer wrapper that handles requests from multiple servers.', - repository: 'https://github.com/dart-lang/http/tree/master/pkgs/http_multi_server', + description: + 'A dart:io HttpServer wrapper that handles requests from multiple servers.', + repository: + 'https://github.com/dart-lang/http/tree/master/pkgs/http_multi_server', authors: [], version: '3.2.2', license: '''Copyright 2014, the Dart project authors. @@ -6076,14 +7524,15 @@ THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.''', isMarkdown: false, isSdk: false, - dependencies: [PackageRef('async')] - ); + dependencies: [PackageRef('async')]); /// http_parser 4.1.2 const _http_parser = Package( name: 'http_parser', - description: 'A platform-independent package for parsing and serializing HTTP formats.', - repository: 'https://github.com/dart-lang/http/tree/master/pkgs/http_parser', + description: + 'A platform-independent package for parsing and serializing HTTP formats.', + repository: + 'https://github.com/dart-lang/http/tree/master/pkgs/http_parser', authors: [], version: '4.1.2', license: '''Copyright 2014, the Dart project authors. @@ -6115,13 +7564,18 @@ THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.''', isMarkdown: false, isSdk: false, - dependencies: [PackageRef('collection'), PackageRef('source_span'), PackageRef('string_scanner'), PackageRef('typed_data')] - ); + dependencies: [ + PackageRef('collection'), + PackageRef('source_span'), + PackageRef('string_scanner'), + PackageRef('typed_data') + ]); /// image 4.5.4 const _image = Package( name: 'image', - description: 'Dart Image Library provides server and web apps the ability to load, manipulate, and save images with various image file formats.', + description: + 'Dart Image Library provides server and web apps the ability to load, manipulate, and save images with various image file formats.', homepage: 'https://github.com/brendan-duncan/image', authors: [], version: '4.5.4', @@ -6149,13 +7603,17 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.''', isMarkdown: false, isSdk: false, - dependencies: [PackageRef('archive'), PackageRef('meta'), PackageRef('xml')] - ); + dependencies: [ + PackageRef('archive'), + PackageRef('meta'), + PackageRef('xml') + ]); /// intl 0.20.2 const _intl = Package( name: 'intl', - description: 'Contains code to deal with internationalized/localized messages, date and number formatting and parsing, bi-directional text, and other internationalization issues.', + description: + 'Contains code to deal with internationalized/localized messages, date and number formatting and parsing, bi-directional text, and other internationalization issues.', repository: 'https://github.com/dart-lang/i18n/tree/main/pkgs/intl', authors: [], version: '0.20.2', @@ -6188,13 +7646,17 @@ THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.''', isMarkdown: false, isSdk: false, - dependencies: [PackageRef('clock'), PackageRef('meta'), PackageRef('path')] - ); + dependencies: [ + PackageRef('clock'), + PackageRef('meta'), + PackageRef('path') + ]); /// io 1.0.5 const _io = Package( name: 'io', - description: 'Utilities for the Dart VM Runtime including support for ANSI colors, file copying, and standard exit code values.', + description: + 'Utilities for the Dart VM Runtime including support for ANSI colors, file copying, and standard exit code values.', repository: 'https://github.com/dart-lang/tools/tree/main/pkgs/io', authors: [], version: '1.0.5', @@ -6227,13 +7689,17 @@ THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.''', isMarkdown: false, isSdk: false, - dependencies: [PackageRef('meta'), PackageRef('path'), PackageRef('string_scanner')] - ); + dependencies: [ + PackageRef('meta'), + PackageRef('path'), + PackageRef('string_scanner') + ]); /// js 0.6.7 const _js = Package( name: 'js', - description: 'Annotations to create static Dart interfaces for JavaScript APIs.', + description: + 'Annotations to create static Dart interfaces for JavaScript APIs.', repository: 'https://github.com/dart-lang/sdk/tree/main/pkg/js', authors: [], version: '0.6.7', @@ -6266,14 +7732,15 @@ THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.''', isMarkdown: false, isSdk: false, - dependencies: [PackageRef('meta')] - ); + dependencies: [PackageRef('meta')]); /// json_annotation 4.9.0 const _json_annotation = Package( name: 'json_annotation', - description: 'Classes and helper functions that support JSON code generation via the `json_serializable` package.', - repository: 'https://github.com/google/json_serializable.dart/tree/master/json_annotation', + description: + 'Classes and helper functions that support JSON code generation via the `json_serializable` package.', + repository: + 'https://github.com/google/json_serializable.dart/tree/master/json_annotation', authors: [], version: '4.9.0', license: '''Copyright 2017, the Dart project authors. All rights reserved. @@ -6304,16 +7771,17 @@ THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.''', isMarkdown: false, isSdk: false, - dependencies: [PackageRef('meta')] - ); + dependencies: [PackageRef('meta')]); -/// json_serializable 6.9.5 +/// json_serializable 6.11.1 const _json_serializable = Package( name: 'json_serializable', - description: 'Automatically generate code for converting to and from JSON by annotating Dart classes.', - repository: 'https://github.com/google/json_serializable.dart/tree/master/json_serializable', + description: + 'Automatically generate code for converting to and from JSON by annotating Dart classes.', + repository: + 'https://github.com/google/json_serializable.dart/tree/master/json_serializable', authors: [], - version: '6.9.5', + version: '6.11.1', license: '''Copyright 2017, the Dart project authors. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are @@ -6342,16 +7810,30 @@ THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.''', isMarkdown: false, isSdk: false, - dependencies: [PackageRef('analyzer'), PackageRef('async'), PackageRef('build'), PackageRef('build_config'), PackageRef('collection'), PackageRef('dart_style'), PackageRef('json_annotation'), PackageRef('meta'), PackageRef('path'), PackageRef('pub_semver'), PackageRef('pubspec_parse'), PackageRef('source_gen'), PackageRef('source_helper')] - ); - -/// leak_tracker 10.0.9 + dependencies: [ + PackageRef('analyzer'), + PackageRef('async'), + PackageRef('build'), + PackageRef('build_config'), + PackageRef('dart_style'), + PackageRef('json_annotation'), + PackageRef('meta'), + PackageRef('path'), + PackageRef('pub_semver'), + PackageRef('pubspec_parse'), + PackageRef('source_gen'), + PackageRef('source_helper') + ]); + +/// leak_tracker 11.0.2 const _leak_tracker = Package( name: 'leak_tracker', - description: 'A framework for memory leak tracking for Dart and Flutter applications.', - repository: 'https://github.com/dart-lang/leak_tracker/tree/main/pkgs/leak_tracker', + description: + 'A framework for memory leak tracking for Dart and Flutter applications.', + repository: + 'https://github.com/dart-lang/leak_tracker/tree/main/pkgs/leak_tracker', authors: [], - version: '10.0.9', + version: '11.0.2', license: '''Copyright 2022, the Dart project authors. Redistribution and use in source and binary forms, with or without @@ -6381,16 +7863,22 @@ THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.''', isMarkdown: false, isSdk: false, - dependencies: [] - ); + dependencies: [ + PackageRef('clock'), + PackageRef('collection'), + PackageRef('meta'), + PackageRef('path'), + PackageRef('vm_service') + ]); -/// leak_tracker_flutter_testing 3.0.9 +/// leak_tracker_flutter_testing 3.0.10 const _leak_tracker_flutter_testing = Package( name: 'leak_tracker_flutter_testing', description: 'An internal package to test leak tracking with Flutter.', - repository: 'https://github.com/dart-lang/leak_tracker/tree/main/pkgs/leak_tracker_flutter_testing', + repository: + 'https://github.com/dart-lang/leak_tracker/tree/main/pkgs/leak_tracker_flutter_testing', authors: [], - version: '3.0.9', + version: '3.0.10', license: '''Copyright 2022, the Dart project authors. Redistribution and use in source and binary forms, with or without @@ -6420,16 +7908,22 @@ THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.''', isMarkdown: false, isSdk: false, - dependencies: [] - ); + dependencies: [ + PackageRef('flutter'), + PackageRef('leak_tracker'), + PackageRef('leak_tracker_testing'), + PackageRef('matcher'), + PackageRef('meta') + ]); -/// leak_tracker_testing 3.0.1 +/// leak_tracker_testing 3.0.2 const _leak_tracker_testing = Package( name: 'leak_tracker_testing', description: 'Leak tracking code intended for usage in tests.', - repository: 'https://github.com/dart-lang/leak_tracker/tree/main/pkgs/leak_tracker_testing', + repository: + 'https://github.com/dart-lang/leak_tracker/tree/main/pkgs/leak_tracker_testing', authors: [], - version: '3.0.1', + version: '3.0.2', license: '''Copyright 2022, the Dart project authors. Redistribution and use in source and binary forms, with or without @@ -6459,13 +7953,17 @@ THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.''', isMarkdown: false, isSdk: false, - dependencies: [] - ); + dependencies: [ + PackageRef('leak_tracker'), + PackageRef('matcher'), + PackageRef('meta') + ]); /// lints 6.0.0 const _lints = Package( name: 'lints', - description: """Official Dart lint rules. Defines the 'core' and 'recommended' set of lints suggested by the Dart team. + description: + """Official Dart lint rules. Defines the 'core' and 'recommended' set of lints suggested by the Dart team. """, repository: 'https://github.com/dart-lang/core/tree/main/pkgs/lints', authors: [], @@ -6499,13 +7997,13 @@ THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.''', isMarkdown: false, isSdk: false, - dependencies: [] - ); + dependencies: []); /// logging 1.3.0 const _logging = Package( name: 'logging', - description: 'Provides APIs for debugging and error logging, similar to loggers in other languages, such as the Closure JS Logger and java.util.logging.Logger.', + description: + 'Provides APIs for debugging and error logging, similar to loggers in other languages, such as the Closure JS Logger and java.util.logging.Logger.', repository: 'https://github.com/dart-lang/core/tree/main/pkgs/logging', authors: [], version: '1.3.0', @@ -6538,13 +8036,13 @@ THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.''', isMarkdown: false, isSdk: false, - dependencies: [] - ); + dependencies: []); /// markdown 7.3.0 const _markdown = Package( name: 'markdown', - description: 'A portable Markdown library written in Dart that can parse Markdown into HTML.', + description: + 'A portable Markdown library written in Dart that can parse Markdown into HTML.', repository: 'https://github.com/dart-lang/tools/tree/main/pkgs/markdown', authors: [], version: '7.3.0', @@ -6577,13 +8075,13 @@ THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.''', isMarkdown: false, isSdk: false, - dependencies: [PackageRef('args'), PackageRef('meta')] - ); + dependencies: [PackageRef('args'), PackageRef('meta')]); /// matcher 0.12.17 const _matcher = Package( name: 'matcher', - description: 'Support for specifying test expectations via an extensible Matcher class. Also includes a number of built-in Matcher implementations for common cases.', + description: + 'Support for specifying test expectations via an extensible Matcher class. Also includes a number of built-in Matcher implementations for common cases.', repository: 'https://github.com/dart-lang/test/tree/master/pkgs/matcher', authors: [], version: '0.12.17', @@ -6616,14 +8114,21 @@ THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.''', isMarkdown: false, isSdk: false, - dependencies: [PackageRef('async'), PackageRef('meta'), PackageRef('stack_trace'), PackageRef('term_glyph'), PackageRef('test_api')] - ); + dependencies: [ + PackageRef('async'), + PackageRef('meta'), + PackageRef('stack_trace'), + PackageRef('term_glyph'), + PackageRef('test_api') + ]); /// material_color_utilities 0.11.1 const _material_color_utilities = Package( name: 'material_color_utilities', - description: 'Algorithms and utilities that power the Material Design 3 color system, including choosing theme colors from images and creating tones of colors; all in a new color space.', - repository: 'https://github.com/material-foundation/material-color-utilities/tree/main/dart', + description: + 'Algorithms and utilities that power the Material Design 3 color system, including choosing theme colors from images and creating tones of colors; all in a new color space.', + repository: + 'https://github.com/material-foundation/material-color-utilities/tree/main/dart', authors: [], version: '0.11.1', license: '''Apache License @@ -6829,13 +8334,13 @@ const _material_color_utilities = Package( limitations under the License.''', isMarkdown: false, isSdk: false, - dependencies: [PackageRef('collection')] - ); + dependencies: [PackageRef('collection')]); /// media_kit 1.2.0 const _media_kit = Package( name: 'media_kit', - description: 'A cross-platform video player & audio player for Flutter & Dart. Performant, stable, feature-proof & modular.', + description: + 'A cross-platform video player & audio player for Flutter & Dart. Performant, stable, feature-proof & modular.', homepage: 'https://github.com/media-kit/media-kit', repository: 'https://github.com/media-kit/media-kit', authors: [], @@ -6863,13 +8368,25 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.''', isMarkdown: false, isSdk: false, - dependencies: [PackageRef('collection'), PackageRef('http'), PackageRef('image'), PackageRef('meta'), PackageRef('path'), PackageRef('safe_local_storage'), PackageRef('synchronized'), PackageRef('universal_platform'), PackageRef('web'), PackageRef('uri_parser'), PackageRef('uuid')] - ); + dependencies: [ + PackageRef('collection'), + PackageRef('http'), + PackageRef('image'), + PackageRef('meta'), + PackageRef('path'), + PackageRef('safe_local_storage'), + PackageRef('synchronized'), + PackageRef('universal_platform'), + PackageRef('web'), + PackageRef('uri_parser'), + PackageRef('uuid') + ]); /// media_kit_libs_android_video 1.3.7 const _media_kit_libs_android_video = Package( name: 'media_kit_libs_android_video', - description: 'Android package providing video (& audio) native libraries for package:media_kit.', + description: + 'Android package providing video (& audio) native libraries for package:media_kit.', homepage: 'https://github.com/media-kit/media-kit.git', repository: 'https://github.com/media-kit/media-kit.git', authors: [], @@ -6897,13 +8414,16 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.''', isMarkdown: false, isSdk: false, - dependencies: [PackageRef('flutter'), PackageRef('plugin_platform_interface')] - ); + dependencies: [ + PackageRef('flutter'), + PackageRef('plugin_platform_interface') + ]); /// media_kit_libs_ios_video 1.1.4 const _media_kit_libs_ios_video = Package( name: 'media_kit_libs_ios_video', - description: 'iOS package providing video (& audio) native libraries for package:media_kit.', + description: + 'iOS package providing video (& audio) native libraries for package:media_kit.', homepage: 'https://github.com/media-kit/media-kit.git', repository: 'https://github.com/media-kit/media-kit.git', authors: [], @@ -6931,13 +8451,16 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.''', isMarkdown: false, isSdk: false, - dependencies: [PackageRef('flutter'), PackageRef('plugin_platform_interface')] - ); + dependencies: [ + PackageRef('flutter'), + PackageRef('plugin_platform_interface') + ]); /// media_kit_libs_linux 1.2.1 const _media_kit_libs_linux = Package( name: 'media_kit_libs_linux', - description: 'GNU/Linux dependency package for package:media_kit. Necessary for initialization.', + description: + 'GNU/Linux dependency package for package:media_kit. Necessary for initialization.', homepage: 'https://github.com/media-kit/media-kit.git', repository: 'https://github.com/media-kit/media-kit.git', authors: [], @@ -6965,13 +8488,13 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.''', isMarkdown: false, isSdk: false, - dependencies: [PackageRef('flutter')] - ); + dependencies: [PackageRef('flutter')]); /// media_kit_libs_macos_video 1.1.4 const _media_kit_libs_macos_video = Package( name: 'media_kit_libs_macos_video', - description: 'macOS package providing video (& audio) native libraries for package:media_kit.', + description: + 'macOS package providing video (& audio) native libraries for package:media_kit.', homepage: 'https://github.com/media-kit/media-kit.git', repository: 'https://github.com/media-kit/media-kit.git', authors: [], @@ -6999,13 +8522,16 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.''', isMarkdown: false, isSdk: false, - dependencies: [PackageRef('flutter'), PackageRef('plugin_platform_interface')] - ); + dependencies: [ + PackageRef('flutter'), + PackageRef('plugin_platform_interface') + ]); /// media_kit_libs_video 1.0.6 const _media_kit_libs_video = Package( name: 'media_kit_libs_video', - description: 'package:media_kit video (& audio) playback native libraries for all platforms.', + description: + 'package:media_kit video (& audio) playback native libraries for all platforms.', homepage: 'https://github.com/media-kit/media-kit.git', repository: 'https://github.com/media-kit/media-kit.git', authors: [], @@ -7033,13 +8559,19 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.''', isMarkdown: false, isSdk: false, - dependencies: [PackageRef('media_kit_libs_android_video'), PackageRef('media_kit_libs_ios_video'), PackageRef('media_kit_libs_macos_video'), PackageRef('media_kit_libs_windows_video'), PackageRef('media_kit_libs_linux')] - ); + dependencies: [ + PackageRef('media_kit_libs_android_video'), + PackageRef('media_kit_libs_ios_video'), + PackageRef('media_kit_libs_macos_video'), + PackageRef('media_kit_libs_windows_video'), + PackageRef('media_kit_libs_linux') + ]); /// media_kit_libs_windows_video 1.0.11 const _media_kit_libs_windows_video = Package( name: 'media_kit_libs_windows_video', - description: 'Windows package providing video (& audio) native libraries for package:media_kit.', + description: + 'Windows package providing video (& audio) native libraries for package:media_kit.', homepage: 'https://github.com/media-kit/media-kit.git', repository: 'https://github.com/media-kit/media-kit.git', authors: [], @@ -7067,13 +8599,13 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.''', isMarkdown: false, isSdk: false, - dependencies: [PackageRef('flutter')] - ); + dependencies: [PackageRef('flutter')]); /// media_kit_video 1.3.0 const _media_kit_video = Package( name: 'media_kit_video', - description: 'Native implementation for video playback in package:media_kit.', + description: + 'Native implementation for video playback in package:media_kit.', homepage: 'https://github.com/media-kit/media-kit', repository: 'https://github.com/media-kit/media-kit', authors: [], @@ -7101,8 +8633,18 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.''', isMarkdown: false, isSdk: false, - dependencies: [PackageRef('flutter'), PackageRef('media_kit'), PackageRef('synchronized'), PackageRef('wakelock_plus'), PackageRef('screen_brightness_android'), PackageRef('screen_brightness_platform_interface'), PackageRef('volume_controller'), PackageRef('universal_platform'), PackageRef('plugin_platform_interface'), PackageRef('web')] - ); + dependencies: [ + PackageRef('flutter'), + PackageRef('media_kit'), + PackageRef('synchronized'), + PackageRef('wakelock_plus'), + PackageRef('screen_brightness_android'), + PackageRef('screen_brightness_platform_interface'), + PackageRef('volume_controller'), + PackageRef('universal_platform'), + PackageRef('plugin_platform_interface'), + PackageRef('web') + ]); /// media_stream 0.0.1 const _media_stream = Package( @@ -7135,13 +8677,19 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.''', isMarkdown: false, isSdk: false, - dependencies: [PackageRef('flutter'), PackageRef('pure_ftp'), PackageRef('smb_connect'), PackageRef('shelf'), PackageRef('shelf_router')] - ); + dependencies: [ + PackageRef('flutter'), + PackageRef('pure_ftp'), + PackageRef('smb_connect'), + PackageRef('shelf'), + PackageRef('shelf_router') + ]); /// meta 1.16.0 const _meta = Package( name: 'meta', - description: "Annotations used to express developer intentions that can't otherwise be deduced by statically analyzing source code.", + description: + "Annotations used to express developer intentions that can't otherwise be deduced by statically analyzing source code.", repository: 'https://github.com/dart-lang/sdk/tree/main/pkg/meta', authors: [], version: '1.16.0', @@ -7174,13 +8722,13 @@ THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.''', isMarkdown: false, isSdk: false, - dependencies: [] - ); + dependencies: []); /// mime 2.0.0 const _mime = Package( name: 'mime', - description: 'Utilities for handling media (MIME) types, including determining a type from a file extension and file contents.', + description: + 'Utilities for handling media (MIME) types, including determining a type from a file extension and file contents.', repository: 'https://github.com/dart-lang/tools/tree/main/pkgs/mime', authors: [], version: '2.0.0', @@ -7213,16 +8761,16 @@ THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.''', isMarkdown: false, isSdk: false, - dependencies: [] - ); + dependencies: []); -/// msix 3.16.9 +/// msix 3.16.12 const _msix = Package( name: 'msix', - description: 'A command-line tool that create Msix installer from your flutter windows-build files.', + description: + 'A command-line tool that create Msix installer from your flutter windows-build files.', homepage: 'https://github.com/YehudaKremer/msix', authors: [], - version: '3.16.9', + version: '3.16.12', license: '''MIT License Copyright (c) 2022 Yehuda Kremer @@ -7246,13 +8794,23 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.''', isMarkdown: false, isSdk: false, - dependencies: [PackageRef('args'), PackageRef('yaml'), PackageRef('path'), PackageRef('package_config'), PackageRef('get_it'), PackageRef('image'), PackageRef('pub_semver'), PackageRef('console'), PackageRef('cli_util')] - ); + dependencies: [ + PackageRef('args'), + PackageRef('yaml'), + PackageRef('path'), + PackageRef('package_config'), + PackageRef('get_it'), + PackageRef('image'), + PackageRef('pub_semver'), + PackageRef('console'), + PackageRef('cli_util') + ]); /// mutex 3.1.0 const _mutex = Package( name: 'mutex', - description: 'Mutual exclusion with implementation of normal and read-write mutex', + description: + 'Mutual exclusion with implementation of normal and read-write mutex', homepage: 'https://github.com/hoylen/dart-mutex', authors: [], version: '3.1.0', @@ -7282,13 +8840,13 @@ ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.''', isMarkdown: false, isSdk: false, - dependencies: [] - ); + dependencies: []); /// nested 1.0.0 const _nested = Package( name: 'nested', - description: 'A Flutter Widget which helps nest multiple widgets without needing to manually nest them.', + description: + 'A Flutter Widget which helps nest multiple widgets without needing to manually nest them.', repository: 'https://github.com/rrousselGit/nested', authors: [], version: '1.0.0', @@ -7315,14 +8873,15 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.''', isMarkdown: false, isSdk: false, - dependencies: [PackageRef('flutter')] - ); + dependencies: [PackageRef('flutter')]); /// package_config 2.2.0 const _package_config = Package( name: 'package_config', - description: 'Support for reading and writing Dart Package Configuration files.', - repository: 'https://github.com/dart-lang/tools/tree/main/pkgs/package_config', + description: + 'Support for reading and writing Dart Package Configuration files.', + repository: + 'https://github.com/dart-lang/tools/tree/main/pkgs/package_config', authors: [], version: '2.2.0', license: '''Copyright 2019, the Dart project authors. @@ -7354,17 +8913,18 @@ THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.''', isMarkdown: false, isSdk: false, - dependencies: [PackageRef('path')] - ); + dependencies: [PackageRef('path')]); -/// package_info_plus 8.3.0 +/// package_info_plus 9.0.0 const _package_info_plus = Package( name: 'package_info_plus', - description: 'Flutter plugin for querying information about the application package, such as CFBundleVersion on iOS or versionCode on Android.', + description: + 'Flutter plugin for querying information about the application package, such as CFBundleVersion on iOS or versionCode on Android.', homepage: 'https://github.com/fluttercommunity/plus_plugins', - repository: 'https://github.com/fluttercommunity/plus_plugins/tree/main/packages/package_info_plus/package_info_plus', + repository: + 'https://github.com/fluttercommunity/plus_plugins/tree/main/packages/package_info_plus/package_info_plus', authors: [], - version: '8.3.0', + version: '9.0.0', license: '''Copyright 2017 The Chromium Authors. All rights reserved. Redistribution and use in source and binary forms, with or without @@ -7394,17 +8954,29 @@ THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.''', isMarkdown: false, isSdk: false, - dependencies: [PackageRef('ffi'), PackageRef('flutter'), PackageRef('http'), PackageRef('meta'), PackageRef('path'), PackageRef('package_info_plus_platform_interface'), PackageRef('web'), PackageRef('win32'), PackageRef('clock')] - ); - -/// package_info_plus_platform_interface 3.2.0 + dependencies: [ + PackageRef('ffi'), + PackageRef('flutter'), + PackageRef('flutter_web_plugins'), + PackageRef('http'), + PackageRef('meta'), + PackageRef('path'), + PackageRef('package_info_plus_platform_interface'), + PackageRef('web'), + PackageRef('win32'), + PackageRef('clock') + ]); + +/// package_info_plus_platform_interface 3.2.1 const _package_info_plus_platform_interface = Package( name: 'package_info_plus_platform_interface', - description: 'A common platform interface for the package_info_plus plugin.', + description: + 'A common platform interface for the package_info_plus plugin.', homepage: 'https://github.com/fluttercommunity/plus_plugins', - repository: 'https://github.com/fluttercommunity/plus_plugins/tree/main/packages/', + repository: + 'https://github.com/fluttercommunity/plus_plugins/tree/main/packages/', authors: [], - version: '3.2.0', + version: '3.2.1', license: '''Copyright 2017 The Chromium Authors. All rights reserved. Redistribution and use in source and binary forms, with or without @@ -7434,13 +9006,17 @@ THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.''', isMarkdown: false, isSdk: false, - dependencies: [PackageRef('flutter'), PackageRef('meta'), PackageRef('plugin_platform_interface')] - ); + dependencies: [ + PackageRef('flutter'), + PackageRef('meta'), + PackageRef('plugin_platform_interface') + ]); /// path 1.9.1 const _path = Package( name: 'path', - description: 'A string-based path manipulation library. All of the path operations you know and love, with solid support for Windows, POSIX (Linux and Mac OS X), and the web.', + description: + 'A string-based path manipulation library. All of the path operations you know and love, with solid support for Windows, POSIX (Linux and Mac OS X), and the web.', repository: 'https://github.com/dart-lang/core/tree/main/pkgs/path', authors: [], version: '1.9.1', @@ -7473,14 +9049,15 @@ THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.''', isMarkdown: false, isSdk: false, - dependencies: [] - ); + dependencies: []); /// path_provider 2.1.5 const _path_provider = Package( name: 'path_provider', - description: 'Flutter plugin for getting commonly used locations on host platform file systems, such as the temp and app data directories.', - repository: 'https://github.com/flutter/packages/tree/main/packages/path_provider/path_provider', + description: + 'Flutter plugin for getting commonly used locations on host platform file systems, such as the temp and app data directories.', + repository: + 'https://github.com/flutter/packages/tree/main/packages/path_provider/path_provider', authors: [], version: '2.1.5', license: '''Copyright 2013 The Flutter Authors. All rights reserved. @@ -7510,16 +9087,23 @@ ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.''', isMarkdown: false, isSdk: false, - dependencies: [PackageRef('flutter'), PackageRef('path_provider_android'), PackageRef('path_provider_foundation'), PackageRef('path_provider_linux'), PackageRef('path_provider_platform_interface'), PackageRef('path_provider_windows')] - ); + dependencies: [ + PackageRef('flutter'), + PackageRef('path_provider_android'), + PackageRef('path_provider_foundation'), + PackageRef('path_provider_linux'), + PackageRef('path_provider_platform_interface'), + PackageRef('path_provider_windows') + ]); -/// path_provider_android 2.2.17 +/// path_provider_android 2.2.18 const _path_provider_android = Package( name: 'path_provider_android', description: 'Android implementation of the path_provider plugin.', - repository: 'https://github.com/flutter/packages/tree/main/packages/path_provider/path_provider_android', + repository: + 'https://github.com/flutter/packages/tree/main/packages/path_provider/path_provider_android', authors: [], - version: '2.2.17', + version: '2.2.18', license: '''Copyright 2013 The Flutter Authors. All rights reserved. Redistribution and use in source and binary forms, with or without modification, @@ -7547,16 +9131,19 @@ ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.''', isMarkdown: false, isSdk: false, - dependencies: [PackageRef('flutter'), PackageRef('path_provider_platform_interface')] - ); + dependencies: [ + PackageRef('flutter'), + PackageRef('path_provider_platform_interface') + ]); -/// path_provider_foundation 2.4.1 +/// path_provider_foundation 2.4.2 const _path_provider_foundation = Package( name: 'path_provider_foundation', description: 'iOS and macOS implementation of the path_provider plugin', - repository: 'https://github.com/flutter/packages/tree/main/packages/path_provider/path_provider_foundation', + repository: + 'https://github.com/flutter/packages/tree/main/packages/path_provider/path_provider_foundation', authors: [], - version: '2.4.1', + version: '2.4.2', license: '''Copyright 2013 The Flutter Authors. All rights reserved. Redistribution and use in source and binary forms, with or without modification, @@ -7584,14 +9171,17 @@ ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.''', isMarkdown: false, isSdk: false, - dependencies: [PackageRef('flutter'), PackageRef('path_provider_platform_interface')] - ); + dependencies: [ + PackageRef('flutter'), + PackageRef('path_provider_platform_interface') + ]); /// path_provider_linux 2.2.1 const _path_provider_linux = Package( name: 'path_provider_linux', description: 'Linux implementation of the path_provider plugin', - repository: 'https://github.com/flutter/packages/tree/main/packages/path_provider/path_provider_linux', + repository: + 'https://github.com/flutter/packages/tree/main/packages/path_provider/path_provider_linux', authors: [], version: '2.2.1', license: '''Copyright 2013 The Flutter Authors. All rights reserved. @@ -7621,14 +9211,20 @@ ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.''', isMarkdown: false, isSdk: false, - dependencies: [PackageRef('ffi'), PackageRef('flutter'), PackageRef('path'), PackageRef('path_provider_platform_interface'), PackageRef('xdg_directories')] - ); + dependencies: [ + PackageRef('ffi'), + PackageRef('flutter'), + PackageRef('path'), + PackageRef('path_provider_platform_interface'), + PackageRef('xdg_directories') + ]); /// path_provider_platform_interface 2.1.2 const _path_provider_platform_interface = Package( name: 'path_provider_platform_interface', description: 'A common platform interface for the path_provider plugin.', - repository: 'https://github.com/flutter/packages/tree/main/packages/path_provider/path_provider_platform_interface', + repository: + 'https://github.com/flutter/packages/tree/main/packages/path_provider/path_provider_platform_interface', authors: [], version: '2.1.2', license: '''Copyright 2013 The Flutter Authors. All rights reserved. @@ -7658,14 +9254,18 @@ ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.''', isMarkdown: false, isSdk: false, - dependencies: [PackageRef('flutter'), PackageRef('platform'), PackageRef('plugin_platform_interface')] - ); + dependencies: [ + PackageRef('flutter'), + PackageRef('platform'), + PackageRef('plugin_platform_interface') + ]); /// path_provider_windows 2.3.0 const _path_provider_windows = Package( name: 'path_provider_windows', description: 'Windows implementation of the path_provider plugin', - repository: 'https://github.com/flutter/packages/tree/main/packages/path_provider/path_provider_windows', + repository: + 'https://github.com/flutter/packages/tree/main/packages/path_provider/path_provider_windows', authors: [], version: '2.3.0', license: '''Copyright 2013 The Flutter Authors. All rights reserved. @@ -7695,13 +9295,18 @@ ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.''', isMarkdown: false, isSdk: false, - dependencies: [PackageRef('ffi'), PackageRef('flutter'), PackageRef('path'), PackageRef('path_provider_platform_interface')] - ); + dependencies: [ + PackageRef('ffi'), + PackageRef('flutter'), + PackageRef('path'), + PackageRef('path_provider_platform_interface') + ]); /// pedantic 1.11.1 const _pedantic = Package( name: 'pedantic', - description: 'The Dart analyzer settings and best practices used internally at Google.', + description: + 'The Dart analyzer settings and best practices used internally at Google.', homepage: 'https://github.com/google/pedantic', authors: [], version: '1.11.1', @@ -7733,16 +9338,16 @@ THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.''', isMarkdown: false, isSdk: false, - dependencies: [] - ); + dependencies: []); -/// permission_handler 12.0.0+1 +/// permission_handler 12.0.1 const _permission_handler = Package( name: 'permission_handler', - description: 'Permission plugin for Flutter. This plugin provides a cross-platform (iOS, Android) API to request and check permissions.', + description: + 'Permission plugin for Flutter. This plugin provides a cross-platform (iOS, Android) API to request and check permissions.', repository: 'https://github.com/baseflow/flutter-permission-handler', authors: [], - version: '12.0.0+1', + version: '12.0.1', license: '''MIT License Copyright (c) 2018 Baseflow @@ -7766,13 +9371,21 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.''', isMarkdown: false, isSdk: false, - dependencies: [PackageRef('flutter'), PackageRef('meta'), PackageRef('permission_handler_android'), PackageRef('permission_handler_apple'), PackageRef('permission_handler_html'), PackageRef('permission_handler_windows'), PackageRef('permission_handler_platform_interface')] - ); + dependencies: [ + PackageRef('flutter'), + PackageRef('meta'), + PackageRef('permission_handler_android'), + PackageRef('permission_handler_apple'), + PackageRef('permission_handler_html'), + PackageRef('permission_handler_windows'), + PackageRef('permission_handler_platform_interface') + ]); /// permission_handler_android 13.0.1 const _permission_handler_android = Package( name: 'permission_handler_android', - description: 'Permission plugin for Flutter. This plugin provides the Android API to request and check permissions.', + description: + 'Permission plugin for Flutter. This plugin provides the Android API to request and check permissions.', homepage: 'https://github.com/baseflow/flutter-permission-handler', authors: [], version: '13.0.1', @@ -7799,13 +9412,16 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.''', isMarkdown: false, isSdk: false, - dependencies: [PackageRef('flutter'), PackageRef('permission_handler_platform_interface')] - ); + dependencies: [ + PackageRef('flutter'), + PackageRef('permission_handler_platform_interface') + ]); /// permission_handler_apple 9.4.7 const _permission_handler_apple = Package( name: 'permission_handler_apple', - description: 'Permission plugin for Flutter. This plugin provides the iOS API to request and check permissions.', + description: + 'Permission plugin for Flutter. This plugin provides the iOS API to request and check permissions.', repository: 'https://github.com/baseflow/flutter-permission-handler', authors: [], version: '9.4.7', @@ -7832,13 +9448,16 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.''', isMarkdown: false, isSdk: false, - dependencies: [PackageRef('flutter'), PackageRef('permission_handler_platform_interface')] - ); + dependencies: [ + PackageRef('flutter'), + PackageRef('permission_handler_platform_interface') + ]); /// permission_handler_html 0.1.3+5 const _permission_handler_html = Package( name: 'permission_handler_html', - description: 'Permission plugin for Flutter. This plugin provides the web API to request and check permissions.', + description: + 'Permission plugin for Flutter. This plugin provides the web API to request and check permissions.', homepage: 'https://github.com/baseflow/flutter-permission-handler', authors: [], version: '0.1.3+5', @@ -7865,14 +9484,20 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.''', isMarkdown: false, isSdk: false, - dependencies: [PackageRef('flutter'), PackageRef('permission_handler_platform_interface'), PackageRef('web')] - ); + dependencies: [ + PackageRef('flutter'), + PackageRef('flutter_web_plugins'), + PackageRef('permission_handler_platform_interface'), + PackageRef('web') + ]); /// permission_handler_platform_interface 4.3.0 const _permission_handler_platform_interface = Package( name: 'permission_handler_platform_interface', - description: 'A common platform interface for the permission_handler plugin.', - homepage: 'https://github.com/baseflow/flutter-permission-handler/tree/master/permission_handler_platform_interface', + description: + 'A common platform interface for the permission_handler plugin.', + homepage: + 'https://github.com/baseflow/flutter-permission-handler/tree/master/permission_handler_platform_interface', authors: [], version: '4.3.0', license: '''MIT License @@ -7898,13 +9523,17 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.''', isMarkdown: false, isSdk: false, - dependencies: [PackageRef('flutter'), PackageRef('meta'), PackageRef('plugin_platform_interface')] - ); + dependencies: [ + PackageRef('flutter'), + PackageRef('meta'), + PackageRef('plugin_platform_interface') + ]); /// permission_handler_windows 0.2.1 const _permission_handler_windows = Package( name: 'permission_handler_windows', - description: 'Permission plugin for Flutter. This plugin provides the Windows API to request and check permissions.', + description: + 'Permission plugin for Flutter. This plugin provides the Windows API to request and check permissions.', homepage: 'https://github.com/baseflow/flutter-permission-handler', authors: [], version: '0.2.1', @@ -7931,20 +9560,23 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.''', isMarkdown: false, isSdk: false, - dependencies: [PackageRef('flutter'), PackageRef('permission_handler_platform_interface')] - ); + dependencies: [ + PackageRef('flutter'), + PackageRef('permission_handler_platform_interface') + ]); -/// petitparser 6.1.0 +/// petitparser 7.0.1 const _petitparser = Package( name: 'petitparser', - description: 'A dynamic parser framework to build efficient grammars and parsers quickly.', + description: + 'A dynamic parser framework to build efficient grammars and parsers quickly.', homepage: 'https://petitparser.github.io', repository: 'https://github.com/petitparser/dart-petitparser', authors: [], - version: '6.1.0', + version: '7.0.1', license: '''The MIT License -Copyright (c) 2006-2025 Lukas Renggli. +Copyright (c) 2006-2024 Lukas Renggli. All rights reserved. Permission is hereby granted, free of charge, to any person obtaining a copy @@ -7966,13 +9598,13 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.''', isMarkdown: false, isSdk: false, - dependencies: [PackageRef('meta'), PackageRef('collection')] - ); + dependencies: [PackageRef('meta'), PackageRef('collection')]); /// platform 3.1.6 const _platform = Package( name: 'platform', - description: 'A pluggable, mockable platform information abstraction for Dart.', + description: + 'A pluggable, mockable platform information abstraction for Dart.', repository: 'https://github.com/dart-lang/core/tree/main/pkgs/platform', authors: [], version: '3.1.6', @@ -8004,14 +9636,15 @@ THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.''', isMarkdown: false, isSdk: false, - dependencies: [] - ); + dependencies: []); /// plugin_platform_interface 2.1.8 const _plugin_platform_interface = Package( name: 'plugin_platform_interface', - description: 'Reusable base class for platform interfaces of Flutter federated plugins, to help enforce best practices.', - repository: 'https://github.com/flutter/packages/tree/main/packages/plugin_platform_interface', + description: + 'Reusable base class for platform interfaces of Flutter federated plugins, to help enforce best practices.', + repository: + 'https://github.com/flutter/packages/tree/main/packages/plugin_platform_interface', authors: [], version: '2.1.8', license: '''Copyright 2013 The Flutter Authors. All rights reserved. @@ -8041,17 +9674,18 @@ ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.''', isMarkdown: false, isSdk: false, - dependencies: [PackageRef('meta')] - ); + dependencies: [PackageRef('meta')]); /// pointycastle 3.9.1 const _pointycastle = Package( name: 'pointycastle', - description: 'A Dart library implementing cryptographic algorithms and primitives, modeled on the BouncyCastle library.', + description: + 'A Dart library implementing cryptographic algorithms and primitives, modeled on the BouncyCastle library.', homepage: 'https://github.com/bcgit/pc-dart', authors: [], version: '3.9.1', - license: '''Copyright (c) 2000 - 2019 The Legion of the Bouncy Castle Inc. (https://www.bouncycastle.org) + license: + '''Copyright (c) 2000 - 2019 The Legion of the Bouncy Castle Inc. (https://www.bouncycastle.org) Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in @@ -8071,13 +9705,17 @@ IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.''', isMarkdown: false, isSdk: false, - dependencies: [PackageRef('collection'), PackageRef('convert'), PackageRef('js')] - ); + dependencies: [ + PackageRef('collection'), + PackageRef('convert'), + PackageRef('js') + ]); /// pool 1.5.1 const _pool = Package( name: 'pool', - description: 'Manage a finite pool of resources. Useful for controlling concurrent file system or network requests.', + description: + 'Manage a finite pool of resources. Useful for controlling concurrent file system or network requests.', repository: 'https://github.com/dart-lang/pool', authors: [], version: '1.5.1', @@ -8110,13 +9748,13 @@ THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.''', isMarkdown: false, isSdk: false, - dependencies: [PackageRef('async'), PackageRef('stack_trace')] - ); + dependencies: [PackageRef('async'), PackageRef('stack_trace')]); /// popover 0.3.1 const _popover = Package( name: 'popover', - description: 'A popover is a transient view that appears above other content onscreen when you tap a control or in an area.', + description: + 'A popover is a transient view that appears above other content onscreen when you tap a control or in an area.', homepage: 'https://github.com/minikin/popover', authors: [], version: '0.3.1', @@ -8143,16 +9781,15 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.''', isMarkdown: false, isSdk: false, - dependencies: [PackageRef('flutter')] - ); + dependencies: [PackageRef('flutter')]); -/// posix 6.0.2 +/// posix 6.0.3 const _posix = Package( name: 'posix', description: 'Exposes the POSIX api on OSx and Linux', homepage: 'https://github.com/onepub-dev/dart_posix', authors: [], - version: '6.0.2', + version: '6.0.3', license: '''MIT License Copyright (c) 2020 Brett Sutton @@ -8176,16 +9813,16 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.''', isMarkdown: false, isSdk: false, - dependencies: [PackageRef('ffi'), PackageRef('meta'), PackageRef('path')] - ); + dependencies: [PackageRef('ffi'), PackageRef('meta'), PackageRef('path')]); -/// provider 6.1.5 +/// provider 6.1.5+1 const _provider = Package( name: 'provider', - description: 'A wrapper around InheritedWidget to make them easier to use and more reusable.', + description: + 'A wrapper around InheritedWidget to make them easier to use and more reusable.', repository: 'https://github.com/rrousselGit/provider', authors: [], - version: '6.1.5', + version: '6.1.5+1', license: '''MIT License Copyright (c) 2019 Remi Rousselet @@ -8209,13 +9846,17 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.''', isMarkdown: false, isSdk: false, - dependencies: [PackageRef('collection'), PackageRef('flutter'), PackageRef('nested')] - ); + dependencies: [ + PackageRef('collection'), + PackageRef('flutter'), + PackageRef('nested') + ]); /// pub_semver 2.2.0 const _pub_semver = Package( name: 'pub_semver', - description: "Versions and version constraints implementing pub's versioning policy. This is very similar to vanilla semver, with a few corner cases.", + description: + "Versions and version constraints implementing pub's versioning policy. This is very similar to vanilla semver, with a few corner cases.", repository: 'https://github.com/dart-lang/tools/tree/main/pkgs/pub_semver', authors: [], version: '2.2.0', @@ -8248,14 +9889,15 @@ THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.''', isMarkdown: false, isSdk: false, - dependencies: [PackageRef('collection')] - ); + dependencies: [PackageRef('collection')]); /// pubspec_parse 1.5.0 const _pubspec_parse = Package( name: 'pubspec_parse', - description: 'Simple package for parsing pubspec.yaml files with a type-safe API and rich error reporting.', - repository: 'https://github.com/dart-lang/tools/tree/main/pkgs/pubspec_parse', + description: + 'Simple package for parsing pubspec.yaml files with a type-safe API and rich error reporting.', + repository: + 'https://github.com/dart-lang/tools/tree/main/pkgs/pubspec_parse', authors: [], version: '1.5.0', license: '''Copyright 2018, the Dart project authors. @@ -8287,13 +9929,19 @@ THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.''', isMarkdown: false, isSdk: false, - dependencies: [PackageRef('checked_yaml'), PackageRef('collection'), PackageRef('json_annotation'), PackageRef('pub_semver'), PackageRef('yaml')] - ); + dependencies: [ + PackageRef('checked_yaml'), + PackageRef('collection'), + PackageRef('json_annotation'), + PackageRef('pub_semver'), + PackageRef('yaml') + ]); /// pure_ftp 0.7.5 const _pure_ftp = Package( name: 'pure_ftp', - description: 'Simple and powerful FTP client for Dart. Allow you to connect to FTP server and perform basic operations. now supports all native platforms(Web in progress)', + description: + 'Simple and powerful FTP client for Dart. Allow you to connect to FTP server and perform basic operations. now supports all native platforms(Web in progress)', homepage: 'https://github.com/crifurch/pure_ftp', authors: [], version: '0.7.5', @@ -8320,16 +9968,229 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.''', isMarkdown: false, isSdk: false, - dependencies: [PackageRef('meta')] - ); + dependencies: [PackageRef('meta')]); -/// saf_util 0.10.0 +/// rxdart 0.28.0 +const _rxdart = Package( + name: 'rxdart', + description: + '''RxDart is an implementation of the popular ReactiveX api for asynchronous programming, leveraging the native Dart Streams api. +''', + repository: 'https://github.com/ReactiveX/rxdart', + authors: [], + version: '0.28.0', + license: '''Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License.''', + isMarkdown: false, + isSdk: false, + dependencies: []); + +/// saf_util 0.11.0 const _saf_util = Package( name: 'saf_util', description: 'Util functions for SAF (Storage Access Framework).', homepage: 'https://github.com/flutter-cavalry/saf_util', authors: [], - version: '0.10.0', + version: '0.11.0', license: '''BSD 3-Clause License Copyright (c) 2023, Mgenware (Liu YuanYuan) @@ -8361,13 +10222,16 @@ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.''', isMarkdown: false, isSdk: false, - dependencies: [PackageRef('flutter'), PackageRef('plugin_platform_interface')] - ); + dependencies: [ + PackageRef('flutter'), + PackageRef('plugin_platform_interface') + ]); /// safe_local_storage 2.0.1 const _safe_local_storage = Package( name: 'safe_local_storage', - description: 'A safe caching library to read/write values on local storage.', + description: + 'A safe caching library to read/write values on local storage.', homepage: 'https://github.com/alexmercerind/safe_local_storage.git', repository: 'https://github.com/alexmercerind/safe_local_storage.git', authors: [], @@ -8395,17 +10259,18 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.''', isMarkdown: false, isSdk: false, - dependencies: [PackageRef('path'), PackageRef('synchronized')] - ); + dependencies: [PackageRef('path'), PackageRef('synchronized')]); -/// screen_brightness 2.1.4 +/// screen_brightness 2.1.7 const _screen_brightness = Package( name: 'screen_brightness', - description: 'A Plugin for controlling screen brightness with application life cycle reset implemented', + description: + 'A Plugin for controlling screen brightness with application life cycle reset implemented', homepage: 'https://github.com/aaassseee/screen_brightness', - repository: 'https://github.com/aaassseee/screen_brightness/tree/master/screen_brightness', + repository: + 'https://github.com/aaassseee/screen_brightness/tree/master/screen_brightness', authors: [], - version: '2.1.4', + version: '2.1.7', license: '''MIT License Copyright (c) 2021 Jack Liu @@ -8429,17 +10294,26 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.''', isMarkdown: false, isSdk: false, - dependencies: [PackageRef('flutter'), PackageRef('screen_brightness_platform_interface'), PackageRef('screen_brightness_android'), PackageRef('screen_brightness_ios'), PackageRef('screen_brightness_macos'), PackageRef('screen_brightness_windows'), PackageRef('screen_brightness_ohos')] - ); + dependencies: [ + PackageRef('flutter'), + PackageRef('screen_brightness_platform_interface'), + PackageRef('screen_brightness_android'), + PackageRef('screen_brightness_ios'), + PackageRef('screen_brightness_macos'), + PackageRef('screen_brightness_windows'), + PackageRef('screen_brightness_ohos') + ]); -/// screen_brightness_android 2.1.1 +/// screen_brightness_android 2.1.3 const _screen_brightness_android = Package( name: 'screen_brightness_android', - description: 'The Android federated plugin implementation of screen_brightness.', + description: + 'The Android federated plugin implementation of screen_brightness.', homepage: 'https://github.com/aaassseee/screen_brightness', - repository: 'https://github.com/aaassseee/screen_brightness/tree/master/screen_brightness_android', + repository: + 'https://github.com/aaassseee/screen_brightness/tree/master/screen_brightness_android', authors: [], - version: '2.1.1', + version: '2.1.3', license: '''MIT License Copyright (c) 2021 Jack Liu @@ -8463,15 +10337,19 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.''', isMarkdown: false, isSdk: false, - dependencies: [PackageRef('flutter'), PackageRef('screen_brightness_platform_interface')] - ); + dependencies: [ + PackageRef('flutter'), + PackageRef('screen_brightness_platform_interface') + ]); /// screen_brightness_ios 2.1.2 const _screen_brightness_ios = Package( name: 'screen_brightness_ios', - description: 'The iOS federated plugin implementation of the screen_brightness.', + description: + 'The iOS federated plugin implementation of the screen_brightness.', homepage: 'https://github.com/aaassseee/screen_brightness', - repository: 'https://github.com/aaassseee/screen_brightness/tree/master/screen_brightness_ios', + repository: + 'https://github.com/aaassseee/screen_brightness/tree/master/screen_brightness_ios', authors: [], version: '2.1.2', license: '''MIT License @@ -8497,15 +10375,19 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.''', isMarkdown: false, isSdk: false, - dependencies: [PackageRef('flutter'), PackageRef('screen_brightness_platform_interface')] - ); + dependencies: [ + PackageRef('flutter'), + PackageRef('screen_brightness_platform_interface') + ]); /// screen_brightness_macos 2.1.1 const _screen_brightness_macos = Package( name: 'screen_brightness_macos', - description: 'The macOS federated plugin implementation of the screen_brightness.', + description: + 'The macOS federated plugin implementation of the screen_brightness.', homepage: 'https://github.com/aaassseee/screen_brightness', - repository: 'https://github.com/aaassseee/screen_brightness/tree/master/screen_brightness_macos', + repository: + 'https://github.com/aaassseee/screen_brightness/tree/master/screen_brightness_macos', authors: [], version: '2.1.1', license: '''MIT License @@ -8531,17 +10413,21 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.''', isMarkdown: false, isSdk: false, - dependencies: [PackageRef('flutter'), PackageRef('screen_brightness_platform_interface')] - ); + dependencies: [ + PackageRef('flutter'), + PackageRef('screen_brightness_platform_interface') + ]); -/// screen_brightness_ohos 2.1.0 +/// screen_brightness_ohos 2.1.2 const _screen_brightness_ohos = Package( name: 'screen_brightness_ohos', - description: 'The ohos federated plugin implementation of screen_brightness.', + description: + 'The ohos federated plugin implementation of screen_brightness.', homepage: 'https://github.com/aaassseee/screen_brightness', - repository: 'https://github.com/aaassseee/screen_brightness/tree/master/screen_brightness_ohos', + repository: + 'https://github.com/aaassseee/screen_brightness/tree/master/screen_brightness_ohos', authors: [], - version: '2.1.0', + version: '2.1.2', license: '''MIT License Copyright (c) 2025 ErBWs, Jack Liu @@ -8565,15 +10451,18 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.''', isMarkdown: false, isSdk: false, - dependencies: [PackageRef('flutter'), PackageRef('screen_brightness_platform_interface')] - ); + dependencies: [ + PackageRef('flutter'), + PackageRef('screen_brightness_platform_interface') + ]); /// screen_brightness_platform_interface 2.1.0 const _screen_brightness_platform_interface = Package( name: 'screen_brightness_platform_interface', description: 'A common platform interface for the screen_brightness plugin', homepage: 'https://github.com/aaassseee/screen_brightness', - repository: 'https://github.com/aaassseee/screen_brightness/tree/patch-1/screen_brightness_platform_interface', + repository: + 'https://github.com/aaassseee/screen_brightness/tree/patch-1/screen_brightness_platform_interface', authors: [], version: '2.1.0', license: '''MIT License @@ -8599,15 +10488,19 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.''', isMarkdown: false, isSdk: false, - dependencies: [PackageRef('flutter'), PackageRef('plugin_platform_interface')] - ); + dependencies: [ + PackageRef('flutter'), + PackageRef('plugin_platform_interface') + ]); /// screen_brightness_windows 2.1.0 const _screen_brightness_windows = Package( name: 'screen_brightness_windows', - description: 'The Windows federated plugin implementation of the screen_brightness.', + description: + 'The Windows federated plugin implementation of the screen_brightness.', homepage: 'https://github.com/aaassseee/screen_brightness', - repository: 'https://github.com/aaassseee/screen_brightness/tree/master/screen_brightness_windows', + repository: + 'https://github.com/aaassseee/screen_brightness/tree/master/screen_brightness_windows', authors: [], version: '2.1.0', license: '''MIT License @@ -8633,13 +10526,16 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.''', isMarkdown: false, isSdk: false, - dependencies: [PackageRef('flutter'), PackageRef('screen_brightness_platform_interface')] - ); + dependencies: [ + PackageRef('flutter'), + PackageRef('screen_brightness_platform_interface') + ]); /// screen_retriever 0.2.0 const _screen_retriever = Package( name: 'screen_retriever', - description: 'This plugin allows Flutter desktop apps to Retrieve information about screen size, displays, cursor position, etc.', + description: + 'This plugin allows Flutter desktop apps to Retrieve information about screen size, displays, cursor position, etc.', homepage: 'https://github.com/leanflutter/screen_retriever', authors: [], version: '0.2.0', @@ -8666,14 +10562,20 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.''', isMarkdown: false, isSdk: false, - dependencies: [PackageRef('flutter'), PackageRef('screen_retriever_linux'), PackageRef('screen_retriever_macos'), PackageRef('screen_retriever_platform_interface'), PackageRef('screen_retriever_windows')] - ); + dependencies: [ + PackageRef('flutter'), + PackageRef('screen_retriever_linux'), + PackageRef('screen_retriever_macos'), + PackageRef('screen_retriever_platform_interface'), + PackageRef('screen_retriever_windows') + ]); /// screen_retriever_linux 0.2.0 const _screen_retriever_linux = Package( name: 'screen_retriever_linux', description: 'Linux implementation of the screen_retriever plugin.', - repository: 'https://github.com/leanflutter/screen_retriever/tree/main/packages/screen_retriever_linux', + repository: + 'https://github.com/leanflutter/screen_retriever/tree/main/packages/screen_retriever_linux', authors: [], version: '0.2.0', license: '''MIT License @@ -8699,14 +10601,17 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.''', isMarkdown: false, isSdk: false, - dependencies: [PackageRef('flutter'), PackageRef('screen_retriever_platform_interface')] - ); + dependencies: [ + PackageRef('flutter'), + PackageRef('screen_retriever_platform_interface') + ]); /// screen_retriever_macos 0.2.0 const _screen_retriever_macos = Package( name: 'screen_retriever_macos', description: 'macOS implementation of the screen_retriever plugin.', - repository: 'https://github.com/leanflutter/screen_retriever/tree/main/packages/screen_retriever_macos', + repository: + 'https://github.com/leanflutter/screen_retriever/tree/main/packages/screen_retriever_macos', authors: [], version: '0.2.0', license: '''MIT License @@ -8732,14 +10637,17 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.''', isMarkdown: false, isSdk: false, - dependencies: [PackageRef('flutter'), PackageRef('screen_retriever_platform_interface')] - ); + dependencies: [ + PackageRef('flutter'), + PackageRef('screen_retriever_platform_interface') + ]); /// screen_retriever_platform_interface 0.2.0 const _screen_retriever_platform_interface = Package( name: 'screen_retriever_platform_interface', description: 'A common platform interface for the screen_retriever plugin.', - homepage: 'https://github.com/leanflutter/screen_retriever/blob/main/packages/screen_retriever_platform_interface', + homepage: + 'https://github.com/leanflutter/screen_retriever/blob/main/packages/screen_retriever_platform_interface', authors: [], version: '0.2.0', license: '''MIT License @@ -8765,14 +10673,18 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.''', isMarkdown: false, isSdk: false, - dependencies: [PackageRef('flutter'), PackageRef('json_annotation'), PackageRef('plugin_platform_interface')] - ); + dependencies: [ + PackageRef('flutter'), + PackageRef('json_annotation'), + PackageRef('plugin_platform_interface') + ]); /// screen_retriever_windows 0.2.0 const _screen_retriever_windows = Package( name: 'screen_retriever_windows', description: 'Windows implementation of the screen_retriever plugin.', - repository: 'https://github.com/leanflutter/screen_retriever/tree/main/packages/screen_retriever_windows', + repository: + 'https://github.com/leanflutter/screen_retriever/tree/main/packages/screen_retriever_windows', authors: [], version: '0.2.0', license: '''MIT License @@ -8798,18 +10710,23 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.''', isMarkdown: false, isSdk: false, - dependencies: [PackageRef('flutter'), PackageRef('screen_retriever_platform_interface')] - ); + dependencies: [ + PackageRef('flutter'), + PackageRef('screen_retriever_platform_interface') + ]); /// scrollable_positioned_list 0.3.8 const _scrollable_positioned_list = Package( name: 'scrollable_positioned_list', - description: '''A list with helper methods to programmatically scroll to an item. + description: + '''A list with helper methods to programmatically scroll to an item. ''', - homepage: 'https://github.com/google/flutter.widgets/tree/master/packages/scrollable_positioned_list', + homepage: + 'https://github.com/google/flutter.widgets/tree/master/packages/scrollable_positioned_list', authors: [], version: '0.3.8', - license: '''Copyright 2018 the Dart project authors, Inc. All rights reserved. + license: + '''Copyright 2018 the Dart project authors, Inc. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: @@ -8837,13 +10754,13 @@ THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.''', isMarkdown: false, isSdk: false, - dependencies: [PackageRef('flutter'), PackageRef('collection')] - ); + dependencies: [PackageRef('flutter'), PackageRef('collection')]); /// shelf 1.4.2 const _shelf = Package( name: 'shelf', - description: '''A model for web server middleware that encourages composition and easy reuse. + description: + '''A model for web server middleware that encourages composition and easy reuse. ''', repository: 'https://github.com/dart-lang/shelf/tree/master/pkgs/shelf', authors: [], @@ -8877,15 +10794,23 @@ THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.''', isMarkdown: false, isSdk: false, - dependencies: [PackageRef('async'), PackageRef('collection'), PackageRef('http_parser'), PackageRef('path'), PackageRef('stack_trace'), PackageRef('stream_channel')] - ); + dependencies: [ + PackageRef('async'), + PackageRef('collection'), + PackageRef('http_parser'), + PackageRef('path'), + PackageRef('stack_trace'), + PackageRef('stream_channel') + ]); /// shelf_router 1.1.4 const _shelf_router = Package( name: 'shelf_router', - description: '''A convenient request router for the shelf web-framework, with support for URL-parameters, nested routers and routers generated from source annotations. + description: + '''A convenient request router for the shelf web-framework, with support for URL-parameters, nested routers and routers generated from source annotations. ''', - repository: 'https://github.com/dart-lang/shelf/tree/master/pkgs/shelf_router', + repository: + 'https://github.com/dart-lang/shelf/tree/master/pkgs/shelf_router', authors: [], version: '1.1.4', license: '''Apache License @@ -9091,14 +11016,19 @@ const _shelf_router = Package( limitations under the License.''', isMarkdown: false, isSdk: false, - dependencies: [PackageRef('http_methods'), PackageRef('meta'), PackageRef('shelf')] - ); + dependencies: [ + PackageRef('http_methods'), + PackageRef('meta'), + PackageRef('shelf') + ]); /// shelf_web_socket 3.0.0 const _shelf_web_socket = Package( name: 'shelf_web_socket', - description: 'A shelf handler that wires up a listener for every connection.', - repository: 'https://github.com/dart-lang/shelf/tree/master/pkgs/shelf_web_socket', + description: + 'A shelf handler that wires up a listener for every connection.', + repository: + 'https://github.com/dart-lang/shelf/tree/master/pkgs/shelf_web_socket', authors: [], version: '3.0.0', license: '''Copyright 2014, the Dart project authors. @@ -9130,13 +11060,17 @@ THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.''', isMarkdown: false, isSdk: false, - dependencies: [PackageRef('shelf'), PackageRef('stream_channel'), PackageRef('web_socket_channel')] - ); + dependencies: [ + PackageRef('shelf'), + PackageRef('stream_channel'), + PackageRef('web_socket_channel') + ]); /// smb_connect 0.0.9 const _smb_connect = Package( name: 'smb_connect', - description: 'Native SMB/CIFS client library written in Dart for Dart. Extremely fast, can be used for streaming music and video. Supported dialects: SMB 1.0, CIFS, SMB 2.0, SMB 2.1.', + description: + 'Native SMB/CIFS client library written in Dart for Dart. Extremely fast, can be used for streaming music and video. Supported dialects: SMB 1.0, CIFS, SMB 2.0, SMB 2.1.', homepage: 'https://github.com/vadia/smb_connect', repository: 'https://github.com/vadia/smb_connect', authors: [], @@ -9344,16 +11278,24 @@ const _smb_connect = Package( limitations under the License.''', isMarkdown: false, isSdk: false, - dependencies: [PackageRef('asn1lib'), PackageRef('charset'), PackageRef('crypto'), PackageRef('cryptography'), PackageRef('mutex'), PackageRef('pointycastle')] - ); + dependencies: [ + PackageRef('asn1lib'), + PackageRef('charset'), + PackageRef('crypto'), + PackageRef('cryptography'), + PackageRef('mutex'), + PackageRef('pointycastle') + ]); -/// source_gen 2.0.0 +/// source_gen 4.0.1 const _source_gen = Package( name: 'source_gen', - description: 'Source code generation builders and utilities for the Dart build system', - repository: 'https://github.com/dart-lang/source_gen/tree/master/source_gen', + description: + 'Source code generation builders and utilities for the Dart build system', + repository: + 'https://github.com/dart-lang/source_gen/tree/master/source_gen', authors: [], - version: '2.0.0', + version: '4.0.1', license: '''Copyright 2015, the Dart project authors. Redistribution and use in source and binary forms, with or without @@ -9383,16 +11325,26 @@ THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.''', isMarkdown: false, isSdk: false, - dependencies: [PackageRef('analyzer'), PackageRef('async'), PackageRef('build'), PackageRef('dart_style'), PackageRef('glob'), PackageRef('path'), PackageRef('pub_semver'), PackageRef('source_span'), PackageRef('yaml')] - ); + dependencies: [ + PackageRef('analyzer'), + PackageRef('async'), + PackageRef('build'), + PackageRef('dart_style'), + PackageRef('glob'), + PackageRef('path'), + PackageRef('pub_semver'), + PackageRef('source_span'), + PackageRef('yaml') + ]); -/// source_helper 1.3.5 +/// source_helper 1.3.8 const _source_helper = Package( name: 'source_helper', - description: 'Utilities to help with Dart source code generation. Includes utilities for properly generating String literals from any String value.', + description: + 'Utilities to help with Dart source code generation. Includes utilities for properly generating String literals from any String value.', repository: 'https://github.com/google/source_helper.dart', authors: [], - version: '1.3.5', + version: '1.3.8', license: '''Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ @@ -9596,13 +11548,13 @@ const _source_helper = Package( limitations under the License.''', isMarkdown: false, isSdk: false, - dependencies: [PackageRef('analyzer'), PackageRef('collection'), PackageRef('source_gen')] - ); + dependencies: [PackageRef('analyzer'), PackageRef('source_gen')]); /// source_span 1.10.1 const _source_span = Package( name: 'source_span', - description: 'Provides a standard representation for source code locations and spans.', + description: + 'Provides a standard representation for source code locations and spans.', repository: 'https://github.com/dart-lang/tools/tree/main/pkgs/source_span', authors: [], version: '1.10.1', @@ -9635,13 +11587,17 @@ THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.''', isMarkdown: false, isSdk: false, - dependencies: [PackageRef('collection'), PackageRef('path'), PackageRef('term_glyph')] - ); + dependencies: [ + PackageRef('collection'), + PackageRef('path'), + PackageRef('term_glyph') + ]); /// sprintf 7.0.0 const _sprintf = Package( name: 'sprintf', - description: 'Dart implementation of sprintf. Provides simple printf like formatting such as sprintf("hello %s", ["world"]);', + description: + 'Dart implementation of sprintf. Provides simple printf like formatting such as sprintf("hello %s", ["world"]);', homepage: 'https://github.com/Naddiseo/dart-sprintf', authors: [], version: '7.0.0', @@ -9669,13 +11625,13 @@ ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.''', isMarkdown: false, isSdk: false, - dependencies: [] - ); + dependencies: []); /// stack_trace 1.12.1 const _stack_trace = Package( name: 'stack_trace', - description: 'A package for manipulating stack traces and printing them readably.', + description: + 'A package for manipulating stack traces and printing them readably.', repository: 'https://github.com/dart-lang/tools/tree/main/pkgs/stack_trace', authors: [], version: '1.12.1', @@ -9708,14 +11664,15 @@ THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.''', isMarkdown: false, isSdk: false, - dependencies: [PackageRef('path')] - ); + dependencies: [PackageRef('path')]); /// stream_channel 2.1.4 const _stream_channel = Package( name: 'stream_channel', - description: 'An abstraction for two-way communication channels based on the Dart Stream class.', - repository: 'https://github.com/dart-lang/tools/tree/main/pkgs/stream_channel', + description: + 'An abstraction for two-way communication channels based on the Dart Stream class.', + repository: + 'https://github.com/dart-lang/tools/tree/main/pkgs/stream_channel', authors: [], version: '2.1.4', license: '''Copyright 2015, the Dart project authors. @@ -9747,14 +11704,15 @@ THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.''', isMarkdown: false, isSdk: false, - dependencies: [PackageRef('async')] - ); + dependencies: [PackageRef('async')]); /// stream_transform 2.1.1 const _stream_transform = Package( name: 'stream_transform', - description: 'A collection of utilities to transform and manipulate streams.', - repository: 'https://github.com/dart-lang/tools/tree/main/pkgs/stream_transform', + description: + 'A collection of utilities to transform and manipulate streams.', + repository: + 'https://github.com/dart-lang/tools/tree/main/pkgs/stream_transform', authors: [], version: '2.1.1', license: '''Copyright 2017, the Dart project authors. @@ -9786,14 +11744,14 @@ THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.''', isMarkdown: false, isSdk: false, - dependencies: [] - ); + dependencies: []); /// string_scanner 1.4.1 const _string_scanner = Package( name: 'string_scanner', description: 'A class for parsing strings using a sequence of patterns.', - repository: 'https://github.com/dart-lang/tools/tree/main/pkgs/string_scanner', + repository: + 'https://github.com/dart-lang/tools/tree/main/pkgs/string_scanner', authors: [], version: '1.4.1', license: '''Copyright 2014, the Dart project authors. @@ -9825,16 +11783,17 @@ THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.''', isMarkdown: false, isSdk: false, - dependencies: [PackageRef('source_span')] - ); + dependencies: [PackageRef('source_span')]); -/// synchronized 3.3.1 +/// synchronized 3.4.0 const _synchronized = Package( name: 'synchronized', - description: 'Lock mechanism to prevent concurrent access to asynchronous code.', - homepage: 'https://github.com/tekartik/synchronized.dart/tree/master/synchronized', + description: + 'Lock mechanism to prevent concurrent access to asynchronous code.', + homepage: + 'https://github.com/tekartik/synchronized.dart/tree/master/synchronized', authors: [], - version: '3.3.1', + version: '3.4.0', license: '''MIT License Copyright (c) 2016, Alexandre Roux Tekartik. @@ -9858,8 +11817,7 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.''', isMarkdown: false, isSdk: false, - dependencies: [] - ); + dependencies: []); /// term_glyph 1.2.2 const _term_glyph = Package( @@ -9897,55 +11855,16 @@ THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.''', isMarkdown: false, isSdk: false, - dependencies: [] - ); + dependencies: []); -/// test_api 0.7.4 +/// test_api 0.7.6 const _test_api = Package( name: 'test_api', - description: 'The user facing API for structuring Dart tests and checking expectations.', + description: + 'The user facing API for structuring Dart tests and checking expectations.', repository: 'https://github.com/dart-lang/test/tree/master/pkgs/test_api', authors: [], - version: '0.7.4', - license: '''Copyright 2018, the Dart project authors. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are -met: - - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above - copyright notice, this list of conditions and the following - disclaimer in the documentation and/or other materials provided - with the distribution. - * Neither the name of Google LLC nor the names of its - contributors may be used to endorse or promote products derived - from this software without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.''', - isMarkdown: false, - isSdk: false, - dependencies: [PackageRef('async'), PackageRef('boolean_selector'), PackageRef('collection'), PackageRef('meta'), PackageRef('source_span'), PackageRef('stack_trace'), PackageRef('stream_channel'), PackageRef('string_scanner'), PackageRef('term_glyph')] - ); - -/// timing 1.0.2 -const _timing = Package( - name: 'timing', - description: 'A simple package for tracking the performance of synchronous and asynchronous actions.', - repository: 'https://github.com/dart-lang/tools/tree/main/pkgs/timing', - authors: [], - version: '1.0.2', + version: '0.7.6', license: '''Copyright 2018, the Dart project authors. Redistribution and use in source and binary forms, with or without @@ -9975,13 +11894,23 @@ THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.''', isMarkdown: false, isSdk: false, - dependencies: [PackageRef('json_annotation')] - ); + dependencies: [ + PackageRef('async'), + PackageRef('boolean_selector'), + PackageRef('collection'), + PackageRef('meta'), + PackageRef('source_span'), + PackageRef('stack_trace'), + PackageRef('stream_channel'), + PackageRef('string_scanner'), + PackageRef('term_glyph') + ]); /// typed_data 1.4.0 const _typed_data = Package( name: 'typed_data', - description: 'Utility functions and classes related to the dart:typed_data library.', + description: + 'Utility functions and classes related to the dart:typed_data library.', repository: 'https://github.com/dart-lang/core/tree/main/pkgs/typed_data', authors: [], version: '1.4.0', @@ -10014,13 +11943,13 @@ THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.''', isMarkdown: false, isSdk: false, - dependencies: [PackageRef('collection')] - ); + dependencies: [PackageRef('collection')]); /// universal_platform 1.1.0 const _universal_platform = Package( name: 'universal_platform', - description: 'Replacement for dart.io.Platform class which works on Web as well as Desktop and Mobile. Allows platform checks in your view/model layer easily.', + description: + 'Replacement for dart.io.Platform class which works on Web as well as Desktop and Mobile. Allows platform checks in your view/model layer easily.', homepage: 'https://github.com/gskinnerTeam/flutter-universal-platform', authors: [], version: '1.1.0', @@ -10035,8 +11964,7 @@ The above copyright notice and this permission notice shall be included in all c THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.''', isMarkdown: false, isSdk: false, - dependencies: [] - ); + dependencies: []); /// uri_parser 3.0.0 const _uri_parser = Package( @@ -10069,16 +11997,17 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.''', isMarkdown: false, isSdk: false, - dependencies: [PackageRef('path'), PackageRef('safe_local_storage')] - ); + dependencies: [PackageRef('path'), PackageRef('safe_local_storage')]); -/// url_launcher 6.3.1 +/// url_launcher 6.3.2 const _url_launcher = Package( name: 'url_launcher', - description: 'Flutter plugin for launching a URL. Supports web, phone, SMS, and email schemes.', - repository: 'https://github.com/flutter/packages/tree/main/packages/url_launcher/url_launcher', + description: + 'Flutter plugin for launching a URL. Supports web, phone, SMS, and email schemes.', + repository: + 'https://github.com/flutter/packages/tree/main/packages/url_launcher/url_launcher', authors: [], - version: '6.3.1', + version: '6.3.2', license: '''Copyright 2013 The Flutter Authors. All rights reserved. Redistribution and use in source and binary forms, with or without modification, @@ -10106,16 +12035,25 @@ ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.''', isMarkdown: false, isSdk: false, - dependencies: [PackageRef('flutter'), PackageRef('url_launcher_android'), PackageRef('url_launcher_ios'), PackageRef('url_launcher_linux'), PackageRef('url_launcher_macos'), PackageRef('url_launcher_platform_interface'), PackageRef('url_launcher_web'), PackageRef('url_launcher_windows')] - ); + dependencies: [ + PackageRef('flutter'), + PackageRef('url_launcher_android'), + PackageRef('url_launcher_ios'), + PackageRef('url_launcher_linux'), + PackageRef('url_launcher_macos'), + PackageRef('url_launcher_platform_interface'), + PackageRef('url_launcher_web'), + PackageRef('url_launcher_windows') + ]); -/// url_launcher_android 6.3.16 +/// url_launcher_android 6.3.21 const _url_launcher_android = Package( name: 'url_launcher_android', description: 'Android implementation of the url_launcher plugin.', - repository: 'https://github.com/flutter/packages/tree/main/packages/url_launcher/url_launcher_android', + repository: + 'https://github.com/flutter/packages/tree/main/packages/url_launcher/url_launcher_android', authors: [], - version: '6.3.16', + version: '6.3.21', license: '''Copyright 2013 The Flutter Authors. All rights reserved. Redistribution and use in source and binary forms, with or without modification, @@ -10143,16 +12081,19 @@ ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.''', isMarkdown: false, isSdk: false, - dependencies: [PackageRef('flutter'), PackageRef('url_launcher_platform_interface')] - ); + dependencies: [ + PackageRef('flutter'), + PackageRef('url_launcher_platform_interface') + ]); -/// url_launcher_ios 6.3.3 +/// url_launcher_ios 6.3.4 const _url_launcher_ios = Package( name: 'url_launcher_ios', description: 'iOS implementation of the url_launcher plugin.', - repository: 'https://github.com/flutter/packages/tree/main/packages/url_launcher/url_launcher_ios', + repository: + 'https://github.com/flutter/packages/tree/main/packages/url_launcher/url_launcher_ios', authors: [], - version: '6.3.3', + version: '6.3.4', license: '''Copyright 2013 The Flutter Authors. All rights reserved. Redistribution and use in source and binary forms, with or without modification, @@ -10180,14 +12121,17 @@ ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.''', isMarkdown: false, isSdk: false, - dependencies: [PackageRef('flutter'), PackageRef('url_launcher_platform_interface')] - ); + dependencies: [ + PackageRef('flutter'), + PackageRef('url_launcher_platform_interface') + ]); /// url_launcher_linux 3.2.1 const _url_launcher_linux = Package( name: 'url_launcher_linux', description: 'Linux implementation of the url_launcher plugin.', - repository: 'https://github.com/flutter/packages/tree/main/packages/url_launcher/url_launcher_linux', + repository: + 'https://github.com/flutter/packages/tree/main/packages/url_launcher/url_launcher_linux', authors: [], version: '3.2.1', license: '''Copyright 2013 The Flutter Authors. All rights reserved. @@ -10217,16 +12161,19 @@ ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.''', isMarkdown: false, isSdk: false, - dependencies: [PackageRef('flutter'), PackageRef('url_launcher_platform_interface')] - ); + dependencies: [ + PackageRef('flutter'), + PackageRef('url_launcher_platform_interface') + ]); -/// url_launcher_macos 3.2.2 +/// url_launcher_macos 3.2.3 const _url_launcher_macos = Package( name: 'url_launcher_macos', description: 'macOS implementation of the url_launcher plugin.', - repository: 'https://github.com/flutter/packages/tree/main/packages/url_launcher/url_launcher_macos', + repository: + 'https://github.com/flutter/packages/tree/main/packages/url_launcher/url_launcher_macos', authors: [], - version: '3.2.2', + version: '3.2.3', license: '''Copyright 2013 The Flutter Authors. All rights reserved. Redistribution and use in source and binary forms, with or without modification, @@ -10254,14 +12201,17 @@ ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.''', isMarkdown: false, isSdk: false, - dependencies: [PackageRef('flutter'), PackageRef('url_launcher_platform_interface')] - ); + dependencies: [ + PackageRef('flutter'), + PackageRef('url_launcher_platform_interface') + ]); /// url_launcher_platform_interface 2.3.2 const _url_launcher_platform_interface = Package( name: 'url_launcher_platform_interface', description: 'A common platform interface for the url_launcher plugin.', - repository: 'https://github.com/flutter/packages/tree/main/packages/url_launcher/url_launcher_platform_interface', + repository: + 'https://github.com/flutter/packages/tree/main/packages/url_launcher/url_launcher_platform_interface', authors: [], version: '2.3.2', license: '''Copyright 2013 The Flutter Authors. All rights reserved. @@ -10291,14 +12241,17 @@ ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.''', isMarkdown: false, isSdk: false, - dependencies: [PackageRef('flutter'), PackageRef('plugin_platform_interface')] - ); + dependencies: [ + PackageRef('flutter'), + PackageRef('plugin_platform_interface') + ]); /// url_launcher_web 2.4.1 const _url_launcher_web = Package( name: 'url_launcher_web', description: 'Web platform implementation of url_launcher', - repository: 'https://github.com/flutter/packages/tree/main/packages/url_launcher/url_launcher_web', + repository: + 'https://github.com/flutter/packages/tree/main/packages/url_launcher/url_launcher_web', authors: [], version: '2.4.1', license: '''url_launcher_web @@ -10534,14 +12487,19 @@ platform_detect limitations under the License.''', isMarkdown: false, isSdk: false, - dependencies: [PackageRef('flutter'), PackageRef('url_launcher_platform_interface'), PackageRef('web')] - ); + dependencies: [ + PackageRef('flutter'), + PackageRef('flutter_web_plugins'), + PackageRef('url_launcher_platform_interface'), + PackageRef('web') + ]); /// url_launcher_windows 3.1.4 const _url_launcher_windows = Package( name: 'url_launcher_windows', description: 'Windows implementation of the url_launcher plugin.', - repository: 'https://github.com/flutter/packages/tree/main/packages/url_launcher/url_launcher_windows', + repository: + 'https://github.com/flutter/packages/tree/main/packages/url_launcher/url_launcher_windows', authors: [], version: '3.1.4', license: '''Copyright 2013 The Flutter Authors. All rights reserved. @@ -10571,13 +12529,16 @@ ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.''', isMarkdown: false, isSdk: false, - dependencies: [PackageRef('flutter'), PackageRef('url_launcher_platform_interface')] - ); + dependencies: [ + PackageRef('flutter'), + PackageRef('url_launcher_platform_interface') + ]); /// uuid 4.5.1 const _uuid = Package( name: 'uuid', - description: '''RFC4122 (v1, v4, v5, v6, v7, v8) UUID Generator and Parser for Dart + description: + '''RFC4122 (v1, v4, v5, v6, v7, v8) UUID Generator and Parser for Dart ''', repository: 'https://github.com/Daegalus/dart-uuid', authors: [], @@ -10591,16 +12552,20 @@ The above copyright notice and this permission notice shall be included in all c THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.''', isMarkdown: false, isSdk: false, - dependencies: [PackageRef('crypto'), PackageRef('sprintf'), PackageRef('meta'), PackageRef('fixnum')] - ); + dependencies: [ + PackageRef('crypto'), + PackageRef('sprintf'), + PackageRef('meta'), + PackageRef('fixnum') + ]); -/// vector_math 2.1.4 +/// vector_math 2.2.0 const _vector_math = Package( name: 'vector_math', description: 'A Vector Math library for 2D and 3D applications.', repository: 'https://github.com/google/vector_math.dart', authors: [], - version: '2.1.4', + version: '2.2.0', license: '''Copyright 2015, Google Inc. All rights reserved. Redistribution and use in source and binary forms, with or without @@ -10649,14 +12614,15 @@ freely, subject to the following restrictions: 3. This notice may not be removed or altered from any source distribution.''', isMarkdown: false, isSdk: false, - dependencies: [] - ); + dependencies: []); /// video_player 2.10.0 const _video_player = Package( name: 'video_player', - description: 'Flutter plugin for displaying inline video with other Flutter widgets on Android, iOS, macOS and web.', - repository: 'https://github.com/flutter/packages/tree/main/packages/video_player/video_player', + description: + 'Flutter plugin for displaying inline video with other Flutter widgets on Android, iOS, macOS and web.', + repository: + 'https://github.com/flutter/packages/tree/main/packages/video_player/video_player', authors: [], version: '2.10.0', license: '''Copyright 2013 The Flutter Authors. All rights reserved. @@ -10686,16 +12652,23 @@ ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.''', isMarkdown: false, isSdk: false, - dependencies: [PackageRef('flutter'), PackageRef('html'), PackageRef('video_player_android'), PackageRef('video_player_avfoundation'), PackageRef('video_player_platform_interface'), PackageRef('video_player_web')] - ); + dependencies: [ + PackageRef('flutter'), + PackageRef('html'), + PackageRef('video_player_android'), + PackageRef('video_player_avfoundation'), + PackageRef('video_player_platform_interface'), + PackageRef('video_player_web') + ]); -/// video_player_android 2.8.7 +/// video_player_android 2.8.13 const _video_player_android = Package( name: 'video_player_android', description: 'Android implementation of the video_player plugin.', - repository: 'https://github.com/flutter/packages/tree/main/packages/video_player/video_player_android', + repository: + 'https://github.com/flutter/packages/tree/main/packages/video_player/video_player_android', authors: [], - version: '2.8.7', + version: '2.8.13', license: '''Copyright 2013 The Flutter Authors. All rights reserved. Redistribution and use in source and binary forms, with or without modification, @@ -10723,16 +12696,19 @@ ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.''', isMarkdown: false, isSdk: false, - dependencies: [PackageRef('flutter'), PackageRef('video_player_platform_interface')] - ); + dependencies: [ + PackageRef('flutter'), + PackageRef('video_player_platform_interface') + ]); -/// video_player_avfoundation 2.7.1 +/// video_player_avfoundation 2.8.4 const _video_player_avfoundation = Package( name: 'video_player_avfoundation', description: 'iOS and macOS implementation of the video_player plugin.', - repository: 'https://github.com/flutter/packages/tree/main/packages/video_player/video_player_avfoundation', + repository: + 'https://github.com/flutter/packages/tree/main/packages/video_player/video_player_avfoundation', authors: [], - version: '2.7.1', + version: '2.8.4', license: '''Copyright 2013 The Flutter Authors. All rights reserved. Redistribution and use in source and binary forms, with or without modification, @@ -10760,16 +12736,19 @@ ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.''', isMarkdown: false, isSdk: false, - dependencies: [PackageRef('flutter'), PackageRef('video_player_platform_interface')] - ); + dependencies: [ + PackageRef('flutter'), + PackageRef('video_player_platform_interface') + ]); -/// video_player_platform_interface 6.3.0 +/// video_player_platform_interface 6.4.0 const _video_player_platform_interface = Package( name: 'video_player_platform_interface', description: 'A common platform interface for the video_player plugin.', - repository: 'https://github.com/flutter/packages/tree/main/packages/video_player/video_player_platform_interface', + repository: + 'https://github.com/flutter/packages/tree/main/packages/video_player/video_player_platform_interface', authors: [], - version: '6.3.0', + version: '6.4.0', license: '''Copyright 2013 The Flutter Authors. All rights reserved. Redistribution and use in source and binary forms, with or without modification, @@ -10797,16 +12776,19 @@ ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.''', isMarkdown: false, isSdk: false, - dependencies: [PackageRef('flutter'), PackageRef('plugin_platform_interface')] - ); + dependencies: [ + PackageRef('flutter'), + PackageRef('plugin_platform_interface') + ]); -/// video_player_web 2.3.5 +/// video_player_web 2.4.0 const _video_player_web = Package( name: 'video_player_web', description: 'Web platform implementation of video_player.', - repository: 'https://github.com/flutter/packages/tree/main/packages/video_player/video_player_web', + repository: + 'https://github.com/flutter/packages/tree/main/packages/video_player/video_player_web', authors: [], - version: '2.3.5', + version: '2.4.0', license: '''Copyright 2013 The Flutter Authors. All rights reserved. Redistribution and use in source and binary forms, with or without modification, @@ -10834,16 +12816,21 @@ ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.''', isMarkdown: false, isSdk: false, - dependencies: [PackageRef('flutter'), PackageRef('video_player_platform_interface'), PackageRef('web')] - ); + dependencies: [ + PackageRef('flutter'), + PackageRef('flutter_web_plugins'), + PackageRef('video_player_platform_interface'), + PackageRef('web') + ]); -/// vm_service 15.0.0 +/// vm_service 15.0.2 const _vm_service = Package( name: 'vm_service', - description: 'A library to communicate with a service implementing the Dart VM service protocol.', + description: + 'A library to communicate with a service implementing the Dart VM service protocol.', repository: 'https://github.com/dart-lang/sdk/tree/main/pkg/vm_service', authors: [], - version: '15.0.0', + version: '15.0.2', license: '''Copyright 2015, the Dart project authors. Redistribution and use in source and binary forms, with or without @@ -10873,13 +12860,13 @@ THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.''', isMarkdown: false, isSdk: false, - dependencies: [] - ); + dependencies: []); /// volume_controller 3.4.0 const _volume_controller = Package( name: 'volume_controller', - description: 'A Flutter volume plugin for multiple platform to control system volume.', + description: + 'A Flutter volume plugin for multiple platform to control system volume.', homepage: 'https://github.com/kurenai7968/volume_controller', authors: [], version: '3.4.0', @@ -10906,16 +12893,17 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.''', isMarkdown: false, isSdk: false, - dependencies: [PackageRef('flutter')] - ); + dependencies: [PackageRef('flutter')]); -/// wakelock_plus 1.3.2 +/// wakelock_plus 1.4.0 const _wakelock_plus = Package( name: 'wakelock_plus', - description: 'Plugin that allows you to keep the device screen awake, i.e. prevent the screen from sleeping on Android, iOS, macOS, Windows, Linux, and web.', - repository: 'https://github.com/fluttercommunity/wakelock_plus/tree/main/wakelock_plus', + description: + 'Plugin that allows you to keep the device screen awake, i.e. prevent the screen from sleeping on Android, iOS, macOS, Windows, Linux, and web.', + repository: + 'https://github.com/fluttercommunity/wakelock_plus/tree/main/wakelock_plus', authors: [], - version: '1.3.2', + version: '1.4.0', license: '''BSD 3-Clause License Copyright (c) 2020-2023, creativecreatorormaybenot @@ -10947,16 +12935,26 @@ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.''', isMarkdown: false, isSdk: false, - dependencies: [PackageRef('flutter'), PackageRef('meta'), PackageRef('wakelock_plus_platform_interface'), PackageRef('win32'), PackageRef('dbus'), PackageRef('package_info_plus'), PackageRef('web')] - ); + dependencies: [ + PackageRef('flutter'), + PackageRef('flutter_web_plugins'), + PackageRef('meta'), + PackageRef('wakelock_plus_platform_interface'), + PackageRef('win32'), + PackageRef('dbus'), + PackageRef('package_info_plus'), + PackageRef('web') + ]); -/// wakelock_plus_platform_interface 1.2.3 +/// wakelock_plus_platform_interface 1.3.0 const _wakelock_plus_platform_interface = Package( name: 'wakelock_plus_platform_interface', - description: 'A common platform interface for the wakelock_plus plugin used by the different platform implementations.', - repository: 'https://github.com/fluttercommunity/wakelock_plus/tree/main/wakelock_plus_platform_interface', + description: + 'A common platform interface for the wakelock_plus plugin used by the different platform implementations.', + repository: + 'https://github.com/fluttercommunity/wakelock_plus/tree/main/wakelock_plus_platform_interface', authors: [], - version: '1.2.3', + version: '1.3.0', license: '''BSD 3-Clause License Copyright (c) 2020-2023, creativecreatorormaybenot @@ -10988,16 +12986,20 @@ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.''', isMarkdown: false, isSdk: false, - dependencies: [PackageRef('flutter'), PackageRef('plugin_platform_interface'), PackageRef('meta')] - ); + dependencies: [ + PackageRef('flutter'), + PackageRef('plugin_platform_interface'), + PackageRef('meta') + ]); -/// watcher 1.1.1 +/// watcher 1.1.3 const _watcher = Package( name: 'watcher', - description: 'A file system watcher. It monitors changes to contents of directories and sends notifications when files have been added, removed, or modified.', + description: + 'A file system watcher. It monitors changes to contents of directories and sends notifications when files have been added, removed, or modified.', repository: 'https://github.com/dart-lang/tools/tree/main/pkgs/watcher', authors: [], - version: '1.1.1', + version: '1.1.3', license: '''Copyright 2014, the Dart project authors. Redistribution and use in source and binary forms, with or without @@ -11027,8 +13029,7 @@ THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.''', isMarkdown: false, isSdk: false, - dependencies: [PackageRef('async'), PackageRef('path')] - ); + dependencies: [PackageRef('async'), PackageRef('path')]); /// web 1.1.1 const _web = Package( @@ -11065,13 +13066,13 @@ THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.''', isMarkdown: false, isSdk: false, - dependencies: [] - ); + dependencies: []); /// web_socket 1.0.1 const _web_socket = Package( name: 'web_socket', - description: 'Any easy-to-use library for communicating with WebSockets that has multiple implementations.', + description: + 'Any easy-to-use library for communicating with WebSockets that has multiple implementations.', repository: 'https://github.com/dart-lang/http/tree/master/pkgs/web_socket', authors: [], version: '1.0.1', @@ -11104,14 +13105,15 @@ THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.''', isMarkdown: false, isSdk: false, - dependencies: [PackageRef('web')] - ); + dependencies: [PackageRef('web')]); /// web_socket_channel 3.0.3 const _web_socket_channel = Package( name: 'web_socket_channel', - description: 'StreamChannel wrappers for WebSockets. Provides a cross-platform WebSocketChannel API, a cross-platform implementation of that API that communicates over an underlying StreamChannel.', - repository: 'https://github.com/dart-lang/http/tree/master/pkgs/web_socket_channel', + description: + 'StreamChannel wrappers for WebSockets. Provides a cross-platform WebSocketChannel API, a cross-platform implementation of that API that communicates over an underlying StreamChannel.', + repository: + 'https://github.com/dart-lang/http/tree/master/pkgs/web_socket_channel', authors: [], version: '3.0.3', license: '''Copyright 2016, the Dart project authors. @@ -11143,8 +13145,13 @@ THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.''', isMarkdown: false, isSdk: false, - dependencies: [PackageRef('async'), PackageRef('crypto'), PackageRef('stream_channel'), PackageRef('web'), PackageRef('web_socket')] - ); + dependencies: [ + PackageRef('async'), + PackageRef('crypto'), + PackageRef('stream_channel'), + PackageRef('web'), + PackageRef('web_socket') + ]); /// webdav_client 1.2.2 const _webdav_client = Package( @@ -11184,18 +13191,22 @@ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.''', isMarkdown: false, isSdk: false, - dependencies: [PackageRef('dio'), PackageRef('xml'), PackageRef('convert')] - ); + dependencies: [ + PackageRef('dio'), + PackageRef('xml'), + PackageRef('convert') + ]); -/// win32 5.13.0 +/// win32 5.14.0 const _win32 = Package( name: 'win32', - description: '''Access common Win32 APIs directly from Dart using FFI — no C required! + description: + '''Access common Win32 APIs directly from Dart using FFI — no C required! ''', homepage: 'https://win32.pub', repository: 'https://github.com/halildurmus/win32', authors: [], - version: '5.13.0', + version: '5.14.0', license: '''BSD 3-Clause License Copyright (c) 2024, Halil Durmus @@ -11226,13 +13237,13 @@ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.''', isMarkdown: false, isSdk: false, - dependencies: [PackageRef('ffi')] - ); + dependencies: [PackageRef('ffi')]); /// win32_registry 2.1.0 const _win32_registry = Package( name: 'win32_registry', - description: 'A package that provides a friendly Dart API for accessing the Windows Registry.', + description: + 'A package that provides a friendly Dart API for accessing the Windows Registry.', repository: 'https://github.com/halildurmus/win32_registry', authors: [], version: '2.1.0', @@ -11266,17 +13277,17 @@ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.''', isMarkdown: false, isSdk: false, - dependencies: [PackageRef('ffi'), PackageRef('meta'), PackageRef('win32')] - ); + dependencies: [PackageRef('ffi'), PackageRef('meta'), PackageRef('win32')]); -/// window_manager 0.5.0 +/// window_manager 0.5.1 const _window_manager = Package( name: 'window_manager', - description: 'This plugin allows Flutter desktop apps to resizing and repositioning the window.', + description: + 'This plugin allows Flutter desktop apps to resizing and repositioning the window.', homepage: 'https://leanflutter.dev', repository: 'https://github.com/leanflutter/window_manager', authors: [], - version: '0.5.0', + version: '0.5.1', license: '''MIT License Copyright (c) 2022-present LiJianying @@ -11300,13 +13311,17 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.''', isMarkdown: false, isSdk: false, - dependencies: [PackageRef('flutter'), PackageRef('path'), PackageRef('screen_retriever')] - ); + dependencies: [ + PackageRef('flutter'), + PackageRef('path'), + PackageRef('screen_retriever') + ]); /// window_size 0.1.0 const _window_size = Package( name: 'window_size', - description: 'Allows resizing and repositioning the window containing Flutter.', + description: + 'Allows resizing and repositioning the window containing Flutter.', authors: [], version: '0.1.0', license: '''Apache License @@ -11512,14 +13527,15 @@ const _window_size = Package( limitations under the License.''', isMarkdown: false, isSdk: false, - dependencies: [PackageRef('flutter')] - ); + dependencies: [PackageRef('flutter')]); /// xdg_directories 1.1.0 const _xdg_directories = Package( name: 'xdg_directories', - description: 'A Dart package for reading XDG directory configuration information on Linux.', - repository: 'https://github.com/flutter/packages/tree/main/packages/xdg_directories', + description: + 'A Dart package for reading XDG directory configuration information on Linux.', + repository: + 'https://github.com/flutter/packages/tree/main/packages/xdg_directories', authors: [], version: '1.1.0', license: '''Copyright 2013 The Flutter Authors. All rights reserved. @@ -11549,19 +13565,19 @@ ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.''', isMarkdown: false, isSdk: false, - dependencies: [PackageRef('meta'), PackageRef('path')] - ); + dependencies: [PackageRef('meta'), PackageRef('path')]); -/// xml 6.5.0 +/// xml 6.6.1 const _xml = Package( name: 'xml', - description: 'A lightweight library for parsing, traversing, querying, transforming and building XML documents.', + description: + 'A lightweight library for parsing, traversing, querying, transforming and building XML documents.', homepage: 'https://github.com/renggli/dart-xml', authors: [], - version: '6.5.0', + version: '6.6.1', license: '''The MIT License -Copyright (c) 2006-2023 Lukas Renggli. +Copyright (c) 2006-2025 Lukas Renggli. All rights reserved. Permission is hereby granted, free of charge, to any person obtaining a copy @@ -11583,13 +13599,17 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.''', isMarkdown: false, isSdk: false, - dependencies: [PackageRef('collection'), PackageRef('meta'), PackageRef('petitparser')] - ); + dependencies: [ + PackageRef('collection'), + PackageRef('meta'), + PackageRef('petitparser') + ]); /// yaml 3.1.3 const _yaml = Package( name: 'yaml', - description: 'A parser for YAML, a human-friendly data serialization standard', + description: + 'A parser for YAML, a human-friendly data serialization standard', repository: 'https://github.com/dart-lang/tools/tree/main/pkgs/yaml', authors: [], version: '3.1.3', @@ -11615,15 +13635,21 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.''', isMarkdown: false, isSdk: false, - dependencies: [PackageRef('collection'), PackageRef('source_span'), PackageRef('string_scanner')] - ); + dependencies: [ + PackageRef('collection'), + PackageRef('source_span'), + PackageRef('string_scanner') + ]); /// zustand 0.0.5 const _zustand = Package( name: 'zustand', - description: "Brings zustand's bear necessities for state management to Dart", - homepage: 'https://github.com/josiahsrc/flutter_zustand/tree/main/packages/zustand', - repository: 'https://github.com/josiahsrc/flutter_zustand/tree/main/packages/zustand', + description: + "Brings zustand's bear necessities for state management to Dart", + homepage: + 'https://github.com/josiahsrc/flutter_zustand/tree/main/packages/zustand', + repository: + 'https://github.com/josiahsrc/flutter_zustand/tree/main/packages/zustand', authors: [], version: '0.0.5', license: '''MIT License @@ -11649,6 +13675,4 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.''', isMarkdown: false, isSdk: false, - dependencies: [PackageRef('meta')] - ); - + dependencies: [PackageRef('meta')]); diff --git a/lib/pages/home/home.dart b/lib/pages/home/home.dart index 47cfe12..362290a 100644 --- a/lib/pages/home/home.dart +++ b/lib/pages/home/home.dart @@ -2,11 +2,10 @@ import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:flutter_zustand/flutter_zustand.dart'; -import 'package:iris/hooks/player/use_fvp_player.dart'; -import 'package:iris/hooks/player/use_media_kit_player.dart'; -import 'package:iris/models/player.dart'; -import 'package:iris/models/store/app_state.dart'; -import 'package:iris/pages/player/iris_player.dart'; +import 'package:iris/hooks/ui/use_full_screen.dart'; +import 'package:iris/hooks/ui/use_orientation.dart'; +import 'package:iris/hooks/ui/use_resize_window.dart'; +import 'package:iris/pages/player/player_view.dart'; import 'package:iris/store/use_app_store.dart'; class Home extends HookWidget { @@ -14,29 +13,13 @@ class Home extends HookWidget { @override Widget build(BuildContext context) { + useFullScreen(); + useOrientation(); + useResizeWindow(); + final playerBackend = useAppStore().select(context, (state) => state.playerBackend); - final playerState = useState(null); - - void handlePlayerCreated(MediaPlayer player) { - WidgetsBinding.instance.addPostFrameCallback((_) { - if (context.mounted) { - playerState.value = player; - } - }); - } - - final Widget playerHost; - switch (playerBackend) { - case PlayerBackend.mediaKit: - playerHost = _MediaKitPlayerHost(onPlayerCreated: handlePlayerCreated); - break; - case PlayerBackend.fvp: - playerHost = _FvpPlayerHost(onPlayerCreated: handlePlayerCreated); - break; - } - return AnnotatedRegion( value: const SystemUiOverlayStyle( statusBarIconBrightness: Brightness.light, @@ -45,40 +28,8 @@ class Home extends HookWidget { ), child: Scaffold( backgroundColor: Color(0xFF2f2f2f), - body: Stack( - children: [ - playerHost, - if (playerState.value != null) - IrisPlayer(player: playerState.value!) - ], - ), + body: PlayerView(playerBackend: playerBackend), ), ); } } - -class _MediaKitPlayerHost extends HookWidget { - final ValueChanged onPlayerCreated; - - const _MediaKitPlayerHost({required this.onPlayerCreated}); - - @override - Widget build(BuildContext context) { - final player = useMediaKitPlayer(context); - onPlayerCreated(player); - return Container(); // Doesn't build any UI itself. - } -} - -class _FvpPlayerHost extends HookWidget { - final ValueChanged onPlayerCreated; - - const _FvpPlayerHost({required this.onPlayerCreated}); - - @override - Widget build(BuildContext context) { - final player = useFvpPlayer(context); - onPlayerCreated(player); - return Container(); // Doesn't build any UI itself. - } -} diff --git a/lib/pages/player/control_bar/control_bar.dart b/lib/pages/player/control_bar/control_bar.dart index d1fe299..604367e 100644 --- a/lib/pages/player/control_bar/control_bar.dart +++ b/lib/pages/player/control_bar/control_bar.dart @@ -6,7 +6,7 @@ import 'package:iris/globals.dart'; import 'package:iris/models/player.dart'; import 'package:iris/models/storages/local.dart'; import 'package:iris/models/store/app_state.dart'; -import 'package:iris/store/use_ui_store.dart'; +import 'package:iris/store/use_player_ui_store.dart'; import 'package:iris/widgets/dialogs/show_open_link_dialog.dart'; import 'package:iris/widgets/dialogs/show_rate_dialog.dart'; import 'package:iris/pages/player/control_bar/control_bar_slider.dart'; @@ -47,17 +47,13 @@ class ControlBar extends HookWidget { final rate = useAppStore().select(context, (state) => state.rate); final volume = useAppStore().select(context, (state) => state.volume); final isMuted = useAppStore().select(context, (state) => state.isMuted); + + final aspectRatio = + usePlayerUiStore().select(context, (state) => state.aspectRatio); final isFullScreen = - useUiStore().select(context, (state) => state.isFullScreen); + usePlayerUiStore().select(context, (state) => state.isFullScreen); final int playQueueLength = usePlayQueueStore().select(context, (state) => state.playQueue.length); - final playQueue = - usePlayQueueStore().select(context, (state) => state.playQueue); - final currentIndex = - usePlayQueueStore().select(context, (state) => state.currentIndex); - final currentPlayIndex = useMemoized( - () => playQueue.indexWhere((element) => element.index == currentIndex), - [playQueue, currentIndex]); final bool shuffle = useAppStore().select(context, (state) => state.shuffle); @@ -65,10 +61,13 @@ class ControlBar extends HookWidget { useAppStore().select(context, (state) => state.repeat); final BoxFit fit = useAppStore().select(context, (state) => state.fit); + final isSeeking = + usePlayerUiStore().select(context, (state) => state.isSeeking); + final displayIsPlaying = useState(player.isPlaying); useEffect(() { - if (!player.seeking) { + if (!isSeeking) { displayIsPlaying.value = player.isPlaying; } return null; @@ -102,26 +101,7 @@ class ControlBar extends HookWidget { mainAxisAlignment: MainAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.center, children: [ - const SizedBox(width: 8), - if (playQueueLength > 1) - IconButton( - tooltip: currentPlayIndex == 0 - ? null - : '${t.previous} ( Ctrl + ← )', - icon: Icon( - Icons.skip_previous_rounded, - size: 28, - color: - currentPlayIndex == 0 ? color?.withAlpha(153) : color, - ), - onPressed: currentPlayIndex == 0 - ? null - : () { - showControl(); - usePlayQueueStore().previous(); - }, - style: ButtonStyle(overlayColor: overlayColor), - ), + const SizedBox(width: 4), Stack( alignment: Alignment.center, children: [ @@ -158,24 +138,47 @@ class ControlBar extends HookWidget { ), ], ), + IconButton( + tooltip: '${t.stop} ( Ctrl + C )', + icon: Icon( + Icons.stop_rounded, + size: 28, + color: color, + ), + onPressed: () { + showControl(); + useAppStore().updateAutoPlay(false); + player.pause(); + usePlayQueueStore().updateCurrentIndex(-1); + }, + style: ButtonStyle(overlayColor: overlayColor), + ), if (playQueueLength > 1) IconButton( - tooltip: currentPlayIndex == playQueueLength - 1 - ? null - : '${t.next} ( Ctrl + → )', + tooltip: '${t.previous} ( Ctrl + ← )', + icon: Icon( + Icons.skip_previous_rounded, + size: 28, + color: color, + ), + onPressed: () { + showControl(); + usePlayQueueStore().previous(); + }, + style: ButtonStyle(overlayColor: overlayColor), + ), + if (playQueueLength > 1) + IconButton( + tooltip: '${t.next} ( Ctrl + → )', icon: Icon( Icons.skip_next_rounded, size: 28, - color: currentPlayIndex == playQueueLength - 1 - ? color?.withAlpha(153) - : color, + color: color, ), - onPressed: currentPlayIndex == playQueueLength - 1 - ? null - : () { - showControl(); - usePlayQueueStore().next(); - }, + onPressed: () { + showControl(); + usePlayQueueStore().next(); + }, style: ButtonStyle(overlayColor: overlayColor), ), if (MediaQuery.of(context).size.width >= 768) @@ -406,9 +409,9 @@ class ControlBar extends HookWidget { onPressed: () async { showControl(); if (isFullScreen) { - await resizeWindow(player.aspect); + await resizeWindow(aspectRatio); } - useUiStore().updateFullScreen(!isFullScreen); + usePlayerUiStore().updateFullScreen(!isFullScreen); }, style: ButtonStyle(overlayColor: overlayColor), ), @@ -654,7 +657,7 @@ class ControlBar extends HookWidget { ), ], ), - const SizedBox(width: 8), + const SizedBox(width: 4), ], ), ], diff --git a/lib/pages/player/control_bar/control_bar_slider.dart b/lib/pages/player/control_bar/control_bar_slider.dart index 3fa7db3..84f7f0a 100644 --- a/lib/pages/player/control_bar/control_bar_slider.dart +++ b/lib/pages/player/control_bar/control_bar_slider.dart @@ -3,6 +3,7 @@ import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:flutter_zustand/flutter_zustand.dart'; import 'package:iris/models/player.dart'; import 'package:iris/store/use_app_store.dart'; +import 'package:iris/store/use_player_ui_store.dart'; import 'package:iris/utils/format_duration_to_minutes.dart'; class ControlBarSlider extends HookWidget { @@ -89,22 +90,18 @@ class ControlBarSlider extends HookWidget { min: 0, max: player.duration.inMilliseconds.toDouble(), onChangeStart: (value) { - player.updateSeeking(true); + usePlayerUiStore().updateIsSeeking(true); player.pause(); }, onChanged: (value) { showControl(); - if (player is MediaKitPlayer) { - player.updatePosition( - Duration(milliseconds: value.toInt())); - } - player.seekTo(Duration(milliseconds: value.toInt())); + player.seek(Duration(milliseconds: value.toInt())); }, onChangeEnd: (value) async { if (autoPlay) { player.play(); } - player.updateSeeking(false); + usePlayerUiStore().updateIsSeeking(false); }, ), ), diff --git a/lib/pages/player/controls_overlay.dart b/lib/pages/player/controls_overlay.dart new file mode 100644 index 0000000..f0d96bb --- /dev/null +++ b/lib/pages/player/controls_overlay.dart @@ -0,0 +1,360 @@ +import 'dart:async'; +import 'package:flutter_zustand/flutter_zustand.dart'; +import 'package:iris/hooks/use_gesture.dart'; +import 'package:iris/models/file.dart'; +import 'package:iris/models/player.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_hooks/flutter_hooks.dart'; +import 'package:iris/pages/player/control_bar/control_bar.dart'; +import 'package:iris/pages/player/control_bar/control_bar_slider.dart'; +import 'package:iris/store/use_app_store.dart'; +import 'package:iris/store/use_player_ui_store.dart'; +import 'package:iris/utils/format_duration_to_minutes.dart'; +import 'package:iris/utils/resize_window.dart'; +import 'package:iris/widgets/drag_aria.dart'; +import 'package:iris/widgets/title_bar.dart'; + +class ControlsOverlay extends HookWidget { + const ControlsOverlay({ + super.key, + required this.player, + required this.currentPlay, + required this.title, + required this.showControl, + required this.showControlForHover, + required this.hideControl, + required this.showProgress, + }); + + final MediaPlayer player; + final PlayQueueItem? currentPlay; + final String title; + final Function() showControl; + final Future Function(Future callback) showControlForHover; + final Function() hideControl; + final Function() showProgress; + + @override + Widget build(BuildContext context) { + final rate = useAppStore().select(context, (state) => state.rate); + + final aspectRatio = + usePlayerUiStore().select(context, (state) => state.aspectRatio); + final isShowControl = + usePlayerUiStore().select(context, (state) => state.isShowControl); + final isShowProgress = + usePlayerUiStore().select(context, (state) => state.isShowProgress); + + final gesture = useGesture( + player: player, + showControl: showControl, + hideControl: hideControl, + showProgress: showProgress, + ); + + final contentColor = useMemoized( + () => Theme.of(context).brightness == Brightness.dark + ? Theme.of(context).colorScheme.onSurface + : Theme.of(context).colorScheme.surface, + [context]); + + final overlayColor = useMemoized( + () => + WidgetStateProperty.resolveWith((Set states) { + if (states.contains(WidgetState.pressed)) { + return contentColor.withValues(alpha: 0.2); + } else if (states.contains(WidgetState.hovered)) { + return contentColor.withValues(alpha: 0.2); + } + return null; + }), + [contentColor]); + + return Stack( + children: [ + Positioned( + left: 0, + top: 0, + right: 0, + bottom: 0, + child: MouseRegion( + cursor: isShowControl || player.isPlaying == false + ? SystemMouseCursors.basic + : SystemMouseCursors.none, + onHover: gesture.onHover, + child: GestureDetector( + behavior: HitTestBehavior.translucent, + onTap: gesture.onTap, + onTapDown: gesture.onTapDown, + onDoubleTapDown: gesture.onDoubleTapDown, + onLongPressStart: gesture.onLongPressStart, + onLongPressMoveUpdate: gesture.onLongPressMoveUpdate, + onLongPressEnd: gesture.onLongPressEnd, + onLongPressCancel: gesture.onLongPressCancel, + onPanStart: gesture.onPanStart, + onPanUpdate: gesture.onPanUpdate, + onPanEnd: gesture.onPanEnd, + onPanCancel: gesture.onPanCancel, + child: Stack( + children: [ + // 播放速度 + if (rate != 1.0 && gesture.isLongPress) + Positioned( + left: 0, + top: 0, + right: 0, + bottom: 0, + child: Center( + child: Container( + padding: const EdgeInsets.fromLTRB(12, 12, 18, 12), + decoration: BoxDecoration( + color: Colors.black54, + borderRadius: BorderRadius.circular(8), + ), + child: Row( + mainAxisSize: MainAxisSize.min, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Transform.translate( + offset: const Offset(0, 1.5), + child: Icon( + Icons.speed_rounded, + color: Colors.white, + size: 24, + ), + ), + const SizedBox(width: 10), + Text( + rate.toString(), + style: const TextStyle( + color: Colors.white, + fontSize: 20, + height: 1, + ), + ), + ], + ), + ), + ), + ), + // 屏幕亮度 + if (gesture.isLeftGesture && gesture.brightness != null) + Positioned( + left: 0, + top: 0, + right: 0, + bottom: 0, + child: Center( + child: Container( + padding: const EdgeInsets.fromLTRB(12, 12, 18, 12), + decoration: BoxDecoration( + color: Colors.black54, + borderRadius: BorderRadius.circular(8), + ), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Icon( + gesture.brightness == 0 + ? Icons.brightness_low_rounded + : gesture.brightness! < 1 + ? Icons.brightness_medium_rounded + : Icons.brightness_high_rounded, + color: Colors.white, + size: 24, + ), + const SizedBox(width: 12), + SizedBox( + width: 100, + child: LinearProgressIndicator( + value: gesture.brightness, + borderRadius: BorderRadius.circular(4), + backgroundColor: Colors.grey, + valueColor: AlwaysStoppedAnimation( + Colors.white), + ), + ), + ], + ), + ), + ), + ), + // 音量 + if (gesture.isRightGesture && gesture.volume != null) + Positioned( + left: 0, + top: 0, + right: 0, + bottom: 0, + child: Center( + child: Container( + padding: const EdgeInsets.fromLTRB(12, 12, 18, 12), + decoration: BoxDecoration( + color: Colors.black54, + borderRadius: BorderRadius.circular(8), + ), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Icon( + gesture.volume == 0 + ? Icons.volume_mute_rounded + : gesture.volume! < 0.5 + ? Icons.volume_down_rounded + : Icons.volume_up_rounded, + color: Colors.white, + size: 24, + ), + const SizedBox(width: 12), + SizedBox( + width: 100, + child: LinearProgressIndicator( + value: gesture.volume, + borderRadius: BorderRadius.circular(4), + backgroundColor: Colors.grey, + valueColor: AlwaysStoppedAnimation( + Colors.white, + ), + ), + ), + ], + ), + ), + ), + ), + if (isShowProgress && + !isShowControl && + currentPlay?.file.type == ContentType.video) + Positioned( + left: -28, + right: -28, + bottom: -16, + height: 32, + child: ControlBarSlider( + player: player, + showControl: showControl, + disabled: true, + ), + ), + if (isShowProgress && + !isShowControl && + currentPlay?.file.type == ContentType.video) + Positioned( + left: 12, + top: 12, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + currentPlay != null ? title : '', + style: TextStyle( + color: Colors.white, + fontSize: 20, + height: 1, + decoration: TextDecoration.none, + shadows: const [ + Shadow( + color: Colors.black, + offset: Offset(0, 0), + blurRadius: 1, + ), + ], + ), + ), + ], + ), + ), + if (isShowProgress && + !isShowControl && + currentPlay?.file.type == ContentType.video) + Positioned( + left: 12, + bottom: 6, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + '${formatDurationToMinutes(player.position)} / ${formatDurationToMinutes(player.duration)}', + style: TextStyle( + color: Colors.white, + fontSize: 16, + height: 2, + decoration: TextDecoration.none, + shadows: const [ + Shadow( + color: Colors.black, + offset: Offset(0, 0), + blurRadius: 1, + ), + ], + ), + ), + ], + ), + ), + ], + ), + ), + ), + ), + // 标题栏 + AnimatedPositioned( + duration: const Duration(milliseconds: 200), + curve: Curves.easeInOutCubicEmphasized, + top: isShowControl || currentPlay?.file.type != ContentType.video + ? 0 + : -72, + left: 0, + right: 0, + child: SafeArea( + child: MouseRegion( + onHover: gesture.onHover, + child: GestureDetector( + onTap: () => showControl(), + child: DragAria( + child: TitleBar( + title: title, + actions: [const SizedBox(width: 8)], + color: contentColor, + overlayColor: overlayColor, + saveProgress: () => player.saveProgress(), + resizeWindow: () => resizeWindow(aspectRatio), + ), + ), + ), + ), + ), + ), + // 控制栏 + AnimatedPositioned( + duration: const Duration(milliseconds: 200), + curve: Curves.easeInOutCubicEmphasized, + bottom: isShowControl || currentPlay?.file.type != ContentType.video + ? 0 + : -128, + left: 0, + right: 0, + child: Align( + alignment: Alignment.bottomCenter, + child: SizedBox( + width: MediaQuery.of(context).size.width, + child: MouseRegion( + onHover: gesture.onHover, + child: GestureDetector( + onTap: () => showControl(), + child: ControlBar( + player: player, + showControl: showControl, + showControlForHover: showControlForHover, + color: contentColor, + overlayColor: overlayColor, + ), + ), + ), + ), + ), + ), + ], + ); + } +} diff --git a/lib/pages/player/iris_player.dart b/lib/pages/player/iris_player.dart deleted file mode 100644 index ef632a6..0000000 --- a/lib/pages/player/iris_player.dart +++ /dev/null @@ -1,696 +0,0 @@ -import 'dart:async'; -import 'dart:io'; -import 'package:desktop_drop/desktop_drop.dart'; -import 'package:flutter/material.dart'; -import 'package:flutter/services.dart'; -import 'package:flutter_hooks/flutter_hooks.dart'; -import 'package:flutter_zustand/flutter_zustand.dart'; -import 'package:iris/hooks/use_app_lifecycle.dart'; -import 'package:iris/hooks/use_cover.dart'; -import 'package:iris/hooks/use_full_screen.dart'; -import 'package:iris/hooks/use_gesture.dart'; -import 'package:iris/hooks/use_keyboard.dart'; -import 'package:iris/hooks/use_orientation.dart'; -import 'package:iris/info.dart'; -import 'package:iris/models/file.dart'; -import 'package:iris/models/player.dart'; -import 'package:iris/models/storages/local.dart'; -import 'package:iris/pages/player/audio.dart'; -import 'package:iris/pages/player/control_bar/control_bar_slider.dart'; -import 'package:iris/store/use_ui_store.dart'; -import 'package:iris/utils/check_content_type.dart'; -import 'package:iris/utils/logger.dart'; -import 'package:iris/utils/platform.dart'; -import 'package:iris/store/use_app_store.dart'; -import 'package:iris/store/use_play_queue_store.dart'; -import 'package:iris/utils/format_duration_to_minutes.dart'; -import 'package:iris/utils/get_localizations.dart'; -import 'package:iris/utils/resize_window.dart'; -import 'package:iris/widgets/title_bar.dart'; -import 'package:iris/pages/player/control_bar/control_bar.dart'; -import 'package:media_kit_video/media_kit_video.dart'; -import 'package:video_player/video_player.dart'; -import 'package:window_manager/window_manager.dart'; - -class IrisPlayer extends HookWidget { - const IrisPlayer({super.key, required this.player}); - - final MediaPlayer player; - - @override - Widget build(BuildContext context) { - useAppLifecycle(player); - useFullScreen(context); - useOrientation(context, player); - final cover = useCover(context, player); - - final isHover = useState(false); - final isShowControl = useState(true); - final isShowProgress = useState(false); - - final controlHideTimer = useRef(null); - final progressHideTimer = useRef(null); - final systemUiHideTimer = useRef(null); - - final t = getLocalizations(context); - final rate = useAppStore().select(context, (state) => state.rate); - final shuffle = useAppStore().select(context, (state) => state.shuffle); - final fit = useAppStore().select(context, (state) => state.fit); - final autoResize = - useAppStore().select(context, (state) => state.autoResize); - - final isFullScreen = - useUiStore().select(context, (state) => state.isFullScreen); - - final playQueue = - usePlayQueueStore().select(context, (state) => state.playQueue); - final currentIndex = - usePlayQueueStore().select(context, (state) => state.currentIndex); - - final int currentPlayIndex = useMemoized( - () => playQueue.indexWhere((element) => element.index == currentIndex), - [playQueue, currentIndex]); - - final PlayQueueItem? currentPlay = useMemoized( - () => playQueue.isEmpty || currentPlayIndex < 0 - ? null - : playQueue[currentPlayIndex], - [playQueue, currentPlayIndex]); - - final title = useMemoized( - () => currentPlay != null - ? playQueue.length > 1 - ? '[${currentPlayIndex + 1}/${playQueue.length}] ${currentPlay.file.name}' - : currentPlay.file.name - : INFO.title, - [currentPlay, currentPlayIndex, playQueue]); - - final contentColor = useMemoized( - () => Theme.of(context).brightness == Brightness.dark - ? Theme.of(context).colorScheme.onSurface - : Theme.of(context).colorScheme.surface, - [context]); - - final overlayColor = useMemoized( - () => - WidgetStateProperty.resolveWith((Set states) { - if (states.contains(WidgetState.pressed)) { - return contentColor.withValues(alpha: 0.2); - } else if (states.contains(WidgetState.hovered)) { - return contentColor.withValues(alpha: 0.2); - } - return null; - }), - [contentColor]); - - useEffect(() { - if (isDesktop) { - resizeWindow(!autoResize ? 0 : player.aspect); - } - return; - }, [player.aspect, autoResize]); - - final focusNode = useFocusNode(); - - useEffect(() { - focusNode.requestFocus(); - return; - }, []); - - final canPop = useState(false); - - useEffect(() { - final timer = Future.delayed(Duration(seconds: 4), () { - canPop.value = false; - }); - return () { - timer.ignore(); - }; - }, [canPop.value]); - - void startControlHideTimer() { - controlHideTimer.value = Timer( - const Duration(seconds: 5), - () { - if (isShowControl.value && !isHover.value) { - isShowControl.value = false; - } - }, - ); - } - - void startProgressHideTimer() { - progressHideTimer.value = Timer( - const Duration(seconds: 5), - () { - if (isShowProgress.value) { - isShowProgress.value = false; - } - }, - ); - } - - void startSystemUiHideTimer() { - systemUiHideTimer.value = Timer( - const Duration(seconds: 3), - () { - if (!isShowControl.value && - currentPlay?.file.type == ContentType.video) { - SystemChrome.setEnabledSystemUIMode(SystemUiMode.immersive); - } - }, - ); - } - - void resetControlHideTimer() { - controlHideTimer.value?.cancel(); - startControlHideTimer(); - } - - void resetBottomProgressTimer() { - progressHideTimer.value?.cancel(); - startProgressHideTimer(); - } - - void resetSystemUiHideTimer() { - systemUiHideTimer.value?.cancel(); - startSystemUiHideTimer(); - } - - void showControl() { - isShowControl.value = true; - isHover.value = false; - resetControlHideTimer(); - } - - void hideControl() { - isShowControl.value = false; - isHover.value = false; - controlHideTimer.value?.cancel(); - } - - Future showControlForHover(Future callback) async { - try { - player.saveProgress(); - showControl(); - isHover.value = true; - await callback; - showControl(); - } catch (e) { - logger(e.toString()); - } - } - - void showProgress() { - isShowProgress.value = true; - resetBottomProgressTimer(); - } - - final gesture = useGesture( - context: context, - player: player, - isFullScreen: isFullScreen, - showControl: showControl, - hideControl: hideControl, - showProgress: showProgress, - isHover: isHover, - isShowControl: isShowControl, - ); - - final onKeyEvent = useKeyboard( - context: context, - player: player, - isFullScreen: isFullScreen, - isShowControl: isShowControl, - showControl: showControl, - showControlForHover: showControlForHover, - showProgress: showProgress, - shuffle: shuffle, - ); - - useEffect(() { - startControlHideTimer(); - return () => controlHideTimer.value?.cancel(); - }, []); - - useEffect(() { - return () => progressHideTimer.value?.cancel(); - }, []); - - useEffect(() { - if (isDesktop) { - windowManager.setTitle(title); - } - return; - }, [title, player.isPlaying]); - - useEffect(() { - if (isShowControl.value || currentPlay?.file.type == ContentType.video) { - SystemChrome.setEnabledSystemUIMode(SystemUiMode.edgeToEdge); - systemUiHideTimer.value?.cancel(); - } else { - SystemChrome.setEnabledSystemUIMode(SystemUiMode.immersive); - } - return; - }, [isShowControl.value, currentPlay?.file.type]); - - useEffect(() { - SystemChrome.setSystemUIChangeCallback((value) async { - if (value) { - resetSystemUiHideTimer(); - } - }); - return null; - }, []); - - final scaleFactor = useMemoized( - () => - View.of(context).physicalSize.width / - MediaQuery.of(context).size.width, - [View.of(context).physicalSize.width, MediaQuery.of(context).size.width], - ); - - final videoViewSize = useMemoized(() { - if (fit != BoxFit.none || player.width == 0 || player.height == 0) { - return MediaQuery.of(context).size; - } else { - return Size(player.width! / scaleFactor, player.height! / scaleFactor); - } - }, [ - fit, - MediaQuery.of(context).size, - player.width, - player.height, - scaleFactor - ]); - - final videoViewOffset = useMemoized( - () => fit == BoxFit.none - ? Offset( - (MediaQuery.of(context).size.width - videoViewSize.width) / 2, - (MediaQuery.of(context).size.height - videoViewSize.height) / 2, - ) - : Offset(0, 0), - [fit, MediaQuery.of(context).size, videoViewSize]); - - return DropTarget( - onDragDone: (details) async { - final files = details.files - .map((file) => checkContentType(file.path) == ContentType.video || - checkContentType(file.path) == ContentType.audio - ? file.path - : null) - .where((element) => element != null) - .toList() as List; - if (files.isNotEmpty) { - final firstFile = files[0]; - if (firstFile.isEmpty) return; - final playQueue = await getLocalPlayQueue(firstFile); - if (playQueue == null || playQueue.playQueue.isEmpty) return; - final List filteredPlayQueue = []; - for (final item in playQueue.playQueue) { - final file = item.file; - if (files.contains(file.uri)) { - filteredPlayQueue.add(item); - } - } - if (filteredPlayQueue.isEmpty) return; - useAppStore().updateAutoPlay(true); - usePlayQueueStore().update( - playQueue: filteredPlayQueue, index: playQueue.currentIndex); - } - }, - child: PopScope( - canPop: false, - onPopInvokedWithResult: (bool didPop, Object? result) async { - if (!didPop) { - await player.saveProgress(); - if (!canPop.value) { - canPop.value = true; - if (context.mounted) { - ScaffoldMessenger.of(context).showSnackBar( - SnackBar(content: Text(t.exit_app_back_again)), - ); - } - } else { - exit(0); - } - } - }, - child: KeyboardListener( - focusNode: focusNode, - onKeyEvent: onKeyEvent, - child: Stack( - children: [ - // Video - Positioned( - left: 0, - top: 0, - right: 0, - bottom: 0, - child: MouseRegion( - cursor: isShowControl.value || player.isPlaying == false - ? SystemMouseCursors.basic - : SystemMouseCursors.none, - onHover: gesture.onHover, - child: GestureDetector( - onTap: gesture.onTap, - onTapDown: gesture.onTapDown, - onDoubleTapDown: gesture.onDoubleTapDown, - onLongPressStart: gesture.onLongPressStart, - onLongPressMoveUpdate: gesture.onLongPressMoveUpdate, - onLongPressEnd: gesture.onLongPressEnd, - onLongPressCancel: gesture.onLongPressCancel, - onPanStart: gesture.onPanStart, - onPanUpdate: gesture.onPanUpdate, - onPanEnd: gesture.onPanEnd, - onPanCancel: gesture.onPanCancel, - child: Stack( - children: [ - Positioned( - left: 0, - top: 0, - right: 0, - bottom: 0, - child: Container( - color: Colors.black, - ), - ), - Positioned( - left: videoViewOffset.dx, - top: videoViewOffset.dy, - width: videoViewSize.width, - height: videoViewSize.height, - child: switch (player) { - MediaKitPlayer player => Video( - key: ValueKey(currentPlay?.file.uri), - controller: player.controller, - controls: NoVideoControls, - fit: - fit == BoxFit.none ? BoxFit.contain : fit, - ), - FvpPlayer player => FittedBox( - fit: fit, - child: SizedBox( - width: player.width, - height: player.height, - child: VideoPlayer(player.controller), - ), - ), - _ => Container(), - }), - // Audio - if (currentPlay?.file.type == ContentType.audio) - Positioned( - left: 0, - top: 0, - right: 0, - bottom: 0, - child: Audio(cover: cover), - ), - // 播放速度 - if (rate != 1.0 && gesture.isLongPress) - Positioned( - left: 0, - top: 0, - right: 0, - bottom: 0, - child: Center( - child: Container( - padding: - const EdgeInsets.fromLTRB(12, 12, 18, 12), - decoration: BoxDecoration( - color: Colors.black54, - borderRadius: BorderRadius.circular(8), - ), - child: Row( - mainAxisSize: MainAxisSize.min, - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Transform.translate( - offset: const Offset(0, 1.5), - child: Icon( - Icons.speed_rounded, - color: Colors.white, - size: 24, - ), - ), - const SizedBox(width: 10), - Text( - rate.toString(), - style: const TextStyle( - color: Colors.white, - fontSize: 20, - height: 1, - ), - ), - ], - ), - ), - ), - ), - // 屏幕亮度 - if (gesture.isLeftGesture && gesture.brightness != null) - Positioned( - left: 0, - top: 0, - right: 0, - bottom: 0, - child: Center( - child: Container( - padding: - const EdgeInsets.fromLTRB(12, 12, 18, 12), - decoration: BoxDecoration( - color: Colors.black54, - borderRadius: BorderRadius.circular(8), - ), - child: Row( - mainAxisSize: MainAxisSize.min, - children: [ - Icon( - gesture.brightness == 0 - ? Icons.brightness_low_rounded - : gesture.brightness! < 1 - ? Icons.brightness_medium_rounded - : Icons.brightness_high_rounded, - color: Colors.white, - size: 24, - ), - const SizedBox(width: 12), - SizedBox( - width: 100, - child: LinearProgressIndicator( - value: gesture.brightness, - borderRadius: BorderRadius.circular(4), - backgroundColor: Colors.grey, - valueColor: - AlwaysStoppedAnimation( - Colors.white), - ), - ), - ], - ), - ), - ), - ), - // 音量 - if (gesture.isRightGesture && gesture.volume != null) - Positioned( - left: 0, - top: 0, - right: 0, - bottom: 0, - child: Center( - child: Container( - padding: - const EdgeInsets.fromLTRB(12, 12, 18, 12), - decoration: BoxDecoration( - color: Colors.black54, - borderRadius: BorderRadius.circular(8), - ), - child: Row( - mainAxisSize: MainAxisSize.min, - children: [ - Icon( - gesture.volume == 0 - ? Icons.volume_mute_rounded - : gesture.volume! < 0.5 - ? Icons.volume_down_rounded - : Icons.volume_up_rounded, - color: Colors.white, - size: 24, - ), - const SizedBox(width: 12), - SizedBox( - width: 100, - child: LinearProgressIndicator( - value: gesture.volume, - borderRadius: BorderRadius.circular(4), - backgroundColor: Colors.grey, - valueColor: - AlwaysStoppedAnimation( - Colors.white, - ), - ), - ), - ], - ), - ), - ), - ), - if (isShowProgress.value && - !isShowControl.value && - currentPlay?.file.type == ContentType.video) - Positioned( - left: -28, - right: -28, - bottom: -16, - height: 32, - child: ControlBarSlider( - player: player, - showControl: showControl, - disabled: true, - ), - ), - if (isShowProgress.value && - !isShowControl.value && - currentPlay?.file.type == ContentType.video) - Positioned( - left: 12, - top: 12, - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - currentPlay != null ? title : '', - style: TextStyle( - color: Colors.white, - fontSize: 20, - height: 1, - decoration: TextDecoration.none, - shadows: const [ - Shadow( - color: Colors.black, - offset: Offset(0, 0), - blurRadius: 1, - ), - ], - ), - ), - ], - ), - ), - if (isShowProgress.value && - !isShowControl.value && - currentPlay?.file.type == ContentType.video) - Positioned( - left: 12, - bottom: 6, - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - '${formatDurationToMinutes(player.position)} / ${formatDurationToMinutes(player.duration)}', - style: TextStyle( - color: Colors.white, - fontSize: 16, - height: 2, - decoration: TextDecoration.none, - shadows: const [ - Shadow( - color: Colors.black, - offset: Offset(0, 0), - blurRadius: 1, - ), - ], - ), - ), - ], - ), - ), - ], - ), - ), - ), - ), - // 标题栏 - AnimatedPositioned( - duration: const Duration(milliseconds: 200), - curve: Curves.easeInOutCubicEmphasized, - top: isShowControl.value || - currentPlay?.file.type != ContentType.video - ? 0 - : -72, - left: 0, - right: 0, - child: SafeArea( - child: MouseRegion( - onHover: gesture.onHover, - child: GestureDetector( - onTap: () => showControl(), - onDoubleTap: () async { - if (isFullScreen) { - await useUiStore().updateFullScreen(false); - } else { - if (isDesktop && await windowManager.isMaximized()) { - await windowManager.unmaximize(); - await resizeWindow(player.aspect); - } else { - await windowManager.maximize(); - } - } - }, - onPanStart: (details) async { - if (isDesktop) { - windowManager.startDragging(); - } - }, - child: TitleBar( - title: title, - actions: [const SizedBox(width: 8)], - color: contentColor, - overlayColor: overlayColor, - saveProgress: () => player.saveProgress(), - resizeWindow: () => resizeWindow(player.aspect), - ), - ), - ), - ), - ), - // 控制栏 - AnimatedPositioned( - duration: const Duration(milliseconds: 200), - curve: Curves.easeInOutCubicEmphasized, - bottom: isShowControl.value || - currentPlay?.file.type != ContentType.video - ? 0 - : -128, - left: 0, - right: 0, - child: Align( - alignment: Alignment.bottomCenter, - child: SizedBox( - width: MediaQuery.of(context).size.width, - child: MouseRegion( - onHover: gesture.onHover, - child: GestureDetector( - onTap: () => showControl(), - child: ControlBar( - player: player, - showControl: showControl, - showControlForHover: showControlForHover, - color: contentColor, - overlayColor: overlayColor, - ), - ), - ), - ), - ), - ), - ], - ), - ), - ), - ); - } -} diff --git a/lib/pages/player/player.dart b/lib/pages/player/player.dart new file mode 100644 index 0000000..140932e --- /dev/null +++ b/lib/pages/player/player.dart @@ -0,0 +1,348 @@ +import 'dart:async'; +import 'dart:io'; +import 'package:desktop_drop/desktop_drop.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_hooks/flutter_hooks.dart'; +import 'package:flutter_zustand/flutter_zustand.dart'; +import 'package:iris/hooks/use_app_lifecycle.dart'; +import 'package:iris/hooks/use_cover.dart'; +import 'package:iris/hooks/use_keyboard.dart'; +import 'package:iris/info.dart'; +import 'package:iris/models/file.dart'; +import 'package:iris/models/player.dart'; +import 'package:iris/models/storages/local.dart'; +import 'package:iris/pages/player/audio.dart'; +import 'package:iris/pages/player/controls_overlay.dart'; +import 'package:iris/pages/player/video_view.dart'; +import 'package:iris/store/use_player_ui_store.dart'; +import 'package:iris/utils/check_content_type.dart'; +import 'package:iris/utils/logger.dart'; +import 'package:iris/utils/platform.dart'; +import 'package:iris/store/use_app_store.dart'; +import 'package:iris/store/use_play_queue_store.dart'; +import 'package:iris/utils/get_localizations.dart'; +import 'package:window_manager/window_manager.dart'; + +class Player extends HookWidget { + const Player({super.key, required this.player}); + + final MediaPlayer player; + + @override + Widget build(BuildContext context) { + final t = getLocalizations(context); + + useAppLifecycle(player.saveProgress); + + final cover = useCover(player.isPlaying); + + final playerUiStore = usePlayerUiStore(); + + final isHovering = + playerUiStore.select(context, (state) => state.isHovering); + final isShowControl = + playerUiStore.select(context, (state) => state.isShowControl); + final isShowProgress = + playerUiStore.select(context, (state) => state.isShowProgress); + + final updateIsHovering = useCallback((bool value) { + playerUiStore.updateIsHovering(value); + }, [playerUiStore.updateIsHovering]); + + final updateIsShowControl = useCallback((bool value) { + playerUiStore.updateIsShowControl(value); + }, [playerUiStore.updateIsShowControl]); + + final updateIsShowProgress = useCallback((bool value) { + playerUiStore.updateIsShowProgress(value); + }, [playerUiStore.updateIsShowProgress]); + + final controlHideTimer = useRef(null); + final progressHideTimer = useRef(null); + final systemUiHideTimer = useRef(null); + + final fit = useAppStore().select(context, (state) => state.fit); + + final playQueue = + usePlayQueueStore().select(context, (state) => state.playQueue); + final currentIndex = + usePlayQueueStore().select(context, (state) => state.currentIndex); + + final int currentPlayIndex = useMemoized( + () => playQueue.indexWhere((element) => element.index == currentIndex), + [playQueue, currentIndex]); + + final PlayQueueItem? currentPlay = useMemoized( + () => playQueue.isEmpty || currentPlayIndex < 0 + ? null + : playQueue[currentPlayIndex], + [playQueue, currentPlayIndex]); + + final title = useMemoized( + () => currentPlay != null + ? playQueue.length > 1 + ? '[${currentPlayIndex + 1}/${playQueue.length}] ${currentPlay.file.name}' + : currentPlay.file.name + : INFO.title, + [currentPlay, currentPlayIndex, playQueue]); + + final focusNode = useFocusNode(); + + useEffect(() { + focusNode.requestFocus(); + return; + }, []); + + final canPop = useState(false); + + useEffect(() { + final timer = Future.delayed(Duration(seconds: 4), () { + canPop.value = false; + }); + return () { + timer.ignore(); + }; + }, [canPop.value]); + + final startControlHideTimer = useCallback(() { + controlHideTimer.value = Timer( + const Duration(seconds: 5), + () { + if (isShowControl && !isHovering) { + updateIsShowControl(false); + } + }, + ); + }, [isShowControl, isHovering, updateIsShowControl]); + + final startProgressHideTimer = useCallback(() { + progressHideTimer.value = Timer( + const Duration(seconds: 5), + () { + if (isShowProgress) { + updateIsShowProgress(false); + } + }, + ); + }, [isShowProgress, updateIsShowProgress]); + + final startSystemUiHideTimer = useCallback(() { + systemUiHideTimer.value = Timer( + const Duration(seconds: 3), + () { + if (!isShowControl && currentPlay?.file.type == ContentType.video) { + SystemChrome.setEnabledSystemUIMode(SystemUiMode.immersive); + } + }, + ); + }, [isShowControl, currentPlay?.file.type]); + + final resetControlHideTimer = useCallback(() { + controlHideTimer.value?.cancel(); + startControlHideTimer(); + }, [startControlHideTimer]); + + final resetBottomProgressTimer = useCallback(() { + progressHideTimer.value?.cancel(); + startProgressHideTimer(); + }, [startProgressHideTimer]); + + final resetSystemUiHideTimer = useCallback(() { + systemUiHideTimer.value?.cancel(); + startSystemUiHideTimer(); + }, [startSystemUiHideTimer]); + + final showControl = useCallback(() { + updateIsShowControl(true); + updateIsHovering(false); + resetControlHideTimer(); + }, [updateIsShowControl, updateIsHovering, resetControlHideTimer]); + + final hideControl = useCallback(() { + updateIsShowControl(false); + updateIsHovering(false); + controlHideTimer.value?.cancel(); + }, [updateIsShowControl, updateIsHovering]); + + final showControlForHover = useCallback((Future callback) async { + try { + player.saveProgress(); + showControl(); + updateIsHovering(true); + await callback; + showControl(); + } catch (e) { + logger(e.toString()); + } + }, [showControl, updateIsHovering]); + + final showProgress = useCallback(() { + updateIsShowProgress(true); + resetBottomProgressTimer(); + }, [updateIsShowProgress, resetBottomProgressTimer]); + + final onKeyEvent = useKeyboard( + player: player, + showControl: showControl, + showControlForHover: showControlForHover, + showProgress: showProgress, + ); + + useEffect(() { + startControlHideTimer(); + return () => controlHideTimer.value?.cancel(); + }, []); + + useEffect(() { + return () => progressHideTimer.value?.cancel(); + }, []); + + useEffect(() { + if (isDesktop) { + windowManager.setTitle(title); + } + return; + }, [title, player.isPlaying]); + + useEffect(() { + if (isShowControl || currentPlay?.file.type == ContentType.video) { + SystemChrome.setEnabledSystemUIMode(SystemUiMode.edgeToEdge); + systemUiHideTimer.value?.cancel(); + } else { + SystemChrome.setEnabledSystemUIMode(SystemUiMode.immersive); + } + return; + }, [isShowControl, currentPlay?.file.type]); + + useEffect(() { + SystemChrome.setSystemUIChangeCallback((value) async { + if (value) { + resetSystemUiHideTimer(); + } + }); + return null; + }, []); + + final scaleFactor = useMemoized( + () => + View.of(context).physicalSize.width / + MediaQuery.of(context).size.width, + [View.of(context).physicalSize.width, MediaQuery.of(context).size.width], + ); + + final videoViewSize = useMemoized(() { + if (fit != BoxFit.none || player.width == 0 || player.height == 0) { + return MediaQuery.of(context).size; + } else { + return Size(player.width! / scaleFactor, player.height! / scaleFactor); + } + }, [ + fit, + MediaQuery.of(context).size, + player.width, + player.height, + scaleFactor + ]); + + final videoViewOffset = useMemoized( + () => fit == BoxFit.none + ? Offset( + (MediaQuery.of(context).size.width - videoViewSize.width) / 2, + (MediaQuery.of(context).size.height - videoViewSize.height) / 2, + ) + : Offset(0, 0), + [fit, MediaQuery.of(context).size, videoViewSize]); + + return DropTarget( + onDragDone: (details) async { + final files = details.files + .map((file) => checkContentType(file.path) == ContentType.video || + checkContentType(file.path) == ContentType.audio + ? file.path + : null) + .where((element) => element != null) + .toList() as List; + if (files.isNotEmpty) { + final firstFile = files[0]; + if (firstFile.isEmpty) return; + final playQueue = await getLocalPlayQueue(firstFile); + if (playQueue == null || playQueue.playQueue.isEmpty) return; + final List filteredPlayQueue = []; + for (final item in playQueue.playQueue) { + final file = item.file; + if (files.contains(file.uri)) { + filteredPlayQueue.add(item); + } + } + if (filteredPlayQueue.isEmpty) return; + useAppStore().updateAutoPlay(true); + usePlayQueueStore().update( + playQueue: filteredPlayQueue, index: playQueue.currentIndex); + } + }, + child: PopScope( + canPop: false, + onPopInvokedWithResult: (bool didPop, Object? result) async { + if (!didPop) { + await player.saveProgress(); + if (!canPop.value) { + canPop.value = true; + if (context.mounted) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar(content: Text(t.exit_app_back_again)), + ); + } + } else { + exit(0); + } + } + }, + child: KeyboardListener( + focusNode: focusNode, + onKeyEvent: onKeyEvent, + child: Stack( + children: [ + // Video + Positioned( + left: videoViewOffset.dx, + top: videoViewOffset.dy, + width: videoViewSize.width, + height: videoViewSize.height, + child: VideoView( + key: ValueKey(currentPlay?.file.uri), + player: player, + fit: fit, + ), + ), + // Audio + if (currentPlay?.file.type == ContentType.audio) + Positioned( + left: 0, + top: 0, + right: 0, + bottom: 0, + child: Audio(cover: cover), + ), + Positioned( + left: 0, + top: 0, + right: 0, + bottom: 0, + child: ControlsOverlay( + player: player, + currentPlay: currentPlay, + title: title, + showControl: showControl, + showControlForHover: showControlForHover, + hideControl: hideControl, + showProgress: showProgress, + ), + ), + ], + ), + ), + ), + ); + } +} diff --git a/lib/pages/player/player_view.dart b/lib/pages/player/player_view.dart new file mode 100644 index 0000000..3193c65 --- /dev/null +++ b/lib/pages/player/player_view.dart @@ -0,0 +1,42 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_hooks/flutter_hooks.dart'; +import 'package:iris/hooks/player/use_fvp_player.dart'; +import 'package:iris/hooks/player/use_media_kit_player.dart'; +import 'package:iris/models/store/app_state.dart'; +import 'package:iris/pages/player/player.dart'; + +class PlayerView extends HookWidget { + const PlayerView({super.key, required this.playerBackend}); + + final PlayerBackend playerBackend; + + @override + Widget build(BuildContext context) { + switch (playerBackend) { + case PlayerBackend.mediaKit: + return const _MediaKitPlayerHost(); + case PlayerBackend.fvp: + return const _FvpPlayerHost(); + } + } +} + +class _MediaKitPlayerHost extends HookWidget { + const _MediaKitPlayerHost(); + + @override + Widget build(BuildContext context) { + final player = useMediaKitPlayer(context); + return Player(player: player); + } +} + +class _FvpPlayerHost extends HookWidget { + const _FvpPlayerHost(); + + @override + Widget build(BuildContext context) { + final player = useFvpPlayer(context); + return Player(player: player); + } +} diff --git a/lib/pages/player/video_view.dart b/lib/pages/player/video_view.dart new file mode 100644 index 0000000..ca20f9a --- /dev/null +++ b/lib/pages/player/video_view.dart @@ -0,0 +1,36 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_hooks/flutter_hooks.dart'; +import 'package:iris/models/player.dart'; +import 'package:media_kit_video/media_kit_video.dart'; +import 'package:video_player/video_player.dart'; + +class VideoView extends HookWidget { + const VideoView({ + super.key, + required this.player, + required this.fit, + }); + + final MediaPlayer player; + final BoxFit fit; + + @override + Widget build(BuildContext context) { + return switch (player) { + MediaKitPlayer player => Video( + controller: player.controller, + controls: NoVideoControls, + fit: fit == BoxFit.none ? BoxFit.contain : fit, + ), + FvpPlayer player => FittedBox( + fit: fit, + child: SizedBox( + width: player.width, + height: player.height, + child: VideoPlayer(player.controller), + ), + ), + _ => Container(), + }; + } +} diff --git a/lib/pages/settings/libraries.dart b/lib/pages/settings/dependencies.dart similarity index 96% rename from lib/pages/settings/libraries.dart rename to lib/pages/settings/dependencies.dart index 193e914..a04ea79 100644 --- a/lib/pages/settings/libraries.dart +++ b/lib/pages/settings/dependencies.dart @@ -3,8 +3,8 @@ import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:iris/oss_licenses.dart'; import 'package:iris/utils/url.dart'; -class Libraries extends HookWidget { - const Libraries({super.key}); +class Dependencies extends HookWidget { + const Dependencies({super.key}); static const title = 'Libraries'; diff --git a/lib/pages/settings/play.dart b/lib/pages/settings/play.dart index 51d9198..7f77b2b 100644 --- a/lib/pages/settings/play.dart +++ b/lib/pages/settings/play.dart @@ -48,8 +48,7 @@ class Play extends HookWidget { DropdownMenuItem( value: PlayerBackend.mediaKit, child: Text('Media Kit')), DropdownMenuItem( - value: PlayerBackend.fvp, - child: Text('FVP (${t.experimental})')), + value: PlayerBackend.fvp, child: Text('FVP')), ], )), Visibility( diff --git a/lib/pages/settings/settings.dart b/lib/pages/settings/settings.dart index 02cfdd6..8066735 100644 --- a/lib/pages/settings/settings.dart +++ b/lib/pages/settings/settings.dart @@ -2,7 +2,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:iris/pages/settings/about.dart'; import 'package:iris/pages/settings/general.dart'; -import 'package:iris/pages/settings/libraries.dart'; +import 'package:iris/pages/settings/dependencies.dart'; import 'package:iris/pages/settings/play.dart'; import 'package:iris/utils/get_localizations.dart'; @@ -29,7 +29,7 @@ class Settings extends HookWidget { ITab(title: t.general, child: const General()), ITab(title: t.play, child: const Play()), ITab(title: t.about, child: const About()), - ITab(title: t.libraries, child: const Libraries()), + ITab(title: t.dependencies, child: const Dependencies()), ]; final tabController = useTabController(initialLength: tabs.length); diff --git a/lib/store/use_play_queue_store.dart b/lib/store/use_play_queue_store.dart index 3816573..15db8aa 100644 --- a/lib/store/use_play_queue_store.dart +++ b/lib/store/use_play_queue_store.dart @@ -103,15 +103,21 @@ class PlayQueueStore extends PersistentStore { Future previous() async { final int currentPlayIndex = state.playQueue .indexWhere((element) => element.index == state.currentIndex); - if (currentPlayIndex <= 0) return; - await updateCurrentIndex(state.playQueue[currentPlayIndex - 1].index); + if (currentPlayIndex <= 0) { + await updateCurrentIndex(state.playQueue.last.index); + } else { + await updateCurrentIndex(state.playQueue[currentPlayIndex - 1].index); + } } Future next() async { final int currentPlayIndex = state.playQueue .indexWhere((element) => element.index == state.currentIndex); - if (currentPlayIndex >= state.playQueue.length - 1) return; - await updateCurrentIndex(state.playQueue[currentPlayIndex + 1].index); + if (currentPlayIndex >= state.playQueue.length - 1) { + await updateCurrentIndex(state.playQueue.first.index); + } else { + await updateCurrentIndex(state.playQueue[currentPlayIndex + 1].index); + } } Future shuffle() async => update( diff --git a/lib/store/use_player_ui_store.dart b/lib/store/use_player_ui_store.dart new file mode 100644 index 0000000..6484548 --- /dev/null +++ b/lib/store/use_player_ui_store.dart @@ -0,0 +1,44 @@ +import 'package:flutter_zustand/flutter_zustand.dart'; +import 'package:iris/models/store/player_ui_state.dart'; +import 'package:iris/utils/platform.dart'; +import 'package:window_manager/window_manager.dart'; + +class PlayerUiStore extends Store { + PlayerUiStore() : super(const PlayerUiState()); + + void updateAspectRatio(double ratio) { + set(state.copyWith(aspectRatio: ratio)); + } + + Future toggleIsAlwaysOnTop() async { + if (isDesktop) { + windowManager.setAlwaysOnTop(!state.isAlwaysOnTop); + set(state.copyWith(isAlwaysOnTop: !state.isAlwaysOnTop)); + } + } + + Future updateFullScreen(bool bool) async { + if (isDesktop) { + windowManager.setFullScreen(!state.isFullScreen); + set(state.copyWith(isFullScreen: !state.isFullScreen)); + } + } + + void updateIsSeeking(bool bool) { + set(state.copyWith(isSeeking: bool)); + } + + void updateIsHovering(bool bool) { + set(state.copyWith(isHovering: bool)); + } + + void updateIsShowControl(bool bool) { + set(state.copyWith(isShowControl: bool)); + } + + void updateIsShowProgress(bool bool) { + set(state.copyWith(isShowProgress: bool)); + } +} + +PlayerUiStore usePlayerUiStore() => create(() => PlayerUiStore()); diff --git a/lib/store/use_ui_store.dart b/lib/store/use_ui_store.dart deleted file mode 100644 index 3a81fad..0000000 --- a/lib/store/use_ui_store.dart +++ /dev/null @@ -1,24 +0,0 @@ -import 'package:flutter_zustand/flutter_zustand.dart'; -import 'package:iris/models/store/ui_state.dart'; -import 'package:iris/utils/platform.dart'; -import 'package:window_manager/window_manager.dart'; - -class UiStore extends Store { - UiStore() : super(const UiState()); - - Future toggleIsAlwaysOnTop() async { - if (isDesktop) { - windowManager.setAlwaysOnTop(!state.isAlwaysOnTop); - set(state.copyWith(isAlwaysOnTop: !state.isAlwaysOnTop)); - } - } - - Future updateFullScreen(bool bool) async { - if (isDesktop) { - windowManager.setFullScreen(!state.isFullScreen); - set(state.copyWith(isFullScreen: !state.isFullScreen)); - } - } -} - -UiStore useUiStore() => create(() => UiStore()); diff --git a/lib/widgets/drag_aria.dart b/lib/widgets/drag_aria.dart new file mode 100644 index 0000000..be887df --- /dev/null +++ b/lib/widgets/drag_aria.dart @@ -0,0 +1,43 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_zustand/flutter_zustand.dart'; +import 'package:iris/store/use_player_ui_store.dart'; +import 'package:iris/utils/platform.dart'; +import 'package:iris/utils/resize_window.dart'; +import 'package:window_manager/window_manager.dart'; + +class DragAria extends StatelessWidget { + const DragAria({ + super.key, + required this.child, + }); + + final Widget child; + @override + Widget build(BuildContext context) { + final aspectRatio = + usePlayerUiStore().select(context, (state) => state.aspectRatio); + final isFullScreen = + usePlayerUiStore().select(context, (state) => state.isFullScreen); + + return GestureDetector( + onDoubleTap: () async { + if (isFullScreen) { + await usePlayerUiStore().updateFullScreen(false); + } else { + if (isDesktop && await windowManager.isMaximized()) { + await windowManager.unmaximize(); + await resizeWindow(aspectRatio); + } else { + await windowManager.maximize(); + } + } + }, + onPanStart: (details) async { + if (isDesktop) { + windowManager.startDragging(); + } + }, + child: child, + ); + } +} diff --git a/lib/widgets/title_bar.dart b/lib/widgets/title_bar.dart index 723a683..28124fe 100644 --- a/lib/widgets/title_bar.dart +++ b/lib/widgets/title_bar.dart @@ -2,7 +2,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:flutter_zustand/flutter_zustand.dart'; import 'package:iris/info.dart'; -import 'package:iris/store/use_ui_store.dart'; +import 'package:iris/store/use_player_ui_store.dart'; import 'package:iris/utils/get_localizations.dart'; import 'package:iris/utils/platform.dart'; import 'package:window_manager/window_manager.dart'; @@ -29,9 +29,9 @@ class TitleBar extends HookWidget { Widget build(BuildContext context) { final t = getLocalizations(context); final isAlwaysOnTop = - useUiStore().select(context, (state) => state.isAlwaysOnTop); + usePlayerUiStore().select(context, (state) => state.isAlwaysOnTop); final isFullScreen = - useUiStore().select(context, (state) => state.isFullScreen); + usePlayerUiStore().select(context, (state) => state.isFullScreen); return Container( padding: isDesktop @@ -103,7 +103,8 @@ class TitleBar extends HookWidget { size: 18, color: color, ), - onPressed: useUiStore().toggleIsAlwaysOnTop, + onPressed: + usePlayerUiStore().toggleIsAlwaysOnTop, style: ButtonStyle(overlayColor: overlayColor), ), ), @@ -124,7 +125,8 @@ class TitleBar extends HookWidget { if (isFullScreen) { await resizeWindow?.call(); } - useUiStore().updateFullScreen(!isFullScreen); + usePlayerUiStore() + .updateFullScreen(!isFullScreen); }, style: ButtonStyle(overlayColor: overlayColor), ), diff --git a/pubspec.lock b/pubspec.lock index 81924dd..024550d 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -17,6 +17,14 @@ packages: url: "https://pub.dev" source: hosted version: "8.1.1" + analyzer_plugin: + dependency: transitive + description: + name: analyzer_plugin + sha256: dd574a0ab77de88b7d9c12bc4b626109a5ca9078216a79041a5c24c3a1bd103c + url: "https://pub.dev" + source: hosted + version: "0.13.7" android_x_storage: dependency: "direct main" description: @@ -169,6 +177,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.0.4" + ci: + dependency: transitive + description: + name: ci + sha256: "145d095ce05cddac4d797a158bc4cf3b6016d1fe63d8c3d2fbd7212590adca13" + url: "https://pub.dev" + source: hosted + version: "0.1.0" cli_util: dependency: transitive description: @@ -257,8 +273,40 @@ packages: url: "https://pub.dev" source: hosted version: "1.0.8" - dart_pubspec_licenses: + custom_lint: + dependency: "direct dev" + description: + name: custom_lint + sha256: "751ee9440920f808266c3ec2553420dea56d3c7837dd2d62af76b11be3fcece5" + url: "https://pub.dev" + source: hosted + version: "0.8.1" + custom_lint_builder: + dependency: transitive + description: + name: custom_lint_builder + sha256: "1128db6f58e71d43842f3b9be7465c83f0c47f4dd8918f878dd6ad3b72a32072" + url: "https://pub.dev" + source: hosted + version: "0.8.1" + custom_lint_core: + dependency: transitive + description: + name: custom_lint_core + sha256: "85b339346154d5646952d44d682965dfe9e12cae5febd706f0db3aa5010d6423" + url: "https://pub.dev" + source: hosted + version: "0.8.1" + custom_lint_visitor: dependency: transitive + description: + name: custom_lint_visitor + sha256: "446d68322747ec1c36797090de776aa72228818d3d80685a91ff524d163fee6d" + url: "https://pub.dev" + source: hosted + version: "1.0.0+8.1.1" + dart_pubspec_licenses: + dependency: "direct dev" description: name: dart_pubspec_licenses sha256: "3e6545923039e35719415dfd2132574083a8bb5c31db074e7b63493cb6c28447" @@ -284,11 +332,12 @@ packages: desktop_drop: dependency: "direct main" description: - name: desktop_drop - sha256: "927511f590ce01ee90d0d80f79bc71b9c919d8522d01e495e89a00c6f4a4fb5b" - url: "https://pub.dev" - source: hosted - version: "0.6.1" + path: "packages/desktop_drop" + ref: main + resolved-ref: c1b887a0c225c5f9f91917a107f52d4d4d5814ee + url: "https://github.com/nini22P/flutter-plugins" + source: git + version: "0.7.0" device_info_plus: dependency: "direct main" description: @@ -415,6 +464,14 @@ packages: url: "https://pub.dev" source: hosted version: "0.21.3+1" + flutter_hooks_lint: + dependency: "direct dev" + description: + name: flutter_hooks_lint + sha256: "8178fbc6270cc69722d84c1743517d6f3031786fa9c8daefd2ecdccd0b6e0bed" + url: "https://pub.dev" + source: hosted + version: "1.4.0" flutter_lints: dependency: "direct dev" description: @@ -436,14 +493,6 @@ packages: url: "https://pub.dev" source: hosted version: "0.7.7+1" - flutter_oss_licenses: - dependency: "direct dev" - description: - name: flutter_oss_licenses - sha256: "57c1e38b44ed8815d0303c278238b54796c4c4f57b0c4b89fa7f3c5359ac815e" - url: "https://pub.dev" - source: hosted - version: "3.0.6" flutter_plugin_android_lifecycle: dependency: transitive description: @@ -598,6 +647,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.1.0" + hotreloader: + dependency: transitive + description: + name: hotreloader + sha256: bc167a1163807b03bada490bfe2df25b0d744df359227880220a5cbd04e5734b + url: "https://pub.dev" + source: hosted + version: "4.3.0" html: dependency: transitive description: @@ -1087,6 +1144,14 @@ packages: url: "https://pub.dev" source: hosted version: "0.7.5" + rxdart: + dependency: transitive + description: + name: rxdart + sha256: "5c3004a4a8dbb94bd4bf5412a4def4acdaa12e12f269737a5751369e12d1a962" + url: "https://pub.dev" + source: hosted + version: "0.28.0" saf_util: dependency: "direct main" description: diff --git a/pubspec.yaml b/pubspec.yaml index c539288..8afb00d 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -41,7 +41,11 @@ dependencies: disks_desktop: ^1.0.1 android_x_storage: ^1.0.2 permission_handler: ^12.0.1 - desktop_drop: ^0.6.1 + desktop_drop: + git: + url: https://github.com/nini22P/flutter-plugins + ref: main + path: packages/desktop_drop app_links: ^6.4.1 device_info_plus: ^12.0.0 saf_util: ^0.11.0 @@ -65,11 +69,13 @@ dev_dependencies: flutter_test: sdk: flutter flutter_lints: ^6.0.0 - flutter_oss_licenses: ^3.0.6 + dart_pubspec_licenses: ^3.0.8 build_runner: ^2.8.0 freezed: ^3.2.3 json_serializable: ^6.11.1 msix: ^3.16.12 + flutter_hooks_lint: ^1.4.0 + custom_lint: ^0.8.1 flutter: generate: true From d7f9ec1e795ab707cf50ac973bff4c9424be046c Mon Sep 17 00:00:00 2001 From: 22 <60903333+nini22P@users.noreply.github.com> Date: Sun, 14 Sep 2025 18:33:37 +0800 Subject: [PATCH 14/31] improve long press check --- lib/hooks/use_gesture.dart | 1 + lib/pages/player/controls_overlay.dart | 2 +- pubspec.lock | 11 +++++------ pubspec.yaml | 6 +----- 4 files changed, 8 insertions(+), 12 deletions(-) diff --git a/lib/hooks/use_gesture.dart b/lib/hooks/use_gesture.dart index 8a9741c..3461c33 100644 --- a/lib/hooks/use_gesture.dart +++ b/lib/hooks/use_gesture.dart @@ -148,6 +148,7 @@ Gesture useGesture({ } void onLongPressMoveUpdate(LongPressMoveUpdateDetails details) { + if (!isLongPress.value) return; int fast = (details.offsetFromOrigin.dx / 50).toInt(); if (fast >= 1) { useAppStore().updateRate(fast > 4 ? 5.0 : (1 + fast).toDouble()); diff --git a/lib/pages/player/controls_overlay.dart b/lib/pages/player/controls_overlay.dart index f0d96bb..ac3aa73 100644 --- a/lib/pages/player/controls_overlay.dart +++ b/lib/pages/player/controls_overlay.dart @@ -83,7 +83,7 @@ class ControlsOverlay extends HookWidget { : SystemMouseCursors.none, onHover: gesture.onHover, child: GestureDetector( - behavior: HitTestBehavior.translucent, + behavior: HitTestBehavior.opaque, onTap: gesture.onTap, onTapDown: gesture.onTapDown, onDoubleTapDown: gesture.onDoubleTapDown, diff --git a/pubspec.lock b/pubspec.lock index 024550d..1c8a7de 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -332,12 +332,11 @@ packages: desktop_drop: dependency: "direct main" description: - path: "packages/desktop_drop" - ref: main - resolved-ref: c1b887a0c225c5f9f91917a107f52d4d4d5814ee - url: "https://github.com/nini22P/flutter-plugins" - source: git - version: "0.7.0" + name: desktop_drop + sha256: "927511f590ce01ee90d0d80f79bc71b9c919d8522d01e495e89a00c6f4a4fb5b" + url: "https://pub.dev" + source: hosted + version: "0.6.1" device_info_plus: dependency: "direct main" description: diff --git a/pubspec.yaml b/pubspec.yaml index 8afb00d..6387a71 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -41,11 +41,7 @@ dependencies: disks_desktop: ^1.0.1 android_x_storage: ^1.0.2 permission_handler: ^12.0.1 - desktop_drop: - git: - url: https://github.com/nini22P/flutter-plugins - ref: main - path: packages/desktop_drop + desktop_drop: ^0.6.1 app_links: ^6.4.1 device_info_plus: ^12.0.0 saf_util: ^0.11.0 From 18c9f0f6c35358596b0aebbff678e42a0a5a88fe Mon Sep 17 00:00:00 2001 From: 22 <60903333+nini22P@users.noreply.github.com> Date: Sun, 14 Sep 2025 18:47:44 +0800 Subject: [PATCH 15/31] disable flutter_hooks_lint --- analysis_options.yaml | 2 +- pubspec.lock | 72 ------------------------------------------- pubspec.yaml | 4 +-- 3 files changed, 3 insertions(+), 75 deletions(-) diff --git a/analysis_options.yaml b/analysis_options.yaml index 4b246ae..8d46a96 100644 --- a/analysis_options.yaml +++ b/analysis_options.yaml @@ -28,6 +28,6 @@ linter: # https://dart.dev/guides/language/analysis-options analyzer: plugins: - - custom_lint + # - custom_lint errors: invalid_annotation_target: ignore \ No newline at end of file diff --git a/pubspec.lock b/pubspec.lock index 1c8a7de..cb0d681 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -17,14 +17,6 @@ packages: url: "https://pub.dev" source: hosted version: "8.1.1" - analyzer_plugin: - dependency: transitive - description: - name: analyzer_plugin - sha256: dd574a0ab77de88b7d9c12bc4b626109a5ca9078216a79041a5c24c3a1bd103c - url: "https://pub.dev" - source: hosted - version: "0.13.7" android_x_storage: dependency: "direct main" description: @@ -177,14 +169,6 @@ packages: url: "https://pub.dev" source: hosted version: "2.0.4" - ci: - dependency: transitive - description: - name: ci - sha256: "145d095ce05cddac4d797a158bc4cf3b6016d1fe63d8c3d2fbd7212590adca13" - url: "https://pub.dev" - source: hosted - version: "0.1.0" cli_util: dependency: transitive description: @@ -273,38 +257,6 @@ packages: url: "https://pub.dev" source: hosted version: "1.0.8" - custom_lint: - dependency: "direct dev" - description: - name: custom_lint - sha256: "751ee9440920f808266c3ec2553420dea56d3c7837dd2d62af76b11be3fcece5" - url: "https://pub.dev" - source: hosted - version: "0.8.1" - custom_lint_builder: - dependency: transitive - description: - name: custom_lint_builder - sha256: "1128db6f58e71d43842f3b9be7465c83f0c47f4dd8918f878dd6ad3b72a32072" - url: "https://pub.dev" - source: hosted - version: "0.8.1" - custom_lint_core: - dependency: transitive - description: - name: custom_lint_core - sha256: "85b339346154d5646952d44d682965dfe9e12cae5febd706f0db3aa5010d6423" - url: "https://pub.dev" - source: hosted - version: "0.8.1" - custom_lint_visitor: - dependency: transitive - description: - name: custom_lint_visitor - sha256: "446d68322747ec1c36797090de776aa72228818d3d80685a91ff524d163fee6d" - url: "https://pub.dev" - source: hosted - version: "1.0.0+8.1.1" dart_pubspec_licenses: dependency: "direct dev" description: @@ -463,14 +415,6 @@ packages: url: "https://pub.dev" source: hosted version: "0.21.3+1" - flutter_hooks_lint: - dependency: "direct dev" - description: - name: flutter_hooks_lint - sha256: "8178fbc6270cc69722d84c1743517d6f3031786fa9c8daefd2ecdccd0b6e0bed" - url: "https://pub.dev" - source: hosted - version: "1.4.0" flutter_lints: dependency: "direct dev" description: @@ -646,14 +590,6 @@ packages: url: "https://pub.dev" source: hosted version: "2.1.0" - hotreloader: - dependency: transitive - description: - name: hotreloader - sha256: bc167a1163807b03bada490bfe2df25b0d744df359227880220a5cbd04e5734b - url: "https://pub.dev" - source: hosted - version: "4.3.0" html: dependency: transitive description: @@ -1143,14 +1079,6 @@ packages: url: "https://pub.dev" source: hosted version: "0.7.5" - rxdart: - dependency: transitive - description: - name: rxdart - sha256: "5c3004a4a8dbb94bd4bf5412a4def4acdaa12e12f269737a5751369e12d1a962" - url: "https://pub.dev" - source: hosted - version: "0.28.0" saf_util: dependency: "direct main" description: diff --git a/pubspec.yaml b/pubspec.yaml index 6387a71..9f87379 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -70,8 +70,8 @@ dev_dependencies: freezed: ^3.2.3 json_serializable: ^6.11.1 msix: ^3.16.12 - flutter_hooks_lint: ^1.4.0 - custom_lint: ^0.8.1 + # flutter_hooks_lint: ^1.4.0 + # custom_lint: ^0.8.1 flutter: generate: true From 849f5ee5c06d2244477016e14d4cd0d830cbbd7c Mon Sep 17 00:00:00 2001 From: 22 <60903333+nini22P@users.noreply.github.com> Date: Sun, 14 Sep 2025 20:56:51 +0800 Subject: [PATCH 16/31] feat: refactor player components to use provider --- lib/hooks/player/use_media_kit_player.dart | 4 +- lib/hooks/use_app_lifecycle.dart | 9 +- lib/hooks/use_cover.dart | 6 +- lib/hooks/use_gesture.dart | 43 +-- lib/hooks/use_keyboard.dart | 11 +- lib/models/player.dart | 4 +- lib/pages/player/audio_track_list.dart | 77 +++--- lib/pages/player/control_bar/control_bar.dart | 40 ++- .../control_bar/control_bar_slider.dart | 48 ++-- lib/pages/player/controls_overlay.dart | 22 +- lib/pages/player/player.dart | 38 ++- lib/pages/player/player_view.dart | 12 +- .../player/subtitle_and_audio_track.dart | 9 +- lib/pages/player/subtitle_list.dart | 252 +++++++----------- lib/pages/player/video_view.dart | 5 +- 15 files changed, 283 insertions(+), 297 deletions(-) diff --git a/lib/hooks/player/use_media_kit_player.dart b/lib/hooks/player/use_media_kit_player.dart index 87121b8..5e76c68 100644 --- a/lib/hooks/player/use_media_kit_player.dart +++ b/lib/hooks/player/use_media_kit_player.dart @@ -368,8 +368,8 @@ MediaKitPlayer useMediaKitPlayer(BuildContext context) { position: duration == Duration.zero ? Duration.zero : position, duration: duration, buffer: duration == Duration.zero ? Duration.zero : buffer, - width: videoParams?.w?.toDouble(), - height: videoParams?.h?.toDouble(), + width: videoParams?.w?.toDouble() ?? 0, + height: videoParams?.h?.toDouble() ?? 0, saveProgress: saveProgress, play: play, pause: pause, diff --git a/lib/hooks/use_app_lifecycle.dart b/lib/hooks/use_app_lifecycle.dart index 4337531..6d31494 100644 --- a/lib/hooks/use_app_lifecycle.dart +++ b/lib/hooks/use_app_lifecycle.dart @@ -1,10 +1,13 @@ import 'dart:ui'; import 'package:flutter_hooks/flutter_hooks.dart'; +import 'package:iris/models/player.dart'; import 'package:iris/utils/logger.dart'; +import 'package:provider/provider.dart'; + +void useAppLifecycle() { + final context = useContext(); + final saveProgress = context.read().saveProgress; -void useAppLifecycle( - Future Function() saveProgress, -) { AppLifecycleState? appLifecycleState = useAppLifecycleState(); useEffect(() { diff --git a/lib/hooks/use_cover.dart b/lib/hooks/use_cover.dart index 712ff14..6295ba7 100644 --- a/lib/hooks/use_cover.dart +++ b/lib/hooks/use_cover.dart @@ -2,14 +2,18 @@ import 'package:collection/collection.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:flutter_zustand/flutter_zustand.dart'; import 'package:iris/models/file.dart'; +import 'package:iris/models/player.dart'; import 'package:iris/models/storages/local.dart'; import 'package:iris/models/storages/storage.dart'; import 'package:iris/store/use_play_queue_store.dart'; import 'package:iris/store/use_storage_store.dart'; import 'package:iris/utils/files_filter.dart'; +import 'package:provider/provider.dart'; -FileItem? useCover(bool isPlaying) { +FileItem? useCover() { final context = useContext(); + final isPlaying = + context.select((player) => player.isPlaying); final playQueue = usePlayQueueStore().select(context, (state) => state.playQueue); diff --git a/lib/hooks/use_gesture.dart b/lib/hooks/use_gesture.dart index 3461c33..8197ae4 100644 --- a/lib/hooks/use_gesture.dart +++ b/lib/hooks/use_gesture.dart @@ -9,6 +9,7 @@ import 'package:iris/store/use_app_store.dart'; import 'package:iris/store/use_player_ui_store.dart'; import 'package:iris/utils/platform.dart'; import 'package:iris/utils/resize_window.dart'; +import 'package:provider/provider.dart'; import 'package:window_manager/window_manager.dart'; class Gesture { @@ -55,13 +56,25 @@ class Gesture { } Gesture useGesture({ - required MediaPlayer player, required void Function() showControl, required void Function() hideControl, required void Function() showProgress, }) { final context = useContext(); + final isPlaying = + context.select((player) => player.isPlaying); + final progress = + context.select( + (player) => (position: player.position, duration: player.duration), + ); + + final play = context.read().play; + final pause = context.read().pause; + final forward = context.read().forward; + final backward = context.read().backward; + final seek = context.read().seek; + final aspectRatio = usePlayerUiStore().select(context, (state) => state.aspectRatio); @@ -112,22 +125,22 @@ Gesture useGesture({ } else { showProgress(); } - await player.forward(5); + await forward(5); } else if (position < 0.25) { if (isShowControl) { showControl(); } else { showProgress(); } - player.backward(5); + backward(5); } else { - if (player.isPlaying == true) { + if (isPlaying == true) { await useAppStore().updateAutoPlay(false); - player.pause(); + pause(); showControl(); } else { await useAppStore().updateAutoPlay(true); - player.play(); + play(); } } } else { @@ -141,7 +154,7 @@ Gesture useGesture({ } void onLongPressStart(LongPressStartDetails details) { - if (isTouch.value && player.isPlaying == true) { + if (isTouch.value && isPlaying == true) { isLongPress.value = true; useAppStore().updateRate(2.0); } @@ -200,14 +213,14 @@ Gesture useGesture({ // 水平滑动 if (isHorizontalGesture.value && isSeeking) { double dx = details.delta.dx; - int seconds = (dx * 2 + player.position.inSeconds).toInt(); + int seconds = (dx * 2 + progress.position.inSeconds).toInt(); Duration position = Duration( seconds: seconds < 0 ? 0 - : seconds > player.duration.inSeconds - ? player.duration.inSeconds + : seconds > progress.duration.inSeconds + ? progress.duration.inSeconds : seconds); - player.seek(position); + seek(position); if (isShowControl) { showControl(); } else { @@ -259,7 +272,7 @@ Gesture useGesture({ isRightGesture.value = false; startPosition.value = null; if (isSeeking) { - await player.seek(player.position); + await seek(progress.position); usePlayerUiStore().updateIsSeeking(false); } } @@ -272,7 +285,7 @@ Gesture useGesture({ startPosition.value = null; if (isSeeking) { isTouch.value = false; - await player.seek(player.position); + await seek(progress.position); usePlayerUiStore().updateIsSeeking(false); } } @@ -285,10 +298,10 @@ Gesture useGesture({ } final cursor = useMemoized(() { - return player.isPlaying == false + return isPlaying == false ? SystemMouseCursors.basic : SystemMouseCursors.none; - }, [player.isPlaying]); + }, [isPlaying]); return Gesture( onTapDown: onTapDown, diff --git a/lib/hooks/use_keyboard.dart b/lib/hooks/use_keyboard.dart index eb9b0c7..bc9d1a5 100644 --- a/lib/hooks/use_keyboard.dart +++ b/lib/hooks/use_keyboard.dart @@ -16,17 +16,22 @@ import 'package:iris/utils/platform.dart'; import 'package:iris/widgets/bottom_sheets/show_open_link_bottom_sheet.dart'; import 'package:iris/widgets/dialogs/show_open_link_dialog.dart'; import 'package:iris/widgets/popup.dart'; +import 'package:provider/provider.dart'; typedef KeyboardEvent = void Function(KeyEvent event); KeyboardEvent useKeyboard({ - required MediaPlayer player, required void Function() showControl, required Future Function(Future) showControlForHover, required void Function() showProgress, }) { final context = useContext(); + final player = context.read(); + + final isPlaying = + context.select((player) => player.isPlaying); + final shuffle = useAppStore().state.shuffle; final isFullScreen = usePlayerUiStore().state.isFullScreen; final isShowControl = usePlayerUiStore().state.isShowControl; @@ -130,7 +135,7 @@ KeyboardEvent useKeyboard({ case LogicalKeyboardKey.space: case LogicalKeyboardKey.mediaPlayPause: showControl(); - if (player.isPlaying) { + if (isPlaying) { useAppStore().updateAutoPlay(false); player.pause(); } else { @@ -173,7 +178,7 @@ KeyboardEvent useKeyboard({ showControlForHover( showPopup( context: context, - child: SubtitleAndAudioTrack(player: player), + child: SubtitleAndAudioTrack(), direction: PopupDirection.right, ), ); diff --git a/lib/models/player.dart b/lib/models/player.dart index 4316193..b0f885b 100644 --- a/lib/models/player.dart +++ b/lib/models/player.dart @@ -11,8 +11,8 @@ class MediaPlayer { final Duration position; final Duration duration; final Duration buffer; - final double? width; - final double? height; + final double width; + final double height; final Future Function() saveProgress; final Future Function() play; final Future Function() pause; diff --git a/lib/pages/player/audio_track_list.dart b/lib/pages/player/audio_track_list.dart index 128c3e6..5a8e3e3 100644 --- a/lib/pages/player/audio_track_list.dart +++ b/lib/pages/player/audio_track_list.dart @@ -5,16 +5,17 @@ import 'package:iris/models/player.dart'; import 'package:iris/utils/get_localizations.dart'; import 'package:iris/utils/logger.dart'; import 'package:media_kit/media_kit.dart'; +import 'package:provider/provider.dart'; class AudioTrackList extends HookWidget { - const AudioTrackList({super.key, required this.player}); - - final MediaPlayer player; + const AudioTrackList({super.key}); @override Widget build(BuildContext context) { final t = getLocalizations(context); + final player = context.read(); + final focusNode = useFocusNode(); useEffect(() { @@ -25,47 +26,41 @@ class AudioTrackList extends HookWidget { if (player is MediaKitPlayer) { return ListView( children: [ - ...(player as MediaKitPlayer).audios.map( - (audio) => ListTile( - focusNode: (player as MediaKitPlayer).audio == audio - ? focusNode - : null, - title: Text( - audio == AudioTrack.auto() - ? t.auto - : audio == AudioTrack.no() - ? t.off - : audio.title ?? audio.language ?? audio.id, - style: (player as MediaKitPlayer).audio == audio - ? TextStyle( - fontWeight: FontWeight.bold, - color: Theme.of(context).colorScheme.primary, - ) - : TextStyle( - color: - Theme.of(context).colorScheme.onSurfaceVariant, - ), - ), - tileColor: (player as MediaKitPlayer).audio == audio - ? Theme.of(context).hoverColor - : null, - onTap: () { - logger( - 'Set audio track: ${audio.title ?? audio.language ?? audio.id}'); - (player as MediaKitPlayer).player.setAudioTrack(audio); - Navigator.of(context).pop(); - }, - ), + ...player.audios.map( + (audio) => ListTile( + focusNode: player.audio == audio ? focusNode : null, + title: Text( + audio == AudioTrack.auto() + ? t.auto + : audio == AudioTrack.no() + ? t.off + : audio.title ?? audio.language ?? audio.id, + style: player.audio == audio + ? TextStyle( + fontWeight: FontWeight.bold, + color: Theme.of(context).colorScheme.primary, + ) + : TextStyle( + color: Theme.of(context).colorScheme.onSurfaceVariant, + ), ), + tileColor: + player.audio == audio ? Theme.of(context).hoverColor : null, + onTap: () { + logger( + 'Set audio track: ${audio.title ?? audio.language ?? audio.id}'); + player.player.setAudioTrack(audio); + Navigator.of(context).pop(); + }, + ), + ), ], ); } if (player is FvpPlayer) { - final audios = - (player as FvpPlayer).controller.getMediaInfo()?.audio ?? []; - final activeAudioTracks = - (player as FvpPlayer).controller.getActiveAudioTracks() ?? []; + final audios = player.controller.getMediaInfo()?.audio ?? []; + final activeAudioTracks = player.controller.getActiveAudioTracks() ?? []; return ListView( children: [ ListTile( @@ -85,7 +80,7 @@ class AudioTrackList extends HookWidget { activeAudioTracks.isEmpty ? Theme.of(context).hoverColor : null, onTap: () { logger('Set audio track: ${t.off}'); - (player as FvpPlayer).controller.setAudioTracks([]); + player.controller.setAudioTracks([]); Navigator.of(context).pop(); }, ), @@ -113,9 +108,7 @@ class AudioTrackList extends HookWidget { onTap: () { logger( 'Set audio track: ${audio.metadata['title'] ?? audio.metadata['language'] ?? audios.indexOf(audio).toString()}'); - (player as FvpPlayer) - .controller - .setAudioTracks([audios.indexOf(audio)]); + player.controller.setAudioTracks([audios.indexOf(audio)]); Navigator.of(context).pop(); }, ), diff --git a/lib/pages/player/control_bar/control_bar.dart b/lib/pages/player/control_bar/control_bar.dart index 604367e..4a93e7c 100644 --- a/lib/pages/player/control_bar/control_bar.dart +++ b/lib/pages/player/control_bar/control_bar.dart @@ -23,18 +23,17 @@ import 'package:iris/utils/platform.dart'; import 'package:iris/utils/resize_window.dart'; import 'package:iris/widgets/popup.dart'; import 'package:iris/pages/storages/storages.dart'; +import 'package:provider/provider.dart'; class ControlBar extends HookWidget { const ControlBar({ super.key, - required this.player, required this.showControl, required this.showControlForHover, this.color, this.overlayColor, }); - final MediaPlayer player; final void Function() showControl; final Future Function(Future callback) showControlForHover; final Color? color; @@ -44,6 +43,15 @@ class ControlBar extends HookWidget { Widget build(BuildContext context) { final t = getLocalizations(context); + final isPlaying = + context.select((player) => player.isPlaying); + + final isInitializing = + context.select((player) => player.isInitializing); + + final play = context.read().play; + final pause = context.read().pause; + final rate = useAppStore().select(context, (state) => state.rate); final volume = useAppStore().select(context, (state) => state.volume); final isMuted = useAppStore().select(context, (state) => state.isMuted); @@ -64,14 +72,14 @@ class ControlBar extends HookWidget { final isSeeking = usePlayerUiStore().select(context, (state) => state.isSeeking); - final displayIsPlaying = useState(player.isPlaying); + final displayIsPlaying = useState(isPlaying); useEffect(() { if (!isSeeking) { - displayIsPlaying.value = player.isPlaying; + displayIsPlaying.value = isPlaying; } return null; - }, [player.isPlaying]); + }, [isPlaying]); return Container( padding: const EdgeInsets.all(8), @@ -92,7 +100,6 @@ class ControlBar extends HookWidget { Visibility( visible: MediaQuery.of(context).size.width < 1024 || !isDesktop, child: ControlBarSlider( - player: player, showControl: showControl, color: color, ), @@ -117,17 +124,17 @@ class ControlBar extends HookWidget { ), onPressed: () { showControl(); - if (player.isPlaying == true) { + if (isPlaying == true) { useAppStore().updateAutoPlay(false); - player.pause(); + pause(); } else { useAppStore().updateAutoPlay(true); - player.play(); + play(); } }, style: ButtonStyle(overlayColor: overlayColor), ), - if (player.isInitializing) + if (isInitializing) SizedBox( width: 36, height: 36, @@ -148,7 +155,7 @@ class ControlBar extends HookWidget { onPressed: () { showControl(); useAppStore().updateAutoPlay(false); - player.pause(); + pause(); usePlayQueueStore().updateCurrentIndex(-1); }, style: ButtonStyle(overlayColor: overlayColor), @@ -331,7 +338,6 @@ class ControlBar extends HookWidget { visible: MediaQuery.of(context).size.width >= 1024 && isDesktop, child: ControlBarSlider( - player: player, showControl: showControl, color: color, ), @@ -349,7 +355,10 @@ class ControlBar extends HookWidget { showControlForHover( showPopup( context: context, - child: SubtitleAndAudioTrack(player: player), + child: Provider.value( + value: context.read(), + child: const SubtitleAndAudioTrack(), + ), direction: PopupDirection.right, ), ); @@ -602,7 +611,10 @@ class ControlBar extends HookWidget { onTap: () => showControlForHover( showPopup( context: context, - child: SubtitleAndAudioTrack(player: player), + child: Provider.value( + value: context.read(), + child: const SubtitleAndAudioTrack(), + ), direction: PopupDirection.right, ), ), diff --git a/lib/pages/player/control_bar/control_bar_slider.dart b/lib/pages/player/control_bar/control_bar_slider.dart index 84f7f0a..e29e0fe 100644 --- a/lib/pages/player/control_bar/control_bar_slider.dart +++ b/lib/pages/player/control_bar/control_bar_slider.dart @@ -5,18 +5,16 @@ import 'package:iris/models/player.dart'; import 'package:iris/store/use_app_store.dart'; import 'package:iris/store/use_player_ui_store.dart'; import 'package:iris/utils/format_duration_to_minutes.dart'; +import 'package:provider/provider.dart'; class ControlBarSlider extends HookWidget { const ControlBarSlider({ super.key, - required this.player, required this.showControl, this.disabled = false, this.color, }); - final MediaPlayer player; - final void Function() showControl; final bool disabled; final Color? color; @@ -25,6 +23,24 @@ class ControlBarSlider extends HookWidget { Widget build(BuildContext context) { final autoPlay = useAppStore().select(context, (state) => state.autoPlay); + final progress = context.select< + MediaPlayer, + ({ + Duration position, + Duration duration, + Duration buffer, + })>( + (player) => ( + position: player.position, + duration: player.duration, + buffer: player.buffer + ), + ); + + final play = context.read().play; + final pause = context.read().pause; + final seek = context.read().seek; + return ExcludeFocus( child: Container( padding: const EdgeInsets.fromLTRB(12, 0, 12, 0), @@ -33,7 +49,7 @@ class ControlBarSlider extends HookWidget { Visibility( visible: !disabled, child: Text( - formatDurationToMinutes(player.position), + formatDurationToMinutes(progress.position), style: TextStyle( color: color, height: 2, @@ -58,12 +74,12 @@ class ControlBarSlider extends HookWidget { trackHeight: 3, ), child: Slider( - value: player.buffer.inMilliseconds.toDouble() > - player.duration.inMilliseconds.toDouble() + value: progress.buffer.inMilliseconds.toDouble() > + progress.duration.inMilliseconds.toDouble() ? 0 - : player.buffer.inMilliseconds.toDouble(), + : progress.buffer.inMilliseconds.toDouble(), min: 0, - max: player.duration.inMilliseconds.toDouble(), + max: progress.duration.inMilliseconds.toDouble(), onChanged: null, ), ), @@ -83,23 +99,23 @@ class ControlBarSlider extends HookWidget { trackHeight: 4, ), child: Slider( - value: player.position.inMilliseconds.toDouble() > - player.duration.inMilliseconds.toDouble() + value: progress.position.inMilliseconds.toDouble() > + progress.duration.inMilliseconds.toDouble() ? 0 - : player.position.inMilliseconds.toDouble(), + : progress.position.inMilliseconds.toDouble(), min: 0, - max: player.duration.inMilliseconds.toDouble(), + max: progress.duration.inMilliseconds.toDouble(), onChangeStart: (value) { usePlayerUiStore().updateIsSeeking(true); - player.pause(); + pause(); }, onChanged: (value) { showControl(); - player.seek(Duration(milliseconds: value.toInt())); + seek(Duration(milliseconds: value.toInt())); }, onChangeEnd: (value) async { if (autoPlay) { - player.play(); + play(); } usePlayerUiStore().updateIsSeeking(false); }, @@ -111,7 +127,7 @@ class ControlBarSlider extends HookWidget { Visibility( visible: !disabled, child: Text( - formatDurationToMinutes(player.duration), + formatDurationToMinutes(progress.duration), style: TextStyle( color: color, height: 2, diff --git a/lib/pages/player/controls_overlay.dart b/lib/pages/player/controls_overlay.dart index ac3aa73..184d2dc 100644 --- a/lib/pages/player/controls_overlay.dart +++ b/lib/pages/player/controls_overlay.dart @@ -13,11 +13,11 @@ import 'package:iris/utils/format_duration_to_minutes.dart'; import 'package:iris/utils/resize_window.dart'; import 'package:iris/widgets/drag_aria.dart'; import 'package:iris/widgets/title_bar.dart'; +import 'package:provider/provider.dart'; class ControlsOverlay extends HookWidget { const ControlsOverlay({ super.key, - required this.player, required this.currentPlay, required this.title, required this.showControl, @@ -26,7 +26,6 @@ class ControlsOverlay extends HookWidget { required this.showProgress, }); - final MediaPlayer player; final PlayQueueItem? currentPlay; final String title; final Function() showControl; @@ -36,6 +35,16 @@ class ControlsOverlay extends HookWidget { @override Widget build(BuildContext context) { + final isPlaying = + context.select((player) => player.isPlaying); + + final progress = + context.select( + (player) => (position: player.position, duration: player.duration), + ); + + final saveProgress = context.read().saveProgress; + final rate = useAppStore().select(context, (state) => state.rate); final aspectRatio = @@ -46,7 +55,6 @@ class ControlsOverlay extends HookWidget { usePlayerUiStore().select(context, (state) => state.isShowProgress); final gesture = useGesture( - player: player, showControl: showControl, hideControl: hideControl, showProgress: showProgress, @@ -78,7 +86,7 @@ class ControlsOverlay extends HookWidget { right: 0, bottom: 0, child: MouseRegion( - cursor: isShowControl || player.isPlaying == false + cursor: isShowControl || isPlaying == false ? SystemMouseCursors.basic : SystemMouseCursors.none, onHover: gesture.onHover, @@ -231,7 +239,6 @@ class ControlsOverlay extends HookWidget { bottom: -16, height: 32, child: ControlBarSlider( - player: player, showControl: showControl, disabled: true, ), @@ -274,7 +281,7 @@ class ControlsOverlay extends HookWidget { crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( - '${formatDurationToMinutes(player.position)} / ${formatDurationToMinutes(player.duration)}', + '${formatDurationToMinutes(progress.position)} / ${formatDurationToMinutes(progress.duration)}', style: TextStyle( color: Colors.white, fontSize: 16, @@ -317,7 +324,7 @@ class ControlsOverlay extends HookWidget { actions: [const SizedBox(width: 8)], color: contentColor, overlayColor: overlayColor, - saveProgress: () => player.saveProgress(), + saveProgress: () => saveProgress(), resizeWindow: () => resizeWindow(aspectRatio), ), ), @@ -343,7 +350,6 @@ class ControlsOverlay extends HookWidget { child: GestureDetector( onTap: () => showControl(), child: ControlBar( - player: player, showControl: showControl, showControlForHover: showControlForHover, color: contentColor, diff --git a/lib/pages/player/player.dart b/lib/pages/player/player.dart index 140932e..5c80ce1 100644 --- a/lib/pages/player/player.dart +++ b/lib/pages/player/player.dart @@ -22,20 +22,27 @@ import 'package:iris/utils/platform.dart'; import 'package:iris/store/use_app_store.dart'; import 'package:iris/store/use_play_queue_store.dart'; import 'package:iris/utils/get_localizations.dart'; +import 'package:provider/provider.dart'; import 'package:window_manager/window_manager.dart'; class Player extends HookWidget { - const Player({super.key, required this.player}); - - final MediaPlayer player; + const Player({super.key}); @override Widget build(BuildContext context) { final t = getLocalizations(context); - useAppLifecycle(player.saveProgress); + final isPlaying = + context.select((player) => player.isPlaying); + final width = context.select((player) => player.width); + final height = + context.select((player) => player.height); + + final saveProgress = context.read().saveProgress; + + useAppLifecycle(); - final cover = useCover(player.isPlaying); + final cover = useCover(); final playerUiStore = usePlayerUiStore(); @@ -167,7 +174,7 @@ class Player extends HookWidget { final showControlForHover = useCallback((Future callback) async { try { - player.saveProgress(); + saveProgress(); showControl(); updateIsHovering(true); await callback; @@ -183,7 +190,6 @@ class Player extends HookWidget { }, [updateIsShowProgress, resetBottomProgressTimer]); final onKeyEvent = useKeyboard( - player: player, showControl: showControl, showControlForHover: showControlForHover, showProgress: showProgress, @@ -203,7 +209,7 @@ class Player extends HookWidget { windowManager.setTitle(title); } return; - }, [title, player.isPlaying]); + }, [title, isPlaying]); useEffect(() { if (isShowControl || currentPlay?.file.type == ContentType.video) { @@ -232,18 +238,12 @@ class Player extends HookWidget { ); final videoViewSize = useMemoized(() { - if (fit != BoxFit.none || player.width == 0 || player.height == 0) { + if (fit != BoxFit.none || width == 0 || height == 0) { return MediaQuery.of(context).size; } else { - return Size(player.width! / scaleFactor, player.height! / scaleFactor); + return Size(width / scaleFactor, height / scaleFactor); } - }, [ - fit, - MediaQuery.of(context).size, - player.width, - player.height, - scaleFactor - ]); + }, [fit, MediaQuery.of(context).size, width, height, scaleFactor]); final videoViewOffset = useMemoized( () => fit == BoxFit.none @@ -285,7 +285,7 @@ class Player extends HookWidget { canPop: false, onPopInvokedWithResult: (bool didPop, Object? result) async { if (!didPop) { - await player.saveProgress(); + await saveProgress(); if (!canPop.value) { canPop.value = true; if (context.mounted) { @@ -311,7 +311,6 @@ class Player extends HookWidget { height: videoViewSize.height, child: VideoView( key: ValueKey(currentPlay?.file.uri), - player: player, fit: fit, ), ), @@ -330,7 +329,6 @@ class Player extends HookWidget { right: 0, bottom: 0, child: ControlsOverlay( - player: player, currentPlay: currentPlay, title: title, showControl: showControl, diff --git a/lib/pages/player/player_view.dart b/lib/pages/player/player_view.dart index 3193c65..784e6b9 100644 --- a/lib/pages/player/player_view.dart +++ b/lib/pages/player/player_view.dart @@ -2,8 +2,10 @@ import 'package:flutter/material.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:iris/hooks/player/use_fvp_player.dart'; import 'package:iris/hooks/player/use_media_kit_player.dart'; +import 'package:iris/models/player.dart'; import 'package:iris/models/store/app_state.dart'; import 'package:iris/pages/player/player.dart'; +import 'package:provider/provider.dart'; class PlayerView extends HookWidget { const PlayerView({super.key, required this.playerBackend}); @@ -27,7 +29,10 @@ class _MediaKitPlayerHost extends HookWidget { @override Widget build(BuildContext context) { final player = useMediaKitPlayer(context); - return Player(player: player); + return Provider.value( + value: player, + child: const Player(key: ValueKey('media_kit_player')), + ); } } @@ -37,6 +42,9 @@ class _FvpPlayerHost extends HookWidget { @override Widget build(BuildContext context) { final player = useFvpPlayer(context); - return Player(player: player); + return Provider.value( + value: player, + child: const Player(key: ValueKey('fvp_player')), + ); } } diff --git a/lib/pages/player/subtitle_and_audio_track.dart b/lib/pages/player/subtitle_and_audio_track.dart index 21d5605..a988bb3 100644 --- a/lib/pages/player/subtitle_and_audio_track.dart +++ b/lib/pages/player/subtitle_and_audio_track.dart @@ -1,6 +1,5 @@ import 'package:flutter/material.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; -import 'package:iris/models/player.dart'; import 'package:iris/pages/player/audio_track_list.dart'; import 'package:iris/pages/player/subtitle_list.dart'; import 'package:iris/utils/get_localizations.dart'; @@ -16,17 +15,15 @@ class ITab { } class SubtitleAndAudioTrack extends HookWidget { - const SubtitleAndAudioTrack({super.key, required this.player}); - - final MediaPlayer player; + const SubtitleAndAudioTrack({super.key}); @override Widget build(BuildContext context) { final t = getLocalizations(context); List tabs = [ - ITab(title: t.subtitle, child: SubtitleList(player: player)), - ITab(title: t.audio_track, child: AudioTrackList(player: player)), + ITab(title: t.subtitle, child: SubtitleList()), + ITab(title: t.audio_track, child: AudioTrackList()), ]; final tabController = useTabController(initialLength: tabs.length); diff --git a/lib/pages/player/subtitle_list.dart b/lib/pages/player/subtitle_list.dart index 36b2bbb..ee93c93 100644 --- a/lib/pages/player/subtitle_list.dart +++ b/lib/pages/player/subtitle_list.dart @@ -10,20 +10,17 @@ import 'package:iris/utils/get_localizations.dart'; import 'package:iris/utils/logger.dart'; import 'package:media_kit/media_kit.dart'; import 'package:media_stream/media_stream.dart'; +import 'package:provider/provider.dart'; class SubtitleList extends HookWidget { - const SubtitleList({super.key, required this.player}); - - final MediaPlayer player; + const SubtitleList({super.key}); @override Widget build(BuildContext context) { final t = getLocalizations(context); - final focusNode = useFocusNode(); - MediaStream mediaStream = MediaStream(); - final streamUrl = mediaStream.url; + final player = context.watch(); final playQueue = usePlayQueueStore().select(context, (state) => state.playQueue); @@ -34,191 +31,124 @@ class SubtitleList extends HookWidget { () => playQueue.indexWhere((element) => element.index == currentIndex), [playQueue, currentIndex]); - final PlayQueueItem? currentPlay = useMemoized( + final file = useMemoized( () => playQueue.isEmpty || currentPlayIndex < 0 ? null - : playQueue[currentPlayIndex], + : playQueue[currentPlayIndex].file, [playQueue, currentPlayIndex]); useEffect(() { focusNode.requestFocus(); - return () => focusNode.unfocus(); + return focusNode.unfocus; }, []); if (player is MediaKitPlayer) { + final activeSubtitle = context.select( + (p) => p is MediaKitPlayer ? p.subtitle : SubtitleTrack.no()); + final subtitles = context.select>( + (p) => p is MediaKitPlayer ? p.subtitles : []); + final externalSubtitles = context.select>( + (p) => p is MediaKitPlayer ? p.externalSubtitles : []); + return ListView( children: [ - ...(player as MediaKitPlayer).subtitles.map( - (subtitle) => ListTile( - focusNode: (player as MediaKitPlayer).subtitle == subtitle - ? focusNode - : null, - title: Text( - subtitle == SubtitleTrack.no() - ? t.off - : subtitle.title ?? subtitle.language ?? subtitle.id, - style: (player as MediaKitPlayer).subtitle == subtitle - ? TextStyle( - fontWeight: FontWeight.bold, - color: Theme.of(context).colorScheme.primary, - ) - : TextStyle( - color: - Theme.of(context).colorScheme.onSurfaceVariant, - ), - ), - tileColor: (player as MediaKitPlayer).subtitle == subtitle - ? Theme.of(context).hoverColor - : null, - onTap: () { - logger( - 'Set subtitle: ${subtitle.title ?? subtitle.language ?? subtitle.id}'); - (player as MediaKitPlayer) - .player - .setSubtitleTrack(subtitle); - Navigator.of(context).pop(); - }, - ), - ), - ...(player as MediaKitPlayer).externalSubtitles.map( - (subtitle) => ListTile( - title: Text( - subtitle.name, - style: TextStyle( - color: Theme.of(context).colorScheme.onSurfaceVariant, - ), - ), - onTap: () { - logger('Set external subtitle: $subtitle'); - final uri = currentPlay?.file.storageType == StorageType.ftp - ? '$streamUrl/${subtitle.uri}' - : subtitle.uri; - logger('External subtitle uri: $uri'); - (player as MediaKitPlayer).player.setSubtitleTrack( - SubtitleTrack.uri( - uri, - title: subtitle.name, - ), - ); - Navigator.of(context).pop(); - }, - ), + ...subtitles.map((subtitle) { + final bool isActive = activeSubtitle == subtitle; + return ListTile( + focusNode: isActive ? focusNode : null, + title: Text( + subtitle == SubtitleTrack.no() + ? t.off + : subtitle.title ?? subtitle.language ?? subtitle.id, + style: isActive + ? TextStyle( + fontWeight: FontWeight.bold, + color: Theme.of(context).colorScheme.primary) + : TextStyle( + color: Theme.of(context).colorScheme.onSurfaceVariant), ), + tileColor: isActive ? Theme.of(context).hoverColor : null, + onTap: () { + logger('Set subtitle: ${subtitle.id}'); + player.player.setSubtitleTrack(subtitle); + Navigator.of(context).pop(); + }, + ); + }), + ...externalSubtitles.map((subtitle) { + return ListTile( + title: Text(subtitle.name, + style: TextStyle( + color: Theme.of(context).colorScheme.onSurfaceVariant)), + onTap: () { + logger('Set external subtitle: $subtitle'); + final mediaStream = MediaStream(); + final uri = file?.storageType == StorageType.ftp + ? '${mediaStream.url}/${subtitle.uri}' + : subtitle.uri; + player.player.setSubtitleTrack( + SubtitleTrack.uri(uri, title: subtitle.name)); + Navigator.of(context).pop(); + }, + ); + }), ], ); } if (player is FvpPlayer) { - final subtitles = - (player as FvpPlayer).controller.getMediaInfo()?.subtitle ?? []; - final activeSubtitles = - (player as FvpPlayer).controller.getActiveSubtitleTracks() ?? []; + final activeSubtitles = useListenableSelector(player.controller, + () => player.controller.getActiveSubtitleTracks() ?? []); + final externalSubtitleIndex = useValueListenable(player.externalSubtitle); + + final subtitles = player.controller.getMediaInfo()?.subtitle ?? []; + final externalSubtitles = player.externalSubtitles; + return ListView( children: [ ListTile( - focusNode: (player as FvpPlayer).externalSubtitle.value == null && - activeSubtitles.isEmpty + selected: externalSubtitleIndex == null && activeSubtitles.isEmpty, + focusNode: externalSubtitleIndex == null && activeSubtitles.isEmpty ? focusNode : null, - title: Text( - t.off, - style: (player as FvpPlayer).externalSubtitle.value == null && - activeSubtitles.isEmpty - ? TextStyle( - fontWeight: FontWeight.bold, - color: Theme.of(context).colorScheme.primary, - ) - : TextStyle( - color: Theme.of(context).colorScheme.onSurfaceVariant, - ), - ), - tileColor: (player as FvpPlayer).externalSubtitle.value == null && - activeSubtitles.isEmpty - ? Theme.of(context).hoverColor - : null, + title: Text(t.off), onTap: () { logger('Set subtitle: ${t.off}'); - (player as FvpPlayer).externalSubtitle.value = null; - (player as FvpPlayer).controller.setSubtitleTracks([]); + player.externalSubtitle.value = null; + player.controller.setSubtitleTracks([]); Navigator.of(context).pop(); }, ), - ...subtitles.map( - (subtitle) => ListTile( - focusNode: (player as FvpPlayer).externalSubtitle.value == null && - activeSubtitles.contains(subtitles.indexOf(subtitle)) - ? focusNode - : null, - title: Text( - subtitle.metadata['title'] ?? - subtitle.metadata['language'] ?? - subtitle.index.toString(), - style: (player as FvpPlayer).externalSubtitle.value == null && - activeSubtitles.contains(subtitles.indexOf(subtitle)) - ? TextStyle( - fontWeight: FontWeight.bold, - color: Theme.of(context).colorScheme.primary, - ) - : TextStyle( - color: Theme.of(context).colorScheme.onSurfaceVariant, - ), - ), - tileColor: (player as FvpPlayer).externalSubtitle.value == null && - activeSubtitles.contains(subtitles.indexOf(subtitle)) - ? Theme.of(context).hoverColor - : null, + ...subtitles.map((subtitle) { + final int index = subtitles.indexOf(subtitle); + final bool isActive = externalSubtitleIndex == null && + activeSubtitles.contains(index); + return ListTile( + selected: isActive, + focusNode: isActive ? focusNode : null, + title: Text(subtitle.metadata['title'] ?? + subtitle.metadata['language'] ?? + subtitle.index.toString()), onTap: () { - logger( - 'Set subtitle: ${subtitle.metadata['title'] ?? subtitle.metadata['language'] ?? subtitle.index.toString()}'); - (player as FvpPlayer).externalSubtitle.value = null; - (player as FvpPlayer) - .controller - .setSubtitleTracks([subtitles.indexOf(subtitle)]); + player.externalSubtitle.value = null; + player.controller.setSubtitleTracks([index]); Navigator.of(context).pop(); }, - ), - ), - ...(player as FvpPlayer).externalSubtitles.map( - (subtitle) => ListTile( - focusNode: (player as FvpPlayer).externalSubtitle.value == - (player as FvpPlayer) - .externalSubtitles - .indexOf(subtitle) - ? focusNode - : null, - title: (player as FvpPlayer).externalSubtitle.value == - (player as FvpPlayer) - .externalSubtitles - .indexOf(subtitle) - ? Text( - subtitle.name, - style: TextStyle( - fontWeight: FontWeight.bold, - color: Theme.of(context).colorScheme.primary), - ) - : Text( - subtitle.name, - style: TextStyle( - color: - Theme.of(context).colorScheme.onSurfaceVariant, - ), - ), - tileColor: (player as FvpPlayer).externalSubtitle.value == - (player as FvpPlayer) - .externalSubtitles - .indexOf(subtitle) - ? Theme.of(context).hoverColor - : null, - onTap: () { - logger('Set external subtitle: $subtitle'); - (player as FvpPlayer).externalSubtitle.value = - (player as FvpPlayer) - .externalSubtitles - .indexOf(subtitle); - Navigator.of(context).pop(); - }, - ), - ), + ); + }), + ...externalSubtitles.map((subtitle) { + final int index = externalSubtitles.indexOf(subtitle); + final bool isActive = externalSubtitleIndex == index; + return ListTile( + selected: isActive, + focusNode: isActive ? focusNode : null, + title: Text(subtitle.name), + onTap: () { + player.externalSubtitle.value = index; + Navigator.of(context).pop(); + }, + ); + }), ], ); } diff --git a/lib/pages/player/video_view.dart b/lib/pages/player/video_view.dart index ca20f9a..d85b474 100644 --- a/lib/pages/player/video_view.dart +++ b/lib/pages/player/video_view.dart @@ -2,20 +2,21 @@ import 'package:flutter/material.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:iris/models/player.dart'; import 'package:media_kit_video/media_kit_video.dart'; +import 'package:provider/provider.dart'; import 'package:video_player/video_player.dart'; class VideoView extends HookWidget { const VideoView({ super.key, - required this.player, required this.fit, }); - final MediaPlayer player; final BoxFit fit; @override Widget build(BuildContext context) { + final player = context.read(); + return switch (player) { MediaKitPlayer player => Video( controller: player.controller, From 1f26c5d2eec1ae125ebccdadea9eb9d295c4e983 Mon Sep 17 00:00:00 2001 From: 22 <60903333+nini22P@users.noreply.github.com> Date: Mon, 15 Sep 2025 09:56:32 +0800 Subject: [PATCH 17/31] feat: improve window resize --- lib/hooks/ui/use_resize_window.dart | 128 +++++++++++++++++- lib/hooks/use_gesture.dart | 7 - lib/pages/player/control_bar/control_bar.dart | 6 - lib/pages/player/controls_overlay.dart | 4 - lib/utils/resize_window.dart | 110 --------------- lib/widgets/drag_aria.dart | 4 - lib/widgets/title_bar.dart | 6 - 7 files changed, 123 insertions(+), 142 deletions(-) delete mode 100644 lib/utils/resize_window.dart diff --git a/lib/hooks/ui/use_resize_window.dart b/lib/hooks/ui/use_resize_window.dart index 7daa871..50f042b 100644 --- a/lib/hooks/ui/use_resize_window.dart +++ b/lib/hooks/ui/use_resize_window.dart @@ -1,21 +1,139 @@ +import 'dart:math' as math; +import 'package:flutter/material.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:flutter_zustand/flutter_zustand.dart'; +import 'package:iris/models/file.dart'; import 'package:iris/store/use_app_store.dart'; +import 'package:iris/store/use_play_queue_store.dart'; import 'package:iris/store/use_player_ui_store.dart'; +import 'package:iris/utils/logger.dart'; import 'package:iris/utils/platform.dart'; -import 'package:iris/utils/resize_window.dart'; +import 'package:window_manager/window_manager.dart'; +import 'package:window_size/window_size.dart'; + +Future _applyResize(Rect newBounds) async { + if (await windowManager.isFullScreen() || await windowManager.isMaximized()) { + return; + } + await windowManager.setBounds(newBounds, animate: true); +} void useResizeWindow() { final context = useContext(); final autoResize = useAppStore().select(context, (state) => state.autoResize); + final isFullScreen = + usePlayerUiStore().select(context, (state) => state.isFullScreen); final aspectRatio = usePlayerUiStore().select(context, (state) => state.aspectRatio); + final currentPlay = usePlayQueueStore().select(context, (state) { + final index = + state.playQueue.indexWhere((e) => e.index == state.currentIndex); + return index != -1 ? state.playQueue[index] : null; + }); + final contentType = currentPlay?.file.type ?? ContentType.other; + + final prevIsFullScreen = usePrevious(isFullScreen); + final prevAspectRatio = usePrevious(aspectRatio); + useEffect(() { - if (isDesktop) { - resizeWindow(!autoResize ? 0 : aspectRatio); + if (!isDesktop) return; + + Future performResize() async { + if (isFullScreen) return; + + if (!autoResize) { + await windowManager.setAspectRatio(0); + return; + } + + if (contentType == ContentType.audio) { + await windowManager.setAspectRatio(0); + const Size defaultAudioSize = Size(450, 600); + if ((await windowManager.getSize()) != defaultAudioSize) { + final screen = await getCurrentScreen(); + if (screen == null) return; + final screenRect = screen.visibleFrame; + final position = Offset( + (screenRect.width - defaultAudioSize.width) / 2, + (screenRect.height - defaultAudioSize.height) / 2, + ); + await _applyResize(Rect.fromLTWH(position.dx, position.dy, + defaultAudioSize.width, defaultAudioSize.height)); + } + return; + } + + if (contentType == ContentType.video) { + if (aspectRatio <= 0) { + await windowManager.setAspectRatio(0); + return; + } + + await windowManager.setAspectRatio(aspectRatio); + final oldBounds = await windowManager.getBounds(); + final screen = await getCurrentScreen(); + if (screen == null) return; + + if (oldBounds.size.aspectRatio.toStringAsFixed(2) == + aspectRatio.toStringAsFixed(2)) { + return; + } + + Size newSize; + final bool isPreviousPortrait = (prevAspectRatio ?? 1.0) < 1.0; + final bool isCurrentLandscape = aspectRatio >= 1.0; + + if (isPreviousPortrait && isCurrentLandscape) { + logger('Resize rule: Portrait to Landscape (Height-based)'); + double newHeight = oldBounds.height; + double newWidth = newHeight * aspectRatio; + newSize = Size(newWidth, newHeight); + } else { + logger('Resize rule: Standard (Normalized Area-based)'); + double currentArea = oldBounds.width * oldBounds.height; + const double standardAspectRatio = 16.0 / 9.0; + double normalizedHeight = + math.sqrt(currentArea / standardAspectRatio); + + double newHeight = normalizedHeight; + double newWidth = newHeight * aspectRatio; + newSize = Size(newWidth, newHeight); + } + + double maxWidth = screen.frame.width / screen.scaleFactor * 0.95; + double maxHeight = screen.frame.height / screen.scaleFactor * 0.95; + + if (newSize.width > maxWidth) { + newSize = Size(maxWidth, maxWidth / aspectRatio); + } + if (newSize.height > maxHeight) { + newSize = Size(maxHeight * aspectRatio, maxHeight); + } + + final newPosition = Offset( + oldBounds.left + (oldBounds.width - newSize.width) / 2, + oldBounds.top + (oldBounds.height - newSize.height) / 2, + ); + + await _applyResize(Rect.fromLTWH( + newPosition.dx, newPosition.dy, newSize.width, newSize.height)); + } } - return; - }, [aspectRatio, autoResize]); + + final wasFullScreen = prevIsFullScreen == true; + if (wasFullScreen && !isFullScreen) { + Future.delayed(const Duration(milliseconds: 50), performResize); + } else { + performResize(); + } + + return null; + }, [ + autoResize, + isFullScreen, + aspectRatio, + contentType, + ]); } diff --git a/lib/hooks/use_gesture.dart b/lib/hooks/use_gesture.dart index 8197ae4..bc36da9 100644 --- a/lib/hooks/use_gesture.dart +++ b/lib/hooks/use_gesture.dart @@ -8,7 +8,6 @@ import 'package:iris/models/player.dart'; import 'package:iris/store/use_app_store.dart'; import 'package:iris/store/use_player_ui_store.dart'; import 'package:iris/utils/platform.dart'; -import 'package:iris/utils/resize_window.dart'; import 'package:provider/provider.dart'; import 'package:window_manager/window_manager.dart'; @@ -75,9 +74,6 @@ Gesture useGesture({ final backward = context.read().backward; final seek = context.read().seek; - final aspectRatio = - usePlayerUiStore().select(context, (state) => state.aspectRatio); - final isFullScreen = usePlayerUiStore().select(context, (state) => state.isFullScreen); @@ -145,9 +141,6 @@ Gesture useGesture({ } } else { if (isDesktop) { - if (isFullScreen) { - await resizeWindow(aspectRatio); - } usePlayerUiStore().updateFullScreen(!isFullScreen); } } diff --git a/lib/pages/player/control_bar/control_bar.dart b/lib/pages/player/control_bar/control_bar.dart index 4a93e7c..6ddbc04 100644 --- a/lib/pages/player/control_bar/control_bar.dart +++ b/lib/pages/player/control_bar/control_bar.dart @@ -20,7 +20,6 @@ import 'package:iris/store/use_play_queue_store.dart'; import 'package:iris/utils/get_localizations.dart'; import 'package:iris/pages/player/play_queue.dart'; import 'package:iris/utils/platform.dart'; -import 'package:iris/utils/resize_window.dart'; import 'package:iris/widgets/popup.dart'; import 'package:iris/pages/storages/storages.dart'; import 'package:provider/provider.dart'; @@ -56,8 +55,6 @@ class ControlBar extends HookWidget { final volume = useAppStore().select(context, (state) => state.volume); final isMuted = useAppStore().select(context, (state) => state.isMuted); - final aspectRatio = - usePlayerUiStore().select(context, (state) => state.aspectRatio); final isFullScreen = usePlayerUiStore().select(context, (state) => state.isFullScreen); final int playQueueLength = @@ -417,9 +414,6 @@ class ControlBar extends HookWidget { ), onPressed: () async { showControl(); - if (isFullScreen) { - await resizeWindow(aspectRatio); - } usePlayerUiStore().updateFullScreen(!isFullScreen); }, style: ButtonStyle(overlayColor: overlayColor), diff --git a/lib/pages/player/controls_overlay.dart b/lib/pages/player/controls_overlay.dart index 184d2dc..b99a3f4 100644 --- a/lib/pages/player/controls_overlay.dart +++ b/lib/pages/player/controls_overlay.dart @@ -10,7 +10,6 @@ import 'package:iris/pages/player/control_bar/control_bar_slider.dart'; import 'package:iris/store/use_app_store.dart'; import 'package:iris/store/use_player_ui_store.dart'; import 'package:iris/utils/format_duration_to_minutes.dart'; -import 'package:iris/utils/resize_window.dart'; import 'package:iris/widgets/drag_aria.dart'; import 'package:iris/widgets/title_bar.dart'; import 'package:provider/provider.dart'; @@ -47,8 +46,6 @@ class ControlsOverlay extends HookWidget { final rate = useAppStore().select(context, (state) => state.rate); - final aspectRatio = - usePlayerUiStore().select(context, (state) => state.aspectRatio); final isShowControl = usePlayerUiStore().select(context, (state) => state.isShowControl); final isShowProgress = @@ -325,7 +322,6 @@ class ControlsOverlay extends HookWidget { color: contentColor, overlayColor: overlayColor, saveProgress: () => saveProgress(), - resizeWindow: () => resizeWindow(aspectRatio), ), ), ), diff --git a/lib/utils/resize_window.dart b/lib/utils/resize_window.dart deleted file mode 100644 index 12b0944..0000000 --- a/lib/utils/resize_window.dart +++ /dev/null @@ -1,110 +0,0 @@ -import 'dart:math' as math; -import 'package:flutter/material.dart'; -import 'package:iris/store/use_app_store.dart'; -import 'package:iris/utils/logger.dart'; -import 'package:window_manager/window_manager.dart'; -import 'package:window_size/window_size.dart'; - -Future resizeWindow(double? videoAspectRatio) async { - if (await windowManager.isFullScreen() || await windowManager.isMaximized()) { - return; - } - - if (videoAspectRatio == null || videoAspectRatio == 0) { - windowManager.setAspectRatio(0); - return; - } - - bool autoResize = useAppStore().state.autoResize; - - if (!autoResize) return; - - windowManager.setAspectRatio(videoAspectRatio); - - Rect oldBounds = await windowManager.getBounds(); - - if (oldBounds.size.aspectRatio.toStringAsFixed(2) == - videoAspectRatio.toStringAsFixed(2)) { - return; - } - - Screen? screen = await getCurrentScreen(); - - if (screen == null) return; - - double screenWidth = screen.frame.size.width; - double screenHeight = screen.frame.size.height; - double screenAspectRatio = screen.frame.size.aspectRatio; - - double oldArea = oldBounds.size.width * oldBounds.size.height; - - double newHeight = math.sqrt(oldArea / videoAspectRatio); - double newWidth = newHeight * videoAspectRatio; - - Size size = oldBounds.size.aspectRatio < 1 - ? Size(oldBounds.size.height * videoAspectRatio, oldBounds.size.height) - : Size(newWidth, newHeight); - - if (size.width < screenWidth / screen.scaleFactor && - size.height < screenHeight / screen.scaleFactor) { - logger('Window resize: $size'); - - windowManager.setBounds( - null, - position: Offset( - oldBounds.left < 0 - ? 0 - : screenWidth / screen.scaleFactor - oldBounds.left < size.width / 2 - ? screenWidth / screen.scaleFactor - size.width - : oldBounds.left + oldBounds.size.width / 2 - size.width / 2 < 0 - ? 0 - : oldBounds.left + - oldBounds.size.width / 2 - - size.width / 2, - oldBounds.top + oldBounds.size.height / 2 - size.height / 2 <= 0 - ? 0 - : oldBounds.top + oldBounds.size.height / 2 - size.height / 2, - ), - size: size, - animate: true, - ); - } else { - if (screenAspectRatio > videoAspectRatio) { - double height = screenHeight * 0.9 / screen.scaleFactor; - double width = height * videoAspectRatio; - Size size = Size(width, height); - - Offset position = await calcWindowPosition( - size, - Alignment.center, - ); - - logger('Window resize to center: $size'); - - windowManager.setBounds( - null, - position: position, - size: size, - animate: true, - ); - } else { - double width = screenWidth * 0.9 / screen.scaleFactor; - double height = width / videoAspectRatio; - Size size = Size(width, height); - - Offset position = await calcWindowPosition( - size, - Alignment.center, - ); - - logger('Window resize to center: $size'); - - windowManager.setBounds( - null, - position: position, - size: size, - animate: true, - ); - } - } -} diff --git a/lib/widgets/drag_aria.dart b/lib/widgets/drag_aria.dart index be887df..cadbed2 100644 --- a/lib/widgets/drag_aria.dart +++ b/lib/widgets/drag_aria.dart @@ -2,7 +2,6 @@ import 'package:flutter/material.dart'; import 'package:flutter_zustand/flutter_zustand.dart'; import 'package:iris/store/use_player_ui_store.dart'; import 'package:iris/utils/platform.dart'; -import 'package:iris/utils/resize_window.dart'; import 'package:window_manager/window_manager.dart'; class DragAria extends StatelessWidget { @@ -14,8 +13,6 @@ class DragAria extends StatelessWidget { final Widget child; @override Widget build(BuildContext context) { - final aspectRatio = - usePlayerUiStore().select(context, (state) => state.aspectRatio); final isFullScreen = usePlayerUiStore().select(context, (state) => state.isFullScreen); @@ -26,7 +23,6 @@ class DragAria extends StatelessWidget { } else { if (isDesktop && await windowManager.isMaximized()) { await windowManager.unmaximize(); - await resizeWindow(aspectRatio); } else { await windowManager.maximize(); } diff --git a/lib/widgets/title_bar.dart b/lib/widgets/title_bar.dart index 28124fe..9027f38 100644 --- a/lib/widgets/title_bar.dart +++ b/lib/widgets/title_bar.dart @@ -15,7 +15,6 @@ class TitleBar extends HookWidget { this.color, this.overlayColor, this.saveProgress, - this.resizeWindow, }); final String? title; @@ -23,7 +22,6 @@ class TitleBar extends HookWidget { final Color? color; final WidgetStateProperty? overlayColor; final Future Function()? saveProgress; - final Future Function()? resizeWindow; @override Widget build(BuildContext context) { @@ -122,9 +120,6 @@ class TitleBar extends HookWidget { color: color, ), onPressed: () async { - if (isFullScreen) { - await resizeWindow?.call(); - } usePlayerUiStore() .updateFullScreen(!isFullScreen); }, @@ -148,7 +143,6 @@ class TitleBar extends HookWidget { onPressed: () async { if (isMaximized) { await windowManager.unmaximize(); - await resizeWindow?.call(); } else { await windowManager.maximize(); } From e340bb112914fcc8145a504a8bccef8bf0fddc5a Mon Sep 17 00:00:00 2001 From: 22 <60903333+nini22P@users.noreply.github.com> Date: Mon, 15 Sep 2025 10:10:59 +0800 Subject: [PATCH 18/31] fix: improve uri builder --- lib/models/storages/ftp.dart | 7 ++++--- lib/models/storages/local.dart | 1 - lib/models/storages/webdav.dart | 25 ++++++++++++------------- lib/utils/build_file_uri.dart | 20 ++++++++++++++++++++ lib/utils/find_subtitle.dart | 19 ++++--------------- 5 files changed, 40 insertions(+), 32 deletions(-) create mode 100644 lib/utils/build_file_uri.dart diff --git a/lib/models/storages/ftp.dart b/lib/models/storages/ftp.dart index 727d18f..4d7e6f5 100644 --- a/lib/models/storages/ftp.dart +++ b/lib/models/storages/ftp.dart @@ -2,6 +2,7 @@ import 'dart:convert'; import 'package:iris/models/file.dart'; import 'package:iris/models/storages/storage.dart'; +import 'package:iris/utils/build_file_uri.dart'; import 'package:iris/utils/check_content_type.dart'; import 'package:iris/utils/find_subtitle.dart'; import 'package:iris/utils/logger.dart'; @@ -26,14 +27,14 @@ Future> getFTPFiles( try { await client.connect(); - await client.fs.changeDirectory(path.join('/').replaceFirst('//', '/')); + await client.fs.changeDirectory(path.join('/').replaceAll('//', '/')); final files = await client.fs.listDirectory(); await client.disconnect(); final baseUri = - 'ftp?host=${storage.host}&port=${storage.port}&path=${path.join('/').replaceFirst('//', '/')}'; + 'ftp?host=${storage.host}&port=${storage.port}&path=${path.join('/').replaceAll('//', '/')}'; final allFileNames = files.map((file) => file.name).toList(); @@ -42,7 +43,7 @@ Future> getFTPFiles( storageId: storage.id, storageType: StorageType.ftp, name: file.name, - uri: Uri.encodeFull('$baseUri/${file.name}'), + uri: buildFileUri(baseUri, file.name), path: [...path, file.name], isDir: file.isDirectory, size: file.isDirectory ? 0 : file.info?.size ?? 0, diff --git a/lib/models/storages/local.dart b/lib/models/storages/local.dart index a5ce30c..c4ed75d 100644 --- a/lib/models/storages/local.dart +++ b/lib/models/storages/local.dart @@ -314,7 +314,6 @@ Future> getContentFiles(String uri) async { allFileNames, file.name, uri, - encodeUri: false, ), )) .toList()); diff --git a/lib/models/storages/webdav.dart b/lib/models/storages/webdav.dart index df59ce9..9ae9e2d 100644 --- a/lib/models/storages/webdav.dart +++ b/lib/models/storages/webdav.dart @@ -1,5 +1,6 @@ import 'dart:convert'; import 'package:iris/models/storages/storage.dart'; +import 'package:iris/utils/build_file_uri.dart'; import 'package:iris/utils/check_content_type.dart'; import 'package:iris/utils/find_subtitle.dart'; import 'package:iris/utils/logger.dart'; @@ -60,26 +61,24 @@ Future> getWebDAVFiles( client.setReceiveTimeout(8000); var files = await client.readDir(path.join('/')); - - final String baseUri = - 'http${https ? 's' : ''}://$host:$port${path.join('/')}'; - final allFileNames = files.map((f) => f.name as String).toList(); + final cleanPathSegments = path.map((e) => e.replaceAll('/', '')).toList(); + final baseUri = Uri( + scheme: storage.https ? 'https' : 'http', + host: storage.host, + port: int.tryParse(storage.port), + pathSegments: cleanPathSegments, + ); + final baseUriString = baseUri.toString(); + return await Future.wait( files.map((file) async { - final fileUri = Uri( - scheme: https ? 'https' : 'http', - host: host, - port: int.tryParse(port), - pathSegments: [...path, file.name!], - ); - return FileItem( storageId: id, storageType: StorageType.webdav, name: '${file.name}', - uri: fileUri.toString(), + uri: buildFileUri(baseUriString, file.name!), path: [...path, '${file.name}'], isDir: file.isDir ?? false, size: file.size ?? 0, @@ -90,7 +89,7 @@ Future> getWebDAVFiles( subtitles: await findSubtitle( allFileNames, file.name as String, - baseUri, + baseUriString, ), ); }), diff --git a/lib/utils/build_file_uri.dart b/lib/utils/build_file_uri.dart new file mode 100644 index 0000000..35e29f5 --- /dev/null +++ b/lib/utils/build_file_uri.dart @@ -0,0 +1,20 @@ +import 'package:iris/utils/platform.dart'; + +String buildFileUri(String baseUri, String fileName) { + if (baseUri.startsWith('ftp?')) { + final separator = baseUri.endsWith('/') ? '' : '/'; + return Uri.encodeFull('$baseUri$separator$fileName'); + } + + if (isAndroid && baseUri.startsWith('content://')) { + return '$baseUri/${Uri.encodeComponent(fileName)}'; + } + + try { + final dirUri = Uri.parse(baseUri.endsWith('/') ? baseUri : '$baseUri/'); + return dirUri.resolve(fileName).toString(); + } catch (e) { + final separator = baseUri.endsWith('/') ? '' : '/'; + return '$baseUri$separator$fileName'; + } +} diff --git a/lib/utils/find_subtitle.dart b/lib/utils/find_subtitle.dart index c8544ef..3cc0f65 100644 --- a/lib/utils/find_subtitle.dart +++ b/lib/utils/find_subtitle.dart @@ -1,4 +1,4 @@ -import 'package:iris/utils/platform.dart'; +import 'package:iris/utils/build_file_uri.dart'; import 'package:iris/models/file.dart'; import 'package:iris/utils/check_content_type.dart'; import 'package:path/path.dart' as path; @@ -6,9 +6,8 @@ import 'package:path/path.dart' as path; Future> findSubtitle( List fileNames, String videoName, - String baseUri, { - bool encodeUri = true, -}) async { + String baseUri, +) async { if (checkContentType(videoName) != ContentType.video) { return []; } @@ -38,19 +37,9 @@ Future> findSubtitle( subTitleName = match.group(1) ?? fileName; } - final Uri fileUri; - if (isAndroid && baseUri.startsWith('content://')) { - fileUri = Uri.parse('$baseUri/${Uri.encodeComponent(fileName)}'); - } else { - final uriParts = Uri.parse(baseUri); - fileUri = uriParts.replace( - pathSegments: [...uriParts.pathSegments, fileName], - ); - } - return Subtitle( name: subTitleName, - uri: encodeUri ? fileUri.toString() : '$baseUri/$fileName', + uri: buildFileUri(baseUri, fileName), ); }).toList(); } From 6c9023fd427e3e553a18ef1f275f6794c50edcd9 Mon Sep 17 00:00:00 2001 From: 22 <60903333+nini22P@users.noreply.github.com> Date: Mon, 15 Sep 2025 16:02:40 +0800 Subject: [PATCH 19/31] feat: improve gesture, add speed selector --- android/settings.gradle | 2 +- lib/globals.dart | 19 + lib/hooks/player/use_fvp_player.dart | 35 +- lib/hooks/use_gesture.dart | 331 +++---- lib/hooks/use_keyboard.dart | 8 +- lib/l10n/app_en.arb | 1 + lib/l10n/app_zh.arb | 1 + lib/main.dart | 3 + lib/models/storages/local.dart | 2 +- lib/pages/home/home.dart | 2 +- lib/pages/player/audio.dart | 3 +- lib/pages/player/control_bar/control_bar.dart | 900 +++++++++--------- .../control_bar/control_bar_slider.dart | 187 ++-- lib/pages/player/controls_overlay.dart | 106 ++- lib/pages/player/player.dart | 134 +-- lib/pages/player/speed_selector.dart | 104 ++ lib/widgets/dialogs/show_language_dialog.dart | 58 +- .../dialogs/show_orientation_dialog.dart | 29 +- lib/widgets/dialogs/show_rate_dialog.dart | 47 +- .../dialogs/show_theme_mode_dialog.dart | 64 +- .../{drag_aria.dart => drag_area.dart} | 4 +- lib/widgets/popup.dart | 116 ++- lib/widgets/title_bar.dart | 259 +++-- pubspec.lock | 4 +- pubspec.yaml | 4 +- 25 files changed, 1240 insertions(+), 1183 deletions(-) create mode 100644 lib/pages/player/speed_selector.dart rename lib/widgets/{drag_aria.dart => drag_area.dart} (94%) diff --git a/android/settings.gradle b/android/settings.gradle index 7cffcd0..0fff400 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 "8.13.0" apply false + id "com.android.application" version "8.12.0" apply false id "org.jetbrains.kotlin.android" version "2.2.20" apply false } diff --git a/lib/globals.dart b/lib/globals.dart index f4fc43a..bbb5fd7 100644 --- a/lib/globals.dart +++ b/lib/globals.dart @@ -9,3 +9,22 @@ String? initUri; PermissionStatus? storagePermissionStatus; final moreMenuKey = GlobalKey(); final rateMenuKey = GlobalKey(); +const double speedSelectorItemWidth = 64.0; +const List speedStops = [ + 0.25, + 0.5, + 0.75, + 1.0, + 1.25, + 1.5, + 1.75, + 2.0, + 3.0, + 4.0, + 5.0, + 6.0, + 7.0, + 8.0, + 9.0, + 10.0, +]; diff --git a/lib/hooks/player/use_fvp_player.dart b/lib/hooks/player/use_fvp_player.dart index d76eb5e..9004666 100644 --- a/lib/hooks/player/use_fvp_player.dart +++ b/lib/hooks/player/use_fvp_player.dart @@ -89,23 +89,10 @@ FvpPlayer useFvpPlayer(BuildContext context) { return; }, [file?.type, width, height]); - Future init() async { + Future init(FileItem? file) async { isInitializing.value = true; try { - await controller.value.initialize(); - await controller.value.setLooping(repeat == Repeat.one ? true : false); - await controller.value.setPlaybackSpeed(rate); - await controller.value.setVolume(isMuted ? 0 : volume / 100); - } catch (e) { - logger('Error initializing player: $e'); - } - - isInitializing.value = false; - } - - useEffect(() { - () async { if (controller.value.value.isInitialized) { logger('Dispose player'); controller.value.dispose(); @@ -138,10 +125,20 @@ FvpPlayer useFvpPlayer(BuildContext context) { httpHeaders: auth != null ? {'authorization': auth} : {}, ); } - await init(); } - }(); + await controller.value.initialize(); + await controller.value.setLooping(repeat == Repeat.one ? true : false); + await controller.value.setPlaybackSpeed(rate); + await controller.value.setVolume(isMuted ? 0 : volume / 100); + } catch (e) { + logger('Error initializing player: $e'); + } finally { + isInitializing.value = false; + } + } + useEffect(() { + init(file); return; }, [file?.uri]); @@ -293,8 +290,10 @@ FvpPlayer useFvpPlayer(BuildContext context) { }, [controller.value.value.isPlaying]); Future play() async { - if (!controller.value.value.isInitialized && !isInitializing.value) { - init(); + if (!controller.value.value.isInitialized && + !isInitializing.value && + file != null) { + init(file); } controller.value.play(); } diff --git a/lib/hooks/use_gesture.dart b/lib/hooks/use_gesture.dart index bc36da9..05e4bb2 100644 --- a/lib/hooks/use_gesture.dart +++ b/lib/hooks/use_gesture.dart @@ -1,12 +1,13 @@ import 'package:flutter/gestures.dart'; import 'package:flutter/material.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; -import 'package:flutter_zustand/flutter_zustand.dart'; +import 'package:iris/globals.dart' show speedStops, speedSelectorItemWidth; import 'package:iris/hooks/use_brightness.dart'; import 'package:iris/hooks/use_volume.dart'; import 'package:iris/models/player.dart'; import 'package:iris/store/use_app_store.dart'; import 'package:iris/store/use_player_ui_store.dart'; +import 'package:iris/utils/logger.dart'; import 'package:iris/utils/platform.dart'; import 'package:provider/provider.dart'; import 'package:window_manager/window_manager.dart'; @@ -58,39 +59,23 @@ Gesture useGesture({ required void Function() showControl, required void Function() hideControl, required void Function() showProgress, + required void Function(Offset position) showSpeedSelector, + required void Function(double finalSpeed) hideSpeedSelector, + required void Function(double speed, double visualOffset) updateSelectedSpeed, }) { final context = useContext(); - final isPlaying = - context.select((player) => player.isPlaying); - final progress = - context.select( - (player) => (position: player.position, duration: player.duration), - ); + final player = context.read(); + + final gestureState = useRef({ + 'isTouch': false, + 'isLongPress': false, + 'isDragging': false, + 'startPanOffset': Offset.zero, + 'startSeekPosition': Duration.zero, + 'panDirection': null, // null: 未确定, Axis.horizontal, Axis.vertical + }); - final play = context.read().play; - final pause = context.read().pause; - final forward = context.read().forward; - final backward = context.read().backward; - final seek = context.read().seek; - - final isFullScreen = - usePlayerUiStore().select(context, (state) => state.isFullScreen); - - final isShowControl = - usePlayerUiStore().select(context, (state) => state.isShowControl); - final isSeeking = - usePlayerUiStore().select(context, (state) => state.isSeeking); - - final updateIsHovering = useCallback((bool value) { - usePlayerUiStore().updateIsHovering(value); - }, [usePlayerUiStore().updateIsHovering]); - - final isTouch = useState(false); - final isLongPress = useState(false); - final startPosition = useState(null); - final isHorizontalGesture = useState(false); - final isVerticalGesture = useState(false); final isLeftGesture = useState(false); final isRightGesture = useState(false); @@ -99,202 +84,224 @@ Gesture useGesture({ void onTapDown(TapDownDetails details) { if (details.kind == PointerDeviceKind.touch) { - isTouch.value = true; + gestureState.value['isTouch'] = true; } } void onTap() { - if (isShowControl) { + if (usePlayerUiStore().state.isShowControl) { hideControl(); } else { showControl(); } } - void onDoubleTapDown(TapDownDetails details) async { + void onDoubleTapDown(TapDownDetails details) { if (details.kind == PointerDeviceKind.touch) { - double position = - details.globalPosition.dx / MediaQuery.of(context).size.width; - if (position > 0.75) { - if (isShowControl) { - showControl(); - } else { - showProgress(); - } - await forward(5); - } else if (position < 0.25) { - if (isShowControl) { - showControl(); - } else { - showProgress(); - } - backward(5); + final screenWidth = MediaQuery.of(context).size.width; + final tapDx = details.globalPosition.dx; + + if (tapDx > screenWidth * 0.7) { + // 右侧 30% + showProgress(); + player.forward(10); + } else if (tapDx < screenWidth * 0.3) { + // 左侧 30% + showProgress(); + player.backward(10); } else { - if (isPlaying == true) { - await useAppStore().updateAutoPlay(false); - pause(); + // 中间 40% + if (player.isPlaying) { + useAppStore().updateAutoPlay(false); + player.pause(); showControl(); } else { - await useAppStore().updateAutoPlay(true); - play(); + useAppStore().updateAutoPlay(true); + player.play(); } } - } else { - if (isDesktop) { - usePlayerUiStore().updateFullScreen(!isFullScreen); - } + } else if (isDesktop) { + // 桌面端双击切换全屏 + usePlayerUiStore() + .updateFullScreen(!usePlayerUiStore().state.isFullScreen); } } void onLongPressStart(LongPressStartDetails details) { - if (isTouch.value && isPlaying == true) { - isLongPress.value = true; - useAppStore().updateRate(2.0); + if (gestureState.value['isTouch'] as bool && player.isPlaying) { + gestureState.value['isLongPress'] = true; + gestureState.value['startPanOffset'] = details.globalPosition; + + final currentRate = useAppStore().state.rate; + final closestSpeed = speedStops.reduce( + (a, b) => (a - currentRate).abs() < (b - currentRate).abs() ? a : b); + gestureState.value['initialSpeedIndex'] = + speedStops.indexOf(closestSpeed); + + showSpeedSelector(details.globalPosition); + updateSelectedSpeed(closestSpeed, 0.0); } } void onLongPressMoveUpdate(LongPressMoveUpdateDetails details) { - if (!isLongPress.value) return; - int fast = (details.offsetFromOrigin.dx / 50).toInt(); - if (fast >= 1) { - useAppStore().updateRate(fast > 4 ? 5.0 : (1 + fast).toDouble()); - } else if (fast <= -1) { - useAppStore() - .updateRate(fast < -3 ? 0.25 : (1 - 0.25 * fast.abs()).toDouble()); - } + if (!(gestureState.value['isLongPress'] as bool)) return; + + final startDx = (gestureState.value['startPanOffset'] as Offset).dx; + final currentDx = details.globalPosition.dx; + final deltaDx = currentDx - startDx; + + const double sensitivity = speedSelectorItemWidth; + final double visualOffset = deltaDx; + + int steps = (-visualOffset / sensitivity).round(); + + int initialIndex = gestureState.value['initialSpeedIndex'] as int? ?? + speedStops.indexOf(1.0); + int finalIndex = (initialIndex + steps).clamp(0, speedStops.length - 1); + + double selectedSpeed = speedStops[finalIndex]; + + updateSelectedSpeed(selectedSpeed, visualOffset); + useAppStore().updateRate(selectedSpeed); } void onLongPressEnd(LongPressEndDetails details) { - if (isLongPress.value) { - useAppStore().updateRate(1.0); + if (gestureState.value['isLongPress'] as bool) { + hideSpeedSelector(useAppStore().state.rate); } - isLongPress.value = false; - isTouch.value = false; + gestureState.value['isLongPress'] = false; + gestureState.value['isTouch'] = false; } void onLongPressCancel() { - if (isLongPress.value) { - useAppStore().updateRate(1.0); + if (gestureState.value['isLongPress'] as bool) { + hideSpeedSelector(useAppStore().state.rate); } - isLongPress.value = false; - isTouch.value = false; + gestureState.value['isLongPress'] = false; + gestureState.value['isTouch'] = false; } - void onPanStart(DragStartDetails details) async { + void onPanStart(DragStartDetails details) { if (isDesktop && details.kind != PointerDeviceKind.touch) { windowManager.startDragging(); - } else if (details.kind == PointerDeviceKind.touch) { - isTouch.value = true; - startPosition.value = details.globalPosition; + return; + } + + if (gestureState.value['isLongPress'] as bool) { + return; + } + + if (details.kind == PointerDeviceKind.touch) { + const double edgeDeadZone = 48.0; + final screenSize = MediaQuery.of(context).size; + final startDx = details.globalPosition.dx; + + if (startDx < edgeDeadZone || startDx > screenSize.width - edgeDeadZone) { + logger("Edge swipe detected. Ignoring for system navigation."); + return; + } + + gestureState.value['isTouch'] = true; + gestureState.value['isDragging'] = true; + gestureState.value['startPanOffset'] = details.globalPosition; + gestureState.value['startSeekPosition'] = player.position; + gestureState.value['panDirection'] = null; + isLeftGesture.value = false; + isRightGesture.value = false; } } - void onPanUpdate(DragUpdateDetails details) async { - if (isTouch.value && startPosition.value != null) { - // 判断滑动方向 - double dx = (details.globalPosition.dx - startPosition.value!.dx).abs(); - double dy = (details.globalPosition.dy - startPosition.value!.dy).abs(); - if (!isHorizontalGesture.value && !isVerticalGesture.value) { - if (dx > dy) { - isHorizontalGesture.value = true; - usePlayerUiStore().updateIsSeeking(true); - } else { - isVerticalGesture.value = true; - } + void onPanUpdate(DragUpdateDetails details) { + if (!(gestureState.value['isDragging'] as bool)) return; + + final startOffset = gestureState.value['startPanOffset'] as Offset; + final totalDx = details.globalPosition.dx - startOffset.dx; + final totalDy = details.globalPosition.dy - startOffset.dy; + + // 增加手势“死区”,防止误触 + const double panDeadzone = 8.0; + if (gestureState.value['panDirection'] == null) { + if (totalDx.abs() > panDeadzone || totalDy.abs() > panDeadzone) { + gestureState.value['panDirection'] = + totalDx.abs() > totalDy.abs() ? Axis.horizontal : Axis.vertical; } + } - // 水平滑动 - if (isHorizontalGesture.value && isSeeking) { - double dx = details.delta.dx; - int seconds = (dx * 2 + progress.position.inSeconds).toInt(); - Duration position = Duration( - seconds: seconds < 0 - ? 0 - : seconds > progress.duration.inSeconds - ? progress.duration.inSeconds - : seconds); - seek(position); - if (isShowControl) { - showControl(); - } else { - showProgress(); - } + final direction = gestureState.value['panDirection']; + if (direction == null) return; + + // 水平滑动 (Seek) + if (direction == Axis.horizontal) { + if (!usePlayerUiStore().state.isSeeking) { + usePlayerUiStore().updateIsSeeking(true); } - // 垂直滑动 - final startDX = startPosition.value?.dx; - if (isVerticalGesture.value && startDX != null) { - if (!isLeftGesture.value && !isRightGesture.value) { - if (startDX < (MediaQuery.of(context).size.width / 2)) { - isLeftGesture.value = true; - } else { - isRightGesture.value = true; - } - } + const double sensitivity = 3.0; // 每滑动3像素代表1秒 + final double seekSecondsOffset = totalDx / sensitivity; + final startSeconds = + (gestureState.value['startSeekPosition'] as Duration).inSeconds; - double dy = details.delta.dy; + int targetSeconds = (startSeconds + seekSecondsOffset).round(); - // 屏幕亮度 - if (isLeftGesture.value && brightness.value != null) { - final newBrightness = brightness.value! - dy / 200; - brightness.value = newBrightness > 1 - ? 1 - : newBrightness < 0 - ? 0 - : newBrightness; - } + // 边界检查 + targetSeconds = targetSeconds.clamp(0, player.duration.inSeconds); - // 音量 - if (isRightGesture.value && volume.value != null) { - final newVolume = volume.value! - dy / 200; - volume.value = newVolume > 1 - ? 1 - : newVolume < 0 - ? 0 - : newVolume; - } + player.seek(Duration(seconds: targetSeconds)); + showProgress(); + } + + // 垂直滑动 (亮度和音量) + if (direction == Axis.vertical) { + // 仅在垂直滑动开始时判断一次左右区域 + if (!isLeftGesture.value && !isRightGesture.value) { + isLeftGesture.value = + startOffset.dx < MediaQuery.of(context).size.width / 2; + isRightGesture.value = !isLeftGesture.value; + } + + final double dy = details.delta.dy; + + if (isLeftGesture.value && brightness.value != null) { + final newBrightness = brightness.value! - dy / 200; + brightness.value = newBrightness.clamp(0.0, 1.0); + } + + if (isRightGesture.value && volume.value != null) { + final newVolume = volume.value! - dy / 200; + volume.value = newVolume.clamp(0.0, 1.0); } } } - void onPanEnd(DragEndDetails details) async { - isTouch.value = false; - isHorizontalGesture.value = false; - isVerticalGesture.value = false; - isLeftGesture.value = false; - isRightGesture.value = false; - startPosition.value = null; - if (isSeeking) { - await seek(progress.position); + // ignore: no_leading_underscores_for_local_identifiers + void _resetPanState() { + if (usePlayerUiStore().state.isSeeking) { usePlayerUiStore().updateIsSeeking(false); } - } - - void onPanCancel() async { - isHorizontalGesture.value = false; - isVerticalGesture.value = false; + gestureState.value = { + ...gestureState.value, + 'isDragging': false, + 'panDirection': null, + }; isLeftGesture.value = false; isRightGesture.value = false; - startPosition.value = null; - if (isSeeking) { - isTouch.value = false; - await seek(progress.position); - usePlayerUiStore().updateIsSeeking(false); - } } + void onPanEnd(DragEndDetails details) => _resetPanState(); + void onPanCancel() => _resetPanState(); + void onHover(PointerHoverEvent event) { if (event.kind != PointerDeviceKind.touch) { - updateIsHovering(true); + usePlayerUiStore().updateIsHovering(true); showControl(); } } final cursor = useMemoized(() { - return isPlaying == false + return player.isPlaying == false ? SystemMouseCursors.basic : SystemMouseCursors.none; - }, [isPlaying]); + }, [player.isPlaying]); return Gesture( onTapDown: onTapDown, @@ -309,7 +316,7 @@ Gesture useGesture({ onPanEnd: onPanEnd, onPanCancel: onPanCancel, onHover: onHover, - isLongPress: isLongPress.value, + isLongPress: gestureState.value['isLongPress'] as bool, isLeftGesture: isLeftGesture.value, isRightGesture: isRightGesture.value, brightness: brightness.value, diff --git a/lib/hooks/use_keyboard.dart b/lib/hooks/use_keyboard.dart index bc9d1a5..fec24d6 100644 --- a/lib/hooks/use_keyboard.dart +++ b/lib/hooks/use_keyboard.dart @@ -17,6 +17,7 @@ import 'package:iris/widgets/bottom_sheets/show_open_link_bottom_sheet.dart'; import 'package:iris/widgets/dialogs/show_open_link_dialog.dart'; import 'package:iris/widgets/popup.dart'; import 'package:provider/provider.dart'; +import 'package:window_manager/window_manager.dart'; typedef KeyboardEvent = void Function(KeyEvent event); @@ -44,7 +45,12 @@ KeyboardEvent useKeyboard({ case LogicalKeyboardKey.keyX: showControl(); await player.saveProgress(); - exit(0); + if (isDesktop) { + windowManager.close(); + } else { + SystemNavigator.pop(); + exit(0); + } } return; } diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index 2c765c8..b4a4caa 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -38,6 +38,7 @@ "edit_local_storage": "Edit local storage", "edit_webdav_storage": "Edit WebDAV storage", "enter_fullscreen": "Enter fullscreen", + "exit": "Exit", "exit_app_back_again": "Press back again to exit.", "exit_fullscreen": "Exit fullscreen", "experimental": "Experimental", diff --git a/lib/l10n/app_zh.arb b/lib/l10n/app_zh.arb index 5c3ac4e..7a0c624 100644 --- a/lib/l10n/app_zh.arb +++ b/lib/l10n/app_zh.arb @@ -38,6 +38,7 @@ "edit_local_storage": "编辑本地存储", "edit_webdav_storage": "编辑 WebDAV 存储", "enter_fullscreen": "进入全屏", + "exit": "退出", "exit_app_back_again": "再次点击返回退出应用。", "exit_fullscreen": "退出全屏", "experimental": "实验性", diff --git a/lib/main.dart b/lib/main.dart index 198f414..cca1849 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,5 +1,6 @@ import 'dart:io'; import 'package:app_links/app_links.dart'; +import 'package:flutter/services.dart'; import 'package:fvp/fvp.dart' as fvp; import 'package:flutter/material.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; @@ -73,6 +74,8 @@ void main(List arguments) async { }); } + SystemChrome.setEnabledSystemUIMode(SystemUiMode.immersiveSticky); + MediaStream mediaStream = MediaStream(); mediaStream.startServer(); diff --git a/lib/models/storages/local.dart b/lib/models/storages/local.dart index c4ed75d..44141ad 100644 --- a/lib/models/storages/local.dart +++ b/lib/models/storages/local.dart @@ -231,7 +231,7 @@ Future> getLocalFiles( final videoStat = await video.stat(); final associatedSubtitles = subtitles.map((sub) { final baseName = p.basenameWithoutExtension(video.path); - String subTitleName = p.basename(sub.path); // Default name + String subTitleName = p.basename(sub.path); final regex = RegExp(r'^' + RegExp.escape(baseName) + r'\.(.+?)\.'); final match = regex.firstMatch(subTitleName); if (match != null) { diff --git a/lib/pages/home/home.dart b/lib/pages/home/home.dart index 362290a..7c0d04d 100644 --- a/lib/pages/home/home.dart +++ b/lib/pages/home/home.dart @@ -27,7 +27,7 @@ class Home extends HookWidget { systemNavigationBarColor: Colors.transparent, ), child: Scaffold( - backgroundColor: Color(0xFF2f2f2f), + backgroundColor: Color.fromARGB(255, 0, 0, 0), body: PlayerView(playerBackend: playerBackend), ), ); diff --git a/lib/pages/player/audio.dart b/lib/pages/player/audio.dart index 2ccede9..bdfbb5a 100644 --- a/lib/pages/player/audio.dart +++ b/lib/pages/player/audio.dart @@ -25,8 +25,7 @@ class Audio extends HookWidget { return IgnorePointer( child: Stack( children: [ - Container( - color: Colors.grey[800], + SizedBox( width: MediaQuery.of(context).size.width, height: MediaQuery.of(context).size.height, child: cover != null diff --git a/lib/pages/player/control_bar/control_bar.dart b/lib/pages/player/control_bar/control_bar.dart index 6ddbc04..e19d1c0 100644 --- a/lib/pages/player/control_bar/control_bar.dart +++ b/lib/pages/player/control_bar/control_bar.dart @@ -1,8 +1,9 @@ import 'dart:io'; import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:flutter_zustand/flutter_zustand.dart'; -import 'package:iris/globals.dart'; +import 'package:iris/globals.dart' show rateMenuKey, speedStops, moreMenuKey; import 'package:iris/models/player.dart'; import 'package:iris/models/storages/local.dart'; import 'package:iris/models/store/app_state.dart'; @@ -23,6 +24,7 @@ import 'package:iris/utils/platform.dart'; import 'package:iris/widgets/popup.dart'; import 'package:iris/pages/storages/storages.dart'; import 'package:provider/provider.dart'; +import 'package:window_manager/window_manager.dart'; class ControlBar extends HookWidget { const ControlBar({ @@ -48,8 +50,7 @@ class ControlBar extends HookWidget { final isInitializing = context.select((player) => player.isInitializing); - final play = context.read().play; - final pause = context.read().pause; + final player = context.read(); final rate = useAppStore().select(context, (state) => state.rate); final volume = useAppStore().select(context, (state) => state.volume); @@ -91,562 +92,491 @@ class ControlBar extends HookWidget { ], ), ), - child: SafeArea( - child: Column( - children: [ - Visibility( - visible: MediaQuery.of(context).size.width < 1024 || !isDesktop, - child: ControlBarSlider( - showControl: showControl, - color: color, - ), + child: Column( + children: [ + Visibility( + visible: MediaQuery.of(context).size.width < 1024 || !isDesktop, + child: ControlBarSlider( + showControl: showControl, + color: color, ), - Row( - mainAxisAlignment: MainAxisAlignment.center, - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - const SizedBox(width: 4), - Stack( - alignment: Alignment.center, - children: [ - IconButton( - tooltip: - '${displayIsPlaying.value ? t.pause : t.play} ( Space )', - icon: Icon( - displayIsPlaying.value - ? Icons.pause_rounded - : Icons.play_arrow_rounded, - size: 36, - color: color, - ), - onPressed: () { - showControl(); - if (isPlaying == true) { - useAppStore().updateAutoPlay(false); - pause(); - } else { - useAppStore().updateAutoPlay(true); - play(); - } - }, - style: ButtonStyle(overlayColor: overlayColor), + ), + Row( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + const SizedBox(width: 4), + Stack( + alignment: Alignment.center, + children: [ + IconButton( + tooltip: + '${displayIsPlaying.value ? t.pause : t.play} ( Space )', + icon: Icon( + displayIsPlaying.value + ? Icons.pause_rounded + : Icons.play_arrow_rounded, + size: 36, + color: color, ), - if (isInitializing) - SizedBox( - width: 36, - height: 36, - child: CircularProgressIndicator( - strokeWidth: 4, - color: Theme.of(context).colorScheme.surface, - ), + onPressed: () { + showControl(); + if (isPlaying == true) { + useAppStore().updateAutoPlay(false); + player.pause(); + } else { + useAppStore().updateAutoPlay(true); + player.play(); + } + }, + style: ButtonStyle(overlayColor: overlayColor), + ), + if (isInitializing) + SizedBox( + width: 36, + height: 36, + child: CircularProgressIndicator( + strokeWidth: 4, + color: Theme.of(context).colorScheme.surface, ), - ], + ), + ], + ), + IconButton( + tooltip: '${t.stop} ( Ctrl + C )', + icon: Icon( + Icons.stop_rounded, + size: 28, + color: color, ), + onPressed: () { + showControl(); + useAppStore().updateAutoPlay(false); + player.pause(); + usePlayQueueStore().updateCurrentIndex(-1); + }, + style: ButtonStyle(overlayColor: overlayColor), + ), + if (playQueueLength > 1) IconButton( - tooltip: '${t.stop} ( Ctrl + C )', + tooltip: '${t.previous} ( Ctrl + ← )', icon: Icon( - Icons.stop_rounded, + Icons.skip_previous_rounded, size: 28, color: color, ), onPressed: () { showControl(); - useAppStore().updateAutoPlay(false); - pause(); - usePlayQueueStore().updateCurrentIndex(-1); + usePlayQueueStore().previous(); }, style: ButtonStyle(overlayColor: overlayColor), ), - if (playQueueLength > 1) - IconButton( - tooltip: '${t.previous} ( Ctrl + ← )', - icon: Icon( - Icons.skip_previous_rounded, - size: 28, - color: color, - ), - onPressed: () { - showControl(); - usePlayQueueStore().previous(); - }, - style: ButtonStyle(overlayColor: overlayColor), + if (playQueueLength > 1) + IconButton( + tooltip: '${t.next} ( Ctrl + → )', + icon: Icon( + Icons.skip_next_rounded, + size: 28, + color: color, ), - if (playQueueLength > 1) - IconButton( - tooltip: '${t.next} ( Ctrl + → )', + onPressed: () { + showControl(); + usePlayQueueStore().next(); + }, + style: ButtonStyle(overlayColor: overlayColor), + ), + if (MediaQuery.of(context).size.width >= 768) + Builder( + builder: (context) => IconButton( + tooltip: + '${t.shuffle}: ${shuffle ? t.on : t.off} ( Ctrl + X )', icon: Icon( - Icons.skip_next_rounded, - size: 28, - color: color, + Icons.shuffle_rounded, + size: 20, + color: !shuffle ? color?.withAlpha(153) : color, ), onPressed: () { showControl(); - usePlayQueueStore().next(); + shuffle + ? usePlayQueueStore().sort() + : usePlayQueueStore().shuffle(); + useAppStore().updateShuffle(!shuffle); }, style: ButtonStyle(overlayColor: overlayColor), ), - if (MediaQuery.of(context).size.width >= 768) - Builder( - builder: (context) => IconButton( - tooltip: - '${t.shuffle}: ${shuffle ? t.on : t.off} ( Ctrl + X )', - icon: Icon( - Icons.shuffle_rounded, - size: 20, - color: !shuffle ? color?.withAlpha(153) : color, - ), - onPressed: () { - showControl(); - shuffle - ? usePlayQueueStore().sort() - : usePlayQueueStore().shuffle(); - useAppStore().updateShuffle(!shuffle); - }, - style: ButtonStyle(overlayColor: overlayColor), - ), - ), - if (MediaQuery.of(context).size.width >= 768) - Builder( - builder: (context) => IconButton( - tooltip: - '${repeat == Repeat.one ? t.repeat_one : repeat == Repeat.all ? t.repeat_all : t.repeat_none} ( Ctrl + R )', - icon: Icon( - repeat == Repeat.one - ? Icons.repeat_one_rounded - : Icons.repeat_rounded, - size: 20, - color: repeat == Repeat.none - ? color?.withAlpha(153) - : color, - ), - onPressed: () { - showControl(); - useAppStore().toggleRepeat(); - }, - style: ButtonStyle(overlayColor: overlayColor), - ), - ), - if (MediaQuery.of(context).size.width >= 768) - IconButton( + ), + if (MediaQuery.of(context).size.width >= 768) + Builder( + builder: (context) => IconButton( tooltip: - '${t.video_zoom}: ${fit == BoxFit.contain ? t.fit : fit == BoxFit.fill ? t.stretch : fit == BoxFit.cover ? t.crop : '100%'} ( Ctrl + V )', + '${repeat == Repeat.one ? t.repeat_one : repeat == Repeat.all ? t.repeat_all : t.repeat_none} ( Ctrl + R )', icon: Icon( - fit == BoxFit.contain - ? Icons.fit_screen_rounded - : fit == BoxFit.fill - ? Icons.aspect_ratio_rounded - : fit == BoxFit.cover - ? Icons.crop_landscape_rounded - : Icons.crop_free_rounded, + repeat == Repeat.one + ? Icons.repeat_one_rounded + : Icons.repeat_rounded, size: 20, - color: color, + color: + repeat == Repeat.none ? color?.withAlpha(153) : color, ), onPressed: () { showControl(); - useAppStore().toggleFit(); + useAppStore().toggleRepeat(); }, style: ButtonStyle(overlayColor: overlayColor), ), - if (MediaQuery.of(context).size.width > 600) - PopupMenuButton( - key: rateMenuKey, - clipBehavior: Clip.hardEdge, - constraints: const BoxConstraints(minWidth: 0), - itemBuilder: (BuildContext context) => [ - 0.25, - 0.5, - 0.75, - 1.0, - 1.25, - 1.5, - 1.75, - 2.0, - 3.0, - 4.0, - 5.0, - ] - .map( - (item) => PopupMenuItem( - child: Text( - '${item}X', - style: TextStyle( - color: item == rate - ? Theme.of(context).colorScheme.primary - : null, - fontWeight: item == rate - ? FontWeight.bold - : FontWeight.w100, - ), + ), + if (MediaQuery.of(context).size.width >= 768) + IconButton( + tooltip: + '${t.video_zoom}: ${fit == BoxFit.contain ? t.fit : fit == BoxFit.fill ? t.stretch : fit == BoxFit.cover ? t.crop : '100%'} ( Ctrl + V )', + icon: Icon( + fit == BoxFit.contain + ? Icons.fit_screen_rounded + : fit == BoxFit.fill + ? Icons.aspect_ratio_rounded + : fit == BoxFit.cover + ? Icons.crop_landscape_rounded + : Icons.crop_free_rounded, + size: 20, + color: color, + ), + onPressed: () { + showControl(); + useAppStore().toggleFit(); + }, + style: ButtonStyle(overlayColor: overlayColor), + ), + if (MediaQuery.of(context).size.width > 600) + PopupMenuButton( + key: rateMenuKey, + clipBehavior: Clip.hardEdge, + constraints: const BoxConstraints(minWidth: 0), + itemBuilder: (BuildContext context) => speedStops + .map( + (item) => PopupMenuItem( + child: Text( + '${item}X', + style: TextStyle( + color: item == rate + ? Theme.of(context).colorScheme.primary + : null, + fontWeight: item == rate + ? FontWeight.bold + : FontWeight.w100, ), - onTap: () async { - showControl(); - useAppStore().updateRate(item); - }, - ), - ) - .toList(), - child: Tooltip( - message: t.playback_speed, - child: TextButton( - onPressed: () => - rateMenuKey.currentState?.showButtonMenu(), - style: ButtonStyle(overlayColor: overlayColor), - child: Text( - '${rate}X', - style: TextStyle( - fontWeight: FontWeight.bold, - color: color, ), + onTap: () async { + showControl(); + useAppStore().updateRate(item); + }, ), - ), - ), - ), - if (MediaQuery.of(context).size.width < 600) - Builder( - builder: (context) => IconButton( - tooltip: '${t.volume}: $volume', - icon: Icon( - isMuted || volume == 0 - ? Icons.volume_off_rounded - : volume < 50 - ? Icons.volume_down_rounded - : Icons.volume_up_rounded, - size: 20, - color: color, - ), - onPressed: () => showControlForHover( - showVolumePopover(context, showControl), - ), + ) + .toList(), + child: Tooltip( + message: t.playback_speed, + child: TextButton( + onPressed: () => + rateMenuKey.currentState?.showButtonMenu(), style: ButtonStyle(overlayColor: overlayColor), - ), - ), - if (MediaQuery.of(context).size.width >= 600) - SizedBox( - width: 160, - child: VolumeControl( - showControl: showControl, - showVolumeText: false, - color: color, - overlayColor: overlayColor, - ), - ), - Expanded( - child: Visibility( - visible: - MediaQuery.of(context).size.width >= 1024 && isDesktop, - child: ControlBarSlider( - showControl: showControl, - color: color, + child: Text( + '${rate}X', + style: TextStyle( + fontWeight: FontWeight.bold, + color: color, + ), + ), ), ), ), - if (MediaQuery.of(context).size.width >= 420) - IconButton( - tooltip: '${t.subtitle_and_audio_track} ( S )', + if (MediaQuery.of(context).size.width < 600) + Builder( + builder: (context) => IconButton( + tooltip: '${t.volume}: $volume', icon: Icon( - Icons.subtitles_rounded, + isMuted || volume == 0 + ? Icons.volume_off_rounded + : volume < 50 + ? Icons.volume_down_rounded + : Icons.volume_up_rounded, size: 20, color: color, ), - onPressed: () async { - showControlForHover( - showPopup( - context: context, - child: Provider.value( - value: context.read(), - child: const SubtitleAndAudioTrack(), - ), - direction: PopupDirection.right, - ), - ); - }, + onPressed: () => showControlForHover( + showVolumePopover(context, showControl), + ), style: ButtonStyle(overlayColor: overlayColor), ), + ), + if (MediaQuery.of(context).size.width >= 600) + SizedBox( + width: 160, + child: VolumeControl( + showControl: showControl, + showVolumeText: false, + color: color, + overlayColor: overlayColor, + ), + ), + Expanded( + child: Visibility( + visible: + MediaQuery.of(context).size.width >= 1024 && isDesktop, + child: ControlBarSlider( + showControl: showControl, + color: color, + ), + ), + ), + if (MediaQuery.of(context).size.width >= 420) IconButton( - tooltip: '${t.play_queue} ( P )', - icon: Transform.translate( - offset: const Offset(0, 1.5), - child: Icon( - Icons.playlist_play_rounded, - size: 28, - color: color, - ), + tooltip: '${t.subtitle_and_audio_track} ( S )', + icon: Icon( + Icons.subtitles_rounded, + size: 20, + color: color, ), onPressed: () async { showControlForHover( showPopup( context: context, - child: const PlayQueue(), + child: Provider.value( + value: context.read(), + child: const SubtitleAndAudioTrack(), + ), direction: PopupDirection.right, ), ); }, style: ButtonStyle(overlayColor: overlayColor), ), - IconButton( - tooltip: '${t.storage} ( F )', - icon: Icon( - Icons.storage_rounded, - size: 18, + IconButton( + tooltip: '${t.play_queue} ( P )', + icon: Transform.translate( + offset: const Offset(0, 1.5), + child: Icon( + Icons.playlist_play_rounded, + size: 28, color: color, ), - onPressed: () => showControlForHover( + ), + onPressed: () async { + showControlForHover( showPopup( context: context, - child: const Storages(), + child: const PlayQueue(), direction: PopupDirection.right, ), + ); + }, + style: ButtonStyle(overlayColor: overlayColor), + ), + IconButton( + tooltip: '${t.storage} ( F )', + icon: Icon( + Icons.storage_rounded, + size: 18, + color: color, + ), + onPressed: () => showControlForHover( + showPopup( + context: context, + child: const Storages(), + direction: PopupDirection.right, ), + ), + style: ButtonStyle(overlayColor: overlayColor), + ), + Visibility( + visible: isDesktop, + child: IconButton( + tooltip: isFullScreen + ? '${t.exit_fullscreen} ( Escape, F11, Enter )' + : '${t.enter_fullscreen} ( F11, Enter )', + icon: Icon( + isFullScreen + ? Icons.close_fullscreen_rounded + : Icons.open_in_full_rounded, + size: 19, + color: color, + ), + onPressed: () async { + showControl(); + usePlayerUiStore().updateFullScreen(!isFullScreen); + }, style: ButtonStyle(overlayColor: overlayColor), ), - Visibility( - visible: isDesktop, - child: IconButton( - tooltip: isFullScreen - ? '${t.exit_fullscreen} ( Escape, F11, Enter )' - : '${t.enter_fullscreen} ( F11, Enter )', - icon: Icon( - isFullScreen - ? Icons.close_fullscreen_rounded - : Icons.open_in_full_rounded, - size: 19, - color: color, + ), + PopupMenuButton( + key: moreMenuKey, + icon: Icon( + Icons.more_vert_rounded, + size: 20, + color: color, + ), + style: ButtonStyle(overlayColor: overlayColor), + clipBehavior: Clip.hardEdge, + constraints: const BoxConstraints(minWidth: 200), + itemBuilder: (BuildContext context) => [ + PopupMenuItem( + child: ListTile( + mouseCursor: SystemMouseCursors.click, + leading: const Icon( + Icons.file_open_rounded, + size: 16.5, + ), + title: Text(t.open_file), + trailing: Text( + 'Ctrl + O', + style: TextStyle( + fontSize: 12, + color: Theme.of(context).dividerColor, + ), + ), ), - onPressed: () async { + onTap: () async { + showControl(); + if (Platform.isAndroid) { + await pickContentFile(); + } else { + await pickLocalFile(); + } showControl(); - usePlayerUiStore().updateFullScreen(!isFullScreen); }, - style: ButtonStyle(overlayColor: overlayColor), ), - ), - PopupMenuButton( - key: moreMenuKey, - icon: Icon( - Icons.more_vert_rounded, - size: 20, - color: color, + PopupMenuItem( + child: ListTile( + mouseCursor: SystemMouseCursors.click, + leading: const Icon( + Icons.file_present_rounded, + size: 16.5, + ), + title: Text(t.open_link), + trailing: Text( + 'Ctrl + L', + style: TextStyle( + fontSize: 12, + color: Theme.of(context).dividerColor, + ), + ), + ), + onTap: () async { + isDesktop + ? await showOpenLinkDialog(context) + : await showOpenLinkBottomSheet(context); + showControl(); + }, ), - style: ButtonStyle(overlayColor: overlayColor), - clipBehavior: Clip.hardEdge, - constraints: const BoxConstraints(minWidth: 200), - itemBuilder: (BuildContext context) => [ + if (MediaQuery.of(context).size.width < 768) PopupMenuItem( child: ListTile( mouseCursor: SystemMouseCursors.click, - leading: const Icon( - Icons.file_open_rounded, - size: 16.5, + leading: Icon( + Icons.shuffle_rounded, + size: 20, + color: !shuffle + ? Theme.of(context).disabledColor + : Theme.of(context).colorScheme.onSurfaceVariant, ), - title: Text(t.open_file), + title: Text('${t.shuffle}: ${shuffle ? t.on : t.off}'), trailing: Text( - 'Ctrl + O', + 'Ctrl + X', style: TextStyle( fontSize: 12, color: Theme.of(context).dividerColor, ), ), ), - onTap: () async { - showControl(); - if (Platform.isAndroid) { - await pickContentFile(); - } else { - await pickLocalFile(); - } + onTap: () { showControl(); + shuffle + ? usePlayQueueStore().sort() + : usePlayQueueStore().shuffle(); + useAppStore().updateShuffle(!shuffle); }, ), + if (MediaQuery.of(context).size.width < 768) PopupMenuItem( child: ListTile( mouseCursor: SystemMouseCursors.click, - leading: const Icon( - Icons.file_present_rounded, - size: 16.5, + leading: Icon( + repeat == Repeat.one + ? Icons.repeat_one_rounded + : Icons.repeat_rounded, + size: 20, + color: repeat == Repeat.none + ? Theme.of(context).disabledColor + : Theme.of(context).colorScheme.onSurfaceVariant, ), - title: Text(t.open_link), + title: Text(repeat == Repeat.one + ? t.repeat_one + : repeat == Repeat.all + ? t.repeat_all + : t.repeat_none), trailing: Text( - 'Ctrl + L', + 'Ctrl + R', style: TextStyle( fontSize: 12, color: Theme.of(context).dividerColor, ), ), ), - onTap: () async { - isDesktop - ? await showOpenLinkDialog(context) - : await showOpenLinkBottomSheet(context); + onTap: () { showControl(); + useAppStore().toggleRepeat(); }, ), - if (MediaQuery.of(context).size.width < 768) - PopupMenuItem( - child: ListTile( - mouseCursor: SystemMouseCursors.click, - leading: Icon( - Icons.shuffle_rounded, - size: 20, - color: !shuffle - ? Theme.of(context).disabledColor - : Theme.of(context) - .colorScheme - .onSurfaceVariant, - ), - title: - Text('${t.shuffle}: ${shuffle ? t.on : t.off}'), - trailing: Text( - 'Ctrl + X', - style: TextStyle( - fontSize: 12, - color: Theme.of(context).dividerColor, - ), - ), - ), - onTap: () { - showControl(); - shuffle - ? usePlayQueueStore().sort() - : usePlayQueueStore().shuffle(); - useAppStore().updateShuffle(!shuffle); - }, - ), - if (MediaQuery.of(context).size.width < 768) - PopupMenuItem( - child: ListTile( - mouseCursor: SystemMouseCursors.click, - leading: Icon( - repeat == Repeat.one - ? Icons.repeat_one_rounded - : Icons.repeat_rounded, - size: 20, - color: repeat == Repeat.none - ? Theme.of(context).disabledColor - : Theme.of(context) - .colorScheme - .onSurfaceVariant, - ), - title: Text(repeat == Repeat.one - ? t.repeat_one - : repeat == Repeat.all - ? t.repeat_all - : t.repeat_none), - trailing: Text( - 'Ctrl + R', - style: TextStyle( - fontSize: 12, - color: Theme.of(context).dividerColor, - ), - ), - ), - onTap: () { - showControl(); - useAppStore().toggleRepeat(); - }, - ), - if (MediaQuery.of(context).size.width < 768) - PopupMenuItem( - child: ListTile( - mouseCursor: SystemMouseCursors.click, - leading: Icon( - fit == BoxFit.contain - ? Icons.fit_screen_rounded - : fit == BoxFit.fill - ? Icons.aspect_ratio_rounded - : fit == BoxFit.cover - ? Icons.crop_landscape_rounded - : Icons.crop_free_rounded, - size: 20, - ), - title: Text( - '${t.video_zoom}: ${fit == BoxFit.contain ? t.fit : fit == BoxFit.fill ? t.stretch : fit == BoxFit.cover ? t.crop : '100%'}'), - trailing: Text( - 'Ctrl + V', - style: TextStyle( - fontSize: 12, - color: Theme.of(context).dividerColor, - ), - ), - ), - onTap: () { - showControl(); - useAppStore().toggleFit(); - }, - ), - if (MediaQuery.of(context).size.width <= 460) - PopupMenuItem( - child: ListTile( - mouseCursor: SystemMouseCursors.click, - leading: const Icon( - Icons.speed_rounded, - size: 20, - ), - title: Text('${t.playback_speed}: ${rate}X'), - ), - onTap: () => - showControlForHover(showRateDialog(context)), - ), - if (MediaQuery.of(context).size.width < 420) - PopupMenuItem( - child: ListTile( - mouseCursor: SystemMouseCursors.click, - leading: const Icon( - Icons.subtitles_rounded, - size: 20, - ), - title: Text(t.subtitle_and_audio_track), - trailing: Text( - 'S', - style: TextStyle( - fontSize: 12, - color: Theme.of(context).dividerColor, - ), - ), - ), - onTap: () => showControlForHover( - showPopup( - context: context, - child: Provider.value( - value: context.read(), - child: const SubtitleAndAudioTrack(), - ), - direction: PopupDirection.right, - ), - ), - ), + if (MediaQuery.of(context).size.width < 768) PopupMenuItem( child: ListTile( mouseCursor: SystemMouseCursors.click, - leading: const Icon( - Icons.history_rounded, + leading: Icon( + fit == BoxFit.contain + ? Icons.fit_screen_rounded + : fit == BoxFit.fill + ? Icons.aspect_ratio_rounded + : fit == BoxFit.cover + ? Icons.crop_landscape_rounded + : Icons.crop_free_rounded, size: 20, ), - title: Text(t.history), + title: Text( + '${t.video_zoom}: ${fit == BoxFit.contain ? t.fit : fit == BoxFit.fill ? t.stretch : fit == BoxFit.cover ? t.crop : '100%'}'), trailing: Text( - 'Ctirl + H', + 'Ctrl + V', style: TextStyle( fontSize: 12, color: Theme.of(context).dividerColor, ), ), ), - onTap: () => showControlForHover( - showPopup( - context: context, - child: const History(), - direction: PopupDirection.right, + onTap: () { + showControl(); + useAppStore().toggleFit(); + }, + ), + if (MediaQuery.of(context).size.width <= 460) + PopupMenuItem( + child: ListTile( + mouseCursor: SystemMouseCursors.click, + leading: const Icon( + Icons.speed_rounded, + size: 20, ), + title: Text('${t.playback_speed}: ${rate}X'), ), + onTap: () => showControlForHover(showRateDialog(context)), ), + if (MediaQuery.of(context).size.width < 420) PopupMenuItem( child: ListTile( mouseCursor: SystemMouseCursors.click, leading: const Icon( - Icons.settings_rounded, + Icons.subtitles_rounded, size: 20, ), - title: Text(t.settings), + title: Text(t.subtitle_and_audio_track), trailing: Text( - 'Ctirl + P', + 'S', style: TextStyle( fontSize: 12, color: Theme.of(context).dividerColor, @@ -656,18 +586,94 @@ class ControlBar extends HookWidget { onTap: () => showControlForHover( showPopup( context: context, - child: const Settings(), + child: Provider.value( + value: context.read(), + child: const SubtitleAndAudioTrack(), + ), direction: PopupDirection.right, ), ), ), - ], - ), - const SizedBox(width: 4), - ], - ), - ], - ), + PopupMenuItem( + child: ListTile( + mouseCursor: SystemMouseCursors.click, + leading: const Icon( + Icons.history_rounded, + size: 20, + ), + title: Text(t.history), + trailing: Text( + 'Ctirl + H', + style: TextStyle( + fontSize: 12, + color: Theme.of(context).dividerColor, + ), + ), + ), + onTap: () => showControlForHover( + showPopup( + context: context, + child: const History(), + direction: PopupDirection.right, + ), + ), + ), + PopupMenuItem( + child: ListTile( + mouseCursor: SystemMouseCursors.click, + leading: const Icon( + Icons.settings_rounded, + size: 20, + ), + title: Text(t.settings), + trailing: Text( + 'Ctirl + P', + style: TextStyle( + fontSize: 12, + color: Theme.of(context).dividerColor, + ), + ), + ), + onTap: () => showControlForHover( + showPopup( + context: context, + child: const Settings(), + direction: PopupDirection.right, + ), + ), + ), + PopupMenuItem( + child: ListTile( + mouseCursor: SystemMouseCursors.click, + leading: const Icon( + Icons.exit_to_app_rounded, + size: 20, + ), + title: Text(t.exit), + trailing: Text( + 'Alt + X', + style: TextStyle( + fontSize: 12, + color: Theme.of(context).dividerColor, + ), + ), + ), + onTap: () async { + await player.saveProgress(); + if (isDesktop) { + windowManager.close(); + } else { + SystemNavigator.pop(); + exit(0); + } + }, + ), + ], + ), + const SizedBox(width: 4), + ], + ), + ], ), ); } diff --git a/lib/pages/player/control_bar/control_bar_slider.dart b/lib/pages/player/control_bar/control_bar_slider.dart index e29e0fe..d068f20 100644 --- a/lib/pages/player/control_bar/control_bar_slider.dart +++ b/lib/pages/player/control_bar/control_bar_slider.dart @@ -33,7 +33,7 @@ class ControlBarSlider extends HookWidget { (player) => ( position: player.position, duration: player.duration, - buffer: player.buffer + buffer: player.buffer, ), ); @@ -41,6 +41,12 @@ class ControlBarSlider extends HookWidget { final pause = context.read().pause; final seek = context.read().seek; + final double max = progress.duration.inMilliseconds.toDouble(); + final double positionValue = + progress.position.inMilliseconds.toDouble().clamp(0.0, max); + final double bufferValue = + progress.buffer.inMilliseconds.toDouble().clamp(0.0, max); + return ExcludeFocus( child: Container( padding: const EdgeInsets.fromLTRB(12, 0, 12, 0), @@ -50,88 +56,67 @@ class ControlBarSlider extends HookWidget { visible: !disabled, child: Text( formatDurationToMinutes(progress.position), - style: TextStyle( - color: color, - height: 2, - ), + style: TextStyle(color: color, height: 2), ), ), Expanded( - child: Stack( - children: [ - SliderTheme( - data: SliderTheme.of(context).copyWith( - disabledActiveTrackColor: color?.withAlpha(111), - thumbShape: const RoundSliderThumbShape( - disabledThumbRadius: 0, - elevation: 0, - pressedElevation: 0, - ), - overlayShape: const RoundSliderOverlayShape( - overlayRadius: 12, - ), - trackShape: const RoundedActiveTrackShape(), - trackHeight: 3, - ), - child: Slider( - value: progress.buffer.inMilliseconds.toDouble() > - progress.duration.inMilliseconds.toDouble() - ? 0 - : progress.buffer.inMilliseconds.toDouble(), - min: 0, - max: progress.duration.inMilliseconds.toDouble(), - onChanged: null, - ), + child: SliderTheme( + data: SliderTheme.of(context).copyWith( + activeTrackColor: color?.withAlpha(222) ?? + Theme.of(context).colorScheme.primary, + inactiveTrackColor: color?.withAlpha(70) ?? + Theme.of(context) + .colorScheme + .onSurface + .withValues(alpha: 0.25), + secondaryActiveTrackColor: color?.withAlpha(120) ?? + Theme.of(context) + .colorScheme + .onSurface + .withValues(alpha: 0.4), + thumbColor: color ?? Theme.of(context).colorScheme.primary, + thumbShape: RoundSliderThumbShape( + enabledThumbRadius: disabled ? 0 : 6, ), - SliderTheme( - data: SliderTheme.of(context).copyWith( - thumbColor: color, - activeTrackColor: color?.withAlpha(222), - inactiveTrackColor: color?.withAlpha(99), - thumbShape: RoundSliderThumbShape( - enabledThumbRadius: disabled ? 0 : 6, - ), - overlayShape: const RoundSliderOverlayShape( - overlayRadius: 12, - ), - trackShape: - disabled ? const RoundedActiveTrackShape() : null, - trackHeight: 4, - ), - child: Slider( - value: progress.position.inMilliseconds.toDouble() > - progress.duration.inMilliseconds.toDouble() - ? 0 - : progress.position.inMilliseconds.toDouble(), - min: 0, - max: progress.duration.inMilliseconds.toDouble(), - onChangeStart: (value) { - usePlayerUiStore().updateIsSeeking(true); - pause(); - }, - onChanged: (value) { - showControl(); - seek(Duration(milliseconds: value.toInt())); - }, - onChangeEnd: (value) async { - if (autoPlay) { - play(); - } - usePlayerUiStore().updateIsSeeking(false); - }, - ), + overlayShape: const RoundSliderOverlayShape( + overlayRadius: 12, ), - ], + trackHeight: 4, + trackShape: const _CustomTrackShape(), + ), + child: Slider( + value: positionValue, + secondaryTrackValue: bufferValue, + min: 0, + max: max > 0 ? max : 1.0, + onChanged: disabled + ? null + : (value) { + showControl(); + seek(Duration(milliseconds: value.toInt())); + }, + onChangeStart: disabled + ? null + : (value) { + usePlayerUiStore().updateIsSeeking(true); + pause(); + }, + onChangeEnd: disabled + ? null + : (value) async { + if (autoPlay) { + play(); + } + usePlayerUiStore().updateIsSeeking(false); + }, + ), ), ), Visibility( visible: !disabled, child: Text( formatDurationToMinutes(progress.duration), - style: TextStyle( - color: color, - height: 2, - ), + style: TextStyle(color: color, height: 2), ), ), ], @@ -141,9 +126,8 @@ class ControlBarSlider extends HookWidget { } } -class RoundedActiveTrackShape extends SliderTrackShape - with BaseSliderTrackShape { - const RoundedActiveTrackShape(); +class _CustomTrackShape extends RoundedRectSliderTrackShape { + const _CustomTrackShape(); @override void paint( @@ -159,21 +143,10 @@ class RoundedActiveTrackShape extends SliderTrackShape bool isEnabled = false, double additionalActiveTrackHeight = 2, }) { - assert(sliderTheme.disabledActiveTrackColor != null); - assert(sliderTheme.disabledInactiveTrackColor != null); - assert(sliderTheme.activeTrackColor != null); - assert(sliderTheme.inactiveTrackColor != null); - assert(sliderTheme.thumbShape != null); if (sliderTheme.trackHeight == null || sliderTheme.trackHeight! <= 0) { return; } - final ColorTween activeTrackColorTween = ColorTween( - begin: sliderTheme.disabledActiveTrackColor, - end: sliderTheme.activeTrackColor); - final Paint activePaint = Paint() - ..color = activeTrackColorTween.evaluate(enableAnimation)!; - final Rect trackRect = getPreferredRect( parentBox: parentBox, offset: offset, @@ -181,20 +154,40 @@ class RoundedActiveTrackShape extends SliderTrackShape isEnabled: isEnabled, isDiscrete: isDiscrete, ); - final Radius activeTrackRadius = - Radius.circular((trackRect.height + additionalActiveTrackHeight) / 2); + final Radius trackRadius = Radius.circular(trackRect.height / 2); + + final Paint inactivePaint = Paint() + ..color = sliderTheme.inactiveTrackColor!; context.canvas.drawRRect( - RRect.fromLTRBAndCorners( + RRect.fromRectAndRadius(trackRect, trackRadius), + inactivePaint, + ); + + if (secondaryOffset != null) { + final Paint secondaryPaint = Paint() + ..color = sliderTheme.secondaryActiveTrackColor!; + final Rect secondaryRect = Rect.fromLTRB( trackRect.left, - trackRect.top - (additionalActiveTrackHeight / 2), - thumbCenter.dx, - trackRect.bottom + (additionalActiveTrackHeight / 2), - topLeft: activeTrackRadius, - bottomLeft: activeTrackRadius, - topRight: activeTrackRadius, - bottomRight: activeTrackRadius, - ), + trackRect.top, + secondaryOffset.dx, + trackRect.bottom, + ); + context.canvas.drawRRect( + RRect.fromRectAndRadius(secondaryRect, trackRadius), + secondaryPaint, + ); + } + + final Paint activePaint = Paint()..color = sliderTheme.activeTrackColor!; + final Rect activeRect = Rect.fromLTRB( + trackRect.left, + trackRect.top, + thumbCenter.dx, + trackRect.bottom, + ); + context.canvas.drawRRect( + RRect.fromRectAndRadius(activeRect, trackRadius), activePaint, ); } diff --git a/lib/pages/player/controls_overlay.dart b/lib/pages/player/controls_overlay.dart index b99a3f4..fc1cd33 100644 --- a/lib/pages/player/controls_overlay.dart +++ b/lib/pages/player/controls_overlay.dart @@ -1,5 +1,6 @@ import 'dart:async'; import 'package:flutter_zustand/flutter_zustand.dart'; +import 'package:iris/globals.dart' show speedStops, speedSelectorItemWidth; import 'package:iris/hooks/use_gesture.dart'; import 'package:iris/models/file.dart'; import 'package:iris/models/player.dart'; @@ -7,10 +8,11 @@ import 'package:flutter/material.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:iris/pages/player/control_bar/control_bar.dart'; import 'package:iris/pages/player/control_bar/control_bar_slider.dart'; +import 'package:iris/pages/player/speed_selector.dart'; import 'package:iris/store/use_app_store.dart'; import 'package:iris/store/use_player_ui_store.dart'; import 'package:iris/utils/format_duration_to_minutes.dart'; -import 'package:iris/widgets/drag_aria.dart'; +import 'package:iris/widgets/drag_area.dart'; import 'package:iris/widgets/title_bar.dart'; import 'package:provider/provider.dart'; @@ -44,17 +46,54 @@ class ControlsOverlay extends HookWidget { final saveProgress = context.read().saveProgress; - final rate = useAppStore().select(context, (state) => state.rate); - final isShowControl = usePlayerUiStore().select(context, (state) => state.isShowControl); final isShowProgress = usePlayerUiStore().select(context, (state) => state.isShowProgress); + final isSpeedSelectorVisible = useState(false); + final selectedSpeed = useState(1.0); + final speedSelectorPosition = useState(Offset.zero); + final visualOffset = useState(0.0); + final initialSpeed = useRef(1.0); + + void showSpeedSelectorCallback(Offset position) { + isSpeedSelectorVisible.value = true; + speedSelectorPosition.value = position; + visualOffset.value = 0.0; + initialSpeed.value = useAppStore().state.rate; + } + + void hideSpeedSelectorCallback(double finalSpeed) { + final initialIndex = speedStops.indexOf(initialSpeed.value); + final finalIndex = speedStops.indexOf(finalSpeed); + + if (initialIndex == -1 || finalIndex == -1) return; + + visualOffset.value = (initialIndex - finalIndex) * speedSelectorItemWidth; + + Future.delayed( + const Duration(milliseconds: 200), + () { + if (context.mounted) { + isSpeedSelectorVisible.value = false; + } + }, + ); + } + + void updateSelectedSpeedCallback(double speed, double newVisualOffset) { + selectedSpeed.value = speed; + visualOffset.value = newVisualOffset; + } + final gesture = useGesture( showControl: showControl, hideControl: hideControl, showProgress: showProgress, + showSpeedSelector: showSpeedSelectorCallback, + hideSpeedSelector: hideSpeedSelectorCallback, + updateSelectedSpeed: updateSelectedSpeedCallback, ); final contentColor = useMemoized( @@ -103,43 +142,16 @@ class ControlsOverlay extends HookWidget { child: Stack( children: [ // 播放速度 - if (rate != 1.0 && gesture.isLongPress) + if (isSpeedSelectorVisible.value) Positioned( left: 0, top: 0, right: 0, bottom: 0, - child: Center( - child: Container( - padding: const EdgeInsets.fromLTRB(12, 12, 18, 12), - decoration: BoxDecoration( - color: Colors.black54, - borderRadius: BorderRadius.circular(8), - ), - child: Row( - mainAxisSize: MainAxisSize.min, - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Transform.translate( - offset: const Offset(0, 1.5), - child: Icon( - Icons.speed_rounded, - color: Colors.white, - size: 24, - ), - ), - const SizedBox(width: 10), - Text( - rate.toString(), - style: const TextStyle( - color: Colors.white, - fontSize: 20, - height: 1, - ), - ), - ], - ), - ), + child: SpeedSelector( + selectedSpeed: selectedSpeed.value, + visualOffset: visualOffset.value, + initialSpeed: initialSpeed.value, ), ), // 屏幕亮度 @@ -310,19 +322,17 @@ class ControlsOverlay extends HookWidget { : -72, left: 0, right: 0, - child: SafeArea( - child: MouseRegion( - onHover: gesture.onHover, - child: GestureDetector( - onTap: () => showControl(), - child: DragAria( - child: TitleBar( - title: title, - actions: [const SizedBox(width: 8)], - color: contentColor, - overlayColor: overlayColor, - saveProgress: () => saveProgress(), - ), + child: MouseRegion( + onHover: gesture.onHover, + child: GestureDetector( + onTap: () => showControl(), + child: DragArea( + child: TitleBar( + title: title, + actions: [const SizedBox(width: 8)], + color: contentColor, + overlayColor: overlayColor, + saveProgress: () => saveProgress(), ), ), ), diff --git a/lib/pages/player/player.dart b/lib/pages/player/player.dart index 5c80ce1..7a9b05b 100644 --- a/lib/pages/player/player.dart +++ b/lib/pages/player/player.dart @@ -21,7 +21,6 @@ import 'package:iris/utils/logger.dart'; import 'package:iris/utils/platform.dart'; import 'package:iris/store/use_app_store.dart'; import 'package:iris/store/use_play_queue_store.dart'; -import 'package:iris/utils/get_localizations.dart'; import 'package:provider/provider.dart'; import 'package:window_manager/window_manager.dart'; @@ -30,8 +29,6 @@ class Player extends HookWidget { @override Widget build(BuildContext context) { - final t = getLocalizations(context); - final isPlaying = context.select((player) => player.isPlaying); final width = context.select((player) => player.width); @@ -44,30 +41,8 @@ class Player extends HookWidget { final cover = useCover(); - final playerUiStore = usePlayerUiStore(); - - final isHovering = - playerUiStore.select(context, (state) => state.isHovering); - final isShowControl = - playerUiStore.select(context, (state) => state.isShowControl); - final isShowProgress = - playerUiStore.select(context, (state) => state.isShowProgress); - - final updateIsHovering = useCallback((bool value) { - playerUiStore.updateIsHovering(value); - }, [playerUiStore.updateIsHovering]); - - final updateIsShowControl = useCallback((bool value) { - playerUiStore.updateIsShowControl(value); - }, [playerUiStore.updateIsShowControl]); - - final updateIsShowProgress = useCallback((bool value) { - playerUiStore.updateIsShowProgress(value); - }, [playerUiStore.updateIsShowProgress]); - final controlHideTimer = useRef(null); final progressHideTimer = useRef(null); - final systemUiHideTimer = useRef(null); final fit = useAppStore().select(context, (state) => state.fit); @@ -101,93 +76,67 @@ class Player extends HookWidget { return; }, []); - final canPop = useState(false); - - useEffect(() { - final timer = Future.delayed(Duration(seconds: 4), () { - canPop.value = false; - }); - return () { - timer.ignore(); - }; - }, [canPop.value]); - - final startControlHideTimer = useCallback(() { + void startControlHideTimer() { controlHideTimer.value = Timer( const Duration(seconds: 5), () { - if (isShowControl && !isHovering) { - updateIsShowControl(false); + if (usePlayerUiStore().state.isShowControl && + !usePlayerUiStore().state.isHovering) { + usePlayerUiStore().updateIsShowControl(false); } }, ); - }, [isShowControl, isHovering, updateIsShowControl]); + } - final startProgressHideTimer = useCallback(() { + void startProgressHideTimer() { progressHideTimer.value = Timer( const Duration(seconds: 5), () { - if (isShowProgress) { - updateIsShowProgress(false); - } - }, - ); - }, [isShowProgress, updateIsShowProgress]); - - final startSystemUiHideTimer = useCallback(() { - systemUiHideTimer.value = Timer( - const Duration(seconds: 3), - () { - if (!isShowControl && currentPlay?.file.type == ContentType.video) { - SystemChrome.setEnabledSystemUIMode(SystemUiMode.immersive); + if (usePlayerUiStore().state.isShowProgress) { + usePlayerUiStore().updateIsShowProgress(false); } }, ); - }, [isShowControl, currentPlay?.file.type]); + } - final resetControlHideTimer = useCallback(() { + void resetControlHideTimer() { controlHideTimer.value?.cancel(); startControlHideTimer(); - }, [startControlHideTimer]); + } - final resetBottomProgressTimer = useCallback(() { + void resetBottomProgressTimer() { progressHideTimer.value?.cancel(); startProgressHideTimer(); - }, [startProgressHideTimer]); - - final resetSystemUiHideTimer = useCallback(() { - systemUiHideTimer.value?.cancel(); - startSystemUiHideTimer(); - }, [startSystemUiHideTimer]); + } - final showControl = useCallback(() { - updateIsShowControl(true); - updateIsHovering(false); + void showControl() { + usePlayerUiStore().updateIsShowControl(true); + usePlayerUiStore().updateIsHovering(false); resetControlHideTimer(); - }, [updateIsShowControl, updateIsHovering, resetControlHideTimer]); + } - final hideControl = useCallback(() { - updateIsShowControl(false); - updateIsHovering(false); + void hideControl() { + usePlayerUiStore().updateIsShowControl(false); + usePlayerUiStore().updateIsHovering(false); controlHideTimer.value?.cancel(); - }, [updateIsShowControl, updateIsHovering]); + } - final showControlForHover = useCallback((Future callback) async { + Future showControlForHover(Future callback) async { try { saveProgress(); showControl(); - updateIsHovering(true); + usePlayerUiStore().updateIsHovering(true); await callback; showControl(); } catch (e) { logger(e.toString()); } - }, [showControl, updateIsHovering]); + } - final showProgress = useCallback(() { - updateIsShowProgress(true); + void showProgress() { + usePlayerUiStore().updateIsShowProgress(true); resetBottomProgressTimer(); - }, [updateIsShowProgress, resetBottomProgressTimer]); + } final onKeyEvent = useKeyboard( showControl: showControl, @@ -211,25 +160,6 @@ class Player extends HookWidget { return; }, [title, isPlaying]); - useEffect(() { - if (isShowControl || currentPlay?.file.type == ContentType.video) { - SystemChrome.setEnabledSystemUIMode(SystemUiMode.edgeToEdge); - systemUiHideTimer.value?.cancel(); - } else { - SystemChrome.setEnabledSystemUIMode(SystemUiMode.immersive); - } - return; - }, [isShowControl, currentPlay?.file.type]); - - useEffect(() { - SystemChrome.setSystemUIChangeCallback((value) async { - if (value) { - resetSystemUiHideTimer(); - } - }); - return null; - }, []); - final scaleFactor = useMemoized( () => View.of(context).physicalSize.width / @@ -286,14 +216,10 @@ class Player extends HookWidget { onPopInvokedWithResult: (bool didPop, Object? result) async { if (!didPop) { await saveProgress(); - if (!canPop.value) { - canPop.value = true; - if (context.mounted) { - ScaffoldMessenger.of(context).showSnackBar( - SnackBar(content: Text(t.exit_app_back_again)), - ); - } + if (isDesktop) { + windowManager.close(); } else { + SystemNavigator.pop(); exit(0); } } diff --git a/lib/pages/player/speed_selector.dart b/lib/pages/player/speed_selector.dart new file mode 100644 index 0000000..d83e90f --- /dev/null +++ b/lib/pages/player/speed_selector.dart @@ -0,0 +1,104 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_hooks/flutter_hooks.dart'; +import 'package:iris/globals.dart' show speedStops, speedSelectorItemWidth; + +class SpeedSelector extends HookWidget { + const SpeedSelector({ + super.key, + required this.selectedSpeed, + required this.visualOffset, + required this.initialSpeed, + }); + + final double selectedSpeed; + final double visualOffset; + final double initialSpeed; + + @override + Widget build(BuildContext context) { + final screenSize = MediaQuery.of(context).size; + + const double itemWidth = speedSelectorItemWidth; + const double horizontalPadding = 12.0; + + final initialIndex = speedStops.indexOf(initialSpeed); + final double initialCenterOffset = (screenSize.width / 2) - + (initialIndex * itemWidth) - + (itemWidth / 2) - + horizontalPadding; + + final double targetOffset = initialCenterOffset + visualOffset; + + final double topPosition = screenSize.height / 2 - 30; + + return IgnorePointer( + child: Stack( + children: [ + AnimatedPositioned( + duration: const Duration(milliseconds: 150), + curve: Curves.easeOutCubic, + left: targetOffset, + top: topPosition, + child: Container( + height: 60, + decoration: BoxDecoration( + color: Colors.black54, + borderRadius: BorderRadius.circular(30), + boxShadow: [ + BoxShadow( + color: Colors.black26, + blurRadius: 10, + spreadRadius: 2, + ) + ], + ), + child: Row( + children: [ + const SizedBox(width: horizontalPadding), + ...speedStops.map((speed) { + final bool isSelected = speed == selectedSpeed; + return SizedBox( + width: itemWidth, + child: Center( + child: AnimatedDefaultTextStyle( + duration: const Duration(milliseconds: 150), + style: TextStyle( + fontSize: isSelected ? 20 : 16, + fontWeight: isSelected + ? FontWeight.bold + : FontWeight.normal, + color: isSelected ? Colors.white : Colors.white70, + height: 1.0, + ), + child: Text('${speed}x'), + ), + ), + ); + }), + const SizedBox(width: horizontalPadding), + ], + ), + ), + ), + Positioned( + left: screenSize.width / 2 - 1.5, + top: topPosition - 10, + child: Container( + width: 3, + height: 80, + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(2), + boxShadow: [ + BoxShadow( + color: Colors.white.withAlpha(100), + blurRadius: 5, + ) + ]), + ), + ), + ], + ), + ); + } +} diff --git a/lib/widgets/dialogs/show_language_dialog.dart b/lib/widgets/dialogs/show_language_dialog.dart index ee6c484..38c374f 100644 --- a/lib/widgets/dialogs/show_language_dialog.dart +++ b/lib/widgets/dialogs/show_language_dialog.dart @@ -17,45 +17,47 @@ class LanguageDialog extends HookWidget { @override Widget build(BuildContext context) { final t = getLocalizations(context); - String language = useAppStore().select(context, (state) => state.language); + final String language = + useAppStore().select(context, (state) => state.language); - void updateLanguage(String language) { - useAppStore().updateLanguage(language); + void updateLanguage(String? newLanguage) { + if (newLanguage == null) return; + useAppStore().updateLanguage(newLanguage); Navigator.pop(context); } + final Map languageOptions = { + 'system': t.system, + ...languages, + }; + return AlertDialog( title: Text(t.select_language), content: SingleChildScrollView( + child: RadioGroup( + groupValue: language, + onChanged: updateLanguage, child: Column( - children: [ - ListTile( - title: Text(t.system), - contentPadding: const EdgeInsets.only(left: 8), - leading: Radio( - value: 'system', - groupValue: language, - onChanged: (_) => updateLanguage('system'), - ), - onTap: () => updateLanguage('system'), - ), - ...languages.entries.map( - (e) => ListTile( - title: Text(e.value), - contentPadding: const EdgeInsets.only(left: 8), - leading: Radio( - value: e.key, - groupValue: language, - onChanged: (_) => updateLanguage(e.key), - ), - onTap: () => updateLanguage(e.key), - ), + mainAxisSize: MainAxisSize.min, + children: languageOptions.entries.map((entry) { + final String langCode = entry.key; + final String langName = entry.value; + + return ListTile( + title: Text(langName), + leading: Radio( + value: langCode, + ), + onTap: () => updateLanguage(langCode), + contentPadding: const EdgeInsets.only(left: 8), + ); + }).toList(), ), - ], - )), + ), + ), actions: [ TextButton( - onPressed: () => Navigator.pop(context, 'Cancel'), + onPressed: () => Navigator.pop(context), child: Text(t.cancel), ), ], diff --git a/lib/widgets/dialogs/show_orientation_dialog.dart b/lib/widgets/dialogs/show_orientation_dialog.dart index 7af901d..d2315d0 100644 --- a/lib/widgets/dialogs/show_orientation_dialog.dart +++ b/lib/widgets/dialogs/show_orientation_dialog.dart @@ -20,8 +20,9 @@ class OrientationDialog extends HookWidget { final orientation = useAppStore().select(context, (state) => state.orientation); - void updateOrientation(ScreenOrientation orientation) { - useAppStore().updateOrientation(orientation); + void updateOrientation(ScreenOrientation? newOrientation) { + if (newOrientation == null) return; + useAppStore().updateOrientation(newOrientation); Navigator.pop(context); } @@ -34,24 +35,26 @@ class OrientationDialog extends HookWidget { return AlertDialog( title: Text(t.screen_orientation), content: SingleChildScrollView( + child: RadioGroup( + groupValue: orientation, + onChanged: updateOrientation, child: Column( - children: ScreenOrientation.values - .map( - (e) => ListTile( + mainAxisSize: MainAxisSize.min, + children: ScreenOrientation.values.map((e) { + return ListTile( title: Text(orientationMap[e] ?? e.name), - leading: Radio( + leading: Radio( value: e, - groupValue: orientation, - onChanged: (_) => updateOrientation(e), ), onTap: () => updateOrientation(e), - ), - ) - .toList(), - )), + ); + }).toList(), + ), + ), + ), actions: [ TextButton( - onPressed: () => Navigator.pop(context, 'Cancel'), + onPressed: () => Navigator.pop(context), child: Text(t.cancel), ), ], diff --git a/lib/widgets/dialogs/show_rate_dialog.dart b/lib/widgets/dialogs/show_rate_dialog.dart index aae7e7f..2670a81 100644 --- a/lib/widgets/dialogs/show_rate_dialog.dart +++ b/lib/widgets/dialogs/show_rate_dialog.dart @@ -1,6 +1,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:flutter_zustand/flutter_zustand.dart'; +import 'package:iris/globals.dart' show speedStops; import 'package:iris/store/use_app_store.dart'; import 'package:iris/utils/get_localizations.dart'; @@ -18,45 +19,35 @@ class RateDialog extends HookWidget { final t = getLocalizations(context); final rate = useAppStore().select(context, (state) => state.rate); - void updateRate(double rate) { - useAppStore().updateRate(rate); + void updateRate(double? newRate) { + if (newRate == null) return; + useAppStore().updateRate(newRate); Navigator.pop(context); } return AlertDialog( title: Text(t.playback_speed), content: SingleChildScrollView( - child: Column( - children: [ - 0.25, - 0.5, - 0.75, - 1.0, - 1.25, - 1.5, - 1.75, - 2.0, - 3.0, - 4.0, - 5.0, - ] - .map( - (item) => ListTile( - title: Text('${item}X'), - leading: Radio( - value: item, - groupValue: rate, - onChanged: (_) => updateRate(item), - ), - onTap: () => updateRate(item), + child: RadioGroup( + groupValue: rate, + onChanged: updateRate, + child: Column( + mainAxisSize: MainAxisSize.min, + children: speedStops.map((item) { + return ListTile( + title: Text('${item}X'), + leading: Radio( + value: item, ), - ) - .toList(), + onTap: () => updateRate(item), + ); + }).toList(), + ), ), ), actions: [ TextButton( - onPressed: () => Navigator.pop(context, 'Cancel'), + onPressed: () => Navigator.pop(context), child: Text(t.cancel), ), ], diff --git a/lib/widgets/dialogs/show_theme_mode_dialog.dart b/lib/widgets/dialogs/show_theme_mode_dialog.dart index 9538778..510cb8d 100644 --- a/lib/widgets/dialogs/show_theme_mode_dialog.dart +++ b/lib/widgets/dialogs/show_theme_mode_dialog.dart @@ -18,51 +18,45 @@ class ThemeModeDialog extends HookWidget { final t = getLocalizations(context); final themeMode = useAppStore().select(context, (state) => state.themeMode); - void updateThemeMode(ThemeMode themeMode) { - useAppStore().updateThemeMode(themeMode); + void updateThemeMode(ThemeMode? newThemeMode) { + if (newThemeMode == null) return; + useAppStore().updateThemeMode(newThemeMode); Navigator.pop(context); } + final Map themeOptions = { + t.system: ThemeMode.system, + t.light: ThemeMode.light, + t.dark: ThemeMode.dark, + }; + return AlertDialog( title: Text(t.theme_mode), content: SingleChildScrollView( + child: RadioGroup( + groupValue: themeMode, + onChanged: updateThemeMode, child: Column( - children: [ - ListTile( - title: Text(t.system), - contentPadding: const EdgeInsets.only(left: 8), - leading: Radio( - value: ThemeMode.system, - groupValue: themeMode, - onChanged: (_) => updateThemeMode(ThemeMode.system), - ), - onTap: () => updateThemeMode(ThemeMode.system), - ), - ListTile( - title: Text(t.light), - contentPadding: const EdgeInsets.only(left: 8), - leading: Radio( - value: ThemeMode.light, - groupValue: themeMode, - onChanged: (_) => updateThemeMode(ThemeMode.light), - ), - onTap: () => updateThemeMode(ThemeMode.light), - ), - ListTile( - title: Text(t.dark), - contentPadding: const EdgeInsets.only(left: 8), - leading: Radio( - value: ThemeMode.dark, - groupValue: themeMode, - onChanged: (_) => updateThemeMode(ThemeMode.dark), - ), - onTap: () => updateThemeMode(ThemeMode.dark), + mainAxisSize: MainAxisSize.min, + children: themeOptions.entries.map((entry) { + final String label = entry.key; + final ThemeMode value = entry.value; + + return ListTile( + title: Text(label), + leading: Radio( + value: value, + ), + onTap: () => updateThemeMode(value), + contentPadding: const EdgeInsets.only(left: 8), + ); + }).toList(), ), - ], - )), + ), + ), actions: [ TextButton( - onPressed: () => Navigator.pop(context, 'Cancel'), + onPressed: () => Navigator.pop(context), child: Text(t.cancel), ), ], diff --git a/lib/widgets/drag_aria.dart b/lib/widgets/drag_area.dart similarity index 94% rename from lib/widgets/drag_aria.dart rename to lib/widgets/drag_area.dart index cadbed2..31b95dd 100644 --- a/lib/widgets/drag_aria.dart +++ b/lib/widgets/drag_area.dart @@ -4,8 +4,8 @@ import 'package:iris/store/use_player_ui_store.dart'; import 'package:iris/utils/platform.dart'; import 'package:window_manager/window_manager.dart'; -class DragAria extends StatelessWidget { - const DragAria({ +class DragArea extends StatelessWidget { + const DragArea({ super.key, required this.child, }); diff --git a/lib/widgets/popup.dart b/lib/widgets/popup.dart index 95e46c2..b6315d6 100644 --- a/lib/widgets/popup.dart +++ b/lib/widgets/popup.dart @@ -52,65 +52,61 @@ class Popup extends PopupRoute { ? 2 : 1; - return SafeArea( - child: Stack( - children: [ - Positioned.fill( - child: GestureDetector( - onPanStart: (details) { - if (Platform.isWindows || - Platform.isLinux || - Platform.isMacOS) { - windowManager.startDragging(); - } - }, - onTap: () => Navigator.of(context).pop(), - ), + return Stack( + children: [ + Positioned.fill( + child: GestureDetector( + onPanStart: (details) { + if (Platform.isWindows || Platform.isLinux || Platform.isMacOS) { + windowManager.startDragging(); + } + }, + onTap: () => Navigator.of(context).pop(), ), - Align( - alignment: direction == PopupDirection.left - ? Alignment.bottomLeft - : Alignment.bottomRight, - child: Padding( - padding: EdgeInsets.only( - top: 0, - bottom: 8, - left: direction == PopupDirection.left ? 8 : 0, - right: direction == PopupDirection.right ? 8 : 0, - ), - child: AnimatedBuilder( - animation: animation, - builder: (context, child) { - return SlideTransition( - position: Tween( - begin: direction == PopupDirection.left - ? const Offset(-1.0, 0.0) - : const Offset(1.0, 0.0), - end: Offset.zero, - ).animate(CurvedAnimation( - parent: animation, - curve: Curves.easeInOutCubicEmphasized, - )), - child: child, - ); - }, - child: ClipRRect( - borderRadius: BorderRadius.circular(16), - child: BackdropFilter( - filter: ImageFilter.blur(sigmaX: 10, sigmaY: 10), - child: Material( - color: Theme.of(context) - .colorScheme - .surface - .withValues(alpha: 0.75), - child: UnconstrainedBox( - child: LimitedBox( - maxWidth: screenWidth / size - 16, - maxHeight: screenHeight - 18 - 48, - child: Column( - mainAxisSize: MainAxisSize.min, - children: [Expanded(child: child)], - ), + ), + Align( + alignment: direction == PopupDirection.left + ? Alignment.bottomLeft + : Alignment.bottomRight, + child: Padding( + padding: EdgeInsets.only( + top: 0, + bottom: 8, + left: direction == PopupDirection.left ? 8 : 0, + right: direction == PopupDirection.right ? 8 : 0, + ), + child: AnimatedBuilder( + animation: animation, + builder: (context, child) { + return SlideTransition( + position: Tween( + begin: direction == PopupDirection.left + ? const Offset(-1.0, 0.0) + : const Offset(1.0, 0.0), + end: Offset.zero, + ).animate(CurvedAnimation( + parent: animation, + curve: Curves.easeInOutCubicEmphasized, + )), + child: child, + ); + }, + child: ClipRRect( + borderRadius: BorderRadius.circular(16), + child: BackdropFilter( + filter: ImageFilter.blur(sigmaX: 10, sigmaY: 10), + child: Material( + color: Theme.of(context) + .colorScheme + .surface + .withValues(alpha: 0.75), + child: UnconstrainedBox( + child: LimitedBox( + maxWidth: screenWidth / size - 16, + maxHeight: screenHeight - 16, + child: Column( + mainAxisSize: MainAxisSize.min, + children: [Expanded(child: child)], ), ), ), @@ -119,8 +115,8 @@ class Popup extends PopupRoute { ), ), ), - ], - ), + ), + ], ); } } diff --git a/lib/widgets/title_bar.dart b/lib/widgets/title_bar.dart index 9027f38..4cee2af 100644 --- a/lib/widgets/title_bar.dart +++ b/lib/widgets/title_bar.dart @@ -47,153 +47,150 @@ class TitleBar extends HookWidget { ), ), child: ExcludeFocus( - child: SafeArea( - child: Row( - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - Image.asset( - 'assets/images/logo_transparent.png', - width: 32, - height: 32, - ), - const SizedBox(width: 8), - Expanded( - child: Text( - title!.isEmpty ? INFO.title : title!, - maxLines: 1, - textAlign: TextAlign.start, - style: TextStyle( - fontSize: 16, - overflow: TextOverflow.ellipsis, - color: color, - ), + child: Row( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Image.asset( + 'assets/images/logo_transparent.png', + width: 32, + height: 32, + ), + const SizedBox(width: 8), + Expanded( + child: Text( + title!.isEmpty ? INFO.title : title!, + maxLines: 1, + textAlign: TextAlign.start, + style: TextStyle( + fontSize: 16, + overflow: TextOverflow.ellipsis, + color: color, ), ), - Row( - children: [ - ...actions ?? [], - if (isDesktop) ...[ - FutureBuilder( - future: () async { - final isMaximized = - isDesktop && await windowManager.isMaximized(); + ), + Row( + children: [ + ...actions ?? [], + if (isDesktop) ...[ + FutureBuilder( + future: () async { + final isMaximized = + isDesktop && await windowManager.isMaximized(); - return isMaximized; - }(), - builder: ( - BuildContext context, - AsyncSnapshot snapshot, - ) { - final isMaximized = snapshot.data ?? false; + return isMaximized; + }(), + builder: ( + BuildContext context, + AsyncSnapshot snapshot, + ) { + final isMaximized = snapshot.data ?? false; - return Row( - children: [ - Visibility( - visible: !isFullScreen, - child: IconButton( - tooltip: isAlwaysOnTop - ? '${t.always_on_top_on} ( F10 )' - : '${t.always_on_top_off} ( F10 )', - icon: Icon( - isAlwaysOnTop - ? Icons.push_pin_rounded - : Icons.push_pin_outlined, - size: 18, - color: color, - ), - onPressed: - usePlayerUiStore().toggleIsAlwaysOnTop, - style: ButtonStyle(overlayColor: overlayColor), + return Row( + children: [ + Visibility( + visible: !isFullScreen, + child: IconButton( + tooltip: isAlwaysOnTop + ? '${t.always_on_top_on} ( F10 )' + : '${t.always_on_top_off} ( F10 )', + icon: Icon( + isAlwaysOnTop + ? Icons.push_pin_rounded + : Icons.push_pin_outlined, + size: 18, + color: color, ), + onPressed: usePlayerUiStore().toggleIsAlwaysOnTop, + style: ButtonStyle(overlayColor: overlayColor), ), - Visibility( - visible: isFullScreen, - child: IconButton( - tooltip: isFullScreen - ? '${t.exit_fullscreen} ( Escape, F11, Enter )' - : '${t.enter_fullscreen} ( F11, Enter )', - icon: Icon( - isFullScreen - ? Icons.close_fullscreen_rounded - : Icons.open_in_full_rounded, - size: 18, - color: color, - ), - onPressed: () async { - usePlayerUiStore() - .updateFullScreen(!isFullScreen); - }, - style: ButtonStyle(overlayColor: overlayColor), + ), + Visibility( + visible: isFullScreen, + child: IconButton( + tooltip: isFullScreen + ? '${t.exit_fullscreen} ( Escape, F11, Enter )' + : '${t.enter_fullscreen} ( F11, Enter )', + icon: Icon( + isFullScreen + ? Icons.close_fullscreen_rounded + : Icons.open_in_full_rounded, + size: 18, + color: color, ), + onPressed: () async { + usePlayerUiStore() + .updateFullScreen(!isFullScreen); + }, + style: ButtonStyle(overlayColor: overlayColor), ), - Visibility( - visible: !isFullScreen, - child: IconButton( - onPressed: () => windowManager.minimize(), - icon: Icon( - Icons.remove_rounded, - color: color, - ), - style: ButtonStyle(overlayColor: overlayColor), + ), + Visibility( + visible: !isFullScreen, + child: IconButton( + onPressed: () => windowManager.minimize(), + icon: Icon( + Icons.remove_rounded, + color: color, ), + style: ButtonStyle(overlayColor: overlayColor), ), - Visibility( - visible: !isFullScreen, - child: IconButton( - onPressed: () async { - if (isMaximized) { - await windowManager.unmaximize(); - } else { - await windowManager.maximize(); - } - }, - icon: isMaximized - ? RotatedBox( - quarterTurns: 2, - child: Icon( - Icons.filter_none_rounded, - size: 18, - color: color, - ), - ) - : Icon( - Icons.crop_din_rounded, - size: 20, + ), + Visibility( + visible: !isFullScreen, + child: IconButton( + onPressed: () async { + if (isMaximized) { + await windowManager.unmaximize(); + } else { + await windowManager.maximize(); + } + }, + icon: isMaximized + ? RotatedBox( + quarterTurns: 2, + child: Icon( + Icons.filter_none_rounded, + size: 18, color: color, ), - style: ButtonStyle(overlayColor: overlayColor), - ), + ) + : Icon( + Icons.crop_din_rounded, + size: 20, + color: color, + ), + style: ButtonStyle(overlayColor: overlayColor), ), - ], - ); - }, + ), + ], + ); + }, + ), + IconButton( + onPressed: () async { + await saveProgress?.call(); + windowManager.close(); + }, + icon: Icon( + Icons.close_rounded, + color: color, ), - IconButton( - onPressed: () async { - await saveProgress?.call(); - windowManager.close(); - }, - icon: Icon( - Icons.close_rounded, - color: color, - ), - style: ButtonStyle( - overlayColor: WidgetStateProperty.resolveWith( - (Set states) { - if (states.contains(WidgetState.pressed)) { - return Colors.red.withValues(alpha: 0.4); - } else if (states.contains(WidgetState.hovered)) { - return Colors.red.withValues(alpha: 0.5); - } - return null; - }), - ), + style: ButtonStyle( + overlayColor: WidgetStateProperty.resolveWith( + (Set states) { + if (states.contains(WidgetState.pressed)) { + return Colors.red.withValues(alpha: 0.4); + } else if (states.contains(WidgetState.hovered)) { + return Colors.red.withValues(alpha: 0.5); + } + return null; + }), ), - ], + ), ], - ), - ], - ), + ], + ), + ], ), ), ); diff --git a/pubspec.lock b/pubspec.lock index cb0d681..cf3599b 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -261,10 +261,10 @@ packages: dependency: "direct dev" description: name: dart_pubspec_licenses - sha256: "3e6545923039e35719415dfd2132574083a8bb5c31db074e7b63493cb6c28447" + sha256: "391ca09a09430e485839118b89b55a5fb142845d6c305d33fdc92d75ec65c2e5" url: "https://pub.dev" source: hosted - version: "3.0.8" + version: "3.0.9" dart_style: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 9f87379..63e4064 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -43,7 +43,7 @@ dependencies: permission_handler: ^12.0.1 desktop_drop: ^0.6.1 app_links: ^6.4.1 - device_info_plus: ^12.0.0 + device_info_plus: ^12.1.0 saf_util: ^0.11.0 screen_brightness: ^2.1.7 flutter_volume_controller: ^1.3.3 @@ -65,7 +65,7 @@ dev_dependencies: flutter_test: sdk: flutter flutter_lints: ^6.0.0 - dart_pubspec_licenses: ^3.0.8 + dart_pubspec_licenses: ^3.0.9 build_runner: ^2.8.0 freezed: ^3.2.3 json_serializable: ^6.11.1 From 7815811ea09cf2f2a0f03bd1698e4b25e95f750f Mon Sep 17 00:00:00 2001 From: 22 <60903333+nini22P@users.noreply.github.com> Date: Mon, 15 Sep 2025 17:57:09 +0800 Subject: [PATCH 20/31] feat: add swipe to close popup --- lib/pages/home/home.dart | 2 +- lib/widgets/popup.dart | 141 +++++++++++++++++++++++---------------- 2 files changed, 84 insertions(+), 59 deletions(-) diff --git a/lib/pages/home/home.dart b/lib/pages/home/home.dart index 7c0d04d..c469423 100644 --- a/lib/pages/home/home.dart +++ b/lib/pages/home/home.dart @@ -23,7 +23,7 @@ class Home extends HookWidget { return AnnotatedRegion( value: const SystemUiOverlayStyle( statusBarIconBrightness: Brightness.light, - statusBarColor: Colors.transparent, + statusBarColor: Colors.black45, systemNavigationBarColor: Colors.transparent, ), child: Scaffold( diff --git a/lib/widgets/popup.dart b/lib/widgets/popup.dart index b6315d6..591057e 100644 --- a/lib/widgets/popup.dart +++ b/lib/widgets/popup.dart @@ -29,17 +29,25 @@ class Popup extends PopupRoute { final Widget child; final PopupDirection direction; + bool _isPopping = false; + + void _popOnce(BuildContext context) { + if (_isPopping) return; + _isPopping = true; + Navigator.of(context).pop(); + } + @override Color? get barrierColor => Colors.transparent; @override - bool get barrierDismissible => true; + bool get barrierDismissible => false; @override String? get barrierLabel => 'Dismiss'; @override - Duration get transitionDuration => const Duration(milliseconds: 300); + Duration get transitionDuration => const Duration(milliseconds: 250); @override Widget buildPage(BuildContext context, Animation animation, @@ -52,61 +60,78 @@ class Popup extends PopupRoute { ? 2 : 1; - return Stack( - children: [ - Positioned.fill( - child: GestureDetector( - onPanStart: (details) { - if (Platform.isWindows || Platform.isLinux || Platform.isMacOS) { - windowManager.startDragging(); - } - }, - onTap: () => Navigator.of(context).pop(), - ), - ), - Align( - alignment: direction == PopupDirection.left - ? Alignment.bottomLeft - : Alignment.bottomRight, - child: Padding( - padding: EdgeInsets.only( - top: 0, - bottom: 8, - left: direction == PopupDirection.left ? 8 : 0, - right: direction == PopupDirection.right ? 8 : 0, - ), - child: AnimatedBuilder( - animation: animation, - builder: (context, child) { - return SlideTransition( - position: Tween( - begin: direction == PopupDirection.left - ? const Offset(-1.0, 0.0) - : const Offset(1.0, 0.0), - end: Offset.zero, - ).animate(CurvedAnimation( - parent: animation, - curve: Curves.easeInOutCubicEmphasized, - )), - child: child, - ); + return Dismissible( + key: UniqueKey(), + direction: direction == PopupDirection.left + ? DismissDirection.endToStart + : DismissDirection.startToEnd, + onUpdate: (details) { + if (details.previousReached) { + _popOnce(context); + } + }, + child: Stack( + children: [ + Positioned.fill( + child: GestureDetector( + onPanStart: (details) { + if (Platform.isWindows || + Platform.isLinux || + Platform.isMacOS) { + windowManager.startDragging(); + } }, - child: ClipRRect( - borderRadius: BorderRadius.circular(16), - child: BackdropFilter( - filter: ImageFilter.blur(sigmaX: 10, sigmaY: 10), - child: Material( - color: Theme.of(context) - .colorScheme - .surface - .withValues(alpha: 0.75), - child: UnconstrainedBox( - child: LimitedBox( - maxWidth: screenWidth / size - 16, - maxHeight: screenHeight - 16, - child: Column( - mainAxisSize: MainAxisSize.min, - children: [Expanded(child: child)], + onTap: () => _popOnce(context), + ), + ), + Align( + alignment: direction == PopupDirection.left + ? Alignment.bottomLeft + : Alignment.bottomRight, + child: Padding( + padding: EdgeInsets.only( + top: 0, + bottom: 8, + left: direction == PopupDirection.left ? 8 : 0, + right: direction == PopupDirection.right ? 8 : 0, + ), + child: AnimatedBuilder( + animation: animation, + builder: (context, child) { + return SlideTransition( + position: Tween( + begin: direction == PopupDirection.left + ? const Offset(-1.0, 0.0) + : const Offset(1.0, 0.0), + end: Offset.zero, + ).animate( + CurvedAnimation( + parent: animation, + curve: Curves.easeInOutCubicEmphasized, + ), + ), + child: child, + ); + }, + child: ClipRRect( + borderRadius: BorderRadius.circular(16), + child: BackdropFilter( + filter: ImageFilter.blur(sigmaX: 10, sigmaY: 10), + child: Material( + color: Theme.of(context) + .colorScheme + .surface + .withValues(alpha: 0.75), + child: UnconstrainedBox( + child: LimitedBox( + maxWidth: screenWidth / size - 16, + maxHeight: screenHeight - 16, + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Expanded(child: child), + ], + ), ), ), ), @@ -115,8 +140,8 @@ class Popup extends PopupRoute { ), ), ), - ), - ], + ], + ), ); } } From 4a3189faf6a1d1f4a1edcc58d0f7c29375836282 Mon Sep 17 00:00:00 2001 From: 22 <60903333+nini22P@users.noreply.github.com> Date: Mon, 15 Sep 2025 18:14:46 +0800 Subject: [PATCH 21/31] fix: check rate on speed selector --- lib/hooks/use_gesture.dart | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/hooks/use_gesture.dart b/lib/hooks/use_gesture.dart index 05e4bb2..ee2faf8 100644 --- a/lib/hooks/use_gesture.dart +++ b/lib/hooks/use_gesture.dart @@ -162,7 +162,9 @@ Gesture useGesture({ double selectedSpeed = speedStops[finalIndex]; updateSelectedSpeed(selectedSpeed, visualOffset); - useAppStore().updateRate(selectedSpeed); + if (useAppStore().state.rate != selectedSpeed) { + useAppStore().updateRate(selectedSpeed); + } } void onLongPressEnd(LongPressEndDetails details) { From b6598f74947f5d6e91a7c3c15f16e7266261e10a Mon Sep 17 00:00:00 2001 From: 22 <60903333+nini22P@users.noreply.github.com> Date: Tue, 16 Sep 2025 10:01:46 +0800 Subject: [PATCH 22/31] feat: improve WebDAV storage configuration entry, display WebDAV port in the storage list. --- lib/pages/storages/favorites.dart | 2 +- lib/pages/storages/storages_list.dart | 2 +- lib/widgets/dialogs/show_webdav_dialog.dart | 6 ++++-- 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/lib/pages/storages/favorites.dart b/lib/pages/storages/favorites.dart index 740079b..bf1b611 100644 --- a/lib/pages/storages/favorites.dart +++ b/lib/pages/storages/favorites.dart @@ -47,7 +47,7 @@ class Favorites extends HookWidget { ); } else if (storage is WebDAVStorage) { return Text( - 'http${storage.https ? 's' : ''}://${storage.host}${favorites[index].path.join('/')}'); + 'http${storage.https ? 's' : ''}://${storage.host}${storage.port.isNotEmpty && storage.port != '80' && storage.port != '443' ? ':${storage.port}' : ''}${favorites[index].path.join('/')}'); } else if (storage is FTPStorage) { return Text( 'ftp://${storage.username.isNotEmpty ? '${storage.username}@' : ''}${storage.host}:${storage.port}${favorites[index].path.join('/').replaceFirst('//', '/')}'); diff --git a/lib/pages/storages/storages_list.dart b/lib/pages/storages/storages_list.dart index c68aa5d..7761c15 100644 --- a/lib/pages/storages/storages_list.dart +++ b/lib/pages/storages/storages_list.dart @@ -54,7 +54,7 @@ class StoragesList extends HookWidget { case StorageType.webdav: final storage = allStorages[index] as WebDAVStorage; subtitle = - 'http${storage.https ? 's' : ''}://${storage.host}${storage.basePath.join('/')}'; + 'http${storage.https ? 's' : ''}://${storage.host}${storage.port.isNotEmpty && storage.port != '80' && storage.port != '443' ? ':${storage.port}' : ''}${storage.basePath.join('/')}'; break; case StorageType.ftp: final storage = allStorages[index] as FTPStorage; diff --git a/lib/widgets/dialogs/show_webdav_dialog.dart b/lib/widgets/dialogs/show_webdav_dialog.dart index 5f4afa1..b560747 100644 --- a/lib/widgets/dialogs/show_webdav_dialog.dart +++ b/lib/widgets/dialogs/show_webdav_dialog.dart @@ -174,9 +174,11 @@ class WebDAVDialog extends HookWidget { value: https.value, onChanged: (_) { isTested.value = false; - if (portController.text == '80') { + if (portController.text == '80' && + https.value == false) { portController.text = '443'; - } else if (portController.text == '443') { + } else if (portController.text == '443' && + https.value == true) { portController.text = '80'; } https.value = !https.value; From 9855975dd99446e4fe9806e568384c5eebb84831 Mon Sep 17 00:00:00 2001 From: 22 <60903333+nini22P@users.noreply.github.com> Date: Tue, 16 Sep 2025 10:13:14 +0800 Subject: [PATCH 23/31] fix some bugs --- lib/hooks/ui/use_resize_window.dart | 12 ----- lib/widgets/card.dart | 70 +++++++++++++++++++++++++++++ lib/widgets/drag_area.dart | 3 +- lib/widgets/popup.dart | 35 ++++++--------- 4 files changed, 86 insertions(+), 34 deletions(-) create mode 100644 lib/widgets/card.dart diff --git a/lib/hooks/ui/use_resize_window.dart b/lib/hooks/ui/use_resize_window.dart index 50f042b..454300d 100644 --- a/lib/hooks/ui/use_resize_window.dart +++ b/lib/hooks/ui/use_resize_window.dart @@ -50,18 +50,6 @@ void useResizeWindow() { if (contentType == ContentType.audio) { await windowManager.setAspectRatio(0); - const Size defaultAudioSize = Size(450, 600); - if ((await windowManager.getSize()) != defaultAudioSize) { - final screen = await getCurrentScreen(); - if (screen == null) return; - final screenRect = screen.visibleFrame; - final position = Offset( - (screenRect.width - defaultAudioSize.width) / 2, - (screenRect.height - defaultAudioSize.height) / 2, - ); - await _applyResize(Rect.fromLTWH(position.dx, position.dy, - defaultAudioSize.width, defaultAudioSize.height)); - } return; } diff --git a/lib/widgets/card.dart b/lib/widgets/card.dart new file mode 100644 index 0000000..173a68a --- /dev/null +++ b/lib/widgets/card.dart @@ -0,0 +1,70 @@ +import 'dart:ui'; +import 'package:flutter/material.dart'; + +class Card extends StatelessWidget { + const Card({ + super.key, + required this.child, + this.padding, + this.borderRadius, + this.color, + this.border, + }); + + final Widget child; + final EdgeInsetsGeometry? padding; + final BorderRadius? borderRadius; + final Color? color; + final Border? border; + + @override + Widget build(BuildContext context) { + final effectiveBorderRadius = borderRadius ?? BorderRadius.circular(16); + final effectiveBorder = border ?? + Border.all( + color: Theme.of(context) + .colorScheme + .onSurfaceVariant + .withValues(alpha: 0.125), + width: 1, + ); + + return Stack( + children: [ + ClipRRect( + borderRadius: effectiveBorderRadius, + child: BackdropFilter( + filter: ImageFilter.blur(sigmaX: 10, sigmaY: 10), + child: Container( + padding: padding ?? const EdgeInsets.all(0), + decoration: BoxDecoration( + borderRadius: effectiveBorderRadius, + color: color ?? + Theme.of(context) + .colorScheme + .surfaceContainer + .withValues(alpha: 0.75), + ), + child: child, + ), + ), + ), + Positioned( + left: 0, + top: 0, + right: 0, + bottom: 0, + child: IgnorePointer( + child: Container( + decoration: BoxDecoration( + color: Colors.transparent, + borderRadius: effectiveBorderRadius, + border: effectiveBorder, + ), + ), + ), + ), + ], + ); + } +} diff --git a/lib/widgets/drag_area.dart b/lib/widgets/drag_area.dart index 31b95dd..fcd8c74 100644 --- a/lib/widgets/drag_area.dart +++ b/lib/widgets/drag_area.dart @@ -18,10 +18,11 @@ class DragArea extends StatelessWidget { return GestureDetector( onDoubleTap: () async { + if (!isDesktop) return; if (isFullScreen) { await usePlayerUiStore().updateFullScreen(false); } else { - if (isDesktop && await windowManager.isMaximized()) { + if (await windowManager.isMaximized()) { await windowManager.unmaximize(); } else { await windowManager.maximize(); diff --git a/lib/widgets/popup.dart b/lib/widgets/popup.dart index 591057e..a8b1a89 100644 --- a/lib/widgets/popup.dart +++ b/lib/widgets/popup.dart @@ -1,6 +1,6 @@ import 'dart:io'; -import 'dart:ui'; -import 'package:flutter/material.dart'; +import 'package:flutter/material.dart' hide Card; +import 'package:iris/widgets/card.dart'; import 'package:window_manager/window_manager.dart'; enum PopupDirection { left, right } @@ -113,25 +113,18 @@ class Popup extends PopupRoute { child: child, ); }, - child: ClipRRect( - borderRadius: BorderRadius.circular(16), - child: BackdropFilter( - filter: ImageFilter.blur(sigmaX: 10, sigmaY: 10), - child: Material( - color: Theme.of(context) - .colorScheme - .surface - .withValues(alpha: 0.75), - child: UnconstrainedBox( - child: LimitedBox( - maxWidth: screenWidth / size - 16, - maxHeight: screenHeight - 16, - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - Expanded(child: child), - ], - ), + child: UnconstrainedBox( + child: LimitedBox( + maxWidth: screenWidth / size - 16, + maxHeight: screenHeight - 16, + child: Card( + child: Material( + color: Colors.transparent, + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Expanded(child: child), + ], ), ), ), From 6ba646fc18ce493acc7e09183cfa23f14adc64ed Mon Sep 17 00:00:00 2001 From: 22 <60903333+nini22P@users.noreply.github.com> Date: Tue, 16 Sep 2025 20:47:09 +0800 Subject: [PATCH 24/31] fix: popup widget rebuild bug --- lib/widgets/popup.dart | 151 +++++++++++++++++++++-------------------- 1 file changed, 77 insertions(+), 74 deletions(-) diff --git a/lib/widgets/popup.dart b/lib/widgets/popup.dart index a8b1a89..8c0f11b 100644 --- a/lib/widgets/popup.dart +++ b/lib/widgets/popup.dart @@ -1,5 +1,6 @@ import 'dart:io'; import 'package:flutter/material.dart' hide Card; +import 'package:iris/utils/platform.dart'; import 'package:iris/widgets/card.dart'; import 'package:window_manager/window_manager.dart'; @@ -52,89 +53,91 @@ class Popup extends PopupRoute { @override Widget buildPage(BuildContext context, Animation animation, Animation secondaryAnimation) { - double screenWidth = MediaQuery.of(context).size.width; - double screenHeight = MediaQuery.of(context).size.height; - int size = screenWidth > 1200 - ? 3 - : screenWidth > 720 - ? 2 - : 1; - - return Dismissible( - key: UniqueKey(), - direction: direction == PopupDirection.left - ? DismissDirection.endToStart - : DismissDirection.startToEnd, - onUpdate: (details) { - if (details.previousReached) { - _popOnce(context); - } - }, - child: Stack( - children: [ - Positioned.fill( - child: GestureDetector( - onPanStart: (details) { - if (Platform.isWindows || - Platform.isLinux || - Platform.isMacOS) { - windowManager.startDragging(); - } - }, - onTap: () => _popOnce(context), - ), + return Stack( + children: [ + Positioned.fill( + child: GestureDetector( + onPanStart: (details) { + if (Platform.isWindows || Platform.isLinux || Platform.isMacOS) { + windowManager.startDragging(); + } + }, + onTap: () => _popOnce(context), ), - Align( - alignment: direction == PopupDirection.left - ? Alignment.bottomLeft - : Alignment.bottomRight, - child: Padding( - padding: EdgeInsets.only( - top: 0, - bottom: 8, - left: direction == PopupDirection.left ? 8 : 0, - right: direction == PopupDirection.right ? 8 : 0, - ), - child: AnimatedBuilder( - animation: animation, - builder: (context, child) { - return SlideTransition( - position: Tween( - begin: direction == PopupDirection.left - ? const Offset(-1.0, 0.0) - : const Offset(1.0, 0.0), - end: Offset.zero, - ).animate( - CurvedAnimation( - parent: animation, - curve: Curves.easeInOutCubicEmphasized, - ), + ), + Align( + alignment: direction == PopupDirection.left + ? Alignment.bottomLeft + : Alignment.bottomRight, + child: Padding( + padding: EdgeInsets.only( + bottom: 8, + left: direction == PopupDirection.left ? 8 : 0, + right: direction == PopupDirection.right ? 8 : 0, + ), + child: AnimatedBuilder( + animation: animation, + builder: (context, child) { + return SlideTransition( + position: Tween( + begin: direction == PopupDirection.left + ? const Offset(-1.0, 0.0) + : const Offset(1.0, 0.0), + end: Offset.zero, + ).animate( + CurvedAnimation( + parent: animation, + curve: Curves.easeInOutCubicEmphasized, ), - child: child, - ); + ), + child: child, + ); + }, + child: Dismissible( + key: UniqueKey(), + direction: direction == PopupDirection.left + ? DismissDirection.endToStart + : DismissDirection.startToEnd, + onUpdate: (details) { + if (details.previousReached) { + _popOnce(context); + } }, - child: UnconstrainedBox( - child: LimitedBox( - maxWidth: screenWidth / size - 16, - maxHeight: screenHeight - 16, - child: Card( - child: Material( - color: Colors.transparent, - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - Expanded(child: child), - ], + child: LayoutBuilder( + builder: (context, constraints) { + final screenWidth = constraints.maxWidth; + final screenHeight = constraints.maxHeight; + final int size = screenWidth > 1200 + ? 3 + : screenWidth > 720 + ? 2 + : 1; + + return UnconstrainedBox( + child: LimitedBox( + maxWidth: screenWidth / size - 16, + maxHeight: + isDesktop ? screenHeight - 48 : screenHeight - 16, + child: Card( + child: Material( + color: Colors.transparent, + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Expanded(child: child), + ], + ), + ), ), ), - ), - ), + ); + }, ), ), ), ), - ], - ), + ), + ], ); } } From 8147caa1755fb03aeb59376d4a8d32a63c65f233 Mon Sep 17 00:00:00 2001 From: 22 <60903333+nini22P@users.noreply.github.com> Date: Tue, 16 Sep 2025 20:48:19 +0800 Subject: [PATCH 25/31] move codes --- lib/hooks/use_keyboard.dart | 6 ++--- lib/pages/player/control_bar/control_bar.dart | 6 ++--- .../{ => overlays}/controls_overlay.dart | 2 +- .../{home => player/overlays}/history.dart | 10 ++++---- .../player/{ => overlays}/play_queue.dart | 10 ++++---- .../player/{ => overlays}/speed_selector.dart | 0 .../track}/audio_track_list.dart | 0 .../track}/subtitle_and_audio_track.dart | 4 ++-- .../{ => overlays/track}/subtitle_list.dart | 0 lib/pages/player/player.dart | 2 +- lib/pages/storages/files.dart | 10 ++++---- lib/widgets/app_menu.dart | 24 ------------------- lib/widgets/{app_chip.dart => chip.dart} | 4 ++-- pubspec.lock | 2 +- 14 files changed, 28 insertions(+), 52 deletions(-) rename lib/pages/player/{ => overlays}/controls_overlay.dart (99%) rename lib/pages/{home => player/overlays}/history.dart (96%) rename lib/pages/player/{ => overlays}/play_queue.dart (96%) rename lib/pages/player/{ => overlays}/speed_selector.dart (100%) rename lib/pages/player/{ => overlays/track}/audio_track_list.dart (100%) rename lib/pages/player/{ => overlays/track}/subtitle_and_audio_track.dart (94%) rename lib/pages/player/{ => overlays/track}/subtitle_list.dart (100%) delete mode 100644 lib/widgets/app_menu.dart rename lib/widgets/{app_chip.dart => chip.dart} (86%) diff --git a/lib/hooks/use_keyboard.dart b/lib/hooks/use_keyboard.dart index fec24d6..54cb567 100644 --- a/lib/hooks/use_keyboard.dart +++ b/lib/hooks/use_keyboard.dart @@ -4,9 +4,9 @@ import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:iris/globals.dart'; import 'package:iris/models/player.dart'; import 'package:iris/models/storages/local.dart'; -import 'package:iris/pages/home/history.dart'; -import 'package:iris/pages/player/play_queue.dart'; -import 'package:iris/pages/player/subtitle_and_audio_track.dart'; +import 'package:iris/pages/player/overlays/history.dart'; +import 'package:iris/pages/player/overlays/play_queue.dart'; +import 'package:iris/pages/player/overlays/track/subtitle_and_audio_track.dart'; import 'package:iris/pages/settings/settings.dart'; import 'package:iris/pages/storages/storages.dart'; import 'package:iris/store/use_app_store.dart'; diff --git a/lib/pages/player/control_bar/control_bar.dart b/lib/pages/player/control_bar/control_bar.dart index e19d1c0..e39aa22 100644 --- a/lib/pages/player/control_bar/control_bar.dart +++ b/lib/pages/player/control_bar/control_bar.dart @@ -11,15 +11,15 @@ import 'package:iris/store/use_player_ui_store.dart'; import 'package:iris/widgets/dialogs/show_open_link_dialog.dart'; import 'package:iris/widgets/dialogs/show_rate_dialog.dart'; import 'package:iris/pages/player/control_bar/control_bar_slider.dart'; -import 'package:iris/pages/home/history.dart'; +import 'package:iris/pages/player/overlays/history.dart'; import 'package:iris/widgets/bottom_sheets/show_open_link_bottom_sheet.dart'; import 'package:iris/pages/settings/settings.dart'; import 'package:iris/pages/player/control_bar/volume_control.dart'; -import 'package:iris/pages/player/subtitle_and_audio_track.dart'; +import 'package:iris/pages/player/overlays/track/subtitle_and_audio_track.dart'; import 'package:iris/store/use_app_store.dart'; import 'package:iris/store/use_play_queue_store.dart'; import 'package:iris/utils/get_localizations.dart'; -import 'package:iris/pages/player/play_queue.dart'; +import 'package:iris/pages/player/overlays/play_queue.dart'; import 'package:iris/utils/platform.dart'; import 'package:iris/widgets/popup.dart'; import 'package:iris/pages/storages/storages.dart'; diff --git a/lib/pages/player/controls_overlay.dart b/lib/pages/player/overlays/controls_overlay.dart similarity index 99% rename from lib/pages/player/controls_overlay.dart rename to lib/pages/player/overlays/controls_overlay.dart index fc1cd33..155df80 100644 --- a/lib/pages/player/controls_overlay.dart +++ b/lib/pages/player/overlays/controls_overlay.dart @@ -8,7 +8,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:iris/pages/player/control_bar/control_bar.dart'; import 'package:iris/pages/player/control_bar/control_bar_slider.dart'; -import 'package:iris/pages/player/speed_selector.dart'; +import 'package:iris/pages/player/overlays/speed_selector.dart'; import 'package:iris/store/use_app_store.dart'; import 'package:iris/store/use_player_ui_store.dart'; import 'package:iris/utils/format_duration_to_minutes.dart'; diff --git a/lib/pages/home/history.dart b/lib/pages/player/overlays/history.dart similarity index 96% rename from lib/pages/home/history.dart rename to lib/pages/player/overlays/history.dart index 7e5f8ba..ea9dcdb 100644 --- a/lib/pages/home/history.dart +++ b/lib/pages/player/overlays/history.dart @@ -1,6 +1,6 @@ import 'dart:math'; -import 'package:flutter/material.dart'; +import 'package:flutter/material.dart' hide Chip; import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:flutter_zustand/flutter_zustand.dart'; import 'package:iris/models/file.dart'; @@ -11,7 +11,7 @@ import 'package:iris/store/use_history_store.dart'; import 'package:iris/store/use_play_queue_store.dart'; import 'package:iris/utils/file_size_convert.dart'; import 'package:iris/utils/get_localizations.dart'; -import 'package:iris/widgets/app_chip.dart'; +import 'package:iris/widgets/chip.dart'; import 'package:scrollable_positioned_list/scrollable_positioned_list.dart'; class History extends HookWidget { @@ -84,14 +84,14 @@ class History extends HookWidget { if ((progress.duration.inMilliseconds - progress.position.inMilliseconds) <= 5000) { - return AppChip(text: '100%'); + return Chip(text: '100%'); } final String progressString = (progress.position.inMilliseconds / progress.duration.inMilliseconds * 100) .toStringAsFixed(0); - return AppChip(text: '$progressString %'); + return Chip(text: '$progressString %'); } else { return const SizedBox(); } @@ -109,7 +109,7 @@ class History extends HookWidget { mainAxisSize: MainAxisSize.min, children: [ const SizedBox(width: 4), - AppChip( + Chip( text: subtitleType, primary: true, ), diff --git a/lib/pages/player/play_queue.dart b/lib/pages/player/overlays/play_queue.dart similarity index 96% rename from lib/pages/player/play_queue.dart rename to lib/pages/player/overlays/play_queue.dart index 5328102..89f545b 100644 --- a/lib/pages/player/play_queue.dart +++ b/lib/pages/player/overlays/play_queue.dart @@ -1,4 +1,4 @@ -import 'package:flutter/material.dart'; +import 'package:flutter/material.dart' hide Chip; import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:flutter_zustand/flutter_zustand.dart'; import 'package:iris/models/file.dart'; @@ -9,7 +9,7 @@ import 'package:iris/store/use_history_store.dart'; import 'package:iris/store/use_play_queue_store.dart'; import 'package:iris/utils/file_size_convert.dart'; import 'package:iris/utils/get_localizations.dart'; -import 'package:iris/widgets/app_chip.dart'; +import 'package:iris/widgets/chip.dart'; import 'package:scrollable_positioned_list/scrollable_positioned_list.dart'; class PlayQueue extends HookWidget { @@ -99,14 +99,14 @@ class PlayQueue extends HookWidget { if ((progress.duration.inMilliseconds - progress.position.inMilliseconds) <= 5000) { - return AppChip(text: '100%'); + return Chip(text: '100%'); } final String progressString = (progress.position.inMilliseconds / progress.duration.inMilliseconds * 100) .toStringAsFixed(0); - return AppChip(text: '$progressString %'); + return Chip(text: '$progressString %'); } else { return const SizedBox(); } @@ -123,7 +123,7 @@ class PlayQueue extends HookWidget { mainAxisSize: MainAxisSize.min, children: [ const SizedBox(width: 4), - AppChip( + Chip( text: subtitleType, primary: true, ), diff --git a/lib/pages/player/speed_selector.dart b/lib/pages/player/overlays/speed_selector.dart similarity index 100% rename from lib/pages/player/speed_selector.dart rename to lib/pages/player/overlays/speed_selector.dart diff --git a/lib/pages/player/audio_track_list.dart b/lib/pages/player/overlays/track/audio_track_list.dart similarity index 100% rename from lib/pages/player/audio_track_list.dart rename to lib/pages/player/overlays/track/audio_track_list.dart diff --git a/lib/pages/player/subtitle_and_audio_track.dart b/lib/pages/player/overlays/track/subtitle_and_audio_track.dart similarity index 94% rename from lib/pages/player/subtitle_and_audio_track.dart rename to lib/pages/player/overlays/track/subtitle_and_audio_track.dart index a988bb3..dc5ea12 100644 --- a/lib/pages/player/subtitle_and_audio_track.dart +++ b/lib/pages/player/overlays/track/subtitle_and_audio_track.dart @@ -1,7 +1,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; -import 'package:iris/pages/player/audio_track_list.dart'; -import 'package:iris/pages/player/subtitle_list.dart'; +import 'package:iris/pages/player/overlays/track/audio_track_list.dart'; +import 'package:iris/pages/player/overlays/track/subtitle_list.dart'; import 'package:iris/utils/get_localizations.dart'; class ITab { diff --git a/lib/pages/player/subtitle_list.dart b/lib/pages/player/overlays/track/subtitle_list.dart similarity index 100% rename from lib/pages/player/subtitle_list.dart rename to lib/pages/player/overlays/track/subtitle_list.dart diff --git a/lib/pages/player/player.dart b/lib/pages/player/player.dart index 7a9b05b..9d798dd 100644 --- a/lib/pages/player/player.dart +++ b/lib/pages/player/player.dart @@ -13,7 +13,7 @@ import 'package:iris/models/file.dart'; import 'package:iris/models/player.dart'; import 'package:iris/models/storages/local.dart'; import 'package:iris/pages/player/audio.dart'; -import 'package:iris/pages/player/controls_overlay.dart'; +import 'package:iris/pages/player/overlays/controls_overlay.dart'; import 'package:iris/pages/player/video_view.dart'; import 'package:iris/store/use_player_ui_store.dart'; import 'package:iris/utils/check_content_type.dart'; diff --git a/lib/pages/storages/files.dart b/lib/pages/storages/files.dart index 56e9e7e..8d52f1a 100644 --- a/lib/pages/storages/files.dart +++ b/lib/pages/storages/files.dart @@ -1,6 +1,6 @@ import 'dart:io'; import 'package:collection/collection.dart'; -import 'package:flutter/material.dart'; +import 'package:flutter/material.dart' hide Chip; import 'package:flutter_breadcrumb/flutter_breadcrumb.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:flutter_zustand/flutter_zustand.dart'; @@ -19,7 +19,7 @@ import 'package:iris/utils/file_size_convert.dart'; import 'package:iris/utils/files_sort.dart'; import 'package:iris/utils/get_localizations.dart'; import 'package:iris/utils/request_storage_permission.dart'; -import 'package:iris/widgets/app_chip.dart'; +import 'package:iris/widgets/chip.dart'; import 'package:scrollable_positioned_list/scrollable_positioned_list.dart'; import 'package:permission_handler/permission_handler.dart'; @@ -217,7 +217,7 @@ class Files extends HookWidget { progress.position .inMilliseconds) <= 5000) { - return AppChip(text: '100%'); + return Chip(text: '100%'); } final String progressString = (progress.position @@ -226,7 +226,7 @@ class Files extends HookWidget { .inMilliseconds * 100) .toStringAsFixed(0); - return AppChip( + return Chip( text: '$progressString %'); } else { return const SizedBox(); @@ -245,7 +245,7 @@ class Files extends HookWidget { mainAxisSize: MainAxisSize.min, children: [ const SizedBox(width: 4), - AppChip( + Chip( text: subtitleType, primary: true, ), diff --git a/lib/widgets/app_menu.dart b/lib/widgets/app_menu.dart deleted file mode 100644 index ec65a1f..0000000 --- a/lib/widgets/app_menu.dart +++ /dev/null @@ -1,24 +0,0 @@ -import 'dart:math'; -import 'package:flutter/material.dart'; -import 'package:popover/popover.dart'; - -Future showCustomMenu( - BuildContext context, { - required List items, - double? width = 128, -}) async => - showPopover( - context: context, - bodyBuilder: (context) => SingleChildScrollView( - child: Column(children: items), - ), - width: width, - height: min(MediaQuery.of(context).size.height * 0.75, - items.length * kMinInteractiveDimension), - radius: 16, - arrowHeight: 0, - arrowWidth: 0, - backgroundColor: - Theme.of(context).colorScheme.surfaceDim.withValues(alpha: 0.9), - barrierColor: Colors.transparent, - ); diff --git a/lib/widgets/app_chip.dart b/lib/widgets/chip.dart similarity index 86% rename from lib/widgets/app_chip.dart rename to lib/widgets/chip.dart index c4a4a75..6e2203a 100644 --- a/lib/widgets/app_chip.dart +++ b/lib/widgets/chip.dart @@ -1,10 +1,10 @@ import 'package:flutter/material.dart'; -class AppChip extends StatelessWidget { +class Chip extends StatelessWidget { final String text; final bool primary; - const AppChip({super.key, required this.text, this.primary = false}); + const Chip({super.key, required this.text, this.primary = false}); @override Widget build(BuildContext context) { diff --git a/pubspec.lock b/pubspec.lock index cf3599b..cb6dbf7 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -811,7 +811,7 @@ packages: description: path: "." ref: main - resolved-ref: ffdd8015191aee63acc32c5657ecf050302861be + resolved-ref: "0abae44bddaa9befe16a90acd23ac544a9fe9325" url: "https://github.com/nini22P/media_stream" source: git version: "0.0.1" From 2fe6ba6577655cb2f82c1c5c532485f0ff29951c Mon Sep 17 00:00:00 2001 From: 22 <60903333+nini22P@users.noreply.github.com> Date: Tue, 16 Sep 2025 21:54:55 +0800 Subject: [PATCH 26/31] update audio ui --- lib/pages/player/audio.dart | 202 ++++++++++++++---- lib/pages/player/control_bar/control_bar.dart | 27 +-- lib/widgets/title_bar.dart | 4 +- 3 files changed, 175 insertions(+), 58 deletions(-) diff --git a/lib/pages/player/audio.dart b/lib/pages/player/audio.dart index bdfbb5a..e61e3ce 100644 --- a/lib/pages/player/audio.dart +++ b/lib/pages/player/audio.dart @@ -6,6 +6,37 @@ import 'package:iris/models/file.dart'; import 'package:iris/models/storages/storage.dart'; import 'package:iris/store/use_storage_store.dart'; +class _CoverImage extends StatelessWidget { + final FileItem cover; + final String? auth; + final BoxFit fit; + + const _CoverImage({ + required this.cover, + required this.auth, + required this.fit, + }); + + @override + Widget build(BuildContext context) { + final isLocal = cover.storageId == localStorageId; + if (isLocal) { + return Image.file( + File(cover.uri), + fit: fit, + gaplessPlayback: true, + ); + } else { + return Image.network( + cover.uri, + headers: auth != null ? {'authorization': auth!} : null, + fit: fit, + gaplessPlayback: true, + ); + } + } +} + class Audio extends HookWidget { const Audio({ super.key, @@ -22,60 +53,145 @@ class Audio extends HookWidget { : useStorageStore().findById(cover!.storageId), [cover?.storageId]); final auth = useMemoized(() => storage?.getAuth(), [storage]); + return IgnorePointer( child: Stack( + fit: StackFit.expand, children: [ - SizedBox( - width: MediaQuery.of(context).size.width, - height: MediaQuery.of(context).size.height, - child: cover != null - ? cover?.storageId == localStorageId - ? Image.file( - File(cover!.uri), - fit: BoxFit.cover, - ) - : Image.network( - cover!.uri, - headers: auth != null ? {'authorization': auth} : null, - fit: BoxFit.cover, - ) - : null, - ), + if (cover != null) + _CoverImage(cover: cover!, auth: auth, fit: BoxFit.cover), BackdropFilter( - filter: ImageFilter.blur(sigmaX: 16.0, sigmaY: 16.0), - child: Container(color: Colors.transparent), + filter: ImageFilter.blur(sigmaX: 24.0, sigmaY: 24.0), + child: DecoratedBox( + decoration: BoxDecoration( + gradient: LinearGradient( + begin: Alignment.topCenter, + end: Alignment.bottomCenter, + colors: [ + Theme.of(context) + .colorScheme + .surface + .withValues(alpha: 0.6), + Theme.of(context) + .colorScheme + .surface + .withValues(alpha: 0.2), + ], + ), + ), + ), ), - Positioned( - left: 0, - top: 0, - right: MediaQuery.of(context).size.width > 800 - ? MediaQuery.of(context).size.width / 2 - : 0, - bottom: 0, - child: Center( - child: SizedBox( - height: MediaQuery.of(context).size.height / 2, - child: ClipRRect( - borderRadius: BorderRadius.circular(8), - child: cover != null - ? cover!.storageId == localStorageId - ? Image.file( - File(cover!.uri), - fit: BoxFit.contain, - ) - : Image.network( - cover!.uri, - headers: - auth != null ? {'authorization': auth} : null, - fit: BoxFit.contain, - ) - : null, + LayoutBuilder( + builder: (context, constraints) { + const double wideLayoutThreshold = 600; + final isWideScreen = constraints.maxWidth >= wideLayoutThreshold; + + if (isWideScreen) { + return _buildWideLayout(context, constraints, cover, auth); + } else { + return _buildNarrowLayout(context, constraints, cover, auth); + } + }, + ), + ], + ), + ); + } + + Widget _buildNarrowLayout(BuildContext context, BoxConstraints constraints, + FileItem? cover, String? auth) { + return Align( + alignment: const Alignment(0.0, -0.2), + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 48.0, vertical: 24.0), + child: ConstrainedBox( + constraints: const BoxConstraints( + maxWidth: 400.0, + maxHeight: 400.0, + ), + child: AspectRatio( + aspectRatio: 1.0, + child: _buildCoverCard( + cover: cover, + auth: auth, + shadowColor: Theme.of(context) + .colorScheme + .onSurface + .withValues(alpha: 0.15), + ), + ), + ), + ), + ); + } + + Widget _buildWideLayout(BuildContext context, BoxConstraints constraints, + FileItem? cover, String? auth) { + return Row( + children: [ + Expanded( + flex: 5, + child: Align( + alignment: const Alignment(0.0, -0.2), + child: Padding( + padding: const EdgeInsets.fromLTRB(48, 24, 24, 24), + child: ConstrainedBox( + constraints: const BoxConstraints( + maxWidth: 400.0, + maxHeight: 400.0, + ), + child: AspectRatio( + aspectRatio: 1.0, + child: _buildCoverCard( + cover: cover, + auth: auth, + shadowColor: Theme.of(context) + .colorScheme + .onSurface + .withValues(alpha: 0.15), + ), ), ), ), ), + ), + Expanded( + flex: 5, + child: Container( + padding: + const EdgeInsets.symmetric(horizontal: 48.0, vertical: 24.0), + ), + ), + ], + ); + } + + Widget _buildCoverCard( + {required FileItem? cover, + required String? auth, + required Color shadowColor}) { + return Container( + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(16), + boxShadow: [ + BoxShadow( + color: shadowColor, + blurRadius: 32, + spreadRadius: 2, + offset: const Offset(0, 8), + ), ], ), + child: ClipRRect( + borderRadius: BorderRadius.circular(16), + child: cover != null + ? _CoverImage( + cover: cover, + auth: auth, + fit: BoxFit.cover, + ) + : Container(), + ), ); } } diff --git a/lib/pages/player/control_bar/control_bar.dart b/lib/pages/player/control_bar/control_bar.dart index e39aa22..2a57e4d 100644 --- a/lib/pages/player/control_bar/control_bar.dart +++ b/lib/pages/player/control_bar/control_bar.dart @@ -86,9 +86,9 @@ class ControlBar extends HookWidget { begin: Alignment.topCenter, end: Alignment.bottomCenter, colors: [ - Colors.black87.withValues(alpha: 0), - Colors.black87.withValues(alpha: 0.3), - Colors.black87.withValues(alpha: 0.8), + Colors.black.withValues(alpha: 0), + Colors.black.withValues(alpha: 0.25), + Colors.black.withValues(alpha: 0.65), ], ), ), @@ -105,7 +105,7 @@ class ControlBar extends HookWidget { mainAxisAlignment: MainAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.center, children: [ - const SizedBox(width: 4), + const SizedBox(width: 2), Stack( alignment: Alignment.center, children: [ @@ -116,7 +116,7 @@ class ControlBar extends HookWidget { displayIsPlaying.value ? Icons.pause_rounded : Icons.play_arrow_rounded, - size: 36, + size: 32, color: color, ), onPressed: () { @@ -133,8 +133,8 @@ class ControlBar extends HookWidget { ), if (isInitializing) SizedBox( - width: 36, - height: 36, + width: 32, + height: 32, child: CircularProgressIndicator( strokeWidth: 4, color: Theme.of(context).colorScheme.surface, @@ -146,7 +146,7 @@ class ControlBar extends HookWidget { tooltip: '${t.stop} ( Ctrl + C )', icon: Icon( Icons.stop_rounded, - size: 28, + size: 26, color: color, ), onPressed: () { @@ -162,7 +162,7 @@ class ControlBar extends HookWidget { tooltip: '${t.previous} ( Ctrl + ← )', icon: Icon( Icons.skip_previous_rounded, - size: 28, + size: 26, color: color, ), onPressed: () { @@ -176,7 +176,7 @@ class ControlBar extends HookWidget { tooltip: '${t.next} ( Ctrl + → )', icon: Icon( Icons.skip_next_rounded, - size: 28, + size: 26, color: color, ), onPressed: () { @@ -263,6 +263,7 @@ class ControlBar extends HookWidget { fontWeight: item == rate ? FontWeight.bold : FontWeight.w100, + height: 1, ), ), onTap: () async { @@ -288,7 +289,7 @@ class ControlBar extends HookWidget { ), ), ), - if (MediaQuery.of(context).size.width < 600) + if (MediaQuery.of(context).size.width < 640) Builder( builder: (context) => IconButton( tooltip: '${t.volume}: $volume', @@ -307,7 +308,7 @@ class ControlBar extends HookWidget { style: ButtonStyle(overlayColor: overlayColor), ), ), - if (MediaQuery.of(context).size.width >= 600) + if (MediaQuery.of(context).size.width >= 640) SizedBox( width: 160, child: VolumeControl( @@ -670,7 +671,7 @@ class ControlBar extends HookWidget { ), ], ), - const SizedBox(width: 4), + const SizedBox(width: 2), ], ), ], diff --git a/lib/widgets/title_bar.dart b/lib/widgets/title_bar.dart index 4cee2af..42637d5 100644 --- a/lib/widgets/title_bar.dart +++ b/lib/widgets/title_bar.dart @@ -40,8 +40,8 @@ class TitleBar extends HookWidget { begin: Alignment.topCenter, end: Alignment.bottomCenter, colors: [ - Colors.black87.withValues(alpha: 0.8), - Colors.black87.withValues(alpha: 0.3), + Colors.black87.withValues(alpha: 0.6), + Colors.black87.withValues(alpha: 0.25), Colors.black87.withValues(alpha: 0), ], ), From df763f29d1a197ce736f200c17f27cbfd2a6d27b Mon Sep 17 00:00:00 2001 From: 22 <60903333+nini22P@users.noreply.github.com> Date: Wed, 17 Sep 2025 18:53:56 +0800 Subject: [PATCH 27/31] feat: improve the performance of the user interface and file list --- android/app/build.gradle | 2 +- lib/hooks/use_cover.dart | 29 ++++----- lib/hooks/use_gesture.dart | 6 +- lib/hooks/use_keyboard.dart | 29 ++++----- lib/hooks/use_volume.dart | 5 +- lib/l10n/iso_639.dart | 8 +-- lib/l10n/languages.dart | 2 +- lib/main.dart | 1 - lib/models/file.dart | 3 +- lib/models/storages/ftp.dart | 65 ++++++++++++------- lib/models/storages/local.dart | 52 ++++++++------- lib/models/storages/webdav.dart | 61 +++++++++++------ lib/pages/player/control_bar/control_bar.dart | 29 +++++---- .../player/overlays/controls_overlay.dart | 65 ++++++------------- lib/pages/player/overlays/play_queue.dart | 12 ++-- lib/pages/player/overlays/speed_selector.dart | 2 +- lib/pages/player/player.dart | 53 +++++++-------- lib/pages/storages/files.dart | 27 +++++--- lib/store/use_play_queue_store.dart | 13 ++-- lib/theme.dart | 18 ++--- lib/utils/build_file_uri.dart | 20 ------ lib/utils/check_content_type.dart | 4 ++ lib/utils/files_filter.dart | 12 +++- lib/utils/files_sort.dart | 59 ++++------------- lib/utils/find_subtitle.dart | 45 ------------- lib/utils/get_subtitle_map.dart | 35 ++++++++++ lib/utils/get_subtitle_title.dart | 42 ------------ lib/widgets/card.dart | 6 +- 28 files changed, 312 insertions(+), 393 deletions(-) delete mode 100644 lib/utils/build_file_uri.dart delete mode 100644 lib/utils/find_subtitle.dart create mode 100644 lib/utils/get_subtitle_map.dart delete mode 100644 lib/utils/get_subtitle_title.dart diff --git a/android/app/build.gradle b/android/app/build.gradle index c9c8bdc..60999ad 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -92,7 +92,7 @@ task downloadFiles(type: Exec) { def calculatedMD5 = MessageDigest.getInstance("MD5").digest(Files.readAllBytes(destFile.toPath())).encodeHex().toString() if (calculatedMD5 != fileInfo.md5) { - throw new FileNotFoundException("MD5 verification failed for ${destFile}") + throw new GradleException("MD5 verification failed for ${destFile}") } } } diff --git a/lib/hooks/use_cover.dart b/lib/hooks/use_cover.dart index 6295ba7..3467bd2 100644 --- a/lib/hooks/use_cover.dart +++ b/lib/hooks/use_cover.dart @@ -2,18 +2,14 @@ import 'package:collection/collection.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:flutter_zustand/flutter_zustand.dart'; import 'package:iris/models/file.dart'; -import 'package:iris/models/player.dart'; import 'package:iris/models/storages/local.dart'; import 'package:iris/models/storages/storage.dart'; import 'package:iris/store/use_play_queue_store.dart'; import 'package:iris/store/use_storage_store.dart'; import 'package:iris/utils/files_filter.dart'; -import 'package:provider/provider.dart'; FileItem? useCover() { final context = useContext(); - final isPlaying = - context.select((player) => player.isPlaying); final playQueue = usePlayQueueStore().select(context, (state) => state.playQueue); @@ -24,10 +20,10 @@ FileItem? useCover() { () => playQueue.indexWhere((element) => element.index == currentIndex), [playQueue, currentIndex]); - final PlayQueueItem? currentPlay = useMemoized( + final FileItem? file = useMemoized( () => playQueue.isEmpty || currentPlayIndex < 0 ? null - : playQueue[currentPlayIndex], + : playQueue[currentPlayIndex].file, [playQueue, currentPlayIndex]); final localStoragesFuture = @@ -37,28 +33,27 @@ FileItem? useCover() { final storages = useStorageStore().select(context, (state) => state.storages); final Storage? storage = useMemoized( - () => currentPlay?.file == null + () => file == null ? null - : [...localStorages, ...storages].firstWhereOrNull( - (storage) => storage.id == currentPlay?.file.storageId), - [currentPlay?.file, localStorages, storages]); + : [...localStorages, ...storages] + .firstWhereOrNull((storage) => storage.id == file.storageId), + [file, localStorages, storages]); final cover = useState(null); useEffect(() { () async { - final dir = currentPlay?.file == null || currentPlay!.file.path.isEmpty - ? [] - : ([...currentPlay.file.path]..removeLast()); - - if (storage == null || currentPlay?.file.type != ContentType.audio) { + if (storage == null || file == null || file.type != ContentType.audio) { cover.value = null; return; } + final dir = + file.path.isEmpty ? [] : ([...file.path]..removeLast()); + final files = await storage.getFiles(dir); - final images = filesFilter(files, [ContentType.image]); + final images = filesFilter(files, types: [ContentType.image]); cover.value = images.firstWhereOrNull((image) => image.name.split('.').first.toLowerCase() == 'cover') ?? @@ -68,7 +63,7 @@ FileItem? useCover() { images.firstOrNull; }(); return null; - }, [storage, isPlaying]); + }, [storage, file]); return cover.value; } diff --git a/lib/hooks/use_gesture.dart b/lib/hooks/use_gesture.dart index ee2faf8..817a6a2 100644 --- a/lib/hooks/use_gesture.dart +++ b/lib/hooks/use_gesture.dart @@ -98,7 +98,7 @@ Gesture useGesture({ void onDoubleTapDown(TapDownDetails details) { if (details.kind == PointerDeviceKind.touch) { - final screenWidth = MediaQuery.of(context).size.width; + final screenWidth = MediaQuery.sizeOf(context).width; final tapDx = details.globalPosition.dx; if (tapDx > screenWidth * 0.7) { @@ -195,7 +195,7 @@ Gesture useGesture({ if (details.kind == PointerDeviceKind.touch) { const double edgeDeadZone = 48.0; - final screenSize = MediaQuery.of(context).size; + final screenSize = MediaQuery.sizeOf(context); final startDx = details.globalPosition.dx; if (startDx < edgeDeadZone || startDx > screenSize.width - edgeDeadZone) { @@ -257,7 +257,7 @@ Gesture useGesture({ // 仅在垂直滑动开始时判断一次左右区域 if (!isLeftGesture.value && !isRightGesture.value) { isLeftGesture.value = - startOffset.dx < MediaQuery.of(context).size.width / 2; + startOffset.dx < MediaQuery.sizeOf(context).width / 2; isRightGesture.value = !isLeftGesture.value; } diff --git a/lib/hooks/use_keyboard.dart b/lib/hooks/use_keyboard.dart index 54cb567..25213f2 100644 --- a/lib/hooks/use_keyboard.dart +++ b/lib/hooks/use_keyboard.dart @@ -30,13 +30,6 @@ KeyboardEvent useKeyboard({ final player = context.read(); - final isPlaying = - context.select((player) => player.isPlaying); - - final shuffle = useAppStore().state.shuffle; - final isFullScreen = usePlayerUiStore().state.isFullScreen; - final isShowControl = usePlayerUiStore().state.isShowControl; - void onKeyEvent(KeyEvent event) async { if (event.runtimeType == KeyDownEvent) { if (HardwareKeyboard.instance.isAltPressed) { @@ -56,6 +49,7 @@ KeyboardEvent useKeyboard({ } if (HardwareKeyboard.instance.isControlPressed) { + final appState = useAppStore().state; switch (event.logicalKey) { // 上一个 case LogicalKeyboardKey.arrowLeft: @@ -86,10 +80,12 @@ KeyboardEvent useKeyboard({ // 随机 case LogicalKeyboardKey.keyX: showControl(); - shuffle - ? usePlayQueueStore().sort() - : usePlayQueueStore().shuffle(); - useAppStore().updateShuffle(!shuffle); + if (appState.shuffle) { + usePlayQueueStore().sort(); + } else { + usePlayQueueStore().shuffle(); + } + useAppStore().updateShuffle(!appState.shuffle); break; // 循环 case LogicalKeyboardKey.keyR: @@ -136,12 +132,13 @@ KeyboardEvent useKeyboard({ return; } + final playerUiState = usePlayerUiStore().state; switch (event.logicalKey) { // 播放 | 暂停 case LogicalKeyboardKey.space: case LogicalKeyboardKey.mediaPlayPause: showControl(); - if (isPlaying) { + if (context.read().isPlaying) { useAppStore().updateAutoPlay(false); player.pause(); } else { @@ -191,7 +188,7 @@ KeyboardEvent useKeyboard({ break; // 退出全屏 case LogicalKeyboardKey.escape: - if (isDesktop && isFullScreen) { + if (isDesktop && playerUiState.isFullScreen) { usePlayerUiStore().updateFullScreen(false); } break; @@ -199,7 +196,7 @@ KeyboardEvent useKeyboard({ case LogicalKeyboardKey.enter: case LogicalKeyboardKey.f11: if (isDesktop) { - usePlayerUiStore().updateFullScreen(!isFullScreen); + usePlayerUiStore().updateFullScreen(!playerUiState.isFullScreen); } break; case LogicalKeyboardKey.tab: @@ -229,7 +226,7 @@ KeyboardEvent useKeyboard({ switch (event.logicalKey) { // 快退 case LogicalKeyboardKey.arrowLeft: - if (isShowControl) { + if (usePlayerUiStore().state.isShowControl) { showControl(); } else { showProgress(); @@ -238,7 +235,7 @@ KeyboardEvent useKeyboard({ break; // 快进 case LogicalKeyboardKey.arrowRight: - if (isShowControl) { + if (usePlayerUiStore().state.isShowControl) { showControl(); } else { showProgress(); diff --git a/lib/hooks/use_volume.dart b/lib/hooks/use_volume.dart index fcfa6be..e6788c3 100644 --- a/lib/hooks/use_volume.dart +++ b/lib/hooks/use_volume.dart @@ -16,7 +16,10 @@ ValueNotifier useVolume(bool isGesture) { } catch (e) { logger('Error getting volume: $e'); } - return () => volume.value = null; + return () { + volume.value = null; + FlutterVolumeController.updateShowSystemUI(true); + }; }, [isGesture]); useEffect(() { diff --git a/lib/l10n/iso_639.dart b/lib/l10n/iso_639.dart index f00489e..6cd3fdd 100644 --- a/lib/l10n/iso_639.dart +++ b/lib/l10n/iso_639.dart @@ -1,15 +1,15 @@ class Info { final List en; - Info({required this.en}); + const Info({required this.en}); } -Map customLanguageCodes = { +const Map customLanguageCodes = { 'chs': Info(en: ['Chinese (Simplified)']), 'cht': Info(en: ['Chinese (Traditional)']), }; -Map iso_639_1 = { +const Map iso_639_1 = { 'aa': Info(en: ['Afar']), 'ab': Info(en: ['Abkhazian']), 'ae': Info(en: ['Avestan']), @@ -203,7 +203,7 @@ Map iso_639_1 = { 'zu': Info(en: ['Zulu']), }; -Map iso_639_2 = { +const Map iso_639_2 = { 'aar': Info(en: ['Afar']), 'abk': Info(en: ['Abkhazian']), 'ace': Info(en: ['Achinese']), diff --git a/lib/l10n/languages.dart b/lib/l10n/languages.dart index 69dafea..57b6516 100644 --- a/lib/l10n/languages.dart +++ b/lib/l10n/languages.dart @@ -1,4 +1,4 @@ -final Map languages = { +const Map languages = { 'zh': '简体中文', 'en': 'English', }; diff --git a/lib/main.dart b/lib/main.dart index cca1849..549d3aa 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -65,7 +65,6 @@ void main(List arguments) async { backgroundColor: Colors.transparent, skipTaskbar: false, titleBarStyle: TitleBarStyle.hidden, - title: INFO.title, ); windowManager.waitUntilReadyToShow(windowOptions, () async { diff --git a/lib/models/file.dart b/lib/models/file.dart index 2d4351d..83340c9 100644 --- a/lib/models/file.dart +++ b/lib/models/file.dart @@ -6,7 +6,6 @@ part 'file.freezed.dart'; part 'file.g.dart'; enum ContentType { - dir, video, audio, image, @@ -38,7 +37,7 @@ abstract class FileItem with _$FileItem { factory FileItem.fromJson(Map json) => _$FileItemFromJson(json); - String getID() => '$storageId:$uri}'; + String getID() => '$storageId:$uri'; } @freezed diff --git a/lib/models/storages/ftp.dart b/lib/models/storages/ftp.dart index 4d7e6f5..0a3f144 100644 --- a/lib/models/storages/ftp.dart +++ b/lib/models/storages/ftp.dart @@ -1,11 +1,10 @@ import 'dart:convert'; - import 'package:iris/models/file.dart'; import 'package:iris/models/storages/storage.dart'; -import 'package:iris/utils/build_file_uri.dart'; import 'package:iris/utils/check_content_type.dart'; -import 'package:iris/utils/find_subtitle.dart'; +import 'package:iris/utils/get_subtitle_map.dart'; import 'package:iris/utils/logger.dart'; +import 'package:path/path.dart' as p; import 'package:pure_ftp/pure_ftp.dart'; Future> getFTPFiles( @@ -36,30 +35,46 @@ Future> getFTPFiles( final baseUri = 'ftp?host=${storage.host}&port=${storage.port}&path=${path.join('/').replaceAll('//', '/')}'; - final allFileNames = files.map((file) => file.name).toList(); + String getUri(String fileName) { + final separator = baseUri.endsWith('/') ? '' : '/'; + return Uri.encodeFull('$baseUri$separator$fileName'); + } + + final subtitleMap = getSubtitleMap( + files: files, + baseUri: baseUri, + getName: (file) => file.name, + getUri: (file) => getUri(file.name), + ); + + List fileItems = []; - return await Future.wait(files.map( - (file) async => FileItem( - storageId: storage.id, - storageType: StorageType.ftp, - name: file.name, - uri: buildFileUri(baseUri, file.name), - path: [...path, file.name], - isDir: file.isDirectory, - size: file.isDirectory ? 0 : file.info?.size ?? 0, - lastModified: file.info?.modifyTime != null - ? DateTime.tryParse(file.info!.modifyTime!) - : null, - type: file.isDirectory ? ContentType.dir : checkContentType(file.name), - subtitles: await findSubtitle( - allFileNames, - file.name, - baseUri, - ), - ), - )); + for (final file in files) { + if (file.isDirectory || isMediaFile(file.name)) { + final basename = p.basenameWithoutExtension(file.name).split('.').first; + fileItems.add( + FileItem( + storageId: storage.id, + storageType: StorageType.ftp, + name: file.name, + uri: getUri(file.name), + path: [...path, file.name], + isDir: file.isDirectory, + size: file.isDirectory ? 0 : file.info?.size ?? 0, + lastModified: file.info?.modifyTime != null + ? DateTime.tryParse(file.info!.modifyTime!) + : null, + type: checkContentType(file.name), + subtitles: + isVideoFile(file.name) ? subtitleMap[basename] ?? [] : [], + ), + ); + } + } + + return fileItems; } catch (error) { - logger('Error testing FTP: $error'); + logger('Error getting FTP files: $error'); return []; } } diff --git a/lib/models/storages/local.dart b/lib/models/storages/local.dart index 44141ad..76673b6 100644 --- a/lib/models/storages/local.dart +++ b/lib/models/storages/local.dart @@ -10,7 +10,7 @@ import 'package:iris/store/use_app_store.dart'; import 'package:iris/store/use_play_queue_store.dart'; import 'package:iris/utils/files_filter.dart'; import 'package:iris/utils/files_sort.dart'; -import 'package:iris/utils/find_subtitle.dart'; +import 'package:iris/utils/get_subtitle_map.dart'; import 'package:iris/utils/get_localizations.dart'; import 'package:iris/utils/logger.dart'; import 'package:iris/utils/path_conv.dart'; @@ -19,6 +19,7 @@ import 'package:path/path.dart' as p; import 'package:iris/models/file.dart'; import 'package:iris/utils/check_content_type.dart'; import 'package:saf_util/saf_util.dart'; +import 'package:saf_util/saf_util_platform_interface.dart'; Future> getLocalStorages( BuildContext context, @@ -156,7 +157,7 @@ Future getLocalPlayQueue(String filePath) async { ).getFiles(dirPath); final List sortedFiles = filesSort(files: files); final List filteredFiles = - filesFilter(sortedFiles, [ContentType.video, ContentType.audio]); + filesFilter(sortedFiles, types: [ContentType.video, ContentType.audio]); final List playQueue = filteredFiles .asMap() .entries @@ -266,7 +267,7 @@ Future> getLocalFiles( isDir: isDir, size: isDir ? 0 : stat.size, lastModified: stat.modified, - type: isDir ? ContentType.dir : checkContentType(entity.path), + type: checkContentType(entity.path), subtitles: [], )); } @@ -296,25 +297,32 @@ Future pickContentFile() async { } Future> getContentFiles(String uri) async { - final list = await SafUtil().list(uri); + final files = await SafUtil().list(uri); - final allFileNames = list.map((e) => e.name).toList(); + final subtitleMap = getSubtitleMap( + files: files, + baseUri: uri, + getName: (file) => file.name, + getUri: (file) => file.uri, + ); - return await Future.wait(list - .map((file) async => FileItem( - name: file.name, - uri: file.uri, - path: [uri, file.name], - isDir: file.isDir, - size: file.isDir ? 0 : file.length, - lastModified: - DateTime.fromMillisecondsSinceEpoch(file.lastModified), - type: file.isDir ? ContentType.dir : checkContentType(file.name), - subtitles: await findSubtitle( - allFileNames, - file.name, - uri, - ), - )) - .toList()); + List fileItems = []; + + for (final file in files) { + if (file.isDir || isMediaFile(file.name)) { + final basename = p.basenameWithoutExtension(file.name).split('.').first; + fileItems.add(FileItem( + name: file.name, + uri: file.uri, + path: [uri, file.name], + isDir: file.isDir, + size: file.isDir ? 0 : file.length, + lastModified: DateTime.fromMillisecondsSinceEpoch(file.lastModified), + type: checkContentType(file.name), + subtitles: isVideoFile(file.name) ? subtitleMap[basename] ?? [] : [], + )); + } + } + + return fileItems; } diff --git a/lib/models/storages/webdav.dart b/lib/models/storages/webdav.dart index 9ae9e2d..515665f 100644 --- a/lib/models/storages/webdav.dart +++ b/lib/models/storages/webdav.dart @@ -1,9 +1,9 @@ import 'dart:convert'; import 'package:iris/models/storages/storage.dart'; -import 'package:iris/utils/build_file_uri.dart'; import 'package:iris/utils/check_content_type.dart'; -import 'package:iris/utils/find_subtitle.dart'; +import 'package:iris/utils/get_subtitle_map.dart'; import 'package:iris/utils/logger.dart'; +import 'package:path/path.dart' as p; import 'package:webdav_client/webdav_client.dart' as webdav; import 'package:iris/models/file.dart'; @@ -61,7 +61,6 @@ Future> getWebDAVFiles( client.setReceiveTimeout(8000); var files = await client.readDir(path.join('/')); - final allFileNames = files.map((f) => f.name as String).toList(); final cleanPathSegments = path.map((e) => e.replaceAll('/', '')).toList(); final baseUri = Uri( @@ -72,28 +71,50 @@ Future> getWebDAVFiles( ); final baseUriString = baseUri.toString(); - return await Future.wait( - files.map((file) async { - return FileItem( + String getUri(String fileName) { + try { + final dirUri = Uri.parse( + baseUriString.endsWith('/') ? baseUriString : '$baseUriString/'); + return dirUri.resolve(fileName).toString(); + } catch (e) { + final separator = baseUriString.endsWith('/') ? '' : '/'; + return '$baseUriString$separator$fileName'; + } + } + + final subtitleMap = getSubtitleMap( + files: files, + baseUri: baseUriString, + getName: (file) => file.name ?? '', + getUri: (file) => getUri(file.name ?? ''), + ); + + List fileItems = []; + + for (final file in files) { + final fileName = file.name; + + if (fileName == null) continue; + + final isDir = file.isDir; + if (isDir == true || isMediaFile(fileName)) { + final basename = p.basenameWithoutExtension(fileName).split('.').first; + fileItems.add(FileItem( storageId: id, storageType: StorageType.webdav, - name: '${file.name}', - uri: buildFileUri(baseUriString, file.name!), - path: [...path, '${file.name}'], + name: fileName, + uri: getUri(fileName), + path: [...path, fileName], isDir: file.isDir ?? false, size: file.size ?? 0, lastModified: file.mTime, - type: file.isDir ?? false - ? ContentType.dir - : checkContentType(file.name!), - subtitles: await findSubtitle( - allFileNames, - file.name as String, - baseUriString, - ), - ); - }), - ); + type: checkContentType(fileName), + subtitles: isVideoFile(fileName) ? subtitleMap[basename] ?? [] : [], + )); + } + } + + return fileItems; } String getWebDAVAuth(WebDAVStorage storage) => diff --git a/lib/pages/player/control_bar/control_bar.dart b/lib/pages/player/control_bar/control_bar.dart index 2a57e4d..a9f5ada 100644 --- a/lib/pages/player/control_bar/control_bar.dart +++ b/lib/pages/player/control_bar/control_bar.dart @@ -42,6 +42,7 @@ class ControlBar extends HookWidget { @override Widget build(BuildContext context) { + final width = MediaQuery.sizeOf(context).width; final t = getLocalizations(context); final isPlaying = @@ -95,7 +96,7 @@ class ControlBar extends HookWidget { child: Column( children: [ Visibility( - visible: MediaQuery.of(context).size.width < 1024 || !isDesktop, + visible: width < 1024 || !isDesktop, child: ControlBarSlider( showControl: showControl, color: color, @@ -185,7 +186,7 @@ class ControlBar extends HookWidget { }, style: ButtonStyle(overlayColor: overlayColor), ), - if (MediaQuery.of(context).size.width >= 768) + if (width >= 768) Builder( builder: (context) => IconButton( tooltip: @@ -205,7 +206,7 @@ class ControlBar extends HookWidget { style: ButtonStyle(overlayColor: overlayColor), ), ), - if (MediaQuery.of(context).size.width >= 768) + if (width >= 768) Builder( builder: (context) => IconButton( tooltip: @@ -225,7 +226,7 @@ class ControlBar extends HookWidget { style: ButtonStyle(overlayColor: overlayColor), ), ), - if (MediaQuery.of(context).size.width >= 768) + if (width >= 768) IconButton( tooltip: '${t.video_zoom}: ${fit == BoxFit.contain ? t.fit : fit == BoxFit.fill ? t.stretch : fit == BoxFit.cover ? t.crop : '100%'} ( Ctrl + V )', @@ -246,7 +247,7 @@ class ControlBar extends HookWidget { }, style: ButtonStyle(overlayColor: overlayColor), ), - if (MediaQuery.of(context).size.width > 600) + if (width > 600) PopupMenuButton( key: rateMenuKey, clipBehavior: Clip.hardEdge, @@ -289,7 +290,7 @@ class ControlBar extends HookWidget { ), ), ), - if (MediaQuery.of(context).size.width < 640) + if (width < 640) Builder( builder: (context) => IconButton( tooltip: '${t.volume}: $volume', @@ -308,7 +309,7 @@ class ControlBar extends HookWidget { style: ButtonStyle(overlayColor: overlayColor), ), ), - if (MediaQuery.of(context).size.width >= 640) + if (width >= 640) SizedBox( width: 160, child: VolumeControl( @@ -321,14 +322,14 @@ class ControlBar extends HookWidget { Expanded( child: Visibility( visible: - MediaQuery.of(context).size.width >= 1024 && isDesktop, + width >= 1024 && isDesktop, child: ControlBarSlider( showControl: showControl, color: color, ), ), ), - if (MediaQuery.of(context).size.width >= 420) + if (width >= 420) IconButton( tooltip: '${t.subtitle_and_audio_track} ( S )', icon: Icon( @@ -467,7 +468,7 @@ class ControlBar extends HookWidget { showControl(); }, ), - if (MediaQuery.of(context).size.width < 768) + if (width < 768) PopupMenuItem( child: ListTile( mouseCursor: SystemMouseCursors.click, @@ -495,7 +496,7 @@ class ControlBar extends HookWidget { useAppStore().updateShuffle(!shuffle); }, ), - if (MediaQuery.of(context).size.width < 768) + if (width < 768) PopupMenuItem( child: ListTile( mouseCursor: SystemMouseCursors.click, @@ -526,7 +527,7 @@ class ControlBar extends HookWidget { useAppStore().toggleRepeat(); }, ), - if (MediaQuery.of(context).size.width < 768) + if (width < 768) PopupMenuItem( child: ListTile( mouseCursor: SystemMouseCursors.click, @@ -555,7 +556,7 @@ class ControlBar extends HookWidget { useAppStore().toggleFit(); }, ), - if (MediaQuery.of(context).size.width <= 460) + if (width <= 460) PopupMenuItem( child: ListTile( mouseCursor: SystemMouseCursors.click, @@ -567,7 +568,7 @@ class ControlBar extends HookWidget { ), onTap: () => showControlForHover(showRateDialog(context)), ), - if (MediaQuery.of(context).size.width < 420) + if (width < 420) PopupMenuItem( child: ListTile( mouseCursor: SystemMouseCursors.click, diff --git a/lib/pages/player/overlays/controls_overlay.dart b/lib/pages/player/overlays/controls_overlay.dart index 155df80..b6c49d4 100644 --- a/lib/pages/player/overlays/controls_overlay.dart +++ b/lib/pages/player/overlays/controls_overlay.dart @@ -19,7 +19,7 @@ import 'package:provider/provider.dart'; class ControlsOverlay extends HookWidget { const ControlsOverlay({ super.key, - required this.currentPlay, + required this.file, required this.title, required this.showControl, required this.showControlForHover, @@ -27,7 +27,7 @@ class ControlsOverlay extends HookWidget { required this.showProgress, }); - final PlayQueueItem? currentPlay; + final FileItem? file; final String title; final Function() showControl; final Future Function(Future callback) showControlForHover; @@ -116,11 +116,7 @@ class ControlsOverlay extends HookWidget { return Stack( children: [ - Positioned( - left: 0, - top: 0, - right: 0, - bottom: 0, + Positioned.fill( child: MouseRegion( cursor: isShowControl || isPlaying == false ? SystemMouseCursors.basic @@ -143,11 +139,7 @@ class ControlsOverlay extends HookWidget { children: [ // 播放速度 if (isSpeedSelectorVisible.value) - Positioned( - left: 0, - top: 0, - right: 0, - bottom: 0, + Positioned.fill( child: SpeedSelector( selectedSpeed: selectedSpeed.value, visualOffset: visualOffset.value, @@ -156,11 +148,7 @@ class ControlsOverlay extends HookWidget { ), // 屏幕亮度 if (gesture.isLeftGesture && gesture.brightness != null) - Positioned( - left: 0, - top: 0, - right: 0, - bottom: 0, + Positioned.fill( child: Center( child: Container( padding: const EdgeInsets.fromLTRB(12, 12, 18, 12), @@ -198,11 +186,7 @@ class ControlsOverlay extends HookWidget { ), // 音量 if (gesture.isRightGesture && gesture.volume != null) - Positioned( - left: 0, - top: 0, - right: 0, - bottom: 0, + Positioned.fill( child: Center( child: Container( padding: const EdgeInsets.fromLTRB(12, 12, 18, 12), @@ -241,7 +225,7 @@ class ControlsOverlay extends HookWidget { ), if (isShowProgress && !isShowControl && - currentPlay?.file.type == ContentType.video) + file?.type == ContentType.video) Positioned( left: -28, right: -28, @@ -254,7 +238,7 @@ class ControlsOverlay extends HookWidget { ), if (isShowProgress && !isShowControl && - currentPlay?.file.type == ContentType.video) + file?.type == ContentType.video) Positioned( left: 12, top: 12, @@ -262,7 +246,7 @@ class ControlsOverlay extends HookWidget { crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( - currentPlay != null ? title : '', + file != null ? title : '', style: TextStyle( color: Colors.white, fontSize: 20, @@ -282,7 +266,7 @@ class ControlsOverlay extends HookWidget { ), if (isShowProgress && !isShowControl && - currentPlay?.file.type == ContentType.video) + file?.type == ContentType.video) Positioned( left: 12, bottom: 6, @@ -317,9 +301,7 @@ class ControlsOverlay extends HookWidget { AnimatedPositioned( duration: const Duration(milliseconds: 200), curve: Curves.easeInOutCubicEmphasized, - top: isShowControl || currentPlay?.file.type != ContentType.video - ? 0 - : -72, + top: isShowControl || file?.type != ContentType.video ? 0 : -72, left: 0, right: 0, child: MouseRegion( @@ -342,25 +324,20 @@ class ControlsOverlay extends HookWidget { AnimatedPositioned( duration: const Duration(milliseconds: 200), curve: Curves.easeInOutCubicEmphasized, - bottom: isShowControl || currentPlay?.file.type != ContentType.video - ? 0 - : -128, + bottom: isShowControl || file?.type != ContentType.video ? 0 : -128, left: 0, right: 0, child: Align( alignment: Alignment.bottomCenter, - child: SizedBox( - width: MediaQuery.of(context).size.width, - child: MouseRegion( - onHover: gesture.onHover, - child: GestureDetector( - onTap: () => showControl(), - child: ControlBar( - showControl: showControl, - showControlForHover: showControlForHover, - color: contentColor, - overlayColor: overlayColor, - ), + child: MouseRegion( + onHover: gesture.onHover, + child: GestureDetector( + onTap: () => showControl(), + child: ControlBar( + showControl: showControl, + showControlForHover: showControlForHover, + color: contentColor, + overlayColor: overlayColor, ), ), ), diff --git a/lib/pages/player/overlays/play_queue.dart b/lib/pages/player/overlays/play_queue.dart index 89f545b..c77bd6c 100644 --- a/lib/pages/player/overlays/play_queue.dart +++ b/lib/pages/player/overlays/play_queue.dart @@ -27,11 +27,13 @@ class PlayQueue extends HookWidget { () => playQueue.indexWhere((element) => element.index == currentIndex), [playQueue, currentIndex]); - ItemScrollController itemScrollController = ItemScrollController(); - ScrollOffsetController scrollOffsetController = ScrollOffsetController(); - ItemPositionsListener itemPositionsListener = - ItemPositionsListener.create(); - ScrollOffsetListener scrollOffsetListener = ScrollOffsetListener.create(); + final itemScrollController = useMemoized(() => ItemScrollController(), []); + final scrollOffsetController = + useMemoized(() => ScrollOffsetController(), []); + final itemPositionsListener = + useMemoized(() => ItemPositionsListener.create(), []); + final scrollOffsetListener = + useMemoized(() => ScrollOffsetListener.create(), []); useEffect(() { WidgetsBinding.instance.addPostFrameCallback((_) { diff --git a/lib/pages/player/overlays/speed_selector.dart b/lib/pages/player/overlays/speed_selector.dart index d83e90f..7eac33b 100644 --- a/lib/pages/player/overlays/speed_selector.dart +++ b/lib/pages/player/overlays/speed_selector.dart @@ -16,7 +16,7 @@ class SpeedSelector extends HookWidget { @override Widget build(BuildContext context) { - final screenSize = MediaQuery.of(context).size; + final screenSize = MediaQuery.sizeOf(context); const double itemWidth = speedSelectorItemWidth; const double horizontalPadding = 12.0; diff --git a/lib/pages/player/player.dart b/lib/pages/player/player.dart index 9d798dd..f6966e9 100644 --- a/lib/pages/player/player.dart +++ b/lib/pages/player/player.dart @@ -29,8 +29,6 @@ class Player extends HookWidget { @override Widget build(BuildContext context) { - final isPlaying = - context.select((player) => player.isPlaying); final width = context.select((player) => player.width); final height = context.select((player) => player.height); @@ -55,19 +53,19 @@ class Player extends HookWidget { () => playQueue.indexWhere((element) => element.index == currentIndex), [playQueue, currentIndex]); - final PlayQueueItem? currentPlay = useMemoized( + final FileItem? file = useMemoized( () => playQueue.isEmpty || currentPlayIndex < 0 ? null - : playQueue[currentPlayIndex], + : playQueue[currentPlayIndex].file, [playQueue, currentPlayIndex]); final title = useMemoized( - () => currentPlay != null + () => file != null ? playQueue.length > 1 - ? '[${currentPlayIndex + 1}/${playQueue.length}] ${currentPlay.file.name}' - : currentPlay.file.name + ? '[${currentPlayIndex + 1}/${playQueue.length}] ${file.name}' + : file.name : INFO.title, - [currentPlay, currentPlayIndex, playQueue]); + [file, currentPlayIndex, playQueue]); final focusNode = useFocusNode(); @@ -158,31 +156,32 @@ class Player extends HookWidget { windowManager.setTitle(title); } return; - }, [title, isPlaying]); + }, [title]); + + final Size windowSize = useMemoized( + () => MediaQuery.sizeOf(context), [MediaQuery.sizeOf(context)]); final scaleFactor = useMemoized( - () => - View.of(context).physicalSize.width / - MediaQuery.of(context).size.width, - [View.of(context).physicalSize.width, MediaQuery.of(context).size.width], + () => View.of(context).physicalSize.width / windowSize.width, + [View.of(context).physicalSize.width], ); final videoViewSize = useMemoized(() { if (fit != BoxFit.none || width == 0 || height == 0) { - return MediaQuery.of(context).size; + return windowSize; } else { return Size(width / scaleFactor, height / scaleFactor); } - }, [fit, MediaQuery.of(context).size, width, height, scaleFactor]); + }, [fit, windowSize, width, height, scaleFactor]); final videoViewOffset = useMemoized( () => fit == BoxFit.none ? Offset( - (MediaQuery.of(context).size.width - videoViewSize.width) / 2, - (MediaQuery.of(context).size.height - videoViewSize.height) / 2, + (windowSize.width - videoViewSize.width) / 2, + (windowSize.height - videoViewSize.height) / 2, ) : Offset(0, 0), - [fit, MediaQuery.of(context).size, videoViewSize]); + [fit, windowSize, videoViewSize]); return DropTarget( onDragDone: (details) async { @@ -236,26 +235,18 @@ class Player extends HookWidget { width: videoViewSize.width, height: videoViewSize.height, child: VideoView( - key: ValueKey(currentPlay?.file.uri), + key: ValueKey(file?.uri), fit: fit, ), ), // Audio - if (currentPlay?.file.type == ContentType.audio) - Positioned( - left: 0, - top: 0, - right: 0, - bottom: 0, + if (file?.type == ContentType.audio) + Positioned.fill( child: Audio(cover: cover), ), - Positioned( - left: 0, - top: 0, - right: 0, - bottom: 0, + Positioned.fill( child: ControlsOverlay( - currentPlay: currentPlay, + file: file, title: title, showControl: showControl, showControlForHover: showControlForHover, diff --git a/lib/pages/storages/files.dart b/lib/pages/storages/files.dart index 8d52f1a..eadb55f 100644 --- a/lib/pages/storages/files.dart +++ b/lib/pages/storages/files.dart @@ -68,8 +68,11 @@ class Files extends HookWidget { final isError = result.error != null; final filteredFiles = useMemoized( - () => filesFilter(result.data ?? [], - [ContentType.dir, ContentType.video, ContentType.audio]), + () => filesFilter( + result.data ?? [], + types: [ContentType.video, ContentType.audio], + includeDirs: true, + ), [result.data]); final files = useMemoized( @@ -81,16 +84,18 @@ class Files extends HookWidget { ), [filteredFiles, sortBy, sortOrder, folderFirst]); - ItemScrollController itemScrollController = ItemScrollController(); - ScrollOffsetController scrollOffsetController = ScrollOffsetController(); - ItemPositionsListener itemPositionsListener = - ItemPositionsListener.create(); - ScrollOffsetListener scrollOffsetListener = ScrollOffsetListener.create(); + final itemScrollController = useMemoized(() => ItemScrollController(), []); + final scrollOffsetController = + useMemoized(() => ScrollOffsetController(), []); + final itemPositionsListener = + useMemoized(() => ItemPositionsListener.create(), []); + final scrollOffsetListener = + useMemoized(() => ScrollOffsetListener.create(), []); void play(List files, int index) async { final clickedFile = files[index]; final List filteredFiles = - filesFilter(files, [ContentType.video, ContentType.audio]); + filesFilter(files, types: [ContentType.video, ContentType.audio]); final List playQueue = filteredFiles .asMap() .entries @@ -152,9 +157,11 @@ class Files extends HookWidget { visualDensity: const VisualDensity( horizontal: 0, vertical: -4), leading: () { + if (files[index].isDir == true && + files[index].name.isNotEmpty) { + return const Icon(Icons.folder_rounded); + } switch (files[index].type) { - case ContentType.dir: - return const Icon(Icons.folder_rounded); case ContentType.video: return const Icon(Icons.movie_rounded); case ContentType.audio: diff --git a/lib/store/use_play_queue_store.dart b/lib/store/use_play_queue_store.dart index 15db8aa..36bb6bd 100644 --- a/lib/store/use_play_queue_store.dart +++ b/lib/store/use_play_queue_store.dart @@ -1,6 +1,6 @@ import 'dart:convert'; import 'dart:io'; -import 'package:collection/collection.dart'; +import 'dart:math'; import 'package:flutter_secure_storage/flutter_secure_storage.dart'; import 'package:flutter_zustand/flutter_zustand.dart'; import 'package:iris/models/file.dart'; @@ -45,13 +45,10 @@ class PlayQueueStore extends PersistentStore { } Future add(List files) async { - final int startIndex = state.playQueue.isEmpty - ? 0 - : state.playQueue - .sorted((a, b) => a.index.compareTo(b.index)) - .last - .index + - 1; + final int maxIndex = state.playQueue.isEmpty + ? -1 + : state.playQueue.map((e) => e.index).reduce(max); + final int startIndex = maxIndex + 1; final List playQueue = files .asMap() diff --git a/lib/theme.dart b/lib/theme.dart index 2830045..e8c24fa 100644 --- a/lib/theme.dart +++ b/lib/theme.dart @@ -21,9 +21,9 @@ ThemeData baseTheme(BuildContext context) { } ColorScheme customColorScheme = - ColorScheme.fromSeed(seedColor: Color(0xFFB3BCDF)); + ColorScheme.fromSeed(seedColor: const Color(0xFFB3BCDF)); ColorScheme customDarkColorScheme = ColorScheme.fromSeed( - seedColor: Color(0xFFB3BCDF), brightness: Brightness.dark); + seedColor: const Color(0xFFB3BCDF), brightness: Brightness.dark); class CustomTheme { final ThemeData light; @@ -42,13 +42,15 @@ CustomTheme getTheme({ ColorScheme darkColorScheme = darkDynamic != null ? darkDynamic.harmonized() : customDarkColorScheme; + final base = baseTheme(context); + final lightTheme = ThemeData( colorScheme: colorScheme, useMaterial3: true, textTheme: GoogleFonts.notoSansScTextTheme(), - popupMenuTheme: baseTheme(context).popupMenuTheme, - dropdownMenuTheme: baseTheme(context).dropdownMenuTheme, - listTileTheme: baseTheme(context).listTileTheme, + popupMenuTheme: base.popupMenuTheme, + dropdownMenuTheme: base.dropdownMenuTheme, + listTileTheme: base.listTileTheme, ); final darkTheme = ThemeData.dark(useMaterial3: true).copyWith( @@ -58,9 +60,9 @@ CustomTheme getTheme({ .copyWith(colorScheme: darkColorScheme) .textTheme, ), - popupMenuTheme: baseTheme(context).popupMenuTheme, - dropdownMenuTheme: baseTheme(context).dropdownMenuTheme, - listTileTheme: baseTheme(context).listTileTheme, + popupMenuTheme: base.popupMenuTheme, + dropdownMenuTheme: base.dropdownMenuTheme, + listTileTheme: base.listTileTheme, ); return CustomTheme(light: lightTheme, dark: darkTheme); diff --git a/lib/utils/build_file_uri.dart b/lib/utils/build_file_uri.dart deleted file mode 100644 index 35e29f5..0000000 --- a/lib/utils/build_file_uri.dart +++ /dev/null @@ -1,20 +0,0 @@ -import 'package:iris/utils/platform.dart'; - -String buildFileUri(String baseUri, String fileName) { - if (baseUri.startsWith('ftp?')) { - final separator = baseUri.endsWith('/') ? '' : '/'; - return Uri.encodeFull('$baseUri$separator$fileName'); - } - - if (isAndroid && baseUri.startsWith('content://')) { - return '$baseUri/${Uri.encodeComponent(fileName)}'; - } - - try { - final dirUri = Uri.parse(baseUri.endsWith('/') ? baseUri : '$baseUri/'); - return dirUri.resolve(fileName).toString(); - } catch (e) { - final separator = baseUri.endsWith('/') ? '' : '/'; - return '$baseUri$separator$fileName'; - } -} diff --git a/lib/utils/check_content_type.dart b/lib/utils/check_content_type.dart index 2c9b8bc..21b8b54 100644 --- a/lib/utils/check_content_type.dart +++ b/lib/utils/check_content_type.dart @@ -81,3 +81,7 @@ ContentType checkContentType(String name) { bool isMediaFile(String name) => checkContentType(name) == ContentType.video || checkContentType(name) == ContentType.audio; + +bool isVideoFile(String name) => checkContentType(name) == ContentType.video; +bool isAudioFile(String name) => checkContentType(name) == ContentType.audio; +bool isImageFile(String name) => checkContentType(name) == ContentType.image; diff --git a/lib/utils/files_filter.dart b/lib/utils/files_filter.dart index 14e7da6..3d7e644 100644 --- a/lib/utils/files_filter.dart +++ b/lib/utils/files_filter.dart @@ -1,5 +1,13 @@ import 'package:iris/models/file.dart'; -List filesFilter(List files, List types) { - return files.where((file) => types.contains(file.type)).toList(); +List filesFilter( + List files, { + List? types, + bool? includeDirs = true, +}) { + return files + .where((file) => + (types == null ? true : types.contains(file.type)) || + (includeDirs == true && file.isDir)) + .toList(); } diff --git a/lib/utils/files_sort.dart b/lib/utils/files_sort.dart index f123815..cb4d74c 100644 --- a/lib/utils/files_sort.dart +++ b/lib/utils/files_sort.dart @@ -7,59 +7,28 @@ List filesSort({ SortOrder sortOrder = SortOrder.asc, bool folderFirst = true, }) { - final dirs_ = files.where((file) => file.isDir).toList(); - final files_ = files.where((file) => !file.isDir).toList(); - - int compare(dynamic a, dynamic b) { - int result; - if (a is String && b is String) { - result = a.toLowerCase().compareTo(b.toLowerCase()); - } else if (a is Comparable && b is Comparable) { - result = a.compareTo(b); - } else { - result = 0; + final sortedFiles = files.toList(); + sortedFiles.sort((a, b) { + if (folderFirst) { + if (a.isDir && !b.isDir) return -1; + if (!a.isDir && b.isDir) return 1; } - return sortOrder == SortOrder.asc ? result : -result; - } - - if (folderFirst) { - switch (sortBy) { - case SortBy.name: - dirs_.sort((a, b) => compare(a.name, b.name)); - files_.sort((a, b) => compare(a.name, b.name)); - break; - case SortBy.size: - dirs_.sort((a, b) => compare(a.size, b.size)); - files_.sort((a, b) => compare(a.size, b.size)); - break; - case SortBy.lastModified: - dirs_.sort((a, b) => compare( - a.lastModified ?? DateTime(0), - b.lastModified ?? DateTime(0), - )); - files_.sort((a, b) => compare( - a.lastModified ?? DateTime(0), - b.lastModified ?? DateTime(0), - )); - break; - } - return [...dirs_, ...files_]; - } else { + int result; switch (sortBy) { case SortBy.name: - files.sort((a, b) => compare(a.name, b.name)); + result = a.name.toLowerCase().compareTo(b.name.toLowerCase()); break; case SortBy.size: - files.sort((a, b) => compare(a.size, b.size)); + result = a.size.compareTo(b.size); break; case SortBy.lastModified: - files.sort((a, b) => compare( - a.lastModified ?? DateTime(0), - b.lastModified ?? DateTime(0), - )); + result = (a.lastModified ?? DateTime(0)) + .compareTo(b.lastModified ?? DateTime(0)); break; } - return files; - } + + return sortOrder == SortOrder.asc ? result : -result; + }); + return sortedFiles; } diff --git a/lib/utils/find_subtitle.dart b/lib/utils/find_subtitle.dart deleted file mode 100644 index 3cc0f65..0000000 --- a/lib/utils/find_subtitle.dart +++ /dev/null @@ -1,45 +0,0 @@ -import 'package:iris/utils/build_file_uri.dart'; -import 'package:iris/models/file.dart'; -import 'package:iris/utils/check_content_type.dart'; -import 'package:path/path.dart' as path; - -Future> findSubtitle( - List fileNames, - String videoName, - String baseUri, -) async { - if (checkContentType(videoName) != ContentType.video) { - return []; - } - - final baseName = path.basenameWithoutExtension(videoName); - - final subtitleExtensions = {'ass', 'srt', 'vtt', 'sub'}; - - final regex = RegExp( - r'^' + - RegExp.escape(baseName) + - r'\.(.+?)\.(' + - subtitleExtensions.join('|') + - r')$', - caseSensitive: false, - ); - - return fileNames.where((fileName) { - final fileExt = path.extension(fileName).replaceFirst('.', ''); - return fileName.startsWith(baseName) && - subtitleExtensions.contains(fileExt); - }).map((fileName) { - String subTitleName = fileName; - final match = regex.firstMatch(fileName); - - if (match != null && match.groupCount >= 1) { - subTitleName = match.group(1) ?? fileName; - } - - return Subtitle( - name: subTitleName, - uri: buildFileUri(baseUri, fileName), - ); - }).toList(); -} diff --git a/lib/utils/get_subtitle_map.dart b/lib/utils/get_subtitle_map.dart new file mode 100644 index 0000000..e2043b0 --- /dev/null +++ b/lib/utils/get_subtitle_map.dart @@ -0,0 +1,35 @@ +import 'package:iris/models/file.dart'; +import 'package:path/path.dart' as path; + +Map> getSubtitleMap({ + required List files, + required String baseUri, + required String Function(T) getName, + required String Function(T) getUri, +}) { + final subtitleExtensions = {'ass', 'srt', 'vtt', 'sub'}; + final Map> subtitleMap = {}; + + for (final file in files) { + final fileName = getName(file); + final fileExt = + path.extension(fileName).replaceFirst('.', '').toLowerCase(); + if (subtitleExtensions.contains(fileExt)) { + final mediaBaseName = + path.basenameWithoutExtension(fileName).split('.').first; + + final subBaseName = path.basenameWithoutExtension(fileName); + final regex = RegExp(r'^' + RegExp.escape(mediaBaseName) + r'\.(.+?)$', + caseSensitive: false); + final match = regex.firstMatch(subBaseName); + final subTitleName = match?.group(1) ?? subBaseName; + + final subtitle = Subtitle( + name: subTitleName, + uri: getUri(file), + ); + subtitleMap.putIfAbsent(mediaBaseName, () => []).add(subtitle); + } + } + return subtitleMap; +} diff --git a/lib/utils/get_subtitle_title.dart b/lib/utils/get_subtitle_title.dart deleted file mode 100644 index 94db3cc..0000000 --- a/lib/utils/get_subtitle_title.dart +++ /dev/null @@ -1,42 +0,0 @@ -import 'package:iris/l10n/iso_639.dart'; -import 'package:media_kit/media_kit.dart'; - -String getTrackTitle(dynamic track) { - if (track is SubtitleTrack || track is AudioTrack) { - if (track.title != null) { - final lowerCaseTitle = track.title!.toLowerCase(); - final languageFromTitle = getTrackLanguage(lowerCaseTitle); - if (languageFromTitle != null) { - return languageFromTitle; - } - return track.title!; - } - - if (track.language != null) { - final lowerCaseLanguage = track.language!.toLowerCase(); - final languageFromLanguage = getTrackLanguage(lowerCaseLanguage); - if (languageFromLanguage != null) { - return languageFromLanguage; - } - return track.language!; - } - return track.id; - } - return ''; -} - -String? getTrackLanguage(String languageCode) { - if (customLanguageCodes[languageCode] != null) { - return '${(customLanguageCodes[languageCode]!.en).join(', ')}, $languageCode'; - } - - if (iso_639_1[languageCode] != null) { - return '${(iso_639_1[languageCode]!.en).join(', ')}, $languageCode'; - } - - if (iso_639_2[languageCode] != null) { - return '${(iso_639_2[languageCode]!.en).join(', ')}, $languageCode'; - } - - return null; -} diff --git a/lib/widgets/card.dart b/lib/widgets/card.dart index 173a68a..ef62cb4 100644 --- a/lib/widgets/card.dart +++ b/lib/widgets/card.dart @@ -49,11 +49,7 @@ class Card extends StatelessWidget { ), ), ), - Positioned( - left: 0, - top: 0, - right: 0, - bottom: 0, + Positioned.fill( child: IgnorePointer( child: Container( decoration: BoxDecoration( From 75daa34cca6671866ff05fb77399027e6487befe Mon Sep 17 00:00:00 2001 From: 22 <60903333+nini22P@users.noreply.github.com> Date: Wed, 17 Sep 2025 21:17:35 +0800 Subject: [PATCH 28/31] update audio ui --- lib/pages/player/audio.dart | 68 ++++++++++++++++++++++--------------- 1 file changed, 41 insertions(+), 27 deletions(-) diff --git a/lib/pages/player/audio.dart b/lib/pages/player/audio.dart index e61e3ce..264ce8a 100644 --- a/lib/pages/player/audio.dart +++ b/lib/pages/player/audio.dart @@ -86,10 +86,14 @@ class Audio extends HookWidget { const double wideLayoutThreshold = 600; final isWideScreen = constraints.maxWidth >= wideLayoutThreshold; + if (cover == null) { + return const SizedBox(); + } + if (isWideScreen) { - return _buildWideLayout(context, constraints, cover, auth); + return _buildWideLayout(context, constraints, cover!, auth); } else { - return _buildNarrowLayout(context, constraints, cover, auth); + return _buildNarrowLayout(context, constraints, cover!, auth); } }, ), @@ -98,12 +102,15 @@ class Audio extends HookWidget { ); } - Widget _buildNarrowLayout(BuildContext context, BoxConstraints constraints, - FileItem? cover, String? auth) { - return Align( - alignment: const Alignment(0.0, -0.2), + Widget _buildNarrowLayout( + BuildContext context, + BoxConstraints constraints, + FileItem cover, + String? auth, + ) { + return Center( child: Padding( - padding: const EdgeInsets.symmetric(horizontal: 48.0, vertical: 24.0), + padding: const EdgeInsets.fromLTRB(48, 56, 48, 96), child: ConstrainedBox( constraints: const BoxConstraints( maxWidth: 400.0, @@ -125,20 +132,28 @@ class Audio extends HookWidget { ); } - Widget _buildWideLayout(BuildContext context, BoxConstraints constraints, - FileItem? cover, String? auth) { + Widget _buildWideLayout( + BuildContext context, + BoxConstraints constraints, + FileItem cover, + String? auth, + ) { return Row( children: [ Expanded( flex: 5, - child: Align( - alignment: const Alignment(0.0, -0.2), + child: Center( child: Padding( - padding: const EdgeInsets.fromLTRB(48, 24, 24, 24), + padding: EdgeInsets.fromLTRB( + 48, + 56, + 24, + constraints.maxWidth > 1024 ? 64 : 96, + ), child: ConstrainedBox( constraints: const BoxConstraints( - maxWidth: 400.0, - maxHeight: 400.0, + maxWidth: 320.0, + maxHeight: 320.0, ), child: AspectRatio( aspectRatio: 1.0, @@ -166,13 +181,14 @@ class Audio extends HookWidget { ); } - Widget _buildCoverCard( - {required FileItem? cover, - required String? auth, - required Color shadowColor}) { + Widget _buildCoverCard({ + required FileItem cover, + required String? auth, + required Color shadowColor, + }) { return Container( decoration: BoxDecoration( - borderRadius: BorderRadius.circular(16), + borderRadius: BorderRadius.circular(8), boxShadow: [ BoxShadow( color: shadowColor, @@ -183,14 +199,12 @@ class Audio extends HookWidget { ], ), child: ClipRRect( - borderRadius: BorderRadius.circular(16), - child: cover != null - ? _CoverImage( - cover: cover, - auth: auth, - fit: BoxFit.cover, - ) - : Container(), + borderRadius: BorderRadius.circular(8), + child: _CoverImage( + cover: cover, + auth: auth, + fit: BoxFit.cover, + ), ), ); } From 77a3598fd8fce95b6f8674af4105cb3550c27661 Mon Sep 17 00:00:00 2001 From: 22 <60903333+nini22P@users.noreply.github.com> Date: Wed, 17 Sep 2025 21:41:08 +0800 Subject: [PATCH 29/31] refactor: replace files_filter with inline logic and fix content types --- lib/hooks/use_cover.dart | 5 +++-- lib/models/storages/ftp.dart | 5 +++-- lib/models/storages/local.dart | 13 +++++++------ lib/models/storages/webdav.dart | 5 ++--- lib/pages/storages/files.dart | 18 ++++++++++-------- lib/utils/files_filter.dart | 13 ------------- lib/utils/get_subtitle_map.dart | 1 - 7 files changed, 25 insertions(+), 35 deletions(-) delete mode 100644 lib/utils/files_filter.dart diff --git a/lib/hooks/use_cover.dart b/lib/hooks/use_cover.dart index 3467bd2..942ffe8 100644 --- a/lib/hooks/use_cover.dart +++ b/lib/hooks/use_cover.dart @@ -6,7 +6,6 @@ import 'package:iris/models/storages/local.dart'; import 'package:iris/models/storages/storage.dart'; import 'package:iris/store/use_play_queue_store.dart'; import 'package:iris/store/use_storage_store.dart'; -import 'package:iris/utils/files_filter.dart'; FileItem? useCover() { final context = useContext(); @@ -53,7 +52,9 @@ FileItem? useCover() { final files = await storage.getFiles(dir); - final images = filesFilter(files, types: [ContentType.image]); + final images = files + .where((file) => [ContentType.image].contains(file.type)) + .toList(); cover.value = images.firstWhereOrNull((image) => image.name.split('.').first.toLowerCase() == 'cover') ?? diff --git a/lib/models/storages/ftp.dart b/lib/models/storages/ftp.dart index 0a3f144..7b28d6e 100644 --- a/lib/models/storages/ftp.dart +++ b/lib/models/storages/ftp.dart @@ -42,7 +42,6 @@ Future> getFTPFiles( final subtitleMap = getSubtitleMap( files: files, - baseUri: baseUri, getName: (file) => file.name, getUri: (file) => getUri(file.name), ); @@ -64,7 +63,9 @@ Future> getFTPFiles( lastModified: file.info?.modifyTime != null ? DateTime.tryParse(file.info!.modifyTime!) : null, - type: checkContentType(file.name), + type: file.isDirectory + ? ContentType.other + : checkContentType(file.name), subtitles: isVideoFile(file.name) ? subtitleMap[basename] ?? [] : [], ), diff --git a/lib/models/storages/local.dart b/lib/models/storages/local.dart index 76673b6..931a5bf 100644 --- a/lib/models/storages/local.dart +++ b/lib/models/storages/local.dart @@ -8,7 +8,6 @@ import 'package:iris/models/storages/storage.dart'; import 'package:iris/models/store/play_queue_state.dart'; import 'package:iris/store/use_app_store.dart'; import 'package:iris/store/use_play_queue_store.dart'; -import 'package:iris/utils/files_filter.dart'; import 'package:iris/utils/files_sort.dart'; import 'package:iris/utils/get_subtitle_map.dart'; import 'package:iris/utils/get_localizations.dart'; @@ -156,8 +155,11 @@ Future getLocalPlayQueue(String filePath) async { basePath: dirPath, ).getFiles(dirPath); final List sortedFiles = filesSort(files: files); - final List filteredFiles = - filesFilter(sortedFiles, types: [ContentType.video, ContentType.audio]); + final List filteredFiles = sortedFiles + .where( + (file) => [ContentType.video, ContentType.audio].contains(file.type)) + .toList(); + final List playQueue = filteredFiles .asMap() .entries @@ -267,7 +269,7 @@ Future> getLocalFiles( isDir: isDir, size: isDir ? 0 : stat.size, lastModified: stat.modified, - type: checkContentType(entity.path), + type: isDir ? ContentType.other : checkContentType(entity.path), subtitles: [], )); } @@ -301,7 +303,6 @@ Future> getContentFiles(String uri) async { final subtitleMap = getSubtitleMap( files: files, - baseUri: uri, getName: (file) => file.name, getUri: (file) => file.uri, ); @@ -318,7 +319,7 @@ Future> getContentFiles(String uri) async { isDir: file.isDir, size: file.isDir ? 0 : file.length, lastModified: DateTime.fromMillisecondsSinceEpoch(file.lastModified), - type: checkContentType(file.name), + type: file.isDir ? ContentType.other : checkContentType(file.name), subtitles: isVideoFile(file.name) ? subtitleMap[basename] ?? [] : [], )); } diff --git a/lib/models/storages/webdav.dart b/lib/models/storages/webdav.dart index 515665f..5bab88f 100644 --- a/lib/models/storages/webdav.dart +++ b/lib/models/storages/webdav.dart @@ -84,7 +84,6 @@ Future> getWebDAVFiles( final subtitleMap = getSubtitleMap( files: files, - baseUri: baseUriString, getName: (file) => file.name ?? '', getUri: (file) => getUri(file.name ?? ''), ); @@ -105,10 +104,10 @@ Future> getWebDAVFiles( name: fileName, uri: getUri(fileName), path: [...path, fileName], - isDir: file.isDir ?? false, + isDir: isDir ?? false, size: file.size ?? 0, lastModified: file.mTime, - type: checkContentType(fileName), + type: isDir ?? false ? ContentType.other : checkContentType(fileName), subtitles: isVideoFile(fileName) ? subtitleMap[basename] ?? [] : [], )); } diff --git a/lib/pages/storages/files.dart b/lib/pages/storages/files.dart index eadb55f..904f96d 100644 --- a/lib/pages/storages/files.dart +++ b/lib/pages/storages/files.dart @@ -14,7 +14,6 @@ import 'package:iris/store/use_app_store.dart'; import 'package:iris/store/use_history_store.dart'; import 'package:iris/store/use_play_queue_store.dart'; import 'package:iris/store/use_storage_store.dart'; -import 'package:iris/utils/files_filter.dart'; import 'package:iris/utils/file_size_convert.dart'; import 'package:iris/utils/files_sort.dart'; import 'package:iris/utils/get_localizations.dart'; @@ -68,11 +67,11 @@ class Files extends HookWidget { final isError = result.error != null; final filteredFiles = useMemoized( - () => filesFilter( - result.data ?? [], - types: [ContentType.video, ContentType.audio], - includeDirs: true, - ), + () => (result.data ?? []) + .where((file) => + [ContentType.video, ContentType.audio].contains(file.type) || + file.isDir) + .toList(), [result.data]); final files = useMemoized( @@ -94,8 +93,11 @@ class Files extends HookWidget { void play(List files, int index) async { final clickedFile = files[index]; - final List filteredFiles = - filesFilter(files, types: [ContentType.video, ContentType.audio]); + final List filteredFiles = files + .where((file) => + [ContentType.video, ContentType.audio].contains(file.type)) + .toList(); + final List playQueue = filteredFiles .asMap() .entries diff --git a/lib/utils/files_filter.dart b/lib/utils/files_filter.dart deleted file mode 100644 index 3d7e644..0000000 --- a/lib/utils/files_filter.dart +++ /dev/null @@ -1,13 +0,0 @@ -import 'package:iris/models/file.dart'; - -List filesFilter( - List files, { - List? types, - bool? includeDirs = true, -}) { - return files - .where((file) => - (types == null ? true : types.contains(file.type)) || - (includeDirs == true && file.isDir)) - .toList(); -} diff --git a/lib/utils/get_subtitle_map.dart b/lib/utils/get_subtitle_map.dart index e2043b0..8dd661f 100644 --- a/lib/utils/get_subtitle_map.dart +++ b/lib/utils/get_subtitle_map.dart @@ -3,7 +3,6 @@ import 'package:path/path.dart' as path; Map> getSubtitleMap({ required List files, - required String baseUri, required String Function(T) getName, required String Function(T) getUri, }) { From b394221fd4445e92c3948a403b16ae00dbed8316 Mon Sep 17 00:00:00 2001 From: 22 <60903333+nini22P@users.noreply.github.com> Date: Wed, 17 Sep 2025 21:42:22 +0800 Subject: [PATCH 30/31] remove SystemUiOverlayStyle, update volume slider track height --- lib/pages/home/home.dart | 14 +++----------- lib/pages/player/control_bar/volume_slider.dart | 2 +- 2 files changed, 4 insertions(+), 12 deletions(-) diff --git a/lib/pages/home/home.dart b/lib/pages/home/home.dart index c469423..c6a9d36 100644 --- a/lib/pages/home/home.dart +++ b/lib/pages/home/home.dart @@ -1,5 +1,4 @@ import 'package:flutter/material.dart'; -import 'package:flutter/services.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:flutter_zustand/flutter_zustand.dart'; import 'package:iris/hooks/ui/use_full_screen.dart'; @@ -20,16 +19,9 @@ class Home extends HookWidget { final playerBackend = useAppStore().select(context, (state) => state.playerBackend); - return AnnotatedRegion( - value: const SystemUiOverlayStyle( - statusBarIconBrightness: Brightness.light, - statusBarColor: Colors.black45, - systemNavigationBarColor: Colors.transparent, - ), - child: Scaffold( - backgroundColor: Color.fromARGB(255, 0, 0, 0), - body: PlayerView(playerBackend: playerBackend), - ), + return Scaffold( + backgroundColor: Colors.black, + body: PlayerView(playerBackend: playerBackend), ); } } diff --git a/lib/pages/player/control_bar/volume_slider.dart b/lib/pages/player/control_bar/volume_slider.dart index 7fcacde..17d4052 100644 --- a/lib/pages/player/control_bar/volume_slider.dart +++ b/lib/pages/player/control_bar/volume_slider.dart @@ -32,7 +32,7 @@ class VolumeSlider extends HookWidget { overlayShape: const RoundSliderOverlayShape( overlayRadius: 4, ), - trackHeight: 3.6, + trackHeight: 2.4, ), child: Slider( value: isMuted ? 0 : volume.toDouble(), From 717ffd18e4ff7fb7236ee26d07ccbcbf40ad76ee Mon Sep 17 00:00:00 2001 From: 22 <60903333+nini22P@users.noreply.github.com> Date: Wed, 17 Sep 2025 22:07:50 +0800 Subject: [PATCH 31/31] update changelog --- CHANGELOG.md | 56 ++++++++++++++++++++++++++++++++++++++-------------- 1 file changed, 41 insertions(+), 15 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0cd40a3..1cb85ff 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,21 @@ +## v1.5.0 + +### Changelog + +* Updated app icon +* Added a stop and an exit button +* Added a playback-speed selector that activates with a touch-and-hold gesture +* Fixed URI handling +* Improved stability and performance + +### 更新日志 + +* 更换应用图标 +* 添加停止按钮和退出按钮 +* 添加触控长按激活的播放速度选择器 +* 修复 uri 处理 +* 优化了稳定性和性能 + ## v1.4.2 ### Changelog @@ -10,7 +28,6 @@ * 修复音频封面问题 * 改进 uri 处理 - ## v1.4.1 ### Changelog @@ -21,7 +38,6 @@ * FTP 串流使用动态 url - ## v1.4.0 ### Changelog @@ -38,7 +54,6 @@ * Windows 版本存储列表支持远程磁盘和网络快捷方式 * 添加 Windows 版本安装器 - ## v1.3.4 ### Changelog @@ -53,7 +68,6 @@ * 添加播放速度按钮。 * 添加快捷键:帧进 `+`,帧退 `-`。 - ## v1.3.3 ### Changelog @@ -64,7 +78,6 @@ * 修复启动后无法继续播放的问题 - ## v1.3.2 ### Changelog @@ -75,7 +88,6 @@ * 添加 WebDAV 存储时支持自定义 https 端口 - ## v1.3.1 ### Changelog @@ -88,7 +100,6 @@ * Windows 版本数据保存位置已修改为 `C:\Users\\AppData\Roaming\nini22P\iris` * 更新上游依赖,修复 FVP 播放器后端切换字幕的问题 - ## v1.3.0 ### Changelog @@ -100,25 +111,27 @@ * Improved some visual effects ### 更新日志 + * 添加 [FVP](https://github.com/wang-bin/fvp) 播放器后端(实验性,有未知bug) * 添加音量调整 * 添加文件排序 * 添加快捷键:提升音量( `Arrow Up` )、降低音量( `Arrow Down` )、静音(`Ctrl + M`)、切换窗口置顶( `F10` )、关闭当前媒体文件( `Ctrl + C` )、退出应用( `Alt + X` ) * 改进了部分视觉效果 - ## v1.2.1 ### Changelog + * Split APKs by architecture to reduce installation size. ### 更新日志 -* 拆分不同架构的 APK 以减小安装包大小 +* 拆分不同架构的 APK 以减小安装包大小 ## v1.2.0 ### Changelog + * Support jumping to video playback from external clicks (Windows version can play by command line or dragging files to the window) * Support adjusting brightness and volume gestures (Brightness gestures are not available on Windows version) * Support playing online links @@ -128,6 +141,7 @@ * Improved some visual effects ### 更新日志 + * 支持从外部点击视频跳转播放(Windows 版本可以通过命令行或者拖拽文件到窗口播放) * 支持调整亮度和音量手势(Windows 版本调整亮度手势不可用) * 支持播放在线链接 @@ -136,22 +150,24 @@ * 改进 WebDAV 测试连接功能 * 改进了部分视觉效果 - ## v1.1.1 ### Changelog + * Restore old update method for windows version (Double-click the `iris-updater.bat` in the same directory as the executable file to upgrade if you have problems updating.) ### 更新日志 -* windows 版本恢复为旧的更新方式(更新出问题的可双击打开可执行文件同级目录下的 `iris-updater.bat` 升级) +* windows 版本恢复为旧的更新方式(更新出问题的可双击打开可执行文件同级目录下的 `iris-updater.bat` 升级) ## v1.1.0 ### Breaking Changes + * All configurations will be cleared. Please reconfigure ### Changlog + * Display all local storage * Support playback history * Support random playback @@ -159,47 +175,57 @@ * Support video zoom ### 重大变更 + * 所有配置将被清空,请重新配置 ### 更新日志 + * 显示所有本地存储 * 支持播放历史 * 支持随机播放 * 支持循环播放 * 支持视频缩放 - ## v1.0.3 + ### Changelog + * Improve Windows version installation updates * Fixes an issue where subtitles may not be found ### 更新日志 + * 改进 Windows 版本安装更新 * 修复可能无法找到字幕的问题 - ## v1.0.2 + ### Changelog + * Support for switching built-in audio tracks * Reduce package size for Windows version ### 更新日志 + * 支持切换内置音轨 * 减小 Windows 版本包体大小 - ## v1.0.1 + ### Changelog + * Windows version support auto update ### 更新日志 -* Windows 版本支持自动更新 +* Windows 版本支持自动更新 ## v1.0.0 + ### Changelog + * Supports WebDAV and local storage video playback ### 更新日志 + * 支持 WebDAV 和本地存储视频播放