import 'dart:convert'; import 'dart:io'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:fluttertoast/fluttertoast.dart'; import 'package:google_fonts/google_fonts.dart'; import 'package:employee_selfservice_mobile/Screens/Settings/RequestHttp/changePassword_post.dart'; import 'package:employee_selfservice_mobile/Screens/Settings/RequestHttp/changeprofileimage_post.dart'; import 'package:employee_selfservice_mobile/Screens/Settings/RequestHttp/getDetail_post.dart'; import 'package:employee_selfservice_mobile/Screens/Settings/RequestHttp/getProfileImage_post.dart'; import 'package:employee_selfservice_mobile/Screens/Settings/RequestHttp/logout_post.dart'; import 'package:image_picker/image_picker.dart'; import 'package:progress_dialog_null_safe/progress_dialog_null_safe.dart'; import 'package:shared_preferences/shared_preferences.dart'; import '../Login/login_screen.dart'; import '../Settings/inputWidget.dart'; import 'dart:developer' as logDev; class SettingsScreen extends StatefulWidget { const SettingsScreen({Key? key}) : super(key: key); @override State createState() => _SettingsScreenState(); } class _SettingsScreenState extends State { bool visible = false; bool visiblePersonalInformation = false; File? _imagePath; String name = "", statusDetail = "", dateOfBirth = "", phone = "", email = "", address = "", position = ""; var _imageToShow; @override initState() { super.initState(); passwordController.clear(); newPasswordController.clear(); retypeNewPasswordController.clear(); getDetail(); _imageToShow = AssetImage('assets/images/ic_administrator.png'); WidgetsBinding.instance.addPostFrameCallback((_) { _imageToShow = getProfileImage(); }); } //Get Profile image getProfileImage() async { final SharedPreferences prefs = await SharedPreferences.getInstance(); final session = prefs.getString('session'); ProgressDialog loading = ProgressDialog(context); loading = ProgressDialog(context, type: ProgressDialogType.normal, isDismissible: false, showLogs: true); loading.style( message: 'Please Wait .....', borderRadius: 5, backgroundColor: Colors.white, progressWidget: CircularProgressIndicator(), elevation: 10.0, padding: EdgeInsets.all(10), insetAnimCurve: Curves.easeInOut, progress: 0.0, maxProgress: 100.0, progressTextStyle: TextStyle( color: Colors.black, fontSize: 10.0, fontWeight: FontWeight.w400), messageTextStyle: TextStyle( color: Colors.black, fontSize: 15.0, fontWeight: FontWeight.w600)); await loading.show(); GetProfileImage_Post.connectToAPI(session!).then((valueResult) async { Map object = json.decode(valueResult); if (object.containsKey("result").toString() == "true") { String status = object['result']['status'].toString(); logDev.log(status, name: "STATUS GET PROFILE"); if (status == "success") { String photo = object['result']['photo'].toString(); if (photo == "false") { setState(() { _imageToShow = AssetImage('assets/icons/ic_pp_2.png'); }); } else if (photo != "false") { Uint8List decodedBytes = Base64Decoder().convert(photo); //logDev.log(decodedBytes.toString(), name: "DECODED BYTES photo"); setState(() { _imageToShow = Image.memory(decodedBytes, gaplessPlayback: true).image; }); } } else if (status == "failed") { String message = object['result']['message'].toString(); Fluttertoast.showToast( msg: message, toastLength: Toast.LENGTH_LONG, gravity: ToastGravity.CENTER, timeInSecForIosWeb: 1, textColor: Colors.white, fontSize: 16.0); } await loading.hide(); } else { Fluttertoast.showToast( msg: "Server Response Error", toastLength: Toast.LENGTH_SHORT, gravity: ToastGravity.CENTER, timeInSecForIosWeb: 1, textColor: Colors.white, fontSize: 16.0); await loading.hide(); } }); await loading.hide(); return _imageToShow; } //Get Detail getDetail() async { GetDetail_Post.connectToAPI().then((valueResult) async { Map object = json.decode(valueResult); if (object.containsKey("result").toString() == "true") { statusDetail = object['result']['status'].toString(); String message = object['result']['message'].toString(); if (statusDetail == "failed" || message == "User Not Found") { Fluttertoast.showToast( msg: message + ", Please restart the Application!", toastLength: Toast.LENGTH_SHORT, gravity: ToastGravity.CENTER, timeInSecForIosWeb: 1, textColor: Colors.white, fontSize: 16.0); } else if (statusDetail != "failed") { name = object['result']['name'].toString(); dateOfBirth = object['result']['date_of_birth'].toString(); phone = object['result']['phone'].toString(); email = object['result']['email'].toString(); address = object['result']['address'].toString(); position = object['result']['position'].toString(); var prefs = await SharedPreferences.getInstance(); if (position == "false"){ position = "-"; await prefs.setString('position', position); } else { await prefs.setString('position', position); } if (name == "false"){ name = "-"; } if (dateOfBirth == "false"){ dateOfBirth = "-"; } if (phone == "false"){ phone = "-"; } if (email == "false"){ email = "-"; } if (address == "false"){ address = "-"; } } } else { Fluttertoast.showToast( msg: "Server Response Error", toastLength: Toast.LENGTH_SHORT, gravity: ToastGravity.CENTER, timeInSecForIosWeb: 1, textColor: Colors.white, fontSize: 16.0); } }); } @override Widget build(BuildContext context) { var size = MediaQuery.of(context).size; ProgressDialog loading = ProgressDialog(context); loading = ProgressDialog(context, type: ProgressDialogType.normal, isDismissible: false, showLogs: true); loading.style( message: 'Please Wait .....', borderRadius: 5, backgroundColor: Colors.white, progressWidget: CircularProgressIndicator(), elevation: 10.0, padding: EdgeInsets.all(10), insetAnimCurve: Curves.easeInOut, progress: 0.0, maxProgress: 100.0, progressTextStyle: TextStyle( color: Colors.black, fontSize: 10.0, fontWeight: FontWeight.w400), messageTextStyle: TextStyle( color: Colors.black, fontSize: 15.0, fontWeight: FontWeight.w600)); return Scaffold( body: SingleChildScrollView( child: Stack( children: [ Container( height: size.height * 0.3, decoration: BoxDecoration( gradient: LinearGradient( begin: Alignment.topRight, end: Alignment.bottomRight, colors: [ Color(0xFF4858A7), Color(0xFF6474C6), ])), ), SafeArea( child: Padding( padding: EdgeInsets.all(20), child: Column( children: [ Stack( alignment: Alignment.topCenter, children: [ Container( margin: EdgeInsets.only(top: (((size.width - 20) * 0.33) * 0.5)), child: Card( elevation: 15, child: Container( height: (size.height * 0.28) * .7, width: size.width - 20, padding: EdgeInsets.all(15), decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.all(Radius.circular(10))), child: Column( mainAxisAlignment: MainAxisAlignment.end, crossAxisAlignment: CrossAxisAlignment.center, children: [ Text(name, maxLines: 2, overflow: TextOverflow.ellipsis, textAlign: TextAlign.center, style: GoogleFonts.inter( fontSize: size.width*0.045, color: Colors.black, fontWeight: FontWeight.bold), ), Text(position, textAlign: TextAlign.center, style: GoogleFonts.inter( fontSize: size.width*0.035, color: Colors.black), ), ], )), ), ), Container( height: (size.width - 20) * 0.33, width: (size.width - 20) * 0.33, decoration: BoxDecoration( color: Colors.black, image: DecorationImage( image: _imageToShow, fit: BoxFit.cover , ), shape: BoxShape.circle, ), ), InkWell( child: Container( height: ((size.width - 20) * 0.33) * 0.25, width: ((size.width - 20) * 0.33) * 0.25, margin: EdgeInsets.only( top: (((size.width - 20) * 0.33) - 20), left: ((size.width - 20) * 0.33) - 10), decoration: BoxDecoration( color: Colors.yellow, image: DecorationImage( image: AssetImage('assets/images/ic_camera.png'), scale: 3, fit: BoxFit.scaleDown, ), shape: BoxShape.circle, ), ), onTap: () async { final ImagePicker _picker = ImagePicker(); final XFile? imagePicked = await _picker.pickImage( source: ImageSource.gallery); if (imagePicked != null) { _imagePath = File(imagePicked.path); List imageBytes = _imagePath!.readAsBytesSync(); String base64Image = base64Encode(imageBytes); //logDev.log(base64Image, name: "String base64"); await loading.show(); ChangeProfileImage_Post.connectToAPI(base64Image).then((valueResult) async { Map object = json.decode(valueResult); if (object.containsKey("result").toString() == "true") { String status = object['result']['status'].toString(); String message = object['result']['message'].toString(); if (status == "success") { _imageToShow = FileImage(File(imagePicked.path)); /* Fluttertoast.showToast( msg: message, toastLength: Toast.LENGTH_LONG, gravity: ToastGravity.CENTER, timeInSecForIosWeb: 1, textColor: Colors.white, fontSize: 16.0);*/ await loading.hide(); showAlertDialogChangeProfile(context, "Success, " + message, "Ok"); } else if (status == "failed") { /* Fluttertoast.showToast( msg: message, toastLength: Toast.LENGTH_LONG, gravity: ToastGravity.CENTER, timeInSecForIosWeb: 1, textColor: Colors.white, fontSize: 16.0);*/ await loading.hide(); showAlertDialogChangeProfile(context, "Failed, " + message, "Back"); } } else { Fluttertoast.showToast( msg: "Server Response Error", toastLength: Toast.LENGTH_SHORT, gravity: ToastGravity.CENTER, timeInSecForIosWeb: 1, textColor: Colors.white, fontSize: 16.0); await loading.hide(); } setState(() {}); }); } else if (imagePicked == null) { //_imagePath = await getImageFileFromAssets('assets/images/ic_administrator.png') as File?; } }, ) ], ), InkWell( child: roundedRectButton( "Personal Information", Gradients1, 20, 5), onTap: () async { setState(() { visiblePersonalInformation = !visiblePersonalInformation; }); }, ), Visibility( visible: visiblePersonalInformation, child: Container( margin: EdgeInsets.only(top: 10), width: size.width - 50, padding: EdgeInsets.all(15), decoration: BoxDecoration( color: Color(0xFFD9D9D9), borderRadius: BorderRadius.all(Radius.circular(5))), child: Column( children: [ Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Padding( padding: EdgeInsets.only(bottom: 5), child: Text( "Name", style: TextStyle( fontSize: 16, color: Colors.black87), ), ), Stack( alignment: Alignment.bottomRight, children: [ roundDetail(name, 0, 5, 3), ], ), ], ), Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Padding( padding: EdgeInsets.only(bottom: 5), child: Text( "Status", style: TextStyle( fontSize: 16, color: Colors.black87), ), ), Stack( alignment: Alignment.bottomRight, children: [ roundDetail(statusDetail, 0, 5, 3), ], ), ], ), Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Padding( padding: EdgeInsets.only(bottom: 5), child: Text( "Date of Birth", style: TextStyle( fontSize: 16, color: Colors.black87), ), ), Stack( alignment: Alignment.bottomRight, children: [ roundDetail(dateOfBirth, 0, 5, 3), ], ), ], ), Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Padding( padding: EdgeInsets.only(bottom: 5), child: Text( "Phone Number", style: TextStyle( fontSize: 16, color: Colors.black87), ), ), Stack( alignment: Alignment.bottomRight, children: [ roundDetail(phone, 0, 5, 3), ], ), ], ), Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Padding( padding: EdgeInsets.only(bottom: 5), child: Text( "Email Address", style: TextStyle( fontSize: 16, color: Colors.black87), ), ), Stack( alignment: Alignment.bottomRight, children: [ roundDetail(email, 0, 5, 3), ], ), ], ), Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Padding( padding: EdgeInsets.only(bottom: 5), child: Text( "Address", style: TextStyle( fontSize: 16, color: Colors.black87), ), ), Stack( alignment: Alignment.bottomRight, children: [ roundDetail(address, 0, 5, 3), ], ), ], ), /*InkWell( child: roundedRectButton( "Save Password", Gradients2, 10, 10), onTap: () { if (!validateFormSavePassword(context)) { return; } else if (validateFormSavePassword(context)) { if (newPasswordController.text.toString() == retypeNewPasswordController.text .toString()) { if (passwordController.text.toString() == newPasswordController.text .toString() || passwordController.text.toString() == retypeNewPasswordController.text .toString()) { Fluttertoast.showToast( msg: "Your new password is the same with your old password, please change with new password!", toastLength: Toast.LENGTH_LONG, gravity: ToastGravity.CENTER, timeInSecForIosWeb: 1, textColor: Colors.white, fontSize: 16.0); } else { showAlertDialogSavePassword(context); } } else if (newPasswordController.text .toString() != retypeNewPasswordController.text .toString()) { Fluttertoast.showToast( msg: "Your retype password was entered incorrectly.\nPlease enter it again!", toastLength: Toast.LENGTH_LONG, gravity: ToastGravity.CENTER, timeInSecForIosWeb: 1, textColor: Colors.white, fontSize: 16.0); } } }),*/ ], ), ), ), InkWell( child: roundedRectButton("Edit Password", Gradients1, 10, 5), onTap: () { setState(() { visible = !visible; }); }, ), Visibility( visible: visible, child: Container( margin: EdgeInsets.only(top: 10), width: size.width - 50, padding: EdgeInsets.all(15), decoration: BoxDecoration( color: Color(0xFFD9D9D9), borderRadius: BorderRadius.all(Radius.circular(5))), child: Column( children: [ Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Padding( padding: EdgeInsets.only(bottom: 5), child: Text( "Type your current Password", style: TextStyle( fontSize: 16, color: Colors.black87), ), ), Stack( alignment: Alignment.bottomRight, children: [ InputWidgetCurrentPassword( "Current Password"), ], ), ], ), Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Padding( padding: EdgeInsets.only(bottom: 5), child: Text( "Type your New Password", style: TextStyle( fontSize: 16, color: Colors.black87), ), ), Stack( alignment: Alignment.bottomRight, children: [ InputWidgetNewPassword("New Password"), ], ), ], ), Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Padding( padding: EdgeInsets.only(bottom: 5), child: Text( "Re-type your New Password", style: TextStyle( fontSize: 16, color: Colors.black87), ), ), Stack( alignment: Alignment.bottomRight, children: [ InputWidgetRetypeNewPassword( "Re-type New Password"), ], ), ], ), InkWell( child: roundedRectButton( "Save Password", Gradients2, 10, 10), onTap: () { if (!validateFormSavePassword(context)) { return; } else if (validateFormSavePassword(context)) { if (newPasswordController.text.toString() == retypeNewPasswordController.text .toString()) { if (passwordController.text.toString() == newPasswordController.text .toString() || passwordController.text.toString() == retypeNewPasswordController.text .toString()) { Fluttertoast.showToast( msg: "Your new password is the same with your old password, please change with new password!", toastLength: Toast.LENGTH_LONG, gravity: ToastGravity.CENTER, timeInSecForIosWeb: 1, textColor: Colors.white, fontSize: 16.0); } else { showAlertDialogSavePassword(context); } } else if (newPasswordController.text .toString() != retypeNewPasswordController.text .toString()) { Fluttertoast.showToast( msg: "Your retype password was entered incorrectly.\nPlease enter it again!", toastLength: Toast.LENGTH_LONG, gravity: ToastGravity.CENTER, timeInSecForIosWeb: 1, textColor: Colors.white, fontSize: 16.0); } } }), ], ), ), ), InkWell( child: roundedRectButton("Logout", Gradients1, 10, 5), onTap: () { showAlertDialogLogout(context); }), ], ), ), ), ], ), )); } } Widget roundedRectButton(String title, List gradient, double marginTop, double radiusCircular) { return Builder(builder: (BuildContext mContext) { return Align( alignment: Alignment.center, child: Stack( children: [ Container( margin: EdgeInsets.only(top: marginTop), alignment: Alignment.center, height: 55, width: MediaQuery.of(mContext).size.width - 50, decoration: ShapeDecoration( shape: RoundedRectangleBorder( borderRadius: BorderRadius.all(Radius.circular(radiusCircular))), gradient: LinearGradient( colors: gradient, begin: Alignment.topLeft, end: Alignment.bottomRight), ), child: Padding( padding: EdgeInsets.fromLTRB(10, 3, 10, 3), child: Text(title, style: TextStyle( color: Colors.white, fontSize: 19, fontWeight: FontWeight.w500)), ), padding: EdgeInsets.all(10), ) ], ), ); }); } Widget roundDetail(String detail, double marginTop, double marginBottom, double radiusCircular) { return Builder(builder: (BuildContext mContext) { return Align( alignment: Alignment.center, child: Stack( children: [ Container( margin: EdgeInsets.only(top: marginTop, bottom: marginBottom), alignment: Alignment.centerLeft, height: 45, width: MediaQuery.of(mContext).size.width - 50, decoration: ShapeDecoration( shape: RoundedRectangleBorder( borderRadius: BorderRadius.all(Radius.circular(radiusCircular))), color: Colors.blueGrey, ), child: Padding( padding: EdgeInsets.fromLTRB(3, 3, 3, 3), child: Text(detail, style: TextStyle( color: Colors.white, fontSize: 15, fontWeight: FontWeight.w500)), ), padding: EdgeInsets.all(10), ) ], ), ); }); } const List Gradients1 = [ //Color(0xFF0EDED2), Color(0xFF03A0FE), Color(0xFF4858A7), //Color(0xFF6474C6), ]; const List Gradients2 = [ Color(0xFFFF9945), Color(0xFFFc6076), ]; //Save New Password showAlertDialogSavePassword(BuildContext context) { // set up the button Widget okButton = TextButton( child: Text("Yes"), onPressed: () async { ProgressDialog loading = ProgressDialog(context); loading = ProgressDialog(context, type: ProgressDialogType.normal, isDismissible: false, showLogs: true); loading.style( message: 'Please Wait .....', borderRadius: 5, backgroundColor: Colors.white, progressWidget: CircularProgressIndicator(), elevation: 10.0, padding: EdgeInsets.all(10), insetAnimCurve: Curves.easeInOut, progress: 0.0, maxProgress: 100.0, progressTextStyle: TextStyle( color: Colors.black, fontSize: 10.0, fontWeight: FontWeight.w400), messageTextStyle: TextStyle( color: Colors.black, fontSize: 15.0, fontWeight: FontWeight.w600)); await loading.show(); ChangePassword_Post.connectToAPI(passwordController.text.toString(), newPasswordController.text.toString()).then((valueResult) async { Map object = json.decode(valueResult); if (object.containsKey("result").toString() == "true") { String status = object['result']['status'].toString(); String message = object['result']['message'].toString(); if (status == "success") { Fluttertoast.showToast( msg: message + ", Please login again!", toastLength: Toast.LENGTH_SHORT, gravity: ToastGravity.CENTER, timeInSecForIosWeb: 1, textColor: Colors.white, fontSize: 16.0); passwordController.clear(); newPasswordController.clear(); retypeNewPasswordController.clear(); final SharedPreferences prefs = await SharedPreferences.getInstance(); await prefs.remove('session'); await loading.hide(); Navigator.of(context, rootNavigator: true).pop(); Navigator.pushReplacement(context, MaterialPageRoute(builder: (context) => LoginView())); } else if (status == "failed") { Fluttertoast.showToast( msg: message, toastLength: Toast.LENGTH_SHORT, gravity: ToastGravity.CENTER, timeInSecForIosWeb: 1, textColor: Colors.white, fontSize: 16.0); Navigator.of(context, rootNavigator: true).pop(); await loading.hide(); } } else { Fluttertoast.showToast( msg: "Server Response Error", toastLength: Toast.LENGTH_SHORT, gravity: ToastGravity.CENTER, timeInSecForIosWeb: 1, textColor: Colors.white, fontSize: 16.0); await loading.hide(); } }); }, ); Widget noButton = TextButton( child: Text("No"), onPressed: () { Navigator.of(context, rootNavigator: true).pop(); }, ); // set up the AlertDialog AlertDialog alert = AlertDialog( title: Text("Employee Self Service"), content: Text("Save New Password?"), actions: [noButton, okButton], ); // show the dialog showDialog( context: context, barrierDismissible: false, builder: (BuildContext context) { return alert; }, ); } //Validate Form Change Password bool validateFormSavePassword(BuildContext context) { bool result = true; if (passwordController.text.toString().isEmpty) { Fluttertoast.showToast( msg: "Old Password Required!", toastLength: Toast.LENGTH_SHORT, gravity: ToastGravity.CENTER, timeInSecForIosWeb: 1, textColor: Colors.white, fontSize: 16.0); result = false; } if (newPasswordController.text.toString().isEmpty) { Fluttertoast.showToast( msg: "New Password Required", toastLength: Toast.LENGTH_SHORT, gravity: ToastGravity.CENTER, timeInSecForIosWeb: 1, textColor: Colors.white, fontSize: 16.0); result = false; } if (retypeNewPasswordController.text.toString().isEmpty) { Fluttertoast.showToast( msg: "Retype New Password Required", toastLength: Toast.LENGTH_SHORT, gravity: ToastGravity.CENTER, timeInSecForIosWeb: 1, textColor: Colors.white, fontSize: 16.0); result = false; } return result; } //LOGOUT showAlertDialogLogout(BuildContext context) { // set up the button Widget okButton = TextButton( child: Text("Yes"), onPressed: () async { ProgressDialog loading = ProgressDialog(context); loading = ProgressDialog(context, type: ProgressDialogType.normal, isDismissible: false, showLogs: true); loading.style( message: 'Please Wait .....', borderRadius: 5, backgroundColor: Colors.white, progressWidget: CircularProgressIndicator(), elevation: 10.0, padding: EdgeInsets.all(10), insetAnimCurve: Curves.easeInOut, progress: 0.0, maxProgress: 100.0, progressTextStyle: TextStyle( color: Colors.black, fontSize: 10.0, fontWeight: FontWeight.w400), messageTextStyle: TextStyle( color: Colors.black, fontSize: 15.0, fontWeight: FontWeight.w600)); await loading.show(); Logout_Post.connectToAPI().then((valueResult) async { Map object = json.decode(valueResult); if (object.containsKey("result").toString() == "true") { String status = object['result']['status'].toString(); if (status == "success") { final SharedPreferences prefs = await SharedPreferences.getInstance(); await prefs.remove('session'); Navigator.of(context, rootNavigator: true).pop(); await loading.hide(); Navigator.pushAndRemoveUntil(context, MaterialPageRoute(builder: (context) => LoginView()), (route) => false); } else if (status == "failed") { String message = object['result']['message'].toString(); Fluttertoast.showToast( msg: message, toastLength: Toast.LENGTH_SHORT, gravity: ToastGravity.CENTER, timeInSecForIosWeb: 1, textColor: Colors.white, fontSize: 16.0); await loading.hide(); Navigator.of(context, rootNavigator: true).pop(); } } else { Fluttertoast.showToast( msg: "Server Response Error", toastLength: Toast.LENGTH_SHORT, gravity: ToastGravity.CENTER, timeInSecForIosWeb: 1, textColor: Colors.white, fontSize: 16.0); await loading.hide(); Navigator.of(context, rootNavigator: true).pop(); } }); }, ); Widget noButton = TextButton( child: Text("No"), onPressed: () { Navigator.of(context, rootNavigator: true).pop(); }, ); // set up the AlertDialog AlertDialog alert = AlertDialog( title: Text("Employee Self Service"), content: Text("Are you sure you want to logout from this Application?"), //content: Text("Apakah Anda yakin ingin keluar dari aplikasi ini?"), actions: [noButton, okButton], ); // show the dialog showDialog( context: context, barrierDismissible: false, builder: (BuildContext context) { return alert; }, ); } //Alert Change Profile showAlertDialogChangeProfile(BuildContext context, String message, String buttonMessage) { // set up the button Widget okButton = TextButton( child: Text(buttonMessage), onPressed: () { Navigator.of(context, rootNavigator: true).pop(); }, ); // set up the AlertDialog AlertDialog alert = AlertDialog( title: Text("Employee Self Service"), content: Text(message), actions: [okButton], ); // show the dialog showDialog( context: context, barrierDismissible: false, builder: (BuildContext context) { return alert; }, ); }