diff --git a/lib/api/api_client.dart b/lib/api/api_client.dart index a06a64e..119cec3 100644 --- a/lib/api/api_client.dart +++ b/lib/api/api_client.dart @@ -2,10 +2,10 @@ import 'dart:async'; import 'dart:convert'; import 'dart:io'; -import 'package:car_provider_app/exceptions/api_exception.dart'; import 'package:flutter/foundation.dart'; import 'package:http/http.dart'; import 'package:http/io_client.dart'; +import 'package:car_provider_app/exceptions/api_exception.dart'; typedef FactoryConstructor = U Function(dynamic); @@ -67,10 +67,13 @@ class ApiClient { } var response = await postJsonForResponse(url, jsonObject, token: token, queryParameters: queryParameters, headers: _headers, retryTimes: retryTimes); try { + if (!kReleaseMode) { + print("res:"+response.body); + } var jsonData = jsonDecode(response.body); return factoryConstructor(jsonData); } catch (ex) { - print(ex); + print("exception:"+response.body); throw APIException(APIException.BAD_RESPONSE_FORMAT, arguments: ex); } } @@ -104,9 +107,14 @@ class ApiClient { var queryString = new Uri(queryParameters: queryParameters).query; url = url + '?' + queryString; } - var response = await _post(Uri.parse(url), body: requestBody, headers: _headers).timeout(Duration(seconds: 60)); + var response = await _post(Uri.parse(url), body: requestBody, headers: _headers).timeout(Duration(seconds: 15)); + if (!kReleaseMode) { - if (response.statusCode >= 200 && response.statusCode < 300) { + print("Url:$url"); + print("body:$requestBody"); + print("res: "+ response.body); + } + if (response.statusCode >= 200 && response.statusCode < 500) { return response; } else { throw _throwAPIException(response); diff --git a/lib/api/shared_prefrence.dart b/lib/api/shared_prefrence.dart new file mode 100644 index 0000000..cad63d1 --- /dev/null +++ b/lib/api/shared_prefrence.dart @@ -0,0 +1,34 @@ +import 'dart:async'; + +import 'package:shared_preferences/shared_preferences.dart'; + +class SharedPrefManager { + static String USER_ID = "user.id"; + static String USER_TOKEN = "user.token"; + + + + static Future _prefs = SharedPreferences.getInstance(); + + static setUserId(String cookie) async { + final prefs = await SharedPreferences.getInstance(); + prefs.setString(USER_ID, cookie) ?? "NA"; + } + + static Future getUserId() async { + SharedPreferences prefs = await _prefs; + return prefs.getString(USER_ID) ?? ""; + } + + static setUserToken(String cookie) async { + final prefs = await SharedPreferences.getInstance(); + prefs.setString(USER_TOKEN, cookie) ?? "NA"; + } + + static Future getUserToken() async { + SharedPreferences prefs = await _prefs; + return prefs.getString(USER_TOKEN) ?? ""; + } + + +} diff --git a/lib/api/tangheem_user_api_client.dart b/lib/api/tangheem_user_api_client.dart index 1b555f0..bfe32a9 100644 --- a/lib/api/tangheem_user_api_client.dart +++ b/lib/api/tangheem_user_api_client.dart @@ -1,3 +1,11 @@ +import 'dart:async'; + +import 'package:car_provider_app/classes/consts.dart'; +import 'package:car_provider_app/models/content_info_model.dart'; +import 'package:car_provider_app/models/member_model.dart'; +import 'package:car_provider_app/models/surah_model.dart'; + +import 'api_client.dart'; class TangheemUserApiClient { static final TangheemUserApiClient _instance = TangheemUserApiClient._internal(); @@ -6,21 +14,21 @@ class TangheemUserApiClient { factory TangheemUserApiClient() => _instance; - // Future getSurahs() async { - // String url = "${ApiConsts.tangheemUsers}AlSuar_Get"; - // var postParams = {}; - // return await ApiClient().postJsonForObject((json) => SurahModel.fromJson(json), url, postParams); - // } - // - // Future getMembers() async { - // String url = "${ApiConsts.tangheemUsers}Committee_Get"; - // var postParams = {}; - // return await ApiClient().postJsonForObject((json) => MemberModel.fromJson(json), url, postParams); - // } - // - // Future getContentInfo(int contentId) async { - // String url = "${ApiConsts.tangheemUsers}ContentInfo_Get"; - // var postParams = {"contentTypeId": contentId}; - // return await ApiClient().postJsonForObject((json) => ContentInfoModel.fromJson(json), url, postParams); - // } + Future getSurahs() async { + String url = "${ApiConsts.baseUrl}AlSuar_Get"; + var postParams = {}; + return await ApiClient().postJsonForObject((json) => SurahModel.fromJson(json), url, postParams); + } + + Future getMembers() async { + String url = "${ApiConsts.baseUrl}Committee_Get"; + var postParams = {}; + return await ApiClient().postJsonForObject((json) => MemberModel.fromJson(json), url, postParams); + } + + Future getContentInfo(int contentId) async { + String url = "${ApiConsts.baseUrl}ContentInfo_Get"; + var postParams = {"contentTypeId": contentId}; + return await ApiClient().postJsonForObject((json) => ContentInfoModel.fromJson(json), url, postParams); + } } diff --git a/lib/api/user_api_client.dart b/lib/api/user_api_client.dart new file mode 100644 index 0000000..cc0cb63 --- /dev/null +++ b/lib/api/user_api_client.dart @@ -0,0 +1,70 @@ +import 'dart:async'; +import 'package:http/http.dart'; +import 'package:car_provider_app/classes/consts.dart'; +import 'package:car_provider_app/models/content_info_model.dart'; +import 'package:car_provider_app/models/member_model.dart'; +import 'package:car_provider_app/models/surah_model.dart'; +import 'package:car_provider_app/models/user/basic_otp.dart'; +import 'package:car_provider_app/models/user/register_user.dart'; + +import 'api_client.dart'; + +class UserApiClent { + static final UserApiClent _instance = UserApiClent._internal(); + + UserApiClent._internal(); + + factory UserApiClent() => _instance; + + Future basicOtp(String phoneNo,{int otpType=1}) async { + var postParams = {"countryID": 1, "userMobileNo": phoneNo, "otpType": otpType, "userRole": 5}; + return await ApiClient().postJsonForObject((json) => BasicOtp.fromJson(json), ApiConsts.BasicOTP, postParams); + } + + Future basicVerify(String phoneNo, String otp, String userToken) async { + var postParams = { + "userMobileNo": phoneNo, + "userOTP": otp, + "userToken": userToken, + }; + return await ApiClient().postJsonForObject((json) => RegisterUser.fromJson(json), ApiConsts.BasicVerify, postParams); + } + + Future basicComplete(String userId, String firstName, String lastName,String email,String password) async { + var postParams = { + "userID": userId, + "firstName": firstName, + "lastName": lastName, + "email": email, + "companyName": "string", + "isEmailVerified": true, + "password": password + }; + return await ApiClient().postJsonForObject((json) => RegisterUser.fromJson(json), ApiConsts.BasicComplete, postParams); + } + + Future login_V1(String phoneNo, String password) async { + var postParams = { + "mobileorEmail": phoneNo, + "password": password, + }; + return await ApiClient().postJsonForResponse(ApiConsts.Login_V1, postParams); + //return await ApiClient().postJsonForObject((json) => BasicOtp.fromJson(json), ApiConsts.Login_V1, postParams); + } + + Future login_V2_OTP(String userToken, String loginType) async { + var postParams = { + "userToken": userToken, + "loginType": loginType, + }; + return await ApiClient().postJsonForResponse(ApiConsts.Login_V2_OTP, postParams); + } + + Future login_V2_OTPVerify(String userToken, String otp) async { + var postParams = { + "userToken": userToken, + "userOTP": otp + }; + return await ApiClient().postJsonForResponse(ApiConsts.Login_V2_OTPVerify, postParams); + } +} diff --git a/lib/classes/colors.dart b/lib/classes/colors.dart new file mode 100644 index 0000000..276dc8e --- /dev/null +++ b/lib/classes/colors.dart @@ -0,0 +1,31 @@ +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; + +class MyColors { + static const Color primaryColor = Colors.white; + static const Color accentColor = Colors.blue; + static const Color darkIconColor = Color(0xff28323A); + static const Color darkTextColor = Color(0xff2B353E); + static const Color normalTextColor = Color(0xff5A5A5A); + static const Color lightTextColor = Color(0xffBFBFBF); + static const Color gradiantStartColor = Color(0xff33c0a5); + static const Color gradiantEndColor = Color(0xff259db7 ); + static const Color textMixColor = Color(0xff2BB8A6); + static const Color backgroundColor = Color(0xffF8F8F8); + static const Color grey57Color = Color(0xff575757); + static const Color grey77Color = Color(0xff777777); + static const Color grey70Color = Color(0xff707070); + static const Color greyACColor = Color(0xffACACAC); + static const Color grey98Color = Color(0xff989898); + static const Color lightGreyEFColor = Color(0xffEFEFEF); + static const Color lightGreyEDColor = Color(0xffEDEDED); + static const Color lightGreyEAColor = Color(0xffEAEAEA); + static const Color darkWhiteColor = Color(0xffE0E0E0); + static const Color redColor = Color(0xffD02127); + static const Color yellowColor = Color(0xffF4E31C); + static const Color backgroundBlackColor = Color(0xff202529); + static const Color black = Color(0xff000000); + static const Color white = Color(0xffffffff); + static const Color green = Color(0xffffffff); + static const Color borderColor = Color(0xffE8E8E8); +} diff --git a/lib/classes/consts.dart b/lib/classes/consts.dart new file mode 100644 index 0000000..046538a --- /dev/null +++ b/lib/classes/consts.dart @@ -0,0 +1,25 @@ +class ApiConsts { + //static String baseUrl = "http://10.200.204.20:2801/"; // Local server + static String baseUrl = "https://mdlaboratories.com"; // production server + static String baseUrlServices = baseUrl + "/mc/"; // production server + // static String baseUrlServices = "https://api.cssynapses.com/tangheem/"; // Live server + static String BasicOTP = baseUrlServices + "api/Register/BasicOTP"; + static String BasicVerify = baseUrlServices + "api/Register/BasicVerify"; + static String BasicComplete = baseUrlServices + "api/Register/BasicComplete"; + + static String Login_V1 = baseUrlServices + "api/Account/Login_V1"; + static String Login_V2_OTP = baseUrlServices + "api/Account/Login_V2_OTP"; + static String Login_V2_OTPVerify = baseUrlServices + "api/Account/Login_V2_OTPVerify"; + static String user = baseUrlServices + "api/User/"; +} + + +class GlobalConsts { + static String isRememberMe = "remember_me"; + static String email = "email"; + static String password = "password"; + static String bookmark = "bookmark"; + static String fontZoomSize = "font_zoom_size"; + static String welcomeVideoUrl = "welcomeVideoUrl"; + static String doNotShowWelcomeVideo = "doNotShowWelcomeVideo"; +} diff --git a/lib/classes/utils.dart b/lib/classes/utils.dart new file mode 100644 index 0000000..bef8218 --- /dev/null +++ b/lib/classes/utils.dart @@ -0,0 +1,71 @@ +import 'package:car_provider_app/widgets/loading_dialog.dart'; +import 'package:flutter/material.dart'; + +// import 'package:fluttertoast/fluttertoast.dart'; +import 'package:car_provider_app/exceptions/api_exception.dart'; +import 'package:fluttertoast/fluttertoast.dart'; +class Utils { + static bool _isLoadingVisible = false; + + static bool get isLoading => _isLoadingVisible; + + static void showToast(String message) { + Fluttertoast.showToast( + msg: message, toastLength: Toast.LENGTH_SHORT, gravity: ToastGravity.BOTTOM, timeInSecForIosWeb: 1, backgroundColor: Colors.black54, textColor: Colors.white, fontSize: 16.0); + } + + static dynamic getNotNullValue(List list, int index) { + try { + return list[index]; + } catch (ex) { + return null; + } + } + + static int stringToHex(String colorCode) { + try { + return int.parse(colorCode.replaceAll("#", "0xff")); + } catch (ex) { + return (0xff000000); + } + } + + static void showLoading(BuildContext context) { + WidgetsBinding.instance?.addPostFrameCallback((_) { + _isLoadingVisible = true; + showDialog( + context: context, + barrierColor: Colors.black.withOpacity(0.5), + builder: (BuildContext context) => LoadingDialog(), + ).then((value) { + _isLoadingVisible = false; + }); + }); + } + + static void hideLoading(BuildContext context) { + if (_isLoadingVisible) { + _isLoadingVisible = false; + Navigator.of(context).pop(); + } + _isLoadingVisible = false; + } + + static void handleException(dynamic exception, Function(String)? onErrorMessage) { + String errorMessage; + if (exception is APIException) { + if (exception.message == APIException.UNAUTHORIZED) { + return; + } else { + errorMessage = exception.error?.errorMessage ?? exception.message; + } + } else { + errorMessage = APIException.UNKNOWN; + } + if (onErrorMessage != null) { + onErrorMessage(errorMessage); + } else { + showToast(errorMessage); + } + } +} diff --git a/lib/config/routes.dart b/lib/config/routes.dart index ec83383..b67e3b2 100644 --- a/lib/config/routes.dart +++ b/lib/config/routes.dart @@ -1,8 +1,11 @@ +import 'package:car_provider_app/models/user/register_user.dart'; import 'package:car_provider_app/pages/dashboard/dashboard_page.dart'; import 'package:car_provider_app/pages/user/complete_profile_page.dart'; import 'package:car_provider_app/pages/user/forget_password_page.dart'; +import 'package:car_provider_app/pages/user/login_method_selection_page.dart'; import 'package:car_provider_app/pages/user/login_verification_page.dart'; import 'package:car_provider_app/pages/user/login_verify_account_page.dart'; +import 'package:car_provider_app/pages/user/login_with_password_page.dart'; import 'package:car_provider_app/pages/user/profile/profile_1_page.dart'; import 'package:car_provider_app/pages/user/profile/profile_2_page.dart'; import 'package:car_provider_app/pages/user/profile/profile_3_page.dart'; @@ -19,13 +22,14 @@ class AppRoutes { static final String register = "/register"; static final String forgetPassword = "/forgetPassword"; static final String loginVerification = "/loginVerification"; - static final String dashboard = "/dashboard"; + static final String loginWithPassword = "/loginWithPassword"; + static final String loginMethodSelection = "/loginMethodSelection"; static final String completeProfile = "/completeProfile"; static final String profile1 = "/profile1"; static final String profile2 = "/profile2"; static final String profile3 = "/profile3"; - + static final String dashboard = "/dashboard"; static final String initialRoute = splash; @@ -37,10 +41,14 @@ class AppRoutes { register: (context) => RegisterPage(), forgetPassword: (context) => ForgetPasswordPage(), loginVerification: (context) => LoginVerificationPage(), - dashboard: (context) => DashboardPage(), + loginWithPassword: (context) => LoginWithPassword(), + loginMethodSelection: (context) => LoginMethodSelectionPage(ModalRoute.of(context)!.settings.arguments as String), completeProfile: (context) => CompleteProfilePage(), profile1: (context) => Profile1Page(), profile2: (context) => Profile2Page(), profile3: (context) => Profile3Page(), + + //Home page + dashboard: (context) => DashboardPage(), }; } diff --git a/lib/extensions/int_extensions.dart b/lib/extensions/int_extensions.dart new file mode 100644 index 0000000..9b90b2f --- /dev/null +++ b/lib/extensions/int_extensions.dart @@ -0,0 +1,7 @@ +import 'package:flutter/cupertino.dart'; + +extension IntExtensions on int { + Widget get height => SizedBox(height: toDouble()); + + Widget get width => SizedBox(width: toDouble()); +} diff --git a/lib/extensions/string_extensions.dart b/lib/extensions/string_extensions.dart new file mode 100644 index 0000000..5cac7c2 --- /dev/null +++ b/lib/extensions/string_extensions.dart @@ -0,0 +1,114 @@ +import 'package:flutter/cupertino.dart'; +import 'package:intl/intl.dart'; +import 'package:car_provider_app/classes/colors.dart'; + +extension EmailValidator on String { + Widget get toWidget => Text(this); + + Widget toText({Color? color, bool isBold = false,double? fontSize}) => Text( + this, + style: TextStyle(fontSize: fontSize??10, fontWeight: isBold ? FontWeight.bold : FontWeight.w600, color: color ?? MyColors.darkTextColor, letterSpacing: -0.4), + ); + + Widget toText10({Color? color, bool isBold = false}) => Text( + this, + style: TextStyle(fontSize: 10, fontWeight: isBold ? FontWeight.bold : FontWeight.w600, color: color ?? MyColors.darkTextColor, letterSpacing: -0.4), + ); + + Widget toText11({Color? color, bool isUnderLine = false, bool isBold = false}) => Text( + this, + style: TextStyle( + fontSize: 11, + fontWeight: isBold ? FontWeight.bold : FontWeight.w600, + color: color ?? MyColors.darkTextColor, + letterSpacing: -0.33, + decoration: isUnderLine ? TextDecoration.underline : null), + ); + + Widget toText12({Color? color, bool isUnderLine = false, bool isBold = false, bool isCenter = false, int maxLine = 0}) => Text( + this, + textAlign: isCenter ? TextAlign.center : null, + maxLines: (maxLine > 0) ? maxLine : null, + style: TextStyle( + fontSize: 12, + fontWeight: isBold ? FontWeight.bold : FontWeight.w600, + color: color ?? MyColors.darkTextColor, + letterSpacing: -0.72, + decoration: isUnderLine ? TextDecoration.underline : null), + ); + + Widget toText13({Color? color, bool isUnderLine = false}) => Text( + this, + style: TextStyle(fontSize: 13, fontWeight: FontWeight.w600, color: color ?? MyColors.darkTextColor, letterSpacing: -0.52, decoration: isUnderLine ? TextDecoration.underline : null), + ); + + Widget toText14({Color? color, bool isBold = false}) => Text( + this, + style: TextStyle(color: color ?? MyColors.darkTextColor, fontSize: 14, letterSpacing: -0.48, fontWeight: isBold ? FontWeight.bold : FontWeight.w600), + ); + + Widget toText16({Color? color, bool isBold = false}) => Text( + this, + style: TextStyle(color: color ?? MyColors.darkTextColor, fontSize: 16, letterSpacing: -0.64, fontWeight: isBold ? FontWeight.bold : FontWeight.w600), + ); + + Widget toText17({Color? color, bool isBold = false}) => Text( + this, + style: TextStyle(color: color ?? MyColors.darkTextColor, fontSize: 17, letterSpacing: -0.68, fontWeight: isBold ? FontWeight.bold : FontWeight.w600), + ); + + Widget toText22({Color? color, bool isBold = false}) => Text( + this, + style: TextStyle(height: 1, color: color ?? MyColors.darkTextColor, fontSize: 22, letterSpacing: -1.44, fontWeight: isBold ? FontWeight.bold : FontWeight.w600), + ); + + Widget toText24({Color? color, bool isBold = false}) => Text( + this, + style: TextStyle(height: 23 / 24, color: color ?? MyColors.darkTextColor, fontSize: 24, letterSpacing: -1.44, fontWeight: isBold ? FontWeight.bold : FontWeight.w600), + ); + + Widget toText32({Color? color, bool isBold = false}) => Text( + this, + style: TextStyle(height: 32 / 32, color: color ?? MyColors.darkTextColor, fontSize: 32, letterSpacing: -1.92, fontWeight: isBold ? FontWeight.bold : FontWeight.w600), + ); + + bool isValidEmail() { + return RegExp(r'^(([^<>()[\]\\.,;:\s@\"]+(\.[^<>()[\]\\.,;:\s@\"]+)*)|(\".+\"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$').hasMatch(this); + } + + String toFormattedDate() { + String date = this.split("T")[0]; + String time = this.split("T")[1]; + var dates = date.split("-"); + return "${dates[2]} ${getMonth(int.parse(dates[1]))} ${dates[0]} ${DateFormat('hh:mm a').format(DateFormat('hh:mm:ss').parse(time))}"; + } + + getMonth(int month) { + switch (month) { + case 1: + return "January"; + case 2: + return "February"; + case 3: + return "March"; + case 4: + return "April"; + case 5: + return "May"; + case 6: + return "June"; + case 7: + return "July"; + case 8: + return "August"; + case 9: + return "September"; + case 10: + return "October"; + case 11: + return "November"; + case 12: + return "December"; + } + } +} diff --git a/lib/extensions/widget_extensions.dart b/lib/extensions/widget_extensions.dart new file mode 100644 index 0000000..787894d --- /dev/null +++ b/lib/extensions/widget_extensions.dart @@ -0,0 +1,11 @@ +import 'package:flutter/material.dart'; +import 'package:flutter/widgets.dart'; + +extension WidgetExtensions on Widget { + Widget onPress(VoidCallback onTap) => InkWell(onTap: onTap, child: this); + + Widget paddingAll(double _value) => Padding(padding: EdgeInsets.all(_value), child: this); + + Widget paddingOnly({double left = 0.0, double right = 0.0, double top = 0.0, double bottom = 0.0}) => + Padding(padding: EdgeInsets.only(left: left, right: right, top: top, bottom: bottom), child: this); +} diff --git a/lib/models/content_info_model.dart b/lib/models/content_info_model.dart new file mode 100644 index 0000000..4eec746 --- /dev/null +++ b/lib/models/content_info_model.dart @@ -0,0 +1,65 @@ +class ContentInfoModel { + int? totalItemsCount; + int? statusCode; + String? message; + List? data; + + ContentInfoModel({this.totalItemsCount, this.statusCode, this.message, this.data}); + + ContentInfoModel.fromJson(Map json) { + totalItemsCount = json['totalItemsCount']; + statusCode = json['statusCode']; + message = json['message']; + if (json['data'] != null) { + data = []; + json['data'].forEach((v) { + data?.add(new ContentInfoDataModel.fromJson(v)); + }); + } + } + + Map toJson() { + final Map data = new Map(); + data['totalItemsCount'] = this.totalItemsCount; + data['statusCode'] = this.statusCode; + data['message'] = this.message; + if (this.data != null) { + data['data'] = this.data?.map((v) => v.toJson()).toList(); + } + return data; + } +} + +class ContentInfoDataModel { + int? contentInfoId; + int? contentTypeId; + String? content; + String? contentTypeNameEn; + String? contentTypeNameAr; + String? fileName; + String? exposeFilePath; + + ContentInfoDataModel({this.contentInfoId, this.contentTypeId, this.content, this.contentTypeNameEn, this.contentTypeNameAr, this.fileName, this.exposeFilePath}); + + ContentInfoDataModel.fromJson(Map json) { + contentInfoId = json['contentInfoId']; + contentTypeId = json['contentTypeId']; + content = json['content']; + contentTypeNameEn = json['contentTypeNameEn']; + contentTypeNameAr = json['contentTypeNameAr']; + fileName = json['fileName']; + exposeFilePath = json['exposeFilePath']; + } + + Map toJson() { + final Map data = new Map(); + data['contentInfoId'] = this.contentInfoId; + data['contentTypeId'] = this.contentTypeId; + data['content'] = this.content; + data['contentTypeNameEn'] = this.contentTypeNameEn; + data['contentTypeNameAr'] = this.contentTypeNameAr; + data['fileName'] = this.fileName; + data['exposeFilePath'] = this.exposeFilePath; + return data; + } +} diff --git a/lib/models/member_model.dart b/lib/models/member_model.dart new file mode 100644 index 0000000..d968827 --- /dev/null +++ b/lib/models/member_model.dart @@ -0,0 +1,62 @@ +class MemberModel { + int? totalItemsCount; + int? statusCode; + String? message; + List? data; + + MemberModel({this.totalItemsCount, this.statusCode, this.message, this.data}); + + MemberModel.fromJson(Map json) { + totalItemsCount = json['totalItemsCount']; + statusCode = json['statusCode']; + message = json['message']; + if (json['data'] != null) { + data = []; + json['data'].forEach((v) { + data?.add(new MemberDataModel.fromJson(v)); + }); + } + } + + Map toJson() { + final Map data = new Map(); + data['totalItemsCount'] = this.totalItemsCount; + data['statusCode'] = this.statusCode; + data['message'] = this.message; + if (this.data != null) { + data['data'] = this.data?.map((v) => v.toJson()).toList(); + } + return data; + } +} + +class MemberDataModel { + int? committeeId; + String? firstName; + String? lastName; + String? description; + String? picture; + int? orderNo; + + MemberDataModel({this.committeeId, this.firstName, this.lastName, this.description, this.picture, this.orderNo}); + + MemberDataModel.fromJson(Map json) { + committeeId = json['committeeId']; + firstName = json['firstName']; + lastName = json['lastName']; + description = json['description']; + picture = json['picture']; + orderNo = json['orderNo']; + } + + Map toJson() { + final Map data = new Map(); + data['committeeId'] = this.committeeId; + data['firstName'] = this.firstName; + data['lastName'] = this.lastName; + data['description'] = this.description; + data['picture'] = this.picture; + data['orderNo'] = this.orderNo; + return data; + } +} diff --git a/lib/models/surah_model.dart b/lib/models/surah_model.dart new file mode 100644 index 0000000..acc171a --- /dev/null +++ b/lib/models/surah_model.dart @@ -0,0 +1,74 @@ +class SurahModel { + int? totalItemsCount; + int? statusCode; + String? message; + List? data; + + SurahModel({this.totalItemsCount, this.statusCode, this.message, this.data}); + + SurahModel.fromJson(Map json) { + totalItemsCount = json['totalItemsCount']; + statusCode = json['statusCode']; + message = json['message']; + if (json['data'] != null) { + data = []; + json['data'].forEach((v) { + data?.add(SurahModelData.fromJson(v)); + }); + } + } + + Map toJson() { + final Map data = new Map(); + data['totalItemsCount'] = totalItemsCount; + data['statusCode'] = statusCode; + data['message'] = message; + if (this.data != null) { + data['data'] = this.data?.map((v) => v.toJson()).toList(); + } + return data; + } +} + +class SurahModelData { + int? id; + int? surahID; + String? nameAR; + String? nameEN; + int? numberOfAyahs; + String? englishNameTranslation; + int? revelationID; + String? revelationType; + int? startPageNo; + int? endPageNo; + + SurahModelData({this.id, this.surahID, this.nameAR, this.nameEN, this.numberOfAyahs, this.englishNameTranslation, this.revelationID, this.revelationType, this.startPageNo, this.endPageNo}); + + SurahModelData.fromJson(Map json) { + id = json['id']; + surahID = json['surahID']; + nameAR = json['nameAR']; + nameEN = json['nameEN']; + numberOfAyahs = json['numberOfAyahs']; + englishNameTranslation = json['englishNameTranslation']; + revelationID = json['revelation_ID']; + revelationType = json['revelationType']; + startPageNo = json['startPageNo']; + endPageNo = json['endPageNo']; + } + + Map toJson() { + final Map data = new Map(); + data['id'] = this.id; + data['surahID'] = this.surahID; + data['nameAR'] = this.nameAR; + data['nameEN'] = this.nameEN; + data['numberOfAyahs'] = this.numberOfAyahs; + data['englishNameTranslation'] = this.englishNameTranslation; + data['revelation_ID'] = this.revelationID; + data['revelationType'] = this.revelationType; + data['startPageNo'] = this.startPageNo; + data['endPageNo'] = this.endPageNo; + return data; + } +} diff --git a/lib/models/user/basic_otp.dart b/lib/models/user/basic_otp.dart new file mode 100644 index 0000000..647a488 --- /dev/null +++ b/lib/models/user/basic_otp.dart @@ -0,0 +1,61 @@ +// To parse this JSON data, do +// +// final basicOtp = basicOtpFromJson(jsonString); + +import 'dart:convert'; + +BasicOtp basicOtpFromJson(String str) => BasicOtp.fromJson(json.decode(str)); + +String basicOtpToJson(BasicOtp data) => json.encode(data.toJson()); + +class BasicOtp { + BasicOtp({ + this.totalItemsCount, + this.data, + this.messageStatus, + this.message, + }); + + dynamic totalItemsCount; + Data? data; + int? messageStatus; + String? message; + + factory BasicOtp.fromJson(Map json) => BasicOtp( + totalItemsCount: json["totalItemsCount"], + data: json["data"] == null ? null : Data.fromJson(json["data"]), + messageStatus: json["messageStatus"] == null ? null : json["messageStatus"], + message: json["message"] == null ? null : json["message"], + ); + + Map toJson() => { + "totalItemsCount": totalItemsCount, + "data": data == null ? null : data!.toJson(), + "messageStatus": messageStatus == null ? null : messageStatus, + "message": message == null ? null : message, + }; +} + +class Data { + Data({ + this.userToken, + }); + + String? userToken; + + factory Data.fromJson(Map json) => Data( + userToken: checkValue(json), + ); + + static String checkValue(Map json) { + try { + return json["userToken"] == null ? null : json["userToken"]; + } catch (e) { + return json["token"] == null ? null : json["token"]; + } + } + + Map toJson() => { + "userToken": userToken == null ? null : userToken, + }; +} diff --git a/lib/models/user/register_user.dart b/lib/models/user/register_user.dart new file mode 100644 index 0000000..fef636f --- /dev/null +++ b/lib/models/user/register_user.dart @@ -0,0 +1,117 @@ +// To parse this JSON data, do +// +// final user = userFromMap(jsonString); + +import 'dart:convert'; + +RegisterUser userFromMap(String str) => RegisterUser.fromJson(json.decode(str)); + +String userToMap(RegisterUser data) => json.encode(data.toMap()); + +class RegisterUser { + RegisterUser({ + this.totalItemsCount, + this.data, + this.messageStatus, + this.message, + }); + + dynamic totalItemsCount; + Data? data; + int? messageStatus; + String? message; + + factory RegisterUser.fromJson(Map json) => RegisterUser( + totalItemsCount: json["totalItemsCount"], + data: json["data"] == null ? null : Data.fromMap(json["data"]), + messageStatus: json["messageStatus"] == null ? null : json["messageStatus"], + message: json["message"] == null ? null : json["message"], + ); + + Map toMap() => { + "totalItemsCount": totalItemsCount, + "data": data == null ? null : data!.toMap(), + "messageStatus": messageStatus == null ? null : messageStatus, + "message": message == null ? null : message, + }; +} + +class Data { + Data({ + this.id, + this.userId, + this.firstName, + this.lastName, + this.mobileNo, + this.email, + this.userImageUrl, + this.roleId, + this.roleName, + this.isEmailVerified, + this.serviceProviderBranch, + this.isVerified, + this.userRoles, + this.isCustomer, + this.isProvider, + this.providerId, + this.customerId, + }); + + int? id; + String? userId; + dynamic? firstName; + dynamic? lastName; + String? mobileNo; + String? email; + dynamic? userImageUrl; + int? roleId; + dynamic? roleName; + bool? isEmailVerified; + List? serviceProviderBranch; + bool? isVerified; + List? userRoles; + bool? isCustomer; + bool? isProvider; + dynamic? providerId; + dynamic? customerId; + + factory Data.fromMap(Map json) => Data( + id: json["id"] == null ? null : json["id"], + userId: json["userID"] == null ? null : json["userID"], + firstName: json["firstName"], + lastName: json["lastName"], + mobileNo: json["mobileNo"] == null ? null : json["mobileNo"], + email: json["email"] == null ? null : json["email"], + userImageUrl: json["userImageUrl"], + roleId: json["roleID"] == null ? null : json["roleID"], + roleName: json["roleName"], + isEmailVerified: json["isEmailVerified"] == null ? null : json["isEmailVerified"], + serviceProviderBranch: json["serviceProviderBranch"] == null ? null : List.from(json["serviceProviderBranch"].map((x) => x)), + isVerified: json["isVerified"] == null ? null : json["isVerified"], + userRoles: json["userRoles"] == null ? null : List.from(json["userRoles"].map((x) => x)), + isCustomer: json["isCustomer"] == null ? null : json["isCustomer"], + isProvider: json["isProvider"] == null ? null : json["isProvider"], + providerId: json["providerID"], + customerId: json["customerID"], + ); + + Map toMap() => { + "id": id == null ? null : id, + "userID": userId == null ? null : userId, + "firstName": firstName, + "lastName": lastName, + "mobileNo": mobileNo == null ? null : mobileNo, + "email": email == null ? null : email, + "userImageUrl": userImageUrl, + "roleID": roleId == null ? null : roleId, + "roleName": roleName, + "isEmailVerified": isEmailVerified == null ? null : isEmailVerified, + "serviceProviderBranch": serviceProviderBranch == null ? null : List.from(serviceProviderBranch!.map((x) => x)), + "isVerified": isVerified == null ? null : isVerified, + "userRoles": userRoles == null ? null : List.from(userRoles!.map((x) => x)), + "isCustomer": isCustomer == null ? null : isCustomer, + "isProvider": isProvider == null ? null : isProvider, + "providerID": providerId, + "customerID": customerId, + }; +} diff --git a/lib/models/user/user.dart b/lib/models/user/user.dart new file mode 100644 index 0000000..aea036b --- /dev/null +++ b/lib/models/user/user.dart @@ -0,0 +1,117 @@ +// To parse this JSON data, do +// +// final user = userFromMap(jsonString); + +import 'dart:convert'; + +User userFromMap(String str) => User.fromMap(json.decode(str)); + +String userToMap(User data) => json.encode(data.toMap()); + +class User { + User({ + this.accessToken, + this.refreshToken, + this.expiryDate, + this.userInfo, + }); + + String? accessToken; + String? refreshToken; + DateTime? expiryDate; + UserInfo? userInfo; + + factory User.fromMap(Map json) => User( + accessToken: json["accessToken"] == null ? null : json["accessToken"], + refreshToken: json["refreshToken"] == null ? null : json["refreshToken"], + expiryDate: json["expiryDate"] == null ? null : DateTime.parse(json["expiryDate"]), + userInfo: json["userInfo"] == null ? null : UserInfo.fromMap(json["userInfo"]), + ); + + Map toMap() => { + "accessToken": accessToken == null ? null : accessToken, + "refreshToken": refreshToken == null ? null : refreshToken, + "expiryDate": expiryDate == null ? null : expiryDate!.toIso8601String(), + "userInfo": userInfo == null ? null : userInfo!.toMap(), + }; +} + +class UserInfo { + UserInfo({ + this.id, + this.userId, + this.firstName, + this.lastName, + this.mobileNo, + this.email, + this.userImageUrl, + this.roleId, + this.roleName, + this.isEmailVerified, + this.serviceProviderBranch, + this.isVerified, + this.userRoles, + this.isCustomer, + this.isProvider, + this.providerId, + this.customerId, + }); + + int? id; + String? userId; + String? firstName; + String? lastName; + String? mobileNo; + String? email; + dynamic? userImageUrl; + int? roleId; + String? roleName; + bool? isEmailVerified; + List? serviceProviderBranch; + bool? isVerified; + List? userRoles; + bool? isCustomer; + bool? isProvider; + dynamic? providerId; + int? customerId; + + factory UserInfo.fromMap(Map json) => UserInfo( + id: json["id"] == null ? null : json["id"], + userId: json["userID"] == null ? null : json["userID"], + firstName: json["firstName"] == null ? null : json["firstName"], + lastName: json["lastName"] == null ? null : json["lastName"], + mobileNo: json["mobileNo"] == null ? null : json["mobileNo"], + email: json["email"] == null ? null : json["email"], + userImageUrl: json["userImageUrl"], + roleId: json["roleID"] == null ? null : json["roleID"], + roleName: json["roleName"] == null ? null : json["roleName"], + isEmailVerified: json["isEmailVerified"] == null ? null : json["isEmailVerified"], + serviceProviderBranch: json["serviceProviderBranch"] == null ? null : List.from(json["serviceProviderBranch"].map((x) => x)), + isVerified: json["isVerified"] == null ? null : json["isVerified"], + userRoles: json["userRoles"] == null ? null : List.from(json["userRoles"].map((x) => x)), + isCustomer: json["isCustomer"] == null ? null : json["isCustomer"], + isProvider: json["isProvider"] == null ? null : json["isProvider"], + providerId: json["providerID"], + customerId: json["customerID"] == null ? null : json["customerID"], + ); + + Map toMap() => { + "id": id == null ? null : id, + "userID": userId == null ? null : userId, + "firstName": firstName == null ? null : firstName, + "lastName": lastName == null ? null : lastName, + "mobileNo": mobileNo == null ? null : mobileNo, + "email": email == null ? null : email, + "userImageUrl": userImageUrl, + "roleID": roleId == null ? null : roleId, + "roleName": roleName == null ? null : roleName, + "isEmailVerified": isEmailVerified == null ? null : isEmailVerified, + "serviceProviderBranch": serviceProviderBranch == null ? null : List.from(serviceProviderBranch!.map((x) => x)), + "isVerified": isVerified == null ? null : isVerified, + "userRoles": userRoles == null ? null : List.from(userRoles!.map((x) => x)), + "isCustomer": isCustomer == null ? null : isCustomer, + "isProvider": isProvider == null ? null : isProvider, + "providerID": providerId, + "customerID": customerId == null ? null : customerId, + }; +} diff --git a/lib/pages/user/forget_password_page.dart b/lib/pages/user/forget_password_page.dart index 000b5be..b562656 100644 --- a/lib/pages/user/forget_password_page.dart +++ b/lib/pages/user/forget_password_page.dart @@ -1,9 +1,11 @@ +import 'package:car_provider_app/utils/utils.dart'; import 'package:car_provider_app/widgets/app_bar.dart'; import 'package:car_provider_app/widgets/dialog/dialogs.dart'; import 'package:car_provider_app/widgets/dialog/message_dialog.dart'; -import 'package:car_provider_app/widgets/extensions/int_extensions.dart'; -import 'package:car_provider_app/widgets/extensions/string_extensions.dart'; import 'package:car_provider_app/widgets/show_fill_button.dart'; +import 'package:car_provider_app/extensions/string_extensions.dart'; +import 'package:car_provider_app/extensions/int_extensions.dart'; +import 'package:car_provider_app/extensions/widget_extensions.dart'; import 'package:car_provider_app/widgets/txt_field.dart'; import 'package:flutter/material.dart'; @@ -18,7 +20,7 @@ class ForgetPasswordPage extends StatelessWidget { padding: EdgeInsets.all(40), child: Column( children: [ - "Retrieve Password".toText16(), + "Retrieve Password".toText24(), 12.height, TxtField( hint: "Phone Number", diff --git a/lib/pages/user/login_method_selection_page.dart b/lib/pages/user/login_method_selection_page.dart new file mode 100644 index 0000000..9825bc6 --- /dev/null +++ b/lib/pages/user/login_method_selection_page.dart @@ -0,0 +1,122 @@ +import 'dart:convert'; + +import 'package:car_provider_app/api/shared_prefrence.dart'; +import 'package:car_provider_app/api/user_api_client.dart'; +import 'package:car_provider_app/classes/utils.dart'; +import 'package:car_provider_app/config/constants.dart'; +import 'package:car_provider_app/config/routes.dart'; +import 'package:car_provider_app/models/user/user.dart'; +import 'package:car_provider_app/utils/navigator.dart'; +import 'package:car_provider_app/utils/utils.dart'; +import 'package:car_provider_app/widgets/app_bar.dart'; +import 'package:car_provider_app/widgets/button/show_image_button.dart'; +import 'package:car_provider_app/widgets/dialog/dialogs.dart'; +import 'package:car_provider_app/widgets/dialog/message_dialog.dart'; +import 'package:car_provider_app/extensions/int_extensions.dart'; +import 'package:car_provider_app/extensions/string_extensions.dart'; +import 'package:car_provider_app/extensions/widget_extensions.dart'; +import 'package:car_provider_app/widgets/dialog/otp_dialog.dart'; +import 'package:car_provider_app/widgets/txt_field.dart'; +import 'package:flutter/material.dart'; +import 'package:http/http.dart'; + +class LoginMethodSelectionPage extends StatelessWidget { + String userToken; + + LoginMethodSelectionPage(this.userToken); + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: appBar(title: "Log In"), + body: Container( + width: double.infinity, + height: double.infinity, + padding: EdgeInsets.all(40), + child: Column( + children: [ + "Login Selection".toText24(), + mFlex(2), + Row( + children: [ + Expanded( + child: ShowImageButton( + onClick: () { + performBasicOtp(context); + }, + title: 'Finger Print', + icon: icons + "ic_fingerprint.png", + ), + ), + 20.width, + Expanded( + child: ShowImageButton( + onClick: () { + performBasicOtp(context); + }, + title: 'Face Recognition', + icon: icons + "ic_face_id.png", + ), + ), + ], + ), + 40.height, + Row( + children: [ + Expanded( + child: ShowImageButton( + onClick: () { + performBasicOtp(context); + }, + title: 'With SMS', + icon: icons + "ic_sms.png", + ), + ), + 20.width, + Expanded( + child: ShowImageButton( + onClick: () { + // navigateWithName(context, AppRoutes.dashboard); + performBasicOtp(context); + }, + title: 'With Whatsapp', + icon: icons + "ic_whatsapp.png", + ), + ), + ], + ), + mFlex(10), + ], + ), + ), + ); + } + + Future performBasicOtp(BuildContext context) async { + Utils.showLoading(context); + Response response = await UserApiClent().login_V2_OTP(userToken, "1"); + Utils.hideLoading(context); + if (response.statusCode == 200) { + String userToken = jsonDecode(response.body)["token"]; + showMDialog(context, child: OtpDialog( + onClick: (String code) async { + pop(context); + Utils.showLoading(context); + Response response2 = await UserApiClent().login_V2_OTPVerify(userToken, code); + Utils.hideLoading(context); + if (response2.statusCode == 200) { + User user = User.fromMap(jsonDecode(response2.body)); + SharedPrefManager.setUserToken(user.accessToken ?? ""); + SharedPrefManager.setUserId(user.userInfo!.userId ?? ""); + navigateWithName(context, AppRoutes.dashboard); + } else { + Utils.showToast("Something went wrong"); + } + }, + )); + } else { + String res = jsonDecode(response.body)["errors"][0] ?? ""; + Utils.showToast(res); + } + } +} diff --git a/lib/pages/user/login_verification_page.dart b/lib/pages/user/login_verification_page.dart index 729d6b9..ad0a815 100644 --- a/lib/pages/user/login_verification_page.dart +++ b/lib/pages/user/login_verification_page.dart @@ -1,14 +1,24 @@ +import 'dart:convert'; + +import 'package:car_provider_app/api/shared_prefrence.dart'; +import 'package:car_provider_app/api/user_api_client.dart'; +import 'package:car_provider_app/classes/utils.dart'; import 'package:car_provider_app/config/constants.dart'; import 'package:car_provider_app/config/routes.dart'; +import 'package:car_provider_app/models/user/user.dart'; import 'package:car_provider_app/utils/navigator.dart'; import 'package:car_provider_app/utils/utils.dart'; import 'package:car_provider_app/widgets/app_bar.dart'; import 'package:car_provider_app/widgets/button/show_image_button.dart'; import 'package:car_provider_app/widgets/dialog/dialogs.dart'; import 'package:car_provider_app/widgets/dialog/message_dialog.dart'; -import 'package:car_provider_app/widgets/extensions/int_extensions.dart'; -import 'package:car_provider_app/widgets/extensions/string_extensions.dart'; +import 'package:car_provider_app/extensions/int_extensions.dart'; +import 'package:car_provider_app/extensions/string_extensions.dart'; +import 'package:car_provider_app/extensions/widget_extensions.dart'; +import 'package:car_provider_app/widgets/dialog/otp_dialog.dart'; +import 'package:car_provider_app/widgets/txt_field.dart'; import 'package:flutter/material.dart'; +import 'package:http/http.dart'; class LoginVerificationPage extends StatelessWidget { @override @@ -21,33 +31,25 @@ class LoginVerificationPage extends StatelessWidget { padding: EdgeInsets.all(40), child: Column( children: [ - "Verify Account".toText16(), + "Verify Account".toText24(), + mFlex(2), Row( children: [ Expanded( child: ShowImageButton( onClick: () { - showMDialog( - context, - child: MessageDialog( - title: "Account Verified", - onClick: () { - pop(context); - navigateWithName(context, AppRoutes.dashboard); - }, - ), - ); + performBasicOtp(context); }, title: 'Finger Print', icon: icons + "ic_fingerprint.png", ), ), - 20.height, + 20.width, Expanded( child: ShowImageButton( onClick: () { - navigateWithName(context, AppRoutes.dashboard); + performBasicOtp(context); }, title: 'Face Recognition', icon: icons + "ic_face_id.png", @@ -55,23 +57,23 @@ class LoginVerificationPage extends StatelessWidget { ), ], ), - 40.height, + 40.height, Row( children: [ Expanded( child: ShowImageButton( onClick: () { - navigateWithName(context, AppRoutes.dashboard); + performBasicOtp(context); }, title: 'With SMS', icon: icons + "ic_sms.png", ), ), - 20.height, + 20.width, Expanded( child: ShowImageButton( onClick: () { - navigateWithName(context, AppRoutes.dashboard); + performBasicOtp(context); }, title: 'With Whatsapp', icon: icons + "ic_whatsapp.png", @@ -85,4 +87,33 @@ class LoginVerificationPage extends StatelessWidget { ), ); } + + Future performBasicOtp(BuildContext context) async { + Utils.showLoading(context); + String userToken=await SharedPrefManager.getUserToken(); + Response response = await UserApiClent().login_V2_OTP(userToken, "1"); + Utils.hideLoading(context); + if (response.statusCode == 200) { + String userToken = jsonDecode(response.body)["token"]; + showMDialog(context, child: OtpDialog( + onClick: (String code) async { + pop(context); + Utils.showLoading(context); + Response response2 = await UserApiClent().login_V2_OTPVerify(userToken, code); + Utils.hideLoading(context); + if (response2.statusCode == 200) { + User user = User.fromMap(jsonDecode(response2.body)); + SharedPrefManager.setUserToken(user.accessToken ?? ""); + SharedPrefManager.setUserId(user.userInfo!.userId ?? ""); + navigateWithName(context, AppRoutes.dashboard); + } else { + Utils.showToast("Something went wrong"); + } + }, + )); + } else { + String res = jsonDecode(response.body)["errors"][0] ?? ""; + Utils.showToast(res); + } + } } diff --git a/lib/pages/user/login_verify_account_page.dart b/lib/pages/user/login_verify_account_page.dart index 9df717f..5869bc9 100644 --- a/lib/pages/user/login_verify_account_page.dart +++ b/lib/pages/user/login_verify_account_page.dart @@ -1,5 +1,9 @@ +import 'package:car_provider_app/api/user_api_client.dart'; +import 'package:car_provider_app/classes/utils.dart'; import 'package:car_provider_app/config/constants.dart'; import 'package:car_provider_app/config/routes.dart'; +import 'package:car_provider_app/models/user/basic_otp.dart'; +import 'package:car_provider_app/models/user/register_user.dart'; import 'package:car_provider_app/utils/navigator.dart'; import 'package:car_provider_app/utils/utils.dart'; import 'package:car_provider_app/widgets/app_bar.dart'; @@ -7,12 +11,16 @@ import 'package:car_provider_app/widgets/button/show_image_button.dart'; import 'package:car_provider_app/widgets/dialog/dialogs.dart'; import 'package:car_provider_app/widgets/dialog/message_dialog.dart'; import 'package:car_provider_app/widgets/dialog/otp_dialog.dart'; -import 'package:car_provider_app/widgets/extensions/int_extensions.dart'; -import 'package:car_provider_app/widgets/extensions/string_extensions.dart'; +import 'package:car_provider_app/extensions/int_extensions.dart'; +import 'package:car_provider_app/extensions/string_extensions.dart'; +import 'package:car_provider_app/extensions/widget_extensions.dart'; import 'package:car_provider_app/widgets/txt_field.dart'; import 'package:flutter/material.dart'; class LoginVerifyAccountPage extends StatelessWidget { + int otpType = 1; + String phoneNum = ""; + @override Widget build(BuildContext context) { return Scaffold( @@ -23,10 +31,13 @@ class LoginVerifyAccountPage extends StatelessWidget { padding: EdgeInsets.all(40), child: Column( children: [ - "Verify Account".toText16(), + "Verify Account".toText24(), mFlex(1), TxtField( hint: "Enter Phone number to verify", + onChanged: (v) { + phoneNum = v; + }, ), mFlex(2), Row( @@ -34,47 +45,55 @@ class LoginVerifyAccountPage extends StatelessWidget { Expanded( child: ShowImageButton( onClick: () { - showMDialog(context, child: OtpDialog( - onClick: () { - pop(context); - delay(300).then( - (value) => showMDialog( - context, - child: MessageDialog( - title: "Phone Number Verified", - onClick: () { - navigateWithName(context, AppRoutes.profile1); - }, - ), - ), - ); - }, - )); + otpType = 1; + performBasicOtp(context); + // showMDialog(context, child: OtpDialog( + // onClick: (String code) { + // pop(context); + // delay(300).then( + // (value) => showMDialog( + // context, + // child: MessageDialog( + // title: "Phone Number Verified", + // onClick: () { + // otpType=1; + // performBasicOtp(context); + // // navigateWithName(context, AppRoutes.completeProfile); + // }, + // ), + // ), + // ); + // }, + // )); }, title: 'With SMS', icon: icons + "ic_sms.png", ), ), - 20.height, + 20.width, Expanded( child: ShowImageButton( onClick: () { - showMDialog(context, child: OtpDialog( - onClick: () { - pop(context); - delay(300).then( - (value) => showMDialog( - context, - child: MessageDialog( - title: "Phone Number Verified", - onClick: () { - navigateWithName(context, AppRoutes.profile1); - }, - ), - ), - ); - }, - )); + otpType = 1; + performBasicOtp(context); + // showMDialog(context, child: OtpDialog( + // onClick: (String code) { + // pop(context); + // delay(300).then( + // (value) => showMDialog( + // context, + // child: MessageDialog( + // title: "Phone Number Verified", + // onClick: () { + // otpType=2; + // performBasicOtp(context); + // // navigateWithName(context, AppRoutes.completeProfile); + // }, + // ), + // ), + // ); + // }, + // )); }, title: 'With Whatsapp', icon: icons + "ic_whatsapp.png", @@ -88,4 +107,36 @@ class LoginVerifyAccountPage extends StatelessWidget { ), ); } + + Future performBasicOtp(BuildContext context) async { + Utils.showLoading(context); + BasicOtp basicOtp = await UserApiClent().basicOtp(phoneNum, otpType: otpType); + Utils.hideLoading(context); + if (basicOtp.messageStatus == 1) { + showMDialog(context, child: OtpDialog( + onClick: (String code) async { + pop(context); + Utils.showLoading(context); + RegisterUser user = await UserApiClent().basicVerify(phoneNum, code, basicOtp.data!.userToken ?? ""); + Utils.hideLoading(context); + if (user.messageStatus == 1) { + Utils.showToast(user.message ?? ""); + showMDialog( + context, + child: MessageDialog( + title: "Phone Number Verified", + onClick: () { + navigateWithName(context, AppRoutes.completeProfile,arguments: user); + }, + ), + ); + } else { + Utils.showToast(user.message ?? ""); + } + }, + )); + } else { + Utils.showToast(basicOtp.message ?? ""); + } + } } diff --git a/lib/pages/user/login_with_password_page.dart b/lib/pages/user/login_with_password_page.dart new file mode 100644 index 0000000..9fcf2fe --- /dev/null +++ b/lib/pages/user/login_with_password_page.dart @@ -0,0 +1,80 @@ +import 'dart:convert'; + +import 'package:car_provider_app/api/user_api_client.dart'; +import 'package:car_provider_app/classes/utils.dart'; +import 'package:car_provider_app/config/constants.dart'; +import 'package:car_provider_app/config/routes.dart'; +import 'package:car_provider_app/models/user/basic_otp.dart'; +import 'package:car_provider_app/models/user/register_user.dart'; +import 'package:car_provider_app/utils/navigator.dart'; +import 'package:car_provider_app/utils/utils.dart'; +import 'package:car_provider_app/widgets/app_bar.dart'; +import 'package:car_provider_app/widgets/button/show_image_button.dart'; +import 'package:car_provider_app/widgets/dialog/dialogs.dart'; +import 'package:car_provider_app/widgets/dialog/message_dialog.dart'; +import 'package:car_provider_app/widgets/dialog/otp_dialog.dart'; +import 'package:car_provider_app/extensions/int_extensions.dart'; +import 'package:car_provider_app/extensions/string_extensions.dart'; +import 'package:car_provider_app/extensions/widget_extensions.dart'; +import 'package:car_provider_app/widgets/show_fill_button.dart'; +import 'package:car_provider_app/widgets/txt_field.dart'; +import 'package:flutter/material.dart'; +import 'package:http/http.dart'; + +class LoginWithPassword extends StatelessWidget { + int otpType = 1; + String phoneNum = "", password = ""; + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: appBar(title: "Log In"), + body: Container( + width: double.infinity, + height: double.infinity, + padding: EdgeInsets.all(40), + child: Column( + children: [ + "Login".toText24(), + mFlex(1), + TxtField( + hint: "Enter Phone number to verify", + onChanged: (v) { + phoneNum = v; + }, + ), + 12.height, + TxtField( + hint: "Password", + onChanged: (v) { + password = v; + }, + ), + 50.height, + ShowFillButton( + title: "Continue", + width: double.infinity, + onPressed: () { + performBasicOtp(context); + }, + ), + mFlex(10), + ], + ), + ), + ); + } + + Future performBasicOtp(BuildContext context) async { + Utils.showLoading(context); + Response response = await UserApiClent().login_V1(phoneNum, password); + Utils.hideLoading(context); + if (response.statusCode == 200) { + String userToken = jsonDecode(response.body)["userToken"]; + navigateWithName(context, AppRoutes.loginMethodSelection, arguments: userToken); + } else { + String res = jsonDecode(response.body)["errors"][0] ?? ""; + Utils.showToast(res); + } + } +} diff --git a/lib/pages/user/register_page.dart b/lib/pages/user/register_page.dart index 5b83cfb..24a40e9 100644 --- a/lib/pages/user/register_page.dart +++ b/lib/pages/user/register_page.dart @@ -1,11 +1,23 @@ +import 'package:car_provider_app/api/api_client.dart'; +import 'package:car_provider_app/api/user_api_client.dart'; +import 'package:car_provider_app/classes/utils.dart'; +import 'package:car_provider_app/models/user/basic_otp.dart'; +import 'package:car_provider_app/models/user/register_user.dart'; +import 'package:car_provider_app/utils/navigator.dart'; +import 'package:car_provider_app/utils/utils.dart'; import 'package:car_provider_app/widgets/app_bar.dart'; -import 'package:car_provider_app/widgets/extensions/int_extensions.dart'; -import 'package:car_provider_app/widgets/extensions/string_extensions.dart'; +import 'package:car_provider_app/widgets/dialog/dialogs.dart'; +import 'package:car_provider_app/widgets/dialog/otp_dialog.dart'; import 'package:car_provider_app/widgets/show_fill_button.dart'; +import 'package:car_provider_app/extensions/int_extensions.dart'; +import 'package:car_provider_app/extensions/string_extensions.dart'; +import 'package:car_provider_app/extensions/widget_extensions.dart'; import 'package:car_provider_app/widgets/txt_field.dart'; import 'package:flutter/material.dart'; class RegisterPage extends StatelessWidget { + String phoneNum = ""; + @override Widget build(BuildContext context) { return Scaffold( @@ -16,29 +28,59 @@ class RegisterPage extends StatelessWidget { padding: EdgeInsets.all(40), child: Column( children: [ - "Enter Phone Number".toText16(), + "Enter Phone Number".toText24(), 12.height, TxtField( hint: "Enter Phone number to Register", - ), - TxtField( - hint: "Enter Phone number to Register", - ), - 40.height, - "Or Enter Email".toText16(), - 12.height, - TxtField( - hint: "Enter Email to Register", + onChanged: (v) { + phoneNum = v; + }, ), 50.height, ShowFillButton( title: "Continue", width: double.infinity, - onPressed: () {}, + onPressed: () { + performBasicOtp(context); + }, ), ], ), ), ); } + + Future performBasicOtp(BuildContext context) async { + Utils.showLoading(context); + BasicOtp basicOtp = await UserApiClent().basicOtp(phoneNum); + Utils.hideLoading(context); + if (basicOtp.messageStatus == 1) { + showMDialog(context, child: OtpDialog( + onClick: (String code) async { + pop(context); + Utils.showLoading(context); + RegisterUser user = await UserApiClent().basicVerify(phoneNum, code, basicOtp.data!.userToken ?? ""); + Utils.hideLoading(context); + if (user.messageStatus == 1) { + Utils.showToast(user.message ?? ""); + } else { + Utils.showToast(user.message ?? ""); + } + // delay(300).then( + // (value) => showMDialog( + // context, + // child: MessageDialog( + // title: "Phone Number Verified", + // onClick: () { + // navigateWithName(context, AppRoutes.completeProfile); + // }, + // ), + // ), + // ); + }, + )); + } else { + Utils.showToast(basicOtp.message ?? ""); + } + } } diff --git a/lib/pages/user/register_selection_page.dart b/lib/pages/user/register_selection_page.dart index 4b78d28..534a13d 100644 --- a/lib/pages/user/register_selection_page.dart +++ b/lib/pages/user/register_selection_page.dart @@ -2,9 +2,10 @@ import 'package:car_provider_app/config/routes.dart'; import 'package:car_provider_app/utils/navigator.dart'; import 'package:car_provider_app/utils/utils.dart'; import 'package:car_provider_app/widgets/app_bar.dart'; -import 'package:car_provider_app/widgets/extensions/int_extensions.dart'; -import 'package:car_provider_app/widgets/extensions/string_extensions.dart'; import 'package:car_provider_app/widgets/show_fill_button.dart'; +import 'package:car_provider_app/extensions/string_extensions.dart'; +import 'package:car_provider_app/extensions/int_extensions.dart'; +import 'package:car_provider_app/extensions/widget_extensions.dart'; import 'package:flutter/material.dart'; class RegisterSelectionPage extends StatelessWidget { @@ -18,8 +19,16 @@ class RegisterSelectionPage extends StatelessWidget { padding: EdgeInsets.all(40), child: Column( children: [ - "Welcome Message".toText16(), + "Welcome Message".toText24(), mFlex(1), + ShowFillButton( + title: "Log In With Password", + width: double.infinity, + onPressed: () { + navigateWithName(context, AppRoutes.loginWithPassword); + }, + ), + 20.height, ShowFillButton( title: "Log In", width: double.infinity, diff --git a/lib/pages/user/splash_page.dart b/lib/pages/user/splash_page.dart index 02708ce..6881d73 100644 --- a/lib/pages/user/splash_page.dart +++ b/lib/pages/user/splash_page.dart @@ -1,8 +1,10 @@ +import 'dart:async'; + import 'package:car_provider_app/config/routes.dart'; +import 'package:car_provider_app/extensions/string_extensions.dart'; import 'package:car_provider_app/utils/navigator.dart'; import 'package:car_provider_app/utils/utils.dart'; -import 'package:car_provider_app/widgets/extensions/string_extensions.dart'; -import 'package:car_provider_app/widgets/extensions/widget_extensions.dart'; +import 'package:car_provider_app/extensions/widget_extensions.dart'; import 'package:flutter/material.dart'; class SplashPage extends StatelessWidget { @@ -15,13 +17,13 @@ class SplashPage extends StatelessWidget { child: Column( children: [ mFlex(5), - "Logo".toText32(isBold: true), + "Logo".toText(fontSize: 45, isBold: true), mFlex(3), - "First Time Log In".toText24().onPress(() { + "First Time Log In".toText(fontSize: 18, isBold: true).onPress(() { navigateWithName(context, AppRoutes.registerSelection); }), mFlex(1), - "Already Signed Up and Logged In".toText24().onPress(() { + "Already Signed Up and Logged In".toText(fontSize: 18, isBold: true).onPress(() { navigateWithName(context, AppRoutes.loginVerification); }), mFlex(5), diff --git a/lib/widgets/app_bar.dart b/lib/widgets/app_bar.dart index a9227a6..c588699 100644 --- a/lib/widgets/app_bar.dart +++ b/lib/widgets/app_bar.dart @@ -1,6 +1,8 @@ import 'package:car_provider_app/theme/colors.dart'; -import 'package:car_provider_app/widgets/extensions/string_extensions.dart'; +import 'package:car_provider_app/extensions/string_extensions.dart'; +import 'package:car_provider_app/extensions/int_extensions.dart'; import 'package:flutter/material.dart'; +import 'package:sizer/sizer.dart'; AppBar appBar({ Color? backgroundColor, @@ -19,6 +21,6 @@ AppBar appBar({ color: backIconColor ?? Colors.black, //change your color here ), actions: actions, - title: (title ?? "").toText16(), + title: (title ?? "").toText(fontSize: 20,isBold: true), ); } diff --git a/lib/widgets/button/show_image_button.dart b/lib/widgets/button/show_image_button.dart index 3067ba0..b283900 100644 --- a/lib/widgets/button/show_image_button.dart +++ b/lib/widgets/button/show_image_button.dart @@ -1,13 +1,16 @@ import 'package:car_provider_app/theme/colors.dart'; -import 'package:car_provider_app/widgets/extensions/int_extensions.dart'; -import 'package:car_provider_app/widgets/extensions/string_extensions.dart'; +import 'package:car_provider_app/utils/utils.dart'; import 'package:flutter/material.dart'; +import 'package:car_provider_app/extensions/string_extensions.dart'; +import 'package:car_provider_app/extensions/int_extensions.dart'; + class ShowImageButton extends StatelessWidget { String icon, title; VoidCallback onClick; - ShowImageButton({required this.icon, required this.title, required this.onClick}); + ShowImageButton( + {required this.icon, required this.title, required this.onClick}); @override Widget build(BuildContext context) { @@ -28,7 +31,7 @@ class ShowImageButton extends StatelessWidget { ), ), 12.height, - title.toText22(color: Colors.blue) + title.toText(color: Colors.blue,isBold: true,fontSize: 18), ], ), ), diff --git a/lib/widgets/dialog/message_dialog.dart b/lib/widgets/dialog/message_dialog.dart index b96226c..0bb6ff0 100644 --- a/lib/widgets/dialog/message_dialog.dart +++ b/lib/widgets/dialog/message_dialog.dart @@ -1,6 +1,9 @@ -import 'package:car_provider_app/widgets/extensions/int_extensions.dart'; -import 'package:car_provider_app/widgets/extensions/string_extensions.dart'; +import 'package:car_provider_app/theme/colors.dart'; +import 'package:car_provider_app/utils/navigator.dart'; +import 'package:car_provider_app/utils/utils.dart'; import 'package:car_provider_app/widgets/show_fill_button.dart'; +import 'package:car_provider_app/extensions/int_extensions.dart'; +import 'package:car_provider_app/extensions/string_extensions.dart'; import 'package:flutter/material.dart'; class MessageDialog extends StatelessWidget { @@ -18,7 +21,7 @@ class MessageDialog extends StatelessWidget { mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, children: [ - (title ?? "message").toText16(), + (title ?? "message").toText24(), 40.height, ShowFillButton( title: buttonTitle ?? "Continue", diff --git a/lib/widgets/dialog/otp_dialog.dart b/lib/widgets/dialog/otp_dialog.dart index d08a3c2..ceca836 100644 --- a/lib/widgets/dialog/otp_dialog.dart +++ b/lib/widgets/dialog/otp_dialog.dart @@ -1,70 +1,102 @@ +import 'package:car_provider_app/classes/colors.dart'; import 'package:car_provider_app/theme/colors.dart'; -import 'package:car_provider_app/widgets/extensions/int_extensions.dart'; -import 'package:car_provider_app/widgets/extensions/string_extensions.dart'; +import 'package:car_provider_app/utils/navigator.dart'; +import 'package:car_provider_app/utils/utils.dart'; import 'package:car_provider_app/widgets/show_fill_button.dart'; +import 'package:car_provider_app/extensions/string_extensions.dart'; +import 'package:car_provider_app/extensions/int_extensions.dart'; import 'package:flutter/material.dart'; +import '../otp_widget.dart'; + class OtpDialog extends StatelessWidget { - VoidCallback onClick; + Function(String) onClick; + OtpDialog({required this.onClick}); + String code=""; + final TextEditingController _pinPutController = TextEditingController(); @override Widget build(BuildContext context) { return Container( color: Colors.white, - padding: EdgeInsets.all(30), + padding: EdgeInsets.all(24), child: Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, children: [ - "Please insert OTP Code".toText16(), + "Please insert OTP Code".toText24(), 20.height, - Row( - children: [ - Expanded( - child: Container( - width: double.infinity, - height: 60, - color: accentColor.withOpacity(0.3), - ), - ), - 12.height, - Expanded( - child: Container( - width: double.infinity, - height: 60, - color: accentColor.withOpacity(0.3), - ), - ), - 12.height, - Expanded( - child: Container( - width: double.infinity, - height: 60, - color: accentColor.withOpacity(0.3), - ), - ), - 12.height, - Expanded( - child: Container( - width: double.infinity, - height: 60, - color: accentColor.withOpacity(0.3), - ), - ), - ], + OTPWidget( + autoFocus: true, + controller: _pinPutController, + defaultBorderColor: const Color(0xffD8D8D8), + maxLength: 4, + onTextChanged: (text) {}, + pinBoxColor: Colors.white, + onDone: (code) => _onOtpCallBack(code, null), + textBorderColor: const Color(0xffD8D8D8), + pinBoxWidth: 60, + pinBoxHeight: 60, + pinTextStyle: const TextStyle(fontSize: 24.0, color: MyColors.darkTextColor), + pinTextAnimatedSwitcherTransition: ProvidedPinBoxTextAnimation.scalingTransition, + pinTextAnimatedSwitcherDuration: const Duration(milliseconds: 300), + pinBoxRadius: 10, + keyboardType: TextInputType.number, ), + // Row( + // children: [ + // Expanded( + // child: Container( + // width: double.infinity, + // height: 60, + // color: accentColor.withOpacity(0.3), + // ), + // ), + // 12.width, + // Expanded( + // child: Container( + // width: double.infinity, + // height: 60, + // color: accentColor.withOpacity(0.3), + // ), + // ), + // 12.width, + // Expanded( + // child: Container( + // width: double.infinity, + // height: 60, + // color: accentColor.withOpacity(0.3), + // ), + // ), + // 12.width, + // Expanded( + // child: Container( + // width: double.infinity, + // height: 60, + // color: accentColor.withOpacity(0.3), + // ), + // ), + // ], + // ), 40.height, ShowFillButton( title: "Check Code", width: double.infinity, onPressed: () { - onClick(); + onClick(code); }, ) ], ), ); } + + _onOtpCallBack(String otpCode, bool? isAutofill) { + if (otpCode.length == 4) { + // onSuccess(otpCode); + code=otpCode; + } + } } diff --git a/lib/widgets/loading_dialog.dart b/lib/widgets/loading_dialog.dart new file mode 100644 index 0000000..e4b1cf8 --- /dev/null +++ b/lib/widgets/loading_dialog.dart @@ -0,0 +1,43 @@ +import 'package:flutter/cupertino.dart'; +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; + +class LoadingDialog extends StatefulWidget { + LoadingDialog({Key? key}) : super(key: key); + + @override + _LoadingDialogState createState() { + return _LoadingDialogState(); + } +} + +class _LoadingDialogState extends State { + @override + void initState() { + super.initState(); + } + + @override + void dispose() { + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return Dialog( + insetPadding: const EdgeInsets.symmetric(horizontal: 60.0, vertical: 24.0), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(16), + ), + elevation: 0, + backgroundColor: Colors.transparent, + child: Directionality( + textDirection: TextDirection.rtl, + child: Center( + child: CircularProgressIndicator(), + ), + ), + ); + } +} diff --git a/lib/widgets/otp_widget.dart b/lib/widgets/otp_widget.dart new file mode 100644 index 0000000..a489e61 --- /dev/null +++ b/lib/widgets/otp_widget.dart @@ -0,0 +1,373 @@ +import 'dart:async'; + +import 'package:flutter/animation.dart'; +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; +import 'package:flutter/services.dart'; + +typedef OnDone = void Function(String text); + +class ProvidedPinBoxTextAnimation { + static AnimatedSwitcherTransitionBuilder scalingTransition = (child, animation) { + return ScaleTransition( + child: child, + scale: animation, + ); + }; + + static AnimatedSwitcherTransitionBuilder defaultNoTransition = (Widget child, Animation animation) { + return child; + }; +} + +class OTPWidget extends StatefulWidget { + final int maxLength; + final TextEditingController? controller; + + final Color defaultBorderColor; + final Color pinBoxColor; + final double pinBoxBorderWidth; + final double pinBoxRadius; + final bool hideDefaultKeyboard; + + final TextStyle? pinTextStyle; + final double pinBoxHeight; + final double pinBoxWidth; + final OnDone? onDone; + final bool hasError; + final Color errorBorderColor; + final Color textBorderColor; + final Function(String)? onTextChanged; + final bool autoFocus; + final FocusNode? focusNode; + final AnimatedSwitcherTransitionBuilder? pinTextAnimatedSwitcherTransition; + final Duration pinTextAnimatedSwitcherDuration; + final TextDirection textDirection; + final TextInputType keyboardType; + final EdgeInsets pinBoxOuterPadding; + + const OTPWidget({ + Key? key, + this.maxLength: 4, + this.controller, + this.pinBoxWidth: 70.0, + this.pinBoxHeight: 70.0, + this.pinTextStyle, + this.onDone, + this.defaultBorderColor: Colors.black, + this.textBorderColor: Colors.black, + this.pinTextAnimatedSwitcherTransition, + this.pinTextAnimatedSwitcherDuration: const Duration(), + this.hasError: false, + this.errorBorderColor: Colors.red, + this.onTextChanged, + this.autoFocus: false, + this.focusNode, + this.textDirection: TextDirection.ltr, + this.keyboardType: TextInputType.number, + this.pinBoxOuterPadding = const EdgeInsets.symmetric(horizontal: 4.0), + this.pinBoxColor = Colors.white, + this.pinBoxBorderWidth = 2.0, + this.pinBoxRadius = 0, + this.hideDefaultKeyboard = false, + }) : super(key: key); + + @override + State createState() { + return OTPWidgetState(); + } +} + +class OTPWidgetState extends State with SingleTickerProviderStateMixin { + AnimationController? _highlightAnimationController; + FocusNode? focusNode; + String text = ""; + int currentIndex = 0; + List strList = []; + bool hasFocus = false; + + @override + void didUpdateWidget(OTPWidget oldWidget) { + super.didUpdateWidget(oldWidget); + focusNode = widget.focusNode ?? focusNode; + + if (oldWidget.maxLength < widget.maxLength) { + setState(() { + currentIndex = text.length; + }); + widget.controller?.text = text; + widget.controller?.selection = TextSelection.collapsed(offset: text.length); + } else if (oldWidget.maxLength > widget.maxLength && widget.maxLength > 0 && text.length > 0 && text.length > widget.maxLength) { + setState(() { + text = text.substring(0, widget.maxLength); + currentIndex = text.length; + }); + widget.controller?.text = text; + widget.controller?.selection = TextSelection.collapsed(offset: text.length); + } + } + + _calculateStrList() { + if (strList.length > widget.maxLength) { + strList.length = widget.maxLength; + } + while (strList.length < widget.maxLength) { + strList.add(""); + } + } + + @override + void initState() { + super.initState(); + focusNode = widget.focusNode ?? FocusNode(); + + _initTextController(); + _calculateStrList(); + widget.controller?.addListener(_controllerListener); + focusNode?.addListener(_focusListener); + } + + void _controllerListener() { + if (mounted == true) { + setState(() { + _initTextController(); + }); + var onTextChanged = widget.onTextChanged; + if (onTextChanged != null) { + onTextChanged(widget.controller?.text ?? ""); + } + } + } + + void _focusListener() { + if (mounted == true) { + setState(() { + hasFocus = focusNode?.hasFocus ?? false; + }); + } + } + + void _initTextController() { + if (widget.controller == null) { + return; + } + strList.clear(); + var text = widget.controller?.text ?? ""; + if (text.isNotEmpty) { + if (text.length > widget.maxLength) { + throw Exception("TextEditingController length exceeded maxLength!"); + } + } + for (var i = 0; i < text.length; i++) { + strList.add(text[i]); + } + } + + double get _width { + var width = 0.0; + for (var i = 0; i < widget.maxLength; i++) { + width += widget.pinBoxWidth; + if (i == 0) { + width += widget.pinBoxOuterPadding.left; + } else if (i + 1 == widget.maxLength) { + width += widget.pinBoxOuterPadding.right; + } else { + width += widget.pinBoxOuterPadding.left; + } + } + return width; + } + + @override + void dispose() { + if (widget.focusNode == null) { + focusNode?.dispose(); + } else { + focusNode?.removeListener(_focusListener); + } + _highlightAnimationController?.dispose(); + widget.controller?.removeListener(_controllerListener); + + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return Stack( + children: [ + _otpTextInput(), + _touchPinBoxRow(), + ], + ); + } + + Widget _touchPinBoxRow() { + return widget.hideDefaultKeyboard + ? _pinBoxRow(context) + : GestureDetector( + behavior: HitTestBehavior.opaque, + onTap: () { + if (hasFocus) { + FocusScope.of(context).requestFocus(FocusNode()); + Future.delayed(Duration(milliseconds: 100), () { + FocusScope.of(context).requestFocus(focusNode); + }); + } else { + FocusScope.of(context).requestFocus(focusNode); + } + }, + child: _pinBoxRow(context), + ); + } + + Widget _otpTextInput() { + var transparentBorder = OutlineInputBorder( + borderSide: BorderSide( + color: Colors.transparent, + width: 0.0, + ), + ); + return Container( + width: _width, + height: widget.pinBoxHeight, + child: TextField( + autofocus: !kIsWeb ? widget.autoFocus : false, + enableInteractiveSelection: false, + focusNode: focusNode, + controller: widget.controller, + keyboardType: widget.keyboardType, + inputFormatters: widget.keyboardType == TextInputType.number ? [FilteringTextInputFormatter.digitsOnly] : null, + style: TextStyle( + height: 0.1, + color: Colors.transparent, + ), + decoration: InputDecoration( + contentPadding: EdgeInsets.all(0), + focusedErrorBorder: transparentBorder, + errorBorder: transparentBorder, + disabledBorder: transparentBorder, + enabledBorder: transparentBorder, + focusedBorder: transparentBorder, + counterText: null, + counterStyle: null, + helperStyle: TextStyle( + height: 0.0, + color: Colors.transparent, + ), + labelStyle: TextStyle(height: 0.1), + fillColor: Colors.transparent, + border: InputBorder.none, + ), + cursorColor: Colors.transparent, + showCursor: false, + maxLength: widget.maxLength, + onChanged: _onTextChanged, + ), + ); + } + + void _onTextChanged(text) { + var onTextChanged = widget.onTextChanged; + if (onTextChanged != null) { + onTextChanged(text); + } + setState(() { + this.text = text; + if (text.length >= currentIndex) { + for (int i = currentIndex; i < text.length; i++) { + strList[i] = text[i]; + } + } + currentIndex = text.length; + }); + if (text.length == widget.maxLength) { + FocusScope.of(context).requestFocus(FocusNode()); + var onDone = widget.onDone; + if (onDone != null) { + onDone(text); + } + } + } + + Widget _pinBoxRow(BuildContext context) { + _calculateStrList(); + List pinCodes = List.generate(widget.maxLength, (int i) { + return _buildPinCode(i, context); + }); + return Row(children: pinCodes, mainAxisSize: MainAxisSize.min); + } + + Widget _buildPinCode(int i, BuildContext context) { + Color borderColor; + Color pinBoxColor = widget.pinBoxColor; + + if (widget.hasError) { + borderColor = widget.errorBorderColor; + } else if (i < text.length) { + borderColor = widget.textBorderColor; + } else { + borderColor = widget.defaultBorderColor; + pinBoxColor = widget.pinBoxColor; + } + + EdgeInsets insets; + if (i == 0) { + insets = EdgeInsets.only( + left: 0, + top: widget.pinBoxOuterPadding.top, + right: widget.pinBoxOuterPadding.right, + bottom: widget.pinBoxOuterPadding.bottom, + ); + } else if (i == strList.length - 1) { + insets = EdgeInsets.only( + left: widget.pinBoxOuterPadding.left, + top: widget.pinBoxOuterPadding.top, + right: 0, + bottom: widget.pinBoxOuterPadding.bottom, + ); + } else { + insets = widget.pinBoxOuterPadding; + } + return Container( + key: ValueKey("container$i"), + alignment: Alignment.center, + padding: EdgeInsets.symmetric(vertical: 4.0, horizontal: 1.0), + margin: insets, + child: _animatedTextBox(strList[i], i), + decoration: BoxDecoration( + border: Border.all( + color: borderColor, + width: widget.pinBoxBorderWidth, + ), + color: pinBoxColor, + borderRadius: BorderRadius.circular(widget.pinBoxRadius), + ), + width: widget.pinBoxWidth, + height: widget.pinBoxHeight, + ); + } + + Widget _animatedTextBox(String text, int i) { + if (widget.pinTextAnimatedSwitcherTransition != null) { + return AnimatedSwitcher( + duration: widget.pinTextAnimatedSwitcherDuration, + transitionBuilder: widget.pinTextAnimatedSwitcherTransition ?? + (Widget child, Animation animation) { + return child; + }, + child: Text( + text, + key: ValueKey("$text$i"), + style: widget.pinTextStyle, + ), + ); + } else { + return Text( + text, + key: ValueKey("${strList[i]}$i"), + style: widget.pinTextStyle, + ); + } + } +} diff --git a/lib/widgets/show_fill_button.dart b/lib/widgets/show_fill_button.dart index 6712800..457b38a 100644 --- a/lib/widgets/show_fill_button.dart +++ b/lib/widgets/show_fill_button.dart @@ -1,5 +1,6 @@ +import 'package:car_provider_app/extensions/string_extensions.dart'; import 'package:car_provider_app/theme/colors.dart'; -import 'package:car_provider_app/widgets/extensions/string_extensions.dart'; + import 'package:flutter/material.dart'; class ShowFillButton extends StatelessWidget { @@ -20,17 +21,18 @@ class ShowFillButton extends StatelessWidget { @override Widget build(BuildContext context) { return ElevatedButton( - style: ElevatedButton.styleFrom( - onPrimary: Colors.black87, - primary: accentColor, - minimumSize: Size(width, 45), - padding: EdgeInsets.symmetric(horizontal: 16), - elevation: elevation, - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.all(Radius.circular(radius)), - ), + style: ElevatedButton.styleFrom( + onPrimary: Colors.black87, + primary: accentColor, + minimumSize: Size(width, 45), + padding: EdgeInsets.symmetric(horizontal: 16), + elevation: elevation, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.all(Radius.circular(radius)), ), - onPressed: onPressed, - child: title.toUpperCase().toText22(color: txtColor)); + ), + onPressed: onPressed, + child: title.toUpperCase().toText(fontSize: 16, isBold: true), + ); } } diff --git a/lib/widgets/txt_field.dart b/lib/widgets/txt_field.dart index 4baaaf2..29ce5b5 100644 --- a/lib/widgets/txt_field.dart +++ b/lib/widgets/txt_field.dart @@ -1,7 +1,7 @@ +import 'package:car_provider_app/extensions/int_extensions.dart'; import 'package:car_provider_app/theme/colors.dart'; import 'package:car_provider_app/utils/utils.dart'; -import 'package:car_provider_app/widgets/extensions/int_extensions.dart'; -import 'package:car_provider_app/widgets/extensions/string_extensions.dart'; +import 'package:car_provider_app/extensions/string_extensions.dart'; import 'package:flutter/material.dart'; import 'package:sizer/sizer.dart'; @@ -21,6 +21,7 @@ class TxtField extends StatelessWidget { int? maxLines; bool isSidePaddingZero; bool isNeedBorder; + Function(String)? onChanged; TxtField({ this.title, @@ -35,8 +36,9 @@ class TxtField extends StatelessWidget { this.isButtonEnable = false, this.buttonTitle, this.maxLines, - this.isSidePaddingZero = false, - this.isNeedBorder = true, + this.isSidePaddingZero=false, + this.isNeedBorder=true, + this.onChanged, }); @override @@ -54,27 +56,28 @@ class TxtField extends StatelessWidget { Expanded( child: Card( elevation: elevation, - margin: isSidePaddingZero ? EdgeInsets.zero : null, + margin: isSidePaddingZero?EdgeInsets.zero:null, child: TextField( autofocus: false, controller: controller, enabled: isNeedClickAll == true ? false : true, maxLines: maxLines, onTap: () {}, + onChanged: onChanged, decoration: InputDecoration( labelText: lable, alignLabelWithHint: true, fillColor: Colors.white, focusedBorder: OutlineInputBorder( - borderSide: BorderSide(color: accentColor, width: isNeedBorder ? 1.0 : 0), + borderSide: BorderSide(color: accentColor, width:isNeedBorder? 1.0:0), borderRadius: BorderRadius.circular(4.0), ), enabledBorder: OutlineInputBorder( - borderSide: BorderSide(color: borderColor, width: isNeedBorder ? 1.0 : 0), + borderSide: BorderSide(color: borderColor, width:isNeedBorder? 1.0:0), borderRadius: BorderRadius.circular(4.0), ), disabledBorder: OutlineInputBorder( - borderSide: BorderSide(color: borderColor, width: isNeedBorder ? 1.0 : 0), + borderSide: BorderSide(color: borderColor, width:isNeedBorder? 1.0:0), borderRadius: BorderRadius.circular(4.0), ), prefixIcon: prefixData != null @@ -134,7 +137,7 @@ class TxtField extends StatelessWidget { child: Center( child: Padding( padding: const EdgeInsets.only(left: 12, right: 12), - child: (buttonTitle ?? "Search").toText17(isBold: true, color: Colors.white), + child: (buttonTitle ?? "Search").toText16(color: Colors.white), ), ), ), diff --git a/pubspec.lock b/pubspec.lock index c66b7ec..aa18217 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -133,6 +133,13 @@ packages: description: flutter source: sdk version: "0.0.0" + fluttertoast: + dependency: "direct main" + description: + name: fluttertoast + url: "https://pub.dartlang.org" + source: hosted + version: "8.0.8" http: dependency: "direct main" description: @@ -316,7 +323,7 @@ packages: source: hosted version: "6.0.1" shared_preferences: - dependency: transitive + dependency: "direct main" description: name: shared_preferences url: "https://pub.dartlang.org" diff --git a/pubspec.yaml b/pubspec.yaml index fc602a8..61ec9bf 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -42,7 +42,8 @@ dependencies: permission_handler: 7.1.0 flutter_svg: ^0.22.0 sizer: ^2.0.15 - + fluttertoast: ^8.0.8 + shared_preferences: ^2.0.6 dev_dependencies: flutter_test: