home_service_vm.dart 18 KB

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