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_smart_dialog/flutter_smart_dialog.dart';
|
||||
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:pilipala/common/constants.dart';
|
||||
import 'package:pilipala/common/widgets/http_error.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/widgets/expandable_section.dart';
|
||||
import 'package:pilipala/common/widgets/network_img_layer.dart';
|
||||
import 'package:pilipala/common/widgets/stat/danmu.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/utils.dart';
|
||||
|
||||
import 'widgets/action_row_item.dart';
|
||||
import 'widgets/intro_detail.dart';
|
||||
import 'widgets/menu_row.dart';
|
||||
import 'widgets/season.dart';
|
||||
|
||||
class VideoIntroPanel extends StatefulWidget {
|
||||
@ -231,135 +230,99 @@ class _VideoInfoState extends State<VideoInfo> with TickerProviderStateMixin {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return SliverPadding(
|
||||
padding: const EdgeInsets.only(left: 12, right: 12, top: 10),
|
||||
padding: const EdgeInsets.only(left: 12, right: 12, top: 15),
|
||||
sliver: SliverToBoxAdapter(
|
||||
child: !widget.loadingStatus || videoItem.isNotEmpty
|
||||
? Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
SelectableRegion(
|
||||
magnifierConfiguration: const TextMagnifierConfiguration(),
|
||||
focusNode: FocusNode(),
|
||||
selectionControls: MaterialTextSelectionControls(),
|
||||
child: Text(
|
||||
!widget.loadingStatus
|
||||
? widget.videoDetail!.title
|
||||
: videoItem['title'],
|
||||
style: Theme.of(context).textTheme.titleMedium!.copyWith(
|
||||
letterSpacing: 0.5,
|
||||
),
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
),
|
||||
InkWell(
|
||||
splashColor: Colors.transparent,
|
||||
hoverColor: Colors.transparent,
|
||||
highlightColor: Colors.transparent,
|
||||
onTap: () {
|
||||
_manualController!.animateTo(isExpand ? 0 : 0.5);
|
||||
setState(() {
|
||||
isExpand = !isExpand;
|
||||
});
|
||||
},
|
||||
child: Row(
|
||||
children: [
|
||||
const SizedBox(width: 2),
|
||||
StatView(
|
||||
theme: 'gray',
|
||||
view: !widget.loadingStatus
|
||||
? widget.videoDetail!.stat!.view
|
||||
: videoItem['stat'].view,
|
||||
size: 'medium',
|
||||
),
|
||||
const SizedBox(width: 10),
|
||||
StatDanMu(
|
||||
theme: 'gray',
|
||||
danmu: !widget.loadingStatus
|
||||
? widget.videoDetail!.stat!.danmaku
|
||||
: videoItem['stat'].danmaku,
|
||||
size: 'medium',
|
||||
),
|
||||
const SizedBox(width: 10),
|
||||
Text(
|
||||
Utils.dateFormat(
|
||||
!widget.loadingStatus
|
||||
? widget.videoDetail!.pubdate
|
||||
: videoItem['pubdate'],
|
||||
formatType: 'detail'),
|
||||
style: TextStyle(
|
||||
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!),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: Text(
|
||||
!widget.loadingStatus
|
||||
? widget.videoDetail!.title
|
||||
: videoItem['title'],
|
||||
style: Theme.of(context)
|
||||
.textTheme
|
||||
.titleMedium!
|
||||
.copyWith(
|
||||
letterSpacing: 0.5,
|
||||
fontWeight: FontWeight.bold),
|
||||
maxLines: 2,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
const SizedBox(width: 20),
|
||||
SizedBox(
|
||||
width: 34,
|
||||
height: 34,
|
||||
child: IconButton(
|
||||
style: ButtonStyle(
|
||||
padding: MaterialStateProperty.all(EdgeInsets.zero),
|
||||
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!);
|
||||
});
|
||||
},
|
||||
icon: const Icon(Icons.more_horiz),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 6),
|
||||
Row(
|
||||
children: [
|
||||
const SizedBox(width: 2),
|
||||
StatView(
|
||||
theme: 'black',
|
||||
view: !widget.loadingStatus
|
||||
? widget.videoDetail!.stat!.view
|
||||
: videoItem['stat'].view,
|
||||
size: 'medium',
|
||||
),
|
||||
const SizedBox(width: 10),
|
||||
StatDanMu(
|
||||
theme: 'black',
|
||||
danmu: !widget.loadingStatus
|
||||
? widget.videoDetail!.stat!.danmaku
|
||||
: videoItem['stat'].danmaku,
|
||||
size: 'medium',
|
||||
),
|
||||
const SizedBox(width: 10),
|
||||
Text(
|
||||
Utils.dateFormat(
|
||||
!widget.loadingStatus
|
||||
? widget.videoDetail!.pubdate
|
||||
: videoItem['pubdate'],
|
||||
formatType: 'detail'),
|
||||
style: const TextStyle(fontSize: 12),
|
||||
),
|
||||
],
|
||||
),
|
||||
// 点赞收藏转发
|
||||
_actionGrid(context, videoIntroController, videoDetailCtr),
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(top: 20),
|
||||
child: SingleChildScrollView(
|
||||
scrollDirection: Axis.horizontal,
|
||||
child: actionRow(
|
||||
context,
|
||||
videoIntroController,
|
||||
videoDetailCtr,
|
||||
),
|
||||
),
|
||||
),
|
||||
// 合集
|
||||
if (!widget.loadingStatus &&
|
||||
widget.videoDetail!.ugcSeason != null) ...[
|
||||
@ -390,12 +353,12 @@ class _VideoInfoState extends State<VideoInfo> with TickerProviderStateMixin {
|
||||
src: !widget.loadingStatus
|
||||
? widget.videoDetail!.owner!.face
|
||||
: videoItem['owner'].face,
|
||||
width: 38,
|
||||
height: 38,
|
||||
width: 34,
|
||||
height: 34,
|
||||
fadeInDuration: Duration.zero,
|
||||
fadeOutDuration: Duration.zero,
|
||||
),
|
||||
const SizedBox(width: 14),
|
||||
const SizedBox(width: 10),
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
@ -421,7 +384,7 @@ class _VideoInfoState extends State<VideoInfo> with TickerProviderStateMixin {
|
||||
opacity: widget.loadingStatus ? 0 : 1,
|
||||
duration: const Duration(milliseconds: 150),
|
||||
child: SizedBox(
|
||||
height: 36,
|
||||
height: 34,
|
||||
child: Obx(
|
||||
() => videoIntroController.followStatus.isNotEmpty
|
||||
? ElevatedButton(
|
||||
@ -444,107 +407,83 @@ class _VideoInfoState extends State<VideoInfo> with TickerProviderStateMixin {
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
const SizedBox(height: 8),
|
||||
Divider(
|
||||
height: 26,
|
||||
height: 12,
|
||||
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 _actionGrid(
|
||||
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(
|
||||
() => ActionItem(
|
||||
icon: const Icon(FontAwesomeIcons.thumbsUp),
|
||||
selectIcon: const Icon(FontAwesomeIcons.solidThumbsUp),
|
||||
onTap: () => videoIntroController.actionLikeVideo(),
|
||||
selectStatus: videoIntroController.hasLike.value,
|
||||
loadingStatus: widget.loadingStatus,
|
||||
text: !widget.loadingStatus
|
||||
? widget.videoDetail!.stat!.like!.toString()
|
||||
: '-'),
|
||||
),
|
||||
// ActionItem(
|
||||
// icon: const Icon(FontAwesomeIcons.thumbsDown),
|
||||
// selectIcon: const Icon(FontAwesomeIcons.solidThumbsDown),
|
||||
// onTap: () => {},
|
||||
// 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,
|
||||
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,
|
||||
text: !widget.loadingStatus
|
||||
? widget.videoDetail!.stat!.share!.toString()
|
||||
: '-'),
|
||||
ActionItem(
|
||||
icon: const Icon(FontAwesomeIcons.comments),
|
||||
onTap: () {
|
||||
videoDetailCtr.tabCtr.animateTo(1);
|
||||
},
|
||||
selectStatus: false,
|
||||
loadingStatus: widget.loadingStatus,
|
||||
text: !widget.loadingStatus
|
||||
? widget.videoDetail!.stat!.reply!.toString()
|
||||
: '-'),
|
||||
],
|
||||
),
|
||||
Widget actionRow(BuildContext context, videoIntroController, videoDetailCtr) {
|
||||
return Row(children: [
|
||||
Obx(
|
||||
() => ActionRowItem(
|
||||
icon: const Icon(FontAwesomeIcons.thumbsUp),
|
||||
onTap: () => videoIntroController.actionLikeVideo(),
|
||||
selectStatus: videoIntroController.hasLike.value,
|
||||
loadingStatus: widget.loadingStatus,
|
||||
text: !widget.loadingStatus
|
||||
? widget.videoDetail!.stat!.like!.toString()
|
||||
: '-',
|
||||
),
|
||||
);
|
||||
});
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
Obx(
|
||||
() => ActionRowItem(
|
||||
icon: const Icon(FontAwesomeIcons.b),
|
||||
onTap: () => videoIntroController.actionCoinVideo(),
|
||||
selectStatus: videoIntroController.hasCoin.value,
|
||||
loadingStatus: widget.loadingStatus,
|
||||
text: !widget.loadingStatus
|
||||
? widget.videoDetail!.stat!.coin!.toString()
|
||||
: '-',
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
Obx(
|
||||
() => ActionRowItem(
|
||||
icon: const Icon(FontAwesomeIcons.heart),
|
||||
onTap: () => showFavBottomSheet(),
|
||||
selectStatus: videoIntroController.hasFav.value,
|
||||
loadingStatus: widget.loadingStatus,
|
||||
text: !widget.loadingStatus
|
||||
? widget.videoDetail!.stat!.favorite!.toString()
|
||||
: '-',
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
ActionRowItem(
|
||||
icon: const Icon(FontAwesomeIcons.comment),
|
||||
onTap: () {
|
||||
videoDetailCtr.tabCtr.animateTo(1);
|
||||
},
|
||||
selectStatus: false,
|
||||
loadingStatus: widget.loadingStatus,
|
||||
text: !widget.loadingStatus
|
||||
? 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) {
|
||||
@ -583,54 +522,3 @@ class _VideoInfoState extends State<VideoInfo> with TickerProviderStateMixin {
|
||||
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:flutter/material.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/controller.dart';
|
||||
import 'package:pilipala/pages/video/detail/introduction/index.dart';
|
||||
@ -234,7 +236,7 @@ class _VideoDetailPageState extends State<VideoDetailPage>
|
||||
children: [
|
||||
Container(
|
||||
width: double.infinity,
|
||||
height: 45,
|
||||
height: 0,
|
||||
decoration: BoxDecoration(
|
||||
border: Border(
|
||||
bottom: BorderSide(
|
||||
@ -254,8 +256,8 @@ class _VideoDetailPageState extends State<VideoDetailPage>
|
||||
() => TabBar(
|
||||
controller: videoDetailController.tabCtr,
|
||||
dividerColor: Colors.transparent,
|
||||
// indicatorColor:
|
||||
// Theme.of(context).colorScheme.background,
|
||||
indicatorColor:
|
||||
Theme.of(context).colorScheme.background,
|
||||
tabs: videoDetailController.tabs
|
||||
.map((String name) => Tab(text: name))
|
||||
.toList(),
|
||||
@ -271,11 +273,19 @@ class _VideoDetailPageState extends State<VideoDetailPage>
|
||||
children: [
|
||||
Builder(
|
||||
builder: (context) {
|
||||
return const CustomScrollView(
|
||||
key: PageStorageKey<String>('简介'),
|
||||
return CustomScrollView(
|
||||
key: const PageStorageKey<String>('简介'),
|
||||
slivers: <Widget>[
|
||||
VideoIntroPanel(),
|
||||
RelatedVideoPanel(),
|
||||
const VideoIntroPanel(),
|
||||
SliverPersistentHeader(
|
||||
floating: true,
|
||||
pinned: true,
|
||||
delegate: SliverHeaderDelegate(
|
||||
height: 50,
|
||||
child: MenuRow(loadingStatus: false),
|
||||
),
|
||||
),
|
||||
const RelatedVideoPanel(),
|
||||
],
|
||||
);
|
||||
},
|
||||
|
Reference in New Issue
Block a user