add structure
This commit is contained in:
650
lib/pages/do/do_creation_form.dart
Normal file
650
lib/pages/do/do_creation_form.dart
Normal file
@@ -0,0 +1,650 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_datetime_picker/flutter_datetime_picker.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:fcs/model/do_model.dart';
|
||||
import 'package:fcs/model/language_model.dart';
|
||||
import 'package:fcs/model/main_model.dart';
|
||||
import 'package:fcs/model/po_model.dart';
|
||||
import 'package:fcs/model/product_model.dart';
|
||||
import 'package:fcs/pages/do/do_product_item.dart';
|
||||
import 'package:fcs/pages/util.dart';
|
||||
import 'package:fcs/theme/theme.dart';
|
||||
import 'package:fcs/vo/do.dart';
|
||||
import 'package:fcs/vo/po.dart';
|
||||
import 'package:fcs/widget/img_file.dart';
|
||||
import 'package:fcs/widget/local_text.dart';
|
||||
import 'package:fcs/widget/localization/app_translations.dart';
|
||||
import 'package:fcs/widget/my_data_table.dart';
|
||||
import 'package:fcs/widget/number_cell.dart';
|
||||
import 'package:fcs/widget/progress.dart';
|
||||
|
||||
import 'do_files.dart';
|
||||
import 'po_selection.dart';
|
||||
|
||||
class DOForm extends StatefulWidget {
|
||||
final DOSubmission doSubmission;
|
||||
const DOForm({this.doSubmission});
|
||||
@override
|
||||
_DOFormState createState() => _DOFormState();
|
||||
}
|
||||
|
||||
class _DOFormState extends State<DOForm> {
|
||||
var dateFormatter = new DateFormat('dd MMM yyyy');
|
||||
final numberFormatter = new NumberFormat("#,###");
|
||||
|
||||
TextEditingController _deliveryDate = new TextEditingController();
|
||||
TextEditingController _licence = new TextEditingController();
|
||||
TextEditingController _driver = new TextEditingController();
|
||||
TextEditingController _carNo = new TextEditingController();
|
||||
TextEditingController _doStatus = new TextEditingController();
|
||||
TextEditingController _storage = new TextEditingController();
|
||||
TextEditingController _doNumber = new TextEditingController();
|
||||
DOSubmission doSubmission = DOSubmission();
|
||||
|
||||
bool _isLoading = false;
|
||||
bool _isNew = true;
|
||||
final _formKey = GlobalKey<FormState>();
|
||||
int doTypeValue = 0;
|
||||
DOLine doLine = new DOLine();
|
||||
DOFiles files = DOFiles();
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
|
||||
if (widget.doSubmission != null) {
|
||||
this.doSubmission = widget.doSubmission;
|
||||
this._isNew = false;
|
||||
_deliveryDate.text =
|
||||
dateFormatter.format(widget.doSubmission.deliveryDate);
|
||||
_licence.text = widget.doSubmission.driverLicenseNumber;
|
||||
_driver.text = widget.doSubmission.driverName;
|
||||
_carNo.text = widget.doSubmission.carNo;
|
||||
_doStatus.text = widget.doSubmission.status;
|
||||
_storage.text = widget.doSubmission.storageCharge == null
|
||||
? ""
|
||||
: numberFormatter.format(widget.doSubmission.storageCharge);
|
||||
_doNumber.text = widget.doSubmission.doNumber;
|
||||
|
||||
if (widget.doSubmission.type == 'multiple') {
|
||||
doTypeValue = 1;
|
||||
} else {
|
||||
doTypeValue = 0;
|
||||
}
|
||||
this.doLine.action = 'update';
|
||||
} else {
|
||||
this.doLine.action = 'create';
|
||||
doSubmission.type = 'single';
|
||||
_storage.text = "0";
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
var languageModel = Provider.of<LanguageModel>(context);
|
||||
var mainModel = Provider.of<MainModel>(context);
|
||||
var poModel = Provider.of<POSubmissionModel>(context);
|
||||
|
||||
bool isBuyer = mainModel.user.isBuyer();
|
||||
|
||||
final doNumberBox = Container(
|
||||
padding: EdgeInsets.only(left: 20, top: 10),
|
||||
child: Row(
|
||||
children: <Widget>[
|
||||
LocalText(context, "do.do_num"),
|
||||
Container(
|
||||
padding: EdgeInsets.only(left: 10),
|
||||
child: Text(
|
||||
_doNumber.text,
|
||||
style: textStyle,
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
);
|
||||
final _doTypeValueBox = Container(
|
||||
padding: EdgeInsets.only(left: 20, top: 0),
|
||||
child: Row(
|
||||
children: <Widget>[
|
||||
LocalText(context, "do.type"),
|
||||
Container(
|
||||
width: 130,
|
||||
child: RadioListTile(
|
||||
dense: true,
|
||||
title: LocalText(context, "do.single"),
|
||||
value: 0,
|
||||
groupValue: doTypeValue,
|
||||
onChanged: handleRadioValueChanged,
|
||||
activeColor: primaryColor,
|
||||
),
|
||||
),
|
||||
Container(
|
||||
width: 136,
|
||||
child: RadioListTile(
|
||||
dense: true,
|
||||
title: LocalText(context, 'do.multiple'),
|
||||
value: 1,
|
||||
groupValue: doTypeValue,
|
||||
onChanged: handleRadioValueChanged,
|
||||
activeColor: primaryColor,
|
||||
))
|
||||
],
|
||||
),
|
||||
);
|
||||
|
||||
final deliveryDateBox = Container(
|
||||
padding: EdgeInsets.only(left: 20, right: 15),
|
||||
child: InkWell(
|
||||
onTap: () {
|
||||
DatePicker.showDatePicker(context,
|
||||
showTitleActions: true,
|
||||
currentTime: _deliveryDate.text == ""
|
||||
? null
|
||||
: dateFormatter.parse(_deliveryDate.text),
|
||||
minTime: DateTime.now(),
|
||||
maxTime: DateTime(2030, 12, 31), onConfirm: (date) {
|
||||
setState(() {
|
||||
_deliveryDate.text = dateFormatter.format(date);
|
||||
doSubmission.deliveryDate =
|
||||
dateFormatter.parse(_deliveryDate.text);
|
||||
doSubmission.updateStorageCharge(mainModel.setting);
|
||||
});
|
||||
}, locale: LocaleType.en);
|
||||
},
|
||||
child: TextFormField(
|
||||
controller: _deliveryDate,
|
||||
autofocus: false,
|
||||
cursorColor: primaryColor,
|
||||
style: textStyle,
|
||||
enabled: false,
|
||||
keyboardType: TextInputType.datetime,
|
||||
decoration: new InputDecoration(
|
||||
border: InputBorder.none,
|
||||
focusedBorder: InputBorder.none,
|
||||
labelText: AppTranslations.of(context).text("do.date"),
|
||||
labelStyle: languageModel.isEng ? labelStyle : labelStyleMM,
|
||||
contentPadding:
|
||||
EdgeInsets.symmetric(vertical: 10.0, horizontal: 0.0),
|
||||
icon: Icon(
|
||||
Icons.date_range,
|
||||
color: primaryColor,
|
||||
)),
|
||||
validator: (value) {
|
||||
if (value.isEmpty) {
|
||||
return AppTranslations.of(context).text("do.form.date");
|
||||
}
|
||||
return null;
|
||||
},
|
||||
),
|
||||
));
|
||||
|
||||
final driverBox = Container(
|
||||
padding: EdgeInsets.only(left: 20, right: 15),
|
||||
child: TextFormField(
|
||||
controller: _driver,
|
||||
autofocus: false,
|
||||
style: textStyle,
|
||||
cursorColor: primaryColor,
|
||||
decoration: new InputDecoration(
|
||||
labelText: AppTranslations.of(context).text("do.driver"),
|
||||
labelStyle: languageModel.isEng ? labelStyle : labelStyleMM,
|
||||
enabledBorder: UnderlineInputBorder(
|
||||
borderSide: BorderSide(color: Colors.grey, width: 1.0)),
|
||||
focusedBorder: UnderlineInputBorder(
|
||||
borderSide: BorderSide(color: Colors.grey, width: 1.0)),
|
||||
icon: Icon(
|
||||
Icons.account_box,
|
||||
color: primaryColor,
|
||||
)),
|
||||
validator: (value) {
|
||||
if (value.isEmpty) {
|
||||
return AppTranslations.of(context).text("do.form.driver");
|
||||
}
|
||||
return null;
|
||||
},
|
||||
));
|
||||
|
||||
final carNoBox = Container(
|
||||
padding: EdgeInsets.only(left: 20, right: 15),
|
||||
child: TextFormField(
|
||||
controller: _carNo,
|
||||
autofocus: false,
|
||||
style: textStyle,
|
||||
cursorColor: primaryColor,
|
||||
decoration: new InputDecoration(
|
||||
labelText: AppTranslations.of(context).text("do.car"),
|
||||
labelStyle: languageModel.isEng ? labelStyle : labelStyleMM,
|
||||
enabledBorder: UnderlineInputBorder(
|
||||
borderSide: BorderSide(color: Colors.grey, width: 1.0)),
|
||||
focusedBorder: UnderlineInputBorder(
|
||||
borderSide: BorderSide(color: Colors.grey, width: 1.0)),
|
||||
icon: Icon(
|
||||
Icons.directions_car,
|
||||
color: primaryColor,
|
||||
)),
|
||||
validator: (value) {
|
||||
if (value.isEmpty) {
|
||||
return AppTranslations.of(context).text("do.form.car");
|
||||
}
|
||||
return null;
|
||||
},
|
||||
));
|
||||
|
||||
final doStatusBox = Container(
|
||||
padding: EdgeInsets.only(left: 20, right: 15),
|
||||
child: TextFormField(
|
||||
controller: _doStatus,
|
||||
autofocus: false,
|
||||
style: textStyle,
|
||||
readOnly: true,
|
||||
decoration: new InputDecoration(
|
||||
border: InputBorder.none,
|
||||
focusedBorder: InputBorder.none,
|
||||
icon: Image.asset("assets/status.png",
|
||||
width: 25, color: primaryColor)),
|
||||
validator: (value) {
|
||||
if (value.isEmpty) {
|
||||
return "Please enter DO Status";
|
||||
}
|
||||
return null;
|
||||
},
|
||||
));
|
||||
|
||||
final storageBox = Container(
|
||||
padding: EdgeInsets.only(left: 20, top: 10),
|
||||
child: Row(
|
||||
children: <Widget>[
|
||||
LocalText(context, "do.storage_charge"),
|
||||
Container(
|
||||
padding: EdgeInsets.only(left: 10),
|
||||
child: Text(
|
||||
doSubmission.storageCharge == null
|
||||
? ""
|
||||
: numberFormatter.format(doSubmission.storageCharge),
|
||||
style: textStyle,
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
);
|
||||
final storagePaymentBox = Container(
|
||||
padding: EdgeInsets.only(left: 20, top: 5),
|
||||
child: Row(children: <Widget>[
|
||||
LocalText(context, "do.storage_receipt"),
|
||||
ImageFile(
|
||||
enabled: isBuyer,
|
||||
title: "Receipt File",
|
||||
initialImgUrl: this.doSubmission.storageReceiptUrl,
|
||||
onFile: (file) {
|
||||
this.files.setStorageChargeFile = file;
|
||||
}),
|
||||
]));
|
||||
final licesebox = Container(
|
||||
padding: EdgeInsets.only(left: 20, top: 5),
|
||||
child: Row(children: <Widget>[
|
||||
LocalText(context, "do.licence"),
|
||||
ImageFile(
|
||||
enabled: isBuyer,
|
||||
title: "Image",
|
||||
initialImgUrl: this.doSubmission.driverLicenceUrl,
|
||||
onFile: (file) {
|
||||
this.files.setlicenseFile = file;
|
||||
}),
|
||||
]));
|
||||
|
||||
final poButtun = Container(
|
||||
padding: EdgeInsets.only(left: 20, top: 5),
|
||||
child: Row(children: <Widget>[
|
||||
LocalText(context, "po.title"),
|
||||
IconButton(
|
||||
onPressed: () async {
|
||||
POSelection.showPOSelection(
|
||||
context, poModel.approvedPOs, doSubmission.pos,
|
||||
ok: (List<POSubmission> pos) async {
|
||||
for (var po in pos) {
|
||||
po.poLines = await poModel.loadPOLines(po.id);
|
||||
}
|
||||
setState(() {
|
||||
doSubmission.pos = pos;
|
||||
doSubmission.loadPOs();
|
||||
doSubmission.updateStorageCharge(mainModel.setting);
|
||||
});
|
||||
});
|
||||
},
|
||||
icon: Icon(Icons.edit)),
|
||||
]));
|
||||
|
||||
return LocalProgress(
|
||||
inAsyncCall: _isLoading,
|
||||
child: Scaffold(
|
||||
appBar: AppBar(
|
||||
backgroundColor: primaryColor,
|
||||
title: Text(AppTranslations.of(context).text("do"),
|
||||
style: Provider.of<LanguageModel>(context).isEng
|
||||
? TextStyle(fontSize: 18)
|
||||
: TextStyle(fontSize: 18, fontFamily: 'MyanmarUnicode')),
|
||||
actions: <Widget>[
|
||||
IconButton(
|
||||
icon: Icon(Icons.send),
|
||||
onPressed: () {
|
||||
if (!_formKey.currentState.validate()) return;
|
||||
showConfirmDialog(context, "do.confirm", () {
|
||||
_submit(mainModel);
|
||||
});
|
||||
},
|
||||
)
|
||||
],
|
||||
),
|
||||
body: Form(
|
||||
key: _formKey,
|
||||
child: Container(
|
||||
child: ListView(
|
||||
children: <Widget>[
|
||||
Container(
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: <Widget>[
|
||||
this.doSubmission.doNumber != null
|
||||
? doNumberBox
|
||||
: Container(),
|
||||
this.doSubmission.hasStorageCharge()
|
||||
? storageBox
|
||||
: Container(),
|
||||
this.doSubmission.hasStorageCharge()
|
||||
? storagePaymentBox
|
||||
: Container(),
|
||||
_doTypeValueBox,
|
||||
deliveryDateBox,
|
||||
driverBox,
|
||||
licesebox,
|
||||
carNoBox,
|
||||
poButtun,
|
||||
widget.doSubmission == null ? Container() : doStatusBox,
|
||||
getProductTable(),
|
||||
getPOProductTable(),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
)),
|
||||
);
|
||||
}
|
||||
|
||||
void handleRadioValueChanged(int value) {
|
||||
var mainModel = Provider.of<MainModel>(context, listen: false);
|
||||
setState(() {
|
||||
doSubmission.type = value == 0 ? 'single' : 'multiple';
|
||||
doTypeValue = value;
|
||||
doSubmission.loadPOs();
|
||||
doSubmission.updateStorageCharge(mainModel.setting);
|
||||
switch (doTypeValue) {
|
||||
case 0:
|
||||
break;
|
||||
case 1:
|
||||
break;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
Widget getProductTable() {
|
||||
return Container(
|
||||
padding: EdgeInsets.only(top: 10),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: <Widget>[
|
||||
Center(
|
||||
child: LocalText(
|
||||
context,
|
||||
'do.products',
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.bold,
|
||||
underline: true,
|
||||
color: secondaryColor,
|
||||
),
|
||||
),
|
||||
SingleChildScrollView(
|
||||
scrollDirection: Axis.horizontal,
|
||||
padding: EdgeInsets.only(top: 10),
|
||||
child: MyDataTable(
|
||||
headingRowHeight: 40,
|
||||
columnSpacing: 20,
|
||||
columns: [
|
||||
MyDataColumn(label: LocalText(context, "do.product")),
|
||||
MyDataColumn(
|
||||
label: LocalText(context, "po.avail.qty"),
|
||||
numeric: true
|
||||
),
|
||||
MyDataColumn(
|
||||
label: LocalText(context, "do.do_qty"),
|
||||
numeric: true,
|
||||
),
|
||||
],
|
||||
rows: getProductRow(doSubmission.doLines),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
List<MyDataRow> getProductRow(List<DOLine> doLines) {
|
||||
ProductModel productModel = Provider.of<ProductModel>(context);
|
||||
if (doLines.isNotEmpty) {
|
||||
doLines.forEach((d) {
|
||||
productModel.products.forEach((p) {
|
||||
if (p.id == d.productID) {
|
||||
d.displayOrder = p.displayOrder;
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
doLines.sort((p1, p2) => p1.displayOrder.compareTo(p2.displayOrder));
|
||||
}
|
||||
return doLines.map((d) {
|
||||
return MyDataRow(
|
||||
onSelectChanged: (bool selected) async {
|
||||
if (doTypeValue == 0) return;
|
||||
var doLine = await showDialog(
|
||||
context: context,
|
||||
builder: (_) => DOProductItem(
|
||||
doLine: DOLine(productID: d.productID, qty: d.qty),
|
||||
));
|
||||
_updateQty(doLine);
|
||||
},
|
||||
cells: [
|
||||
MyDataCell(
|
||||
new Text(
|
||||
d.productName,
|
||||
style: textStyle,
|
||||
),
|
||||
),
|
||||
MyDataCell(
|
||||
NumberCell(d.poBalQty)
|
||||
),
|
||||
MyDataCell(
|
||||
Container(
|
||||
color: Colors.cyan,
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.end,
|
||||
children: <Widget>[
|
||||
new Text(d.qty == null ? "0" : d.qty.toString(),
|
||||
style: textStyle),
|
||||
],
|
||||
)),
|
||||
),
|
||||
],
|
||||
);
|
||||
}).toList();
|
||||
}
|
||||
|
||||
Widget getPOProductTable() {
|
||||
return Container(
|
||||
padding: EdgeInsets.only(top: 10),
|
||||
child: Column(
|
||||
children: <Widget>[
|
||||
Center(
|
||||
child: LocalText(context, 'po.info',
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.bold,
|
||||
underline: true,
|
||||
color: secondaryColor),
|
||||
),
|
||||
SingleChildScrollView(
|
||||
scrollDirection: Axis.horizontal,
|
||||
child: MyDataTable(
|
||||
headingRowHeight: 40,
|
||||
columnSpacing: 20,
|
||||
columns: [
|
||||
MyDataColumn(label: LocalText(context, "po.number")),
|
||||
MyDataColumn(label: LocalText(context, "po.product")),
|
||||
MyDataColumn(
|
||||
label: LocalText(context, "do.po_qty"),numeric: true,
|
||||
),
|
||||
MyDataColumn(
|
||||
label: LocalText(context, "po.avail.qty"),numeric: true,
|
||||
),
|
||||
MyDataColumn(
|
||||
label: LocalText(context, "po.retrieved.amount"),numeric: true,
|
||||
),
|
||||
MyDataColumn(
|
||||
label: LocalText(context, "do.do_qty"),numeric: true,
|
||||
),
|
||||
],
|
||||
rows: getPOProductRow(),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
List<MyDataRow> getPOProductRow() {
|
||||
ProductModel productModel = Provider.of<ProductModel>(context);
|
||||
if (doSubmission.dopoLies.isNotEmpty) {
|
||||
doSubmission.dopoLies
|
||||
.sort((p1, p2) => p1.poNumber.compareTo(p2.poNumber));
|
||||
doSubmission.dopoLies.forEach((d) {
|
||||
productModel.products.forEach((p) {
|
||||
if (p.id == d.productID) {
|
||||
d.displayOrder = p.displayOrder;
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
doSubmission.dopoLies.sort((p1, p2) {
|
||||
if (p1.displayOrder!=p2.displayOrder)
|
||||
return p1.displayOrder.compareTo(p2.displayOrder);
|
||||
return p1.poNumber.compareTo(p2.poNumber);
|
||||
});
|
||||
}
|
||||
return doSubmission.dopoLies.map((d) {
|
||||
return MyDataRow(
|
||||
cells: [
|
||||
MyDataCell(
|
||||
new Text(
|
||||
d.poNumber,
|
||||
style: textStyle,
|
||||
),
|
||||
),
|
||||
MyDataCell(
|
||||
new Text(
|
||||
d.productName,
|
||||
style: textStyle,
|
||||
),
|
||||
),
|
||||
MyDataCell(
|
||||
NumberCell(d.poQty)
|
||||
),
|
||||
MyDataCell(
|
||||
NumberCell(d.poBalQty)
|
||||
),
|
||||
MyDataCell(
|
||||
NumberCell(d.getPoBalanceQty)
|
||||
),
|
||||
MyDataCell(
|
||||
Container(
|
||||
color: Colors.grey,
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.end,
|
||||
children: <Widget>[
|
||||
new Text(d.doQty == null ? "0" : d.doQty.toString(),
|
||||
style: textStyle),
|
||||
],
|
||||
)),
|
||||
),
|
||||
],
|
||||
);
|
||||
}).toList();
|
||||
}
|
||||
|
||||
_updateQty(DOLine doLine) {
|
||||
if (doLine == null) return;
|
||||
|
||||
try {
|
||||
var mainModel = Provider.of<MainModel>(context);
|
||||
setState(() {
|
||||
doSubmission.updateDoline(doLine.productID, doLine.qty);
|
||||
doSubmission.updateStorageCharge(mainModel.setting);
|
||||
});
|
||||
} catch (e) {
|
||||
showMsgDialog(context, "Error", e.toString());
|
||||
}
|
||||
}
|
||||
|
||||
_submit(MainModel mainModel) async {
|
||||
if (doSubmission.doLines.length == 0) {
|
||||
showMsgDialog(context, "Error", "No product line");
|
||||
return;
|
||||
}
|
||||
if (files.licenseFile == null) {
|
||||
showMsgDialog(context, "Error", "Please insert driver licence");
|
||||
return;
|
||||
}
|
||||
|
||||
int total = 0;
|
||||
doSubmission.doLines.forEach((doLine) {
|
||||
total += doLine.qty;
|
||||
});
|
||||
|
||||
if (total <= 0) {
|
||||
showMsgDialog(context, "Error", "must be greater than zero");
|
||||
return;
|
||||
}
|
||||
|
||||
doSubmission.carNo = _carNo.text;
|
||||
doSubmission.driverName = _driver.text;
|
||||
doSubmission.driverLicenseNumber = _licence.text;
|
||||
doSubmission.deliveryDate = dateFormatter.parse(_deliveryDate.text);
|
||||
doSubmission.type = doTypeValue == 0 ? 'single' : 'multiple';
|
||||
doSubmission.doLines.removeWhere((d) => d.qty == 0);
|
||||
doSubmission.dopoLies.removeWhere((d) => d.doQty == 0);
|
||||
|
||||
setState(() {
|
||||
_isLoading = true;
|
||||
});
|
||||
try {
|
||||
DOModel doModel = Provider.of<DOModel>(context);
|
||||
|
||||
if (_isNew) {
|
||||
await doModel.createDO(doSubmission, files);
|
||||
} else {
|
||||
await doModel.updateDO(doSubmission, files);
|
||||
}
|
||||
Navigator.pop<bool>(context, true);
|
||||
} catch (e) {
|
||||
showMsgDialog(context, "Error", e.toString());
|
||||
} finally {
|
||||
setState(() {
|
||||
_isLoading = false;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user