From a4413b952088ccfd7bdcfa77e012eb72c230bd42 Mon Sep 17 00:00:00 2001 From: guozhigq Date: Fri, 15 Nov 2024 23:34:42 +0800 Subject: [PATCH 01/15] =?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 8c8c8620952af22b6ac4c464bba187169eefc528 Mon Sep 17 00:00:00 2001 From: guozhigq Date: Mon, 18 Nov 2024 19:32:15 +0800 Subject: [PATCH 02/15] 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 5693c9bedab0bd91faa3cc2d5ca54669d90d3c67 Mon Sep 17 00:00:00 2001 From: guozhigq Date: Tue, 3 Dec 2024 23:07:44 +0800 Subject: [PATCH 03/15] 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 7a1d5404086c696737f5a1d14cb82977e105e471 Mon Sep 17 00:00:00 2001 From: guozhigq Date: Tue, 3 Dec 2024 23:29:41 +0800 Subject: [PATCH 04/15] =?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 05/15] 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 06/15] 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 07/15] 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 08/15] 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 09/15] 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 10/15] 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 11/15] 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 12/15] 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 13/15] 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 14/15] 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 15/15] 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']), + ), + ), + ] ], ), ),