diff --git a/lib/models/live/follow.dart b/lib/models/live/follow.dart index 4a941b8b..411087a5 100644 --- a/lib/models/live/follow.dart +++ b/lib/models/live/follow.dart @@ -63,7 +63,7 @@ class LiveFollowingItemModel { String? roomNews; String? watchIcon; String? textSmall; - String? roomCover; + String? cover; String? pic; int? parentAreaId; int? areaId; @@ -90,7 +90,7 @@ class LiveFollowingItemModel { this.roomNews, this.watchIcon, this.textSmall, - this.roomCover, + this.cover, this.pic, this.parentAreaId, this.areaId, @@ -108,7 +108,8 @@ class LiveFollowingItemModel { isAttention = json['is_attention']; clipNum = json['clipnum']; fansNum = json['fans_num']; - areaName = json['area_name']; + areaName = + json['area_name'] == '' ? json['area_name_v2'] : json['area_name']; areaValue = json['area_value']; tags = json['tags']; recentRecordIdV2 = json['recent_record_id_v2']; @@ -118,7 +119,7 @@ class LiveFollowingItemModel { roomNews = json['room_news']; watchIcon = json['watch_icon']; textSmall = json['text_small']; - roomCover = json['room_cover']; + cover = json['room_cover']; pic = json['room_cover']; parentAreaId = json['parent_area_id']; areaId = json['area_id']; diff --git a/lib/pages/live/controller.dart b/lib/pages/live/controller.dart index 0ca9c815..492024e5 100644 --- a/lib/pages/live/controller.dart +++ b/lib/pages/live/controller.dart @@ -14,6 +14,7 @@ class LiveController extends GetxController { RxList liveList = [].obs; RxList liveFollowingList = [].obs; + RxInt liveFollowingCount = 0.obs; bool flag = false; OverlayEntry? popupDialog; Box setting = GStrorage.setting; @@ -27,9 +28,6 @@ class LiveController extends GetxController { // 获取推荐 Future queryLiveList(type) async { - // if (type == 'init') { - // _currentPage = 1; - // } var res = await LiveHttp.liveList( pn: _currentPage, ); @@ -68,13 +66,14 @@ class LiveController extends GetxController { // Future fetchLiveFollowing() async { - var res = await LiveHttp.liveFollowing(pn: 1, ps: 20); + var res = await LiveHttp.liveFollowing(pn: 1, ps: 10); if (res['status']) { liveFollowingList.value = (res['data'].list as List) .where((LiveFollowingItemModel item) => item.liveStatus == 1 && item.recordLiveTime == 0) // 根据条件过滤 .toList(); + liveFollowingCount.value = res['data'].liveCount; } return res; } diff --git a/lib/pages/live/view.dart b/lib/pages/live/view.dart index 51316fe3..eac705c7 100644 --- a/lib/pages/live/view.dart +++ b/lib/pages/live/view.dart @@ -162,34 +162,61 @@ class _LivePageState extends State mainAxisAlignment: MainAxisAlignment.spaceBetween, crossAxisAlignment: CrossAxisAlignment.start, children: [ - Obx( - () => Text.rich( - TextSpan( - children: [ - const TextSpan( - text: ' 我的关注 ', - style: TextStyle( - fontWeight: FontWeight.bold, - fontSize: 15, - ), - ), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Obx( + () => Text.rich( TextSpan( - text: ' ${_liveController.liveFollowingList.length}', - style: TextStyle( - fontSize: 12, - color: Theme.of(context).colorScheme.primary, - ), + children: [ + const TextSpan( + text: ' 我的关注 ', + style: TextStyle( + fontWeight: FontWeight.bold, + fontSize: 15, + ), + ), + TextSpan( + text: ' ${_liveController.liveFollowingCount}', + style: TextStyle( + fontSize: 12, + color: Theme.of(context).colorScheme.primary, + ), + ), + TextSpan( + text: '人正在直播', + style: TextStyle( + fontSize: 12, + color: Theme.of(context).colorScheme.outline, + ), + ), + ], ), - TextSpan( - text: '人正在直播', - style: TextStyle( - fontSize: 12, + ), + ), + InkWell( + onTap: () { + Get.toNamed('/liveFollowing'); + }, + highlightColor: Colors.transparent, + splashColor: Colors.transparent, + child: Row( + children: [ + Text( + '查看更多', + style: TextStyle( + fontSize: 14, + color: Theme.of(context).colorScheme.outline, + ), + ), + Icon( + Icons.chevron_right, color: Theme.of(context).colorScheme.outline, ), - ), - ], + ], + ), ), - ), + ], ), FutureBuilder( future: _futureBuilderFuture2, @@ -201,8 +228,7 @@ class _LivePageState extends State Map? data = snapshot.data; if (data?['status']) { RxList list = _liveController.liveFollowingList; - // ignore: invalid_use_of_protected_member - return Obx(() => LiveFollowingListView(list: list.value)); + return LiveFollowingListView(list: list); } else { return SizedBox( height: 80, @@ -230,69 +256,71 @@ class _LivePageState extends State } class LiveFollowingListView extends StatelessWidget { - final List list; + final RxList list; const LiveFollowingListView({super.key, required this.list}); @override Widget build(BuildContext context) { - return SizedBox( - height: 100, - child: ListView.builder( - scrollDirection: Axis.horizontal, - itemBuilder: (context, index) { - final LiveFollowingItemModel item = list[index]; - return Padding( - padding: const EdgeInsets.fromLTRB(3, 12, 3, 0), - child: Column( - children: [ - InkWell( - onTap: () { - Get.toNamed( - '/liveRoom?roomid=${item.roomId}', - arguments: { - 'liveItem': item, - 'heroTag': item.roomId.toString() - }, - ); - }, - child: Container( - width: 54, - height: 54, - padding: const EdgeInsets.all(2), - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(27), - border: Border.all( - color: Theme.of(context).colorScheme.primary, - width: 1.5, + return Obx( + () => SizedBox( + height: 100, + child: ListView.builder( + scrollDirection: Axis.horizontal, + itemBuilder: (context, index) { + final LiveFollowingItemModel item = list[index]; + return Padding( + padding: const EdgeInsets.fromLTRB(3, 12, 3, 0), + child: Column( + children: [ + InkWell( + onTap: () { + Get.toNamed( + '/liveRoom?roomid=${item.roomId}', + arguments: { + 'liveItem': item, + 'heroTag': item.roomId.toString() + }, + ); + }, + child: Container( + width: 54, + height: 54, + padding: const EdgeInsets.all(2), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(27), + border: Border.all( + color: Theme.of(context).colorScheme.primary, + width: 1.5, + ), + ), + child: NetworkImgLayer( + width: 50, + height: 50, + type: 'avatar', + src: list[index].face, ), ), - child: NetworkImgLayer( - width: 50, - height: 50, - type: 'avatar', - src: list[index].face, + ), + const SizedBox(height: 6), + SizedBox( + width: 62, + child: Text( + list[index].uname, + maxLines: 1, + overflow: TextOverflow.ellipsis, + textAlign: TextAlign.center, + style: const TextStyle( + fontSize: 12, + ), ), ), - ), - const SizedBox(height: 6), - SizedBox( - width: 62, - child: Text( - list[index].uname, - maxLines: 1, - overflow: TextOverflow.ellipsis, - textAlign: TextAlign.center, - style: const TextStyle( - fontSize: 12, - ), - ), - ), - ], - ), - ); - }, - itemCount: list.length, + ], + ), + ); + }, + itemCount: list.length, + ), ), ); } diff --git a/lib/pages/live/widgets/live_item.dart b/lib/pages/live/widgets/live_item.dart index f70ba82b..c19567af 100644 --- a/lib/pages/live/widgets/live_item.dart +++ b/lib/pages/live/widgets/live_item.dart @@ -2,6 +2,8 @@ import 'package:flutter/material.dart'; import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; import 'package:get/get.dart'; import 'package:pilipala/common/constants.dart'; +import 'package:pilipala/common/widgets/badge.dart'; +import 'package:pilipala/models/live/follow.dart'; import 'package:pilipala/models/live/item.dart'; import 'package:pilipala/utils/image_save.dart'; import 'package:pilipala/utils/utils.dart'; @@ -9,7 +11,7 @@ import 'package:pilipala/common/widgets/network_img_layer.dart'; // 视频卡片 - 垂直布局 class LiveCardV extends StatelessWidget { - final LiveItemModel liveItem; + final dynamic liveItem; final int crossAxisCount; const LiveCardV({ @@ -64,6 +66,9 @@ class LiveCardV extends StatelessWidget { ), ), ), + if (liveItem is LiveFollowingItemModel && + liveItem.liveStatus == 1) + const PBadge(top: 8, right: 8, text: '直播中'), ], ); }), @@ -148,7 +153,7 @@ class LiveContent extends StatelessWidget { } class VideoStat extends StatelessWidget { - final LiveItemModel? liveItem; + final dynamic liveItem; const VideoStat({ Key? key, @@ -178,25 +183,20 @@ class VideoStat extends StatelessWidget { liveItem!.areaName!, style: const TextStyle(fontSize: 11, color: Colors.white), ), - Text( - liveItem!.watchedShow!['text_small'], - style: const TextStyle(fontSize: 11, color: Colors.white), - ), + if (liveItem is LiveItemModel) ...[ + Text( + liveItem!.watchedShow?['text_small'], + style: const TextStyle(fontSize: 11, color: Colors.white), + ), + ], + if (liveItem is LiveFollowingItemModel) ...[ + Text( + '${liveItem.textSmall}', + style: const TextStyle(fontSize: 11, color: Colors.white), + ), + ] ], ), - - // child: RichText( - // maxLines: 1, - // textAlign: TextAlign.justify, - // softWrap: false, - // text: TextSpan( - // style: const TextStyle(fontSize: 11, color: Colors.white), - // children: [ - // TextSpan(text: liveItem!.areaName!), - // TextSpan(text: liveItem!.watchedShow!['text_small']), - // ], - // ), - // ), ); } } diff --git a/lib/pages/live_follow/controller.dart b/lib/pages/live_follow/controller.dart new file mode 100644 index 00000000..65c99384 --- /dev/null +++ b/lib/pages/live_follow/controller.dart @@ -0,0 +1,50 @@ +import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; +import 'package:get/get.dart'; +import 'package:hive/hive.dart'; +import 'package:pilipala/http/live.dart'; +import 'package:pilipala/models/live/follow.dart'; +import 'package:pilipala/utils/storage.dart'; + +class LiveFollowController extends GetxController { + RxInt crossAxisCount = 2.obs; + Box setting = GStrorage.setting; + int _currentPage = 1; + RxInt liveFollowingCount = 0.obs; + RxList liveFollowingList = + [].obs; + + @override + void onInit() { + super.onInit(); + crossAxisCount.value = + setting.get(SettingBoxKey.customRows, defaultValue: 2); + } + + Future queryLiveFollowList(type) async { + var res = await LiveHttp.liveFollowing( + pn: _currentPage, + ps: 20, + ); + if (res['status']) { + if (type == 'init') { + liveFollowingList.value = res['data'].list; + liveFollowingCount.value = res['data'].liveCount; + } else if (type == 'onLoad') { + liveFollowingList.addAll(res['data'].list); + } + _currentPage += 1; + } else { + SmartDialog.showToast(res['msg']); + } + return res; + } + + Future onRefresh() async { + _currentPage = 1; + await queryLiveFollowList('init'); + } + + void onLoad() async { + queryLiveFollowList('onLoad'); + } +} diff --git a/lib/pages/live_follow/index.dart b/lib/pages/live_follow/index.dart new file mode 100644 index 00000000..6bba50bc --- /dev/null +++ b/lib/pages/live_follow/index.dart @@ -0,0 +1,4 @@ +library live_follow; + +export 'view.dart'; +export 'controller.dart'; diff --git a/lib/pages/live_follow/view.dart b/lib/pages/live_follow/view.dart new file mode 100644 index 00000000..2b116991 --- /dev/null +++ b/lib/pages/live_follow/view.dart @@ -0,0 +1,136 @@ +import 'package:easy_debounce/easy_throttle.dart'; +import 'package:flutter/material.dart'; +import 'package:get/get.dart'; +import 'package:pilipala/common/constants.dart'; +import 'package:pilipala/common/skeleton/video_card_v.dart'; +import 'package:pilipala/common/widgets/http_error.dart'; +import 'package:pilipala/pages/live/widgets/live_item.dart'; + +import 'controller.dart'; + +class LiveFollowPage extends StatefulWidget { + const LiveFollowPage({super.key}); + + @override + State createState() => _LiveFollowPageState(); +} + +class _LiveFollowPageState extends State { + late Future _futureBuilderFuture; + final ScrollController scrollController = ScrollController(); + final LiveFollowController _liveFollowController = + Get.put(LiveFollowController()); + + @override + void initState() { + super.initState(); + _futureBuilderFuture = _liveFollowController.queryLiveFollowList('init'); + scrollController.addListener( + () { + if (scrollController.position.pixels >= + scrollController.position.maxScrollExtent - 200) { + EasyThrottle.throttle( + 'liveFollowList', const Duration(milliseconds: 200), () { + _liveFollowController.onLoad(); + }); + } + }, + ); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + elevation: 0, + scrolledUnderElevation: 0, + titleSpacing: 0, + centerTitle: false, + title: Obx(() => Text( + '${_liveFollowController.liveFollowingCount}人正在直播中', + style: Theme.of(context).textTheme.titleMedium, + )), + ), + body: Container( + clipBehavior: Clip.hardEdge, + margin: const EdgeInsets.only( + left: StyleString.safeSpace, right: StyleString.safeSpace), + decoration: const BoxDecoration( + borderRadius: BorderRadius.all(StyleString.imgRadius), + ), + child: RefreshIndicator( + onRefresh: () async { + return await _liveFollowController.onRefresh(); + }, + child: CustomScrollView( + controller: scrollController, + slivers: [ + SliverPadding( + padding: + const EdgeInsets.fromLTRB(0, StyleString.safeSpace, 0, 0), + sliver: FutureBuilder( + future: _futureBuilderFuture, + builder: (context, snapshot) { + if (snapshot.connectionState == ConnectionState.done) { + if (snapshot.data == null) { + return const SliverToBoxAdapter(child: SizedBox()); + } + Map data = snapshot.data as Map; + if (data['status']) { + return SliverLayoutBuilder( + builder: (context, boxConstraints) { + return Obx( + () => contentGrid(_liveFollowController, + _liveFollowController.liveFollowingList), + ); + }); + } else { + return HttpError( + errMsg: data['msg'], + fn: () { + setState(() { + _futureBuilderFuture = _liveFollowController + .queryLiveFollowList('init'); + }); + }, + ); + } + } else { + return contentGrid(_liveFollowController, []); + } + }, + ), + ), + ], + ), + ), + )); + } + + Widget contentGrid(ctr, liveList) { + int crossAxisCount = ctr.crossAxisCount.value; + return SliverGrid( + gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( + mainAxisSpacing: StyleString.safeSpace, + crossAxisSpacing: StyleString.safeSpace, + crossAxisCount: crossAxisCount, + mainAxisExtent: + Get.size.width / crossAxisCount / StyleString.aspectRatio + + MediaQuery.textScalerOf(context).scale( + (crossAxisCount == 1 ? 48 : 68), + ), + ), + delegate: SliverChildBuilderDelegate( + (BuildContext context, int index) { + return liveList!.isNotEmpty + ? LiveCardV( + liveItem: liveList[index], + crossAxisCount: crossAxisCount, + ) + : const VideoCardVSkeleton(); + }, + childCount: liveList!.isNotEmpty ? liveList!.length : 10, + ), + ); + } +} diff --git a/lib/router/app_pages.dart b/lib/router/app_pages.dart index 7a14b499..2d581293 100644 --- a/lib/router/app_pages.dart +++ b/lib/router/app_pages.dart @@ -5,6 +5,7 @@ import 'package:get/get.dart'; import 'package:hive/hive.dart'; import 'package:pilipala/pages/fav_edit/index.dart'; import 'package:pilipala/pages/follow_search/view.dart'; +import 'package:pilipala/pages/live_follow/index.dart'; import 'package:pilipala/pages/member_article/index.dart'; import 'package:pilipala/pages/message/at/index.dart'; import 'package:pilipala/pages/message/like/index.dart'; @@ -199,6 +200,8 @@ class Routes { name: '/memberArticle', page: () => const MemberArticlePage()), // 用户信息编辑 CustomGetPage(name: '/mineEdit', page: () => const MineEditPage()), + // 关注的直播up + CustomGetPage(name: '/liveFollowing', page: () => const LiveFollowPage()), ]; }