refa: query data (#659)

Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
This commit is contained in:
dom
2025-04-10 21:43:01 +08:00
committed by GitHub
parent 99b19e7b03
commit e1b73f4766
128 changed files with 1493 additions and 1987 deletions

View File

@@ -10,7 +10,7 @@ import 'package:PiliPlus/http/html.dart';
import 'package:PiliPlus/http/reply.dart';
import 'package:fixnum/fixnum.dart' as $fixnum;
class DynamicDetailController extends ReplyController {
class DynamicDetailController extends ReplyController<MainListReply> {
DynamicDetailController(this.oid, this.type);
int oid;
int type;
@@ -47,7 +47,13 @@ class DynamicDetailController extends ReplyController {
}
@override
Future<LoadingState> customGetData() => ReplyHttp.replyListGrpc(
List<ReplyInfo>? getDataList(MainListReply response) {
return response.replies;
}
@override
Future<LoadingState<MainListReply>> customGetData() =>
ReplyHttp.replyListGrpc(
type: type,
oid: oid,
cursor: CursorReq(

View File

@@ -775,7 +775,7 @@ class _DynamicDetailPageState extends State<DynamicDetailPage>
);
}
Widget replyList(LoadingState loadingState) {
Widget replyList(LoadingState<List<ReplyInfo>?> loadingState) {
return switch (loadingState) {
Loading() => SliverList(
delegate: SliverChildBuilderDelegate(
@@ -785,11 +785,11 @@ class _DynamicDetailPageState extends State<DynamicDetailPage>
childCount: 8,
),
),
Success() => (loadingState.response.replies as List?)?.isNotEmpty == true
Success() => loadingState.response?.isNotEmpty == true
? SliverList(
delegate: SliverChildBuilderDelegate(
(context, index) {
if (index == loadingState.response.replies.length) {
if (index == loadingState.response!.length) {
_dynamicDetailController.onLoadMore();
return Container(
alignment: Alignment.center,
@@ -799,7 +799,7 @@ class _DynamicDetailPageState extends State<DynamicDetailPage>
child: Text(
_dynamicDetailController.isEnd.not
? '加载中...'
: loadingState.response.replies.isEmpty
: loadingState.response!.isEmpty
? '还没有评论'
: '没有更多了',
style: TextStyle(
@@ -810,19 +810,20 @@ class _DynamicDetailPageState extends State<DynamicDetailPage>
);
} else {
return ReplyItemGrpc(
replyItem: loadingState.response.replies[index],
replyItem: loadingState.response![index],
replyLevel: '1',
replyReply: (replyItem, id) =>
replyReply(context, replyItem, id),
onReply: () {
_dynamicDetailController.onReply(
context,
replyItem: loadingState.response.replies[index],
replyItem: loadingState.response![index],
index: index,
);
},
onDelete: _dynamicDetailController.onMDelete,
upMid: loadingState.response.subjectControl.upMid,
onDelete: (subIndex) =>
_dynamicDetailController.onRemove(index, subIndex),
upMid: _dynamicDetailController.upMid,
callback: _getImageCallback,
onCheckReply: (item) =>
_dynamicDetailController.onCheckReply(context, item),
@@ -837,7 +838,7 @@ class _DynamicDetailPageState extends State<DynamicDetailPage>
);
}
},
childCount: loadingState.response.replies.length + 1,
childCount: loadingState.response!.length + 1,
),
)
: HttpError(

View File

@@ -1,15 +1,15 @@
import 'package:PiliPlus/http/loading_state.dart';
import 'package:PiliPlus/http/msg.dart';
import 'package:PiliPlus/models/dynamics/result.dart';
import 'package:PiliPlus/pages/common/common_controller.dart';
import 'package:PiliPlus/pages/common/common_list_controller.dart';
import 'package:PiliPlus/pages/main/controller.dart';
import 'package:PiliPlus/utils/extension.dart';
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
import 'package:get/get.dart';
import '../../../http/dynamics.dart';
class DynamicsTabController extends CommonController {
class DynamicsTabController
extends CommonListController<DynamicsDataModel, DynamicItemModel> {
DynamicsTabController({required this.dynamicsType});
final String dynamicsType;
String offset = '';
@@ -32,22 +32,20 @@ class DynamicsTabController extends CommonController {
}
@override
bool customHandleResponse(Success response) {
offset = response.response.offset;
if ((response.response.items as List?).isNullOrEmpty) {
isEnd = true;
}
if (currentPage != 1 && loadingState.value is Success) {
response.response.items ??= <DynamicItemModel>[];
response.response.items!
.insertAll(0, (loadingState.value as Success).response);
}
loadingState.value = LoadingState.success(response.response.items);
return true;
List<DynamicItemModel>? getDataList(DynamicsDataModel response) {
return response.items;
}
@override
Future<LoadingState> customGetData() => DynamicsHttp.followDynamic(
bool customHandleResponse(
bool isRefresh, Success<DynamicsDataModel> response) {
offset = response.response.offset ?? '';
return false;
}
@override
Future<LoadingState<DynamicsDataModel>> customGetData() =>
DynamicsHttp.followDynamic(
type: dynamicsType == "up" ? "all" : dynamicsType,
offset: offset,
mid: dynamicsType == "up" ? mid : -1,
@@ -56,9 +54,9 @@ class DynamicsTabController extends CommonController {
Future onRemove(dynamic dynamicId) async {
var res = await MsgHttp.removeDynamic(dynamicId);
if (res['status']) {
List list = (loadingState.value as Success).response;
List<DynamicItemModel> list = (loadingState.value as Success).response;
list.removeWhere((item) => item.idStr == dynamicId);
loadingState.value = LoadingState.success(list);
loadingState.refresh();
SmartDialog.showToast('删除成功');
} else {
SmartDialog.showToast(res['msg']);

View File

@@ -2,6 +2,7 @@ import 'dart:async';
import 'package:PiliPlus/common/widgets/refresh_indicator.dart';
import 'package:PiliPlus/http/loading_state.dart';
import 'package:PiliPlus/models/dynamics/result.dart';
import 'package:PiliPlus/pages/common/common_page.dart';
import 'package:PiliPlus/pages/main/controller.dart';
import 'package:PiliPlus/utils/storage.dart';
@@ -137,10 +138,10 @@ class _DynamicsTabPageState
);
}
Widget _buildBody(LoadingState loadingState) {
Widget _buildBody(LoadingState<List<DynamicItemModel>?> loadingState) {
return switch (loadingState) {
Loading() => skeleton(),
Success() => (loadingState.response as List?)?.isNotEmpty == true
Success() => loadingState.response?.isNotEmpty == true
? SliverPadding(
padding: EdgeInsets.only(
bottom: MediaQuery.paddingOf(context).bottom + 80,
@@ -153,23 +154,23 @@ class _DynamicsTabPageState
// mainAxisSpacing: StyleString.cardSpace / 2,
lastChildLayoutTypeBuilder: (index) {
if (index == loadingState.response.length - 1) {
if (index == loadingState.response!.length - 1) {
controller.onLoadMore();
}
return index == loadingState.response.length
return index == loadingState.response!.length
? LastChildLayoutType.foot
: LastChildLayoutType.none;
},
children: [
if (dynamicsController.tabController.index == 4 &&
dynamicsController.mid.value != -1) ...[
for (var i in loadingState.response)
for (var i in loadingState.response!)
DynamicPanel(
item: i,
onRemove: controller.onRemove,
),
] else ...[
for (var i in loadingState.response)
for (var i in loadingState.response!)
if (!dynamicsController.tempBannedList
.contains(i.modules?.moduleAuthor?.mid))
DynamicPanel(
@@ -187,23 +188,24 @@ class _DynamicsTabPageState
sliver: SliverList(
delegate: SliverChildBuilderDelegate(
(context, index) {
if (index == loadingState.response.length - 1) {
if (index ==
loadingState.response!.length - 1) {
controller.onLoadMore();
}
final item = loadingState.response![index];
if ((dynamicsController.tabController.index ==
4 &&
dynamicsController.mid.value != -1) ||
!dynamicsController.tempBannedList.contains(
loadingState.response[index].modules
?.moduleAuthor?.mid)) {
item.modules?.moduleAuthor?.mid)) {
return DynamicPanel(
item: loadingState.response[index],
item: item,
onRemove: controller.onRemove,
);
}
return const SizedBox.shrink();
},
childCount: loadingState.response.length,
childCount: loadingState.response!.length,
),
),
),

View File

@@ -1,293 +0,0 @@
import 'dart:math';
import 'package:PiliPlus/grpc/app/dynamic/v2/dynamic.pb.dart' as dyn;
import 'package:PiliPlus/utils/extension.dart';
import 'package:PiliPlus/utils/storage.dart';
import 'package:flutter/material.dart';
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
import 'package:get/get.dart';
import 'package:PiliPlus/common/widgets/network_img_layer.dart';
import 'package:PiliPlus/http/user.dart';
import 'package:PiliPlus/utils/feed_back.dart';
import 'package:PiliPlus/utils/utils.dart';
import '../../../http/constants.dart';
import '../controller.dart';
class AuthorPanelGrpc extends StatelessWidget {
final dyn.DynamicItem item;
final Function? addBannedList;
final String? source;
final Function? onRemove;
const AuthorPanelGrpc({
super.key,
required this.item,
this.addBannedList,
this.source,
this.onRemove,
});
@override
Widget build(BuildContext context) {
String heroTag = Utils.makeHeroTag(item.modules.first.moduleAuthor.mid);
return Row(
children: [
GestureDetector(
onTap: () {
// 番剧
// if (item.modules.first.moduleAuthor.type == 'AUTHOR_TYPE_PGC') {
// return;
// }
feedBack();
Get.toNamed(
'/member?mid=${item.modules.first.moduleAuthor.author.mid}',
arguments: {
'face': item.modules.first.moduleAuthor.author.face,
'heroTag': heroTag
},
);
},
child: Hero(
tag: heroTag,
child: NetworkImgLayer(
width: 40,
height: 40,
type: 'avatar',
src: item.modules.first.moduleAuthor.author.face,
),
),
),
const SizedBox(width: 10),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Text(
item.modules.first.moduleAuthor.author.name,
// semanticsLabel: "UP主${item.modules.moduleAuthor.name}",
style: TextStyle(
color: item.modules.first.moduleAuthor.author.vip.status >
0 &&
item.modules.first.moduleAuthor.author.vip.type == 2
? context.vipColor
: Theme.of(context).colorScheme.onSurface,
fontSize: Theme.of(context).textTheme.titleSmall!.fontSize,
),
),
],
),
// DefaultTextStyle.merge(
// style: TextStyle(
// color: Theme.of(context).colorScheme.outline,
// fontSize: Theme.of(context).textTheme.labelSmall!.fontSize,
// ),
// child: Row(
// children: [
// Text(item.modules.first.moduleAuthor.pubTime),
// if (item.modules.first.moduleAuthor.pubTime != '' &&
// item.modules.first.moduleAuthor.pubAction != '')
// const Text(' '),
// Text(item.modules.first.moduleAuthor.pubAction),
// ],
// ),
// )
],
),
const Spacer(),
// if (source != 'detail' && item.modules.first?.moduleTag?.text != null)
// Container(
// padding: const EdgeInsets.symmetric(horizontal: 4, vertical: 2),
// decoration: BoxDecoration(
// color: Theme.of(context).colorScheme.surface,
// borderRadius: const BorderRadius.all(Radius.circular(4)),
// border: Border.all(
// width: 1.25,
// color: Theme.of(context).colorScheme.primary,
// ),
// ),
// child: Text(
// item.modules.first.moduleTag.text,
// style: TextStyle(
// height: 1,
// fontSize: 12,
// color: Theme.of(context).colorScheme.primary,
// ),
// strutStyle: const StrutStyle(
// leading: 0,
// height: 1,
// fontSize: 12,
// ),
// ),
// ),
SizedBox(
width: 32,
height: 32,
child: IconButton(
tooltip: '更多',
style: ButtonStyle(
padding: WidgetStateProperty.all(EdgeInsets.zero),
),
onPressed: () {
showModalBottomSheet(
context: context,
useSafeArea: true,
isScrollControlled: true,
constraints: BoxConstraints(
maxWidth: min(640, min(Get.width, Get.height)),
),
builder: (context) {
return MorePanel(
item: item,
onRemove: onRemove,
);
},
);
},
icon: const Icon(Icons.more_vert_outlined, size: 18),
),
),
],
);
}
}
class MorePanel extends StatelessWidget {
final dynamic item;
final Function? onRemove;
const MorePanel({
super.key,
required this.item,
this.onRemove,
});
@override
Widget build(BuildContext context) {
return Padding(
padding: EdgeInsets.only(bottom: MediaQuery.of(context).padding.bottom),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
InkWell(
onTap: Get.back,
borderRadius: const BorderRadius.only(
topLeft: Radius.circular(28),
topRight: Radius.circular(28),
),
child: Container(
height: 35,
padding: const EdgeInsets.only(bottom: 2),
child: Center(
child: Container(
width: 32,
height: 3,
decoration: BoxDecoration(
color: Theme.of(context).colorScheme.outline,
borderRadius: const BorderRadius.all(Radius.circular(3))),
),
),
),
),
if (item.type == 'DYNAMIC_TYPE_AV')
ListTile(
onTap: () async {
try {
String bvid = item.modules.moduleDynamic.major.archive.bvid;
var res = await UserHttp.toViewLater(bvid: bvid);
SmartDialog.showToast(res['msg']);
Get.back();
} catch (err) {
SmartDialog.showToast('出错了:${err.toString()}');
}
},
minLeadingWidth: 0,
// dense: true,
leading: const Icon(Icons.watch_later_outlined, size: 19),
title: Text(
'稍后再看',
style: Theme.of(context).textTheme.titleSmall,
),
),
ListTile(
title: Text(
'分享动态',
style: Theme.of(context).textTheme.titleSmall,
),
leading: const Icon(Icons.share_outlined, size: 19),
onTap: () {
Get.back();
Utils.shareText(
'${HttpString.dynamicShareBaseUrl}/${item.idStr}');
},
minLeadingWidth: 0,
),
ListTile(
title: Text(
'临时屏蔽:${item.modules.moduleAuthor.name}',
style: Theme.of(context).textTheme.titleSmall,
),
leading: const Icon(Icons.visibility_off_outlined, size: 19),
onTap: () {
Get.back();
DynamicsController dynamicsController =
Get.find<DynamicsController>();
dynamicsController.tempBannedList
.add(item.modules.moduleAuthor.mid);
SmartDialog.showToast(
'已临时屏蔽${item.modules.moduleAuthor.name}(${item.modules.moduleAuthor.mid}),重启恢复');
},
minLeadingWidth: 0,
),
if (item.modules.moduleAuthor.mid == Accounts.main.mid)
ListTile(
onTap: () async {
Get.back();
showDialog(
context: context,
builder: (context) => AlertDialog(
title: const Text('确定删除该动态?'),
actions: [
TextButton(
onPressed: Get.back,
child: Text(
'取消',
style: TextStyle(
color: Theme.of(context).colorScheme.outline,
),
),
),
TextButton(
onPressed: () {
Get.back();
onRemove?.call(item.idStr);
},
child: const Text('确定'),
),
],
));
},
minLeadingWidth: 0,
leading: Icon(Icons.delete_outline,
color: Theme.of(context).colorScheme.error, size: 19),
title: Text('删除',
style: Theme.of(context)
.textTheme
.titleSmall!
.copyWith(color: Theme.of(context).colorScheme.error)),
),
const Divider(thickness: 0.1, height: 1),
ListTile(
onTap: () => Get.back(),
minLeadingWidth: 0,
dense: true,
title: Text(
'取消',
style: TextStyle(color: Theme.of(context).colorScheme.outline),
textAlign: TextAlign.center,
),
),
],
),
);
}
}

View File

@@ -1,82 +0,0 @@
// 内容
import 'package:PiliPlus/common/widgets/image_view.dart';
import 'package:PiliPlus/grpc/app/dynamic/v2/dynamic.pb.dart';
import 'package:flutter/material.dart';
import 'rich_node_panel.dart';
class ContentGrpc extends StatelessWidget {
final DynamicItem item;
final String? source;
const ContentGrpc({
super.key,
required this.item,
this.source,
});
InlineSpan picsNodes() {
return WidgetSpan(
child: LayoutBuilder(
builder: (context, constraints) => imageView(
constraints.maxWidth,
item.modules.first.moduleDynamic.dynDraw.items
.map(
(item) => ImageModel(
width: item.width,
height: item.height,
url: item.src,
),
)
.toList(),
),
),
);
}
@override
Widget build(BuildContext context) {
TextStyle authorStyle =
TextStyle(color: Theme.of(context).colorScheme.primary);
InlineSpan? richNodes = richNode(item, context);
return Container(
width: double.infinity,
padding: const EdgeInsets.fromLTRB(12, 0, 12, 6),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
if (item.modules.first.moduleDynamic.hasDynTopicSet()) ...[
GestureDetector(
child: Text(
'#${item.modules.first.moduleDynamic.dynTopicSet.topics.first.topicName}',
style: authorStyle,
),
),
],
if (richNodes != null)
IgnorePointer(
// 禁用SelectableRegion的触摸交互功能
ignoring: source == 'detail' ? false : true,
child: SelectableRegion(
magnifierConfiguration: const TextMagnifierConfiguration(),
focusNode: FocusNode(),
selectionControls: MaterialTextSelectionControls(),
child: Text.rich(
/// fix 默认20px高度
style: const TextStyle(height: 0),
richNodes,
maxLines: source == 'detail' ? null : 6,
overflow: source == 'detail' ? null : TextOverflow.ellipsis,
),
),
),
if (item.modules.first.moduleDynamic.hasDynDraw())
Text.rich(
picsNodes(),
// semanticsLabel: '动态图片',
),
],
),
);
}
}

View File

@@ -1,65 +0,0 @@
import 'package:PiliPlus/grpc/app/dynamic/v2/dynamic.pb.dart';
import 'package:PiliPlus/pages/dynamics/widgets/author_panel_grpc.dart';
import 'package:PiliPlus/pages/dynamics/widgets/content_panel_grpc.dart';
import 'package:PiliPlus/utils/utils.dart';
import 'package:flutter/material.dart';
class DynamicPanelGrpc extends StatelessWidget {
final DynamicItem item;
final String? source;
final Function? onRemove;
const DynamicPanelGrpc({
required this.item,
this.source,
this.onRemove,
super.key,
});
@override
Widget build(BuildContext context) {
return Container(
padding: source == 'detail'
? const EdgeInsets.only(bottom: 12)
: EdgeInsets.zero,
// decoration: BoxDecoration(
// border: Border(
// bottom: BorderSide(
// width: 8,
// color: Theme.of(context).dividerColor.withOpacity(0.05),
// ),
// ),
// ),
child: Material(
elevation: 0,
clipBehavior: Clip.hardEdge,
color: Theme.of(context).cardColor.withOpacity(0.5),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(5),
),
child: InkWell(
onTap: source == 'detail' && item.itemType == DynamicType.draw
? null
: () => Utils.pushDynDetail(item, 1),
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
children: [
Padding(
padding: const EdgeInsets.fromLTRB(12, 12, 12, 6),
child: AuthorPanelGrpc(
item: item,
source: source,
onRemove: onRemove,
),
),
ContentGrpc(item: item, source: source),
// forWard(item, context, _dynamicsController, source),
const SizedBox(height: 2),
// if (source == null) ActionPanel(item: item),
],
),
),
),
);
}
}