mod: 视频详情页样式
This commit is contained in:
24
lib/common/widgets/sliver_header.dart
Normal file
24
lib/common/widgets/sliver_header.dart
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
class SliverHeaderDelegate extends SliverPersistentHeaderDelegate {
|
||||||
|
SliverHeaderDelegate({required this.height, required this.child});
|
||||||
|
|
||||||
|
final double height;
|
||||||
|
final Widget child;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(
|
||||||
|
BuildContext context, double shrinkOffset, bool overlapsContent) {
|
||||||
|
return child;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
double get maxExtent => height;
|
||||||
|
|
||||||
|
@override
|
||||||
|
double get minExtent => height;
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool shouldRebuild(covariant SliverPersistentHeaderDelegate oldDelegate) =>
|
||||||
|
true;
|
||||||
|
}
|
||||||
@ -1,15 +1,11 @@
|
|||||||
import 'package:flutter/cupertino.dart';
|
|
||||||
import 'package:flutter/gestures.dart';
|
import 'package:flutter/gestures.dart';
|
||||||
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
|
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
|
||||||
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
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/video/detail/index.dart';
|
import 'package:pilipala/pages/video/detail/index.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';
|
||||||
import 'package:pilipala/common/widgets/stat/view.dart';
|
import 'package:pilipala/common/widgets/stat/view.dart';
|
||||||
@ -18,6 +14,9 @@ import 'package:pilipala/pages/video/detail/introduction/controller.dart';
|
|||||||
import 'package:pilipala/utils/storage.dart';
|
import 'package:pilipala/utils/storage.dart';
|
||||||
import 'package:pilipala/utils/utils.dart';
|
import 'package:pilipala/utils/utils.dart';
|
||||||
|
|
||||||
|
import 'widgets/action_row_item.dart';
|
||||||
|
import 'widgets/intro_detail.dart';
|
||||||
|
import 'widgets/menu_row.dart';
|
||||||
import 'widgets/season.dart';
|
import 'widgets/season.dart';
|
||||||
|
|
||||||
class VideoIntroPanel extends StatefulWidget {
|
class VideoIntroPanel extends StatefulWidget {
|
||||||
@ -231,42 +230,63 @@ class _VideoInfoState extends State<VideoInfo> with TickerProviderStateMixin {
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return SliverPadding(
|
return SliverPadding(
|
||||||
padding: const EdgeInsets.only(left: 12, right: 12, top: 10),
|
padding: const EdgeInsets.only(left: 12, right: 12, top: 15),
|
||||||
sliver: SliverToBoxAdapter(
|
sliver: SliverToBoxAdapter(
|
||||||
child: !widget.loadingStatus || videoItem.isNotEmpty
|
child: !widget.loadingStatus || videoItem.isNotEmpty
|
||||||
? Column(
|
? Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
SelectableRegion(
|
Row(
|
||||||
magnifierConfiguration: const TextMagnifierConfiguration(),
|
children: [
|
||||||
focusNode: FocusNode(),
|
Expanded(
|
||||||
selectionControls: MaterialTextSelectionControls(),
|
|
||||||
child: Text(
|
child: Text(
|
||||||
!widget.loadingStatus
|
!widget.loadingStatus
|
||||||
? widget.videoDetail!.title
|
? widget.videoDetail!.title
|
||||||
: videoItem['title'],
|
: videoItem['title'],
|
||||||
style: Theme.of(context).textTheme.titleMedium!.copyWith(
|
style: Theme.of(context)
|
||||||
|
.textTheme
|
||||||
|
.titleMedium!
|
||||||
|
.copyWith(
|
||||||
letterSpacing: 0.5,
|
letterSpacing: 0.5,
|
||||||
),
|
fontWeight: FontWeight.bold),
|
||||||
maxLines: 1,
|
maxLines: 2,
|
||||||
overflow: TextOverflow.ellipsis,
|
overflow: TextOverflow.ellipsis,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
InkWell(
|
const SizedBox(width: 20),
|
||||||
splashColor: Colors.transparent,
|
SizedBox(
|
||||||
hoverColor: Colors.transparent,
|
width: 34,
|
||||||
highlightColor: Colors.transparent,
|
height: 34,
|
||||||
onTap: () {
|
child: IconButton(
|
||||||
_manualController!.animateTo(isExpand ? 0 : 0.5);
|
style: ButtonStyle(
|
||||||
setState(() {
|
padding: MaterialStateProperty.all(EdgeInsets.zero),
|
||||||
isExpand = !isExpand;
|
backgroundColor:
|
||||||
|
MaterialStateProperty.resolveWith((states) {
|
||||||
|
return Theme.of(context)
|
||||||
|
.highlightColor
|
||||||
|
.withOpacity(0.2);
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
onPressed: () {
|
||||||
|
showBottomSheet(
|
||||||
|
context: context,
|
||||||
|
enableDrag: true,
|
||||||
|
builder: (BuildContext context) {
|
||||||
|
return IntroDetail(
|
||||||
|
videoDetail: widget.videoDetail!);
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
child: Row(
|
icon: const Icon(Icons.more_horiz),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
const SizedBox(height: 6),
|
||||||
|
Row(
|
||||||
children: [
|
children: [
|
||||||
const SizedBox(width: 2),
|
const SizedBox(width: 2),
|
||||||
StatView(
|
StatView(
|
||||||
theme: 'gray',
|
theme: 'black',
|
||||||
view: !widget.loadingStatus
|
view: !widget.loadingStatus
|
||||||
? widget.videoDetail!.stat!.view
|
? widget.videoDetail!.stat!.view
|
||||||
: videoItem['stat'].view,
|
: videoItem['stat'].view,
|
||||||
@ -274,7 +294,7 @@ class _VideoInfoState extends State<VideoInfo> with TickerProviderStateMixin {
|
|||||||
),
|
),
|
||||||
const SizedBox(width: 10),
|
const SizedBox(width: 10),
|
||||||
StatDanMu(
|
StatDanMu(
|
||||||
theme: 'gray',
|
theme: 'black',
|
||||||
danmu: !widget.loadingStatus
|
danmu: !widget.loadingStatus
|
||||||
? widget.videoDetail!.stat!.danmaku
|
? widget.videoDetail!.stat!.danmaku
|
||||||
: videoItem['stat'].danmaku,
|
: videoItem['stat'].danmaku,
|
||||||
@ -287,79 +307,22 @@ class _VideoInfoState extends State<VideoInfo> with TickerProviderStateMixin {
|
|||||||
? widget.videoDetail!.pubdate
|
? widget.videoDetail!.pubdate
|
||||||
: videoItem['pubdate'],
|
: videoItem['pubdate'],
|
||||||
formatType: 'detail'),
|
formatType: 'detail'),
|
||||||
style: TextStyle(
|
style: const TextStyle(fontSize: 12),
|
||||||
fontSize: 12,
|
|
||||||
color: Theme.of(context).colorScheme.outline),
|
|
||||||
),
|
|
||||||
const Spacer(),
|
|
||||||
RotationTransition(
|
|
||||||
turns: _manualAnimation!,
|
|
||||||
child: SizedBox(
|
|
||||||
width: 35,
|
|
||||||
height: 35,
|
|
||||||
child: IconButton(
|
|
||||||
padding: const EdgeInsets.all(2.0),
|
|
||||||
onPressed: () {
|
|
||||||
/// 0.5代表 180弧度
|
|
||||||
_manualController!
|
|
||||||
.animateTo(isExpand ? 0 : 0.5);
|
|
||||||
setState(() {
|
|
||||||
isExpand = !isExpand;
|
|
||||||
});
|
|
||||||
},
|
|
||||||
icon: Icon(
|
|
||||||
FontAwesomeIcons.angleUp,
|
|
||||||
size: 15,
|
|
||||||
color: Theme.of(context).colorScheme.outline,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(width: 10),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
// 简介 默认收起
|
|
||||||
if (!widget.loadingStatus)
|
|
||||||
ExpandedSection(
|
|
||||||
expand: isExpand,
|
|
||||||
begin: 0.0,
|
|
||||||
end: 1.0,
|
|
||||||
child: DefaultTextStyle(
|
|
||||||
style: TextStyle(
|
|
||||||
color: Theme.of(context).colorScheme.outline,
|
|
||||||
height: 1.5,
|
|
||||||
fontSize:
|
|
||||||
Theme.of(context).textTheme.labelMedium?.fontSize,
|
|
||||||
),
|
|
||||||
child: Padding(
|
|
||||||
padding: const EdgeInsets.only(bottom: 10),
|
|
||||||
child: SelectableRegion(
|
|
||||||
magnifierConfiguration:
|
|
||||||
const TextMagnifierConfiguration(),
|
|
||||||
focusNode: FocusNode(),
|
|
||||||
selectionControls: MaterialTextSelectionControls(),
|
|
||||||
child: Column(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: [
|
|
||||||
Text(widget.videoDetail!.bvid!),
|
|
||||||
Text.rich(
|
|
||||||
TextSpan(
|
|
||||||
children: [
|
|
||||||
buildContent(
|
|
||||||
context, widget.videoDetail!),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(height: 8),
|
|
||||||
// 点赞收藏转发
|
// 点赞收藏转发
|
||||||
_actionGrid(context, videoIntroController, videoDetailCtr),
|
Padding(
|
||||||
|
padding: const EdgeInsets.only(top: 20),
|
||||||
|
child: SingleChildScrollView(
|
||||||
|
scrollDirection: Axis.horizontal,
|
||||||
|
child: actionRow(
|
||||||
|
context,
|
||||||
|
videoIntroController,
|
||||||
|
videoDetailCtr,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
// 合集
|
// 合集
|
||||||
if (!widget.loadingStatus &&
|
if (!widget.loadingStatus &&
|
||||||
widget.videoDetail!.ugcSeason != null) ...[
|
widget.videoDetail!.ugcSeason != null) ...[
|
||||||
@ -390,12 +353,12 @@ class _VideoInfoState extends State<VideoInfo> with TickerProviderStateMixin {
|
|||||||
src: !widget.loadingStatus
|
src: !widget.loadingStatus
|
||||||
? widget.videoDetail!.owner!.face
|
? widget.videoDetail!.owner!.face
|
||||||
: videoItem['owner'].face,
|
: videoItem['owner'].face,
|
||||||
width: 38,
|
width: 34,
|
||||||
height: 38,
|
height: 34,
|
||||||
fadeInDuration: Duration.zero,
|
fadeInDuration: Duration.zero,
|
||||||
fadeOutDuration: Duration.zero,
|
fadeOutDuration: Duration.zero,
|
||||||
),
|
),
|
||||||
const SizedBox(width: 14),
|
const SizedBox(width: 10),
|
||||||
Column(
|
Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
@ -421,7 +384,7 @@ class _VideoInfoState extends State<VideoInfo> with TickerProviderStateMixin {
|
|||||||
opacity: widget.loadingStatus ? 0 : 1,
|
opacity: widget.loadingStatus ? 0 : 1,
|
||||||
duration: const Duration(milliseconds: 150),
|
duration: const Duration(milliseconds: 150),
|
||||||
child: SizedBox(
|
child: SizedBox(
|
||||||
height: 36,
|
height: 34,
|
||||||
child: Obx(
|
child: Obx(
|
||||||
() => videoIntroController.followStatus.isNotEmpty
|
() => videoIntroController.followStatus.isNotEmpty
|
||||||
? ElevatedButton(
|
? ElevatedButton(
|
||||||
@ -444,94 +407,63 @@ class _VideoInfoState extends State<VideoInfo> with TickerProviderStateMixin {
|
|||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
const SizedBox(height: 8),
|
||||||
Divider(
|
Divider(
|
||||||
height: 26,
|
height: 12,
|
||||||
color: Theme.of(context).dividerColor.withOpacity(0.1),
|
color: Theme.of(context).dividerColor.withOpacity(0.1),
|
||||||
),
|
),
|
||||||
// const SizedBox(height: 10),
|
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
: const Center(child: CircularProgressIndicator()),
|
: const SizedBox(
|
||||||
|
height: 100,
|
||||||
|
child: Center(
|
||||||
|
child: CircularProgressIndicator(),
|
||||||
|
),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 喜欢 投币 分享
|
Widget actionRow(BuildContext context, videoIntroController, videoDetailCtr) {
|
||||||
Widget _actionGrid(
|
return Row(children: [
|
||||||
BuildContext context, videoIntroController, videoDetailCtr) {
|
|
||||||
return LayoutBuilder(builder: (context, constraints) {
|
|
||||||
return SizedBox(
|
|
||||||
height: constraints.maxWidth / 5 * 0.8,
|
|
||||||
child: Material(
|
|
||||||
child: GridView.count(
|
|
||||||
primary: false,
|
|
||||||
padding: const EdgeInsets.all(0),
|
|
||||||
crossAxisCount: 5,
|
|
||||||
childAspectRatio: 1.25,
|
|
||||||
children: <Widget>[
|
|
||||||
// InkWell(
|
|
||||||
// onTap: () => videoIntroController.actionOneThree(),
|
|
||||||
// borderRadius: StyleString.mdRadius,
|
|
||||||
// child: Padding(
|
|
||||||
// padding: const EdgeInsets.all(12),
|
|
||||||
// child: Image.asset(
|
|
||||||
// 'assets/images/logo/logo_big.png',
|
|
||||||
// width: 10,
|
|
||||||
// height: 10,
|
|
||||||
// ),
|
|
||||||
// ),
|
|
||||||
// ),
|
|
||||||
Obx(
|
Obx(
|
||||||
() => ActionItem(
|
() => ActionRowItem(
|
||||||
icon: const Icon(FontAwesomeIcons.thumbsUp),
|
icon: const Icon(FontAwesomeIcons.thumbsUp),
|
||||||
selectIcon: const Icon(FontAwesomeIcons.solidThumbsUp),
|
|
||||||
onTap: () => videoIntroController.actionLikeVideo(),
|
onTap: () => videoIntroController.actionLikeVideo(),
|
||||||
selectStatus: videoIntroController.hasLike.value,
|
selectStatus: videoIntroController.hasLike.value,
|
||||||
loadingStatus: widget.loadingStatus,
|
loadingStatus: widget.loadingStatus,
|
||||||
text: !widget.loadingStatus
|
text: !widget.loadingStatus
|
||||||
? widget.videoDetail!.stat!.like!.toString()
|
? widget.videoDetail!.stat!.like!.toString()
|
||||||
: '-'),
|
: '-',
|
||||||
),
|
),
|
||||||
// ActionItem(
|
),
|
||||||
// icon: const Icon(FontAwesomeIcons.thumbsDown),
|
const SizedBox(width: 8),
|
||||||
// selectIcon: const Icon(FontAwesomeIcons.solidThumbsDown),
|
|
||||||
// onTap: () => {},
|
|
||||||
// selectStatus: false,
|
|
||||||
// loadingStatus: widget.loadingStatus,
|
|
||||||
// text: '不喜欢'),
|
|
||||||
Obx(
|
Obx(
|
||||||
() => ActionItem(
|
() => ActionRowItem(
|
||||||
icon: const Icon(FontAwesomeIcons.b),
|
icon: const Icon(FontAwesomeIcons.b),
|
||||||
selectIcon: const Icon(FontAwesomeIcons.b),
|
|
||||||
onTap: () => videoIntroController.actionCoinVideo(),
|
onTap: () => videoIntroController.actionCoinVideo(),
|
||||||
selectStatus: videoIntroController.hasCoin.value,
|
selectStatus: videoIntroController.hasCoin.value,
|
||||||
loadingStatus: widget.loadingStatus,
|
loadingStatus: widget.loadingStatus,
|
||||||
text: !widget.loadingStatus
|
text: !widget.loadingStatus
|
||||||
? widget.videoDetail!.stat!.coin!.toString()
|
? widget.videoDetail!.stat!.coin!.toString()
|
||||||
: '-'),
|
: '-',
|
||||||
),
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(width: 8),
|
||||||
Obx(
|
Obx(
|
||||||
() => ActionItem(
|
() => ActionRowItem(
|
||||||
icon: const Icon(FontAwesomeIcons.heart),
|
icon: const Icon(FontAwesomeIcons.heart),
|
||||||
selectIcon: const Icon(FontAwesomeIcons.heartCircleCheck),
|
|
||||||
onTap: () => showFavBottomSheet(),
|
onTap: () => showFavBottomSheet(),
|
||||||
selectStatus: videoIntroController.hasFav.value,
|
selectStatus: videoIntroController.hasFav.value,
|
||||||
loadingStatus: widget.loadingStatus,
|
loadingStatus: widget.loadingStatus,
|
||||||
text: !widget.loadingStatus
|
text: !widget.loadingStatus
|
||||||
? widget.videoDetail!.stat!.favorite!.toString()
|
? widget.videoDetail!.stat!.favorite!.toString()
|
||||||
: '-'),
|
: '-',
|
||||||
),
|
),
|
||||||
ActionItem(
|
),
|
||||||
icon: const Icon(FontAwesomeIcons.shareFromSquare),
|
const SizedBox(width: 8),
|
||||||
onTap: () => videoIntroController.actionShareVideo(),
|
ActionRowItem(
|
||||||
selectStatus: false,
|
icon: const Icon(FontAwesomeIcons.comment),
|
||||||
loadingStatus: widget.loadingStatus,
|
|
||||||
text: !widget.loadingStatus
|
|
||||||
? widget.videoDetail!.stat!.share!.toString()
|
|
||||||
: '-'),
|
|
||||||
ActionItem(
|
|
||||||
icon: const Icon(FontAwesomeIcons.comments),
|
|
||||||
onTap: () {
|
onTap: () {
|
||||||
videoDetailCtr.tabCtr.animateTo(1);
|
videoDetailCtr.tabCtr.animateTo(1);
|
||||||
},
|
},
|
||||||
@ -539,12 +471,19 @@ class _VideoInfoState extends State<VideoInfo> with TickerProviderStateMixin {
|
|||||||
loadingStatus: widget.loadingStatus,
|
loadingStatus: widget.loadingStatus,
|
||||||
text: !widget.loadingStatus
|
text: !widget.loadingStatus
|
||||||
? widget.videoDetail!.stat!.reply!.toString()
|
? widget.videoDetail!.stat!.reply!.toString()
|
||||||
: '-'),
|
: '-',
|
||||||
],
|
|
||||||
),
|
),
|
||||||
|
const SizedBox(width: 8),
|
||||||
|
ActionRowItem(
|
||||||
|
icon: const Icon(FontAwesomeIcons.share),
|
||||||
|
onTap: () => videoIntroController.actionShareVideo(),
|
||||||
|
selectStatus: false,
|
||||||
|
loadingStatus: widget.loadingStatus,
|
||||||
|
text: !widget.loadingStatus
|
||||||
|
? widget.videoDetail!.stat!.share!.toString()
|
||||||
|
: '-',
|
||||||
),
|
),
|
||||||
);
|
]);
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
InlineSpan buildContent(BuildContext context, content) {
|
InlineSpan buildContent(BuildContext context, content) {
|
||||||
@ -583,54 +522,3 @@ class _VideoInfoState extends State<VideoInfo> with TickerProviderStateMixin {
|
|||||||
return TextSpan(children: spanChilds);
|
return TextSpan(children: spanChilds);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class ActionItem extends StatelessWidget {
|
|
||||||
Icon? icon;
|
|
||||||
Icon? selectIcon;
|
|
||||||
Function? onTap;
|
|
||||||
bool? loadingStatus;
|
|
||||||
String? text;
|
|
||||||
bool selectStatus = false;
|
|
||||||
|
|
||||||
ActionItem({
|
|
||||||
Key? key,
|
|
||||||
this.icon,
|
|
||||||
this.selectIcon,
|
|
||||||
this.onTap,
|
|
||||||
this.loadingStatus,
|
|
||||||
this.text,
|
|
||||||
required this.selectStatus,
|
|
||||||
}) : super(key: key);
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return InkWell(
|
|
||||||
onTap: () => onTap!(),
|
|
||||||
borderRadius: StyleString.mdRadius,
|
|
||||||
child: Column(
|
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
|
||||||
children: [
|
|
||||||
const SizedBox(height: 4),
|
|
||||||
selectStatus
|
|
||||||
? Icon(selectIcon!.icon!,
|
|
||||||
size: 21, color: Theme.of(context).primaryColor)
|
|
||||||
: Icon(icon!.icon!,
|
|
||||||
size: 21, color: Theme.of(context).colorScheme.outline),
|
|
||||||
const SizedBox(height: 4),
|
|
||||||
AnimatedOpacity(
|
|
||||||
opacity: loadingStatus! ? 0 : 1,
|
|
||||||
duration: const Duration(milliseconds: 200),
|
|
||||||
child: Text(
|
|
||||||
text ?? '',
|
|
||||||
style: TextStyle(
|
|
||||||
color: selectStatus
|
|
||||||
? Theme.of(context).primaryColor
|
|
||||||
: Theme.of(context).colorScheme.outline,
|
|
||||||
fontSize: Theme.of(context).textTheme.labelSmall?.fontSize),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
53
lib/pages/video/detail/introduction/widgets/action_item.dart
Normal file
53
lib/pages/video/detail/introduction/widgets/action_item.dart
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:pilipala/common/constants.dart';
|
||||||
|
|
||||||
|
class ActionItem extends StatelessWidget {
|
||||||
|
Icon? icon;
|
||||||
|
Icon? selectIcon;
|
||||||
|
Function? onTap;
|
||||||
|
bool? loadingStatus;
|
||||||
|
String? text;
|
||||||
|
bool selectStatus = false;
|
||||||
|
|
||||||
|
ActionItem({
|
||||||
|
Key? key,
|
||||||
|
this.icon,
|
||||||
|
this.selectIcon,
|
||||||
|
this.onTap,
|
||||||
|
this.loadingStatus,
|
||||||
|
this.text,
|
||||||
|
required this.selectStatus,
|
||||||
|
}) : super(key: key);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return InkWell(
|
||||||
|
onTap: () => onTap!(),
|
||||||
|
borderRadius: StyleString.mdRadius,
|
||||||
|
child: Column(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
const SizedBox(height: 4),
|
||||||
|
selectStatus
|
||||||
|
? Icon(selectIcon!.icon!,
|
||||||
|
size: 21, color: Theme.of(context).primaryColor)
|
||||||
|
: Icon(icon!.icon!,
|
||||||
|
size: 21, color: Theme.of(context).colorScheme.outline),
|
||||||
|
const SizedBox(height: 4),
|
||||||
|
AnimatedOpacity(
|
||||||
|
opacity: loadingStatus! ? 0 : 1,
|
||||||
|
duration: const Duration(milliseconds: 200),
|
||||||
|
child: Text(
|
||||||
|
text ?? '',
|
||||||
|
style: TextStyle(
|
||||||
|
color: selectStatus
|
||||||
|
? Theme.of(context).primaryColor
|
||||||
|
: Theme.of(context).colorScheme.outline,
|
||||||
|
fontSize: Theme.of(context).textTheme.labelSmall?.fontSize),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,64 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:pilipala/common/constants.dart';
|
||||||
|
|
||||||
|
class ActionRowItem extends StatelessWidget {
|
||||||
|
Icon? icon;
|
||||||
|
Icon? selectIcon;
|
||||||
|
Function? onTap;
|
||||||
|
bool? loadingStatus;
|
||||||
|
String? text;
|
||||||
|
bool selectStatus = false;
|
||||||
|
|
||||||
|
ActionRowItem({
|
||||||
|
Key? key,
|
||||||
|
this.icon,
|
||||||
|
this.selectIcon,
|
||||||
|
this.onTap,
|
||||||
|
this.loadingStatus,
|
||||||
|
this.text,
|
||||||
|
required this.selectStatus,
|
||||||
|
}) : super(key: key);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Material(
|
||||||
|
color: selectStatus
|
||||||
|
? Theme.of(context).colorScheme.primaryContainer.withOpacity(0.6)
|
||||||
|
: Theme.of(context).highlightColor.withOpacity(0.2),
|
||||||
|
borderRadius: const BorderRadius.all(Radius.circular(30)),
|
||||||
|
clipBehavior: Clip.hardEdge,
|
||||||
|
child: InkWell(
|
||||||
|
onTap: () => onTap!(),
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.fromLTRB(13, 6.5, 15, 6.3),
|
||||||
|
child: Row(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
if (icon != null) ...[
|
||||||
|
Icon(icon!.icon!,
|
||||||
|
size: 13,
|
||||||
|
color: selectStatus
|
||||||
|
? Theme.of(context).colorScheme.primary
|
||||||
|
: Theme.of(context).colorScheme.onSecondaryContainer),
|
||||||
|
const SizedBox(width: 6),
|
||||||
|
],
|
||||||
|
AnimatedOpacity(
|
||||||
|
opacity: loadingStatus! ? 0 : 1,
|
||||||
|
duration: const Duration(milliseconds: 200),
|
||||||
|
child: Text(
|
||||||
|
text ?? '',
|
||||||
|
style: TextStyle(
|
||||||
|
color: selectStatus
|
||||||
|
? Theme.of(context).colorScheme.primary
|
||||||
|
: null,
|
||||||
|
fontSize:
|
||||||
|
Theme.of(context).textTheme.labelMedium?.fontSize),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
140
lib/pages/video/detail/introduction/widgets/intro_detail.dart
Normal file
140
lib/pages/video/detail/introduction/widgets/intro_detail.dart
Normal file
@ -0,0 +1,140 @@
|
|||||||
|
import 'package:flutter/gestures.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:get/get.dart';
|
||||||
|
import 'package:pilipala/common/widgets/stat/danmu.dart';
|
||||||
|
import 'package:pilipala/common/widgets/stat/view.dart';
|
||||||
|
import 'package:pilipala/utils/utils.dart';
|
||||||
|
|
||||||
|
class IntroDetail extends StatelessWidget {
|
||||||
|
var videoDetail;
|
||||||
|
|
||||||
|
IntroDetail({
|
||||||
|
Key? key,
|
||||||
|
this.videoDetail,
|
||||||
|
}) : super(key: key);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Container(
|
||||||
|
color: Theme.of(context).colorScheme.background,
|
||||||
|
padding: const EdgeInsets.only(left: 14, right: 14),
|
||||||
|
height: 570,
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
Container(
|
||||||
|
height: 25,
|
||||||
|
padding: const EdgeInsets.only(bottom: 2),
|
||||||
|
child: Center(
|
||||||
|
child: Container(
|
||||||
|
width: 40,
|
||||||
|
height: 3,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: Theme.of(context)
|
||||||
|
.colorScheme
|
||||||
|
.onSecondaryContainer
|
||||||
|
.withOpacity(0.5),
|
||||||
|
borderRadius: const BorderRadius.all(Radius.circular(3))),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Expanded(
|
||||||
|
child: SingleChildScrollView(
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
videoDetail!.title,
|
||||||
|
style: Theme.of(context).textTheme.titleMedium!.copyWith(
|
||||||
|
letterSpacing: 0.5, fontWeight: FontWeight.bold),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 10),
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
const SizedBox(width: 2),
|
||||||
|
StatView(
|
||||||
|
theme: 'black',
|
||||||
|
view: videoDetail!.stat!.view,
|
||||||
|
size: 'medium',
|
||||||
|
),
|
||||||
|
const SizedBox(width: 10),
|
||||||
|
StatDanMu(
|
||||||
|
theme: 'black',
|
||||||
|
danmu: videoDetail!.stat!.danmaku,
|
||||||
|
size: 'medium',
|
||||||
|
),
|
||||||
|
const SizedBox(width: 10),
|
||||||
|
Text(
|
||||||
|
Utils.dateFormat(videoDetail!.pubdate,
|
||||||
|
formatType: 'detail'),
|
||||||
|
style: const TextStyle(fontSize: 12),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
const SizedBox(height: 20),
|
||||||
|
SizedBox(
|
||||||
|
width: double.infinity,
|
||||||
|
child: SelectableRegion(
|
||||||
|
magnifierConfiguration:
|
||||||
|
const TextMagnifierConfiguration(),
|
||||||
|
focusNode: FocusNode(),
|
||||||
|
selectionControls: MaterialTextSelectionControls(),
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text(videoDetail!.bvid!),
|
||||||
|
const SizedBox(height: 4),
|
||||||
|
Text.rich(
|
||||||
|
TextSpan(
|
||||||
|
children: [
|
||||||
|
buildContent(context, videoDetail!),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
InlineSpan buildContent(BuildContext context, content) {
|
||||||
|
String desc = content.desc;
|
||||||
|
List descV2 = content.descV2;
|
||||||
|
// type
|
||||||
|
// 1 普通文本
|
||||||
|
// 2 @用户
|
||||||
|
List<InlineSpan> spanChilds = [];
|
||||||
|
if (descV2.isNotEmpty) {
|
||||||
|
for (var i = 0; i < descV2.length; i++) {
|
||||||
|
if (descV2[i].type == 1) {
|
||||||
|
spanChilds.add(TextSpan(text: descV2[i].rawText));
|
||||||
|
} else if (descV2[i].type == 2) {
|
||||||
|
spanChilds.add(
|
||||||
|
TextSpan(
|
||||||
|
text: '@${descV2[i].rawText}',
|
||||||
|
style: TextStyle(
|
||||||
|
color: Theme.of(context).colorScheme.primary,
|
||||||
|
),
|
||||||
|
recognizer: TapGestureRecognizer()
|
||||||
|
..onTap = () {
|
||||||
|
String heroTag = Utils.makeHeroTag(descV2[i].bizId);
|
||||||
|
Get.toNamed(
|
||||||
|
'/member?mid=${descV2[i].bizId}',
|
||||||
|
arguments: {'face': '', 'heroTag': heroTag},
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
spanChilds.add(TextSpan(text: desc));
|
||||||
|
}
|
||||||
|
return TextSpan(children: spanChilds);
|
||||||
|
}
|
||||||
|
}
|
||||||
95
lib/pages/video/detail/introduction/widgets/menu_row.dart
Normal file
95
lib/pages/video/detail/introduction/widgets/menu_row.dart
Normal file
@ -0,0 +1,95 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
class MenuRow extends StatelessWidget {
|
||||||
|
bool? loadingStatus;
|
||||||
|
MenuRow({
|
||||||
|
Key? key,
|
||||||
|
this.loadingStatus,
|
||||||
|
}) : super(key: key);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Container(
|
||||||
|
width: double.infinity,
|
||||||
|
color: Theme.of(context).colorScheme.background,
|
||||||
|
padding: const EdgeInsets.only(top: 9, bottom: 9, left: 12),
|
||||||
|
child: SingleChildScrollView(
|
||||||
|
scrollDirection: Axis.horizontal,
|
||||||
|
child: Row(children: [
|
||||||
|
actionRowLineItem(
|
||||||
|
context,
|
||||||
|
() => {},
|
||||||
|
loadingStatus,
|
||||||
|
'推荐',
|
||||||
|
selectStatus: true,
|
||||||
|
),
|
||||||
|
const SizedBox(width: 8),
|
||||||
|
actionRowLineItem(
|
||||||
|
context,
|
||||||
|
() => {},
|
||||||
|
loadingStatus,
|
||||||
|
'弹幕',
|
||||||
|
selectStatus: false,
|
||||||
|
),
|
||||||
|
const SizedBox(width: 8),
|
||||||
|
actionRowLineItem(
|
||||||
|
context,
|
||||||
|
() => {},
|
||||||
|
loadingStatus,
|
||||||
|
'评论列表',
|
||||||
|
selectStatus: false,
|
||||||
|
),
|
||||||
|
const SizedBox(width: 8),
|
||||||
|
actionRowLineItem(
|
||||||
|
context,
|
||||||
|
() => {},
|
||||||
|
loadingStatus,
|
||||||
|
'播放列表',
|
||||||
|
selectStatus: false,
|
||||||
|
),
|
||||||
|
]),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget actionRowLineItem(
|
||||||
|
context, Function? onTap, bool? loadingStatus, String? text,
|
||||||
|
{bool selectStatus = false}) {
|
||||||
|
return Material(
|
||||||
|
color: selectStatus
|
||||||
|
? Theme.of(context).highlightColor.withOpacity(0.2)
|
||||||
|
: Colors.transparent,
|
||||||
|
borderRadius: const BorderRadius.all(Radius.circular(30)),
|
||||||
|
clipBehavior: Clip.hardEdge,
|
||||||
|
child: InkWell(
|
||||||
|
onTap: () => onTap!(),
|
||||||
|
child: Container(
|
||||||
|
padding: const EdgeInsets.fromLTRB(13, 5.5, 13, 5.5),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
borderRadius: const BorderRadius.all(Radius.circular(30)),
|
||||||
|
border: Border.all(
|
||||||
|
color: selectStatus
|
||||||
|
? Colors.transparent
|
||||||
|
: Theme.of(context).highlightColor.withOpacity(0.2),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
child: Row(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
AnimatedOpacity(
|
||||||
|
opacity: loadingStatus! ? 0 : 1,
|
||||||
|
duration: const Duration(milliseconds: 200),
|
||||||
|
child: Text(
|
||||||
|
text!,
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize:
|
||||||
|
Theme.of(context).textTheme.labelMedium?.fontSize),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -5,6 +5,8 @@ import 'package:flutter_meedu_media_kit/meedu_player.dart';
|
|||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:pilipala/common/widgets/network_img_layer.dart';
|
import 'package:pilipala/common/widgets/network_img_layer.dart';
|
||||||
|
import 'package:pilipala/common/widgets/sliver_header.dart';
|
||||||
|
import 'package:pilipala/pages/video/detail/introduction/widgets/menu_row.dart';
|
||||||
import 'package:pilipala/pages/video/detail/reply/index.dart';
|
import 'package:pilipala/pages/video/detail/reply/index.dart';
|
||||||
import 'package:pilipala/pages/video/detail/controller.dart';
|
import 'package:pilipala/pages/video/detail/controller.dart';
|
||||||
import 'package:pilipala/pages/video/detail/introduction/index.dart';
|
import 'package:pilipala/pages/video/detail/introduction/index.dart';
|
||||||
@ -234,7 +236,7 @@ class _VideoDetailPageState extends State<VideoDetailPage>
|
|||||||
children: [
|
children: [
|
||||||
Container(
|
Container(
|
||||||
width: double.infinity,
|
width: double.infinity,
|
||||||
height: 45,
|
height: 0,
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
border: Border(
|
border: Border(
|
||||||
bottom: BorderSide(
|
bottom: BorderSide(
|
||||||
@ -254,8 +256,8 @@ class _VideoDetailPageState extends State<VideoDetailPage>
|
|||||||
() => TabBar(
|
() => TabBar(
|
||||||
controller: videoDetailController.tabCtr,
|
controller: videoDetailController.tabCtr,
|
||||||
dividerColor: Colors.transparent,
|
dividerColor: Colors.transparent,
|
||||||
// indicatorColor:
|
indicatorColor:
|
||||||
// Theme.of(context).colorScheme.background,
|
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(),
|
||||||
@ -271,11 +273,19 @@ class _VideoDetailPageState extends State<VideoDetailPage>
|
|||||||
children: [
|
children: [
|
||||||
Builder(
|
Builder(
|
||||||
builder: (context) {
|
builder: (context) {
|
||||||
return const CustomScrollView(
|
return CustomScrollView(
|
||||||
key: PageStorageKey<String>('简介'),
|
key: const PageStorageKey<String>('简介'),
|
||||||
slivers: <Widget>[
|
slivers: <Widget>[
|
||||||
VideoIntroPanel(),
|
const VideoIntroPanel(),
|
||||||
RelatedVideoPanel(),
|
SliverPersistentHeader(
|
||||||
|
floating: true,
|
||||||
|
pinned: true,
|
||||||
|
delegate: SliverHeaderDelegate(
|
||||||
|
height: 50,
|
||||||
|
child: MenuRow(loadingStatus: false),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const RelatedVideoPanel(),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|||||||
Reference in New Issue
Block a user