refa: search panel

Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
This commit is contained in:
bggRGjQaUbCoE
2025-04-17 16:13:11 +08:00
parent 7b9e4b2f82
commit 4db7711a36
21 changed files with 1607 additions and 1504 deletions

View File

@@ -0,0 +1,126 @@
import 'dart:math';
import 'package:PiliPlus/models/search/result.dart';
import 'package:PiliPlus/pages/search/widgets/search_text.dart';
import 'package:PiliPlus/pages/search_panel/controller.dart';
import 'package:flutter/material.dart';
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
import 'package:get/get.dart';
class SearchUserController
extends SearchPanelController<SearchUserModel, SearchUserItemModel> {
SearchUserController({
required super.keyword,
required super.searchType,
required super.tag,
});
// sort
late final List orderFiltersList = [
{'label': '默认排序', 'value': 0, 'orderSort': 0, 'order': ''},
{'label': '粉丝数由高到低', 'value': 1, 'orderSort': 0, 'order': 'fans'},
{'label': '粉丝数由低到高', 'value': 2, 'orderSort': 1, 'order': 'fans'},
{'label': 'Lv等级由高到低', 'value': 3, 'orderSort': 0, 'order': 'level'},
{'label': 'Lv等级由低到高', 'value': 4, 'orderSort': 1, 'order': 'level'},
];
late final List userTypeFiltersList = [
{'label': '全部用户', 'value': 0, 'userType': 0},
{'label': 'UP主', 'value': 1, 'userType': 1},
{'label': '普通用户', 'value': 2, 'userType': 2},
{'label': '认证用户', 'value': 3, 'userType': 3},
];
RxInt currentOrderFilterval = 0.obs;
RxInt currentUserTypeFilterval = 0.obs;
onShowFilterDialog(BuildContext context) {
showModalBottomSheet(
context: context,
useSafeArea: true,
isScrollControlled: true,
clipBehavior: Clip.hardEdge,
backgroundColor: Theme.of(context).colorScheme.surface,
constraints: BoxConstraints(
maxWidth: min(640, min(Get.width, Get.height)),
),
builder: (context) => SingleChildScrollView(
child: Container(
width: double.infinity,
padding: EdgeInsets.only(
top: 20,
left: 16,
right: 16,
bottom: 80 + MediaQuery.of(context).padding.bottom,
),
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const SizedBox(height: 10),
const Text('用户粉丝数及等级排序顺序', style: TextStyle(fontSize: 16)),
const SizedBox(height: 10),
Wrap(
spacing: 8,
runSpacing: 8,
children: orderFiltersList
.map(
(item) => SearchText(
text: item['label'],
onTap: (_) async {
Get.back();
currentOrderFilterval.value = item['value'];
SmartDialog.dismiss();
SmartDialog.showToast("${item['label']}」的筛选结果");
orderSort = item['orderSort'];
order.value = item['order'];
SmartDialog.showLoading(msg: 'loading');
await onReload();
SmartDialog.dismiss();
},
bgColor: item['value'] == currentOrderFilterval.value
? Theme.of(context).colorScheme.secondaryContainer
: null,
textColor: item['value'] == currentOrderFilterval.value
? Theme.of(context).colorScheme.onSecondaryContainer
: null,
),
)
.toList(),
),
const SizedBox(height: 20),
const Text('用户分类', style: TextStyle(fontSize: 16)),
const SizedBox(height: 10),
Wrap(
spacing: 8,
runSpacing: 8,
children: userTypeFiltersList
.map(
(item) => SearchText(
text: item['label'],
onTap: (_) async {
Get.back();
currentUserTypeFilterval.value = item['value'];
SmartDialog.dismiss();
SmartDialog.showToast("${item['label']}」的筛选结果");
userType = item['userType'];
SmartDialog.showLoading(msg: 'loading');
await onReload();
SmartDialog.dismiss();
},
bgColor: item['value'] == currentUserTypeFilterval.value
? Theme.of(context).colorScheme.secondaryContainer
: null,
textColor: item['value'] ==
currentUserTypeFilterval.value
? Theme.of(context).colorScheme.onSecondaryContainer
: null,
),
)
.toList(),
),
],
),
),
),
);
}
}

View File

@@ -0,0 +1,202 @@
import 'package:PiliPlus/common/widgets/custom_sliver_persistent_header_delegate.dart';
import 'package:PiliPlus/common/widgets/network_img_layer.dart';
import 'package:PiliPlus/http/loading_state.dart';
import 'package:PiliPlus/models/search/result.dart';
import 'package:PiliPlus/pages/search_panel/user/controller.dart';
import 'package:PiliPlus/pages/search_panel/view.dart';
import 'package:PiliPlus/utils/grid.dart';
import 'package:PiliPlus/utils/utils.dart';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
class SearchUserPanel extends CommonSearchPanel {
const SearchUserPanel({
super.key,
required super.keyword,
required super.tag,
required super.searchType,
super.hasHeader = true,
});
@override
State<SearchUserPanel> createState() => _SearchUserPanelState();
}
class _SearchUserPanelState extends CommonSearchPanelState<SearchUserPanel,
SearchUserModel, SearchUserItemModel> {
@override
late final SearchUserController controller = Get.put(
SearchUserController(
keyword: widget.keyword,
searchType: widget.searchType,
tag: widget.tag,
),
tag: widget.searchType.name + widget.tag,
);
late TextStyle style;
@override
void didChangeDependencies() {
super.didChangeDependencies();
style = TextStyle(
fontSize: Theme.of(context).textTheme.labelSmall!.fontSize,
color: Theme.of(context).colorScheme.outline,
);
}
@override
Widget buildHeader(LoadingState<List<SearchUserItemModel>?> loadingState) {
if (loadingState is Success) {
return SliverPersistentHeader(
pinned: false,
floating: true,
delegate: CustomSliverPersistentHeaderDelegate(
extent: 40,
bgColor: Theme.of(context).colorScheme.surface,
child: Container(
height: 40,
padding: const EdgeInsets.only(left: 25, right: 12),
child: Row(
children: [
Obx(
() => Text(
'排序: ${controller.orderFiltersList[controller.currentOrderFilterval.value]['label']}',
maxLines: 1,
style:
TextStyle(color: Theme.of(context).colorScheme.outline),
),
),
const Spacer(),
Obx(
() => Text(
'用户类型: ${controller.userTypeFiltersList[controller.currentUserTypeFilterval.value]['label']}',
maxLines: 1,
style:
TextStyle(color: Theme.of(context).colorScheme.outline),
),
),
const Spacer(),
SizedBox(
width: 32,
height: 32,
child: IconButton(
tooltip: '筛选',
style: ButtonStyle(
padding: WidgetStateProperty.all(EdgeInsets.zero),
),
onPressed: () {
controller.onShowFilterDialog(context);
},
icon: Icon(
Icons.filter_list_outlined,
size: 18,
color: Theme.of(context).colorScheme.primary,
),
),
),
],
),
),
),
);
}
return const SliverToBoxAdapter();
}
@override
Widget buildList(List<SearchUserItemModel> list) {
return SliverPadding(
padding: const EdgeInsets.only(bottom: 80),
sliver: SliverGrid(
gridDelegate: SliverGridDelegateWithMaxCrossAxisExtent(
maxCrossAxisExtent: Grid.smallCardWidth * 2,
mainAxisExtent: 66,
),
delegate: SliverChildBuilderDelegate(
(BuildContext context, int index) {
if (index == list.length - 1) {
controller.onLoadMore();
}
final item = list[index];
String heroTag = Utils.makeHeroTag(item.mid);
return InkWell(
onTap: () => Get.toNamed('/member?mid=${item.mid}',
arguments: {'heroTag': heroTag, 'face': item.upic}),
child: Row(
children: [
const SizedBox(width: 15),
Stack(
clipBehavior: Clip.none,
children: [
NetworkImgLayer(
width: 42,
height: 42,
src: item.upic,
type: 'avatar',
),
if (item.officialVerify?['type'] == 0 ||
item.officialVerify?['type'] == 1)
Positioned(
bottom: 0,
right: 0,
child: Container(
decoration: BoxDecoration(
shape: BoxShape.circle,
color: Theme.of(context).colorScheme.surface,
),
child: Icon(
Icons.offline_bolt,
color: item.officialVerify?['type'] == 0
? Colors.yellow
: Colors.lightBlueAccent,
size: 14,
),
),
),
],
),
const SizedBox(width: 10),
Column(
mainAxisSize: MainAxisSize.max,
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.center,
children: [
Row(
children: [
Text(
item.uname!,
style: const TextStyle(
fontSize: 14,
),
),
const SizedBox(width: 6),
Image.asset(
'assets/images/lv/lv${item.isSeniorMember == 1 ? '6_s' : item.level}.png',
height: 11,
semanticLabel: '等级${item.level}',
),
],
),
Text(
'粉丝:${Utils.numFormat(item.fans)} 视频:${Utils.numFormat(item.videos)}',
style: style,
),
if (item.officialVerify?['desc'] != null &&
item.officialVerify?['desc'] != '')
Text(
item.officialVerify?['desc'],
style: style,
),
],
)
],
),
);
},
childCount: list.length,
),
),
);
}
}