feat: 图文动态详情页&评论渲染
This commit is contained in:
80
lib/pages/dynamics/deatil/controller.dart
Normal file
80
lib/pages/dynamics/deatil/controller.dart
Normal file
@ -0,0 +1,80 @@
|
||||
import 'package:get/get.dart';
|
||||
import 'package:pilipala/http/reply.dart';
|
||||
import 'package:pilipala/models/dynamics/result.dart';
|
||||
import 'package:pilipala/models/video/reply/data.dart';
|
||||
import 'package:pilipala/models/video/reply/item.dart';
|
||||
|
||||
class DynamicDetailController extends GetxController {
|
||||
DynamicDetailController(this.oid, this.type);
|
||||
int? oid;
|
||||
int? type;
|
||||
var item;
|
||||
int? floor;
|
||||
int currentPage = 0;
|
||||
bool isLoadingMore = false;
|
||||
RxString noMore = ''.obs;
|
||||
RxList<ReplyItemModel> replyList = [ReplyItemModel()].obs;
|
||||
RxInt acount = 0.obs;
|
||||
|
||||
@override
|
||||
void onInit() {
|
||||
super.onInit();
|
||||
item = Get.arguments['item'];
|
||||
floor = Get.arguments['floor'];
|
||||
if (floor == 1) {
|
||||
acount.value =
|
||||
int.parse(item!.modules!.moduleStat!.comment!.count ?? '0');
|
||||
}
|
||||
}
|
||||
|
||||
Future queryReplyList({reqType = 'init'}) async {
|
||||
if (reqType == 'init') {
|
||||
currentPage = 0;
|
||||
}
|
||||
var res = await ReplyHttp.replyList(
|
||||
oid: oid!,
|
||||
pageNum: currentPage + 1,
|
||||
type: type!,
|
||||
);
|
||||
if (res['status']) {
|
||||
res['data'] = ReplyData.fromJson(res['data']);
|
||||
acount.value = res['data'].page.acount;
|
||||
if (res['data'].replies.isNotEmpty) {
|
||||
currentPage = currentPage + 1;
|
||||
noMore.value = '加载中...';
|
||||
if (replyList.isEmpty) {
|
||||
noMore.value = '没有更多了';
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
if (currentPage == 0) {
|
||||
noMore.value = '还没有评论';
|
||||
} else {
|
||||
noMore.value = '没有更多了';
|
||||
return;
|
||||
}
|
||||
}
|
||||
List<ReplyItemModel> replies = res['data'].replies;
|
||||
if (reqType == 'init') {
|
||||
// 添加置顶回复
|
||||
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);
|
||||
replyList.value = replies;
|
||||
} else {
|
||||
replyList.addAll(replies);
|
||||
}
|
||||
}
|
||||
isLoadingMore = false;
|
||||
return res;
|
||||
}
|
||||
}
|
||||
4
lib/pages/dynamics/deatil/index.dart
Normal file
4
lib/pages/dynamics/deatil/index.dart
Normal file
@ -0,0 +1,4 @@
|
||||
library dynamic_detail;
|
||||
|
||||
export './controller.dart';
|
||||
export './view.dart';
|
||||
248
lib/pages/dynamics/deatil/view.dart
Normal file
248
lib/pages/dynamics/deatil/view.dart
Normal file
@ -0,0 +1,248 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:pilipala/common/skeleton/video_reply.dart';
|
||||
import 'package:pilipala/common/widgets/http_error.dart';
|
||||
import 'package:pilipala/pages/dynamics/deatil/index.dart';
|
||||
import 'package:pilipala/pages/dynamics/widgets/author_panel.dart';
|
||||
import 'package:pilipala/pages/video/detail/reply/widgets/reply_item.dart';
|
||||
|
||||
import '../widgets/dynamic_panel.dart';
|
||||
|
||||
class DynamicDetailPage extends StatefulWidget {
|
||||
// const DynamicDetailPage({super.key});
|
||||
const DynamicDetailPage({Key? key}) : super(key: key);
|
||||
|
||||
@override
|
||||
State<DynamicDetailPage> createState() => _DynamicDetailPageState();
|
||||
}
|
||||
|
||||
class _DynamicDetailPageState extends State<DynamicDetailPage> {
|
||||
late DynamicDetailController? _dynamicDetailController;
|
||||
Future? _futureBuilderFuture;
|
||||
late StreamController<bool> titleStreamC; // appBar title
|
||||
final ScrollController scrollController = ScrollController();
|
||||
bool _visibleTitle = false;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
int oid = 0;
|
||||
int type = 0;
|
||||
if (Get.arguments['floor'] == 1) {
|
||||
oid = int.parse(Get.arguments['item'].basic!['comment_id_str']);
|
||||
type = Get.arguments['item'].basic!['comment_type'];
|
||||
} else {
|
||||
oid = Get.arguments['item'].modules.moduleDynamic.major.draw.id;
|
||||
type = 11;
|
||||
}
|
||||
|
||||
_dynamicDetailController = Get.put(DynamicDetailController(oid, type));
|
||||
_futureBuilderFuture = _dynamicDetailController!.queryReplyList();
|
||||
titleStreamC = StreamController<bool>();
|
||||
scrollController.addListener(_listen);
|
||||
}
|
||||
|
||||
void _listen() async {
|
||||
if (scrollController.position.pixels >=
|
||||
scrollController.position.maxScrollExtent - 300) {
|
||||
if (!_dynamicDetailController!.isLoadingMore) {
|
||||
_dynamicDetailController!.isLoadingMore = true;
|
||||
await _dynamicDetailController!.queryReplyList(reqType: 'onLoad');
|
||||
}
|
||||
}
|
||||
|
||||
if (scrollController.offset > 55 && !_visibleTitle) {
|
||||
_visibleTitle = true;
|
||||
titleStreamC.add(true);
|
||||
} else if (scrollController.offset <= 55 && _visibleTitle) {
|
||||
_visibleTitle = false;
|
||||
titleStreamC.add(false);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
elevation: 0,
|
||||
scrolledUnderElevation: 0,
|
||||
centerTitle: false,
|
||||
titleSpacing: 0,
|
||||
title: StreamBuilder(
|
||||
stream: titleStreamC.stream,
|
||||
initialData: false,
|
||||
builder: (context, AsyncSnapshot snapshot) {
|
||||
return AnimatedOpacity(
|
||||
opacity: snapshot.data ? 1 : 0,
|
||||
duration: const Duration(milliseconds: 300),
|
||||
child: author(_dynamicDetailController!.item, context),
|
||||
);
|
||||
},
|
||||
),
|
||||
// actions: _detailModel != null ? appBarAction() : [],
|
||||
),
|
||||
body: RefreshIndicator(
|
||||
onRefresh: () async {
|
||||
await _dynamicDetailController!.queryReplyList();
|
||||
},
|
||||
child: CustomScrollView(
|
||||
controller: scrollController,
|
||||
slivers: [
|
||||
SliverToBoxAdapter(
|
||||
child: DynamicPanel(
|
||||
item: _dynamicDetailController!.item,
|
||||
source: 'detail',
|
||||
),
|
||||
),
|
||||
SliverPersistentHeader(
|
||||
delegate: _MySliverPersistentHeaderDelegate(
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context).colorScheme.surface,
|
||||
border: Border(
|
||||
top: BorderSide(
|
||||
width: 0.6,
|
||||
color: Theme.of(context).dividerColor.withOpacity(0.05),
|
||||
),
|
||||
),
|
||||
),
|
||||
height: 45,
|
||||
padding: const EdgeInsets.only(left: 15, right: 12),
|
||||
child: Row(
|
||||
children: [
|
||||
Obx(
|
||||
() => AnimatedSwitcher(
|
||||
duration: const Duration(milliseconds: 400),
|
||||
transitionBuilder:
|
||||
(Widget child, Animation<double> animation) {
|
||||
return ScaleTransition(
|
||||
scale: animation, child: child);
|
||||
},
|
||||
child: Text(
|
||||
'${_dynamicDetailController!.acount.value}',
|
||||
key: ValueKey<int>(
|
||||
_dynamicDetailController!.acount.value),
|
||||
),
|
||||
),
|
||||
),
|
||||
const Text('条回复'),
|
||||
const Spacer(),
|
||||
// TextButton.icon(
|
||||
// onPressed: () {},
|
||||
// icon: const Icon(
|
||||
// Icons.subject_rounded,
|
||||
// size: 15,
|
||||
// ),
|
||||
// style: TextButton.styleFrom(
|
||||
// padding: const EdgeInsets.fromLTRB(12, 0, 12, 0),
|
||||
// foregroundColor:
|
||||
// Theme.of(context).colorScheme.outline,
|
||||
// ),
|
||||
// label: Text(
|
||||
// '按时间',
|
||||
// style: TextStyle(
|
||||
// fontSize: Theme.of(context)
|
||||
// .textTheme
|
||||
// .titleSmall!
|
||||
// .fontSize,
|
||||
// ),
|
||||
// ),
|
||||
// ),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
pinned: true,
|
||||
),
|
||||
FutureBuilder(
|
||||
future: _futureBuilderFuture,
|
||||
builder: (context, snapshot) {
|
||||
if (snapshot.connectionState == ConnectionState.done) {
|
||||
Map data = snapshot.data as Map;
|
||||
if (snapshot.data['status']) {
|
||||
// 请求成功
|
||||
return Obx(
|
||||
() => SliverList(
|
||||
delegate: SliverChildBuilderDelegate(
|
||||
(context, index) {
|
||||
if (index ==
|
||||
_dynamicDetailController!.replyList.length) {
|
||||
return Container(
|
||||
padding: EdgeInsets.only(
|
||||
bottom:
|
||||
MediaQuery.of(context).padding.bottom),
|
||||
height:
|
||||
MediaQuery.of(context).padding.bottom + 100,
|
||||
child: Center(
|
||||
child: Obx(() => Text(
|
||||
_dynamicDetailController!.noMore.value)),
|
||||
),
|
||||
);
|
||||
} else {
|
||||
return ReplyItem(
|
||||
replyItem: _dynamicDetailController!
|
||||
.replyList[index],
|
||||
showReplyRow: true,
|
||||
replyLevel: '1');
|
||||
}
|
||||
},
|
||||
childCount:
|
||||
_dynamicDetailController!.replyList.length + 1,
|
||||
),
|
||||
),
|
||||
);
|
||||
} else {
|
||||
// 请求错误
|
||||
return HttpError(
|
||||
errMsg: data['msg'],
|
||||
fn: () => setState(() {}),
|
||||
);
|
||||
}
|
||||
} else {
|
||||
// 骨架屏
|
||||
return SliverList(
|
||||
delegate: SliverChildBuilderDelegate((context, index) {
|
||||
return const VideoReplySkeleton();
|
||||
}, childCount: 5),
|
||||
);
|
||||
}
|
||||
},
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _MySliverPersistentHeaderDelegate extends SliverPersistentHeaderDelegate {
|
||||
final double _minExtent = 45;
|
||||
final double _maxExtent = 45;
|
||||
final Widget child;
|
||||
|
||||
_MySliverPersistentHeaderDelegate({required this.child});
|
||||
|
||||
@override
|
||||
Widget build(
|
||||
BuildContext context, double shrinkOffset, bool overlapsContent) {
|
||||
//创建child子组件
|
||||
//shrinkOffset:child偏移值minExtent~maxExtent
|
||||
//overlapsContent:SliverPersistentHeader覆盖其他子组件返回true,否则返回false
|
||||
return child;
|
||||
}
|
||||
|
||||
//SliverPersistentHeader最大高度
|
||||
@override
|
||||
double get maxExtent => _maxExtent;
|
||||
|
||||
//SliverPersistentHeader最小高度
|
||||
@override
|
||||
double get minExtent => _minExtent;
|
||||
|
||||
@override
|
||||
bool shouldRebuild(covariant _MySliverPersistentHeaderDelegate oldDelegate) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user