Finish H2O Services

merge-requests/226/merge
Elham Rababah 5 years ago
parent b853bede68
commit b1b6c77d62

@ -214,7 +214,7 @@ const CHECK_ACTIVATION_CODE_FOR_ADVANCE_PAYMENT = 'Services/Authentication.svc/R
// H2O
const H2O_GET_USER_PROGRESS = "/Services/H2ORemainder.svc/REST/H2O_GetUserProgress";
const H2O_INSERT_USER_ACTIVITY="Services/H2ORemainder.svc/REST/H2O_InsertUserActivity";

@ -0,0 +1,76 @@
class InsertUserActivityRequestModel {
String identificationNo;
String mobileNumber;
int quantityIntake;
double versionID;
int channel;
int languageID;
String iPAdress;
String generalid;
int patientOutSA;
String sessionID;
bool isDentalAllowedBackend;
int deviceTypeID;
int patientID;
String tokenID;
int patientTypeID;
int patientType;
InsertUserActivityRequestModel(
{this.identificationNo,
this.mobileNumber,
this.quantityIntake,
this.versionID,
this.channel,
this.languageID,
this.iPAdress,
this.generalid,
this.patientOutSA,
this.sessionID,
this.isDentalAllowedBackend,
this.deviceTypeID,
this.patientID,
this.tokenID,
this.patientTypeID,
this.patientType});
InsertUserActivityRequestModel.fromJson(Map<String, dynamic> json) {
identificationNo = json['IdentificationNo'];
mobileNumber = json['MobileNumber'];
quantityIntake = json['QuantityIntake'];
versionID = json['VersionID'];
channel = json['Channel'];
languageID = json['LanguageID'];
iPAdress = json['IPAdress'];
generalid = json['generalid'];
patientOutSA = json['PatientOutSA'];
sessionID = json['SessionID'];
isDentalAllowedBackend = json['isDentalAllowedBackend'];
deviceTypeID = json['DeviceTypeID'];
patientID = json['PatientID'];
tokenID = json['TokenID'];
patientTypeID = json['PatientTypeID'];
patientType = json['PatientType'];
}
Map<String, dynamic> toJson() {
final Map<String, dynamic> data = new Map<String, dynamic>();
data['IdentificationNo'] = this.identificationNo;
data['MobileNumber'] = this.mobileNumber;
data['QuantityIntake'] = this.quantityIntake;
data['VersionID'] = this.versionID;
data['Channel'] = this.channel;
data['LanguageID'] = this.languageID;
data['IPAdress'] = this.iPAdress;
data['generalid'] = this.generalid;
data['PatientOutSA'] = this.patientOutSA;
data['SessionID'] = this.sessionID;
data['isDentalAllowedBackend'] = this.isDentalAllowedBackend;
data['DeviceTypeID'] = this.deviceTypeID;
data['PatientID'] = this.patientID;
data['TokenID'] = this.tokenID;
data['PatientTypeID'] = this.patientTypeID;
data['PatientType'] = this.patientType;
return data;
}
}

@ -1,4 +1,5 @@
import 'package:diplomaticquarterapp/config/config.dart';
import 'package:diplomaticquarterapp/core/model/AlHabibMedicalService/insert_user_activity_request_model.dart';
import 'package:diplomaticquarterapp/core/model/AlHabibMedicalService/user_progress_for_today_data_model.dart';
import 'package:diplomaticquarterapp/core/model/AlHabibMedicalService/user_progress_request_model.dart';
import 'package:diplomaticquarterapp/core/service/base_service.dart';
@ -10,7 +11,7 @@ class H2OService extends BaseService {
Future getUserProgressForTodayData() async {
userProgressRequestModel.progress = 1;
userProgressRequestModel.mobileNumber = "537503378";//super.user.mobileNumber;
userProgressRequestModel.mobileNumber = super.user.mobileNumber.substring(1);
userProgressRequestModel.identificationNo = super.user.patientIdentificationNo;
hasError = false;
@ -27,6 +28,23 @@ class H2OService extends BaseService {
}, body: userProgressRequestModel.toJson());
}
Future insertUserActivity(InsertUserActivityRequestModel insertUserActivityRequestModel) async {
hasError = false;
await baseAppClient.post(H2O_INSERT_USER_ACTIVITY,
onSuccess: (dynamic response, int statusCode) {
userProgressForTodayDataList.clear();
response['UserProgressForTodayData'].forEach((progressData) {
userProgressForTodayDataList
.add(UserProgressForTodayDataModel.fromJson(progressData));
});
}, onFailure: (String error, int statusCode) {
hasError = true;
super.error = error;
}, body: insertUserActivityRequestModel.toJson());
}
Future getUserProgressForWeekData() async {
userProgressRequestModel.progress = 2;

@ -1,21 +1,44 @@
import 'package:diplomaticquarterapp/core/enum/viewstate.dart';
import 'package:diplomaticquarterapp/core/model/AlHabibMedicalService/insert_user_activity_request_model.dart';
import 'package:diplomaticquarterapp/core/model/AlHabibMedicalService/user_progress_for_today_data_model.dart';
import 'package:diplomaticquarterapp/core/model/AlHabibMedicalService/user_progress_request_model.dart';
import 'package:diplomaticquarterapp/core/service/AlHabibMedicalService/H2O_service.dart';
import 'package:diplomaticquarterapp/core/viewModels/base_view_model.dart';
import '../../../locator.dart';
class H2OViewModel extends BaseViewModel {
H2OService _h2OService =
locator<H2OService>();
H2OService _h2OService = locator<H2OService>();
UserProgressForTodayDataModel get userProgressData => _h2OService.userProgressForTodayDataList[0];
Future getUserProgressForTodayData() async {
// UserProgressRequestModel userProgressRequestModel =
// UserProgressRequestModel();
// userProgressRequestModel.progress = 1;
// userProgressRequestModel.mobileNumber = user.mobileNumber.substring(1);
// userProgressRequestModel.identificationNo = user.patientIdentificationNo;
setState(ViewState.BusyLocal);
await _h2OService.getUserProgressForTodayData();
if (_h2OService.hasError) {
error = _h2OService.error;
setState(ViewState.Error);
setState(ViewState.ErrorLocal);
} else {
setState(ViewState.Idle);
}
}
Future insertUserActivity(InsertUserActivityRequestModel insertUserActivityRequestModel) async {
setState(ViewState.BusyLocal);
insertUserActivityRequestModel.mobileNumber = user.mobileNumber.substring(1);
insertUserActivityRequestModel.identificationNo = user.patientIdentificationNo;
await _h2OService.insertUserActivity(insertUserActivityRequestModel);
if (_h2OService.hasError) {
error = _h2OService.error;
setState(ViewState.ErrorLocal);
} else {
setState(ViewState.Idle);
}

@ -0,0 +1,107 @@
import 'package:diplomaticquarterapp/core/model/AlHabibMedicalService/insert_user_activity_request_model.dart';
import 'package:diplomaticquarterapp/core/model/hospitals/hospitals_model.dart';
import 'package:diplomaticquarterapp/core/viewModels/AlHabibMedicalService/H2O_view_model.dart';
import 'package:diplomaticquarterapp/uitl/translations_delegate_base.dart';
import 'package:diplomaticquarterapp/widgets/data_display/text.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
// ignore: must_be_immutable
class ConfirmAddAmountDialog extends StatefulWidget {
final int amount;
final String unit;
final H2OViewModel model;
ConfirmAddAmountDialog(
{Key key, this.model,this.amount,this.unit ="ml"});
@override
_ConfirmAddAmountDialogState createState() => _ConfirmAddAmountDialogState();
}
class _ConfirmAddAmountDialogState extends State<ConfirmAddAmountDialog> {
@override
void initState() {
super.initState();
}
@override
Widget build(BuildContext context) {
return SimpleDialog(
contentPadding: EdgeInsets.fromLTRB(28.0, 24.0, 28.0, 0.0),
title: Center(
child: Texts(
"Confirm",
color: Colors.black,
),
),
children: [
Column(
children: [
Divider(),
Center(
child: Texts(
"Are you sure you want to Add ${widget.amount} ${widget.unit} ?",
color: Colors.grey,
),
),
SizedBox(
height: 5.0,
),
Row(
// mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
Expanded(
flex: 1,
child: InkWell(
onTap: () {
Navigator.pop(context);
},
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Container(
child: Center(
child: Texts(
TranslationBase.of(context).cancel.toUpperCase(),
color: Colors.red,
),
),
),
),
),
),
Container(
width: 1,
height: 30,
color: Colors.grey[500],
),
Expanded(
flex: 1,
child: InkWell(
onTap: () async{
InsertUserActivityRequestModel insertUserActivityRequestModel= InsertUserActivityRequestModel(quantityIntake:widget.amount );
await widget.model.insertUserActivity(insertUserActivityRequestModel);
Navigator.pop(context);
},
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Center(
child: Texts(
TranslationBase.of(context).ok,
fontWeight: FontWeight.w400,
)),
),
),
),
],
)
],
)
],
);
}
}

@ -0,0 +1,157 @@
import 'package:diplomaticquarterapp/uitl/translations_delegate_base.dart';
import 'package:diplomaticquarterapp/widgets/data_display/text.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
// ignore: must_be_immutable
class SelectAmountDialog extends StatefulWidget {
List<AmountModel> searchAmount = [
AmountModel(name: "l",nameAr:"لتر",value: 1),
AmountModel(name: "ml",nameAr:"مم لتر",value: 2),
];
final Function(AmountModel) onValueSelected;
AmountModel selectedAmount;
SelectAmountDialog(
{Key key, this.onValueSelected, this.selectedAmount});
@override
_SelectAmountDialogState createState() => _SelectAmountDialogState();
}
class _SelectAmountDialogState extends State<SelectAmountDialog> {
@override
void initState() {
super.initState();
widget.selectedAmount = widget.selectedAmount ?? widget.searchAmount[0];
}
@override
Widget build(BuildContext context) {
return SimpleDialog(
children: [
Column(
children: [
Texts("Select the preferred unit", fontSize: 20,),
Divider(),
...List.generate(
widget.searchAmount.length,
(index) => Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
SizedBox(
height: 2,
),
Row(
children: <Widget>[
Expanded(
flex: 1,
child: InkWell(
onTap: () {
setState(() {
widget.selectedAmount = widget.searchAmount[index];
});
},
child: ListTile(
title: Text(widget.searchAmount[index].name),
leading: Radio(
value: widget.searchAmount[index],
groupValue: widget.selectedAmount,
activeColor: Colors.red[800],
onChanged: (value) {
setState(() {
widget.selectedAmount = value;
});
},
),
),
),
)
],
),
SizedBox(
height: 5.0,
),
],
),
),
SizedBox(
height: 5.0,
),
Row(
// mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
Expanded(
flex: 1,
child: InkWell(
onTap: () {
Navigator.pop(context);
},
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Container(
child: Center(
child: Texts(
TranslationBase.of(context).cancel.toUpperCase(),
color: Colors.red,
),
),
),
),
),
),
Container(
width: 1,
height: 30,
color: Colors.grey[500],
),
Expanded(
flex: 1,
child: InkWell(
onTap: () {
widget.onValueSelected(widget.selectedAmount);
Navigator.pop(context);
},
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Center(
child: Texts(
TranslationBase.of(context).ok,
fontWeight: FontWeight.w400,
)),
),
),
),
],
)
],
)
],
);
}
}
class AmountModel {
String name;
String nameAr;
int value;
AmountModel({this.name, this.nameAr, this.value});
AmountModel.fromJson(Map<String, dynamic> json) {
name = json['name'];
nameAr = json['nameAr'];
value = json['value'];
}
Map<String, dynamic> toJson() {
final Map<String, dynamic> data = new Map<String, dynamic>();
data['name'] = this.name;
data['nameAr'] = this.nameAr;
data['value'] = this.value;
return data;
}
}

@ -0,0 +1,131 @@
import 'package:diplomaticquarterapp/core/viewModels/AlHabibMedicalService/H2O_view_model.dart';
import 'package:diplomaticquarterapp/pages/AlHabibMedicalService/h2o/Dialog/confirm_add_amount_dialog.dart';
import 'package:diplomaticquarterapp/pages/medical/balance/new_text_Field.dart';
import 'package:diplomaticquarterapp/widgets/buttons/secondary_button.dart';
import 'package:diplomaticquarterapp/widgets/data_display/text.dart';
import 'package:diplomaticquarterapp/widgets/others/app_scaffold_widget.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'Dialog/select_amount_dialog.dart';
class AddCustomAmount extends StatefulWidget {
final H2OViewModel model;
final Function changePageViewIndex;
const AddCustomAmount({Key key, this.model, this.changePageViewIndex})
: super(key: key);
@override
_AddCustomAmountState createState() => _AddCustomAmountState();
}
class _AddCustomAmountState extends State<AddCustomAmount> {
TextEditingController _nameTextController = TextEditingController();
AmountModel selectedAmount;
@override
void initState() {
setState(() {
_nameTextController.text = "0";
});
super.initState();
}
@override
Widget build(BuildContext context) {
return AppScaffold(
isShowAppBar: true,
appBarTitle: "Enter amount",
body: SingleChildScrollView(
physics: ScrollPhysics(),
child: Container(
margin: EdgeInsets.all(12),
child: Center(
child: FractionallySizedBox(
widthFactor: 0.94,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
SizedBox(
height: 12,
),
NewTextFields(
hintText: "Enter the amount of water:",
// type: "Number",
controller: _nameTextController,
),
SizedBox(
height: 12,
),
InkWell(
onTap: () => confirmAmountTypeDialog(),
child: Container(
padding: EdgeInsets.all(12),
width: double.infinity,
height: 65,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(12),
color: Colors.white),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Texts(getAmountName()),
Icon(Icons.arrow_drop_down)
],
),
),
),
SizedBox(
height: 12,
),
SecondaryButton(
textColor: Colors.white,
label: "OK",
onTap: () async {
Navigator.of(context).pop();
showConfirmMessage (int.parse(_nameTextController.text), widget.model);
},
// loading: model.state == ViewState.BusyLocal,
disabled: _nameTextController.text.isEmpty || selectedAmount == null),
SizedBox(
height: 12,
),
],
),
),
),
),
),
);
}
void confirmAmountTypeDialog() {
showDialog(
context: context,
child: SelectAmountDialog(
selectedAmount: selectedAmount,
onValueSelected: (value) {
setState(() {
selectedAmount = value;
});
},
),
);
}
String getAmountName() {
if (selectedAmount != null)
return selectedAmount.name;
else
return "Select unit";
}
void showConfirmMessage(int amount, H2OViewModel model) {
showDialog(context: context, child: ConfirmAddAmountDialog(model: model,amount:amount,));
}
}

@ -2,6 +2,7 @@ import 'package:diplomaticquarterapp/core/enum/viewstate.dart';
import 'package:diplomaticquarterapp/core/viewModels/AlHabibMedicalService/H2O_view_model.dart';
import 'package:diplomaticquarterapp/pages/AlHabibMedicalService/h2o/widgets/h20_floating_action_button.dart';
import 'package:diplomaticquarterapp/pages/base/base_view.dart';
import 'package:diplomaticquarterapp/widgets/errors/app_embedded_error.dart';
import 'package:diplomaticquarterapp/widgets/others/app_scaffold_widget.dart';
import 'package:diplomaticquarterapp/widgets/progress_indicator/app_circular_progress_Indeicator.dart';
import 'package:flutter/cupertino.dart';
@ -47,7 +48,7 @@ class _TodayPageState extends State<TodayPage> with TickerProviderStateMixin {
child: Container(
margin: EdgeInsets.only(top: 90),
child: AppCircularProgressIndicator()))
: Container(
: model.state == ViewState.ErrorLocal ? AppEmbeddedError(error: model.error):Container(
margin: EdgeInsets.only(top: 60),
child: Column(
children: [
@ -59,6 +60,7 @@ class _TodayPageState extends State<TodayPage> with TickerProviderStateMixin {
lineWidth: 15.0,
percent:
(model.userProgressData.percentageConsumed /
100) >= 1?1:(model.userProgressData.percentageConsumed /
100),
//,
center: Center(
@ -76,7 +78,7 @@ class _TodayPageState extends State<TodayPage> with TickerProviderStateMixin {
),
Text(
model.userProgressData.quantityConsumed
.toString(),
.toString() + 'ml',
style: TextStyle(
fontWeight: FontWeight.bold,
fontSize: 20.0,
@ -89,9 +91,12 @@ class _TodayPageState extends State<TodayPage> with TickerProviderStateMixin {
height: 5,
width: 50,
child: Container(
color: Colors.grey,
),
),
SizedBox(
height: 4,
),
Text(
"Remaining",
style: TextStyle(fontSize: 20.0),
@ -102,11 +107,13 @@ class _TodayPageState extends State<TodayPage> with TickerProviderStateMixin {
Text(
(model.userProgressData.quantityLimit -
model.userProgressData
.quantityConsumed)
.toString(),
.quantityConsumed) <0 ?"0 ml":
(model.userProgressData.quantityLimit -
model.userProgressData
.quantityConsumed).toString() +' ml',
style: TextStyle(
fontWeight: FontWeight.bold,
fontSize: 20.0),
fontSize: 18.0),
),
],
),
@ -167,18 +174,19 @@ class _TodayPageState extends State<TodayPage> with TickerProviderStateMixin {
SizedBox(
height: 0.5,
width: MediaQuery.of(context).size.width,
child: Container(
color: Colors.grey,
),
child: Container(
color: Colors.grey,
),
],
),
),
],
),
),
floatingActionButton: H20FloatingActionButton(
controller: _controller,
backgroundColor: backgroundColor,
foregroundColor: foregroundColor)),
),
),
floatingActionButton: H20FloatingActionButton(
controller: _controller,
model: model,
),
),
);
}
}

@ -1,111 +1,129 @@
import 'dart:math' as math;
import 'package:diplomaticquarterapp/core/model/AlHabibMedicalService/insert_user_activity_request_model.dart';
import 'package:diplomaticquarterapp/core/viewModels/AlHabibMedicalService/H2O_view_model.dart';
import 'package:diplomaticquarterapp/pages/AlHabibMedicalService/h2o/Dialog/confirm_add_amount_dialog.dart';
import 'package:diplomaticquarterapp/pages/base/base_view.dart';
import 'package:diplomaticquarterapp/uitl/translations_delegate_base.dart';
import 'package:diplomaticquarterapp/widgets/data_display/text.dart';
import 'package:diplomaticquarterapp/widgets/transitions/fade_page.dart';
import 'package:flutter/material.dart';
import '../add_custom_amount.dart';
class H20FloatingActionButton extends StatelessWidget {
const H20FloatingActionButton({
Key key,
@required AnimationController controller,
@required this.backgroundColor,
@required this.foregroundColor,
@required this.model
}) : _controller = controller,
super(key: key);
final AnimationController _controller;
final Color backgroundColor;
final Color foregroundColor;
final H2OViewModel model;
@override
Widget build(BuildContext context) {
_showMaterialDialog(String count) {
return SimpleDialog(
contentPadding: EdgeInsets.fromLTRB(28.0, 24.0, 28.0, 0.0),
title: Center(
child: Texts(
"Confirm",
color: Colors.black,
),
),
children: [
Column(
children: [
Divider(),
Center(
child: Texts(
"Are you sure you want to Add $count ?",
color: Colors.grey,
),
),
SizedBox(
height: 5.0,
),
Row(
// mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
Expanded(
flex: 1,
child: InkWell(
onTap: () {
Navigator.pop(context);
},
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Container(
child: Center(
child: Texts(
TranslationBase.of(context).cancel.toUpperCase(),
color: Colors.red,
),
),
),
),
),
),
Container(
width: 1,
height: 30,
color: Colors.grey[500],
),
Expanded(
flex: 1,
child: InkWell(
onTap: () {
// _showMaterialDialog(String count, H2OViewModel model) {
// return SimpleDialog(
// contentPadding: EdgeInsets.fromLTRB(28.0, 24.0, 28.0, 0.0),
// title: Center(
// child: Texts(
// "Confirm",
// color: Colors.black,
// ),
// ),
// children: [
// Column(
// children: [
// Divider(),
// Center(
// child: Texts(
// "Are you sure you want to Add $count ?",
// color: Colors.grey,
// ),
// ),
// SizedBox(
// height: 5.0,
// ),
// Row(
// // mainAxisAlignment: MainAxisAlignment.spaceBetween,
// children: <Widget>[
// Expanded(
// flex: 1,
// child: InkWell(
// onTap: () {
// Navigator.pop(context);
// },
// child: Padding(
// padding: const EdgeInsets.all(8.0),
// child: Container(
// child: Center(
// child: Texts(
// TranslationBase.of(context).cancel.toUpperCase(),
// color: Colors.red,
// ),
// ),
// ),
// ),
// ),
// ),
// Container(
// width: 1,
// height: 30,
// color: Colors.grey[500],
// ),
// Expanded(
// flex: 1,
// child: InkWell(
// onTap: () async{
// // insertUserActivity
// int addedValue;
//
// switch(count) {
// case "600ml": {
// addedValue = 600;
// }
// break;
//
// case "330ml": {
// addedValue = 330;
//
// }
// break;
// case "200ml": {
// addedValue = 200;
// }
// break;
//
// }
//
// InsertUserActivityRequestModel insertUserActivityRequestModel= InsertUserActivityRequestModel(quantityIntake:addedValue );
// await model.insertUserActivity(insertUserActivityRequestModel);
// Navigator.pop(context);
// },
// child: Padding(
// padding: const EdgeInsets.all(8.0),
// child: Center(
// child: Texts(
// TranslationBase.of(context).ok,
// fontWeight: FontWeight.w400,
// )),
// ),
// ),
// ),
// ],
// )
// ],
// )
// ],
// );
// }
Navigator.pop(context);
},
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Center(
child: Texts(
TranslationBase.of(context).ok,
fontWeight: FontWeight.w400,
)),
),
),
),
],
)
],
)
],
);
void showConfirmMessage(int amount, H2OViewModel model) {
showDialog(context: context, child: ConfirmAddAmountDialog(model: model,amount:amount,));
}
void ShowConfirmMessage(String count){
showDialog(
context: context,
child: _showMaterialDialog(count)
);
}
return Container(
margin: EdgeInsets.only(left: 20),
child: new Column(mainAxisSize: MainAxisSize.min, children: [
@ -119,21 +137,21 @@ class H20FloatingActionButton extends StatelessWidget {
controller: _controller,
text: "600ml",
onTap: () {
ShowConfirmMessage("600ml");
showConfirmMessage(600, model);
},
),
ActionButton(
controller: _controller,
text: "330ml",
onTap: () {
ShowConfirmMessage("330ml");
showConfirmMessage(330, model);
},
),
ActionButton(
controller: _controller,
text: "200ml",
onTap: () {
ShowConfirmMessage("200ml");
showConfirmMessage(200, model);
},
),
],
@ -174,15 +192,24 @@ class H20FloatingActionButton extends StatelessWidget {
curve: Curves.easeOut),
),
child: new FloatingActionButton(
backgroundColor: Colors.white,
heroTag: null,
backgroundColor: backgroundColor,
mini: true,
child: Text(
"Custom",
textAlign: TextAlign.center,
style: TextStyle(fontSize: 14.0, color: Colors.grey),
),
onPressed: () {},
onPressed: () {
Navigator.push(
context,
FadePage(
page: AddCustomAmount(
model: model,
),
),
);
},
),
),
),
@ -195,8 +222,8 @@ class H20FloatingActionButton extends StatelessWidget {
curve: Curves.easeOut),
),
child: new FloatingActionButton(
backgroundColor: Colors.white,
heroTag: null,
backgroundColor: backgroundColor,
mini: true,
child: Text(
"Undo",

Loading…
Cancel
Save