refa: live page

Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
This commit is contained in:
bggRGjQaUbCoE
2025-05-05 17:03:49 +08:00
parent 7689fe8aa4
commit 562f9035e8
31 changed files with 1334 additions and 461 deletions

View File

@@ -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';
}

View File

@@ -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<LoadingState<List<LiveEmoteDatum>?>> getLiveEmoticons(
{required int roomId}) async {
var res = await Request().get(
@@ -191,4 +168,65 @@ class LiveHttp {
return LoadingState.error(res.data['message']);
}
}
static Future<LoadingState<LiveIndexData>> 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<LoadingState<LiveFollowData>> 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']);
}
}
}

View File

@@ -1,118 +0,0 @@
class LiveFollowingModel {
int? count;
List<LiveFollowingItemModel>? 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<String, dynamic> 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<String, dynamic> 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'];
}
}

View File

@@ -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<String, dynamic> 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<String, dynamic> toJson() => {
'cover': cover,
'event': event,
'text': text,
'uid': uid,
'url': url,
};
}

View File

@@ -0,0 +1,20 @@
class CalendarButton {
String? text;
String? link;
CalendarButton({this.text, this.link});
factory CalendarButton.fromJson(Map<String, dynamic> json) {
return CalendarButton(
text: json['text'] as String?,
link: json['link'] as String?,
);
}
Map<String, dynamic> toJson() => {
'text': text,
'link': link,
};
}

View File

@@ -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<String, dynamic> json) => CardData(
bannerV2: json['banner_v2'] == null
? null
: CardDataItem.fromJson(json['banner_v2'] as Map<String, dynamic>),
myIdolV1: json['my_idol_v1'] == null
? null
: CardDataItem.fromJson(json['my_idol_v1'] as Map<String, dynamic>),
smallCardV1: json['small_card_v1'] == null
? null
: CardLiveItem.fromJson(
json['small_card_v1'] as Map<String, dynamic>),
);
Map<String, dynamic> toJson() => {
'banner_v2': bannerV2?.toJson(),
};
}

View File

@@ -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<CardLiveItem>? list;
dynamic topView;
ExtraInfo? extraInfo;
CardDataItem({
this.moduleInfo,
this.list,
this.topView,
this.extraInfo,
});
factory CardDataItem.fromJson(Map<String, dynamic> json) => CardDataItem(
moduleInfo: json['module_info'] == null
? null
: ModuleInfo.fromJson(json['module_info'] as Map<String, dynamic>),
list: (json['list'] as List<dynamic>?)
?.map((e) => CardLiveItem.fromJson(e as Map<String, dynamic>))
.toList(),
topView: json['top_view'] as dynamic,
extraInfo: json['extra_info'] == null
? null
: ExtraInfo.fromJson(json['extra_info'] as Map<String, dynamic>),
);
Map<String, dynamic> toJson() => {
'module_info': moduleInfo?.toJson(),
'list': list?.map((e) => e.toJson()).toList(),
'top_view': topView,
};
}
class ExtraInfo {
int? totalCount;
ExtraInfo.fromJson(Map<String, dynamic> json) {
totalCount = json['total_count'];
}
}

View File

@@ -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>? qualityDescription;
String? playUrlCard;
int? flag;
List<dynamic>? 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<String, dynamic> 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<dynamic>?)
?.map((e) => QualityDescription.fromJson(e as Map<String, dynamic>))
.toList(),
playUrlCard: json['play_url_card'] as String?,
flag: json['flag'] as int?,
pendentList: json['pendent_list'] as List<dynamic>?,
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<String, dynamic>),
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<String, dynamic> 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,
};
}

View File

@@ -0,0 +1,20 @@
import 'card_data.dart';
class LiveCardList {
String? cardType;
CardData? cardData;
LiveCardList({this.cardType, this.cardData});
factory LiveCardList.fromJson(Map<String, dynamic> json) => LiveCardList(
cardType: json['card_type'] as String?,
cardData: json['card_data'] == null
? null
: CardData.fromJson(json['card_data'] as Map<String, dynamic>),
);
Map<String, dynamic> toJson() => {
'card_type': cardType,
'card_data': cardData?.toJson(),
};
}

View File

@@ -0,0 +1,44 @@
import 'card_list.dart';
class LiveIndexData {
List<LiveCardList>? cardList;
int? isRollback;
int? hasMore;
int? triggerTime;
int? isNeedRefresh;
LiveIndexData({
this.cardList,
this.isRollback,
this.hasMore,
this.triggerTime,
this.isNeedRefresh,
});
LiveIndexData.fromJson(Map<String, dynamic> json) {
if ((json['card_list'] as List<dynamic>?)?.isNotEmpty == true) {
cardList = <LiveCardList>[];
// 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<String, dynamic> toJson() => {
'card_list': cardList?.map((e) => e.toJson()).toList(),
'is_rollback': isRollback,
'has_more': hasMore,
'trigger_time': triggerTime,
'is_need_refresh': isNeedRefresh,
};
}

View File

@@ -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<String, dynamic> 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<String, dynamic> 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,
};
}

View File

@@ -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<String, dynamic> json) => InlineLive(
avatar: json['avatar'] == null
? null
: Avatar.fromJson(json['avatar'] as Map<String, dynamic>),
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<String, dynamic>),
rankListInfo: json['rank_list_info'] as dynamic,
rightTopLiveBadge: json['right_top_live_badge'] == null
? null
: RightTopLiveBadge.fromJson(json['right_top_live_badge'] as Map<String, dynamic>),
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<String, dynamic>),
);
Map<String, dynamic> 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(),
};
}

View File

@@ -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<String, dynamic> 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<String, dynamic>),
);
Map<String, dynamic> toJson() => {
'code': code,
'message': message,
'ttl': ttl,
'data': data?.toJson(),
};
}

View File

@@ -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<String, dynamic> 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<String, dynamic> toJson() => {
'id': id,
'link': link,
'pic': pic,
'title': title,
'type': type,
'sort': sort,
'count': count,
};
}

View File

@@ -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<String, dynamic> 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<String, dynamic> toJson() => {
'can_play': canPlay,
'hide_danmu_switch': hideDanmuSwitch,
'live_status': liveStatus,
'parent_area_id': parentAreaId,
'area_id': areaId,
'room_id': roomId,
'up_id': upId,
};
}

View File

@@ -0,0 +1,20 @@
class QualityDescription {
int? qn;
String? desc;
QualityDescription({this.qn, this.desc});
factory QualityDescription.fromJson(Map<String, dynamic> json) {
return QualityDescription(
qn: json['qn'] as int?,
desc: json['desc'] as String?,
);
}
Map<String, dynamic> toJson() => {
'qn': qn,
'desc': desc,
};
}

View File

@@ -0,0 +1,24 @@
import 'in_live.dart';
class RightTopLiveBadge {
InLive? inLive;
int? liveStatus;
RightTopLiveBadge({this.inLive, this.liveStatus});
factory RightTopLiveBadge.fromJson(Map<String, dynamic> json) {
return RightTopLiveBadge(
inLive: json['in_live'] == null
? null
: InLive.fromJson(json['in_live'] as Map<String, dynamic>),
liveStatus: json['live_status'] as int?,
);
}
Map<String, dynamic> toJson() => {
'in_live': inLive?.toJson(),
'live_status': liveStatus,
};
}

View File

@@ -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<String, dynamic> 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<String, dynamic> toJson() => {
'switch': sw1tch,
'num': num,
'text_small': textSmall,
'text_large': textLarge,
'icon': icon,
'icon_location': iconLocation,
'icon_web': iconWeb,
};
}

View File

@@ -0,0 +1,52 @@
import 'item.dart';
class LiveFollowData {
String? title;
int? pageSize;
int? totalPage;
List<LiveFollowItem>? list;
int? count;
int? neverLivedCount;
int? liveCount;
List<dynamic>? neverLivedFaces;
LiveFollowData({
this.title,
this.pageSize,
this.totalPage,
this.list,
this.count,
this.neverLivedCount,
this.liveCount,
this.neverLivedFaces,
});
LiveFollowData.fromJson(Map<String, dynamic> json) {
title = json['title'] as String?;
pageSize = json['pageSize'] as int?;
totalPage = json['totalPage'] as int?;
if ((json['list'] as List<dynamic>?)?.isNotEmpty == true) {
list = <LiveFollowItem>[];
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<dynamic>?;
}
Map<String, dynamic> 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,
};
}

View File

@@ -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<String, dynamic> 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<String, dynamic> 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,
};
}

View File

@@ -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<List<LiveItemModel>?, LiveItemModel> {
class LiveController extends CommonListController<LiveIndexData, LiveCardList> {
@override
void onInit() {
super.onInit();
queryData();
if (isLogin.value) {
fetchLiveFollowing();
}
}
String? gaiaVtoken;
final RxBool isLogin = Accounts.main.isLogin.obs;
@override
Future<LoadingState<List<LiveItemModel>?>> 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<LiveCardList>? 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<LoadingState<List<LiveFollowingItemModel>?>> 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<LiveFollowingItemModel>? 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<LiveFollowingItemModel> 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<LoadingState<LiveIndexData>> customGetData() =>
LiveHttp.liveFeedIndex(pn: currentPage, isLogin: isLogin.value);
}

View File

@@ -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<LivePage, LiveController>
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<LivePage, LiveController>
);
}
Widget _buildBody(LoadingState<List<LiveItemModel>?> loadingState) {
Widget _buildBody(LoadingState<List<LiveCardList>?> loadingState) {
return switch (loadingState) {
Loading() => SliverGrid(
gridDelegate: SliverGridDelegateWithExtentAndRatio(
@@ -92,25 +80,48 @@ class _LivePageState extends CommonPageState<LivePage, LiveController>
),
),
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<LiveCardList> 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<LivePage, LiveController>
};
}
Widget _buildFollowList() {
Widget _buildFollowList(LiveCardList item) {
final theme = Theme.of(context);
return Column(
mainAxisSize: MainAxisSize.min,
@@ -126,33 +137,32 @@ class _LivePageState extends CommonPageState<LivePage, LiveController>
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<LivePage, LiveController>
),
],
),
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<CardLiveItem>? 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,
),
);
}
}

View File

@@ -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: <Color>[
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),
),
],
),
);
}
}

View File

@@ -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<LiveFollowData, LiveFollowItem> {
@override
void onInit() {
super.onInit();
queryData();
}
Rx<int?> count = Rx<int?>(null);
@override
void checkIsEnd(int length) {
if (count.value != null && length >= count.value!) {
isEnd = true;
}
}
@override
List<LiveFollowItem>? getDataList(LiveFollowData response) {
count.value = response.liveCount;
return response.list;
}
@override
Future<LoadingState<LiveFollowData>> customGetData() =>
LiveHttp.liveFollow(currentPage);
}

View File

@@ -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<LiveFollowPage> createState() => _LiveFollowPageState();
}
class _LiveFollowPageState extends State<LiveFollowPage> {
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<List<LiveFollowItem>?> 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,
),
};
}
}

View File

@@ -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,

View File

@@ -85,9 +85,7 @@ class LoginUtils {
} catch (_) {}
try {
Get.find<LiveController>()
..isLogin.value = true
..fetchLiveFollowing();
Get.find<LiveController>().isLogin.value = true;
} catch (_) {}
try {
@@ -152,9 +150,7 @@ class LoginUtils {
} catch (_) {}
try {
Get.find<LiveController>()
..isLogin.value = false
..followListState.value = LoadingState.loading();
Get.find<LiveController>().isLogin.value = false;
} catch (_) {}
for (int i = 0; i < tabsConfig.length; i++) {