From 08cb807f39d4983743fda5e8359ef1655cbf9714 Mon Sep 17 00:00:00 2001 From: guozhigq Date: Mon, 11 Nov 2024 23:46:08 +0800 Subject: [PATCH 01/43] fix: history cid --- lib/pages/video/detail/introduction/controller.dart | 10 +++++----- lib/pages/video/detail/introduction/view.dart | 2 +- .../detail/introduction/widgets/season_panel.dart | 12 ++++++++---- 3 files changed, 14 insertions(+), 10 deletions(-) diff --git a/lib/pages/video/detail/introduction/controller.dart b/lib/pages/video/detail/introduction/controller.dart index 2671f4d8..eafedd08 100644 --- a/lib/pages/video/detail/introduction/controller.dart +++ b/lib/pages/video/detail/introduction/controller.dart @@ -85,15 +85,14 @@ class VideoIntroController extends GetxController { } // 获取视频简介&分p - Future queryVideoIntro({cover}) async { + Future queryVideoIntro({String? cover, String? type, int? cid}) async { var result = await VideoHttp.videoIntro(bvid: bvid); if (result['status']) { videoDetail.value = result['data']!; ugcSeason = result['data']!.ugcSeason; pages.value = result['data']!.pages!; - lastPlayCid.value = videoDetail.value.cid!; - if (pages.isNotEmpty) { - lastPlayCid.value = pages.first.cid!; + if (type == null) { + lastPlayCid.value = cid ?? videoDetail.value.cid!; } final VideoDetailController videoDetailCtr = Get.find(tag: heroTag); @@ -473,7 +472,8 @@ class VideoIntroController extends GetxController { videoReplyCtr.queryReplyList(type: 'init'); } catch (_) {} this.bvid = bvid; - await queryVideoIntro(cover: cover); + // 点击切换时,优先取当前cid + await queryVideoIntro(cover: cover, cid: cid); } void startTimer() { diff --git a/lib/pages/video/detail/introduction/view.dart b/lib/pages/video/detail/introduction/view.dart index a02ed530..9ceb35e0 100644 --- a/lib/pages/video/detail/introduction/view.dart +++ b/lib/pages/video/detail/introduction/view.dart @@ -57,7 +57,7 @@ class _VideoIntroPanelState extends State heroTag = Get.arguments['heroTag']; videoIntroController = Get.put(VideoIntroController(bvid: widget.bvid), tag: heroTag); - _futureBuilderFuture = videoIntroController.queryVideoIntro(); + _futureBuilderFuture = videoIntroController.queryVideoIntro(type: 'init'); videoIntroController.videoDetail.listen((value) { videoDetail = value; }); diff --git a/lib/pages/video/detail/introduction/widgets/season_panel.dart b/lib/pages/video/detail/introduction/widgets/season_panel.dart index e5a9ea84..291f1622 100644 --- a/lib/pages/video/detail/introduction/widgets/season_panel.dart +++ b/lib/pages/video/detail/introduction/widgets/season_panel.dart @@ -27,7 +27,7 @@ class SeasonPanel extends StatefulWidget { } class _SeasonPanelState extends State { - late List episodes; + List? episodes; late int cid; late RxInt currentIndex = (-1).obs; final String heroTag = Get.arguments['heroTag']; @@ -75,7 +75,10 @@ class _SeasonPanelState extends State { // 获取currentIndex void getCurrentIndex() { - currentIndex.value = episodes.indexWhere((EpisodeItem e) => e.cid == cid); + if (episodes != null) { + currentIndex.value = + episodes!.indexWhere((EpisodeItem e) => e.cid == cid); + } final List sections = widget.ugcSeason.sections!; if (sections.length == 1 && sections.first.type == 1) { final List episodesList = sections.first.episodes!; @@ -83,6 +86,7 @@ class _SeasonPanelState extends State { for (int j = 0; j < episodesList[i].pages!.length; j++) { if (episodesList[i].pages![j].cid == cid) { currentIndex.value = i; + episodes = episodesList; continue; } } @@ -137,7 +141,7 @@ class _SeasonPanelState extends State { widget.videoIntroCtr.bottomSheetController = _bottomSheetController = EpisodeBottomSheet( currentCid: cid, - episodes: episodes, + episodes: episodes!, changeFucCall: changeFucCall, sheetHeight: widget.sheetHeight, dataType: VideoEpidoesType.videoEpisode, @@ -165,7 +169,7 @@ class _SeasonPanelState extends State { ), const SizedBox(width: 10), Obx(() => Text( - '${currentIndex.value + 1}/${episodes.length}', + '${currentIndex.value + 1}/${episodes!.length}', style: Theme.of(context).textTheme.labelMedium, )), const SizedBox(width: 6), From a4413b952088ccfd7bdcfa77e012eb72c230bd42 Mon Sep 17 00:00:00 2001 From: guozhigq Date: Fri, 15 Nov 2024 23:34:42 +0800 Subject: [PATCH 02/43] =?UTF-8?q?feat:=20cookie=E7=99=BB=E5=BD=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/http/init.dart | 21 +-- lib/pages/login/controller.dart | 33 +++++ lib/pages/login/view.dart | 235 ++++++++++++++++++++------------ lib/utils/login.dart | 2 - 4 files changed, 183 insertions(+), 108 deletions(-) diff --git a/lib/http/init.dart b/lib/http/init.dart index 3117666e..584be0a9 100644 --- a/lib/http/init.dart +++ b/lib/http/init.dart @@ -44,17 +44,6 @@ class Request { final List cookie = await cookieManager.cookieJar .loadForRequest(Uri.parse(HttpString.baseUrl)); final userInfo = userInfoCache.get('userInfoCache'); - if (userInfo != null && userInfo.mid != null) { - final List cookie2 = await cookieManager.cookieJar - .loadForRequest(Uri.parse(HttpString.tUrl)); - if (cookie2.isEmpty) { - try { - await Request().get(HttpString.tUrl); - } catch (e) { - log("setCookie, ${e.toString()}"); - } - } - } setOptionsHeaders(userInfo, userInfo != null && userInfo.mid != null); String baseUrlType = 'default'; if (setting.get(SettingBoxKey.enableGATMode, defaultValue: false)) { @@ -77,11 +66,11 @@ class Request { // 从cookie中获取 csrf token static Future getCsrf() async { List cookies = await cookieManager.cookieJar - .loadForRequest(Uri.parse(HttpString.apiBaseUrl)); - String token = ''; - if (cookies.where((e) => e.name == 'bili_jct').isNotEmpty) { - token = cookies.firstWhere((e) => e.name == 'bili_jct').value; - } + .loadForRequest(Uri.parse(HttpString.baseUrl)); + String token = cookies + .firstWhere((e) => e.name == 'bili_jct', + orElse: () => Cookie('bili_jct', '')) + .value; return token; } diff --git a/lib/pages/login/controller.dart b/lib/pages/login/controller.dart index 9e7fb339..b43eda64 100644 --- a/lib/pages/login/controller.dart +++ b/lib/pages/login/controller.dart @@ -1,14 +1,19 @@ import 'dart:async'; import 'dart:io'; +import 'package:cookie_jar/cookie_jar.dart'; +import 'package:dio_cookie_manager/dio_cookie_manager.dart'; import 'package:encrypt/encrypt.dart'; import 'package:flutter/material.dart'; import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; import 'package:get/get.dart'; +import 'package:pilipala/http/constants.dart'; +import 'package:pilipala/http/index.dart'; import 'package:pilipala/http/login.dart'; import 'package:gt3_flutter_plugin/gt3_flutter_plugin.dart'; import 'package:pilipala/models/login/index.dart'; import 'package:pilipala/utils/login.dart'; +import 'package:pilipala/utils/utils.dart'; class LoginPageController extends GetxController { final GlobalKey mobFormKey = GlobalKey(); @@ -341,4 +346,32 @@ class LoginPageController extends GetxController { Get.back(); } } + + // cookie登录 + Future loginInByCookie({ + required String cookiesStr, + String domain = HttpString.baseUrl, + }) async { + final List cookiesStrList = cookiesStr.split('; '); + final List cookiesList = cookiesStrList.map((cookie) { + final cookieArr = cookie.split('='); + return Cookie(cookieArr[0], cookieArr[1]); + }).toList(); + + final String cookiePath = await Utils.getCookiePath(); + final cookieJar = PersistCookieJar( + ignoreExpires: true, + storage: FileStorage(cookiePath), + ); + CookieManager cookieManager = CookieManager(cookieJar); + Request.cookieManager = cookieManager; + await Request.cookieManager.cookieJar + .saveFromResponse(Uri.parse(HttpString.baseUrl), cookiesList); + try { + Request.dio.options.headers['cookie'] = cookiesStr; + } catch (err) { + debugPrint(err.toString()); + } + LoginUtils.confirmLogin('', null); + } } diff --git a/lib/pages/login/view.dart b/lib/pages/login/view.dart index 4fe21792..8ba012ea 100644 --- a/lib/pages/login/view.dart +++ b/lib/pages/login/view.dart @@ -14,6 +14,144 @@ class LoginPage extends StatefulWidget { class _LoginPageState extends State { final LoginPageController _loginPageCtr = Get.put(LoginPageController()); + // 浏览器登录 + void loginInByWeb() { + Get.offNamed( + '/webview', + parameters: { + 'url': 'https://passport.bilibili.com/h5-app/passport/login', + 'type': 'login', + 'pageTitle': '登录bilibili', + }, + ); + } + + // 二维码方式登录 + void loginInByWebQrcode() { + showDialog( + context: context, + builder: (context) { + return StatefulBuilder(builder: (context, StateSetter setState) { + return AlertDialog( + title: Row( + children: [ + const Text('扫码登录'), + IconButton( + onPressed: () { + setState(() {}); + }, + icon: const Icon(Icons.refresh), + ), + ], + ), + contentPadding: const EdgeInsets.fromLTRB(0, 0, 0, 4), + content: AspectRatio( + aspectRatio: 1, + child: Container( + width: 200, + padding: const EdgeInsets.all(12), + child: FutureBuilder( + future: _loginPageCtr.getWebQrcode(), + builder: (context, snapshot) { + if (snapshot.connectionState == ConnectionState.done) { + if (snapshot.data == null) { + return const SizedBox(); + } + Map data = snapshot.data as Map; + return QrImageView( + data: data['data']['url'], + backgroundColor: Colors.white, + ); + } else { + return const Center( + child: SizedBox( + width: 40, + height: 40, + child: CircularProgressIndicator(), + ), + ); + } + }, + ), + ), + ), + actions: [ + TextButton( + onPressed: () {}, + child: Obx(() { + return Text( + '有效期: ${_loginPageCtr.validSeconds.value}s', + style: Theme.of(context).textTheme.titleMedium, + ); + }), + ), + TextButton( + onPressed: () {}, + child: Text( + '检查登录状态', + style: TextStyle( + fontSize: Theme.of(context).textTheme.titleMedium!.fontSize, + ), + ), + ) + ], + ); + }); + }, + ).then((value) { + _loginPageCtr.validTimer!.cancel(); + }); + } + + // cookie登录 + // cookie登录 + void loginInByCookie() async { + var cookies = ''; + final outline = Theme.of(context).colorScheme.outline; + showDialog( + context: context, + builder: (BuildContext context) { + return AlertDialog( + title: const Text('Cookie登录'), + content: Column( + mainAxisSize: MainAxisSize.min, + children: [ + const Text('请将主站cookie粘贴到下方输入框中,点击「确认」即可完成登录。(记得清空粘贴板~)'), + const SizedBox(height: 12), + TextField( + minLines: 1, + maxLines: 3, + decoration: InputDecoration( + labelText: 'cookie', + border: OutlineInputBorder( + borderRadius: BorderRadius.circular(6.0), + ), + ), + onChanged: (e) => cookies = e, + ), + ], + ), + actions: [ + TextButton( + onPressed: Navigator.of(context).pop, + child: Text('取消', style: TextStyle(color: outline))), + TextButton( + onPressed: () async { + if (cookies.isEmpty) { + return; + } + await _loginPageCtr.loginInByCookie(cookiesStr: cookies); + if (context.mounted) { + Navigator.of(context).pop(); + } + }, + child: const Text('确认')) + ], + ); + }, + ); + } + @override void dispose() { _loginPageCtr.validTimer?.cancel(); @@ -43,100 +181,17 @@ class _LoginPageState extends State { actions: [ IconButton( tooltip: '浏览器打开', - onPressed: () { - Get.offNamed( - '/webview', - parameters: { - 'url': 'https://passport.bilibili.com/h5-app/passport/login', - 'type': 'login', - 'pageTitle': '登录bilibili', - }, - ); - }, + onPressed: loginInByWeb, icon: const Icon(Icons.language, size: 20), ), + IconButton( + tooltip: 'cookie登录', + onPressed: loginInByCookie, + icon: const Icon(Icons.cookie_outlined, size: 20), + ), IconButton( tooltip: '二维码登录', - onPressed: () { - showDialog( - context: context, - builder: (context) { - return StatefulBuilder( - builder: (context, StateSetter setState) { - return AlertDialog( - title: Row( - children: [ - const Text('扫码登录'), - IconButton( - onPressed: () { - setState(() {}); - }, - icon: const Icon(Icons.refresh), - ), - ], - ), - contentPadding: const EdgeInsets.fromLTRB(0, 0, 0, 4), - content: AspectRatio( - aspectRatio: 1, - child: Container( - width: 200, - padding: const EdgeInsets.all(12), - child: FutureBuilder( - future: _loginPageCtr.getWebQrcode(), - builder: (context, snapshot) { - if (snapshot.connectionState == - ConnectionState.done) { - if (snapshot.data == null) { - return const SizedBox(); - } - Map data = snapshot.data as Map; - return QrImageView( - data: data['data']['url'], - backgroundColor: Colors.white, - ); - } else { - return const Center( - child: SizedBox( - width: 40, - height: 40, - child: CircularProgressIndicator(), - ), - ); - } - }, - ), - ), - ), - actions: [ - TextButton( - onPressed: () {}, - child: Obx(() { - return Text( - '有效期: ${_loginPageCtr.validSeconds.value}s', - style: Theme.of(context).textTheme.titleMedium, - ); - }), - ), - TextButton( - onPressed: () {}, - child: Text( - '检查登录状态', - style: TextStyle( - fontSize: Theme.of(context) - .textTheme - .titleMedium! - .fontSize, - ), - ), - ) - ], - ); - }); - }, - ).then((value) { - _loginPageCtr.validTimer!.cancel(); - }); - }, + onPressed: loginInByWebQrcode, icon: const Icon(Icons.qr_code, size: 20), ), const SizedBox(width: 22), diff --git a/lib/utils/login.dart b/lib/utils/login.dart index 841251b8..29527edd 100644 --- a/lib/utils/login.dart +++ b/lib/utils/login.dart @@ -12,7 +12,6 @@ import 'package:pilipala/http/user.dart'; import 'package:pilipala/pages/dynamics/index.dart'; import 'package:pilipala/pages/home/index.dart'; import 'package:pilipala/pages/mine/index.dart'; -import 'package:pilipala/utils/cookie.dart'; import 'package:pilipala/utils/global_data_cache.dart'; import 'package:pilipala/utils/storage.dart'; import 'package:uuid/uuid.dart'; @@ -71,7 +70,6 @@ class LoginUtils { content = '${content + url}; \n'; } try { - await SetCookie.onSet(); final result = await UserHttp.userInfo(); if (result['status'] && result['data'].isLogin) { SmartDialog.showToast('登录成功'); From f44b5793c21e0d951e0d9a3cab0f7c8e0ebf1de3 Mon Sep 17 00:00:00 2001 From: guozhigq Date: Sun, 17 Nov 2024 20:40:17 +0800 Subject: [PATCH 03/43] fix: repeated reply --- lib/pages/video/detail/reply/controller.dart | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/lib/pages/video/detail/reply/controller.dart b/lib/pages/video/detail/reply/controller.dart index 4af59b9d..38d59617 100644 --- a/lib/pages/video/detail/reply/controller.dart +++ b/lib/pages/video/detail/reply/controller.dart @@ -70,6 +70,13 @@ class VideoReplyController extends GetxController { isEnd = res['data'].cursor.isEnd ?? false; nextOffset = res['data'].cursor.paginationReply.nextOffset ?? ""; if (replies.isNotEmpty) { + /// 临时修复 + final bool flag = replyList + .any((ReplyItemModel reply) => reply.rpid == replies.first.rpid); + if (replies.length == 1 && flag) { + replies.clear(); + isEnd = true; + } noMore.value = isEnd ? '没有更多了' : '加载中...'; } else { noMore.value = From 8c8c8620952af22b6ac4c464bba187169eefc528 Mon Sep 17 00:00:00 2001 From: guozhigq Date: Mon, 18 Nov 2024 19:32:15 +0800 Subject: [PATCH 04/43] feat: pure dark mode --- lib/main.dart | 25 +++++++++++++++++++++++-- lib/pages/setting/style_setting.dart | 9 +++++++++ lib/utils/storage.dart | 3 ++- 3 files changed, 34 insertions(+), 3 deletions(-) diff --git a/lib/main.dart b/lib/main.dart index 1ec86c8e..30464b0c 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -214,6 +214,27 @@ class BuildMainApp extends StatelessWidget { @override Widget build(BuildContext context) { + Box setting = GStorage.setting; + + /// 纯黑模式主题配置 + ColorScheme? pureDarkColorScheme; + final bool enablePureBlack = + setting.get(SettingBoxKey.enablePureBlack, defaultValue: false); + if (enablePureBlack) { + pureDarkColorScheme = darkColorScheme.copyWith( + background: Colors.black, + surface: Colors.black, + primary: Colors.white, + secondary: Colors.white, + error: Colors.red, + onPrimary: Colors.black, + onSecondary: Colors.black, + onSurface: Colors.white, + onBackground: Colors.white, + onError: Colors.white, + ); + } + final SnackBarThemeData snackBarTheme = SnackBarThemeData( actionTextColor: lightColorScheme.primary, backgroundColor: lightColorScheme.secondaryContainer, @@ -253,13 +274,13 @@ class BuildMainApp extends StatelessWidget { title: 'PiliPala', theme: buildThemeData( currentThemeValue == ThemeType.dark - ? darkColorScheme + ? pureDarkColorScheme ?? darkColorScheme : lightColorScheme, ), darkTheme: buildThemeData( currentThemeValue == ThemeType.light ? lightColorScheme - : darkColorScheme, + : pureDarkColorScheme ?? darkColorScheme, ), localizationsDelegates: const [ GlobalCupertinoLocalizations.delegate, diff --git a/lib/pages/setting/style_setting.dart b/lib/pages/setting/style_setting.dart index 5b59397e..12f52414 100644 --- a/lib/pages/setting/style_setting.dart +++ b/lib/pages/setting/style_setting.dart @@ -249,6 +249,15 @@ class _StyleSettingState extends State { '当前模式:${settingController.themeType.value.description}', style: subTitleStyle)), ), + SetSwitchItem( + title: '纯黑模式', + subTitle: '深色模式时使用纯黑色背景,适用于OLED屏幕', + setKey: SettingBoxKey.enablePureBlack, + defaultVal: false, + callFn: (bool val) => { + if (val && Get.isDarkMode) {Get.appUpdate()} + }, + ), ListTile( dense: false, onTap: () => settingController.setDynamicBadgeMode(context), diff --git a/lib/utils/storage.dart b/lib/utils/storage.dart index a5b36768..ac02592e 100644 --- a/lib/utils/storage.dart +++ b/lib/utils/storage.dart @@ -133,7 +133,8 @@ class SettingBoxKey { enableGradientBg = 'enableGradientBg', enableDynamicSwitch = 'enableDynamicSwitch', navBarSort = 'navBarSort', - actionTypeSort = 'actionTypeSort'; + actionTypeSort = 'actionTypeSort', + enablePureBlack = 'enablePureBlack'; } class LocalCacheKey { From 4008b8caef561575f447d57c74d804fc731c3de1 Mon Sep 17 00:00:00 2001 From: guozhigq Date: Wed, 20 Nov 2024 09:46:05 +0800 Subject: [PATCH 05/43] fix: controlsLock exit fullscreen --- lib/pages/video/detail/view.dart | 5 +++++ lib/plugin/pl_player/controller.dart | 3 ++- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/lib/pages/video/detail/view.dart b/lib/pages/video/detail/view.dart index 0fb97066..d357d3ea 100644 --- a/lib/pages/video/detail/view.dart +++ b/lib/pages/video/detail/view.dart @@ -684,6 +684,11 @@ class _VideoDetailPageState extends State canPop: plPlayerController?.isFullScreen.value != true, onPopInvoked: (bool didPop) { + if (plPlayerController?.controlsLock.value == + true) { + plPlayerController?.onLockControl(false); + return; + } if (plPlayerController?.isFullScreen.value == true) { plPlayerController! diff --git a/lib/plugin/pl_player/controller.dart b/lib/plugin/pl_player/controller.dart index de4cd9df..61711bf3 100644 --- a/lib/plugin/pl_player/controller.dart +++ b/lib/plugin/pl_player/controller.dart @@ -909,7 +909,7 @@ class PlPlayerController { if (videoType == 'live') { return; } - if (controlsLock.value) { + if (_controlsLock.value) { return; } _doubleSpeedStatus.value = val; @@ -1081,6 +1081,7 @@ class PlPlayerController { videoFitChangedTimer?.cancel(); // _position.close(); _playerEventSubs?.cancel(); + _controlsLock.value = false; // _sliderPosition.close(); // _sliderTempPosition.close(); // _isSliderMoving.close(); From 796db85b21125af56e51c50d65e14945e409815a Mon Sep 17 00:00:00 2001 From: guozhigq Date: Sun, 24 Nov 2024 16:29:53 +0800 Subject: [PATCH 06/43] opt: videoPlayer control --- assets/images/video/dlna.png | Bin 0 -> 1372 bytes assets/images/video/fullscreen.png | Bin 0 -> 274 bytes assets/images/video/fullscreen_exit.png | Bin 0 -> 246 bytes assets/images/video/pip.png | Bin 0 -> 1072 bytes lib/pages/video/detail/controller.dart | 1 + .../video/detail/widgets/header_control.dart | 123 +++--------------- lib/plugin/pl_player/controller.dart | 72 +++++----- lib/plugin/pl_player/view.dart | 59 ++++++--- 8 files changed, 105 insertions(+), 150 deletions(-) create mode 100755 assets/images/video/dlna.png create mode 100755 assets/images/video/fullscreen.png create mode 100755 assets/images/video/fullscreen_exit.png create mode 100755 assets/images/video/pip.png diff --git a/assets/images/video/dlna.png b/assets/images/video/dlna.png new file mode 100755 index 0000000000000000000000000000000000000000..a5d658721fd044c54976a607a9a1cbaab4e73ded GIT binary patch literal 1372 zcmV-i1*7_jP)oQ2-H@gh1wrRq{RA*Ql!`y?JuzSq=-V1h@vD&g82vRLwyo` zl{RTXS`aCu&8b7rkeyCjWL^Kckj0knapmI-FxrK&C-XQhs)fVIX~`q z&YU@OE-cUjEzkmalasGgE2-qUdC>L`!_J`ZEN#_gjc7h;M3=R-umKy?qf$|iHsmwm zn9?dGrFCq!35|AeOmSuAO?5_bjoG)x9LLlizB1N~QNhn#<`#qV;Fv3UiFbLEF)B>Y z`_9)>C@fRI`Sy%@w0XWaq&BO^>g(6Csm>#ImAdqFDdK{jR+sg_BU4?SN=cH&wWd_z zNozDNNlK{`P`O+K&4p4%NnJ2Y;;KACjft8Ou9h@bUnOF;Moon3oS<4t%4(~QP-b5; zB{d?ett|3?I7Q2R+~*ly&wtWdiaRk-JyGXcK`C3vIM<)h2(92RmihYm_YCs^sLlB( z@A#K|2x{YFM}7NoU!@{cE=5p<>^>Ne+(tMxhk>&=mnQnw<7rG*kF9GBAuQ@eNf*cB3B-;UWR;^qHXbmdO|T{Ba1`2*>4w*&&#nal#*mu#tc^`g7j; zX$!&>vJn;wg5wZA5^&L8AP2b^jHMVO9@|#h6N~ArqA3^-#TjwaWET(U@m!MgTmw3s z^T89{>{ms7);q%vtz$L6IS+kiZdHvF_tBiymM&u;dkG2PcrjwL@`4w@xie>8+*edq z+dzakGQqaK==vBbbV=ob`Og?W*OZJ@tx=O9O8WzeSfNg9_?WFhx$4x4 zK>Yg+M@plb_6;>#k=P9MtJDuElEW}x>%T;r>aJGaWsN*SHJiEeo z^v=9(S|P7+g(@?5h$>D4EeufwK#C!DI)CKMUq_97d6{?EX|E6|eqo0*o}F5)yP7C? z)$5pcI&IOgoj0VE4glzYQW`S76B^bQ>%1KWWlrd>W(NgT>$;?e1z6StXt&0#3HLRs zU6y;7Ms?qcAJ=Yk@5oONC0$p(xTM8u)+4z}C^4hHozy)o0v%GuZ058^G@wm0xHoA) zBgR)ImC+&4BHdGRMqN9hgj_vRvlbV2n^kJkjahd0sSeaC85Kz-71P=|ylWLx(kM+! zY6aD)Zw%i2#qN;3n_o`(>b&e9H#LSPr;E*za7>BHmy+ ze*8_plgx7%1d9vw<5CU`Q^}uvnscnrq@{e$^9)l7Ob~Zo46o+6QOb!VLCMvm0)1q# ze4>X*5=GlSLT|wW(Gy|OBB{?KlFTh4#U*_bZV@=D7PCEro6!(BJubqK3SMPN(8>P! e*8(k2Vfqj3bVE?>l~cn20000YP);M1& literal 0 HcmV?d00001 diff --git a/assets/images/video/fullscreen_exit.png b/assets/images/video/fullscreen_exit.png new file mode 100755 index 0000000000000000000000000000000000000000..9881ed1d91ae66a4ad74165ab928112e15400f41 GIT binary patch literal 246 zcmVbFY}^EsZi+BEQ6T=DZ}v434nEY=mNk_kRU;V1o<5`V$UJ79!3ytUi^*$)WIhU zi`Gp!lH}HwhE^Ko`{~q(ak;f*U;)J*!;sP{4sE>Vu2&p#06e*6)|t6Q>pnNShkp!$ w1PK!K56}vLYj|h^fGguO?=z;ZrKUQ01L+?Gh~%R}YXATM07*qoM6N<$f*=oM!~g&Q literal 0 HcmV?d00001 diff --git a/assets/images/video/pip.png b/assets/images/video/pip.png new file mode 100755 index 0000000000000000000000000000000000000000..1b7421256a1088dc5a8ccb7718564f9218a9fda0 GIT binary patch literal 1072 zcmV-01kd}4P)7%g6otPRrzV7&pOh-0rBrQ_1yU+NWI;D5i;|-9Cl+a?5|vF6f-OHwBowI|V2M<* ziV#&1e*$IEw2e}cuxUX`i;xJhi_{R3Hfn-WO6oYV4+}p}oQXa5%-HV6cQt-*X5N`I z=iPhe8tPGxdep-^OyObepo4~rt~A4SbGyuQ)vGU+RCVHt-qF@l0CwxFq>QQ=3wq59 z!am*5nraarsjv*RUGolv#fra>w4{Hv;{5)wpfL3VFJXWcA|yG@7&8@=WrR+8c@aZn zb~z52VK)y?ScFMsbY891*^27WxLqfsDMj4VgI12q8rPQUWdv=}h|(6w5%=W0BxzPD zbyPPosa4BXCEstHc-YxK-&H?9>ZIl3PHvB6vtmvIEu5=CmL9XKHnArkM2mC6)f#}f z;T%1d4`N-Xt1jve>l5{P$ZWrM_y&?y(M_}acnE;kc{1Gl@dh#CWYoh9=|0zNv{EAR1YthsvpiNt)KVR#mNG7H z@wIbiH)Gm)7eJT{{hYVTXrz;$)LDBbFezT;lHFEOS{c_*bn*0P{D!+~T%P^0)X_$7&7&Oq}OQvt$e2EQ7pVa}Z!Ac!ekn zw)7v2~SA33;8v^p^pT? zZQwWw+Q|^HoO**$jA;s&=@3rTWOR_TbTDe-dj`|Sjm^?=}%^vehTrrujd7`moDZk{yJ+`x}tP}q- zSIaiKK9=96VWoV0r7!7(Vig6U{efBu3GK}nW~lCzwsB!|DlQ{-b0*71gX@zg65wX1 z0zt^y#rM~rWtT(IQQtf?_|k&Ae>JCu91}CF+05tPxV00!pV3Sk?G}U)7wvGr?=DT4 zu1-_-`kW*`wUxe!FMM`t8pCI*9Eh)Mn=HZsMyuVeDe@d9tv5Jh$DZML4RMZmk&pO0 zOqf))+BgGwy7gB4yq+$Q=z>Mh#x(T-AA|km^VY?7h7_&bP8yFm z4l_&{U(R0dO4#NUdw7?NmXD&vm1*LeI#B*&E_;2k|2wwQ#-ko~?iaI~%b5Jb{Q*H- zrj4E9{Rp5F0VO%5v<51*t71xMH2{R0nsj-JP%!rD4`sAarmI+1LK8}BGUq2M$Hsu> qeDE-*fpr#4MWKZ8nDwYfUi=T2nexsIOUV5I0000 { final Box localCache = GStorage.localCache; final Box videoStorage = GStorage.video; late List speedsList; - double buttonSpace = 8; + double buttonSpace = 4; RxBool isFullScreen = false.obs; late String heroTag; late VideoIntroController videoIntroController; @@ -486,65 +486,6 @@ class _HeaderControlState extends State { }); } - /// 选择倍速 - void showSetSpeedSheet() { - final double currentSpeed = widget.controller!.playbackSpeed; - showDialog( - context: Get.context!, - builder: (BuildContext context) { - return AlertDialog( - title: const Text('播放速度'), - content: StatefulBuilder( - builder: (BuildContext context, StateSetter setState) { - return Wrap( - spacing: 8, - runSpacing: 2, - children: [ - for (final double i in speedsList) ...[ - if (i == currentSpeed) ...[ - FilledButton( - onPressed: () async { - // setState(() => currentSpeed = i), - await widget.controller!.setPlaybackSpeed(i); - Get.back(); - }, - child: Text(i.toString()), - ), - ] else ...[ - FilledButton.tonal( - onPressed: () async { - // setState(() => currentSpeed = i), - await widget.controller!.setPlaybackSpeed(i); - Get.back(); - }, - child: Text(i.toString()), - ), - ] - ] - ], - ); - }), - actions: [ - TextButton( - onPressed: () => Get.back(), - child: Text( - '取消', - style: TextStyle(color: Theme.of(context).colorScheme.outline), - ), - ), - TextButton( - onPressed: () async { - await widget.controller!.setDefaultSpeed(); - Get.back(); - }, - child: const Text('默认速度'), - ), - ], - ); - }, - ); - } - /// 选择画质 void showSetVideoQa() { final List videoFormat = videoInfo.supportFormats!; @@ -1222,7 +1163,7 @@ class _HeaderControlState extends State { fuc: () async { // 销毁播放器实例 await widget.controller!.dispose(type: 'all'); - if (mounted) { + if (context.mounted) { Navigator.popUntil( context, (Route route) => route.isFirst); } @@ -1230,21 +1171,9 @@ class _HeaderControlState extends State { ), ], const Spacer(), - // ComBtn( - // icon: const Icon( - // FontAwesomeIcons.cropSimple, - // size: 15, - // color: Colors.white, - // ), - // fuc: () => _.screenshot(), - // ), if (GlobalDataCache.enableDlna) ...[ ComBtn( - icon: const Icon( - Icons.cast, - size: 19, - color: Colors.white, - ), + icon: Image.asset('assets/images/video/dlna.png', width: 19), fuc: () async { showDialog( context: context, @@ -1255,7 +1184,10 @@ class _HeaderControlState extends State { ); }, ), + SizedBox(width: buttonSpace), ], + + /// 弹幕开关(全屏时) if (isFullScreen.value) ...[ SizedBox( width: 56, @@ -1271,6 +1203,7 @@ class _HeaderControlState extends State { ), ), ), + SizedBox(width: buttonSpace), SizedBox( width: 34, height: 34, @@ -1292,8 +1225,10 @@ class _HeaderControlState extends State { ), ), ), + SizedBox(width: buttonSpace), ], - SizedBox(width: buttonSpace), + + /// pip if (Platform.isAndroid) ...[ SizedBox( width: 34, @@ -1318,9 +1253,9 @@ class _HeaderControlState extends State { await widget.floating!.enable(aspectRatio: aspectRatio); } else {} }, - icon: const Icon( - Icons.picture_in_picture_outlined, - size: 19, + icon: Image.asset( + 'assets/images/video/pip.png', + width: 19, color: Colors.white, ), ), @@ -1329,37 +1264,21 @@ class _HeaderControlState extends State { ], /// 字幕 - if (widget.showSubtitleBtn) + if (widget.showSubtitleBtn) ...[ ComBtn( - icon: const Icon( - Icons.closed_caption_off, - size: 22, - color: Colors.white, + icon: Icon( + FontAwesomeIcons.closedCaptioning, + size: 16, + color: Colors.white.withOpacity(0.9), ), fuc: () => showSubtitleDialog(), ), - SizedBox(width: buttonSpace), - Obx( - () => SizedBox( - width: 45, - height: 34, - child: TextButton( - style: ButtonStyle( - padding: MaterialStateProperty.all(EdgeInsets.zero), - ), - onPressed: () => showSetSpeedSheet(), - child: Text( - '${_.playbackSpeed}X', - style: textStyle, - ), - ), - ), - ), - SizedBox(width: buttonSpace), + SizedBox(width: buttonSpace), + ], ComBtn( icon: const Icon( Icons.more_vert_outlined, - size: 18, + size: 19, color: Colors.white, ), fuc: () => showSettingSheet(), diff --git a/lib/plugin/pl_player/controller.dart b/lib/plugin/pl_player/controller.dart index 2013d5b2..7291cc98 100644 --- a/lib/plugin/pl_player/controller.dart +++ b/lib/plugin/pl_player/controller.dart @@ -6,6 +6,7 @@ import 'dart:typed_data'; import 'package:easy_debounce/easy_throttle.dart'; import 'package:flutter/material.dart'; +import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; import 'package:flutter_volume_controller/flutter_volume_controller.dart'; import 'package:get/get.dart'; import 'package:hive/hive.dart'; @@ -113,12 +114,13 @@ class PlPlayerController { // final Durations durations; List> videoFitType = [ - {'attr': BoxFit.contain, 'desc': '包含'}, - {'attr': BoxFit.cover, 'desc': '覆盖'}, - {'attr': BoxFit.fill, 'desc': '填充'}, - {'attr': BoxFit.fitHeight, 'desc': '高度适应'}, - {'attr': BoxFit.fitWidth, 'desc': '宽度适应'}, - {'attr': BoxFit.scaleDown, 'desc': '缩小适应'}, + {'attr': BoxFit.contain, 'desc': '自动'}, + {'attr': BoxFit.cover, 'desc': '铺满'}, + {'attr': BoxFit.fill, 'desc': '填满'}, + {'attr': BoxFit.fitHeight, 'desc': '等高'}, + {'attr': BoxFit.fitWidth, 'desc': '等宽'}, + {'attr': BoxFit.scaleDown, 'desc': '缩放'}, + {'attr': BoxFit.none, 'desc': '原始'}, ]; PreferredSizeWidget? headerControl; @@ -827,47 +829,53 @@ class PlPlayerController { } /// Toggle Change the videofit accordingly - void toggleVideoFit() { - showDialog( - context: Get.context!, - builder: (context) { - return AlertDialog( - title: const Text('画面比例'), - content: StatefulBuilder(builder: (context, StateSetter setState) { - return Wrap( - alignment: WrapAlignment.start, + void toggleVideoFit(String toggleType) { + if (toggleType == 'press') { + final String videoFitDEsc = _videoFitDesc.value; + final int index = videoFitType.indexWhere( + (element) => element['desc'] == videoFitDEsc, + ); + final int newIndex = index + 1 >= videoFitType.length ? 0 : index + 1; + _videoFit.value = videoFitType[newIndex]['attr']; + _videoFitDesc.value = videoFitType[newIndex]['desc']; + setVideoFit(); + SmartDialog.showToast('画面比例:${videoFitType[newIndex]['desc']}'); + } else { + void onPressed(item) { + _videoFit.value = item['attr']; + _videoFitDesc.value = item['desc']; + setVideoFit(); + Navigator.of(Get.context!).pop(); + } + + showDialog( + context: Get.context!, + builder: (context) { + return AlertDialog( + title: const Text('画面比例'), + content: Wrap( spacing: 8, runSpacing: 2, children: [ for (var i in videoFitType) ...[ if (_videoFit.value == i['attr']) ...[ FilledButton( - onPressed: () async { - _videoFit.value = i['attr']; - _videoFitDesc.value = i['desc']; - setVideoFit(); - Get.back(); - }, + onPressed: () => onPressed(i), child: Text(i['desc']), ), ] else ...[ FilledButton.tonal( - onPressed: () async { - _videoFit.value = i['attr']; - _videoFitDesc.value = i['desc']; - setVideoFit(); - Get.back(); - }, + onPressed: () => onPressed(i), child: Text(i['desc']), ), ] ] ], - ); - }), - ); - }, - ); + ), + ); + }, + ); + } } /// 缓存fit diff --git a/lib/plugin/pl_player/view.dart b/lib/plugin/pl_player/view.dart index c73bc55f..415830e0 100644 --- a/lib/plugin/pl_player/view.dart +++ b/lib/plugin/pl_player/view.dart @@ -301,16 +301,22 @@ class _PLVideoPlayerState extends State /// 画面比例 BottomControlType.fit: SizedBox( + width: 45, height: 30, child: TextButton( - onPressed: () => _.toggleVideoFit(), + onPressed: () => _.toggleVideoFit('press'), + onLongPress: () => _.toggleVideoFit('longPress'), style: ButtonStyle( padding: MaterialStateProperty.all(EdgeInsets.zero), ), child: Obx( () => Text( _.videoFitDEsc.value, - style: const TextStyle(color: Colors.white, fontSize: 13), + style: const TextStyle( + color: Colors.white, + fontSize: 12, + fontWeight: FontWeight.bold, + ), ), ), ), @@ -320,29 +326,49 @@ class _PLVideoPlayerState extends State BottomControlType.speed: SizedBox( width: 45, height: 34, - child: TextButton( - style: ButtonStyle( - padding: MaterialStateProperty.all(EdgeInsets.zero), - ), - onPressed: () {}, - child: Obx( - () => Text( - '${_.playbackSpeed.toString()}X', - style: textStyle, + child: PopupMenuButton( + tooltip: '更改播放速度', + onSelected: (double value) { + _.setPlaybackSpeed(value); + }, + initialValue: _.playbackSpeed, + color: Colors.black.withOpacity(0.8), + itemBuilder: (BuildContext context) { + return _.speedsList.map((double speed) { + return PopupMenuItem( + height: 40, + padding: const EdgeInsets.only(left: 20), + value: speed, + child: Text( + '${speed}x', + style: textStyle.copyWith(fontWeight: FontWeight.bold), + ), + ); + }).toList(); + }, + child: Container( + width: 45, + height: 34, + alignment: Alignment.center, + margin: const EdgeInsets.only(right: 4), + child: Obx( + () => Text( + '${_.playbackSpeed.toString()}x', + style: textStyle.copyWith(fontWeight: FontWeight.bold), + ), ), ), ), ), - /// 字幕 /// 全屏 BottomControlType.fullscreen: ComBtn( icon: Obx( - () => Icon( + () => Image.asset( _.isFullScreen.value - ? FontAwesomeIcons.compress - : FontAwesomeIcons.expand, - size: 15, + ? 'assets/images/video/fullscreen_exit.png' + : 'assets/images/video/fullscreen.png', + width: 19, color: Colors.white, ), ), @@ -359,6 +385,7 @@ class _PLVideoPlayerState extends State BottomControlType.time, BottomControlType.space, BottomControlType.fit, + BottomControlType.speed, BottomControlType.fullscreen, ]; for (var i = 0; i < userSpecifyItem.length; i++) { From 5201c66ac163af98fa1ad0047a2b240857b4015e Mon Sep 17 00:00:00 2001 From: guozhigq Date: Sun, 24 Nov 2024 17:38:35 +0800 Subject: [PATCH 07/43] feat: hardwareDecodeFormat --- lib/pages/setting/play_setting.dart | 31 ++++++++++++++++++++++++++++ lib/plugin/pl_player/controller.dart | 1 + lib/utils/global_data_cache.dart | 5 +++++ lib/utils/storage.dart | 3 ++- 4 files changed, 39 insertions(+), 1 deletion(-) diff --git a/lib/pages/setting/play_setting.dart b/lib/pages/setting/play_setting.dart index 7090bfaf..c6d7fe0b 100644 --- a/lib/pages/setting/play_setting.dart +++ b/lib/pages/setting/play_setting.dart @@ -31,6 +31,7 @@ class _PlaySettingState extends State { late int defaultFullScreenMode; late int defaultBtmProgressBehavior; late String defaultAoOutput; + late String hardwareDecodeFormat; @override void initState() { @@ -49,6 +50,8 @@ class _PlaySettingState extends State { defaultValue: BtmProgresBehavior.values.first.code); defaultAoOutput = setting.get(SettingBoxKey.defaultAoOutput, defaultValue: '0'); + hardwareDecodeFormat = setting.get(SettingBoxKey.hardwareDecodeFormat, + defaultValue: Platform.isAndroid ? 'auto-safe' : 'auto'); } @override @@ -294,6 +297,34 @@ class _PlaySettingState extends State { } }, ), + ListTile( + dense: false, + title: Text('硬解方式', style: titleStyle), + subtitle: Text( + '当前硬解方式(--hwdec):$hardwareDecodeFormat', + style: subTitleStyle, + ), + onTap: () async { + String? result = await showDialog( + context: context, + builder: (context) { + return SelectDialog( + title: '硬解方式', + value: hardwareDecodeFormat, + values: ['no', 'auto-safe', 'auto', 'yes', 'auto-copy'] + .map((e) { + return {'title': e, 'value': e}; + }).toList()); + }, + ); + if (result != null) { + setting.put(SettingBoxKey.hardwareDecodeFormat, result); + hardwareDecodeFormat = result; + GlobalDataCache.hardwareDecodeFormat = result; + setState(() {}); + } + }, + ), ListTile( dense: false, title: Text('默认全屏方式', style: titleStyle), diff --git a/lib/plugin/pl_player/controller.dart b/lib/plugin/pl_player/controller.dart index 2013d5b2..075a2e91 100644 --- a/lib/plugin/pl_player/controller.dart +++ b/lib/plugin/pl_player/controller.dart @@ -470,6 +470,7 @@ class PlPlayerController { configuration: VideoControllerConfiguration( enableHardwareAcceleration: enableHA, androidAttachSurfaceAfterVideoParameters: false, + hwdec: enableHA ? GlobalDataCache.hardwareDecodeFormat : null, ), ); diff --git a/lib/utils/global_data_cache.dart b/lib/utils/global_data_cache.dart index a421f829..911a4285 100644 --- a/lib/utils/global_data_cache.dart +++ b/lib/utils/global_data_cache.dart @@ -1,3 +1,4 @@ +import 'dart:io'; import 'package:hive/hive.dart'; import 'package:pilipala/models/user/info.dart'; import 'package:pilipala/plugin/pl_player/models/play_repeat.dart'; @@ -55,6 +56,8 @@ class GlobalDataCache { static late bool enableDynamicSwitch; // 投屏开关 static bool enableDlna = false; + // 硬件解码格式 + static late String hardwareDecodeFormat; // 私有构造函数 GlobalDataCache._(); @@ -123,5 +126,7 @@ class GlobalDataCache { enableDynamicSwitch = setting.get(SettingBoxKey.enableDynamicSwitch, defaultValue: true); enableDlna = setting.get(SettingBoxKey.enableDlna, defaultValue: false); + hardwareDecodeFormat = setting.get(SettingBoxKey.hardwareDecodeFormat, + defaultValue: Platform.isAndroid ? 'auto-safe' : 'auto'); } } diff --git a/lib/utils/storage.dart b/lib/utils/storage.dart index 49a5c734..04896341 100644 --- a/lib/utils/storage.dart +++ b/lib/utils/storage.dart @@ -116,7 +116,8 @@ class SettingBoxKey { enableAutoExpand = 'enableAutoExpand', defaultHomePage = 'defaultHomePage', enableRelatedVideo = 'enableRelatedVideo', - enableDlna = 'enableDlna'; + enableDlna = 'enableDlna', + hardwareDecodeFormat = 'hardwareDecodeFormat'; /// 外观 static const String themeMode = 'themeMode', From 747ed377b510a66d98cd4559cd83f0cd2ee805b4 Mon Sep 17 00:00:00 2001 From: guozhigq Date: Sun, 24 Nov 2024 17:46:20 +0800 Subject: [PATCH 08/43] mod --- lib/pages/whisper_detail/view.dart | 16 +--------------- 1 file changed, 1 insertion(+), 15 deletions(-) diff --git a/lib/pages/whisper_detail/view.dart b/lib/pages/whisper_detail/view.dart index 3c3d9f18..e70c6f3c 100644 --- a/lib/pages/whisper_detail/view.dart +++ b/lib/pages/whisper_detail/view.dart @@ -101,25 +101,11 @@ class _WhisperDetailPageState extends State Widget build(BuildContext context) { return Scaffold( appBar: AppBar( - automaticallyImplyLeading: false, title: SizedBox( - width: double.infinity, + width: double.maxFinite, height: 50, child: Row( children: [ - SizedBox( - width: 34, - height: 34, - child: IconButton( - onPressed: () => Get.back(), - icon: Icon( - Icons.arrow_back_ios, - size: 18, - color: Theme.of(context).colorScheme.onPrimaryContainer, - ), - ), - ), - const SizedBox(width: 10), GestureDetector( onTap: () { feedBack(); From ea900ffe4eaf3f4ceef1d3b57064b845a5045844 Mon Sep 17 00:00:00 2001 From: guozhigq Date: Sun, 24 Nov 2024 22:53:48 +0800 Subject: [PATCH 09/43] opt: jumpTo rebound --- lib/common/pages_bottom_sheet.dart | 64 ++++++++++++++++-------------- 1 file changed, 34 insertions(+), 30 deletions(-) diff --git a/lib/common/pages_bottom_sheet.dart b/lib/common/pages_bottom_sheet.dart index dd8c5490..fa6f61c7 100644 --- a/lib/common/pages_bottom_sheet.dart +++ b/lib/common/pages_bottom_sheet.dart @@ -1,4 +1,5 @@ import 'package:flutter/material.dart'; +import 'package:flutter/widgets.dart'; import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; import 'package:get/get.dart'; import 'package:pilipala/common/constants.dart'; @@ -98,7 +99,8 @@ class _PagesBottomSheetState extends State with TickerProviderStateMixin { final ScrollController _listScrollController = ScrollController(); late ListObserverController _listObserverController; - final ScrollController _scrollController = ScrollController(); + late GridObserverController _gridObserverController; + final ScrollController _gridScrollController = ScrollController(); late int currentIndex; TabController? tabController; List? _listObserverControllerList; @@ -163,6 +165,9 @@ class _PagesBottomSheetState extends State ); }, ); + } else { + _gridObserverController = + GridObserverController(controller: _gridScrollController); } } @@ -185,18 +190,12 @@ class _PagesBottomSheetState extends State ); } } + } else { + _gridObserverController.initialIndexModel = ObserverIndexPositionModel( + index: currentIndex, + isFixedHeight: false, + ); } - - WidgetsBinding.instance.addPostFrameCallback((_) { - if (widget.dataType != VideoEpidoesType.videoEpisode) { - double itemHeight = (widget.isFullScreen - ? 400 - : Get.size.width - 3 * StyleString.safeSpace) / - 5.2; - double offset = ((currentIndex - 1) / 2).ceil() * itemHeight; - _scrollController.jumpTo(offset); - } - }); } // 获取订阅状态 @@ -236,7 +235,9 @@ class _PagesBottomSheetState extends State void dispose() { try { _listObserverController.controller?.dispose(); + _gridObserverController.controller?.dispose(); _listScrollController.dispose(); + _gridScrollController.dispose(); for (var element in _listObserverControllerList!) { element.controller?.dispose(); } @@ -303,24 +304,27 @@ class _PagesBottomSheetState extends State : Padding( padding: const EdgeInsets.symmetric( horizontal: 12.0), // 设置左右间距为12 - child: GridView.count( - controller: _scrollController, - crossAxisCount: 2, - crossAxisSpacing: StyleString.safeSpace, - childAspectRatio: 2.6, - children: List.generate( - widget.episodes.length, - (index) { - bool isCurrentIndex = currentIndex == index; - return EpisodeGridItem( - episode: widget.episodes[index], - index: index, - isCurrentIndex: isCurrentIndex, - dataType: widget.dataType, - changeFucCall: widget.changeFucCall, - isFullScreen: widget.isFullScreen, - ); - }, + child: GridViewObserver( + controller: _gridObserverController, + child: GridView.count( + controller: _gridScrollController, + crossAxisCount: 2, + crossAxisSpacing: StyleString.safeSpace, + childAspectRatio: 2.6, + children: List.generate( + widget.episodes.length, + (index) { + bool isCurrentIndex = currentIndex == index; + return EpisodeGridItem( + episode: widget.episodes[index], + index: index, + isCurrentIndex: isCurrentIndex, + dataType: widget.dataType, + changeFucCall: widget.changeFucCall, + isFullScreen: widget.isFullScreen, + ); + }, + ), ), ), ), From 0ae6238d0e4bb39152ad0ff249fc465d39950bc3 Mon Sep 17 00:00:00 2001 From: guozhigq Date: Mon, 25 Nov 2024 22:39:04 +0800 Subject: [PATCH 10/43] opt: pl_gallery layout --- .../pl_gallery/interactiveviewer_gallery.dart | 109 +++++++++--------- 1 file changed, 53 insertions(+), 56 deletions(-) diff --git a/lib/plugin/pl_gallery/interactiveviewer_gallery.dart b/lib/plugin/pl_gallery/interactiveviewer_gallery.dart index f0b11619..88c9129c 100644 --- a/lib/plugin/pl_gallery/interactiveviewer_gallery.dart +++ b/lib/plugin/pl_gallery/interactiveviewer_gallery.dart @@ -234,17 +234,17 @@ class _InteractiveviewerGalleryState extends State @override Widget build(BuildContext context) { - return InteractiveViewerBoundary( - controller: _transformationController, - boundaryWidth: MediaQuery.of(context).size.width, - onScaleChanged: _onScaleChanged, - onLeftBoundaryHit: _onLeftBoundaryHit, - onRightBoundaryHit: _onRightBoundaryHit, - onNoBoundaryHit: _onNoBoundaryHit, - maxScale: widget.maxScale, - minScale: widget.minScale, - child: Stack(children: [ - CustomDismissible( + return Stack(children: [ + InteractiveViewerBoundary( + controller: _transformationController, + boundaryWidth: MediaQuery.of(context).size.width, + onScaleChanged: _onScaleChanged, + onLeftBoundaryHit: _onLeftBoundaryHit, + onRightBoundaryHit: _onRightBoundaryHit, + onNoBoundaryHit: _onNoBoundaryHit, + maxScale: widget.maxScale, + minScale: widget.minScale, + child: CustomDismissible( onDismissed: () { Navigator.of(context).pop(); widget.onDismissed?.call(_pageController!.page!.floor()); @@ -275,53 +275,50 @@ class _InteractiveviewerGalleryState extends State }, ), ), - Positioned( - bottom: 0, - left: 0, - right: 0, - child: Container( - padding: EdgeInsets.fromLTRB( - 12, 8, 20, MediaQuery.of(context).padding.bottom + 8), - decoration: _enablePageView - ? BoxDecoration( - gradient: LinearGradient( - begin: Alignment.topCenter, - end: Alignment.bottomCenter, - colors: [ - Colors.transparent, - Colors.black.withOpacity(0.3) - ], - ), - ) - : null, - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - IconButton( - icon: const Icon(Icons.close, color: Colors.white), - onPressed: () { - Navigator.of(context).pop(); - widget.onDismissed?.call(_pageController!.page!.floor()); - }, - ), - widget.sources.length > 1 - ? Text( - "${currentIndex! + 1}/${widget.sources.length}", - style: const TextStyle(color: Colors.white), - ) - : const SizedBox(), - PopupMenuButton( - itemBuilder: (context) { - return _buildPopupMenuList(); - }, - child: const Icon(Icons.more_horiz, color: Colors.white), - ), - ], - ), + ), + Positioned( + bottom: 0, + left: 0, + right: 0, + child: Container( + padding: EdgeInsets.fromLTRB( + 12, 8, 20, MediaQuery.of(context).padding.bottom + 8), + decoration: _enablePageView + ? BoxDecoration( + gradient: LinearGradient( + begin: Alignment.topCenter, + end: Alignment.bottomCenter, + colors: [Colors.transparent, Colors.black.withOpacity(0.3)], + ), + ) + : null, + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + IconButton( + icon: const Icon(Icons.close, color: Colors.white), + onPressed: () { + Navigator.of(context).pop(); + widget.onDismissed?.call(_pageController!.page!.floor()); + }, + ), + widget.sources.length > 1 + ? Text( + "${currentIndex! + 1}/${widget.sources.length}", + style: const TextStyle(color: Colors.white), + ) + : const SizedBox(), + PopupMenuButton( + itemBuilder: (context) { + return _buildPopupMenuList(); + }, + child: const Icon(Icons.more_horiz, color: Colors.white), + ), + ], ), ), - ]), - ); + ), + ]); } // 图片分享 From 859ee82367162c82a92d1cd7bd0f15fa8122d18a Mon Sep 17 00:00:00 2001 From: guozhigq Date: Mon, 25 Nov 2024 23:40:48 +0800 Subject: [PATCH 11/43] opt: bottomSheet DragHandle --- lib/common/widgets/drag_handle.dart | 25 +++ lib/common/widgets/video_card_h.dart | 20 +-- lib/common/widgets/video_card_v.dart | 20 +-- lib/pages/bangumi/introduction/view.dart | 20 +-- .../introduction/widgets/intro_detail.dart | 154 ++++++++---------- lib/pages/dynamics/widgets/author_panel.dart | 21 +-- .../detail/reply/widgets/reply_item.dart | 20 +-- lib/pages/video/detail/widgets/ai_detail.dart | 36 ++-- .../video/detail/widgets/header_control.dart | 18 +- .../pl_gallery/interactiveviewer_gallery.dart | 22 +-- 10 files changed, 129 insertions(+), 227 deletions(-) create mode 100644 lib/common/widgets/drag_handle.dart diff --git a/lib/common/widgets/drag_handle.dart b/lib/common/widgets/drag_handle.dart new file mode 100644 index 00000000..1143a732 --- /dev/null +++ b/lib/common/widgets/drag_handle.dart @@ -0,0 +1,25 @@ +import 'package:flutter/material.dart'; + +class DragHandle extends StatelessWidget { + const DragHandle({super.key}); + + @override + Widget build(BuildContext context) { + return InkWell( + onTap: Navigator.of(context).pop, + child: SizedBox( + height: 36, + child: Center( + child: Container( + width: 32, + height: 4, + decoration: BoxDecoration( + color: Theme.of(context).colorScheme.outline, + borderRadius: BorderRadius.circular(4), + ), + ), + ), + ), + ); + } +} diff --git a/lib/common/widgets/video_card_h.dart b/lib/common/widgets/video_card_h.dart index 78c4ba87..b662a3b0 100644 --- a/lib/common/widgets/video_card_h.dart +++ b/lib/common/widgets/video_card_h.dart @@ -12,6 +12,7 @@ import '../../http/video.dart'; import '../../utils/utils.dart'; import '../constants.dart'; import 'badge.dart'; +import 'drag_handle.dart'; import 'network_img_layer.dart'; import 'stat/danmu.dart'; import 'stat/view.dart'; @@ -373,27 +374,12 @@ class MorePanel extends StatelessWidget { @override Widget build(BuildContext context) { - return Container( + return Padding( padding: EdgeInsets.only(bottom: MediaQuery.of(context).padding.bottom), child: Column( mainAxisSize: MainAxisSize.min, children: [ - InkWell( - onTap: () => Get.back(), - child: Container( - height: 35, - padding: const EdgeInsets.only(bottom: 2), - child: Center( - child: Container( - width: 32, - height: 3, - decoration: BoxDecoration( - color: Theme.of(context).colorScheme.outline, - borderRadius: const BorderRadius.all(Radius.circular(3))), - ), - ), - ), - ), + const DragHandle(), ListTile( onTap: () async => await menuActionHandler('block'), minLeadingWidth: 0, diff --git a/lib/common/widgets/video_card_v.dart b/lib/common/widgets/video_card_v.dart index 8cec3523..72cfb998 100644 --- a/lib/common/widgets/video_card_v.dart +++ b/lib/common/widgets/video_card_v.dart @@ -5,6 +5,7 @@ import 'package:pilipala/utils/feed_back.dart'; import 'package:pilipala/utils/image_save.dart'; import 'package:pilipala/utils/route_push.dart'; import '../../models/model_rec_video_item.dart'; +import 'drag_handle.dart'; import 'stat/danmu.dart'; import 'stat/view.dart'; import '../../http/dynamics.dart'; @@ -368,27 +369,12 @@ class MorePanel extends StatelessWidget { @override Widget build(BuildContext context) { - return Container( + return Padding( padding: EdgeInsets.only(bottom: MediaQuery.of(context).padding.bottom), child: Column( mainAxisSize: MainAxisSize.min, children: [ - InkWell( - onTap: () => Get.back(), - child: Container( - height: 35, - padding: const EdgeInsets.only(bottom: 2), - child: Center( - child: Container( - width: 32, - height: 3, - decoration: BoxDecoration( - color: Theme.of(context).colorScheme.outline, - borderRadius: const BorderRadius.all(Radius.circular(3))), - ), - ), - ), - ), + const DragHandle(), ListTile( onTap: () async => await menuActionHandler('block'), minLeadingWidth: 0, diff --git a/lib/pages/bangumi/introduction/view.dart b/lib/pages/bangumi/introduction/view.dart index 75e293c4..4fc6a1b1 100644 --- a/lib/pages/bangumi/introduction/view.dart +++ b/lib/pages/bangumi/introduction/view.dart @@ -5,6 +5,7 @@ import 'package:get/get.dart'; import 'package:hive/hive.dart'; import 'package:pilipala/common/constants.dart'; import 'package:pilipala/common/widgets/badge.dart'; +import 'package:pilipala/common/widgets/drag_handle.dart'; import 'package:pilipala/common/widgets/network_img_layer.dart'; import 'package:pilipala/common/widgets/stat/danmu.dart'; import 'package:pilipala/common/widgets/stat/view.dart'; @@ -445,27 +446,12 @@ class BangumiStatusWidget extends StatelessWidget { } Widget morePanel(BuildContext context, BangumiIntroController ctr) { - return Container( + return Padding( padding: EdgeInsets.only(bottom: MediaQuery.of(context).padding.bottom), child: Column( mainAxisSize: MainAxisSize.min, children: [ - InkWell( - onTap: () => Get.back(), - child: Container( - height: 35, - padding: const EdgeInsets.only(bottom: 2), - child: Center( - child: Container( - width: 32, - height: 3, - decoration: BoxDecoration( - color: Theme.of(context).colorScheme.outline, - borderRadius: const BorderRadius.all(Radius.circular(3))), - ), - ), - ), - ), + const DragHandle(), ...ctr.followStatusList .map( (e) => ListTile( diff --git a/lib/pages/bangumi/introduction/widgets/intro_detail.dart b/lib/pages/bangumi/introduction/widgets/intro_detail.dart index a4c469de..04c035ed 100644 --- a/lib/pages/bangumi/introduction/widgets/intro_detail.dart +++ b/lib/pages/bangumi/introduction/widgets/intro_detail.dart @@ -1,5 +1,6 @@ import 'package:flutter/material.dart'; import 'package:hive/hive.dart'; +import 'package:pilipala/common/widgets/drag_handle.dart'; import 'package:pilipala/common/widgets/stat/danmu.dart'; import 'package:pilipala/common/widgets/stat/view.dart'; import 'package:pilipala/utils/storage.dart'; @@ -23,94 +24,81 @@ class IntroDetail extends StatelessWidget { color: Theme.of(context).colorScheme.onSurface, ); return Container( - color: Theme.of(context).colorScheme.surface, - padding: const EdgeInsets.only(left: 14, right: 14), + padding: EdgeInsets.only(bottom: MediaQuery.of(context).padding.bottom), height: sheetHeight, child: Column( children: [ - Container( - height: 35, - padding: const EdgeInsets.only(bottom: 2), - child: Center( - child: Container( - width: 32, - height: 3, - decoration: BoxDecoration( - color: Theme.of(context) - .colorScheme - .onSecondaryContainer - .withOpacity(0.5), - borderRadius: const BorderRadius.all(Radius.circular(3))), - ), - ), - ), + const DragHandle(), Expanded( - child: SingleChildScrollView( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - bangumiDetail!.title, - style: const TextStyle( - fontSize: 16, - fontWeight: FontWeight.w500, + child: Padding( + padding: const EdgeInsets.only(left: 16, right: 16), + child: SingleChildScrollView( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + bangumiDetail!.title, + style: const TextStyle( + fontSize: 16, + fontWeight: FontWeight.w500, + ), ), - ), - const SizedBox(height: 4), - Row( - children: [ - StatView( - view: bangumiDetail!.stat!['views'], - size: 'medium', - ), - const SizedBox(width: 6), - StatDanMu( - danmu: bangumiDetail!.stat!['danmakus'], - size: 'medium', - ), - ], - ), - const SizedBox(height: 4), - Row( - children: [ - Text( - bangumiDetail!.areas!.first['name'], - style: smallTitle, - ), - const SizedBox(width: 6), - Text( - bangumiDetail!.publish!['pub_time_show'], - style: smallTitle, - ), - const SizedBox(width: 6), - Text( - bangumiDetail!.newEp!['desc'], - style: smallTitle, - ), - ], - ), - const SizedBox(height: 20), - Text( - '简介:', - style: Theme.of(context).textTheme.titleMedium, - ), - const SizedBox(height: 4), - Text( - '${bangumiDetail!.evaluate!}', - style: smallTitle.copyWith(fontSize: 13), - ), - const SizedBox(height: 20), - Text( - '声优:', - style: Theme.of(context).textTheme.titleMedium, - ), - const SizedBox(height: 4), - Text( - bangumiDetail.actors, - style: smallTitle.copyWith(fontSize: 13), - ), - SizedBox(height: MediaQuery.of(context).padding.bottom + 20) - ], + const SizedBox(height: 4), + Row( + children: [ + StatView( + view: bangumiDetail!.stat!['views'], + size: 'medium', + ), + const SizedBox(width: 6), + StatDanMu( + danmu: bangumiDetail!.stat!['danmakus'], + size: 'medium', + ), + ], + ), + const SizedBox(height: 4), + Row( + children: [ + Text( + bangumiDetail!.areas!.first['name'], + style: smallTitle, + ), + const SizedBox(width: 6), + Text( + bangumiDetail!.publish!['pub_time_show'], + style: smallTitle, + ), + const SizedBox(width: 6), + Text( + bangumiDetail!.newEp!['desc'], + style: smallTitle, + ), + ], + ), + const SizedBox(height: 20), + Text( + '简介:', + style: Theme.of(context).textTheme.titleMedium, + ), + const SizedBox(height: 4), + Text( + '${bangumiDetail!.evaluate!}', + style: smallTitle.copyWith(fontSize: 13), + ), + const SizedBox(height: 20), + Text( + '声优:', + style: Theme.of(context).textTheme.titleMedium, + ), + const SizedBox(height: 4), + Text( + bangumiDetail.actors, + style: smallTitle.copyWith(fontSize: 13), + ), + SizedBox(height: MediaQuery.of(context).padding.bottom + 20) + ], + ), ), ), ) diff --git a/lib/pages/dynamics/widgets/author_panel.dart b/lib/pages/dynamics/widgets/author_panel.dart index 8acdc26a..64068b0a 100644 --- a/lib/pages/dynamics/widgets/author_panel.dart +++ b/lib/pages/dynamics/widgets/author_panel.dart @@ -1,6 +1,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; import 'package:get/get.dart'; +import 'package:pilipala/common/widgets/drag_handle.dart'; import 'package:pilipala/common/widgets/network_img_layer.dart'; import 'package:pilipala/http/user.dart'; import 'package:pilipala/utils/feed_back.dart'; @@ -108,28 +109,12 @@ class MorePanel extends StatelessWidget { @override Widget build(BuildContext context) { - return Container( + return Padding( padding: EdgeInsets.only(bottom: MediaQuery.of(context).padding.bottom), - // clipBehavior: Clip.hardEdge, child: Column( mainAxisSize: MainAxisSize.min, children: [ - InkWell( - onTap: () => Get.back(), - child: Container( - height: 35, - padding: const EdgeInsets.only(bottom: 2), - child: Center( - child: Container( - width: 32, - height: 3, - decoration: BoxDecoration( - color: Theme.of(context).colorScheme.outline, - borderRadius: const BorderRadius.all(Radius.circular(3))), - ), - ), - ), - ), + const DragHandle(), ListTile( onTap: () async { try { diff --git a/lib/pages/video/detail/reply/widgets/reply_item.dart b/lib/pages/video/detail/reply/widgets/reply_item.dart index e0a6d07f..a453a0fb 100644 --- a/lib/pages/video/detail/reply/widgets/reply_item.dart +++ b/lib/pages/video/detail/reply/widgets/reply_item.dart @@ -7,6 +7,7 @@ import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; import 'package:get/get.dart'; import 'package:hive/hive.dart'; import 'package:pilipala/common/widgets/badge.dart'; +import 'package:pilipala/common/widgets/drag_handle.dart'; import 'package:pilipala/common/widgets/network_img_layer.dart'; import 'package:pilipala/http/reply.dart'; import 'package:pilipala/models/common/reply_type.dart'; @@ -1117,27 +1118,12 @@ class MorePanel extends StatelessWidget { ColorScheme colorScheme = Theme.of(context).colorScheme; TextTheme textTheme = Theme.of(context).textTheme; Color errorColor = colorScheme.error; - return Container( + return Padding( padding: EdgeInsets.only(bottom: MediaQuery.of(context).padding.bottom), child: Column( mainAxisSize: MainAxisSize.min, children: [ - InkWell( - onTap: () => Get.back(), - child: Container( - height: 35, - padding: const EdgeInsets.only(bottom: 2), - child: Center( - child: Container( - width: 32, - height: 3, - decoration: BoxDecoration( - color: colorScheme.outline, - borderRadius: const BorderRadius.all(Radius.circular(3))), - ), - ), - ), - ), + const DragHandle(), ListTile( onTap: () async => await menuActionHandler('copyAll'), minLeadingWidth: 0, diff --git a/lib/pages/video/detail/widgets/ai_detail.dart b/lib/pages/video/detail/widgets/ai_detail.dart index c17591fb..a82e3506 100644 --- a/lib/pages/video/detail/widgets/ai_detail.dart +++ b/lib/pages/video/detail/widgets/ai_detail.dart @@ -1,6 +1,7 @@ import 'package:flutter/gestures.dart'; import 'package:flutter/material.dart'; import 'package:get/get.dart'; +import 'package:pilipala/common/widgets/drag_handle.dart'; import 'package:pilipala/models/video/ai.dart'; import 'package:pilipala/pages/video/detail/index.dart'; import 'package:pilipala/utils/global_data_cache.dart'; @@ -17,21 +18,24 @@ class AiDetail extends StatelessWidget { @override Widget build(BuildContext context) { return Container( - padding: const EdgeInsets.only(left: 16, right: 16), + padding: EdgeInsets.only(bottom: MediaQuery.of(context).padding.bottom), height: GlobalDataCache.sheetHeight, child: Column( children: [ - _buildHeader(context), + const DragHandle(), Expanded( child: SingleChildScrollView( - child: Column( - children: [ - if (modelResult!.summary != '') ...[ - _buildSummaryText(modelResult!.summary!), - const SizedBox(height: 20), + child: Padding( + padding: const EdgeInsets.only(left: 16, right: 16), + child: Column( + children: [ + if (modelResult!.summary != '') ...[ + _buildSummaryText(modelResult!.summary!), + const SizedBox(height: 20), + ], + _buildOutlineList(context), ], - _buildOutlineList(context), - ], + ), ), ), ), @@ -40,20 +44,6 @@ class AiDetail extends StatelessWidget { ); } - Widget _buildHeader(BuildContext context) { - return Center( - child: Container( - decoration: BoxDecoration( - color: Theme.of(context).hintColor, - borderRadius: const BorderRadius.all(Radius.circular(10)), - ), - height: 4, - width: 40, - margin: const EdgeInsets.symmetric(vertical: 16), - ), - ); - } - Widget _buildSummaryText(String summary) { return SelectableText( summary, diff --git a/lib/pages/video/detail/widgets/header_control.dart b/lib/pages/video/detail/widgets/header_control.dart index 5b2c51ae..0fbdc493 100644 --- a/lib/pages/video/detail/widgets/header_control.dart +++ b/lib/pages/video/detail/widgets/header_control.dart @@ -8,6 +8,7 @@ import 'package:font_awesome_flutter/font_awesome_flutter.dart'; import 'package:get/get.dart'; import 'package:hive/hive.dart'; import 'package:ns_danmaku/ns_danmaku.dart'; +import 'package:pilipala/common/widgets/drag_handle.dart'; import 'package:pilipala/http/user.dart'; import 'package:pilipala/models/video/play/quality.dart'; import 'package:pilipala/models/video/play/url.dart'; @@ -102,22 +103,7 @@ class _HeaderControlState extends State { margin: const EdgeInsets.all(12), child: Column( children: [ - SizedBox( - height: 35, - child: Center( - child: Container( - width: 32, - height: 3, - decoration: BoxDecoration( - color: Theme.of(context) - .colorScheme - .onSecondaryContainer - .withOpacity(0.5), - borderRadius: - const BorderRadius.all(Radius.circular(3))), - ), - ), - ), + const DragHandle(), Expanded( child: Material( child: ListView( diff --git a/lib/plugin/pl_gallery/interactiveviewer_gallery.dart b/lib/plugin/pl_gallery/interactiveviewer_gallery.dart index 88c9129c..6776b10c 100644 --- a/lib/plugin/pl_gallery/interactiveviewer_gallery.dart +++ b/lib/plugin/pl_gallery/interactiveviewer_gallery.dart @@ -7,8 +7,8 @@ import 'package:dio/dio.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; -import 'package:get/get.dart'; import 'package:path_provider/path_provider.dart'; +import 'package:pilipala/common/widgets/drag_handle.dart'; import 'package:pilipala/utils/download.dart'; import 'package:share_plus/share_plus.dart'; import 'package:status_bar_control/status_bar_control.dart'; @@ -423,29 +423,13 @@ class _InteractiveviewerGalleryState extends State useRootNavigator: true, isScrollControlled: true, builder: (context) { - return Container( + return Padding( padding: EdgeInsets.only(bottom: MediaQuery.of(context).padding.bottom), child: Column( mainAxisSize: MainAxisSize.min, children: [ - InkWell( - onTap: () => Get.back(), - child: Container( - height: 35, - padding: const EdgeInsets.only(bottom: 2), - child: Center( - child: Container( - width: 32, - height: 3, - decoration: BoxDecoration( - color: Theme.of(context).colorScheme.outline, - borderRadius: - const BorderRadius.all(Radius.circular(3))), - ), - ), - ), - ), + const DragHandle(), ..._buildListTitles(), ], ), From 6ba022828c8ba4aa4fdd92f9bb3d01bb21ffae23 Mon Sep 17 00:00:00 2001 From: guozhigq Date: Tue, 26 Nov 2024 23:31:20 +0800 Subject: [PATCH 12/43] opt: videoDetail SliverAppBar height change --- lib/pages/video/detail/view.dart | 4 +-- lib/plugin/pl_player/view.dart | 42 +++++++++++++++++++------------- 2 files changed, 27 insertions(+), 19 deletions(-) diff --git a/lib/pages/video/detail/view.dart b/lib/pages/video/detail/view.dart index 564ab65a..55aafeb5 100644 --- a/lib/pages/video/detail/view.dart +++ b/lib/pages/video/detail/view.dart @@ -680,8 +680,8 @@ class _VideoDetailPageState extends State forceElevated: innerBoxIsScrolled, expandedHeight: expandedHeight, backgroundColor: Colors.black, - flexibleSpace: FlexibleSpaceBar( - background: PopScope( + flexibleSpace: SizedBox.expand( + child: PopScope( canPop: plPlayerController?.isFullScreen.value != true, onPopInvoked: (bool didPop) { diff --git a/lib/plugin/pl_player/view.dart b/lib/plugin/pl_player/view.dart index c73bc55f..4c405cc0 100644 --- a/lib/plugin/pl_player/view.dart +++ b/lib/plugin/pl_player/view.dart @@ -739,29 +739,37 @@ class _PLVideoPlayerState extends State // 头部、底部控制条 Obx( () => Column( + mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - if (widget.headerControl != null || _.headerControl != null) - ClipRect( + if (widget.headerControl != null || _.headerControl != null) ...[ + Flexible( + child: ClipRect( + child: AppBarAni( + controller: animationController, + visible: !_.controlsLock.value && _.showControls.value, + position: 'top', + child: widget.headerControl ?? _.headerControl!, + ), + ), + ), + ] else ...[ + const SizedBox.shrink() + ], + Flexible( + flex: _.videoType == 'live' ? 0 : 1, + child: ClipRect( child: AppBarAni( controller: animationController, visible: !_.controlsLock.value && _.showControls.value, - position: 'top', - child: widget.headerControl ?? _.headerControl!, + position: 'bottom', + child: widget.bottomControl ?? + BottomControl( + controller: widget.controller, + triggerFullScreen: _.triggerFullScreen, + buildBottomControl: buildBottomControl(), + ), ), ), - const Spacer(), - ClipRect( - child: AppBarAni( - controller: animationController, - visible: !_.controlsLock.value && _.showControls.value, - position: 'bottom', - child: widget.bottomControl ?? - BottomControl( - controller: widget.controller, - triggerFullScreen: _.triggerFullScreen, - buildBottomControl: buildBottomControl(), - ), - ), ), ], ), From bae15505938b5ac49560bf1531bf07852311925f Mon Sep 17 00:00:00 2001 From: guozhigq Date: Tue, 26 Nov 2024 23:35:48 +0800 Subject: [PATCH 13/43] mod: enableCDN default false --- lib/pages/live_room/controller.dart | 2 +- lib/pages/setting/play_setting.dart | 2 +- lib/pages/video/detail/controller.dart | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/pages/live_room/controller.dart b/lib/pages/live_room/controller.dart index a918ec7b..28f92005 100644 --- a/lib/pages/live_room/controller.dart +++ b/lib/pages/live_room/controller.dart @@ -69,7 +69,7 @@ class LiveRoomController extends GetxController { Request.getBuvid().then((value) => buvid = value); } // CDN优化 - enableCDN = setting.get(SettingBoxKey.enableCDN, defaultValue: true); + enableCDN = setting.get(SettingBoxKey.enableCDN, defaultValue: false); final userInfo = userInfoCache.get('userInfoCache'); if (userInfo != null && userInfo.mid != null) { userId = userInfo.mid; diff --git a/lib/pages/setting/play_setting.dart b/lib/pages/setting/play_setting.dart index c6d7fe0b..1151775e 100644 --- a/lib/pages/setting/play_setting.dart +++ b/lib/pages/setting/play_setting.dart @@ -95,7 +95,7 @@ class _PlaySettingState extends State { title: 'CDN优化', subTitle: '使用优质CDN线路', setKey: SettingBoxKey.enableCDN, - defaultVal: true, + defaultVal: false, ), const SetSwitchItem( title: '自动播放', diff --git a/lib/pages/video/detail/controller.dart b/lib/pages/video/detail/controller.dart index 78028904..cc2cbffc 100644 --- a/lib/pages/video/detail/controller.dart +++ b/lib/pages/video/detail/controller.dart @@ -169,7 +169,7 @@ class VideoDetailController extends GetxController } // CDN优化 - enableCDN = setting.get(SettingBoxKey.enableCDN, defaultValue: true); + enableCDN = setting.get(SettingBoxKey.enableCDN, defaultValue: false); // 预设的画质 cacheVideoQa = setting.get(SettingBoxKey.defaultVideoQa); // 预设的解码格式 From 518f49973c967f026f90393d49ead3cdff9bd1b1 Mon Sep 17 00:00:00 2001 From: guozhigq Date: Wed, 27 Nov 2024 00:05:10 +0800 Subject: [PATCH 14/43] mod: fav folder edit --- lib/pages/fav_detail/controller.dart | 5 ++-- lib/pages/fav_edit/controller.dart | 11 +++++++++ lib/pages/fav_edit/view.dart | 35 +++++++++++----------------- 3 files changed, 27 insertions(+), 24 deletions(-) diff --git a/lib/pages/fav_detail/controller.dart b/lib/pages/fav_detail/controller.dart index 022e0103..32bad5c9 100644 --- a/lib/pages/fav_detail/controller.dart +++ b/lib/pages/fav_detail/controller.dart @@ -134,8 +134,9 @@ class FavDetailController extends GetxController { 'privacy': [22, 0].contains(item!.attr) ? 0 : 1, }, ); - title.value = res['title']; - print(title); + if (res != null) { + title.value = res['title']; + } } Future toViewPlayAll() async { diff --git a/lib/pages/fav_edit/controller.dart b/lib/pages/fav_edit/controller.dart index bf310389..18e88a03 100644 --- a/lib/pages/fav_edit/controller.dart +++ b/lib/pages/fav_edit/controller.dart @@ -53,6 +53,7 @@ class FavEditController extends GetxController { intro: intro, mediaId: mediaId!, cover: cover, + privacy: privacy.value, ); if (res['status']) { SmartDialog.showToast('编辑成功'); @@ -74,4 +75,14 @@ class FavEditController extends GetxController { SmartDialog.showToast(res['msg']); } } + + void togglePrivacy() { + if (privacy.value == 0) { + privacy.value = 1; + SmartDialog.showToast('设置为私密后,只有自己可见'); + } else { + privacy.value = 0; + SmartDialog.showToast('设置为公开后,所有人可见'); + } + } } diff --git a/lib/pages/fav_edit/view.dart b/lib/pages/fav_edit/view.dart index 2fecf070..4b5db10c 100644 --- a/lib/pages/fav_edit/view.dart +++ b/lib/pages/fav_edit/view.dart @@ -21,31 +21,22 @@ class _FavEditPageState extends State { appBar: AppBar( title: Obx( () => _favEditController.type.value == 'add' - ? Text( - '新建收藏夹', - style: Theme.of(context).textTheme.titleMedium, - ) - : Text( - '编辑收藏夹', - style: Theme.of(context).textTheme.titleMedium, - ), + ? const Text('新建收藏夹') + : const Text('编辑收藏夹'), ), actions: [ Obx( - () => _favEditController.privacy.value == 0 - ? IconButton( - onPressed: () { - _favEditController.privacy.value = 1; - }, - icon: const Icon(Icons.lock_open_outlined)) - : IconButton( - onPressed: () { - _favEditController.privacy.value = 0; - }, - icon: Icon( - Icons.lock_outlined, - color: Theme.of(context).colorScheme.error, - )), + () => IconButton( + onPressed: _favEditController.togglePrivacy, + icon: Icon( + _favEditController.privacy.value == 0 + ? Icons.lock_open_outlined + : Icons.lock_outlined, + color: _favEditController.privacy.value == 0 + ? null + : Theme.of(context).colorScheme.error, + ), + ), ), TextButton( onPressed: _favEditController.onSubmit, child: const Text('保存')), From 9f16296b0a9f0752b24f577593337887837fd8df Mon Sep 17 00:00:00 2001 From: guozhigq Date: Wed, 27 Nov 2024 23:55:13 +0800 Subject: [PATCH 15/43] =?UTF-8?q?fix=EF=BC=9AcurrentEpisodeIndex=20null?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/common/pages_bottom_sheet.dart | 3 +-- lib/pages/video/detail/introduction/controller.dart | 3 +++ 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/lib/common/pages_bottom_sheet.dart b/lib/common/pages_bottom_sheet.dart index fa6f61c7..434b73c4 100644 --- a/lib/common/pages_bottom_sheet.dart +++ b/lib/common/pages_bottom_sheet.dart @@ -1,5 +1,4 @@ import 'package:flutter/material.dart'; -import 'package:flutter/widgets.dart'; import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; import 'package:get/get.dart'; import 'package:pilipala/common/constants.dart'; @@ -183,7 +182,7 @@ class _PagesBottomSheetState extends State isFixedHeight: true, ); } else { - _listObserverControllerList![widget.currentEpisodeIndex!] + _listObserverControllerList![widget.currentEpisodeIndex ?? 0] .initialIndexModel = ObserverIndexPositionModel( index: currentIndex, isFixedHeight: true, diff --git a/lib/pages/video/detail/introduction/controller.dart b/lib/pages/video/detail/introduction/controller.dart index eafedd08..6578a882 100644 --- a/lib/pages/video/detail/introduction/controller.dart +++ b/lib/pages/video/detail/introduction/controller.dart @@ -608,6 +608,7 @@ class VideoIntroController extends GetxController { // 播放器底栏 选集 回调 void showEposideHandler() { late List episodes; + int currentEpisodeIndex = 0; VideoEpidoesType dataType = VideoEpidoesType.videoEpisode; if (videoDetail.value.ugcSeason != null) { dataType = VideoEpidoesType.videoEpisode; @@ -616,6 +617,7 @@ class VideoIntroController extends GetxController { final List episodesList = sections[i].episodes!; for (int j = 0; j < episodesList.length; j++) { if (episodesList[j].cid == lastPlayCid.value) { + currentEpisodeIndex = i; episodes = episodesList; continue; } @@ -635,6 +637,7 @@ class VideoIntroController extends GetxController { sheetHeight: Get.size.height, isFullScreen: true, ugcSeason: ugcSeason, + currentEpisodeIndex: currentEpisodeIndex, changeFucCall: (item, index) { if (dataType == VideoEpidoesType.videoEpisode) { changeSeasonOrbangu( From 973a61c54a6a7e3b991aead0bc2dda27e29a1be4 Mon Sep 17 00:00:00 2001 From: guozhigq Date: Thu, 28 Nov 2024 00:07:18 +0800 Subject: [PATCH 16/43] refactor: currentEpisodeIndex calc --- .../introduction/widgets/season_panel.dart | 28 +++++++++++-------- 1 file changed, 17 insertions(+), 11 deletions(-) diff --git a/lib/pages/video/detail/introduction/widgets/season_panel.dart b/lib/pages/video/detail/introduction/widgets/season_panel.dart index 291f1622..4a35f45b 100644 --- a/lib/pages/video/detail/introduction/widgets/season_panel.dart +++ b/lib/pages/video/detail/introduction/widgets/season_panel.dart @@ -42,23 +42,14 @@ class _SeasonPanelState extends State { _videoDetailController = Get.find(tag: heroTag); /// 根据 cid 找到对应集,找到对应 episodes - final List sections = widget.ugcSeason.sections!; - for (int i = 0; i < sections.length; i++) { - final List episodesList = sections[i].episodes!; - for (int j = 0; j < episodesList.length; j++) { - if (episodesList[j].cid == cid) { - currentEpisodeIndex = i; - episodes = episodesList; - continue; - } - } - } + getCurrentEpisodeIndex(); /// 取对应 season_id 的 episodes getCurrentIndex(); _videoDetailController.cid.listen((int p0) { cid = p0; getCurrentIndex(); + getCurrentEpisodeIndex(); }); } @@ -94,6 +85,21 @@ class _SeasonPanelState extends State { } } + // 获取currentEpisodeIndex + void getCurrentEpisodeIndex() { + final List sections = widget.ugcSeason.sections!; + for (int i = 0; i < sections.length; i++) { + final List episodesList = sections[i].episodes!; + for (int j = 0; j < episodesList.length; j++) { + if (episodesList[j].cid == cid) { + currentEpisodeIndex = i; + episodes = episodesList; + continue; + } + } + } + } + Widget buildEpisodeListItem( EpisodeItem episode, int index, From 65c9027ef390422939a7b676b4b0768e9f37602b Mon Sep 17 00:00:00 2001 From: guozhigq Date: Thu, 28 Nov 2024 23:05:02 +0800 Subject: [PATCH 17/43] feat: marquee title --- .../video/detail/widgets/header_control.dart | 417 ++++++++++-------- pubspec.lock | 16 + pubspec.yaml | 2 + 3 files changed, 246 insertions(+), 189 deletions(-) diff --git a/lib/pages/video/detail/widgets/header_control.dart b/lib/pages/video/detail/widgets/header_control.dart index 5412d326..28448f51 100644 --- a/lib/pages/video/detail/widgets/header_control.dart +++ b/lib/pages/video/detail/widgets/header_control.dart @@ -7,6 +7,7 @@ import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; import 'package:font_awesome_flutter/font_awesome_flutter.dart'; import 'package:get/get.dart'; import 'package:hive/hive.dart'; +import 'package:marquee/marquee.dart'; import 'package:ns_danmaku/ns_danmaku.dart'; import 'package:pilipala/common/widgets/drag_handle.dart'; import 'package:pilipala/http/user.dart'; @@ -62,6 +63,7 @@ class _HeaderControlState extends State { late String heroTag; late VideoIntroController videoIntroController; late VideoDetailData videoDetail; + DateTime initialTime = DateTime.now(); @override void initState() { @@ -108,30 +110,6 @@ class _HeaderControlState extends State { child: Material( child: ListView( children: [ - // ListTile( - // onTap: () {}, - // dense: true, - // enabled: false, - // leading: - // const Icon(Icons.network_cell_outlined, size: 20), - // title: Text('省流模式', style: titleStyle), - // subtitle: Text('低画质 | 减少视频缓存', style: subTitleStyle), - // trailing: Transform.scale( - // scale: 0.75, - // child: Switch( - // thumbIcon: MaterialStateProperty.resolveWith( - // (Set states) { - // if (states.isNotEmpty && - // states.first == MaterialState.selected) { - // return const Icon(Icons.done); - // } - // return null; // All other states will use the default thumbIcon. - // }), - // value: false, - // onChanged: (value) => {}, - // ), - // ), - // ), ListTile( onTap: () async { final res = await UserHttp.toViewLater( @@ -1071,6 +1049,16 @@ class _HeaderControlState extends State { ); } + Stream _getTimeStream() { + return Stream.periodic(const Duration(seconds: 60), (count) { + return DateTime.now(); + }); + } + + String _formatTime(DateTime dateTime) { + return '${dateTime.hour}:${dateTime.minute < 10 ? '0${dateTime.minute}' : dateTime.minute}'; + } + @override Widget build(BuildContext context) { final _ = widget.controller!; @@ -1086,49 +1074,12 @@ class _HeaderControlState extends State { primary: false, automaticallyImplyLeading: false, titleSpacing: 14, - title: Row( + title: Column( children: [ - ComBtn( - icon: const Icon( - FontAwesomeIcons.arrowLeft, - size: 15, - color: Colors.white, - ), - fuc: () => >{ - if (widget.controller!.isFullScreen.value) - {widget.controller!.triggerFullScreen(status: false)} - else - { - if (MediaQuery.of(context).orientation == - Orientation.landscape) - { - SystemChrome.setPreferredOrientations([ - DeviceOrientation.portraitUp, - ]) - }, - Get.back() - } - }, - ), - SizedBox(width: buttonSpace), - if (isFullScreen.value && - isLandscape && - widget.videoType == SearchType.video) ...[ - Column( - crossAxisAlignment: CrossAxisAlignment.start, + if (isFullScreen.value && isLandscape) ...[ + Row( children: [ - ConstrainedBox( - constraints: const BoxConstraints(maxWidth: 200), - child: Obx( - () => Text( - videoIntroController.videoDetail.value.title ?? '', - style: const TextStyle( - color: Colors.white, - fontSize: 16, - ), - ), - ), - ), + const SizedBox(width: 40), if (videoIntroController.isShowOnlineTotal) Text( '${videoIntroController.total.value}人正在看', @@ -1136,138 +1087,226 @@ class _HeaderControlState extends State { color: Colors.white, fontSize: 12, ), - ) + ), + const Spacer(), + Expanded( + child: Align( + alignment: Alignment.center, + child: StreamBuilder( + stream: _getTimeStream(), + builder: (context, snapshot) { + if (snapshot.hasData) { + String currentTime = _formatTime(snapshot.data!); + return Text( + currentTime, + style: const TextStyle(fontSize: 12), + ); + } else if (snapshot.connectionState == + ConnectionState.waiting) { + // 如果Stream还未发出数据,先显示初始获取的时间 + String currentTime = _formatTime(initialTime); + return Text( + currentTime, + style: const TextStyle(fontSize: 12), + ); + } else { + return const SizedBox(); + } + }, + ), + ), + ), + const Spacer(), + + /// TODO 网络&电量 ], - ) - ] else ...[ - ComBtn( - icon: const Icon( - FontAwesomeIcons.house, - size: 15, - color: Colors.white, - ), - fuc: () async { - // 销毁播放器实例 - await widget.controller!.dispose(type: 'all'); - if (context.mounted) { - Navigator.popUntil( - context, (Route route) => route.isFirst); - } - }, ), ], - const Spacer(), - if (GlobalDataCache.enableDlna) ...[ - ComBtn( - icon: Image.asset('assets/images/video/dlna.png', width: 19), - fuc: () async { - showDialog( - context: context, - builder: (BuildContext context) { - return LiveDlnaPage( - datasource: widget.videoDetailCtr!.videoUrl); - }, - ); - }, - ), - SizedBox(width: buttonSpace), - ], - - /// 弹幕开关(全屏时) - if (isFullScreen.value) ...[ - SizedBox( - width: 56, - height: 34, - child: TextButton( - style: ButtonStyle( - padding: MaterialStateProperty.all(EdgeInsets.zero), - ), - onPressed: () => showShootDanmakuSheet(), - child: const Text( - '发弹幕', - style: textStyle, - ), - ), - ), - SizedBox(width: buttonSpace), - SizedBox( - width: 34, - height: 34, - child: Obx( - () => IconButton( - style: ButtonStyle( - padding: MaterialStateProperty.all(EdgeInsets.zero), - ), - onPressed: () { - _.isOpenDanmu.value = !_.isOpenDanmu.value; - }, - icon: Icon( - _.isOpenDanmu.value - ? Icons.subtitles_outlined - : Icons.subtitles_off_outlined, - size: 19, - color: Colors.white, - ), - ), - ), - ), - SizedBox(width: buttonSpace), - ], - - /// pip - if (Platform.isAndroid) ...[ - SizedBox( - width: 34, - height: 34, - child: IconButton( - style: ButtonStyle( - padding: MaterialStateProperty.all(EdgeInsets.zero), - ), - onPressed: () async { - bool canUsePiP = false; - widget.controller!.hiddenControls(false); - try { - canUsePiP = await widget.floating!.isPipAvailable; - } on PlatformException catch (_) { - canUsePiP = false; - } - if (canUsePiP) { - final Rational aspectRatio = Rational( - widget.videoDetailCtr!.data.dash!.video!.first.width!, - widget.videoDetailCtr!.data.dash!.video!.first.height!, - ); - await widget.floating!.enable(aspectRatio: aspectRatio); - } else {} - }, - icon: Image.asset( - 'assets/images/video/pip.png', - width: 19, + Row( + children: [ + ComBtn( + icon: const Icon( + FontAwesomeIcons.arrowLeft, + size: 15, color: Colors.white, ), + fuc: () => >{ + if (widget.controller!.isFullScreen.value) + {widget.controller!.triggerFullScreen(status: false)} + else + { + if (MediaQuery.of(context).orientation == + Orientation.landscape) + { + SystemChrome.setPreferredOrientations([ + DeviceOrientation.portraitUp, + ]) + }, + Get.back() + } + }, ), - ), - SizedBox(width: buttonSpace), - ], + SizedBox(width: buttonSpace), + if (isFullScreen.value && + isLandscape && + widget.videoType == SearchType.video) ...[ + Expanded( + child: LayoutBuilder(builder: (context, constraints) { + return SizedBox( + width: constraints.maxWidth, + height: 25, + child: Obx( + () => Marquee( + text: videoIntroController.videoDetail.value.title ?? + '', + style: const TextStyle(fontSize: 16), + scrollAxis: Axis.horizontal, + crossAxisAlignment: CrossAxisAlignment.center, + blankSpace: constraints.maxWidth, + velocity: 100, + pauseAfterRound: const Duration(seconds: 1), + startPadding: 0, + accelerationDuration: const Duration(seconds: 1), + accelerationCurve: Curves.linear, + decelerationDuration: const Duration(seconds: 1), + decelerationCurve: Curves.easeOut, + ), + ), + ); + }), + ), + ] else ...[ + ComBtn( + icon: const Icon( + FontAwesomeIcons.house, + size: 15, + color: Colors.white, + ), + fuc: () async { + // 销毁播放器实例 + await widget.controller!.dispose(type: 'all'); + if (context.mounted) { + Navigator.popUntil( + context, (Route route) => route.isFirst); + } + }, + ), + ], + const Spacer(), + if (GlobalDataCache.enableDlna) ...[ + ComBtn( + icon: Image.asset('assets/images/video/dlna.png', width: 19), + fuc: () async { + showDialog( + context: context, + builder: (BuildContext context) { + return LiveDlnaPage( + datasource: widget.videoDetailCtr!.videoUrl); + }, + ); + }, + ), + SizedBox(width: buttonSpace), + ], - /// 字幕 - if (widget.showSubtitleBtn) ...[ - ComBtn( - icon: Icon( - FontAwesomeIcons.closedCaptioning, - size: 16, - color: Colors.white.withOpacity(0.9), + /// 弹幕开关(全屏时) + if (isFullScreen.value) ...[ + SizedBox( + width: 56, + height: 34, + child: TextButton( + style: ButtonStyle( + padding: MaterialStateProperty.all(EdgeInsets.zero), + ), + onPressed: () => showShootDanmakuSheet(), + child: const Text( + '发弹幕', + style: textStyle, + ), + ), + ), + SizedBox(width: buttonSpace), + SizedBox( + width: 34, + height: 34, + child: Obx( + () => IconButton( + style: ButtonStyle( + padding: MaterialStateProperty.all(EdgeInsets.zero), + ), + onPressed: () { + _.isOpenDanmu.value = !_.isOpenDanmu.value; + }, + icon: Icon( + _.isOpenDanmu.value + ? Icons.subtitles_outlined + : Icons.subtitles_off_outlined, + size: 19, + color: Colors.white, + ), + ), + ), + ), + SizedBox(width: buttonSpace), + ], + + /// pip + if (Platform.isAndroid) ...[ + SizedBox( + width: 34, + height: 34, + child: IconButton( + style: ButtonStyle( + padding: MaterialStateProperty.all(EdgeInsets.zero), + ), + onPressed: () async { + bool canUsePiP = false; + widget.controller!.hiddenControls(false); + try { + canUsePiP = await widget.floating!.isPipAvailable; + } on PlatformException catch (_) { + canUsePiP = false; + } + if (canUsePiP) { + final Rational aspectRatio = Rational( + widget.videoDetailCtr!.data.dash!.video!.first.width!, + widget + .videoDetailCtr!.data.dash!.video!.first.height!, + ); + await widget.floating!.enable(aspectRatio: aspectRatio); + } else {} + }, + icon: Image.asset( + 'assets/images/video/pip.png', + width: 19, + color: Colors.white, + ), + ), + ), + SizedBox(width: buttonSpace), + ], + + /// 字幕 + if (widget.showSubtitleBtn) ...[ + ComBtn( + icon: Icon( + FontAwesomeIcons.closedCaptioning, + size: 16, + color: Colors.white.withOpacity(0.9), + ), + fuc: () => showSubtitleDialog(), + ), + SizedBox(width: buttonSpace), + ], + ComBtn( + icon: const Icon( + Icons.more_vert_outlined, + size: 19, + color: Colors.white, + ), + fuc: () => showSettingSheet(), ), - fuc: () => showSubtitleDialog(), - ), - SizedBox(width: buttonSpace), - ], - ComBtn( - icon: const Icon( - Icons.more_vert_outlined, - size: 19, - color: Colors.white, - ), - fuc: () => showSettingSheet(), + ], ), ], ), diff --git a/pubspec.lock b/pubspec.lock index a3dd3af9..715b23a5 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -497,6 +497,14 @@ packages: url: "https://pub.flutter-io.cn" source: hosted version: "6.2.1" + fading_edge_scrollview: + dependency: transitive + description: + name: fading_edge_scrollview + sha256: c25c2231652ce774cc31824d0112f11f653881f43d7f5302c05af11942052031 + url: "https://pub.flutter-io.cn" + source: hosted + version: "3.0.0" fake_async: dependency: transitive description: @@ -990,6 +998,14 @@ packages: url: "https://pub.flutter-io.cn" source: hosted version: "6.1.0" + marquee: + dependency: "direct main" + description: + name: marquee + sha256: "4b5243d2804373bdc25fc93d42c3b402d6ec1f4ee8d0bb72276edd04ae7addb8" + url: "https://pub.flutter-io.cn" + source: hosted + version: "2.2.3" matcher: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index b2275398..80a8b067 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -152,6 +152,8 @@ dependencies: re_highlight: ^0.0.3 # 图片选择器 image_picker: ^1.1.2 + # 跑马灯 + marquee: ^2.2.3 dev_dependencies: flutter_test: From b79a664e94e0444d5552475276e358e8cbd87965 Mon Sep 17 00:00:00 2001 From: guozhigq Date: Fri, 29 Nov 2024 23:51:58 +0800 Subject: [PATCH 18/43] refactor: progressBar --- lib/plugin/pl_player/view.dart | 3 +- .../pl_player/widgets/bottom_control.dart | 50 +++--------------- .../pl_player/widgets/progress_bar.dart | 52 +++++++++++++++++++ 3 files changed, 59 insertions(+), 46 deletions(-) create mode 100644 lib/plugin/pl_player/widgets/progress_bar.dart diff --git a/lib/plugin/pl_player/view.dart b/lib/plugin/pl_player/view.dart index c379303e..b47d38de 100644 --- a/lib/plugin/pl_player/view.dart +++ b/lib/plugin/pl_player/view.dart @@ -842,8 +842,7 @@ class _PLVideoPlayerState extends State total: Duration(seconds: max), progressBarColor: colorTheme, baseBarColor: Colors.white.withOpacity(0.2), - bufferedBarColor: - Theme.of(context).colorScheme.primary.withOpacity(0.4), + bufferedBarColor: Colors.white.withOpacity(0.6), timeLabelLocation: TimeLabelLocation.none, thumbColor: colorTheme, barHeight: 3, diff --git a/lib/plugin/pl_player/widgets/bottom_control.dart b/lib/plugin/pl_player/widgets/bottom_control.dart index b3ff37db..3c21c2af 100644 --- a/lib/plugin/pl_player/widgets/bottom_control.dart +++ b/lib/plugin/pl_player/widgets/bottom_control.dart @@ -1,8 +1,6 @@ -import 'package:audio_video_progress_bar/audio_video_progress_bar.dart'; import 'package:flutter/material.dart'; -import 'package:get/get.dart'; import 'package:pilipala/plugin/pl_player/index.dart'; -import 'package:pilipala/utils/feed_back.dart'; +import 'progress_bar.dart'; class BottomControl extends StatelessWidget implements PreferredSizeWidget { final PlPlayerController? controller; @@ -20,54 +18,18 @@ class BottomControl extends StatelessWidget implements PreferredSizeWidget { @override Widget build(BuildContext context) { - Color colorTheme = Theme.of(context).colorScheme.primary; - final _ = controller!; return Container( color: Colors.transparent, height: 90, - padding: const EdgeInsets.only(left: 18, right: 18), + padding: const EdgeInsets.symmetric(horizontal: 18), child: Column( mainAxisAlignment: MainAxisAlignment.end, children: [ - Obx( - () { - final int value = _.sliderPositionSeconds.value; - final int max = _.durationSeconds.value; - final int buffer = _.bufferedSeconds.value; - if (value > max || max <= 0) { - return const SizedBox(); - } - return Padding( - padding: const EdgeInsets.only(left: 7, right: 7, bottom: 6), - child: ProgressBar( - progress: Duration(seconds: value), - buffered: Duration(seconds: buffer), - total: Duration(seconds: max), - progressBarColor: colorTheme, - baseBarColor: Colors.white.withOpacity(0.2), - bufferedBarColor: colorTheme.withOpacity(0.4), - timeLabelLocation: TimeLabelLocation.none, - thumbColor: colorTheme, - barHeight: 3.5, - thumbRadius: 7, - onDragStart: (duration) { - feedBack(); - _.onChangedSliderStart(); - }, - onDragUpdate: (duration) { - _.onUpdatedSliderProgress(duration.timeStamp); - }, - onSeek: (duration) { - _.onChangedSliderEnd(); - _.onChangedSlider(duration.inSeconds.toDouble()); - _.seekTo(Duration(seconds: duration.inSeconds), - type: 'slider'); - }, - ), - ); - }, + Padding( + padding: const EdgeInsets.fromLTRB(7, 0, 7, 6), + child: ProgressBarWidget(controller: controller!), ), - Row(children: [...buildBottomControl!]), + Row(children: buildBottomControl!), const SizedBox(height: 10), ], ), diff --git a/lib/plugin/pl_player/widgets/progress_bar.dart b/lib/plugin/pl_player/widgets/progress_bar.dart new file mode 100644 index 00000000..d0ed0a4e --- /dev/null +++ b/lib/plugin/pl_player/widgets/progress_bar.dart @@ -0,0 +1,52 @@ +import 'package:audio_video_progress_bar/audio_video_progress_bar.dart'; +import 'package:flutter/material.dart'; +import 'package:get/get.dart'; +import 'package:pilipala/plugin/pl_player/index.dart'; +import 'package:pilipala/utils/feed_back.dart'; + +class ProgressBarWidget extends StatelessWidget { + final PlPlayerController controller; + + const ProgressBarWidget({ + required this.controller, + Key? key, + }) : super(key: key); + + @override + Widget build(BuildContext context) { + return Obx(() { + Color colorTheme = Theme.of(context).colorScheme.primary; + final _ = controller; + final int value = _.sliderPositionSeconds.value; + final int max = _.durationSeconds.value; + final int buffer = _.bufferedSeconds.value; + if (value > max || max <= 0) { + return const SizedBox(); + } + return ProgressBar( + progress: Duration(seconds: value), + buffered: Duration(seconds: buffer), + total: Duration(seconds: max), + progressBarColor: colorTheme, + baseBarColor: Colors.white.withOpacity(0.2), + bufferedBarColor: Colors.white.withOpacity(0.6), + timeLabelLocation: TimeLabelLocation.none, + thumbColor: colorTheme, + barHeight: 3.5, + thumbRadius: 7, + onDragStart: (duration) { + feedBack(); + _.onChangedSliderStart(); + }, + onDragUpdate: (duration) { + _.onUpdatedSliderProgress(duration.timeStamp); + }, + onSeek: (duration) { + _.onChangedSliderEnd(); + _.onChangedSlider(duration.inSeconds.toDouble()); + _.seekTo(Duration(seconds: duration.inSeconds), type: 'slider'); + }, + ); + }); + } +} From 1127ef74370d7f5faef07ffb03718a9b585f94f6 Mon Sep 17 00:00:00 2001 From: guozhigq Date: Sat, 30 Nov 2024 14:18:26 +0800 Subject: [PATCH 19/43] fix: _futureBuilderFuture init --- lib/pages/dynamics/detail/view.dart | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/lib/pages/dynamics/detail/view.dart b/lib/pages/dynamics/detail/view.dart index b1f9bea8..186a9e9a 100644 --- a/lib/pages/dynamics/detail/view.dart +++ b/lib/pages/dynamics/detail/view.dart @@ -20,7 +20,6 @@ import '../../../models/video/reply/item.dart'; import '../widgets/dynamic_panel.dart'; class DynamicDetailPage extends StatefulWidget { - // const DynamicDetailPage({super.key}); const DynamicDetailPage({Key? key}) : super(key: key); @override @@ -90,19 +89,22 @@ class _DynamicDetailPageState extends State _dynamicDetailController = Get.put( DynamicDetailController(oid, replyType), tag: opusId.toString()); + _futureBuilderFuture = _dynamicDetailController.queryReplyList(); await _dynamicDetailController.reqHtmlByOpusId(opusId!); setState(() {}); } } else { oid = moduleDynamic.major!.draw!.id!; } - } catch (_) {} + } catch (err) { + print('err:${err.toString()}'); + } } if (!isOpusId) { _dynamicDetailController = Get.put(DynamicDetailController(oid, replyType), tag: oid.toString()); + _futureBuilderFuture ??= _dynamicDetailController.queryReplyList(); } - _futureBuilderFuture = _dynamicDetailController.queryReplyList(); } // 查看二级评论 @@ -132,7 +134,7 @@ class _DynamicDetailPageState extends State // 分页加载 if (scrollController.position.pixels >= scrollController.position.maxScrollExtent - 300) { - EasyThrottle.throttle('replylist', const Duration(seconds: 2), () { + EasyThrottle.throttle('replyList', const Duration(seconds: 2), () { _dynamicDetailController.onLoad(); }); } From 038f6c00d7a2da3cb39d45a5667208da96e4f108 Mon Sep 17 00:00:00 2001 From: guozhigq Date: Sun, 1 Dec 2024 00:30:56 +0800 Subject: [PATCH 20/43] opt: article render --- lib/common/widgets/html_render.dart | 24 ++++++++++++++++++------ lib/pages/opus/view.dart | 21 ++++++++++++--------- lib/pages/read/view.dart | 21 ++++++++++++--------- 3 files changed, 42 insertions(+), 24 deletions(-) diff --git a/lib/common/widgets/html_render.dart b/lib/common/widgets/html_render.dart index b2aa75ff..e4e3ef77 100644 --- a/lib/common/widgets/html_render.dart +++ b/lib/common/widgets/html_render.dart @@ -5,6 +5,8 @@ import 'package:pilipala/plugin/pl_gallery/hero_dialog_route.dart'; import 'package:pilipala/plugin/pl_gallery/interactiveviewer_gallery.dart'; import 'package:pilipala/utils/highlight.dart'; +import '../../utils/global_data_cache.dart'; + // ignore: must_be_immutable class HtmlRender extends StatelessWidget { const HtmlRender({ @@ -41,6 +43,8 @@ class HtmlRender extends StatelessWidget { TagExtension( tagsToExtend: {'img'}, builder: (ExtensionContext extensionContext) { + int defaultImgQuality = 10; + defaultImgQuality = GlobalDataCache.imgQuality; try { final Map attributes = extensionContext.attributes; @@ -99,13 +103,13 @@ class HtmlRender extends StatelessWidget { ), ); }, - child: CachedNetworkImage(imageUrl: imgUrl), + child: ClipRRect( + borderRadius: BorderRadius.circular(12), + child: CachedNetworkImage( + imageUrl: '$imgUrl@${defaultImgQuality}q.webp', + ), + ), ); - // return NetworkImgLayer( - // width: isEmote ? 22 : Get.size.width - 24, - // height: isEmote ? 22 : 200, - // src: imgUrl, - // ); } catch (err) { return const SizedBox(); } @@ -138,6 +142,14 @@ class HtmlRender extends StatelessWidget { textAlign: TextAlign.justify, ), 'img': Style(margin: Margins.only(top: 4, bottom: 4)), + 'figcaption': Style( + textAlign: TextAlign.center, + margin: Margins.only(top: 8, bottom: 20), + color: Theme.of(context).colorScheme.secondary, + ), + 'figure': Style( + margin: Margins.zero, + ), }, ); } diff --git a/lib/pages/opus/view.dart b/lib/pages/opus/view.dart index 42c0c419..971bb678 100644 --- a/lib/pages/opus/view.dart +++ b/lib/pages/opus/view.dart @@ -2,6 +2,7 @@ import 'package:flutter/material.dart'; import 'package:get/get.dart'; import 'package:pilipala/common/widgets/network_img_layer.dart'; import 'package:pilipala/models/read/opus.dart'; + import 'controller.dart'; import 'text_helper.dart'; @@ -26,16 +27,18 @@ class _OpusPageState extends State { Widget build(BuildContext context) { return Scaffold( appBar: _buildAppBar(), - body: SingleChildScrollView( + body: CustomScrollView( controller: controller.scrollController, - child: Column( - mainAxisAlignment: MainAxisAlignment.start, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - _buildTitle(), - _buildFutureContent(), - ], - ), + slivers: [ + SliverList( + delegate: SliverChildListDelegate( + [ + _buildTitle(), + _buildFutureContent(), + ], + ), + ), + ], ), ); } diff --git a/lib/pages/read/view.dart b/lib/pages/read/view.dart index 710934eb..4bc93326 100644 --- a/lib/pages/read/view.dart +++ b/lib/pages/read/view.dart @@ -6,6 +6,7 @@ import 'package:pilipala/models/read/opus.dart'; import 'package:pilipala/models/read/read.dart'; import 'package:pilipala/pages/opus/text_helper.dart'; import 'package:pilipala/utils/utils.dart'; + import 'controller.dart'; class ReadPage extends StatefulWidget { @@ -38,16 +39,18 @@ class _ReadPageState extends State { Widget build(BuildContext context) { return Scaffold( appBar: _buildAppBar(), - body: SingleChildScrollView( + body: CustomScrollView( controller: controller.scrollController, - child: Column( - mainAxisAlignment: MainAxisAlignment.start, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - _buildTitle(), - _buildFutureContent(), - ], - ), + slivers: [ + SliverList( + delegate: SliverChildListDelegate( + [ + _buildTitle(), + _buildFutureContent(), + ], + ), + ), + ], ), ); } From fc921cdcb5d1b7ffa318b2ca1e96d9d5d5cb522e Mon Sep 17 00:00:00 2001 From: guozhigq Date: Sun, 1 Dec 2024 20:14:22 +0800 Subject: [PATCH 21/43] fix: vertical video fullScreen --- lib/pages/video/detail/view.dart | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/lib/pages/video/detail/view.dart b/lib/pages/video/detail/view.dart index c7896480..72af55db 100644 --- a/lib/pages/video/detail/view.dart +++ b/lib/pages/video/detail/view.dart @@ -554,6 +554,11 @@ class _VideoDetailPageState extends State playerController: plPlayerController!, ), bottomList: vdCtr.bottomList, + fullScreenCb: (bool status) { + if (vdCtr.videoDirection.value == 'vertical') { + videoHeight.value = status ? Get.size.height : verticalHeight; + } + }, showEposideCb: () => vdCtr.videoType == SearchType.video ? videoIntroController.showEposideHandler() : bangumiIntroController.showEposideHandler(), From a84efecb3aa8a513f0b734257ec28ccc7657c6bd Mon Sep 17 00:00:00 2001 From: guozhigq Date: Sun, 1 Dec 2024 22:49:38 +0800 Subject: [PATCH 22/43] fix: PlPlayerController videoType --- lib/plugin/pl_player/controller.dart | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/lib/plugin/pl_player/controller.dart b/lib/plugin/pl_player/controller.dart index dab32b5c..e95fe647 100644 --- a/lib/plugin/pl_player/controller.dart +++ b/lib/plugin/pl_player/controller.dart @@ -81,7 +81,7 @@ class PlPlayerController { final Rx _subTitleOpen = false.obs; final Rx _subTitleCode = (-1).obs; // 默认投稿视频格式 - static Rx _videoType = 'archive'.obs; + static String _videoType = 'archive'; final Rx _direction = 'horizontal'.obs; @@ -127,7 +127,6 @@ class PlPlayerController { PreferredSizeWidget? bottomControl; Widget? danmuWidget; RxList subtitles = [].obs; - String videoType = 'archive'; /// 数据加载监听 Stream get onDataStatusChanged => dataStatus.status.stream; @@ -225,7 +224,7 @@ class PlPlayerController { Rx get playerCount => _playerCount; /// - // Rx get videoType => _videoType; + String get videoType => _videoType; /// 弹幕开关 Rx isOpenDanmu = false.obs; @@ -279,7 +278,7 @@ class PlPlayerController { } // 添加一个私有构造函数 - PlPlayerController._internal(this.videoType) { + PlPlayerController._internal() { isOpenDanmu.value = GlobalDataCache.isOpenDanmu; blockTypes = GlobalDataCache.blockTypes; showArea = GlobalDataCache.showArea; @@ -306,11 +305,11 @@ class PlPlayerController { String videoType = 'archive', }) { // 如果实例尚未创建,则创建一个新实例 - _instance ??= PlPlayerController._internal(videoType); + _instance ??= PlPlayerController._internal(); if (videoType != 'none') { _instance!._playerCount.value += 1; - _videoType.value = videoType; } + _videoType = videoType; return _instance!; } From fbd0dd1d8f0cd4da38b260b4525b77f8c5a773c8 Mon Sep 17 00:00:00 2001 From: guozhigq Date: Sun, 1 Dec 2024 23:21:33 +0800 Subject: [PATCH 23/43] mod: _futureBuilderFuture init --- lib/pages/dynamics/detail/view.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/pages/dynamics/detail/view.dart b/lib/pages/dynamics/detail/view.dart index 186a9e9a..5c8f85e3 100644 --- a/lib/pages/dynamics/detail/view.dart +++ b/lib/pages/dynamics/detail/view.dart @@ -103,7 +103,7 @@ class _DynamicDetailPageState extends State if (!isOpusId) { _dynamicDetailController = Get.put(DynamicDetailController(oid, replyType), tag: oid.toString()); - _futureBuilderFuture ??= _dynamicDetailController.queryReplyList(); + _futureBuilderFuture = _dynamicDetailController.queryReplyList(); } } From 3d3a306e199cb7a81d3ff4030dbe218fb8e50bfe Mon Sep 17 00:00:00 2001 From: guozhigq Date: Sun, 1 Dec 2024 23:26:09 +0800 Subject: [PATCH 24/43] typo: getSubtitleContent --- lib/pages/video/detail/controller.dart | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/pages/video/detail/controller.dart b/lib/pages/video/detail/controller.dart index cc2cbffc..4fdf8420 100644 --- a/lib/pages/video/detail/controller.dart +++ b/lib/pages/video/detail/controller.dart @@ -487,7 +487,7 @@ class VideoDetailController extends GetxController if (result['status']) { if (result['data'].subtitles.isNotEmpty) { subtitles = result['data'].subtitles; - getDanmaku(subtitles); + getSubtitleContent(subtitles); } } headerControl = HeaderControl( @@ -501,8 +501,8 @@ class VideoDetailController extends GetxController plPlayerController.setHeaderControl(headerControl); } - // 获取弹幕 - Future getDanmaku(List subtitles) async { + // 获取字幕 + Future getSubtitleContent(List subtitles) async { if (subtitles.isNotEmpty) { for (var i in subtitles) { final Map res = await VideoHttp.getSubtitleContent( From 97d20b1145930a4043833f48d98c4c940ecd0ed6 Mon Sep 17 00:00:00 2001 From: guozhigq Date: Mon, 2 Dec 2024 00:04:04 +0800 Subject: [PATCH 25/43] opt: nextPlay logic & style --- lib/pages/later/controller.dart | 1 + lib/pages/video/detail/introduction/controller.dart | 2 +- lib/pages/video/detail/widgets/watch_later_list.dart | 9 ++++++--- 3 files changed, 8 insertions(+), 4 deletions(-) diff --git a/lib/pages/later/controller.dart b/lib/pages/later/controller.dart index 6cb8a3c0..cf408d97 100644 --- a/lib/pages/later/controller.dart +++ b/lib/pages/later/controller.dart @@ -122,6 +122,7 @@ class LaterController extends GetxController { 'heroTag': heroTag, 'sourceType': 'watchLater', 'count': laterList.length, + 'mediaId': userInfo!.mid, }, ); } diff --git a/lib/pages/video/detail/introduction/controller.dart b/lib/pages/video/detail/introduction/controller.dart index c30a4dbd..d16d85a8 100644 --- a/lib/pages/video/detail/introduction/controller.dart +++ b/lib/pages/video/detail/introduction/controller.dart @@ -523,7 +523,7 @@ class VideoIntroController extends GetxController { Get.find(tag: heroTag); /// 优先稍后再看、收藏夹 - if (videoDetailCtr.isWatchLaterVisible.value) { + if (videoDetailCtr.sourceType.value != 'normal') { episodes.addAll(videoDetailCtr.mediaList); } else if (videoDetail.value.ugcSeason != null) { final UgcSeason ugcSeason = videoDetail.value.ugcSeason!; diff --git a/lib/pages/video/detail/widgets/watch_later_list.dart b/lib/pages/video/detail/widgets/watch_later_list.dart index 93326ec3..6d675dd8 100644 --- a/lib/pages/video/detail/widgets/watch_later_list.dart +++ b/lib/pages/video/detail/widgets/watch_later_list.dart @@ -85,9 +85,12 @@ class _MediaListPanelState extends State { AppBar( toolbarHeight: 45, automaticallyImplyLeading: false, - title: Text( - widget.panelTitle ?? '稍后再看', - style: Theme.of(context).textTheme.titleSmall, + title: Padding( + padding: const EdgeInsets.only(left: 12), + child: Text( + widget.panelTitle ?? '稍后再看', + style: Theme.of(context).textTheme.titleSmall, + ), ), actions: [ IconButton( From 1395f7ebf3e36659e3cb40e039d67007a7806f78 Mon Sep 17 00:00:00 2001 From: guozhigq Date: Mon, 2 Dec 2024 21:01:33 +0800 Subject: [PATCH 26/43] opt: watchLater visible logic --- lib/pages/video/detail/controller.dart | 6 ++---- lib/pages/video/detail/view.dart | 7 +++---- 2 files changed, 5 insertions(+), 8 deletions(-) diff --git a/lib/pages/video/detail/controller.dart b/lib/pages/video/detail/controller.dart index cc2cbffc..3d0d68e9 100644 --- a/lib/pages/video/detail/controller.dart +++ b/lib/pages/video/detail/controller.dart @@ -624,10 +624,8 @@ class VideoDetailController extends GetxController } void toggeleWatchLaterVisible(bool val) { - if (sourceType.value == 'watchLater' || - sourceType.value == 'fav' || - sourceType.value == 'up_archive') { - isWatchLaterVisible.value = !isWatchLaterVisible.value; + if (['watchLater', 'fav', 'up_archive'].contains(sourceType.value)) { + isWatchLaterVisible.value = val; } } diff --git a/lib/pages/video/detail/view.dart b/lib/pages/video/detail/view.dart index c7896480..cad795fc 100644 --- a/lib/pages/video/detail/view.dart +++ b/lib/pages/video/detail/view.dart @@ -206,12 +206,12 @@ class _VideoDetailPageState extends State vdCtr.bottomList.insert(3, BottomControlType.episode); } } + vdCtr.toggeleWatchLaterVisible(false); } else { if (vdCtr.bottomList.contains(BottomControlType.episode)) { vdCtr.bottomList.removeAt(3); } } - vdCtr.toggeleWatchLaterVisible(!isFullScreen); }); } @@ -825,9 +825,8 @@ class _VideoDetailPageState extends State /// 稍后再看列表 Obx( () => Visibility( - visible: vdCtr.sourceType.value == 'watchLater' || - vdCtr.sourceType.value == 'fav' || - vdCtr.sourceType.value == 'up_archive', + visible: ['watchLater', 'fav', 'up_archive'] + .contains(vdCtr.sourceType.value), child: AnimatedPositioned( duration: const Duration(milliseconds: 400), curve: Curves.easeInOut, From 15e5eec5b100d5beda6b92339beadcb9d3bfc1cb Mon Sep 17 00:00:00 2001 From: guozhigq Date: Mon, 2 Dec 2024 22:35:53 +0800 Subject: [PATCH 27/43] upgrade: AGP from 7.2.0 to 8.7.2 and Gradle from 7.5 to 8.10.2 --- android/app/build.gradle | 19 +++++------- android/app/proguard-rules.pro | 4 +++ android/build.gradle | 21 ++++++------- .../gradle/wrapper/gradle-wrapper.properties | 2 +- android/settings.gradle | 30 ++++++++++++++----- pubspec.lock | 12 ++++---- pubspec.yaml | 4 +-- 7 files changed, 52 insertions(+), 40 deletions(-) create mode 100644 android/app/proguard-rules.pro diff --git a/android/app/build.gradle b/android/app/build.gradle index 3dc4f82a..0e6ecb11 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -1,3 +1,9 @@ +plugins { + id "com.android.application" + id "kotlin-android" + id "dev.flutter.flutter-gradle-plugin" +} + def localProperties = new Properties() def localPropertiesFile = rootProject.file('local.properties') if (localPropertiesFile.exists()) { @@ -6,10 +12,6 @@ if (localPropertiesFile.exists()) { } } -def flutterRoot = localProperties.getProperty('flutter.sdk') -if (flutterRoot == null) { - throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.") -} def flutterVersionCode = localProperties.getProperty('flutter.versionCode') if (flutterVersionCode == null) { @@ -21,9 +23,6 @@ if (flutterVersionName == null) { flutterVersionName = '1.0' } -apply plugin: 'com.android.application' -apply plugin: 'kotlin-android' -apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" def keystorePropertiesFile = rootProject.file('key.properties') def keystoreProperties = new Properties() @@ -37,8 +36,9 @@ def _keyAlias = System.getenv("KEY_ALIAS") ?: keystoreProperties["keyAlias"] def _keyPassword = System.getenv("KEY_PASSWORD") ?: keystoreProperties["keyPassword"] android { + namespace "com.guozhigq.pilipala" compileSdkVersion flutter.compileSdkVersion - ndkVersion flutter.ndkVersion + ndkVersion "27.0.12077973" compileOptions { sourceCompatibility JavaVersion.VERSION_1_8 @@ -91,9 +91,6 @@ flutter { source '../..' } -dependencies { - implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" -} ext.abiCodes = ["x86_64": 1, "armeabi-v7a": 2, "arm64-v8a": 3] import com.android.build.OutputFile diff --git a/android/app/proguard-rules.pro b/android/app/proguard-rules.pro new file mode 100644 index 00000000..2bcd166d --- /dev/null +++ b/android/app/proguard-rules.pro @@ -0,0 +1,4 @@ +# missing R8 error +-dontwarn javax.annotation.Nullable +-dontwarn org.conscrypt.Conscrypt +-dontwarn org.conscrypt.OpenSSLProvider \ No newline at end of file diff --git a/android/build.gradle b/android/build.gradle index 674e96f4..a7b975c2 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -1,15 +1,3 @@ -buildscript { - ext.kotlin_version = '1.9.0' - repositories { - google() - mavenCentral() - } - - dependencies { - classpath 'com.android.tools.build:gradle:7.2.0' - classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" - } -} allprojects { repositories { @@ -20,6 +8,15 @@ allprojects { rootProject.buildDir = '../build' subprojects { + afterEvaluate { project -> + if (project.hasProperty('android')) { + project.android { + if (namespace == null) { + namespace project.group + } + } + } + } project.buildDir = "${rootProject.buildDir}/${project.name}" } subprojects { diff --git a/android/gradle/wrapper/gradle-wrapper.properties b/android/gradle/wrapper/gradle-wrapper.properties index 3c472b99..afa1e8eb 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-7.5-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.10.2-all.zip diff --git a/android/settings.gradle b/android/settings.gradle index 44e62bcf..f2420f21 100644 --- a/android/settings.gradle +++ b/android/settings.gradle @@ -1,11 +1,25 @@ -include ':app' +pluginManagement { + def flutterSdkPath = { + def properties = new Properties() + file("local.properties").withInputStream { properties.load(it) } + def flutterSdkPath = properties.getProperty("flutter.sdk") + assert flutterSdkPath != null, "flutter.sdk not set in local.properties" + return flutterSdkPath + }() -def localPropertiesFile = new File(rootProject.projectDir, "local.properties") -def properties = new Properties() + includeBuild("$flutterSdkPath/packages/flutter_tools/gradle") -assert localPropertiesFile.exists() -localPropertiesFile.withReader("UTF-8") { reader -> properties.load(reader) } + repositories { + google() + mavenCentral() + gradlePluginPortal() + } +} -def flutterSdkPath = properties.getProperty("flutter.sdk") -assert flutterSdkPath != null, "flutter.sdk not set in local.properties" -apply from: "$flutterSdkPath/packages/flutter_tools/gradle/app_plugin_loader.gradle" +plugins { + id "dev.flutter.flutter-plugin-loader" version "1.0.0" + id "com.android.application" version "8.7.2" apply false + id "org.jetbrains.kotlin.android" version "1.9.0" apply false +} + +include ":app" \ No newline at end of file diff --git a/pubspec.lock b/pubspec.lock index 715b23a5..d29eb4f3 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -85,10 +85,10 @@ packages: dependency: "direct main" description: name: audio_service - sha256: "4547c312a94f9cb2c48b60823fb190767cbd63454a83c73049384d5d3cba4650" + sha256: "9dd5ba7e77567b290c35908b1950d61485b4dfdd3a0ac398e98cfeec04651b75" url: "https://pub.flutter-io.cn" source: hosted - version: "0.18.13" + version: "0.18.15" audio_service_platform_interface: dependency: transitive description: @@ -101,18 +101,18 @@ packages: dependency: transitive description: name: audio_service_web - sha256: "9d7d5ae5f98a5727f2580fad73062f2484f400eef6cef42919413268e62a363e" + sha256: "4cdc2127cd4562b957fb49227dc58e3303fafb09bde2573bc8241b938cf759d9" url: "https://pub.flutter-io.cn" source: hosted - version: "0.1.2" + version: "0.1.3" audio_session: dependency: "direct main" description: name: audio_session - sha256: "6fdf255ed3af86535c96452c33ecff1245990bb25a605bfb1958661ccc3d467f" + sha256: "343e83bc7809fbda2591a49e525d6b63213ade10c76f15813be9aed6657b3261" url: "https://pub.flutter-io.cn" source: hosted - version: "0.1.18" + version: "0.1.21" audio_video_progress_bar: dependency: "direct main" description: diff --git a/pubspec.yaml b/pubspec.yaml index 80a8b067..6ee98cc3 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -89,8 +89,8 @@ dependencies: media_kit_libs_video: ^1.0.4 # 媒体通知 - audio_service: ^0.18.13 - audio_session: ^0.1.18 + audio_service: ^0.18.15 + audio_session: ^0.1.21 # 音量、亮度、屏幕控制 flutter_volume_controller: ^1.3.2 From 5693c9bedab0bd91faa3cc2d5ca54669d90d3c67 Mon Sep 17 00:00:00 2001 From: guozhigq Date: Tue, 3 Dec 2024 23:07:44 +0800 Subject: [PATCH 28/43] fix: dynamic push & style --- lib/pages/dynamics/controller.dart | 12 +++-- .../dynamics/up_dynamic/route_panel.dart | 54 ++++++++++--------- lib/pages/dynamics/widgets/up_panel.dart | 1 + 3 files changed, 38 insertions(+), 29 deletions(-) diff --git a/lib/pages/dynamics/controller.dart b/lib/pages/dynamics/controller.dart index cdc08bdd..5b94ee20 100644 --- a/lib/pages/dynamics/controller.dart +++ b/lib/pages/dynamics/controller.dart @@ -117,20 +117,23 @@ class DynamicsController extends GetxController { /// 点击评论action 直接查看评论 if (action == 'comment') { Get.toNamed('/dynamicDetail', - arguments: {'item': item, 'floor': floor, 'action': action}); + arguments: {'item': item, 'floor': floor, 'action': action}, + preventDuplicates: false); return false; } switch (item!.type) { /// 转发的动态 case 'DYNAMIC_TYPE_FORWARD': Get.toNamed('/dynamicDetail', - arguments: {'item': item, 'floor': floor}); + arguments: {'item': item, 'floor': floor}, + preventDuplicates: false); break; /// 图文动态查看 case 'DYNAMIC_TYPE_DRAW': Get.toNamed('/dynamicDetail', - arguments: {'item': item, 'floor': floor}); + arguments: {'item': item, 'floor': floor}, + preventDuplicates: false); break; case 'DYNAMIC_TYPE_AV': String bvid = item.modules.moduleDynamic.major.archive.bvid; @@ -188,7 +191,8 @@ class DynamicsController extends GetxController { case 'DYNAMIC_TYPE_WORD': print('纯文本'); Get.toNamed('/dynamicDetail', - arguments: {'item': item, 'floor': floor}); + arguments: {'item': item, 'floor': floor}, + preventDuplicates: false); break; case 'DYNAMIC_TYPE_LIVE_RCMD': DynamicLiveModel liveRcmd = item.modules.moduleDynamic.major.liveRcmd; diff --git a/lib/pages/dynamics/up_dynamic/route_panel.dart b/lib/pages/dynamics/up_dynamic/route_panel.dart index 40c725a8..983a7fe0 100644 --- a/lib/pages/dynamics/up_dynamic/route_panel.dart +++ b/lib/pages/dynamics/up_dynamic/route_panel.dart @@ -1,3 +1,5 @@ +import 'dart:ui'; + import 'package:easy_debounce/easy_throttle.dart'; import 'package:get/get.dart'; import 'package:flutter/material.dart'; @@ -61,6 +63,7 @@ class _OverlayPanelState extends State void onClickUp(data, i, {type = 'click'}) { if (type == 'click') { + data.hasUpdate = false; pageController.jumpToPage(i); } } @@ -81,11 +84,11 @@ class _OverlayPanelState extends State color: Colors.transparent, borderRadius: BorderRadius.circular(20), ), - child: Column( - children: [ - SizedBox( - height: 50, - child: TabBar( + child: BackdropFilter( + filter: ImageFilter.blur(sigmaX: 8.0, sigmaY: 8.0), + child: Column( + children: [ + TabBar( controller: _tabController, dividerColor: Colors.transparent, automaticIndicatorColorAdjustment: false, @@ -106,25 +109,26 @@ class _OverlayPanelState extends State }); }, ), - ), - Expanded( - child: PageView.builder( - itemCount: upList.length, - controller: pageController, - itemBuilder: (BuildContext context, int index) { - return Container( - clipBehavior: Clip.antiAlias, - margin: const EdgeInsets.fromLTRB(10, 12, 10, 0), - decoration: BoxDecoration( - color: Theme.of(context).colorScheme.surface, - borderRadius: BorderRadius.circular(20), - ), - child: UpDyanmicsPage(upInfo: upList[index], ctr: widget.ctr), - ); - }, + Expanded( + child: PageView.builder( + itemCount: upList.length, + controller: pageController, + itemBuilder: (BuildContext context, int index) { + return Container( + clipBehavior: Clip.antiAlias, + margin: const EdgeInsets.fromLTRB(10, 12, 10, 0), + decoration: BoxDecoration( + color: Theme.of(context).colorScheme.surface, + borderRadius: BorderRadius.circular(20), + ), + child: + UpDyanmicsPage(upInfo: upList[index], ctr: widget.ctr), + ); + }, + ), ), - ), - ], + ], + ), ), ); } @@ -139,8 +143,8 @@ class _OverlayPanelState extends State duration: const Duration(milliseconds: 200), scale: currentMid == data.mid ? 1 : 0.9, child: NetworkImgLayer( - width: contentWidth, - height: contentWidth, + width: 46, + height: 46, src: data.face, type: 'avatar', ), diff --git a/lib/pages/dynamics/widgets/up_panel.dart b/lib/pages/dynamics/widgets/up_panel.dart index dedb51bb..01ef9a61 100644 --- a/lib/pages/dynamics/widgets/up_panel.dart +++ b/lib/pages/dynamics/widgets/up_panel.dart @@ -44,6 +44,7 @@ class _UpPanelState extends State { void onClickUp(data, i) { currentMid.value = data.mid; + data.hasUpdate = false; Navigator.push( context, PlPopupRoute( From 498cba0ac69076f4c139cdc5e7677bf6aa40036d Mon Sep 17 00:00:00 2001 From: guozhigq Date: Tue, 3 Dec 2024 23:12:57 +0800 Subject: [PATCH 29/43] fix: search suggest richText color --- lib/models/search/suggest.dart | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/lib/models/search/suggest.dart b/lib/models/search/suggest.dart index ff161476..df53d46c 100644 --- a/lib/models/search/suggest.dart +++ b/lib/models/search/suggest.dart @@ -84,10 +84,7 @@ Widget highlightText(String str) { String remainingText = str.substring(currentIndex); // 将剩余的普通文本部分添加到 children 列表中 - children.add(TextSpan( - text: remainingText, - style: DefaultTextStyle.of(Get.context!).style, - )); + children.add(TextSpan(text: remainingText)); } // 使用 Text.rich 创建包含高亮显示的富文本小部件,并返回 From 7a1d5404086c696737f5a1d14cb82977e105e471 Mon Sep 17 00:00:00 2001 From: guozhigq Date: Tue, 3 Dec 2024 23:29:41 +0800 Subject: [PATCH 30/43] =?UTF-8?q?opt:=20=E8=BD=AC=E8=BD=BD=E8=A7=86?= =?UTF-8?q?=E9=A2=91=E6=A0=87=E8=AF=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../video/detail/introduction/controller.dart | 5 ++- lib/pages/video/detail/introduction/view.dart | 42 ++++++++++--------- 2 files changed, 27 insertions(+), 20 deletions(-) diff --git a/lib/pages/video/detail/introduction/controller.dart b/lib/pages/video/detail/introduction/controller.dart index d16d85a8..5c84b3bc 100644 --- a/lib/pages/video/detail/introduction/controller.dart +++ b/lib/pages/video/detail/introduction/controller.dart @@ -66,6 +66,8 @@ class VideoIntroController extends GetxController { late bool enableRelatedVideo; UgcSeason? ugcSeason; RxList pages = [].obs; + // 默认原创视频 + int copyright = 1; @override void onInit() { @@ -94,6 +96,7 @@ class VideoIntroController extends GetxController { videoDetail.value = result['data']!; ugcSeason = result['data']!.ugcSeason; pages.value = result['data']!.pages!; + copyright = result['data']!.copyright!; if (type == null) { lastPlayCid.value = cid ?? videoDetail.value.cid!; } @@ -215,7 +218,7 @@ class VideoIntroController extends GetxController { contentPadding: const EdgeInsets.fromLTRB(0, 12, 0, 24), content: Column( mainAxisSize: MainAxisSize.min, - children: [1, 2] + children: (copyright == 2 ? [1] : [1, 2]) .map( (e) => ListTile( title: Padding( diff --git a/lib/pages/video/detail/introduction/view.dart b/lib/pages/video/detail/introduction/view.dart index c8ea5fa0..e35b1ee3 100644 --- a/lib/pages/video/detail/introduction/view.dart +++ b/lib/pages/video/detail/introduction/view.dart @@ -8,6 +8,7 @@ import 'package:flutter/material.dart'; import 'package:hive/hive.dart'; import 'package:pilipala/common/constants.dart'; import 'package:pilipala/common/skeleton/video_intro.dart'; +import 'package:pilipala/common/widgets/badge.dart'; import 'package:pilipala/common/widgets/http_error.dart'; import 'package:pilipala/pages/video/detail/index.dart'; import 'package:pilipala/common/widgets/network_img_layer.dart'; @@ -264,6 +265,26 @@ class _VideoInfoState extends State with TickerProviderStateMixin { Widget build(BuildContext context) { final ThemeData t = Theme.of(context); final Color outline = t.colorScheme.outline; + const TextStyle titleStyle = TextStyle( + fontSize: 18, + fontWeight: FontWeight.bold, + ); + + TextSpan titltWidget = TextSpan( + children: [ + WidgetSpan( + child: Visibility( + visible: widget.videoDetail!.copyright == 2, + child: const PBadge(text: '转载', type: 'color'), + ), + ), + const TextSpan(text: ' '), + TextSpan( + text: widget.videoDetail!.title!, + style: titleStyle, + ), + ], + ); return SliverPadding( padding: const EdgeInsets.only( left: StyleString.safeSpace, @@ -285,25 +306,8 @@ class _VideoInfoState extends State with TickerProviderStateMixin { }, child: ExpandablePanel( controller: _expandableCtr, - collapsed: Text( - widget.videoDetail!.title!, - softWrap: true, - maxLines: 2, - overflow: TextOverflow.ellipsis, - style: const TextStyle( - fontSize: 18, - fontWeight: FontWeight.bold, - ), - ), - expanded: Text( - widget.videoDetail!.title!, - softWrap: true, - maxLines: 10, - style: const TextStyle( - fontSize: 18, - fontWeight: FontWeight.bold, - ), - ), + collapsed: Text.rich(softWrap: true, maxLines: 2, titltWidget), + expanded: Text.rich(softWrap: true, maxLines: 10, titltWidget), theme: const ExpandableThemeData( animationDuration: Duration(milliseconds: 300), scrollAnimationDuration: Duration(milliseconds: 300), From c9883fc10fe0cf10ba745be236ae4cb8d252a85e Mon Sep 17 00:00:00 2001 From: guozhigq Date: Tue, 3 Dec 2024 23:41:24 +0800 Subject: [PATCH 31/43] opt: ugcSeason intro scrollable --- lib/common/pages_bottom_sheet.dart | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/lib/common/pages_bottom_sheet.dart b/lib/common/pages_bottom_sheet.dart index 434b73c4..88c7b758 100644 --- a/lib/common/pages_bottom_sheet.dart +++ b/lib/common/pages_bottom_sheet.dart @@ -766,10 +766,15 @@ class UgcSeasonBuild extends StatelessWidget { ], ), if (ugcSeason.intro != null && ugcSeason.intro != '') ...[ - const SizedBox(height: 4), - Text( - ugcSeason.intro!, - style: TextStyle(color: outline, fontSize: 12), + const SizedBox(height: 6), + Container( + constraints: const BoxConstraints(maxHeight: 100), + child: SingleChildScrollView( + child: Text( + ugcSeason.intro!, + style: TextStyle(color: outline, fontSize: 12), + ), + ), ), ], const SizedBox(height: 4), From 417c225b27a6df680c044ab7027b6ff80821dd9b Mon Sep 17 00:00:00 2001 From: guozhigq Date: Wed, 4 Dec 2024 23:35:59 +0800 Subject: [PATCH 32/43] mod: pureBlack theme config --- lib/main.dart | 6 ------ 1 file changed, 6 deletions(-) diff --git a/lib/main.dart b/lib/main.dart index 8dc243ab..6c43a139 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -226,14 +226,8 @@ class BuildMainApp extends StatelessWidget { pureDarkColorScheme = darkColorScheme.copyWith( background: Colors.black, surface: Colors.black, - primary: Colors.white, - secondary: Colors.white, - error: Colors.red, onPrimary: Colors.black, onSecondary: Colors.black, - onSurface: Colors.white, - onBackground: Colors.white, - onError: Colors.white, ); } From e48ab793cc3b6831cc0817404502cabcb542aa15 Mon Sep 17 00:00:00 2001 From: guozhigq Date: Thu, 5 Dec 2024 23:55:22 +0800 Subject: [PATCH 33/43] Update README.md --- README.md | 47 +++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 41 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 228d17bb..af462d6f 100644 --- a/README.md +++ b/README.md @@ -26,15 +26,50 @@ Xcode 13.4 不支持**auto_orientation**,请注释相关代码 ```bash -[✓] Flutter (Channel stable, 3.16.5, on macOS 14.1.2 23B92 darwin-arm64, locale - zh-Hans-CN) +[!] Flutter (Channel [user-branch], 3.19.6, on macOS 14.6.1 23G93 darwin-arm64, + locale zh-Hans-CN) + ! Flutter version 3.19.6 on channel [user-branch] at + /Users/rr/Documents/sdk/flutter + Currently on an unknown channel. Run `flutter channel` to switch to an + official channel. + If that doesn't fix the issue, reinstall Flutter by following instructions + at https://flutter.dev/docs/get-started/install. + ! Upstream repository unknown source is not a standard remote. + Set environment variable "FLUTTER_GIT_URL" to unknown source to dismiss + this error. + • Framework revision 54e66469a9 (8 months ago), 2024-04-17 13:08:03 -0700 + • Engine revision c4cd48e186 + • Dart version 3.3.4 + • DevTools version 2.31.1 + • Pub download mirror https://pub.flutter-io.cn + • Flutter download mirror https://storage.flutter-io.cn + • If those were intentional, you can disregard the above warnings; however + it is recommended to use "git" directly to perform update checks and + upgrades. + [✓] Android toolchain - develop for Android devices (Android SDK version 34.0.0) + • Android SDK at /Users/rr/Library/Android/sdk + • Platform android-34, build-tools 34.0.0 + • Java binary at: /Applications/Android + Studio.app/Contents/jbr/Contents/Home/bin/java + • Java version OpenJDK Runtime Environment (build 21.0.3+-79915917-b509.11) + • All Android licenses accepted. + [✓] Xcode - develop for iOS and macOS (Xcode 15.1) + • Xcode at /Applications/Xcode.app/Contents/Developer + • Build 15C65 + • CocoaPods version 1.14.3 + [✓] Chrome - develop for the web -[✓] Android Studio (version 2022.3) -[✓] VS Code (version 1.87.2) -[✓] Connected device (3 available) -[✓] Network resources + • Chrome at /Applications/Google Chrome.app/Contents/MacOS/Google Chrome + +[✓] Android Studio (version 2024.2) + • Android Studio at /Applications/Android Studio.app/Contents + • Flutter plugin can be installed from: + 🔨 https://plugins.jetbrains.com/plugin/9212-flutter + • Dart plugin can be installed from: + 🔨 https://plugins.jetbrains.com/plugin/6351-dart + • Java version OpenJDK Runtime Environment (build 21.0.3+-79915917-b509.11) ``` From 16088df3a337d5a96520318aa8f4c4b07b3cc0c0 Mon Sep 17 00:00:00 2001 From: guozhigq Date: Fri, 6 Dec 2024 23:30:21 +0800 Subject: [PATCH 34/43] mod: member search loading status --- lib/pages/member_search/controller.dart | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/pages/member_search/controller.dart b/lib/pages/member_search/controller.dart index be4f5b1a..a81e7746 100644 --- a/lib/pages/member_search/controller.dart +++ b/lib/pages/member_search/controller.dart @@ -37,6 +37,7 @@ class MemberSearchController extends GetxController { } else { Get.back(); } + loadingStatus.value = 'init'; } void onChange(value) { @@ -76,7 +77,7 @@ class MemberSearchController extends GetxController { archivePn += 1; hasRequest = true; } - // loadingStatus.value = 'finish'; + loadingStatus.value = 'finish'; return res; } From 9d1bba8a346da7750a5720509166b2ebf41e5d82 Mon Sep 17 00:00:00 2001 From: guozhigq Date: Fri, 6 Dec 2024 23:54:18 +0800 Subject: [PATCH 35/43] opt: fullScreen logic --- lib/pages/video/detail/view.dart | 74 ++++++++++++++++---------------- lib/plugin/pl_player/view.dart | 2 + 2 files changed, 40 insertions(+), 36 deletions(-) diff --git a/lib/pages/video/detail/view.dart b/lib/pages/video/detail/view.dart index 0ec777c4..96a06fa6 100644 --- a/lib/pages/video/detail/view.dart +++ b/lib/pages/video/detail/view.dart @@ -502,7 +502,7 @@ class _VideoDetailPageState extends State @override Widget build(BuildContext context) { final sizeContext = MediaQuery.sizeOf(context); - final _context = MediaQuery.of(context); + final orientation = MediaQuery.orientationOf(context); late final double verticalHeight = sizeContext.width * 22 / 16; late double defaultVideoHeight = vdCtr.videoDirection.value == 'vertical' ? verticalHeight @@ -517,12 +517,12 @@ class _VideoDetailPageState extends State }); // 竖屏 - final bool isPortrait = _context.orientation == Orientation.portrait; + final bool isPortrait = orientation == Orientation.portrait; // 横屏 - final bool isLandscape = _context.orientation == Orientation.landscape; + final bool isLandscape = orientation == Orientation.landscape; final Rx isFullScreen = plPlayerController?.isFullScreen ?? false.obs; // 全屏时高度撑满 - if (isLandscape || isFullScreen.value == true) { + if (isLandscape || isFullScreen.value) { videoHeight.value = Get.size.height; enterFullScreen(); } else { @@ -634,10 +634,8 @@ class _VideoDetailPageState extends State } Widget childWhenDisabled = SafeArea( - top: MediaQuery.of(context).orientation == Orientation.portrait && - plPlayerController?.isFullScreen.value == true, - bottom: MediaQuery.of(context).orientation == Orientation.portrait && - plPlayerController?.isFullScreen.value == true, + top: isPortrait && isFullScreen.value, + bottom: isPortrait && isFullScreen.value, left: false, right: false, child: Stack( @@ -660,22 +658,25 @@ class _VideoDetailPageState extends State return [ Obx( () { - final Orientation orientation = - MediaQuery.of(context).orientation; + final bool isLandscape = + MediaQuery.orientationOf(context) == + Orientation.landscape; final bool isFullScreen = - plPlayerController?.isFullScreen.value == true; - final double expandedHeight = - orientation == Orientation.landscape || isFullScreen - ? (MediaQuery.sizeOf(context).height - - (orientation == Orientation.landscape - ? 0 - : MediaQuery.of(context).padding.top)) - : videoHeight.value; - if (orientation == Orientation.landscape || - isFullScreen) { + plPlayerController?.isFullScreen.value ?? false; + + late double expandedHeight; + if (isLandscape || isFullScreen) { enterFullScreen(); + expandedHeight = (MediaQuery.sizeOf(context).height - + (isLandscape + ? 0 + : MediaQuery.paddingOf(context).top)); } else { exitFullScreen(); + if (vdCtr.videoDirection.value == 'vertical') { + videoHeight.value = verticalHeight; + } + expandedHeight = videoHeight.value; } return SliverAppBar( automaticallyImplyLeading: false, @@ -687,21 +688,24 @@ class _VideoDetailPageState extends State backgroundColor: Colors.black, flexibleSpace: SizedBox.expand( child: PopScope( - canPop: - plPlayerController?.isFullScreen.value != true, + canPop: !isFullScreen, onPopInvoked: (bool didPop) { - if (plPlayerController?.controlsLock.value == - true) { - plPlayerController?.onLockControl(false); - return; + if (plPlayerController != null) { + if (plPlayerController!.controlsLock.value) { + plPlayerController!.onLockControl(false); + return; + } + if (isFullScreen) { + plPlayerController! + .triggerFullScreen(status: false); + if (vdCtr.videoDirection.value == + 'vertical') { + videoHeight.value = verticalHeight; + } + } } - if (plPlayerController?.isFullScreen.value == - true) { - plPlayerController! - .triggerFullScreen(status: false); - } - if (MediaQuery.of(context).orientation == - Orientation.landscape) { + + if (isLandscape) { verticalScreen(); } }, @@ -746,9 +750,7 @@ class _VideoDetailPageState extends State /// 不收回 pinnedHeaderSliverHeightBuilder: () { - return MediaQuery.of(context).orientation == - Orientation.landscape || - plPlayerController?.isFullScreen.value == true + return isLandscape || isFullScreen.value ? MediaQuery.sizeOf(context).height : playerStatus.value != PlayerStatus.playing ? kToolbarHeight diff --git a/lib/plugin/pl_player/view.dart b/lib/plugin/pl_player/view.dart index b47d38de..3bc6b288 100644 --- a/lib/plugin/pl_player/view.dart +++ b/lib/plugin/pl_player/view.dart @@ -732,6 +732,7 @@ class _PLVideoPlayerState extends State lastFullScreenToggleTime = DateTime.now(); // 下滑退出全屏 await widget.controller.triggerFullScreen(status: flag); + widget.fullScreenCb?.call(flag); } _distance.value = 0.0; } else if (dy < _distance.value && @@ -741,6 +742,7 @@ class _PLVideoPlayerState extends State lastFullScreenToggleTime = DateTime.now(); // 上滑进入全屏 await widget.controller.triggerFullScreen(status: !flag); + widget.fullScreenCb?.call(!flag); } _distance.value = 0.0; } From 7b57ef17ececcbfe2093b4d7137ec49f321d4bad Mon Sep 17 00:00:00 2001 From: guozhigq Date: Sat, 7 Dec 2024 00:34:03 +0800 Subject: [PATCH 36/43] opt: http interceptor --- lib/http/interceptor.dart | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/lib/http/interceptor.dart b/lib/http/interceptor.dart index b33d18df..843a5163 100644 --- a/lib/http/interceptor.dart +++ b/lib/http/interceptor.dart @@ -3,7 +3,7 @@ import 'package:connectivity_plus/connectivity_plus.dart'; import 'package:dio/dio.dart'; import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; -import 'package:pilipala/utils/login.dart'; +// import 'package:pilipala/utils/login.dart'; class ApiInterceptor extends Interceptor { @override @@ -19,9 +19,9 @@ class ApiInterceptor extends Interceptor { void onResponse(Response response, ResponseInterceptorHandler handler) { try { // 在响应之后处理数据 - if (response.data is Map && response.data['code'] == -101) { - LoginUtils.loginOut(); - } + // if (response.data is Map && response.data['code'] == -101) { + // LoginUtils.loginOut(); + // } } catch (err) { print('ApiInterceptor: $err'); } @@ -39,6 +39,8 @@ class ApiInterceptor extends Interceptor { SmartDialog.showToast( await dioError(err), displayType: SmartToastType.onlyRefresh, + displayTime: const Duration(seconds: 1), + debounce: true, ); } super.onError(err, handler); @@ -62,7 +64,7 @@ class ApiInterceptor extends Interceptor { return '发送请求超时,请检查网络设置'; case DioExceptionType.unknown: final String res = await checkConnect(); - return '$res,网络异常!'; + return '$res ${error.error}'; } } From 6624cc6a22c5339fdb9655a326c850b65f131c76 Mon Sep 17 00:00:00 2001 From: guozhigq Date: Sat, 7 Dec 2024 20:01:51 +0800 Subject: [PATCH 37/43] opt: reply sheetHeight --- lib/pages/video/detail/view.dart | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/lib/pages/video/detail/view.dart b/lib/pages/video/detail/view.dart index 96a06fa6..bc953d21 100644 --- a/lib/pages/video/detail/view.dart +++ b/lib/pages/video/detail/view.dart @@ -1,5 +1,6 @@ import 'dart:async'; import 'dart:io'; +import 'dart:math'; import 'package:extended_nested_scroll_view/extended_nested_scroll_view.dart'; import 'package:floating/floating.dart'; @@ -76,7 +77,7 @@ class _VideoDetailPageState extends State getStatusHeight(); heroTag = Get.arguments['heroTag']; vdCtr = Get.put(VideoDetailController(), tag: heroTag); - vdCtr.sheetHeight.value = localCache.get('sheetHeight'); + vdCtr.sheetHeight.value = GlobalDataCache.sheetHeight; videoIntroController = Get.put( VideoIntroController(bvid: Get.parameters['bvid']!), tag: heroTag); @@ -223,8 +224,8 @@ class _VideoDetailPageState extends State void _extendNestCtrListener() { final double offset = _extendNestCtr.position.pixels; if (vdCtr.videoDirection.value == 'horizontal') { - vdCtr.sheetHeight.value = - Get.size.height - videoHeight - statusBarHeight + offset; + vdCtr.sheetHeight.value = max(GlobalDataCache.sheetHeight, + Get.size.height - videoHeight - statusBarHeight + offset); appbarStream.add(offset); } else { if (offset > (Get.size.width * 22 / 16 - videoHeight)) { From 30c1a5037da503222e9c624722b5c7c5f7722c5e Mon Sep 17 00:00:00 2001 From: guozhigq Date: Sat, 7 Dec 2024 20:45:05 +0800 Subject: [PATCH 38/43] opt: control bar height --- .../video/detail/widgets/header_control.dart | 11 +- lib/plugin/pl_player/view.dart | 858 +++++++++--------- .../pl_player/widgets/bottom_control.dart | 9 +- 3 files changed, 437 insertions(+), 441 deletions(-) diff --git a/lib/pages/video/detail/widgets/header_control.dart b/lib/pages/video/detail/widgets/header_control.dart index 28448f51..718cdac6 100644 --- a/lib/pages/video/detail/widgets/header_control.dart +++ b/lib/pages/video/detail/widgets/header_control.dart @@ -1068,13 +1068,10 @@ class _HeaderControlState extends State { ); final bool isLandscape = MediaQuery.of(context).orientation == Orientation.landscape; - return AppBar( - backgroundColor: Colors.transparent, - foregroundColor: Colors.white, - primary: false, - automaticallyImplyLeading: false, - titleSpacing: 14, - title: Column( + return Padding( + padding: const EdgeInsets.symmetric(horizontal: 14), + child: Column( + mainAxisSize: MainAxisSize.min, children: [ if (isFullScreen.value && isLandscape) ...[ Row( diff --git a/lib/plugin/pl_player/view.dart b/lib/plugin/pl_player/view.dart index b47d38de..0790d10a 100644 --- a/lib/plugin/pl_player/view.dart +++ b/lib/plugin/pl_player/view.dart @@ -443,334 +443,336 @@ class _PLVideoPlayerState extends State color: Colors.white, fontSize: 12, ); - return Stack( - fit: StackFit.passthrough, - children: [ - Obx( - () => Video( - key: ValueKey(_.videoFit.value), - controller: videoController, - controls: NoVideoControls, - alignment: widget.alignment!, - pauseUponEnteringBackgroundMode: !enableBackgroundPlay, - resumeUponEnteringForegroundMode: true, - subtitleViewConfiguration: const SubtitleViewConfiguration( - style: subTitleStyle, - padding: EdgeInsets.all(24.0), + return ClipRect( + child: Stack( + fit: StackFit.passthrough, + children: [ + Obx( + () => Video( + key: ValueKey(_.videoFit.value), + controller: videoController, + controls: NoVideoControls, + alignment: widget.alignment!, + pauseUponEnteringBackgroundMode: !enableBackgroundPlay, + resumeUponEnteringForegroundMode: true, + subtitleViewConfiguration: const SubtitleViewConfiguration( + style: subTitleStyle, + padding: EdgeInsets.all(24.0), + ), + fit: _.videoFit.value, ), - fit: _.videoFit.value, ), - ), - /// 长按倍速 toast - Obx( - () => Align( - alignment: Alignment.topCenter, - child: FractionalTranslation( - translation: const Offset(0.0, 0.3), // 上下偏移量(负数向上偏移) - child: AnimatedOpacity( - curve: Curves.easeInOut, - opacity: _.doubleSpeedStatus.value ? 1.0 : 0.0, - duration: const Duration(milliseconds: 150), - child: Container( - alignment: Alignment.center, - decoration: BoxDecoration( - color: const Color(0x88000000), - borderRadius: BorderRadius.circular(16.0), - ), - height: 32.0, - width: 70.0, - child: const Center( - child: Text( - '倍速中', - style: TextStyle(color: Colors.white, fontSize: 13), + /// 长按倍速 toast + Obx( + () => Align( + alignment: Alignment.topCenter, + child: FractionalTranslation( + translation: const Offset(0.0, 0.3), // 上下偏移量(负数向上偏移) + child: AnimatedOpacity( + curve: Curves.easeInOut, + opacity: _.doubleSpeedStatus.value ? 1.0 : 0.0, + duration: const Duration(milliseconds: 150), + child: Container( + alignment: Alignment.center, + decoration: BoxDecoration( + color: const Color(0x88000000), + borderRadius: BorderRadius.circular(16.0), ), - )), + height: 32.0, + width: 70.0, + child: const Center( + child: Text( + '倍速中', + style: TextStyle(color: Colors.white, fontSize: 13), + ), + )), + ), ), ), ), - ), - /// 时间进度 toast - Obx( - () => Align( - alignment: Alignment.topCenter, - child: FractionalTranslation( - translation: const Offset(0.0, 1.0), // 上下偏移量(负数向上偏移) - child: AnimatedOpacity( - curve: Curves.easeInOut, - opacity: _.isSliderMoving.value ? 1.0 : 0.0, - duration: const Duration(milliseconds: 150), - child: IntrinsicWidth( - child: Container( - alignment: Alignment.center, - decoration: BoxDecoration( - color: const Color(0x88000000), - borderRadius: BorderRadius.circular(64.0), - ), - height: 34.0, - padding: const EdgeInsets.only(left: 10, right: 10), - child: Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Obx(() { - return Text( - _.sliderTempPosition.value.inMinutes >= 60 - ? printDurationWithHours( - _.sliderTempPosition.value) - : printDuration(_.sliderTempPosition.value), - style: textStyle, - ); - }), - const SizedBox(width: 2), - const Text('/', style: textStyle), - const SizedBox(width: 2), - Obx( - () => Text( - _.duration.value.inMinutes >= 60 - ? printDurationWithHours(_.duration.value) - : printDuration(_.duration.value), - style: textStyle, + /// 时间进度 toast + Obx( + () => Align( + alignment: Alignment.topCenter, + child: FractionalTranslation( + translation: const Offset(0.0, 1.0), // 上下偏移量(负数向上偏移) + child: AnimatedOpacity( + curve: Curves.easeInOut, + opacity: _.isSliderMoving.value ? 1.0 : 0.0, + duration: const Duration(milliseconds: 150), + child: IntrinsicWidth( + child: Container( + alignment: Alignment.center, + decoration: BoxDecoration( + color: const Color(0x88000000), + borderRadius: BorderRadius.circular(64.0), + ), + height: 34.0, + padding: const EdgeInsets.only(left: 10, right: 10), + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Obx(() { + return Text( + _.sliderTempPosition.value.inMinutes >= 60 + ? printDurationWithHours( + _.sliderTempPosition.value) + : printDuration(_.sliderTempPosition.value), + style: textStyle, + ); + }), + const SizedBox(width: 2), + const Text('/', style: textStyle), + const SizedBox(width: 2), + Obx( + () => Text( + _.duration.value.inMinutes >= 60 + ? printDurationWithHours(_.duration.value) + : printDuration(_.duration.value), + style: textStyle, + ), ), - ), - ], + ], + ), ), ), ), ), ), ), - ), - /// 音量🔊 控制条展示 - Obx( - () => ControlBar( - visible: _volumeIndicator.value, - icon: _volumeValue.value < 1.0 / 3.0 - ? Icons.volume_mute - : _volumeValue.value < 2.0 / 3.0 - ? Icons.volume_down - : Icons.volume_up, - value: _volumeValue.value, + /// 音量🔊 控制条展示 + Obx( + () => ControlBar( + visible: _volumeIndicator.value, + icon: _volumeValue.value < 1.0 / 3.0 + ? Icons.volume_mute + : _volumeValue.value < 2.0 / 3.0 + ? Icons.volume_down + : Icons.volume_up, + value: _volumeValue.value, + ), ), - ), - /// 亮度🌞 控制条展示 - Obx( - () => ControlBar( - visible: _brightnessIndicator.value, - icon: _brightnessValue.value < 1.0 / 3.0 - ? Icons.brightness_low - : _brightnessValue.value < 2.0 / 3.0 - ? Icons.brightness_medium - : Icons.brightness_high, - value: _brightnessValue.value, + /// 亮度🌞 控制条展示 + Obx( + () => ControlBar( + visible: _brightnessIndicator.value, + icon: _brightnessValue.value < 1.0 / 3.0 + ? Icons.brightness_low + : _brightnessValue.value < 2.0 / 3.0 + ? Icons.brightness_medium + : Icons.brightness_high, + value: _brightnessValue.value, + ), ), - ), - // Obx(() { - // if (_.buffered.value == Duration.zero) { - // return Positioned.fill( - // child: Container( - // color: Colors.black, - // child: Center( - // child: Image.asset( - // 'assets/images/loading.gif', - // height: 25, - // ), - // ), - // ), - // ); - // } else { - // return Container(); - // } - // }), + // Obx(() { + // if (_.buffered.value == Duration.zero) { + // return Positioned.fill( + // child: Container( + // color: Colors.black, + // child: Center( + // child: Image.asset( + // 'assets/images/loading.gif', + // height: 25, + // ), + // ), + // ), + // ); + // } else { + // return Container(); + // } + // }), - /// 弹幕面板 - if (widget.danmuWidget != null) - Positioned.fill(top: 4, child: widget.danmuWidget!), + /// 弹幕面板 + if (widget.danmuWidget != null) + Positioned.fill(top: 4, child: widget.danmuWidget!), - /// 开启且有字幕时展示 - Stack( - children: [ - Positioned( - left: 0, - right: 0, - bottom: 30, - child: Align( - alignment: Alignment.center, - child: Obx( - () => Visibility( - visible: widget.controller.subTitleCode.value != -1, - child: Container( - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(4), - color: widget.controller.subtitleContent.value != '' - ? Colors.black.withOpacity(0.6) - : Colors.transparent, - ), - padding: widget.controller.subTitleCode.value != -1 - ? const EdgeInsets.symmetric( - horizontal: 10, - vertical: 4, - ) - : EdgeInsets.zero, - child: Text( - widget.controller.subtitleContent.value, - style: const TextStyle( - color: Colors.white, - fontSize: 12, + /// 开启且有字幕时展示 + Stack( + children: [ + Positioned( + left: 0, + right: 0, + bottom: 30, + child: Align( + alignment: Alignment.center, + child: Obx( + () => Visibility( + visible: widget.controller.subTitleCode.value != -1, + child: Container( + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(4), + color: widget.controller.subtitleContent.value != '' + ? Colors.black.withOpacity(0.6) + : Colors.transparent, ), - ), - )), + padding: widget.controller.subTitleCode.value != -1 + ? const EdgeInsets.symmetric( + horizontal: 10, + vertical: 4, + ) + : EdgeInsets.zero, + child: Text( + widget.controller.subtitleContent.value, + style: const TextStyle( + color: Colors.white, + fontSize: 12, + ), + ), + )), + ), ), ), - ), - ], - ), - - /// 手势 - Positioned.fill( - left: 16, - top: 25, - right: 15, - bottom: 15, - child: GestureDetector( - onTap: () { - _.controls = !_.showControls.value; - }, - onDoubleTapDown: (TapDownDetails details) { - // live模式下禁用 锁定时🔒禁用 - if (_.videoType == 'live' || _.controlsLock.value) { - return; - } - final double totalWidth = MediaQuery.sizeOf(context).width; - final double tapPosition = details.localPosition.dx; - final double sectionWidth = totalWidth / 3; - String type = 'left'; - if (tapPosition < sectionWidth) { - type = 'left'; - } else if (tapPosition < sectionWidth * 2) { - type = 'center'; - } else { - type = 'right'; - } - doubleTapFuc(type); - }, - onLongPressStart: (LongPressStartDetails detail) { - feedBack(); - _.setDoubleSpeedStatus(true); - }, - onLongPressEnd: (LongPressEndDetails details) { - _.setDoubleSpeedStatus(false); - }, - - /// 水平位置 快进 live模式下禁用 - onHorizontalDragUpdate: (DragUpdateDetails details) { - // live模式下禁用 锁定时🔒禁用 - if (_.videoType == 'live' || _.controlsLock.value) { - return; - } - // final double tapPosition = details.localPosition.dx; - final int curSliderPosition = - _.sliderPosition.value.inMilliseconds; - final double scale = 90000 / MediaQuery.sizeOf(context).width; - final Duration pos = Duration( - milliseconds: - curSliderPosition + (details.delta.dx * scale).round()); - final Duration result = - pos.clamp(Duration.zero, _.duration.value); - _.onUpdatedSliderProgress(result); - _.onChangedSliderStart(); - }, - onHorizontalDragEnd: (DragEndDetails details) { - if (_.videoType == 'live' || _.controlsLock.value) { - return; - } - _.onChangedSliderEnd(); - _.seekTo(_.sliderPosition.value, type: 'slider'); - }, - // 垂直方向 音量/亮度调节 - onVerticalDragUpdate: (DragUpdateDetails details) async { - final double totalWidth = MediaQuery.sizeOf(context).width; - final double tapPosition = details.localPosition.dx; - final double sectionWidth = - fullScreenGestureMode == FullScreenGestureMode.none - ? totalWidth / 2 - : totalWidth / 3; - final double delta = details.delta.dy; - - /// 锁定时禁用 - if (_.controlsLock.value) { - return; - } - if (lastFullScreenToggleTime != null && - DateTime.now().difference(lastFullScreenToggleTime!) < - const Duration(milliseconds: 500)) { - return; - } - if (tapPosition < sectionWidth) { - // 左边区域 👈 - final double level = (_.isFullScreen.value - ? Get.size.height - : screenWidth * 9 / 16) * - 3; - final double brightness = - _brightnessValue.value - delta / level; - final double result = brightness.clamp(0.0, 1.0); - setBrightness(result); - } else if (isUsingFullScreenGestures(tapPosition, sectionWidth)) { - // 全屏 - final double dy = details.delta.dy; - const double threshold = 7.0; // 滑动阈值 - final bool flag = fullScreenGestureMode != - FullScreenGestureMode.fromBottomtoTop; - if (dy > _distance.value && - dy > threshold && - !_.controlsLock.value) { - if (_.isFullScreen.value ^ flag) { - lastFullScreenToggleTime = DateTime.now(); - // 下滑退出全屏 - await widget.controller.triggerFullScreen(status: flag); - } - _distance.value = 0.0; - } else if (dy < _distance.value && - dy < -threshold && - !_.controlsLock.value) { - if (!_.isFullScreen.value ^ flag) { - lastFullScreenToggleTime = DateTime.now(); - // 上滑进入全屏 - await widget.controller.triggerFullScreen(status: !flag); - } - _distance.value = 0.0; - } - _distance.value = dy; - } else { - // 右边区域 👈 - EasyThrottle.throttle( - 'setVolume', const Duration(milliseconds: 20), () { - final double level = (_.isFullScreen.value - ? Get.size.height - : screenWidth * 9 / 16); - final double volume = _volumeValue.value - - double.parse(delta.toStringAsFixed(1)) / level; - final double result = volume.clamp(0.0, 1.0); - setVolume(result); - }); - } - }, - onVerticalDragEnd: (DragEndDetails details) {}, + ], ), - ), - // 头部、底部控制条 - Obx( - () => Column( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - if (widget.headerControl != null || _.headerControl != null) ...[ - Flexible( - child: ClipRect( + /// 手势 + Positioned.fill( + left: 16, + top: 25, + right: 15, + bottom: 15, + child: GestureDetector( + onTap: () { + _.controls = !_.showControls.value; + }, + onDoubleTapDown: (TapDownDetails details) { + // live模式下禁用 锁定时🔒禁用 + if (_.videoType == 'live' || _.controlsLock.value) { + return; + } + final double totalWidth = MediaQuery.sizeOf(context).width; + final double tapPosition = details.localPosition.dx; + final double sectionWidth = totalWidth / 3; + String type = 'left'; + if (tapPosition < sectionWidth) { + type = 'left'; + } else if (tapPosition < sectionWidth * 2) { + type = 'center'; + } else { + type = 'right'; + } + doubleTapFuc(type); + }, + onLongPressStart: (LongPressStartDetails detail) { + feedBack(); + _.setDoubleSpeedStatus(true); + }, + onLongPressEnd: (LongPressEndDetails details) { + _.setDoubleSpeedStatus(false); + }, + + /// 水平位置 快进 live模式下禁用 + onHorizontalDragUpdate: (DragUpdateDetails details) { + // live模式下禁用 锁定时🔒禁用 + if (_.videoType == 'live' || _.controlsLock.value) { + return; + } + // final double tapPosition = details.localPosition.dx; + final int curSliderPosition = + _.sliderPosition.value.inMilliseconds; + final double scale = 90000 / MediaQuery.sizeOf(context).width; + final Duration pos = Duration( + milliseconds: + curSliderPosition + (details.delta.dx * scale).round()); + final Duration result = + pos.clamp(Duration.zero, _.duration.value); + _.onUpdatedSliderProgress(result); + _.onChangedSliderStart(); + }, + onHorizontalDragEnd: (DragEndDetails details) { + if (_.videoType == 'live' || _.controlsLock.value) { + return; + } + _.onChangedSliderEnd(); + _.seekTo(_.sliderPosition.value, type: 'slider'); + }, + // 垂直方向 音量/亮度调节 + onVerticalDragUpdate: (DragUpdateDetails details) async { + final double totalWidth = MediaQuery.sizeOf(context).width; + final double tapPosition = details.localPosition.dx; + final double sectionWidth = + fullScreenGestureMode == FullScreenGestureMode.none + ? totalWidth / 2 + : totalWidth / 3; + final double delta = details.delta.dy; + + /// 锁定时禁用 + if (_.controlsLock.value) { + return; + } + if (lastFullScreenToggleTime != null && + DateTime.now().difference(lastFullScreenToggleTime!) < + const Duration(milliseconds: 500)) { + return; + } + if (tapPosition < sectionWidth) { + // 左边区域 👈 + final double level = (_.isFullScreen.value + ? Get.size.height + : screenWidth * 9 / 16) * + 3; + final double brightness = + _brightnessValue.value - delta / level; + final double result = brightness.clamp(0.0, 1.0); + setBrightness(result); + } else if (isUsingFullScreenGestures( + tapPosition, sectionWidth)) { + // 全屏 + final double dy = details.delta.dy; + const double threshold = 7.0; // 滑动阈值 + final bool flag = fullScreenGestureMode != + FullScreenGestureMode.fromBottomtoTop; + if (dy > _distance.value && + dy > threshold && + !_.controlsLock.value) { + if (_.isFullScreen.value ^ flag) { + lastFullScreenToggleTime = DateTime.now(); + // 下滑退出全屏 + await widget.controller.triggerFullScreen(status: flag); + } + _distance.value = 0.0; + } else if (dy < _distance.value && + dy < -threshold && + !_.controlsLock.value) { + if (!_.isFullScreen.value ^ flag) { + lastFullScreenToggleTime = DateTime.now(); + // 上滑进入全屏 + await widget.controller.triggerFullScreen(status: !flag); + } + _distance.value = 0.0; + } + _distance.value = dy; + } else { + // 右边区域 👈 + EasyThrottle.throttle( + 'setVolume', const Duration(milliseconds: 20), () { + final double level = (_.isFullScreen.value + ? Get.size.height + : screenWidth * 9 / 16); + final double volume = _volumeValue.value - + double.parse(delta.toStringAsFixed(1)) / level; + final double result = volume.clamp(0.0, 1.0); + setVolume(result); + }); + } + }, + onVerticalDragEnd: (DragEndDetails details) {}, + ), + ), + + // 头部、底部控制条 + Obx( + () => Column( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + if (widget.headerControl != null || + _.headerControl != null) ...[ + Flexible( child: AppBarAni( controller: animationController, visible: !_.controlsLock.value && _.showControls.value, @@ -778,13 +780,11 @@ class _PLVideoPlayerState extends State child: widget.headerControl ?? _.headerControl!, ), ), - ), - ] else ...[ - const SizedBox.shrink() - ], - Flexible( - flex: _.videoType == 'live' ? 0 : 1, - child: ClipRect( + ] else ...[ + const SizedBox.shrink() + ], + Flexible( + flex: _.videoType == 'live' ? 0 : 1, child: AppBarAni( controller: animationController, visible: !_.controlsLock.value && _.showControls.value, @@ -797,141 +797,141 @@ class _PLVideoPlayerState extends State ), ), ), - ), - ], + ], + ), ), - ), - /// 进度条 live模式下禁用 + /// 进度条 live模式下禁用 - Obx( - () { - final int value = _.sliderPositionSeconds.value; - final int max = _.durationSeconds.value; - final int buffer = _.bufferedSeconds.value; - if (_.showControls.value) { - return Container(); - } - if (defaultBtmProgressBehavior == - BtmProgresBehavior.alwaysHide.code) { - return const SizedBox(); - } - if (defaultBtmProgressBehavior == - BtmProgresBehavior.onlyShowFullScreen.code && - !_.isFullScreen.value) { - return const SizedBox(); - } else if (defaultBtmProgressBehavior == - BtmProgresBehavior.onlyHideFullScreen.code && - _.isFullScreen.value) { - return const SizedBox(); - } + Obx( + () { + final int value = _.sliderPositionSeconds.value; + final int max = _.durationSeconds.value; + final int buffer = _.bufferedSeconds.value; + if (_.showControls.value) { + return Container(); + } + if (defaultBtmProgressBehavior == + BtmProgresBehavior.alwaysHide.code) { + return const SizedBox(); + } + if (defaultBtmProgressBehavior == + BtmProgresBehavior.onlyShowFullScreen.code && + !_.isFullScreen.value) { + return const SizedBox(); + } else if (defaultBtmProgressBehavior == + BtmProgresBehavior.onlyHideFullScreen.code && + _.isFullScreen.value) { + return const SizedBox(); + } - if (_.videoType == 'live') { - return const SizedBox(); - } - if (value > max || max <= 0) { - return const SizedBox(); - } - return Positioned( - bottom: -1.5, - left: 0, - right: 0, - child: ProgressBar( - progress: Duration(seconds: value), - buffered: Duration(seconds: buffer), - total: Duration(seconds: max), - progressBarColor: colorTheme, - baseBarColor: Colors.white.withOpacity(0.2), - bufferedBarColor: Colors.white.withOpacity(0.6), - timeLabelLocation: TimeLabelLocation.none, - thumbColor: colorTheme, - barHeight: 3, - thumbRadius: 0.0, - // onDragStart: (duration) { - // _.onChangedSliderStart(); - // }, - // onDragEnd: () { - // _.onChangedSliderEnd(); - // }, - // onDragUpdate: (details) { - // print(details); - // }, - // onSeek: (duration) { - // feedBack(); - // _.onChangedSlider(duration.inSeconds.toDouble()); - // _.seekTo(duration); - // }, - ), - // SlideTransition( - // position: Tween( - // begin: Offset.zero, - // end: const Offset(0, -1), - // ).animate(CurvedAnimation( - // parent: animationController, - // curve: Curves.easeInOut, - // )), - // child: ), - ); - }, - ), + if (_.videoType == 'live') { + return const SizedBox(); + } + if (value > max || max <= 0) { + return const SizedBox(); + } + return Positioned( + bottom: -1.5, + left: 0, + right: 0, + child: ProgressBar( + progress: Duration(seconds: value), + buffered: Duration(seconds: buffer), + total: Duration(seconds: max), + progressBarColor: colorTheme, + baseBarColor: Colors.white.withOpacity(0.2), + bufferedBarColor: Colors.white.withOpacity(0.6), + timeLabelLocation: TimeLabelLocation.none, + thumbColor: colorTheme, + barHeight: 3, + thumbRadius: 0.0, + // onDragStart: (duration) { + // _.onChangedSliderStart(); + // }, + // onDragEnd: () { + // _.onChangedSliderEnd(); + // }, + // onDragUpdate: (details) { + // print(details); + // }, + // onSeek: (duration) { + // feedBack(); + // _.onChangedSlider(duration.inSeconds.toDouble()); + // _.seekTo(duration); + // }, + ), + // SlideTransition( + // position: Tween( + // begin: Offset.zero, + // end: const Offset(0, -1), + // ).animate(CurvedAnimation( + // parent: animationController, + // curve: Curves.easeInOut, + // )), + // child: ), + ); + }, + ), - // 锁 - Obx( - () => Visibility( - visible: _.videoType != 'live' && _.isFullScreen.value, - child: Align( - alignment: Alignment.centerLeft, - child: FractionalTranslation( - translation: const Offset(1, 0.0), - child: Visibility( - visible: _.showControls.value, - child: ComBtn( - icon: Icon( - _.controlsLock.value - ? FontAwesomeIcons.lock - : FontAwesomeIcons.lockOpen, - size: 15, - color: Colors.white, + // 锁 + Obx( + () => Visibility( + visible: _.videoType != 'live' && _.isFullScreen.value, + child: Align( + alignment: Alignment.centerLeft, + child: FractionalTranslation( + translation: const Offset(1, 0.0), + child: Visibility( + visible: _.showControls.value, + child: ComBtn( + icon: Icon( + _.controlsLock.value + ? FontAwesomeIcons.lock + : FontAwesomeIcons.lockOpen, + size: 15, + color: Colors.white, + ), + fuc: () => _.onLockControl(!_.controlsLock.value), ), - fuc: () => _.onLockControl(!_.controlsLock.value), ), ), ), ), ), - ), - // - Obx(() { - if (_.dataStatus.loading || _.isBuffering.value) { - return Center( - child: Container( - padding: const EdgeInsets.all(30), - decoration: const BoxDecoration( - shape: BoxShape.circle, - gradient: RadialGradient( - colors: [Colors.black26, Colors.transparent], + // + Obx(() { + if (_.dataStatus.loading || _.isBuffering.value) { + return Center( + child: Container( + padding: const EdgeInsets.all(30), + decoration: const BoxDecoration( + shape: BoxShape.circle, + gradient: RadialGradient( + colors: [Colors.black26, Colors.transparent], + ), + ), + child: Lottie.asset( + 'assets/loading.json', + width: 200, ), ), - child: Lottie.asset( - 'assets/loading.json', - width: 200, - ), - ), - ); - } else { - return const SizedBox(); - } - }), + ); + } else { + return const SizedBox(); + } + }), - /// 快进/快退面板 - SeekPanel( - mountSeekBackwardButton: _mountSeekBackwardButton, - mountSeekForwardButton: _mountSeekForwardButton, - hideSeekBackwardButton: _hideSeekBackwardButton, - hideSeekForwardButton: _hideSeekForwardButton, - onSubmittedcb: _handleSubmittedCallback, - ), - ], + /// 快进/快退面板 + SeekPanel( + mountSeekBackwardButton: _mountSeekBackwardButton, + mountSeekForwardButton: _mountSeekForwardButton, + hideSeekBackwardButton: _hideSeekBackwardButton, + hideSeekForwardButton: _hideSeekForwardButton, + onSubmittedcb: _handleSubmittedCallback, + ), + ], + ), ); } } diff --git a/lib/plugin/pl_player/widgets/bottom_control.dart b/lib/plugin/pl_player/widgets/bottom_control.dart index 3c21c2af..67652fff 100644 --- a/lib/plugin/pl_player/widgets/bottom_control.dart +++ b/lib/plugin/pl_player/widgets/bottom_control.dart @@ -18,19 +18,18 @@ class BottomControl extends StatelessWidget implements PreferredSizeWidget { @override Widget build(BuildContext context) { - return Container( - color: Colors.transparent, - height: 90, + return Padding( padding: const EdgeInsets.symmetric(horizontal: 18), child: Column( mainAxisAlignment: MainAxisAlignment.end, + mainAxisSize: MainAxisSize.min, children: [ Padding( - padding: const EdgeInsets.fromLTRB(7, 0, 7, 6), + padding: const EdgeInsets.fromLTRB(7, 0, 7, 4), child: ProgressBarWidget(controller: controller!), ), Row(children: buildBottomControl!), - const SizedBox(height: 10), + const SizedBox(height: 6), ], ), ); From b3674be2e5422aa2826185fe35f87fdc7996e89c Mon Sep 17 00:00:00 2001 From: guozhigq Date: Sat, 7 Dec 2024 22:00:50 +0800 Subject: [PATCH 39/43] opt: whisper exception handle --- lib/pages/whisper/controller.dart | 58 ++++++++++++++++++------------- lib/pages/whisper/view.dart | 19 +++++++++- 2 files changed, 51 insertions(+), 26 deletions(-) diff --git a/lib/pages/whisper/controller.dart b/lib/pages/whisper/controller.dart index b4deb014..c2c790cb 100644 --- a/lib/pages/whisper/controller.dart +++ b/lib/pages/whisper/controller.dart @@ -45,36 +45,44 @@ class WhisperController extends GetxController { if (isLoading) return; var res = await MsgHttp.sessionList( endTs: type == 'onLoad' ? sessionList.last.sessionTs : null); - if (res['status'] && - res['data'].sessionList != null && - res['data'].sessionList.isNotEmpty) { - await queryAccountList(res['data'].sessionList); - // 将 accountList 转换为 Map 结构 - Map accountMap = {}; - for (var j in accountList) { - accountMap[j.mid!] = j; - } + try { + if (res['status'] && + res['data'].sessionList != null && + res['data'].sessionList.isNotEmpty) { + await queryAccountList(res['data'].sessionList); + // 将 accountList 转换为 Map 结构 + Map accountMap = {}; + for (var j in accountList) { + accountMap[j.mid!] = j; + } - // 遍历 sessionList,通过 mid 查找并赋值 accountInfo - for (var i in res['data'].sessionList) { - var accountInfo = accountMap[i.talkerId]; - if (accountInfo != null) { - i.accountInfo = accountInfo; + // 遍历 sessionList,通过 mid 查找并赋值 accountInfo + for (var i in res['data'].sessionList) { + var accountInfo = accountMap[i.talkerId]; + if (accountInfo != null) { + i.accountInfo = accountInfo; + } + if (i.talkerId == 844424930131966) { + i.accountInfo = AccountListModel( + name: 'UP主小助手', + face: + 'https://message.biliimg.com/bfs/im/489a63efadfb202366c2f88853d2217b5ddc7a13.png', + ); + } } - if (i.talkerId == 844424930131966) { - i.accountInfo = AccountListModel( - name: 'UP主小助手', - face: - 'https://message.biliimg.com/bfs/im/489a63efadfb202366c2f88853d2217b5ddc7a13.png', - ); + if (type == 'onLoad') { + sessionList.addAll(res['data'].sessionList); + } else { + sessionList.value = res['data'].sessionList; } } - if (type == 'onLoad') { - sessionList.addAll(res['data'].sessionList); - } else { - sessionList.value = res['data'].sessionList; - } + } catch (err) { + res = { + 'status': false, + 'message': err.toString(), + }; } + isLoading = false; return res; } diff --git a/lib/pages/whisper/view.dart b/lib/pages/whisper/view.dart index 75e6e1c2..ee8a8f66 100644 --- a/lib/pages/whisper/view.dart +++ b/lib/pages/whisper/view.dart @@ -44,7 +44,24 @@ class _WhisperPageState extends State { @override Widget build(BuildContext context) { return Scaffold( - appBar: AppBar(title: const Text('消息')), + appBar: AppBar( + title: const Text('消息'), + actions: [ + IconButton( + icon: Icon(Icons.open_in_browser_rounded, + color: Theme.of(context).colorScheme.primary), + tooltip: '用浏览器打开', + onPressed: () { + Get.toNamed('/webview', parameters: { + 'url': 'https://message.bilibili.com', + 'type': 'whisper', + 'pageTitle': '消息中心', + }); + }, + ), + const SizedBox(width: 12) + ], + ), body: RefreshIndicator( onRefresh: () async { _whisperController.unread(); From f7fda95696ac919782d3a8f03f77c69e4e3cf7ea Mon Sep 17 00:00:00 2001 From: guozhigq Date: Sun, 8 Dec 2024 14:22:08 +0800 Subject: [PATCH 40/43] opt: relation modify --- .../widgets/group_panel.dart | 5 +- lib/pages/follow/widgets/follow_item.dart | 27 +-- lib/pages/member/controller.dart | 95 ++------ .../video/detail/introduction/controller.dart | 162 +++----------- lib/pages/video/detail/introduction/view.dart | 9 +- lib/utils/follow.dart | 207 ++++++++++++++++++ 6 files changed, 276 insertions(+), 229 deletions(-) rename lib/{pages/video/detail/introduction => common}/widgets/group_panel.dart (96%) create mode 100644 lib/utils/follow.dart diff --git a/lib/pages/video/detail/introduction/widgets/group_panel.dart b/lib/common/widgets/group_panel.dart similarity index 96% rename from lib/pages/video/detail/introduction/widgets/group_panel.dart rename to lib/common/widgets/group_panel.dart index 89b0c9a8..932dec6f 100644 --- a/lib/pages/video/detail/introduction/widgets/group_panel.dart +++ b/lib/common/widgets/group_panel.dart @@ -1,12 +1,10 @@ import 'package:flutter/material.dart'; import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; import 'package:get/get.dart'; -import 'package:hive/hive.dart'; import 'package:pilipala/common/widgets/http_error.dart'; import 'package:pilipala/http/member.dart'; import 'package:pilipala/models/member/tags.dart'; import 'package:pilipala/utils/feed_back.dart'; -import 'package:pilipala/utils/storage.dart'; class GroupPanel extends StatefulWidget { final int? mid; @@ -18,7 +16,6 @@ class GroupPanel extends StatefulWidget { } class _GroupPanelState extends State { - final Box localCache = GStorage.localCache; late Future _futureBuilderFuture; late List tagsList; bool showDefault = true; @@ -137,7 +134,7 @@ class _GroupPanelState extends State { left: 20, right: 20, top: 12, - bottom: MediaQuery.of(context).padding.bottom + 12, + bottom: MediaQuery.paddingOf(context).bottom + 12, ), child: Row( mainAxisAlignment: MainAxisAlignment.end, diff --git a/lib/pages/follow/widgets/follow_item.dart b/lib/pages/follow/widgets/follow_item.dart index f26cec06..c8fdcbad 100644 --- a/lib/pages/follow/widgets/follow_item.dart +++ b/lib/pages/follow/widgets/follow_item.dart @@ -3,8 +3,8 @@ import 'package:get/get.dart'; import 'package:pilipala/common/widgets/network_img_layer.dart'; import 'package:pilipala/models/follow/result.dart'; import 'package:pilipala/pages/follow/index.dart'; -import 'package:pilipala/pages/video/detail/introduction/widgets/group_panel.dart'; import 'package:pilipala/utils/feed_back.dart'; +import 'package:pilipala/utils/follow.dart'; import 'package:pilipala/utils/utils.dart'; class FollowItem extends StatelessWidget { @@ -47,28 +47,11 @@ class FollowItem extends StatelessWidget { height: 34, child: TextButton( onPressed: () async { - await showModalBottomSheet( + int followStatus = await FollowUtils( context: context, - useSafeArea: true, - isScrollControlled: true, - builder: (BuildContext context) { - return DraggableScrollableSheet( - initialChildSize: 0.6, - minChildSize: 0, - maxChildSize: 1, - snap: true, - expand: false, - snapSizes: const [0.6], - builder: (BuildContext context, - ScrollController scrollController) { - return GroupPanel( - mid: item.mid!, - scrollController: scrollController, - ); - }, - ); - }, - ); + followStatus: 2, + mid: item.mid!, + ).showFollowSheet(); }, style: TextButton.styleFrom( padding: const EdgeInsets.fromLTRB(15, 0, 15, 0), diff --git a/lib/pages/member/controller.dart b/lib/pages/member/controller.dart index 3c63d560..bb5be0f1 100644 --- a/lib/pages/member/controller.dart +++ b/lib/pages/member/controller.dart @@ -1,15 +1,14 @@ -import 'package:flutter/material.dart'; import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; import 'package:get/get.dart'; import 'package:hive/hive.dart'; import 'package:pilipala/http/member.dart'; import 'package:pilipala/http/user.dart'; -import 'package:pilipala/http/video.dart'; import 'package:pilipala/models/member/archive.dart'; import 'package:pilipala/models/member/coin.dart'; import 'package:pilipala/models/member/info.dart'; import 'package:pilipala/models/member/like.dart'; import 'package:pilipala/models/user/info.dart'; +import 'package:pilipala/utils/follow.dart'; import 'package:pilipala/utils/storage.dart'; import 'package:share_plus/share_plus.dart'; @@ -29,6 +28,12 @@ class MemberController extends GetxController { RxList recentCoinsList = [].obs; RxList recentLikeList = [].obs; RxBool isOwner = false.obs; + final Map attributeTextMap = { + 1: '悄悄关注', + 2: '已关注', + 6: '已互粉', + 128: '已拉黑', + }; @override void onInit() { @@ -85,11 +90,12 @@ class MemberController extends GetxController { SmartDialog.showToast('账号未登录'); return; } - if (attribute.value == 128) { - modifyRelation('block'); - } else { - modifyRelation('follow'); - } + attribute.value = await FollowUtils( + context: Get.context!, + followStatus: attribute.value, + mid: mid, + ).showFollowSheet(); + attributeText.value = attributeTextMap[attribute.value] ?? '关注'; } // 关系查询 @@ -99,16 +105,10 @@ class MemberController extends GetxController { var res = await UserHttp.hasFollow(mid); if (res['status']) { attribute.value = res['data']['attribute']; - final Map attributeTextMap = { - 1: '悄悄关注', - 2: '已关注', - 6: '已互关', - 128: '已拉黑', - }; - attributeText.value = attributeTextMap[attribute.value] ?? '关注'; if (res['data']['special'] == 1) { attributeText.value = '特别关注'; } + attributeText.value = attributeTextMap[attribute.value] ?? '关注'; } } @@ -118,66 +118,15 @@ class MemberController extends GetxController { SmartDialog.showToast('账号未登录'); return; } - modifyRelation('block'); - } - -// 合并关注/取关和拉黑逻辑 - Future modifyRelation(String actionType) async { - String contentText; - int act; - if (actionType == 'follow') { - contentText = memberInfo.value.isFollowed! ? '确定取消关注UP主?' : '确定关注UP主?'; - act = memberInfo.value.isFollowed! ? 2 : 1; - } else if (actionType == 'block') { - contentText = attribute.value != 128 ? '确定拉黑UP主?' : '确定从黑名单移除UP主?'; - act = attribute.value != 128 ? 5 : 6; - } else { - return; - } - - showDialog( + final String actionType = attribute.value == 128 ? 'remove' : 'block'; + attribute.value = await FollowUtils( context: Get.context!, - builder: (BuildContext context) { - return AlertDialog( - title: const Text('提示'), - content: Text(contentText), - actions: [ - TextButton( - onPressed: () => Navigator.of(context).pop(), - child: Text( - '点错了', - style: TextStyle(color: Theme.of(context).colorScheme.outline), - ), - ), - TextButton( - onPressed: () async { - var res = await VideoHttp.relationMod( - mid: mid, - act: act, - reSrc: 11, - ); - SmartDialog.dismiss(); - if (res['status']) { - if (actionType == 'follow') { - memberInfo.value.isFollowed = !memberInfo.value.isFollowed!; - } else if (actionType == 'block') { - attribute.value = attribute.value != 128 ? 128 : 0; - attributeText.value = attribute.value == 128 ? '已拉黑' : '关注'; - memberInfo.value.isFollowed = false; - } - relationSearch(); - if (context.mounted) { - Navigator.of(context).pop(); - } - memberInfo.update((val) {}); - } - }, - child: const Text('确定'), - ) - ], - ); - }, - ); + followStatus: attribute.value, + mid: mid, + ).modifyRelationFetch(actionType, isDirect: true); + if (attribute.value != -1) { + attributeText.value = attributeTextMap[attribute.value] ?? '关注'; + } } void shareUser() { diff --git a/lib/pages/video/detail/introduction/controller.dart b/lib/pages/video/detail/introduction/controller.dart index 5c84b3bc..6fe4af5b 100644 --- a/lib/pages/video/detail/introduction/controller.dart +++ b/lib/pages/video/detail/introduction/controller.dart @@ -17,6 +17,7 @@ import 'package:pilipala/pages/video/detail/controller.dart'; import 'package:pilipala/pages/video/detail/reply/index.dart'; import 'package:pilipala/plugin/pl_player/models/play_repeat.dart'; import 'package:pilipala/utils/feed_back.dart'; +import 'package:pilipala/utils/follow.dart'; import 'package:pilipala/utils/global_data_cache.dart'; import 'package:pilipala/utils/id_utils.dart'; import 'package:pilipala/utils/storage.dart'; @@ -26,7 +27,6 @@ import '../../../../common/pages_bottom_sheet.dart'; import '../../../../models/common/video_episode_type.dart'; import '../../../../utils/drawer.dart'; import '../related/index.dart'; -import 'widgets/group_panel.dart'; class VideoIntroController extends GetxController { VideoIntroController({required this.bvid}); @@ -50,7 +50,7 @@ class VideoIntroController extends GetxController { List addMediaIdsNew = []; List delMediaIdsNew = []; // 关注状态 默认未关注 - RxMap followStatus = {}.obs; + RxInt followStatus = (-1).obs; RxInt lastPlayCid = 0.obs; UserInfoData? userInfo; RxList videoTags = [].obs; @@ -337,113 +337,23 @@ class VideoIntroController extends GetxController { } var result = await VideoHttp.hasFollow(mid: videoDetail.value.owner!.mid!); if (result['status']) { - followStatus.value = result['data']; + followStatus.value = result['data']['attribute']; } return result; } // 关注/取关up - Future actionRelationMod() async { + Future actionRelationMod(BuildContext context) async { feedBack(); if (userInfo == null) { SmartDialog.showToast('账号未登录'); return; } - final int currentStatus = followStatus['attribute']; - if (currentStatus == 128) { - modifyRelation('block', currentStatus); - } else { - modifyRelation('follow', currentStatus); - } - } - - // 操作用户关系 - Future modifyRelation(String actionType, int currentStatus) async { - final int mid = videoDetail.value.owner!.mid!; - String contentText; - int act; - if (actionType == 'follow') { - contentText = currentStatus != 0 ? '确定取消关注UP主?' : '确定关注UP主?'; - act = currentStatus != 0 ? 2 : 1; - } else if (actionType == 'block') { - contentText = '确定从黑名单移除UP主?'; - act = 6; - } else { - return; - } - - showDialog( - context: Get.context!, - builder: (BuildContext context) { - final Color outline = Theme.of(Get.context!).colorScheme.outline; - return AlertDialog( - title: const Text('提示'), - content: Text(contentText), - actions: [ - TextButton( - onPressed: Navigator.of(context).pop, - child: Text('点错了', style: TextStyle(color: outline)), - ), - TextButton( - onPressed: () => modifyRelationFetch( - context, - mid, - act, - currentStatus, - actionType, - ), - child: const Text('确定'), - ) - ], - ); - }, - ); - } - - // 操作用户关系Future - Future modifyRelationFetch( - BuildContext context, - mid, - act, - currentStatus, - actionType, - ) async { - var res = await VideoHttp.relationMod(mid: mid, act: act, reSrc: 11); - if (context.mounted) { - Navigator.of(context).pop(); - } - if (res['status']) { - if (actionType == 'follow') { - final Map statusMap = { - 0: 2, - 2: 0, - }; - late int actionStatus; - actionStatus = statusMap[currentStatus] ?? 0; - followStatus['attribute'] = actionStatus; - if (currentStatus == 0 && Get.context!.mounted) { - ScaffoldMessenger.of(Get.context!).showSnackBar( - SnackBar( - content: const Text('关注成功'), - duration: const Duration(seconds: 2), - action: SnackBarAction( - label: '设置分组', - onPressed: setFollowGroup, - ), - showCloseIcon: true, - ), - ); - } else { - SmartDialog.showToast('取消关注成功'); - } - } else if (actionType == 'block') { - followStatus['attribute'] = 0; - SmartDialog.showToast('取消拉黑成功'); - } - followStatus.refresh(); - } else { - SmartDialog.showToast(res['msg']); - } + followStatus.value = await FollowUtils( + context: context, + followStatus: followStatus.value, + mid: videoDetail.value.owner!.mid!, + ).showFollowSheet(); } // 修改分P或番剧分集 @@ -568,33 +478,33 @@ class VideoIntroController extends GetxController { } // 设置关注分组 - void setFollowGroup() async { - final mediaQueryData = MediaQuery.of(Get.context!); - final contentHeight = mediaQueryData.size.height - kToolbarHeight; - final double initialChildSize = - (contentHeight - Get.width * 9 / 16) / contentHeight; - await showModalBottomSheet( - context: Get.context!, - useSafeArea: true, - isScrollControlled: true, - builder: (BuildContext context) { - return DraggableScrollableSheet( - initialChildSize: initialChildSize, - minChildSize: 0, - maxChildSize: 1, - snap: true, - expand: false, - snapSizes: [initialChildSize], - builder: (BuildContext context, ScrollController scrollController) { - return GroupPanel( - mid: videoDetail.value.owner!.mid!, - scrollController: scrollController, - ); - }, - ); - }, - ); - } + // void setFollowGroup() async { + // final mediaQueryData = MediaQuery.of(Get.context!); + // final contentHeight = mediaQueryData.size.height - kToolbarHeight; + // final double initialChildSize = + // (contentHeight - Get.width * 9 / 16) / contentHeight; + // await showModalBottomSheet( + // context: Get.context!, + // useSafeArea: true, + // isScrollControlled: true, + // builder: (BuildContext context) { + // return DraggableScrollableSheet( + // initialChildSize: initialChildSize, + // minChildSize: 0, + // maxChildSize: 1, + // snap: true, + // expand: false, + // snapSizes: [initialChildSize], + // builder: (BuildContext context, ScrollController scrollController) { + // return GroupPanel( + // mid: videoDetail.value.owner!.mid!, + // scrollController: scrollController, + // ); + // }, + // ); + // }, + // ); + // } // ai总结 Future aiConclusion() async { diff --git a/lib/pages/video/detail/introduction/view.dart b/lib/pages/video/detail/introduction/view.dart index e35b1ee3..aac1d657 100644 --- a/lib/pages/video/detail/introduction/view.dart +++ b/lib/pages/video/detail/introduction/view.dart @@ -18,6 +18,7 @@ import 'package:pilipala/models/video_detail_res.dart'; import 'package:pilipala/pages/video/detail/introduction/controller.dart'; import 'package:pilipala/pages/video/detail/widgets/ai_detail.dart'; import 'package:pilipala/utils/feed_back.dart'; +import 'package:pilipala/utils/follow.dart'; import 'package:pilipala/utils/global_data_cache.dart'; import 'package:pilipala/utils/storage.dart'; import 'package:pilipala/utils/utils.dart'; @@ -458,14 +459,14 @@ class _VideoInfoState extends State with TickerProviderStateMixin { Obx( () { final int attr = - videoIntroController.followStatus['attribute'] ?? 0; - return videoIntroController.followStatus.isEmpty + videoIntroController.followStatus.value; + return attr == -1 ? const SizedBox() : SizedBox( height: 32, child: TextButton( - onPressed: - videoIntroController.actionRelationMod, + onPressed: () => videoIntroController + .actionRelationMod(context), style: TextButton.styleFrom( padding: const EdgeInsets.only( left: 8, diff --git a/lib/utils/follow.dart b/lib/utils/follow.dart new file mode 100644 index 00000000..c081705a --- /dev/null +++ b/lib/utils/follow.dart @@ -0,0 +1,207 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; +import 'package:pilipala/common/widgets/drag_handle.dart'; +import 'package:pilipala/common/widgets/group_panel.dart'; +import 'package:pilipala/http/video.dart'; +import 'package:pilipala/utils/global_data_cache.dart'; + +class FollowUtils { + final BuildContext context; + final int followStatus; + final int mid; + + FollowUtils({ + required this.context, + required this.followStatus, + required this.mid, + }); + + // static final Map> followMap = { + // // 未关注 + // 0: { + // 'desc': '确定关注UP主?', + // // 1 关注 5 拉黑 + // 'act': 1, + // }, + // // 已关注 + // 2: { + // 'desc': '确定取消关注UP主?', + // 'act': 2, + // }, + // // 已互粉 + // 6: { + // 'desc': '确定取消关注UP主?', + // 'act': 2, + // }, + // // 已拉黑 + // 128: { + // 'desc': '确定从黑名单移除UP主?', + // 'act': 6, + // }, + // }; + + static final Map> actionTypeMap = { + 'remove': { + 'desc': '确定从黑名单移除UP主?', + 'tips': '已从黑名单移除', + 'act': 6, + 'followStatus': 0, + }, + 'unFollow': { + 'desc': '确定取消关注UP主?', + 'tips': '已取消关注', + 'act': 2, + 'followStatus': 0, + }, + 'follow': { + 'desc': '确定关注UP主?', + 'tips': '关注成功', + 'act': 1, + 'followStatus': 2, + }, + 'block': { + 'desc': '确定拉黑UP主?', + 'tips': '已拉黑', + 'act': 5, + 'followStatus': 128, + }, + }; + + Future showFollowSheet() async { + var res = await showModalBottomSheet( + context: context, + isScrollControlled: true, + builder: (context) { + return Padding( + padding: + EdgeInsets.only(bottom: MediaQuery.paddingOf(context).bottom), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + const DragHandle(), + if (followStatus == 128) ...[ + ListTile( + leading: const Icon(Icons.remove_circle_outline_rounded), + onTap: () => modifyRelation('remove'), + title: const Text('从黑名单移除'), + ), + ], + if ([2, 6].contains(followStatus)) ...[ + ListTile( + leading: const Icon(Icons.group_add_outlined), + onTap: () { + Navigator.of(context).pop(); + setFollowGroup(); + }, + title: const Text('设置分组'), + ), + ListTile( + leading: const Icon(Icons.heart_broken_outlined), + onTap: () => modifyRelation('unFollow'), + title: const Text('取消关注'), + ), + ], + if (followStatus == 0) ...[ + ListTile( + leading: const Icon(Icons.favorite_border_rounded), + onTap: () => modifyRelation('follow'), + title: const Text('关注up主'), + ), + ListTile( + leading: const Icon(Icons.block_rounded), + onTap: () => modifyRelation('block'), + title: const Text('拉黑up主'), + ), + ], + ], + ), + ); + }, + ); + return res ?? followStatus; + } + + // 操作用户关系 + Future modifyRelation(String actionType) async { + showDialog( + context: context, + builder: (BuildContext context) { + final Color outline = Theme.of(context).colorScheme.outline; + return AlertDialog( + title: const Text('提示'), + content: Text(actionTypeMap[actionType]!['desc']), + actions: [ + TextButton( + onPressed: Navigator.of(context).pop, + child: Text('点错了', style: TextStyle(color: outline)), + ), + TextButton( + onPressed: () => modifyRelationFetch(actionType), + child: const Text('确定'), + ) + ], + ); + }, + ); + } + + // 操作用户关系Future + Future modifyRelationFetch(String actionType, {bool? isDirect}) async { + if (isDirect != true) { + Navigator.of(context).pop(); + } + + SmartDialog.showLoading(msg: '请求中'); + var res = await VideoHttp.relationMod( + mid: mid, + act: actionTypeMap[actionType]!['act'], + reSrc: 11, + ); + SmartDialog.dismiss(); + if (res['status']) { + final int newFollowStatus = actionTypeMap[actionType]!['followStatus']; + SmartDialog.showToast(actionTypeMap[actionType]!['tips']); + if (context.mounted) { + if (isDirect != true) { + Navigator.of(context).pop(newFollowStatus); + } + } + return newFollowStatus; + } else { + SmartDialog.showToast(res['msg']); + if (context.mounted && isDirect != true) { + Navigator.of(context).pop(-1); + } + return -1; + } + } + + // 设置分组 + Future setFollowGroup() async { + final size = MediaQuery.sizeOf(context); + final contentHeight = size.height - kToolbarHeight; + final double initialChildSize = + (contentHeight - size.width * 9 / 16) / contentHeight; + await showModalBottomSheet( + context: context, + useSafeArea: true, + isScrollControlled: true, + builder: (BuildContext context) { + return DraggableScrollableSheet( + initialChildSize: initialChildSize, + minChildSize: 0, + maxChildSize: 1, + snap: true, + expand: false, + snapSizes: [initialChildSize], + builder: (BuildContext context, ScrollController scrollController) { + return GroupPanel( + mid: GlobalDataCache.userInfo!.mid!, + scrollController: scrollController, + ); + }, + ); + }, + ); + } +} From 3fca7938dd2a89fe3f3a1c11d48bf41b8bca6dc7 Mon Sep 17 00:00:00 2001 From: guozhigq Date: Sun, 8 Dec 2024 17:57:58 +0800 Subject: [PATCH 41/43] opt: SystemNotice --- .../whisper_detail/widget/chat_item.dart | 36 +++++++++++++++---- 1 file changed, 30 insertions(+), 6 deletions(-) diff --git a/lib/pages/whisper_detail/widget/chat_item.dart b/lib/pages/whisper_detail/widget/chat_item.dart index 973dca5e..64e16c9f 100644 --- a/lib/pages/whisper_detail/widget/chat_item.dart +++ b/lib/pages/whisper_detail/widget/chat_item.dart @@ -521,6 +521,7 @@ class SystemNotice extends StatelessWidget { @override Widget build(BuildContext context) { Map content = item.content ?? ''; + Color primary = Theme.of(context).colorScheme.primary; return Row( children: [ const SizedBox(width: 12), @@ -557,12 +558,35 @@ class SystemNotice extends StatelessWidget { .labelSmall! .copyWith(color: Theme.of(context).colorScheme.outline), ), - Divider( - color: Theme.of(context).colorScheme.primary.withOpacity(0.05), - ), - SelectableText( - content['text'], - ) + Divider(color: primary.withOpacity(0.05)), + SelectableText(content['text']), + if (content['jump_text'] != null && + content['jump_uri'] != null) ...[ + Divider(color: primary.withOpacity(0.05)), + Align( + alignment: Alignment.center, + child: TextButton( + onPressed: () { + Get.toNamed('/webview', parameters: { + 'url': content['jump_uri'], + 'type': 'url', + 'pageTitle': content['jump_text'] == '' + ? '查看详情' + : content['jump_text'], + }); + }, + style: ButtonStyle( + backgroundColor: + MaterialStateProperty.resolveWith((states) { + return primary.withAlpha(20); + }), + ), + child: Text(content['jump_text'] == '' + ? '查看详情' + : content['jump_text']), + ), + ), + ] ], ), ), From d91d4ab62e1a78e33eba987ae773f5b4262d9cbd Mon Sep 17 00:00:00 2001 From: guozhigq Date: Sun, 8 Dec 2024 20:48:11 +0800 Subject: [PATCH 42/43] fix: forward dynamics reply --- lib/http/api.dart | 3 +++ lib/http/dynamics.dart | 21 +++++++++++++++++++++ lib/pages/dynamics/detail/controller.dart | 18 +++++++++++------- lib/pages/dynamics/detail/view.dart | 3 +-- 4 files changed, 36 insertions(+), 9 deletions(-) diff --git a/lib/http/api.dart b/lib/http/api.dart index 379540a5..04847cab 100644 --- a/lib/http/api.dart +++ b/lib/http/api.dart @@ -626,4 +626,7 @@ class Api { /// 修复标题和海报 // /api/view?id=${aid} /all/video/av${aid} /video/av${aid}/ static const String fixTitleAndPic = '${HttpString.biliplusBaseUrl}/api/view'; + + /// 专栏详情 + static const String opusDetail = '/x/polymer/web-dynamic/v1/opus/detail'; } diff --git a/lib/http/dynamics.dart b/lib/http/dynamics.dart index 53ba6fc1..74b9b566 100644 --- a/lib/http/dynamics.dart +++ b/lib/http/dynamics.dart @@ -215,4 +215,25 @@ class DynamicsHttp { }; } } + + static Future opusDetail({ + required int opusId, + }) async { + var res = await Request().get( + Api.opusDetail, + data: {'id': opusId}, + ); + if (res.data['code'] == 0) { + return { + 'status': true, + 'data': res.data['data'], + }; + } else { + return { + 'status': false, + 'data': [], + 'msg': res.data['message'], + }; + } + } } diff --git a/lib/pages/dynamics/detail/controller.dart b/lib/pages/dynamics/detail/controller.dart index a5f04bbe..ac68565d 100644 --- a/lib/pages/dynamics/detail/controller.dart +++ b/lib/pages/dynamics/detail/controller.dart @@ -1,7 +1,7 @@ import 'package:flutter/material.dart'; import 'package:get/get.dart'; import 'package:hive/hive.dart'; -import 'package:pilipala/http/html.dart'; +import 'package:pilipala/http/dynamics.dart'; import 'package:pilipala/http/reply.dart'; import 'package:pilipala/models/common/reply_sort_type.dart'; import 'package:pilipala/models/video/reply/item.dart'; @@ -12,6 +12,7 @@ class DynamicDetailController extends GetxController { DynamicDetailController(this.oid, this.type); int? oid; int? type; + int? opusId; dynamic item; int? floor; String nextOffset = ""; @@ -56,6 +57,12 @@ class DynamicDetailController extends GetxController { if (reqType == 'init') { nextOffset = ''; noMore.value = ''; + if (opusId != null && oid == 0) { + var res = await DynamicsHttp.opusDetail(opusId: opusId!); + if (res['status']) { + oid = int.parse(res['data']['item']['basic']['comment_id_str']); + } + } } var res = await ReplyHttp.replyList( oid: oid!, @@ -110,15 +117,12 @@ class DynamicDetailController extends GetxController { sortTypeTitle.value = _sortType.titles; sortTypeLabel.value = _sortType.labels; replyList.clear(); + noMore.value = ''; + isLoadingMore = false; + isEnd = false; queryReplyList(reqType: 'init'); } - // 根据jumpUrl获取动态html - reqHtmlByOpusId(int id) async { - var res = await HtmlHttp.reqHtml(id, 'opus'); - oid = res['commentId']; - } - // 上拉加载 Future onLoad() async { queryReplyList(reqType: 'onLoad'); diff --git a/lib/pages/dynamics/detail/view.dart b/lib/pages/dynamics/detail/view.dart index 5c8f85e3..cd180fbe 100644 --- a/lib/pages/dynamics/detail/view.dart +++ b/lib/pages/dynamics/detail/view.dart @@ -89,9 +89,8 @@ class _DynamicDetailPageState extends State _dynamicDetailController = Get.put( DynamicDetailController(oid, replyType), tag: opusId.toString()); + _dynamicDetailController.opusId = opusId; _futureBuilderFuture = _dynamicDetailController.queryReplyList(); - await _dynamicDetailController.reqHtmlByOpusId(opusId!); - setState(() {}); } } else { oid = moduleDynamic.major!.draw!.id!; From ef7d84409d5c23007e53f1eef1ec7ec0c2101a77 Mon Sep 17 00:00:00 2001 From: guozhigq Date: Sun, 8 Dec 2024 21:03:18 +0800 Subject: [PATCH 43/43] fix: repeated reply logic --- lib/pages/video/detail/reply/controller.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/pages/video/detail/reply/controller.dart b/lib/pages/video/detail/reply/controller.dart index 38d59617..9c4855c5 100644 --- a/lib/pages/video/detail/reply/controller.dart +++ b/lib/pages/video/detail/reply/controller.dart @@ -73,7 +73,7 @@ class VideoReplyController extends GetxController { /// 临时修复 final bool flag = replyList .any((ReplyItemModel reply) => reply.rpid == replies.first.rpid); - if (replies.length == 1 && flag) { + if (replies.length == 1 && flag && type == 'onLoad') { replies.clear(); isEnd = true; }