mirror of
https://github.com/bggRGjQaUbCoE/PiliPlus.git
synced 2026-08-04 17:50:13 +08:00
feat: pgc review
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
This commit is contained in:
89
lib/pages/pgc_review/child/controller.dart
Normal file
89
lib/pages/pgc_review/child/controller.dart
Normal file
@@ -0,0 +1,89 @@
|
||||
import 'package:PiliPlus/http/bangumi.dart';
|
||||
import 'package:PiliPlus/http/loading_state.dart';
|
||||
import 'package:PiliPlus/models/bangumi/pgc_review/data.dart';
|
||||
import 'package:PiliPlus/models/bangumi/pgc_review/list.dart';
|
||||
import 'package:PiliPlus/models/common/pgc_review_type.dart';
|
||||
import 'package:PiliPlus/pages/common/common_list_controller.dart';
|
||||
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
|
||||
|
||||
class PgcReviewController
|
||||
extends CommonListController<PgcReviewData, PgcReviewItemModel> {
|
||||
PgcReviewController({required this.type, required this.mediaId});
|
||||
|
||||
final PgcReviewType type;
|
||||
final dynamic mediaId;
|
||||
|
||||
int? count;
|
||||
String? next;
|
||||
|
||||
@override
|
||||
void onInit() {
|
||||
super.onInit();
|
||||
queryData();
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> onRefresh() {
|
||||
count = null;
|
||||
next = null;
|
||||
return super.onRefresh();
|
||||
}
|
||||
|
||||
@override
|
||||
void checkIsEnd(int length) {
|
||||
if (count != null && length >= count!) {
|
||||
isEnd = true;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
List<PgcReviewItemModel>? getDataList(PgcReviewData response) {
|
||||
count = response.count;
|
||||
next = response.next;
|
||||
return response.list;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<LoadingState<PgcReviewData>> customGetData() => BangumiHttp.pgcReview(
|
||||
type: type,
|
||||
mediaId: mediaId,
|
||||
next: next,
|
||||
);
|
||||
|
||||
Future<void> onLike(int index, bool isLike, reviewId) async {
|
||||
var res = await BangumiHttp.pgcReviewLike(
|
||||
mediaId: mediaId,
|
||||
reviewId: reviewId,
|
||||
);
|
||||
if (res['status']) {
|
||||
final item = loadingState.value.data![index];
|
||||
int likes = item.stat?.likes ?? 0;
|
||||
item.stat
|
||||
?..liked = isLike ? 0 : 1
|
||||
..likes = isLike ? likes - 1 : likes + 1;
|
||||
if (!isLike) {
|
||||
item.stat?.disliked = 0;
|
||||
}
|
||||
loadingState.refresh();
|
||||
} else {
|
||||
SmartDialog.showToast(res['msg']);
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> onDislike(int index, bool isDislike, reviewId) async {
|
||||
var res = await BangumiHttp.pgcReviewDislike(
|
||||
mediaId: mediaId,
|
||||
reviewId: reviewId,
|
||||
);
|
||||
if (res['status']) {
|
||||
final item = loadingState.value.data![index];
|
||||
item.stat?.disliked = isDislike ? 0 : 1;
|
||||
if (!isDislike) {
|
||||
item.stat?.liked = 0;
|
||||
}
|
||||
loadingState.refresh();
|
||||
} else {
|
||||
SmartDialog.showToast(res['msg']);
|
||||
}
|
||||
}
|
||||
}
|
||||
298
lib/pages/pgc_review/child/view.dart
Normal file
298
lib/pages/pgc_review/child/view.dart
Normal file
@@ -0,0 +1,298 @@
|
||||
import 'package:PiliPlus/common/skeleton/video_reply.dart';
|
||||
import 'package:PiliPlus/common/widgets/custom_icon.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/bangumi/pgc_review/list.dart';
|
||||
import 'package:PiliPlus/models/common/image_type.dart';
|
||||
import 'package:PiliPlus/models/common/pgc_review_type.dart';
|
||||
import 'package:PiliPlus/pages/pgc_review/child/controller.dart';
|
||||
import 'package:PiliPlus/utils/extension.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.mediaId,
|
||||
});
|
||||
|
||||
final PgcReviewType type;
|
||||
final dynamic mediaId;
|
||||
|
||||
@override
|
||||
State<PgcReviewChildPage> createState() => _PgcReviewChildPageState();
|
||||
}
|
||||
|
||||
class _PgcReviewChildPageState extends State<PgcReviewChildPage>
|
||||
with AutomaticKeepAliveClientMixin {
|
||||
late final _controller = Get.put(
|
||||
PgcReviewController(type: widget.type, mediaId: widget.mediaId),
|
||||
tag: '${widget.mediaId}${widget.type.name}',
|
||||
);
|
||||
late final isLongReview = widget.type == PgcReviewType.long;
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
Get.delete<PgcReviewController>(
|
||||
tag: '${widget.mediaId}${widget.type.name}');
|
||||
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: [
|
||||
SliverPadding(
|
||||
padding: EdgeInsets.only(
|
||||
bottom: MediaQuery.paddingOf(context).bottom + 80),
|
||||
sliver:
|
||||
Obx(() => _buildBody(theme, _controller.loadingState.value)),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildBody(
|
||||
ThemeData theme, LoadingState<List<PgcReviewItemModel>?> loadingState) {
|
||||
return switch (loadingState) {
|
||||
Loading() => SliverToBoxAdapter(
|
||||
child: ListView.builder(
|
||||
shrinkWrap: true,
|
||||
physics: const NeverScrollableScrollPhysics(),
|
||||
itemBuilder: (context, index) {
|
||||
return 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(
|
||||
height: 1,
|
||||
color: theme.colorScheme.outline.withValues(alpha: 0.1),
|
||||
),
|
||||
)
|
||||
: HttpError(onReload: _controller.onReload),
|
||||
Error(:var errMsg) => HttpError(
|
||||
errMsg: errMsg,
|
||||
onReload: _controller.onReload,
|
||||
),
|
||||
};
|
||||
}
|
||||
|
||||
Widget _itemWidget(ThemeData theme, int index, PgcReviewItemModel item) {
|
||||
return InkWell(
|
||||
onTap: isLongReview
|
||||
? () => Get.toNamed(
|
||||
'/articlePage',
|
||||
parameters: {
|
||||
'id': item.articleId!.toString(),
|
||||
'type': 'read',
|
||||
},
|
||||
)
|
||||
: null,
|
||||
onLongPress: isLongReview
|
||||
? null
|
||||
: () => showConfirmDialog(
|
||||
context: context,
|
||||
title: '确定举报该点评?',
|
||||
onConfirm: () => Get.toNamed(
|
||||
'/webview',
|
||||
parameters: {
|
||||
'url':
|
||||
'https://www.bilibili.com/appeal/?reviewId=${item.reviewId}&type=shortComment&mediaId=${widget.mediaId}'
|
||||
},
|
||||
),
|
||||
),
|
||||
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
|
||||
? context.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(
|
||||
CustomIcon.star_favorite_solid,
|
||||
size: 13,
|
||||
color: Color(0xFFFFAD35),
|
||||
);
|
||||
}
|
||||
return const Icon(
|
||||
CustomIcon.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(
|
||||
index, 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(
|
||||
index, isLike, item.reviewId),
|
||||
child: Row(
|
||||
spacing: 4,
|
||||
children: [
|
||||
Icon(
|
||||
isLike
|
||||
? FontAwesomeIcons.solidThumbsUp
|
||||
: FontAwesomeIcons.thumbsUp,
|
||||
size: 16,
|
||||
color: isLike ? primary : color,
|
||||
),
|
||||
Text(
|
||||
Utils.numFormat(item.stat?.likes ?? 0),
|
||||
style: TextStyle(
|
||||
color: isLike ? primary : color,
|
||||
fontSize: 12,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
bool get wantKeepAlive => true;
|
||||
}
|
||||
Reference in New Issue
Block a user