diff --git a/lib/http/api.dart b/lib/http/api.dart index 1c3fcf1f3..363b644b3 100644 --- a/lib/http/api.dart +++ b/lib/http/api.dart @@ -737,10 +737,6 @@ class Api { /// 稍后再看&收藏夹视频列表 static const String mediaList = '/x/v2/medialist/resource/list'; - /// 我的关注 - 正在直播 - static const String getFollowingLive = - '${HttpString.liveBaseUrl}/xlive/web-ucenter/user/following'; - static const String pgcIndexCondition = '/pgc/season/index/condition'; static const String pgcIndexResult = '/pgc/season/index/result'; @@ -800,4 +796,10 @@ class Api { static const String voteInfo = '/x/vote/vote_info'; static const String doVote = '/x/vote/do_vote'; + + static const String liveFeedIndex = + '${HttpString.liveBaseUrl}/xlive/app-interface/v2/index/feed'; + + static const String liveFollow = + '${HttpString.liveBaseUrl}/xlive/web-ucenter/user/following'; } diff --git a/lib/http/live.dart b/lib/http/live.dart index 89e60daa3..096e27cd3 100644 --- a/lib/http/live.dart +++ b/lib/http/live.dart @@ -2,14 +2,16 @@ import 'package:PiliPlus/common/constants.dart'; import 'package:PiliPlus/http/api.dart'; import 'package:PiliPlus/http/init.dart'; import 'package:PiliPlus/http/loading_state.dart'; -import 'package:PiliPlus/models/live/danmu_info.dart'; -import 'package:PiliPlus/models/live/follow.dart'; -import 'package:PiliPlus/models/live/item.dart'; import 'package:PiliPlus/models/live/live_emoticons/data.dart'; import 'package:PiliPlus/models/live/live_emoticons/datum.dart'; -import 'package:PiliPlus/models/live/room_info.dart'; -import 'package:PiliPlus/models/live/room_info_h5.dart'; +import 'package:PiliPlus/models/live/live_feed_index/data.dart'; +import 'package:PiliPlus/models/live/live_follow/data.dart'; +import 'package:PiliPlus/models/live/live_room/danmu_info.dart'; +import 'package:PiliPlus/models/live/live_room/item.dart'; +import 'package:PiliPlus/models/live/live_room/room_info.dart'; +import 'package:PiliPlus/models/live/live_room/room_info_h5.dart'; import 'package:PiliPlus/utils/storage.dart'; +import 'package:PiliPlus/utils/utils.dart'; import 'package:PiliPlus/utils/wbi_sign.dart'; import 'package:dio/dio.dart'; @@ -150,31 +152,6 @@ class LiveHttp { } } - // 我的关注 正在直播 - static Future liveFollowing({required int pn, required int ps}) async { - var res = await Request().get( - Api.getFollowingLive, - queryParameters: { - 'page': pn, - 'page_size': ps, - 'platform': 'web', - 'ignoreRecord': 1, - 'hit_ab': true, - }, - ); - if (res.data['code'] == 0) { - return { - 'status': true, - 'data': LiveFollowingModel.fromJson(res.data['data']) - }; - } else { - return { - 'status': false, - 'msg': res.data['message'], - }; - } - } - static Future?>> getLiveEmoticons( {required int roomId}) async { var res = await Request().get( @@ -191,4 +168,65 @@ class LiveHttp { return LoadingState.error(res.data['message']); } } + + static Future> liveFeedIndex({ + required int pn, + required bool isLogin, + }) async { + final params = { + if (isLogin) 'access_key': Accounts.main.accessKey, + 'appkey': Constants.appKey, + 'actionKey': 'appkey', + 'build': '8350200', + 'c_locale': 'zh_CN', + 'device': 'pad', + 'device_name': 'vivo', + 'device_type': '0', + 'fnval': '912', + 'disable_rcmd': '0', + 'https_url_req': '1', + 'login_event': isLogin ? '1' : '0', + 'mobi_app': 'android_hd', + 'module_select': '0', + 'network': 'wifi', + 'page': pn.toString(), + 'platform': 'android', + if (isLogin) 'relation_page': '1', + 's_locale': 'zh_CN', + 'scale': '2', + 'statistics': Constants.statistics, + 'ts': (DateTime.now().millisecondsSinceEpoch ~/ 1000).toString(), + }; + Utils.appSign( + params, + Constants.appKey, + Constants.appSec, + ); + var res = await Request().get( + Api.liveFeedIndex, + queryParameters: params, + ); + if (res.data['code'] == 0) { + return LoadingState.success(LiveIndexData.fromJson(res.data['data'])); + } else { + return LoadingState.error(res.data['message']); + } + } + + static Future> liveFollow(int page) async { + var res = await Request().get( + Api.liveFollow, + queryParameters: { + 'page': page, + 'page_size': 9, + 'ignoreRecord': 1, + 'hit_ab': true, + }, + ); + if (res.data['code'] == 0) { + return LoadingState.success(LiveFollowData.fromJson(res.data['data'])); + } else { + return LoadingState.error(res.data['message']); + } + } } diff --git a/lib/models/live/follow.dart b/lib/models/live/follow.dart deleted file mode 100644 index 83454fe06..000000000 --- a/lib/models/live/follow.dart +++ /dev/null @@ -1,118 +0,0 @@ -class LiveFollowingModel { - int? count; - List? list; - int? liveCount; - int? neverLivedCount; - List? neverLivedFaces; - int? pageSize; - String? title; - int? totalPage; - - LiveFollowingModel({ - this.count, - this.list, - this.liveCount, - this.neverLivedCount, - this.neverLivedFaces, - this.pageSize, - this.title, - this.totalPage, - }); - - LiveFollowingModel.fromJson(Map json) { - count = json['count']; - list = (json['list'] as List?) - ?.map((item) => LiveFollowingItemModel.fromJson(item)) - .toList(); - liveCount = json['live_count']; - neverLivedCount = json['never_lived_count']; - neverLivedFaces = json['never_lived_faces']; - pageSize = json['pageSize']; - title = json['title']; - totalPage = json['totalPage']; - } -} - -class LiveFollowingItemModel { - int? roomId; - int? uid; - String? uname; - String? title; - String? face; - int? liveStatus; - int? recordNum; - String? recentRecordId; - int? isAttention; - int? clipNum; - int? fansNum; - String? areaName; - String? areaValue; - String? tags; - String? recentRecordIdV2; - int? recordNumV2; - int? recordLiveTime; - String? areaNameV2; - String? roomNews; - String? watchIcon; - String? textSmall; - String? roomCover; - String? pic; - int? parentAreaId; - int? areaId; - - LiveFollowingItemModel({ - this.roomId, - this.uid, - this.uname, - this.title, - this.face, - this.liveStatus, - this.recordNum, - this.recentRecordId, - this.isAttention, - this.clipNum, - this.fansNum, - this.areaName, - this.areaValue, - this.tags, - this.recentRecordIdV2, - this.recordNumV2, - this.recordLiveTime, - this.areaNameV2, - this.roomNews, - this.watchIcon, - this.textSmall, - this.roomCover, - this.pic, - this.parentAreaId, - this.areaId, - }); - - LiveFollowingItemModel.fromJson(Map json) { - roomId = json['roomid']; - uid = json['uid']; - uname = json['uname']; - title = json['title']; - face = json['face']; - liveStatus = json['live_status']; - recordNum = json['record_num']; - recentRecordId = json['recent_record_id']; - isAttention = json['is_attention']; - clipNum = json['clipnum']; - fansNum = json['fans_num']; - areaName = json['area_name']; - areaValue = json['area_value']; - tags = json['tags']; - recentRecordIdV2 = json['recent_record_id_v2']; - recordNumV2 = json['record_num_v2']; - recordLiveTime = json['record_live_time']; - areaNameV2 = json['area_name_v2']; - roomNews = json['room_news']; - watchIcon = json['watch_icon']; - textSmall = json['text_small']; - roomCover = json['room_cover']; - pic = json['room_cover']; - parentAreaId = json['parent_area_id']; - areaId = json['area_id']; - } -} diff --git a/lib/models/live/live_feed_index/avatar.dart b/lib/models/live/live_feed_index/avatar.dart new file mode 100644 index 000000000..5edb7c89c --- /dev/null +++ b/lib/models/live/live_feed_index/avatar.dart @@ -0,0 +1,25 @@ +class Avatar { + String? cover; + String? event; + String? text; + int? uid; + String? url; + + Avatar({this.cover, this.event, this.text, this.uid, this.url}); + + factory Avatar.fromJson(Map json) => Avatar( + cover: json['cover'] as String?, + event: json['event'] as String?, + text: json['text'] as String?, + uid: json['uid'] as int?, + url: json['url'] as String?, + ); + + Map toJson() => { + 'cover': cover, + 'event': event, + 'text': text, + 'uid': uid, + 'url': url, + }; +} diff --git a/lib/models/live/live_feed_index/calendar_button.dart b/lib/models/live/live_feed_index/calendar_button.dart new file mode 100644 index 000000000..585ea2c87 --- /dev/null +++ b/lib/models/live/live_feed_index/calendar_button.dart @@ -0,0 +1,20 @@ +class CalendarButton { + String? text; + String? link; + + CalendarButton({this.text, this.link}); + + factory CalendarButton.fromJson(Map json) { + return CalendarButton( + text: json['text'] as String?, + link: json['link'] as String?, + ); + } + + + + Map toJson() => { + 'text': text, + 'link': link, + }; +} diff --git a/lib/models/live/live_feed_index/card_data.dart b/lib/models/live/live_feed_index/card_data.dart new file mode 100644 index 000000000..1ff86f29f --- /dev/null +++ b/lib/models/live/live_feed_index/card_data.dart @@ -0,0 +1,32 @@ +import 'package:PiliPlus/models/live/live_feed_index/card_data_list_item.dart'; + +import 'card_data_item.dart'; + +class CardData { + CardDataItem? bannerV2; + CardDataItem? myIdolV1; + CardLiveItem? smallCardV1; + + CardData({ + this.bannerV2, + this.myIdolV1, + this.smallCardV1, + }); + + factory CardData.fromJson(Map json) => CardData( + bannerV2: json['banner_v2'] == null + ? null + : CardDataItem.fromJson(json['banner_v2'] as Map), + myIdolV1: json['my_idol_v1'] == null + ? null + : CardDataItem.fromJson(json['my_idol_v1'] as Map), + smallCardV1: json['small_card_v1'] == null + ? null + : CardLiveItem.fromJson( + json['small_card_v1'] as Map), + ); + + Map toJson() => { + 'banner_v2': bannerV2?.toJson(), + }; +} diff --git a/lib/models/live/live_feed_index/card_data_item.dart b/lib/models/live/live_feed_index/card_data_item.dart new file mode 100644 index 000000000..3c586894e --- /dev/null +++ b/lib/models/live/live_feed_index/card_data_item.dart @@ -0,0 +1,44 @@ +import 'package:PiliPlus/models/live/live_feed_index/card_data_list_item.dart'; + +import 'module_info.dart'; + +class CardDataItem { + ModuleInfo? moduleInfo; + List? list; + dynamic topView; + ExtraInfo? extraInfo; + + CardDataItem({ + this.moduleInfo, + this.list, + this.topView, + this.extraInfo, + }); + + factory CardDataItem.fromJson(Map json) => CardDataItem( + moduleInfo: json['module_info'] == null + ? null + : ModuleInfo.fromJson(json['module_info'] as Map), + list: (json['list'] as List?) + ?.map((e) => CardLiveItem.fromJson(e as Map)) + .toList(), + topView: json['top_view'] as dynamic, + extraInfo: json['extra_info'] == null + ? null + : ExtraInfo.fromJson(json['extra_info'] as Map), + ); + + Map toJson() => { + 'module_info': moduleInfo?.toJson(), + 'list': list?.map((e) => e.toJson()).toList(), + 'top_view': topView, + }; +} + +class ExtraInfo { + int? totalCount; + + ExtraInfo.fromJson(Map json) { + totalCount = json['total_count']; + } +} diff --git a/lib/models/live/live_feed_index/card_data_list_item.dart b/lib/models/live/live_feed_index/card_data_list_item.dart new file mode 100644 index 000000000..2a387f7f1 --- /dev/null +++ b/lib/models/live/live_feed_index/card_data_list_item.dart @@ -0,0 +1,187 @@ +import 'quality_description.dart'; +import 'watched_show.dart'; + +class CardLiveItem { + int? id; + int? roomid; + int? uid; + String? uname; + String? face; + String? cover; + String? title; + int? area; + int? liveTime; + String? areaName; + int? areaV2Id; + String? areaV2Name; + String? areaV2ParentName; + int? areaV2ParentId; + String? liveTagName; + int? online; + String? playUrl; + String? playUrlH265; + List? acceptQuality; + int? currentQuality; + int? pkId; + String? link; + int? specialAttention; + int? broadcastType; + String? pendentRu; + String? pendentRuColor; + String? pendentRuPic; + int? officialVerify; + int? currentQn; + List? qualityDescription; + String? playUrlCard; + int? flag; + List? pendentList; + int? p2pType; + String? sessionId; + int? groupId; + WatchedShow? watchedShow; + int? isNft; + String? nftDmark; + String? statusText; + String? darkFace; + String? trackid; + + CardLiveItem({ + this.id, + this.roomid, + this.uid, + this.uname, + this.face, + this.cover, + this.title, + this.area, + this.liveTime, + this.areaName, + this.areaV2Id, + this.areaV2Name, + this.areaV2ParentName, + this.areaV2ParentId, + this.liveTagName, + this.online, + this.playUrl, + this.playUrlH265, + this.acceptQuality, + this.currentQuality, + this.pkId, + this.link, + this.specialAttention, + this.broadcastType, + this.pendentRu, + this.pendentRuColor, + this.pendentRuPic, + this.officialVerify, + this.currentQn, + this.qualityDescription, + this.playUrlCard, + this.flag, + this.pendentList, + this.p2pType, + this.sessionId, + this.groupId, + this.watchedShow, + this.isNft, + this.nftDmark, + this.statusText, + this.darkFace, + this.trackid, + }); + + factory CardLiveItem.fromJson(Map json) => CardLiveItem( + id: json['id'] as int?, + roomid: json['roomid'] as int?, + uid: json['uid'] as int?, + uname: json['uname'] as String?, + face: json['face'] as String?, + cover: json['cover'] as String?, + title: json['title'] as String?, + area: json['area'] as int?, + liveTime: json['live_time'] as int?, + areaName: json['area_name'] as String?, + areaV2Id: json['area_v2_id'] as int?, + areaV2Name: json['area_v2_name'] as String?, + areaV2ParentName: json['area_v2_parent_name'] as String?, + areaV2ParentId: json['area_v2_parent_id'] as int?, + liveTagName: json['live_tag_name'] as String?, + online: json['online'] as int?, + playUrl: json['play_url'] as String?, + playUrlH265: json['play_url_h265'] as String?, + acceptQuality: json['accept_quality'], + currentQuality: json['current_quality'] as int?, + pkId: json['pk_id'] as int?, + link: json['link'] as String?, + specialAttention: json['special_attention'] as int?, + broadcastType: json['broadcast_type'] as int?, + pendentRu: json['pendent_ru'] as String?, + pendentRuColor: json['pendent_ru_color'] as String?, + pendentRuPic: json['pendent_ru_pic'] as String?, + officialVerify: json['official_verify'] as int?, + currentQn: json['current_qn'] as int?, + qualityDescription: (json['quality_description'] as List?) + ?.map((e) => QualityDescription.fromJson(e as Map)) + .toList(), + playUrlCard: json['play_url_card'] as String?, + flag: json['flag'] as int?, + pendentList: json['pendent_list'] as List?, + p2pType: json['p2p_type'] as int?, + sessionId: json['session_id'] as String?, + groupId: json['group_id'] as int?, + watchedShow: json['watched_show'] == null + ? null + : WatchedShow.fromJson( + json['watched_show'] as Map), + isNft: json['is_nft'] as int?, + nftDmark: json['nft_dmark'] as String?, + statusText: json['status_text'] as String?, + darkFace: json['dark_face'] as String?, + trackid: json['trackid'] as String?, + ); + + Map toJson() => { + 'roomid': roomid, + 'uid': uid, + 'uname': uname, + 'face': face, + 'cover': cover, + 'title': title, + 'area': area, + 'live_time': liveTime, + 'area_name': areaName, + 'area_v2_id': areaV2Id, + 'area_v2_name': areaV2Name, + 'area_v2_parent_name': areaV2ParentName, + 'area_v2_parent_id': areaV2ParentId, + 'live_tag_name': liveTagName, + 'online': online, + 'play_url': playUrl, + 'play_url_h265': playUrlH265, + 'accept_quality': acceptQuality, + 'current_quality': currentQuality, + 'pk_id': pkId, + 'link': link, + 'special_attention': specialAttention, + 'broadcast_type': broadcastType, + 'pendent_ru': pendentRu, + 'pendent_ru_color': pendentRuColor, + 'pendent_ru_pic': pendentRuPic, + 'official_verify': officialVerify, + 'current_qn': currentQn, + 'quality_description': + qualityDescription?.map((e) => e.toJson()).toList(), + 'play_url_card': playUrlCard, + 'flag': flag, + 'pendent_list': pendentList, + 'p2p_type': p2pType, + 'session_id': sessionId, + 'group_id': groupId, + 'watched_show': watchedShow?.toJson(), + 'is_nft': isNft, + 'nft_dmark': nftDmark, + 'status_text': statusText, + 'dark_face': darkFace, + 'trackid': trackid, + }; +} diff --git a/lib/models/live/live_feed_index/card_list.dart b/lib/models/live/live_feed_index/card_list.dart new file mode 100644 index 000000000..1774a7e9d --- /dev/null +++ b/lib/models/live/live_feed_index/card_list.dart @@ -0,0 +1,20 @@ +import 'card_data.dart'; + +class LiveCardList { + String? cardType; + CardData? cardData; + + LiveCardList({this.cardType, this.cardData}); + + factory LiveCardList.fromJson(Map json) => LiveCardList( + cardType: json['card_type'] as String?, + cardData: json['card_data'] == null + ? null + : CardData.fromJson(json['card_data'] as Map), + ); + + Map toJson() => { + 'card_type': cardType, + 'card_data': cardData?.toJson(), + }; +} diff --git a/lib/models/live/live_feed_index/data.dart b/lib/models/live/live_feed_index/data.dart new file mode 100644 index 000000000..9639f3d05 --- /dev/null +++ b/lib/models/live/live_feed_index/data.dart @@ -0,0 +1,44 @@ +import 'card_list.dart'; + +class LiveIndexData { + List? cardList; + int? isRollback; + int? hasMore; + int? triggerTime; + int? isNeedRefresh; + + LiveIndexData({ + this.cardList, + this.isRollback, + this.hasMore, + this.triggerTime, + this.isNeedRefresh, + }); + + LiveIndexData.fromJson(Map json) { + if ((json['card_list'] as List?)?.isNotEmpty == true) { + cardList = []; + // banner_v2 + // my_idol_v1 + // area_entrance_v3 + // small_card_v1 + for (var json in json['card_list']) { + if (const ['my_idol_v1', 'small_card_v1'].contains(json['card_type'])) { + cardList!.add(LiveCardList.fromJson(json)); + } + } + } + isRollback = json['is_rollback'] as int?; + hasMore = json['has_more'] as int?; + triggerTime = json['trigger_time'] as int?; + isNeedRefresh = json['is_need_refresh'] as int?; + } + + Map toJson() => { + 'card_list': cardList?.map((e) => e.toJson()).toList(), + 'is_rollback': isRollback, + 'has_more': hasMore, + 'trigger_time': triggerTime, + 'is_need_refresh': isNeedRefresh, + }; +} diff --git a/lib/models/live/live_feed_index/in_live.dart b/lib/models/live/live_feed_index/in_live.dart new file mode 100644 index 000000000..5a66d98af --- /dev/null +++ b/lib/models/live/live_feed_index/in_live.dart @@ -0,0 +1,43 @@ +class InLive { + int? alphaLight; + int? alphaNight; + String? animationUrl; + String? animationUrlHash; + String? backgroundColorLight; + String? backgroundColorNight; + String? fontColor; + String? text; + + InLive({ + this.alphaLight, + this.alphaNight, + this.animationUrl, + this.animationUrlHash, + this.backgroundColorLight, + this.backgroundColorNight, + this.fontColor, + this.text, + }); + + factory InLive.fromJson(Map json) => InLive( + alphaLight: json['alpha_light'] as int?, + alphaNight: json['alpha_night'] as int?, + animationUrl: json['animation_url'] as String?, + animationUrlHash: json['animation_url_hash'] as String?, + backgroundColorLight: json['background_color_light'] as String?, + backgroundColorNight: json['background_color_night'] as String?, + fontColor: json['font_color'] as String?, + text: json['text'] as String?, + ); + + Map toJson() => { + 'alpha_light': alphaLight, + 'alpha_night': alphaNight, + 'animation_url': animationUrl, + 'animation_url_hash': animationUrlHash, + 'background_color_light': backgroundColorLight, + 'background_color_night': backgroundColorNight, + 'font_color': fontColor, + 'text': text, + }; +} diff --git a/lib/models/live/live_feed_index/inline_live.dart b/lib/models/live/live_feed_index/inline_live.dart new file mode 100644 index 000000000..7c3b88590 --- /dev/null +++ b/lib/models/live/live_feed_index/inline_live.dart @@ -0,0 +1,76 @@ +import 'avatar.dart'; +import 'calendar_button.dart'; +import 'player_args.dart'; +import 'right_top_live_badge.dart'; + +class InlineLive { + Avatar? avatar; + String? cover; + int? inlineStartDelayTime; + int? inlineSustainDuration; + String? link; + PlayerArgs? playerArgs; + dynamic rankListInfo; + RightTopLiveBadge? rightTopLiveBadge; + String? title; + dynamic topViewInfo; + String? upName; + String? inlinePlayUrl; + CalendarButton? calendarButton; + + InlineLive({ + this.avatar, + this.cover, + this.inlineStartDelayTime, + this.inlineSustainDuration, + this.link, + this.playerArgs, + this.rankListInfo, + this.rightTopLiveBadge, + this.title, + this.topViewInfo, + this.upName, + this.inlinePlayUrl, + this.calendarButton, + }); + + factory InlineLive.fromJson(Map json) => InlineLive( + avatar: json['avatar'] == null + ? null + : Avatar.fromJson(json['avatar'] as Map), + cover: json['cover'] as String?, + inlineStartDelayTime: json['inline_start_delay_time'] as int?, + inlineSustainDuration: json['inline_sustain_duration'] as int?, + link: json['link'] as String?, + playerArgs: json['player_args'] == null + ? null + : PlayerArgs.fromJson(json['player_args'] as Map), + rankListInfo: json['rank_list_info'] as dynamic, + rightTopLiveBadge: json['right_top_live_badge'] == null + ? null + : RightTopLiveBadge.fromJson(json['right_top_live_badge'] as Map), + title: json['title'] as String?, + topViewInfo: json['top_view_info'] as dynamic, + upName: json['up_name'] as String?, + inlinePlayUrl: json['inline_play_url'] as String?, + calendarButton: json['calendar_button'] == null + ? null + : CalendarButton.fromJson(json['calendar_button'] as Map), + ); + + Map toJson() => { + 'avatar': avatar?.toJson(), + 'cover': cover, + 'inline_start_delay_time': inlineStartDelayTime, + 'inline_sustain_duration': inlineSustainDuration, + 'link': link, + 'player_args': playerArgs?.toJson(), + 'rank_list_info': rankListInfo, + 'right_top_live_badge': rightTopLiveBadge?.toJson(), + 'title': title, + 'top_view_info': topViewInfo, + 'up_name': upName, + 'inline_play_url': inlinePlayUrl, + 'calendar_button': calendarButton?.toJson(), + }; +} diff --git a/lib/models/live/live_feed_index/live_feed_index.dart b/lib/models/live/live_feed_index/live_feed_index.dart new file mode 100644 index 000000000..7c5c8bb97 --- /dev/null +++ b/lib/models/live/live_feed_index/live_feed_index.dart @@ -0,0 +1,26 @@ +import 'data.dart'; + +class LiveFeedIndex { + int? code; + String? message; + int? ttl; + LiveIndexData? data; + + LiveFeedIndex({this.code, this.message, this.ttl, this.data}); + + factory LiveFeedIndex.fromJson(Map json) => LiveFeedIndex( + code: json['code'] as int?, + message: json['message'] as String?, + ttl: json['ttl'] as int?, + data: json['data'] == null + ? null + : LiveIndexData.fromJson(json['data'] as Map), + ); + + Map toJson() => { + 'code': code, + 'message': message, + 'ttl': ttl, + 'data': data?.toJson(), + }; +} diff --git a/lib/models/live/live_feed_index/module_info.dart b/lib/models/live/live_feed_index/module_info.dart new file mode 100644 index 000000000..fa151beda --- /dev/null +++ b/lib/models/live/live_feed_index/module_info.dart @@ -0,0 +1,39 @@ +class ModuleInfo { + int? id; + String? link; + String? pic; + String? title; + int? type; + int? sort; + int? count; + + ModuleInfo({ + this.id, + this.link, + this.pic, + this.title, + this.type, + this.sort, + this.count, + }); + + factory ModuleInfo.fromJson(Map json) => ModuleInfo( + id: json['id'] as int?, + link: json['link'] as String?, + pic: json['pic'] as String?, + title: json['title'] as String?, + type: json['type'] as int?, + sort: json['sort'] as int?, + count: json['count'] as int?, + ); + + Map toJson() => { + 'id': id, + 'link': link, + 'pic': pic, + 'title': title, + 'type': type, + 'sort': sort, + 'count': count, + }; +} diff --git a/lib/models/live/live_feed_index/player_args.dart b/lib/models/live/live_feed_index/player_args.dart new file mode 100644 index 000000000..a68f26c6f --- /dev/null +++ b/lib/models/live/live_feed_index/player_args.dart @@ -0,0 +1,39 @@ +class PlayerArgs { + int? canPlay; + bool? hideDanmuSwitch; + int? liveStatus; + int? parentAreaId; + int? areaId; + int? roomId; + int? upId; + + PlayerArgs({ + this.canPlay, + this.hideDanmuSwitch, + this.liveStatus, + this.parentAreaId, + this.areaId, + this.roomId, + this.upId, + }); + + factory PlayerArgs.fromJson(Map json) => PlayerArgs( + canPlay: json['can_play'] as int?, + hideDanmuSwitch: json['hide_danmu_switch'] as bool?, + liveStatus: json['live_status'] as int?, + parentAreaId: json['parent_area_id'] as int?, + areaId: json['area_id'] as int?, + roomId: json['room_id'] as int?, + upId: json['up_id'] as int?, + ); + + Map toJson() => { + 'can_play': canPlay, + 'hide_danmu_switch': hideDanmuSwitch, + 'live_status': liveStatus, + 'parent_area_id': parentAreaId, + 'area_id': areaId, + 'room_id': roomId, + 'up_id': upId, + }; +} diff --git a/lib/models/live/live_feed_index/quality_description.dart b/lib/models/live/live_feed_index/quality_description.dart new file mode 100644 index 000000000..5b3f1326a --- /dev/null +++ b/lib/models/live/live_feed_index/quality_description.dart @@ -0,0 +1,20 @@ +class QualityDescription { + int? qn; + String? desc; + + QualityDescription({this.qn, this.desc}); + + factory QualityDescription.fromJson(Map json) { + return QualityDescription( + qn: json['qn'] as int?, + desc: json['desc'] as String?, + ); + } + + + + Map toJson() => { + 'qn': qn, + 'desc': desc, + }; +} diff --git a/lib/models/live/live_feed_index/right_top_live_badge.dart b/lib/models/live/live_feed_index/right_top_live_badge.dart new file mode 100644 index 000000000..fd54f2cfb --- /dev/null +++ b/lib/models/live/live_feed_index/right_top_live_badge.dart @@ -0,0 +1,24 @@ +import 'in_live.dart'; + +class RightTopLiveBadge { + InLive? inLive; + int? liveStatus; + + RightTopLiveBadge({this.inLive, this.liveStatus}); + + factory RightTopLiveBadge.fromJson(Map json) { + return RightTopLiveBadge( + inLive: json['in_live'] == null + ? null + : InLive.fromJson(json['in_live'] as Map), + liveStatus: json['live_status'] as int?, + ); + } + + + + Map toJson() => { + 'in_live': inLive?.toJson(), + 'live_status': liveStatus, + }; +} diff --git a/lib/models/live/live_feed_index/watched_show.dart b/lib/models/live/live_feed_index/watched_show.dart new file mode 100644 index 000000000..1e635e7a4 --- /dev/null +++ b/lib/models/live/live_feed_index/watched_show.dart @@ -0,0 +1,39 @@ +class WatchedShow { + bool? sw1tch; + int? num; + String? textSmall; + String? textLarge; + String? icon; + int? iconLocation; + String? iconWeb; + + WatchedShow({ + this.sw1tch, + this.num, + this.textSmall, + this.textLarge, + this.icon, + this.iconLocation, + this.iconWeb, + }); + + factory WatchedShow.fromJson(Map json) => WatchedShow( + sw1tch: json['switch'] as bool?, + num: json['num'] as int?, + textSmall: json['text_small'] as String?, + textLarge: json['text_large'] as String?, + icon: json['icon'] as String?, + iconLocation: json['icon_location'] as int?, + iconWeb: json['icon_web'] as String?, + ); + + Map toJson() => { + 'switch': sw1tch, + 'num': num, + 'text_small': textSmall, + 'text_large': textLarge, + 'icon': icon, + 'icon_location': iconLocation, + 'icon_web': iconWeb, + }; +} diff --git a/lib/models/live/live_follow/data.dart b/lib/models/live/live_follow/data.dart new file mode 100644 index 000000000..678384f85 --- /dev/null +++ b/lib/models/live/live_follow/data.dart @@ -0,0 +1,52 @@ +import 'item.dart'; + +class LiveFollowData { + String? title; + int? pageSize; + int? totalPage; + List? list; + int? count; + int? neverLivedCount; + int? liveCount; + List? neverLivedFaces; + + LiveFollowData({ + this.title, + this.pageSize, + this.totalPage, + this.list, + this.count, + this.neverLivedCount, + this.liveCount, + this.neverLivedFaces, + }); + + LiveFollowData.fromJson(Map json) { + title = json['title'] as String?; + pageSize = json['pageSize'] as int?; + totalPage = json['totalPage'] as int?; + if ((json['list'] as List?)?.isNotEmpty == true) { + list = []; + for (var json in json['list']) { + if (json['live_status'] == 1) { + list!.add(LiveFollowItem.fromJson(json)); + } + } + } + count = json['count'] as int?; + neverLivedCount = json['never_lived_count'] as int?; + liveCount = json['live_count'] as int?; + neverLivedFaces = json['never_lived_faces'] as List?; + } + + Map toJson() => { + 'title': title, + 'pageSize': pageSize, + 'totalPage': totalPage, + 'list': list?.map((e) => e.toJson()).toList(), + 'count': count, + 'never_lived_count': neverLivedCount, + 'live_count': liveCount, + 'never_lived_faces': neverLivedFaces, + }; +} diff --git a/lib/models/live/live_follow/item.dart b/lib/models/live/live_follow/item.dart new file mode 100644 index 000000000..1d1b12e80 --- /dev/null +++ b/lib/models/live/live_follow/item.dart @@ -0,0 +1,111 @@ +class LiveFollowItem { + int? roomid; + int? uid; + String? uname; + String? title; + String? face; + int? liveStatus; + int? recordNum; + String? recentRecordId; + int? isAttention; + int? clipnum; + int? fansNum; + String? areaName; + String? areaValue; + String? tags; + String? recentRecordIdV2; + int? recordNumV2; + int? recordLiveTime; + String? areaNameV2; + String? roomNews; + bool? sw1tch; + String? watchIcon; + String? textSmall; + String? roomCover; + int? parentAreaId; + int? areaId; + + LiveFollowItem({ + this.roomid, + this.uid, + this.uname, + this.title, + this.face, + this.liveStatus, + this.recordNum, + this.recentRecordId, + this.isAttention, + this.clipnum, + this.fansNum, + this.areaName, + this.areaValue, + this.tags, + this.recentRecordIdV2, + this.recordNumV2, + this.recordLiveTime, + this.areaNameV2, + this.roomNews, + this.sw1tch, + this.watchIcon, + this.textSmall, + this.roomCover, + this.parentAreaId, + this.areaId, + }); + + factory LiveFollowItem.fromJson(Map json) => LiveFollowItem( + roomid: json['roomid'] as int?, + uid: json['uid'] as int?, + uname: json['uname'] as String?, + title: json['title'] as String?, + face: json['face'] as String?, + liveStatus: json['live_status'] as int?, + recordNum: json['record_num'] as int?, + recentRecordId: json['recent_record_id'] as String?, + isAttention: json['is_attention'] as int?, + clipnum: json['clipnum'] as int?, + fansNum: json['fans_num'] as int?, + areaName: json['area_name'] as String?, + areaValue: json['area_value'] as String?, + tags: json['tags'] as String?, + recentRecordIdV2: json['recent_record_id_v2'] as String?, + recordNumV2: json['record_num_v2'] as int?, + recordLiveTime: json['record_live_time'] as int?, + areaNameV2: json['area_name_v2'] as String?, + roomNews: json['room_news'] as String?, + sw1tch: json['switch'] as bool?, + watchIcon: json['watch_icon'] as String?, + textSmall: json['text_small'] as String?, + roomCover: json['room_cover'] as String?, + parentAreaId: json['parent_area_id'] as int?, + areaId: json['area_id'] as int?, + ); + + Map toJson() => { + 'roomid': roomid, + 'uid': uid, + 'uname': uname, + 'title': title, + 'face': face, + 'live_status': liveStatus, + 'record_num': recordNum, + 'recent_record_id': recentRecordId, + 'is_attention': isAttention, + 'clipnum': clipnum, + 'fans_num': fansNum, + 'area_name': areaName, + 'area_value': areaValue, + 'tags': tags, + 'recent_record_id_v2': recentRecordIdV2, + 'record_num_v2': recordNumV2, + 'record_live_time': recordLiveTime, + 'area_name_v2': areaNameV2, + 'room_news': roomNews, + 'switch': sw1tch, + 'watch_icon': watchIcon, + 'text_small': textSmall, + 'room_cover': roomCover, + 'parent_area_id': parentAreaId, + 'area_id': areaId, + }; +} diff --git a/lib/models/live/danmu_info.dart b/lib/models/live/live_room/danmu_info.dart similarity index 100% rename from lib/models/live/danmu_info.dart rename to lib/models/live/live_room/danmu_info.dart diff --git a/lib/models/live/item.dart b/lib/models/live/live_room/item.dart similarity index 100% rename from lib/models/live/item.dart rename to lib/models/live/live_room/item.dart diff --git a/lib/models/live/room_info.dart b/lib/models/live/live_room/room_info.dart similarity index 100% rename from lib/models/live/room_info.dart rename to lib/models/live/live_room/room_info.dart diff --git a/lib/models/live/room_info_h5.dart b/lib/models/live/live_room/room_info_h5.dart similarity index 100% rename from lib/models/live/room_info_h5.dart rename to lib/models/live/live_room/room_info_h5.dart diff --git a/lib/pages/live/controller.dart b/lib/pages/live/controller.dart index e82a3f0f8..2faf4f09f 100644 --- a/lib/pages/live/controller.dart +++ b/lib/pages/live/controller.dart @@ -1,109 +1,26 @@ import 'package:PiliPlus/http/live.dart'; import 'package:PiliPlus/http/loading_state.dart'; -import 'package:PiliPlus/models/live/follow.dart'; -import 'package:PiliPlus/models/live/item.dart'; +import 'package:PiliPlus/models/live/live_feed_index/card_list.dart'; +import 'package:PiliPlus/models/live/live_feed_index/data.dart'; import 'package:PiliPlus/pages/common/common_list_controller.dart'; -import 'package:PiliPlus/utils/extension.dart'; -import 'package:PiliPlus/utils/request_utils.dart'; import 'package:PiliPlus/utils/storage.dart'; import 'package:get/get_rx/src/rx_types/rx_types.dart'; -class LiveController - extends CommonListController?, LiveItemModel> { +class LiveController extends CommonListController { @override void onInit() { super.onInit(); queryData(); - if (isLogin.value) { - fetchLiveFollowing(); - } } - String? gaiaVtoken; + final RxBool isLogin = Accounts.main.isLogin.obs; @override - Future?>> customGetData() => - LiveHttp.liveList(pn: currentPage, gaiaVtoken: gaiaVtoken); - - @override - bool handleError(String? errMsg) { - if (errMsg?.startsWith('voucher') == true) { - loadingState.value = LoadingState.error(' -352 '); - RequestUtils.validate( - errMsg!, - (gaiaVtoken) { - this.gaiaVtoken = gaiaVtoken; - onReload(); - }, - ); - return true; - } - return false; + List? getDataList(LiveIndexData response) { + return response.cardList; } @override - Future onRefresh() { - fetchLiveFollowing(); - return super.onRefresh(); - } - - @override - Future onReload() { - if (loadingState.value is Error) { - String? errMsg = (loadingState.value as Error).errMsg; - if (errMsg == '-352') { - gaiaVtoken = null; - } - } - return super.onReload(); - } - - late RxBool isLogin = Accounts.main.isLogin.obs; - late Rx?>> followListState = - Rx(LoadingState.loading()); - late int followPage = 1; - late bool followEnd = false; - late RxInt liveCount = 0.obs; - - Future fetchLiveFollowing([bool isRefresh = true]) async { - if (!isLogin.value || (isRefresh.not && followEnd)) { - return; - } - if (isRefresh) { - followPage = 1; - followEnd = false; - } - dynamic res = await LiveHttp.liveFollowing(pn: followPage, ps: 20); - if (res['status']) { - LiveFollowingModel data = res['data']; - liveCount.value = data.liveCount ?? 0; - List? dataList = data.list - ?.where((LiveFollowingItemModel item) => - item.liveStatus == 1 && item.recordLiveTime == 0) - .toList(); - if (dataList.isNullOrEmpty) { - followEnd = true; - if (isRefresh) { - followListState.value = LoadingState.success(dataList); - } - return; - } - if (isRefresh) { - if (dataList!.length >= liveCount.value) { - followEnd = true; - } - followListState.value = LoadingState.success(dataList); - } else if (followListState.value is Success) { - List list = followListState.value.data! - ..addAll(dataList!); - if (list.length >= liveCount.value) { - followEnd = true; - } - followListState.refresh(); - } - followPage++; - } else if (isRefresh) { - followListState.value = LoadingState.error(res['msg']); - } - } + Future> customGetData() => + LiveHttp.liveFeedIndex(pn: currentPage, isLogin: isLogin.value); } diff --git a/lib/pages/live/view.dart b/lib/pages/live/view.dart index 9a9c6687f..afd5964f5 100644 --- a/lib/pages/live/view.dart +++ b/lib/pages/live/view.dart @@ -2,17 +2,17 @@ import 'package:PiliPlus/common/constants.dart'; import 'package:PiliPlus/common/skeleton/video_card_v.dart'; import 'package:PiliPlus/common/widgets/image/network_img_layer.dart'; import 'package:PiliPlus/common/widgets/loading_widget/http_error.dart'; -import 'package:PiliPlus/common/widgets/loading_widget/loading_widget.dart'; import 'package:PiliPlus/common/widgets/refresh_indicator.dart'; import 'package:PiliPlus/common/widgets/self_sized_horizontal_list.dart'; import 'package:PiliPlus/http/loading_state.dart'; -import 'package:PiliPlus/models/live/item.dart'; +import 'package:PiliPlus/models/live/live_feed_index/card_data_list_item.dart'; +import 'package:PiliPlus/models/live/live_feed_index/card_list.dart'; import 'package:PiliPlus/pages/common/common_page.dart'; import 'package:PiliPlus/pages/live/controller.dart'; -import 'package:PiliPlus/pages/live/widgets/live_item.dart'; -import 'package:PiliPlus/pages/live/widgets/live_item_follow.dart'; +import 'package:PiliPlus/pages/live/widgets/live_item_app.dart'; +import 'package:PiliPlus/pages/live_follow/view.dart'; +import 'package:PiliPlus/utils/extension.dart'; import 'package:PiliPlus/utils/grid.dart'; -import 'package:PiliPlus/utils/utils.dart'; import 'package:flutter/material.dart'; import 'package:get/get.dart'; @@ -49,18 +49,6 @@ class _LivePageState extends CommonPageState controller: controller.scrollController, physics: const AlwaysScrollableScrollPhysics(), slivers: [ - Obx( - () => controller.isLogin.value - ? SliverPadding( - padding: const EdgeInsets.symmetric( - vertical: StyleString.cardSpace, - ), - sliver: SliverToBoxAdapter( - child: _buildFollowList(), - ), - ) - : const SliverToBoxAdapter(), - ), SliverPadding( padding: EdgeInsets.only( top: StyleString.cardSpace, @@ -74,7 +62,7 @@ class _LivePageState extends CommonPageState ); } - Widget _buildBody(LoadingState?> loadingState) { + Widget _buildBody(LoadingState?> loadingState) { return switch (loadingState) { Loading() => SliverGrid( gridDelegate: SliverGridDelegateWithExtentAndRatio( @@ -92,25 +80,48 @@ class _LivePageState extends CommonPageState ), ), Success() => loadingState.response?.isNotEmpty == true - ? SliverGrid( - gridDelegate: SliverGridDelegateWithExtentAndRatio( - mainAxisSpacing: StyleString.cardSpace, - crossAxisSpacing: StyleString.cardSpace, - maxCrossAxisExtent: Grid.smallCardWidth, - childAspectRatio: StyleString.aspectRatio, - mainAxisExtent: MediaQuery.textScalerOf(context).scale(90), - ), - delegate: SliverChildBuilderDelegate( - (context, index) { - if (index == loadingState.response!.length - 1) { - controller.onLoadMore(); - } - return LiveCardV(liveItem: loadingState.response![index]); - }, - childCount: loadingState.response!.length, - ), - ) - : scrollErrorWidget(onReload: controller.onReload), + ? Builder(builder: (context) { + List list = loadingState.response!; + LiveCardList? followItem; + if (list.first.cardType == 'my_idol_v1') { + followItem = list.first; + list = list.sublist(1); + } + return SliverMainAxisGroup( + slivers: [ + if (followItem != null) + SliverPadding( + padding: const EdgeInsets.only(bottom: 12), + sliver: SliverToBoxAdapter( + child: _buildFollowList(followItem), + ), + ), + if (list.isNotEmpty) + SliverGrid( + gridDelegate: SliverGridDelegateWithExtentAndRatio( + mainAxisSpacing: StyleString.cardSpace, + crossAxisSpacing: StyleString.cardSpace, + maxCrossAxisExtent: Grid.smallCardWidth, + childAspectRatio: StyleString.aspectRatio, + mainAxisExtent: + MediaQuery.textScalerOf(context).scale(90), + ), + delegate: SliverChildBuilderDelegate( + (context, index) { + if (index == list.length - 1) { + controller.onLoadMore(); + } + return LiveCardVApp( + item: list[index].cardData!.smallCardV1!, + ); + }, + childCount: list.length, + ), + ), + ], + ); + }) + : HttpError(onReload: controller.onReload), Error() => HttpError( errMsg: loadingState.errMsg, onReload: controller.onReload, @@ -118,7 +129,7 @@ class _LivePageState extends CommonPageState }; } - Widget _buildFollowList() { + Widget _buildFollowList(LiveCardList item) { final theme = Theme.of(context); return Column( mainAxisSize: MainAxisSize.min, @@ -126,33 +137,32 @@ class _LivePageState extends CommonPageState children: [ Row( children: [ - Obx( - () => Text.rich( - TextSpan( - children: [ - const TextSpan(text: '我的关注 '), - TextSpan( - text: '${controller.liveCount.value}', - style: TextStyle( - fontSize: 13, - color: theme.colorScheme.primary, - ), + Text.rich( + TextSpan( + children: [ + const TextSpan(text: '我的关注 '), + TextSpan( + text: + '${item.cardData?.myIdolV1?.extraInfo?.totalCount ?? 0}', + style: TextStyle( + fontSize: 13, + color: theme.colorScheme.primary, ), - TextSpan( - text: '人正在直播', - style: TextStyle( - fontSize: 13, - color: theme.colorScheme.outline, - ), + ), + TextSpan( + text: '人正在直播', + style: TextStyle( + fontSize: 13, + color: theme.colorScheme.outline, ), - ], - ), + ), + ], ), ), const Spacer(), GestureDetector( onTap: () { - Get.to(_buildFollowListPage); + Get.to(const LiveFollowPage()); }, child: Row( mainAxisSize: MainAxisSize.min, @@ -173,161 +183,69 @@ class _LivePageState extends CommonPageState ), ], ), - Obx(() => _buildFollowBody(theme, controller.followListState.value)), + _buildFollowBody(theme, item.cardData?.myIdolV1?.list), ], ); } - Widget _buildFollowBody(ThemeData theme, LoadingState loadingState) { - return switch (loadingState) { - Loading() => SizedBox( - height: 80, - child: loadingWidget, - ), - Success() => (loadingState.response as List?)?.isNotEmpty == true - ? MediaQuery.removePadding( - context: context, - removeLeft: context.orientation == Orientation.landscape, - child: SelfSizedHorizontalList( - gapSize: 5, - childBuilder: (index) { - if (index == loadingState.response.length - 1) { - controller.fetchLiveFollowing(false); - } - return SizedBox( - width: 65, - child: GestureDetector( - onTap: () { - Get.toNamed( - '/liveRoom?roomid=${loadingState.response[index].roomId}', - ); - }, - onLongPress: () { - Feedback.forLongPress(context); - Get.toNamed( - '/member?mid=${loadingState.response[index].uid}', - arguments: { - 'face': loadingState.response[index].face, - 'heroTag': Utils.makeHeroTag( - loadingState.response[index].uid) - }, - ); - }, - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - const SizedBox(height: 8), - Container( - margin: const EdgeInsets.all(2), - padding: const EdgeInsets.all(2), - decoration: BoxDecoration( - border: Border.all( - width: 1.5, - color: theme.colorScheme.primary, - strokeAlign: BorderSide.strokeAlignOutside, - ), - shape: BoxShape.circle, - ), - child: NetworkImgLayer( - type: 'avatar', - width: 45, - height: 45, - src: loadingState.response[index].face, - ), - ), - const SizedBox(height: 4), - Text( - loadingState.response[index].uname, - maxLines: 1, - overflow: TextOverflow.ellipsis, - style: const TextStyle(fontSize: 12), - textAlign: TextAlign.center, - ), - ], - ), - ), - ); - }, - itemCount: loadingState.response.length, - ), - ) - : const SizedBox.shrink(), - Error() => GestureDetector( - onTap: () { - controller.fetchLiveFollowing(); - }, - child: Container( - height: 80, - alignment: Alignment.center, - padding: const EdgeInsets.symmetric(horizontal: 16), - child: Text( - loadingState.errMsg, - textAlign: TextAlign.center, - ), - ), - ), - }; - } - - Widget get _buildFollowListPage => Scaffold( - appBar: AppBar( - title: Obx( - () => Text('${controller.liveCount.value}人正在直播'), - ), - ), - body: SafeArea( - top: false, - bottom: false, - child: CustomScrollView( - slivers: [ - Obx(() => _buildFollowListBody(controller.followListState.value)), - ], - ), - ), - ); - - Widget _buildFollowListBody(LoadingState loadingState) { - return switch (loadingState) { - Success() || Loading() => SliverPadding( - padding: EdgeInsets.only( - top: StyleString.safeSpace, - left: StyleString.safeSpace, - right: StyleString.safeSpace, - bottom: MediaQuery.paddingOf(context).bottom + 80, - ), - sliver: SliverGrid( - gridDelegate: SliverGridDelegateWithExtentAndRatio( - mainAxisSpacing: StyleString.cardSpace, - crossAxisSpacing: StyleString.cardSpace, - maxCrossAxisExtent: Grid.smallCardWidth, - childAspectRatio: StyleString.aspectRatio, - mainAxisExtent: MediaQuery.textScalerOf(context).scale(90), - ), - delegate: SliverChildBuilderDelegate( - (BuildContext context, int index) { - if (loadingState is Success && - index == loadingState.response.length - 1) { - controller.fetchLiveFollowing(false); - } - return loadingState is Success - ? LiveCardVFollow( - liveItem: loadingState.response[index], - ) - : const VideoCardVSkeleton(); + Widget _buildFollowBody(ThemeData theme, List? list) { + if (list.isNullOrEmpty) { + return const SizedBox.shrink(); + } + return MediaQuery.removePadding( + context: context, + removeLeft: context.orientation == Orientation.landscape, + child: SelfSizedHorizontalList( + gapSize: 5, + childBuilder: (index) { + final item = list[index]; + return SizedBox( + width: 65, + child: GestureDetector( + onTap: () { + Get.toNamed('/liveRoom?roomid=${item.roomid}'); }, - childCount: - loadingState is Success ? loadingState.response.length : 10, + onLongPress: () { + Feedback.forLongPress(context); + Get.toNamed('/member?mid=${item.uid}'); + }, + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + const SizedBox(height: 8), + Container( + margin: const EdgeInsets.all(2), + padding: const EdgeInsets.all(2), + decoration: BoxDecoration( + border: Border.all( + width: 1.5, + color: theme.colorScheme.primary, + strokeAlign: BorderSide.strokeAlignOutside, + ), + shape: BoxShape.circle, + ), + child: NetworkImgLayer( + type: 'avatar', + width: 45, + height: 45, + src: item.face, + ), + ), + const SizedBox(height: 4), + Text( + item.uname!, + maxLines: 1, + overflow: TextOverflow.ellipsis, + style: const TextStyle(fontSize: 12), + textAlign: TextAlign.center, + ), + ], + ), ), - ), - ), - Error() => HttpError( - errMsg: loadingState.errMsg, - onReload: () { - controller - ..followListState.value = LoadingState.loading() - ..fetchLiveFollowing(); - }, - ), - }; + ); + }, + itemCount: list!.length, + ), + ); } } diff --git a/lib/pages/live/widgets/live_item_app.dart b/lib/pages/live/widgets/live_item_app.dart new file mode 100644 index 000000000..c1c1831b4 --- /dev/null +++ b/lib/pages/live/widgets/live_item_app.dart @@ -0,0 +1,143 @@ +import 'package:PiliPlus/common/constants.dart'; +import 'package:PiliPlus/common/widgets/image/image_save.dart'; +import 'package:PiliPlus/common/widgets/image/network_img_layer.dart'; +import 'package:PiliPlus/models/live/live_feed_index/card_data_list_item.dart'; +import 'package:PiliPlus/utils/utils.dart'; +import 'package:flutter/material.dart'; +import 'package:get/get.dart'; + +// 视频卡片 - 垂直布局 +class LiveCardVApp extends StatelessWidget { + final CardLiveItem item; + + const LiveCardVApp({ + super.key, + required this.item, + }); + + @override + Widget build(BuildContext context) { + String heroTag = Utils.makeHeroTag(item.id); + return Card( + clipBehavior: Clip.hardEdge, + margin: EdgeInsets.zero, + child: InkWell( + onTap: () { + Get.toNamed('/liveRoom?roomid=${item.id}'); + }, + onLongPress: () => imageSaveDialog( + title: item.title, + cover: item.cover, + ), + child: Column( + children: [ + AspectRatio( + aspectRatio: StyleString.aspectRatio, + child: LayoutBuilder(builder: (context, boxConstraints) { + double maxWidth = boxConstraints.maxWidth; + double maxHeight = boxConstraints.maxHeight; + return Stack( + clipBehavior: Clip.none, + children: [ + Hero( + tag: heroTag, + child: NetworkImgLayer( + src: item.cover!, + width: maxWidth, + height: maxHeight, + ), + ), + Positioned( + left: 0, + right: 0, + bottom: 0, + child: AnimatedOpacity( + opacity: 1, + duration: const Duration(milliseconds: 200), + child: videoStat(context), + ), + ), + ], + ); + }), + ), + liveContent(context) + ], + ), + ), + ); + } + + Widget liveContent(context) { + final theme = Theme.of(context); + return Expanded( + flex: 1, + child: Padding( + padding: const EdgeInsets.fromLTRB(5, 8, 5, 4), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + '${item.title}', + textAlign: TextAlign.start, + style: const TextStyle( + letterSpacing: 0.3, + ), + maxLines: 2, + overflow: TextOverflow.ellipsis, + ), + Row( + children: [ + Expanded( + child: Text( + '${item.uname}', + textAlign: TextAlign.start, + style: TextStyle( + fontSize: theme.textTheme.labelMedium!.fontSize, + color: theme.colorScheme.outline, + ), + maxLines: 1, + overflow: TextOverflow.ellipsis, + ), + ), + ], + ), + ], + ), + ), + ); + } + + Widget videoStat(context) { + return Container( + height: 50, + padding: const EdgeInsets.only(top: 26, left: 10, right: 10), + decoration: const BoxDecoration( + gradient: LinearGradient( + begin: Alignment.topCenter, + end: Alignment.bottomCenter, + colors: [ + Colors.transparent, + Colors.black54, + ], + tileMode: TileMode.mirror, + ), + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + '${item.areaName}', + style: const TextStyle(fontSize: 11, color: Colors.white), + ), + if (item.watchedShow?.textSmall != null) + Text( + '${Utils.numFormat(item.watchedShow!.textSmall)}围观', + style: const TextStyle(fontSize: 11, color: Colors.white), + ), + ], + ), + ); + } +} diff --git a/lib/pages/live_follow/controller.dart b/lib/pages/live_follow/controller.dart new file mode 100644 index 000000000..70c3460ff --- /dev/null +++ b/lib/pages/live_follow/controller.dart @@ -0,0 +1,34 @@ +import 'package:PiliPlus/http/live.dart'; +import 'package:PiliPlus/http/loading_state.dart'; +import 'package:PiliPlus/models/live/live_follow/data.dart'; +import 'package:PiliPlus/models/live/live_follow/item.dart'; +import 'package:PiliPlus/pages/common/common_list_controller.dart'; +import 'package:get/get.dart'; + +class LiveFollowController + extends CommonListController { + @override + void onInit() { + super.onInit(); + queryData(); + } + + Rx count = Rx(null); + + @override + void checkIsEnd(int length) { + if (count.value != null && length >= count.value!) { + isEnd = true; + } + } + + @override + List? getDataList(LiveFollowData response) { + count.value = response.liveCount; + return response.list; + } + + @override + Future> customGetData() => + LiveHttp.liveFollow(currentPage); +} diff --git a/lib/pages/live_follow/view.dart b/lib/pages/live_follow/view.dart new file mode 100644 index 000000000..403c2f46a --- /dev/null +++ b/lib/pages/live_follow/view.dart @@ -0,0 +1,102 @@ +import 'package:PiliPlus/common/constants.dart'; +import 'package:PiliPlus/common/skeleton/video_card_v.dart'; +import 'package:PiliPlus/common/widgets/loading_widget/http_error.dart'; +import 'package:PiliPlus/common/widgets/refresh_indicator.dart'; +import 'package:PiliPlus/http/loading_state.dart'; +import 'package:PiliPlus/models/live/live_follow/item.dart'; +import 'package:PiliPlus/pages/live_follow/controller.dart'; +import 'package:PiliPlus/pages/live_follow/widgets/live_item_follow.dart'; +import 'package:PiliPlus/utils/grid.dart'; +import 'package:flutter/material.dart'; +import 'package:get/get.dart'; + +class LiveFollowPage extends StatefulWidget { + const LiveFollowPage({super.key}); + + @override + State createState() => _LiveFollowPageState(); +} + +class _LiveFollowPageState extends State { + final _controller = Get.put(LiveFollowController()); + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: Obx( + () => Text(_controller.count.value != null + ? '${_controller.count.value}人正在直播' + : '关注直播'), + ), + ), + body: SafeArea( + top: false, + bottom: false, + child: refreshIndicator( + onRefresh: () async { + await _controller.onRefresh(); + }, + child: CustomScrollView( + slivers: [ + SliverPadding( + padding: EdgeInsets.only( + left: StyleString.safeSpace, + right: StyleString.safeSpace, + bottom: MediaQuery.paddingOf(context).bottom + 80, + ), + sliver: Obx(() => _buildBody(_controller.loadingState.value)), + ), + ], + ), + ), + ), + ); + } + + Widget _buildBody(LoadingState?> loadingState) { + return switch (loadingState) { + Loading() => SliverGrid( + gridDelegate: SliverGridDelegateWithExtentAndRatio( + mainAxisSpacing: StyleString.cardSpace, + crossAxisSpacing: StyleString.cardSpace, + maxCrossAxisExtent: Grid.smallCardWidth, + childAspectRatio: StyleString.aspectRatio, + mainAxisExtent: MediaQuery.textScalerOf(context).scale(90), + ), + delegate: SliverChildBuilderDelegate( + (context, index) { + return const VideoCardVSkeleton(); + }, + childCount: 10, + ), + ), + Success() => loadingState.response?.isNotEmpty == true + ? SliverGrid( + gridDelegate: SliverGridDelegateWithExtentAndRatio( + mainAxisSpacing: StyleString.cardSpace, + crossAxisSpacing: StyleString.cardSpace, + maxCrossAxisExtent: Grid.smallCardWidth, + childAspectRatio: StyleString.aspectRatio, + mainAxisExtent: MediaQuery.textScalerOf(context).scale(90), + ), + delegate: SliverChildBuilderDelegate( + (context, index) { + if (index == loadingState.response!.length - 1) { + _controller.onLoadMore(); + } + return LiveCardVFollow( + liveItem: loadingState.response![index], + ); + }, + childCount: loadingState.response!.length, + ), + ) + : HttpError(onReload: _controller.onReload), + Error() => HttpError( + errMsg: loadingState.errMsg, + onReload: _controller.onReload, + ), + }; + } +} diff --git a/lib/pages/live/widgets/live_item_follow.dart b/lib/pages/live_follow/widgets/live_item_follow.dart similarity index 95% rename from lib/pages/live/widgets/live_item_follow.dart rename to lib/pages/live_follow/widgets/live_item_follow.dart index e1e7f5529..d48778de5 100644 --- a/lib/pages/live/widgets/live_item_follow.dart +++ b/lib/pages/live_follow/widgets/live_item_follow.dart @@ -1,14 +1,14 @@ import 'package:PiliPlus/common/constants.dart'; import 'package:PiliPlus/common/widgets/image/image_save.dart'; import 'package:PiliPlus/common/widgets/image/network_img_layer.dart'; -import 'package:PiliPlus/models/live/follow.dart'; +import 'package:PiliPlus/models/live/live_follow/item.dart'; import 'package:PiliPlus/utils/utils.dart'; import 'package:flutter/material.dart'; import 'package:get/get.dart'; // 视频卡片 - 垂直布局 class LiveCardVFollow extends StatelessWidget { - final LiveFollowingItemModel liveItem; + final LiveFollowItem liveItem; const LiveCardVFollow({ super.key, @@ -17,13 +17,13 @@ class LiveCardVFollow extends StatelessWidget { @override Widget build(BuildContext context) { - String heroTag = Utils.makeHeroTag(liveItem.roomId); + String heroTag = Utils.makeHeroTag(liveItem.roomid); return Card( clipBehavior: Clip.hardEdge, margin: EdgeInsets.zero, child: InkWell( onTap: () { - Get.toNamed('/liveRoom?roomid=${liveItem.roomId}'); + Get.toNamed('/liveRoom?roomid=${liveItem.roomid}'); }, onLongPress: () => imageSaveDialog( title: liveItem.title, diff --git a/lib/utils/login_utils.dart b/lib/utils/login_utils.dart index d6d5cd9ae..03c49d302 100644 --- a/lib/utils/login_utils.dart +++ b/lib/utils/login_utils.dart @@ -85,9 +85,7 @@ class LoginUtils { } catch (_) {} try { - Get.find() - ..isLogin.value = true - ..fetchLiveFollowing(); + Get.find().isLogin.value = true; } catch (_) {} try { @@ -152,9 +150,7 @@ class LoginUtils { } catch (_) {} try { - Get.find() - ..isLogin.value = false - ..followListState.value = LoadingState.loading(); + Get.find().isLogin.value = false; } catch (_) {} for (int i = 0; i < tabsConfig.length; i++) {