From c6cb3bbe0ef53c64fddf0cf15afadb901244160b Mon Sep 17 00:00:00 2001 From: Sikander Saleem Date: Thu, 23 Dec 2021 12:32:30 +0300 Subject: [PATCH 1/4] forgot password screen added. --- assets/langs/ar-SA.json | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/assets/langs/ar-SA.json b/assets/langs/ar-SA.json index ba920a8..816a208 100644 --- a/assets/langs/ar-SA.json +++ b/assets/langs/ar-SA.json @@ -36,6 +36,10 @@ "offers": "Offers & ", "discounts": "Discounts", "newString": "New", + "setTheNewPassword": "Set the new password", + "typeYourNewPasswordBelow": "Type your new password below", + "confirmPassword": "Confirm Password", + "update": "Update", "title": "Title", "home": "Home", "mySalary": "My Salary", From f48e09b2d18caab7a1d81a57b0d88f2dcbe0641e Mon Sep 17 00:00:00 2001 From: Sikander Saleem Date: Thu, 23 Dec 2021 12:32:59 +0300 Subject: [PATCH 2/4] forgot password screen added. --- assets/langs/en-US.json | 4 ++ lib/config/routes.dart | 4 +- lib/generated/codegen_loader.g.dart | 10 +++ lib/ui/login/login_screen.dart | 36 +++++------ lib/ui/login/new_password_screen.dart | 92 +++++++++++++++++++++++++++ 5 files changed, 125 insertions(+), 21 deletions(-) create mode 100644 lib/ui/login/new_password_screen.dart diff --git a/assets/langs/en-US.json b/assets/langs/en-US.json index ba920a8..816a208 100644 --- a/assets/langs/en-US.json +++ b/assets/langs/en-US.json @@ -36,6 +36,10 @@ "offers": "Offers & ", "discounts": "Discounts", "newString": "New", + "setTheNewPassword": "Set the new password", + "typeYourNewPasswordBelow": "Type your new password below", + "confirmPassword": "Confirm Password", + "update": "Update", "title": "Title", "home": "Home", "mySalary": "My Salary", diff --git a/lib/config/routes.dart b/lib/config/routes.dart index d60f2b8..0224e73 100644 --- a/lib/config/routes.dart +++ b/lib/config/routes.dart @@ -1,6 +1,7 @@ import 'package:flutter/material.dart'; import 'package:mohem_flutter_app/ui/dashboard.dart'; import 'package:mohem_flutter_app/ui/login/login_screen.dart'; +import 'package:mohem_flutter_app/ui/login/new_password_screen.dart'; import 'package:mohem_flutter_app/ui/login/verify_login_screen.dart'; class AppRoutes { @@ -9,7 +10,7 @@ class AppRoutes { static const String loginVerifyAccount = "/loginVerifyAccount"; static const String login = "/login"; static const String verifyLogin = "/verifyLogin"; - static const String forgetPassword = "/forgetPassword"; + static const String newPassword = "/newPassword"; static const String loginVerification = "/loginVerification"; static const String dashboard = "/dashboard"; static const String initialRoute = login; @@ -18,5 +19,6 @@ class AppRoutes { login: (context) => LoginScreen(), verifyLogin: (context) => VerifyLoginScreen(), dashboard: (context) => Dashboard(), + newPassword: (context) => NewPasswordScreen(), }; } diff --git a/lib/generated/codegen_loader.g.dart b/lib/generated/codegen_loader.g.dart index 0991c28..824dea8 100644 --- a/lib/generated/codegen_loader.g.dart +++ b/lib/generated/codegen_loader.g.dart @@ -51,6 +51,11 @@ class CodegenLoader extends AssetLoader{ "viewAllOffers": "View All Offers", "offers": "Offers & ", "discounts": "Discounts", + "newString": "New", + "setTheNewPassword": "Set the new password", + "typeYourNewPasswordBelow": "Type your new password below", + "confirmPassword": "Confirm Password", + "update": "Update", "title": "Title", "home": "Home", "mySalary": "My Salary", @@ -130,6 +135,11 @@ static const Map en_US = { "viewAllOffers": "View All Offers", "offers": "Offers & ", "discounts": "Discounts", + "newString": "New", + "setTheNewPassword": "Set the new password", + "typeYourNewPasswordBelow": "Type your new password below", + "confirmPassword": "Confirm Password", + "update": "Update", "title": "Title", "home": "Home", "mySalary": "My Salary", diff --git a/lib/ui/login/login_screen.dart b/lib/ui/login/login_screen.dart index 6e043bd..f7f8e05 100644 --- a/lib/ui/login/login_screen.dart +++ b/lib/ui/login/login_screen.dart @@ -2,13 +2,14 @@ import 'package:easy_localization/easy_localization.dart'; import 'package:easy_localization/src/public_ext.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; -import 'package:mohem_flutter_app/config/routes.dart'; -import 'package:mohem_flutter_app/generated/locale_keys.g.dart'; import 'package:mohem_flutter_app/classes/colors.dart'; +import 'package:mohem_flutter_app/config/routes.dart'; import 'package:mohem_flutter_app/extensions/int_extensions.dart'; +import 'package:mohem_flutter_app/extensions/string_extensions.dart'; import 'package:mohem_flutter_app/extensions/widget_extensions.dart'; -import 'package:mohem_flutter_app/widgets/input_widget.dart'; +import 'package:mohem_flutter_app/generated/locale_keys.g.dart'; import 'package:mohem_flutter_app/widgets/button/default_button.dart'; +import 'package:mohem_flutter_app/widgets/input_widget.dart'; class LoginScreen extends StatefulWidget { LoginScreen({Key? key}) : super(key: key); @@ -50,20 +51,14 @@ class _LoginScreenState extends State { Expanded(child: SizedBox()), Row( children: [ - Text( - LocaleKeys.english.tr(), - style: TextStyle(color: MyColors.textMixColor, fontSize: 14, letterSpacing: -0.48, fontWeight: FontWeight.w600), - ).onPress(() {}), + LocaleKeys.english.tr().toText14(color: MyColors.textMixColor).onPress(() {}), Container( width: 1, color: MyColors.darkWhiteColor, height: 16, margin: const EdgeInsets.only(left: 10, right: 10), ), - Text( - LocaleKeys.arabic.tr(), - style: TextStyle(color: MyColors.darkTextColor, fontSize: 14, letterSpacing: -0.48, fontWeight: FontWeight.w600), - ).onPress(() {}), + LocaleKeys.arabic.tr().toText14().onPress(() {}), ], ), ], @@ -74,18 +69,19 @@ class _LoginScreenState extends State { crossAxisAlignment: CrossAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.center, children: [ - Text( - LocaleKeys.login.tr(), - style: TextStyle(color: MyColors.darkTextColor, fontSize: 24, letterSpacing: -1.44, fontWeight: FontWeight.w700), - ), - Text( - LocaleKeys.pleaseEnterLoginDetails.tr(), - style: TextStyle(color: MyColors.darkTextColor, fontSize: 16, letterSpacing: -0.64, fontWeight: FontWeight.w600), - ), + LocaleKeys.login.tr().toText24(isBold: true), + LocaleKeys.pleaseEnterLoginDetails.tr().toText16(), 16.height, InputWidget(LocaleKeys.username.tr(), "123456", username), 12.height, - InputWidget(LocaleKeys.password.tr(), "xxxxxx", password, isObscureText: true) + InputWidget(LocaleKeys.password.tr(), "xxxxxx", password, isObscureText: true), + 9.height, + Align( + alignment: Alignment.centerRight, + child: LocaleKeys.forgotPassword.tr().toText12(isUnderLine: true, color: MyColors.textMixColor).onPress(() { + Navigator.pushNamed(context, AppRoutes.newPassword); + }), + ), ], ), ) diff --git a/lib/ui/login/new_password_screen.dart b/lib/ui/login/new_password_screen.dart new file mode 100644 index 0000000..dca855e --- /dev/null +++ b/lib/ui/login/new_password_screen.dart @@ -0,0 +1,92 @@ +import 'package:easy_localization/src/public_ext.dart'; +import 'package:flutter/material.dart'; +import 'package:mohem_flutter_app/classes/colors.dart'; +import 'package:mohem_flutter_app/config/routes.dart'; +import 'package:mohem_flutter_app/extensions/int_extensions.dart'; +import 'package:mohem_flutter_app/extensions/string_extensions.dart'; +import 'package:mohem_flutter_app/extensions/widget_extensions.dart'; +import 'package:mohem_flutter_app/generated/locale_keys.g.dart'; +import 'package:mohem_flutter_app/widgets/button/default_button.dart'; +import 'package:mohem_flutter_app/widgets/input_widget.dart'; + +class NewPasswordScreen extends StatefulWidget { + NewPasswordScreen({Key? key}) : super(key: key); + + @override + _NewPasswordScreenState createState() { + return _NewPasswordScreenState(); + } +} + +class _NewPasswordScreenState extends State { + TextEditingController password = TextEditingController(); + TextEditingController confirmPassword = TextEditingController(); + + @override + void initState() { + super.initState(); + } + + @override + void dispose() { + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + body: Column( + children: [ + const SizedBox(height: 23), + Expanded( + child: Padding( + padding: const EdgeInsets.all(21.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + Expanded(child: SizedBox()), + Row( + children: [ + LocaleKeys.english.tr().toText14(color: MyColors.textMixColor).onPress(() {}), + Container( + width: 1, + color: MyColors.darkWhiteColor, + height: 16, + margin: const EdgeInsets.only(left: 10, right: 10), + ), + LocaleKeys.arabic.tr().toText14().onPress(() {}), + ], + ), + ], + ), + Expanded( + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + LocaleKeys.setTheNewPassword.tr().toText24(isBold: true), + LocaleKeys.typeYourNewPasswordBelow.tr().toText16(), + 16.height, + InputWidget(LocaleKeys.password.tr(), "**********", password), + 12.height, + InputWidget(LocaleKeys.confirmPassword.tr(), "**********", confirmPassword, isObscureText: true) + ], + ), + ) + ], + ), + ), + ), + DefaultButton(LocaleKeys.update.tr(), () async { + // context.setLocale(const Locale("en", "US")); // to change Loacle + + Navigator.pushNamed(context, AppRoutes.verifyLogin); + }).insideContainer + ], + ), + ); + } +} From b38f4287b6e746a41b6f214cd789314425fcbd93 Mon Sep 17 00:00:00 2001 From: Sikander Saleem Date: Sun, 26 Dec 2021 16:32:47 +0300 Subject: [PATCH 3/4] today attendance progress added(cont) --- lib/classes/colors.dart | 3 + lib/config/routes.dart | 5 +- lib/extensions/string_extensions.dart | 14 +- lib/ui/{ => landing}/dashboard.dart | 7 +- lib/ui/landing/today_attendance_screen.dart | 144 ++++++ lib/widgets/circular_step_progress_bar.dart | 515 ++++++++++++++++++++ 6 files changed, 683 insertions(+), 5 deletions(-) rename lib/ui/{ => landing}/dashboard.dart (98%) create mode 100644 lib/ui/landing/today_attendance_screen.dart create mode 100644 lib/widgets/circular_step_progress_bar.dart diff --git a/lib/classes/colors.dart b/lib/classes/colors.dart index e8b8b2a..20e6cc1 100644 --- a/lib/classes/colors.dart +++ b/lib/classes/colors.dart @@ -9,10 +9,13 @@ class MyColors { 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 darkWhiteColor = Color(0xffE0E0E0); static const Color redColor = Color(0xffD02127); static const Color yellowColor = Color(0xffF4E31C); + static const Color backgroundBlackColor = Color(0xff202529); } diff --git a/lib/config/routes.dart b/lib/config/routes.dart index 0224e73..ac167c6 100644 --- a/lib/config/routes.dart +++ b/lib/config/routes.dart @@ -1,5 +1,6 @@ import 'package:flutter/material.dart'; -import 'package:mohem_flutter_app/ui/dashboard.dart'; +import 'package:mohem_flutter_app/ui/landing/dashboard.dart'; +import 'package:mohem_flutter_app/ui/landing/today_attendance_screen.dart'; import 'package:mohem_flutter_app/ui/login/login_screen.dart'; import 'package:mohem_flutter_app/ui/login/new_password_screen.dart'; import 'package:mohem_flutter_app/ui/login/verify_login_screen.dart'; @@ -13,6 +14,7 @@ class AppRoutes { static const String newPassword = "/newPassword"; static const String loginVerification = "/loginVerification"; static const String dashboard = "/dashboard"; + static const String todayAttendance = "/todayAttendance"; static const String initialRoute = login; static final Map routes = { @@ -20,5 +22,6 @@ class AppRoutes { verifyLogin: (context) => VerifyLoginScreen(), dashboard: (context) => Dashboard(), newPassword: (context) => NewPasswordScreen(), + todayAttendance: (context) => TodayAttendanceScreen(), }; } diff --git a/lib/extensions/string_extensions.dart b/lib/extensions/string_extensions.dart index d77bbff..f4ea9b1 100644 --- a/lib/extensions/string_extensions.dart +++ b/lib/extensions/string_extensions.dart @@ -47,9 +47,19 @@ extension EmailValidator on String { style: TextStyle(color: color ?? MyColors.darkTextColor, fontSize: 16, letterSpacing: -0.64, fontWeight: isBold ? FontWeight.bold : FontWeight.w600), ); - Widget toText24({bool isBold = false}) => Text( + 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: 23 / 24, color: MyColors.darkTextColor, fontSize: 24, letterSpacing: -1.44, fontWeight: isBold ? FontWeight.bold : FontWeight.w600), + style: TextStyle(height: 32 / 32, color: color ?? MyColors.darkTextColor, fontSize: 32, letterSpacing: -1.92, fontWeight: isBold ? FontWeight.bold : FontWeight.w600), ); bool isValidEmail() { diff --git a/lib/ui/dashboard.dart b/lib/ui/landing/dashboard.dart similarity index 98% rename from lib/ui/dashboard.dart rename to lib/ui/landing/dashboard.dart index 2c9cf50..568ba30 100644 --- a/lib/ui/dashboard.dart +++ b/lib/ui/landing/dashboard.dart @@ -3,6 +3,7 @@ import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:flutter_svg/flutter_svg.dart'; import 'package:mohem_flutter_app/classes/colors.dart'; +import 'package:mohem_flutter_app/config/routes.dart'; import 'package:mohem_flutter_app/extensions/int_extensions.dart'; import 'package:mohem_flutter_app/extensions/string_extensions.dart'; import 'package:mohem_flutter_app/extensions/widget_extensions.dart'; @@ -171,7 +172,9 @@ class _DashboardState extends State { ), ], ), - ), + ).onPress(() { + Navigator.pushNamed(context, AppRoutes.todayAttendance); + }), ), ), 9.width, @@ -346,7 +349,7 @@ class _DashboardState extends State { ), ), 4.height, - Expanded(child: namesD[6 % (index + 1)].toText12(isCenter: true,maxLine: 2)), + Expanded(child: namesD[6 % (index + 1)].toText12(isCenter: true, maxLine: 2)), ], ), ); diff --git a/lib/ui/landing/today_attendance_screen.dart b/lib/ui/landing/today_attendance_screen.dart new file mode 100644 index 0000000..d58fc4c --- /dev/null +++ b/lib/ui/landing/today_attendance_screen.dart @@ -0,0 +1,144 @@ +import 'package:easy_localization/src/public_ext.dart'; +import 'package:flutter/material.dart'; +import 'package:mohem_flutter_app/classes/colors.dart'; +import 'package:mohem_flutter_app/extensions/int_extensions.dart'; +import 'package:mohem_flutter_app/extensions/string_extensions.dart'; +import 'package:mohem_flutter_app/generated/locale_keys.g.dart'; +import 'package:mohem_flutter_app/widgets/circular_step_progress_bar.dart'; + +class TodayAttendanceScreen extends StatefulWidget { + TodayAttendanceScreen({Key? key}) : super(key: key); + + @override + _TodayAttendanceScreenState createState() { + return _TodayAttendanceScreenState(); + } +} + +class _TodayAttendanceScreenState extends State { + @override + void initState() { + super.initState(); + } + + @override + void dispose() { + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + backgroundColor: MyColors.backgroundBlackColor, + leading: IconButton( + icon: const Icon(Icons.arrow_back_ios, color: Colors.white), + onPressed: () => Navigator.pop(context), + ), + ), + backgroundColor: Colors.white, + body: ListView( + children: [ + Container( + color: MyColors.backgroundBlackColor, + padding: EdgeInsets.only(left: 21, right: 21, bottom: 21), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + "June 13, 2021".toText24(isBold: true, color: Colors.white), + LocaleKeys.timeLeftToday.tr().toText16(color: Color(0xffACACAC)), + 21.height, + Center( + child: CircularStepProgressBar( + totalSteps: 16 * 4, + currentStep: 16, + width: 216, + height: 216, + selectedColor: MyColors.gradiantEndColor, + unselectedColor: MyColors.grey70Color, + child: Center( + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + "08:58:15".toText32(color: Colors.white, isBold: true), + 19.height, + "Shift Time".tr().toText12(color: MyColors.greyACColor), + "08:00 - 17:00".toText22(color: Colors.white, isBold: true), + ], + ), + ), + ), + ), + ], + ), + ), + Container( + color: MyColors.backgroundBlackColor, + child: Stack( + children: [ + Container( + height: 187, + padding: EdgeInsets.only(left: 31, right: 31, top: 31, bottom: 16), + decoration: BoxDecoration( + borderRadius: BorderRadius.only(topLeft: Radius.circular(25), topRight: Radius.circular(25)), + gradient: const LinearGradient(transform: GradientRotation(.64), begin: Alignment.topRight, end: Alignment.bottomRight, colors: [ + MyColors.gradiantEndColor, + MyColors.gradiantStartColor, + ]), + ), + child: Column( + children: [ + Row( + children: [commonStatusView("Check In", "09:27"), commonStatusView("Check Out", "- - : - -")], + ), + 21.height, + Row( + children: [commonStatusView("Late In", "00:27"), commonStatusView("Regular", "08:00")], + ), + ], + ), + ), + Container( + width: double.infinity, + decoration: BoxDecoration(borderRadius: BorderRadius.only(topLeft: Radius.circular(25), topRight: Radius.circular(25)), color: Colors.white), + margin: EdgeInsets.only(top: 187 - 31), + padding: EdgeInsets.only(left: 21, right: 21, top: 24, bottom: 24), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.min, + children: ["Mark".tr().toText12(), "Attendance".tr().toText24(), "Select the method to mark the attendance".tr().toText12(color: Color(0xff535353)), 24.height], + ), + ), + // Positioned( + // top: 187 - 21, + // child: Container( + // padding: EdgeInsets.only(left: 31, right: 31, top: 31, bottom: 16), + // decoration: BoxDecoration(borderRadius: BorderRadius.only(topLeft: Radius.circular(25), topRight: Radius.circular(25)), color: Colors.white), + // child: Column( + // children: [ + // Row( + // children: [commonStatusView("Check In", "09:27"), commonStatusView("Check Out", "- - : - -")], + // ), + // 21.height, + // Row( + // children: [commonStatusView("Late In", "00:27"), commonStatusView("Regular", "08:00")], + // ), + // ], + // ), + // ), + // ), + ], + ), + ) + ], + ), + ); + } + + Widget commonStatusView(String title, String time) => Expanded( + child: Column(mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, children: [ + title.toText12(color: Colors.white), + time.toText22(color: Colors.white, isBold: true), + ]), + ); +} diff --git a/lib/widgets/circular_step_progress_bar.dart b/lib/widgets/circular_step_progress_bar.dart new file mode 100644 index 0000000..75724c8 --- /dev/null +++ b/lib/widgets/circular_step_progress_bar.dart @@ -0,0 +1,515 @@ +import 'dart:math' as math; + +import 'package:flutter/material.dart'; + +class CircularStepProgressBar extends StatelessWidget { + /// Defines if steps grow from + /// clockwise [CircularDirection.clockwise] or + /// counterclockwise [CircularDirection.counterclockwise] + final CircularDirection circularDirection; + + /// Number of steps to underline, all the steps with + /// index <= [currentStep] will have [Color] equal to + /// [selectedColor] + /// + /// Only used when [customColor] is [null] + /// + /// Default value: 0 + final int currentStep; + + /// Total number of step of the complete indicator + final int totalSteps; + + /// Radial spacing between each step. Remember to + /// define the value in radiant units + /// + /// Default value: math.pi / 20 + final double padding; + + /// Height of the indicator's box container + final double? height; + + /// Width of the indicator's box container + final double? width; + + /// Assign a custom [Color] for each step + /// + /// Takes a [int], index of the current step starting from 0, and + /// must return a [Color] + /// + /// **NOTE**: If provided, it overrides + /// [selectedColor] and [unselectedColor] + final Color Function(int)? customColor; + + /// [Color] of the selected steps + /// + /// All the steps with index <= [currentStep] + /// + /// Default value: [Colors.blue] + final Color? selectedColor; + + /// [Color] of the unselected steps + /// + /// All the steps with index between + /// [currentStep] and [totalSteps] + /// + /// Default value: [Colors.grey] + final Color? unselectedColor; + + /// The size of a single step in the indicator + /// + /// Default value: 6.0 + final double stepSize; + + /// Specify a custom size for selected steps + /// + /// Only applicable when not custom setting (customColor, customStepSize) is defined + /// + /// This value will replace the [stepSize] only for selected steps + final double? selectedStepSize; + + /// Specify a custom size for unselected steps + /// + /// Only applicable when not custom setting (customColor, customStepSize) is defined + /// + /// This value will replace the [stepSize] only for unselected steps + final double? unselectedStepSize; + + /// Assign a custom size [double] for each step + /// + /// Function takes a [int], index of the current step starting from 0, and + /// a [bool], which tells if the step is selected based on [currentStep], + /// and must return a [double] size of the step + /// + /// **NOTE**: If provided, it overrides [stepSize] + final double Function(int, bool)? customStepSize; + + /// [Widget] contained inside the circular indicator + final Widget? child; + + /// Height of the indicator container in case no [height] parameter + /// given and parent height is [double.infinity] + /// + /// Default value: 100.0 + final double fallbackHeight; + + /// Height of the indicator container in case no [width] parameter + /// given and parent height is [double.infinity] + /// + /// Default value: 100.0 + final double fallbackWidth; + + /// Angle in radiants in which the first step of the indicator is placed. + /// The initial value is on the top of the indicator (- math.pi / 2) + /// - 0 => TOP + /// - math.pi / 2 => LEFT + /// - math.pi => BOTTOM + /// - math.pi / 2 * 3 => RIGHT + /// - math.pi / 2 => TOP (again) + final double startingAngle; + + /// Angle in radiants which represents the size of the arc used to display the indicator. + /// It allows you to draw a semi-circle instead of a full 360° (math.pi * 2) circle. + final double arcSize; + + /// Adds rounded edges at the beginning and at the end of the circular indicator + /// given a [int], index of each step, and a [bool], + /// which tells if the step is selected based on [currentStep], and must return a + /// [bool] that tells if the edges are rounded or not + /// + /// **NOTE**: For continuous circular indicators (`padding: 0`), to check if to apply + /// the rounded edges the packages uses index 0 (for the first arc painted) and + /// 1 (for the second arc painted) + /// + /// ```dart + /// // Example: Add rounded edges for all the steps + /// roundedCap: (index, _) => true + /// ``` + /// + /// ```dart + /// // Example: Add rounded edges for the selected arc of the indicator + /// roundedCap: (index, _) => index == 0, + /// padding: 0 + /// ``` + final bool Function(int, bool)? roundedCap; + + /// Adds a gradient color to the circular indicator + /// + /// **NOTE**: If provided, it overrides [selectedColor], [unselectedColor], and [customColor] + final Gradient? gradientColor; + + /// Removes the extra angle caused by [StrokeCap.round] when [roundedCap] is applied + final bool removeRoundedCapExtraAngle; + + const CircularStepProgressBar({ + required this.totalSteps, + this.child, + this.height, + this.width, + this.customColor, + this.customStepSize, + this.selectedStepSize, + this.unselectedStepSize, + this.roundedCap, + this.gradientColor, + this.circularDirection = CircularDirection.clockwise, + this.fallbackHeight = 100.0, + this.fallbackWidth = 100.0, + this.currentStep = 0, + this.selectedColor = Colors.blue, + this.unselectedColor = Colors.grey, + this.padding = math.pi / 20, + this.stepSize = 6.0, + this.startingAngle = 0, + this.arcSize = math.pi * 2, + this.removeRoundedCapExtraAngle = false, + Key? key, + }) : assert(totalSteps > 0, "Number of total steps (totalSteps) of the CircularStepProgressBar must be greater than 0"), + assert(currentStep >= 0, "Current step (currentStep) of the CircularStepProgressBar must be greater than or equal to 0"), + assert(padding >= 0.0, "Padding (padding) of the CircularStepProgressBar must be greater or equal to 0"), + super(key: key); + + @override + Widget build(BuildContext context) { + // Print warning when arcSize greater than math.pi * 2 which causes steps to overlap + if (arcSize > math.pi * 2) print("WARNING (step_progress_indicator): arcSize of CircularStepProgressBar is greater than 360° (math.pi * 2), this will cause some steps to overlap!"); + final TextDirection textDirection = Directionality.of(context); + + return LayoutBuilder( + builder: (context, constraints) => SizedBox( + // Apply fallback for both height and width + // if their value is null and no parent size limit + height: height != null + ? height + : constraints.maxHeight != double.infinity + ? constraints.maxHeight + : fallbackHeight, + width: width != null + ? width + : constraints.maxWidth != double.infinity + ? constraints.maxWidth + : fallbackWidth, + child: CustomPaint( + painter: _CircularIndicatorPainter( + totalSteps: totalSteps, + currentStep: currentStep, + customColor: customColor, + padding: padding, + circularDirection: circularDirection, + selectedColor: selectedColor, + unselectedColor: unselectedColor, + arcSize: arcSize, + stepSize: stepSize, + customStepSize: customStepSize, + maxDefinedSize: maxDefinedSize, + selectedStepSize: selectedStepSize, + unselectedStepSize: unselectedStepSize, + startingAngle: startingAngleTopOfIndicator, + roundedCap: roundedCap, + gradientColor: gradientColor, + textDirection: textDirection, + removeRoundedCapExtraAngle: removeRoundedCapExtraAngle, + ), + // Padding needed to show the indicator when child is placed on top of it + child: Padding( + padding: EdgeInsets.all(maxDefinedSize), + child: child, + ), + ), + ), + ); + } + + /// Compute the maximum possible size of the indicator between + /// [stepSize] and [customStepSize] + double get maxDefinedSize { + if (customStepSize == null) { + return math.max(stepSize, math.max(selectedStepSize ?? 0, unselectedStepSize ?? 0)); + } + + // When customSize defined, compute and return max possible size + double currentMaxSize = 0; + + for (int step = 0; step < totalSteps; ++step) { + // Consider max between selected and unselected case + final customSizeValue = math.max(customStepSize!(step, false), customStepSize!(step, true)); + if (customSizeValue > currentMaxSize) { + currentMaxSize = customSizeValue; + } + } + + return currentMaxSize; + } + + /// Make [startingAngle] to top-center of indicator (0°) by default + double get startingAngleTopOfIndicator => startingAngle - math.pi / 2; +} + +class _CircularIndicatorPainter implements CustomPainter { + final int totalSteps; + final int currentStep; + final double padding; + final Color? selectedColor; + final Color? unselectedColor; + final double stepSize; + final double? selectedStepSize; + final double? unselectedStepSize; + final double Function(int, bool)? customStepSize; + final double maxDefinedSize; + final Color Function(int)? customColor; + final CircularDirection circularDirection; + final double startingAngle; + final double arcSize; + final bool Function(int, bool)? roundedCap; + final Gradient? gradientColor; + final TextDirection textDirection; + final bool removeRoundedCapExtraAngle; + + _CircularIndicatorPainter({ + required this.totalSteps, + required this.circularDirection, + required this.customColor, + required this.currentStep, + required this.selectedColor, + required this.unselectedColor, + required this.padding, + required this.stepSize, + required this.selectedStepSize, + required this.unselectedStepSize, + required this.customStepSize, + required this.startingAngle, + required this.arcSize, + required this.maxDefinedSize, + required this.roundedCap, + required this.gradientColor, + required this.textDirection, + required this.removeRoundedCapExtraAngle, + }); + + @override + void paint(Canvas canvas, Size size) { + final w = size.width; + final h = size.height; + + // Step length is user-defined arcSize + // divided by the total number of steps (each step same size) + final stepLength = arcSize / totalSteps; + + // Define general arc paint + Paint paint = Paint() + ..style = PaintingStyle.stroke + ..strokeWidth = maxDefinedSize; + + final rect = Rect.fromCenter( + // Rect created from the center of the widget + center: Offset(w / 2, h / 2), + // For both height and width, subtract maxDefinedSize to fit indicator inside the parent container + height: h - maxDefinedSize, + width: w - maxDefinedSize, + ); + + if (gradientColor != null) { + paint.shader = gradientColor!.createShader(rect, textDirection: textDirection); + } + + // Change color selected or unselected based on the circularDirection + final isClockwise = circularDirection == CircularDirection.clockwise; + + // Make a continuous arc without rendering all the steps when possible + if (padding == 0 && customColor == null && customStepSize == null && roundedCap == null) { + _drawContinuousArc(canvas, paint, rect, isClockwise); + } else { + _drawStepArc(canvas, paint, rect, isClockwise, stepLength); + } + } + + /// Draw a series of arcs, each composing the full steps of the indicator + void _drawStepArc(Canvas canvas, Paint paint, Rect rect, bool isClockwise, double stepLength) { + // Draw a series of circular arcs to compose the indicator + // Starting based on startingAngle attribute + // + // When clockwise: + // - Start drawing counterclockwise so to have the selected steps on top of the unselected + int step = isClockwise ? totalSteps - 1 : 0; + double stepAngle = isClockwise ? startingAngle - stepLength : startingAngle; + for (; isClockwise ? step >= 0 : step < totalSteps; isClockwise ? stepAngle -= stepLength : stepAngle += stepLength, isClockwise ? --step : ++step) { + // Check if the current step is selected or unselected + final isSelectedColor = _isSelectedColor(step, isClockwise); + + // Size of the step + final indexStepSize = customStepSize != null + // Consider step index inverted when counterclockwise + ? customStepSize!(_indexOfStep(step, isClockwise), isSelectedColor) + : isSelectedColor + ? selectedStepSize ?? stepSize + : unselectedStepSize ?? stepSize; + + // Use customColor if defined + final stepColor = customColor != null + // Consider step index inverted when counterclockwise + ? customColor!(_indexOfStep(step, isClockwise)) + : isSelectedColor + ? selectedColor! + : unselectedColor!; + + // Apply stroke cap to each step + final hasStrokeCap = roundedCap != null ? roundedCap!(_indexOfStep(step, isClockwise), isSelectedColor) : false; + final strokeCap = hasStrokeCap ? StrokeCap.round : StrokeCap.butt; + + // Remove extra size caused by rounded stroke cap + // https://github.com/SandroMaglione/step-progress-indicator/issues/20#issue-786114745 + final extraCapSize = indexStepSize / 2; + final extraCapAngle = extraCapSize / (rect.width / 2); + final extraCapRemove = hasStrokeCap && removeRoundedCapExtraAngle; + + // Draw arc steps of the indicator + _drawArcOnCanvas( + canvas: canvas, + rect: rect, + startingAngle: stepAngle + (extraCapRemove ? extraCapAngle : 0), + sweepAngle: stepLength - padding - (extraCapRemove ? extraCapAngle * 2 : 0), + paint: paint, + color: stepColor, + strokeWidth: indexStepSize, + strokeCap: strokeCap, + ); + } + } + + /// Draw optimized continuous indicator instead of multiple steps + void _drawContinuousArc(Canvas canvas, Paint paint, Rect rect, bool isClockwise) { + // Compute color of the selected and unselected bars + final firstStepColor = isClockwise ? selectedColor : unselectedColor; + final secondStepColor = !isClockwise ? selectedColor : unselectedColor; + + // Selected and unselected step sizes if defined, otherwise use stepSize + final firstStepSize = isClockwise ? selectedStepSize ?? stepSize : unselectedStepSize ?? stepSize; + final secondStepSize = !isClockwise ? selectedStepSize ?? stepSize : unselectedStepSize ?? stepSize; + + // Compute length and starting angle of the selected and unselected bars + final firstArcLength = arcSize * (currentStep / totalSteps); + final secondArcLength = arcSize - firstArcLength; + + // firstArcStartingAngle = startingAngle + final secondArcStartingAngle = startingAngle + firstArcLength; + + // Apply stroke cap to both arcs + // NOTE: For continuous circular indicator, it uses 0 and 1 as index to + // apply the rounded cap + final firstArcStrokeCap = roundedCap != null + ? isClockwise + ? roundedCap!(0, true) + : roundedCap!(1, false) + : false; + final secondArcStrokeCap = roundedCap != null + ? isClockwise + ? roundedCap!(1, false) + : roundedCap!(0, true) + : false; + final firstCap = firstArcStrokeCap ? StrokeCap.round : StrokeCap.butt; + final secondCap = secondArcStrokeCap ? StrokeCap.round : StrokeCap.butt; + + // When clockwise, draw the second arc first and the first on top of it + // Required when stroke cap is rounded to make the selected step on top of the unselected + if (circularDirection == CircularDirection.clockwise) { + // Second arc, selected when counterclockwise, unselected otherwise + _drawArcOnCanvas( + canvas: canvas, + rect: rect, + paint: paint, + startingAngle: secondArcStartingAngle, + sweepAngle: secondArcLength, + strokeWidth: secondStepSize, + color: secondStepColor!, + strokeCap: secondCap, + ); + + // First arc, selected when clockwise, unselected otherwise + _drawArcOnCanvas( + canvas: canvas, + rect: rect, + paint: paint, + startingAngle: startingAngle, + sweepAngle: firstArcLength, + strokeWidth: firstStepSize, + color: firstStepColor!, + strokeCap: firstCap, + ); + } else { + // First arc, selected when clockwise, unselected otherwise + _drawArcOnCanvas( + canvas: canvas, + rect: rect, + paint: paint, + startingAngle: startingAngle, + sweepAngle: firstArcLength, + strokeWidth: firstStepSize, + color: firstStepColor!, + strokeCap: firstCap, + ); + + // Second arc, selected when counterclockwise, unselected otherwise + _drawArcOnCanvas( + canvas: canvas, + rect: rect, + paint: paint, + startingAngle: secondArcStartingAngle, + sweepAngle: secondArcLength, + strokeWidth: secondStepSize, + color: secondStepColor!, + strokeCap: secondCap, + ); + } + } + + /// Draw the actual arc for a continuous indicator + void _drawArcOnCanvas({ + required Canvas canvas, + required Rect rect, + required double startingAngle, + required double sweepAngle, + required Paint paint, + required Color color, + required double strokeWidth, + required StrokeCap strokeCap, + }) => + canvas.drawArc( + rect, + startingAngle, + sweepAngle, + false /*isRadial*/, + paint + ..color = color + ..strokeWidth = strokeWidth + ..strokeCap = strokeCap, + ); + + bool _isSelectedColor(int step, bool isClockwise) => isClockwise ? step < currentStep : (step + 1) > (totalSteps - currentStep); + + /// Start counting indexes from the right if clockwise and on the left if counterclockwise + int _indexOfStep(int step, bool isClockwise) => isClockwise ? step : totalSteps - step - 1; + + @override + bool shouldRepaint(CustomPainter oldDelegate) => oldDelegate != this; + + @override + bool hitTest(Offset position) => false; + + @override + void addListener(listener) {} + + @override + void removeListener(listener) {} + + @override + get semanticsBuilder => null; + + @override + bool shouldRebuildSemantics(CustomPainter oldDelegate) => false; +} + +/// Used to define the [circularDirection] attribute of the [CircularStepProgressBar] +enum CircularDirection { + clockwise, + counterclockwise, +} From cbe471c7b0655f6f8d872a1e49697ebfaf317d55 Mon Sep 17 00:00:00 2001 From: Sikander Saleem Date: Mon, 27 Dec 2021 16:47:51 +0300 Subject: [PATCH 4/4] improvements --- lib/classes/colors.dart | 1 + lib/extensions/string_extensions.dart | 11 ++++-- lib/generated/codegen_loader.g.dart | 4 +++ lib/ui/app_bar.dart | 3 +- lib/ui/landing/dashboard.dart | 4 ++- lib/ui/landing/today_attendance_screen.dart | 34 ++++++++++++++++++- .../missing_swipe/missing_swipe_screen.dart | 16 +++------ lib/ui/work_list/work_list_screen.dart | 2 +- 8 files changed, 57 insertions(+), 18 deletions(-) diff --git a/lib/classes/colors.dart b/lib/classes/colors.dart index f20e621..6820e7d 100644 --- a/lib/classes/colors.dart +++ b/lib/classes/colors.dart @@ -16,6 +16,7 @@ class MyColors { 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); diff --git a/lib/extensions/string_extensions.dart b/lib/extensions/string_extensions.dart index f4ea9b1..9db4829 100644 --- a/lib/extensions/string_extensions.dart +++ b/lib/extensions/string_extensions.dart @@ -47,10 +47,15 @@ extension EmailValidator on String { 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), - ); + 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, diff --git a/lib/generated/codegen_loader.g.dart b/lib/generated/codegen_loader.g.dart index 824dea8..9829f4c 100644 --- a/lib/generated/codegen_loader.g.dart +++ b/lib/generated/codegen_loader.g.dart @@ -65,6 +65,8 @@ class CodegenLoader extends AssetLoader{ "msg": "Hello {} in the {} world ", "msg_named": "{} are written in the {lang} language", "clickMe": "Click me", + "human": "Human", + "resources": "Resources", "profile": { "reset_password": { "label": "Reset Password", @@ -149,6 +151,8 @@ static const Map en_US = { "msg": "Hello {} in the {} world ", "msg_named": "{} are written in the {lang} language", "clickMe": "Click me", + "human": "Human", + "resources": "Resources", "profile": { "reset_password": { "label": "Reset Password", diff --git a/lib/ui/app_bar.dart b/lib/ui/app_bar.dart index 0823a5a..c39e249 100644 --- a/lib/ui/app_bar.dart +++ b/lib/ui/app_bar.dart @@ -6,10 +6,11 @@ import 'package:mohem_flutter_app/extensions/string_extensions.dart'; AppBar appBar(BuildContext context, {required String title}) { return AppBar( - title: title.toText24(textColor: MyColors.darkTextColor), + title: title.toText24(color: MyColors.darkTextColor), centerTitle: false, automaticallyImplyLeading: false, backgroundColor: Colors.white, + actions: [ IconButton( onPressed: () { diff --git a/lib/ui/landing/dashboard.dart b/lib/ui/landing/dashboard.dart index 568ba30..795460d 100644 --- a/lib/ui/landing/dashboard.dart +++ b/lib/ui/landing/dashboard.dart @@ -207,7 +207,9 @@ class _DashboardState extends State { ) ], ).paddingOnly(left: 10, right: 10, bottom: 6, top: 6), - ); + ).onPress(() { + Navigator.pushNamed(context, AppRoutes.workList); + }); }, ), ), diff --git a/lib/ui/landing/today_attendance_screen.dart b/lib/ui/landing/today_attendance_screen.dart index d58fc4c..0ba3791 100644 --- a/lib/ui/landing/today_attendance_screen.dart +++ b/lib/ui/landing/today_attendance_screen.dart @@ -1,8 +1,10 @@ import 'package:easy_localization/src/public_ext.dart'; import 'package:flutter/material.dart'; +import 'package:flutter_svg/flutter_svg.dart'; import 'package:mohem_flutter_app/classes/colors.dart'; import 'package:mohem_flutter_app/extensions/int_extensions.dart'; import 'package:mohem_flutter_app/extensions/string_extensions.dart'; +import 'package:mohem_flutter_app/extensions/widget_extensions.dart'; import 'package:mohem_flutter_app/generated/locale_keys.g.dart'; import 'package:mohem_flutter_app/widgets/circular_step_progress_bar.dart'; @@ -106,7 +108,22 @@ class _TodayAttendanceScreenState extends State { child: Column( crossAxisAlignment: CrossAxisAlignment.start, mainAxisSize: MainAxisSize.min, - children: ["Mark".tr().toText12(), "Attendance".tr().toText24(), "Select the method to mark the attendance".tr().toText12(color: Color(0xff535353)), 24.height], + children: [ + "Mark".tr().toText12(), + "Attendance".tr().toText24(), + "Select the method to mark the attendance".tr().toText12(color: Color(0xff535353)), + 24.height, + GridView( + physics: const NeverScrollableScrollPhysics(), + shrinkWrap: true, + padding: EdgeInsets.zero, + gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(crossAxisCount: 3, childAspectRatio: 1 / 1, crossAxisSpacing: 8, mainAxisSpacing: 8), + children: [ + attendanceMethod("NFC", "assets/images/nfc.svg", () {}), + attendanceMethod("Wifi", "assets/images/wufu.svg", () {}), + ], + ) + ], ), ), // Positioned( @@ -135,6 +152,21 @@ class _TodayAttendanceScreenState extends State { ); } + Widget attendanceMethod(String title, String image, VoidCallback onPress) => Container( + padding: const EdgeInsets.only(left: 10, right: 10, top: 14, bottom: 14), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(15), + gradient: const LinearGradient(transform: GradientRotation(.64), begin: Alignment.topRight, end: Alignment.bottomRight, colors: [ + MyColors.gradiantEndColor, + MyColors.gradiantStartColor, + ]), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [Expanded(child: SvgPicture.asset(image)), title.toText17(isBold: true, color: Colors.white)], + ), + ).onPress(onPress); + Widget commonStatusView(String title, String time) => Expanded( child: Column(mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, children: [ title.toText12(color: Colors.white), diff --git a/lib/ui/work_list/missing_swipe/missing_swipe_screen.dart b/lib/ui/work_list/missing_swipe/missing_swipe_screen.dart index 850f565..4af620e 100644 --- a/lib/ui/work_list/missing_swipe/missing_swipe_screen.dart +++ b/lib/ui/work_list/missing_swipe/missing_swipe_screen.dart @@ -23,17 +23,11 @@ class MissingSwipeScreen extends StatelessWidget { children: [ Container( decoration: BoxDecoration( - borderRadius: BorderRadius.only( - bottomLeft: Radius.circular(20), - bottomRight: Radius.circular(20)), - gradient: LinearGradient( - transform: GradientRotation(.46), - begin: Alignment.topRight, - end: Alignment.bottomRight, - colors: [ - MyColors.gradiantEndColor, - MyColors.gradiantStartColor, - ]), + borderRadius: BorderRadius.only(bottomLeft: Radius.circular(20), bottomRight: Radius.circular(20)), + gradient: LinearGradient(transform: GradientRotation(.46), begin: Alignment.topRight, end: Alignment.bottomRight, colors: [ + MyColors.gradiantEndColor, + MyColors.gradiantStartColor, + ]), ), clipBehavior: Clip.antiAlias, child: TabBar( diff --git a/lib/ui/work_list/work_list_screen.dart b/lib/ui/work_list/work_list_screen.dart index c3de75f..06a545c 100644 --- a/lib/ui/work_list/work_list_screen.dart +++ b/lib/ui/work_list/work_list_screen.dart @@ -52,7 +52,7 @@ class _WorkListScreenState extends State { borderRadius: BorderRadius.circular(6), color: tabList[index].isSelected ? MyColors.darkIconColor - : MyColors.darkWhiteColor, + : MyColors.lightGreyEAColor, ), child: tabList[index].title.toText12( color: tabList[index].isSelected