feat: live following

This commit is contained in:
guozhigq
2024-10-26 23:46:19 +08:00
parent 4f6ac4aff4
commit 638e22a60d
8 changed files with 327 additions and 106 deletions

View File

@ -63,7 +63,7 @@ class LiveFollowingItemModel {
String? roomNews; String? roomNews;
String? watchIcon; String? watchIcon;
String? textSmall; String? textSmall;
String? roomCover; String? cover;
String? pic; String? pic;
int? parentAreaId; int? parentAreaId;
int? areaId; int? areaId;
@ -90,7 +90,7 @@ class LiveFollowingItemModel {
this.roomNews, this.roomNews,
this.watchIcon, this.watchIcon,
this.textSmall, this.textSmall,
this.roomCover, this.cover,
this.pic, this.pic,
this.parentAreaId, this.parentAreaId,
this.areaId, this.areaId,
@ -108,7 +108,8 @@ class LiveFollowingItemModel {
isAttention = json['is_attention']; isAttention = json['is_attention'];
clipNum = json['clipnum']; clipNum = json['clipnum'];
fansNum = json['fans_num']; fansNum = json['fans_num'];
areaName = json['area_name']; areaName =
json['area_name'] == '' ? json['area_name_v2'] : json['area_name'];
areaValue = json['area_value']; areaValue = json['area_value'];
tags = json['tags']; tags = json['tags'];
recentRecordIdV2 = json['recent_record_id_v2']; recentRecordIdV2 = json['recent_record_id_v2'];
@ -118,7 +119,7 @@ class LiveFollowingItemModel {
roomNews = json['room_news']; roomNews = json['room_news'];
watchIcon = json['watch_icon']; watchIcon = json['watch_icon'];
textSmall = json['text_small']; textSmall = json['text_small'];
roomCover = json['room_cover']; cover = json['room_cover'];
pic = json['room_cover']; pic = json['room_cover'];
parentAreaId = json['parent_area_id']; parentAreaId = json['parent_area_id'];
areaId = json['area_id']; areaId = json['area_id'];

View File

@ -14,6 +14,7 @@ class LiveController extends GetxController {
RxList<LiveItemModel> liveList = <LiveItemModel>[].obs; RxList<LiveItemModel> liveList = <LiveItemModel>[].obs;
RxList<LiveFollowingItemModel> liveFollowingList = RxList<LiveFollowingItemModel> liveFollowingList =
<LiveFollowingItemModel>[].obs; <LiveFollowingItemModel>[].obs;
RxInt liveFollowingCount = 0.obs;
bool flag = false; bool flag = false;
OverlayEntry? popupDialog; OverlayEntry? popupDialog;
Box setting = GStrorage.setting; Box setting = GStrorage.setting;
@ -27,9 +28,6 @@ class LiveController extends GetxController {
// 获取推荐 // 获取推荐
Future queryLiveList(type) async { Future queryLiveList(type) async {
// if (type == 'init') {
// _currentPage = 1;
// }
var res = await LiveHttp.liveList( var res = await LiveHttp.liveList(
pn: _currentPage, pn: _currentPage,
); );
@ -68,13 +66,14 @@ class LiveController extends GetxController {
// //
Future fetchLiveFollowing() async { Future fetchLiveFollowing() async {
var res = await LiveHttp.liveFollowing(pn: 1, ps: 20); var res = await LiveHttp.liveFollowing(pn: 1, ps: 10);
if (res['status']) { if (res['status']) {
liveFollowingList.value = liveFollowingList.value =
(res['data'].list as List<LiveFollowingItemModel>) (res['data'].list as List<LiveFollowingItemModel>)
.where((LiveFollowingItemModel item) => .where((LiveFollowingItemModel item) =>
item.liveStatus == 1 && item.recordLiveTime == 0) // 根据条件过滤 item.liveStatus == 1 && item.recordLiveTime == 0) // 根据条件过滤
.toList(); .toList();
liveFollowingCount.value = res['data'].liveCount;
} }
return res; return res;
} }

View File

@ -162,34 +162,61 @@ class _LivePageState extends State<LivePage>
mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
Obx( Row(
() => Text.rich( mainAxisAlignment: MainAxisAlignment.spaceBetween,
TextSpan( children: [
children: [ Obx(
const TextSpan( () => Text.rich(
text: ' 我的关注 ',
style: TextStyle(
fontWeight: FontWeight.bold,
fontSize: 15,
),
),
TextSpan( TextSpan(
text: ' ${_liveController.liveFollowingList.length}', children: [
style: TextStyle( const TextSpan(
fontSize: 12, text: ' 我的关注 ',
color: Theme.of(context).colorScheme.primary, style: TextStyle(
), fontWeight: FontWeight.bold,
fontSize: 15,
),
),
TextSpan(
text: ' ${_liveController.liveFollowingCount}',
style: TextStyle(
fontSize: 12,
color: Theme.of(context).colorScheme.primary,
),
),
TextSpan(
text: '人正在直播',
style: TextStyle(
fontSize: 12,
color: Theme.of(context).colorScheme.outline,
),
),
],
), ),
TextSpan( ),
text: '人正在直播', ),
style: TextStyle( InkWell(
fontSize: 12, onTap: () {
Get.toNamed('/liveFollowing');
},
highlightColor: Colors.transparent,
splashColor: Colors.transparent,
child: Row(
children: [
Text(
'查看更多',
style: TextStyle(
fontSize: 14,
color: Theme.of(context).colorScheme.outline,
),
),
Icon(
Icons.chevron_right,
color: Theme.of(context).colorScheme.outline, color: Theme.of(context).colorScheme.outline,
), ),
), ],
], ),
), ),
), ],
), ),
FutureBuilder( FutureBuilder(
future: _futureBuilderFuture2, future: _futureBuilderFuture2,
@ -201,8 +228,7 @@ class _LivePageState extends State<LivePage>
Map? data = snapshot.data; Map? data = snapshot.data;
if (data?['status']) { if (data?['status']) {
RxList list = _liveController.liveFollowingList; RxList list = _liveController.liveFollowingList;
// ignore: invalid_use_of_protected_member return LiveFollowingListView(list: list);
return Obx(() => LiveFollowingListView(list: list.value));
} else { } else {
return SizedBox( return SizedBox(
height: 80, height: 80,
@ -230,69 +256,71 @@ class _LivePageState extends State<LivePage>
} }
class LiveFollowingListView extends StatelessWidget { class LiveFollowingListView extends StatelessWidget {
final List list; final RxList list;
const LiveFollowingListView({super.key, required this.list}); const LiveFollowingListView({super.key, required this.list});
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return SizedBox( return Obx(
height: 100, () => SizedBox(
child: ListView.builder( height: 100,
scrollDirection: Axis.horizontal, child: ListView.builder(
itemBuilder: (context, index) { scrollDirection: Axis.horizontal,
final LiveFollowingItemModel item = list[index]; itemBuilder: (context, index) {
return Padding( final LiveFollowingItemModel item = list[index];
padding: const EdgeInsets.fromLTRB(3, 12, 3, 0), return Padding(
child: Column( padding: const EdgeInsets.fromLTRB(3, 12, 3, 0),
children: [ child: Column(
InkWell( children: [
onTap: () { InkWell(
Get.toNamed( onTap: () {
'/liveRoom?roomid=${item.roomId}', Get.toNamed(
arguments: { '/liveRoom?roomid=${item.roomId}',
'liveItem': item, arguments: {
'heroTag': item.roomId.toString() 'liveItem': item,
}, 'heroTag': item.roomId.toString()
); },
}, );
child: Container( },
width: 54, child: Container(
height: 54, width: 54,
padding: const EdgeInsets.all(2), height: 54,
decoration: BoxDecoration( padding: const EdgeInsets.all(2),
borderRadius: BorderRadius.circular(27), decoration: BoxDecoration(
border: Border.all( borderRadius: BorderRadius.circular(27),
color: Theme.of(context).colorScheme.primary, border: Border.all(
width: 1.5, color: Theme.of(context).colorScheme.primary,
width: 1.5,
),
),
child: NetworkImgLayer(
width: 50,
height: 50,
type: 'avatar',
src: list[index].face,
), ),
), ),
child: NetworkImgLayer( ),
width: 50, const SizedBox(height: 6),
height: 50, SizedBox(
type: 'avatar', width: 62,
src: list[index].face, child: Text(
list[index].uname,
maxLines: 1,
overflow: TextOverflow.ellipsis,
textAlign: TextAlign.center,
style: const TextStyle(
fontSize: 12,
),
), ),
), ),
), ],
const SizedBox(height: 6), ),
SizedBox( );
width: 62, },
child: Text( itemCount: list.length,
list[index].uname, ),
maxLines: 1,
overflow: TextOverflow.ellipsis,
textAlign: TextAlign.center,
style: const TextStyle(
fontSize: 12,
),
),
),
],
),
);
},
itemCount: list.length,
), ),
); );
} }

View File

@ -2,6 +2,8 @@ import 'package:flutter/material.dart';
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:pilipala/common/constants.dart'; import 'package:pilipala/common/constants.dart';
import 'package:pilipala/common/widgets/badge.dart';
import 'package:pilipala/models/live/follow.dart';
import 'package:pilipala/models/live/item.dart'; import 'package:pilipala/models/live/item.dart';
import 'package:pilipala/utils/image_save.dart'; import 'package:pilipala/utils/image_save.dart';
import 'package:pilipala/utils/utils.dart'; import 'package:pilipala/utils/utils.dart';
@ -9,7 +11,7 @@ import 'package:pilipala/common/widgets/network_img_layer.dart';
// 视频卡片 - 垂直布局 // 视频卡片 - 垂直布局
class LiveCardV extends StatelessWidget { class LiveCardV extends StatelessWidget {
final LiveItemModel liveItem; final dynamic liveItem;
final int crossAxisCount; final int crossAxisCount;
const LiveCardV({ const LiveCardV({
@ -64,6 +66,9 @@ class LiveCardV extends StatelessWidget {
), ),
), ),
), ),
if (liveItem is LiveFollowingItemModel &&
liveItem.liveStatus == 1)
const PBadge(top: 8, right: 8, text: '直播中'),
], ],
); );
}), }),
@ -148,7 +153,7 @@ class LiveContent extends StatelessWidget {
} }
class VideoStat extends StatelessWidget { class VideoStat extends StatelessWidget {
final LiveItemModel? liveItem; final dynamic liveItem;
const VideoStat({ const VideoStat({
Key? key, Key? key,
@ -178,25 +183,20 @@ class VideoStat extends StatelessWidget {
liveItem!.areaName!, liveItem!.areaName!,
style: const TextStyle(fontSize: 11, color: Colors.white), style: const TextStyle(fontSize: 11, color: Colors.white),
), ),
Text( if (liveItem is LiveItemModel) ...[
liveItem!.watchedShow!['text_small'], Text(
style: const TextStyle(fontSize: 11, color: Colors.white), liveItem!.watchedShow?['text_small'],
), style: const TextStyle(fontSize: 11, color: Colors.white),
),
],
if (liveItem is LiveFollowingItemModel) ...[
Text(
'${liveItem.textSmall}',
style: const TextStyle(fontSize: 11, color: Colors.white),
),
]
], ],
), ),
// child: RichText(
// maxLines: 1,
// textAlign: TextAlign.justify,
// softWrap: false,
// text: TextSpan(
// style: const TextStyle(fontSize: 11, color: Colors.white),
// children: [
// TextSpan(text: liveItem!.areaName!),
// TextSpan(text: liveItem!.watchedShow!['text_small']),
// ],
// ),
// ),
); );
} }
} }

View File

@ -0,0 +1,50 @@
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
import 'package:get/get.dart';
import 'package:hive/hive.dart';
import 'package:pilipala/http/live.dart';
import 'package:pilipala/models/live/follow.dart';
import 'package:pilipala/utils/storage.dart';
class LiveFollowController extends GetxController {
RxInt crossAxisCount = 2.obs;
Box setting = GStrorage.setting;
int _currentPage = 1;
RxInt liveFollowingCount = 0.obs;
RxList<LiveFollowingItemModel> liveFollowingList =
<LiveFollowingItemModel>[].obs;
@override
void onInit() {
super.onInit();
crossAxisCount.value =
setting.get(SettingBoxKey.customRows, defaultValue: 2);
}
Future queryLiveFollowList(type) async {
var res = await LiveHttp.liveFollowing(
pn: _currentPage,
ps: 20,
);
if (res['status']) {
if (type == 'init') {
liveFollowingList.value = res['data'].list;
liveFollowingCount.value = res['data'].liveCount;
} else if (type == 'onLoad') {
liveFollowingList.addAll(res['data'].list);
}
_currentPage += 1;
} else {
SmartDialog.showToast(res['msg']);
}
return res;
}
Future onRefresh() async {
_currentPage = 1;
await queryLiveFollowList('init');
}
void onLoad() async {
queryLiveFollowList('onLoad');
}
}

View File

@ -0,0 +1,4 @@
library live_follow;
export 'view.dart';
export 'controller.dart';

View File

@ -0,0 +1,136 @@
import 'package:easy_debounce/easy_throttle.dart';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:pilipala/common/constants.dart';
import 'package:pilipala/common/skeleton/video_card_v.dart';
import 'package:pilipala/common/widgets/http_error.dart';
import 'package:pilipala/pages/live/widgets/live_item.dart';
import 'controller.dart';
class LiveFollowPage extends StatefulWidget {
const LiveFollowPage({super.key});
@override
State<LiveFollowPage> createState() => _LiveFollowPageState();
}
class _LiveFollowPageState extends State<LiveFollowPage> {
late Future _futureBuilderFuture;
final ScrollController scrollController = ScrollController();
final LiveFollowController _liveFollowController =
Get.put(LiveFollowController());
@override
void initState() {
super.initState();
_futureBuilderFuture = _liveFollowController.queryLiveFollowList('init');
scrollController.addListener(
() {
if (scrollController.position.pixels >=
scrollController.position.maxScrollExtent - 200) {
EasyThrottle.throttle(
'liveFollowList', const Duration(milliseconds: 200), () {
_liveFollowController.onLoad();
});
}
},
);
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
elevation: 0,
scrolledUnderElevation: 0,
titleSpacing: 0,
centerTitle: false,
title: Obx(() => Text(
'${_liveFollowController.liveFollowingCount}人正在直播中',
style: Theme.of(context).textTheme.titleMedium,
)),
),
body: Container(
clipBehavior: Clip.hardEdge,
margin: const EdgeInsets.only(
left: StyleString.safeSpace, right: StyleString.safeSpace),
decoration: const BoxDecoration(
borderRadius: BorderRadius.all(StyleString.imgRadius),
),
child: RefreshIndicator(
onRefresh: () async {
return await _liveFollowController.onRefresh();
},
child: CustomScrollView(
controller: scrollController,
slivers: [
SliverPadding(
padding:
const EdgeInsets.fromLTRB(0, StyleString.safeSpace, 0, 0),
sliver: FutureBuilder(
future: _futureBuilderFuture,
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.done) {
if (snapshot.data == null) {
return const SliverToBoxAdapter(child: SizedBox());
}
Map data = snapshot.data as Map;
if (data['status']) {
return SliverLayoutBuilder(
builder: (context, boxConstraints) {
return Obx(
() => contentGrid(_liveFollowController,
_liveFollowController.liveFollowingList),
);
});
} else {
return HttpError(
errMsg: data['msg'],
fn: () {
setState(() {
_futureBuilderFuture = _liveFollowController
.queryLiveFollowList('init');
});
},
);
}
} else {
return contentGrid(_liveFollowController, []);
}
},
),
),
],
),
),
));
}
Widget contentGrid(ctr, liveList) {
int crossAxisCount = ctr.crossAxisCount.value;
return SliverGrid(
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
mainAxisSpacing: StyleString.safeSpace,
crossAxisSpacing: StyleString.safeSpace,
crossAxisCount: crossAxisCount,
mainAxisExtent:
Get.size.width / crossAxisCount / StyleString.aspectRatio +
MediaQuery.textScalerOf(context).scale(
(crossAxisCount == 1 ? 48 : 68),
),
),
delegate: SliverChildBuilderDelegate(
(BuildContext context, int index) {
return liveList!.isNotEmpty
? LiveCardV(
liveItem: liveList[index],
crossAxisCount: crossAxisCount,
)
: const VideoCardVSkeleton();
},
childCount: liveList!.isNotEmpty ? liveList!.length : 10,
),
);
}
}

View File

@ -5,6 +5,7 @@ import 'package:get/get.dart';
import 'package:hive/hive.dart'; import 'package:hive/hive.dart';
import 'package:pilipala/pages/fav_edit/index.dart'; import 'package:pilipala/pages/fav_edit/index.dart';
import 'package:pilipala/pages/follow_search/view.dart'; import 'package:pilipala/pages/follow_search/view.dart';
import 'package:pilipala/pages/live_follow/index.dart';
import 'package:pilipala/pages/member_article/index.dart'; import 'package:pilipala/pages/member_article/index.dart';
import 'package:pilipala/pages/message/at/index.dart'; import 'package:pilipala/pages/message/at/index.dart';
import 'package:pilipala/pages/message/like/index.dart'; import 'package:pilipala/pages/message/like/index.dart';
@ -199,6 +200,8 @@ class Routes {
name: '/memberArticle', page: () => const MemberArticlePage()), name: '/memberArticle', page: () => const MemberArticlePage()),
// 用户信息编辑 // 用户信息编辑
CustomGetPage(name: '/mineEdit', page: () => const MineEditPage()), CustomGetPage(name: '/mineEdit', page: () => const MineEditPage()),
// 关注的直播up
CustomGetPage(name: '/liveFollowing', page: () => const LiveFollowPage()),
]; ];
} }