refa: member search

Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
This commit is contained in:
bggRGjQaUbCoE
2025-05-06 20:00:09 +08:00
parent bd31ab5d07
commit 867efecc54
8 changed files with 394 additions and 430 deletions

View File

@@ -383,6 +383,53 @@ class MemberHttp {
}
}
static Future<LoadingState> memberArchiveNew({
required mid,
int ps = 25,
int tid = 0,
int? pn,
String? keyword,
String order = 'pubdate',
bool orderAvoided = true,
}) async {
String dmImgStr = Utils.base64EncodeRandomString(16, 64);
String dmCoverImgStr = Utils.base64EncodeRandomString(32, 128);
Map params = await WbiSign.makSign({
'mid': mid,
'ps': ps,
'tid': tid,
'pn': pn,
if (keyword != null) 'keyword': keyword,
'order': order,
'platform': 'web',
'web_location': '333.1387',
'order_avoided': orderAvoided,
'dm_img_list': '[]',
'dm_img_str': dmImgStr,
'dm_cover_img_str': dmCoverImgStr,
'dm_img_inter': '{"ds":[],"wh":[0,0,0],"of":[0,0,0]}',
});
var res = await Request().get(
Api.memberArchive,
queryParameters: params,
options: Options(headers: {
HttpHeaders.userAgentHeader: Request.headerUa(type: 'pc'),
HttpHeaders.refererHeader: '${HttpString.spaceBaseUrl}/$mid',
'origin': HttpString.spaceBaseUrl,
}),
);
if (res.data['code'] == 0) {
return LoadingState.success(
MemberArchiveDataModel.fromJson(res.data['data']));
} else {
Map errMap = {
-352: '风控校验失败,请检查登录状态',
};
return LoadingState.error(
errMap[res.data['code']] ?? res.data['message']);
}
}
// 用户动态
static Future<LoadingState<DynamicsDataModel>> memberDynamic({
String? offset,
@@ -455,6 +502,30 @@ class MemberHttp {
}
}
static Future<LoadingState> memberDynamicSearchNew({
required int pn,
required dynamic mid,
required dynamic offset,
required String keyword,
}) async {
var res = await Request().get(
Api.memberDynamicSearch,
queryParameters: {
'host_mid': mid,
'page': pn,
'offset': offset,
'keyword': keyword,
'features': 'itemOpusStyle,listOnlyfans',
'web_location': 333.1387,
},
);
if (res.data['code'] == 0) {
return LoadingState.success(DynamicsDataModel.fromJson(res.data['data']));
} else {
return LoadingState.error(res.data['message']);
}
}
// 查询分组
static Future followUpTags() async {
var res = await Request().get(Api.followUpTag);

View File

@@ -0,0 +1 @@
enum MemberSearchType { archive, dynamic }

View File

@@ -0,0 +1,68 @@
import 'package:PiliPlus/http/loading_state.dart';
import 'package:PiliPlus/http/member.dart';
import 'package:PiliPlus/models/common/member/search_type.dart';
import 'package:PiliPlus/pages/common/common_list_controller.dart';
import 'package:PiliPlus/pages/member_search/controller.dart';
class MemberSearchChildController extends CommonListController {
MemberSearchChildController(this.controller, this.searchType);
final MemberSearchController controller;
final MemberSearchType searchType;
dynamic offset;
@override
void checkIsEnd(int length) {
switch (searchType) {
case MemberSearchType.archive:
if (controller.counts.first != -1 &&
length >= controller.counts.first) {
isEnd = true;
}
break;
case MemberSearchType.dynamic:
if (controller.counts[1] != -1 && length >= controller.counts[1]) {
isEnd = true;
}
break;
}
}
@override
List? getDataList(response) {
switch (searchType) {
case MemberSearchType.archive:
controller.counts[searchType.index] = response.page?['count'] ?? 0;
return response.list?.vlist;
case MemberSearchType.dynamic:
offset = response.offset;
controller.counts[searchType.index] = response.total ?? 0;
return response.items;
}
}
@override
Future<void> onRefresh() {
offset = null;
return super.onRefresh();
}
@override
Future<LoadingState> customGetData() {
return switch (searchType) {
MemberSearchType.archive => MemberHttp.memberArchiveNew(
mid: controller.mid,
pn: currentPage,
keyword: controller.editingController.text,
order: 'pubdate',
),
MemberSearchType.dynamic => MemberHttp.memberDynamicSearchNew(
mid: controller.mid,
pn: currentPage,
offset: offset ?? '',
keyword: controller.editingController.text,
),
};
}
}

View File

@@ -0,0 +1,180 @@
import 'package:PiliPlus/common/constants.dart';
import 'package:PiliPlus/common/skeleton/dynamic_card.dart';
import 'package:PiliPlus/common/skeleton/video_card_h.dart';
import 'package:PiliPlus/common/widgets/loading_widget/http_error.dart';
import 'package:PiliPlus/common/widgets/refresh_indicator.dart';
import 'package:PiliPlus/common/widgets/video_card/video_card_h.dart';
import 'package:PiliPlus/http/loading_state.dart';
import 'package:PiliPlus/models/common/member/search_type.dart';
import 'package:PiliPlus/pages/dynamics/widgets/dynamic_panel.dart';
import 'package:PiliPlus/pages/member_search/child/controller.dart';
import 'package:PiliPlus/utils/grid.dart';
import 'package:PiliPlus/utils/storage.dart';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:waterfall_flow/waterfall_flow.dart';
class MemberSearchChildPage extends StatefulWidget {
const MemberSearchChildPage({
super.key,
required this.controller,
required this.searchType,
});
final MemberSearchChildController controller;
final MemberSearchType searchType;
@override
State<MemberSearchChildPage> createState() => _MemberSearchChildPageState();
}
class _MemberSearchChildPageState extends State<MemberSearchChildPage>
with AutomaticKeepAliveClientMixin {
late final bool dynamicsWaterfallFlow = GStorage.setting
.get(SettingBoxKey.dynamicsWaterfallFlow, defaultValue: true);
MemberSearchChildController get _controller => widget.controller;
@override
Widget build(BuildContext context) {
super.build(context);
return refreshIndicator(
onRefresh: _controller.onRefresh,
child: CustomScrollView(
slivers: [
SliverPadding(
padding: EdgeInsets.only(
top: widget.searchType == MemberSearchType.archive ? 7 : 0,
bottom: MediaQuery.paddingOf(context).bottom + 80,
),
sliver: Obx(() => _buildBody(_controller.loadingState.value)),
),
],
),
);
}
Widget get _buildLoading {
return switch (widget.searchType) {
MemberSearchType.archive => SliverGrid(
gridDelegate: Grid.videoCardHDelegate(context),
delegate: SliverChildBuilderDelegate(
(context, index) {
return const VideoCardHSkeleton();
},
childCount: 10,
),
),
MemberSearchType.dynamic => dynSkeleton(),
};
}
Widget dynSkeleton() {
if (!dynamicsWaterfallFlow) {
return SliverCrossAxisGroup(
slivers: [
const SliverFillRemaining(),
SliverConstrainedCrossAxis(
maxExtent: Grid.smallCardWidth * 2,
sliver: SliverList.builder(
itemBuilder: (context, index) {
return const DynamicCardSkeleton();
},
itemCount: 10,
),
),
const SliverFillRemaining()
],
);
}
return SliverGrid(
gridDelegate: SliverGridDelegateWithExtentAndRatio(
crossAxisSpacing: StyleString.cardSpace / 2,
mainAxisSpacing: StyleString.cardSpace / 2,
maxCrossAxisExtent: Grid.smallCardWidth * 2,
childAspectRatio: StyleString.aspectRatio,
mainAxisExtent: 50,
),
delegate: SliverChildBuilderDelegate(
(context, index) {
return const DynamicCardSkeleton();
},
childCount: 10,
),
);
}
Widget _buildBody(LoadingState<List?> loadingState) {
return switch (loadingState) {
Loading() => _buildLoading,
Success() => loadingState.response?.isNotEmpty == true
? Builder(
builder: (context) {
return switch (widget.searchType) {
MemberSearchType.archive => SliverGrid(
gridDelegate: Grid.videoCardHDelegate(context),
delegate: SliverChildBuilderDelegate(
(context, index) {
if (index == loadingState.response!.length - 1) {
_controller.onLoadMore();
}
return VideoCardH(
videoItem: loadingState.response![index],
);
},
childCount: loadingState.response!.length,
),
),
MemberSearchType.dynamic => dynamicsWaterfallFlow
? SliverWaterfallFlow.extent(
maxCrossAxisExtent: Grid.smallCardWidth * 2,
crossAxisSpacing: StyleString.safeSpace,
mainAxisSpacing: StyleString.safeSpace,
lastChildLayoutTypeBuilder: (index) {
if (index == loadingState.response!.length - 1) {
_controller.onLoadMore();
}
return index == loadingState.response!.length
? LastChildLayoutType.foot
: LastChildLayoutType.none;
},
children: loadingState.response!
.map((item) => DynamicPanel(item: item))
.toList(),
)
: SliverCrossAxisGroup(
slivers: [
const SliverFillRemaining(),
SliverConstrainedCrossAxis(
maxExtent: Grid.smallCardWidth * 2,
sliver: SliverList.builder(
itemBuilder: (context, index) {
if (index ==
loadingState.response!.length - 1) {
_controller.onLoadMore();
}
return DynamicPanel(
item: loadingState.response![index],
);
},
itemCount: loadingState.response!.length,
),
),
const SliverFillRemaining(),
],
),
};
},
)
: HttpError(
onReload: _controller.onReload,
),
Error() => HttpError(
errMsg: loadingState.errMsg,
onReload: _controller.onReload,
),
};
}
@override
bool get wantKeepAlive => true;
}

View File

@@ -1,169 +1,51 @@
import 'package:PiliPlus/http/loading_state.dart';
import 'package:PiliPlus/http/member.dart';
import 'package:PiliPlus/models/dynamics/result.dart';
import 'package:PiliPlus/models/member/archive.dart';
import 'package:PiliPlus/utils/extension.dart';
import 'package:PiliPlus/models/common/member/search_type.dart';
import 'package:PiliPlus/pages/member_search/child/controller.dart';
import 'package:PiliPlus/utils/utils.dart';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
class MemberSearchController extends GetxController
with GetSingleTickerProviderStateMixin {
final scrollController = ScrollController();
late final tabController = TabController(vsync: this, length: 2);
final textEditingController = TextEditingController();
final searchFocusNode = FocusNode();
final editingController = TextEditingController();
final focusNode = FocusNode();
RxBool hasData = false.obs;
final mid = Get.parameters['mid'];
final uname = Get.parameters['uname'];
late int mid;
RxString uname = ''.obs;
final RxBool hasData = false.obs;
final RxList<int> counts = <int>[-1, -1].obs;
int archivePn = 1;
RxInt archiveCount = (-1).obs;
bool isEndArchive = false;
Rx<LoadingState<List<VListItemModel>?>> archiveState =
LoadingState<List<VListItemModel>?>.loading().obs;
late final arcCtr = Get.put(
MemberSearchChildController(this, MemberSearchType.archive),
tag: Utils.generateRandomString(8));
late final dynCtr = Get.put(
MemberSearchChildController(this, MemberSearchType.dynamic),
tag: Utils.generateRandomString(8));
String offset = '';
int dynamicPn = 1;
RxInt dynamicCount = (-1).obs;
bool isEndDynamic = false;
Rx<LoadingState<List<DynamicItemModel>?>> dynamicState =
LoadingState<List<DynamicItemModel>?>.loading().obs;
@override
void onInit() {
super.onInit();
mid = int.parse(Get.parameters['mid']!);
uname.value = Get.parameters['uname']!;
}
// 清空搜索
void onClear() {
if (textEditingController.value.text.isNotEmpty) {
textEditingController.clear();
if (editingController.value.text.isNotEmpty) {
editingController.clear();
counts.value = <int>[-1, -1];
hasData.value = false;
searchFocusNode.requestFocus();
focusNode.requestFocus();
} else {
Get.back();
}
}
// 提交搜索内容
void submit() {
if (textEditingController.text.isNotEmpty) {
if (editingController.text.isNotEmpty) {
hasData.value = true;
dynamicCount.value = -1;
dynamicState.value = LoadingState.loading();
refreshArchive();
archiveCount.value = -1;
archiveState.value = LoadingState.loading();
refreshDynamic();
}
}
Future refreshDynamic() async {
offset = '';
dynamicPn = 1;
isEndDynamic = false;
await searchDynamic();
}
Future refreshArchive() async {
archivePn = 1;
isEndArchive = false;
await searchArchives();
}
Future searchDynamic([bool isRefresh = true]) async {
if (isRefresh.not && isEndDynamic) return;
dynamic res = await MemberHttp.memberDynamicSearch(
mid: mid,
pn: dynamicPn,
offset: offset,
keyword: textEditingController.text,
);
if (res['status']) {
DynamicsDataModel data = res['data'];
List<DynamicItemModel>? items = data.items;
dynamicCount.value = data.total ?? 0;
offset = data.offset ?? '';
if (data.hasMore == false || items.isNullOrEmpty) {
isEndDynamic = true;
if (isRefresh) {
dynamicState.value = LoadingState.success(items);
}
return;
}
if (isRefresh) {
if (items!.length >= dynamicCount.value) {
isEndDynamic = true;
}
dynamicState.value = LoadingState.success(items);
} else if (dynamicState.value is Success) {
List<DynamicItemModel> currentList = dynamicState.value.data!
..addAll(items!);
if (currentList.length >= dynamicCount.value) {
isEndDynamic = true;
}
dynamicState.refresh();
}
dynamicPn++;
} else if (isRefresh) {
dynamicState.value = LoadingState.error(res['msg']);
}
}
// 搜索视频
Future searchArchives([bool isRefresh = true]) async {
if (isRefresh.not && isEndArchive) return;
dynamic res = await MemberHttp.memberArchive(
mid: mid,
pn: archivePn,
keyword: textEditingController.text,
order: 'pubdate',
);
if (res['status']) {
MemberArchiveDataModel data = res['data'];
List<VListItemModel>? vlist = data.list?.vlist;
archiveCount.value = data.page?['count'] ?? 0;
if (vlist.isNullOrEmpty) {
isEndArchive = true;
if (isRefresh) {
archiveState.value = LoadingState.success(vlist);
}
return;
}
if (isRefresh) {
if (vlist!.length >= archiveCount.value) {
isEndArchive = true;
}
archiveState.value = LoadingState.success(vlist);
} else if (archiveState.value is Success) {
List<VListItemModel> currentList = archiveState.value.data!
..addAll(vlist!);
if (currentList.length >= archiveCount.value) {
isEndArchive = true;
}
archiveState.refresh();
}
archivePn++;
} else if (isRefresh) {
archiveState.value = LoadingState.error(res['msg']);
arcCtr.onReload();
dynCtr.onReload();
}
}
@override
void onClose() {
textEditingController.dispose();
searchFocusNode.dispose();
scrollController.dispose();
editingController.dispose();
focusNode.dispose();
tabController.dispose();
super.onClose();
}

View File

@@ -1,100 +0,0 @@
import 'package:PiliPlus/common/constants.dart';
import 'package:PiliPlus/common/skeleton/video_card_h.dart';
import 'package:PiliPlus/common/widgets/loading_widget/http_error.dart';
import 'package:PiliPlus/common/widgets/refresh_indicator.dart';
import 'package:PiliPlus/common/widgets/video_card/video_card_h.dart';
import 'package:PiliPlus/http/loading_state.dart';
import 'package:PiliPlus/models/member/archive.dart';
import 'package:PiliPlus/pages/member_search/controller.dart';
import 'package:PiliPlus/utils/grid.dart';
import 'package:easy_debounce/easy_throttle.dart';
import 'package:flutter/material.dart';
import 'package:get/get_state_manager/src/rx_flutter/rx_obx_widget.dart';
class SearchArchive extends StatefulWidget {
const SearchArchive({
super.key,
required this.ctr,
});
final MemberSearchController ctr;
@override
State<SearchArchive> createState() => _SearchArchiveState();
}
class _SearchArchiveState extends State<SearchArchive>
with AutomaticKeepAliveClientMixin {
@override
Widget build(BuildContext context) {
super.build(context);
return refreshIndicator(
onRefresh: () async {
await widget.ctr.refreshArchive();
},
child: CustomScrollView(
physics: const AlwaysScrollableScrollPhysics(),
slivers: [
SliverPadding(
padding: EdgeInsets.only(
top: StyleString.safeSpace - 5,
bottom: MediaQuery.paddingOf(context).bottom + 80,
),
sliver:
Obx(() => _buildBody(context, widget.ctr.archiveState.value)),
)
],
),
);
}
Widget _buildBody(
BuildContext context, LoadingState<List<VListItemModel>?> loadingState) {
return switch (loadingState) {
Loading() => SliverGrid(
gridDelegate: Grid.videoCardHDelegate(context),
delegate: SliverChildBuilderDelegate(
(context, index) {
return const VideoCardHSkeleton();
},
childCount: 10,
),
),
Success() => loadingState.response?.isNotEmpty == true
? SliverGrid(
gridDelegate: Grid.videoCardHDelegate(context),
delegate: SliverChildBuilderDelegate(
(context, index) {
if (index == loadingState.response!.length - 1) {
EasyThrottle.throttle(
'searchArchives', const Duration(milliseconds: 500),
() {
widget.ctr.searchArchives(false);
});
}
return VideoCardH(
videoItem: loadingState.response![index],
);
},
childCount: loadingState.response!.length,
),
)
: HttpError(
onReload: () {
widget.ctr.archiveState.value = LoadingState.loading();
widget.ctr.refreshArchive();
},
),
Error() => HttpError(
errMsg: loadingState.errMsg,
onReload: () {
widget.ctr.archiveState.value = LoadingState.loading();
widget.ctr.refreshArchive();
},
),
};
}
@override
bool get wantKeepAlive => true;
}

View File

@@ -1,157 +0,0 @@
import 'package:PiliPlus/common/constants.dart';
import 'package:PiliPlus/common/skeleton/dynamic_card.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/dynamics/result.dart';
import 'package:PiliPlus/pages/dynamics/widgets/dynamic_panel.dart';
import 'package:PiliPlus/pages/member_search/controller.dart';
import 'package:PiliPlus/utils/grid.dart';
import 'package:PiliPlus/utils/storage.dart';
import 'package:easy_debounce/easy_throttle.dart';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:waterfall_flow/waterfall_flow.dart';
class SearchDynamic extends StatefulWidget {
const SearchDynamic({
super.key,
required this.ctr,
});
final MemberSearchController ctr;
@override
State<SearchDynamic> createState() => _SearchDynamicState();
}
class _SearchDynamicState extends State<SearchDynamic>
with AutomaticKeepAliveClientMixin {
@override
Widget build(BuildContext context) {
super.build(context);
return refreshIndicator(
onRefresh: () async {
await widget.ctr.refreshDynamic();
},
child: CustomScrollView(
physics: const AlwaysScrollableScrollPhysics(),
slivers: [
SliverPadding(
padding: EdgeInsets.only(
bottom: MediaQuery.paddingOf(context).bottom + 80,
),
sliver:
Obx(() => _buildBody(context, widget.ctr.dynamicState.value)),
)
],
),
);
}
late final bool dynamicsWaterfallFlow = GStorage.setting
.get(SettingBoxKey.dynamicsWaterfallFlow, defaultValue: true);
Widget skeleton() {
if (!dynamicsWaterfallFlow) {
return SliverCrossAxisGroup(
slivers: [
const SliverFillRemaining(),
SliverConstrainedCrossAxis(
maxExtent: Grid.smallCardWidth * 2,
sliver: SliverList.builder(
itemBuilder: (context, index) {
return const DynamicCardSkeleton();
},
itemCount: 10,
),
),
const SliverFillRemaining()
],
);
}
return SliverGrid(
gridDelegate: SliverGridDelegateWithExtentAndRatio(
crossAxisSpacing: StyleString.cardSpace / 2,
mainAxisSpacing: StyleString.cardSpace / 2,
maxCrossAxisExtent: Grid.smallCardWidth * 2,
childAspectRatio: StyleString.aspectRatio,
mainAxisExtent: 50,
),
delegate: SliverChildBuilderDelegate(
(context, index) {
return const DynamicCardSkeleton();
},
childCount: 10,
),
);
}
Widget _buildBody(BuildContext context,
LoadingState<List<DynamicItemModel>?> loadingState) {
return switch (loadingState) {
Loading() => skeleton(),
Success() => loadingState.response?.isNotEmpty == true
? dynamicsWaterfallFlow
? SliverWaterfallFlow.extent(
maxCrossAxisExtent: Grid.smallCardWidth * 2,
crossAxisSpacing: StyleString.safeSpace,
mainAxisSpacing: StyleString.safeSpace,
lastChildLayoutTypeBuilder: (index) {
if (index == loadingState.response!.length - 1) {
EasyThrottle.throttle(
'member_dynamics', const Duration(milliseconds: 1000),
() {
widget.ctr.searchDynamic(false);
});
}
return index == loadingState.response!.length
? LastChildLayoutType.foot
: LastChildLayoutType.none;
},
children: loadingState.response!
.map((item) => DynamicPanel(item: item))
.toList(),
)
: SliverCrossAxisGroup(
slivers: [
const SliverFillRemaining(),
SliverConstrainedCrossAxis(
maxExtent: Grid.smallCardWidth * 2,
sliver: SliverList.builder(
itemBuilder: (context, index) {
if (index == loadingState.response!.length - 1) {
EasyThrottle.throttle('member_dynamics',
const Duration(milliseconds: 1000), () {
widget.ctr.searchDynamic(false);
});
}
return DynamicPanel(
item: loadingState.response![index],
);
},
itemCount: loadingState.response!.length,
),
),
const SliverFillRemaining(),
],
)
: HttpError(
onReload: () {
widget.ctr.dynamicState.value = LoadingState.loading();
widget.ctr.refreshDynamic();
},
),
Error() => HttpError(
errMsg: loadingState.errMsg,
onReload: () {
widget.ctr.dynamicState.value = LoadingState.loading();
widget.ctr.refreshDynamic();
},
),
};
}
@override
bool get wantKeepAlive => true;
}

View File

@@ -1,7 +1,8 @@
import 'package:PiliPlus/common/widgets/scroll_physics.dart';
import 'package:PiliPlus/models/common/member/search_type.dart';
import 'package:PiliPlus/pages/member_search/child/view.dart';
import 'package:PiliPlus/pages/member_search/controller.dart';
import 'package:PiliPlus/pages/member_search/search_archive.dart';
import 'package:PiliPlus/pages/member_search/search_dynamic.dart';
import 'package:PiliPlus/utils/utils.dart';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
@@ -13,7 +14,8 @@ class MemberSearchPage extends StatefulWidget {
}
class _MemberSearchPageState extends State<MemberSearchPage> {
final _memberSearchCtr = Get.put(MemberSearchController());
final _controller =
Get.put(MemberSearchController(), tag: Utils.generateRandomString(8));
@override
Widget build(BuildContext context) {
@@ -23,15 +25,15 @@ class _MemberSearchPageState extends State<MemberSearchPage> {
actions: [
IconButton(
tooltip: '搜索',
onPressed: _memberSearchCtr.submit,
onPressed: _controller.submit,
icon: const Icon(Icons.search, size: 22),
),
const SizedBox(width: 10)
],
title: TextField(
autofocus: true,
focusNode: _memberSearchCtr.searchFocusNode,
controller: _memberSearchCtr.textEditingController,
focusNode: _controller.focusNode,
controller: _controller.editingController,
textInputAction: TextInputAction.search,
textAlignVertical: TextAlignVertical.center,
decoration: InputDecoration(
@@ -40,63 +42,80 @@ class _MemberSearchPageState extends State<MemberSearchPage> {
suffixIcon: IconButton(
tooltip: '清空',
icon: const Icon(Icons.clear, size: 22),
onPressed: _memberSearchCtr.onClear,
onPressed: _controller.onClear,
),
),
onSubmitted: (value) => _memberSearchCtr.submit(),
onSubmitted: (value) => _controller.submit(),
onChanged: (value) {
if (value.isEmpty) {
_memberSearchCtr.hasData.value = false;
_controller.hasData.value = false;
}
},
),
),
body: Obx(
() => _memberSearchCtr.hasData.value
? SafeArea(
top: false,
bottom: false,
body: SafeArea(
top: false,
bottom: false,
child: Stack(
clipBehavior: Clip.none,
children: [
Obx(() {
return Opacity(
opacity: _controller.hasData.value ? 1 : 0,
child: Column(
children: [
TabBar(
controller: _memberSearchCtr.tabController,
controller: _controller.tabController,
tabs: [
Obx(
() => Tab(
text:
'视频 ${_memberSearchCtr.archiveCount.value != -1 ? '${_memberSearchCtr.archiveCount.value}' : ''}',
'视频 ${_controller.counts[0] != -1 ? _controller.counts[0] : ''}',
),
),
Obx(
() => Tab(
text:
'动态 ${_memberSearchCtr.dynamicCount.value != -1 ? '${_memberSearchCtr.dynamicCount.value}' : ''}',
'动态 ${_controller.counts[1] != -1 ? _controller.counts[1] : ''}',
),
),
],
),
Expanded(
child: tabBarView(
controller: _memberSearchCtr.tabController,
controller: _controller.tabController,
children: [
SearchArchive(ctr: _memberSearchCtr),
SearchDynamic(ctr: _memberSearchCtr),
MemberSearchChildPage(
controller: _controller.arcCtr,
searchType: MemberSearchType.archive,
),
MemberSearchChildPage(
controller: _controller.dynCtr,
searchType: MemberSearchType.dynamic,
),
],
),
),
],
),
)
: FractionallySizedBox(
heightFactor: 0.5,
widthFactor: 1.0,
child: Center(
child: Text(
'搜索「${_memberSearchCtr.uname.value}」的动态、视频',
textAlign: TextAlign.center,
),
),
),
);
}),
Obx(
() => _controller.hasData.value
? const SizedBox.shrink()
: FractionallySizedBox(
heightFactor: 0.5,
widthFactor: 1.0,
child: Center(
child: Text(
'搜索「${_controller.uname}」的动态、视频',
textAlign: TextAlign.center,
),
),
),
),
],
),
),
);
}