mod: 评论刷新、翻页
This commit is contained in:
@ -22,10 +22,12 @@ class ReplyData {
|
|||||||
ReplyData.fromJson(Map<String, dynamic> json) {
|
ReplyData.fromJson(Map<String, dynamic> json) {
|
||||||
page = ReplyPage.fromJson(json['page']);
|
page = ReplyPage.fromJson(json['page']);
|
||||||
config = ReplyConfig.fromJson(json['config']);
|
config = ReplyConfig.fromJson(json['config']);
|
||||||
replies = json['replies']
|
replies = json['replies'] != null
|
||||||
.map<ReplyItemModel>(
|
? json['replies']
|
||||||
(item) => ReplyItemModel.fromJson(item, json['upper']['mid']))
|
.map<ReplyItemModel>(
|
||||||
.toList();
|
(item) => ReplyItemModel.fromJson(item, json['upper']['mid']))
|
||||||
|
.toList()
|
||||||
|
: [];
|
||||||
topReplies = json['top_replies'] != null
|
topReplies = json['top_replies'] != null
|
||||||
? json['top_replies']
|
? json['top_replies']
|
||||||
.map<ReplyItemModel>((item) => ReplyItemModel.fromJson(
|
.map<ReplyItemModel>((item) => ReplyItemModel.fromJson(
|
||||||
|
|||||||
@ -1,22 +1,62 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
import 'package:pilipala/http/reply.dart';
|
import 'package:pilipala/http/reply.dart';
|
||||||
import 'package:pilipala/models/video/reply/data.dart';
|
import 'package:pilipala/models/video/reply/data.dart';
|
||||||
|
import 'package:pilipala/models/video/reply/item.dart';
|
||||||
|
|
||||||
class VideoReplyController extends GetxController {
|
class VideoReplyController extends GetxController {
|
||||||
|
final ScrollController scrollController = ScrollController();
|
||||||
// 视频aid
|
// 视频aid
|
||||||
String aid = Get.parameters['aid']!;
|
String aid = Get.parameters['aid']!;
|
||||||
|
RxList<ReplyItemModel> replyList = [ReplyItemModel()].obs;
|
||||||
|
// 当前页
|
||||||
|
int currentPage = 0;
|
||||||
|
bool isLoadingMore = false;
|
||||||
|
bool noMore = false;
|
||||||
|
|
||||||
@override
|
Future queryReplyList({type = 'init'}) async {
|
||||||
void onInit() {
|
isLoadingMore = true;
|
||||||
super.onInit();
|
var res =
|
||||||
queryReplyList();
|
await ReplyHttp.replyList(oid: aid, pageNum: currentPage + 1, type: 1);
|
||||||
}
|
|
||||||
|
|
||||||
Future queryReplyList() async {
|
|
||||||
var res = await ReplyHttp.replyList(oid: aid, pageNum: 1, type: 1);
|
|
||||||
if (res['status']) {
|
if (res['status']) {
|
||||||
res['data'] = ReplyData.fromJson(res['data']);
|
res['data'] = ReplyData.fromJson(res['data']);
|
||||||
|
if (res['data'].replies.isNotEmpty) {
|
||||||
|
currentPage = currentPage + 1;
|
||||||
|
noMore = false;
|
||||||
|
} else {
|
||||||
|
if (currentPage == 0) {
|
||||||
|
} else {
|
||||||
|
noMore = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (type == 'init') {
|
||||||
|
List<ReplyItemModel> replies = res['data'].replies;
|
||||||
|
// 添加置顶回复
|
||||||
|
if (res['data'].upper.top != null) {
|
||||||
|
bool flag = false;
|
||||||
|
for (var i = 0; i < res['data'].topReplies.length; i++) {
|
||||||
|
if (res['data'].topReplies[i].rpid == res['data'].upper.top.rpid) {
|
||||||
|
flag = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!flag) {
|
||||||
|
replies.insert(0, res['data'].upper.top);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
replies.insertAll(0, res['data'].topReplies);
|
||||||
|
res['data'].replies = replies;
|
||||||
|
replyList.value = res['data'].replies!;
|
||||||
|
} else {
|
||||||
|
replyList.addAll(res['data'].replies!);
|
||||||
|
res['data'].replies.addAll(replyList);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
isLoadingMore = false;
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 上拉加载
|
||||||
|
Future onLoad() async {
|
||||||
|
queryReplyList(type: 'onLoad');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -17,64 +17,95 @@ class _VideoReplyPanelState extends State<VideoReplyPanel>
|
|||||||
with AutomaticKeepAliveClientMixin {
|
with AutomaticKeepAliveClientMixin {
|
||||||
final VideoReplyController _videoReplyController =
|
final VideoReplyController _videoReplyController =
|
||||||
Get.put(VideoReplyController(), tag: Get.arguments['heroTag']);
|
Get.put(VideoReplyController(), tag: Get.arguments['heroTag']);
|
||||||
|
// List<ReplyItemModel>? replyList;
|
||||||
|
Future? _futureBuilderFuture;
|
||||||
// 添加页面缓存
|
// 添加页面缓存
|
||||||
@override
|
@override
|
||||||
bool get wantKeepAlive => true;
|
bool get wantKeepAlive => true;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
void initState() {
|
||||||
return FutureBuilder(
|
super.initState();
|
||||||
future: _videoReplyController.queryReplyList(),
|
|
||||||
builder: (context, snapshot) {
|
|
||||||
if (snapshot.connectionState == ConnectionState.done) {
|
|
||||||
if (snapshot.data['status']) {
|
|
||||||
List<ReplyItemModel> replies = snapshot.data['data'].replies;
|
|
||||||
// 添加置顶回复
|
|
||||||
if (snapshot.data['data'].upper.top != null) {
|
|
||||||
bool flag = false;
|
|
||||||
for (var i = 0;
|
|
||||||
i < snapshot.data['data'].topReplies.length;
|
|
||||||
i++) {
|
|
||||||
if (snapshot.data['data'].topReplies[i].rpid ==
|
|
||||||
snapshot.data['data'].upper.top.rpid) {
|
|
||||||
flag = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (!flag) {
|
|
||||||
replies.insert(0, snapshot.data['data'].upper.top);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
replies.insertAll(0, snapshot.data['data'].topReplies);
|
_futureBuilderFuture = _videoReplyController.queryReplyList();
|
||||||
// 请求成功
|
_videoReplyController.scrollController.addListener(
|
||||||
return SliverList(
|
() {
|
||||||
delegate: SliverChildBuilderDelegate((context, index) {
|
if (_videoReplyController.scrollController.position.pixels >=
|
||||||
if (index == replies.length) {
|
_videoReplyController.scrollController.position.maxScrollExtent -
|
||||||
return SizedBox(height: MediaQuery.of(context).padding.bottom);
|
300) {
|
||||||
} else {
|
if (!_videoReplyController.isLoadingMore) {
|
||||||
return ReplyItem(
|
_videoReplyController.onLoad();
|
||||||
replyItem: replies[index],
|
|
||||||
isUp:
|
|
||||||
replies[index].mid == snapshot.data['data'].upper.mid);
|
|
||||||
}
|
|
||||||
}, childCount: replies.length + 1));
|
|
||||||
} else {
|
|
||||||
// 请求错误
|
|
||||||
return HttpError(
|
|
||||||
errMsg: snapshot.data['msg'],
|
|
||||||
fn: () => setState(() {}),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
// 骨架屏
|
|
||||||
return SliverList(
|
|
||||||
delegate: SliverChildBuilderDelegate((context, index) {
|
|
||||||
return const VideoCardHSkeleton();
|
|
||||||
}, childCount: 5),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return RefreshIndicator(
|
||||||
|
onRefresh: () async {
|
||||||
|
setState(() {});
|
||||||
|
_videoReplyController.currentPage = 0;
|
||||||
|
return await _videoReplyController.queryReplyList();
|
||||||
|
},
|
||||||
|
child: CustomScrollView(
|
||||||
|
controller: _videoReplyController.scrollController,
|
||||||
|
key: const PageStorageKey<String>('评论'),
|
||||||
|
slivers: <Widget>[
|
||||||
|
FutureBuilder(
|
||||||
|
future: _futureBuilderFuture,
|
||||||
|
builder: (context, snapshot) {
|
||||||
|
if (snapshot.connectionState == ConnectionState.done) {
|
||||||
|
Map data = snapshot.data as Map;
|
||||||
|
if (data['status']) {
|
||||||
|
// 请求成功
|
||||||
|
return Obx(
|
||||||
|
() => SliverList(
|
||||||
|
delegate: SliverChildBuilderDelegate(
|
||||||
|
(context, index) {
|
||||||
|
if (index == _videoReplyController.replyList.length) {
|
||||||
|
return Container(
|
||||||
|
padding: EdgeInsets.only(
|
||||||
|
bottom:
|
||||||
|
MediaQuery.of(context).padding.bottom),
|
||||||
|
height:
|
||||||
|
MediaQuery.of(context).padding.bottom + 60,
|
||||||
|
child: Center(
|
||||||
|
child: Text(_videoReplyController.noMore
|
||||||
|
? '没有更多了'
|
||||||
|
: '加载中'),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return ReplyItem(
|
||||||
|
replyItem: _videoReplyController.replyList[index],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
childCount: _videoReplyController.replyList.length + 1,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
// 请求错误
|
||||||
|
return HttpError(
|
||||||
|
errMsg: data['msg'],
|
||||||
|
fn: () => setState(() {}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// 骨架屏
|
||||||
|
return SliverList(
|
||||||
|
delegate: SliverChildBuilderDelegate((context, index) {
|
||||||
|
return const VideoCardHSkeleton();
|
||||||
|
}, childCount: 5),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -5,9 +5,8 @@ import 'package:pilipala/models/video/reply/item.dart';
|
|||||||
import 'package:pilipala/utils/utils.dart';
|
import 'package:pilipala/utils/utils.dart';
|
||||||
|
|
||||||
class ReplyItem extends StatelessWidget {
|
class ReplyItem extends StatelessWidget {
|
||||||
ReplyItem({super.key, this.replyItem, required this.isUp});
|
ReplyItem({super.key, this.replyItem});
|
||||||
ReplyItemModel? replyItem;
|
ReplyItemModel? replyItem;
|
||||||
bool isUp = false;
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
@ -45,51 +44,46 @@ class ReplyItem extends StatelessWidget {
|
|||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
// 头像、昵称
|
// 头像、昵称
|
||||||
Row(
|
GestureDetector(
|
||||||
// 两端对齐
|
// onTap: () =>
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
// Get.toNamed('/member/${reply.userName}', parameters: {
|
||||||
children: <Widget>[
|
// 'memberAvatar': reply.avatar,
|
||||||
GestureDetector(
|
// 'heroTag': reply.userName + heroTag,
|
||||||
// onTap: () =>
|
// }),
|
||||||
// Get.toNamed('/member/${reply.userName}', parameters: {
|
child: Row(
|
||||||
// 'memberAvatar': reply.avatar,
|
crossAxisAlignment: CrossAxisAlignment.center,
|
||||||
// 'heroTag': reply.userName + heroTag,
|
mainAxisSize: MainAxisSize.min,
|
||||||
// }),
|
children: <Widget>[
|
||||||
child: Row(
|
lfAvtar(context),
|
||||||
crossAxisAlignment: CrossAxisAlignment.center,
|
const SizedBox(width: 12),
|
||||||
mainAxisSize: MainAxisSize.min,
|
Column(
|
||||||
children: <Widget>[
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
lfAvtar(context),
|
children: [
|
||||||
const SizedBox(width: 12),
|
Row(
|
||||||
Column(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: [
|
children: [
|
||||||
Row(
|
Text(
|
||||||
children: [
|
replyItem!.member!.uname!,
|
||||||
Text(
|
style: TextStyle(
|
||||||
replyItem!.member!.uname!,
|
color: replyItem!.isUp!
|
||||||
style: Theme.of(context)
|
? Theme.of(context).colorScheme.primary
|
||||||
.textTheme
|
: Theme.of(context).colorScheme.outline,
|
||||||
.titleSmall!
|
fontSize:
|
||||||
.copyWith(
|
Theme.of(context).textTheme.titleSmall!.fontSize,
|
||||||
color: replyItem!.isUp!
|
),
|
||||||
? Theme.of(context).colorScheme.primary
|
|
||||||
: Theme.of(context).colorScheme.outline,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(width: 6),
|
|
||||||
Image.asset(
|
|
||||||
'assets/images/lv/lv${replyItem!.member!.level}.png',
|
|
||||||
height: 13,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
),
|
||||||
|
const SizedBox(width: 6),
|
||||||
|
Image.asset(
|
||||||
|
'assets/images/lv/lv${replyItem!.member!.level}.png',
|
||||||
|
height: 11,
|
||||||
|
),
|
||||||
|
const SizedBox(width: 6),
|
||||||
|
if (replyItem!.isUp!) UpTag()
|
||||||
],
|
],
|
||||||
)
|
),
|
||||||
],
|
],
|
||||||
),
|
)
|
||||||
),
|
],
|
||||||
],
|
),
|
||||||
),
|
),
|
||||||
// title
|
// title
|
||||||
Container(
|
Container(
|
||||||
@ -102,6 +96,8 @@ class ReplyItem extends StatelessWidget {
|
|||||||
style: const TextStyle(height: 1.65),
|
style: const TextStyle(height: 1.65),
|
||||||
TextSpan(
|
TextSpan(
|
||||||
children: [
|
children: [
|
||||||
|
if (replyItem!.isTop!)
|
||||||
|
WidgetSpan(child: UpTag(tagText: '置顶')),
|
||||||
buildContent(context, replyItem!.content!),
|
buildContent(context, replyItem!.content!),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
@ -136,26 +132,9 @@ class ReplyItem extends StatelessWidget {
|
|||||||
.labelMedium!
|
.labelMedium!
|
||||||
.copyWith(color: Theme.of(context).colorScheme.outline),
|
.copyWith(color: Theme.of(context).colorScheme.outline),
|
||||||
),
|
),
|
||||||
if (replyItem!.isTop!) ...[
|
|
||||||
Text(
|
|
||||||
' • 置顶',
|
|
||||||
style: TextStyle(
|
|
||||||
fontSize: Theme.of(context).textTheme.labelMedium!.fontSize,
|
|
||||||
color: Theme.of(context).colorScheme.primary,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
if (replyControl!.isUpTop!) ...[
|
|
||||||
Text(
|
|
||||||
' • 超赞',
|
|
||||||
style: TextStyle(
|
|
||||||
fontSize: Theme.of(context).textTheme.labelMedium!.fontSize,
|
|
||||||
color: Theme.of(context).colorScheme.primary,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
// const SizedBox(width: 4),
|
|
||||||
],
|
|
||||||
const Spacer(),
|
const Spacer(),
|
||||||
|
if (replyControl!.isUpTop!)
|
||||||
|
Icon(Icons.favorite, color: Colors.red[400], size: 18),
|
||||||
SizedBox(
|
SizedBox(
|
||||||
height: 35,
|
height: 35,
|
||||||
child: TextButton(
|
child: TextButton(
|
||||||
@ -202,7 +181,7 @@ class ReplyItemRow extends StatelessWidget {
|
|||||||
return Container(
|
return Container(
|
||||||
margin: const EdgeInsets.only(left: 42, right: 4, top: 0),
|
margin: const EdgeInsets.only(left: 42, right: 4, top: 0),
|
||||||
child: Material(
|
child: Material(
|
||||||
color: Theme.of(context).colorScheme.onInverseSurface.withOpacity(0.7),
|
color: Theme.of(context).colorScheme.onInverseSurface,
|
||||||
borderRadius: BorderRadius.circular(6),
|
borderRadius: BorderRadius.circular(6),
|
||||||
clipBehavior: Clip.hardEdge,
|
clipBehavior: Clip.hardEdge,
|
||||||
animationDuration: Duration.zero,
|
animationDuration: Duration.zero,
|
||||||
@ -245,21 +224,15 @@ class ReplyItemRow extends StatelessWidget {
|
|||||||
child: Padding(
|
child: Padding(
|
||||||
padding: EdgeInsets.fromLTRB(8, index == 0 ? 8 : 4, 8, 4),
|
padding: EdgeInsets.fromLTRB(8, index == 0 ? 8 : 4, 8, 4),
|
||||||
child: Text.rich(
|
child: Text.rich(
|
||||||
overflow: TextOverflow.ellipsis,
|
overflow: extraRow == 1
|
||||||
maxLines: 2,
|
? TextOverflow.ellipsis
|
||||||
|
: TextOverflow.visible,
|
||||||
|
maxLines: extraRow == 1 ? 2 : null,
|
||||||
TextSpan(
|
TextSpan(
|
||||||
children: [
|
children: [
|
||||||
if (replies![index].isUp)
|
if (replies![index].isUp)
|
||||||
TextSpan(
|
WidgetSpan(
|
||||||
text: 'UP • ',
|
child: UpTag(),
|
||||||
style: TextStyle(
|
|
||||||
fontWeight: FontWeight.bold,
|
|
||||||
fontSize: Theme.of(context)
|
|
||||||
.textTheme
|
|
||||||
.labelMedium!
|
|
||||||
.fontSize,
|
|
||||||
color: Theme.of(context).colorScheme.primary,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
TextSpan(
|
TextSpan(
|
||||||
text: replies![index].member.uname + ' ',
|
text: replies![index].member.uname + ' ',
|
||||||
@ -417,3 +390,31 @@ InlineSpan buildContent(BuildContext context, content) {
|
|||||||
// spanChilds.add(TextSpan(text: matchMember));
|
// spanChilds.add(TextSpan(text: matchMember));
|
||||||
return TextSpan(children: spanChilds);
|
return TextSpan(children: spanChilds);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class UpTag extends StatelessWidget {
|
||||||
|
String? tagText;
|
||||||
|
UpTag({super.key, this.tagText = 'UP'});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Container(
|
||||||
|
width: tagText == 'UP' ? 28 : 38,
|
||||||
|
height: tagText == 'UP' ? 17 : 19,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
borderRadius: BorderRadius.circular(3),
|
||||||
|
// color: Theme.of(context).colorScheme.primary,
|
||||||
|
border: Border.all(color: Theme.of(context).colorScheme.primary)),
|
||||||
|
margin: const EdgeInsets.only(right: 4),
|
||||||
|
// padding: const EdgeInsets.symmetric(vertical: 0.5, horizontal: 4),
|
||||||
|
child: Center(
|
||||||
|
child: Text(
|
||||||
|
tagText!,
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: Theme.of(context).textTheme.labelSmall!.fontSize,
|
||||||
|
color: Theme.of(context).colorScheme.primary,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@ -1,3 +1,4 @@
|
|||||||
|
import 'package:extended_nested_scroll_view/extended_nested_scroll_view.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';
|
||||||
@ -19,6 +20,10 @@ class _VideoDetailPageState extends State<VideoDetailPage> {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
final double statusBarHeight = MediaQuery.of(context).padding.top;
|
||||||
|
final double pinnedHeaderHeight = statusBarHeight +
|
||||||
|
kToolbarHeight +
|
||||||
|
MediaQuery.of(context).size.width * 9 / 16;
|
||||||
return DefaultTabController(
|
return DefaultTabController(
|
||||||
initialIndex: videoDetailController.tabInitialIndex,
|
initialIndex: videoDetailController.tabInitialIndex,
|
||||||
length: videoDetailController.tabs.length, // tab的数量.
|
length: videoDetailController.tabs.length, // tab的数量.
|
||||||
@ -26,126 +31,115 @@ class _VideoDetailPageState extends State<VideoDetailPage> {
|
|||||||
top: false,
|
top: false,
|
||||||
bottom: false,
|
bottom: false,
|
||||||
child: Scaffold(
|
child: Scaffold(
|
||||||
body: NestedScrollView(
|
body: ExtendedNestedScrollView(
|
||||||
headerSliverBuilder:
|
headerSliverBuilder:
|
||||||
(BuildContext context, bool innerBoxIsScrolled) {
|
(BuildContext context, bool innerBoxIsScrolled) {
|
||||||
return <Widget>[
|
return <Widget>[
|
||||||
SliverOverlapAbsorber(
|
SliverAppBar(
|
||||||
handle:
|
title: const Text("视频详情"),
|
||||||
NestedScrollView.sliverOverlapAbsorberHandleFor(context),
|
pinned: true,
|
||||||
sliver: SliverAppBar(
|
elevation: 0,
|
||||||
title: const Text("视频详情"),
|
scrolledUnderElevation: 0,
|
||||||
// floating: true,
|
forceElevated: innerBoxIsScrolled,
|
||||||
// snap: true,
|
expandedHeight: MediaQuery.of(context).size.width * 9 / 16,
|
||||||
pinned: true,
|
collapsedHeight: MediaQuery.of(context).size.width * 9 / 16,
|
||||||
elevation: 0,
|
flexibleSpace: FlexibleSpaceBar(
|
||||||
scrolledUnderElevation: 0,
|
background: Padding(
|
||||||
forceElevated: innerBoxIsScrolled,
|
padding: EdgeInsets.only(
|
||||||
expandedHeight: MediaQuery.of(context).size.width * 9 / 16,
|
top: MediaQuery.of(context).padding.top),
|
||||||
collapsedHeight: MediaQuery.of(context).size.width * 9 / 16,
|
child: LayoutBuilder(
|
||||||
toolbarHeight: kToolbarHeight,
|
builder: (context, boxConstraints) {
|
||||||
flexibleSpace: FlexibleSpaceBar(
|
double maxWidth = boxConstraints.maxWidth;
|
||||||
background: Padding(
|
double maxHeight = boxConstraints.maxHeight;
|
||||||
padding: EdgeInsets.only(
|
double PR = MediaQuery.of(context).devicePixelRatio;
|
||||||
bottom: 50,
|
return Hero(
|
||||||
top: MediaQuery.of(context).padding.top),
|
tag: videoDetailController.heroTag,
|
||||||
child: LayoutBuilder(
|
child: NetworkImgLayer(
|
||||||
builder: (context, boxConstraints) {
|
src: videoDetailController.videoItem['pic'],
|
||||||
double maxWidth = boxConstraints.maxWidth;
|
width: maxWidth,
|
||||||
double maxHeight = boxConstraints.maxHeight;
|
height: maxHeight,
|
||||||
double PR = MediaQuery.of(context).devicePixelRatio;
|
|
||||||
return Hero(
|
|
||||||
tag: videoDetailController.heroTag,
|
|
||||||
child: NetworkImgLayer(
|
|
||||||
src: videoDetailController.videoItem['pic'],
|
|
||||||
width: maxWidth,
|
|
||||||
height: maxHeight,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
bottom: PreferredSize(
|
|
||||||
preferredSize: const Size.fromHeight(50.0),
|
|
||||||
child: Container(
|
|
||||||
height: 50,
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
border: Border(
|
|
||||||
bottom: BorderSide(
|
|
||||||
color: Theme.of(context)
|
|
||||||
.dividerColor
|
|
||||||
.withOpacity(0.1)))),
|
|
||||||
child: Row(
|
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
|
||||||
mainAxisSize: MainAxisSize.max,
|
|
||||||
children: [
|
|
||||||
Container(
|
|
||||||
width: 280,
|
|
||||||
margin: const EdgeInsets.only(left: 20),
|
|
||||||
child: Obx(
|
|
||||||
() => TabBar(
|
|
||||||
splashBorderRadius: BorderRadius.circular(6),
|
|
||||||
dividerColor: Colors.transparent,
|
|
||||||
tabs: videoDetailController.tabs
|
|
||||||
.map((String name) => Tab(text: name))
|
|
||||||
.toList(),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
// 弹幕开关
|
);
|
||||||
// const Spacer(),
|
},
|
||||||
// Flexible(
|
|
||||||
// flex: 2,
|
|
||||||
// child: Container(
|
|
||||||
// height: 50,
|
|
||||||
// ),
|
|
||||||
// ),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
];
|
];
|
||||||
},
|
},
|
||||||
body: TabBarView(
|
pinnedHeaderSliverHeightBuilder: () {
|
||||||
|
return pinnedHeaderHeight;
|
||||||
|
},
|
||||||
|
onlyOneScrollInBody: true,
|
||||||
|
body: Column(
|
||||||
children: [
|
children: [
|
||||||
Builder(builder: (context) {
|
Container(
|
||||||
return CustomScrollView(
|
height: 50,
|
||||||
key: const PageStorageKey<String>('简介'),
|
decoration: BoxDecoration(
|
||||||
slivers: <Widget>[
|
border: Border(
|
||||||
SliverOverlapInjector(
|
bottom: BorderSide(
|
||||||
handle: NestedScrollView.sliverOverlapAbsorberHandleFor(
|
color: Theme.of(context).dividerColor.withOpacity(0.1),
|
||||||
context),
|
|
||||||
),
|
),
|
||||||
const VideoIntroPanel(),
|
),
|
||||||
SliverPadding(
|
),
|
||||||
padding: const EdgeInsets.only(top: 8, bottom: 5),
|
child: Row(
|
||||||
sliver: SliverToBoxAdapter(
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
child: Divider(
|
mainAxisSize: MainAxisSize.max,
|
||||||
height: 1,
|
children: [
|
||||||
color:
|
Container(
|
||||||
Theme.of(context).dividerColor.withOpacity(0.1),
|
width: 280,
|
||||||
|
margin: const EdgeInsets.only(left: 20),
|
||||||
|
child: Obx(
|
||||||
|
() => TabBar(
|
||||||
|
splashBorderRadius: BorderRadius.circular(6),
|
||||||
|
dividerColor: Colors.transparent,
|
||||||
|
tabs: videoDetailController.tabs
|
||||||
|
.map((String name) => Tab(text: name))
|
||||||
|
.toList(),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const RelatedVideoPanel(),
|
// 弹幕开关
|
||||||
|
// const Spacer(),
|
||||||
|
// Flexible(
|
||||||
|
// flex: 2,
|
||||||
|
// child: Container(
|
||||||
|
// height: 50,
|
||||||
|
// ),
|
||||||
|
// ),
|
||||||
],
|
],
|
||||||
);
|
),
|
||||||
}),
|
),
|
||||||
Builder(builder: (context) {
|
Expanded(
|
||||||
return CustomScrollView(
|
child: TabBarView(
|
||||||
key: const PageStorageKey<String>('评论'),
|
children: [
|
||||||
slivers: <Widget>[
|
Builder(
|
||||||
SliverOverlapInjector(
|
builder: (context) {
|
||||||
handle: NestedScrollView.sliverOverlapAbsorberHandleFor(
|
return CustomScrollView(
|
||||||
context),
|
key: const PageStorageKey<String>('简介'),
|
||||||
|
slivers: <Widget>[
|
||||||
|
const VideoIntroPanel(),
|
||||||
|
SliverPadding(
|
||||||
|
padding:
|
||||||
|
const EdgeInsets.only(top: 8, bottom: 5),
|
||||||
|
sliver: SliverToBoxAdapter(
|
||||||
|
child: Divider(
|
||||||
|
height: 1,
|
||||||
|
color: Theme.of(context)
|
||||||
|
.dividerColor
|
||||||
|
.withOpacity(0.1),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const RelatedVideoPanel(),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
},
|
||||||
),
|
),
|
||||||
const VideoReplyPanel()
|
const VideoReplyPanel()
|
||||||
],
|
],
|
||||||
);
|
),
|
||||||
})
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|||||||
18
pubspec.lock
18
pubspec.lock
@ -153,6 +153,14 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.6.3"
|
version: "1.6.3"
|
||||||
|
extended_nested_scroll_view:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: extended_nested_scroll_view
|
||||||
|
sha256: fc55b8f7e2c78701320d7eccda3b256387290b8498f0363d8ffd6f16760949d7
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "6.0.0"
|
||||||
fake_async:
|
fake_async:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -509,6 +517,14 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.1.4"
|
version: "2.1.4"
|
||||||
|
visibility_detector:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: visibility_detector
|
||||||
|
sha256: "15c54a459ec2c17b4705450483f3d5a2858e733aee893dcee9d75fd04814940d"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "0.3.3"
|
||||||
win32:
|
win32:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -535,4 +551,4 @@ packages:
|
|||||||
version: "6.2.2"
|
version: "6.2.2"
|
||||||
sdks:
|
sdks:
|
||||||
dart: ">=2.19.6 <3.0.0"
|
dart: ">=2.19.6 <3.0.0"
|
||||||
flutter: ">=3.4.0-17.0.pre"
|
flutter: ">=3.7.0"
|
||||||
|
|||||||
@ -52,6 +52,8 @@ dependencies:
|
|||||||
# 存储
|
# 存储
|
||||||
path_provider: ^2.0.14
|
path_provider: ^2.0.14
|
||||||
|
|
||||||
|
extended_nested_scroll_view: ^6.0.0
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
flutter_test:
|
flutter_test:
|
||||||
sdk: flutter
|
sdk: flutter
|
||||||
|
|||||||
Reference in New Issue
Block a user