mod: 播放页样式

This commit is contained in:
guozhigq
2023-07-09 00:35:28 +08:00
parent 6da12da92f
commit 54a5fc61f0
4 changed files with 256 additions and 222 deletions

View File

@ -10,8 +10,10 @@ import 'package:pilipala/models/video/play/url.dart';
import 'package:pilipala/models/video/reply/item.dart'; import 'package:pilipala/models/video/reply/item.dart';
import 'package:pilipala/pages/video/detail/replyReply/index.dart'; import 'package:pilipala/pages/video/detail/replyReply/index.dart';
class VideoDetailController extends GetxController { class VideoDetailController extends GetxController
with GetSingleTickerProviderStateMixin {
int tabInitialIndex = 0; int tabInitialIndex = 0;
TabController? tabCtr;
// tabs // tabs
RxList<String> tabs = <String>['简介', '评论'].obs; RxList<String> tabs = <String>['简介', '评论'].obs;
@ -63,6 +65,7 @@ class VideoDetailController extends GetxController {
} }
heroTag = Get.arguments['heroTag']; heroTag = Get.arguments['heroTag'];
} }
tabCtr = TabController(length: 2, vsync: this);
queryVideoUrl(); queryVideoUrl();
} }

View File

@ -7,6 +7,7 @@ import 'package:pilipala/common/constants.dart';
import 'package:pilipala/common/widgets/http_error.dart'; import 'package:pilipala/common/widgets/http_error.dart';
import 'package:pilipala/pages/fav/index.dart'; import 'package:pilipala/pages/fav/index.dart';
import 'package:pilipala/pages/favDetail/index.dart'; import 'package:pilipala/pages/favDetail/index.dart';
import 'package:pilipala/pages/video/detail/index.dart';
import 'package:pilipala/pages/video/detail/widgets/expandable_section.dart'; import 'package:pilipala/pages/video/detail/widgets/expandable_section.dart';
import 'package:pilipala/common/widgets/network_img_layer.dart'; import 'package:pilipala/common/widgets/network_img_layer.dart';
import 'package:pilipala/common/widgets/stat/danmu.dart'; import 'package:pilipala/common/widgets/stat/danmu.dart';
@ -99,6 +100,7 @@ class _VideoInfoState extends State<VideoInfo> with TickerProviderStateMixin {
final FavController _favController = Get.put(FavController()); final FavController _favController = Get.put(FavController());
late VideoDetailController? videoDetailCtr;
@override @override
void initState() { void initState() {
super.initState(); super.initState();
@ -110,6 +112,8 @@ class _VideoInfoState extends State<VideoInfo> with TickerProviderStateMixin {
); );
_manualAnimation = _manualAnimation =
Tween<double>(begin: 0.5, end: 1.5).animate(_manualController!); Tween<double>(begin: 0.5, end: 1.5).animate(_manualController!);
videoDetailCtr =
Get.find<VideoDetailController>(tag: Get.arguments['heroTag']);
} }
showFavBottomSheet() { showFavBottomSheet() {
@ -345,7 +349,7 @@ class _VideoInfoState extends State<VideoInfo> with TickerProviderStateMixin {
), ),
const SizedBox(height: 8), const SizedBox(height: 8),
// 点赞收藏转发 // 点赞收藏转发
_actionGrid(context, videoIntroController), _actionGrid(context, videoIntroController, videoDetailCtr),
// 合集 // 合集
if (!widget.loadingStatus && if (!widget.loadingStatus &&
widget.videoDetail!.ugcSeason != null) ...[ widget.videoDetail!.ugcSeason != null) ...[
@ -425,88 +429,90 @@ class _VideoInfoState extends State<VideoInfo> with TickerProviderStateMixin {
} }
// 喜欢 投币 分享 // 喜欢 投币 分享
Widget _actionGrid(BuildContext context, videoIntroController) { Widget _actionGrid(
BuildContext context, videoIntroController, videoDetailCtr) {
return LayoutBuilder(builder: (context, constraints) { return LayoutBuilder(builder: (context, constraints) {
return SizedBox( return SizedBox(
height: constraints.maxWidth / 5 * 0.8, height: constraints.maxWidth / 5 * 0.8,
child: GridView.count( child: Material(
primary: false, child: GridView.count(
padding: const EdgeInsets.all(0), primary: false,
crossAxisCount: 5, padding: const EdgeInsets.all(0),
childAspectRatio: 1.25, crossAxisCount: 5,
children: <Widget>[ childAspectRatio: 1.25,
// ActionItem( children: <Widget>[
// icon: const Icon(FontAwesomeIcons.s), // InkWell(
// selectIcon: const Icon(FontAwesomeIcons.s), // onTap: () => videoIntroController.actionOneThree(),
// onTap: () => {}, // borderRadius: StyleString.mdRadius,
// selectStatus: true, // child: Padding(
// loadingStatus: false, // padding: const EdgeInsets.all(12),
// text: '三连', // child: Image.asset(
// ), // 'assets/images/logo/logo_big.png',
// Column( // width: 10,
// children: [], // height: 10,
// ), // ),
InkWell( // ),
onTap: () => videoIntroController.actionOneThree(), // ),
borderRadius: StyleString.mdRadius, Obx(
child: Padding( () => ActionItem(
padding: const EdgeInsets.all(12), icon: const Icon(FontAwesomeIcons.thumbsUp),
child: Image.asset( selectIcon: const Icon(FontAwesomeIcons.solidThumbsUp),
'assets/images/logo/logo_big.png', onTap: () => videoIntroController.actionLikeVideo(),
width: 10, selectStatus: videoIntroController.hasLike.value,
height: 10, loadingStatus: widget.loadingStatus,
), text: !widget.loadingStatus
? widget.videoDetail!.stat!.like!.toString()
: '-'),
), ),
), // ActionItem(
Obx( // icon: const Icon(FontAwesomeIcons.thumbsDown),
() => ActionItem( // selectIcon: const Icon(FontAwesomeIcons.solidThumbsDown),
icon: const Icon(FontAwesomeIcons.thumbsUp), // onTap: () => {},
selectIcon: const Icon(FontAwesomeIcons.solidThumbsUp), // selectStatus: false,
onTap: () => videoIntroController.actionLikeVideo(), // loadingStatus: widget.loadingStatus,
selectStatus: videoIntroController.hasLike.value, // text: '不喜欢'),
Obx(
() => ActionItem(
icon: const Icon(FontAwesomeIcons.b),
selectIcon: const Icon(FontAwesomeIcons.b),
onTap: () => videoIntroController.actionCoinVideo(),
selectStatus: videoIntroController.hasCoin.value,
loadingStatus: widget.loadingStatus,
text: !widget.loadingStatus
? widget.videoDetail!.stat!.coin!.toString()
: '-'),
),
Obx(
() => ActionItem(
icon: const Icon(FontAwesomeIcons.heart),
selectIcon: const Icon(FontAwesomeIcons.heartCircleCheck),
onTap: () => showFavBottomSheet(),
selectStatus: videoIntroController.hasFav.value,
loadingStatus: widget.loadingStatus,
text: !widget.loadingStatus
? widget.videoDetail!.stat!.favorite!.toString()
: '-'),
),
ActionItem(
icon: const Icon(FontAwesomeIcons.shareFromSquare),
onTap: () => videoIntroController.actionShareVideo(),
selectStatus: false,
loadingStatus: widget.loadingStatus, loadingStatus: widget.loadingStatus,
text: !widget.loadingStatus text: !widget.loadingStatus
? widget.videoDetail!.stat!.like!.toString() ? widget.videoDetail!.stat!.share!.toString()
: '-'), : '-'),
), ActionItem(
// ActionItem( icon: const Icon(FontAwesomeIcons.comments),
// icon: const Icon(FontAwesomeIcons.thumbsDown), onTap: () {
// selectIcon: const Icon(FontAwesomeIcons.solidThumbsDown), videoDetailCtr.tabCtr.animateTo(1);
// onTap: () => {}, },
// selectStatus: false, selectStatus: false,
// loadingStatus: widget.loadingStatus,
// text: '不喜欢'),
Obx(
() => ActionItem(
icon: const Icon(FontAwesomeIcons.b),
selectIcon: const Icon(FontAwesomeIcons.b),
onTap: () => videoIntroController.actionCoinVideo(),
selectStatus: videoIntroController.hasCoin.value,
loadingStatus: widget.loadingStatus, loadingStatus: widget.loadingStatus,
text: !widget.loadingStatus text: !widget.loadingStatus
? widget.videoDetail!.stat!.coin!.toString() ? widget.videoDetail!.stat!.reply!.toString()
: '-'), : '-'),
), ],
Obx( ),
() => ActionItem(
icon: const Icon(FontAwesomeIcons.heart),
selectIcon: const Icon(FontAwesomeIcons.heartCircleCheck),
onTap: () => showFavBottomSheet(),
selectStatus: videoIntroController.hasFav.value,
loadingStatus: widget.loadingStatus,
text: !widget.loadingStatus
? widget.videoDetail!.stat!.favorite!.toString()
: '-'),
),
ActionItem(
icon: const Icon(FontAwesomeIcons.shareFromSquare),
onTap: () => videoIntroController.actionShareVideo(),
selectStatus: false,
loadingStatus: widget.loadingStatus,
text: !widget.loadingStatus
? widget.videoDetail!.stat!.share!.toString()
: '-'),
],
), ),
); );
}); });

View File

@ -136,6 +136,7 @@ class _VideoReplyPanelState extends State<VideoReplyPanel>
controller: _videoReplyController.scrollController, controller: _videoReplyController.scrollController,
key: const PageStorageKey<String>('评论'), key: const PageStorageKey<String>('评论'),
slivers: <Widget>[ slivers: <Widget>[
const SliverToBoxAdapter(child: SizedBox(height: 12)),
FutureBuilder( FutureBuilder(
future: _futureBuilderFuture, future: _futureBuilderFuture,
builder: (context, snapshot) { builder: (context, snapshot) {

View File

@ -1,4 +1,5 @@
import 'dart:async'; import 'dart:async';
import 'dart:ui';
import 'package:extended_image/extended_image.dart'; import 'package:extended_image/extended_image.dart';
import 'package:extended_nested_scroll_view/extended_nested_scroll_view.dart'; import 'package:extended_nested_scroll_view/extended_nested_scroll_view.dart';
@ -144,120 +145,139 @@ class _VideoDetailPageState extends State<VideoDetailPage>
final videoHeight = MediaQuery.of(context).size.width * 9 / 16; final videoHeight = MediaQuery.of(context).size.width * 9 / 16;
final double pinnedHeaderHeight = final double pinnedHeaderHeight =
statusBarHeight + kToolbarHeight + videoHeight; statusBarHeight + kToolbarHeight + videoHeight;
return DefaultTabController( return SafeArea(
initialIndex: videoDetailController.tabInitialIndex, top: false,
length: videoDetailController.tabs.length, // tab的数量. bottom: false,
child: SafeArea( child: Stack(
top: false, children: [
bottom: false, Positioned(
child: Stack( top: 0,
children: [ left: 0,
Scaffold( right: 0,
resizeToAvoidBottomInset: false, child: NetworkImgLayer(
key: videoDetailController.scaffoldKey, type: 'emote',
body: ExtendedNestedScrollView( src: videoDetailController.videoItem['pic'],
controller: _extendNestCtr, width: Get.size.width,
headerSliverBuilder: height: videoHeight + 100,
(BuildContext context, bool innerBoxIsScrolled) { ),
return <Widget>[ ),
SliverAppBar( Positioned.fill(
automaticallyImplyLeading: false, child: BackdropFilter(
pinned: false, filter: ImageFilter.blur(sigmaX: 100, sigmaY: 100), //可以看源码
elevation: 0, child: Container(
scrolledUnderElevation: 0, decoration: BoxDecoration(
forceElevated: innerBoxIsScrolled, color:
expandedHeight: videoHeight, Theme.of(context).colorScheme.background.withOpacity(0.1),
// collapsedHeight: videoHeight, ),
backgroundColor: Theme.of(context).colorScheme.background, ),
flexibleSpace: FlexibleSpaceBar( ),
background: Padding( ),
padding: EdgeInsets.only( Scaffold(
top: MediaQuery.of(context).padding.top), resizeToAvoidBottomInset: false,
child: LayoutBuilder( key: videoDetailController.scaffoldKey,
builder: (context, boxConstraints) { backgroundColor: Colors.transparent,
double maxWidth = boxConstraints.maxWidth; body: ExtendedNestedScrollView(
double maxHeight = boxConstraints.maxHeight; controller: _extendNestCtr,
// double PR = headerSliverBuilder:
// MediaQuery.of(context).devicePixelRatio; (BuildContext context, bool innerBoxIsScrolled) {
return Hero( return <Widget>[
tag: videoDetailController.heroTag, SliverAppBar(
child: Stack( automaticallyImplyLeading: false,
children: [ pinned: false,
AspectRatio( elevation: 0,
aspectRatio: 16 / 9, scrolledUnderElevation: 0,
child: MeeduVideoPlayer( forceElevated: innerBoxIsScrolled,
controller: _meeduPlayerController!, expandedHeight: videoHeight,
header: (BuildContext context, backgroundColor: Colors.transparent,
MeeduPlayerController // backgroundColor: Theme.of(context).colorScheme.background,
_meeduPlayerController, flexibleSpace: FlexibleSpaceBar(
Responsive) { background: Padding(
return AppBar( padding: EdgeInsets.only(
toolbarHeight: 40, top: MediaQuery.of(context).padding.top),
backgroundColor: Colors.transparent, child: LayoutBuilder(
primary: false, builder: (context, boxConstraints) {
elevation: 0, double maxWidth = boxConstraints.maxWidth;
scrolledUnderElevation: 0, double maxHeight = boxConstraints.maxHeight;
foregroundColor: Colors.white, return Hero(
leading: IconButton( tag: videoDetailController.heroTag,
onPressed: () { child: Stack(
Get.back(); children: [
}, AspectRatio(
icon: const Icon( aspectRatio: 16 / 9,
Icons.arrow_back_ios, child: MeeduVideoPlayer(
size: 19, controller: _meeduPlayerController!,
), header: (BuildContext context,
MeeduPlayerController
_meeduPlayerController,
Responsive) {
return AppBar(
toolbarHeight: 40,
backgroundColor: Colors.transparent,
primary: false,
elevation: 0,
scrolledUnderElevation: 0,
foregroundColor: Colors.white,
leading: IconButton(
onPressed: () {
Get.back();
},
icon: const Icon(
Icons.arrow_back_ios,
size: 19,
), ),
title: Text( ),
'视频详情', title: Text(
style: TextStyle( '视频详情',
color: Colors.white, style: TextStyle(
fontSize: Theme.of(context) color: Colors.white,
.textTheme fontSize: Theme.of(context)
.titleSmall! .textTheme
.fontSize), .titleSmall!
), .fontSize),
); ),
}, );
},
),
),
Visibility(
visible: isShowCover,
child: Positioned(
top: 0,
left: 0,
right: 0,
child: NetworkImgLayer(
type: 'emote',
src: videoDetailController
.videoItem['pic'],
width: maxWidth,
height: maxHeight,
), ),
), ),
Visibility( ),
visible: isShowCover, ],
child: Positioned( ),
top: 0, );
left: 0, },
right: 0,
child: NetworkImgLayer(
type: 'emote',
src: videoDetailController
.videoItem['pic'],
width: maxWidth,
height: maxHeight,
),
),
),
],
),
);
},
),
), ),
), ),
), ),
]; ),
}, ];
pinnedHeaderSliverHeightBuilder: () { },
return playerStatus != PlayerStatus.playing pinnedHeaderSliverHeightBuilder: () {
? MediaQuery.of(context).padding.top + 50 return playerStatus != PlayerStatus.playing
: pinnedHeaderHeight; ? statusBarHeight + kToolbarHeight
}, : pinnedHeaderHeight;
onlyOneScrollInBody: true, },
body: Column( onlyOneScrollInBody: true,
body: Container(
color: Theme.of(context).colorScheme.background,
child: Column(
children: [ children: [
Container( Container(
width: double.infinity, width: double.infinity,
height: 45, height: 0,
decoration: BoxDecoration( decoration: BoxDecoration(
color: Theme.of(context).colorScheme.background,
border: Border( border: Border(
bottom: BorderSide( bottom: BorderSide(
color: color:
@ -274,7 +294,10 @@ class _VideoDetailPageState extends State<VideoDetailPage>
margin: const EdgeInsets.only(left: 20), margin: const EdgeInsets.only(left: 20),
child: Obx( child: Obx(
() => TabBar( () => TabBar(
controller: videoDetailController.tabCtr,
dividerColor: Colors.transparent, dividerColor: Colors.transparent,
indicatorColor:
Theme.of(context).colorScheme.background,
tabs: videoDetailController.tabs tabs: videoDetailController.tabs
.map((String name) => Tab(text: name)) .map((String name) => Tab(text: name))
.toList(), .toList(),
@ -294,6 +317,7 @@ class _VideoDetailPageState extends State<VideoDetailPage>
), ),
Expanded( Expanded(
child: TabBarView( child: TabBarView(
controller: videoDetailController.tabCtr,
children: [ children: [
Builder( Builder(
builder: (context) { builder: (context) {
@ -314,55 +338,55 @@ class _VideoDetailPageState extends State<VideoDetailPage>
), ),
), ),
), ),
// 播放完成/暂停播放 ),
Positioned( // 播放完成/暂停播放
top: -MediaQuery.of(context).padding.top + Positioned(
(doubleOffset / videoHeight) * 50, top: -statusBarHeight +
left: 0, (doubleOffset / (videoHeight - kToolbarHeight)) *
right: 0, (kToolbarHeight - 9),
child: Opacity( left: 0,
opacity: doubleOffset / videoHeight, right: 0,
child: Container( child: Opacity(
height: 50 + MediaQuery.of(context).padding.top, opacity: doubleOffset / (videoHeight - kToolbarHeight),
color: Theme.of(context).colorScheme.background, child: Container(
padding: height: statusBarHeight + kToolbarHeight,
EdgeInsets.only(top: MediaQuery.of(context).padding.top), color: Theme.of(context).colorScheme.background,
child: AppBar( padding: EdgeInsets.only(top: statusBarHeight),
primary: false, child: AppBar(
elevation: 0, primary: false,
scrolledUnderElevation: 0, elevation: 0,
centerTitle: true, scrolledUnderElevation: 0,
title: TextButton( centerTitle: true,
onPressed: () => continuePlay(), title: TextButton(
child: Row( onPressed: () => continuePlay(),
mainAxisSize: MainAxisSize.min, child: Row(
children: [ mainAxisSize: MainAxisSize.min,
const Icon(Icons.play_arrow_rounded), children: [
Text( const Icon(Icons.play_arrow_rounded),
playerStatus == PlayerStatus.paused Text(
? '继续播放' playerStatus == PlayerStatus.paused
: playerStatus == PlayerStatus.completed ? '继续播放'
? '重新播放' : playerStatus == PlayerStatus.completed
: '播放', ? '重新播放'
) : '播放中',
], )
), ],
), ),
actions: [
IconButton(
onPressed: () {},
icon: const Icon(
Icons.share,
size: 20,
)),
const SizedBox(width: 12)
],
), ),
actions: [
IconButton(
onPressed: () {},
icon: const Icon(
Icons.share,
size: 20,
)),
const SizedBox(width: 12)
],
), ),
), ),
), ),
], ),
), ],
), ),
); );
} }