diff --git a/lib/common/skeleton/video_card_h.dart b/lib/common/skeleton/video_card_h.dart index 656fbf81d..55dfa68ad 100644 --- a/lib/common/skeleton/video_card_h.dart +++ b/lib/common/skeleton/video_card_h.dart @@ -8,86 +8,79 @@ class VideoCardHSkeleton extends StatelessWidget { @override Widget build(BuildContext context) { return Skeleton( - child: Padding( - padding: const EdgeInsets.fromLTRB( - StyleString.safeSpace, 7, StyleString.safeSpace, 7), - child: LayoutBuilder( - builder: (context, boxConstraints) { - double width = - (boxConstraints.maxWidth - StyleString.cardSpace * 6) / 2; - return SizedBox( - height: width / StyleString.aspectRatio, - child: Row( - mainAxisAlignment: MainAxisAlignment.start, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - AspectRatio( - aspectRatio: StyleString.aspectRatio, - child: LayoutBuilder( - builder: (context, boxConstraints) { - return Container( - decoration: BoxDecoration( + child: LayoutBuilder( + builder: (context, boxConstraints) { + double width = + (boxConstraints.maxWidth - StyleString.cardSpace * 6) / 2; + return SizedBox( + height: width / StyleString.aspectRatio, + child: Row( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + AspectRatio( + aspectRatio: StyleString.aspectRatio, + child: LayoutBuilder( + builder: (context, boxConstraints) { + return Container( + decoration: BoxDecoration( + color: Theme.of(context).colorScheme.onInverseSurface, + borderRadius: + BorderRadius.circular(StyleString.imgRadius.x), + ), + ); + }, + ), + ), + // VideoContent(videoItem: videoItem) + Expanded( + child: Padding( + padding: const EdgeInsets.fromLTRB(10, 4, 6, 4), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Container( + color: Theme.of(context).colorScheme.onInverseSurface, + width: 200, + height: 11, + margin: const EdgeInsets.only(bottom: 5), + ), + Container( + color: Theme.of(context).colorScheme.onInverseSurface, + width: 150, + height: 13, + ), + const Spacer(), + Container( + color: Theme.of(context).colorScheme.onInverseSurface, + width: 100, + height: 13, + margin: const EdgeInsets.only(bottom: 5), + ), + Row( + children: [ + Container( + color: + Theme.of(context).colorScheme.onInverseSurface, + width: 40, + height: 13, + margin: const EdgeInsets.only(right: 8), + ), + Container( color: Theme.of(context).colorScheme.onInverseSurface, - borderRadius: - BorderRadius.circular(StyleString.imgRadius.x), + width: 40, + height: 13, ), - ); - }, - ), + ], + ) + ], ), - // VideoContent(videoItem: videoItem) - Expanded( - child: Padding( - padding: const EdgeInsets.fromLTRB(10, 4, 6, 4), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Container( - color: Theme.of(context).colorScheme.onInverseSurface, - width: 200, - height: 11, - margin: const EdgeInsets.only(bottom: 5), - ), - Container( - color: Theme.of(context).colorScheme.onInverseSurface, - width: 150, - height: 13, - ), - const Spacer(), - Container( - color: Theme.of(context).colorScheme.onInverseSurface, - width: 100, - height: 13, - margin: const EdgeInsets.only(bottom: 5), - ), - Row( - children: [ - Container( - color: Theme.of(context) - .colorScheme - .onInverseSurface, - width: 40, - height: 13, - margin: const EdgeInsets.only(right: 8), - ), - Container( - color: Theme.of(context) - .colorScheme - .onInverseSurface, - width: 40, - height: 13, - ), - ], - ) - ], - ), - )), - ], - ), - ); - }, - ), + )), + ], + ), + ); + }, ), ); } diff --git a/lib/common/widgets/overlay_pop.dart b/lib/common/widgets/overlay_pop.dart index fd03c98d6..6b9c03e1b 100644 --- a/lib/common/widgets/overlay_pop.dart +++ b/lib/common/widgets/overlay_pop.dart @@ -1,4 +1,7 @@ +import 'dart:math'; + import 'package:flutter/material.dart'; +import 'package:get/get.dart'; import '../../utils/download.dart'; import '../constants.dart'; import 'network_img_layer.dart'; @@ -11,9 +14,10 @@ class OverlayPop extends StatelessWidget { @override Widget build(BuildContext context) { - final double imgWidth = MediaQuery.sizeOf(context).width - 8 * 2; + final double imgWidth = min(Get.height,Get.width) - 8 * 2; return Container( margin: const EdgeInsets.symmetric(horizontal: 8), + width: imgWidth, decoration: BoxDecoration( color: Theme.of(context).colorScheme.background, borderRadius: BorderRadius.circular(10.0), @@ -70,6 +74,7 @@ class OverlayPop extends StatelessWidget { tooltip: '保存封面图', onPressed: () async { await DownloadUtils.downloadImg( + context, videoItem.pic != null ? videoItem.pic as String : videoItem.cover as String, diff --git a/lib/common/widgets/video_card_h.dart b/lib/common/widgets/video_card_h.dart index 7f61e2ad3..868280bb2 100644 --- a/lib/common/widgets/video_card_h.dart +++ b/lib/common/widgets/video_card_h.dart @@ -72,88 +72,73 @@ class VideoCardH extends StatelessWidget { SmartDialog.showToast(err.toString()); } }, - child: Padding( - padding: const EdgeInsets.fromLTRB( - StyleString.safeSpace, 5, StyleString.safeSpace, 5), - child: LayoutBuilder( - builder: - (BuildContext context, BoxConstraints boxConstraints) { - final double width = (boxConstraints.maxWidth - - StyleString.cardSpace * - 6 / - MediaQuery.textScalerOf(context).scale(1.0)) / - 2; - return Container( - constraints: const BoxConstraints(minHeight: 88), - height: width / StyleString.aspectRatio, - child: Row( - mainAxisAlignment: MainAxisAlignment.start, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - AspectRatio( - aspectRatio: StyleString.aspectRatio, - child: LayoutBuilder( - builder: (BuildContext context, - BoxConstraints boxConstraints) { - final double maxWidth = boxConstraints.maxWidth; - final double maxHeight = - boxConstraints.maxHeight; - return Stack( - children: [ - Hero( - tag: heroTag, - child: NetworkImgLayer( - src: videoItem.pic as String, - width: maxWidth, - height: maxHeight, - ), - ), - if (videoItem.duration != 0) - PBadge( - text: Utils.timeFormat(videoItem.duration!), - right: 6.0, - bottom: 6.0, - type: 'gray', - ), - if (type != 'video') - PBadge( - text: type, - left: 6.0, - bottom: 6.0, - type: 'primary', - ), - // if (videoItem.rcmdReason != null && - // videoItem.rcmdReason.content != '') - // pBadge(videoItem.rcmdReason.content, context, - // 6.0, 6.0, null, null), - ], - ); - }, - ), - ), - VideoContent( - videoItem: videoItem, - source: source, - showOwner: showOwner, - showView: showView, - showDanmaku: showDanmaku, - showPubdate: showPubdate, - ) - ], + child: LayoutBuilder( + builder: (BuildContext context, BoxConstraints boxConstraints) { + return Row( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + AspectRatio( + aspectRatio: StyleString.aspectRatio, + child: LayoutBuilder( + builder: (BuildContext context, + BoxConstraints boxConstraints) { + final double maxWidth = boxConstraints.maxWidth; + final double maxHeight = boxConstraints.maxHeight; + return Stack( + children: [ + Hero( + tag: heroTag, + child: NetworkImgLayer( + src: videoItem.pic as String, + width: maxWidth, + height: maxHeight, + ), + ), + if (videoItem.duration != 0) + PBadge( + text: Utils.timeFormat(videoItem.duration!), + right: 6.0, + bottom: 6.0, + type: 'gray', + ), + if (type != 'video') + PBadge( + text: type, + left: 6.0, + bottom: 6.0, + type: 'primary', + ), + // if (videoItem.rcmdReason != null && + // videoItem.rcmdReason.content != '') + // pBadge(videoItem.rcmdReason.content, context, + // 6.0, 6.0, null, null), + ], + ); + }, + ), ), - ); - }, - ), + VideoContent( + videoItem: videoItem, + source: source, + showOwner: showOwner, + showView: showView, + showDanmaku: showDanmaku, + showPubdate: showPubdate, + ) + ], + ); + }, ), ), )), if (source == 'normal') Positioned( - bottom: 1, + bottom: 0, right: 10, child: VideoPopupMenu( - size: 30, - iconSize: 16, + size: 29, + iconSize: 17, videoItem: videoItem, ), ), @@ -182,6 +167,10 @@ class VideoContent extends StatelessWidget { @override Widget build(BuildContext context) { + String pubdate = showPubdate + ? Utils.dateFormat(videoItem.pubdate!, formatType: 'day') + : ''; + if (pubdate != '') pubdate += ' '; return Expanded( child: Padding( padding: const EdgeInsets.fromLTRB(10, 0, 6, 0), @@ -247,7 +236,7 @@ class VideoContent extends StatelessWidget { Expanded( flex: 0, child: Text( - "${showPubdate ? Utils.dateFormat(videoItem.pubdate!, formatType: 'day') + ' ' : ''} ${showOwner ? videoItem.owner.name : ''}", + "${pubdate}${showOwner ? videoItem.owner.name : ''}", maxLines: 1, style: TextStyle( fontSize: 11, @@ -276,7 +265,7 @@ class VideoContent extends StatelessWidget { if (source == 'normal') const SizedBox(width: 24), ], ), - const SizedBox(height: 2), + const SizedBox(height: 5), ], ), ), diff --git a/lib/common/widgets/video_card_v.dart b/lib/common/widgets/video_card_v.dart index e72a51e59..dd8951479 100644 --- a/lib/common/widgets/video_card_v.dart +++ b/lib/common/widgets/video_card_v.dart @@ -191,8 +191,8 @@ class VideoCardV extends StatelessWidget { right: 0, bottom: 0, child: VideoPopupMenu( - size: 30, - iconSize: 16, + size: 29, + iconSize: 17, videoItem: videoItem, )), ]); @@ -224,7 +224,8 @@ class VideoContent extends StatelessWidget { ), ], ), - const SizedBox(height: 2), + const Spacer(), + // const SizedBox(height: 2), VideoStat( videoItem: videoItem, ), diff --git a/lib/common/widgets/video_popup_menu.dart b/lib/common/widgets/video_popup_menu.dart index 707e5d1c4..a9a949fad 100644 --- a/lib/common/widgets/video_popup_menu.dart +++ b/lib/common/widgets/video_popup_menu.dart @@ -1,6 +1,7 @@ import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; +import 'package:get/get.dart'; import '../../http/user.dart'; import '../../http/video.dart'; @@ -11,7 +12,7 @@ class VideoPopupMenu extends StatelessWidget { final double? size; final double? iconSize; final dynamic videoItem; - final double menuItemHeight = 50; + final double menuItemHeight = 45; const VideoPopupMenu({ Key? key, @@ -53,10 +54,44 @@ class VideoPopupMenu extends StatelessWidget { ), PopupMenuItem( onTap: () async { - SmartDialog.show( - useSystem: true, - animationType: SmartAnimationType.centerFade_otherSlide, - builder: (BuildContext context) { + Get.toNamed('/member?mid=${videoItem.owner.mid}', arguments: { + 'face': videoItem.owner.face, + 'heroTag': '${videoItem.owner.mid}', + }); + }, + value: 'visit', + height: menuItemHeight, + child: Row( + children: [ + const Icon(CupertinoIcons.person, size: 16), + const SizedBox(width: 6), + Text('访问:${videoItem.owner.name}', + style: const TextStyle(fontSize: 13)) + ], + ), + ), + // 不感兴趣 + PopupMenuItem( + onTap: () async { + // var res = await VideoHttp.dislike(bvid: videoItem.bvid as String); + // SmartDialog.showToast(res['msg']); + SmartDialog.showToast("暂未实现"); + }, + value: 'dislike', + height: menuItemHeight, + child: const Row( + children: [ + Icon(CupertinoIcons.hand_thumbsdown, size: 16), + SizedBox(width: 6), + Text('不感兴趣', style: TextStyle(fontSize: 13)) + ], + ), + ), + PopupMenuItem( + onTap: () async { + await showDialog( + context: context, + builder: (context) { return AlertDialog( title: const Text('提示'), content: Text( @@ -64,7 +99,7 @@ class VideoPopupMenu extends StatelessWidget { '\n\n注:被拉黑的Up可以在隐私设置-黑名单管理中解除'), actions: [ TextButton( - onPressed: () => SmartDialog.dismiss(), + onPressed: () => Get.back(), child: Text( '点错了', style: TextStyle( @@ -86,7 +121,7 @@ class VideoPopupMenu extends StatelessWidget { blackMidsList.insert(0, videoItem.owner.mid); GStrorage.setting .put(SettingBoxKey.blackMidsList, blackMidsList); - SmartDialog.dismiss(); + Get.back(); SmartDialog.showToast(res['msg'] ?? '成功'); }, child: const Text('确认'), diff --git a/lib/http/api.dart b/lib/http/api.dart index f0307ae45..8cfeb4d62 100644 --- a/lib/http/api.dart +++ b/lib/http/api.dart @@ -42,6 +42,12 @@ class Api { // 视频点踩 web端不支持 + // 点踩 Post(app端) + /// access_key str APP登录Token 必要 + /// aid num 稿件avid 必要 + /// + static const String dislikeVideo = '${HttpString.appBaseUrl}/x/v2/view/dislike'; + // 投币视频(web端)POST /// aid num 稿件avid 必要(可选) avid与bvid任选一个 /// bvid str 稿件bvid 必要(可选) avid与bvid任选一个 @@ -334,10 +340,26 @@ class Api { static const String webDanmaku = '/x/v2/dm/web/seg.so'; - //发送视频弹幕 + // 发送视频弹幕 //https://github.com/SocialSisterYi/bilibili-API-collect/blob/master/docs/danmaku/action.md static const String shootDanmaku = '/x/v2/dm/post'; + // 弹幕屏蔽查询(Get) + static const String danmakuFilter = '/x/dm/filter/user'; + + // 弹幕屏蔽词添加(Post) + // 表单内容: + // type: 0(关键词)1(正则)2(用户) + // filter: 屏蔽内容 + // csrf + static const String danmakuFilterAdd = '/x/dm/filter/user/add'; + + // 弹幕屏蔽词删除(Post) + // 表单内容: + // ids: 被删除条目编号 + // csrf + static const String danmakuFilterDel = '/x/dm/filter/user/del'; + // up主分组 static const String followUpTag = '/x/relation/tags'; diff --git a/lib/http/danmaku_block.dart b/lib/http/danmaku_block.dart new file mode 100644 index 000000000..6ef1e493a --- /dev/null +++ b/lib/http/danmaku_block.dart @@ -0,0 +1,63 @@ +import '../models/user/danmaku_block.dart'; +import 'index.dart'; + +class DanmakuFilterHttp { + static Future danmakuFilter() async { + var res = await Request().get(Api.danmakuFilter); + if (res.data['code'] == 0) { + return { + 'status': true, + 'data': DanmakuBlockDataModel.fromJson(res.data['data']) + }; + } else { + return { + 'status': false, + 'data': [], + 'msg': res.data['message'], + }; + } + } + + static Future danmakuFilterDel({required int ids}) async { + var res = await Request().post( + Api.danmakuFilterDel, + queryParameters: { + 'ids': ids, + 'csrf': await Request.getCsrf(), + }, + ); + if (res.data['code'] == 0) { + return { + 'status': true, + 'msg': '操作成功', + }; + } else { + return { + 'status': false, + 'msg': res.data['message'], + }; + } + } + + static Future danmakuFilterAdd({required String filter, required int type}) async { + var res = await Request().post( + Api.danmakuFilterAdd, + queryParameters: { + 'type': type, + 'filter': filter, + 'csrf': await Request.getCsrf(), + }, + ); + if (res.data['code'] == 0) { + return { + 'status': true, + 'data': Rule.fromJson(res.data['data']), + }; + } else { + return { + 'status': false, + 'msg': res.data['message'], + }; + } + } +} diff --git a/lib/http/dynamics.dart b/lib/http/dynamics.dart index a81436b26..ac5effda8 100644 --- a/lib/http/dynamics.dart +++ b/lib/http/dynamics.dart @@ -5,15 +5,13 @@ import 'index.dart'; class DynamicsHttp { static Future followDynamic({ String? type, - int? page, String? offset, int? mid, }) async { Map data = { 'type': type ?? 'all', - 'page': page ?? 1, 'timezone_offset': '-480', - 'offset': page == 1 ? '' : offset, + 'offset': offset, 'features': 'itemOpusStyle' }; if (mid != -1) { diff --git a/lib/http/video.dart b/lib/http/video.dart index b83dbfe75..17187ec77 100644 --- a/lib/http/video.dart +++ b/lib/http/video.dart @@ -10,6 +10,7 @@ import '../models/user/fav_folder.dart'; import '../models/video/ai.dart'; import '../models/video/play/url.dart'; import '../models/video_detail_res.dart'; +import '../utils/id_utils.dart'; import '../utils/recommend_filter.dart'; import '../utils/storage.dart'; import '../utils/wbi_sign.dart'; @@ -319,6 +320,29 @@ class VideoHttp { } } + // (取消)点踩 + static Future dislikeVideo({required String bvid, required bool type}) async { + String? accessKey = GStrorage.localCache + .get(LocalCacheKey.accessKey, defaultValue: {})['value']; + if (accessKey == null || accessKey == "") { + return {'status': false, 'data': [], 'msg': "本操作使用app端接口,请前往【隐私设置】刷新access_key"}; + } + var res = await Request().post( + Api.dislikeVideo, + queryParameters: { + 'aid': IdUtils.bv2av(bvid), + 'dislike': type ? 0 : 1, + 'access_key': accessKey, + }, + ); + print(res); + if (res.data is! String && res.data['code'] == 0) { + return {'status': true, 'data': res.data['data']}; + } else { + return {'status': false, 'data': [], 'msg': res.data['message']}; + } + } + // (取消)收藏 static Future favVideo( {required int aid, String? addIds, String? delIds}) async { diff --git a/lib/models/common/color_type.dart b/lib/models/common/color_type.dart index 25d5caa92..ced687ad8 100644 --- a/lib/models/common/color_type.dart +++ b/lib/models/common/color_type.dart @@ -2,7 +2,7 @@ import 'package:flutter/material.dart'; final List> colorThemeTypes = [ {'color': const Color.fromARGB(255, 92, 182, 123), 'label': '默认绿'}, - {'color': Colors.pink, 'label': '粉红色'}, + {'color': const Color.fromARGB(255, 251, 114, 153), 'label': '粉红色'}, {'color': Colors.red, 'label': '红色'}, {'color': Colors.orange, 'label': '橙色'}, {'color': Colors.amber, 'label': '琥珀色'}, diff --git a/lib/models/common/dynamics_type.dart b/lib/models/common/dynamics_type.dart index f4e20a4b9..5e89213f1 100644 --- a/lib/models/common/dynamics_type.dart +++ b/lib/models/common/dynamics_type.dart @@ -1,11 +1,56 @@ +import 'package:get/get.dart'; +import '../../pages/dynamics/tab/controller.dart'; +import '../../pages/dynamics/tab/view.dart'; + enum DynamicsType { all, video, pgc, article, + up, } extension BusinessTypeExtension on DynamicsType { - String get values => ['all', 'video', 'pgc', 'article'][index]; - String get labels => ['全部', '投稿', '番剧', '专栏'][index]; + String get values => ['all', 'video', 'pgc', 'article', 'up'][index]; + String get labels => ['全部', '投稿', '番剧', '专栏', 'Up'][index]; } + +List tabsConfig = [ + { + 'value': DynamicsType.all, + 'label': '全部', + 'enabled': true, + 'ctr': Get.put(DynamicsTabController(), tag: 'all'), + 'page': const DynamicsTabPage(dynamicsType: 'all'), + }, + { + 'value': DynamicsType.video, + 'label': '投稿', + 'enabled': true, + 'ctr': + Get.put(DynamicsTabController(), tag: 'video'), + 'page': const DynamicsTabPage(dynamicsType: 'video'), + }, + { + 'value': DynamicsType.pgc, + 'label': '番剧', + 'enabled': true, + 'ctr': Get.put(DynamicsTabController(), tag: 'pgc'), + 'page': const DynamicsTabPage(dynamicsType: 'pgc'), + }, + { + 'value': DynamicsType.article, + 'label': '专栏', + 'enabled': true, + 'ctr': + Get.put(DynamicsTabController(), tag: 'article'), + 'page': const DynamicsTabPage(dynamicsType: 'article'), + }, + { + 'value': DynamicsType.up, + 'label': 'Up', + 'enabled': true, + 'ctr': Get.put(DynamicsTabController(), tag: 'up'), + 'page': const DynamicsTabPage(dynamicsType: 'up'), + }, +]; diff --git a/lib/models/common/nav_bar_config.dart b/lib/models/common/nav_bar_config.dart index e43a799d7..5c874fb44 100644 --- a/lib/models/common/nav_bar_config.dart +++ b/lib/models/common/nav_bar_config.dart @@ -5,30 +5,17 @@ List defaultNavigationBars = [ 'id': 0, 'icon': const Icon( Icons.home_outlined, - size: 21, + size: 23, ), 'selectIcon': const Icon( Icons.home, - size: 21, + size: 23, ), 'label': "首页", 'count': 0, }, { 'id': 1, - 'icon': const Icon( - Icons.leaderboard_outlined, - size: 21, - ), - 'selectIcon': const Icon( - Icons.leaderboard, - size: 21, - ), - 'label': "排行榜", - 'count': 0, - }, - { - 'id': 2, 'icon': const Icon( Icons.motion_photos_on_outlined, size: 21, @@ -41,7 +28,7 @@ List defaultNavigationBars = [ 'count': 0, }, { - 'id': 3, + 'id': 2, 'icon': const Icon( Icons.video_collection_outlined, size: 20, diff --git a/lib/models/common/rank_type.dart b/lib/models/common/rank_type.dart index ca6ff5a5d..7eaae2406 100644 --- a/lib/models/common/rank_type.dart +++ b/lib/models/common/rank_type.dart @@ -74,7 +74,7 @@ List tabsConfig = [ ), 'label': '全站', 'type': RandType.all, - 'ctr': Get.put, + 'ctr': Get.put(ZoneController(), tag: '0'), 'page': const ZonePage(rid: 0), }, { @@ -84,7 +84,7 @@ List tabsConfig = [ ), 'label': '国创', 'type': RandType.creation, - 'ctr': Get.put, + 'ctr': Get.put(ZoneController(), tag: '168'), 'page': const ZonePage(rid: 168), }, { @@ -94,7 +94,7 @@ List tabsConfig = [ ), 'label': '动画', 'type': RandType.animation, - 'ctr': Get.put, + 'ctr': Get.put(ZoneController(), tag: '1'), 'page': const ZonePage(rid: 1), }, { @@ -104,7 +104,7 @@ List tabsConfig = [ ), 'label': '音乐', 'type': RandType.music, - 'ctr': Get.put, + 'ctr': Get.put(ZoneController(), tag: '3'), 'page': const ZonePage(rid: 3), }, { @@ -114,7 +114,7 @@ List tabsConfig = [ ), 'label': '舞蹈', 'type': RandType.dance, - 'ctr': Get.put, + 'ctr': Get.put(ZoneController(), tag: '129'), 'page': const ZonePage(rid: 129), }, { @@ -124,7 +124,7 @@ List tabsConfig = [ ), 'label': '游戏', 'type': RandType.game, - 'ctr': Get.put, + 'ctr': Get.put(ZoneController(), tag: '4'), 'page': const ZonePage(rid: 4), }, { @@ -134,7 +134,7 @@ List tabsConfig = [ ), 'label': '知识', 'type': RandType.knowledge, - 'ctr': Get.put, + 'ctr': Get.put(ZoneController(), tag: '36'), 'page': const ZonePage(rid: 36), }, { @@ -144,7 +144,7 @@ List tabsConfig = [ ), 'label': '科技', 'type': RandType.technology, - 'ctr': Get.put, + 'ctr': Get.put(ZoneController(), tag: '188'), 'page': const ZonePage(rid: 188), }, { @@ -154,7 +154,7 @@ List tabsConfig = [ ), 'label': '运动', 'type': RandType.sport, - 'ctr': Get.put, + 'ctr': Get.put(ZoneController(), tag: '234'), 'page': const ZonePage(rid: 234), }, { @@ -164,7 +164,7 @@ List tabsConfig = [ ), 'label': '汽车', 'type': RandType.car, - 'ctr': Get.put, + 'ctr': Get.put(ZoneController(), tag: '223'), 'page': const ZonePage(rid: 223), }, { @@ -174,7 +174,7 @@ List tabsConfig = [ ), 'label': '生活', 'type': RandType.life, - 'ctr': Get.put, + 'ctr': Get.put(ZoneController(), tag: '160'), 'page': const ZonePage(rid: 160), }, { @@ -184,7 +184,7 @@ List tabsConfig = [ ), 'label': '美食', 'type': RandType.food, - 'ctr': Get.put, + 'ctr': Get.put(ZoneController(), tag: '211'), 'page': const ZonePage(rid: 211), }, { @@ -194,7 +194,7 @@ List tabsConfig = [ ), 'label': '动物', 'type': RandType.animal, - 'ctr': Get.put, + 'ctr': Get.put(ZoneController(), tag: '217'), 'page': const ZonePage(rid: 217), }, { @@ -204,7 +204,7 @@ List tabsConfig = [ ), 'label': '鬼畜', 'type': RandType.madness, - 'ctr': Get.put, + 'ctr': Get.put(ZoneController(), tag: '119'), 'page': const ZonePage(rid: 119), }, { @@ -214,7 +214,7 @@ List tabsConfig = [ ), 'label': '时尚', 'type': RandType.fashion, - 'ctr': Get.put, + 'ctr': Get.put(ZoneController(), tag: '155'), 'page': const ZonePage(rid: 155), }, { @@ -224,7 +224,7 @@ List tabsConfig = [ ), 'label': '娱乐', 'type': RandType.entertainment, - 'ctr': Get.put, + 'ctr': Get.put(ZoneController(), tag: '5'), 'page': const ZonePage(rid: 5), }, { @@ -234,7 +234,7 @@ List tabsConfig = [ ), 'label': '影视', 'type': RandType.film, - 'ctr': Get.put, + 'ctr': Get.put(ZoneController(), tag: '181'), 'page': const ZonePage(rid: 181), } ]; diff --git a/lib/models/common/tab_type.dart b/lib/models/common/tab_type.dart index bc7cb0b54..b4d1c73f0 100644 --- a/lib/models/common/tab_type.dart +++ b/lib/models/common/tab_type.dart @@ -1,3 +1,4 @@ +import 'package:PiliPalaX/pages/rank/index.dart'; import 'package:flutter/material.dart'; import 'package:get/get.dart'; import 'package:PiliPalaX/pages/bangumi/index.dart'; @@ -5,11 +6,11 @@ import 'package:PiliPalaX/pages/hot/index.dart'; import 'package:PiliPalaX/pages/live/index.dart'; import 'package:PiliPalaX/pages/rcmd/index.dart'; -enum TabType { live, rcmd, hot, bangumi } +enum TabType { live, rcmd, hot, rank, bangumi } extension TabTypeDesc on TabType { - String get description => ['直播', '推荐', '热门', '番剧'][index]; - String get id => ['live', 'rcmd', 'hot', 'bangumi'][index]; + String get description => ['直播', '推荐', '热门', '分区', '番剧'][index]; + String get id => ['live', 'rcmd', 'hot', 'rank', 'bangumi'][index]; } List tabsConfig = [ @@ -43,6 +44,16 @@ List tabsConfig = [ 'ctr': Get.find, 'page': const HotPage(), }, + { + 'icon': const Icon( + Icons.category_outlined, + size: 15, + ), + 'label': '分区', + 'type': TabType.rank, + 'ctr': Get.find, + 'page': const RankPage(), + }, { 'icon': const Icon( Icons.play_circle_outlined, diff --git a/lib/models/common/up_panel_position.dart b/lib/models/common/up_panel_position.dart new file mode 100644 index 000000000..cbb582371 --- /dev/null +++ b/lib/models/common/up_panel_position.dart @@ -0,0 +1,15 @@ +enum UpPanelPosition { + leftFixed, + rightFixed, + leftDrawer, + rightDrawer, +} + +extension UpPanelPositionDesc on UpPanelPosition { + String get values => ['left_fixed', 'right_fixed', 'left_drawer', 'right_drawer'][index]; + String get labels => ['左侧常驻','右侧常驻','左侧抽屉','右侧抽屉'][index]; +} + +extension UpPanelPositionCode on UpPanelPosition { + int get code => [0, 1, 2, 3][index]; +} diff --git a/lib/models/user/danmaku_block.dart b/lib/models/user/danmaku_block.dart new file mode 100644 index 000000000..ef0b6a6a8 --- /dev/null +++ b/lib/models/user/danmaku_block.dart @@ -0,0 +1,94 @@ +class DanmakuBlockDataModel { + List? rule; + String? toast; + int? valid; + int? ver; + + DanmakuBlockDataModel({this.rule, this.toast, this.valid, this.ver}); + + DanmakuBlockDataModel.fromJson(Map json) { + if (json['rule'] != null) { + rule = []; + json['rule'].forEach((v) { + rule!.add(Rule.fromJson(v)); + }); + } + toast = json['toast']; + valid = json['valid']; + ver = json['ver']; + } + + Map toJson() { + final Map data = {}; + if (rule != null) { + data['rule'] = rule!.map((v) => v.toJson()).toList(); + } + data['toast'] = toast; + data['valid'] = valid; + data['ver'] = ver; + return data; + } +} + +class Rule { + int? id; + int? mid; + int? type; + String? filter; + String? comment; + int? ctime; + int? mtime; + + Rule( + {this.id, + this.mid, + this.type, + this.filter, + this.comment, + this.ctime, + this.mtime}); + + Rule.fromJson(Map json) { + id = json['id']; + mid = json['mid']; + type = json['type']; + filter = json['filter']; + comment = json['comment']; + ctime = json['ctime']; + mtime = json['mtime']; + } + + Map toJson() { + final Map data = {}; + data['id'] = id; + data['mid'] = mid; + data['type'] = type; + data['filter'] = filter; + data['comment'] = comment; + data['ctime'] = ctime; + data['mtime'] = mtime; + return data; + } +} + +class SimpleRule { + final int id; + final int type; + final String filter; + SimpleRule(this.id, this.type, this.filter); + Map toMap() { + return { + 'id': id, + 'type': type, + 'filter': filter, + }; + } + + factory SimpleRule.fromMap(Map map) { + return SimpleRule( + map['id'], + map['type'], + map['filter'], + ); + } +} diff --git a/lib/pages/about/index.dart b/lib/pages/about/index.dart index 615a7054e..66bdd5518 100644 --- a/lib/pages/about/index.dart +++ b/lib/pages/about/index.dart @@ -127,7 +127,7 @@ class _AboutPageState extends State { ), ), ListTile( - onTap: () => _aboutController.feedback(), + onTap: () => _aboutController.feedback(context), leading: const Icon(Icons.feedback_outlined), title: const Text('问题反馈'), trailing: Icon( @@ -174,7 +174,7 @@ class _AboutPageState extends State { ), ListTile( onTap: () async { - await CacheManage().clearCacheAll(); + await CacheManage().clearCacheAll(context); getCacheSize(); }, leading: const Icon(Icons.delete_outline), @@ -185,17 +185,17 @@ class _AboutPageState extends State { title: const Text('导入/导出设置'), dense: false, leading: const Icon(Icons.import_export_outlined), - onTap: () { - SmartDialog.show( - useSystem: true, - builder: (BuildContext context) { + onTap: () async { + await showDialog( + context: context, + builder: (context) { return SimpleDialog( title: const Text('导入/导出设置'), children: [ ListTile( title: const Text('导出设置至剪贴板'), onTap: () async { - SmartDialog.dismiss(); + Get.back(); String data = await GStrorage.exportAllSettings(); Clipboard.setData(ClipboardData(text: data)); SmartDialog.showToast('已复制到剪贴板'); @@ -204,30 +204,35 @@ class _AboutPageState extends State { ListTile( title: const Text('从剪贴板导入设置'), onTap: () async { - SmartDialog.dismiss(); - ClipboardData? data = await Clipboard.getData('text/plain'); - if (data == null || data.text == null || data.text!.isEmpty) { + Get.back(); + ClipboardData? data = + await Clipboard.getData('text/plain'); + if (data == null || + data.text == null || + data.text!.isEmpty) { SmartDialog.showToast('剪贴板无数据'); return; } - SmartDialog.show( - useSystem: true, - builder: (BuildContext context) { + if (!context.mounted) return; + await showDialog( + context: context, + builder: (context) { return AlertDialog( title: const Text('是否导入如下设置?'), content: Text(data.text!), actions: [ TextButton( onPressed: () { - SmartDialog.dismiss(); + Get.back(); }, child: const Text('取消'), ), TextButton( onPressed: () async { - SmartDialog.dismiss(); - try{ - await GStrorage.importAllSettings(data.text!); + Get.back(); + try { + await GStrorage.importAllSettings( + data.text!); SmartDialog.showToast('导入成功'); } catch (e) { SmartDialog.showToast('导入失败:$e'); @@ -249,27 +254,27 @@ class _AboutPageState extends State { ListTile( title: const Text('重置所有设置'), leading: const Icon(Icons.settings_backup_restore_outlined), - onTap: () { - SmartDialog.show( - useSystem: true, - builder: (BuildContext context) { + onTap: () async { + await showDialog( + context: context, + builder: (context) { return AlertDialog( title: const Text('重置所有设置'), content: const Text('是否重置所有设置?'), actions: [ TextButton( onPressed: () { - SmartDialog.dismiss(); + Get.back(); }, child: const Text('取消'), ), TextButton( onPressed: () { + Get.back(); GStrorage.setting.clear(); GStrorage.localCache.clear(); GStrorage.video.clear(); SmartDialog.showToast('重置成功'); - SmartDialog.dismiss(); }, child: const Text('确定'), ), @@ -384,11 +389,10 @@ class AboutController extends GetxController { } // 问题反馈 - feedback() { - SmartDialog.show( - useSystem: true, - animationType: SmartAnimationType.centerFade_otherSlide, - builder: (BuildContext context) { + feedback(BuildContext context) async { + await showDialog( + context: context, + builder: (context) { return SimpleDialog( title: const Text('问题反馈'), children: [ diff --git a/lib/pages/bangumi/introduction/controller.dart b/lib/pages/bangumi/introduction/controller.dart index 2ebf452d6..7b0efd9a5 100644 --- a/lib/pages/bangumi/introduction/controller.dart +++ b/lib/pages/bangumi/introduction/controller.dart @@ -1,4 +1,5 @@ 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:hive/hive.dart'; @@ -231,9 +232,29 @@ class BangumiIntroController extends GetxController { // 分享视频 Future actionShareVideo() async { - var result = await Share.share('${HttpString.baseUrl}/video/$bvid') - .whenComplete(() {}); - return result; + showDialog( + context: Get.context!, + builder: (context) { + String videoUrl = '${HttpString.baseUrl}/video/$bvid'; + return AlertDialog( + title: const Text('分享方式'), + actions: [ + TextButton( + onPressed: () { + Clipboard.setData(ClipboardData(text: videoUrl)); + SmartDialog.showToast('已复制'); + }, + child: const Text('复制链接到剪贴板')), + TextButton( + onPressed: () async { + var result = + await Share.share(videoUrl).whenComplete(() {}); + return result; + }, + child: const Text('分享视频')), + ], + ); + }); } // 选择文件夹 diff --git a/lib/pages/bangumi/introduction/view.dart b/lib/pages/bangumi/introduction/view.dart index 162aff902..b33c87d54 100644 --- a/lib/pages/bangumi/introduction/view.dart +++ b/lib/pages/bangumi/introduction/view.dart @@ -1,3 +1,4 @@ +import 'package:PiliPalaX/plugin/pl_player/index.dart'; import 'package:flutter/material.dart'; import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; import 'package:font_awesome_flutter/font_awesome_flutter.dart'; @@ -283,56 +284,35 @@ class _BangumiInfoState extends State { : bangumiItem!.stat!['danmakus'], size: 'medium', ), + if (isLandscape) ...[ + const SizedBox(width: 6), + AreasAndPubTime( + widget: widget, + bangumiItem: bangumiItem, + t: t), + const SizedBox(width: 6), + NewEpDesc( + widget: widget, + bangumiItem: bangumiItem, + t: t), + ] ], ), SizedBox(height: isLandscape ? 2 : 6), - Row( - children: [ - Text( - !widget.loadingStatus - ? (widget.bangumiDetail!.areas! - .isNotEmpty - ? widget.bangumiDetail!.areas! - .first['name'] - : '') - : (bangumiItem!.areas!.isNotEmpty - ? bangumiItem! - .areas!.first['name'] - : ''), - style: TextStyle( - fontSize: 12, - color: t.colorScheme.outline, - ), - ), - const SizedBox(width: 6), - Text( - !widget.loadingStatus - ? widget.bangumiDetail! - .publish!['pub_time_show'] - : bangumiItem! - .publish!['pub_time_show'], - style: TextStyle( - fontSize: 12, - color: t.colorScheme.outline, - ), - ), - ], - ), - // const SizedBox(height: 4), - Text( - !widget.loadingStatus - ? widget.bangumiDetail!.newEp!['desc'] - : bangumiItem!.newEp!['desc'], - style: TextStyle( - fontSize: 12, - color: t.colorScheme.outline, - ), - ), - // const SizedBox(height: 10), + if (!isLandscape) + AreasAndPubTime( + widget: widget, + bangumiItem: bangumiItem, + t: t), + if (!isLandscape) + NewEpDesc( + widget: widget, + bangumiItem: bangumiItem, + t: t), const Spacer(), Text( '简介:${!widget.loadingStatus ? widget.bangumiDetail!.evaluate! : bangumiItem!.evaluate!}', - maxLines: isLandscape ? 1 : 3, + maxLines: isLandscape ? 2 : 3, overflow: TextOverflow.ellipsis, style: TextStyle( fontSize: 13, @@ -525,3 +505,73 @@ class _BangumiInfoState extends State { ]); } } + +class AreasAndPubTime extends StatelessWidget { + const AreasAndPubTime({ + super.key, + required this.widget, + required this.bangumiItem, + required this.t, + }); + + final BangumiInfo widget; + final BangumiInfoModel? bangumiItem; + final ThemeData t; + + @override + Widget build(BuildContext context) { + return Row( + children: [ + Text( + !widget.loadingStatus + ? (widget.bangumiDetail!.areas!.isNotEmpty + ? widget.bangumiDetail!.areas!.first['name'] + : '') + : (bangumiItem!.areas!.isNotEmpty + ? bangumiItem!.areas!.first['name'] + : ''), + style: TextStyle( + fontSize: 12, + color: t.colorScheme.outline, + ), + ), + const SizedBox(width: 6), + Text( + !widget.loadingStatus + ? widget.bangumiDetail!.publish!['pub_time_show'] + : bangumiItem!.publish!['pub_time_show'], + style: TextStyle( + fontSize: 12, + color: t.colorScheme.outline, + ), + ), + ], + ); + } +} + +class NewEpDesc extends StatelessWidget { + const NewEpDesc({ + super.key, + required this.widget, + required this.bangumiItem, + required this.t, + }); + + final BangumiInfo widget; + final BangumiInfoModel? bangumiItem; + final ThemeData t; + + @override + Widget build(BuildContext context) { + return Text( + !widget.loadingStatus + ? widget.bangumiDetail!.newEp!['desc'] + : bangumiItem!.newEp!['desc'], + style: TextStyle( + fontSize: 12, + color: t.colorScheme.outline, + ), + ); + } +} diff --git a/lib/pages/bangumi/view.dart b/lib/pages/bangumi/view.dart index 00fce50ff..e8f924d41 100644 --- a/lib/pages/bangumi/view.dart +++ b/lib/pages/bangumi/view.dart @@ -115,7 +115,7 @@ class _BangumiPageState extends State ), ), SizedBox( - height: 268, + height: Grid.maxRowWidth * 1, child: FutureBuilder( future: _futureBuilderFutureFollow, builder: @@ -135,8 +135,8 @@ class _BangumiPageState extends State itemCount: list.length, itemBuilder: (context, index) { return Container( - width: Get.size.width / 3, - height: 254, + width: Grid.maxRowWidth / 2, + height: Grid.maxRowWidth * 1, margin: EdgeInsets.only( left: StyleString.safeSpace, right: index == @@ -219,17 +219,16 @@ class _BangumiPageState extends State } Widget contentGrid(ctr, bangumiList) { - return SliverGrid( - gridDelegate: SliverGridDelegateWithMaxCrossAxisExtent( + gridDelegate: SliverGridDelegateWithExtentAndRatio( // 行间距 - mainAxisSpacing: StyleString.cardSpace - 2, - // 列间距 - crossAxisSpacing: StyleString.cardSpace, - // 最大宽度 - maxCrossAxisExtent: Grid.maxRowWidth / 3 * 2, - mainAxisExtent: Grid.calculateActualWidth(context, Grid.maxRowWidth / 3 * 2, StyleString.safeSpace) / 0.65+ - MediaQuery.textScalerOf(context).scale(60), + mainAxisSpacing: StyleString.cardSpace - 2, + // 列间距 + crossAxisSpacing: StyleString.cardSpace, + // 最大宽度 + maxCrossAxisExtent: Grid.maxRowWidth / 3 * 2, + childAspectRatio: 0.65, + mainAxisExtent: MediaQuery.textScalerOf(context).scale(60), ), delegate: SliverChildBuilderDelegate( (BuildContext context, int index) { diff --git a/lib/pages/danmaku/view.dart b/lib/pages/danmaku/view.dart index ea7e05774..d14d238cc 100644 --- a/lib/pages/danmaku/view.dart +++ b/lib/pages/danmaku/view.dart @@ -36,6 +36,7 @@ class _PlDanmakuState extends State { late double fontSizeVal; late double danmakuDurationVal; late double strokeWidth; + late int fontWeight; int latestAddedPosition = -1; @override @@ -68,6 +69,7 @@ class _PlDanmakuState extends State { opacityVal = playerController.opacityVal; fontSizeVal = playerController.fontSizeVal; strokeWidth = playerController.strokeWidth; + fontWeight = playerController.fontWeight; danmakuDurationVal = playerController.danmakuDurationVal; } @@ -132,6 +134,7 @@ class _PlDanmakuState extends State { }, option: DanmakuOption( fontSize: 15 * fontSizeVal, + fontWeight: fontWeight, area: showArea, opacity: opacityVal, hideTop: blockTypes.contains(5), diff --git a/lib/pages/danmaku_block/index.dart b/lib/pages/danmaku_block/index.dart new file mode 100644 index 000000000..512dd3597 --- /dev/null +++ b/lib/pages/danmaku_block/index.dart @@ -0,0 +1,182 @@ +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:PiliPalaX/utils/storage.dart'; + +import '../../http/danmaku_block.dart'; +import '../../models/user/danmaku_block.dart'; + +class DanmakuBlockPage extends StatefulWidget { + const DanmakuBlockPage({super.key}); + + @override + State createState() => _DanmakuBlockPageState(); +} + +class _DanmakuBlockPageState extends State { + final DanmakuBlockController _danmakuBlockController = + Get.put(DanmakuBlockController()); + final ScrollController scrollController = ScrollController(); + Box setting = GStrorage.setting; + + @override + void initState() { + super.initState(); + _danmakuBlockController.queryDanmakuFilter(); + } + + @override + void dispose() { + List> simpleRuleList = + _danmakuBlockController.danmakuRules.map>((e) { + return SimpleRule(e.id!, e.type!, e.filter!).toMap(); + }).toList(); + setting.put(SettingBoxKey.danmakuFilterRule, simpleRuleList); + scrollController.removeListener(() {}); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: Obx( + () => TabBar( + controller: _danmakuBlockController.tabController, + dividerColor: Colors.transparent, + tabs: [ + Tab(text: '文本(${_danmakuBlockController.textRules.length})'), + Tab(text: '正则(${_danmakuBlockController.regexRules.length})'), + Tab(text: '用户(${_danmakuBlockController.userRules.length})'), + ], + ), + ), + ), + body: RefreshIndicator( + onRefresh: () async => + await _danmakuBlockController.queryDanmakuFilter(), + child: TabBarView( + controller: _danmakuBlockController.tabController, + children: [ + Obx(() => tabViewBuilder(0, _danmakuBlockController.textRules)), + Obx(() => tabViewBuilder(1, _danmakuBlockController.regexRules)), + Obx(() => tabViewBuilder(2, _danmakuBlockController.userRules)), + ], + ), + ), + ); + } + Widget tabViewBuilder(int index, List list) { + return ListView.builder( + controller: scrollController, + itemCount: list.length, + itemBuilder: (BuildContext context, int index) { + return ListTile( + title: Text( + list[index].filter, + style: Theme.of(context).textTheme.subtitle1, + ), + trailing: IconButton( + icon: const Icon(Icons.delete), + onPressed: () async { + await _danmakuBlockController.danmakuFilterDel( + 1, list[index].id); + }, + ), + ); + }, + ); + } +} + +class DanmakuBlockController extends GetxController + with GetTickerProviderStateMixin { + RxList danmakuRules = [].obs; + RxList textRules = [].obs; + RxList regexRules = [].obs; + RxList userRules = [].obs; + late TabController tabController; + + @override + void onInit() { + super.onInit(); + tabController = TabController(length: 3, vsync: this); + } + + @override + void onClose() { + tabController.dispose(); + super.onClose(); + } + + Future queryDanmakuFilter() async { + var result = await DanmakuFilterHttp.danmakuFilter(); + if (result['status']) { + danmakuRules.value = result['data'].rule; + danmakuRules.map((e) { + SimpleRule simpleRule = SimpleRule(e.id!, e.type!, e.filter!); + switch (e.type!) { + case 0: + textRules.add(simpleRule); + break; + case 1: + regexRules.add(simpleRule); + break; + case 2: + userRules.add(simpleRule); + break; + default: + SmartDialog.showToast('未知的规则类型:${e.type},内容为:${e.filter}'); + } + }).toList(); + SmartDialog.showToast(result['data'].toast); + } + return result; + } + + Future danmakuFilterDel(int type, int id) async { + var result = await DanmakuFilterHttp.danmakuFilterDel(ids: id); + if (result['status']) { + danmakuRules.removeWhere((e) => e.id == id); + switch (type) { + case 0: + textRules.removeWhere((e) => e.id == id); + break; + case 1: + regexRules.removeWhere((e) => e.id == id); + break; + case 2: + userRules.removeWhere((e) => e.id == id); + break; + } + SmartDialog.showToast(result['msg']); + } else { + SmartDialog.showToast(result['msg']); + } + } + + Future danmakuFilterAdd({required String filter, required int type}) async { + var result = + await DanmakuFilterHttp.danmakuFilterAdd(filter: filter, type: type); + if (result['status']) { + Rule data = result['data']; + danmakuRules.add(data); + SimpleRule simpleRule = SimpleRule(data.id!, data.type!, data.filter!); + switch (data.type!) { + case 0: + textRules.add(simpleRule); + break; + case 1: + regexRules.add(simpleRule); + break; + case 2: + userRules.add(simpleRule); + break; + } + SmartDialog.showToast('添加成功'); + } else { + SmartDialog.showToast(result['msg']); + } + } +} diff --git a/lib/pages/dynamics/controller.dart b/lib/pages/dynamics/controller.dart index ca33e40eb..6cfb240f1 100644 --- a/lib/pages/dynamics/controller.dart +++ b/lib/pages/dynamics/controller.dart @@ -1,5 +1,6 @@ // ignore_for_file: avoid_print +import 'package:PiliPalaX/pages/dynamics/tab/index.dart'; import 'package:flutter/material.dart'; import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; import 'package:get/get.dart'; @@ -17,39 +18,17 @@ import 'package:PiliPalaX/utils/id_utils.dart'; import 'package:PiliPalaX/utils/storage.dart'; import 'package:PiliPalaX/utils/utils.dart'; -class DynamicsController extends GetxController { - int page = 1; +class DynamicsController extends GetxController + with GetTickerProviderStateMixin { String? offset = ''; - RxList dynamicsList = [].obs; - Rx dynamicsType = DynamicsType.values[0].obs; - RxString dynamicsTypeLabel = '全部'.obs; final ScrollController scrollController = ScrollController(); Rx upData = FollowUpModel().obs; // 默认获取全部动态 RxInt mid = (-1).obs; Rx upInfo = UpItem().obs; - List filterTypeList = [ - { - 'label': DynamicsType.all.labels, - 'value': DynamicsType.all, - 'enabled': true - }, - { - 'label': DynamicsType.video.labels, - 'value': DynamicsType.video, - 'enabled': true - }, - { - 'label': DynamicsType.pgc.labels, - 'value': DynamicsType.pgc, - 'enabled': true - }, - { - 'label': DynamicsType.article.labels, - 'value': DynamicsType.article, - 'enabled': true - }, - ]; + late TabController tabController; + RxList tempBannedList = [].obs; + late List tabsPageList; bool flag = false; RxInt initialValue = 0.obs; Box userInfoCache = GStrorage.userInfo; @@ -63,53 +42,23 @@ class DynamicsController extends GetxController { userInfo = userInfoCache.get('userInfoCache'); userLogin.value = userInfo != null; super.onInit(); - initialValue.value = - setting.get(SettingBoxKey.defaultDynamicType, defaultValue: 0); - dynamicsType = DynamicsType.values[initialValue.value].obs; + + tabController = TabController( + length: tabsConfig.length, + vsync: this, + initialIndex: + setting.get(SettingBoxKey.defaultDynamicType, defaultValue: 0)); + tabsPageList = tabsConfig.map((e) { + return e['page'] as Widget; + }).toList(); } - Future queryFollowDynamic({type = 'init'}) async { - if (!userLogin.value) { - return {'status': false, 'msg': '账号未登录'}; - } - if (type == 'init') { - dynamicsList.clear(); - } - // 下拉刷新数据渲染时会触发onLoad - if (type == 'onLoad' && page == 1) { - return; - } - isLoadingDynamic.value = true; - var res = await DynamicsHttp.followDynamic( - page: type == 'init' ? 1 : page, - type: dynamicsType.value.values, - offset: offset, - mid: mid.value, - ); - isLoadingDynamic.value = false; - if (res['status']) { - if (type == 'onLoad' && res['data'].items.isEmpty) { - SmartDialog.showToast('没有更多了'); - return; - } - if (type == 'init') { - dynamicsList.value = res['data'].items; - } else { - dynamicsList.addAll(res['data'].items); - } - offset = res['data'].offset; - page++; - } - return res; + void refreshNotifier() { + queryFollowUp(); } onSelectType(value) async { - dynamicsType.value = filterTypeList[value]['value']; - dynamicsList.value = [DynamicItemModel()]; - page = 1; initialValue.value = value; - await queryFollowDynamic(); - scrollController.jumpTo(0); } pushDetail(item, floor, {action = 'all'}) async { @@ -276,21 +225,27 @@ class DynamicsController extends GetxController { } onSelectUp(mid) async { - dynamicsType.value = DynamicsType.values[0]; - dynamicsList.value = [DynamicItemModel()]; - page = 1; - queryFollowDynamic(); + if (mid == this.mid.value) { + this.mid.refresh(); + return; + } + this.mid.value = mid; + tabController.index = (mid == -1 ? 0 : 4); } onRefresh() async { - page = 1; print('onRefresh'); - await queryFollowUp(); - await queryFollowDynamic(); + print(tabsConfig[tabController.index]['ctr']); + await Future.wait([ + queryFollowUp(), + tabsConfig[tabController.index]['ctr'].onRefresh() + ]); } // 返回顶部并刷新 void animateToTop() async { + tabsConfig[tabController.index]['ctr'].animateToTop(); + if (!scrollController.hasClients) return; if (scrollController.offset >= MediaQuery.of(Get.context!).size.height * 5) { scrollController.jumpTo(0); @@ -299,14 +254,4 @@ class DynamicsController extends GetxController { duration: const Duration(milliseconds: 500), curve: Curves.easeInOut); } } - - // 重置搜索 - void resetSearch() { - mid.value = -1; - dynamicsType.value = DynamicsType.values[0]; - initialValue.value = 0; - SmartDialog.showToast('还原默认加载'); - dynamicsList.value = [DynamicItemModel()]; - queryFollowDynamic(); - } } diff --git a/lib/pages/dynamics/tab/controller.dart b/lib/pages/dynamics/tab/controller.dart new file mode 100644 index 000000000..cf5917bcd --- /dev/null +++ b/lib/pages/dynamics/tab/controller.dart @@ -0,0 +1,76 @@ +import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; +import 'package:get/get.dart'; +import 'package:flutter/material.dart'; +// import 'package:hive/hive.dart'; + +import '../../../http/dynamics.dart'; +import '../../../models/dynamics/result.dart'; +// import '../../../utils/storage.dart'; + +class DynamicsTabController extends GetxController { + String offset = ''; + ScrollController scrollController = ScrollController(); + RxList dynamicsList = [].obs; + RxBool isLoadingMore = false.obs; + String dynamicsType = 'all'; + // Box userInfoCache = GStrorage.userInfo; + // bool userLogin = false; + int mid = -1; + + Future queryFollowDynamic(String type, String dynamicsType, int? mid) async { + this.dynamicsType = dynamicsType; + if (mid != null) this.mid = mid; + if (type != 'onLoad') { + dynamicsList.clear(); + offset = ''; + } + // // 下拉刷新数据渲染时会触发onLoad + // if (type == 'onLoad' && page == 1) { + // return; + // } + isLoadingMore.value = true; + var res = await DynamicsHttp.followDynamic( + type: dynamicsType == "up" ? "all" : dynamicsType, + offset: offset, + mid: dynamicsType == "up" ? mid : -1, + ); + isLoadingMore.value = false; + if (res['status']) { + if (type == 'onLoad' && res['data'].items.isEmpty) { + SmartDialog.showToast('没有更多了'); + return; + } + if (type == 'onLoad') { + dynamicsList.addAll(res['data'].items); + } else { + dynamicsList.value = res['data'].items; + } + // print('dynamicsList: $dynamicsList'); + dynamicsList.refresh(); + offset = res['data'].offset; + // print("page: $page[dynamicsType]!"); + } + return res; + } + + // 下拉刷新 + Future onRefresh() async { + await queryFollowDynamic('onRefresh', dynamicsType, mid); + } + + // 上拉加载 + Future onLoad() async { + await queryFollowDynamic('onLoad', dynamicsType, mid); + } + + // 返回顶部并刷新 + void animateToTop() async { + if (scrollController.offset >= + MediaQuery.of(Get.context!).size.height * 5) { + scrollController.jumpTo(0); + } else { + await scrollController.animateTo(0, + duration: const Duration(milliseconds: 500), curve: Curves.easeInOut); + } + } +} diff --git a/lib/pages/dynamics/tab/index.dart b/lib/pages/dynamics/tab/index.dart new file mode 100644 index 000000000..ac0e4f4ba --- /dev/null +++ b/lib/pages/dynamics/tab/index.dart @@ -0,0 +1,4 @@ +library dynamics.tab; + +export './controller.dart'; +export './view.dart'; \ No newline at end of file diff --git a/lib/pages/dynamics/tab/view.dart b/lib/pages/dynamics/tab/view.dart new file mode 100644 index 000000000..05d275f14 --- /dev/null +++ b/lib/pages/dynamics/tab/view.dart @@ -0,0 +1,234 @@ +import 'dart:async'; +import 'dart:math'; + +import 'package:PiliPalaX/utils/storage.dart'; +import 'package:easy_debounce/easy_throttle.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; +import 'package:get/get.dart'; +import 'package:PiliPalaX/common/constants.dart'; +import 'package:PiliPalaX/common/widgets/http_error.dart'; +import 'package:PiliPalaX/pages/home/index.dart'; +import 'package:PiliPalaX/pages/main/index.dart'; +import 'package:PiliPalaX/common/widgets/no_data.dart'; +import 'package:PiliPalaX/common/widgets/http_error.dart'; +import 'package:waterfall_flow/waterfall_flow.dart'; + +import '../../../common/skeleton/dynamic_card.dart'; +import '../../../models/dynamics/result.dart'; +import '../../../utils/grid.dart'; + +import '../index.dart'; +import '../widgets/dynamic_panel.dart'; +import 'controller.dart'; + +class DynamicsTabPage extends StatefulWidget { + const DynamicsTabPage({Key? key, required this.dynamicsType}) + : super(key: key); + + final String dynamicsType; + + @override + State createState() => _DynamicsTabPageState(); +} + +class _DynamicsTabPageState extends State + with AutomaticKeepAliveClientMixin { + late DynamicsTabController _dynamicsTabController; + late Future _futureBuilderFuture; + late ScrollController scrollController; + late bool dynamicsWaterfallFlow; + late final DynamicsController dynamicsController; + + @override + bool get wantKeepAlive => true; + + @override + void initState() { + super.initState(); + _dynamicsTabController = + Get.put(DynamicsTabController(), tag: widget.dynamicsType); + dynamicsController = Get.find(); + + _futureBuilderFuture = _dynamicsTabController.queryFollowDynamic( + 'init', widget.dynamicsType, dynamicsController.mid.value); + scrollController = _dynamicsTabController.scrollController + ..addListener(() { + if (scrollController.position.pixels >= + scrollController.position.maxScrollExtent - 200) { + if (!_dynamicsTabController.isLoadingMore.value) { + EasyThrottle.throttle('_dynamicsTabController_onLoad', + const Duration(milliseconds: 500), () { + _dynamicsTabController.isLoadingMore.value = true; + _dynamicsTabController.onLoad(); + _dynamicsTabController.isLoadingMore.value = false; + }); + } + } + }); + dynamicsController.mid.listen((mid) { + print('midListen: $mid'); + scrollController.jumpTo(0); + _futureBuilderFuture = _dynamicsTabController.queryFollowDynamic( + 'init', widget.dynamicsType, mid); + }); + dynamicsWaterfallFlow = GStrorage.setting + .get(SettingBoxKey.dynamicsWaterfallFlow, defaultValue: true); + } + + @override + void dispose() { + scrollController.removeListener(() {}); + dynamicsController.mid.close(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + super.build(context); + // print(widget.dynamicsType + widget.mid.value.toString()); + return RefreshIndicator( + // key: + // ValueKey(widget.dynamicsType + widget.mid.value.toString()), + onRefresh: () async { + dynamicsWaterfallFlow = GStrorage.setting + .get(SettingBoxKey.dynamicsWaterfallFlow, defaultValue: true); + await Future.wait([ + _dynamicsTabController.onRefresh(), + dynamicsController.queryFollowUp() + ]); + }, + child: CustomScrollView( + physics: const AlwaysScrollableScrollPhysics(), + controller: _dynamicsTabController.scrollController, + slivers: [ + FutureBuilder( + future: _futureBuilderFuture, + builder: (context, snapshot) { + // print(snapshot); + // print(widget.dynamicsType + "${widget.mid?.value}"); + if (snapshot.connectionState == ConnectionState.done) { + if (snapshot.data == null) { + return const NoData(); + } + Map data = snapshot.data; + // print('data: $data'); + if (data['status']) { + List list = + _dynamicsTabController.dynamicsList; + // print('list: $list'); + return Obx( + () { + if (list.isEmpty) { + if (_dynamicsTabController.isLoadingMore.value) { + return skeleton(); + } + return const NoData(); + } + if (!dynamicsWaterfallFlow) { + return SliverCrossAxisGroup( + slivers: [ + const SliverFillRemaining(), + SliverConstrainedCrossAxis( + maxExtent: Grid.maxRowWidth * 2, + sliver: SliverList( + delegate: SliverChildBuilderDelegate( + (context, index) { + if ((dynamicsController + .tabController.index == + 4 && + dynamicsController.mid.value != + -1) || + !dynamicsController.tempBannedList + .contains(list[index] + .modules + ?.moduleAuthor + ?.mid)) { + return DynamicPanel( + item: list[index]); + } + return const SizedBox(); + }, + childCount: list.length, + ), + )), + const SliverFillRemaining(), + ], + ); + } + return SliverWaterfallFlow.extent( + maxCrossAxisExtent: Grid.maxRowWidth * 2, + //cacheExtent: 0.0, + crossAxisSpacing: StyleString.cardSpace / 2, + mainAxisSpacing: StyleString.cardSpace / 2, + + lastChildLayoutTypeBuilder: (index) => + index == list.length + ? LastChildLayoutType.foot + : LastChildLayoutType.none, + children: [ + if (dynamicsController.tabController.index == 4 && + dynamicsController.mid.value != -1) ...[ + for (var i in list) DynamicPanel(item: i), + ] else ...[ + for (var i in list) + if (!dynamicsController.tempBannedList + .contains(i.modules?.moduleAuthor?.mid)) + DynamicPanel(item: i), + ] + ], + ); + }, + ); + } else { + return HttpError( + errMsg: data['msg'], + fn: () { + // setState(() { + _futureBuilderFuture = + _dynamicsTabController.queryFollowDynamic( + 'init', + widget.dynamicsType, + dynamicsController.mid.value); + // }); + dynamicsController.onRefresh(); + }, + ); + } + } else { + // 骨架屏 + return skeleton(); + } + }, + ) + ], + )); + } + + Widget skeleton() { + if (!dynamicsWaterfallFlow) { + return SliverCrossAxisGroup(slivers: [ + const SliverFillRemaining(), + SliverConstrainedCrossAxis( + maxExtent: Grid.maxRowWidth * 2, + sliver: SliverList( + delegate: SliverChildBuilderDelegate((context, index) { + return const DynamicCardSkeleton(); + }, childCount: 10)), + ), + const SliverFillRemaining() + ]); + } + return SliverGrid( + gridDelegate: SliverGridDelegateWithExtentAndRatio( + crossAxisSpacing: StyleString.cardSpace / 2, + mainAxisSpacing: StyleString.cardSpace / 2, + maxCrossAxisExtent: Grid.maxRowWidth * 2, + childAspectRatio: StyleString.aspectRatio, + mainAxisExtent: 50), + delegate: SliverChildBuilderDelegate((context, index) { + return const DynamicCardSkeleton(); + }, childCount: 10), + ); + } +} diff --git a/lib/pages/dynamics/view.dart b/lib/pages/dynamics/view.dart index d9fdefe9d..a38fb3ee6 100644 --- a/lib/pages/dynamics/view.dart +++ b/lib/pages/dynamics/view.dart @@ -1,24 +1,15 @@ import 'dart:async'; -import 'package:custom_sliding_segmented_control/custom_sliding_segmented_control.dart'; -import 'package:easy_debounce/easy_throttle.dart'; +import 'package:PiliPalaX/models/common/dynamics_type.dart'; +import 'package:PiliPalaX/models/common/up_panel_position.dart'; import 'package:flutter/material.dart'; import 'package:flutter/rendering.dart'; import 'package:get/get.dart'; import 'package:hive/hive.dart'; -import 'package:waterfall_flow/waterfall_flow.dart'; -import 'package:PiliPalaX/common/skeleton/dynamic_card.dart'; -import 'package:PiliPalaX/common/widgets/http_error.dart'; -import 'package:PiliPalaX/common/widgets/no_data.dart'; -import 'package:PiliPalaX/models/dynamics/result.dart'; -import 'package:PiliPalaX/pages/main/index.dart'; import 'package:PiliPalaX/utils/feed_back.dart'; import 'package:PiliPalaX/utils/storage.dart'; -import '../../common/constants.dart'; -import '../../utils/grid.dart'; import 'controller.dart'; -import 'widgets/dynamic_panel.dart'; import 'widgets/up_panel.dart'; class DynamicsPage extends StatefulWidget { @@ -29,12 +20,12 @@ class DynamicsPage extends StatefulWidget { } class _DynamicsPageState extends State - with AutomaticKeepAliveClientMixin { + with AutomaticKeepAliveClientMixin, SingleTickerProviderStateMixin { final DynamicsController _dynamicsController = Get.put(DynamicsController()); - late Future _futureBuilderFuture; late Future _futureBuilderFutureUp; Box userInfoCache = GStrorage.userInfo; late ScrollController scrollController; + late UpPanelPosition upPanelPosition; @override bool get wantKeepAlive => true; @@ -42,269 +33,125 @@ class _DynamicsPageState extends State @override void initState() { super.initState(); - _futureBuilderFuture = _dynamicsController.queryFollowDynamic(); _futureBuilderFutureUp = _dynamicsController.queryFollowUp(); - scrollController = _dynamicsController.scrollController; - StreamController mainStream = - Get.find().bottomBarStream; - scrollController.addListener( - () async { - if (scrollController.position.pixels >= - scrollController.position.maxScrollExtent - 200) { - EasyThrottle.throttle( - 'queryFollowDynamic', const Duration(seconds: 1), () { - _dynamicsController.queryFollowDynamic(type: 'onLoad'); - }); - } - - final ScrollDirection direction = - scrollController.position.userScrollDirection; - if (direction == ScrollDirection.forward) { - mainStream.add(true); - } else if (direction == ScrollDirection.reverse) { - mainStream.add(false); - } - }, - ); - + // _dynamicsController.tabController = + // TabController(vsync: this, length: DynamicsType.values.length); + // ..addListener(() { + // if (!_dynamicsController.tabController.indexIsChanging) { + // // if (!mounted) return; + // // print('indexChanging: ${_dynamicsController.tabController.index}'); + // _dynamicsController + // .onSelectType(_dynamicsController.tabController.index); + // } + // }); _dynamicsController.userLogin.listen((status) { if (mounted) { setState(() { - _futureBuilderFuture = _dynamicsController.queryFollowDynamic(); _futureBuilderFutureUp = _dynamicsController.queryFollowUp(); }); } }); + upPanelPosition = UpPanelPosition.values[setting.get( + SettingBoxKey.upPanelPosition, + defaultValue: UpPanelPosition.leftFixed.code)]; + print('upPanelPosition: $upPanelPosition'); + scrollController = _dynamicsController.scrollController; } @override void dispose() { - scrollController.removeListener(() {}); + _dynamicsController.tabController.removeListener(() {}); + _dynamicsController.tabController.dispose(); super.dispose(); } + Widget upPanelPart() { + return Padding( + padding: const EdgeInsets.symmetric(horizontal: 4), + child: Container( + //抽屉模式增加底色 + color: upPanelPosition.code > 1? Theme.of(context).colorScheme.surface: Colors.transparent, + width: 56, + child: FutureBuilder( + future: _futureBuilderFutureUp, + builder: (context, snapshot) { + if (snapshot.connectionState == ConnectionState.done) { + if (snapshot.data == null) { + return const SizedBox(); + } + Map data = snapshot.data; + if (data['status']) { + return Obx(() => UpPanel( + _dynamicsController.upData.value, + scrollController)); + } else { + return const SizedBox(); + } + } else { + return const SizedBox( + width: 56, + child: UpPanelSkeleton(), + ); + } + }, + ), + )); + } @override Widget build(BuildContext context) { + print('upPanelPosition1: $upPanelPosition'); super.build(context); return Scaffold( - appBar: AppBar( - elevation: 0, - scrolledUnderElevation: 0, - titleSpacing: 0, - title: SizedBox( - height: 34, - child: Stack( - children: [ - Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Obx(() { - if (_dynamicsController.mid.value != -1 && - _dynamicsController.upInfo.value.uname != null) { - return SizedBox( - height: 36, - child: AnimatedSwitcher( - duration: const Duration(milliseconds: 300), - transitionBuilder: - (Widget child, Animation animation) { - return ScaleTransition( - scale: animation, child: child); - }, - child: Text( - '${_dynamicsController.upInfo.value.uname!}的动态', - key: ValueKey( - _dynamicsController.upInfo.value.uname!), - style: TextStyle( - fontSize: Theme.of(context) - .textTheme - .labelLarge! - .fontSize, - )), - ), - ); - } else { - return const SizedBox(); - } - }), - Obx( - () => _dynamicsController.userLogin.value - ? Visibility( - visible: _dynamicsController.mid.value == -1, - child: Theme( - data: ThemeData( - splashColor: - Colors.transparent, // 点击时的水波纹颜色设置为透明 - highlightColor: - Colors.transparent, // 点击时的背景高亮颜色设置为透明 - ), - child: CustomSlidingSegmentedControl( - initialValue: - _dynamicsController.initialValue.value, - children: { - 0: Text( - '全部', - style: TextStyle( - fontSize: Theme.of(context) - .textTheme - .labelMedium! - .fontSize), - ), - 1: Text('投稿', - style: TextStyle( - fontSize: Theme.of(context) - .textTheme - .labelMedium! - .fontSize)), - 2: Text('番剧', - style: TextStyle( - fontSize: Theme.of(context) - .textTheme - .labelMedium! - .fontSize)), - 3: Text('专栏', - style: TextStyle( - fontSize: Theme.of(context) - .textTheme - .labelMedium! - .fontSize)), - }, - padding: 13.0, - decoration: BoxDecoration( - color: Theme.of(context) - .colorScheme - .surfaceVariant - .withOpacity(0.7), - borderRadius: BorderRadius.circular(20), - ), - thumbDecoration: BoxDecoration( - color: - Theme.of(context).colorScheme.background, - borderRadius: BorderRadius.circular(20), - ), - duration: const Duration(milliseconds: 300), - curve: Curves.easeInOut, - onValueChanged: (v) { - feedBack(); - _dynamicsController.onSelectType(v); - }, - ), - ), - ) - : Text('动态', - style: Theme.of(context).textTheme.titleMedium), - ) - ], - ), - ], - ), - ), - ), - body: RefreshIndicator( - onRefresh: () => _dynamicsController.onRefresh(), - child: CustomScrollView( - physics: const AlwaysScrollableScrollPhysics(), - controller: _dynamicsController.scrollController, - slivers: [ - FutureBuilder( - future: _futureBuilderFutureUp, - builder: (context, snapshot) { - if (snapshot.connectionState == ConnectionState.done) { - if (snapshot.data == null) { - return const SliverToBoxAdapter(child: SizedBox()); - } - Map data = snapshot.data; - if (data['status']) { - return Obx(() => UpPanel(_dynamicsController.upData.value)); - } else { - return const SliverToBoxAdapter( - child: SizedBox(height: 80), - ); - } - } else { - return const SliverToBoxAdapter( - child: SizedBox( - height: 90, - child: UpPanelSkeleton(), - )); + backgroundColor: Colors.transparent, + appBar: AppBar( + toolbarHeight: 50, + elevation: 0, + backgroundColor: Colors.transparent, + title: SizedBox( + height: 50, + child: TabBar( + controller: _dynamicsController.tabController, + isScrollable: true, + dividerColor: Colors.transparent, + dividerHeight: 0, + tabAlignment: TabAlignment.center, + indicatorColor: Theme.of(context).colorScheme.primary, + labelColor: Theme.of(context).colorScheme.primary, + unselectedLabelColor: Theme.of(context).colorScheme.onSurface, + labelStyle: TextStyle( + fontSize: Theme.of(context).textTheme.labelMedium!.fontSize, + ), + tabs: DynamicsType.values + .map((e) => Tab(text: e.labels)) + .toList(), + onTap: (index) { + print('index: $index'); + feedBack(); + tabsConfig[_dynamicsController.tabController.index]['ctr'].animateToTop(); + // _dynamicsController.tabController + // _dynamicsController.tabController.index = index; + // _dynamicsController.onSelectType(index); + // _ } - }, - ), - FutureBuilder( - future: _futureBuilderFuture, - builder: (context, snapshot) { - if (snapshot.connectionState == ConnectionState.done) { - if (snapshot.data == null) { - return const SliverToBoxAdapter(child: SizedBox()); - } - Map data = snapshot.data; - if (data['status']) { - List list = - _dynamicsController.dynamicsList; - return Obx( - () { - if (list.isEmpty) { - if (_dynamicsController.isLoadingDynamic.value) { - return skeleton(); - } else { - return const NoData(); - } - } else { - return SliverWaterfallFlow.extent( - maxCrossAxisExtent: Grid.maxRowWidth * 2, - //cacheExtent: 0.0, - crossAxisSpacing: StyleString.safeSpace, - mainAxisSpacing: StyleString.cardSpace, - - /// follow max child trailing layout offset and layout with full cross axis extend - /// last child as loadmore item/no more item in [GridView] and [WaterfallFlow] - /// with full cross axis extend - // LastChildLayoutType.fullCrossAxisExtend, - - /// as foot at trailing and layout with full cross axis extend - /// show no more item at trailing when children are not full of viewport - /// if children is full of viewport, it's the same as fullCrossAxisExtend - // LastChildLayoutType.foot, - lastChildLayoutTypeBuilder: (index) => - index == list.length - ? LastChildLayoutType.foot - : LastChildLayoutType.none, - children: [ - for (var i in list) DynamicPanel(item: i), - ]); - } - }, - ); - } else { - return HttpError( - errMsg: data['msg'], - fn: () { - setState(() { - _futureBuilderFuture = - _dynamicsController.queryFollowDynamic(); - _futureBuilderFutureUp = - _dynamicsController.queryFollowUp(); - }); - }, - ); - } - } else { - // 骨架屏 - return skeleton(); - } - }, - ), - const SliverToBoxAdapter(child: SizedBox(height: 40)) - ], + )), ), - ), - ); - } - - Widget skeleton() { - return SliverList( - delegate: SliverChildBuilderDelegate((context, index) { - return const DynamicCardSkeleton(); - }, childCount: 5), - ); + drawer: upPanelPosition == UpPanelPosition.leftDrawer ? + upPanelPart(): null, + drawerEnableOpenDragGesture: true, + endDrawer: upPanelPosition == UpPanelPosition.rightDrawer ? + upPanelPart(): null, + endDrawerEnableOpenDragGesture: true, + body: Row(children: [ + if (upPanelPosition == UpPanelPosition.leftFixed) + upPanelPart(), + Expanded( + child: TabBarView( + physics: const AlwaysScrollableScrollPhysics(), + controller: _dynamicsController.tabController, + children: _dynamicsController.tabsPageList, + )), + if (upPanelPosition == UpPanelPosition.rightFixed) + upPanelPart(), + ])); } } diff --git a/lib/pages/dynamics/widgets/author_panel.dart b/lib/pages/dynamics/widgets/author_panel.dart index 7c092a45c..d784dc486 100644 --- a/lib/pages/dynamics/widgets/author_panel.dart +++ b/lib/pages/dynamics/widgets/author_panel.dart @@ -5,10 +5,15 @@ import 'package:PiliPalaX/common/widgets/network_img_layer.dart'; import 'package:PiliPalaX/http/user.dart'; import 'package:PiliPalaX/utils/feed_back.dart'; import 'package:PiliPalaX/utils/utils.dart'; +import 'package:share_plus/share_plus.dart'; + +import '../../../http/constants.dart'; +import '../controller.dart'; class AuthorPanel extends StatelessWidget { final dynamic item; - const AuthorPanel({super.key, required this.item}); + final Function? addBannedList; + const AuthorPanel({super.key, required this.item, this.addBannedList}); @override Widget build(BuildContext context) { @@ -77,28 +82,27 @@ class AuthorPanel extends StatelessWidget { ], ), const Spacer(), - if (item.type == 'DYNAMIC_TYPE_AV') - SizedBox( - width: 32, - height: 32, - child: IconButton( - tooltip: '更多', - style: ButtonStyle( - padding: MaterialStateProperty.all(EdgeInsets.zero), - ), - onPressed: () { - showModalBottomSheet( - context: context, - useRootNavigator: true, - isScrollControlled: true, - builder: (context) { - return MorePanel(item: item); - }, - ); - }, - icon: const Icon(Icons.more_vert_outlined, size: 18), + SizedBox( + width: 32, + height: 32, + child: IconButton( + tooltip: '更多', + style: ButtonStyle( + padding: MaterialStateProperty.all(EdgeInsets.zero), ), + onPressed: () { + showModalBottomSheet( + context: context, + useRootNavigator: true, + isScrollControlled: true, + builder: (context) { + return MorePanel(item: item); + }, + ); + }, + icon: const Icon(Icons.more_vert_outlined, size: 18), ), + ), ], ); } @@ -132,24 +136,56 @@ class MorePanel extends StatelessWidget { ), ), ), + if (item.type == 'DYNAMIC_TYPE_AV') + ListTile( + onTap: () async { + try { + String bvid = item.modules.moduleDynamic.major.archive.bvid; + var res = await UserHttp.toViewLater(bvid: bvid); + SmartDialog.showToast(res['msg']); + Get.back(); + } catch (err) { + SmartDialog.showToast('出错了:${err.toString()}'); + } + }, + minLeadingWidth: 0, + // dense: true, + leading: const Icon(Icons.watch_later_outlined, size: 19), + title: Text( + '稍后再看', + style: Theme.of(context).textTheme.titleSmall, + ), + ), ListTile( + title: Text( + '分享动态', + style: Theme.of(context).textTheme.titleSmall, + ), + leading: const Icon(Icons.share_outlined, size: 19), onTap: () async { - try { - String bvid = item.modules.moduleDynamic.major.archive.bvid; - var res = await UserHttp.toViewLater(bvid: bvid); - SmartDialog.showToast(res['msg']); - Get.back(); - } catch (err) { - SmartDialog.showToast('出错了:${err.toString()}'); - } + var result = await Share.share( + '${HttpString.baseUrl}/dynamic/${item.idStr} UP主: ${item.modules.moduleAuthor.name}') + .whenComplete(() {}); + return result; }, minLeadingWidth: 0, - // dense: true, - leading: const Icon(Icons.watch_later_outlined, size: 19), + ), + ListTile( title: Text( - '稍后再看', + '临时屏蔽:${item.modules.moduleAuthor.name}', style: Theme.of(context).textTheme.titleSmall, ), + leading: const Icon(Icons.visibility_off_outlined, size: 19), + onTap: () { + Get.back(); + DynamicsController dynamicsController = + Get.find(); + dynamicsController.tempBannedList + .add(item.modules.moduleAuthor.mid); + SmartDialog.showToast( + '已临时屏蔽${item.modules.moduleAuthor.name}(${item.modules.moduleAuthor.mid}),重启恢复'); + }, + minLeadingWidth: 0, ), const Divider(thickness: 0.1, height: 1), ListTile( diff --git a/lib/pages/dynamics/widgets/dynamic_panel.dart b/lib/pages/dynamics/widgets/dynamic_panel.dart index dbe93b089..c3e1eeb8e 100644 --- a/lib/pages/dynamics/widgets/dynamic_panel.dart +++ b/lib/pages/dynamics/widgets/dynamic_panel.dart @@ -1,3 +1,4 @@ +import 'package:PiliPalaX/models/dynamics/result.dart'; import 'package:flutter/material.dart'; import 'package:get/get.dart'; import 'package:PiliPalaX/pages/dynamics/index.dart'; @@ -9,7 +10,7 @@ import 'forward_panel.dart'; class DynamicPanel extends StatelessWidget { final dynamic item; final String? source; - DynamicPanel({this.item, this.source, Key? key}) : super(key: key); + DynamicPanel({required this.item, this.source, Key? key}) : super(key: key); final DynamicsController _dynamicsController = Get.put(DynamicsController()); @override @@ -18,19 +19,20 @@ class DynamicPanel extends StatelessWidget { padding: source == 'detail' ? const EdgeInsets.only(bottom: 12) : EdgeInsets.zero, - decoration: BoxDecoration( - border: Border( - bottom: BorderSide( - width: 8, - color: Theme.of(context).dividerColor.withOpacity(0.05), - ), - ), - ), + // decoration: BoxDecoration( + // border: Border( + // bottom: BorderSide( + // width: 8, + // color: Theme.of(context).dividerColor.withOpacity(0.05), + // ), + // ), + // ), child: Material( elevation: 0, clipBehavior: Clip.hardEdge, + color: Theme.of(context).cardColor.withOpacity(0.5), shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(0), + borderRadius: BorderRadius.circular(5), ), child: InkWell( onTap: () => _dynamicsController.pushDetail(item, 1), @@ -38,7 +40,7 @@ class DynamicPanel extends StatelessWidget { mainAxisAlignment: MainAxisAlignment.start, children: [ Padding( - padding: const EdgeInsets.fromLTRB(12, 12, 12, 8), + padding: const EdgeInsets.fromLTRB(12, 12, 12, 6), child: AuthorPanel(item: item), ), if (item!.modules!.moduleDynamic!.desc != null || diff --git a/lib/pages/dynamics/widgets/up_panel.dart b/lib/pages/dynamics/widgets/up_panel.dart index 0d1172817..9fba01c43 100644 --- a/lib/pages/dynamics/widgets/up_panel.dart +++ b/lib/pages/dynamics/widgets/up_panel.dart @@ -11,26 +11,27 @@ import 'package:PiliPalaX/utils/utils.dart'; class UpPanel extends StatefulWidget { final FollowUpModel? upData; - const UpPanel(this.upData, {Key? key}) : super(key: key); + final ScrollController scrollController; + const UpPanel(this.upData, this.scrollController, {Key? key}) : super(key: key); @override State createState() => _UpPanelState(); } class _UpPanelState extends State { - final ScrollController scrollController = ScrollController(); int currentMid = -1; - late double contentWidth = 56; List upList = []; List liveList = []; - static const itemPadding = EdgeInsets.symmetric(horizontal: 5, vertical: 0); Box userInfoCache = GStrorage.userInfo; var userInfo; + bool _showLiveItems = false; + late DynamicsController dynamicsController; @override void initState() { super.initState(); userInfo = userInfoCache.get('userInfoCache'); + dynamicsController = Get.find(); } @override @@ -39,95 +40,81 @@ class _UpPanelState extends State { if (widget.upData!.liveUsers != null) { liveList = widget.upData!.liveUsers!.items!; } - return SliverPersistentHeader( - floating: true, - pinned: false, - delegate: _SliverHeaderDelegate( - height: 126, - child: Column( - mainAxisAlignment: MainAxisAlignment.start, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Container( - color: Theme.of(context).colorScheme.background, - padding: const EdgeInsets.only(left: 16, right: 16), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - const Text('最新关注'), - GestureDetector( - onTap: () { - feedBack(); - Get.toNamed('/follow?mid=${userInfo.mid}'); - }, - child: Container( - padding: const EdgeInsets.only(top: 5, bottom: 5), - child: Text( - '查看全部', - style: TextStyle( - color: Theme.of(context).colorScheme.outline), - ), - ), - ), - ], + // return const SizedBox(); + return CustomScrollView( + physics: const AlwaysScrollableScrollPhysics(), + controller: widget.scrollController, + slivers: [ + SliverToBoxAdapter( + child: SizedBox( + height: 45, + child: TextButton( + style: ButtonStyle( + padding: MaterialStateProperty.all(const EdgeInsets.only()), ), - ), - Container( - height: 90, - color: Theme.of(context).colorScheme.background, - child: Row( + child: Column( children: [ - Expanded( - child: ListView( - physics: const BouncingScrollPhysics(), - scrollDirection: Axis.horizontal, - controller: scrollController, - children: [ - const SizedBox(width: 10), - if (liveList.isNotEmpty) ...[ - for (int i = 0; i < liveList.length; i++) ...[ - upItemBuild(liveList[i], i) - ], - VerticalDivider( - indent: 20, - endIndent: 40, - width: 26, - color: Theme.of(context) - .colorScheme - .primary - .withOpacity(0.5), - ), - ], - upItemBuild( - UpItem(face: '', uname: '全部动态', mid: -1), 0), - upItemBuild( - UpItem( - face: userInfo.face, - uname: '我', - mid: userInfo.mid, - ), - 1), - for (int i = 0; i < upList.length; i++) ...[ - upItemBuild(upList[i], i + 2) - ], - const SizedBox(width: 10), - ], + const Spacer(), + const SizedBox(height: 12), + Text( + 'Live(${liveList.length})', + style: const TextStyle( + fontSize: 13, ), + semanticsLabel: + '${_showLiveItems ? '展开' : '收起'}直播中的${liveList.length}个Up', ), + Icon(_showLiveItems ? Icons.expand_less : Icons.expand_more, + size: 12), + const Spacer(), ], ), + onPressed: () { + setState(() { + _showLiveItems = !_showLiveItems; + }); + }, ), - Container( - height: 6, - color: Theme.of(context) - .colorScheme - .onInverseSurface - .withOpacity(0.5), + ), + ), + const SliverToBoxAdapter( + child: SizedBox( + height: 10, + ), + ), + SliverGrid( + gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount( + crossAxisCount: 1, + mainAxisExtent: 76, + crossAxisSpacing: 0, + mainAxisSpacing: 0, ), - ], - )), - ); + delegate: SliverChildListDelegate( + [ + if (_showLiveItems && liveList.isNotEmpty) ...[ + for (int i = 0; i < liveList.length; i++) ...[ + upItemBuild(liveList[i], i) + ], + ], + upItemBuild(UpItem(face: '', uname: '全部动态', mid: -1), 0), + upItemBuild( + UpItem( + face: userInfo.face, + uname: '我', + mid: userInfo.mid, + ), + 1), + for (int i = 0; i < upList.length; i++) ...[ + upItemBuild(upList[i], i + 2) + ], + ], + )), + const SliverToBoxAdapter( + child: SizedBox( + height: 200, + ), + ), + ]); } Widget upItemBuild(data, i) { @@ -137,28 +124,27 @@ class _UpPanelState extends State { feedBack(); if (data.type == 'up') { currentMid = data.mid; - Get.find().mid.value = data.mid; - Get.find().upInfo.value = data; - Get.find().onSelectUp(data.mid); - int liveLen = liveList.length; - int upLen = upList.length; - double itemWidth = contentWidth + itemPadding.horizontal; - double screenWidth = MediaQuery.sizeOf(context).width; - double moveDistance = 0.0; - if (itemWidth * (upList.length + liveList.length) <= screenWidth) { - } else if ((upLen - i - 0.5) * itemWidth > screenWidth / 2) { - moveDistance = - (i + liveLen + 0.5) * itemWidth + 46 - screenWidth / 2; - } else { - moveDistance = (upLen + liveLen) * itemWidth + 46 - screenWidth; - } + // dynamicsController.mid.value = data.mid; + dynamicsController.upInfo.value = data; + dynamicsController.onSelectUp(data.mid); + // int liveLen = liveList.length; + // int upLen = upList.length; + // double itemWidth = contentWidth + itemPadding.horizontal; + // double screenWidth = MediaQuery.sizeOf(context).width; + // double moveDistance = 0.0; + // if (itemWidth * (upList.length + liveList.length) <= screenWidth) { + // } else if ((upLen - i - 0.5) * itemWidth > screenWidth / 2) { + // moveDistance = + // (i + liveLen + 0.5) * itemWidth + 46 - screenWidth / 2; + // } else { + // moveDistance = (upLen + liveLen) * itemWidth + 46 - screenWidth; + // } data.hasUpdate = false; - scrollController.animateTo( - moveDistance, - duration: const Duration(milliseconds: 500), - curve: Curves.easeInOut, - ); - + // scrollController.animateTo( + // moveDistance, + // duration: const Duration(milliseconds: 500), + // curve: Curves.easeInOut, + // ); setState(() {}); } else if (data.type == 'live') { LiveItemModel liveItem = LiveItemModel.fromJson({ @@ -182,63 +168,59 @@ class _UpPanelState extends State { Get.toNamed('/member?mid=${data.mid}', arguments: {'face': data.face, 'heroTag': heroTag}); }, - child: Padding( - padding: itemPadding, - child: AnimatedOpacity( - opacity: isCurrent ? 1 : 0.3, - duration: const Duration(milliseconds: 200), - curve: Curves.easeInOut, - child: Column( - mainAxisSize: MainAxisSize.min, - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Badge( - smallSize: 8, - label: data.type == 'live' ? const Text('Live') : null, - textColor: Theme.of(context).colorScheme.onSecondaryContainer, - alignment: data.type == 'live' - ? AlignmentDirectional.topCenter - : AlignmentDirectional.topEnd, - padding: const EdgeInsets.only(left: 6, right: 6), - isLabelVisible: data.type == 'live' || - (data.type == 'up' && (data.hasUpdate ?? false)), - backgroundColor: data.type == 'live' - ? Theme.of(context).colorScheme.secondaryContainer - : Theme.of(context).colorScheme.primary, - child: data.face != '' - ? NetworkImgLayer( - width: 50, - height: 50, - src: data.face, - type: 'avatar', - ) - : const CircleAvatar( - radius: 25, - backgroundImage: AssetImage( - 'assets/images/noface.jpeg', - ), + child: AnimatedOpacity( + opacity: isCurrent ? 1 : 0.6, + duration: const Duration(milliseconds: 200), + curve: Curves.easeInOut, + child: Column( + mainAxisSize: MainAxisSize.min, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Badge( + smallSize: 8, + label: data.type == 'live' ? const Text('Live') : null, + textColor: Theme.of(context).colorScheme.onSecondaryContainer, + alignment: data.type == 'live' + ? AlignmentDirectional.topCenter + : AlignmentDirectional.topEnd, + padding: const EdgeInsets.only(left: 6, right: 6), + isLabelVisible: data.type == 'live' || + (data.type == 'up' && (data.hasUpdate ?? false)), + backgroundColor: data.type == 'live' + ? Theme.of(context) + .colorScheme + .secondaryContainer + .withOpacity(0.7) + : Theme.of(context).colorScheme.primary, + child: data.face != '' + ? NetworkImgLayer( + width: 38, + height: 38, + src: data.face, + type: 'avatar', + ) + : const CircleAvatar( + radius: 19, + backgroundImage: AssetImage( + 'assets/images/logo/logo_android_2.png', ), - ), - Padding( - padding: const EdgeInsets.only(top: 4), - child: SizedBox( - width: contentWidth, - child: Text( - data.uname, - overflow: TextOverflow.ellipsis, - softWrap: false, - textAlign: TextAlign.center, - style: TextStyle( - color: currentMid == data.mid - ? Theme.of(context).colorScheme.primary - : Theme.of(context).colorScheme.outline, - fontSize: - Theme.of(context).textTheme.labelMedium!.fontSize), - ), - ), - ), - ], - ), + ), + ), + const SizedBox(height: 3), + Text( + data.uname, + overflow: TextOverflow.clip, + maxLines: 2, + softWrap: true, + textAlign: TextAlign.center, + style: TextStyle( + color: currentMid == data.mid + ? Theme.of(context).colorScheme.primary + : Theme.of(context).colorScheme.outline, + height: 1.1, + fontSize: 12.5), + ), + ], ), ), ); @@ -273,35 +255,25 @@ class UpPanelSkeleton extends StatelessWidget { @override Widget build(BuildContext context) { - return ListView.builder( - scrollDirection: Axis.horizontal, - physics: const NeverScrollableScrollPhysics(), - itemCount: 10, - itemBuilder: ((context, index) { - return Padding( - padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 0), - child: Column( - mainAxisSize: MainAxisSize.min, - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Container( - width: 50, - height: 50, - decoration: BoxDecoration( - color: Theme.of(context).colorScheme.onInverseSurface, - borderRadius: BorderRadius.circular(50), - ), - ), - Container( - margin: const EdgeInsets.only(top: 6), - width: 45, - height: 12, - color: Theme.of(context).colorScheme.onInverseSurface, - ), - ], + return Column( + mainAxisSize: MainAxisSize.min, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Container( + width: 50, + height: 50, + decoration: BoxDecoration( + color: Theme.of(context).colorScheme.onInverseSurface, + borderRadius: BorderRadius.circular(50), ), - ); - }), + ), + Container( + margin: const EdgeInsets.only(top: 6), + width: 45, + height: 12, + color: Theme.of(context).colorScheme.onInverseSurface, + ), + ], ); } } diff --git a/lib/pages/dynamics/widgets/video_panel.dart b/lib/pages/dynamics/widgets/video_panel.dart index f163593cd..b19ae40e8 100644 --- a/lib/pages/dynamics/widgets/video_panel.dart +++ b/lib/pages/dynamics/widgets/video_panel.dart @@ -105,8 +105,8 @@ Widget videoSeasonWidget(item, context, type, {floor = 1}) { right: 0, bottom: 0, child: Container( - height: 80, - padding: const EdgeInsets.fromLTRB(12, 0, 10, 10), + height: 70, + padding: const EdgeInsets.fromLTRB(10, 0, 8, 8), clipBehavior: Clip.hardEdge, decoration: BoxDecoration( gradient: const LinearGradient( @@ -139,17 +139,17 @@ Widget videoSeasonWidget(item, context, type, {floor = 1}) { '时长${Utils.durationReadFormat(content.durationText)}', ), if (content.durationText != null) - const SizedBox(width: 10), + const SizedBox(width: 6), Text(content.stat.play + '次围观'), - const SizedBox(width: 10), + const SizedBox(width: 6), Text(content.stat.danmu + '条弹幕') ], ), ), Image.asset( 'assets/images/play.png', - width: 60, - height: 60, + width: 50, + height: 50, ), ], ), diff --git a/lib/pages/fan/controller.dart b/lib/pages/fan/controller.dart index 29aad57f1..ccaec2706 100644 --- a/lib/pages/fan/controller.dart +++ b/lib/pages/fan/controller.dart @@ -29,7 +29,7 @@ class FansController extends GetxController { } Future queryFans(type) async { - if (type == 'init') { + if (type == 'init' || type == 'refresh') { pn = 1; loadingText.value == '加载中...'; } @@ -49,11 +49,14 @@ class FansController extends GetxController { } else if (type == 'onLoad') { fansList.addAll(res['data'].list); } - print(total); + print('fansList: ${fansList.length}, total: $total'); if ((pn == 1 && total < ps) || res['data'].list.isEmpty) { loadingText.value = '没有更多了'; } pn += 1; + if (total > ps && pn == 2) { + queryFans('onLoad'); + } } else { SmartDialog.showToast(res['msg']); } diff --git a/lib/pages/fan/view.dart b/lib/pages/fan/view.dart index 4d8482868..eac766f5e 100644 --- a/lib/pages/fan/view.dart +++ b/lib/pages/fan/view.dart @@ -5,6 +5,8 @@ import 'package:PiliPalaX/common/widgets/http_error.dart'; import 'package:PiliPalaX/common/widgets/no_data.dart'; import 'package:PiliPalaX/models/fans/result.dart'; +import '../../common/constants.dart'; +import '../../utils/grid.dart'; import 'controller.dart'; import 'widgets/fan_item.dart'; @@ -60,60 +62,56 @@ class _FansPageState extends State { ), body: RefreshIndicator( onRefresh: () async => await _fansController.queryFans('init'), - child: FutureBuilder( - future: _futureBuilderFuture, - builder: (context, snapshot) { - if (snapshot.connectionState == ConnectionState.done) { - var data = snapshot.data; - if (data['status']) { - List list = _fansController.fansList; - return Obx( - () => list.isNotEmpty - ? ListView.builder( - controller: scrollController, - itemCount: list.length + 1, - itemBuilder: (BuildContext context, int index) { - if (index == list.length) { - return Container( - height: - MediaQuery.of(context).padding.bottom + 60, - padding: EdgeInsets.only( - bottom: - MediaQuery.of(context).padding.bottom), - child: Center( - child: Obx( - () => Text( - _fansController.loadingText.value, - style: TextStyle( - color: Theme.of(context) - .colorScheme - .outline, - fontSize: 13), - ), - ), - ), - ); - } else { - return fanItem(item: list[index]); - } - }, - ) - : const CustomScrollView( - slivers: [NoData()], + child: CustomScrollView( + physics: const AlwaysScrollableScrollPhysics(), + controller: scrollController, + slivers: [ + FutureBuilder( + future: _futureBuilderFuture, + builder: (context, snapshot) { + if (snapshot.connectionState == ConnectionState.done && + snapshot.data != null) { + var data = snapshot.data; + if (data['status']) { + return Obx(() { + List list = _fansController.fansList; + return list.isNotEmpty + ? SliverGrid( + gridDelegate: + SliverGridDelegateWithMaxCrossAxisExtent( + mainAxisSpacing: StyleString.cardSpace, + crossAxisSpacing: StyleString.safeSpace, + maxCrossAxisExtent: + Grid.maxRowWidth * 2, + mainAxisExtent: 56), + delegate: SliverChildBuilderDelegate( + (BuildContext context, int index) { + return fanItem(item: list[index]); + }, + childCount: list.length, + )) + : const NoData(); + }); + } else { + return HttpError( + errMsg: data['msg'], + fn: () => _fansController.queryFans('init'), + ); + } + } else { + // 骨架屏 + return const SliverToBoxAdapter( + child: SizedBox( + height: 200, + child: Center( + child: CircularProgressIndicator(), ), - ); - } else { - return HttpError( - errMsg: data['msg'], - fn: () => _fansController.queryFans('init'), - ); - } - } else { - // 骨架屏 - return const SizedBox(); - } - }, - ), + ), + ); + } + }, + ), + ]), ), ); } diff --git a/lib/pages/fav/view.dart b/lib/pages/fav/view.dart index e2abb2ee8..25da31c2e 100644 --- a/lib/pages/fav/view.dart +++ b/lib/pages/fav/view.dart @@ -1,3 +1,5 @@ +import 'package:PiliPalaX/common/skeleton/video_card_h.dart'; +import 'package:PiliPalaX/common/skeleton/video_card_v.dart'; import 'package:easy_debounce/easy_throttle.dart'; import 'package:flutter/material.dart'; import 'package:get/get.dart'; @@ -68,17 +70,12 @@ class _FavPageState extends State { physics: const AlwaysScrollableScrollPhysics(), slivers: [ SliverGrid( - gridDelegate: - SliverGridDelegateWithMaxCrossAxisExtent( - mainAxisSpacing: StyleString.cardSpace, - crossAxisSpacing: StyleString.safeSpace, - maxCrossAxisExtent: Grid.maxRowWidth * 2, - mainAxisExtent: Grid.calculateActualWidth( - context, - Grid.maxRowWidth * 2, - StyleString.safeSpace) / - 2.1 / - StyleString.aspectRatio), + gridDelegate: SliverGridDelegateWithExtentAndRatio( + mainAxisSpacing: StyleString.cardSpace, + crossAxisSpacing: StyleString.safeSpace, + maxCrossAxisExtent: Grid.maxRowWidth * 2, + childAspectRatio: StyleString.aspectRatio * 2.3, + mainAxisExtent: 0), delegate: SliverChildBuilderDelegate( childCount: _favController.favFolderData.value.list!.length, @@ -103,7 +100,25 @@ class _FavPageState extends State { } } else { // 骨架屏 - return const Text('请求中'); + return CustomScrollView( + physics: const NeverScrollableScrollPhysics(), + slivers: [ + SliverGrid( + gridDelegate: SliverGridDelegateWithExtentAndRatio( + mainAxisSpacing: StyleString.cardSpace, + crossAxisSpacing: StyleString.safeSpace, + maxCrossAxisExtent: Grid.maxRowWidth * 2, + childAspectRatio: StyleString.aspectRatio * 2.3, + mainAxisExtent: 0), + delegate: SliverChildBuilderDelegate( + (BuildContext context, int index) { + return const VideoCardHSkeleton(); + }, + childCount: 10, + ), + ), + ], + ); } }, ), diff --git a/lib/pages/fav_detail/view.dart b/lib/pages/fav_detail/view.dart index a0a1fca1e..360884f3d 100644 --- a/lib/pages/fav_detail/view.dart +++ b/lib/pages/fav_detail/view.dart @@ -215,16 +215,13 @@ class _FavDetailPageState extends State { ? const SliverToBoxAdapter(child: SizedBox()) : SliverGrid( gridDelegate: - SliverGridDelegateWithMaxCrossAxisExtent( + SliverGridDelegateWithExtentAndRatio( mainAxisSpacing: StyleString.cardSpace, crossAxisSpacing: StyleString.safeSpace, maxCrossAxisExtent: Grid.maxRowWidth * 2, - mainAxisExtent: Grid.calculateActualWidth( - context, - Grid.maxRowWidth * 2, - StyleString.safeSpace) / - 2.1 / - StyleString.aspectRatio), + childAspectRatio: + StyleString.aspectRatio * 2.3, + mainAxisExtent: 0), delegate: SliverChildBuilderDelegate((context, index) { return FavVideoCardH( @@ -244,7 +241,13 @@ class _FavDetailPageState extends State { } } else { // 骨架屏 - return SliverList( + return SliverGrid( + gridDelegate: SliverGridDelegateWithExtentAndRatio( + mainAxisSpacing: StyleString.cardSpace, + crossAxisSpacing: StyleString.safeSpace, + maxCrossAxisExtent: Grid.maxRowWidth * 2, + childAspectRatio: StyleString.aspectRatio * 2.3, + mainAxisExtent: 0), delegate: SliverChildBuilderDelegate((context, index) { return const VideoCardHSkeleton(); }, childCount: 10), diff --git a/lib/pages/history/controller.dart b/lib/pages/history/controller.dart index 8324c028c..55dacc79f 100644 --- a/lib/pages/history/controller.dart +++ b/lib/pages/history/controller.dart @@ -51,18 +51,17 @@ class HistoryController extends GetxController { } // 暂停观看历史 - Future onPauseHistory() async { - SmartDialog.show( - useSystem: true, - animationType: SmartAnimationType.centerFade_otherSlide, - builder: (BuildContext context) { + Future onPauseHistory(BuildContext context) async { + await showDialog( + context: context, + builder: (context) { return AlertDialog( title: const Text('提示'), content: Text(!pauseStatus.value ? '啊叻?你要暂停历史记录功能吗?' : '啊叻?要恢复历史记录功能吗?'), actions: [ TextButton( - onPressed: () => SmartDialog.dismiss(), + onPressed: () => Get.back(), child: const Text('取消')), TextButton( onPressed: () async { @@ -75,7 +74,7 @@ class HistoryController extends GetxController { pauseStatus.value = !pauseStatus.value; localCache.put(LocalCacheKey.historyPause, pauseStatus.value); } - SmartDialog.dismiss(); + Get.back(); }, child: Text(!pauseStatus.value ? '确认暂停' : '确认恢复'), ) @@ -97,17 +96,16 @@ class HistoryController extends GetxController { } // 清空观看历史 - Future onClearHistory() async { - SmartDialog.show( - useSystem: true, - animationType: SmartAnimationType.centerFade_otherSlide, - builder: (BuildContext context) { + Future onClearHistory(BuildContext context) async { + await showDialog( + context: context, + builder: (context) { return AlertDialog( title: const Text('提示'), content: const Text('啊叻?你要清空历史记录功能吗?'), actions: [ TextButton( - onPressed: () => SmartDialog.dismiss(), + onPressed: () => Get.back(), child: const Text('取消')), TextButton( onPressed: () async { @@ -117,7 +115,7 @@ class HistoryController extends GetxController { if (res.data['code'] == 0) { SmartDialog.showToast('清空观看历史'); } - SmartDialog.dismiss(); + Get.back(); historyList.clear(); }, child: const Text('确认清空'), @@ -158,17 +156,16 @@ class HistoryController extends GetxController { } // 删除选中的记录 - Future onDelCheckedHistory() async { - SmartDialog.show( - useSystem: true, - animationType: SmartAnimationType.centerFade_otherSlide, - builder: (BuildContext context) { + Future onDelCheckedHistory(BuildContext context) async { + await showDialog( + context: context, + builder: (context) { return AlertDialog( title: const Text('提示'), content: const Text('确认删除所选历史记录吗?'), actions: [ TextButton( - onPressed: () => SmartDialog.dismiss(), + onPressed: () => Get.back(), child: Text( '取消', style: TextStyle( @@ -179,7 +176,7 @@ class HistoryController extends GetxController { TextButton( onPressed: () async { /// TODO 优化 - await SmartDialog.dismiss(); + Get.back(); SmartDialog.showLoading(msg: '请求中'); List result = historyList.where((e) => e.checked!).toList(); diff --git a/lib/pages/history/view.dart b/lib/pages/history/view.dart index f886100a4..a921f23e8 100644 --- a/lib/pages/history/view.dart +++ b/lib/pages/history/view.dart @@ -87,10 +87,10 @@ class _HistoryPageState extends State { // 处理菜单项选择的逻辑 switch (type) { case 'pause': - _historyController.onPauseHistory(); + _historyController.onPauseHistory(context); break; case 'clear': - _historyController.onClearHistory(); + _historyController.onClearHistory(context); break; case 'del': _historyController.onDelHistory(); @@ -162,7 +162,7 @@ class _HistoryPageState extends State { child: const Text('全选'), ), TextButton( - onPressed: () => _historyController.onDelCheckedHistory(), + onPressed: () => _historyController.onDelCheckedHistory(context), child: Text( '删除', style: TextStyle(color: Theme.of(context).colorScheme.error), @@ -194,16 +194,13 @@ class _HistoryPageState extends State { () => _historyController.historyList.isNotEmpty ? SliverGrid( gridDelegate: - SliverGridDelegateWithMaxCrossAxisExtent( + SliverGridDelegateWithExtentAndRatio( mainAxisSpacing: StyleString.cardSpace, crossAxisSpacing: StyleString.safeSpace, maxCrossAxisExtent: Grid.maxRowWidth * 2, - mainAxisExtent: Grid.calculateActualWidth( - context, - Grid.maxRowWidth * 2, - StyleString.safeSpace) / - 2.1 / - StyleString.aspectRatio), + childAspectRatio: + StyleString.aspectRatio * 2.3, + mainAxisExtent: 0), delegate: SliverChildBuilderDelegate( (context, index) { return HistoryItem( @@ -231,7 +228,13 @@ class _HistoryPageState extends State { } } else { // 骨架屏 - return SliverList( + return SliverGrid( + gridDelegate: SliverGridDelegateWithExtentAndRatio( + mainAxisSpacing: StyleString.cardSpace, + crossAxisSpacing: StyleString.safeSpace, + maxCrossAxisExtent: Grid.maxRowWidth * 2, + childAspectRatio: StyleString.aspectRatio * 2.3, + mainAxisExtent: 0), delegate: SliverChildBuilderDelegate((context, index) { return const VideoCardHSkeleton(); }, childCount: 10), diff --git a/lib/pages/history_search/view.dart b/lib/pages/history_search/view.dart index efb602fa5..b63b0ee4c 100644 --- a/lib/pages/history_search/view.dart +++ b/lib/pages/history_search/view.dart @@ -86,14 +86,12 @@ class _HistorySearchPageState extends State { controller: scrollController, slivers: [ Obx(() => SliverGrid( - gridDelegate: SliverGridDelegateWithMaxCrossAxisExtent( + gridDelegate: SliverGridDelegateWithExtentAndRatio( mainAxisSpacing: StyleString.cardSpace, crossAxisSpacing: StyleString.safeSpace, maxCrossAxisExtent: Grid.maxRowWidth * 2, - mainAxisExtent: Grid.calculateActualWidth(context, - Grid.maxRowWidth * 2, StyleString.safeSpace) / - 2.1 / - StyleString.aspectRatio), + childAspectRatio: StyleString.aspectRatio * 2.3, + mainAxisExtent: 0), delegate: SliverChildBuilderDelegate( (context, index) { return HistoryItem( diff --git a/lib/pages/home/controller.dart b/lib/pages/home/controller.dart index ea0c03de5..a5956774c 100644 --- a/lib/pages/home/controller.dart +++ b/lib/pages/home/controller.dart @@ -67,8 +67,11 @@ class HomeController extends GetxController with GetTickerProviderStateMixin { void setTabConfig() async { defaultTabs = [...tabsConfig]; - tabbarSort = settingStorage.get(SettingBoxKey.tabbarSort, - defaultValue: ['live', 'rcmd', 'hot', 'bangumi']).map((i) => i.toString()).toList(); + tabbarSort = settingStorage + .get(SettingBoxKey.tabbarSort, + defaultValue: ['live', 'rcmd', 'hot', 'rank', 'bangumi']) + .map((i) => i.toString()) + .toList(); defaultTabs.retainWhere( (item) => tabbarSort.contains((item['type'] as TabType).id)); defaultTabs.sort((a, b) => tabbarSort diff --git a/lib/pages/home/view.dart b/lib/pages/home/view.dart index da460d4f3..3271a2aae 100644 --- a/lib/pages/home/view.dart +++ b/lib/pages/home/view.dart @@ -46,104 +46,78 @@ class _HomePageState extends State @override Widget build(BuildContext context) { super.build(context); - Brightness currentBrightness = MediaQuery.of(context).platformBrightness; - // 设置状态栏图标的亮度 - if (_homeController.enableGradientBg) { - SystemChrome.setSystemUIOverlayStyle(SystemUiOverlayStyle( - statusBarIconBrightness: currentBrightness == Brightness.light - ? Brightness.dark - : Brightness.light, - )); - } + // Brightness currentBrightness = MediaQuery.of(context).platformBrightness; + // // 设置状态栏图标的亮度 + // if (_homeController.enableGradientBg) { + // SystemChrome.setSystemUIOverlayStyle(SystemUiOverlayStyle( + // statusBarIconBrightness: currentBrightness == Brightness.light + // ? Brightness.dark + // : Brightness.light, + // )); + // } return Scaffold( extendBody: true, extendBodyBehindAppBar: true, - appBar: _homeController.enableGradientBg - ? null - : AppBar(toolbarHeight: 0, elevation: 0), - body: Stack( + backgroundColor: Colors.transparent, + appBar: AppBar( + toolbarHeight: 0, + elevation: 0, + backgroundColor: Colors.transparent, + systemOverlayStyle: SystemUiOverlayStyle( + statusBarIconBrightness: + MediaQuery.of(context).platformBrightness == Brightness.dark + ? Brightness.light + : Brightness.dark, + ), + ), + body: Column( children: [ - // gradient background - if (_homeController.enableGradientBg) ...[ - Align( - alignment: Alignment.topLeft, - child: Opacity( - opacity: 0.6, - child: Container( - width: MediaQuery.of(context).size.width, - height: MediaQuery.of(context).size.height, - decoration: BoxDecoration( - gradient: LinearGradient( - colors: [ - Theme.of(context) - .colorScheme - .primary - .withOpacity(0.9), - Theme.of(context) - .colorScheme - .primary - .withOpacity(0.5), - Theme.of(context).colorScheme.surface - ], - begin: Alignment.topLeft, - end: Alignment.bottomRight, - stops: const [0, 0.0034, 0.34]), - ), - ), - ), - ), - ], - Column( - children: [ - CustomAppBar( - stream: _homeController.hideSearchBar - ? stream - : StreamController.broadcast().stream, - ctr: _homeController, - callback: showUserBottomSheet, - ), - if (_homeController.tabs.length > 1) ...[ - if (_homeController.enableGradientBg) ...[ - const CustomTabs(), - ] else ...[ - const SizedBox(height: 4), - SizedBox( - width: double.infinity, - height: 42, - child: Align( - alignment: Alignment.center, - child: TabBar( - controller: _homeController.tabController, - tabs: [ - for (var i in _homeController.tabs) - Tab(text: i['label']) - ], - isScrollable: true, - dividerColor: Colors.transparent, - enableFeedback: true, - splashBorderRadius: BorderRadius.circular(10), - tabAlignment: TabAlignment.center, - onTap: (value) { - feedBack(); - if (_homeController.initialIndex.value == value) { - _homeController.tabsCtrList[value]().animateToTop(); - } - _homeController.initialIndex.value = value; - }, - ), - ), + CustomAppBar( + stream: _homeController.hideSearchBar + ? stream + : StreamController.broadcast().stream, + ctr: _homeController, + callback: showUserBottomSheet, + ), + if (_homeController.tabs.length > 1) ...[ + if (_homeController.enableGradientBg) ...[ + const CustomTabs(), + ] else ...[ + const SizedBox(height: 4), + SizedBox( + width: double.infinity, + height: 42, + child: Align( + alignment: Alignment.center, + child: TabBar( + controller: _homeController.tabController, + tabs: [ + for (var i in _homeController.tabs) Tab(text: i['label']) + ], + isScrollable: true, + dividerColor: Colors.transparent, + enableFeedback: true, + splashBorderRadius: BorderRadius.circular(10), + tabAlignment: TabAlignment.center, + onTap: (value) { + feedBack(); + if (_homeController.initialIndex.value == value) { + _homeController.tabsCtrList[value]().animateToTop(); + } + _homeController.initialIndex.value = value; + }, ), - ], - ] else ...[ - const SizedBox(height: 6), - ], - Expanded( - child: TabBarView( - controller: _homeController.tabController, - children: _homeController.tabsPageList, ), ), ], + ] else ...[ + const SizedBox(height: 6), + ], + Expanded( + child: TabBarView( + controller: _homeController.tabController, + children: _homeController.tabsPageList, + ), ), ], ), diff --git a/lib/pages/hot/controller.dart b/lib/pages/hot/controller.dart index 824ae7171..cab8646c3 100644 --- a/lib/pages/hot/controller.dart +++ b/lib/pages/hot/controller.dart @@ -31,6 +31,7 @@ class HotController extends GetxController { videoList.addAll(res['data']); } _currentPage += 1; + if (_currentPage == 2) queryHotFeed('onLoad'); } isLoadingMore = false; return res; diff --git a/lib/pages/hot/view.dart b/lib/pages/hot/view.dart index 4bfd9518e..c28c23412 100644 --- a/lib/pages/hot/view.dart +++ b/lib/pages/hot/view.dart @@ -82,8 +82,8 @@ class _HotPageState extends State with AutomaticKeepAliveClientMixin { slivers: [ SliverPadding( // 单列布局 EdgeInsets.zero - padding: - const EdgeInsets.fromLTRB(0, StyleString.safeSpace - 5, 0, 0), + padding: const EdgeInsets.fromLTRB( + StyleString.safeSpace, StyleString.safeSpace - 5, 0, 0), sliver: FutureBuilder( future: _futureBuilderFuture, builder: (context, snapshot) { @@ -92,19 +92,12 @@ class _HotPageState extends State with AutomaticKeepAliveClientMixin { if (data['status']) { return Obx( () => SliverGrid( - gridDelegate: SliverGridDelegateWithMaxCrossAxisExtent( - // 行间距 - mainAxisSpacing: StyleString.cardSpace, - // 列间距 - crossAxisSpacing: StyleString.cardSpace, - // 最大宽度 + gridDelegate: SliverGridDelegateWithExtentAndRatio( + mainAxisSpacing: StyleString.safeSpace, + crossAxisSpacing: StyleString.safeSpace, maxCrossAxisExtent: Grid.maxRowWidth * 2, - mainAxisExtent: Grid.calculateActualWidth( - context, - Grid.maxRowWidth * 2, - StyleString.safeSpace) / - 2.1 / - StyleString.aspectRatio), + childAspectRatio: StyleString.aspectRatio * 2.3, + mainAxisExtent: 0), delegate: SliverChildBuilderDelegate((context, index) { return VideoCardH( videoItem: _hotController.videoList[index], @@ -135,7 +128,12 @@ class _HotPageState extends State with AutomaticKeepAliveClientMixin { } } else { // 骨架屏 - return SliverList( + return SliverGrid( + gridDelegate: SliverGridDelegateWithMaxCrossAxisExtent( + mainAxisSpacing: StyleString.cardSpace, + crossAxisSpacing: StyleString.cardSpace, + maxCrossAxisExtent: Grid.maxRowWidth * 2, + childAspectRatio: StyleString.aspectRatio * 2.3), delegate: SliverChildBuilderDelegate((context, index) { return const VideoCardHSkeleton(); }, childCount: 10), diff --git a/lib/pages/later/controller.dart b/lib/pages/later/controller.dart index 46ed360aa..bf3d070e2 100644 --- a/lib/pages/later/controller.dart +++ b/lib/pages/later/controller.dart @@ -23,18 +23,17 @@ class LaterController extends GetxController { return res; } - Future toViewDel({int? aid}) async { - SmartDialog.show( - useSystem: true, - animationType: SmartAnimationType.centerFade_otherSlide, - builder: (BuildContext context) { + Future toViewDel(BuildContext context, {int? aid}) async { + await showDialog( + context: context, + builder: (context) { return AlertDialog( title: const Text('提示'), content: Text( aid != null ? '即将移除该视频,确定是否移除' : '即将删除所有已观看视频,此操作不可恢复。确定是否删除?'), actions: [ TextButton( - onPressed: () => SmartDialog.dismiss(), + onPressed: () => Get.back(), child: Text( '取消', style: TextStyle(color: Theme.of(context).colorScheme.outline), @@ -51,7 +50,7 @@ class LaterController extends GetxController { queryLaterList(); } } - SmartDialog.dismiss(); + Get.back(); SmartDialog.showToast(res['msg']); }, child: Text(aid != null ? '确认移除' : '确认删除'), @@ -63,17 +62,16 @@ class LaterController extends GetxController { } // 一键清空 - Future toViewClear() async { - SmartDialog.show( - useSystem: true, - animationType: SmartAnimationType.centerFade_otherSlide, - builder: (BuildContext context) { + Future toViewClear(BuildContext context) async { + await showDialog( + context: context, + builder: (context) { return AlertDialog( title: const Text('清空确认'), content: const Text('确定要清空你的稍后再看列表吗?'), actions: [ TextButton( - onPressed: () => SmartDialog.dismiss(), + onPressed: () => Get.back(), child: Text( '取消', style: TextStyle(color: Theme.of(context).colorScheme.outline), @@ -85,7 +83,7 @@ class LaterController extends GetxController { if (res['status']) { laterList.clear(); } - SmartDialog.dismiss(); + Get.back(); SmartDialog.showToast(res['msg']); }, child: const Text('确认'), diff --git a/lib/pages/later/view.dart b/lib/pages/later/view.dart index af742aaa7..9a8049263 100644 --- a/lib/pages/later/view.dart +++ b/lib/pages/later/view.dart @@ -47,7 +47,7 @@ class _LaterPageState extends State { Obx( () => _laterController.laterList.isNotEmpty ? TextButton( - onPressed: () => _laterController.toViewDel(), + onPressed: () => _laterController.toViewDel(context), child: const Text('移除已看'), ) : const SizedBox(), @@ -56,7 +56,7 @@ class _LaterPageState extends State { () => _laterController.laterList.isNotEmpty ? IconButton( tooltip: '一键清空', - onPressed: () => _laterController.toViewClear(), + onPressed: () => _laterController.toViewClear(context), icon: Icon( Icons.clear_all_outlined, size: 21, @@ -72,61 +72,73 @@ class _LaterPageState extends State { physics: const AlwaysScrollableScrollPhysics(), controller: _laterController.scrollController, slivers: [ - FutureBuilder( - future: _futureBuilderFuture, - builder: (context, snapshot) { - if (snapshot.connectionState == ConnectionState.done) { - Map data = snapshot.data as Map; - if (data['status']) { - return Obx( - () => _laterController.laterList.isNotEmpty && - !_laterController.isLoading.value - ? SliverGrid( - gridDelegate: - SliverGridDelegateWithMaxCrossAxisExtent( - mainAxisSpacing: StyleString.cardSpace, - crossAxisSpacing: StyleString.safeSpace, - maxCrossAxisExtent: Grid.maxRowWidth * 2, - mainAxisExtent: Grid.calculateActualWidth( - context, + SliverPadding( + padding: + const EdgeInsets.symmetric(horizontal: StyleString.safeSpace), + sliver: FutureBuilder( + future: _futureBuilderFuture, + builder: (context, snapshot) { + if (snapshot.connectionState == ConnectionState.done) { + Map data = snapshot.data as Map; + if (data['status']) { + return Obx( + () => _laterController.laterList.isNotEmpty && + !_laterController.isLoading.value + ? SliverGrid( + gridDelegate: + SliverGridDelegateWithExtentAndRatio( + mainAxisSpacing: StyleString.safeSpace, + crossAxisSpacing: StyleString.safeSpace, + maxCrossAxisExtent: Grid.maxRowWidth * 2, - StyleString.safeSpace) / - 2.1 / - StyleString.aspectRatio), - delegate: - SliverChildBuilderDelegate((context, index) { - var videoItem = _laterController.laterList[index]; - return VideoCardH( - videoItem: videoItem, - source: 'later', - longPress: () => _laterController.toViewDel( - aid: videoItem.aid)); - }, childCount: _laterController.laterList.length), - ) - : _laterController.isLoading.value - ? const SliverToBoxAdapter( - child: Center(child: Text('加载中')), + childAspectRatio: + StyleString.aspectRatio * 2.3, + mainAxisExtent: 0), + delegate: SliverChildBuilderDelegate( + (context, index) { + var videoItem = + _laterController.laterList[index]; + return VideoCardH( + videoItem: videoItem, + source: 'later', + longPress: () => + _laterController.toViewDel(context, + aid: videoItem.aid)); + }, + childCount: + _laterController.laterList.length), ) - : const NoData(), - ); - } else { - return HttpError( - errMsg: data['msg'], - fn: () => setState(() { - _futureBuilderFuture = _laterController.queryLaterList(); - }), - ); - } - } else { - // 骨架屏 - return SliverList( - delegate: SliverChildBuilderDelegate((context, index) { - return const VideoCardHSkeleton(); - }, childCount: 10), - ); - } - }, - ), + : _laterController.isLoading.value + ? const SliverToBoxAdapter( + child: Center(child: Text('加载中')), + ) + : const NoData(), + ); + } else { + return HttpError( + errMsg: data['msg'], + fn: () => setState(() { + _futureBuilderFuture = + _laterController.queryLaterList(); + }), + ); + } + } else { + // 骨架屏 + return SliverGrid( + gridDelegate: SliverGridDelegateWithExtentAndRatio( + mainAxisSpacing: StyleString.safeSpace, + crossAxisSpacing: StyleString.safeSpace, + maxCrossAxisExtent: Grid.maxRowWidth * 2, + childAspectRatio: StyleString.aspectRatio * 2.3, + mainAxisExtent: 0), + delegate: SliverChildBuilderDelegate((context, index) { + return const VideoCardHSkeleton(); + }, childCount: 10), + ); + } + }, + )), SliverToBoxAdapter( child: SizedBox( height: MediaQuery.of(context).padding.bottom + 10, diff --git a/lib/pages/live/view.dart b/lib/pages/live/view.dart index f1104d4dc..540ec59e6 100644 --- a/lib/pages/live/view.dart +++ b/lib/pages/live/view.dart @@ -141,15 +141,12 @@ class _LivePageState extends State Widget contentGrid(ctr, liveList) { return SliverGrid( - gridDelegate: SliverGridDelegateWithMaxCrossAxisExtent( - // 行间距 + gridDelegate: SliverGridDelegateWithExtentAndRatio( mainAxisSpacing: StyleString.cardSpace, - // 列间距 crossAxisSpacing: StyleString.cardSpace, - // 最大宽度 maxCrossAxisExtent: Grid.maxRowWidth, - mainAxisExtent: Grid.calculateActualWidth(context, Grid.maxRowWidth, StyleString.safeSpace) / StyleString.aspectRatio+ - MediaQuery.textScalerOf(context).scale(80), + childAspectRatio: StyleString.aspectRatio, + mainAxisExtent: MediaQuery.textScalerOf(context).scale(80), ), delegate: SliverChildBuilderDelegate( (BuildContext context, int index) { diff --git a/lib/pages/login/controller.dart b/lib/pages/login/controller.dart index 1527199ec..b1fce8075 100644 --- a/lib/pages/login/controller.dart +++ b/lib/pages/login/controller.dart @@ -111,6 +111,7 @@ class LoginPageController extends GetxController { Future getCaptcha(oncall) async { SmartDialog.showLoading(msg: '请求中...'); var result = await LoginHttp.queryCaptcha(); + SmartDialog.dismiss(); if (result['status']) { CaptchaDataModel captchaData = result['data']; var registerData = Gt3RegisterData( @@ -119,7 +120,6 @@ class LoginPageController extends GetxController { success: true, ); captcha.addEventHandler(onShow: (Map message) async { - SmartDialog.dismiss(); }, onClose: (Map message) async { SmartDialog.showToast('关闭验证'); }, onResult: (Map message) async { diff --git a/lib/pages/main/controller.dart b/lib/pages/main/controller.dart index 7352e6628..b856e285a 100644 --- a/lib/pages/main/controller.dart +++ b/lib/pages/main/controller.dart @@ -12,13 +12,14 @@ import 'package:PiliPalaX/pages/media/index.dart'; import 'package:PiliPalaX/pages/rank/index.dart'; import 'package:PiliPalaX/utils/storage.dart'; import 'package:PiliPalaX/utils/utils.dart'; +import 'package:path/path.dart'; import '../../models/common/dynamic_badge_mode.dart'; import '../../models/common/nav_bar_config.dart'; class MainController extends GetxController { List pages = [ const HomePage(), - const RankPage(), + // const RankPage(), const DynamicsPage(), const MediaPage(), ]; diff --git a/lib/pages/main/view.dart b/lib/pages/main/view.dart index 99d1df6bc..af6e0220b 100644 --- a/lib/pages/main/view.dart +++ b/lib/pages/main/view.dart @@ -1,14 +1,13 @@ import 'dart:async'; +import 'package:PiliPalaX/common/constants.dart'; import 'package:flutter/material.dart'; -import 'package:flutter_adaptive_scaffold/flutter_adaptive_scaffold.dart'; import 'package:get/get.dart'; import 'package:hive/hive.dart'; import 'package:PiliPalaX/models/common/dynamic_badge_mode.dart'; import 'package:PiliPalaX/pages/dynamics/index.dart'; import 'package:PiliPalaX/pages/home/index.dart'; import 'package:PiliPalaX/pages/media/index.dart'; -import 'package:PiliPalaX/pages/rank/index.dart'; import 'package:PiliPalaX/utils/event_bus.dart'; import 'package:PiliPalaX/utils/feed_back.dart'; import 'package:PiliPalaX/utils/storage.dart'; @@ -24,7 +23,6 @@ class MainApp extends StatefulWidget { class _MainAppState extends State with SingleTickerProviderStateMixin { final MainController _mainController = Get.put(MainController()); final HomeController _homeController = Get.put(HomeController()); - final RankController _rankController = Get.put(RankController()); final DynamicsController _dynamicController = Get.put(DynamicsController()); final MediaController _mediaController = Get.put(MediaController()); @@ -32,6 +30,7 @@ class _MainAppState extends State with SingleTickerProviderStateMixin { Box setting = GStrorage.setting; late bool enableMYBar; late bool adaptiveNavBar; + late bool enableGradientBg; @override void initState() { @@ -42,6 +41,8 @@ class _MainAppState extends State with SingleTickerProviderStateMixin { enableMYBar = setting.get(SettingBoxKey.enableMYBar, defaultValue: true); adaptiveNavBar = setting.get(SettingBoxKey.adaptiveNavBar, defaultValue: false); + enableGradientBg = setting.get(SettingBoxKey.enableGradientBg, + defaultValue: true); } void setIndex(int value) async { @@ -63,20 +64,20 @@ class _MainAppState extends State with SingleTickerProviderStateMixin { _homeController.flag = false; } - if (currentPage is RankPage) { - if (_rankController.flag) { - // 单击返回顶部 双击并刷新 - if (DateTime.now().millisecondsSinceEpoch - _lastSelectTime! < 500) { - _rankController.onRefresh(); - } else { - _rankController.animateToTop(); - } - _lastSelectTime = DateTime.now().millisecondsSinceEpoch; - } - _rankController.flag = true; - } else { - _rankController.flag = false; - } + // if (currentPage is RankPage) { + // if (_rankController.flag) { + // // 单击返回顶部 双击并刷新 + // if (DateTime.now().millisecondsSinceEpoch - _lastSelectTime! < 500) { + // _rankController.onRefresh(); + // } else { + // _rankController.animateToTop(); + // } + // _lastSelectTime = DateTime.now().millisecondsSinceEpoch; + // } + // _rankController.flag = true; + // } else { + // _rankController.flag = false; + // } if (currentPage is DynamicsPage) { if (_dynamicController.flag) { @@ -113,55 +114,100 @@ class _MainAppState extends State with SingleTickerProviderStateMixin { onPopInvoked: (bool didPop) async { _mainController.onBackPressed(context); }, - child: adaptiveNavBar - ? AdaptiveScaffold( - body: (_) => PageView( - physics: const NeverScrollableScrollPhysics(), - controller: _mainController.pageController, - onPageChanged: (index) { - _mainController.selectedIndex = index; - setState(() {}); - }, - children: _mainController.pages, + child: Scaffold( + extendBody: true, + body: Stack(children: [ + // gradient background + if (enableGradientBg) ...[ + Align( + alignment: Alignment.topLeft, + child: Opacity( + opacity: 0.6, + child: Container( + width: MediaQuery.of(context).size.width, + height: MediaQuery.of(context).size.height, + decoration: BoxDecoration( + gradient: LinearGradient( + colors: [ + Theme.of(context) + .colorScheme + .primary + .withOpacity(0.9), + Theme.of(context) + .colorScheme + .primary + .withOpacity(0.5), + Theme.of(context).colorScheme.surface + ], + begin: Alignment.topLeft, + end: Alignment.bottomRight, + stops: const [0, 0.0034, 0.34]), ), - destinations: _mainController.navigationBars - .map((e) => NavigationDestination( - icon: Badge( - label: _mainController.dynamicBadgeType == - DynamicBadgeMode.number - ? Text(e['count'].toString()) - : null, - padding: const EdgeInsets.fromLTRB(2, 0, 2, 0), - isLabelVisible: _mainController.dynamicBadgeType != - DynamicBadgeMode.hidden && - e['count'] > 0, - child: e['icon'], - backgroundColor: - Theme.of(context).colorScheme.primary, - textColor: - Theme.of(context).colorScheme.onInverseSurface, - ), - selectedIcon: e['selectIcon'], - label: e['label'], - )) - .toList(), - onSelectedIndexChange: (value) => setIndex(value), - selectedIndex: _mainController.selectedIndex, - extendedNavigationRailWidth: 180, - transitionDuration: const Duration(milliseconds: 500), - useDrawer: true) - : Scaffold( - extendBody: true, - body: PageView( - physics: const NeverScrollableScrollPhysics(), - controller: _mainController.pageController, - onPageChanged: (index) { - _mainController.selectedIndex = index; - setState(() {}); - }, - children: _mainController.pages, + ), ), - bottomNavigationBar: StreamBuilder( + ), + ], + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + if (adaptiveNavBar) ...[ + SizedBox( + width: 55, + child: NavigationRail( + groupAlignment: 0.0, + minWidth: 40.0, + backgroundColor: Theme.of(context) + .colorScheme + .surface + .withOpacity(0.2), + selectedIndex: _mainController.selectedIndex, + onDestinationSelected: (value) => setIndex(value), + labelType: NavigationRailLabelType.none, + destinations: _mainController.navigationBars + .map((e) => NavigationRailDestination( + icon: Badge( + label: _mainController.dynamicBadgeType == + DynamicBadgeMode.number + ? Text(e['count'].toString()) + : null, + padding: + const EdgeInsets.fromLTRB(2, 0, 2, 0), + isLabelVisible: + _mainController.dynamicBadgeType != + DynamicBadgeMode.hidden && + e['count'] > 0, + child: e['icon'], + backgroundColor: + Theme.of(context).colorScheme.primary, + textColor: Theme.of(context) + .colorScheme + .onInverseSurface, + ), + selectedIcon: e['selectIcon'], + label: Text(e['label']), + padding: + const EdgeInsets.symmetric(vertical: 6), + )) + .toList(), + )), + ], + Expanded( + child: PageView( + physics: const NeverScrollableScrollPhysics(), + controller: _mainController.pageController, + onPageChanged: (index) { + _mainController.selectedIndex = index; + setState(() {}); + }, + children: _mainController.pages, + ), + ), + ], + ) + ]), + bottomNavigationBar: adaptiveNavBar + ? null + : StreamBuilder( stream: _mainController.hideTabBar ? _mainController.bottomBarStream.stream : StreamController.broadcast().stream, @@ -243,7 +289,7 @@ class _MainAppState extends State with SingleTickerProviderStateMixin { ); }, ), - ), + ), ); } } diff --git a/lib/pages/media/view.dart b/lib/pages/media/view.dart index 858447799..2bb48c429 100644 --- a/lib/pages/media/view.dart +++ b/lib/pages/media/view.dart @@ -62,34 +62,35 @@ class _MediaPageState extends State super.build(context); Color primary = Theme.of(context).colorScheme.primary; return Scaffold( - appBar: AppBar(toolbarHeight: 30), + backgroundColor: Colors.transparent, + appBar: AppBar(toolbarHeight: 30, backgroundColor: Colors.transparent), body: SingleChildScrollView( controller: mediaController.scrollController, child: Column( children: [ ListTile( - leading: null, - title: Padding( - padding: const EdgeInsets.only(left: 20), - child: Text( - '媒体库', - style: TextStyle( - fontSize: Theme.of(context).textTheme.titleLarge!.fontSize, - fontWeight: FontWeight.bold, + leading: null, + title: Padding( + padding: const EdgeInsets.only(left: 20), + child: Text( + '媒体库', + style: TextStyle( + fontSize: + Theme.of(context).textTheme.titleLarge!.fontSize, + fontWeight: FontWeight.bold, + ), ), ), - ), - trailing: IconButton( - tooltip: '设置', - onPressed: () { - Get.toNamed('/setting'); - }, - icon: const Icon( - Icons.settings_outlined, - size: 20, - ), - ) - ), + trailing: IconButton( + tooltip: '设置', + onPressed: () { + Get.toNamed('/setting'); + }, + icon: const Icon( + Icons.settings_outlined, + size: 20, + ), + )), for (var i in mediaController.list) ...[ ListTile( onTap: () => i['onTap'](), @@ -145,14 +146,19 @@ class _MediaPageState extends State ), if (mediaController.favFolderData.value.count != null) TextSpan( - text: mediaController.favFolderData.value.count - .toString(), + text: "${mediaController.favFolderData.value.count} ", style: TextStyle( fontSize: Theme.of(context).textTheme.titleSmall!.fontSize, color: Theme.of(context).colorScheme.primary, ), ), + WidgetSpan( + child: Icon( + Icons.arrow_forward_ios, + size: 18, + color: Theme.of(context).colorScheme.primary, + )), ], ), ), @@ -275,10 +281,16 @@ class FavFolderItem extends StatelessWidget { clipBehavior: Clip.hardEdge, decoration: BoxDecoration( borderRadius: BorderRadius.circular(12), - color: Theme.of(context).colorScheme.onInverseSurface, + color: Theme.of(context) + .colorScheme + .onInverseSurface + .withOpacity(0.4), boxShadow: [ BoxShadow( - color: Theme.of(context).colorScheme.onInverseSurface, + color: Theme.of(context) + .colorScheme + .onInverseSurface + .withOpacity(0.4), offset: const Offset(4, -12), // 阴影与容器的距离 blurRadius: 0.0, // 高斯的标准偏差与盒子的形状卷积。 spreadRadius: 0.0, // 在应用模糊之前,框应该膨胀的量。 diff --git a/lib/pages/member/controller.dart b/lib/pages/member/controller.dart index ae05ad57f..4b10de223 100644 --- a/lib/pages/member/controller.dart +++ b/lib/pages/member/controller.dart @@ -68,7 +68,7 @@ class MemberController extends GetxController { } // 关注/取关up - Future actionRelationMod() async { + Future actionRelationMod(BuildContext context) async { if (userInfo == null) { SmartDialog.showToast('账号未登录'); return; @@ -78,19 +78,18 @@ class MemberController extends GetxController { return; } if (attribute.value == 128) { - blockUser(); + blockUser(context); return; } - SmartDialog.show( - useSystem: true, - animationType: SmartAnimationType.centerFade_otherSlide, - builder: (BuildContext context) { + await showDialog( + context: context, + builder: (context) { return AlertDialog( title: const Text('提示'), content: Text(memberInfo.value.isFollowed! ? '取消关注UP主?' : '关注UP主?'), actions: [ TextButton( - onPressed: () => SmartDialog.dismiss(), + onPressed: () => Get.back(), child: Text( '点错了', style: TextStyle(color: Theme.of(context).colorScheme.outline), @@ -98,6 +97,7 @@ class MemberController extends GetxController { ), TextButton( onPressed: () async { + Get.back(); await VideoHttp.relationMod( mid: mid, act: memberInfo.value.isFollowed! ? 2 : 1, @@ -105,7 +105,6 @@ class MemberController extends GetxController { ); memberInfo.value.isFollowed = !memberInfo.value.isFollowed!; relationSearch(); - SmartDialog.dismiss(); memberInfo.update((val) {}); }, child: const Text('确认'), @@ -146,21 +145,20 @@ class MemberController extends GetxController { } // 拉黑用户 - Future blockUser() async { + Future blockUser(BuildContext context) async { if (userInfo == null) { SmartDialog.showToast('账号未登录'); return; } - SmartDialog.show( - useSystem: true, - animationType: SmartAnimationType.centerFade_otherSlide, - builder: (BuildContext context) { + await showDialog( + context: context, + builder: (context) { return AlertDialog( title: const Text('提示'), content: Text(attribute.value != 128 ? '确定拉黑UP主?' : '从黑名单移除UP主'), actions: [ TextButton( - onPressed: () => SmartDialog.dismiss(), + onPressed: () => Get.back(), child: Text( '点错了', style: TextStyle(color: Theme.of(context).colorScheme.outline), @@ -168,12 +166,12 @@ class MemberController extends GetxController { ), TextButton( onPressed: () async { + Get.back(); var res = await VideoHttp.relationMod( mid: mid, act: attribute.value != 128 ? 5 : 6, reSrc: 11, ); - SmartDialog.dismiss(); if (res['status']) { attribute.value = attribute.value != 128 ? 128 : 0; attributeText.value = attribute.value == 128 ? '已拉黑' : '关注'; diff --git a/lib/pages/member/view.dart b/lib/pages/member/view.dart index 5c3d2c613..bebd30329 100644 --- a/lib/pages/member/view.dart +++ b/lib/pages/member/view.dart @@ -63,6 +63,7 @@ class _MemberPageState extends State @override Widget build(BuildContext context) { + bool isHorizontal = context.width > context.height; return Scaffold( primary: true, body: Column( @@ -118,7 +119,7 @@ class _MemberPageState extends State itemBuilder: (BuildContext context) => [ if (_memberController.ownerMid != _memberController.mid) ...[ PopupMenuItem( - onTap: () => _memberController.blockUser(), + onTap: () => _memberController.blockUser(context), child: Row( mainAxisSize: MainAxisSize.min, children: [ @@ -158,29 +159,42 @@ class _MemberPageState extends State ), child: Column( children: [ - profileWidget(), - - /// 动态链接 - ListTile( - onTap: _memberController.pushDynamicsPage, - title: const Text('Ta的动态'), - trailing: - const Icon(Icons.arrow_forward_outlined, size: 19), - ), - - /// 视频 - ListTile( - onTap: _memberController.pushArchivesPage, - title: const Text('Ta的投稿'), - trailing: - const Icon(Icons.arrow_forward_outlined, size: 19), - ), - - /// 专栏 - ListTile( - onTap: () {}, - title: const Text('Ta的专栏'), - ), + profileWidget(isHorizontal), + Row(children: [ + const Spacer(), + InkWell( + onTap: _memberController.pushDynamicsPage, + child: const Row( + children: [ + Text('Ta的动态', style: TextStyle(height: 2)), + SizedBox(width: 5), + Icon(Icons.arrow_forward_ios, size: 19), + ], + ), + ), + const Spacer(), + InkWell( + onTap: _memberController.pushArchivesPage, + child: const Row( + children: [ + Text('Ta的投稿', style: TextStyle(height: 2)), + SizedBox(width: 5), + Icon(Icons.arrow_forward_ios, size: 19), + ], + ), + ), + const Spacer(), + InkWell( + onTap: () {}, + child: const Row( + children: [ + Text('Ta的专栏', style: TextStyle(height: 2)), + SizedBox(width: 5), + ], + ), + ), + const Spacer(), + ]), MediaQuery.removePadding( removeTop: true, removeBottom: true, @@ -279,153 +293,163 @@ class _MemberPageState extends State ); } - Widget profileWidget() { + Widget profileWidget(bool isHorizontal) { return Padding( padding: const EdgeInsets.only(left: 18, right: 18, bottom: 20), child: FutureBuilder( future: _futureBuilderFuture, builder: (context, snapshot) { - if (snapshot.connectionState == ConnectionState.done) { + if (snapshot.connectionState == ConnectionState.done && + snapshot.hasData) { Map data = snapshot.data!; if (data['status']) { return Obx( () => Stack( - alignment: AlignmentDirectional.center, - children: [ - Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - ProfilePanel(ctr: _memberController), - const SizedBox(height: 20), - Row( - children: [ - Flexible( - child: Text( - _memberController.memberInfo.value.name!, - maxLines: 1, - overflow: TextOverflow.ellipsis, - style: Theme.of(context) - .textTheme - .titleMedium! - .copyWith(fontWeight: FontWeight.bold), - )), - const SizedBox(width: 2), - if (_memberController.memberInfo.value.sex == '女') - const Icon( - FontAwesomeIcons.venus, - size: 14, - color: Colors.pink, - semanticLabel: '女', - ), - if (_memberController.memberInfo.value.sex == '男') - const Icon( - FontAwesomeIcons.mars, - size: 14, - color: Colors.blue, - semanticLabel: '男', - ), - const SizedBox(width: 4), - Image.asset( - 'assets/images/lv/lv${_memberController.memberInfo.value.level}.png', - height: 11, - semanticLabel: - '等级${_memberController.memberInfo.value.level}', - ), - const SizedBox(width: 6), - if (_memberController - .memberInfo.value.vip!.status == - 1 && - _memberController.memberInfo.value.vip! - .label!['img_label_uri_hans'] != - '') ...[ - Image.network( - _memberController.memberInfo.value.vip! - .label!['img_label_uri_hans'], - height: 20, - semanticLabel: _memberController - .memberInfo.value.vip!.label!['text'], - ), - ] else if (_memberController - .memberInfo.value.vip!.status == - 1 && - _memberController.memberInfo.value.vip! - .label!['img_label_uri_hans_static'] != - '') ...[ - Image.network( - _memberController.memberInfo.value.vip! - .label!['img_label_uri_hans_static'], - height: 20, - semanticLabel: _memberController - .memberInfo.value.vip!.label!['text'], - ), - ], - TextButton( - child: Text("UID ${_memberController.mid}", - style: TextStyle( - color: Theme.of(context) - .colorScheme - .secondary - .withOpacity(0.5), - fontSize: 12, - // fontWeight: FontWeight.w200, - )), - onPressed: () { - Clipboard.setData( - ClipboardData( - text: _memberController.mid.toString()), - ); - SmartDialog.showToast( - '已复制${_memberController.mid}至剪贴板'); - }), - ], - ), - if (_memberController - .memberInfo.value.official!['title'] != - '') ...[ - const SizedBox(height: 6), - Text.rich( - maxLines: 2, - TextSpan( - text: _memberController - .memberInfo.value.official!['role'] == - 1 - ? '个人认证:' - : '企业认证:', - style: TextStyle( - color: Theme.of(context).primaryColor, - ), - children: [ - TextSpan( - text: _memberController - .memberInfo.value.official!['title'], - ), - ], - ), - softWrap: true, - ), - ], - const SizedBox(height: 6), - if (_memberController.memberInfo.value.sign != '') - SelectableText( - _memberController.memberInfo.value.sign!, - ), - ], - ), - ], - ), + alignment: AlignmentDirectional.center, + children: [profilePanelAndDetailInfo(isHorizontal, false)]), ); } else { return const SizedBox(); } } else { // 骨架屏 - return ProfilePanel(ctr: _memberController, loadingStatus: true); + return profilePanelAndDetailInfo(isHorizontal, true); } }, ), ); } + Widget profilePanelAndDetailInfo(bool isHorizontal, bool loadingStatus) { + if (isHorizontal) { + return Row( + children: [ + Expanded( + child: ProfilePanel( + ctr: _memberController, loadingStatus: loadingStatus)), + const SizedBox(width: 20), + Expanded(child: profileDetailInfo()), + ], + ); + } + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + ProfilePanel(ctr: _memberController, loadingStatus: loadingStatus), + const SizedBox(height: 20), + profileDetailInfo(), + ], + ); + } + + Widget profileDetailInfo() { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + Flexible( + child: Text( + _memberController.memberInfo.value.name ?? '', + maxLines: 1, + overflow: TextOverflow.ellipsis, + style: Theme.of(context) + .textTheme + .titleMedium! + .copyWith(fontWeight: FontWeight.bold), + )), + const SizedBox(width: 2), + if (_memberController.memberInfo.value.sex == '女') + const Icon( + FontAwesomeIcons.venus, + size: 14, + color: Colors.pink, + semanticLabel: '女', + ), + if (_memberController.memberInfo.value.sex == '男') + const Icon( + FontAwesomeIcons.mars, + size: 14, + color: Colors.blue, + semanticLabel: '男', + ), + const SizedBox(width: 4), + if (_memberController.memberInfo.value.level != null) + Image.asset( + 'assets/images/lv/lv${_memberController.memberInfo.value.level}.png', + height: 11, + semanticLabel: '等级${_memberController.memberInfo.value.level}', + ), + const SizedBox(width: 6), + if (_memberController.memberInfo.value.vip?.status == 1) ...[ + if (_memberController + .memberInfo.value.vip?.label?['img_label_uri_hans'] != + '') + Image.network( + _memberController + .memberInfo.value.vip!.label!['img_label_uri_hans'], + height: 20, + semanticLabel: + _memberController.memberInfo.value.vip!.label!['text'], + ), + if (_memberController.memberInfo.value.vip + ?.label?['img_label_uri_hans_static'] != + '') + Image.network( + _memberController.memberInfo.value.vip! + .label!['img_label_uri_hans_static'], + height: 20, + semanticLabel: + _memberController.memberInfo.value.vip!.label!['text'], + ), + ], + TextButton( + child: Text("UID ${_memberController.mid}", + style: TextStyle( + color: Theme.of(context) + .colorScheme + .secondary + .withOpacity(0.5), + fontSize: 12, + // fontWeight: FontWeight.w200, + )), + onPressed: () { + Clipboard.setData( + ClipboardData(text: _memberController.mid.toString()), + ); + SmartDialog.showToast('已复制${_memberController.mid}至剪贴板'); + }), + ], + ), + if (_memberController.memberInfo.value.official != null && + _memberController.memberInfo.value.official!['title'] != '') ...[ + const SizedBox(height: 6), + Text.rich( + maxLines: 2, + TextSpan( + text: _memberController.memberInfo.value.official!['role'] == 1 + ? '个人认证:' + : '企业认证:', + style: TextStyle( + color: Theme.of(context).primaryColor, + ), + children: [ + TextSpan( + text: _memberController.memberInfo.value.official!['title'], + ), + ], + ), + softWrap: true, + ), + ], + const SizedBox(height: 6), + SelectableText( + _memberController.memberInfo.value.sign ?? '', + ), + ], + ); + } + Widget commenWidget(msg) { return Padding( padding: const EdgeInsets.only( diff --git a/lib/pages/member/widgets/profile.dart b/lib/pages/member/widgets/profile.dart index e25852aa6..9e2b7d882 100644 --- a/lib/pages/member/widgets/profile.dart +++ b/lib/pages/member/widgets/profile.dart @@ -19,125 +19,147 @@ class ProfilePanel extends StatelessWidget { MemberInfoModel memberInfo = ctr.memberInfo.value; return Builder( builder: ((context) { - return Padding( - padding: - EdgeInsets.only(top: MediaQuery.of(context).padding.top - 20), - child: Row( - children: [ - Hero( - tag: ctr.heroTag!, - child: Stack( - children: [ - NetworkImgLayer( - width: 90, - height: 90, - type: 'avatar', - src: !loadingStatus ? memberInfo.face : ctr.face.value, - ), - if (!loadingStatus && - memberInfo.liveRoom != null && - memberInfo.liveRoom!.liveStatus == 1) - Positioned( - bottom: 0, - left: 14, - child: GestureDetector( + return Row( + children: [ + Hero( + tag: ctr.heroTag!, + child: Stack( + children: [ + NetworkImgLayer( + width: 90, + height: 90, + type: 'avatar', + src: !loadingStatus ? memberInfo.face : ctr.face.value, + ), + if (!loadingStatus && + memberInfo.liveRoom != null && + memberInfo.liveRoom!.liveStatus == 1) + Positioned( + bottom: 0, + left: 14, + child: GestureDetector( + onTap: () { + LiveItemModel liveItem = LiveItemModel.fromJson({ + 'title': memberInfo.liveRoom!.title, + 'uname': memberInfo.name, + 'face': memberInfo.face, + 'roomid': memberInfo.liveRoom!.roomId, + 'watched_show': memberInfo.liveRoom!.watchedShow, + }); + Get.toNamed( + '/liveRoom?roomid=${memberInfo.liveRoom!.roomId}', + arguments: {'liveItem': liveItem}, + ); + }, + child: Container( + padding: const EdgeInsets.fromLTRB(6, 2, 6, 2), + decoration: BoxDecoration( + color: Theme.of(context).colorScheme.primary, + borderRadius: + const BorderRadius.all(Radius.circular(10)), + ), + child: Row(children: [ + Image.asset( + 'assets/images/live.gif', + height: 10, + ), + Text( + ' 直播中', + style: TextStyle( + color: Colors.white, + fontSize: Theme.of(context) + .textTheme + .labelSmall! + .fontSize), + ) + ]), + ), + ), + ) + ], + ), + ), + const SizedBox(width: 12), + Expanded( + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Padding( + padding: + const EdgeInsets.only(top: 10, left: 10, right: 10), + child: Row( + mainAxisSize: MainAxisSize.max, + mainAxisAlignment: MainAxisAlignment.spaceAround, + children: [ + InkWell( onTap: () { - LiveItemModel liveItem = LiveItemModel.fromJson({ - 'title': memberInfo.liveRoom!.title, - 'uname': memberInfo.name, - 'face': memberInfo.face, - 'roomid': memberInfo.liveRoom!.roomId, - 'watched_show': memberInfo.liveRoom!.watchedShow, - }); Get.toNamed( - '/liveRoom?roomid=${memberInfo.liveRoom!.roomId}', - arguments: {'liveItem': liveItem}, - ); + '/follow?mid=${memberInfo.mid}&name=${memberInfo.name}'); }, - child: Container( - padding: const EdgeInsets.fromLTRB(6, 2, 6, 2), - decoration: BoxDecoration( - color: Theme.of(context).colorScheme.primary, - borderRadius: - const BorderRadius.all(Radius.circular(10)), - ), - child: Row(children: [ - Image.asset( - 'assets/images/live.gif', - height: 10, + child: Column( + children: [ + Text( + !loadingStatus + ? ctr.userStat!['following'].toString() + : '-', + style: const TextStyle( + fontWeight: FontWeight.bold), ), Text( - ' 直播中', + '关注', style: TextStyle( - color: Colors.white, fontSize: Theme.of(context) .textTheme - .labelSmall! + .labelMedium! .fontSize), ) - ]), + ], ), ), - ) - ], - ), - ), - const SizedBox(width: 12), - Expanded( - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - Padding( - padding: - const EdgeInsets.only(top: 10, left: 10, right: 10), - child: Row( - mainAxisSize: MainAxisSize.max, - mainAxisAlignment: MainAxisAlignment.spaceAround, - children: [ - InkWell( - onTap: () { - Get.toNamed( - '/follow?mid=${memberInfo.mid}&name=${memberInfo.name}'); - }, - child: Column( - children: [ - Text( + InkWell( + onTap: () { + Get.toNamed( + '/fan?mid=${memberInfo.mid}&name=${memberInfo.name}'); + }, + child: Column( + children: [ + Text( !loadingStatus - ? ctr.userStat!['following'].toString() + ? ctr.userStat!['follower'] != null + ? Utils.numFormat( + ctr.userStat!['follower'], + ) + : '-' : '-', style: const TextStyle( - fontWeight: FontWeight.bold), - ), - Text( - '关注', - style: TextStyle( - fontSize: Theme.of(context) - .textTheme - .labelMedium! - .fontSize), - ) - ], - ), + fontWeight: FontWeight.bold)), + Text( + '粉丝', + style: TextStyle( + fontSize: Theme.of(context) + .textTheme + .labelMedium! + .fontSize), + ) + ], ), - InkWell( - onTap: () { - Get.toNamed( - '/fan?mid=${memberInfo.mid}&name=${memberInfo.name}'); - }, + ), + InkWell( + onTap: null, child: Column( children: [ Text( !loadingStatus - ? ctr.userStat!['follower'] != null + ? ctr.userStat!['likes'] != null ? Utils.numFormat( - ctr.userStat!['follower'], + ctr.userStat!['likes'], ) : '-' : '-', style: const TextStyle( fontWeight: FontWeight.bold)), Text( - '粉丝', + '获赞', style: TextStyle( fontSize: Theme.of(context) .textTheme @@ -145,117 +167,87 @@ class ProfilePanel extends StatelessWidget { .fontSize), ) ], - ), - ), - InkWell( - onTap: null, - child: Column( - children: [ - Text( - !loadingStatus - ? ctr.userStat!['likes'] != null - ? Utils.numFormat( - ctr.userStat!['likes'], - ) - : '-' - : '-', - style: const TextStyle( - fontWeight: FontWeight.bold)), - Text( - '获赞', - style: TextStyle( - fontSize: Theme.of(context) - .textTheme - .labelMedium! - .fontSize), - ) - ], - )), - ], - ), + )), + ], ), - const SizedBox(height: 10), - if (ctr.ownerMid != ctr.mid && ctr.ownerMid != -1) ...[ - Row( - children: [ - Obx( - () => Expanded( - child: TextButton( - onPressed: () => ctr.actionRelationMod(), - style: TextButton.styleFrom( - foregroundColor: ctr.attribute.value == -1 - ? Colors.transparent - : ctr.attribute.value != 0 - ? Theme.of(context) - .colorScheme - .outline - : Theme.of(context) - .colorScheme - .onPrimary, - backgroundColor: ctr.attribute.value != 0 - ? Theme.of(context) - .colorScheme - .onInverseSurface - : Theme.of(context) - .colorScheme - .primary, // 设置按钮背景色 - ), - child: Obx(() => Text(ctr.attributeText.value)), - ), - ), - ), - const SizedBox(width: 8), - Expanded( + ), + const SizedBox(height: 10), + if (ctr.ownerMid != ctr.mid && ctr.ownerMid != -1) ...[ + Row( + children: [ + Obx( + () => Expanded( child: TextButton( - onPressed: () {}, + onPressed: () => ctr.actionRelationMod(context), style: TextButton.styleFrom( - backgroundColor: Theme.of(context) - .colorScheme - .onInverseSurface, + foregroundColor: ctr.attribute.value == -1 + ? Colors.transparent + : ctr.attribute.value != 0 + ? Theme.of(context).colorScheme.outline + : Theme.of(context) + .colorScheme + .onPrimary, + backgroundColor: ctr.attribute.value != 0 + ? Theme.of(context) + .colorScheme + .onInverseSurface + : Theme.of(context) + .colorScheme + .primary, // 设置按钮背景色 ), - child: const Text('发消息'), + child: Obx(() => Text(ctr.attributeText.value)), ), - ) - ], - ) - ], - if (ctr.ownerMid == ctr.mid && ctr.ownerMid != -1) ...[ - TextButton( - onPressed: () { - Get.toNamed('/webview', parameters: { - 'url': 'https://account.bilibili.com/account/home', - 'pageTitle': '个人中心(建议浏览器打开)', - 'type': 'url' - }); - }, - style: TextButton.styleFrom( - padding: const EdgeInsets.only(left: 80, right: 80), - foregroundColor: - Theme.of(context).colorScheme.onPrimary, - backgroundColor: - Theme.of(context).colorScheme.primary, - ), - child: const Text('个人中心(web)'), - ) - ], - if (ctr.ownerMid == -1) ...[ - TextButton( - onPressed: () {}, - style: TextButton.styleFrom( - padding: const EdgeInsets.only(left: 80, right: 80), - foregroundColor: - Theme.of(context).colorScheme.outline, - backgroundColor: - Theme.of(context).colorScheme.onInverseSurface, + ), ), - child: const Text('未登录'), - ) - ] + const SizedBox(width: 8), + Expanded( + child: TextButton( + onPressed: () {}, + style: TextButton.styleFrom( + backgroundColor: Theme.of(context) + .colorScheme + .onInverseSurface, + ), + child: const Text('发消息'), + ), + ) + ], + ) ], - ), + if (ctr.ownerMid == ctr.mid && ctr.ownerMid != -1) ...[ + TextButton( + onPressed: () { + Get.toNamed('/webview', parameters: { + 'url': 'https://account.bilibili.com/account/home', + 'pageTitle': '个人中心(建议浏览器打开)', + 'type': 'url' + }); + }, + style: TextButton.styleFrom( + padding: const EdgeInsets.only(left: 80, right: 80), + foregroundColor: + Theme.of(context).colorScheme.onPrimary, + backgroundColor: Theme.of(context).colorScheme.primary, + ), + child: const Text('个人中心(web)'), + ) + ], + if (ctr.ownerMid == -1) ...[ + TextButton( + onPressed: () {}, + style: TextButton.styleFrom( + padding: const EdgeInsets.only(left: 80, right: 80), + foregroundColor: Theme.of(context).colorScheme.outline, + backgroundColor: + Theme.of(context).colorScheme.onInverseSurface, + ), + child: const Text('未登录'), + ) + ] + ], ), - ], - ), + ), + ], ); }), ); diff --git a/lib/pages/member/widgets/seasons.dart b/lib/pages/member/widgets/seasons.dart index 1e6f4b120..9601ef86a 100644 --- a/lib/pages/member/widgets/seasons.dart +++ b/lib/pages/member/widgets/seasons.dart @@ -5,6 +5,8 @@ import 'package:PiliPalaX/common/widgets/badge.dart'; import 'package:PiliPalaX/models/member/seasons.dart'; import 'package:PiliPalaX/pages/member_seasons/widgets/item.dart'; +import '../../../utils/grid.dart'; + class MemberSeasonsPanel extends StatelessWidget { final MemberSeasonsDataModel? data; const MemberSeasonsPanel({super.key, this.data}); @@ -38,10 +40,9 @@ class MemberSeasonsPanel extends StatelessWidget { size: 'small', text: item.meta!.total.toString(), ), - const Spacer(), SizedBox( - width: 35, - height: 35, + width: 30, + height: 30, child: IconButton( tooltip: '前往', onPressed: () => Get.toNamed( @@ -50,7 +51,7 @@ class MemberSeasonsPanel extends StatelessWidget { padding: MaterialStateProperty.all(EdgeInsets.zero), ), icon: const Icon( - Icons.arrow_forward, + Icons.arrow_forward_ios, size: 20, ), ), @@ -61,12 +62,12 @@ class MemberSeasonsPanel extends StatelessWidget { LayoutBuilder( builder: (context, boxConstraints) { return GridView.builder( - gridDelegate: - const SliverGridDelegateWithFixedCrossAxisCount( - crossAxisCount: 2, // Use a fixed count for GridView - crossAxisSpacing: StyleString.safeSpace, - mainAxisSpacing: StyleString.safeSpace, + gridDelegate: SliverGridDelegateWithExtentAndRatio( + mainAxisSpacing: StyleString.cardSpace, + crossAxisSpacing: StyleString.cardSpace, + maxCrossAxisExtent: Grid.maxRowWidth, childAspectRatio: 0.94, + mainAxisExtent: 0, ), physics: const NeverScrollableScrollPhysics(), shrinkWrap: true, diff --git a/lib/pages/member_archive/view.dart b/lib/pages/member_archive/view.dart index 1fd82b7dc..4b84f96d9 100644 --- a/lib/pages/member_archive/view.dart +++ b/lib/pages/member_archive/view.dart @@ -65,58 +65,60 @@ class _MemberArchivePageState extends State { physics: const AlwaysScrollableScrollPhysics(), controller: _memberArchivesController.scrollController, slivers: [ - FutureBuilder( - future: _futureBuilderFuture, - builder: (BuildContext context, snapshot) { - if (snapshot.connectionState == ConnectionState.done) { - if (snapshot.data != null) { - Map data = snapshot.data as Map; - List list = _memberArchivesController.archivesList; - if (data['status']) { - return Obx( - () => list.isNotEmpty - ? SliverGrid( - gridDelegate: - SliverGridDelegateWithMaxCrossAxisExtent( - mainAxisSpacing: StyleString.cardSpace, - crossAxisSpacing: StyleString.safeSpace, - maxCrossAxisExtent: Grid.maxRowWidth * 2, - mainAxisExtent: Grid.calculateActualWidth( - context, - Grid.maxRowWidth * 2, - StyleString.safeSpace) / - 2.1 / - StyleString.aspectRatio), - delegate: SliverChildBuilderDelegate( - (BuildContext context, index) { - return VideoCardH( - videoItem: list[index], - showOwner: false, - showPubdate: true, - ); - }, - childCount: list.length, - ), - ) - : const SliverToBoxAdapter(), - ); + SliverPadding( + padding: + const EdgeInsets.symmetric(horizontal: StyleString.safeSpace), + sliver: FutureBuilder( + future: _futureBuilderFuture, + builder: (BuildContext context, snapshot) { + if (snapshot.connectionState == ConnectionState.done) { + if (snapshot.data != null) { + Map data = snapshot.data as Map; + List list = _memberArchivesController.archivesList; + if (data['status']) { + return Obx( + () => list.isNotEmpty + ? SliverGrid( + gridDelegate: + SliverGridDelegateWithExtentAndRatio( + mainAxisSpacing: StyleString.safeSpace, + crossAxisSpacing: StyleString.safeSpace, + maxCrossAxisExtent: + Grid.maxRowWidth * 2, + childAspectRatio: + StyleString.aspectRatio * 2.3, + mainAxisExtent: 0), + delegate: SliverChildBuilderDelegate( + (BuildContext context, index) { + return VideoCardH( + videoItem: list[index], + showOwner: false, + showPubdate: true, + ); + }, + childCount: list.length, + ), + ) + : const SliverToBoxAdapter(), + ); + } else { + return HttpError( + errMsg: snapshot.data['msg'], + fn: () {}, + ); + } } else { return HttpError( - errMsg: snapshot.data['msg'], + errMsg: "投稿页出现错误", fn: () {}, ); } } else { - return HttpError( - errMsg: "投稿页出现错误", - fn: () {}, - ); + return const SliverToBoxAdapter(); } - } else { - return const SliverToBoxAdapter(); - } - }, - ), + }, + ), + ) ], ), ); diff --git a/lib/pages/member_dynamics/view.dart b/lib/pages/member_dynamics/view.dart index 4937bd66e..246a61d63 100644 --- a/lib/pages/member_dynamics/view.dart +++ b/lib/pages/member_dynamics/view.dart @@ -7,6 +7,7 @@ import 'package:PiliPalaX/utils/utils.dart'; import '../../common/constants.dart'; import '../../common/widgets/http_error.dart'; import '../../utils/grid.dart'; +import '../../utils/storage.dart'; import '../dynamics/widgets/dynamic_panel.dart'; import 'package:waterfall_flow/waterfall_flow.dart'; @@ -22,6 +23,7 @@ class _MemberDynamicsPageState extends State { late Future _futureBuilderFuture; late ScrollController scrollController; late int mid; + late bool dynamicsWaterfallFlow; @override void initState() { @@ -44,6 +46,8 @@ class _MemberDynamicsPageState extends State { } }, ); + dynamicsWaterfallFlow = GStrorage.setting + .get(SettingBoxKey.dynamicsWaterfallFlow, defaultValue: true); } @override @@ -72,32 +76,51 @@ class _MemberDynamicsPageState extends State { Map data = snapshot.data as Map; List list = _memberDynamicController.dynamicsList; if (data['status']) { - return Obx( - () => list.isNotEmpty - ? SliverWaterfallFlow.extent( - maxCrossAxisExtent: Grid.maxRowWidth * 2, - //cacheExtent: 0.0, - crossAxisSpacing: StyleString.safeSpace, - mainAxisSpacing: StyleString.safeSpace, + return Obx(() { + if (list.isEmpty) { + return const SliverToBoxAdapter(); + } + if (!dynamicsWaterfallFlow) { + return SliverCrossAxisGroup( + slivers: [ + const SliverFillRemaining(), + SliverConstrainedCrossAxis( + maxExtent: Grid.maxRowWidth * 2, + sliver: SliverList( + delegate: SliverChildBuilderDelegate( + (context, index) { + return DynamicPanel(item: list[index]); + }, + childCount: list.length, + ), + )), + const SliverFillRemaining(), + ], + ); + } + return SliverWaterfallFlow.extent( + maxCrossAxisExtent: Grid.maxRowWidth * 2, + //cacheExtent: 0.0, + crossAxisSpacing: StyleString.safeSpace, + mainAxisSpacing: StyleString.safeSpace, - /// follow max child trailing layout offset and layout with full cross axis extend - /// last child as loadmore item/no more item in [GridView] and [WaterfallFlow] - /// with full cross axis extend - // LastChildLayoutType.fullCrossAxisExtend, + /// follow max child trailing layout offset and layout with full cross axis extend + /// last child as loadmore item/no more item in [GridView] and [WaterfallFlow] + /// with full cross axis extend + // LastChildLayoutType.fullCrossAxisExtend, - /// as foot at trailing and layout with full cross axis extend - /// show no more item at trailing when children are not full of viewport - /// if children is full of viewport, it's the same as fullCrossAxisExtend - // LastChildLayoutType.foot, - lastChildLayoutTypeBuilder: (index) => - index == list.length - ? LastChildLayoutType.foot - : LastChildLayoutType.none, - children: [ - for (var i in list) DynamicPanel(item: i), - ]) - : const SliverToBoxAdapter(), - ); + /// as foot at trailing and layout with full cross axis extend + /// show no more item at trailing when children are not full of viewport + /// if children is full of viewport, it's the same as fullCrossAxisExtend + // LastChildLayoutType.foot, + lastChildLayoutTypeBuilder: (index) => + index == list.length + ? LastChildLayoutType.foot + : LastChildLayoutType.none, + children: [ + for (var i in list) DynamicPanel(item: i), + ]); + }); } else { return HttpError( errMsg: snapshot.data['msg'], diff --git a/lib/pages/member_search/view.dart b/lib/pages/member_search/view.dart index 3cd7c9aaf..c132c2f90 100644 --- a/lib/pages/member_search/view.dart +++ b/lib/pages/member_search/view.dart @@ -7,6 +7,8 @@ import 'package:PiliPalaX/common/widgets/http_error.dart'; import 'package:PiliPalaX/common/widgets/no_data.dart'; import 'package:PiliPalaX/common/widgets/video_card_h.dart'; +import '../../common/constants.dart'; +import '../../utils/grid.dart'; import 'controller.dart'; class MemberSearchPage extends StatefulWidget { @@ -48,150 +50,132 @@ class _MemberSearchPageState extends State @override Widget build(BuildContext context) { return Scaffold( - appBar: AppBar( - titleSpacing: 0, - actions: [ - IconButton( - tooltip: '搜索', - onPressed: () => _memberSearchCtr.submit(), - icon: const Icon(CupertinoIcons.search, size: 22)), - const SizedBox(width: 10) - ], - title: Obx( - () => TextField( - autofocus: true, - focusNode: _memberSearchCtr.searchFocusNode, - controller: _memberSearchCtr.controller.value, - textInputAction: TextInputAction.search, - onChanged: (value) => _memberSearchCtr.onChange(value), - decoration: InputDecoration( - hintText: _memberSearchCtr.hintText, - border: InputBorder.none, - suffixIcon: IconButton( - tooltip: '清空', - icon: Icon( - Icons.clear, - size: 22, - color: Theme.of(context).colorScheme.outline, + appBar: AppBar( + titleSpacing: 0, + actions: [ + IconButton( + tooltip: '搜索', + onPressed: () => _memberSearchCtr.submit(), + icon: const Icon(CupertinoIcons.search, size: 22)), + const SizedBox(width: 10) + ], + title: Obx( + () => TextField( + autofocus: true, + focusNode: _memberSearchCtr.searchFocusNode, + controller: _memberSearchCtr.controller.value, + textInputAction: TextInputAction.search, + onChanged: (value) => _memberSearchCtr.onChange(value), + decoration: InputDecoration( + hintText: _memberSearchCtr.hintText, + border: InputBorder.none, + suffixIcon: IconButton( + tooltip: '清空', + icon: Icon( + Icons.clear, + size: 22, + color: Theme.of(context).colorScheme.outline, + ), + onPressed: () => _memberSearchCtr.onClear(), ), - onPressed: () => _memberSearchCtr.onClear(), ), + onSubmitted: (String value) => _memberSearchCtr.submit(), ), - onSubmitted: (String value) => _memberSearchCtr.submit(), ), ), - ), - body: Obx( - () => Column( - children: _memberSearchCtr.loadingStatus.value == 'init' - ? [ - Expanded( - child: Center( - child: Text('搜索「${_memberSearchCtr.uname.value}」的动态、视频'), - ), - ), - ] - : [ - // TabBar( - // controller: _tabController, - // tabs: const [ - // Tab(text: "视频"), - // Tab(text: "动态"), - // ], - // ), - Expanded( - child: - // TabBarView( - // controller: _tabController, - // children: [ - FutureBuilder( - future: _memberSearchCtr.searchArchives(), - builder: (context, snapshot) { - if (snapshot.connectionState == ConnectionState.done) { - Map data = snapshot.data as Map; - if (data['status']) { - return Obx( + body: Obx( + () { + if (_memberSearchCtr.loadingStatus.value == 'init') { + return Center( + child: Text('搜索「${_memberSearchCtr.uname.value}」的动态、视频'), + ); + } + return CustomScrollView( + physics: const AlwaysScrollableScrollPhysics(), + controller: scrollController, + slivers: [ + FutureBuilder( + future: _memberSearchCtr.searchArchives(), + builder: (context, snapshot) { + if (snapshot.connectionState == ConnectionState.done && + snapshot.hasData) { + Map data = snapshot.data as Map; + if (data['status']) { + return SliverPadding( + padding: + const EdgeInsets.all(StyleString.safeSpace), + sliver: Obx( () => _memberSearchCtr.archiveList.isNotEmpty - ? ListView.builder( - controller: scrollController, - itemCount: - _memberSearchCtr.archiveList.length + - 1, - itemBuilder: (context, index) { - if (index == - _memberSearchCtr - .archiveList.length) { - return Container( - height: MediaQuery.of(context) - .padding - .bottom + - 60, - padding: EdgeInsets.only( - bottom: MediaQuery.of(context) - .padding - .bottom), - child: Center( - child: Obx( - () => Text( - _memberSearchCtr - .loadingText.value, - style: TextStyle( - color: Theme.of(context) - .colorScheme - .outline, - fontSize: 13), - ), - ), - ), - ); - } else { - return VideoCardH( - videoItem: _memberSearchCtr - .archiveList[index]); - } + ? SliverGrid( + gridDelegate: + SliverGridDelegateWithExtentAndRatio( + mainAxisSpacing: StyleString + .safeSpace, + crossAxisSpacing: StyleString + .safeSpace, + maxCrossAxisExtent: Grid.maxRowWidth * + 2, + childAspectRatio: + StyleString.aspectRatio * 2.3, + mainAxisExtent: 0), + delegate: SliverChildBuilderDelegate( + (context, index) { + return VideoCardH( + videoItem: _memberSearchCtr + .archiveList[index]); }, - ) + childCount: _memberSearchCtr.archiveList + .length)) : _memberSearchCtr.loadingStatus.value == 'loading' - ? ListView.builder( - itemCount: 10, - itemBuilder: (context, index) { + ? SliverGrid( + gridDelegate: + SliverGridDelegateWithExtentAndRatio( + mainAxisSpacing: + StyleString.cardSpace, + crossAxisSpacing: + StyleString.safeSpace, + maxCrossAxisExtent: + Grid.maxRowWidth * 2, + childAspectRatio: + StyleString.aspectRatio * + 2.1, + mainAxisExtent: 0), + delegate: SliverChildBuilderDelegate( + (context, index) { return const VideoCardHSkeleton(); - }, - ) - : const CustomScrollView( - slivers: [ - NoData(), - ], - ), - ); - } else { - return CustomScrollView( - slivers: [ - HttpError( - errMsg: data['msg'], - fn: () => setState(() {}), - ) - ], - ); - } - } else { - // 骨架屏 - return ListView.builder( - itemCount: 10, - itemBuilder: (context, index) { - return const VideoCardHSkeleton(); - }, - ); - } - }, - ), - // ], - // ), - ), - ], - ), - ), - ); + }, childCount: 10)) + : const NoData(), + )); + } else { + return HttpError( + errMsg: data['msg'], + fn: () => setState(() {}), + ); + } + } else { + // 骨架屏 + return SliverPadding( + padding: const EdgeInsets.all(StyleString.safeSpace), + sliver: SliverGrid( + gridDelegate: + SliverGridDelegateWithExtentAndRatio( + mainAxisSpacing: StyleString.cardSpace, + crossAxisSpacing: StyleString.safeSpace, + maxCrossAxisExtent: Grid.maxRowWidth * 2, + childAspectRatio: + StyleString.aspectRatio * 2.3, + mainAxisExtent: 0), + delegate: + SliverChildBuilderDelegate((context, index) { + return const VideoCardHSkeleton(); + }, childCount: 10))); + } + }, + ), + ], + ); + }, + )); } } diff --git a/lib/pages/mine/view.dart b/lib/pages/mine/view.dart index 6adcb6e6c..36f2f4820 100644 --- a/lib/pages/mine/view.dart +++ b/lib/pages/mine/view.dart @@ -151,7 +151,10 @@ class _MinePageState extends State { semanticsLabel: '头像', width: 85, height: 85) - : Image.asset('assets/images/noface.jpeg',semanticLabel: "默认头像",), + : Image.asset( + 'assets/images/noface.jpeg', + semanticLabel: "默认头像", + ), ), ), ), @@ -183,8 +186,8 @@ class _MinePageState extends State { style: TextStyle(color: Theme.of(context).colorScheme.outline)), TextSpan( - text: (_mineController.userInfo.value.money ?? '-') - .toString(), + text: + (_mineController.userInfo.value.money ?? '-').toString(), style: TextStyle(color: Theme.of(context).colorScheme.primary)), ])) @@ -256,12 +259,12 @@ class _MinePageState extends State { color: Theme.of(context).colorScheme.primary, fontWeight: FontWeight.bold); return SizedBox( - height: constraints.maxWidth / 3 * 0.6, + height: constraints.maxWidth * 0.33 * 0.6, child: GridView.count( primary: false, padding: const EdgeInsets.all(0), crossAxisCount: 3, - childAspectRatio: 1.67, + childAspectRatio: 1.66, children: [ InkWell( onTap: () => _mineController.pushDynamic(), diff --git a/lib/pages/preview/view.dart b/lib/pages/preview/view.dart index 6e8f4f7b4..47ec986b0 100644 --- a/lib/pages/preview/view.dart +++ b/lib/pages/preview/view.dart @@ -90,7 +90,8 @@ class _ImagePreviewState extends State ListTile( onTap: () { Get.back(); - DownloadUtils.downloadImg(_previewController.currentImgUrl); + DownloadUtils.downloadImg( + context, _previewController.currentImgUrl); }, dense: true, title: const Text('保存到手机', style: TextStyle(fontSize: 14)), diff --git a/lib/pages/rank/controller.dart b/lib/pages/rank/controller.dart index 53d43b5ab..ef3c06944 100644 --- a/lib/pages/rank/controller.dart +++ b/lib/pages/rank/controller.dart @@ -34,7 +34,7 @@ class RankController extends GetxController with GetTickerProviderStateMixin { void animateToTop() { int index = tabController.index; var ctr = tabsCtrList[index]; - ctr().animateToTop(); + ctr.animateToTop(); } void setTabConfig() async { diff --git a/lib/pages/rank/view.dart b/lib/pages/rank/view.dart index b203186f5..350e0922b 100644 --- a/lib/pages/rank/view.dart +++ b/lib/pages/rank/view.dart @@ -35,72 +35,94 @@ class _RankPageState extends State } }); } + @override void dispose() { _rankController.tabController.removeListener(() {}); _rankController.tabController.dispose(); super.dispose(); } + @override Widget build(BuildContext context) { super.build(context); - return Scaffold( - appBar: AppBar( - toolbarHeight: 0, - elevation: 0, - systemOverlayStyle: SystemUiOverlayStyle( - // Customize the status bar here - statusBarIconBrightness: - MediaQuery.of(context).platformBrightness == Brightness.dark - ? Brightness.light - : Brightness.dark, + return Row( + children: [ + const SizedBox( + width: StyleString.cardSpace, ), - ), - body: Row( - children: [ - const SizedBox( - width: StyleString.cardSpace, + // SizedBox( + // width: 55, + // child: NavigationRail( + // + // backgroundColor: Colors.transparent, + // minWidth: 50.0, + // // elevation: 0, + // selectedIndex: _selectedTabIndex, + // onDestinationSelected: (int index) { + // feedBack(); + // if (_selectedTabIndex == index) { + // _rankController.tabsCtrList[index]().animateToTop(); + // } else { + // setState(() { + // _rankController.tabController.index = index; + // _selectedTabIndex = index; + // }); + // } + // }, + // labelType: NavigationRailLabelType.none, + // destinations: [ + // for (var tab in _rankController.tabs) + // NavigationRailDestination( + // padding: EdgeInsets.zero, + // icon: Text(tab['label']), + // // selectedIcon: Text(tab['label']), + // label: const SizedBox.shrink(), + // ), + // ], + // trailing: const SizedBox(height: 100), + // )), + LayoutBuilder(builder: (context, constraint) { + return SingleChildScrollView( + child: ConstrainedBox( + constraints: BoxConstraints(minHeight: constraint.maxHeight), + child: IntrinsicHeight( + child: NavigationRail( + backgroundColor: Colors.transparent, + minWidth: 50.0, + // elevation: 0, + selectedIndex: _selectedTabIndex, + onDestinationSelected: (int index) { + feedBack(); + if (_selectedTabIndex == index) { + _rankController.tabsCtrList[index]().animateToTop(); + } else { + setState(() { + _rankController.tabController.index = index; + _selectedTabIndex = index; + }); + } + }, + labelType: NavigationRailLabelType.none, + destinations: [ + for (var tab in _rankController.tabs) + NavigationRailDestination( + icon: Text(tab['label']), + // selectedIcon: Text(tab['label']), + label: const SizedBox.shrink(), + ), + ], + trailing: const SizedBox(height: 100), + )))); + }), + Expanded( + child: TabBarView( + controller: _rankController.tabController, + children: _rankController.tabsPageList, ), - LayoutBuilder(builder: (context, constraint) { - return SingleChildScrollView( - child: ConstrainedBox( - constraints: - BoxConstraints(minHeight: constraint.maxHeight + 100), - child: IntrinsicHeight( - child: NavigationRail( - minWidth: 55.0, - selectedIndex: _selectedTabIndex, - onDestinationSelected: (int index) { - feedBack(); - if (_selectedTabIndex == index) { - _rankController.tabsCtrList[index]().animateToTop(); - } else { - setState(() { - _rankController.tabController.index = index; - _selectedTabIndex = index; - }); - } - }, - labelType: NavigationRailLabelType.none, - destinations: [ - for (var tab in _rankController.tabs) - NavigationRailDestination( - icon: Text(tab['label']), - // selectedIcon: Text(tab['label']), - label: const SizedBox.shrink(), - ), - ], - )))); - }), - Expanded( - child: TabBarView( - controller: _rankController.tabController, - children: _rankController.tabsPageList, - ), - ), - ], - ), + ), + ], ); } } diff --git a/lib/pages/rank/zone/view.dart b/lib/pages/rank/zone/view.dart index 4fd8e1672..526e549be 100644 --- a/lib/pages/rank/zone/view.dart +++ b/lib/pages/rank/zone/view.dart @@ -27,7 +27,6 @@ class ZonePage extends StatefulWidget { class _ZonePageState extends State with AutomaticKeepAliveClientMixin { late ZoneController _zoneController; - List videoList = []; Future? _futureBuilderFuture; late ScrollController scrollController; @@ -81,12 +80,12 @@ class _ZonePageState extends State return await _zoneController.onRefresh(); }, child: CustomScrollView( - controller: _zoneController.scrollController, + controller: scrollController, slivers: [ SliverPadding( // 单列布局 EdgeInsets.zero padding: - const EdgeInsets.fromLTRB(0, StyleString.safeSpace - 5, 0, 0), + const EdgeInsets.fromLTRB(StyleString.safeSpace, StyleString.safeSpace, 0, 0), sliver: FutureBuilder( future: _futureBuilderFuture, builder: (context, snapshot) { @@ -95,19 +94,12 @@ class _ZonePageState extends State if (data['status']) { return Obx( () => SliverGrid( - gridDelegate: SliverGridDelegateWithMaxCrossAxisExtent( - // 行间距 - mainAxisSpacing: StyleString.cardSpace, - // 列间距 - crossAxisSpacing: StyleString.cardSpace, - // 最大宽度 + gridDelegate: SliverGridDelegateWithExtentAndRatio( + mainAxisSpacing: StyleString.safeSpace, + crossAxisSpacing: StyleString.safeSpace, maxCrossAxisExtent: Grid.maxRowWidth * 2, - mainAxisExtent: Grid.calculateActualWidth(context, - Grid.maxRowWidth * 2, StyleString.cardSpace, - screenWidthOffset: - StyleString.cardSpace + 55) / - 2.1 / - StyleString.aspectRatio), + childAspectRatio: StyleString.aspectRatio * 2.3, + mainAxisExtent: 0), delegate: SliverChildBuilderDelegate((context, index) { return VideoCardH( videoItem: _zoneController.videoList[index], @@ -138,7 +130,13 @@ class _ZonePageState extends State } } else { // 骨架屏 - return SliverList( + return SliverGrid( + gridDelegate: SliverGridDelegateWithExtentAndRatio( + mainAxisSpacing: StyleString.safeSpace, + crossAxisSpacing: StyleString.safeSpace, + maxCrossAxisExtent: Grid.maxRowWidth * 2, + childAspectRatio: StyleString.aspectRatio * 2.3, + mainAxisExtent: 0), delegate: SliverChildBuilderDelegate((context, index) { return const VideoCardHSkeleton(); }, childCount: 10), diff --git a/lib/pages/rcmd/view.dart b/lib/pages/rcmd/view.dart index a0ea6e12f..cb3ad2150 100644 --- a/lib/pages/rcmd/view.dart +++ b/lib/pages/rcmd/view.dart @@ -146,17 +146,15 @@ class _RcmdPageState extends State Widget contentGrid(ctr, videoList) { return SliverGrid( - gridDelegate: SliverGridDelegateWithMaxCrossAxisExtent( + gridDelegate: SliverGridDelegateWithExtentAndRatio( // 行间距 mainAxisSpacing: StyleString.cardSpace, // 列间距 crossAxisSpacing: StyleString.cardSpace, // 最大宽度 maxCrossAxisExtent: Grid.maxRowWidth, - mainAxisExtent: Grid.calculateActualWidth( - context, Grid.maxRowWidth, StyleString.safeSpace) / - StyleString.aspectRatio + - MediaQuery.textScalerOf(context).scale(90), + childAspectRatio: StyleString.aspectRatio, + mainAxisExtent: MediaQuery.textScalerOf(context).scale(90), ), delegate: SliverChildBuilderDelegate( (BuildContext context, int index) { diff --git a/lib/pages/search_panel/view.dart b/lib/pages/search_panel/view.dart index e247f7159..ca1ceef5c 100644 --- a/lib/pages/search_panel/view.dart +++ b/lib/pages/search_panel/view.dart @@ -6,6 +6,8 @@ import 'package:PiliPalaX/common/skeleton/video_card_h.dart'; import 'package:PiliPalaX/common/widgets/http_error.dart'; import 'package:PiliPalaX/models/common/search_type.dart'; +import '../../common/constants.dart'; +import '../../utils/grid.dart'; import 'controller.dart'; import 'widgets/article_panel.dart'; import 'widgets/live_panel.dart'; @@ -132,25 +134,34 @@ class _SearchPanelState extends State } } else { // 骨架屏 - return ListView.builder( - addAutomaticKeepAlives: false, - addRepaintBoundaries: false, - itemCount: 15, - itemBuilder: (context, index) { - switch (widget.searchType) { - case SearchType.video: - return const VideoCardHSkeleton(); - case SearchType.media_bangumi: - return const MediaBangumiSkeleton(); - case SearchType.bili_user: - return const VideoCardHSkeleton(); - case SearchType.live_room: - return const VideoCardHSkeleton(); - default: - return const VideoCardHSkeleton(); - } - }, - ); + return CustomScrollView( + physics: const AlwaysScrollableScrollPhysics(), + slivers: [ + SliverGrid( + gridDelegate: SliverGridDelegateWithExtentAndRatio( + mainAxisSpacing: StyleString.safeSpace, + crossAxisSpacing: StyleString.safeSpace, + maxCrossAxisExtent: Grid.maxRowWidth * 2, + childAspectRatio: StyleString.aspectRatio * 2.3, + mainAxisExtent: 0), + delegate: SliverChildBuilderDelegate( + (context, index) { + switch (widget.searchType) { + case SearchType.video: + return const VideoCardHSkeleton(); + case SearchType.media_bangumi: + return const MediaBangumiSkeleton(); + case SearchType.bili_user: + return const VideoCardHSkeleton(); + case SearchType.live_room: + return const VideoCardHSkeleton(); + default: + return const VideoCardHSkeleton(); + } + }, + childCount: 15, + )) + ]); } }, ), diff --git a/lib/pages/search_panel/widgets/article_panel.dart b/lib/pages/search_panel/widgets/article_panel.dart index 6dfd22850..2473fa77c 100644 --- a/lib/pages/search_panel/widgets/article_panel.dart +++ b/lib/pages/search_panel/widgets/article_panel.dart @@ -12,14 +12,12 @@ Widget searchArticlePanel(BuildContext context, ctr, list) { color: Theme.of(context).colorScheme.outline); return CustomScrollView(controller: ctr.scrollController, slivers: [ SliverGrid( - gridDelegate: SliverGridDelegateWithMaxCrossAxisExtent( - mainAxisSpacing: StyleString.cardSpace, + gridDelegate: SliverGridDelegateWithExtentAndRatio( + mainAxisSpacing: StyleString.safeSpace, crossAxisSpacing: StyleString.safeSpace, maxCrossAxisExtent: Grid.maxRowWidth * 2, - mainAxisExtent: Grid.calculateActualWidth( - context, Grid.maxRowWidth * 2, StyleString.safeSpace) / - 2.1 / - StyleString.aspectRatio), + childAspectRatio: StyleString.aspectRatio * 2.3, + mainAxisExtent: 0), delegate: SliverChildBuilderDelegate( (BuildContext context, int index) { return InkWell( @@ -32,8 +30,8 @@ Widget searchArticlePanel(BuildContext context, ctr, list) { }); }, child: Padding( - padding: const EdgeInsets.fromLTRB( - StyleString.safeSpace, 5, StyleString.safeSpace, 5), + padding: const EdgeInsets.symmetric( + horizontal: StyleString.safeSpace), child: LayoutBuilder(builder: (context, boxConstraints) { final double width = (boxConstraints.maxWidth - StyleString.cardSpace * diff --git a/lib/pages/search_panel/widgets/live_panel.dart b/lib/pages/search_panel/widgets/live_panel.dart index c7011506b..00a61b13d 100644 --- a/lib/pages/search_panel/widgets/live_panel.dart +++ b/lib/pages/search_panel/widgets/live_panel.dart @@ -13,14 +13,12 @@ Widget searchLivePanel(BuildContext context, ctr, list) { child: GridView.builder( primary: false, controller: ctr!.scrollController, - gridDelegate: SliverGridDelegateWithMaxCrossAxisExtent( + gridDelegate: SliverGridDelegateWithExtentAndRatio( maxCrossAxisExtent: Grid.maxRowWidth, crossAxisSpacing: StyleString.safeSpace, mainAxisSpacing: StyleString.safeSpace, - mainAxisExtent: Grid.calculateActualWidth( - context, Grid.maxRowWidth, StyleString.safeSpace) / - StyleString.aspectRatio + - MediaQuery.textScalerOf(context).scale(80), + childAspectRatio: StyleString.aspectRatio, + mainAxisExtent: MediaQuery.textScalerOf(context).scale(80), ), itemCount: list.length, itemBuilder: (context, index) { diff --git a/lib/pages/search_panel/widgets/media_bangumi_panel.dart b/lib/pages/search_panel/widgets/media_bangumi_panel.dart index 292a41995..53cd19a38 100644 --- a/lib/pages/search_panel/widgets/media_bangumi_panel.dart +++ b/lib/pages/search_panel/widgets/media_bangumi_panel.dart @@ -19,10 +19,10 @@ Widget searchMbangumiPanel(BuildContext context, ctr, list) { slivers: [ SliverGrid( gridDelegate: SliverGridDelegateWithMaxCrossAxisExtent( - mainAxisSpacing: StyleString.cardSpace, + mainAxisSpacing: StyleString.safeSpace, crossAxisSpacing: StyleString.safeSpace, maxCrossAxisExtent: Grid.maxRowWidth * 2, - mainAxisExtent: 157, + mainAxisExtent: 160, ), delegate: SliverChildBuilderDelegate((BuildContext context, int index) { var i = list![index]; @@ -36,8 +36,8 @@ Widget searchMbangumiPanel(BuildContext context, ctr, list) { // }); }, child: Padding( - padding: const EdgeInsets.fromLTRB( - StyleString.safeSpace, 7, StyleString.safeSpace, 2), + padding: const EdgeInsets.fromLTRB(StyleString.safeSpace, + StyleString.safeSpace, StyleString.safeSpace, 2), child: Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ @@ -125,7 +125,10 @@ Widget searchMbangumiPanel(BuildContext context, ctr, list) { if (res['status']) { EpisodeItem episode = res['data'].episodes.first; - int? epId = res['data'].userStatus?.progress?.lastEpId; + int? epId = res['data'] + .userStatus + ?.progress + ?.lastEpId; if (epId == null) { epId = episode.epId; } else { diff --git a/lib/pages/search_panel/widgets/video_panel.dart b/lib/pages/search_panel/widgets/video_panel.dart index 4ed105b1e..ca0e980b0 100644 --- a/lib/pages/search_panel/widgets/video_panel.dart +++ b/lib/pages/search_panel/widgets/video_panel.dart @@ -28,7 +28,8 @@ class SearchVideoPanel extends StatelessWidget { Container( width: context.width, height: 34, - padding: const EdgeInsets.only(left: 8, top: 0, right: 12), + padding: const EdgeInsets.only( + left: StyleString.safeSpace, top: 0, right: 12), child: Row( children: [ Expanded( @@ -69,7 +70,7 @@ class SearchVideoPanel extends StatelessWidget { style: ButtonStyle( padding: MaterialStateProperty.all(EdgeInsets.zero), ), - onPressed: () => controller.onShowFilterDialog(ctr), + onPressed: () => controller.onShowFilterDialog(context, ctr), icon: Icon( Icons.filter_list_outlined, size: 18, @@ -84,22 +85,23 @@ class SearchVideoPanel extends StatelessWidget { child: CustomScrollView( controller: ctr.scrollController, slivers: [ - SliverGrid( - gridDelegate: SliverGridDelegateWithMaxCrossAxisExtent( - mainAxisSpacing: StyleString.cardSpace, - crossAxisSpacing: StyleString.safeSpace, - maxCrossAxisExtent: Grid.maxRowWidth * 2, - mainAxisExtent: Grid.calculateActualWidth(context, - Grid.maxRowWidth * 2, StyleString.safeSpace) / - 2.1 / - StyleString.aspectRatio), - delegate: SliverChildBuilderDelegate( - (BuildContext context, int index) { - return VideoCardH(videoItem: list[index], showPubdate: true); - }, - childCount: list.length, - ), - ), + SliverPadding( + padding: const EdgeInsets.all(StyleString.safeSpace), + sliver: SliverGrid( + gridDelegate: SliverGridDelegateWithExtentAndRatio( + mainAxisSpacing: StyleString.safeSpace, + crossAxisSpacing: StyleString.safeSpace, + maxCrossAxisExtent: Grid.maxRowWidth * 2, + childAspectRatio: StyleString.aspectRatio * 2.3, + mainAxisExtent: 0), + delegate: SliverChildBuilderDelegate( + (BuildContext context, int index) { + return VideoCardH( + videoItem: list[index], showPubdate: true); + }, + childCount: list.length, + ), + )), ], )), ], @@ -176,10 +178,10 @@ class VideoPanelController extends GetxController { super.onInit(); } - onShowFilterDialog(searchPanelCtr) { - SmartDialog.show( - animationType: SmartAnimationType.centerFade_otherSlide, - builder: (BuildContext context) { + onShowFilterDialog(BuildContext context, SearchPanelController searchPanelCtr) { + showDialog( + context: context, + builder: (context) { TextStyle textStyle = Theme.of(context).textTheme.titleMedium!; return AlertDialog( title: const Text('时长筛选'), diff --git a/lib/pages/setting/controller.dart b/lib/pages/setting/controller.dart index 0487cebd0..6e868f4b0 100644 --- a/lib/pages/setting/controller.dart +++ b/lib/pages/setting/controller.dart @@ -48,17 +48,16 @@ class SettingController extends GetxController { setting.get(SettingBoxKey.defaultHomePage, defaultValue: 0); } - loginOut() async { - SmartDialog.show( - useSystem: true, - animationType: SmartAnimationType.centerFade_otherSlide, - builder: (BuildContext context) { + loginOut(BuildContext context) async { + await showDialog( + context: context, + builder: (context) { return AlertDialog( title: const Text('提示'), content: const Text('确认要退出登录吗'), actions: [ TextButton( - onPressed: () => SmartDialog.dismiss(), + onPressed: () => Get.back(), child: const Text('点错了'), ), TextButton( @@ -73,7 +72,7 @@ class SettingController extends GetxController { .put(LocalCacheKey.accessKey, {'mid': -1, 'value': ''}); await LoginUtils.refreshLoginStatus(false); - SmartDialog.dismiss().then((value) => Get.back()); + Get.back(); }, child: const Text('确认'), ) diff --git a/lib/pages/setting/extra_setting.dart b/lib/pages/setting/extra_setting.dart index c2fb2a808..b3d04893c 100644 --- a/lib/pages/setting/extra_setting.dart +++ b/lib/pages/setting/extra_setting.dart @@ -54,10 +54,9 @@ class _ExtraSettingState extends State { var systemProxyHost = ''; var systemProxyPort = ''; - SmartDialog.show( - useSystem: true, - animationType: SmartAnimationType.centerFade_otherSlide, - builder: (BuildContext context) { + showDialog( + context: context, + builder: (context) { return AlertDialog( title: const Text('设置代理'), content: Column( @@ -101,7 +100,7 @@ class _ExtraSettingState extends State { actions: [ TextButton( onPressed: () async { - SmartDialog.dismiss(); + Get.back(); }, child: Text( '取消', @@ -112,7 +111,7 @@ class _ExtraSettingState extends State { onPressed: () async { setting.put(SettingBoxKey.systemProxyHost, systemProxyHost); setting.put(SettingBoxKey.systemProxyPort, systemProxyPort); - SmartDialog.dismiss(); + Get.back(); // Request.dio; }, child: const Text('确认'), @@ -210,6 +209,13 @@ class _ExtraSettingState extends State { setKey: SettingBoxKey.disableLikeMsg, defaultVal: false, ), + const SetSwitchItem( + title: '默认展示评论区', + subTitle: '在视频详情页默认切换至评论区页(仅tab型布局)', + leading: Icon(Icons.mode_comment_outlined), + setKey: SettingBoxKey.defaultShowComment, + defaultVal: false, + ), ListTile( dense: false, title: Text('评论展示', style: titleStyle), diff --git a/lib/pages/setting/pages/font_size_select.dart b/lib/pages/setting/pages/font_size_select.dart index ca25dc44d..31e531937 100644 --- a/lib/pages/setting/pages/font_size_select.dart +++ b/lib/pages/setting/pages/font_size_select.dart @@ -12,7 +12,8 @@ class FontSizeSelectPage extends StatefulWidget { class _FontSizeSelectPageState extends State { Box setting = GStrorage.setting; - List list = [0.9, 0.95, 1.0, 1.05, 1.1, 1.15, 1.2, 1.25, 1.3]; + List list = List.generate(12, (index) => 0.85 + index * 0.05); + //[0.85, 0.9, 0.95, 1.0, 1.05, 1.1, 1.15, 1.2, 1.25, 1.3, 1.35]; late double minsize; late double maxSize; late double currentSize; diff --git a/lib/pages/setting/pages/home_tabbar_set.dart b/lib/pages/setting/pages/home_tabbar_set.dart index bfd289350..df5f51931 100644 --- a/lib/pages/setting/pages/home_tabbar_set.dart +++ b/lib/pages/setting/pages/home_tabbar_set.dart @@ -21,7 +21,7 @@ class _TabbarSetPageState extends State { super.initState(); defaultTabs = tabsConfig; tabbarSort = settingStorage.get(SettingBoxKey.tabbarSort, - defaultValue: ['live', 'rcmd', 'hot', 'bangumi']); + defaultValue: ['live', 'rcmd', 'hot', 'rank', 'bangumi']); // 对 tabData 进行排序 defaultTabs.sort((a, b) { int indexA = tabbarSort.indexOf((a['type'] as TabType).id); diff --git a/lib/pages/setting/pages/play_speed_set.dart b/lib/pages/setting/pages/play_speed_set.dart index 1f6e94181..317dcf2d4 100644 --- a/lib/pages/setting/pages/play_speed_set.dart +++ b/lib/pages/setting/pages/play_speed_set.dart @@ -1,5 +1,6 @@ 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:PiliPalaX/pages/setting/widgets/switch_item.dart'; import 'package:PiliPalaX/plugin/pl_player/index.dart'; @@ -76,10 +77,9 @@ class _PlaySpeedPageState extends State { // 添加自定义倍速 void onAddSpeed() { double customSpeed = 1.0; - SmartDialog.show( - useSystem: true, - animationType: SmartAnimationType.centerFade_otherSlide, - builder: (BuildContext context) { + showDialog( + context: context, + builder: (context) { return AlertDialog( title: const Text('添加倍速'), content: Column( @@ -103,7 +103,7 @@ class _PlaySpeedPageState extends State { ), actions: [ TextButton( - onPressed: () => SmartDialog.dismiss(), + onPressed: () => Get.back(), child: const Text('取消'), ), TextButton( @@ -112,7 +112,7 @@ class _PlaySpeedPageState extends State { await videoStorage.put( VideoBoxKey.customSpeedsList, customSpeedsList); setState(() {}); - SmartDialog.dismiss(); + Get.back(); }, child: const Text('确认添加'), ) diff --git a/lib/pages/setting/play_setting.dart b/lib/pages/setting/play_setting.dart index 2239a00bf..f95fa8450 100644 --- a/lib/pages/setting/play_setting.dart +++ b/lib/pages/setting/play_setting.dart @@ -120,7 +120,7 @@ class _PlaySettingState extends State { ), const SetSwitchItem( title: '竖屏扩大展示', - subTitle: '小屏竖屏视频宽高比由16:9扩大至4:5(!暂不支持临时收起)', + subTitle: '小屏竖屏视频宽高比由16:9扩大至1:1(不支持收起);横屏适配时,扩大至9:16', leading: Icon(Icons.expand_outlined), setKey: SettingBoxKey.enableVerticalExpand, defaultVal: false, @@ -151,7 +151,7 @@ class _PlaySettingState extends State { subTitle: '进入后台时继续播放', leading: Icon(Icons.motion_photos_pause_outlined), setKey: SettingBoxKey.continuePlayInBackground, - defaultVal: true, + defaultVal: false, ), if (Platform.isAndroid) SetSwitchItem( diff --git a/lib/pages/setting/privacy_setting.dart b/lib/pages/setting/privacy_setting.dart index 2799e3471..41ef1c1ba 100644 --- a/lib/pages/setting/privacy_setting.dart +++ b/lib/pages/setting/privacy_setting.dart @@ -121,7 +121,8 @@ class _PrivacySettingState extends State { )), ListTile( onTap: () { - SmartDialog.show( + showDialog( + context: context, builder: (context) { return AlertDialog( title: const Text('查看详情'), @@ -130,7 +131,7 @@ class _PrivacySettingState extends State { actions: [ TextButton( onPressed: () async { - SmartDialog.dismiss(); + Get.back(); }, child: const Text('确认'), ) @@ -150,9 +151,9 @@ class _PrivacySettingState extends State { } void import_export_cookies(TextStyle titleStyle, TextStyle subTitleStyle) { - SmartDialog.show( - useSystem: true, - builder: (BuildContext context) { + showDialog( + context: context, + builder: (context) { return SimpleDialog( title: const Text('导入/导出cookie', style: TextStyle(color: Colors.red)), children: [ @@ -175,13 +176,14 @@ class _PrivacySettingState extends State { ), dense: false, onTap: () async { - await SmartDialog.dismiss(); + Navigator.of(context).pop(); if (!userLogin) { SmartDialog.showToast('请先登录'); return; } final String cookie = await CookieTool.exportCookie(); - await SmartDialog.show( + await showDialog( + context: context, builder: (context) { return AlertDialog( title: const Text('导出cookie(危险)', @@ -190,7 +192,7 @@ class _PrivacySettingState extends State { actions: [ TextButton( onPressed: () async { - await SmartDialog.dismiss(); + Navigator.of(context).pop(); await Clipboard.setData( ClipboardData(text: cookie)); }, @@ -199,7 +201,7 @@ class _PrivacySettingState extends State { ), TextButton( onPressed: () async { - await SmartDialog.dismiss(); + Navigator.of(context).pop(); }, child: const Text('取消'), ), @@ -223,13 +225,14 @@ class _PrivacySettingState extends State { ), dense: false, onTap: () async { - await SmartDialog.dismiss(); ClipboardData? data = await Clipboard.getData('text/plain'); if (data == null || data.text == null || data.text == '') { SmartDialog.showToast('未检测到剪贴板内容'); return; } - await SmartDialog.show( + if (!context.mounted) return; + await showDialog( + context: context, builder: (context) { return AlertDialog( title: const Text('导入剪贴板中的cookie'), @@ -237,13 +240,13 @@ class _PrivacySettingState extends State { actions: [ TextButton( onPressed: () async { - await SmartDialog.dismiss(); + Get.back(); }, child: const Text('取消'), ), TextButton( onPressed: () async { - await SmartDialog.dismiss(); + Get.back(); final String cookie = data.text!; try { await CookieTool.importCookie(cookie); diff --git a/lib/pages/setting/recommend_setting.dart b/lib/pages/setting/recommend_setting.dart index f7cd1f1e0..3a8cec618 100644 --- a/lib/pages/setting/recommend_setting.dart +++ b/lib/pages/setting/recommend_setting.dart @@ -6,6 +6,7 @@ import 'package:PiliPalaX/models/common/rcmd_type.dart'; import 'package:PiliPalaX/pages/setting/widgets/select_dialog.dart'; import 'package:PiliPalaX/utils/recommend_filter.dart'; import 'package:PiliPalaX/utils/storage.dart'; +import 'package:get/get.dart'; import 'widgets/switch_item.dart'; @@ -93,8 +94,9 @@ class _RecommendSettingState extends State { return; } // 显示一个确认框,告知用户可能会导致账号被风控 - SmartDialog.show( - animationType: SmartAnimationType.centerFade_otherSlide, + if (!context.mounted) return; + await showDialog( + context: context, builder: (context) { return AlertDialog( title: const Text('提示'), @@ -104,14 +106,20 @@ class _RecommendSettingState extends State { TextButton( onPressed: () { result = null; - SmartDialog.dismiss(); + Get.back(); }, child: const Text('取消'), ), TextButton( onPressed: () async { - SmartDialog.dismiss(); - await MemberHttp.cookieToKey(); + Get.back(); + var res = await MemberHttp.cookieToKey(); + if (res['status']) { + SmartDialog.showToast(res['msg']); + } else { + SmartDialog.showToast( + '获取access_key失败:${res['msg']}'); + } }, child: const Text('确定'), ), @@ -254,10 +262,9 @@ class _RecommendSettingState extends State { '* 其它(如热门视频、手动搜索、链接跳转等)均不受过滤器影响。\n' '* 设定较严苛的条件可导致推荐项数锐减或多次请求,请酌情选择。\n' '* 后续可能会增加更多过滤条件,敬请期待。', - style: Theme.of(context) - .textTheme - .labelSmall! - .copyWith(color: Theme.of(context).colorScheme.outline.withOpacity(0.7)), + style: Theme.of(context).textTheme.labelSmall!.copyWith( + color: + Theme.of(context).colorScheme.outline.withOpacity(0.7)), ), ) ], diff --git a/lib/pages/setting/style_setting.dart b/lib/pages/setting/style_setting.dart index 174c776f6..f7241de72 100644 --- a/lib/pages/setting/style_setting.dart +++ b/lib/pages/setting/style_setting.dart @@ -13,6 +13,7 @@ import 'package:PiliPalaX/utils/global_data.dart'; import 'package:PiliPalaX/utils/storage.dart'; import '../../models/common/dynamic_badge_mode.dart'; +import '../../models/common/up_panel_position.dart'; import '../../plugin/pl_player/utils/fullscreen.dart'; import '../../models/common/nav_bar_config.dart'; import 'controller.dart'; @@ -34,13 +35,18 @@ class _StyleSettingState extends State { late int picQuality; late ThemeType _tempThemeValue; late double maxRowWidth; + late UpPanelPosition upPanelPosition; @override void initState() { super.initState(); picQuality = setting.get(SettingBoxKey.defaultPicQa, defaultValue: 10); _tempThemeValue = settingController.themeType.value; - maxRowWidth = setting.get(SettingBoxKey.maxRowWidth, defaultValue: 240.0) as double; + maxRowWidth = + setting.get(SettingBoxKey.maxRowWidth, defaultValue: 240.0) as double; + upPanelPosition = UpPanelPosition.values[setting.get( + SettingBoxKey.upPanelPosition, + defaultValue: UpPanelPosition.leftFixed.code)]; } @override @@ -70,15 +76,15 @@ class _StyleSettingState extends State { callFn: (value) { if (value) { autoScreen(); - SmartDialog.showToast('已开启横屏适配'); + SmartDialog.showToast('已开启横屏适配,推荐将全屏方式设为【不改变当前方向】'); } else { AutoOrientation.portraitUpMode(); SmartDialog.showToast('已关闭横屏适配'); } }), const SetSwitchItem( - title: '自适应底栏/侧边栏', - subTitle: '横竖屏自动切换(其它底栏设置失效)', + title: '改用侧边栏', + subTitle: '开启后底栏被替换,且底栏相关设置失效', leading: Icon(Icons.chrome_reader_mode_outlined), setKey: SettingBoxKey.adaptiveNavBar, defaultVal: false, @@ -90,22 +96,6 @@ class _StyleSettingState extends State { setKey: SettingBoxKey.enableMYBar, defaultVal: true, ), - const SetSwitchItem( - title: '首页顶栏收起', - subTitle: '首页列表滑动时,收起顶栏', - leading: Icon(Icons.vertical_align_top_outlined), - setKey: SettingBoxKey.hideSearchBar, - defaultVal: false, - needReboot: true, - ), - const SetSwitchItem( - title: '首页底栏收起', - subTitle: '首页列表滑动时,收起底栏', - leading: Icon(Icons.vertical_align_bottom_outlined), - setKey: SettingBoxKey.hideTabBar, - defaultVal: false, - needReboot: true, - ), const SetSwitchItem( title: '首页背景渐变', setKey: SettingBoxKey.enableGradientBg, @@ -116,18 +106,17 @@ class _StyleSettingState extends State { ListTile( onTap: () async { double? result = await showDialog( - context: context, - builder: (context) { - return SlideDialog( - title: '最大列宽度(默认240dp)', - value: maxRowWidth, - min: 150.0, - max: 500.0, - divisions: 35, - suffix: 'dp', - ); - } - ); + context: context, + builder: (context) { + return SlideDialog( + title: '最大列宽度(默认240dp)', + value: maxRowWidth, + min: 150.0, + max: 500.0, + divisions: 35, + suffix: 'dp', + ); + }); if (result != null) { maxRowWidth = result; setting.put(SettingBoxKey.maxRowWidth, result); @@ -137,13 +126,80 @@ class _StyleSettingState extends State { }, leading: const Icon(Icons.calendar_view_week_outlined), dense: false, - title: Text('列表宽度(dp)上限', style: titleStyle), + title: Text('列表宽度(dp)限制', style: titleStyle), subtitle: Text( '当前:${maxRowWidth.toInt()}dp,屏幕宽度:${MediaQuery.of(context).size.width.toPrecision(2)}dp。' - '宽度越小列数越多,横条、大卡会2倍折算', + '宽度越小列数越多,横条、大卡会2倍折算', style: subTitleStyle, ), ), + const SetSwitchItem( + title: '播放页移除安全边距', + subTitle: '隐藏状态栏、撑满屏幕,但播放控件仍处于安全域内', + leading: Icon(Icons.crop_outlined), + setKey: SettingBoxKey.videoPlayerRemoveSafeArea, + defaultVal: false, + needReboot: true, + ), + const SetSwitchItem( + title: '动态页启用瀑布流', + subTitle: '关闭会显示为单列', + leading: Icon(Icons.view_array_outlined), + setKey: SettingBoxKey.dynamicsWaterfallFlow, + defaultVal: true, + needReboot: true, + ), + ListTile( + dense: false, + title: Text('动态页Up主显示位置', style: titleStyle), + leading: const Icon(Icons.person_outlined), + subtitle: Text('当前:${upPanelPosition.labels}', style: subTitleStyle), + onTap: () async { + UpPanelPosition? result = await showDialog( + context: context, + builder: (context) { + return SelectDialog( + title: '动态页Up主显示位置', + value: upPanelPosition, + values: UpPanelPosition.values.map((e) { + return {'title': e.labels, 'value': e}; + }).toList(), + ); + }, + ); + if (result != null) { + upPanelPosition = result; + setting.put(SettingBoxKey.upPanelPosition, result.code); + SmartDialog.showToast('重启生效'); + setState(() {}); + } + }, + ), + ListTile( + dense: false, + onTap: () => settingController.setDynamicBadgeMode(context), + title: Text('动态未读标记', style: titleStyle), + leading: const Icon(Icons.motion_photos_on_outlined), + subtitle: Obx(() => Text( + '当前标记样式:${settingController.dynamicBadgeType.value.description}', + style: subTitleStyle)), + ), + const SetSwitchItem( + title: '首页顶栏收起', + subTitle: '首页列表滑动时,收起顶栏', + leading: Icon(Icons.vertical_align_top_outlined), + setKey: SettingBoxKey.hideSearchBar, + defaultVal: false, + needReboot: true, + ), + const SetSwitchItem( + title: '首页底栏收起', + subTitle: '首页列表滑动时,收起底栏', + leading: Icon(Icons.vertical_align_bottom_outlined), + setKey: SettingBoxKey.hideTabBar, + defaultVal: false, + needReboot: true, + ), ListTile( dense: false, onTap: () { @@ -235,6 +291,9 @@ class _StyleSettingState extends State { leading: const Icon(Icons.opacity_outlined), title: Text('气泡提示不透明度', style: titleStyle), subtitle: Text('自定义气泡提示(Toast)不透明度', style: subTitleStyle), + trailing: Obx(() => Text( + settingController.toastOpacity.value.toStringAsFixed(1), + style: Theme.of(context).textTheme.titleSmall)), ), ListTile( dense: false, @@ -263,15 +322,6 @@ class _StyleSettingState extends State { '当前模式:${settingController.themeType.value.description}', style: subTitleStyle)), ), - ListTile( - dense: false, - onTap: () => settingController.setDynamicBadgeMode(context), - title: Text('动态未读标记', style: titleStyle), - leading: const Icon(Icons.motion_photos_on_outlined), - subtitle: Obx(() => Text( - '当前标记样式:${settingController.dynamicBadgeType.value.description}', - style: subTitleStyle)), - ), ListTile( dense: false, onTap: () => Get.toNamed('/colorSetting'), @@ -281,13 +331,6 @@ class _StyleSettingState extends State { '当前主题:${colorSelectController.type.value == 0 ? '动态取色' : '指定颜色'}', style: subTitleStyle)), ), - const SetSwitchItem( - title: '默认展示评论区', - subTitle: '在视频详情页默认切换至评论区页', - leading: Icon(Icons.mode_comment_outlined), - setKey: SettingBoxKey.defaultShowComment, - defaultVal: false, - ), ListTile( dense: false, onTap: () => settingController.seteDefaultHomePage(context), diff --git a/lib/pages/setting/view.dart b/lib/pages/setting/view.dart index 400083a6c..6b02b763a 100644 --- a/lib/pages/setting/view.dart +++ b/lib/pages/setting/view.dart @@ -56,7 +56,7 @@ class SettingPage extends StatelessWidget { leading: const Icon(Icons.style_outlined), dense: false, title: const Text('外观设置'), - subtitle: Text('横屏适配(平板)、列宽、首页、主题、字号、图片、动态红点、帧率等', style: subTitleStyle), + subtitle: Text('横屏适配(平板)、侧栏、列宽、首页、动态红点、主题、字号、图片、帧率等', style: subTitleStyle), ), ListTile( onTap: () => Get.toNamed('/extraSetting'), @@ -81,7 +81,7 @@ class SettingPage extends StatelessWidget { visible: settingController.userLogin.value, child: ListTile( leading: const Icon(Icons.logout_outlined), - onTap: () => settingController.loginOut(), + onTap: () => settingController.loginOut(context), dense: false, title: const Text('退出登录'), ), diff --git a/lib/pages/subscription/view.dart b/lib/pages/subscription/view.dart index 819c32615..e0cd7ce47 100644 --- a/lib/pages/subscription/view.dart +++ b/lib/pages/subscription/view.dart @@ -58,17 +58,12 @@ class _SubPageState extends State { physics: const AlwaysScrollableScrollPhysics(), slivers: [ SliverGrid( - gridDelegate: - SliverGridDelegateWithMaxCrossAxisExtent( - mainAxisSpacing: StyleString.cardSpace, - crossAxisSpacing: StyleString.safeSpace, - maxCrossAxisExtent: Grid.maxRowWidth * 2, - mainAxisExtent: Grid.calculateActualWidth( - context, - Grid.maxRowWidth * 2, - StyleString.safeSpace) / - 2.1 / - StyleString.aspectRatio), + gridDelegate: SliverGridDelegateWithExtentAndRatio( + mainAxisSpacing: StyleString.cardSpace, + crossAxisSpacing: StyleString.safeSpace, + maxCrossAxisExtent: Grid.maxRowWidth * 2, + childAspectRatio: StyleString.aspectRatio * 2.3, + mainAxisExtent: 0), delegate: SliverChildBuilderDelegate( childCount: _subController.subFolderData.value.list!.length, diff --git a/lib/pages/video/detail/controller.dart b/lib/pages/video/detail/controller.dart index 4ea2c55e8..c2c496063 100644 --- a/lib/pages/video/detail/controller.dart +++ b/lib/pages/video/detail/controller.dart @@ -266,7 +266,7 @@ class VideoDetailController extends GetxController type: DataSourceType.network, httpHeaders: { 'user-agent': - 'Mozilla/5.0 (Macintosh; Intel Mac OS X 13_3_1) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/16.4 Safari/605.1.15', + 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.36', 'referer': HttpString.baseUrl }, ), @@ -329,7 +329,7 @@ class VideoDetailController extends GetxController return result; } final List allVideosList = data.dash!.video!; - print("allVideosList:${allVideosList}"); + // print("allVideosList:${allVideosList}"); // 当前可播放的最高质量视频 int currentHighVideoQa = allVideosList.first.quality!.code; // 预设的画质为null,则当前可用的最高质量 @@ -426,6 +426,7 @@ class VideoDetailController extends GetxController } else { if (result['code'] == -404) { isShowCover.value = false; + SmartDialog.showToast('视频不存在或已被删除'); } if (result['code'] == 87008) { SmartDialog.showToast("当前视频可能是专属视频,可能需包月充电观看(${result['msg']})"); diff --git a/lib/pages/video/detail/introduction/controller.dart b/lib/pages/video/detail/introduction/controller.dart index f632b43f0..01d87f855 100644 --- a/lib/pages/video/detail/introduction/controller.dart +++ b/lib/pages/video/detail/introduction/controller.dart @@ -1,6 +1,7 @@ import 'dart:async'; 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:hive/hive.dart'; @@ -42,6 +43,8 @@ class VideoIntroController extends GetxController { // 是否点赞 RxBool hasLike = false.obs; + // 是否点踩 + RxBool hasDislike = false.obs; // 是否投币 RxBool hasCoin = false.obs; // 是否收藏 @@ -145,15 +148,16 @@ class VideoIntroController extends GetxController { // 获取点赞状态 Future queryHasLikeVideo() async { var result = await VideoHttp.hasLikeVideo(bvid: bvid); - // data num 被点赞标志 0:未点赞 1:已点赞 - hasLike.value = result["data"] == 1 ? true : false; + // data num 被点赞标志 0:未点赞 1:已点赞 2:已点踩 + hasLike.value = result["data"] == 1; + hasDislike.value = result["data"] == 2; } // 获取投币状态 Future queryHasCoinVideo() async { var result = await VideoHttp.hasCoinVideo(bvid: bvid); if (result['status']) { - hasCoin.value = result["data"]['multiply'] == 0 ? false : true; + hasCoin.value = result["data"]['multiply'] != 0; } } @@ -170,7 +174,7 @@ class VideoIntroController extends GetxController { } // 一键三连 - Future actionOneThree() async { + Future actionOneThree(BuildContext context) async { if (userInfo == null) { SmartDialog.showToast('账号未登录'); return; @@ -180,19 +184,19 @@ class VideoIntroController extends GetxController { SmartDialog.showToast('🙏 UP已经收到了~'); return false; } - SmartDialog.show( - useSystem: true, - animationType: SmartAnimationType.centerFade_otherSlide, - builder: (BuildContext context) { + await showDialog( + context: context, + builder: (context) { return AlertDialog( title: const Text('提示'), content: const Text('一键三连 给UP送温暖'), actions: [ TextButton( - onPressed: () => SmartDialog.dismiss(), + onPressed: () => Get.back(), child: const Text('点错了')), TextButton( onPressed: () async { + Get.back(); var result = await VideoHttp.oneThree(bvid: bvid); if (result['status']) { hasLike.value = result["data"]["like"]; @@ -202,7 +206,6 @@ class VideoIntroController extends GetxController { } else { SmartDialog.showToast(result['msg']); } - SmartDialog.dismiss(); }, child: const Text('确认'), ) @@ -224,6 +227,7 @@ class VideoIntroController extends GetxController { if (!hasLike.value) { SmartDialog.showToast('点赞成功'); hasLike.value = true; + hasDislike.value = false; videoDetail.value.stat!.like = videoDetail.value.stat!.like! + 1; } else if (hasLike.value) { SmartDialog.showToast('取消赞'); @@ -236,6 +240,29 @@ class VideoIntroController extends GetxController { } } + Future actionDislikeVideo() async { + if (userInfo == null) { + SmartDialog.showToast('账号未登录'); + return; + } + var result = + await VideoHttp.dislikeVideo(bvid: bvid, type: !hasDislike.value); + if (result['status']) { + // hasLike.value = result["data"] == 1 ? true : false; + if (!hasDislike.value) { + SmartDialog.showToast('点踩成功'); + hasDislike.value = true; + hasLike.value = false; + } else { + SmartDialog.showToast('取消踩'); + hasDislike.value = false; + } + // hasDislike.refresh(); + } else { + SmartDialog.showToast(result['msg']); + } + } + // 投币 Future actionCoinVideo() async { if (userInfo == null) { @@ -350,10 +377,33 @@ class VideoIntroController extends GetxController { // 分享视频 Future actionShareVideo() async { - var result = await Share.share( - '${videoDetail.value.title} UP主: ${videoDetail.value.owner!.name!} - ${HttpString.baseUrl}/video/$bvid') - .whenComplete(() {}); - return result; + showDialog( + context: Get.context!, + builder: (context) { + String videoUrl = '${HttpString.baseUrl}/video/$bvid'; + return AlertDialog( + title: const Text('分享方式'), + actions: [ + TextButton( + onPressed: () { + Clipboard.setData(ClipboardData(text: videoUrl)); + SmartDialog.showToast('已复制'); + Get.back(); + }, + child: const Text('复制链接')), + TextButton( + onPressed: () async { + var result = await Share.share('${videoDetail.value.title} ' + 'UP主: ${videoDetail.value.owner!.name!}' + ' - $videoUrl') + .whenComplete(() {}); + Get.back(); + return result; + }, + child: const Text('分享视频')), + ], + ); + }); } Future queryVideoInFolder() async { @@ -394,7 +444,7 @@ class VideoIntroController extends GetxController { } // 关注/取关up - Future actionRelationMod() async { + Future actionRelationMod(BuildContext context) async { feedBack(); if (userInfo == null) { SmartDialog.showToast('账号未登录'); @@ -413,16 +463,15 @@ class VideoIntroController extends GetxController { actionStatus = 0; break; } - SmartDialog.show( - useSystem: true, - animationType: SmartAnimationType.centerFade_otherSlide, - builder: (BuildContext context) { + await showDialog( + context: context, + builder: (context) { return AlertDialog( title: const Text('提示'), content: Text(currentStatus == 0 ? '关注UP主?' : '取消关注UP主?'), actions: [ TextButton( - onPressed: () => SmartDialog.dismiss(), + onPressed: () => Get.back(), child: Text( '点错了', style: TextStyle(color: Theme.of(context).colorScheme.outline), @@ -465,7 +514,7 @@ class VideoIntroController extends GetxController { } } } - SmartDialog.dismiss(); + Get.back(); }, child: const Text('确认'), ) diff --git a/lib/pages/video/detail/introduction/view.dart b/lib/pages/video/detail/introduction/view.dart index 6ff37ba06..c76f7d884 100644 --- a/lib/pages/video/detail/introduction/view.dart +++ b/lib/pages/video/detail/introduction/view.dart @@ -244,6 +244,7 @@ class _VideoInfoState extends State with TickerProviderStateMixin { Widget build(BuildContext context) { final ThemeData t = Theme.of(context); final Color outline = t.colorScheme.outline; + bool isHorizontal = context.width > context.height * 1.25; return SliverPadding( padding: const EdgeInsets.only( left: StyleString.safeSpace, right: StyleString.safeSpace, top: 10), @@ -252,20 +253,132 @@ class _VideoInfoState extends State with TickerProviderStateMixin { ? Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ + Row(children: [ + Expanded( + child: GestureDetector( + onTap: onPushMember, + child: Container( + padding: const EdgeInsets.symmetric( + vertical: 1, horizontal: 0), + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + NetworkImgLayer( + type: 'avatar', + src: loadingStatus + ? owner.face + : widget.videoDetail!.owner!.face, + width: 30, + height: 30, + fadeInDuration: Duration.zero, + fadeOutDuration: Duration.zero, + ), + const SizedBox(width: 10), + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + owner.name, + style: TextStyle( + fontSize: 12, + color: t.colorScheme.primary), + // semanticsLabel: "Up主:${owner.name}", + ), + const SizedBox(height: 0), + Text( + follower, + semanticsLabel: "$follower粉丝", + style: TextStyle( + fontSize: 12, + color: outline, + ), + ), + ]), + const Spacer(), + Obx(() => AnimatedOpacity( + opacity: loadingStatus || + videoIntroController + .followStatus.isEmpty + ? 0 + : 1, + duration: const Duration(milliseconds: 50), + child: SizedBox( + height: 32, + child: Obx( + () => videoIntroController + .followStatus.isNotEmpty + ? TextButton( + onPressed: () => + videoIntroController + .actionRelationMod( + context), + style: TextButton.styleFrom( + padding: const EdgeInsets.only( + left: 8, right: 8), + foregroundColor: + followStatus['attribute'] != + 0 + ? outline + : t.colorScheme + .onPrimary, + backgroundColor: + followStatus['attribute'] != + 0 + ? t.colorScheme + .onInverseSurface + : t.colorScheme + .primary, // 设置按钮背景色 + ), + child: Text( + followStatus['attribute'] != 0 + ? '已关注' + : '关注', + style: TextStyle( + fontSize: t.textTheme + .labelMedium!.fontSize), + ), + ) + : ElevatedButton( + onPressed: () => + videoIntroController + .actionRelationMod( + context), + child: const Text('关注'), + ), + ), + ), + )), + ], + ), + ), + )), + if (isHorizontal) + Expanded( + child: actionGrid(context, videoIntroController)), + ]), + const SizedBox(height: 8), GestureDetector( behavior: HitTestBehavior.translucent, onTap: () => showIntroDetail(), - child: Text( - !loadingStatus - ? widget.videoDetail!.title - : videoItem['title'], - style: const TextStyle( - fontSize: 18, - fontWeight: FontWeight.bold, + child: Row(children: [ + Expanded( + child: Text( + !loadingStatus + ? widget.videoDetail!.title + : videoItem['title'], + style: const TextStyle( + fontSize: 16, + fontWeight: FontWeight.w500, + ), + maxLines: 2, + overflow: TextOverflow.ellipsis, + )), + Icon( + Icons.arrow_forward_ios, + size: 16, + color: t.colorScheme.outline, ), - maxLines: 2, - overflow: TextOverflow.ellipsis, - ), + ]), ), Stack( children: [ @@ -363,7 +476,7 @@ class _VideoInfoState extends State with TickerProviderStateMixin { // ), // ), // 点赞收藏转发 布局样式2 - actionGrid(context, videoIntroController), + if (!isHorizontal) actionGrid(context, videoIntroController), // 合集 if (!loadingStatus && widget.videoDetail!.ugcSeason != null) ...[ @@ -387,93 +500,10 @@ class _VideoInfoState extends State with TickerProviderStateMixin { changeFuc: videoIntroController.changeSeasonOrbangu, )) ], - GestureDetector( - onTap: onPushMember, - child: Container( - padding: const EdgeInsets.symmetric( - vertical: 8, horizontal: 4), - child: Row( - children: [ - NetworkImgLayer( - type: 'avatar', - src: loadingStatus - ? owner.face - : widget.videoDetail!.owner!.face, - width: 34, - height: 34, - fadeInDuration: Duration.zero, - fadeOutDuration: Duration.zero, - ), - const SizedBox(width: 10), - Text( - owner.name, - style: const TextStyle(fontSize: 13), - // semanticsLabel: "Up主:${owner.name}", - ), - const SizedBox(width: 6), - Text( - follower, - semanticsLabel: "粉丝数:$follower", - style: TextStyle( - fontSize: t.textTheme.labelSmall!.fontSize, - color: outline, - ), - ), - const Spacer(), - Obx(() => AnimatedOpacity( - opacity: loadingStatus || - videoIntroController - .followStatus.isEmpty - ? 0 - : 1, - duration: const Duration(milliseconds: 50), - child: SizedBox( - height: 32, - child: Obx( - () => videoIntroController - .followStatus.isNotEmpty - ? TextButton( - onPressed: videoIntroController - .actionRelationMod, - style: TextButton.styleFrom( - padding: const EdgeInsets.only( - left: 8, right: 8), - foregroundColor: - followStatus['attribute'] != 0 - ? outline - : t.colorScheme.onPrimary, - backgroundColor: - followStatus['attribute'] != 0 - ? t.colorScheme - .onInverseSurface - : t.colorScheme - .primary, // 设置按钮背景色 - ), - child: Text( - followStatus['attribute'] != 0 - ? '已关注' - : '关注', - style: TextStyle( - fontSize: t.textTheme - .labelMedium!.fontSize), - ), - ) - : ElevatedButton( - onPressed: videoIntroController - .actionRelationMod, - child: const Text('关注'), - ), - ), - ), - )), - ], - ), - ), - ), ], ) : const SizedBox( - height: 100, + height: 130, child: Center( child: CircularProgressIndicator(), ), @@ -503,6 +533,16 @@ class _VideoInfoState extends State with TickerProviderStateMixin { ? Utils.numFormat(widget.videoDetail!.stat!.like!) : '-'), ), + Obx( + () => ActionItem( + icon: const Icon(FontAwesomeIcons.thumbsDown), + selectIcon: const Icon(FontAwesomeIcons.solidThumbsDown), + onTap: handleState(videoIntroController.actionDislikeVideo), + selectStatus: videoIntroController.hasDislike.value, + loadingStatus: loadingStatus, + semanticsLabel: '点踩', + text: "点踩"), + ), // ActionItem( // icon: const Icon(FontAwesomeIcons.clock), // onTap: () => videoIntroController.actionShareVideo(), @@ -536,7 +576,8 @@ class _VideoInfoState extends State with TickerProviderStateMixin { ), ActionItem( icon: const Icon(FontAwesomeIcons.comment), - onTap: () => videoDetailCtr.tabCtr.animateTo(1), + onTap: () => videoDetailCtr.tabCtr + .animateTo(videoDetailCtr.tabCtr.index == 1 ? 0 : 1), selectStatus: false, loadingStatus: loadingStatus, semanticsLabel: '评论', diff --git a/lib/pages/video/detail/introduction/widgets/intro_detail.dart b/lib/pages/video/detail/introduction/widgets/intro_detail.dart index 261557432..98696b447 100644 --- a/lib/pages/video/detail/introduction/widgets/intro_detail.dart +++ b/lib/pages/video/detail/introduction/widgets/intro_detail.dart @@ -104,6 +104,7 @@ class IntroDetail extends StatelessWidget { ), ), ), + const SizedBox(height: 100), ], ), ), diff --git a/lib/pages/video/detail/related/view.dart b/lib/pages/video/detail/related/view.dart index cd8e40568..e998f2d48 100644 --- a/lib/pages/video/detail/related/view.dart +++ b/lib/pages/video/detail/related/view.dart @@ -5,6 +5,8 @@ import 'package:PiliPalaX/common/widgets/animated_dialog.dart'; import 'package:PiliPalaX/common/widgets/http_error.dart'; import 'package:PiliPalaX/common/widgets/overlay_pop.dart'; import 'package:PiliPalaX/common/widgets/video_card_h.dart'; +import '../../../../common/constants.dart'; +import '../../../../utils/grid.dart'; import './controller.dart'; class RelatedVideoPanel extends StatefulWidget { @@ -33,61 +35,75 @@ class _RelatedVideoPanelState extends State @override Widget build(BuildContext context) { super.build(context); - return FutureBuilder( - future: _futureBuilder, - builder: (BuildContext context, AsyncSnapshot snapshot) { - if (snapshot.connectionState == ConnectionState.done) { - if (snapshot.data == null) { - return const SliverToBoxAdapter(child: SizedBox()); - } - if (snapshot.data!['status'] && snapshot.data != null) { - RxList relatedVideoList = _releatedController.relatedVideoList; - // 请求成功 - return Obx( - () => SliverList( + return SliverPadding( + padding: const EdgeInsets.all(StyleString.safeSpace), + sliver: FutureBuilder( + future: _futureBuilder, + builder: (BuildContext context, AsyncSnapshot snapshot) { + if (snapshot.connectionState == ConnectionState.done) { + if (snapshot.data == null) { + return const SliverToBoxAdapter(child: SizedBox()); + } + if (snapshot.data!['status'] && snapshot.hasData) { + RxList relatedVideoList = _releatedController.relatedVideoList; + // 请求成功 + return Obx( + () => SliverGrid( + gridDelegate: SliverGridDelegateWithExtentAndRatio( + mainAxisSpacing: StyleString.safeSpace, + crossAxisSpacing: StyleString.safeSpace, + maxCrossAxisExtent: Grid.maxRowWidth * 2, + childAspectRatio: StyleString.aspectRatio * 2.3, + mainAxisExtent: 0), + delegate: SliverChildBuilderDelegate((context, index) { + if (index == relatedVideoList.length) { + return SizedBox( + height: MediaQuery.of(context).padding.bottom); + } else { + return Material( + child: VideoCardH( + videoItem: relatedVideoList[index], + showPubdate: true, + longPress: () { + try { + _releatedController.popupDialog = + _createPopupDialog(_releatedController + .relatedVideoList[index]); + Overlay.of(context) + .insert(_releatedController.popupDialog!); + } catch (err) { + return {}; + } + }, + longPressEnd: () { + _releatedController.popupDialog?.remove(); + }, + ), + ); + } + }, childCount: relatedVideoList.length + 1), + ), + ); + } else { + // 请求错误 + return HttpError(errMsg: '出错了', fn: () {}); + } + } else { + // 骨架屏 + return SliverGrid( + gridDelegate: SliverGridDelegateWithExtentAndRatio( + mainAxisSpacing: StyleString.safeSpace, + crossAxisSpacing: StyleString.safeSpace, + maxCrossAxisExtent: Grid.maxRowWidth * 2, + childAspectRatio: StyleString.aspectRatio * 2.3, + mainAxisExtent: 0), delegate: SliverChildBuilderDelegate((context, index) { - if (index == relatedVideoList.length) { - return SizedBox( - height: MediaQuery.of(context).padding.bottom); - } else { - return Material( - child: VideoCardH( - videoItem: relatedVideoList[index], - showPubdate: true, - longPress: () { - try { - _releatedController.popupDialog = - _createPopupDialog(_releatedController - .relatedVideoList[index]); - Overlay.of(context) - .insert(_releatedController.popupDialog!); - } catch (err) { - return {}; - } - }, - longPressEnd: () { - _releatedController.popupDialog?.remove(); - }, - ), - ); - } - }, childCount: relatedVideoList.length + 1), - ), - ); - } else { - // 请求错误 - return HttpError(errMsg: '出错了', fn: () {}); - } - } else { - // 骨架屏 - return SliverList( - delegate: SliverChildBuilderDelegate((context, index) { - return const VideoCardHSkeleton(); - }, childCount: 5), - ); - } - }, - ); + return const VideoCardHSkeleton(); + }, childCount: 5), + ); + } + }, + )); } OverlayEntry _createPopupDialog(videoItem) { diff --git a/lib/pages/video/detail/view.dart b/lib/pages/video/detail/view.dart index 82b35244a..b2ce60c1f 100644 --- a/lib/pages/video/detail/view.dart +++ b/lib/pages/video/detail/view.dart @@ -59,6 +59,7 @@ class _VideoDetailPageState extends State late bool enableVerticalExpand; late bool autoPiP; late bool pipNoDanmaku; + late bool removeSafeArea; final Floating floating = Floating(); // 生命周期监听 // late final AppLifecycleListener _lifecycleListener; @@ -66,7 +67,7 @@ class _VideoDetailPageState extends State RxBool isFullScreen = false.obs; late StreamSubscription fullScreenStatusListener; late final MethodChannel onUserLeaveHintListener; - StreamSubscription? _bufferedListener; + // StreamSubscription? _bufferedListener; @override void initState() { @@ -97,6 +98,9 @@ class _VideoDetailPageState extends State pipNoDanmaku = setting.get(SettingBoxKey.pipNoDanmaku, defaultValue: true); enableVerticalExpand = setting.get(SettingBoxKey.enableVerticalExpand, defaultValue: false); + removeSafeArea = setting.get(SettingBoxKey.videoPlayerRemoveSafeArea, + defaultValue: false); + if (removeSafeArea) hideStatusBar(); videoSourceInit(); appbarStreamListen(); // lifecycleListener(); @@ -146,6 +150,7 @@ class _VideoDetailPageState extends State if (status == PlayerStatus.completed) { shutdownTimerService.handleWaitingFinished(); bool notExitFlag = false; + /// 顺序播放 列表循环 if (plPlayerController!.playRepeat != PlayRepeat.pause && plPlayerController!.playRepeat != PlayRepeat.singleCycle) { @@ -246,7 +251,7 @@ class _VideoDetailPageState extends State AutoOrientation.portraitUpMode(); } shutdownTimerService.handleWaitingFinished(); - _bufferedListener?.cancel(); + // _bufferedListener?.cancel(); if (plPlayerController != null) { plPlayerController!.removeStatusLister(playerListener); fullScreenStatusListener.cancel(); @@ -262,7 +267,7 @@ class _VideoDetailPageState extends State @override // 离开当前页面时 void didPushNext() async { - _bufferedListener?.cancel(); + // _bufferedListener?.cancel(); /// 开启 if (setting.get(SettingBoxKey.enableAutoBrightness, defaultValue: false) @@ -392,10 +397,57 @@ class _VideoDetailPageState extends State return const SizedBox(); } }); + Widget manualPlayerWidget = Obx( + () => Visibility( + visible: videoDetailController.isShowCover.value && + videoDetailController.isEffective.value, + child: Stack( + children: [ + Positioned( + top: 0, + left: 0, + right: 0, + child: AppBar( + primary: false, + foregroundColor: Colors.white, + elevation: 0, + scrolledUnderElevation: 0, + backgroundColor: Colors.transparent, + actions: [ + IconButton( + tooltip: '稍后再看', + onPressed: () async { + var res = await UserHttp.toViewLater( + bvid: videoDetailController.bvid); + SmartDialog.showToast(res['msg']); + }, + icon: const Icon(Icons.history_outlined), + ), + const SizedBox(width: 14) + ], + ), + ), + Positioned( + right: 12, + bottom: 10, + child: IconButton( + tooltip: '播放', + onPressed: () => handlePlay(), + icon: Image.asset( + 'assets/images/play.png', + width: 60, + height: 60, + )), + ), + ], + )), + ); Widget childWhenDisabled = SafeArea( - top: MediaQuery.of(context).orientation == Orientation.portrait && + top: !removeSafeArea && + MediaQuery.of(context).orientation == Orientation.portrait && isFullScreen.value == true, - bottom: MediaQuery.of(context).orientation == Orientation.portrait && + bottom: !removeSafeArea && + MediaQuery.of(context).orientation == Orientation.portrait && isFullScreen.value == true, left: false, //isFullScreen != true, right: false, //isFullScreen != true, @@ -404,17 +456,26 @@ class _VideoDetailPageState extends State Scaffold( resizeToAvoidBottomInset: false, key: videoDetailController.scaffoldKey, - backgroundColor: Colors.black, - appBar: PreferredSize( - preferredSize: const Size.fromHeight(0), - child: AppBar( - backgroundColor: Colors.transparent, - elevation: 0, - // systemOverlayStyle: const SystemUiOverlayStyle( - // statusBarColor: Colors.transparent, - // statusBarIconBrightness: Brightness.light), - ), - ), + // backgroundColor: Colors.black, + appBar: removeSafeArea + ? null + : AppBar( + backgroundColor: Colors.black, + elevation: 0, + toolbarHeight: 0, + systemOverlayStyle: const SystemUiOverlayStyle( + statusBarIconBrightness: Brightness.light), + ), + // appBar: PreferredSize( + // preferredSize: const Size.fromHeight(0), + // child: AppBar( + // backgroundColor: Colors.transparent, + // elevation: 0, + // systemOverlayStyle: const SystemUiOverlayStyle( + // statusBarColor: Colors.transparent, + // statusBarIconBrightness: Brightness.light), + // ), + // ), body: Column( children: [ Obx( @@ -424,7 +485,7 @@ class _VideoDetailPageState extends State // print(videoDetailController.tabCtr.index); if (enableVerticalExpand && plPlayerController?.direction.value == 'vertical') { - videoheight = context.width * 5 / 4; + videoheight = context.width; } if (MediaQuery.of(context).orientation == Orientation.landscape && @@ -439,15 +500,17 @@ class _VideoDetailPageState extends State !isFullScreen.value && isShowing && mounted) { - showStatusBar(); + if (!removeSafeArea) showStatusBar(); } - return SizedBox( + return Container( + color: Colors.black, height: MediaQuery.of(context).orientation == Orientation.landscape || isFullScreen.value == true ? MediaQuery.sizeOf(context).height - (MediaQuery.of(context).orientation == - Orientation.landscape + Orientation.landscape || + removeSafeArea ? 0 : MediaQuery.of(context).padding.top) : videoheight, @@ -498,59 +561,7 @@ class _VideoDetailPageState extends State ), ), ), - Obx( - () => Visibility( - visible: videoDetailController - .isShowCover.value && - videoDetailController - .isEffective.value, - child: Stack( - children: [ - Positioned( - top: 0, - left: 0, - right: 0, - child: AppBar( - primary: false, - foregroundColor: Colors.white, - elevation: 0, - scrolledUnderElevation: 0, - backgroundColor: - Colors.transparent, - actions: [ - IconButton( - tooltip: '稍后再看', - onPressed: () async { - var res = await UserHttp - .toViewLater( - bvid: - videoDetailController - .bvid); - SmartDialog.showToast( - res['msg']); - }, - icon: const Icon( - Icons.history_outlined), - ), - const SizedBox(width: 14) - ], - ), - ), - Positioned( - right: 12, - bottom: 10, - child: IconButton( - tooltip: '播放', - onPressed: () => handlePlay(), - icon: Image.asset( - 'assets/images/play.png', - width: 60, - height: 60, - )), - ), - ], - )), - ), + manualPlayerWidget, ] ], )), @@ -629,15 +640,308 @@ class _VideoDetailPageState extends State ], ), ); + Widget childWhenDisabledAlmostSquareInner = Obx(() { + if (enableVerticalExpand && + plPlayerController?.direction.value == 'vertical') { + final double videoheight = context.height - + (removeSafeArea + ? 0 + : (MediaQuery.of(context).padding.top + + MediaQuery.of(context).padding.bottom)); + final double videowidth = videoheight * 9 / 16; + return Row(children: [ + SizedBox( + height: videoheight, + width: isFullScreen.value == true ? context.width : videowidth, + child: PopScope( + canPop: isFullScreen.value != true, + onPopInvoked: (bool didPop) { + if (isFullScreen.value == true) { + plPlayerController!.triggerFullScreen(status: false); + } + if (MediaQuery.of(context).orientation == + Orientation.landscape && + !horizontalScreen) { + verticalScreenForTwoSeconds(); + } + }, + child: Stack(children: [ + if (isShowing) plPlayer, + + /// 关闭自动播放时 手动播放 + if (!videoDetailController.autoPlay.value) ...[ + Obx( + () => Visibility( + visible: videoDetailController.isShowCover.value, + child: Positioned( + top: 0, + left: 0, + right: 0, + child: GestureDetector( + onTap: () { + handlePlay(); + }, + child: NetworkImgLayer( + type: 'emote', + src: videoDetailController.videoItem['pic'], + width: videowidth, + height: videoheight, + ), + ), + ), + ), + ), + manualPlayerWidget, + ] + ]), + ), + ), + Expanded( + child: TabBarView( + physics: const BouncingScrollPhysics(), + controller: videoDetailController.tabCtr, + children: [ + CustomScrollView( + key: const PageStorageKey('简介'), + slivers: [ + if (videoDetailController.videoType == + SearchType.video) ...[ + const VideoIntroPanel(), + ] else if (videoDetailController.videoType == + SearchType.media_bangumi) ...[ + Obx(() => BangumiIntroPanel( + cid: videoDetailController.cid.value)), + ], + SliverToBoxAdapter( + child: Divider( + indent: 12, + endIndent: 12, + color: Theme.of(context).dividerColor.withOpacity(0.06), + ), + ), + const RelatedVideoPanel(), + ], + ), + Obx( + () => VideoReplyPanel( + bvid: videoDetailController.bvid, + oid: videoDetailController.oid.value, + ), + ) + ], + ), + ), + ]); + } + final double videoheight = context.height / 2.5; + final double videowidth = context.width; + return Column( + children: [ + SizedBox( + width: videowidth, + height: isFullScreen.value == true + ? context.height - + (removeSafeArea + ? 0 + : (MediaQuery.of(context).padding.top + + MediaQuery.of(context).padding.bottom)) + : videoheight, + child: PopScope( + canPop: isFullScreen.value != true, + onPopInvoked: (bool didPop) { + if (isFullScreen.value == true) { + plPlayerController!.triggerFullScreen(status: false); + } + if (MediaQuery.of(context).orientation == + Orientation.landscape && + !horizontalScreen) { + verticalScreenForTwoSeconds(); + } + }, + child: Stack(children: [ + if (isShowing) plPlayer, + + /// 关闭自动播放时 手动播放 + if (!videoDetailController.autoPlay.value) ...[ + Obx( + () => Visibility( + visible: videoDetailController.isShowCover.value, + child: Positioned( + top: 0, + left: 0, + right: 0, + child: GestureDetector( + onTap: () { + handlePlay(); + }, + child: NetworkImgLayer( + type: 'emote', + src: videoDetailController.videoItem['pic'], + width: videowidth, + height: videoheight, + ), + ), + ), + ), + ), + manualPlayerWidget, + ] + ]), + ), + ), + Expanded( + child: Row(children: [ + Expanded( + child: CustomScrollView( + key: PageStorageKey('简介${videoDetailController.bvid}'), + slivers: [ + if (videoDetailController.videoType == SearchType.video) ...[ + const VideoIntroPanel(), + const RelatedVideoPanel(), + ] else if (videoDetailController.videoType == + SearchType.media_bangumi) ...[ + Obx(() => + BangumiIntroPanel(cid: videoDetailController.cid.value)), + ] + ], + )), + Expanded( + child: Obx( + () => VideoReplyPanel( + bvid: videoDetailController.bvid, + oid: videoDetailController.oid.value, + ), + ), + ) + ])) + ], + ); + }); Widget childWhenDisabledLandscapeInner = Obx(() { - // 系数是以下三个方程(分别代表特定平板、折叠屏内屏、普通手机横屏尺寸)的近似解 - // 820x+1180y+983.67z=450 - // 1812x+2176y+1985.68z=680 - // 1080x+2340y+1589.72z=560 - final double videoheight = sqrt(context.height * context.width) * 12.555 - - context.height * 7.690 - - context.width * 4.741; - final double videowidth = videoheight * 16 / 9; + if (enableVerticalExpand && + plPlayerController?.direction.value == 'vertical') { + final double videoheight = context.height - + (removeSafeArea + ? 0 + : (MediaQuery.of(context).padding.top + + MediaQuery.of(context).padding.bottom)); + final double videowidth = videoheight * 9 / 16; + return Row( + children: [ + SizedBox( + height: videoheight, + width: isFullScreen.value == true ? context.width : videowidth, + child: PopScope( + canPop: isFullScreen.value != true, + onPopInvoked: (bool didPop) { + if (isFullScreen.value == true) { + plPlayerController!.triggerFullScreen(status: false); + } + if (MediaQuery.of(context).orientation == + Orientation.landscape && + !horizontalScreen) { + verticalScreenForTwoSeconds(); + } + }, + child: Stack( + children: [ + if (isShowing) plPlayer, + + /// 关闭自动播放时 手动播放 + if (!videoDetailController.autoPlay.value) ...[ + Obx( + () => Visibility( + visible: videoDetailController.isShowCover.value, + child: Positioned( + top: 0, + left: 0, + right: 0, + child: GestureDetector( + onTap: () { + handlePlay(); + }, + child: NetworkImgLayer( + type: 'emote', + src: videoDetailController.videoItem['pic'], + width: videowidth, + height: videoheight, + ), + ), + ), + ), + ), + manualPlayerWidget, + ] + ], + ), + ), + ), + Expanded( + child: Row(children: [ + Expanded( + child: CustomScrollView( + key: PageStorageKey('简介${videoDetailController.bvid}'), + slivers: [ + if (videoDetailController.videoType == SearchType.video) ...[ + const VideoIntroPanel(), + const RelatedVideoPanel(), + ] else if (videoDetailController.videoType == + SearchType.media_bangumi) ...[ + Obx(() => BangumiIntroPanel( + cid: videoDetailController.cid.value)), + ] + ], + )), + Expanded( + child: Obx( + () => VideoReplyPanel( + bvid: videoDetailController.bvid, + oid: videoDetailController.oid.value, + ), + ), + ) + ])) + // Expanded( + // child: TabBarView( + // physics: const BouncingScrollPhysics(), + // controller: videoDetailController.tabCtr, + // children: [ + // CustomScrollView( + // key: const PageStorageKey('简介'), + // slivers: [ + // if (videoDetailController.videoType == + // SearchType.video) ...[ + // const VideoIntroPanel(), + // ] else if (videoDetailController.videoType == + // SearchType.media_bangumi) ...[ + // Obx(() => BangumiIntroPanel( + // cid: videoDetailController.cid.value)), + // ], + // SliverToBoxAdapter( + // child: Divider( + // indent: 12, + // endIndent: 12, + // color: Theme.of(context).dividerColor.withOpacity(0.06), + // ), + // ), + // const RelatedVideoPanel(), + // ], + // ), + // Obx( + // () => VideoReplyPanel( + // bvid: videoDetailController.bvid, + // oid: videoDetailController.oid.value, + // ), + // ) + // ], + // ), + // ), + ], + ); + } + final double videowidth = + max(context.height / context.width * 1.04, 1 / 2) * context.width; + final double videoheight = videowidth * 9 / 16; return Row( children: [ Column( @@ -689,57 +993,7 @@ class _VideoDetailPageState extends State ), ), ), - Obx( - () => Visibility( - visible: videoDetailController - .isShowCover.value && - videoDetailController.isEffective.value, - child: Stack( - children: [ - Positioned( - top: 0, - left: 0, - right: 0, - child: AppBar( - primary: false, - foregroundColor: Colors.white, - elevation: 0, - scrolledUnderElevation: 0, - backgroundColor: Colors.transparent, - actions: [ - IconButton( - tooltip: '稍后再看', - onPressed: () async { - var res = - await UserHttp.toViewLater( - bvid: - videoDetailController - .bvid); - SmartDialog.showToast( - res['msg']); - }, - icon: const Icon( - Icons.history_outlined), - ), - const SizedBox(width: 14) - ], - ), - ), - Positioned( - right: 12, - bottom: 10, - child: IconButton( - tooltip: '播放', - onPressed: () => handlePlay(), - icon: Image.asset( - 'assets/images/play.png', - width: 60, - height: 60, - )), - ), - ], - )), - ), + manualPlayerWidget, ] ], ))), @@ -750,8 +1004,10 @@ class _VideoDetailPageState extends State ? 0 : context.height - videoheight - - MediaQuery.of(context).padding.top - - MediaQuery.of(context).padding.bottom, + (removeSafeArea + ? 0 + : (MediaQuery.of(context).padding.top + + MediaQuery.of(context).padding.bottom)), child: CustomScrollView( key: PageStorageKey( '简介${videoDetailController.bvid}'), @@ -759,6 +1015,7 @@ class _VideoDetailPageState extends State if (videoDetailController.videoType == SearchType.video) ...[ const VideoIntroPanel(), + const RelatedVideoPanel(), ] else if (videoDetailController.videoType == SearchType.media_bangumi) ...[ Obx(() => BangumiIntroPanel( @@ -769,59 +1026,88 @@ class _VideoDetailPageState extends State ], ), SizedBox( - width: isFullScreen.value == true - ? 0 - : (context.width - - MediaQuery.of(context).padding.left - - MediaQuery.of(context).padding.right - - videowidth), - height: context.height - - MediaQuery.of(context).padding.top - - MediaQuery.of(context).padding.bottom, - child: TabBarView( - physics: const BouncingScrollPhysics(), - controller: videoDetailController.tabCtr, - children: [ - if (videoDetailController.videoType == SearchType.video) - const CustomScrollView( - slivers: [ - RelatedVideoPanel(), - ], - ), - Obx( - () => VideoReplyPanel( - bvid: videoDetailController.bvid, - oid: videoDetailController.oid.value, - ), - ) - ], - ), - ) + width: isFullScreen.value == true + ? 0 + : (context.width - + videowidth - + (removeSafeArea + ? 0 + : (MediaQuery.of(context).padding.left + + MediaQuery.of(context).padding.right))), + height: context.height - + (removeSafeArea + ? 0 + : (MediaQuery.of(context).padding.top + + MediaQuery.of(context).padding.bottom)), + child: + // TabBarView( + // physics: const BouncingScrollPhysics(), + // controller: videoDetailController.tabCtr, + // children: [ + // if (videoDetailController.videoType == SearchType.video) + // const CustomScrollView( + // slivers: [ + // RelatedVideoPanel(), + // ], + // ), + Obx( + () => VideoReplyPanel( + bvid: videoDetailController.bvid, + oid: videoDetailController.oid.value, + ), + ) + // ], + // ), + ) ], ); }); - Widget childWhenDisabledLandscape = Container( - color: Theme.of(context).colorScheme.background, - child: SafeArea( - left: isFullScreen.value != true, - right: isFullScreen.value != true, - child: Stack(children: [ - Scaffold( - resizeToAvoidBottomInset: false, - key: videoDetailController.scaffoldKey, - backgroundColor: Theme.of(context).colorScheme.background, - appBar: PreferredSize( - preferredSize: const Size.fromHeight(0), - child: AppBar( - backgroundColor: Colors.transparent, + Widget childWhenDisabledLandscape = SafeArea( + left: !removeSafeArea && isFullScreen.value != true, + right: !removeSafeArea && isFullScreen.value != true, + top: !removeSafeArea, + bottom: !removeSafeArea, + child: Stack(children: [ + Scaffold( + resizeToAvoidBottomInset: false, + key: videoDetailController.scaffoldKey, + backgroundColor: Colors.black, + appBar: removeSafeArea + ? null + : AppBar( + backgroundColor: Colors.black, elevation: 0, - // systemOverlayStyle: const SystemUiOverlayStyle( - // statusBarColor: Colors.transparent, - // statusBarIconBrightness: Brightness.dark), + toolbarHeight: 0, + systemOverlayStyle: const SystemUiOverlayStyle( + statusBarIconBrightness: Brightness.light), ), - ), - body: childWhenDisabledLandscapeInner) - ]))); + body: Container( + color: Theme.of(context).colorScheme.background, + child: childWhenDisabledLandscapeInner)) + ])); + Widget childWhenDisabledAlmostSquare = SafeArea( + left: !removeSafeArea && isFullScreen.value != true, + right: !removeSafeArea && isFullScreen.value != true, + top: !removeSafeArea, + bottom: !removeSafeArea, + child: Stack(children: [ + Scaffold( + resizeToAvoidBottomInset: false, + key: videoDetailController.scaffoldKey, + backgroundColor: Colors.black, + appBar: removeSafeArea + ? null + : AppBar( + backgroundColor: Colors.black, + elevation: 0, + toolbarHeight: 0, + systemOverlayStyle: const SystemUiOverlayStyle( + statusBarIconBrightness: Brightness.light), + ), + body: Container( + color: Theme.of(context).colorScheme.background, + child: childWhenDisabledAlmostSquareInner)) + ])); Widget childWhenEnabled = Obx( () => !videoDetailController.autoPlay.value ? const SizedBox() @@ -851,7 +1137,7 @@ class _VideoDetailPageState extends State ), ), ); - if (!horizontalScreen) { + Widget autoChoose(Widget childWhenDisabled) { if (Platform.isAndroid) { return PiPSwitcher( childWhenDisabled: childWhenDisabled, @@ -862,34 +1148,59 @@ class _VideoDetailPageState extends State return childWhenDisabled; } - return OrientationBuilder( - builder: (BuildContext context, Orientation orientation) { + if (!horizontalScreen) { + return autoChoose(childWhenDisabled); + } + + return LayoutBuilder( + builder: (BuildContext context, BoxConstraints constraints) { if (!isShowing) { return ColoredBox(color: Theme.of(context).colorScheme.background); } - if (orientation == Orientation.landscape) { - if (!horizontalScreen) { - hideStatusBar(); - videoDetailController.hiddenReplyReplyPanel(); + if (constraints.maxWidth > constraints.maxHeight * 1.25) { +// hideStatusBar(); +// videoDetailController.hiddenReplyReplyPanel(); + return autoChoose(childWhenDisabledLandscape); + } else if (constraints.maxWidth * (9 / 16) < + (2 / 5) * constraints.maxHeight) { + if (!isFullScreen.value) { + if (!removeSafeArea) showStatusBar(); } + return autoChoose(childWhenDisabled); } else { if (!isFullScreen.value) { - showStatusBar(); + if (!removeSafeArea) showStatusBar(); } + return autoChoose(childWhenDisabledAlmostSquare); } - if (Platform.isAndroid) { - return PiPSwitcher( - childWhenDisabled: - !horizontalScreen || orientation == Orientation.portrait - ? childWhenDisabled - : childWhenDisabledLandscape, - childWhenEnabled: childWhenEnabled, - floating: floating, - ); - } - return !horizontalScreen || orientation == Orientation.portrait - ? childWhenDisabled - : childWhenDisabledLandscape; + // + // final Orientation orientation = + // constraints.maxWidth > constraints.maxHeight * 1.25 + // ? Orientation.landscape + // : Orientation.portrait; + // if (orientation == Orientation.landscape) { + // if (!horizontalScreen) { + // hideStatusBar(); + // videoDetailController.hiddenReplyReplyPanel(); + // } + // } else { + // if (!isFullScreen.value) { + // showStatusBar(); + // } + // } + // if (Platform.isAndroid) { + // return PiPSwitcher( + // childWhenDisabled: + // !horizontalScreen || orientation == Orientation.portrait + // ? childWhenDisabled + // : childWhenDisabledLandscape, + // childWhenEnabled: childWhenEnabled, + // floating: floating, + // ); + // } + // return !horizontalScreen || orientation == Orientation.portrait + // ? childWhenDisabled + // : childWhenDisabledLandscape; }); } } diff --git a/lib/pages/video/detail/widgets/header_control.dart b/lib/pages/video/detail/widgets/header_control.dart index b06368193..2eaca001e 100644 --- a/lib/pages/video/detail/widgets/header_control.dart +++ b/lib/pages/video/detail/widgets/header_control.dart @@ -237,7 +237,7 @@ class _HeaderControlState extends State { builder: (BuildContext context) { // TODO: 支持更多类型和颜色的弹幕 return AlertDialog( - title: const Text('发送弹幕(测试)'), + title: const Text('发送弹幕'), content: StatefulBuilder( builder: (BuildContext context, StateSetter setState) { return TextField( @@ -733,6 +733,8 @@ class _HeaderControlState extends State { double danmakuDurationVal = widget.controller!.danmakuDurationVal; // 弹幕描边 double strokeWidth = widget.controller!.strokeWidth; + // 字体粗细 + int fontWeight = widget.controller!.fontWeight; final DanmakuController danmakuController = widget.controller!.danmakuController!; @@ -762,7 +764,20 @@ class _HeaderControlState extends State { child: Center(child: Text('弹幕设置', style: titleStyle)), ), const SizedBox(height: 10), - Text('智能云屏蔽 $danmakuWeight 级'), + Row( + children: [ + Text('智能云屏蔽 $danmakuWeight 级'), + const Spacer(), + TextButton( + style: TextButton.styleFrom( + padding: EdgeInsets.zero, + minimumSize: Size.zero, + tapTargetSize: MaterialTapTargetSize.shrinkWrap, + ), + onPressed: () => Get.toNamed('/danmakuBlock'), + child: const Text("屏蔽管理")) + ], + ), Padding( padding: const EdgeInsets.only( top: 0, @@ -909,6 +924,45 @@ class _HeaderControlState extends State { ), ), ), + Text('字体粗细 ${fontWeight + 1}(可能无法精确调节)'), + Padding( + padding: const EdgeInsets.only( + top: 0, + bottom: 6, + left: 10, + right: 10, + ), + child: SliderTheme( + data: SliderThemeData( + trackShape: MSliderTrackShape(), + thumbColor: Theme.of(context).colorScheme.primary, + activeTrackColor: Theme.of(context).colorScheme.primary, + trackHeight: 10, + thumbShape: const RoundSliderThumbShape( + enabledThumbRadius: 6.0), + ), + child: Slider( + min: 0, + max: 8, + value: fontWeight.toDouble(), + divisions: 9, + label: '${fontWeight + 1}', + onChanged: (double val) { + fontWeight = val.toInt(); + widget.controller!.fontWeight = fontWeight; + widget.controller?.putDanmakuSettings(); + setState(() {}); + try { + final DanmakuOption currentOption = + danmakuController.option; + final DanmakuOption updatedOption = + currentOption.copyWith(fontWeight: fontWeight); + danmakuController.updateOption(updatedOption); + } catch (_) {} + }, + ), + ), + ), Text('描边粗细 $strokeWidth'), Padding( padding: const EdgeInsets.only( diff --git a/lib/pages/whisper/view.dart b/lib/pages/whisper/view.dart index 83092135a..d297ae8fb 100644 --- a/lib/pages/whisper/view.dart +++ b/lib/pages/whisper/view.dart @@ -203,17 +203,19 @@ class _WhisperPageState extends State { .content != '' ? (sessionList[i] - .lastMsg - .content['text'] ?? - sessionList[i] - .lastMsg - .content['content'] ?? - sessionList[i] - .lastMsg - .content['title'] ?? - sessionList[i] - .lastMsg - .content['reply_content']) + .lastMsg + .content['text'] ?? + sessionList[i] + .lastMsg + .content['content'] ?? + sessionList[i] + .lastMsg + .content['title'] ?? + sessionList[i] + .lastMsg + .content[ + 'reply_content']) ?? + sessionList[i].lastMsg.content : '不支持的消息类型', maxLines: 1, overflow: TextOverflow.ellipsis, diff --git a/lib/plugin/pl_player/controller.dart b/lib/plugin/pl_player/controller.dart index 243dff74c..9f9dd929e 100644 --- a/lib/plugin/pl_player/controller.dart +++ b/lib/plugin/pl_player/controller.dart @@ -243,6 +243,7 @@ class PlPlayerController { late double opacityVal; late double fontSizeVal; late double strokeWidth; + late int fontWeight; late double danmakuDurationVal; late List speedsList; double? defaultDuration; @@ -286,12 +287,10 @@ class PlPlayerController { setting.get(SettingBoxKey.enableShowDanmaku, defaultValue: false); danmakuWeight.value = setting.get(SettingBoxKey.danmakuWeight, defaultValue: 0); - blockTypes = - setting.get(SettingBoxKey.danmakuBlockType, defaultValue: []); + blockTypes = setting.get(SettingBoxKey.danmakuBlockType, defaultValue: []); showArea = setting.get(SettingBoxKey.danmakuShowArea, defaultValue: 0.5); // 不透明度 - opacityVal = - setting.get(SettingBoxKey.danmakuOpacity, defaultValue: 1.0); + opacityVal = setting.get(SettingBoxKey.danmakuOpacity, defaultValue: 1.0); // 字体大小 fontSizeVal = setting.get(SettingBoxKey.danmakuFontScale, defaultValue: 1.0); @@ -300,6 +299,8 @@ class PlPlayerController { setting.get(SettingBoxKey.danmakuDuration, defaultValue: 4.0); // 描边粗细 strokeWidth = setting.get(SettingBoxKey.strokeWidth, defaultValue: 1.5); + // 弹幕字体粗细 + fontWeight = setting.get(SettingBoxKey.fontWeight, defaultValue: 5); playRepeat = PlayRepeat.values.toList().firstWhere( (e) => e.value == @@ -312,7 +313,7 @@ class PlPlayerController { .get(SettingBoxKey.enableAutoLongPressSpeed, defaultValue: false); // 后台播放 _continuePlayInBackground.value = - setting.get(SettingBoxKey.continuePlayInBackground, defaultValue: true); + setting.get(SettingBoxKey.continuePlayInBackground, defaultValue: false); if (!enableAutoLongPressSpeed) { _longPressSpeed.value = videoStorage .get(VideoBoxKey.longPressSpeedDefault, defaultValue: 3.0); @@ -356,7 +357,7 @@ class PlPlayerController { double speed = 1.0, // 硬件加速 bool enableHA = true, - String? hwdec, + String? hwdec, double? width, double? height, Duration? duration, @@ -458,7 +459,6 @@ class PlPlayerController { bufferSize: bufferSize, ), ); - var pp = player.platform as NativePlayer; // 解除倍速限制 await pp.setProperty("af", "scaletempo2=max-speed=8"); @@ -515,7 +515,7 @@ class PlPlayerController { configuration: VideoControllerConfiguration( enableHardwareAcceleration: enableHA, androidAttachSurfaceAfterVideoParameters: false, - hwdec: hwdec, + hwdec: enableHA ? hwdec: null, ), ); @@ -991,8 +991,7 @@ class PlPlayerController { /// 设置后台播放 Future setBackgroundPlay(bool val) async { - _continuePlayInBackground.value = val; - setting.put(SettingBoxKey.continuePlayInBackground, val); + setting.put(SettingBoxKey.enableBackgroundPlay, val); videoPlayerServiceHandler.revalidateSetting(); } @@ -1150,6 +1149,7 @@ class PlPlayerController { setting.put(SettingBoxKey.danmakuFontScale, fontSizeVal); setting.put(SettingBoxKey.danmakuDuration, danmakuDurationVal); setting.put(SettingBoxKey.strokeWidth, strokeWidth); + setting.put(SettingBoxKey.fontWeight, fontWeight); } Future dispose({String type = 'single'}) async { @@ -1161,6 +1161,7 @@ class PlPlayerController { return; } _playerCount.value = 0; + pause(); try { _timer?.cancel(); _timerForVolume?.cancel(); diff --git a/lib/plugin/pl_player/view.dart b/lib/plugin/pl_player/view.dart index 5d5f76bfb..6c391e84a 100644 --- a/lib/plugin/pl_player/view.dart +++ b/lib/plugin/pl_player/view.dart @@ -5,6 +5,7 @@ import 'package:PiliPalaX/pages/video/detail/introduction/controller.dart'; import 'package:PiliPalaX/utils/id_utils.dart'; import 'package:flutter/material.dart'; import 'package:flutter/rendering.dart'; +import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; import 'package:flutter_volume_controller/flutter_volume_controller.dart'; import 'package:font_awesome_flutter/font_awesome_flutter.dart'; import 'package:get/get.dart'; @@ -17,6 +18,7 @@ import 'package:PiliPalaX/plugin/pl_player/models/fullscreen_mode.dart'; import 'package:PiliPalaX/plugin/pl_player/utils.dart'; import 'package:PiliPalaX/utils/feed_back.dart'; import 'package:PiliPalaX/utils/storage.dart'; +import 'package:saver_gallery/saver_gallery.dart'; import 'package:screen_brightness/screen_brightness.dart'; import '../../common/widgets/audio_video_progress_bar.dart'; @@ -330,8 +332,10 @@ class _PLVideoPlayerState extends State episodes.addAll(pages); changeFucCall = videoIntroController!.changeSeasonOrbangu; } else if (isBangumi) { - episodes.addAll(bangumiIntroController!.bangumiDetail.value.episodes!); - changeFucCall = bangumiIntroController!.changeSeasonOrbangu; + episodes.addAll(bangumiIntroController! + .bangumiDetail.value.episodes!); + changeFucCall = + bangumiIntroController!.changeSeasonOrbangu; } ListSheet( episodes: episodes, @@ -500,11 +504,11 @@ class _PLVideoPlayerState extends State controls: NoVideoControls, pauseUponEnteringBackgroundMode: !_.continuePlayInBackground.value, resumeUponEnteringForegroundMode: true, - subtitleViewConfiguration: const SubtitleViewConfiguration( - style: subTitleStyle, - padding: EdgeInsets.all(24.0), - textScaleFactor: 1.0, - ), + // 字幕尺寸调节 + subtitleViewConfiguration: SubtitleViewConfiguration( + style: subTitleStyle, + padding: const EdgeInsets.all(24.0), + textScaleFactor: MediaQuery.textScaleFactorOf(context)), fit: _.videoFit.value, ), ), @@ -848,36 +852,32 @@ class _PLVideoPlayerState extends State ), // 头部、底部控制条 - SafeArea( - top: false, - bottom: false, - child: Obx( - () => Column( - children: [ - if (widget.headerControl != null || _.headerControl != null) - ClipRect( - child: AppBarAni( - controller: animationController, - visible: !_.controlsLock.value && _.showControls.value, - position: 'top', - child: widget.headerControl ?? _.headerControl!, - ), - ), - const Spacer(), + Obx( + () => Column( + children: [ + if (widget.headerControl != null || _.headerControl != null) ClipRect( child: AppBarAni( controller: animationController, visible: !_.controlsLock.value && _.showControls.value, - position: 'bottom', - child: widget.bottomControl ?? - BottomControl( - controller: widget.controller, - buildBottomControl: buildBottomControl(), - ), + position: 'top', + child: widget.headerControl ?? _.headerControl!, ), ), - ], - ), + const Spacer(), + ClipRect( + child: AppBarAni( + controller: animationController, + visible: !_.controlsLock.value && _.showControls.value, + position: 'bottom', + child: widget.bottomControl ?? + BottomControl( + controller: widget.controller, + buildBottomControl: buildBottomControl(), + ), + ), + ), + ], ), ), @@ -981,7 +981,7 @@ class _PLVideoPlayerState extends State child: Align( alignment: Alignment.centerLeft, child: FractionalTranslation( - translation: const Offset(1, 0.0), + translation: const Offset(1, -0.4), child: Visibility( visible: _.showControls.value, child: ComBtn( @@ -1000,6 +1000,42 @@ class _PLVideoPlayerState extends State ), ), ), + + // 截图 + Obx( + () => Align( + alignment: Alignment.centerRight, + child: FractionalTranslation( + translation: const Offset(-1, -0.4), + child: Visibility( + visible: _.showControls.value && _.isFullScreen.value, + child: ComBtn( + tooltip: '截图', + icon: const Icon( + Icons.photo_camera, + size: 20, + color: Colors.white, + ), + fuc: () => { + _.videoPlayerController + ?.screenshot(format: 'image/png') + .then((value) { + if (value != null) { + SmartDialog.showToast('截图成功'); + String _name = DateTime.now().toString(); + SaverGallery.saveImage(value, + name: _name, + androidRelativePath: "Pictures/Screenshots", + androidExistNotSave: false); + SmartDialog.showToast('$_name.png已保存到相册/截图'); + } + }) + }, + ), + ), + ), + ), + ), // Obx(() { if (_.dataStatus.loading || _.isBuffering.value) { diff --git a/lib/plugin/pl_player/widgets/app_bar_ani.dart b/lib/plugin/pl_player/widgets/app_bar_ani.dart index 9a3af2670..fa65783ff 100644 --- a/lib/plugin/pl_player/widgets/app_bar_ani.dart +++ b/lib/plugin/pl_player/widgets/app_bar_ani.dart @@ -50,7 +50,7 @@ class AppBarAni extends StatelessWidget implements PreferredSizeWidget { tileMode: TileMode.mirror, ), ), - child: child, + child: SafeArea(child: child), ), ); } diff --git a/lib/router/app_pages.dart b/lib/router/app_pages.dart index bacbc48cb..2d5ef4f0d 100644 --- a/lib/router/app_pages.dart +++ b/lib/router/app_pages.dart @@ -11,6 +11,7 @@ import 'package:PiliPalaX/pages/setting/pages/logs.dart'; import '../pages/about/index.dart'; import '../pages/blacklist/index.dart'; +import '../pages/danmaku_block/index.dart'; import '../pages/dynamics/detail/index.dart'; import '../pages/dynamics/index.dart'; import '../pages/fan/index.dart'; @@ -184,6 +185,8 @@ class Routes { CustomGetPage(name: '/subscription', page: () => const SubPage()), // 订阅详情 CustomGetPage(name: '/subDetail', page: () => const SubDetailPage()), + // 弹幕屏蔽管理 + CustomGetPage(name: '/danmakuBlock', page: () => const DanmakuBlockPage()), ]; } diff --git a/lib/utils/cache_manage.dart b/lib/utils/cache_manage.dart index e8aac6259..daa58bc2a 100644 --- a/lib/utils/cache_manage.dart +++ b/lib/utils/cache_manage.dart @@ -3,6 +3,7 @@ import 'dart:io'; import 'package:flutter/material.dart'; import 'package:path_provider/path_provider.dart'; import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; +import 'package:get/get.dart'; class CacheManage { CacheManage._internal(); @@ -76,17 +77,16 @@ class CacheManage { } // 清除缓存 - Future clearCacheAll() async { - bool cleanStatus = await SmartDialog.show( - useSystem: true, - animationType: SmartAnimationType.centerFade_otherSlide, - builder: (BuildContext context) { + Future clearCacheAll(BuildContext context) async { + bool cleanStatus = await showDialog( + context: context, + builder: (context) { return AlertDialog( title: const Text('提示'), content: const Text('该操作将清除图片及网络请求缓存数据,确认清除?'), actions: [ TextButton( - onPressed: (() => {SmartDialog.dismiss()}), + onPressed: () => Get.back(), child: Text( '取消', style: TextStyle(color: Theme.of(context).colorScheme.outline), @@ -94,7 +94,7 @@ class CacheManage { ), TextButton( onPressed: () async { - SmartDialog.dismiss(); + Get.back(); SmartDialog.showLoading(msg: '正在清除...'); try { // 清除缓存 图片缓存 diff --git a/lib/utils/download.dart b/lib/utils/download.dart index 7ffe6cc9c..5e37ef97a 100644 --- a/lib/utils/download.dart +++ b/lib/utils/download.dart @@ -10,15 +10,15 @@ import 'dart:io'; class DownloadUtils { // 获取存储权限 - static Future requestStoragePer() async { + static Future requestStoragePer(BuildContext context) async { await Permission.storage.request(); PermissionStatus status = await Permission.storage.status; if (status == PermissionStatus.denied || status == PermissionStatus.permanentlyDenied) { - SmartDialog.show( - useSystem: true, - animationType: SmartAnimationType.centerFade_otherSlide, - builder: (BuildContext context) { + if (!context.mounted) return false; + await showDialog( + context: context, + builder: (context) { return AlertDialog( title: const Text('提示'), content: const Text('存储权限未授权'), @@ -69,21 +69,22 @@ class DownloadUtils { } } - static Future checkPermissionDependOnSdkInt() async { + static Future checkPermissionDependOnSdkInt(BuildContext context) async { if (Platform.isAndroid) { final androidInfo = await DeviceInfoPlugin().androidInfo; if (androidInfo.version.sdkInt <= 32) { - return await requestStoragePer(); + if (!context.mounted) return false; + return await requestStoragePer(context); } else { return await requestPhotoPer(); } } - return await requestStoragePer(); + return await requestStoragePer(context); } - static Future downloadImg(String imgUrl, + static Future downloadImg(BuildContext context, String imgUrl, {String imgType = 'cover'}) async { try { - if (!await checkPermissionDependOnSdkInt()) { + if (!await checkPermissionDependOnSdkInt(context)) { // // return false; } SmartDialog.showLoading(msg: '正在下载原图'); diff --git a/lib/utils/grid.dart b/lib/utils/grid.dart index 119e67676..322153b0e 100644 --- a/lib/utils/grid.dart +++ b/lib/utils/grid.dart @@ -1,18 +1,112 @@ +import 'dart:math'; + import 'package:flutter/cupertino.dart'; +import 'package:flutter/rendering.dart'; import 'storage.dart'; class Grid { static double maxRowWidth = GStrorage.setting.get(SettingBoxKey.maxRowWidth, defaultValue: 240.0) as double; + // + // static double calculateActualWidth(BuildContext context, double maxCrossAxisExtent, double crossAxisSpacing, {double? screenWidthOffset}) { + // double screenWidth = MediaQuery.of(context).size.width; + // if (screenWidthOffset != null) { + // screenWidth -= screenWidthOffset; + // } + // if (GStrorage.setting.get(SettingBoxKey.adaptiveNavBar, defaultValue: false) as bool) { + // screenWidth -= 55; + // } + // int columnCount = ((screenWidth - crossAxisSpacing) / (maxCrossAxisExtent + crossAxisSpacing)).ceil(); + // if (columnCount < 1){ + // columnCount = 1; + // } + // double columnWidth = (screenWidth - crossAxisSpacing) ~/ columnCount - crossAxisSpacing; + // return columnWidth; + // } +} +class SliverGridDelegateWithExtentAndRatio extends SliverGridDelegate { + /// Creates a delegate that makes grid layouts with tiles that have a maximum + /// cross-axis extent. + /// + /// The [maxCrossAxisExtent], [mainAxisExtent], [mainAxisSpacing], + /// and [crossAxisSpacing] arguments must not be negative. + /// The [childAspectRatio] argument must be greater than zero. + const SliverGridDelegateWithExtentAndRatio({ + required this.maxCrossAxisExtent, + this.mainAxisSpacing = 0.0, + this.crossAxisSpacing = 0.0, + this.childAspectRatio = 1.0, + this.mainAxisExtent = 0.0, + }) : assert(maxCrossAxisExtent > 0), + assert(mainAxisSpacing >= 0), + assert(crossAxisSpacing >= 0), + assert(childAspectRatio > 0); + + /// The maximum extent of tiles in the cross axis. + /// + /// This delegate will select a cross-axis extent for the tiles that is as + /// large as possible subject to the following conditions: + /// + /// - The extent evenly divides the cross-axis extent of the grid. + /// - The extent is at most [maxCrossAxisExtent]. + /// + /// For example, if the grid is vertical, the grid is 500.0 pixels wide, and + /// [maxCrossAxisExtent] is 150.0, this delegate will create a grid with 4 + /// columns that are 125.0 pixels wide. + final double maxCrossAxisExtent; + + /// The number of logical pixels between each child along the main axis. + final double mainAxisSpacing; + + /// The number of logical pixels between each child along the cross axis. + final double crossAxisSpacing; + + /// The ratio of the cross-axis to the main-axis extent of each child. + final double childAspectRatio; + + + /// The extent of each tile in the main axis. If provided, it would add + /// after [childAspectRatio] is used. + final double mainAxisExtent; - static double calculateActualWidth(BuildContext context, double maxCrossAxisExtent, double crossAxisSpacing, {double? screenWidthOffset}) { - double screenWidth = MediaQuery.of(context).size.width; - if (screenWidthOffset != null) { - screenWidth -= screenWidthOffset; - } - int columnCount = ((screenWidth - crossAxisSpacing) / (maxCrossAxisExtent + crossAxisSpacing)).ceil(); - if (columnCount < 1){ - columnCount = 1; - } - double columnWidth = (screenWidth - crossAxisSpacing) ~/ columnCount - crossAxisSpacing; - return columnWidth; + + + bool _debugAssertIsValid(double crossAxisExtent) { + assert(crossAxisExtent > 0.0); + assert(maxCrossAxisExtent > 0.0); + assert(mainAxisSpacing >= 0.0); + assert(crossAxisSpacing >= 0.0); + assert(childAspectRatio > 0.0); + return true; } -} + + @override + SliverGridLayout getLayout(SliverConstraints constraints) { + assert(_debugAssertIsValid(constraints.crossAxisExtent)); + int crossAxisCount = ((constraints.crossAxisExtent - crossAxisSpacing) / (maxCrossAxisExtent + crossAxisSpacing)).ceil(); + // Ensure a minimum count of 1, can be zero and result in an infinite extent + // below when the window size is 0. + crossAxisCount = max(1, crossAxisCount); + final double usableCrossAxisExtent = max( + 0.0, + constraints.crossAxisExtent - crossAxisSpacing * (crossAxisCount - 1), + ); + final double childCrossAxisExtent = usableCrossAxisExtent / crossAxisCount; + final double childMainAxisExtent = childCrossAxisExtent / childAspectRatio + mainAxisExtent; + return SliverGridRegularTileLayout( + crossAxisCount: crossAxisCount, + mainAxisStride: childMainAxisExtent + mainAxisSpacing, + crossAxisStride: childCrossAxisExtent + crossAxisSpacing, + childMainAxisExtent: childMainAxisExtent, + childCrossAxisExtent: childCrossAxisExtent, + reverseCrossAxis: axisDirectionIsReversed(constraints.crossAxisDirection), + ); + } + + @override + bool shouldRelayout(SliverGridDelegateWithExtentAndRatio oldDelegate) { + return oldDelegate.maxCrossAxisExtent != maxCrossAxisExtent + || oldDelegate.mainAxisSpacing != mainAxisSpacing + || oldDelegate.crossAxisSpacing != crossAxisSpacing + || oldDelegate.childAspectRatio != childAspectRatio + || oldDelegate.mainAxisExtent != mainAxisExtent; + } +} \ No newline at end of file diff --git a/lib/utils/storage.dart b/lib/utils/storage.dart index e9020abf6..033b6493c 100644 --- a/lib/utils/storage.dart +++ b/lib/utils/storage.dart @@ -145,6 +145,7 @@ class SettingBoxKey { /// 其他 autoUpdate = 'autoUpdate', autoClearCache = 'autoClearCache', + defaultShowComment = 'defaultShowComment', replySortType = 'replySortType', defaultDynamicType = 'defaultDynamicType', enableHotKey = 'enableHotKey', @@ -156,7 +157,7 @@ class SettingBoxKey { disableLikeMsg = 'disableLikeMsg', defaultHomePage = 'defaultHomePage', - // 弹幕相关设置 权重(云屏蔽) 屏蔽类型 显示区域 透明度 字体大小 弹幕时间 描边粗细 + // 弹幕相关设置 权重(云屏蔽) 屏蔽类型 显示区域 透明度 字体大小 弹幕时间 描边粗细 字体粗细 danmakuWeight = 'danmakuWeight', danmakuBlockType = 'danmakuBlockType', danmakuShowArea = 'danmakuShowArea', @@ -164,6 +165,8 @@ class SettingBoxKey { danmakuFontScale = 'danmakuFontScale', danmakuDuration = 'danmakuDuration', strokeWidth = 'strokeWidth', + fontWeight = 'fontWeight', + danmakuFilterRule = 'danmakuFilterRule', // 代理host port systemProxyHost = 'systemProxyHost', @@ -177,6 +180,9 @@ class SettingBoxKey { enableSingleRow = 'enableSingleRow', // 首页单列 displayMode = 'displayMode', maxRowWidth = 'maxRowWidth', // 首页列最大宽度(dp) + videoPlayerRemoveSafeArea = 'videoPlayerHideStatusBar', + dynamicsWaterfallFlow = 'dynamicsWaterfallFlow', // 动态瀑布流 + upPanelPosition = 'upPanelPosition', // up主面板位置 adaptiveNavBar = 'adaptiveNavBar', enableMYBar = 'enableMYBar', hideSearchBar = 'hideSearchBar', // 收起顶栏 @@ -184,8 +190,7 @@ class SettingBoxKey { tabbarSort = 'tabbarSort', // 首页tabbar dynamicBadgeMode = 'dynamicBadgeMode', hiddenSettingUnlocked = 'hiddenSettingUnlocked', - enableGradientBg = 'enableGradientBg', - defaultShowComment = 'defaultShowComment'; + enableGradientBg = 'enableGradientBg'; } class LocalCacheKey { diff --git a/lib/utils/utils.dart b/lib/utils/utils.dart index fd39a9bfe..dc1b9d343 100644 --- a/lib/utils/utils.dart +++ b/lib/utils/utils.dart @@ -351,7 +351,7 @@ class Utils { child: Text( "点此查看完整更新(即commit)内容", style: - TextStyle(color: Theme.of(context).primaryColor), + TextStyle(color: Theme.of(context).colorScheme.primary), )), ], ), @@ -361,7 +361,7 @@ class Utils { TextButton( onPressed: () { setting.put(SettingBoxKey.autoUpdate, false); - SmartDialog.dismiss(); + Get.back(); }, child: Text( '不再提醒', @@ -370,7 +370,7 @@ class Utils { ), ), TextButton( - onPressed: () => SmartDialog.dismiss(), + onPressed: () => Get.back(), child: Text( '取消', style: diff --git a/pubspec.lock b/pubspec.lock index 64a3ac3c9..707f3e719 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -37,26 +37,26 @@ packages: dependency: transitive description: name: archive - sha256: "22600aa1e926be775fa5fe7e6894e7fb3df9efda8891c73f70fb3262399a432d" + sha256: ecf4273855368121b1caed0d10d4513c7241dfc813f7d3c8933b36622ae9b265 url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/" source: hosted - version: "3.4.10" + version: "3.5.1" args: dependency: transitive description: name: args - sha256: eef6c46b622e0494a36c5a12d10d77fb4e855501a91c1b9ef9339326e58f0596 + sha256: "7cf60b9f0cc88203c5a190b4cd62a99feea42759a7fa695010eb5de1c0b2252a" url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/" source: hosted - version: "2.4.2" + version: "2.5.0" asn1lib: dependency: transitive description: name: asn1lib - sha256: c9c85fedbe2188b95133cbe960e16f5f448860f7133330e272edbbca5893ddc6 + sha256: "58082b3f0dca697204dbab0ef9ff208bfaea7767ea771076af9a343488428dda" url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/" source: hosted - version: "1.5.2" + version: "1.5.3" async: dependency: transitive description: @@ -69,10 +69,10 @@ packages: dependency: "direct main" description: name: audio_service - sha256: a4d989f1225ea9621898d60f23236dcbfc04876fa316086c23c5c4af075dbac4 + sha256: "4547c312a94f9cb2c48b60823fb190767cbd63454a83c73049384d5d3cba4650" url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/" source: hosted - version: "0.18.12" + version: "0.18.13" audio_service_platform_interface: dependency: transitive description: @@ -85,18 +85,18 @@ packages: dependency: transitive description: name: audio_service_web - sha256: "523e64ddc914c714d53eec2da85bba1074f08cf26c786d4efb322de510815ea7" + sha256: "9d7d5ae5f98a5727f2580fad73062f2484f400eef6cef42919413268e62a363e" url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/" source: hosted - version: "0.1.1" + version: "0.1.2" audio_session: dependency: "direct main" description: name: audio_session - sha256: "6fdf255ed3af86535c96452c33ecff1245990bb25a605bfb1958661ccc3d467f" + sha256: a49af9981eec5d7cd73b37bacb6ee73f8143a6a9f9bd5b6021e6c346b9b6cf4e url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/" source: hosted - version: "0.1.18" + version: "0.1.19" auto_orientation: dependency: "direct main" description: @@ -149,10 +149,10 @@ packages: dependency: "direct dev" description: name: build_runner - sha256: "581bacf68f89ec8792f5e5a0b2c4decd1c948e97ce659dc783688c8a88fbec21" + sha256: "3ac61a79bfb6f6cc11f693591063a7f19a7af628dc52f141743edac5c16e8c22" url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/" source: hosted - version: "2.4.8" + version: "2.4.9" build_runner_core: dependency: transitive description: @@ -173,10 +173,10 @@ packages: dependency: transitive description: name: built_value - sha256: a3ec2e0f967bc47f69f95009bb93db936288d61d5343b9436e378b28a2f830c6 + sha256: c7913a9737ee4007efedaffc968c049fd0f3d0e49109e778edc10de9426005cb url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/" source: hosted - version: "8.9.0" + version: "8.9.2" cached_network_image: dependency: "direct main" description: @@ -197,18 +197,18 @@ packages: dependency: transitive description: name: cached_network_image_web - sha256: "42a835caa27c220d1294311ac409a43361088625a4f23c820b006dd9bffb3316" + sha256: "205d6a9f1862de34b93184f22b9d2d94586b2f05c581d546695e3d8f6a805cd7" url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/" source: hosted - version: "1.1.1" + version: "1.2.0" catcher_2: dependency: "direct main" description: name: catcher_2 - sha256: "0691d0a5a2b7ccbe434ff67071218bbff86d264d215c6afc10f404cd6d6e7a50" + sha256: "2c2c6f8cf8c817730cd1dbb010d55292396930e7a3d42c04c3039e3fd411a2f8" url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/" source: hosted - version: "1.2.0" + version: "1.2.6" characters: dependency: transitive description: @@ -317,26 +317,26 @@ packages: dependency: "direct main" description: name: cupertino_icons - sha256: d57953e10f9f8327ce64a508a355f0b1ec902193f66288e8cb5070e7c47eeb2d + sha256: ba631d1c7f7bef6b729a622b7b752645a2d076dba9976925b8f25725a30e1ee6 url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/" source: hosted - version: "1.0.6" + version: "1.0.8" custom_sliding_segmented_control: dependency: "direct main" description: name: custom_sliding_segmented_control - sha256: "05b73fa48d57218bfdf806bad68a859812b216cd81fe81c6cbefde89f39eb257" + sha256: "8b29c39e053136cdb899cf1b049d11e5ac0f4622f6444ae4f80b6ba72a640763" url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/" source: hosted - version: "1.8.1" + version: "1.8.2" dart_style: dependency: transitive description: name: dart_style - sha256: "40ae61a5d43feea6d24bd22c0537a6629db858963b99b4bc1c3db80676f32368" + sha256: "99e066ce75c89d6b29903d788a7bb9369cf754f7b24bf70bf4b6d6d6b26853b9" url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/" source: hosted - version: "2.3.4" + version: "2.3.6" dbus: dependency: transitive description: @@ -365,10 +365,10 @@ packages: dependency: "direct main" description: name: dio - sha256: "797e1e341c3dd2f69f2dad42564a6feff3bfb87187d05abb93b9609e6f1645c3" + sha256: "11e40df547d418cc0c4900a9318b26304e665da6fa4755399a9ff9efd09034b5" url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/" source: hosted - version: "5.4.0" + version: "5.4.3+1" dio_cookie_manager: dependency: "direct main" description: @@ -381,10 +381,10 @@ packages: dependency: "direct main" description: name: dio_http2_adapter - sha256: "3bb35e81eb8a688eb1cb15beb97f46823698b44037e7b55227aa1060f5593adc" + sha256: ea2f5e7906a157cb049abce95f89a693459f82e5b376be4636086368e3a350f1 url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/" source: hosted - version: "2.4.0" + version: "2.5.2" dismissible_page: dependency: "direct main" description: @@ -397,10 +397,10 @@ packages: dependency: "direct main" description: name: dynamic_color - sha256: a866f1f8947bfdaf674d7928e769eac7230388a2e7a2542824fad4bb5b87be3b + sha256: eae98052fa6e2826bdac3dd2e921c6ce2903be15c6b7f8b6d8a5d49b5086298d url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/" source: hosted - version: "1.6.9" + version: "1.7.0" easy_debounce: dependency: "direct main" description: @@ -510,14 +510,6 @@ packages: description: flutter source: sdk version: "0.0.0" - flutter_adaptive_scaffold: - dependency: "direct main" - description: - name: flutter_adaptive_scaffold - sha256: "600bbe237530a249f957f7d0f36273c20bd38d137e28e098c5231c30cadbe927" - url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/" - source: hosted - version: "0.1.10+1" flutter_cache_manager: dependency: transitive description: @@ -575,26 +567,26 @@ packages: dependency: transitive description: name: flutter_plugin_android_lifecycle - sha256: b068ffc46f82a55844acfa4fdbb61fad72fa2aef0905548419d97f0f95c456da + sha256: "8cf40eebf5dec866a6d1956ad7b4f7016e6c0cc69847ab946833b7d43743809f" url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/" source: hosted - version: "2.0.17" + version: "2.0.19" flutter_smart_dialog: dependency: "direct main" description: name: flutter_smart_dialog - sha256: e9ee69eeac16165d142f1974b4db05ca9846cffafb7c94674a38ec07d7e6cda1 + sha256: "9b23a0b23b52a259f2901997eaf0b169bf5c61ff2178204872709610e9f6c0be" url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/" source: hosted - version: "4.9.6" + version: "4.9.6+1" flutter_svg: dependency: "direct main" description: name: flutter_svg - sha256: d39e7f95621fc84376bc0f7d504f05c3a41488c562f4a8ad410569127507402c + sha256: "7b4ca6cf3304575fe9c8ec64813c8d02ee41d2afe60bcfe0678bcb5375d596a2" url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/" source: hosted - version: "2.0.9" + version: "2.0.10+1" flutter_test: dependency: "direct dev" description: flutter @@ -604,10 +596,10 @@ packages: dependency: "direct main" description: name: flutter_volume_controller - sha256: "0f10cc759499cb6c3e152a8f6ff8e5ce385b99db7e1f586d1a29d8e6c11f4082" + sha256: fa4c36dfe7ef7f423704f34ab8e64e00b4a30a90aa6e56f251e9dba649efcd7f url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/" source: hosted - version: "1.3.1" + version: "1.3.2" flutter_web_plugins: dependency: transitive description: flutter @@ -617,10 +609,10 @@ packages: dependency: transitive description: name: fluttertoast - sha256: dfdde255317af381bfc1c486ed968d5a43a2ded9c931e87cbecd88767d6a71c1 + sha256: "81b68579e23fcbcada2db3d50302813d2371664afe6165bc78148050ab94bf66" url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/" source: hosted - version: "8.2.4" + version: "8.2.5" font_awesome_flutter: dependency: "direct main" description: @@ -633,10 +625,10 @@ packages: dependency: transitive description: name: frontend_server_client - sha256: "408e3ca148b31c20282ad6f37ebfa6f4bdc8fede5b74bc2f08d9d92b55db3612" + sha256: f64a0333a82f30b0cca061bc3d143813a486dc086b574bfb233b7c1372427694 url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/" source: hosted - version: "3.2.0" + version: "4.0.0" get: dependency: "direct main" description: @@ -745,10 +737,10 @@ packages: dependency: transitive description: name: image - sha256: "49a0d4b0c12402853d3f227fe7c315601b238d126aa4caa5dbb2dcf99421aa4a" + sha256: "4c68bfd5ae83e700b5204c1e74451e7bf3cf750e6843c6e158289cf56bda018e" url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/" source: hosted - version: "4.1.6" + version: "4.1.7" intl: dependency: transitive description: @@ -777,10 +769,10 @@ packages: dependency: transitive description: name: json_annotation - sha256: b10a7b2ff83d83c777edba3c6a0f97045ddadd56c944e1a23a3fdf43a1bf4467 + sha256: "1ce844379ca14835a50d2f019a3099f419082cfdd231cd86a142af94dd5c6bb1" url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/" source: hosted - version: "4.8.1" + version: "4.9.0" lints: dependency: transitive description: @@ -817,10 +809,10 @@ packages: dependency: "direct main" description: name: logger - sha256: "6bbb9d6f7056729537a4309bda2e74e18e5d9f14302489cc1e93f33b3fe32cac" + sha256: af05cc8714f356fd1f3888fb6741cbe9fbe25cdb6eedbab80e1a6db21047d4a4 url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/" source: hosted - version: "2.0.2+1" + version: "2.3.0" logging: dependency: transitive description: @@ -833,10 +825,10 @@ packages: dependency: transitive description: name: mailer - sha256: "57f6dd1496699999a7bfd0aa6be0645384f477f4823e16d4321c40a434346382" + sha256: d25d89555c1031abacb448f07b801d7c01b4c21d4558e944b12b64394c84a3cb url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/" source: hosted - version: "6.0.1" + version: "6.1.0" marquee: dependency: "direct main" description: @@ -971,8 +963,8 @@ packages: description: path: "." ref: master - resolved-ref: d1cb3f0190ca67ec4d7fd372dac96f4f17a81a1a - url: "https://github.com/guozhigq/flutter_ns_danmaku.git" + resolved-ref: e12184de6f4ab4647d098c561b0508c47ce359e8 + url: "https://github.com/orz12/flutter_ns_danmaku.git" source: git version: "0.0.5" octo_image: @@ -1027,18 +1019,18 @@ packages: dependency: "direct main" description: name: path_provider - sha256: b27217933eeeba8ff24845c34003b003b2b22151de3c908d0e679e8fe1aa078b + sha256: c9e7d3a4cd1410877472158bee69963a4579f78b68c65a2b7d40d1a7a88bb161 url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/" source: hosted - version: "2.1.2" + version: "2.1.3" path_provider_android: dependency: transitive description: name: path_provider_android - sha256: "477184d672607c0a3bf68fbbf601805f92ef79c82b64b4d6eb318cbca4c48668" + sha256: a248d8146ee5983446bf03ed5ea8f6533129a12b11f12057ad1b4a67a2b3b41d url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/" source: hosted - version: "2.2.2" + version: "2.2.4" path_provider_foundation: dependency: transitive description: @@ -1075,26 +1067,26 @@ packages: dependency: "direct main" description: name: permission_handler - sha256: "45ff3fbcb99040fde55c528d5e3e6ca29171298a85436274d49c6201002087d6" + sha256: "18bf33f7fefbd812f37e72091a15575e72d5318854877e0e4035a24ac1113ecb" url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/" source: hosted - version: "11.2.0" + version: "11.3.1" permission_handler_android: dependency: transitive description: name: permission_handler_android - sha256: "758284a0976772f9c744d6384fc5dc4834aa61e3f7aa40492927f244767374eb" + sha256: "8bb852cd759488893805c3161d0b2b5db55db52f773dbb014420b304055ba2c5" url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/" source: hosted - version: "12.0.3" + version: "12.0.6" permission_handler_apple: dependency: transitive description: name: permission_handler_apple - sha256: c6bf440f80acd2a873d3d91a699e4cc770f86e7e6b576dda98759e8b92b39830 + sha256: e9ad66020b89ff1b63908f247c2c6f931c6e62699b756ef8b3c4569350cd8662 url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/" source: hosted - version: "9.3.0" + version: "9.4.4" permission_handler_html: dependency: transitive description: @@ -1107,10 +1099,10 @@ packages: dependency: transitive description: name: permission_handler_platform_interface - sha256: "5c43148f2bfb6d14c5a8162c0a712afe891f2d847f35fcff29c406b37da43c3c" + sha256: "48d4fcf201a1dad93ee869ab0d4101d084f49136ec82a8a06ed9cfeacab9fd20" url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/" source: hosted - version: "4.1.0" + version: "4.2.1" permission_handler_windows: dependency: transitive description: @@ -1147,10 +1139,10 @@ packages: dependency: transitive description: name: pointycastle - sha256: "43ac87de6e10afabc85c445745a7b799e04de84cebaa4fd7bf55a5e1e9604d29" + sha256: "4be0097fcf3fd3e8449e53730c631200ebc7b88016acecab2b0da2f0149222fe" url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/" source: hosted - version: "3.7.4" + version: "3.9.1" pool: dependency: transitive description: @@ -1211,10 +1203,10 @@ packages: dependency: "direct main" description: name: saver_gallery - sha256: cceebad1f792adad4eb5015b415dcd521779a772ea574f0d7fc534b128deac83 + sha256: "0f740608072053a0da3b19cc5812a87e36f5c3c0b959d2475c4eb3d697f4a782" url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/" source: hosted - version: "3.0.2" + version: "3.0.3" screen_brightness: dependency: "direct main" description: @@ -1275,10 +1267,10 @@ packages: dependency: transitive description: name: sentry - sha256: a7946f4a90b0feb47214981d881b98149e05f6c576da9f2a2f33945bf561de25 + sha256: fd1fbfe860c05f5c52820ec4dbf2b6473789e83ead26cfc18bca4fe80bf3f008 url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/" source: hosted - version: "7.16.0" + version: "8.2.0" share_plus: dependency: "direct main" description: @@ -1291,10 +1283,10 @@ packages: dependency: transitive description: name: share_plus_platform_interface - sha256: df08bc3a07d01f5ea47b45d03ffcba1fa9cd5370fb44b3f38c70e42cced0f956 + sha256: "251eb156a8b5fa9ce033747d73535bf53911071f8d3b6f4f0b578505ce0d4496" url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/" source: hosted - version: "3.3.1" + version: "3.4.0" shelf: dependency: transitive description: @@ -1472,26 +1464,26 @@ packages: dependency: "direct main" description: name: url_launcher - sha256: c512655380d241a337521703af62d2c122bf7b77a46ff7dd750092aa9433499c + sha256: "6ce1e04375be4eed30548f10a315826fd933c1e493206eab82eed01f438c8d2e" url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/" source: hosted - version: "6.2.4" + version: "6.2.6" url_launcher_android: dependency: transitive description: name: url_launcher_android - sha256: "507dc655b1d9cb5ebc756032eb785f114e415f91557b73bf60b7e201dfedeb2f" + sha256: "360a6ed2027f18b73c8d98e159dda67a61b7f2e0f6ec26e86c3ada33b0621775" url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/" source: hosted - version: "6.2.2" + version: "6.3.1" url_launcher_ios: dependency: transitive description: name: url_launcher_ios - sha256: "75bb6fe3f60070407704282a2d295630cab232991eb52542b18347a8a941df03" + sha256: "9149d493b075ed740901f3ee844a38a00b33116c7c5c10d7fb27df8987fb51d5" url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/" source: hosted - version: "6.2.4" + version: "6.2.5" url_launcher_linux: dependency: transitive description: @@ -1504,18 +1496,18 @@ packages: dependency: transitive description: name: url_launcher_macos - sha256: b7244901ea3cf489c5335bdacda07264a6e960b1c1b1a9f91e4bc371d9e68234 + sha256: "9a1a42d5d2d95400c795b2914c36fdcb525870c752569438e4ebb09a2b5d90de" url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/" source: hosted - version: "3.1.0" + version: "3.2.0" url_launcher_platform_interface: dependency: transitive description: name: url_launcher_platform_interface - sha256: a932c3a8082e118f80a475ce692fde89dc20fddb24c57360b96bc56f7035de1f + sha256: "552f8a1e663569be95a8190206a38187b531910283c3e982193e4f2733f01029" url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/" source: hosted - version: "2.3.1" + version: "2.3.2" url_launcher_web: dependency: transitive description: @@ -1544,26 +1536,26 @@ packages: dependency: transitive description: name: vector_graphics - sha256: "18f6690295af52d081f6808f2f7c69f0eed6d7e23a71539d75f4aeb8f0062172" + sha256: "32c3c684e02f9bc0afb0ae0aa653337a2fe022e8ab064bcd7ffda27a74e288e3" url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/" source: hosted - version: "1.1.9+2" + version: "1.1.11+1" vector_graphics_codec: dependency: transitive description: name: vector_graphics_codec - sha256: "531d20465c10dfac7f5cd90b60bbe4dd9921f1ec4ca54c83ebb176dbacb7bb2d" + sha256: c86987475f162fadff579e7320c7ddda04cd2fdeffbe1129227a85d9ac9e03da url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/" source: hosted - version: "1.1.9+2" + version: "1.1.11+1" vector_graphics_compiler: dependency: transitive description: name: vector_graphics_compiler - sha256: "03012b0a33775c5530576b70240308080e1d5050f0faf000118c20e6463bc0ad" + sha256: "12faff3f73b1741a36ca7e31b292ddeb629af819ca9efe9953b70bd63fc8cd81" url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/" source: hosted - version: "1.1.9+2" + version: "1.1.11+1" vector_math: dependency: transitive description: @@ -1648,18 +1640,18 @@ packages: dependency: "direct main" description: name: webview_flutter - sha256: d81b68e88cc353e546afb93fb38958e3717282c5ac6e5d3be4a4aef9fc3c1413 + sha256: "4445dea609bcdf4bcf6b0521e8c936abb34711d9e9b73c5ff8a6fd4eecb0db3c" url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/" source: hosted - version: "4.5.0" + version: "4.6.0" webview_flutter_android: dependency: transitive description: name: webview_flutter_android - sha256: "4ea3c4e1b8ed590162b15b8a61b41b1ef3ff179a314627c16ce40c086d94b8af" + sha256: dad3313c9ead95517bb1cae5e1c9d20ba83729d5a59e5e83c0a2d66203f27f91 url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/" source: hosted - version: "3.14.0" + version: "3.16.1" webview_flutter_platform_interface: dependency: transitive description: @@ -1672,10 +1664,10 @@ packages: dependency: transitive description: name: webview_flutter_wkwebview - sha256: "4d062ad505390ecef1c4bfb6001cd857a51e00912cc9dfb66edb1886a9ebd80c" + sha256: f12f8d8a99784b863e8b85e4a9a5e3cf1839d6803d2c0c3e0533a8f3c5a992a7 url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/" source: hosted - version: "3.10.2" + version: "3.13.0" win32: dependency: transitive description: @@ -1717,5 +1709,5 @@ packages: source: hosted version: "3.1.2" sdks: - dart: ">=3.2.0 <4.0.0" + dart: ">=3.2.3 <4.0.0" flutter: ">=3.16.0" diff --git a/pubspec.yaml b/pubspec.yaml index 3425a644b..1ba6974e9 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -37,8 +37,6 @@ dependencies: cupertino_icons: ^1.0.5 # 动态取色 dynamic_color: ^1.6.8 - # Adaptive scaffold - flutter_adaptive_scaffold: ^0.1.10+1 get: ^4.6.5 @@ -117,7 +115,7 @@ dependencies: # 弹幕 ns_danmaku: git: - url: https://github.com/guozhigq/flutter_ns_danmaku.git + url: https://github.com/orz12/flutter_ns_danmaku.git ref: master # 状态栏图标控制 status_bar_control: ^3.2.1