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: