home_service_vm.dart 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534
  1. import 'package:cs_resources/generated/assets.dart';
  2. import 'package:cs_resources/theme/app_colors_theme.dart';
  3. import 'package:domain/entity/garage_sale_rent_entity.dart';
  4. import 'package:domain/entity/newsfeed_detail_entity.dart';
  5. import 'package:domain/entity/paid_service_entity.dart';
  6. import 'package:flutter/cupertino.dart';
  7. import 'package:flutter/material.dart';
  8. import 'package:plugin_basic/constants/app_constant.dart';
  9. import 'package:plugin_platform/engine/dialog/dialog_engine.dart';
  10. import 'package:plugin_platform/engine/sp/sp_util.dart';
  11. import 'package:plugin_platform/engine/toast/toast_engine.dart';
  12. import 'package:riverpod_annotation/riverpod_annotation.dart';
  13. import 'package:router/ext/auto_router_extensions.dart';
  14. import 'package:shared/utils/log_utils.dart';
  15. import 'package:widgets/dialog/app_custom_dialog.dart';
  16. import 'package:widgets/load_state_layout.dart';
  17. import 'package:widgets/my_checkbox_group.dart';
  18. import 'package:widgets/picker/option_pick_util.dart';
  19. import 'package:widgets/widget_export.dart';
  20. import '../../../constants_services.dart';
  21. import '../../../respository/services_respository.dart';
  22. import '../../../router/page/services_page_router.dart';
  23. import '../services_vm.dart';
  24. import 'home_service_state.dart';
  25. part 'home_service_vm.g.dart';
  26. @riverpod
  27. class HomeServiceVm extends _$HomeServiceVm {
  28. late ServicesRespository servicesRespositoryInstance;
  29. bool _needShowPlaceholder = false; //是否展示LoadingView
  30. int _page = 1; // 当前页
  31. int _limit = 10; // 每页数量
  32. int _count = 0; // 总条数
  33. bool _isSingleSelect = true;
  34. List<Map<String, dynamic>> _currentSelectedCategory = [];
  35. Map<String, dynamic> _queryParams = {
  36. 'category_id': null,
  37. 'keyword': null,
  38. 'is_liked': null,
  39. };
  40. // Refresh 控制器
  41. final EasyRefreshController refreshController = EasyRefreshController(
  42. controlFinishRefresh: true, //允许刷新
  43. controlFinishLoad: true, //允许加载
  44. );
  45. HomeServiceState initState() {
  46. return HomeServiceState(
  47. list: []
  48. );
  49. }
  50. @override
  51. HomeServiceState build(){
  52. // 引入数据仓库
  53. servicesRespositoryInstance = ref.read(servicesRespositoryProvider);
  54. final state = initState();
  55. Log.d("--------------------------build---------------------");
  56. return state;
  57. }
  58. //刷新页面状态
  59. void changeLoadingState(LoadState loadState, String? errorMsg) {
  60. state = state.copyWith(
  61. loadingState: loadState,
  62. errorMessage: errorMsg
  63. );
  64. }
  65. // 初始化页面数据
  66. initPageData() {
  67. Log.d("----home_service_vm-----initPageData ${state.loadingState}");
  68. onRefresh();
  69. }
  70. // 上拉加载 更多
  71. Future loadMore() async {
  72. Log.d("----home_service_vm-----loadMore");
  73. _page++;
  74. getListData();
  75. }
  76. // 下拉刷新
  77. Future onRefresh() async {
  78. // 当前pageView 页面正处于显示状态
  79. Log.d("----forsale_vm-----onRefresh ");
  80. // await Future.delayed(const Duration(seconds: 2));
  81. _page = 1;
  82. getListData();
  83. }
  84. // 手动进行刷新
  85. Future triggerRefresh() async {
  86. Log.d("trggerRefresh");
  87. refreshController.callRefresh();
  88. }
  89. // 手动进行刷新
  90. Future directRefresh() async {
  91. state = state.copyWith(list:[]);
  92. // 注意:由于 nestedscrollview 嵌套easyfresh 组件 refreshController.callRefresh() 自动刷新只能滚动顶部但是不会触发下拉刷新,这里调用是 用到了将其滚动到顶部的作用,进而刷新操作主动掉接口
  93. // https://github.com/xuelongqy/flutter_easy_refresh/issues/692
  94. refreshController.callRefresh();
  95. refreshController.resetFooter();
  96. _page = 1;
  97. _needShowPlaceholder = true;
  98. getListData();
  99. }
  100. // 重试请求
  101. Future retryRequest() async {
  102. _page = 1;
  103. _needShowPlaceholder = true;
  104. getListData();
  105. }
  106. // 获取list 列表数据
  107. Future getListData<T>({bool? isLoadMore}) async {
  108. if (_needShowPlaceholder) {
  109. changeLoadingState(LoadState.State_Loading, null);
  110. }
  111. // List<Map<String, dynamic>> list = [
  112. // {
  113. // 'id':1,
  114. // 'service_type': 0, // 0 房屋保洁 1 空调保洁 2 维修
  115. // 'cover_img': 'https://img2.baidu.com/it/u=3489233687,2364672159&fm=253&fmt=auto&app=120&f=JPEG?w=507&h=500',
  116. // 'resources': ['https://img2.baidu.com/it/u=3489233687,2364672159&fm=253&fmt=auto&app=120&f=JPEG?w=507&h=500'],
  117. // 'title': 'House Cleaning Services',
  118. // 'price': 30,
  119. // 'unit': '/hr',
  120. // 'liked': true,
  121. // 'likes_count': 12,
  122. // 'company_name': 'HONG YE GROUP PTE LTD',
  123. // },
  124. // {
  125. // 'id':2,
  126. // 'service_type': 1, // 0 房屋保洁 1 空调保洁 2 维修
  127. // 'cover_img': 'https://img2.baidu.com/it/u=3489233687,2364672159&fm=253&fmt=auto&app=120&f=JPEG?w=507&h=500',
  128. // 'resources': ['https://img2.baidu.com/it/u=3489233687,2364672159&fm=253&fmt=auto&app=120&f=JPEG?w=507&h=500'],
  129. // 'title': 'Air Conditioning Cleaning',
  130. // 'price': 10,
  131. // 'unit': '/unit',
  132. // 'liked': false,
  133. // 'likes_count': 10,
  134. // 'company_name': 'HONG YE GROUP PTE LTD',
  135. // },
  136. // {
  137. // 'id':3,
  138. // 'service_type': 1, // 0 房屋保洁 1 空调保洁 2 维修
  139. // 'cover_img': 'https://img2.baidu.com/it/u=3489233687,2364672159&fm=253&fmt=auto&app=120&f=JPEG?w=507&h=500',
  140. // 'resources': ['https://img2.baidu.com/it/u=3489233687,2364672159&fm=253&fmt=auto&app=120&f=JPEG?w=507&h=500'],
  141. // 'title': 'Cleaning Services',
  142. // 'price': 200,
  143. // 'unit': '/hr',
  144. // 'liked': true,
  145. // 'likes_count': 1212,
  146. // 'company_name': 'HONG YE GROUP PTE LTD',
  147. // },
  148. // ];
  149. // handlerResultData(true, list:list);
  150. try {
  151. //请求网络
  152. Map<String, dynamic> params = {
  153. "sort": null, // 排序(desc=降序,asc=升序)
  154. "sort_by": null, // 排序(likes_count=点赞量,orders_count=下单量,clicks_count=点击量)
  155. "category_id": _queryParams['category_id'],
  156. "keyword": _queryParams['keyword'],
  157. "page": _page,
  158. "limit": _limit,
  159. };
  160. Log.d("请求参数------$params");
  161. final result = await servicesRespositoryInstance.fetchPaidServiceDataList(params);
  162. //校验成功失败
  163. if (result.isSuccess) {
  164. handlerResultList((result.data as PaidServiceEntity)?.list as List<PaidServiceList>?, isLoadMore ?? false);
  165. } else {
  166. String errorMessage = result.errorMsg!;
  167. changeLoadingState(LoadState.State_Error, errorMessage);
  168. ToastEngine.show(result.errorMsg ?? "Network Load Error");
  169. }
  170. } catch (e) {
  171. ToastEngine.show("Error: $e");
  172. }
  173. // 最后赋值
  174. _needShowPlaceholder = false;
  175. }
  176. handlerResultData(bool isList, {List<PaidServiceList>? list, dynamic? data}){
  177. Future.delayed(const Duration(seconds: 1)).then((value) {
  178. if(isList){
  179. // list 数据模式
  180. if(list != null && list.isNotEmpty){
  181. if(_page == 1){
  182. state.list.clear();
  183. state.list!.addAll(list);
  184. refreshController.finishRefresh();
  185. changeLoadingState(LoadState.State_Success, null);
  186. }else {
  187. final allList = state.list;
  188. allList!.addAll(list);
  189. state = state.copyWith(list: allList);
  190. refreshController.finishLoad();
  191. }
  192. }else {
  193. if(_page == 1){
  194. state.list.clear();
  195. changeLoadingState(LoadState.State_Empty, null);
  196. refreshController.finishRefresh();
  197. }else {
  198. refreshController.finishLoad(IndicatorResult.noMore);
  199. }
  200. }
  201. }else {
  202. // 单个数据模式
  203. if(data!=null){
  204. if(_page == 1){
  205. refreshController.finishRefresh();
  206. }else{
  207. refreshController.finishLoad();
  208. }
  209. changeLoadingState(LoadState.State_Success, null);
  210. }else {
  211. if(_page == 1){
  212. refreshController.finishRefresh();
  213. }else{
  214. refreshController.finishLoad();
  215. }
  216. changeLoadingState(LoadState.State_Empty, null);
  217. }
  218. }
  219. });
  220. }
  221. void handlerResultList(List<PaidServiceList>? list, bool isLoadMore) {
  222. if (list != null && list.isNotEmpty) {
  223. //有数据,判断是刷新还是加载更多的数据
  224. if (_page == 1) {
  225. //刷新的方式
  226. state.list!.clear();
  227. state.list!.addAll(list);
  228. refreshController.finishRefresh();
  229. //更新展示的状态
  230. changeLoadingState(LoadState.State_Success, null);
  231. } else {
  232. //加载更多
  233. final allList = state.list;
  234. allList!.addAll(list);
  235. state = state.copyWith(list: allList);
  236. refreshController.finishLoad();
  237. }
  238. } else {
  239. if (_page == 1) {
  240. //展示无数据的布局
  241. state.list!.clear();
  242. changeLoadingState(LoadState.State_Empty, null);
  243. refreshController.finishRefresh();
  244. } else {
  245. //展示加载完成,没有更多数据了
  246. if (_page == 1) {
  247. //展示无数据的布局
  248. state.list!.clear();
  249. changeLoadingState(LoadState.State_Empty, null);
  250. refreshController.finishRefresh();
  251. } else {
  252. //展示加载完成,没有更多数据了
  253. if(state.list!.length == 0){
  254. changeLoadingState(LoadState.State_Empty, null);
  255. refreshController.finishLoad();
  256. }else {
  257. if(_needShowPlaceholder){
  258. changeLoadingState(LoadState.State_Success, null);
  259. }
  260. }
  261. //更新展示的状态
  262. refreshController.finishLoad(IndicatorResult.noMore);
  263. }
  264. }
  265. }
  266. }
  267. // 获取 homeservice 的分类选项
  268. Future<List<Map<String, dynamic>>> getHomeServiceCategoryOptions() async{
  269. // await Future.delayed(const Duration(milliseconds: 300));
  270. // ToastEngine.dismiss();
  271. List<Map<String, dynamic>> serviceCategoryList = [
  272. // {
  273. // 'id': 1,
  274. // 'name': 'House Cleaning Serivces',
  275. // },
  276. // {
  277. // 'id': 2,
  278. // 'name': 'Household Appliance Cleaning',
  279. // },
  280. ];
  281. // 获取分类列表
  282. try {
  283. // 加入有缓存 就取缓存
  284. List<Map<String, dynamic>>? StorageCategoryList = SPUtil.getObjectList(
  285. AppConstant.storagePaidServiceCategoryList)?.cast<Map<String, dynamic>>();
  286. if (StorageCategoryList != null && StorageCategoryList.isNotEmpty) {
  287. Log.d("取StorageCategoryList 缓存: $StorageCategoryList ");
  288. serviceCategoryList = StorageCategoryList;
  289. } else {
  290. final paidServiceLayoutVm = ref.watch(servicesVmProvider.notifier);
  291. Map<String, dynamic> params = {
  292. 'parent_id': paidServiceLayoutVm?.state.parentCategoryId,
  293. };
  294. final result = await servicesRespositoryInstance.fetchServiceCateGoryList(params);
  295. if (result.isSuccess) {
  296. final listJson = result.getListJson();
  297. // 将 listJson 转换为 List<Map<String, dynamic>>
  298. serviceCategoryList = (listJson as List?)
  299. ?.map((item) => item as Map<String, dynamic>)
  300. .toList() ?? [];
  301. // 将 serviceCategoryList 存入缓存
  302. Log.d("设置StorageCategoryList 缓存");
  303. SPUtil.putObjectList(
  304. AppConstant.storagePaidServiceCategoryList, serviceCategoryList);
  305. }
  306. }
  307. } catch(error){
  308. Log.d("获取分类列表失败: $error");
  309. }
  310. return serviceCategoryList;
  311. }
  312. // 点击了filter icon
  313. handlerClickFilterIcon(BuildContext context) async{
  314. List<Map<String, dynamic>> serviceCategoryList = await getHomeServiceCategoryOptions();
  315. // 显示弹框
  316. handlerShowChooseServiceCategoryDialog(context, serviceCategoryList);
  317. }
  318. Future<void> handlerShowChooseServiceCategoryDialog(BuildContext context, List<Map<String, dynamic>> categoryList) async{
  319. await DialogEngine.show(
  320. tag: "chooseServiceCategory",
  321. position: DialogPosition.center,
  322. widget: AppCustomDialog(
  323. message: '',
  324. title: 'Choose a Category',
  325. dialogWidth: MediaQuery.of(context).size.width * 0.8,
  326. // contentBoxMaxHeight: 350,
  327. // contentBoxMinHeight: 300,
  328. isShowConfirmBtn: categoryList!.length > 0 ? true: false,
  329. confirmTxt: "Ok",
  330. messageBuilder: (BuildContext context){
  331. return Container(
  332. color: context.appColors.textWhite,
  333. child: Column(
  334. mainAxisAlignment: MainAxisAlignment.start,
  335. crossAxisAlignment: CrossAxisAlignment.start,
  336. children: categoryList!.length > 0 ? [
  337. MyCheckboxGroup(
  338. isSingleSelect: _isSingleSelect,
  339. labelStyle: const TextStyle(
  340. fontSize: 16,
  341. fontWeight: FontWeight.w500,
  342. ),
  343. items: categoryList!,
  344. defaultSelectedItems: [],
  345. onChanged: (List<Map<String, dynamic>> selectedItems){
  346. Log.d("----MyCheckboxGroup onChanged $selectedItems");
  347. _currentSelectedCategory = selectedItems;
  348. }
  349. )
  350. ]: [
  351. Container(
  352. child: CircularProgressIndicator(
  353. strokeWidth: 3,
  354. valueColor: AlwaysStoppedAnimation(context.appColors.textDarkGray),
  355. ),
  356. )
  357. ],
  358. ),
  359. );
  360. },
  361. isShowCancelBtn:false,
  362. confirmAction: (){
  363. // 点击了确定
  364. Log.d("----点击了确定按钮");
  365. int? categoryId;
  366. if(_isSingleSelect){
  367. if(_currentSelectedCategory.length > 0){
  368. categoryId = _currentSelectedCategory[0]['id'];
  369. }
  370. }
  371. setCurrentQueryParams({
  372. "category_id": _queryParams?['categoryId'],
  373. });
  374. directRefresh();
  375. },
  376. )
  377. );
  378. }
  379. // 点击 收藏/取消收藏
  380. Future<bool?> handlerClickCollection(int id, bool? isCollection) async{
  381. try {
  382. //请求网络
  383. Map<String, dynamic> params = {
  384. "id": id,
  385. "type": 'paid', // 类型(paid=付费服务,inquiry=询价服务)
  386. };
  387. Log.d("请求参数------$params");
  388. final result = await servicesRespositoryInstance.fetchServiceLiked(params);
  389. //校验成功失败
  390. if (result.isSuccess) {
  391. // 修改 该id 的 liked 和 likes_count 字段
  392. state.list!.forEach((PaidServiceList elementEntity) {
  393. if(elementEntity.id == id){
  394. elementEntity.liked = !elementEntity.liked!;
  395. elementEntity.likesCount = elementEntity.liked! ? (elementEntity.likesCount! + 1) : (elementEntity.likesCount! - 1);
  396. }
  397. });
  398. return true;
  399. } else {
  400. String errorMessage = result.errorMsg!;
  401. changeLoadingState(LoadState.State_Error, errorMessage);
  402. ToastEngine.show(result.errorMsg ?? "Network Load Error");
  403. }
  404. } catch (e) {
  405. Log.e("Error: $e");
  406. ToastEngine.show("Error: $e");
  407. }
  408. }
  409. // 设置当前的 _queryParams
  410. setCurrentQueryParams(Map<String, dynamic> params){
  411. _queryParams.addAll(params);
  412. }
  413. // 获取当前的 _queryParams
  414. Map<String, dynamic> getCurrentQueryParams(String? key){
  415. if(key!=null && key!.isNotEmpty){
  416. return _queryParams[key];
  417. }
  418. return _queryParams;
  419. }
  420. // 点击 sort icon
  421. handlerClickSortIcon(BuildContext context) async{
  422. // await SmartDialog.showAttach(
  423. // targetContext: context,
  424. // alignment: Alignment.bottomCenter,
  425. // highlightBuilder: (Offset targetOffset, Size targetSize) {
  426. // Log.d("666 ${targetOffset} ${targetSize}");
  427. // return Positioned(
  428. // right: 0,
  429. // top: 0,
  430. // child: Container(height: targetOffset.dy + 50, width: targetSize.width, color: Colors.white),
  431. // );
  432. // },
  433. // builder: (_) => _listDialog(),
  434. // );
  435. // DialogEngine.showAttach(
  436. // targetContext: context,
  437. // position: DialogPosition.top,
  438. // clickMaskDismiss:false,
  439. // usePenetrate: true,
  440. // widget: _listDialog()
  441. // );
  442. OptionPickerUtil.showCupertinoOptionPicker(
  443. items: state.sortByOptionsList,
  444. initialSelectIndex: 0,
  445. onPickerChanged: (_, index) {
  446. state = state.copyWith(sortBySelectedOption: state.sortByOptionsList?[index]);
  447. // 调用list 接口
  448. },
  449. );
  450. }
  451. Widget _listDialog() {
  452. return Container(
  453. width: 200,
  454. height: 200,
  455. color: Colors.white,
  456. child: Column(
  457. children: [
  458. Text("排序方式"),
  459. Text("价格从低到高"),
  460. Text("价格从高到低"),
  461. Text("距离最近"),
  462. Text("距离最远"),
  463. ],
  464. ),
  465. );
  466. }
  467. // 去详情页面
  468. void handlerGotoDetail({
  469. BuildContext? context,
  470. required int id,
  471. required PaidServiceListCategory paidServiceCategory,
  472. liked,
  473. likesCount,
  474. }){
  475. final cleanServiceType = paidServiceCategory.name!;
  476. Log.d("去详情页面: $id $cleanServiceType");
  477. if(cleanServiceType == servicesConstants.servicesType['houseCleaning']!['code'] || cleanServiceType == servicesConstants.servicesType['airConditioner']!['code']){
  478. // clean service 跳转到 clean 详情页
  479. appRouter.push(ServiceCleanDetailPageRoute(id: id, cleanServiceType: cleanServiceType,liked: liked,likesCount: likesCount));
  480. }else {
  481. // other service 跳转到 other 详情页
  482. ToastEngine.show("${cleanServiceType} 类型的详情暂未开放");
  483. }
  484. }
  485. }