diff --git a/lib/models_new/live/live_feed_index/card_data_list_item.dart b/lib/models_new/live/live_feed_index/card_data_list_item.dart index 9fdfba22b..2d4639161 100644 --- a/lib/models_new/live/live_feed_index/card_data_list_item.dart +++ b/lib/models_new/live/live_feed_index/card_data_list_item.dart @@ -1,4 +1,5 @@ import 'package:PiliPlus/models_new/live/live_feed_index/watched_show.dart'; +import 'package:PiliPlus/utils/parse_string.dart'; class CardLiveItem { int? roomid; @@ -6,6 +7,7 @@ class CardLiveItem { String? uname; String? face; String? cover; + String? systemCover; String? title; int? liveTime; String? areaName; @@ -21,6 +23,7 @@ class CardLiveItem { this.uname, this.face, this.cover, + this.systemCover, this.title, this.liveTime, this.areaName, @@ -37,6 +40,7 @@ class CardLiveItem { uname: json['uname'] as String?, face: json['face'] as String?, cover: json['cover'] as String?, + systemCover: noneNullOrEmptyString(json['system_cover']), title: json['title'] as String?, liveTime: json['live_time'] as int?, areaName: json['area_name'] as String?, diff --git a/lib/pages/live/controller.dart b/lib/pages/live/controller.dart index 7ddacfeed..29ada1b21 100644 --- a/lib/pages/live/controller.dart +++ b/lib/pages/live/controller.dart @@ -36,6 +36,8 @@ class LiveController extends CommonListController with AccountMixin { final followController = ScrollController(); + bool showFirstFrame = false; + @override void checkIsEnd(int length) { if (count != null && length >= count!) { diff --git a/lib/pages/live/view.dart b/lib/pages/live/view.dart index 248ac325e..4d00f4a93 100644 --- a/lib/pages/live/view.dart +++ b/lib/pages/live/view.dart @@ -21,6 +21,7 @@ import 'package:PiliPlus/utils/platform_utils.dart'; import 'package:PiliPlus/utils/utils.dart'; import 'package:flutter/material.dart'; import 'package:get/get.dart'; +import 'package:material_design_icons_flutter/material_design_icons_flutter.dart'; class LivePage extends StatefulWidget { const LivePage({super.key}); @@ -128,6 +129,20 @@ class _LivePageState extends State }), ), ), + iconButton( + size: 26, + iconSize: 18, + context: context, + tooltip: '切换${controller.showFirstFrame ? '封面' : '首帧'}', + icon: controller.showFirstFrame + ? const Icon(MdiIcons.alphaFBox) + : const Icon(MdiIcons.image), + onPressed: () { + controller.showFirstFrame = !controller.showFirstFrame; + setState(() {}); + }, + ), + const SizedBox(width: 8), iconButton( size: 26, iconSize: 16, @@ -224,9 +239,13 @@ class _LivePageState extends State if (item is LiveCardList) { return LiveCardVApp( item: item.cardData!.smallCardV1!, + showFirstFrame: controller.showFirstFrame, ); } - return LiveCardVApp(item: item); + return LiveCardVApp( + item: item, + showFirstFrame: controller.showFirstFrame, + ); }, itemCount: response.length, ) diff --git a/lib/pages/live/widgets/live_item_app.dart b/lib/pages/live/widgets/live_item_app.dart index 44da5125d..388d63712 100644 --- a/lib/pages/live/widgets/live_item_app.dart +++ b/lib/pages/live/widgets/live_item_app.dart @@ -10,10 +10,12 @@ import 'package:flutter/material.dart' hide LayoutBuilder; // 视频卡片 - 垂直布局 class LiveCardVApp extends StatelessWidget { final CardLiveItem item; + final bool showFirstFrame; const LiveCardVApp({ super.key, required this.item, + this.showFirstFrame = false, }); @override @@ -40,7 +42,9 @@ class LiveCardVApp extends StatelessWidget { clipBehavior: Clip.none, children: [ NetworkImgLayer( - src: item.cover!, + src: showFirstFrame + ? (item.systemCover ?? item.cover) + : item.cover, width: maxWidth, height: maxHeight, type: .emote, diff --git a/lib/pages/live_area/controller.dart b/lib/pages/live_area/controller.dart index 400fe5617..151c25203 100644 --- a/lib/pages/live_area/controller.dart +++ b/lib/pages/live_area/controller.dart @@ -4,16 +4,19 @@ import 'package:PiliPlus/models_new/live/live_area_list/area_item.dart'; import 'package:PiliPlus/models_new/live/live_area_list/area_list.dart'; import 'package:PiliPlus/pages/common/common_list_controller.dart'; import 'package:PiliPlus/utils/accounts.dart'; +import 'package:flutter/material.dart' show TabController; import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; import 'package:get/get.dart'; -class LiveAreaController - extends CommonListController?, AreaList> { +class LiveAreaController extends CommonListController?, AreaList> + with GetSingleTickerProviderStateMixin { late final isLogin = Accounts.main.isLogin; late final isEditing = false.obs; late final favInfo = {}; + TabController? tabController; + @override void onInit() { super.onInit(); @@ -31,6 +34,16 @@ class LiveAreaController return super.onRefresh(); } + @override + bool customHandleResponse(bool isRefresh, Success?> response) { + assert(tabController == null); + final length = response.response?.length; + if (length != null && length != 0) { + tabController = TabController(length: length, vsync: this); + } + return super.customHandleResponse(isRefresh, response); + } + Rx>> favState = LoadingState>.loading().obs; @@ -65,4 +78,11 @@ class LiveAreaController isEditing.value = !isEditing.value; } } + + @override + void onClose() { + tabController?.dispose(); + tabController = null; + super.onClose(); + } } diff --git a/lib/pages/live_area/view.dart b/lib/pages/live_area/view.dart index 6a6709afa..51858e1d0 100644 --- a/lib/pages/live_area/view.dart +++ b/lib/pages/live_area/view.dart @@ -80,82 +80,78 @@ class _LiveAreaPageState extends State { Loading() => const SizedBox.shrink(), Success(:final response) => response != null && response.isNotEmpty - ? DefaultTabController( - length: response.length, - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - TabBar( - isScrollable: true, - tabAlignment: TabAlignment.start, - tabs: response.map((e) => Tab(text: e.name)).toList(), - ), - Expanded( - child: tabBarView( - children: response - .map( - (e) => KeepAliveWrapper( - builder: (context) { - if (e.areaList.isNullOrEmpty) { - return const SizedBox.shrink(); - } - return GridView.builder( - padding: EdgeInsets.only( - top: 12, - bottom: bottom + 100, - ), - gridDelegate: - const SliverGridDelegateWithMaxCrossAxisExtent( - maxCrossAxisExtent: 100, - mainAxisSpacing: 10, - crossAxisSpacing: 10, - mainAxisExtent: 80, - ), - itemCount: e.areaList!.length, - itemBuilder: (context, index) { - final item = e.areaList![index]; - return _tagItem( - theme: theme, - item: item, - onPressed: () { - // success - bool? isFav = - _controller.favInfo[item.id]; - if (isFav == true) { - _controller.favInfo[item.id] = - false; + ? Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + TabBar( + isScrollable: true, + tabAlignment: TabAlignment.start, + controller: _controller.tabController, + tabs: response.map((e) => Tab(text: e.name)).toList(), + ), + Expanded( + child: tabBarView( + controller: _controller.tabController, + children: response + .map( + (e) => KeepAliveWrapper( + builder: (context) { + if (e.areaList.isNullOrEmpty) { + return const SizedBox.shrink(); + } + return GridView.builder( + padding: EdgeInsets.only( + top: 12, + bottom: bottom + 100, + ), + gridDelegate: + const SliverGridDelegateWithMaxCrossAxisExtent( + maxCrossAxisExtent: 100, + mainAxisSpacing: 10, + crossAxisSpacing: 10, + mainAxisExtent: 80, + ), + itemCount: e.areaList!.length, + itemBuilder: (context, index) { + final item = e.areaList![index]; + return _tagItem( + theme: theme, + item: item, + onPressed: () { + // success + bool? isFav = + _controller.favInfo[item.id]; + if (isFav == true) { + _controller.favInfo[item.id] = false; + _controller.favState + ..value.data.remove(item) + ..refresh(); + (context as Element).markNeedsBuild(); + } else { + // check + if (_controller + .favState + .value + .isSuccess) { + _controller.favInfo[item.id] = true; _controller.favState - ..value.data.remove(item) + ..value.data.add(item) ..refresh(); (context as Element) .markNeedsBuild(); - } else { - // check - if (_controller - .favState - .value - .isSuccess) { - _controller.favInfo[item.id] = - true; - _controller.favState - ..value.data.add(item) - ..refresh(); - (context as Element) - .markNeedsBuild(); - } } - }, - ); - }, - ); - }, - ), - ) - .toList(), - ), + } + }, + ); + }, + ); + }, + ), + ) + .toList(), ), - ], - ), + ), + ], ) : scrollErrorWidget(onReload: _controller.onReload), Error(:final errMsg) => scrollErrorWidget( @@ -192,9 +188,7 @@ class _LiveAreaPageState extends State { const SizedBox(height: 8), if (response != null && response.isNotEmpty) ...[ SortableWrap( - onSortStart: (index) { - _controller.isEditing.value = true; - }, + onSortStart: (index) => _controller.isEditing.value = true, onSorted: (int oldIndex, int newIndex) { response.insert(newIndex, response.removeAt(oldIndex)); }, diff --git a/lib/pages/live_area_detail/child/view.dart b/lib/pages/live_area_detail/child/view.dart index 510be10c9..532ff8ed9 100644 --- a/lib/pages/live_area_detail/child/view.dart +++ b/lib/pages/live_area_detail/child/view.dart @@ -17,10 +17,12 @@ class LiveAreaChildPage extends StatefulWidget { super.key, required this.areaId, required this.parentAreaId, + required this.showFirstFrame, }); final dynamic areaId; final dynamic parentAreaId; + final bool showFirstFrame; @override State createState() => _LiveAreaChildPageState(); @@ -120,7 +122,10 @@ class _LiveAreaChildPageState extends State if (index == response.length - 1) { _controller.onLoadMore(); } - return LiveCardVApp(item: response[index]); + return LiveCardVApp( + item: response[index], + showFirstFrame: widget.showFirstFrame, + ); }, itemCount: response.length, ) diff --git a/lib/pages/live_area_detail/controller.dart b/lib/pages/live_area_detail/controller.dart index 0585eeb28..e14a13f82 100644 --- a/lib/pages/live_area_detail/controller.dart +++ b/lib/pages/live_area_detail/controller.dart @@ -4,14 +4,19 @@ import 'package:PiliPlus/http/live.dart'; import 'package:PiliPlus/http/loading_state.dart'; import 'package:PiliPlus/models_new/live/live_area_list/area_item.dart'; import 'package:PiliPlus/pages/common/common_list_controller.dart'; +import 'package:flutter/material.dart' show TabController; +import 'package:get/get_state_manager/src/rx_flutter/rx_ticket_provider_mixin.dart'; class LiveAreaDetailController - extends CommonListController?, AreaItem> { + extends CommonListController?, AreaItem> + with GetSingleTickerProviderStateMixin { LiveAreaDetailController(this.areaId, this.parentAreaId); final dynamic areaId; final dynamic parentAreaId; - late int initialIndex = 0; + TabController? tabController; + + bool showFirstFrame = false; @override void onInit() { @@ -22,7 +27,13 @@ class LiveAreaDetailController @override List? getDataList(List? response) { if (response != null && response.isNotEmpty) { - initialIndex = max(0, response.indexWhere((e) => e.id == areaId)); + assert(tabController == null); + final initialIndex = max(0, response.indexWhere((e) => e.id == areaId)); + tabController = TabController( + length: response.length, + initialIndex: initialIndex, + vsync: this, + ); } return response; } @@ -30,4 +41,11 @@ class LiveAreaDetailController @override Future?>> customGetData() => LiveHttp.liveRoomAreaList(parentid: parentAreaId); + + @override + void onClose() { + tabController?.dispose(); + tabController = null; + super.onClose(); + } } diff --git a/lib/pages/live_area_detail/view.dart b/lib/pages/live_area_detail/view.dart index cc828b3d3..3624e49c4 100644 --- a/lib/pages/live_area_detail/view.dart +++ b/lib/pages/live_area_detail/view.dart @@ -10,6 +10,7 @@ import 'package:PiliPlus/pages/live_area_detail/controller.dart'; import 'package:PiliPlus/pages/live_search/view.dart'; import 'package:flutter/material.dart'; import 'package:get/get.dart'; +import 'package:material_design_icons_flutter/material_design_icons_flutter.dart'; class LiveAreaDetailPage extends StatefulWidget { const LiveAreaDetailPage({ @@ -73,71 +74,81 @@ class _LiveAreaDetailPageState extends State { Loading() => const SizedBox.shrink(), Success(:final response) => response != null && response.isNotEmpty - ? DefaultTabController( - initialIndex: _controller.initialIndex, - length: response.length, - child: Builder( - builder: (context) { - return Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Row( - children: [ - Expanded( - child: TabBar( - dividerHeight: 0, - dividerColor: Colors.transparent, - isScrollable: true, - tabAlignment: TabAlignment.start, - tabs: response - .map((e) => Tab(text: e.name ?? '')) - .toList(), - onTap: (index) { - try { - if (!DefaultTabController.of( - context, - ).indexIsChanging) { - final item = response[index]; - Get.find( - tag: '${item.id}${item.parentId}', - ).animateToTop(); - } - } catch (_) {} - }, - ), - ), - iconButton( - icon: const Icon(Icons.menu), - onPressed: () => - _showTags(context, theme, bottom, response), - ), - ], + ? Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + Expanded( + child: TabBar( + dividerHeight: 0, + isScrollable: true, + tabAlignment: TabAlignment.start, + dividerColor: Colors.transparent, + controller: _controller.tabController, + tabs: response + .map((e) => Tab(text: e.name ?? '')) + .toList(), + onTap: (index) { + try { + if (!_controller.tabController!.indexIsChanging) { + final item = response[index]; + Get.find( + tag: '${item.id}${item.parentId}', + ).animateToTop(); + } + } catch (_) {} + }, ), - const Divider(height: 1), - Expanded( - child: tabBarView( - children: response - .map( - (e) => LiveAreaChildPage( - areaId: e.id, - parentAreaId: e.parentId, - ), - ) - .toList(), - ), - ), - ], - ); - }, - ), + ), + iconButton( + iconSize: 20, + tooltip: + '切换${_controller.showFirstFrame ? '封面' : '首帧'}', + icon: _controller.showFirstFrame + ? const Icon(MdiIcons.alphaFBox) + : const Icon(MdiIcons.image), + onPressed: () { + _controller.showFirstFrame = + !_controller.showFirstFrame; + setState(() {}); + }, + ), + iconButton( + iconSize: 20, + tooltip: '显示菜单', + icon: const Icon(Icons.menu), + onPressed: () => + _showTags(context, theme, bottom, response), + ), + ], + ), + const Divider(height: 1), + Expanded( + child: tabBarView( + controller: _controller.tabController, + children: response + .map( + (e) => LiveAreaChildPage( + areaId: e.id, + parentAreaId: e.parentId, + showFirstFrame: _controller.showFirstFrame, + ), + ) + .toList(), + ), + ), + ], ) : LiveAreaChildPage( areaId: widget.areaId, parentAreaId: widget.parentAreaId, + showFirstFrame: _controller.showFirstFrame, ), Error() => LiveAreaChildPage( areaId: widget.areaId, parentAreaId: widget.parentAreaId, + showFirstFrame: _controller.showFirstFrame, ), }; } @@ -226,7 +237,7 @@ class _LiveAreaDetailPageState extends State { item: list[index], onTap: () { Get.back(); - DefaultTabController.of(context).index = index; + _controller.tabController?.index = index; }, ); },