mirror of
https://github.com/HChaZZY/PiliPlus.git
synced 2025-12-06 09:13:48 +08:00
425 lines
15 KiB
Dart
425 lines
15 KiB
Dart
import 'package:PiliPlus/common/skeleton/video_reply.dart';
|
|
import 'package:PiliPlus/common/widgets/custom_icon.dart';
|
|
import 'package:PiliPlus/common/widgets/custom_sliver_persistent_header_delegate.dart';
|
|
import 'package:PiliPlus/common/widgets/dialog/dialog.dart';
|
|
import 'package:PiliPlus/common/widgets/image/network_img_layer.dart';
|
|
import 'package:PiliPlus/common/widgets/loading_widget/http_error.dart';
|
|
import 'package:PiliPlus/common/widgets/refresh_indicator.dart';
|
|
import 'package:PiliPlus/http/loading_state.dart';
|
|
import 'package:PiliPlus/models/common/image_type.dart';
|
|
import 'package:PiliPlus/models/common/pgc_review_type.dart';
|
|
import 'package:PiliPlus/models_new/pgc/pgc_review/list.dart';
|
|
import 'package:PiliPlus/pages/pgc_review/child/controller.dart';
|
|
import 'package:PiliPlus/pages/pgc_review/post/view.dart';
|
|
import 'package:PiliPlus/utils/accounts.dart';
|
|
import 'package:PiliPlus/utils/extension.dart';
|
|
import 'package:PiliPlus/utils/num_utils.dart';
|
|
import 'package:PiliPlus/utils/utils.dart';
|
|
import 'package:flutter/material.dart';
|
|
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
|
import 'package:get/get.dart';
|
|
|
|
class PgcReviewChildPage extends StatefulWidget {
|
|
const PgcReviewChildPage({
|
|
super.key,
|
|
required this.type,
|
|
required this.name,
|
|
required this.mediaId,
|
|
});
|
|
|
|
final PgcReviewType type;
|
|
final String name;
|
|
final dynamic mediaId;
|
|
|
|
@override
|
|
State<PgcReviewChildPage> createState() => _PgcReviewChildPageState();
|
|
}
|
|
|
|
class _PgcReviewChildPageState extends State<PgcReviewChildPage>
|
|
with AutomaticKeepAliveClientMixin {
|
|
late final _tag = '${widget.mediaId}${widget.type.name}';
|
|
late final _controller = Get.put(
|
|
PgcReviewController(type: widget.type, mediaId: widget.mediaId),
|
|
tag: _tag,
|
|
);
|
|
late final isLongReview = widget.type == PgcReviewType.long;
|
|
|
|
@override
|
|
void dispose() {
|
|
Get.delete<PgcReviewController>(tag: _tag);
|
|
super.dispose();
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
super.build(context);
|
|
final theme = Theme.of(context);
|
|
return refreshIndicator(
|
|
onRefresh: _controller.onRefresh,
|
|
child: CustomScrollView(
|
|
controller: _controller.scrollController,
|
|
physics: const AlwaysScrollableScrollPhysics(),
|
|
slivers: [
|
|
_buildHeader(theme),
|
|
SliverPadding(
|
|
padding: EdgeInsets.only(
|
|
bottom: MediaQuery.viewPaddingOf(context).bottom + 100,
|
|
),
|
|
sliver: Obx(
|
|
() => _buildBody(theme, _controller.loadingState.value),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
|
|
Widget _buildBody(
|
|
ThemeData theme,
|
|
LoadingState<List<PgcReviewItemModel>?> loadingState,
|
|
) {
|
|
late final divider = Divider(
|
|
height: 1,
|
|
color: theme.colorScheme.outline.withValues(alpha: 0.1),
|
|
);
|
|
return switch (loadingState) {
|
|
Loading() => SliverPrototypeExtentList.builder(
|
|
prototypeItem: const VideoReplySkeleton(),
|
|
itemBuilder: (_, _) => const VideoReplySkeleton(),
|
|
itemCount: 8,
|
|
),
|
|
Success(:var response) =>
|
|
response?.isNotEmpty == true
|
|
? SliverList.separated(
|
|
itemBuilder: (context, index) {
|
|
if (index == response.length - 1) {
|
|
_controller.onLoadMore();
|
|
}
|
|
return _itemWidget(theme, index, response[index]);
|
|
},
|
|
itemCount: response!.length,
|
|
separatorBuilder: (context, index) => divider,
|
|
)
|
|
: HttpError(onReload: _controller.onReload),
|
|
Error(:var errMsg) => HttpError(
|
|
errMsg: errMsg,
|
|
onReload: _controller.onReload,
|
|
),
|
|
};
|
|
}
|
|
|
|
Widget _itemWidget(ThemeData theme, int index, PgcReviewItemModel item) {
|
|
void showMore() => showDialog(
|
|
context: context,
|
|
builder: (context) => AlertDialog(
|
|
clipBehavior: Clip.hardEdge,
|
|
contentPadding: const EdgeInsets.symmetric(vertical: 12),
|
|
content: Column(
|
|
mainAxisSize: MainAxisSize.min,
|
|
children: [
|
|
if (item.author!.mid == Accounts.main.mid) ...[
|
|
ListTile(
|
|
dense: true,
|
|
title: const Text(
|
|
'编辑',
|
|
style: TextStyle(fontSize: 14),
|
|
),
|
|
onTap: () {
|
|
Get.back();
|
|
showModalBottomSheet(
|
|
context: context,
|
|
useSafeArea: true,
|
|
isScrollControlled: true,
|
|
builder: (context) {
|
|
return PgcReviewPostPanel(
|
|
name: widget.name,
|
|
mediaId: widget.mediaId,
|
|
reviewId: item.reviewId,
|
|
content: item.content,
|
|
score: item.score,
|
|
);
|
|
},
|
|
);
|
|
},
|
|
),
|
|
ListTile(
|
|
dense: true,
|
|
title: const Text(
|
|
'删除',
|
|
style: TextStyle(fontSize: 14),
|
|
),
|
|
onTap: () {
|
|
Get.back();
|
|
showConfirmDialog(
|
|
context: context,
|
|
title: '删除短评,同时删除评分?',
|
|
onConfirm: () => _controller.onDel(index, item.reviewId),
|
|
);
|
|
},
|
|
),
|
|
],
|
|
ListTile(
|
|
dense: true,
|
|
title: const Text(
|
|
'举报',
|
|
style: TextStyle(fontSize: 14),
|
|
),
|
|
onTap: () => Get
|
|
..back()
|
|
..toNamed(
|
|
'/webview',
|
|
parameters: {
|
|
'url':
|
|
'https://www.bilibili.com/appeal/?reviewId=${item.reviewId}&type=shortComment&mediaId=${widget.mediaId}',
|
|
},
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
);
|
|
|
|
return Material(
|
|
type: MaterialType.transparency,
|
|
child: InkWell(
|
|
onTap: isLongReview
|
|
? () => Get.toNamed(
|
|
'/articlePage',
|
|
parameters: {
|
|
'id': item.articleId!.toString(),
|
|
'type': 'read',
|
|
},
|
|
)
|
|
: null,
|
|
onLongPress: !isLongReview ? showMore : null,
|
|
onSecondaryTap: !isLongReview && !Utils.isMobile ? showMore : null,
|
|
child: Padding(
|
|
padding: const EdgeInsets.all(12),
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
GestureDetector(
|
|
behavior: HitTestBehavior.opaque,
|
|
onTap: () => Get.toNamed('/member?mid=${item.author!.mid}'),
|
|
child: Row(
|
|
mainAxisSize: MainAxisSize.min,
|
|
children: [
|
|
NetworkImgLayer(
|
|
height: 34,
|
|
width: 34,
|
|
src: item.author!.avatar,
|
|
type: ImageType.avatar,
|
|
),
|
|
const SizedBox(width: 10),
|
|
Column(
|
|
spacing: 2,
|
|
mainAxisSize: MainAxisSize.min,
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Row(
|
|
spacing: 6,
|
|
mainAxisSize: MainAxisSize.min,
|
|
children: [
|
|
Text(
|
|
item.author!.uname!,
|
|
style: TextStyle(
|
|
color:
|
|
item.author?.vip?.status != null &&
|
|
item.author!.vip!.status > 0 &&
|
|
item.author!.vip!.type == 2
|
|
? theme.colorScheme.vipColor
|
|
: theme.colorScheme.outline,
|
|
fontSize: 13,
|
|
),
|
|
),
|
|
Image.asset(
|
|
'assets/images/lv/lv${item.author!.level}.png',
|
|
height: 11,
|
|
),
|
|
],
|
|
),
|
|
Row(
|
|
children: [
|
|
if (item.pushTimeStr != null) ...[
|
|
Text(
|
|
item.pushTimeStr!,
|
|
style: TextStyle(
|
|
color: theme.colorScheme.outline,
|
|
fontSize: 12,
|
|
),
|
|
),
|
|
const SizedBox(width: 10),
|
|
],
|
|
...List.generate(
|
|
5,
|
|
(index) {
|
|
if (index <= item.score - 1) {
|
|
return const Icon(
|
|
CustomIcons.star_favorite_solid,
|
|
size: 13,
|
|
color: Color(0xFFFFAD35),
|
|
);
|
|
}
|
|
return const Icon(
|
|
CustomIcons.star_favorite_line,
|
|
size: 14,
|
|
color: Colors.grey,
|
|
);
|
|
},
|
|
),
|
|
],
|
|
),
|
|
],
|
|
),
|
|
],
|
|
),
|
|
),
|
|
const SizedBox(height: 5),
|
|
if (item.title != null)
|
|
Text(
|
|
item.title!,
|
|
style: const TextStyle(
|
|
height: 1.75,
|
|
fontSize: 15,
|
|
fontWeight: FontWeight.bold,
|
|
),
|
|
),
|
|
if (isLongReview)
|
|
Text(
|
|
item.content!,
|
|
style: const TextStyle(height: 1.75),
|
|
)
|
|
else
|
|
SelectableText(
|
|
item.content!,
|
|
style: const TextStyle(height: 1.75),
|
|
),
|
|
Builder(
|
|
builder: (context) {
|
|
final Color color = theme.colorScheme.outline;
|
|
final Color primary = theme.colorScheme.primary;
|
|
final ButtonStyle style = TextButton.styleFrom(
|
|
padding: EdgeInsets.zero,
|
|
tapTargetSize: MaterialTapTargetSize.shrinkWrap,
|
|
visualDensity: VisualDensity.compact,
|
|
);
|
|
final isLike = item.stat?.liked == 1;
|
|
late final isDislike = item.stat?.disliked == 1;
|
|
return Row(
|
|
mainAxisAlignment: MainAxisAlignment.end,
|
|
children: [
|
|
if (!isLongReview)
|
|
SizedBox(
|
|
height: 32,
|
|
child: TextButton(
|
|
style: style,
|
|
onPressed: () => _controller.onDislike(
|
|
item,
|
|
isDislike,
|
|
item.reviewId,
|
|
),
|
|
child: Icon(
|
|
isDislike
|
|
? FontAwesomeIcons.solidThumbsDown
|
|
: FontAwesomeIcons.thumbsDown,
|
|
size: 16,
|
|
color: isDislike ? primary : color,
|
|
),
|
|
),
|
|
),
|
|
SizedBox(
|
|
height: 32,
|
|
child: TextButton(
|
|
style: style,
|
|
onPressed: isLongReview
|
|
? null
|
|
: () => _controller.onLike(
|
|
item,
|
|
isLike,
|
|
item.reviewId,
|
|
),
|
|
child: Row(
|
|
spacing: 4,
|
|
children: [
|
|
Icon(
|
|
isLike
|
|
? FontAwesomeIcons.solidThumbsUp
|
|
: FontAwesomeIcons.thumbsUp,
|
|
size: 16,
|
|
color: isLike ? primary : color,
|
|
),
|
|
Text(
|
|
NumUtils.numFormat(item.stat?.likes ?? 0),
|
|
style: TextStyle(
|
|
color: isLike ? primary : color,
|
|
fontSize: 12,
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
],
|
|
);
|
|
},
|
|
),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
Widget _buildHeader(ThemeData theme) => SliverPersistentHeader(
|
|
pinned: false,
|
|
floating: true,
|
|
delegate: CustomSliverPersistentHeaderDelegate(
|
|
extent: 40,
|
|
bgColor: theme.colorScheme.surface,
|
|
child: Container(
|
|
height: 40,
|
|
padding: const EdgeInsets.fromLTRB(12, 0, 6, 0),
|
|
child: Row(
|
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
|
children: [
|
|
Obx(
|
|
() {
|
|
final count = _controller.count.value;
|
|
return count == null
|
|
? const SizedBox.shrink()
|
|
: Text(
|
|
'${NumUtils.numFormat(count)}条点评',
|
|
style: const TextStyle(fontSize: 13),
|
|
);
|
|
},
|
|
),
|
|
SizedBox(
|
|
height: 35,
|
|
child: TextButton.icon(
|
|
onPressed: _controller.queryBySort,
|
|
icon: Icon(
|
|
Icons.sort,
|
|
size: 16,
|
|
color: theme.colorScheme.secondary,
|
|
),
|
|
label: Obx(
|
|
() => Text(
|
|
_controller.sortType.value.label,
|
|
style: TextStyle(
|
|
fontSize: 13,
|
|
color: theme.colorScheme.secondary,
|
|
),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
);
|
|
|
|
@override
|
|
bool get wantKeepAlive => true;
|
|
}
|