Merge branch 'feature-m3Design'
This commit is contained in:
@ -1,6 +1,4 @@
|
||||
import 'dart:async';
|
||||
import 'dart:developer';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_meedu_media_kit/meedu_player.dart';
|
||||
import 'package:get/get.dart';
|
||||
|
@ -105,7 +105,6 @@ class VideoIntroController extends GetxController {
|
||||
// 获取up主粉丝数
|
||||
Future queryUserStat() async {
|
||||
var result = await UserHttp.userStat(mid: videoDetail.value.owner!.mid!);
|
||||
print('🌹:$result');
|
||||
if (result['status']) {
|
||||
userStat = result['data'];
|
||||
}
|
||||
@ -185,9 +184,11 @@ class VideoIntroController extends GetxController {
|
||||
if (!hasLike.value) {
|
||||
SmartDialog.showToast('点赞成功 👍');
|
||||
hasLike.value = true;
|
||||
videoDetail.value.stat!.like = videoDetail.value.stat!.like! + 1;
|
||||
} else if (hasLike.value) {
|
||||
SmartDialog.showToast('取消赞');
|
||||
hasLike.value = false;
|
||||
videoDetail.value.stat!.like = videoDetail.value.stat!.like! - 1;
|
||||
}
|
||||
hasLike.refresh();
|
||||
} else {
|
||||
@ -238,14 +239,15 @@ class VideoIntroController extends GetxController {
|
||||
onPressed: () async {
|
||||
var res = await VideoHttp.coinVideo(
|
||||
bvid: bvid, multiply: _tempThemeValue);
|
||||
print(res);
|
||||
if (res['status']) {
|
||||
SmartDialog.showToast('投币成功');
|
||||
SmartDialog.showToast('投币成功 👏');
|
||||
hasCoin.value = true;
|
||||
videoDetail.value.stat!.coin =
|
||||
videoDetail.value.stat!.coin! + _tempThemeValue;
|
||||
} else {
|
||||
SmartDialog.showToast(res['msg']);
|
||||
}
|
||||
Get.back();
|
||||
queryHasCoinVideo();
|
||||
},
|
||||
child: const Text('确定'))
|
||||
],
|
||||
@ -263,7 +265,10 @@ class VideoIntroController extends GetxController {
|
||||
delMediaIdsNew.add(i.id);
|
||||
}
|
||||
}
|
||||
} catch (e) {}
|
||||
} catch (e) {
|
||||
// ignore: avoid_print
|
||||
print(e);
|
||||
}
|
||||
var result = await VideoHttp.favVideo(
|
||||
aid: IdUtils.bv2av(bvid),
|
||||
addIds: addMediaIdsNew.join(','),
|
||||
@ -282,10 +287,8 @@ class VideoIntroController extends GetxController {
|
||||
|
||||
// 分享视频
|
||||
Future actionShareVideo() async {
|
||||
var result =
|
||||
await Share.share('${HttpString.baseUrl}/video/$bvid').whenComplete(() {
|
||||
print("share completion block ");
|
||||
});
|
||||
var result = await Share.share('${HttpString.baseUrl}/video/$bvid')
|
||||
.whenComplete(() {});
|
||||
return result;
|
||||
}
|
||||
|
||||
|
@ -1,4 +1,3 @@
|
||||
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';
|
||||
@ -7,10 +6,7 @@ import 'package:flutter/material.dart';
|
||||
import 'package:hive/hive.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';
|
||||
@ -19,7 +15,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/fav_panel.dart';
|
||||
import 'widgets/intro_detail.dart';
|
||||
import 'widgets/season.dart';
|
||||
|
||||
class VideoIntroPanel extends StatefulWidget {
|
||||
@ -55,6 +53,7 @@ class _VideoIntroPanelState extends State<VideoIntroPanel>
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
super.build(context);
|
||||
return FutureBuilder(
|
||||
future: videoIntroController.queryVideoIntro(),
|
||||
builder: (context, snapshot) {
|
||||
@ -79,10 +78,10 @@ class _VideoIntroPanelState extends State<VideoIntroPanel>
|
||||
}
|
||||
|
||||
class VideoInfo extends StatefulWidget {
|
||||
bool loadingStatus = false;
|
||||
VideoDetailData? videoDetail;
|
||||
final bool loadingStatus;
|
||||
final VideoDetailData? videoDetail;
|
||||
|
||||
VideoInfo({Key? key, required this.loadingStatus, this.videoDetail})
|
||||
const VideoInfo({Key? key, this.loadingStatus = false, this.videoDetail})
|
||||
: super(key: key);
|
||||
|
||||
@override
|
||||
@ -95,14 +94,6 @@ class _VideoInfoState extends State<VideoInfo> with TickerProviderStateMixin {
|
||||
Get.put(VideoIntroController(), tag: Get.arguments['heroTag']);
|
||||
bool isExpand = false;
|
||||
|
||||
/// 手动控制动画的控制器
|
||||
late AnimationController? _manualController;
|
||||
|
||||
/// 手动控制
|
||||
late Animation<double>? _manualAnimation;
|
||||
|
||||
final FavController _favController = Get.put(FavController());
|
||||
|
||||
late VideoDetailController? videoDetailCtr;
|
||||
Box localCache = GStrorage.localCache;
|
||||
late double sheetHeight;
|
||||
@ -111,13 +102,6 @@ class _VideoInfoState extends State<VideoInfo> with TickerProviderStateMixin {
|
||||
void initState() {
|
||||
super.initState();
|
||||
|
||||
/// 不设置重复,使用代码控制进度,动画时间1秒
|
||||
_manualController = AnimationController(
|
||||
vsync: this,
|
||||
duration: const Duration(milliseconds: 400),
|
||||
);
|
||||
_manualAnimation =
|
||||
Tween<double>(begin: 0.5, end: 1.5).animate(_manualController!);
|
||||
videoDetailCtr =
|
||||
Get.find<VideoDetailController>(tag: Get.arguments['heroTag']);
|
||||
sheetHeight = localCache.get('sheetHeight');
|
||||
@ -141,143 +125,123 @@ 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: StyleString.safeSpace, right: StyleString.safeSpace, top: 13),
|
||||
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,
|
||||
),
|
||||
),
|
||||
),
|
||||
InkWell(
|
||||
splashColor: Colors.transparent,
|
||||
hoverColor: Colors.transparent,
|
||||
highlightColor: Colors.transparent,
|
||||
GestureDetector(
|
||||
onTap: () {
|
||||
_manualController!.animateTo(isExpand ? 0 : 0.5);
|
||||
setState(() {
|
||||
isExpand = !isExpand;
|
||||
});
|
||||
showBottomSheet(
|
||||
context: context,
|
||||
enableDrag: true,
|
||||
builder: (BuildContext context) {
|
||||
return IntroDetail(videoDetail: widget.videoDetail!);
|
||||
},
|
||||
);
|
||||
},
|
||||
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,
|
||||
),
|
||||
Expanded(
|
||||
child: Text(
|
||||
!widget.loadingStatus
|
||||
? widget.videoDetail!.title
|
||||
: videoItem['title'],
|
||||
style: const TextStyle(
|
||||
fontSize: 18,
|
||||
letterSpacing: 0.3,
|
||||
fontWeight: FontWeight.w500,
|
||||
),
|
||||
maxLines: 2,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
),
|
||||
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(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: 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),
|
||||
),
|
||||
],
|
||||
),
|
||||
// 点赞收藏转发
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(top: 15),
|
||||
child: SingleChildScrollView(
|
||||
scrollDirection: Axis.horizontal,
|
||||
child: actionRow(
|
||||
context,
|
||||
videoIntroController,
|
||||
videoDetailCtr,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
// 点赞收藏转发
|
||||
_actionGrid(context, videoIntroController, videoDetailCtr),
|
||||
),
|
||||
// 合集
|
||||
if (!widget.loadingStatus &&
|
||||
widget.videoDetail!.ugcSeason != null) ...[
|
||||
seasonPanel(widget.videoDetail!.ugcSeason!,
|
||||
widget.videoDetail!.pages!.first.cid, sheetHeight)
|
||||
],
|
||||
Divider(
|
||||
height: 26,
|
||||
color: Theme.of(context).dividerColor.withOpacity(0.1),
|
||||
),
|
||||
// Divider(
|
||||
// height: 26,
|
||||
// color: Theme.of(context).dividerColor.withOpacity(0.1),
|
||||
// ),
|
||||
const SizedBox(height: 20),
|
||||
GestureDetector(
|
||||
onTap: () {
|
||||
int mid = !widget.loadingStatus
|
||||
@ -293,53 +257,84 @@ class _VideoInfoState extends State<VideoInfo> with TickerProviderStateMixin {
|
||||
},
|
||||
child: Row(
|
||||
children: [
|
||||
const SizedBox(width: 5),
|
||||
NetworkImgLayer(
|
||||
type: 'avatar',
|
||||
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),
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(!widget.loadingStatus
|
||||
? widget.videoDetail!.owner!.name
|
||||
: videoItem['owner'].name),
|
||||
// const SizedBox(width: 10),
|
||||
Text(
|
||||
widget.loadingStatus
|
||||
? '- 粉丝'
|
||||
: '${Utils.numFormat(videoIntroController.userStat['follower'])}粉丝',
|
||||
style: TextStyle(
|
||||
fontSize: Theme.of(context)
|
||||
.textTheme
|
||||
.labelSmall!
|
||||
.fontSize,
|
||||
color: Theme.of(context).colorScheme.outline),
|
||||
),
|
||||
],
|
||||
const SizedBox(width: 10),
|
||||
Text(
|
||||
!widget.loadingStatus
|
||||
? widget.videoDetail!.owner!.name
|
||||
: videoItem['owner'].name,
|
||||
style: const TextStyle(fontSize: 13),
|
||||
),
|
||||
const SizedBox(width: 10),
|
||||
Text(
|
||||
widget.loadingStatus
|
||||
? '- 粉丝'
|
||||
: Utils.numFormat(
|
||||
videoIntroController.userStat['follower']),
|
||||
style: TextStyle(
|
||||
fontSize: Theme.of(context)
|
||||
.textTheme
|
||||
.labelSmall!
|
||||
.fontSize,
|
||||
color: Theme.of(context).colorScheme.outline),
|
||||
),
|
||||
const Spacer(),
|
||||
AnimatedOpacity(
|
||||
opacity: widget.loadingStatus ? 0 : 1,
|
||||
duration: const Duration(milliseconds: 150),
|
||||
child: SizedBox(
|
||||
height: 36,
|
||||
height: 32,
|
||||
child: Obx(
|
||||
() => videoIntroController.followStatus.isNotEmpty
|
||||
? ElevatedButton(
|
||||
? TextButton(
|
||||
onPressed: () => videoIntroController
|
||||
.actionRelationMod(),
|
||||
child: Text(videoIntroController
|
||||
.followStatus['attribute'] ==
|
||||
0
|
||||
? '关注'
|
||||
: '已关注'),
|
||||
style: TextButton.styleFrom(
|
||||
padding: const EdgeInsets.only(
|
||||
left: 8, right: 8),
|
||||
foregroundColor:
|
||||
videoIntroController.followStatus[
|
||||
'attribute'] !=
|
||||
0
|
||||
? Theme.of(context)
|
||||
.colorScheme
|
||||
.outline
|
||||
: Theme.of(context)
|
||||
.colorScheme
|
||||
.onPrimary,
|
||||
backgroundColor:
|
||||
videoIntroController.followStatus[
|
||||
'attribute'] !=
|
||||
0
|
||||
? Theme.of(context)
|
||||
.colorScheme
|
||||
.onInverseSurface
|
||||
: Theme.of(context)
|
||||
.colorScheme
|
||||
.primary, // 设置按钮背景色
|
||||
),
|
||||
child: Text(
|
||||
videoIntroController.followStatus[
|
||||
'attribute'] !=
|
||||
0
|
||||
? '已关注'
|
||||
: '关注',
|
||||
style: TextStyle(
|
||||
fontSize: Theme.of(context)
|
||||
.textTheme
|
||||
.labelMedium!
|
||||
.fontSize),
|
||||
),
|
||||
)
|
||||
: ElevatedButton(
|
||||
onPressed: () => videoIntroController
|
||||
@ -349,110 +344,87 @@ class _VideoInfoState extends State<VideoInfo> with TickerProviderStateMixin {
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 4)
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
Divider(
|
||||
height: 26,
|
||||
color: Theme.of(context).dividerColor.withOpacity(0.1),
|
||||
),
|
||||
// const SizedBox(height: 10),
|
||||
const SizedBox(height: 12),
|
||||
// Divider(
|
||||
// height: 12,
|
||||
// color: Theme.of(context).dividerColor.withOpacity(0.1),
|
||||
// ),
|
||||
],
|
||||
)
|
||||
: 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()
|
||||
// : '-',
|
||||
text: '转发'),
|
||||
]);
|
||||
}
|
||||
|
||||
InlineSpan buildContent(BuildContext context, content) {
|
||||
@ -491,54 +463,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 {
|
||||
final Icon? icon;
|
||||
final Icon? selectIcon;
|
||||
final Function? onTap;
|
||||
final bool? loadingStatus;
|
||||
final String? text;
|
||||
final bool selectStatus;
|
||||
|
||||
const ActionItem({
|
||||
Key? key,
|
||||
this.icon,
|
||||
this.selectIcon,
|
||||
this.onTap,
|
||||
this.loadingStatus,
|
||||
this.text,
|
||||
this.selectStatus = false,
|
||||
}) : 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,71 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class ActionRowItem extends StatelessWidget {
|
||||
final Icon? icon;
|
||||
final Icon? selectIcon;
|
||||
final Function? onTap;
|
||||
final bool? loadingStatus;
|
||||
final String? text;
|
||||
final bool selectStatus;
|
||||
|
||||
const ActionRowItem({
|
||||
Key? key,
|
||||
this.icon,
|
||||
this.selectIcon,
|
||||
this.onTap,
|
||||
this.loadingStatus,
|
||||
this.text,
|
||||
this.selectStatus = false,
|
||||
}) : 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: AnimatedSwitcher(
|
||||
duration: const Duration(milliseconds: 300),
|
||||
transitionBuilder:
|
||||
(Widget child, Animation<double> animation) {
|
||||
return ScaleTransition(scale: animation, child: child);
|
||||
},
|
||||
child: Text(
|
||||
text ?? '',
|
||||
key: ValueKey<String>(text ?? ''),
|
||||
style: TextStyle(
|
||||
color: selectStatus
|
||||
? Theme.of(context).colorScheme.primary
|
||||
: null,
|
||||
fontSize:
|
||||
Theme.of(context).textTheme.labelSmall!.fontSize),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
@ -5,8 +5,8 @@ import 'package:pilipala/common/widgets/http_error.dart';
|
||||
import 'package:pilipala/utils/storage.dart';
|
||||
|
||||
class FavPanel extends StatefulWidget {
|
||||
var ctr;
|
||||
FavPanel({this.ctr});
|
||||
final dynamic ctr;
|
||||
const FavPanel({super.key, this.ctr});
|
||||
|
||||
@override
|
||||
State<FavPanel> createState() => _FavPanelState();
|
||||
@ -111,7 +111,7 @@ class _FavPanelState extends State<FavPanel> {
|
||||
}
|
||||
} else {
|
||||
// 骨架屏
|
||||
return Text('请求中');
|
||||
return const Text('请求中');
|
||||
}
|
||||
},
|
||||
),
|
||||
|
149
lib/pages/video/detail/introduction/widgets/intro_detail.dart
Normal file
149
lib/pages/video/detail/introduction/widgets/intro_detail.dart
Normal file
@ -0,0 +1,149 @@
|
||||
import 'package:flutter/gestures.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:hive/hive.dart';
|
||||
import 'package:pilipala/common/widgets/stat/danmu.dart';
|
||||
import 'package:pilipala/common/widgets/stat/view.dart';
|
||||
import 'package:pilipala/utils/storage.dart';
|
||||
import 'package:pilipala/utils/utils.dart';
|
||||
|
||||
Box localCache = GStrorage.localCache;
|
||||
late double sheetHeight;
|
||||
|
||||
class IntroDetail extends StatelessWidget {
|
||||
final dynamic videoDetail;
|
||||
|
||||
const IntroDetail({
|
||||
Key? key,
|
||||
this.videoDetail,
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
sheetHeight = localCache.get('sheetHeight');
|
||||
return Container(
|
||||
color: Theme.of(context).colorScheme.background,
|
||||
padding: const EdgeInsets.only(left: 14, right: 14),
|
||||
height: sheetHeight,
|
||||
child: Column(
|
||||
children: [
|
||||
Container(
|
||||
height: 35,
|
||||
padding: const EdgeInsets.only(bottom: 2),
|
||||
child: Center(
|
||||
child: Container(
|
||||
width: 32,
|
||||
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: const TextStyle(
|
||||
fontSize: 18,
|
||||
letterSpacing: 0.5,
|
||||
fontWeight: FontWeight.w500,
|
||||
),
|
||||
),
|
||||
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);
|
||||
}
|
||||
}
|
93
lib/pages/video/detail/introduction/widgets/menu_row.dart
Normal file
93
lib/pages/video/detail/introduction/widgets/menu_row.dart
Normal file
@ -0,0 +1,93 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class MenuRow extends StatelessWidget {
|
||||
final bool? loadingStatus;
|
||||
const 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: const TextStyle(fontSize: 13),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
@ -1,5 +1,4 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:pilipala/models/video_detail_res.dart';
|
||||
|
||||
Widget seasonPanel(UgcSeason ugcSeason, cid, sheetHeight) {
|
||||
@ -75,21 +74,28 @@ Widget seasonPanel(UgcSeason ugcSeason, cid, sheetHeight) {
|
||||
),
|
||||
),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.fromLTRB(10, 10, 10, 10),
|
||||
padding: const EdgeInsets.fromLTRB(8, 12, 8, 12),
|
||||
child: Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: Text(
|
||||
'合集:${ugcSeason.title!}',
|
||||
style: Theme.of(context).textTheme.labelMedium,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 15),
|
||||
Image.asset(
|
||||
'assets/images/live.gif',
|
||||
color: Theme.of(context).colorScheme.primary,
|
||||
height: 11,
|
||||
),
|
||||
const SizedBox(width: 4),
|
||||
Text(
|
||||
'${currentIndex + 1} / ${ugcSeason.epCount}',
|
||||
style: Theme.of(context).textTheme.labelSmall,
|
||||
style: Theme.of(context).textTheme.labelMedium,
|
||||
),
|
||||
const SizedBox(width: 2),
|
||||
const SizedBox(width: 6),
|
||||
const Icon(
|
||||
Icons.arrow_forward_ios_outlined,
|
||||
size: 13,
|
||||
|
@ -114,6 +114,7 @@ class _VideoReplyPanelState extends State<VideoReplyPanel>
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
super.build(context);
|
||||
return RefreshIndicator(
|
||||
onRefresh: () async {
|
||||
_videoReplyController.currentPage = 0;
|
||||
@ -130,8 +131,18 @@ class _VideoReplyPanelState extends State<VideoReplyPanel>
|
||||
floating: true,
|
||||
delegate: _MySliverPersistentHeaderDelegate(
|
||||
child: Container(
|
||||
color: Theme.of(context).colorScheme.background,
|
||||
padding: const EdgeInsets.fromLTRB(12, 6, 10, 6),
|
||||
height: 45,
|
||||
padding: const EdgeInsets.fromLTRB(12, 0, 6, 0),
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context).colorScheme.background,
|
||||
border: Border(
|
||||
bottom: BorderSide(
|
||||
color: Theme.of(context)
|
||||
.colorScheme
|
||||
.outline
|
||||
.withOpacity(0.1)),
|
||||
),
|
||||
),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
@ -155,9 +166,11 @@ class _VideoReplyPanelState extends State<VideoReplyPanel>
|
||||
child: TextButton.icon(
|
||||
onPressed: () =>
|
||||
_videoReplyController.queryBySort(),
|
||||
icon: const Icon(Icons.sort, size: 17),
|
||||
icon: const Icon(Icons.sort, size: 15),
|
||||
label: Obx(() => Text(
|
||||
_videoReplyController.sortTypeLabel.value)),
|
||||
_videoReplyController.sortTypeLabel.value,
|
||||
style: const TextStyle(fontSize: 12),
|
||||
)),
|
||||
),
|
||||
)
|
||||
],
|
||||
|
@ -12,21 +12,21 @@ import 'package:pilipala/utils/utils.dart';
|
||||
import 'zan.dart';
|
||||
|
||||
class ReplyItem extends StatelessWidget {
|
||||
ReplyItem({
|
||||
super.key,
|
||||
const ReplyItem({
|
||||
this.replyItem,
|
||||
this.addReply,
|
||||
this.replyLevel,
|
||||
this.showReplyRow,
|
||||
this.showReplyRow = true,
|
||||
this.replyReply,
|
||||
this.replyType,
|
||||
});
|
||||
ReplyItemModel? replyItem;
|
||||
Function? addReply;
|
||||
String? replyLevel;
|
||||
bool? showReplyRow = true;
|
||||
Function? replyReply;
|
||||
ReplyType? replyType;
|
||||
Key? key,
|
||||
}) : super(key: key);
|
||||
final ReplyItemModel? replyItem;
|
||||
final Function? addReply;
|
||||
final String? replyLevel;
|
||||
final bool? showReplyRow;
|
||||
final Function? replyReply;
|
||||
final ReplyType? replyType;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
@ -108,8 +108,7 @@ class ReplyItem extends StatelessWidget {
|
||||
replyItem!.member!.vip!['vipStatus'] > 0
|
||||
? Theme.of(context).colorScheme.primary
|
||||
: Theme.of(context).colorScheme.outline,
|
||||
fontSize:
|
||||
Theme.of(context).textTheme.titleSmall!.fontSize,
|
||||
fontSize: 13,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 6),
|
||||
@ -118,7 +117,7 @@ class ReplyItem extends StatelessWidget {
|
||||
height: 11,
|
||||
),
|
||||
const SizedBox(width: 6),
|
||||
if (replyItem!.isUp!) UpTag(),
|
||||
if (replyItem!.isUp!) const UpTag(),
|
||||
],
|
||||
),
|
||||
Positioned(
|
||||
@ -186,7 +185,7 @@ class ReplyItem extends StatelessWidget {
|
||||
TextSpan(
|
||||
children: [
|
||||
if (replyItem!.isTop!)
|
||||
WidgetSpan(child: UpTag(tagText: 'TOP')),
|
||||
const WidgetSpan(child: UpTag(tagText: 'TOP')),
|
||||
buildContent(context, replyItem!, replyReply, null),
|
||||
],
|
||||
),
|
||||
@ -204,7 +203,7 @@ class ReplyItem extends StatelessWidget {
|
||||
child: ReplyItemRow(
|
||||
replies: replyItem!.replies,
|
||||
replyControl: replyItem!.replyControl,
|
||||
f_rpid: replyItem!.rpid,
|
||||
// f_rpid: replyItem!.rpid,
|
||||
replyItem: replyItem,
|
||||
replyReply: replyReply,
|
||||
),
|
||||
@ -216,7 +215,6 @@ class ReplyItem extends StatelessWidget {
|
||||
|
||||
// 感谢、回复、复制
|
||||
Widget bottonAction(context, replyControl) {
|
||||
var color = Theme.of(context).colorScheme.outline;
|
||||
return Row(
|
||||
children: [
|
||||
const SizedBox(width: 48),
|
||||
@ -297,13 +295,13 @@ class ReplyItemRow extends StatelessWidget {
|
||||
super.key,
|
||||
this.replies,
|
||||
this.replyControl,
|
||||
this.f_rpid,
|
||||
// this.f_rpid,
|
||||
this.replyItem,
|
||||
this.replyReply,
|
||||
});
|
||||
List? replies;
|
||||
ReplyControl? replyControl;
|
||||
int? f_rpid;
|
||||
// int? f_rpid;
|
||||
ReplyItemModel? replyItem;
|
||||
Function? replyReply;
|
||||
|
||||
@ -361,7 +359,7 @@ class ReplyItemRow extends StatelessWidget {
|
||||
},
|
||||
),
|
||||
if (replies![i].isUp)
|
||||
WidgetSpan(
|
||||
const WidgetSpan(
|
||||
child: UpTag(),
|
||||
),
|
||||
buildContent(
|
||||
@ -439,16 +437,14 @@ InlineSpan buildContent(
|
||||
color: Theme.of(context).colorScheme.primary,
|
||||
),
|
||||
recognizer: TapGestureRecognizer()
|
||||
..onTap = () => {
|
||||
Get.toNamed(
|
||||
'/webview',
|
||||
parameters: {
|
||||
'url': content.vote['url'],
|
||||
'type': 'vote',
|
||||
'pageTitle': content.vote['title'],
|
||||
},
|
||||
)
|
||||
},
|
||||
..onTap = () => Get.toNamed(
|
||||
'/webview',
|
||||
parameters: {
|
||||
'url': content.vote['url'],
|
||||
'type': 'vote',
|
||||
'pageTitle': content.vote['title'],
|
||||
},
|
||||
),
|
||||
),
|
||||
);
|
||||
return '';
|
||||
@ -554,11 +550,9 @@ InlineSpan buildContent(
|
||||
color: Theme.of(context).colorScheme.primary,
|
||||
),
|
||||
recognizer: TapGestureRecognizer()
|
||||
..onTap = () => {
|
||||
Get.toNamed('/searchResult', parameters: {
|
||||
'keyword': content.jumpUrl[matchStr]['title']
|
||||
})
|
||||
},
|
||||
..onTap = () => Get.toNamed('/searchResult', parameters: {
|
||||
'keyword': content.jumpUrl[matchStr]['title']
|
||||
}),
|
||||
),
|
||||
);
|
||||
spanChilds.add(
|
||||
@ -718,16 +712,14 @@ InlineSpan buildContent(
|
||||
color: Theme.of(context).colorScheme.primary,
|
||||
),
|
||||
recognizer: TapGestureRecognizer()
|
||||
..onTap = () => {
|
||||
Get.toNamed(
|
||||
'/webview',
|
||||
parameters: {
|
||||
'url': content.richText['note']['click_url'],
|
||||
'type': 'note',
|
||||
'pageTitle': '笔记预览'
|
||||
},
|
||||
)
|
||||
},
|
||||
..onTap = () => Get.toNamed(
|
||||
'/webview',
|
||||
parameters: {
|
||||
'url': content.richText['note']['click_url'],
|
||||
'type': 'note',
|
||||
'pageTitle': '笔记预览'
|
||||
},
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
@ -736,8 +728,8 @@ InlineSpan buildContent(
|
||||
}
|
||||
|
||||
class UpTag extends StatelessWidget {
|
||||
String? tagText;
|
||||
UpTag({super.key, this.tagText = 'UP'});
|
||||
final String? tagText;
|
||||
const UpTag({super.key, this.tagText = 'UP'});
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
Color primary = Theme.of(context).colorScheme.primary;
|
||||
|
@ -6,13 +6,13 @@ import 'package:pilipala/models/common/reply_type.dart';
|
||||
import 'package:pilipala/models/video/reply/item.dart';
|
||||
|
||||
class ZanButton extends StatefulWidget {
|
||||
ZanButton({
|
||||
const ZanButton({
|
||||
super.key,
|
||||
this.replyItem,
|
||||
this.replyType,
|
||||
});
|
||||
|
||||
ReplyItemModel? replyItem;
|
||||
final ReplyItemModel? replyItem;
|
||||
final ReplyType? replyType;
|
||||
|
||||
@override
|
||||
|
@ -1,5 +1,4 @@
|
||||
import 'dart:async';
|
||||
import 'dart:developer';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
|
||||
import 'package:get/get.dart';
|
||||
@ -10,13 +9,14 @@ import 'package:pilipala/models/video/reply/item.dart';
|
||||
import 'package:pilipala/utils/storage.dart';
|
||||
|
||||
class VideoReplyNewDialog extends StatefulWidget {
|
||||
int? oid;
|
||||
int? root;
|
||||
int? parent;
|
||||
ReplyType? replyType;
|
||||
ReplyItemModel? replyItem;
|
||||
final int? oid;
|
||||
final int? root;
|
||||
final int? parent;
|
||||
final ReplyType? replyType;
|
||||
final ReplyItemModel? replyItem;
|
||||
|
||||
VideoReplyNewDialog({
|
||||
const VideoReplyNewDialog({
|
||||
super.key,
|
||||
this.oid,
|
||||
this.root,
|
||||
this.parent,
|
||||
@ -56,7 +56,9 @@ class _VideoReplyNewDialogState extends State<VideoReplyNewDialog>
|
||||
|
||||
_autoFocus() async {
|
||||
await Future.delayed(const Duration(milliseconds: 300));
|
||||
FocusScope.of(context).requestFocus(replyContentFocusNode);
|
||||
if (context.mounted) {
|
||||
FocusScope.of(context).requestFocus(replyContentFocusNode);
|
||||
}
|
||||
}
|
||||
|
||||
_printLatestValue() {
|
||||
@ -91,9 +93,8 @@ class _VideoReplyNewDialogState extends State<VideoReplyNewDialog>
|
||||
super.didChangeMetrics();
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
// 键盘高度
|
||||
final viewInsets = EdgeInsets.fromWindowPadding(
|
||||
WidgetsBinding.instance.window.viewInsets,
|
||||
WidgetsBinding.instance.window.devicePixelRatio);
|
||||
final viewInsets = EdgeInsets.fromViewPadding(
|
||||
View.of(context).viewInsets, View.of(context).devicePixelRatio);
|
||||
_debouncer.run(() {
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
@ -197,7 +198,7 @@ class _VideoReplyNewDialogState extends State<VideoReplyNewDialog>
|
||||
}
|
||||
}
|
||||
|
||||
typedef void DebounceCallback();
|
||||
typedef DebounceCallback = void Function();
|
||||
|
||||
class Debouncer {
|
||||
DebounceCallback? callback;
|
||||
|
@ -81,7 +81,7 @@ class _VideoReplyReplyPanelState extends State<VideoReplyReplyPanel> {
|
||||
if (widget.source == 'videoDetail')
|
||||
Container(
|
||||
height: 45,
|
||||
padding: const EdgeInsets.only(left: 14, right: 14),
|
||||
padding: const EdgeInsets.only(left: 12, right: 2),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
|
@ -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';
|
||||
@ -171,8 +173,8 @@ class _VideoDetailPageState extends State<VideoDetailPage>
|
||||
controller: _meeduPlayerController!,
|
||||
header: (BuildContext context,
|
||||
MeeduPlayerController
|
||||
_meeduPlayerController,
|
||||
Responsive) {
|
||||
meeduPlayerController,
|
||||
Responsive responsive) {
|
||||
return AppBar(
|
||||
toolbarHeight: 40,
|
||||
backgroundColor: Colors.transparent,
|
||||
@ -230,7 +232,7 @@ class _VideoDetailPageState extends State<VideoDetailPage>
|
||||
children: [
|
||||
Container(
|
||||
width: double.infinity,
|
||||
height: 45,
|
||||
height: 0,
|
||||
decoration: BoxDecoration(
|
||||
border: Border(
|
||||
bottom: BorderSide(
|
||||
@ -250,8 +252,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(),
|
||||
@ -267,11 +269,20 @@ 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:
|
||||
const MenuRow(loadingStatus: false),
|
||||
),
|
||||
),
|
||||
const RelatedVideoPanel(),
|
||||
],
|
||||
);
|
||||
},
|
||||
@ -296,6 +307,7 @@ class _VideoDetailPageState extends State<VideoDetailPage>
|
||||
snapshot.data!.toDouble(),
|
||||
continuePlay,
|
||||
playerStatus,
|
||||
null,
|
||||
);
|
||||
}),
|
||||
)
|
||||
|
@ -3,14 +3,15 @@ import 'package:flutter_meedu_media_kit/meedu_player.dart';
|
||||
|
||||
class ScrollAppBar extends StatelessWidget {
|
||||
final double scrollVal;
|
||||
Function callback;
|
||||
final Function callback;
|
||||
final PlayerStatus playerStatus;
|
||||
|
||||
ScrollAppBar(
|
||||
const ScrollAppBar(
|
||||
this.scrollVal,
|
||||
this.callback,
|
||||
this.playerStatus,
|
||||
);
|
||||
Key? key,
|
||||
) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
|
@ -1,16 +1,20 @@
|
||||
// ignore_for_file: library_private_types_in_public_api
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class ExpandedSection extends StatefulWidget {
|
||||
final Widget child;
|
||||
final Widget? child;
|
||||
final bool expand;
|
||||
double begin = 0.0;
|
||||
double end = 1.0;
|
||||
final double begin;
|
||||
final double end;
|
||||
|
||||
ExpandedSection(
|
||||
{this.expand = false,
|
||||
required this.child,
|
||||
required this.begin,
|
||||
required this.end});
|
||||
const ExpandedSection({
|
||||
super.key,
|
||||
this.expand = false,
|
||||
this.child,
|
||||
this.begin = 0.0,
|
||||
this.end = 1.0,
|
||||
});
|
||||
|
||||
@override
|
||||
_ExpandedSectionState createState() => _ExpandedSectionState();
|
||||
|
Reference in New Issue
Block a user