337 lines
10 KiB
Dart
337 lines
10 KiB
Dart
import 'package:flutter/material.dart';
|
|
import 'package:get/get.dart';
|
|
import 'package:hgdj/assets_tool/images.dart';
|
|
import 'package:hgdj/hj_page/live/live_item_widget.dart';
|
|
import 'package:hgdj/hj_page/live/live_model.dart';
|
|
import 'package:hgdj/hj_utils/screen.dart';
|
|
import 'package:hgdj/hj_utils/sliver_delegate.dart';
|
|
import 'package:hgdj/tools_base/banner/ads_grid_view_widget.dart';
|
|
import 'package:hgdj/tools_base/loading/loading_center_widget.dart';
|
|
import 'package:hgdj/tools_base/refresh/pull_refresh.dart';
|
|
import 'package:hgdj/tools_base/widget/net_image_widget.dart';
|
|
import 'package:video_player/video_player.dart';
|
|
|
|
import '../video/video_full_page.dart';
|
|
import 'live_detail_logic.dart';
|
|
|
|
//直播详情页
|
|
class LiveDetailPage extends StatefulWidget {
|
|
final LiveAnchor? anchor;
|
|
const LiveDetailPage({super.key, this.anchor});
|
|
|
|
@override
|
|
State<LiveDetailPage> createState() => _LiveDetailPageState();
|
|
}
|
|
|
|
class _LiveDetailPageState extends State<LiveDetailPage> {
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return Scaffold(
|
|
body: SafeArea(
|
|
child: GetBuilder<LiveDetailLogic>(
|
|
init: LiveDetailLogic(anchor: widget.anchor),
|
|
builder: (logic) => Stack(
|
|
children: [
|
|
_buildBody(logic),
|
|
Positioned(left: 10, top: 10, child: _buildBack()),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
//页面框架
|
|
Widget _buildBody(LiveDetailLogic logic) {
|
|
return pullYsRefresh(
|
|
enablePullDown: false,
|
|
onInit: (c) => logic.controller = c,
|
|
onLoading: (c) => logic.loadMoreData(),
|
|
child: CustomScrollView(
|
|
slivers: [
|
|
SliverPersistentHeader(
|
|
pinned: true,
|
|
delegate: MySliverDelegate(
|
|
forceRefresh: true,
|
|
maxHeight: Get.width / (logic.aspectRatio ?? 1),
|
|
minHeight: logic.isVerticalVideo
|
|
? Get.width / (logic.aspectRatio ?? 1)
|
|
: Get.height / 3 - 60,
|
|
child: ClipRRect(
|
|
//部分机型会超出比例,裁剪掉多余部分
|
|
child: Container(color: Colors.black, child: _buildPlayer()),
|
|
),
|
|
),
|
|
),
|
|
SliverToBoxAdapter(child: _buildAnchorInfo(logic)),
|
|
SliverToBoxAdapter(child: _buildAds()),
|
|
10.sliverSizeBoxH,
|
|
_buildDivider(),
|
|
SliverToBoxAdapter(
|
|
child: Container(
|
|
padding: const EdgeInsets.fromLTRB(16, 18, 16, 12),
|
|
child: const Text(
|
|
'热门直播',
|
|
style: TextStyle(
|
|
fontSize: 16,
|
|
color: Colors.white,
|
|
fontWeight: FontWeight.w500),
|
|
),
|
|
),
|
|
),
|
|
_buildRecommend(),
|
|
18.sliverSizeBoxH,
|
|
_buildDivider(),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
|
|
//视频播放区
|
|
Widget _buildPlayer() {
|
|
return GetBuilder<LiveDetailLogic>(
|
|
id: 'player',
|
|
builder: (logic) => Stack(
|
|
alignment: Alignment.center,
|
|
children: [
|
|
if (logic.loadingUserInfo) ...[
|
|
//1.加载用户直播权益信息
|
|
AspectRatio(
|
|
aspectRatio: logic.aspectRatio ?? 1, child: _buildCover(logic)),
|
|
const LoadingCenterWidget(),
|
|
] else if (!logic.canWatchLive) ...[
|
|
//2.无权限
|
|
_buildNoPermission(logic),
|
|
] else if (logic.anchor?.isOnline != true) ...[
|
|
//3.主播离线
|
|
_buildOffline(logic),
|
|
] else ...[
|
|
//4.主播在线
|
|
Hero(
|
|
tag: 'player',
|
|
child: AspectRatio(
|
|
aspectRatio: logic.aspectRatio ?? 1,
|
|
child: logic.initedVideoController
|
|
? VideoPlayer(logic.playerController!)
|
|
: _buildCover(logic),
|
|
),
|
|
),
|
|
if (logic.initedVideoController)
|
|
Positioned(
|
|
right: 12,
|
|
bottom: 6,
|
|
child: GestureDetector(
|
|
onTap: () => Get.to(() => VideoFullPage(
|
|
playCtr: logic.playerController!, showMenu: false)),
|
|
child: Image.asset("full_icon.png".videoPath,
|
|
width: 26, height: 26),
|
|
),
|
|
),
|
|
Obx(() => Offstage(
|
|
offstage: !logic.isLoading.value,
|
|
child: const LoadingCenterWidget())),
|
|
],
|
|
],
|
|
),
|
|
);
|
|
}
|
|
|
|
//无权限
|
|
Widget _buildNoPermission(LiveDetailLogic logic) {
|
|
return Container(
|
|
color: Colors.black,
|
|
child: Stack(
|
|
fit: StackFit.expand,
|
|
children: [
|
|
_buildCover(logic),
|
|
Container(
|
|
color: Colors.black.withValues(alpha: .7),
|
|
child: Column(
|
|
mainAxisAlignment: MainAxisAlignment.center,
|
|
children: [
|
|
const Text(
|
|
'温馨提示',
|
|
style: TextStyle(
|
|
fontSize: 14,
|
|
color: Colors.white,
|
|
fontWeight: FontWeight.w500),
|
|
),
|
|
12.sizeBoxH,
|
|
const Text('开通直播卡全站直播骚货任选~',
|
|
style: TextStyle(fontSize: 12, color: Colors.white)),
|
|
18.sizeBoxH,
|
|
GestureDetector(
|
|
onTap: logic.onChargeAction,
|
|
child: Container(
|
|
decoration: BoxDecoration(
|
|
borderRadius: BorderRadius.circular(3),
|
|
gradient: const LinearGradient(
|
|
begin: Alignment.centerLeft,
|
|
end: Alignment.centerRight,
|
|
colors: [Color(0xffffe8be), Color(0xffe5b764)],
|
|
),
|
|
),
|
|
padding:
|
|
const EdgeInsets.symmetric(horizontal: 8, vertical: 6),
|
|
child: const Text('立即开通直播卡',
|
|
style:
|
|
TextStyle(fontSize: 14, color: Color(0xFF7b0000))),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
|
|
//主播离线
|
|
Widget _buildOffline(LiveDetailLogic logic) {
|
|
return Container(
|
|
color: Colors.black,
|
|
child: Stack(
|
|
fit: StackFit.expand,
|
|
children: [
|
|
_buildCover(logic),
|
|
Container(
|
|
color: Colors.black.withValues(alpha: .7),
|
|
alignment: Alignment.center,
|
|
child: const Text('当前主播离线,请前往观看其她热门主播喔~',
|
|
style: TextStyle(fontSize: 14, color: Colors.white)),
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
|
|
//封面图
|
|
Widget _buildCover(LiveDetailLogic logic) {
|
|
return NetworkImageLoader(
|
|
imageUrl: logic.anchor?.coverImg ?? '',
|
|
encrypt: false,
|
|
borderRadius: 0,
|
|
width: Get.width,
|
|
);
|
|
}
|
|
|
|
//热门直播推荐列表
|
|
Widget _buildRecommend() {
|
|
return GetBuilder<LiveDetailLogic>(
|
|
id: 'recommend',
|
|
builder: (logic) {
|
|
if (logic.loading) {
|
|
return const SliverToBoxAdapter(child: LoadingWidget());
|
|
}
|
|
if (logic.recommendList.isEmpty) {
|
|
return const SliverToBoxAdapter(
|
|
child: SizedBox(height: 300, child: CErrorWidget()));
|
|
}
|
|
return SliverPadding(
|
|
padding: const EdgeInsets.symmetric(horizontal: 16),
|
|
sliver: SliverGrid(
|
|
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
|
|
crossAxisCount: 2,
|
|
mainAxisSpacing: 12,
|
|
crossAxisSpacing: 7,
|
|
childAspectRatio: 168 / 105,
|
|
),
|
|
delegate: SliverChildBuilderDelegate(
|
|
(context, index) {
|
|
final anchor = logic.recommendList[index];
|
|
return LiveItemWidget(
|
|
showType: 5,
|
|
anchor: anchor,
|
|
action: () => logic.onChangeLiveAction(anchor),
|
|
);
|
|
},
|
|
childCount: logic.recommendList.length,
|
|
),
|
|
),
|
|
);
|
|
},
|
|
);
|
|
}
|
|
|
|
//返回按钮
|
|
Widget _buildBack() {
|
|
return GestureDetector(
|
|
onTap: () => Get.back(),
|
|
child: Container(
|
|
padding: const EdgeInsets.only(left: 10, right: 2, top: 2, bottom: 2),
|
|
decoration:
|
|
const BoxDecoration(color: Colors.grey, shape: BoxShape.circle),
|
|
child: Icon(Icons.arrow_back_ios,
|
|
color: Colors.black.withValues(alpha: 0.8)),
|
|
),
|
|
);
|
|
}
|
|
|
|
//主播信息
|
|
Widget _buildAnchorInfo(LiveDetailLogic logic) {
|
|
return Padding(
|
|
padding: const EdgeInsets.fromLTRB(16, 12, 16, 0),
|
|
child: Column(
|
|
children: [
|
|
Row(
|
|
children: [
|
|
NetworkImageLoader(
|
|
imageUrl: logic.anchor?.coverImg ?? '',
|
|
width: 36,
|
|
height: 36,
|
|
borderRadius: 18,
|
|
encrypt: false,
|
|
),
|
|
4.sizeBoxW,
|
|
Text(logic.anchor?.name ?? '',
|
|
style: const TextStyle(fontSize: 14, color: Colors.white)),
|
|
],
|
|
),
|
|
18.sizeBoxH,
|
|
Row(
|
|
children: [
|
|
Text(
|
|
'${logic.anchor?.viewCount}人观众',
|
|
style: const TextStyle(fontSize: 12, color: Color(0xff999999)),
|
|
),
|
|
const Spacer(),
|
|
GestureDetector(
|
|
onTap: () => logic.onShareAction(),
|
|
child: Row(
|
|
children: [
|
|
Image.asset('live_share.png'.livePath, width: 24),
|
|
2.sizeBoxW,
|
|
const Text('分享',
|
|
style:
|
|
TextStyle(fontSize: 12, color: Color(0xff999999))),
|
|
],
|
|
),
|
|
),
|
|
],
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
|
|
//广告位
|
|
Widget _buildAds() {
|
|
return AdsGridViewWidget(
|
|
36,
|
|
accordingAdsType: true,
|
|
padding: const EdgeInsets.fromLTRB(16, 18, 0, 0),
|
|
);
|
|
}
|
|
|
|
//分割线
|
|
Widget _buildDivider() {
|
|
return SliverToBoxAdapter(
|
|
child: Container(
|
|
padding: const EdgeInsets.symmetric(horizontal: 16),
|
|
height: .5,
|
|
color: Colors.black.withValues(alpha: .1),
|
|
),
|
|
);
|
|
}
|
|
}
|