| this.activityTypeName, | this.activityTypeName, | ||||
| this.activityTypeDescription}); | this.activityTypeDescription}); | ||||
| Activities.clone(Activities activities) { | |||||
| this.id = activities.id; | |||||
| this.cropId = activities.cropId; | |||||
| this.activityTypeName = activities.activityTypeName; | |||||
| this.activityTypeDescription = activities.activityTypeDescription; | |||||
| this.executeDate = activities.executeDate; | |||||
| } | |||||
| Activities.fromJson(Map<String, dynamic> json) { | Activities.fromJson(Map<String, dynamic> json) { | ||||
| id = json['id']; | id = json['id']; | ||||
| ageDay = json['ageDay']; | ageDay = json['ageDay']; |
| var token = await pref.getString(DATA_CONST.TOKEN_KEY); | var token = await pref.getString(DATA_CONST.TOKEN_KEY); | ||||
| options.headers["Authorization"] = "Bearer $token"; | options.headers["Authorization"] = "Bearer $token"; | ||||
| options.receiveTimeout = 20000; | options.receiveTimeout = 20000; | ||||
| log("onRequest: ${options.uri}\n" | |||||
| "data=${options.data}\n" | |||||
| "method=${options.method}\n" | |||||
| "headers=${options.headers}\n" | |||||
| "queryParameters=${options.queryParameters}"); | |||||
| // log("onRequest: ${options.uri}\n" | |||||
| // "data=${options.data}\n" | |||||
| // "method=${options.method}\n" | |||||
| // "headers=${options.headers}\n" | |||||
| // "queryParameters=${options.queryParameters}"); | |||||
| return options; | return options; | ||||
| } | } | ||||
| @override | @override | ||||
| Future onResponse(Response response) { | Future onResponse(Response response) { | ||||
| log("onResponse: $response"); | |||||
| // log("onResponse: $response"); | |||||
| return super.onResponse(response); | return super.onResponse(response); | ||||
| } | } | ||||
| @override | @override | ||||
| Future onError(DioError err) { | Future onError(DioError err) { | ||||
| log("onError: $err\n" | |||||
| "Response: ${err.response}"); | |||||
| // log("onError: $err\n" | |||||
| // "Response: ${err.response}"); | |||||
| return super.onError(err); | return super.onError(err); | ||||
| } | } | ||||
| } | } |
| } | } | ||||
| //Action | //Action | ||||
| Future<void> createNursery(Function(dynamic) onSuccess, | |||||
| Function(String) onError, String activityNursery, | |||||
| {List<String> filePaths}) async { | |||||
| Future<void> createAction( | |||||
| Function(dynamic) onSuccess, Function(String) onError, | |||||
| {String apiAddAction, | |||||
| String paramActivity, | |||||
| String activityAction, | |||||
| List<String> filePaths}) async { | |||||
| var formData = FormData(); | var formData = FormData(); | ||||
| filePaths.forEach((f) { | filePaths.forEach((f) { | ||||
| formData.files.add(MapEntry("images", MultipartFile.fromFileSync(f))); | formData.files.add(MapEntry("images", MultipartFile.fromFileSync(f))); | ||||
| }); | }); | ||||
| formData.fields.add(MapEntry("activityNursery", activityNursery)); | |||||
| formData.fields.add(MapEntry(paramActivity, activityAction)); | |||||
| try { | try { | ||||
| await dio | await dio | ||||
| .post("${ConstCommon.baseUrl}/api/createNursery", data: formData) | |||||
| .post("${ConstCommon.baseUrl}/$apiAddAction", data: formData) | |||||
| .then((value) { | .then((value) { | ||||
| onSuccess(value); | |||||
| onSuccess(value.data); | |||||
| }).catchError((onError) { | }).catchError((onError) { | ||||
| onError(AppException.handleError(onError)); | onError(AppException.handleError(onError)); | ||||
| }); | }); | ||||
| } | } | ||||
| } | } | ||||
| Future<void> updateNursery(Function(dynamic) onSuccess, | |||||
| Function(String) onError, String activityNursery, | |||||
| {List<String> filePaths}) async { | |||||
| Future<void> updateAction( | |||||
| Function(dynamic) onSuccess, Function(String) onError, | |||||
| {String apiUpdateAction, | |||||
| String paramActivity, | |||||
| String activityAction, | |||||
| List<String> filePaths}) async { | |||||
| var formData = FormData(); | var formData = FormData(); | ||||
| filePaths.forEach((f) { | filePaths.forEach((f) { | ||||
| formData.files.add(MapEntry("images", MultipartFile.fromFileSync(f))); | formData.files.add(MapEntry("images", MultipartFile.fromFileSync(f))); | ||||
| }); | }); | ||||
| formData.fields.add(MapEntry("activityNursery", activityNursery)); | |||||
| formData.fields.add(MapEntry(paramActivity, activityAction)); | |||||
| try { | try { | ||||
| await dio | await dio | ||||
| .post("${ConstCommon.baseUrl}/api/updateNursery", data: formData) | |||||
| .post("${ConstCommon.baseUrl}/$apiUpdateAction", data: formData) | |||||
| .then((value) { | .then((value) { | ||||
| onSuccess(value); | |||||
| onSuccess(value.data); | |||||
| }).catchError((onError) { | }).catchError((onError) { | ||||
| onError(AppException.handleError(onError)); | onError(AppException.handleError(onError)); | ||||
| }); | }); |
| try { | try { | ||||
| yield ActionDetailLoading(); | yield ActionDetailLoading(); | ||||
| Response<String> response = await dio.get( | Response<String> response = await dio.get( | ||||
| "${ConstCommon.baseUrl}/api/activity-nursery/${event.activityId}"); | |||||
| "${ConstCommon.baseUrl}/${event.apiActivity}/${event.activityId}"); | |||||
| final jsonData = json.decode(response.data); | final jsonData = json.decode(response.data); | ||||
| yield ActionDetailSuccess(item: jsonData); | yield ActionDetailSuccess(item: jsonData); | ||||
| } catch (error) { | } catch (error) { |
| class FetchData extends ActionDetailEvent { | class FetchData extends ActionDetailEvent { | ||||
| final bool isNeedFetchData; | final bool isNeedFetchData; | ||||
| final String apiActivity; | |||||
| final int activityId; | final int activityId; | ||||
| FetchData({@required this.isNeedFetchData, @required this.activityId}); | |||||
| FetchData( | |||||
| {@required this.isNeedFetchData, | |||||
| @required this.apiActivity, | |||||
| @required this.activityId}); | |||||
| } | } |
| var activityNursery = jsonEncode(_nursery.toJson()).toString(); | var activityNursery = jsonEncode(_nursery.toJson()).toString(); | ||||
| //ADD NEW | //ADD NEW | ||||
| if (_nursery.activityId == null) { | if (_nursery.activityId == null) { | ||||
| _repository.createNursery((value) { | |||||
| _repository.createAction((value) { | |||||
| LoadingDialog.hideLoadingDialog(context); | LoadingDialog.hideLoadingDialog(context); | ||||
| Get.back(); | |||||
| Get.back(result: value); | |||||
| Get.snackbar(label_add_success, "Hoạt động ươm", | Get.snackbar(label_add_success, "Hoạt động ươm", | ||||
| snackPosition: SnackPosition.BOTTOM); | snackPosition: SnackPosition.BOTTOM); | ||||
| }, (error) { | }, (error) { | ||||
| backgroundColor: Colors.red, | backgroundColor: Colors.red, | ||||
| duration: Duration(seconds: 3), | duration: Duration(seconds: 3), | ||||
| )); | )); | ||||
| }, activityNursery, filePaths: filePaths); | |||||
| }, | |||||
| apiAddAction: ConstCommon.apiAddNursery, | |||||
| paramActivity: ConstCommon.paramsActionNursery, | |||||
| activityAction: activityNursery, | |||||
| filePaths: filePaths); | |||||
| } else { | } else { | ||||
| //UPDATE | //UPDATE | ||||
| _repository.updateNursery((value) { | |||||
| _repository.updateAction((value) { | |||||
| LoadingDialog.hideLoadingDialog(context); | LoadingDialog.hideLoadingDialog(context); | ||||
| Get.back(); | |||||
| Get.back(result: value); | |||||
| Get.snackbar(label_update_success, "Hoạt động ươm", | Get.snackbar(label_update_success, "Hoạt động ươm", | ||||
| snackPosition: SnackPosition.BOTTOM); | snackPosition: SnackPosition.BOTTOM); | ||||
| }, (error) { | }, (error) { | ||||
| backgroundColor: Colors.red, | backgroundColor: Colors.red, | ||||
| duration: Duration(seconds: 3), | duration: Duration(seconds: 3), | ||||
| )); | )); | ||||
| }, activityNursery, filePaths: filePaths); | |||||
| }, | |||||
| apiUpdateAction: ConstCommon.apiUpdateNursery, | |||||
| paramActivity: ConstCommon.paramsActionNursery, | |||||
| activityAction: activityNursery, | |||||
| filePaths: filePaths); | |||||
| } | } | ||||
| } else { | } else { | ||||
| _autoValidate = true; | _autoValidate = true; | ||||
| ActionDetailBloc(repository: Repository()) | ActionDetailBloc(repository: Repository()) | ||||
| ..add(FetchData( | ..add(FetchData( | ||||
| isNeedFetchData: widget.isEdit, | isNeedFetchData: widget.isEdit, | ||||
| apiActivity: ConstCommon.apiDetailNursery, | |||||
| activityId: widget.activityId))), | activityId: widget.activityId))), | ||||
| BlocProvider<MediaHelperBloc>( | BlocProvider<MediaHelperBloc>( | ||||
| create: (context) => | create: (context) => |
| } catch (e) { | } catch (e) { | ||||
| yield PlotDetailFailure(errorString: AppException.handleError(e)); | yield PlotDetailFailure(errorString: AppException.handleError(e)); | ||||
| } | } | ||||
| } else if (event is OnUpdate) { | |||||
| yield PlotDetailLoading(); | |||||
| try { | |||||
| yield PlotDetailSuccess( | |||||
| items: event.currentItems, | |||||
| page: event.currentPage, | |||||
| hasReachedMax: event.hasReachedMax); | |||||
| } catch (e) { | |||||
| yield PlotDetailFailure(errorString: AppException.handleError(e)); | |||||
| } | |||||
| } | } | ||||
| } | } | ||||
| } | } |
| part of 'plot_detail_bloc.dart'; | part of 'plot_detail_bloc.dart'; | ||||
| abstract class PlotDetailEvent extends Equatable { | |||||
| abstract class PlotDetailEvent { | |||||
| const PlotDetailEvent(); | const PlotDetailEvent(); | ||||
| @override | @override | ||||
| final String cropCode; | final String cropCode; | ||||
| OnRefresh({this.cropId, this.cropCode}); | OnRefresh({this.cropId, this.cropCode}); | ||||
| } | } | ||||
| class OnUpdate<T> extends PlotDetailEvent { | |||||
| final List<T> currentItems; | |||||
| final int currentPage; | |||||
| final bool hasReachedMax; | |||||
| OnUpdate( | |||||
| {@required this.currentItems, | |||||
| @required this.currentPage, | |||||
| @required this.hasReachedMax}); | |||||
| } |
| part of 'plot_detail_bloc.dart'; | part of 'plot_detail_bloc.dart'; | ||||
| abstract class PlotDetailState extends Equatable { | |||||
| abstract class PlotDetailState { | |||||
| const PlotDetailState(); | const PlotDetailState(); | ||||
| @override | @override |
| class _PlotActionScreenState extends State<PlotActionScreen> { | class _PlotActionScreenState extends State<PlotActionScreen> { | ||||
| List<ActionType> actions = new List<ActionType>(); | List<ActionType> actions = new List<ActionType>(); | ||||
| ScrollController _scrollController; | ScrollController _scrollController; | ||||
| var changeToRefresh = Get.put(ChangeToRefreshLists()); | |||||
| @override | @override | ||||
| void initState() { | void initState() { | ||||
| super.initState(); | super.initState(); | ||||
| changeToRefresh.initValue(); | |||||
| _scrollController = ScrollController()..addListener(() => setState(() {})); | _scrollController = ScrollController()..addListener(() => setState(() {})); | ||||
| _initActionButtons(); | _initActionButtons(); | ||||
| } | } | ||||
| actions.add(ActionType(plot_action_finish, null, EditActionEndScreen())); | actions.add(ActionType(plot_action_finish, null, EditActionEndScreen())); | ||||
| } | } | ||||
| Widget _createActionButtons(ActionType actionType) { | |||||
| return GestureDetector( | |||||
| onTap: () { | |||||
| Navigator.of(context).push( | |||||
| MaterialPageRoute(builder: (context) => actionType.listScreen)); | |||||
| }, | |||||
| child: Container( | |||||
| height: 75, | |||||
| margin: EdgeInsets.all(4.0), | |||||
| padding: EdgeInsets.all(0.0), | |||||
| decoration: BoxDecoration( | |||||
| color: COLOR_CONST.WHITE, | |||||
| borderRadius: BorderRadius.only( | |||||
| topLeft: Radius.circular(8.0), | |||||
| bottomLeft: Radius.circular(8.0), | |||||
| bottomRight: Radius.circular(8.0), | |||||
| topRight: Radius.circular(8.0)), | |||||
| boxShadow: <BoxShadow>[ | |||||
| BoxShadow( | |||||
| color: COLOR_CONST.GRAY1.withOpacity(0.2), | |||||
| offset: Offset(1.1, 1.1), | |||||
| blurRadius: 10.0), | |||||
| ], | |||||
| ), | |||||
| child: Stack( | |||||
| children: <Widget>[ | |||||
| Positioned( | |||||
| top: -10, | |||||
| right: -3, | |||||
| child: (actionType.addScreen == null) | |||||
| ? Container() | |||||
| : IconButton( | |||||
| icon: Icon( | |||||
| Icons.add_circle, | |||||
| size: 40, | |||||
| color: Colors.green, | |||||
| ), | |||||
| onPressed: () { | |||||
| Navigator.of(context).push(MaterialPageRoute( | |||||
| builder: (context) => actionType.addScreen)); | |||||
| })), | |||||
| Positioned.fill( | |||||
| child: Align( | |||||
| alignment: Alignment.center, | |||||
| child: Text( | |||||
| actionType.actionName, | |||||
| textAlign: TextAlign.center, | |||||
| style: TextStyle( | |||||
| fontWeight: FontWeight.w400, | |||||
| fontSize: 13, | |||||
| color: COLOR_CONST.BLACK2, | |||||
| ), | |||||
| )), | |||||
| ), | |||||
| ], | |||||
| ), | |||||
| )); | |||||
| Widget _createActionButtons(ActionType actionType, BuildContext _context) { | |||||
| return BlocProvider<PlotDetailBloc>( | |||||
| create: (context) => PlotDetailBloc(repository: Repository()), | |||||
| child: GestureDetector( | |||||
| onTap: () { | |||||
| Navigator.of(context) | |||||
| .push(MaterialPageRoute( | |||||
| builder: (context) => actionType.listScreen)) | |||||
| .then((value) { | |||||
| if (1 == 1) { | |||||
| try { | |||||
| //TODO: refresh list | |||||
| } catch (e) { | |||||
| print(e.toString()); | |||||
| } | |||||
| } | |||||
| }); | |||||
| }, | |||||
| child: Container( | |||||
| height: 75, | |||||
| margin: EdgeInsets.all(4.0), | |||||
| padding: EdgeInsets.all(0.0), | |||||
| decoration: BoxDecoration( | |||||
| color: COLOR_CONST.WHITE, | |||||
| borderRadius: BorderRadius.only( | |||||
| topLeft: Radius.circular(8.0), | |||||
| bottomLeft: Radius.circular(8.0), | |||||
| bottomRight: Radius.circular(8.0), | |||||
| topRight: Radius.circular(8.0)), | |||||
| boxShadow: <BoxShadow>[ | |||||
| BoxShadow( | |||||
| color: COLOR_CONST.GRAY1.withOpacity(0.2), | |||||
| offset: Offset(1.1, 1.1), | |||||
| blurRadius: 10.0), | |||||
| ], | |||||
| ), | |||||
| child: Stack( | |||||
| children: <Widget>[ | |||||
| Positioned( | |||||
| top: -10, | |||||
| right: -3, | |||||
| child: (actionType.addScreen == null) | |||||
| ? Container() | |||||
| : IconButton( | |||||
| icon: Icon( | |||||
| Icons.add_circle, | |||||
| size: 40, | |||||
| color: Colors.green, | |||||
| ), | |||||
| onPressed: () { | |||||
| Navigator.of(context).push(MaterialPageRoute( | |||||
| builder: (context) => actionType.addScreen)); | |||||
| })), | |||||
| Positioned.fill( | |||||
| child: Align( | |||||
| alignment: Alignment.center, | |||||
| child: Text( | |||||
| actionType.actionName, | |||||
| textAlign: TextAlign.center, | |||||
| style: TextStyle( | |||||
| fontWeight: FontWeight.w400, | |||||
| fontSize: 13, | |||||
| color: COLOR_CONST.BLACK2, | |||||
| ), | |||||
| )), | |||||
| ), | |||||
| ], | |||||
| ), | |||||
| )), | |||||
| ); | |||||
| } | } | ||||
| bool _showTitle(BuildContext context) { | bool _showTitle(BuildContext context) { | ||||
| expandedHeight: MediaQuery.of(context).size.width * 1.125 + 32, | expandedHeight: MediaQuery.of(context).size.width * 1.125 + 32, | ||||
| flexibleSpace: _showTitle(context) | flexibleSpace: _showTitle(context) | ||||
| ? null | ? null | ||||
| : FlexibleSpaceBar( | |||||
| centerTitle: true, | |||||
| title: GridView.count( | |||||
| shrinkWrap: true, | |||||
| crossAxisCount: 2, | |||||
| childAspectRatio: 16 / 6, | |||||
| children: actions.map( | |||||
| (item) { | |||||
| return _createActionButtons(item); | |||||
| }, | |||||
| ).toList()), | |||||
| : BlocProvider<PlotDetailBloc>( | |||||
| create: (context) => PlotDetailBloc(repository: Repository()), | |||||
| child: BlocBuilder<PlotDetailBloc, PlotDetailState>( | |||||
| builder: (contextB, state) { | |||||
| return FlexibleSpaceBar( | |||||
| centerTitle: true, | |||||
| title: GridView.count( | |||||
| shrinkWrap: true, | |||||
| crossAxisCount: 2, | |||||
| childAspectRatio: 16 / 6, | |||||
| children: actions.map( | |||||
| (item) { | |||||
| return _createActionButtons(item, contextB); | |||||
| }, | |||||
| ).toList()), | |||||
| ); | |||||
| }), | |||||
| ), | ), | ||||
| ), | ), | ||||
| ], | ], | ||||
| final _scrollController = ScrollController(); | final _scrollController = ScrollController(); | ||||
| final _scrollThreshold = 250.0; | final _scrollThreshold = 250.0; | ||||
| PlotDetailBloc _plotDetailBloc; | PlotDetailBloc _plotDetailBloc; | ||||
| var a = Get.put(ChangeToRefreshLists()); | |||||
| @override | @override | ||||
| void initState() { | void initState() { | ||||
| if (state.items.isEmpty) { | if (state.items.isEmpty) { | ||||
| return Center(child: Text(label_list_empty)); | return Center(child: Text(label_list_empty)); | ||||
| } | } | ||||
| List<Activities> currentItems = List<Activities>.from(state.items); | |||||
| Get.find<ChangeToRefreshLists>() | |||||
| .updateValue(currentItems, state.page, state.hasReachedMax); | |||||
| return RefreshIndicator( | return RefreshIndicator( | ||||
| child: Column( | child: Column( | ||||
| children: [ | children: [ | ||||
| itemBuilder: (BuildContext context, int index) { | itemBuilder: (BuildContext context, int index) { | ||||
| return index >= state.items.length | return index >= state.items.length | ||||
| ? BottomLoader() | ? BottomLoader() | ||||
| : ItemInfinityWidget(item: state.items[index]); | |||||
| : ItemInfinityWidget( | |||||
| currentItems: currentItems, | |||||
| item: state.items[index], | |||||
| currentPage: state.page, | |||||
| currentReachedMax: state.hasReachedMax); | |||||
| }, | }, | ||||
| itemCount: state.hasReachedMax | itemCount: state.hasReachedMax | ||||
| ? state.items.length | ? state.items.length | ||||
| } | } | ||||
| class ItemInfinityWidget extends StatelessWidget { | class ItemInfinityWidget extends StatelessWidget { | ||||
| final List<Activities> currentItems; | |||||
| final Activities item; | final Activities item; | ||||
| final int currentPage; | |||||
| final bool currentReachedMax; | |||||
| const ItemInfinityWidget({Key key, @required this.item}) : super(key: key); | |||||
| const ItemInfinityWidget( | |||||
| {Key key, | |||||
| @required this.currentItems, | |||||
| @required this.item, | |||||
| @required this.currentPage, | |||||
| @required this.currentReachedMax}) | |||||
| : super(key: key); | |||||
| @override | @override | ||||
| Widget build(BuildContext context) { | Widget build(BuildContext context) { | ||||
| cropId: item.cropId, | cropId: item.cropId, | ||||
| activityId: item.id, | activityId: item.id, | ||||
| isEdit: true, | isEdit: true, | ||||
| )); | |||||
| )).then((value) { | |||||
| if (value != null) { | |||||
| try { | |||||
| //TODO: refresh when edit activity | |||||
| // BlocProvider.of<PlotDetailBloc>(context) | |||||
| // .add(OnRefresh(cropId: item.cropId)); | |||||
| // var updatedItem = Activities.fromJson(value); | |||||
| // List<Activities> updatedItems = new List<Activities>(); | |||||
| // currentItems.forEach((e) { | |||||
| // if (e.id == updatedItem.id) { | |||||
| // e.executeDate = updatedItem.executeDate; | |||||
| // } else { | |||||
| // // | |||||
| // } | |||||
| // updatedItems.add(Activities.clone(e)); | |||||
| // }); | |||||
| // BlocProvider.of<PlotDetailBloc>(context).add( | |||||
| // OnUpdate<Activities>( | |||||
| // currentItems: updatedItems, | |||||
| // currentPage: currentPage, | |||||
| // hasReachedMax: currentReachedMax)); | |||||
| } catch (e) { | |||||
| print(e.toString()); | |||||
| } | |||||
| } | |||||
| }); | |||||
| } | } | ||||
| }); | }); | ||||
| } | } | ||||
| this.listScreen = listScreen; | this.listScreen = listScreen; | ||||
| } | } | ||||
| } | } | ||||
| class ChangeToRefreshLists extends GetxController { | |||||
| List<Activities> currentItems; | |||||
| int currentPage; | |||||
| bool currentReachedMax; | |||||
| void initValue() { | |||||
| currentItems = List<Activities>(); | |||||
| currentPage = 0; | |||||
| currentReachedMax = true; | |||||
| update(); | |||||
| } | |||||
| void updateValue(List<Activities> updateItems, int page, bool reachedMax) { | |||||
| currentItems = updateItems; | |||||
| currentPage = page; | |||||
| currentReachedMax = reachedMax; | |||||
| update(); | |||||
| } | |||||
| } |
| static const String baseUrl = "http://tpf.aztrace.vn"; | static const String baseUrl = "http://tpf.aztrace.vn"; | ||||
| static const String baseImageUrl = "http://s3.tpf.aztrace.vn/upload/"; | static const String baseImageUrl = "http://s3.tpf.aztrace.vn/upload/"; | ||||
| static const String apiDetailNursery = "api/activity-nursery"; | |||||
| static const String paramsActionNursery = "activityNursery"; | |||||
| static const String apiUpdateNursery = "api/updateNursery"; | |||||
| static const String apiAddNursery = "api/createNursery"; | |||||
| static const String supplyTypeSeed = "GIONG"; | static const String supplyTypeSeed = "GIONG"; | ||||
| static const String supplyTypeDung = "PHANBON"; | static const String supplyTypeDung = "PHANBON"; | ||||
| static const String supplyTypeSubStrate = "GIATHE"; | static const String supplyTypeSubStrate = "GIATHE"; |
| } | } | ||||
| } | } | ||||
| //TODO: Check timezone with api update true/false | |||||
| String format_DDMMYY_HHmm() { | String format_DDMMYY_HHmm() { | ||||
| try { | try { | ||||
| final str = this.toString(); | final str = this.toString(); | ||||
| var dateFromString = | var dateFromString = | ||||
| DateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'").parse(str, true).toLocal(); | |||||
| DateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'").parse(str, false).toLocal(); | |||||
| return DateFormat("dd/MM/yyyy HH:mm").format(dateFromString); | return DateFormat("dd/MM/yyyy HH:mm").format(dateFromString); | ||||
| } catch (_) { | } catch (_) { | ||||
| return ""; | return ""; |