From bf37c33291655044834674d93768caaa724a0d90 Mon Sep 17 00:00:00 2001 From: yezi Date: Sat, 9 Mar 2024 19:39:21 +0800 Subject: [PATCH 1/4] =?UTF-8?q?feat:=E6=94=AF=E6=8C=81=E6=8E=92=E8=A1=8C?= =?UTF-8?q?=E6=A6=9C=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/http/api.dart | 4 + lib/http/video.dart | 23 +++ lib/models/common/nav_bar_config.dart | 15 +- lib/models/common/rank_type.dart | 240 ++++++++++++++++++++++++++ lib/pages/main/controller.dart | 2 + lib/pages/main/view.dart | 17 ++ lib/pages/rank/controller.dart | 70 ++++++++ lib/pages/rank/index.dart | 4 + lib/pages/rank/view.dart | 149 ++++++++++++++++ lib/pages/rank/zone/controller.dart | 53 ++++++ lib/pages/rank/zone/index.dart | 4 + lib/pages/rank/zone/view.dart | 148 ++++++++++++++++ 12 files changed, 728 insertions(+), 1 deletion(-) create mode 100644 lib/models/common/rank_type.dart create mode 100644 lib/pages/rank/controller.dart create mode 100644 lib/pages/rank/index.dart create mode 100644 lib/pages/rank/view.dart create mode 100644 lib/pages/rank/zone/controller.dart create mode 100644 lib/pages/rank/zone/index.dart create mode 100644 lib/pages/rank/zone/view.dart diff --git a/lib/http/api.dart b/lib/http/api.dart index 3a23ef80e..8aa62233b 100644 --- a/lib/http/api.dart +++ b/lib/http/api.dart @@ -499,4 +499,8 @@ class Api { /// 发送私信 static const String sendMsg = '${HttpString.tUrl}/web_im/v1/web_im/send_msg'; + + /// 排行榜 + static const String getRankApi = "/x/web-interface/ranking/v2"; + } diff --git a/lib/http/video.dart b/lib/http/video.dart index 30df62c3e..73e8b6988 100644 --- a/lib/http/video.dart +++ b/lib/http/video.dart @@ -475,4 +475,27 @@ class VideoHttp { return {'status': false, 'data': []}; } } + + // 视频排行 + static Future getRankVideoList(int rid) async { + try { + var rankApi = "${Api.getRankApi}?rid=$rid&type=all"; + var res = await Request().get(rankApi); + if (res.data['code'] == 0) { + List list = []; + List blackMidsList = + setting.get(SettingBoxKey.blackMidsList, defaultValue: [-1]); + for (var i in res.data['data']['list']) { + if (!blackMidsList.contains(i['owner']['mid'])) { + list.add(HotVideoItemModel.fromJson(i)); + } + } + return {'status': true, 'data': list}; + } else { + return {'status': false, 'data': [], 'msg': res.data['message']}; + } + } catch (err) { + return {'status': false, 'data': [], 'msg': err}; + } + } } diff --git a/lib/models/common/nav_bar_config.dart b/lib/models/common/nav_bar_config.dart index 7fb22e48d..9ebe8e6fd 100644 --- a/lib/models/common/nav_bar_config.dart +++ b/lib/models/common/nav_bar_config.dart @@ -16,6 +16,19 @@ List defaultNavigationBars = [ }, { 'id': 1, + 'icon': const Icon( + Icons.trending_up, + size: 21, + ), + 'selectIcon': const Icon( + Icons.trending_up_outlined, + size: 21, + ), + 'label': "排行榜", + 'count': 0, + }, + { + 'id': 2, 'icon': const Icon( Icons.motion_photos_on_outlined, size: 21, @@ -28,7 +41,7 @@ List defaultNavigationBars = [ 'count': 0, }, { - 'id': 2, + 'id': 3, '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 new file mode 100644 index 000000000..2ce6d3b5b --- /dev/null +++ b/lib/models/common/rank_type.dart @@ -0,0 +1,240 @@ +import 'package:flutter/material.dart'; +import 'package:get/get.dart'; +import 'package:pilipala/pages/rank/zone/index.dart'; + +enum RandType { + all, + creation, + animation, + music, + dance, + game, + knowledge, + technology, + sport, + car, + life, + food, + animal, + madness, + fashion, + entertainment, + film, + origin, + rookie +} + +extension RankTypeDesc on RandType { + String get description => [ + '全站', + '国创相关', + '动画', + '音乐', + '舞蹈', + '游戏', + '知识', + '科技', + '运动', + '汽车', + '生活', + '美食', + '动物圈', + '鬼畜', + '时尚', + '娱乐', + '影视' + ][index]; + + String get id => [ + 'all', + 'creation', + 'animation', + 'music', + 'dance', + 'game', + 'knowledge', + 'technology', + 'sport', + 'car', + 'life', + 'food', + 'animal', + 'madness', + 'fashion', + 'entertainment', + 'film' + ][index]; +} + +List tabsConfig = [ + { + 'icon': const Icon( + Icons.live_tv_outlined, + size: 15, + ), + 'label': '全站', + 'type': RandType.all, + 'ctr': Get.put, + 'page': const ZonePage(rid: 0), + }, + { + 'icon': const Icon( + Icons.live_tv_outlined, + size: 15, + ), + 'label': '国创相关', + 'type': RandType.creation, + 'ctr': Get.put, + 'page': const ZonePage(rid: 168), + }, + { + 'icon': const Icon( + Icons.live_tv_outlined, + size: 15, + ), + 'label': '动画', + 'type': RandType.animation, + 'ctr': Get.put, + 'page': const ZonePage(rid: 1), + }, + { + 'icon': const Icon( + Icons.live_tv_outlined, + size: 15, + ), + 'label': '音乐', + 'type': RandType.music, + 'ctr': Get.put, + 'page': const ZonePage(rid: 3), + }, + { + 'icon': const Icon( + Icons.live_tv_outlined, + size: 15, + ), + 'label': '舞蹈', + 'type': RandType.dance, + 'ctr': Get.put, + 'page': const ZonePage(rid: 129), + }, + { + 'icon': const Icon( + Icons.live_tv_outlined, + size: 15, + ), + 'label': '游戏', + 'type': RandType.game, + 'ctr': Get.put, + 'page': const ZonePage(rid: 4), + }, + { + 'icon': const Icon( + Icons.live_tv_outlined, + size: 15, + ), + 'label': '知识', + 'type': RandType.knowledge, + 'ctr': Get.put, + 'page': const ZonePage(rid: 36), + }, + { + 'icon': const Icon( + Icons.live_tv_outlined, + size: 15, + ), + 'label': '科技', + 'type': RandType.technology, + 'ctr': Get.put, + 'page': const ZonePage(rid: 188), + }, + { + 'icon': const Icon( + Icons.live_tv_outlined, + size: 15, + ), + 'label': '运动', + 'type': RandType.sport, + 'ctr': Get.put, + 'page': const ZonePage(rid: 234), + }, + { + 'icon': const Icon( + Icons.live_tv_outlined, + size: 15, + ), + 'label': '汽车', + 'type': RandType.car, + 'ctr': Get.put, + 'page': const ZonePage(rid: 223), + }, + { + 'icon': const Icon( + Icons.live_tv_outlined, + size: 15, + ), + 'label': '生活', + 'type': RandType.life, + 'ctr': Get.put, + 'page': const ZonePage(rid: 160), + }, + { + 'icon': const Icon( + Icons.live_tv_outlined, + size: 15, + ), + 'label': '美食', + 'type': RandType.food, + 'ctr': Get.put, + 'page': const ZonePage(rid: 211), + }, + { + 'icon': const Icon( + Icons.live_tv_outlined, + size: 15, + ), + 'label': '动物圈', + 'type': RandType.animal, + 'ctr': Get.put, + 'page': const ZonePage(rid: 217), + }, + { + 'icon': const Icon( + Icons.live_tv_outlined, + size: 15, + ), + 'label': '鬼畜', + 'type': RandType.madness, + 'ctr': Get.put, + 'page': const ZonePage(rid: 119), + }, + { + 'icon': const Icon( + Icons.live_tv_outlined, + size: 15, + ), + 'label': '时尚', + 'type': RandType.fashion, + 'ctr': Get.put, + 'page': const ZonePage(rid: 155), + }, + { + 'icon': const Icon( + Icons.live_tv_outlined, + size: 15, + ), + 'label': '娱乐', + 'type': RandType.entertainment, + 'ctr': Get.put, + 'page': const ZonePage(rid: 5), + }, + { + 'icon': const Icon( + Icons.live_tv_outlined, + size: 15, + ), + 'label': '影视', + 'type': RandType.film, + 'ctr': Get.put, + 'page': const ZonePage(rid: 181), + } +]; diff --git a/lib/pages/main/controller.dart b/lib/pages/main/controller.dart index f3bcb75a8..ddbd364a5 100644 --- a/lib/pages/main/controller.dart +++ b/lib/pages/main/controller.dart @@ -9,6 +9,7 @@ import 'package:pilipala/http/common.dart'; import 'package:pilipala/pages/dynamics/index.dart'; import 'package:pilipala/pages/home/view.dart'; import 'package:pilipala/pages/media/index.dart'; +import 'package:pilipala/pages/rank/index.dart'; import 'package:pilipala/utils/storage.dart'; import 'package:pilipala/utils/utils.dart'; import '../../models/common/dynamic_badge_mode.dart'; @@ -17,6 +18,7 @@ import '../../models/common/nav_bar_config.dart'; class MainController extends GetxController { List pages = [ const HomePage(), + const RankPage(), const DynamicsPage(), const MediaPage(), ]; diff --git a/lib/pages/main/view.dart b/lib/pages/main/view.dart index 04e0f087f..c551e6905 100644 --- a/lib/pages/main/view.dart +++ b/lib/pages/main/view.dart @@ -7,6 +7,7 @@ import 'package:pilipala/models/common/dynamic_badge_mode.dart'; import 'package:pilipala/pages/dynamics/index.dart'; import 'package:pilipala/pages/home/index.dart'; import 'package:pilipala/pages/media/index.dart'; +import 'package:pilipala/pages/rank/index.dart'; import 'package:pilipala/utils/event_bus.dart'; import 'package:pilipala/utils/feed_back.dart'; import 'package:pilipala/utils/storage.dart'; @@ -22,6 +23,7 @@ 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()); @@ -57,6 +59,21 @@ 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 DynamicsPage) { if (_dynamicController.flag) { // 单击返回顶部 双击并刷新 diff --git a/lib/pages/rank/controller.dart b/lib/pages/rank/controller.dart new file mode 100644 index 000000000..61475d97b --- /dev/null +++ b/lib/pages/rank/controller.dart @@ -0,0 +1,70 @@ +import 'dart:async'; + +import 'package:flutter/material.dart'; +import 'package:get/get.dart'; +import 'package:hive/hive.dart'; +import 'package:pilipala/models/common/rank_type.dart'; +import 'package:pilipala/utils/storage.dart'; + +class RankController extends GetxController with GetTickerProviderStateMixin { + bool flag = false; + late RxList tabs = [].obs; + RxInt initialIndex = 1.obs; + late TabController tabController; + late List tabsCtrList; + late List tabsPageList; + Box setting = GStrorage.setting; + late final StreamController searchBarStream = + StreamController.broadcast(); + late bool enableGradientBg; + + @override + void onInit() { + super.onInit(); + enableGradientBg = + setting.get(SettingBoxKey.enableGradientBg, defaultValue: true); + // 进行tabs配置 + setTabConfig(); + } + + void onRefresh() { + int index = tabController.index; + var ctr = tabsCtrList[index]; + ctr().onRefresh(); + } + + void animateToTop() { + int index = tabController.index; + var ctr = tabsCtrList[index]; + ctr().animateToTop(); + } + + void setTabConfig() async { + tabs.value = tabsConfig; + initialIndex.value = 0; + tabsCtrList = tabs.map((e) => e['ctr']).toList(); + tabsPageList = tabs.map((e) => e['page']).toList(); + + tabController = TabController( + initialIndex: initialIndex.value, + length: tabs.length, + vsync: this, + ); + // 监听 tabController 切换 + if (enableGradientBg) { + tabController.animation!.addListener(() { + if (tabController.indexIsChanging) { + if (initialIndex.value != tabController.index) { + initialIndex.value = tabController.index; + } + } else { + final int temp = tabController.animation!.value.round(); + if (initialIndex.value != temp) { + initialIndex.value = temp; + tabController.index = initialIndex.value; + } + } + }); + } + } +} diff --git a/lib/pages/rank/index.dart b/lib/pages/rank/index.dart new file mode 100644 index 000000000..eaac0a341 --- /dev/null +++ b/lib/pages/rank/index.dart @@ -0,0 +1,4 @@ +library rank; + +export './controller.dart'; +export './view.dart'; diff --git a/lib/pages/rank/view.dart b/lib/pages/rank/view.dart new file mode 100644 index 000000000..7b5b49065 --- /dev/null +++ b/lib/pages/rank/view.dart @@ -0,0 +1,149 @@ +import 'dart:async'; + +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:get/get.dart'; +import 'package:pilipala/utils/feed_back.dart'; +import './controller.dart'; + +class RankPage extends StatefulWidget { + const RankPage({Key? key}) : super(key: key); + + @override + State createState() => _RankPageState(); +} + +class _RankPageState extends State + with AutomaticKeepAliveClientMixin, TickerProviderStateMixin { + final RankController _rankController = Get.put(RankController()); + List videoList = []; + late Stream stream; + + @override + bool get wantKeepAlive => true; + + @override + void initState() { + super.initState(); + stream = _rankController.searchBarStream.stream; + } + + @override + Widget build(BuildContext context) { + super.build(context); + Brightness currentBrightness = MediaQuery.of(context).platformBrightness; + // 设置状态栏图标的亮度 + if (_rankController.enableGradientBg) { + SystemChrome.setSystemUIOverlayStyle(SystemUiOverlayStyle( + statusBarIconBrightness: currentBrightness == Brightness.light + ? Brightness.dark + : Brightness.light, + )); + } + return Scaffold( + extendBody: true, + extendBodyBehindAppBar: false, + appBar: _rankController.enableGradientBg + ? null + : AppBar(toolbarHeight: 0, elevation: 0), + body: Stack( + children: [ + // gradient background + if (_rankController.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: [ + const CustomAppBar(), + if (_rankController.tabs.length > 1) ...[ + const SizedBox(height: 4), + SizedBox( + width: double.infinity, + height: 42, + child: Align( + alignment: Alignment.center, + child: TabBar( + controller: _rankController.tabController, + tabs: [ + for (var i in _rankController.tabs) + Tab(text: i['label']) + ], + isScrollable: true, + dividerColor: Colors.transparent, + enableFeedback: true, + splashBorderRadius: BorderRadius.circular(10), + tabAlignment: TabAlignment.center, + onTap: (value) { + feedBack(); + if (_rankController.initialIndex.value == value) { + _rankController.tabsCtrList[value]().animateToTop(); + } + _rankController.initialIndex.value = value; + }, + ), + ), + ), + ] else ...[ + const SizedBox(height: 6), + ], + Expanded( + child: TabBarView( + controller: _rankController.tabController, + children: _rankController.tabsPageList, + ), + ), + ], + ), + ], + ), + ); + } +} + +class CustomAppBar extends StatelessWidget implements PreferredSizeWidget { + final double height; + + const CustomAppBar({ + super.key, + this.height = kToolbarHeight, + }); + + @override + Size get preferredSize => Size.fromHeight(height); + + @override + Widget build(BuildContext context) { + final double top = MediaQuery.of(context).padding.top; + return Container( + width: MediaQuery.of(context).size.width, + height: top, + color: Colors.transparent, + ); + } +} diff --git a/lib/pages/rank/zone/controller.dart b/lib/pages/rank/zone/controller.dart new file mode 100644 index 000000000..f9f4dc6e0 --- /dev/null +++ b/lib/pages/rank/zone/controller.dart @@ -0,0 +1,53 @@ +import 'package:get/get.dart'; +import 'package:flutter/material.dart'; +import 'package:pilipala/http/video.dart'; +import 'package:pilipala/models/model_hot_video_item.dart'; + +class ZoneController extends GetxController { + final ScrollController scrollController = ScrollController(); + RxList videoList = [].obs; + bool isLoadingMore = false; + bool flag = false; + OverlayEntry? popupDialog; + int zoneID = 0; + + // 获取推荐 + Future queryRankFeed(type, rid) async { + zoneID = rid; + var res = await VideoHttp.getRankVideoList(zoneID); + if (res['status']) { + if (type == 'init') { + videoList.value = res['data']; + } else if (type == 'onRefresh') { + videoList.clear(); + videoList.addAll(res['data']); + } else if (type == 'onLoad') { + videoList.clear(); + videoList.addAll(res['data']); + } + } + isLoadingMore = false; + return res; + } + + // 下拉刷新 + Future onRefresh() async { + queryRankFeed('onRefresh', zoneID); + } + + // 上拉加载 + Future onLoad() async { + queryRankFeed('onLoad', zoneID); + } + + // 返回顶部并刷新 + 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/rank/zone/index.dart b/lib/pages/rank/zone/index.dart new file mode 100644 index 000000000..8f5357361 --- /dev/null +++ b/lib/pages/rank/zone/index.dart @@ -0,0 +1,4 @@ +library rank.zone; + +export './controller.dart'; +export './view.dart'; diff --git a/lib/pages/rank/zone/view.dart b/lib/pages/rank/zone/view.dart new file mode 100644 index 000000000..58ca187fa --- /dev/null +++ b/lib/pages/rank/zone/view.dart @@ -0,0 +1,148 @@ +import 'dart:async'; + +import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; +import 'package:get/get.dart'; +import 'package:pilipala/common/constants.dart'; +import 'package:pilipala/common/widgets/animated_dialog.dart'; +import 'package:pilipala/common/widgets/overlay_pop.dart'; +import 'package:pilipala/common/skeleton/video_card_h.dart'; +import 'package:pilipala/common/widgets/http_error.dart'; +import 'package:pilipala/common/widgets/video_card_h.dart'; +import 'package:pilipala/pages/home/index.dart'; +import 'package:pilipala/pages/main/index.dart'; +import 'package:pilipala/pages/rank/zone/index.dart'; + +class ZonePage extends StatefulWidget { + const ZonePage({Key? key, required this.rid}) : super(key: key); + + final int rid; + + @override + State createState() => _ZonePageState(); +} + +class _ZonePageState extends State { + final ZoneController _zoneController = Get.put(ZoneController()); + List videoList = []; + Future? _futureBuilderFuture; + late ScrollController scrollController; + + @override + void initState() { + super.initState(); + _futureBuilderFuture = _zoneController.queryRankFeed('init', widget.rid); + scrollController = _zoneController.scrollController; + StreamController mainStream = + Get.find().bottomBarStream; + StreamController searchBarStream = + Get.find().searchBarStream; + scrollController.addListener( + () { + if (scrollController.position.pixels >= + scrollController.position.maxScrollExtent - 200) { + if (!_zoneController.isLoadingMore) { + _zoneController.isLoadingMore = true; + _zoneController.onLoad(); + } + } + + final ScrollDirection direction = + scrollController.position.userScrollDirection; + if (direction == ScrollDirection.forward) { + mainStream.add(true); + searchBarStream.add(true); + } else if (direction == ScrollDirection.reverse) { + mainStream.add(false); + searchBarStream.add(false); + } + }, + ); + } + + @override + void dispose() { + scrollController.removeListener(() {}); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return RefreshIndicator( + onRefresh: () async { + return await _zoneController.onRefresh(); + }, + child: CustomScrollView( + controller: _zoneController.scrollController, + slivers: [ + SliverPadding( + // 单列布局 EdgeInsets.zero + padding: + const EdgeInsets.fromLTRB(0, StyleString.safeSpace - 5, 0, 0), + sliver: FutureBuilder( + future: _futureBuilderFuture, + builder: (context, snapshot) { + if (snapshot.connectionState == ConnectionState.done) { + Map data = snapshot.data as Map; + if (data['status']) { + return Obx( + () => SliverList( + delegate: SliverChildBuilderDelegate((context, index) { + return VideoCardH( + videoItem: _zoneController.videoList[index], + showPubdate: true, + longPress: () { + _zoneController.popupDialog = _createPopupDialog( + _zoneController.videoList[index]); + Overlay.of(context) + .insert(_zoneController.popupDialog!); + }, + longPressEnd: () { + _zoneController.popupDialog?.remove(); + }, + ); + }, childCount: _zoneController.videoList.length), + ), + ); + } else { + return HttpError( + errMsg: data['msg'], + fn: () { + setState(() { + _futureBuilderFuture = + _zoneController.queryRankFeed('init', widget.rid); + }); + }, + ); + } + } else { + // 骨架屏 + return SliverList( + delegate: SliverChildBuilderDelegate((context, index) { + return const VideoCardHSkeleton(); + }, childCount: 10), + ); + } + }, + ), + ), + SliverToBoxAdapter( + child: SizedBox( + height: MediaQuery.of(context).padding.bottom + 10, + ), + ) + ], + ), + ); + } + + OverlayEntry _createPopupDialog(videoItem) { + return OverlayEntry( + builder: (context) => AnimatedDialog( + closeFn: _zoneController.popupDialog?.remove, + child: OverlayPop( + videoItem: videoItem, closeFn: _zoneController.popupDialog?.remove), + ), + ); + } +} From b7b75e956fbc66fa02a31e9aace44ff39997421f Mon Sep 17 00:00:00 2001 From: guozhigq Date: Sun, 10 Mar 2024 16:13:11 +0800 Subject: [PATCH 2/4] =?UTF-8?q?fix:=20=E5=88=A0=E9=99=A4=E5=8D=95=E4=B8=AA?= =?UTF-8?q?=E6=90=9C=E7=B4=A2=E5=8E=86=E5=8F=B2=E6=97=A0=E6=95=88?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/pages/search/controller.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/pages/search/controller.dart b/lib/pages/search/controller.dart index cbb864056..1853c238c 100644 --- a/lib/pages/search/controller.dart +++ b/lib/pages/search/controller.dart @@ -115,7 +115,7 @@ class SSearchController extends GetxController { onLongSelect(word) { int index = historyList.indexOf(word); - historyList.value = historyList.removeAt(index); + historyList.removeAt(index); historyList.refresh(); histiryWord.put('cacheList', historyList); } From 31405750e619914c0dabb0dfbdb015270203b1e0 Mon Sep 17 00:00:00 2001 From: guozhigq Date: Sun, 10 Mar 2024 22:44:07 +0800 Subject: [PATCH 3/4] =?UTF-8?q?mod:=20=E8=A7=86=E9=A2=91=E8=AF=A6=E6=83=85?= =?UTF-8?q?=E9=A1=B5=E5=B8=83=E5=B1=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/pages/video/detail/controller.dart | 84 ++++ lib/pages/video/detail/view.dart | 450 +++++++++--------- .../video/detail/widgets/header_control.dart | 119 ----- 3 files changed, 308 insertions(+), 345 deletions(-) diff --git a/lib/pages/video/detail/controller.dart b/lib/pages/video/detail/controller.dart index b4005b5a5..40723efae 100644 --- a/lib/pages/video/detail/controller.dart +++ b/lib/pages/video/detail/controller.dart @@ -5,6 +5,7 @@ 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:ns_danmaku/ns_danmaku.dart'; import 'package:pilipala/http/constants.dart'; import 'package:pilipala/http/video.dart'; import 'package:pilipala/models/common/reply_type.dart'; @@ -19,6 +20,7 @@ import 'package:pilipala/utils/utils.dart'; import 'package:pilipala/utils/video_utils.dart'; import 'package:screen_brightness/screen_brightness.dart'; +import '../../../http/danmaku.dart'; import '../../../utils/id_utils.dart'; import 'widgets/header_control.dart'; @@ -383,4 +385,86 @@ class VideoDetailController extends GetxController ? replyReplyBottomSheetCtr!.close() : print('replyReplyBottomSheetCtr is null'); } + + /// 发送弹幕 + void showShootDanmakuSheet() { + final TextEditingController textController = TextEditingController(); + bool isSending = false; // 追踪是否正在发送 + showDialog( + context: Get.context!, + builder: (BuildContext context) { + // TODO: 支持更多类型和颜色的弹幕 + return AlertDialog( + title: const Text('发送弹幕'), + content: StatefulBuilder( + builder: (BuildContext context, StateSetter setState) { + return TextField( + controller: textController, + ); + }), + actions: [ + TextButton( + onPressed: () => Get.back(), + child: Text( + '取消', + style: TextStyle(color: Theme.of(context).colorScheme.outline), + ), + ), + StatefulBuilder( + builder: (BuildContext context, StateSetter setState) { + return TextButton( + onPressed: isSending + ? null + : () async { + final String msg = textController.text; + if (msg.isEmpty) { + SmartDialog.showToast('弹幕内容不能为空'); + return; + } else if (msg.length > 100) { + SmartDialog.showToast('弹幕内容不能超过100个字符'); + return; + } + setState(() { + isSending = true; // 开始发送,更新状态 + }); + //修改按钮文字 + // SmartDialog.showToast('弹幕发送中,\n$msg'); + final dynamic res = await DanmakaHttp.shootDanmaku( + oid: cid.value, + msg: textController.text, + bvid: bvid, + progress: + plPlayerController.position.value.inMilliseconds, + type: 1, + ); + setState(() { + isSending = false; // 发送结束,更新状态 + }); + if (res['status']) { + SmartDialog.showToast('发送成功'); + // 发送成功,自动预览该弹幕,避免重新请求 + // TODO: 暂停状态下预览弹幕仍会移动与计时,可考虑添加到dmSegList或其他方式实现 + plPlayerController.danmakuController?.addItems([ + DanmakuItem( + msg, + color: Colors.white, + time: plPlayerController + .position.value.inMilliseconds, + type: DanmakuItemType.scroll, + isSend: true, + ) + ]); + Get.back(); + } else { + SmartDialog.showToast('发送失败,错误信息为${res['msg']}'); + } + }, + child: Text(isSending ? '发送中...' : '发送'), + ); + }) + ], + ); + }, + ); + } } diff --git a/lib/pages/video/detail/view.dart b/lib/pages/video/detail/view.dart index 04d41b2e3..e8ca935de 100644 --- a/lib/pages/video/detail/view.dart +++ b/lib/pages/video/detail/view.dart @@ -25,7 +25,7 @@ import 'package:pilipala/services/service_locator.dart'; import 'package:pilipala/utils/storage.dart'; import '../../../services/shutdown_timer_service.dart'; -import 'widgets/header_control.dart'; +import 'widgets/app_bar.dart'; class VideoDetailPage extends StatefulWidget { const VideoDetailPage({Key? key}) : super(key: key); @@ -38,7 +38,7 @@ class VideoDetailPage extends StatefulWidget { class _VideoDetailPageState extends State with TickerProviderStateMixin, RouteAware { - late VideoDetailController videoDetailController; + late VideoDetailController vdCtr; PlPlayerController? plPlayerController; final ScrollController _extendNestCtr = ScrollController(); late StreamController appbarStream; @@ -65,20 +65,18 @@ class _VideoDetailPageState extends State void initState() { super.initState(); heroTag = Get.arguments['heroTag']; - videoDetailController = Get.put(VideoDetailController(), tag: heroTag); + vdCtr = Get.put(VideoDetailController(), tag: heroTag); videoIntroController = Get.put( VideoIntroController(bvid: Get.parameters['bvid']!), tag: heroTag); videoIntroController.videoDetail.listen((value) { - videoPlayerServiceHandler.onVideoDetailChange( - value, videoDetailController.cid.value); + videoPlayerServiceHandler.onVideoDetailChange(value, vdCtr.cid.value); }); bangumiIntroController = Get.put(BangumiIntroController(), tag: heroTag); bangumiIntroController.bangumiDetail.listen((value) { - videoPlayerServiceHandler.onVideoDetailChange( - value, videoDetailController.cid.value); + videoPlayerServiceHandler.onVideoDetailChange(value, vdCtr.cid.value); }); - videoDetailController.cid.listen((p0) { + vdCtr.cid.listen((p0) { videoPlayerServiceHandler.onVideoDetailChange( bangumiIntroController.bangumiDetail.value, p0); }); @@ -93,16 +91,16 @@ class _VideoDetailPageState extends State appbarStreamListen(); fullScreenStatusListener(); if (Platform.isAndroid) { - floating = videoDetailController.floating!; + floating = vdCtr.floating!; autoEnterPip(); } } // 获取视频资源,初始化播放器 Future videoSourceInit() async { - _futureBuilderFuture = videoDetailController.queryVideoUrl(); - if (videoDetailController.autoPlay.value) { - plPlayerController = videoDetailController.plPlayerController; + _futureBuilderFuture = vdCtr.queryVideoUrl(); + if (vdCtr.autoPlay.value) { + plPlayerController = vdCtr.plPlayerController; plPlayerController!.addStatusLister(playerListener); } } @@ -131,10 +129,10 @@ class _VideoDetailPageState extends State /// 顺序播放 列表循环 if (plPlayerController!.playRepeat != PlayRepeat.pause && plPlayerController!.playRepeat != PlayRepeat.singleCycle) { - if (videoDetailController.videoType == SearchType.video) { + if (vdCtr.videoType == SearchType.video) { videoIntroController.nextPlay(); } - if (videoDetailController.videoType == SearchType.media_bangumi) { + if (vdCtr.videoType == SearchType.media_bangumi) { bangumiIntroController.nextPlay(); } } @@ -146,8 +144,7 @@ class _VideoDetailPageState extends State } // 播放完展示控制栏 try { - PiPStatus currentStatus = - await videoDetailController.floating!.pipStatus; + PiPStatus currentStatus = await vdCtr.floating!.pipStatus; if (currentStatus == PiPStatus.disabled) { plPlayerController!.onLockControl(false); } @@ -168,17 +165,17 @@ class _VideoDetailPageState extends State /// 未开启自动播放时触发播放 Future handlePlay() async { - await videoDetailController.playerInit(); - plPlayerController = videoDetailController.plPlayerController; + await vdCtr.playerInit(); + plPlayerController = vdCtr.plPlayerController; plPlayerController!.addStatusLister(playerListener); - videoDetailController.autoPlay.value = true; - videoDetailController.isShowCover.value = false; + vdCtr.autoPlay.value = true; + vdCtr.isShowCover.value = false; } void fullScreenStatusListener() { plPlayerController?.isFullScreen.listen((bool isFullScreen) { if (isFullScreen) { - videoDetailController.hiddenReplyReplyPanel(); + vdCtr.hiddenReplyReplyPanel(); } }); } @@ -190,8 +187,8 @@ class _VideoDetailPageState extends State plPlayerController!.removeStatusLister(playerListener); plPlayerController!.dispose(); } - if (videoDetailController.floating != null) { - videoDetailController.floating!.dispose(); + if (vdCtr.floating != null) { + vdCtr.floating!.dispose(); } videoPlayerServiceHandler.onVideoDetailDispose(); if (Platform.isAndroid) { @@ -207,10 +204,10 @@ class _VideoDetailPageState extends State /// 开启 if (setting.get(SettingBoxKey.enableAutoBrightness, defaultValue: false) as bool) { - videoDetailController.brightness = plPlayerController!.brightness.value; + vdCtr.brightness = plPlayerController!.brightness.value; } if (plPlayerController != null) { - videoDetailController.defaultST = plPlayerController!.position.value; + vdCtr.defaultST = plPlayerController!.position.value; videoIntroController.isPaused = true; plPlayerController!.removeStatusLister(playerListener); plPlayerController!.pause(); @@ -226,17 +223,16 @@ class _VideoDetailPageState extends State plPlayerController!.videoPlayerController != null) { setState(() => isShowing = true); } - videoDetailController.isFirstTime = false; + vdCtr.isFirstTime = false; final bool autoplay = autoPlayEnable; - videoDetailController.playerInit(autoplay: autoplay); + vdCtr.playerInit(autoplay: autoplay); /// 未开启自动播放时,未播放跳转下一页返回/播放后跳转下一页返回 - videoDetailController.autoPlay.value = - !videoDetailController.isShowCover.value; + vdCtr.autoPlay.value = !vdCtr.isShowCover.value; videoIntroController.isPaused = false; if (_extendNestCtr.position.pixels == 0 && autoplay) { await Future.delayed(const Duration(milliseconds: 300)); - plPlayerController?.seekTo(videoDetailController.defaultST); + plPlayerController?.seekTo(vdCtr.defaultST); plPlayerController?.play(); } plPlayerController?.addStatusLister(playerListener); @@ -259,9 +255,158 @@ class _VideoDetailPageState extends State @override Widget build(BuildContext context) { - final double videoHeight = MediaQuery.sizeOf(context).width * 9 / 16; + // final double videoHeight = MediaQuery.sizeOf(context).width * 9 / 16; + final sizeContext = MediaQuery.sizeOf(context); + final _context = MediaQuery.of(context); + late double defaultVideoHeight = sizeContext.width * 9 / 16; + late RxDouble videoHeight = defaultVideoHeight.obs; final double pinnedHeaderHeight = - statusBarHeight + kToolbarHeight + videoHeight; + statusBarHeight + kToolbarHeight + videoHeight.value; + // ignore: no_leading_underscores_for_local_identifiers + + // 竖屏 + final bool isPortrait = _context.orientation == Orientation.portrait; + // 横屏 + final bool isLandscape = _context.orientation == Orientation.landscape; + final Rx isFullScreen = plPlayerController?.isFullScreen ?? false.obs; + // 全屏时高度撑满 + if (isLandscape || isFullScreen.value == true) { + videoHeight.value = Get.size.height; + enterFullScreen(); + } else { + videoHeight.value = defaultVideoHeight; + exitFullScreen(); + } + + /// 播放器面板 + Widget videoPlayerPanel = FutureBuilder( + future: _futureBuilderFuture, + builder: (BuildContext context, AsyncSnapshot snapshot) { + if (snapshot.hasData && snapshot.data['status']) { + return Obx( + () { + return !vdCtr.autoPlay.value + ? const SizedBox() + : PLVideoPlayer( + controller: plPlayerController!, + headerControl: vdCtr.headerControl, + danmuWidget: Obx( + () => PlDanmaku( + key: Key(vdCtr.danmakuCid.value.toString()), + cid: vdCtr.danmakuCid.value, + playerController: plPlayerController!, + ), + ), + ); + }, + ); + } else { + // 加载失败异常处理 + return const SizedBox(); + } + }, + ); + + /// tabbar + Widget tabbarBuild = SizedBox( + width: double.infinity, + height: 45, + child: Row( + children: [ + Expanded( + child: TabBar( + controller: vdCtr.tabCtr, + dividerColor: Colors.transparent, + tabs: vdCtr.tabs.map((String name) => Tab(text: name)).toList(), + ), + ), + SizedBox( + width: 200, + child: Center( + child: Row( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + SizedBox( + height: 32, + child: TextButton( + style: ButtonStyle( + padding: MaterialStateProperty.all(EdgeInsets.zero), + ), + onPressed: () => vdCtr.showShootDanmakuSheet(), + child: const Text( + '发弹幕', + ), + ), + ), + const SizedBox(width: 4), + SizedBox( + width: 34, + height: 32, + child: TextButton( + style: ButtonStyle( + padding: MaterialStateProperty.all(EdgeInsets.zero), + ), + onPressed: () { + plPlayerController?.isOpenDanmu.value = + !(plPlayerController?.isOpenDanmu.value ?? false); + }, + child: Obx(() => Text( + '弹', + style: TextStyle( + color: (plPlayerController?.isOpenDanmu.value ?? + false) + ? Theme.of(context).colorScheme.primary + : Theme.of(context).colorScheme.outline, + ), + )), + ), + ), + const SizedBox(width: 14), + ], + ), + ), + ), + ], + ), + ); + + /// 手动播放 + Widget handlePlayPanel() { + return Stack( + children: [ + GestureDetector( + onTap: () { + handlePlay(); + }, + child: NetworkImgLayer( + type: 'emote', + src: vdCtr.videoItem['pic'], + width: Get.width, + height: videoHeight.value, + ), + ), + Positioned( + top: 0, + left: 0, + right: 0, + child: buildCustomAppBar(), + ), + 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 && plPlayerController?.isFullScreen.value == true, @@ -273,7 +418,7 @@ class _VideoDetailPageState extends State children: [ Scaffold( resizeToAvoidBottomInset: false, - key: videoDetailController.scaffoldKey, + key: vdCtr.scaffoldKey, backgroundColor: Colors.black, appBar: PreferredSize( preferredSize: const Size.fromHeight(0), @@ -299,21 +444,19 @@ class _VideoDetailPageState extends State return SliverAppBar( automaticallyImplyLeading: false, // 假装使用一个非空变量,避免Obx检测不到而罢工 - pinned: videoDetailController.autoPlay.value ^ - false ^ - videoDetailController.autoPlay.value, + pinned: vdCtr.autoPlay.value, elevation: 0, scrolledUnderElevation: 0, forceElevated: innerBoxIsScrolled, expandedHeight: MediaQuery.of(context).orientation == Orientation.landscape || plPlayerController?.isFullScreen.value == true - ? MediaQuery.sizeOf(context).height - + ? (MediaQuery.sizeOf(context).height - (MediaQuery.of(context).orientation == Orientation.landscape ? 0 - : MediaQuery.of(context).padding.top) - : videoHeight, + : MediaQuery.of(context).padding.top)) + : videoHeight.value, backgroundColor: Colors.black, flexibleSpace: FlexibleSpaceBar( background: PopScope( @@ -333,108 +476,27 @@ class _VideoDetailPageState extends State child: LayoutBuilder( builder: (BuildContext context, BoxConstraints boxConstraints) { - final double maxWidth = - boxConstraints.maxWidth; - final double maxHeight = - boxConstraints.maxHeight; + // final double maxWidth = + // boxConstraints.maxWidth; + // final double maxHeight = + // boxConstraints.maxHeight; return Stack( children: [ - if (isShowing) - FutureBuilder( - future: _futureBuilderFuture, - builder: (BuildContext context, - AsyncSnapshot snapshot) { - if (snapshot.hasData && - snapshot.data['status']) { - return Obx( - () => - !videoDetailController - .autoPlay.value - ? nil - : PLVideoPlayer( - controller: - plPlayerController!, - headerControl: - videoDetailController - .headerControl, - danmuWidget: Obx( - () => PlDanmaku( - key: Key(videoDetailController - .danmakuCid - .value - .toString()), - cid: videoDetailController - .danmakuCid - .value, - playerController: - plPlayerController!, - ), - ), - ), - ); - } else { - return buildCustomAppBar(); - } - }, - ), + if (isShowing) videoPlayerPanel, /// 关闭自动播放时 手动播放 - 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: maxWidth, - height: maxHeight, - ), - ), - ), + Obx( + () => Visibility( + visible: !vdCtr.autoPlay.value && + vdCtr.isShowCover.value, + child: Positioned( + top: 0, + left: 0, + right: 0, + child: handlePlayPanel(), ), ), - Obx( - () => Visibility( - visible: videoDetailController - .isShowCover.value && - videoDetailController - .isEffective.value, - child: Stack( - children: [ - Positioned( - top: 0, - left: 0, - right: 0, - child: buildCustomAppBar(), - ), - Positioned( - right: 12, - bottom: 10, - child: IconButton( - tooltip: '播放', - onPressed: () => - handlePlay(), - icon: Image.asset( - 'assets/images/play.png', - width: 60, - height: 60, - )), - ), - ], - )), - ), - ] + ), ], ); }, @@ -445,18 +507,16 @@ class _VideoDetailPageState extends State ), ]; }, - // pinnedHeaderSliverHeightBuilder: () { - // return playerStatus != PlayerStatus.playing - // ? statusBarHeight + kToolbarHeight - // : pinnedHeaderHeight; - // }, + /// 不收回 pinnedHeaderSliverHeightBuilder: () { return MediaQuery.of(context).orientation == Orientation.landscape || plPlayerController?.isFullScreen.value == true ? MediaQuery.sizeOf(context).height - : pinnedHeaderHeight; + : playerStatus != PlayerStatus.playing + ? kToolbarHeight + : pinnedHeaderHeight; }, onlyOneScrollInBody: true, body: ColoredBox( @@ -464,54 +524,23 @@ class _VideoDetailPageState extends State color: Theme.of(context).colorScheme.background, child: Column( children: [ - Opacity( - opacity: 0, - child: SizedBox( - width: double.infinity, - height: 0, - child: Obx( - () => TabBar( - controller: videoDetailController.tabCtr, - dividerColor: Colors.transparent, - indicatorColor: - Theme.of(context).colorScheme.background, - tabs: videoDetailController.tabs - .map((String name) => Tab(text: name)) - .toList(), - ), - ), - ), - ), + tabbarBuild, Expanded( child: TabBarView( - controller: videoDetailController.tabCtr, + controller: vdCtr.tabCtr, children: [ Builder( builder: (BuildContext context) { return CustomScrollView( key: const PageStorageKey('简介'), slivers: [ - if (videoDetailController.videoType == - SearchType.video) ...[ - VideoIntroPanel( - bvid: videoDetailController.bvid), - ] else if (videoDetailController.videoType == + if (vdCtr.videoType == SearchType.video) ...[ + VideoIntroPanel(bvid: vdCtr.bvid), + ] else if (vdCtr.videoType == SearchType.media_bangumi) ...[ Obx(() => BangumiIntroPanel( - cid: videoDetailController.cid.value)), + cid: vdCtr.cid.value)), ], - // if (videoDetailController.videoType == - // SearchType.video) ...[ - // SliverPersistentHeader( - // floating: true, - // pinned: true, - // delegate: SliverHeaderDelegate( - // height: 50, - // child: - // const MenuRow(loadingStatus: false), - // ), - // ), - // ], SliverToBoxAdapter( child: Divider( indent: 12, @@ -528,8 +557,8 @@ class _VideoDetailPageState extends State ), Obx( () => VideoReplyPanel( - bvid: videoDetailController.bvid, - oid: videoDetailController.oid.value, + bvid: vdCtr.bvid, + oid: vdCtr.oid.value, ), ) ], @@ -543,56 +572,26 @@ class _VideoDetailPageState extends State /// 重新进入会刷新 // 播放完成/暂停播放 - // StreamBuilder( - // stream: appbarStream.stream, - // initialData: 0, - // builder: ((context, snapshot) { - // return ScrollAppBar( - // snapshot.data!.toDouble(), - // () => continuePlay(), - // playerStatus, - // null, - // ); - // }), - // ) + StreamBuilder( + stream: appbarStream.stream, + initialData: 0, + builder: ((context, snapshot) { + return ScrollAppBar( + snapshot.data!.toDouble(), + () => continuePlay(), + playerStatus, + null, + ); + }), + ) ], ), ); - Widget childWhenEnabled = FutureBuilder( - key: Key(heroTag), - future: _futureBuilderFuture, - builder: (BuildContext context, AsyncSnapshot snapshot) { - if (snapshot.hasData && snapshot.data['status']) { - return Obx( - () => !videoDetailController.autoPlay.value - ? const SizedBox() - : PLVideoPlayer( - controller: plPlayerController!, - headerControl: HeaderControl( - controller: plPlayerController, - videoDetailCtr: videoDetailController, - bvid: videoDetailController.bvid, - videoType: videoDetailController.videoType, - ), - danmuWidget: Obx( - () => PlDanmaku( - key: Key( - videoDetailController.danmakuCid.value.toString()), - cid: videoDetailController.danmakuCid.value, - playerController: plPlayerController!, - ), - ), - ), - ); - } else { - return nil; - } - }, - ); + if (Platform.isAndroid) { return PiPSwitcher( childWhenDisabled: childWhenDisabled, - childWhenEnabled: childWhenEnabled, + childWhenEnabled: videoPlayerPanel, floating: floating, ); } else { @@ -633,8 +632,7 @@ class _VideoDetailPageState extends State ComBtn( icon: const Icon(Icons.history_outlined, size: 22), fuc: () async { - var res = await UserHttp.toViewLater( - bvid: videoDetailController.bvid); + var res = await UserHttp.toViewLater(bvid: vdCtr.bvid); SmartDialog.showToast(res['msg']); }, ), diff --git a/lib/pages/video/detail/widgets/header_control.dart b/lib/pages/video/detail/widgets/header_control.dart index e72e6a694..a8414c189 100644 --- a/lib/pages/video/detail/widgets/header_control.dart +++ b/lib/pages/video/detail/widgets/header_control.dart @@ -1,5 +1,4 @@ import 'dart:io'; -import 'dart:math'; import 'package:floating/floating.dart'; import 'package:flutter/material.dart'; @@ -17,7 +16,6 @@ import 'package:pilipala/pages/video/detail/introduction/widgets/menu_row.dart'; import 'package:pilipala/plugin/pl_player/index.dart'; import 'package:pilipala/plugin/pl_player/models/play_repeat.dart'; import 'package:pilipala/utils/storage.dart'; -import 'package:pilipala/http/danmaku.dart'; import 'package:pilipala/services/shutdown_timer_service.dart'; import '../../../../models/common/search_type.dart'; import '../../../../models/video_detail_res.dart'; @@ -221,88 +219,6 @@ class _HeaderControlState extends State { ); } - /// 发送弹幕 - void showShootDanmakuSheet() { - final TextEditingController textController = TextEditingController(); - bool isSending = false; // 追踪是否正在发送 - showDialog( - context: Get.context!, - builder: (BuildContext context) { - // TODO: 支持更多类型和颜色的弹幕 - return AlertDialog( - title: const Text('发送弹幕(测试)'), - content: StatefulBuilder( - builder: (BuildContext context, StateSetter setState) { - return TextField( - controller: textController, - ); - }), - actions: [ - TextButton( - onPressed: () => Get.back(), - child: Text( - '取消', - style: TextStyle(color: Theme.of(context).colorScheme.outline), - ), - ), - StatefulBuilder( - builder: (BuildContext context, StateSetter setState) { - return TextButton( - onPressed: isSending - ? null - : () async { - final String msg = textController.text; - if (msg.isEmpty) { - SmartDialog.showToast('弹幕内容不能为空'); - return; - } else if (msg.length > 100) { - SmartDialog.showToast('弹幕内容不能超过100个字符'); - return; - } - setState(() { - isSending = true; // 开始发送,更新状态 - }); - //修改按钮文字 - // SmartDialog.showToast('弹幕发送中,\n$msg'); - final dynamic res = await DanmakaHttp.shootDanmaku( - oid: widget.videoDetailCtr!.cid.value, - msg: textController.text, - bvid: widget.videoDetailCtr!.bvid, - progress: - widget.controller!.position.value.inMilliseconds, - type: 1, - ); - setState(() { - isSending = false; // 发送结束,更新状态 - }); - if (res['status']) { - SmartDialog.showToast('发送成功'); - // 发送成功,自动预览该弹幕,避免重新请求 - // TODO: 暂停状态下预览弹幕仍会移动与计时,可考虑添加到dmSegList或其他方式实现 - widget.controller!.danmakuController!.addItems([ - DanmakuItem( - msg, - color: Colors.white, - time: widget - .controller!.position.value.inMilliseconds, - type: DanmakuItemType.scroll, - isSend: true, - ) - ]); - Get.back(); - } else { - SmartDialog.showToast('发送失败,错误信息为${res['msg']}'); - } - }, - child: Text(isSending ? '发送中...' : '发送'), - ); - }) - ], - ); - }, - ); - } - /// 定时关闭 void scheduleExit() async { const List scheduleTimeChoices = [ @@ -1166,41 +1082,6 @@ class _HeaderControlState extends State { // ), // fuc: () => _.screenshot(), // ), - SizedBox( - width: 56, - height: 34, - child: TextButton( - style: ButtonStyle( - padding: MaterialStateProperty.all(EdgeInsets.zero), - ), - onPressed: () => showShootDanmakuSheet(), - child: const Text( - '发弹幕', - style: textStyle, - ), - ), - ), - SizedBox( - width: 34, - height: 34, - child: Obx( - () => IconButton( - style: ButtonStyle( - padding: MaterialStateProperty.all(EdgeInsets.zero), - ), - onPressed: () { - _.isOpenDanmu.value = !_.isOpenDanmu.value; - }, - icon: Icon( - _.isOpenDanmu.value - ? Icons.subtitles_outlined - : Icons.subtitles_off_outlined, - size: 19, - color: Colors.white, - ), - ), - ), - ), SizedBox(width: buttonSpace), if (Platform.isAndroid) ...[ SizedBox( From d3766ae31b5465257c92aa74b0b4a5c4d30fafc8 Mon Sep 17 00:00:00 2001 From: guozhigq Date: Mon, 11 Mar 2024 23:35:07 +0800 Subject: [PATCH 4/4] typo: dynamic type --- lib/models/common/dynamics_type.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/models/common/dynamics_type.dart b/lib/models/common/dynamics_type.dart index 337f6aecc..f4e20a4b9 100644 --- a/lib/models/common/dynamics_type.dart +++ b/lib/models/common/dynamics_type.dart @@ -7,5 +7,5 @@ enum DynamicsType { extension BusinessTypeExtension on DynamicsType { String get values => ['all', 'video', 'pgc', 'article'][index]; - String get labels => ['全部', '视频', '追番', '专栏'][index]; + String get labels => ['全部', '投稿', '番剧', '专栏'][index]; }