diff --git a/assets/icons/checkOff.svg b/assets/icons/checkOff.svg new file mode 100644 index 0000000..ff51395 --- /dev/null +++ b/assets/icons/checkOff.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/assets/icons/checkOn.svg b/assets/icons/checkOn.svg new file mode 100644 index 0000000..9e17bfc --- /dev/null +++ b/assets/icons/checkOn.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/assets/icons/email.svg b/assets/icons/email.svg new file mode 100644 index 0000000..86d875a --- /dev/null +++ b/assets/icons/email.svg @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/assets/icons/key.svg b/assets/icons/key.svg new file mode 100644 index 0000000..eca9fe2 --- /dev/null +++ b/assets/icons/key.svg @@ -0,0 +1,35 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/lib/classes/const.dart b/lib/classes/const.dart index 8fb8115..b1cc55b 100644 --- a/lib/classes/const.dart +++ b/lib/classes/const.dart @@ -16,6 +16,7 @@ class Const { static Color textGrey = Color(0xff787F95); static Color textGrey1 = Color(0xffA6A8BA); + static Color textGrey2 = Color(0xff868787); static Color textHintGrey = Color(0xffcccccc); static Color sliderBackground = Color(0xffdfdfdf); diff --git a/lib/classes/utils.dart b/lib/classes/utils.dart new file mode 100644 index 0000000..8d367a1 --- /dev/null +++ b/lib/classes/utils.dart @@ -0,0 +1,9 @@ +import 'package:flutter/material.dart'; +import 'package:fluttertoast/fluttertoast.dart'; + +class Utils { + 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); + } +} diff --git a/lib/extensions/email_validator.dart b/lib/extensions/email_validator.dart new file mode 100644 index 0000000..e980d96 --- /dev/null +++ b/lib/extensions/email_validator.dart @@ -0,0 +1,7 @@ +extension EmailValidator on String { + 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); + } +} \ No newline at end of file diff --git a/lib/main.dart b/lib/main.dart index 526e50b..0ae0279 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -3,7 +3,10 @@ import 'dart:io'; import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:tangheem/ui/common_appbar.dart'; +import 'package:tangheem/ui/screens/forgot_password_screen.dart'; import 'package:tangheem/ui/screens/home_screen.dart'; +import 'package:tangheem/ui/screens/login_screen.dart'; +import 'package:tangheem/ui/screens/registration_screen.dart'; import 'package:tangheem/ui/screens/surah_screen.dart'; import 'package:tangheem/ui/screens/tangheem_screen.dart'; import 'classes/const.dart'; @@ -13,30 +16,39 @@ void main() { } class Application extends StatelessWidget { - // This widget is the root of your application. @override Widget build(BuildContext context) { return MaterialApp( - title: 'Tangheem', - theme: lightTheme(), - debugShowCheckedModeBanner: false, - initialRoute: HomeScreen.routeName, - onGenerateRoute: (settings) { - var className; - switch (settings.name) { - case HomeScreen.routeName: - className = CommonAppbar(showDrawer: true, child: HomeScreen()); - break; - case TangheemScreen.routeName: - className = CommonAppbar(child: TangheemScreen()); - break; - case SurahScreen.routeName: - String query = settings.arguments.toString(); - className = CommonAppbar(child: SurahScreen(query: query)); - break; - } - return goToNavigation(className); - }); + title: 'Tangheem', + theme: lightTheme(), + debugShowCheckedModeBanner: false, + initialRoute: LoginScreen.routeName, + onGenerateRoute: (settings) { + var className; + switch (settings.name) { + case LoginScreen.routeName: + className = CommonAppbar(isFirst: true, child: LoginScreen()); + break; + case RegistrationScreen.routeName: + className = CommonAppbar(child: RegistrationScreen()); + break; + case ForgotPasswordScreen.routeName: + className = CommonAppbar(child: ForgotPasswordScreen()); + break; + case HomeScreen.routeName: + className = CommonAppbar(showDrawer: true, child: HomeScreen()); + break; + case TangheemScreen.routeName: + className = CommonAppbar(child: TangheemScreen()); + break; + case SurahScreen.routeName: + String query = settings.arguments.toString(); + className = CommonAppbar(child: SurahScreen(query: query)); + break; + } + return goToNavigation(className); + }, + ); } PageRoute goToNavigation(var className) { diff --git a/lib/ui/common_appbar.dart b/lib/ui/common_appbar.dart index c278932..079dd9e 100644 --- a/lib/ui/common_appbar.dart +++ b/lib/ui/common_appbar.dart @@ -5,8 +5,9 @@ import 'package:tangheem/classes/const.dart'; class CommonAppbar extends StatefulWidget { final bool showDrawer; final Widget child; + final bool isFirst; - CommonAppbar({Key key, this.showDrawer = false, @required this.child}) : super(key: key); + CommonAppbar({Key key, this.showDrawer = false, @required this.child, this.isFirst = false}) : super(key: key); @override _CommonAppbarState createState() { @@ -42,20 +43,21 @@ class _CommonAppbarState extends State { height: 100, padding: EdgeInsets.only(top: 8, bottom: 8, right: 16), child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, crossAxisAlignment: CrossAxisAlignment.center, children: [ - IconButton( - icon: Icon(widget.showDrawer ? Icons.menu : Icons.arrow_back_ios, color: Const.textGrey), - padding: EdgeInsets.only(left: 16), - onPressed: () { - if (widget.showDrawer) { - _scaffoldKey.currentState.openDrawer(); - } else { - Navigator.pop(context); - } - }, - ), + if (!widget.isFirst) + IconButton( + icon: Icon(widget.showDrawer ? Icons.menu : Icons.arrow_back_ios, color: Const.textGrey), + padding: EdgeInsets.only(left: 16), + onPressed: () { + if (widget.showDrawer) { + _scaffoldKey.currentState.openDrawer(); + } else { + Navigator.pop(context); + } + }, + ), + Expanded(child: SizedBox()), Hero( tag: "logo", child: SvgPicture.asset( @@ -68,7 +70,9 @@ class _CommonAppbarState extends State { ], ), ), - Expanded(child: Directionality(textDirection: TextDirection.rtl, child: widget.child)), + Expanded( + child: Directionality(textDirection: TextDirection.rtl, child: widget.child), + ), ], ), ), diff --git a/lib/ui/dialogs/opt_dialog.dart b/lib/ui/dialogs/opt_dialog.dart new file mode 100644 index 0000000..1657589 --- /dev/null +++ b/lib/ui/dialogs/opt_dialog.dart @@ -0,0 +1,118 @@ +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; +import 'package:tangheem/classes/const.dart'; +import 'package:tangheem/widgets/otp_widget.dart'; + +class OTPDialog extends StatefulWidget { + OTPDialog({Key key}) : super(key: key); + + @override + _OTPDialogState createState() { + return _OTPDialogState(); + } +} + +class _OTPDialogState extends State { + final TextEditingController _pinPutController = TextEditingController(); + final FocusNode _pinPutFocusNode = FocusNode(); + + bool hasError = false; + String errorMessage; + @override + void initState() { + super.initState(); + } + + @override + void dispose() { + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return Dialog( + insetPadding: EdgeInsets.symmetric(horizontal: 60.0, vertical: 24.0), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(12), + ), + elevation: 0, + backgroundColor: Colors.transparent, + child: Container( + width: double.infinity, + decoration: BoxDecoration( + color: Const.primaryBlue, + borderRadius: BorderRadius.circular(16), + ), + padding: EdgeInsets.symmetric(vertical: 32, horizontal: 16), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Text( + "الرجاء ادخال الرقم المرسل الى جوالك", + textAlign: TextAlign.center, + style: TextStyle(color: Colors.white), + ), + Container( + // width: double.infinity, + margin: EdgeInsets.only(top: 8, bottom: 8), + padding: EdgeInsets.all(6), + decoration: BoxDecoration( + // color: Colors.white, + border: Border.all(width: 1, color: Colors.white), + ), + child: OTPWidget( + autofocus: true, + controller: _pinPutController, + defaultBorderColor: Colors.transparent, + maxLength: 4, + hasError: hasError, + onTextChanged: (text) { + setState(() { + hasError = false; + }); + }, + pinBoxColor: Const.secondaryWhite.withOpacity(0.2), + onDone: (text) {}, + textBorderColor: Colors.transparent, + pinBoxWidth: 40, + pinBoxHeight: 40, + pinTextStyle: TextStyle(fontSize: 20.0, color: Colors.white), + pinTextAnimatedSwitcherTransition: ProvidedPinBoxTextAnimation.scalingTransition, + pinTextAnimatedSwitcherDuration: Duration(milliseconds: 300), + keyboardType: TextInputType.number, + ), + ), + SizedBox( + width: double.infinity, + height: 40, + child: TextButton( + onPressed: () { + Navigator.pop(context); + }, + style: TextButton.styleFrom( + primary: Colors.white, + padding: EdgeInsets.all(2), + backgroundColor: Const.secondaryPink, + textStyle: TextStyle(fontSize: 14, fontFamily: "DroidKufi"), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(6.0), + ), + ), + child: Text("تحقق من الرقم"), + ), + ), + ], + ), + ), + ); + } + + BoxDecoration get _pinPutDecoration { + return BoxDecoration( + color: Const.secondaryWhite.withOpacity(0.2), + //border: Border.all(color: Colors.deepPurpleAccent), + //borderRadius: BorderRadius.circular(0.0), + ); + } +} diff --git a/lib/ui/screens/forgot_password_screen.dart b/lib/ui/screens/forgot_password_screen.dart new file mode 100644 index 0000000..f0d7859 --- /dev/null +++ b/lib/ui/screens/forgot_password_screen.dart @@ -0,0 +1,120 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_svg/svg.dart'; +import 'package:tangheem/classes/const.dart'; +import 'package:tangheem/classes/utils.dart'; +import 'package:tangheem/ui/dialogs/opt_dialog.dart'; +import 'package:tangheem/widgets/common_textfield_widget.dart'; + +class ForgotPasswordScreen extends StatefulWidget { + static const String routeName = "/forgot_password"; + ForgotPasswordScreen({Key key}) : super(key: key); + + @override + _ForgotPasswordScreenState createState() { + return _ForgotPasswordScreenState(); + } +} + +class _ForgotPasswordScreenState extends State { + TextEditingController _usernameController = TextEditingController(); + TextEditingController _emailController = TextEditingController(); + + @override + void initState() { + super.initState(); + } + + @override + void dispose() { + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + backgroundColor: Const.secondaryWhite, + body: SingleChildScrollView( + padding: EdgeInsets.all(32.0), + physics: BouncingScrollPhysics(), + child: Container( + width: MediaQuery.of(context).size.width, + decoration: BoxDecoration(borderRadius: BorderRadius.circular(8.0), color: Colors.white), + child: Column( + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisSize: MainAxisSize.min, + children: [ + Padding( + padding: EdgeInsets.only(top: 36, bottom: 24), + child: SvgPicture.asset("assets/icons/key.svg", width: 100, height: 100), + ), + Text( + "نسيت كلمة المرور؟", + style: TextStyle(fontSize: 22, color: Const.primaryBlue), + ), + Container( + margin: EdgeInsets.only(top: 16), + width: double.infinity, + padding: EdgeInsets.all(32.0), + decoration: BoxDecoration( + color: Const.primaryBlue, + borderRadius: BorderRadius.only( + bottomLeft: Radius.circular(8), + bottomRight: Radius.circular(8), + ), + ), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + CommonTextFieldWidget(hint: "اسم المستخدم", controller: _usernameController, prefixIcon: "assets/icons/user.svg"), + SizedBox(height: 8), + Text( + "-أو-", + style: TextStyle(color: Colors.white), + ), + SizedBox(height: 8), + CommonTextFieldWidget(hint: "الايميل المسجل", controller: _emailController, prefixIcon: "assets/icons/email.svg"), + SizedBox(height: 12), + SizedBox( + width: double.infinity, + height: 50, + child: TextButton( + onPressed: () { + if (_usernameController.text.length < 1) { + Utils.showToast("Username is empty."); + return; + } + if (_emailController.text.length < 1) { + Utils.showToast("Email is empty."); + return; + } + getOTP(); + }, + style: TextButton.styleFrom( + primary: Colors.white, + backgroundColor: Const.secondaryPink, + textStyle: TextStyle(fontSize: 16, fontFamily: "DroidKufi"), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(6.0), + ), + ), + child: Text("اعادة تعيين كلمة المرور"), + ), + ), + ], + ), + ), + ], + ), + ), + ), + ); + } + + getOTP() { + showDialog( + context: context, + barrierColor: Const.secondaryWhite.withOpacity(0.8), + builder: (BuildContext context) => OTPDialog(), + ); + } +} diff --git a/lib/ui/screens/home_screen.dart b/lib/ui/screens/home_screen.dart index 461dcb0..9245a20 100644 --- a/lib/ui/screens/home_screen.dart +++ b/lib/ui/screens/home_screen.dart @@ -9,7 +9,7 @@ import 'package:tangheem/ui/screens/tangheem_screen.dart'; import 'package:tangheem/widgets/common_dropdown_button.dart'; class HomeScreen extends StatefulWidget { - static const String routeName = "/"; + static const String routeName = "/home"; HomeScreen({Key key}) : super(key: key); diff --git a/lib/ui/screens/login_screen.dart b/lib/ui/screens/login_screen.dart new file mode 100644 index 0000000..adc42ea --- /dev/null +++ b/lib/ui/screens/login_screen.dart @@ -0,0 +1,169 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_svg/flutter_svg.dart'; +import 'package:tangheem/classes/const.dart'; +import 'package:tangheem/classes/utils.dart'; +import 'package:tangheem/ui/screens/forgot_password_screen.dart'; +import 'package:tangheem/ui/screens/registration_screen.dart'; +import 'package:tangheem/widgets/common_textfield_widget.dart'; + +import 'home_screen.dart'; + +class LoginScreen extends StatefulWidget { + static const String routeName = "/"; + LoginScreen({Key key}) : super(key: key); + + @override + _LoginScreenState createState() { + return _LoginScreenState(); + } +} + +class _LoginScreenState extends State { + TextEditingController _usernameController = TextEditingController(); + TextEditingController _passwordController = TextEditingController(); + bool _isRemember = true; + + @override + void initState() { + super.initState(); + } + + @override + void dispose() { + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + backgroundColor: Const.secondaryWhite, + body: SafeArea( + child: Directionality( + textDirection: TextDirection.rtl, + child: SingleChildScrollView( + padding: EdgeInsets.all(32.0), + physics: BouncingScrollPhysics(), + child: Container( + width: MediaQuery.of(context).size.width, + decoration: BoxDecoration(borderRadius: BorderRadius.circular(8.0), color: Colors.white), + child: Column( + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisSize: MainAxisSize.min, + children: [ + Padding( + padding: EdgeInsets.only(top: 24, bottom: 24), + child: SvgPicture.asset("assets/logos/tangheem_logo.svg", width: 100, height: 100), + ), + Text( + "تسجيل الدخول", + style: TextStyle(fontSize: 22, color: Const.primaryBlue), + ), + Container( + margin: EdgeInsets.only(top: 16), + width: double.infinity, + padding: EdgeInsets.all(32.0), + color: Const.primaryBlue, + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + CommonTextFieldWidget(hint: "اسم المستخدم", controller: _usernameController, prefixIcon: "assets/icons/user.svg"), + SizedBox(height: 16), + CommonTextFieldWidget(hint: "كلمة المرور", controller: _passwordController, isPassword: true, prefixIcon: "assets/icons/password.svg"), + SizedBox(height: 16), + SizedBox( + width: double.infinity, + height: 50, + child: TextButton( + onPressed: () { + if (_usernameController.text.length < 1) { + Utils.showToast("Username is empty."); + return; + } + if (_passwordController.text.length < 1) { + Utils.showToast("Password is empty."); + return; + } + Navigator.pushNamed(context, HomeScreen.routeName); + }, + style: TextButton.styleFrom( + primary: Colors.white, + backgroundColor: Const.secondaryPink, + textStyle: TextStyle(fontSize: 16, fontFamily: "DroidKufi"), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(6.0), + ), + ), + child: Text("تسجيل الدخول"), + ), + ), + SizedBox(height: 8), + Row( + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Row( + mainAxisSize: MainAxisSize.min, + children: [ + Text( + "تذكرني", + style: TextStyle(color: Colors.white), + ), + SizedBox(width: 8), + InkWell( + onTap: () { + setState(() { + _isRemember = !_isRemember; + }); + }, + child: SvgPicture.asset(_isRemember ? "assets/icons/checkOn.svg" : "assets/icons/checkOff.svg", width: 16, height: 16)) + ], + ), + InkWell( + onTap: () { + Navigator.pushNamed(context, ForgotPasswordScreen.routeName); + }, + child: Text( + "نسيت كلمة المرور؟", + style: TextStyle(color: Colors.white), + ), + ), + ], + ), + ], + ), + ), + InkWell( + onTap: () { + Navigator.pushNamed(context, RegistrationScreen.routeName); + }, + child: Container( + height: 50, + alignment: Alignment.center, + decoration: BoxDecoration( + color: Const.tertiaryPurple, + borderRadius: BorderRadius.only( + bottomLeft: Radius.circular(8), + bottomRight: Radius.circular(8), + ), + ), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Text( + "انشاء حساب جديد", + style: TextStyle(fontSize: 14, color: Colors.white), + ), + Icon(Icons.arrow_forward_ios, color: Colors.white, size: 12), + ], + ), + ), + ) + ], + ), + ), + ), + ), + ), + ); + } +} diff --git a/lib/ui/screens/registration_screen.dart b/lib/ui/screens/registration_screen.dart new file mode 100644 index 0000000..adc1b5f --- /dev/null +++ b/lib/ui/screens/registration_screen.dart @@ -0,0 +1,183 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_svg/svg.dart'; +import 'package:tangheem/classes/const.dart'; +import 'package:tangheem/classes/utils.dart'; +import 'package:tangheem/widgets/common_textfield_widget.dart'; +import 'file:///C:/Users/sikander.saleem/Documents/AndroidStudioProjects/tangheem/lib/extensions/email_validator.dart'; + +class RegistrationScreen extends StatefulWidget { + static const String routeName = "/registration"; + RegistrationScreen({Key key}) : super(key: key); + + @override + _RegistrationScreenState createState() { + return _RegistrationScreenState(); + } +} + +class _RegistrationScreenState extends State { + TextEditingController _usernameController = TextEditingController(); + TextEditingController _fullNameController = TextEditingController(); + TextEditingController _emailController = TextEditingController(); + TextEditingController _mobileNumberController = TextEditingController(); + TextEditingController _passwordController = TextEditingController(); + TextEditingController _confirmPasswordController = TextEditingController(); + bool _isAccept = false; + + @override + void initState() { + super.initState(); + } + + @override + void dispose() { + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + backgroundColor: Const.secondaryWhite, + body: SingleChildScrollView( + padding: EdgeInsets.all(32.0), + physics: BouncingScrollPhysics(), + child: Container( + width: MediaQuery.of(context).size.width, + decoration: BoxDecoration(borderRadius: BorderRadius.circular(8.0), color: Colors.white), + child: Column( + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisSize: MainAxisSize.min, + children: [ + Padding( + padding: EdgeInsets.only(top: 24, bottom: 24), + child: SvgPicture.asset("assets/logos/tangheem_logo.svg", width: 100, height: 100), + ), + Text( + "انشاء حساب جديد", + style: TextStyle(fontSize: 22, color: Const.primaryBlue), + ), + Padding( + padding: EdgeInsets.all(8), + child: Text( + "قم بتعبئة بيانات طلب انشاء الحساب و ستصلك رسالة تأكيد, ومن ثم يمكنك الدخول لحسابك الجديد", + textAlign: TextAlign.center, + style: TextStyle(fontSize: 14, color: Const.primaryBlue, height: 1), + ), + ), + Container( + margin: EdgeInsets.only(top: 16), + width: double.infinity, + padding: EdgeInsets.all(32.0), + decoration: BoxDecoration( + color: Const.primaryBlue, + borderRadius: BorderRadius.only( + bottomLeft: Radius.circular(8), + bottomRight: Radius.circular(8), + ), + ), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + CommonTextFieldWidget(hint: "اسم المستخدم", controller: _usernameController), + SizedBox(height: 8), + CommonTextFieldWidget(hint: "الايميل المسجل", controller: _fullNameController), + SizedBox(height: 8), + CommonTextFieldWidget(hint: "الايميل", controller: _emailController), + SizedBox(height: 8), + CommonTextFieldWidget(hint: "البلد", controller: _mobileNumberController, showSuffix: true), + SizedBox(height: 8), + CommonTextFieldWidget(hint: " رقم الجوال (9xx xxxxxxxxx+)", controller: _mobileNumberController), + SizedBox(height: 8), + CommonTextFieldWidget(hint: "تعيين كلمة المرور", controller: _passwordController), + SizedBox(height: 8), + CommonTextFieldWidget(hint: "تأكيد كلمة المرور", controller: _confirmPasswordController), + SizedBox(height: 8), + Row( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + InkWell( + onTap: () { + setState(() { + _isAccept = !_isAccept; + }); + }, + child: SvgPicture.asset(_isAccept ? "assets/icons/checkOn.svg" : "assets/icons/checkOff.svg", width: 16, height: 16), + ), + SizedBox(width: 8), + Expanded( + child: Text( + "أقر بأني إطلعت على شروط الاستخدام و سياسية الخصوصية و أوافق عليها", + style: TextStyle(color: Colors.white, fontSize: 13), + ), + ), + ], + ), + SizedBox(height: 12), + SizedBox( + width: double.infinity, + height: 50, + child: TextButton( + onPressed: () { + if (_usernameController.text.length < 1) { + Utils.showToast("Username is empty."); + return; + } + if (_fullNameController.text.length < 1) { + Utils.showToast("Name is empty."); + return; + } + if (_emailController.text.length < 1) { + Utils.showToast("Email is empty."); + return; + } + if (_mobileNumberController.text.length < 1) { + Utils.showToast("Phone number is empty."); + return; + } + if (_passwordController.text.length < 1) { + Utils.showToast("Password is empty."); + return; + } + if (_confirmPasswordController.text.length < 1) { + Utils.showToast("Enter same password to confirm"); + return; + } + + if (!_emailController.text.isValidEmail()) { + Utils.showToast("Invalid email."); + return; + } + + if (_passwordController.text != _confirmPasswordController.text) { + Utils.showToast("Your passwords are incorrect."); + return; + } + + if (!_isAccept) { + Utils.showToast("You must accept statement to proceed."); + return; + } + Utils.showToast("اعادة تعيين كلمة المرور"); + }, + style: TextButton.styleFrom( + primary: Colors.white, + backgroundColor: Const.secondaryPink, + textStyle: TextStyle(fontSize: 16, fontFamily: "DroidKufi"), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(6.0), + ), + ), + child: Text("انشئ حسابك"), + ), + ), + ], + ), + ), + ], + ), + ), + ), + ); + } +} diff --git a/lib/widgets/common_textfield_widget.dart b/lib/widgets/common_textfield_widget.dart new file mode 100644 index 0000000..21f6b53 --- /dev/null +++ b/lib/widgets/common_textfield_widget.dart @@ -0,0 +1,50 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_svg/svg.dart'; +import 'package:tangheem/classes/const.dart'; + +class CommonTextFieldWidget extends StatelessWidget { + final String hint; + final TextEditingController controller; + final bool isPassword; + final String prefixIcon; + final bool showSuffix; + CommonTextFieldWidget({Key key, @required this.hint, @required this.controller, this.isPassword = false, this.prefixIcon, this.showSuffix = false}) : super(key: key); + + @override + Widget build(BuildContext context) { + return SizedBox( + height: 50, + child: TextField( + textAlignVertical: TextAlignVertical.center, + controller: controller, + obscureText: isPassword, + textInputAction: TextInputAction.next, + style: TextStyle(color: Const.primaryBlack, fontSize: 14), + cursorColor: Const.primaryBlue, + decoration: InputDecoration( + contentPadding: EdgeInsets.fromLTRB(4, 4, 8, 4), + alignLabelWithHint: true, + fillColor: Colors.white, + filled: true, + hintStyle: TextStyle(color: Const.textGrey2, fontSize: 14), + hintText: hint, + suffixIcon: !showSuffix + ? null + : Padding( + padding: EdgeInsets.only(left: 8, right: 0), + child: Icon(Icons.keyboard_arrow_down, size: 18, color: Const.secondaryOrange), + ), + suffixIconConstraints: !showSuffix ? null : BoxConstraints(maxHeight: 30), + prefixIconConstraints: prefixIcon == null ? prefixIcon : BoxConstraints(maxHeight: 18), + prefixIcon: prefixIcon == null + ? prefixIcon + : Padding( + padding: EdgeInsets.only(left: 4, right: 0), + child: SvgPicture.asset(prefixIcon, color: Const.textGrey2), + ), + border: OutlineInputBorder(borderRadius: BorderRadius.circular(6), borderSide: BorderSide.none), + ), + ), + ); + } +} diff --git a/lib/widgets/otp_widget.dart b/lib/widgets/otp_widget.dart new file mode 100644 index 0000000..d9fb2c8 --- /dev/null +++ b/lib/widgets/otp_widget.dart @@ -0,0 +1,381 @@ +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 bool isCupertino; + 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.isCupertino: false, + 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; + Animation _highlightAnimationColorTween; + 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() async { + 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, +// 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 Padding( + padding: insets, + child: Container( + key: ValueKey("container$i"), + child: Padding( + padding: EdgeInsets.symmetric(vertical: 1.0, horizontal: 1.0), + child: Center( + 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/pubspec.yaml b/pubspec.yaml index 797779e..18a19ac 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -28,6 +28,7 @@ dependencies: # Use with the CupertinoIcons class for iOS style icons. cupertino_icons: ^1.0.2 flutter_svg: ^0.19.3 + fluttertoast: ^7.1.8 dev_dependencies: flutter_test: