diff --git a/lib/core/routes/route_name.dart b/lib/core/routes/route_name.dart index 13b4df3..a442173 100644 --- a/lib/core/routes/route_name.dart +++ b/lib/core/routes/route_name.dart @@ -29,7 +29,7 @@ class RouteName { //Portfolio details static const String porfolioDetails = 'porfolioDetails'; - //Portfolio details + //Portfolio details static const String academyDetails = 'academyDetails'; //Biometric @@ -41,4 +41,10 @@ class RouteName { //Pin Screen static const String pinScreen = 'pinScreen'; static const String confirmPinScreen = 'confirmPinScreen'; + + //Forgot Password + + static const String forgotPasswordPhoneVerificationScreen = + 'forgotPasswordPhoneVerificationScreen'; + static const String forgotPasswordScreen = 'forgotPasswordScreen'; } diff --git a/lib/core/routes/routes.dart b/lib/core/routes/routes.dart index 0fbd531..d4f431e 100644 --- a/lib/core/routes/routes.dart +++ b/lib/core/routes/routes.dart @@ -5,17 +5,19 @@ import 'package:tanami_app/core/routes/route_name.dart'; import 'package:tanami_app/features/MainScreens/Academy/presentation/pages/detailsScreen.dart'; import 'package:tanami_app/features/MainScreens/MainScreen.dart'; -import 'package:tanami_app/features/MainScreens/Portfolio/presentation/pages/detailsScreen.dart'; +import 'package:tanami_app/features/MainScreens/Portfolio/presentation/pages/portfolio_details_screen.dart'; import 'package:tanami_app/features/biometric/presentation/pages/biometric_screen.dart'; import 'package:tanami_app/features/countrySelection/presentation/pages/choose_country_screen.dart'; +import 'package:tanami_app/features/forgotPassword/presentation/pages/restore_password_screen.dart'; import 'package:tanami_app/features/otpVerification/presentation/pages/otp_screen.dart'; import 'package:tanami_app/features/register/presentation/pages/register_screen.dart'; import 'package:tanami_app/features/register/presentation/pages/register_step_screen.dart'; import 'package:tanami_app/features/securePin/presentation/pages/pin_screen.dart'; import 'package:tanami_app/features/welcome/presentation/pages/weclome_screen.dart'; +import '../../features/forgotPassword/presentation/pages/restore_password_phone_verification_screen.dart'; import '../../features/login/presentation/pages/login_screen.dart'; import '../../features/register/presentation/pages/register_user_details_screen.dart'; import '../../features/securePin/presentation/pages/confirm_pin_screen.dart'; @@ -99,9 +101,11 @@ final goRouter = GoRouter( ), GoRoute( name: RouteName.otpScreen, - path: RouteName.otpScreen, + path: "${RouteName.otpScreen}/:fromScreen", builder: (context, state) { - return const OtpScreen(); + return OtpScreen( + fromScreen: state.pathParameters["fromScreen"]!, + ); }, ), GoRoute( @@ -134,6 +138,20 @@ final goRouter = GoRouter( return const ConfirmPinScreen(); }, ), + GoRoute( + name: RouteName.forgotPasswordPhoneVerificationScreen, + path: RouteName.forgotPasswordPhoneVerificationScreen, + builder: (context, state) { + return const RestorePasswordPhoneVerificationScreen(); + }, + ), + GoRoute( + name: RouteName.forgotPasswordScreen, + path: RouteName.forgotPasswordScreen, + builder: (context, state) { + return const RestorePasswordScreen(); + }, + ), ], ), // GoRoute( diff --git a/lib/core/styles/app_color.dart b/lib/core/styles/app_color.dart index 3a7cd4a..50cf8bb 100644 --- a/lib/core/styles/app_color.dart +++ b/lib/core/styles/app_color.dart @@ -52,4 +52,10 @@ class AppColor { static const Color pinFillColor = Color(0xFFC9D9CB); static const Color pinFillBorderColor = Color(0xFF648774); static const Color pinInActiveBorderColor = Color(0xFFDFDFE3); + + //Portfolio Color + static const Color portfolioCardBgColor = Color(0xFFF8F8F8); + static const Color negativePercentageColor = Color(0xFFde9595); + static const Color statusTextColor = Color(0xFF0FA4A4); + static const Color portoflioCardTextColor = Color(0xFF535353); } diff --git a/lib/core/styles/app_images.dart b/lib/core/styles/app_images.dart index feab15b..4165fde 100644 --- a/lib/core/styles/app_images.dart +++ b/lib/core/styles/app_images.dart @@ -56,4 +56,11 @@ class AppImages { //Dialog static const String exitAppIcon = "assets/images/dialog/svg/exit_icon.svg"; + + //Portfolio + static const String portfolioBg = 'assets/images/portfolio_screen/bg.png'; + static const String portfolioClock = + 'assets/images/portfolio_screen/clock.png'; + static const String portfolioClockOff = + 'assets/images/portfolio_screen/clock_off.png'; } diff --git a/lib/core/styles/app_text.dart b/lib/core/styles/app_text.dart index 4c1ffb6..17fcef8 100644 --- a/lib/core/styles/app_text.dart +++ b/lib/core/styles/app_text.dart @@ -19,6 +19,8 @@ class AppText { static const String welcomeText = "Welcome back!"; static const String pleaseEnterLoginDetails = "Please enter your login details to get started"; + static const String toGetYourAccountPleaseSetYourInfo = + "To get your account please set your info"; static const String countryOfResidence = "Country of residence"; static const String chooseCountry = "Choose country"; static const String phoneNumber = "Phone Number"; @@ -86,9 +88,6 @@ class AppText { //Academy static const String videosTitle = "Videos"; - //Wallet - static const String walletTitle = "Wallet balance"; - //Dialog static const String exitText = "Exit"; static const String cancelText = "Cancel"; @@ -106,6 +105,7 @@ class AppText { //Pin Code static const String pinCode = "Pin Code"; static const String createPinCode = "Create Pin Code"; + static const String changePinCode = "Change Pin Code"; static const String confirmPinCode = "Confirm Pin Code"; static const String incorrectPinCode = "Incorrect PIN. Please try again."; static const String welcomeBackText = "Welcome back"; @@ -118,4 +118,15 @@ class AppText { "To restore PIN you will be Logged out"; static const String allowText = "Allow"; static const String declineText = "Decline"; + + //Forgot Password + + static const String restorePasswordText = "Restore password"; + static const String toRestorePasswordPleaseEnterPhoneNumber = + "To restore password please enter phone number"; + static const String createNewPasswordText = "Create new password"; + static const String submitText = "Submit"; + + //Wallet + static const String walletTitle = "Wallet balance"; } diff --git a/lib/features/MainScreens/MainScreen.dart b/lib/features/MainScreens/MainScreen.dart index 14b577f..50e94db 100644 --- a/lib/features/MainScreens/MainScreen.dart +++ b/lib/features/MainScreens/MainScreen.dart @@ -1,7 +1,7 @@ import 'package:flutter/material.dart'; import 'package:tanami_app/features/MainScreens/Academy/presentation/pages/academyScreen.dart'; import 'package:tanami_app/features/MainScreens/Invest/presentation/pages/investScreen.dart'; -import 'package:tanami_app/features/MainScreens/Portfolio/presentation/pages/portfolioScreen.dart'; +import 'package:tanami_app/features/MainScreens/Portfolio/presentation/pages/portfolio_screen.dart'; import 'package:tanami_app/features/MainScreens/Settings/presentation/pages/settingsScreen.dart'; import 'package:tanami_app/features/MainScreens/Wallet/presentation/pages/walletScreen.dart'; import 'package:tanami_app/shared/components/common_bottom_navigation.dart'; diff --git a/lib/features/MainScreens/Portfolio/domain/model/portfolio_model.dart b/lib/features/MainScreens/Portfolio/domain/model/portfolio_model.dart new file mode 100644 index 0000000..899f46b --- /dev/null +++ b/lib/features/MainScreens/Portfolio/domain/model/portfolio_model.dart @@ -0,0 +1,64 @@ +import 'package:json_annotation/json_annotation.dart'; + +part 'portfolio_model.g.dart'; + +@JsonSerializable() +class PortfolioModel { + final String date; + final String title; + final String status; + final String investmentAmountSAR; + final String investmentAmountUSD; + final String currentValuationSAR; + final String currentValuationUSD; + final String totalReturnPercentage; + + PortfolioModel({ + required this.date, + required this.title, + required this.status, + required this.investmentAmountSAR, + required this.investmentAmountUSD, + required this.currentValuationSAR, + required this.currentValuationUSD, + required this.totalReturnPercentage, + }); + + factory PortfolioModel.fromJson(Map json) => + _$PortfolioModelFromJson(json); + + Map toJson() => _$PortfolioModelToJson(this); +} + +List portfolioModelList = [ + PortfolioModel( + date: "Mar 01 2024", + title: "Private equity portfolio I", + status: "Pending", + investmentAmountSAR: "SAR 100,000", + investmentAmountUSD: "26,700", + currentValuationSAR: "SAR 100,000", + currentValuationUSD: "26,700", + totalReturnPercentage: "20.0%", + ), + PortfolioModel( + date: "Sept 1 2023", + title: "Real estate portfolio IV", + status: "Pending", + investmentAmountSAR: "SAR 50,000", + investmentAmountUSD: "13,350", + currentValuationSAR: "SAR 55,000", + currentValuationUSD: "14,685", + totalReturnPercentage: "10.0%", + ), + PortfolioModel( + date: "Jul 10 2025", + title: "Real estate III", + status: "Exited", + investmentAmountSAR: "SAR 10,000", + investmentAmountUSD: "2,670", + currentValuationSAR: "SAR 8,000", + currentValuationUSD: "2,136", + totalReturnPercentage: "20.0%", + ) +]; diff --git a/lib/features/MainScreens/Portfolio/domain/model/portfolio_model.g.dart b/lib/features/MainScreens/Portfolio/domain/model/portfolio_model.g.dart new file mode 100644 index 0000000..2da3608 --- /dev/null +++ b/lib/features/MainScreens/Portfolio/domain/model/portfolio_model.g.dart @@ -0,0 +1,31 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'portfolio_model.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +PortfolioModel _$PortfolioModelFromJson(Map json) => + PortfolioModel( + date: json['date'] as String, + title: json['title'] as String, + status: json['status'] as String, + investmentAmountSAR: json['investmentAmountSAR'] as String, + investmentAmountUSD: json['investmentAmountUSD'] as String, + currentValuationSAR: json['currentValuationSAR'] as String, + currentValuationUSD: json['currentValuationUSD'] as String, + totalReturnPercentage: json['totalReturnPercentage'] as String, + ); + +Map _$PortfolioModelToJson(PortfolioModel instance) => + { + 'date': instance.date, + 'title': instance.title, + 'status': instance.status, + 'investmentAmountSAR': instance.investmentAmountSAR, + 'investmentAmountUSD': instance.investmentAmountUSD, + 'currentValuationSAR': instance.currentValuationSAR, + 'currentValuationUSD': instance.currentValuationUSD, + 'totalReturnPercentage': instance.totalReturnPercentage, + }; diff --git a/lib/features/MainScreens/Portfolio/domain/model/protfolio_detail_model.dart b/lib/features/MainScreens/Portfolio/domain/model/protfolio_detail_model.dart new file mode 100644 index 0000000..e69de29 diff --git a/lib/features/MainScreens/Portfolio/presentation/pages/portfolio_details_layout.dart b/lib/features/MainScreens/Portfolio/presentation/pages/portfolio_details_layout.dart new file mode 100644 index 0000000..e69de29 diff --git a/lib/features/MainScreens/Portfolio/presentation/pages/detailsScreen.dart b/lib/features/MainScreens/Portfolio/presentation/pages/portfolio_details_screen.dart similarity index 100% rename from lib/features/MainScreens/Portfolio/presentation/pages/detailsScreen.dart rename to lib/features/MainScreens/Portfolio/presentation/pages/portfolio_details_screen.dart diff --git a/lib/features/MainScreens/Portfolio/presentation/pages/portfolio_layout.dart b/lib/features/MainScreens/Portfolio/presentation/pages/portfolio_layout.dart new file mode 100644 index 0000000..9fdd72d --- /dev/null +++ b/lib/features/MainScreens/Portfolio/presentation/pages/portfolio_layout.dart @@ -0,0 +1,106 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:gap/gap.dart'; +import 'package:tanami_app/core/styles/app_color.dart'; +import 'package:tanami_app/core/styles/app_images.dart'; +import 'package:tanami_app/features/MainScreens/Portfolio/domain/model/portfolio_model.dart'; +import 'package:tanami_app/features/MainScreens/Portfolio/presentation/widgets/exited_card.dart'; +import 'package:tanami_app/features/MainScreens/Portfolio/presentation/widgets/pending_card.dart'; +import 'package:tanami_app/shared/components/text_widget.dart'; + +import '../../../../../core/styles/app_text.dart'; + +class PortfolioLayout extends StatelessWidget { + const PortfolioLayout({super.key}); +//2339.712 - 2326.866 + @override + Widget build(BuildContext context) { + return Scaffold( + body: CustomScrollView( + slivers: [ + SliverAppBar( + elevation: 0, + scrolledUnderElevation: 0, + expandedHeight: 230.0, + automaticallyImplyLeading: false, + snap: false, + pinned: true, + floating: false, + flexibleSpace: LayoutBuilder( + builder: (BuildContext context, BoxConstraints constraints) { + var top = constraints.biggest.height; + var percentage = + ((top - kToolbarHeight) / (200.0 - kToolbarHeight)) + .clamp(0.0, 1.0); + var opacity = (1 - percentage).clamp(0.0, 1.0); + return FlexibleSpaceBar( + // centerTitle: true, + titlePadding: + const EdgeInsets.only(left: 30, top: 0, bottom: 15), + title: Opacity( + opacity: opacity, + child: Row( + children: [ + TextWidget().text12W700( + AppText.portfolio, + clr: const Color(0xFF888888), + ), + TextWidget().text14W700( + 'SAR 178,000', + clr: AppColor.plainBlack, + ), + ], + ), + ), + background: Stack( + fit: StackFit.expand, + children: [ + Image.asset( + AppImages.portfolioBg, + fit: BoxFit.cover, + ), + Padding( + padding: const EdgeInsets.only(left: 40.0), + child: Opacity( + opacity: percentage, + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + TextWidget().text14W700( + AppText.portfolio, + clr: const Color(0xFFC9D9CB), + ), + Gap( + 8.h, + ), + TextWidget().text28W700( + 'SAR 178,000', + ), + ], + ), + ), + ), + ], + ), + ); + }), + backgroundColor: Colors.white, + ), + SliverList( + delegate: SliverChildBuilderDelegate( + (context, index) => portfolioModelList[index].status == "Exited" + ? ExitedCard( + portfolioModel: portfolioModelList[index], + ) + : PendingCard( + portfolioModel: portfolioModelList[index], + ), + childCount: 3, + ), + ), + ], + ), + ); + } +} diff --git a/lib/features/MainScreens/Portfolio/presentation/pages/portfolio_screen.dart b/lib/features/MainScreens/Portfolio/presentation/pages/portfolio_screen.dart new file mode 100644 index 0000000..bd8c978 --- /dev/null +++ b/lib/features/MainScreens/Portfolio/presentation/pages/portfolio_screen.dart @@ -0,0 +1,16 @@ +import 'package:flutter/material.dart'; +import 'package:tanami_app/features/MainScreens/Portfolio/presentation/pages/portfolio_layout.dart'; + +class PortfolioScreen extends StatefulWidget { + const PortfolioScreen({super.key}); + + @override + State createState() => _PortfolioScreenState(); +} + +class _PortfolioScreenState extends State { + @override + Widget build(BuildContext context) { + return const Scaffold(body: PortfolioLayout()); + } +} diff --git a/lib/features/MainScreens/Portfolio/presentation/widgets/exited_card.dart b/lib/features/MainScreens/Portfolio/presentation/widgets/exited_card.dart new file mode 100644 index 0000000..638d3f1 --- /dev/null +++ b/lib/features/MainScreens/Portfolio/presentation/widgets/exited_card.dart @@ -0,0 +1,173 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:gap/gap.dart'; +import 'package:tanami_app/features/MainScreens/Portfolio/domain/model/portfolio_model.dart'; + +import '../../../../../core/routes/route_name.dart'; +import '../../../../../core/routes/routes.dart'; +import '../../../../../core/styles/app_color.dart'; +import '../../../../../core/styles/app_images.dart'; +import '../../../../../core/styles/app_text.dart'; +import '../../../../../shared/components/text_widget.dart'; + +class ExitedCard extends StatelessWidget { + final PortfolioModel portfolioModel; + const ExitedCard({ + super.key, + required this.portfolioModel, + }); + + @override + Widget build(BuildContext context) { + return Padding( + padding: const EdgeInsets.all(10.0), + child: GestureDetector( + onTap: () { + goRouter.pushNamed(RouteName.porfolioDetails); + }, + child: Container( + decoration: BoxDecoration( + color: AppColor.plainWhite, + borderRadius: const BorderRadius.all(Radius.circular(20.0)), + boxShadow: [ + BoxShadow( + color: AppColor.plainBlack.withOpacity(0.15), + spreadRadius: 2, + blurRadius: 10, + offset: const Offset(0, 3), // changes position of shadow + ), + ], + ), + child: Column( + children: [ + Padding( + padding: const EdgeInsets.symmetric( + horizontal: 20.0, vertical: 16.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + Image.asset( + AppImages.portfolioClockOff, + height: 25.sp, + ), + Gap( + 5.w, + ), + TextWidget().text12W500( + portfolioModel.date, + clr: AppColor.primaryColor2, + ), + ], + ), + TextWidget().text14W700( + portfolioModel.status, + clr: AppColor.hintTextColor, + ), + ], + ), + Gap( + 8.h, + ), + TextWidget().text17W700( + portfolioModel.title, + clr: AppColor.plainBlack, + ), + ], + ), + ), + Container( + decoration: const BoxDecoration( + color: AppColor.portfolioCardBgColor, + borderRadius: BorderRadius.only( + bottomLeft: Radius.circular(20.0), + bottomRight: Radius.circular(20.0), + ), + ), + child: Padding( + padding: const EdgeInsets.symmetric( + horizontal: 20.0, vertical: 16.0), + child: Column( + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + TextWidget().text14W500( + AppText.investmentamount, + clr: AppColor.hintTextColor, + ), + Column( + crossAxisAlignment: CrossAxisAlignment.end, + children: [ + TextWidget().text14W700( + portfolioModel.investmentAmountSAR, + clr: AppColor.hintTextColor, + ), + TextWidget().text11W400( + '\$ ${portfolioModel.investmentAmountUSD}', + clr: AppColor.hintTextColor, + ), + ], + ) + ], + ), + Gap( + 8.h, + ), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + TextWidget().text14W500( + AppText.currentval, + clr: AppColor.hintTextColor, + ), + Column( + crossAxisAlignment: CrossAxisAlignment.end, + children: [ + TextWidget().text14W700( + portfolioModel.currentValuationSAR, + clr: AppColor.hintTextColor, + ), + TextWidget().text11W400( + '\$ ${portfolioModel.currentValuationUSD}', + clr: AppColor.hintTextColor, + ), + ], + ) + ], + ), + SizedBox( + height: 8.h, + ), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + TextWidget().text14W500( + AppText.totalreturn, + clr: AppColor.hintTextColor, + ), + TextWidget().text14W700( + '- ${portfolioModel.totalReturnPercentage}', + clr: AppColor.negativePercentageColor, + ), + ], + ) + ], + ), + ), + ) + ], + ), + ), + ), + ); + } +} diff --git a/lib/features/MainScreens/Portfolio/presentation/widgets/pending_card.dart b/lib/features/MainScreens/Portfolio/presentation/widgets/pending_card.dart new file mode 100644 index 0000000..6ee14dd --- /dev/null +++ b/lib/features/MainScreens/Portfolio/presentation/widgets/pending_card.dart @@ -0,0 +1,173 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:gap/gap.dart'; +import 'package:tanami_app/features/MainScreens/Portfolio/domain/model/portfolio_model.dart'; + +import '../../../../../core/routes/route_name.dart'; +import '../../../../../core/routes/routes.dart'; +import '../../../../../core/styles/app_color.dart'; +import '../../../../../core/styles/app_images.dart'; +import '../../../../../core/styles/app_text.dart'; +import '../../../../../shared/components/text_widget.dart'; + +class PendingCard extends StatelessWidget { + final PortfolioModel portfolioModel; + const PendingCard({ + super.key, + required this.portfolioModel, + }); + + @override + Widget build(BuildContext context) { + return Padding( + padding: const EdgeInsets.all(10.0), + child: GestureDetector( + onTap: () { + goRouter.pushNamed(RouteName.porfolioDetails); + }, + child: Container( + decoration: BoxDecoration( + color: AppColor.plainWhite, + borderRadius: const BorderRadius.all(Radius.circular(20.0)), + boxShadow: [ + BoxShadow( + color: AppColor.plainBlack.withOpacity(0.15), + spreadRadius: 2, + blurRadius: 10, + offset: const Offset(0, 3), // changes position of shadow + ), + ], + ), + child: Column( + children: [ + Padding( + padding: const EdgeInsets.symmetric( + horizontal: 20.0, vertical: 16.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + Image.asset( + AppImages.portfolioClock, + height: 25.sp, + ), + Gap( + 5.w, + ), + TextWidget().text12W500( + portfolioModel.date, + clr: AppColor.primaryColor2, + ), + ], + ), + TextWidget().text14W700( + portfolioModel.status, + clr: AppColor.statusTextColor, + ), + ], + ), + Gap( + 8.h, + ), + TextWidget().text17W700( + portfolioModel.title, + clr: AppColor.plainBlack, + ), + ], + ), + ), + Container( + decoration: const BoxDecoration( + color: AppColor.portfolioCardBgColor, + borderRadius: BorderRadius.only( + bottomLeft: Radius.circular(20.0), + bottomRight: Radius.circular(20.0), + ), + ), + child: Padding( + padding: const EdgeInsets.symmetric( + horizontal: 20.0, vertical: 16.0), + child: Column( + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + TextWidget().text14W500( + AppText.investmentamount, + clr: AppColor.portoflioCardTextColor, + ), + Column( + crossAxisAlignment: CrossAxisAlignment.end, + children: [ + TextWidget().text14W700( + portfolioModel.investmentAmountSAR, + clr: AppColor.plainBlack, + ), + TextWidget().text11W400( + ' \$ ${portfolioModel.investmentAmountUSD}', + clr: Colors.black, + ), + ], + ) + ], + ), + Gap( + 8.h, + ), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + TextWidget().text14W500( + AppText.currentval, + clr: AppColor.portoflioCardTextColor, + ), + Column( + crossAxisAlignment: CrossAxisAlignment.end, + children: [ + TextWidget().text14W700( + portfolioModel.currentValuationSAR, + clr: AppColor.plainBlack, + ), + TextWidget().text11W400( + ' \$ ${portfolioModel.currentValuationUSD}', + clr: AppColor.plainBlack, + ), + ], + ) + ], + ), + SizedBox( + height: 8.h, + ), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + TextWidget().text14W500( + AppText.totalreturn, + clr: const Color(0xFF535353), + ), + TextWidget().text14W700( + '+ ${portfolioModel.totalReturnPercentage}', + clr: const Color(0xFF066123), + ), + ], + ) + ], + ), + ), + ) + ], + ), + ), + ), + ); + } +} diff --git a/lib/features/forgotPassword/presentation/bloc/restore_password_bloc.dart b/lib/features/forgotPassword/presentation/bloc/restore_password_bloc.dart new file mode 100644 index 0000000..49d732e --- /dev/null +++ b/lib/features/forgotPassword/presentation/bloc/restore_password_bloc.dart @@ -0,0 +1,76 @@ +import 'package:bloc/bloc.dart'; +import 'package:flutter/material.dart'; + +import 'restore_password_event.dart'; +import 'restore_password_state.dart'; + +class RestorePasswordBloc + extends Bloc { + final GlobalKey formKey = GlobalKey(); + final TextEditingController passwordTextField = TextEditingController(); + final TextEditingController repeatPasswordTextField = TextEditingController(); + + GlobalKey getFormKey() { + return formKey; + } + + RestorePasswordBloc() : super(RestorePasswordInitial()) { + passwordTextField.addListener(_onFormFieldChanged); + repeatPasswordTextField.addListener(_onFormFieldChanged); + on(_onLoginFormChanged); + on((event, emit) async { + if (!formKey.currentState!.validate()) { + return; + } + emit(RestorePasswordLoading()); + try { + // Simulate API call + await Future.delayed(const Duration(seconds: 2)); + // Replace the next line with actual API call + final isSuccess = await _mockLoginApi(event.password); + if (isSuccess) { + emit(RestorePasswordSuccess()); + } else { + emit(const RestorePasswordFailure( + "Failed. Please check your credentials.")); + } + } catch (e) { + emit(RestorePasswordFailure(e.toString())); + } + }); + } + void _onFormFieldChanged() { + add(RestorePasswordFormChanged( + passwordTextField.text, + repeatPasswordTextField.text, + )); + } + + void _onLoginFormChanged( + RestorePasswordFormChanged event, Emitter emit) { + final areFieldsFilled = + event.password.isNotEmpty && event.repeatPassword.isNotEmpty; + emit(RestorePasswordFieldsState(areFieldsFilled)); + } + + // Method to reset text fields + void resetFields() { + passwordTextField.clear(); + repeatPasswordTextField.clear(); + } + + // Mock API function, replace with actual API call + Future _mockLoginApi( + String phoneNumber, + ) async { + return true; + } + + @override + Future close() { + passwordTextField.dispose(); + repeatPasswordTextField.dispose(); + + return super.close(); + } +} diff --git a/lib/features/forgotPassword/presentation/bloc/restore_password_event.dart b/lib/features/forgotPassword/presentation/bloc/restore_password_event.dart new file mode 100644 index 0000000..1ee3d13 --- /dev/null +++ b/lib/features/forgotPassword/presentation/bloc/restore_password_event.dart @@ -0,0 +1,31 @@ +import 'package:equatable/equatable.dart'; + +abstract class RestorePasswordEvent extends Equatable { + const RestorePasswordEvent(); + + @override + List get props => []; +} + +class RestorePasswordSubmitted extends RestorePasswordEvent { + final String password; + final String repeatPassword; + + const RestorePasswordSubmitted( + this.password, + this.repeatPassword, + ); + + @override + List get props => [password, repeatPassword]; +} + +class RestorePasswordFormChanged extends RestorePasswordEvent { + final String password; + final String repeatPassword; + + const RestorePasswordFormChanged(this.password, this.repeatPassword); + + @override + List get props => [password, repeatPassword]; +} diff --git a/lib/features/forgotPassword/presentation/bloc/restore_password_phone_verification_bloc.dart b/lib/features/forgotPassword/presentation/bloc/restore_password_phone_verification_bloc.dart new file mode 100644 index 0000000..fee4a10 --- /dev/null +++ b/lib/features/forgotPassword/presentation/bloc/restore_password_phone_verification_bloc.dart @@ -0,0 +1,79 @@ +import 'package:bloc/bloc.dart'; +import 'package:flutter/material.dart'; + +import 'restore_password_phone_verification_event.dart'; +import 'restore_password_phone_verification_state.dart'; + +class RestorePasswordPhoneVerificationBloc extends Bloc< + RestorePasswordPhoneVerificationEvent, + RestorePasswordPhoneVerificationState> { + final GlobalKey formKey = GlobalKey(); + final TextEditingController phoneNumberTextField = TextEditingController(); + final TextEditingController countrySelectionTextField = + TextEditingController(); + + GlobalKey getFormKey() { + return formKey; + } + + RestorePasswordPhoneVerificationBloc() + : super(RestorePasswordPhoneVerificationInitial()) { + phoneNumberTextField.addListener(_onFormFieldChanged); + countrySelectionTextField.addListener(_onFormFieldChanged); + on(_onLoginFormChanged); + on((event, emit) async { + if (!formKey.currentState!.validate()) { + return; + } + emit(RestorePasswordPhoneVerificationLoading()); + try { + // Simulate API call + await Future.delayed(const Duration(seconds: 2)); + // Replace the next line with actual API call + final isSuccess = await _mockLoginApi(event.phoneNumber); + if (isSuccess) { + emit(RestorePasswordPhoneVerificationSuccess()); + } else { + emit(const RestorePasswordPhoneVerificationFailure( + "Failed. Please check your credentials.")); + } + } catch (e) { + emit(RestorePasswordPhoneVerificationFailure(e.toString())); + } + }); + } + void _onFormFieldChanged() { + add(RestorePasswordPhoneVerificationFormChanged( + phoneNumberTextField.text, + countrySelectionTextField.text, + )); + } + + void _onLoginFormChanged(RestorePasswordPhoneVerificationFormChanged event, + Emitter emit) { + final areFieldsFilled = + event.phoneNumber.isNotEmpty && event.country.isNotEmpty; + emit(RestorePasswordPhoneVerificationFieldsState(areFieldsFilled)); + } + + // Method to reset text fields + void resetFields() { + phoneNumberTextField.clear(); + countrySelectionTextField.clear(); + } + + // Mock API function, replace with actual API call + Future _mockLoginApi( + String phoneNumber, + ) async { + return phoneNumber == "1234567891"; + } + + @override + Future close() { + phoneNumberTextField.dispose(); + countrySelectionTextField.dispose(); + + return super.close(); + } +} diff --git a/lib/features/forgotPassword/presentation/bloc/restore_password_phone_verification_event.dart b/lib/features/forgotPassword/presentation/bloc/restore_password_phone_verification_event.dart new file mode 100644 index 0000000..cc3dbcc --- /dev/null +++ b/lib/features/forgotPassword/presentation/bloc/restore_password_phone_verification_event.dart @@ -0,0 +1,34 @@ +import 'package:equatable/equatable.dart'; + +abstract class RestorePasswordPhoneVerificationEvent extends Equatable { + const RestorePasswordPhoneVerificationEvent(); + + @override + List get props => []; +} + +class RestorePasswordPhoneVerificationSubmitted + extends RestorePasswordPhoneVerificationEvent { + final String phoneNumber; + final String countryResidence; + + const RestorePasswordPhoneVerificationSubmitted( + this.phoneNumber, + this.countryResidence, + ); + + @override + List get props => [phoneNumber, countryResidence]; +} + +class RestorePasswordPhoneVerificationFormChanged + extends RestorePasswordPhoneVerificationEvent { + final String phoneNumber; + final String country; + + const RestorePasswordPhoneVerificationFormChanged( + this.phoneNumber, this.country); + + @override + List get props => [phoneNumber, country]; +} diff --git a/lib/features/forgotPassword/presentation/bloc/restore_password_phone_verification_state.dart b/lib/features/forgotPassword/presentation/bloc/restore_password_phone_verification_state.dart new file mode 100644 index 0000000..68ac7dc --- /dev/null +++ b/lib/features/forgotPassword/presentation/bloc/restore_password_phone_verification_state.dart @@ -0,0 +1,37 @@ +import 'package:equatable/equatable.dart'; + +abstract class RestorePasswordPhoneVerificationState extends Equatable { + const RestorePasswordPhoneVerificationState(); + + @override + List get props => []; +} + +class RestorePasswordPhoneVerificationInitial + extends RestorePasswordPhoneVerificationState {} + +class RestorePasswordPhoneVerificationLoading + extends RestorePasswordPhoneVerificationState {} + +class RestorePasswordPhoneVerificationSuccess + extends RestorePasswordPhoneVerificationState {} + +class RestorePasswordPhoneVerificationFailure + extends RestorePasswordPhoneVerificationState { + final String error; + + const RestorePasswordPhoneVerificationFailure(this.error); + + @override + List get props => [error]; +} + +class RestorePasswordPhoneVerificationFieldsState + extends RestorePasswordPhoneVerificationState { + final bool areFieldsFilled; + + const RestorePasswordPhoneVerificationFieldsState(this.areFieldsFilled); + + @override + List get props => [areFieldsFilled]; +} diff --git a/lib/features/forgotPassword/presentation/bloc/restore_password_state.dart b/lib/features/forgotPassword/presentation/bloc/restore_password_state.dart new file mode 100644 index 0000000..97c1591 --- /dev/null +++ b/lib/features/forgotPassword/presentation/bloc/restore_password_state.dart @@ -0,0 +1,32 @@ +import 'package:equatable/equatable.dart'; + +abstract class RestorePasswordState extends Equatable { + const RestorePasswordState(); + + @override + List get props => []; +} + +class RestorePasswordInitial extends RestorePasswordState {} + +class RestorePasswordLoading extends RestorePasswordState {} + +class RestorePasswordSuccess extends RestorePasswordState {} + +class RestorePasswordFailure extends RestorePasswordState { + final String error; + + const RestorePasswordFailure(this.error); + + @override + List get props => [error]; +} + +class RestorePasswordFieldsState extends RestorePasswordState { + final bool areFieldsFilled; + + const RestorePasswordFieldsState(this.areFieldsFilled); + + @override + List get props => [areFieldsFilled]; +} diff --git a/lib/features/forgotPassword/presentation/pages/restore_password_layout.dart b/lib/features/forgotPassword/presentation/pages/restore_password_layout.dart new file mode 100644 index 0000000..e1c0337 --- /dev/null +++ b/lib/features/forgotPassword/presentation/pages/restore_password_layout.dart @@ -0,0 +1,23 @@ +import 'package:flutter/material.dart'; +import 'package:gap/gap.dart'; + +import '../widgets/restore_password_bottom_section.dart'; +import '../widgets/restore_password_form.dart'; +import '../widgets/restore_password_top_section.dart'; + +class RestorePasswordLayout extends StatelessWidget { + const RestorePasswordLayout({super.key}); + + @override + Widget build(BuildContext context) { + return Scaffold( + body: ListView( + children: const [ + RestorePasswordTopSection(), + RestorePasswordForm(), + Gap(150), + RestorePasswordBottomSection(), + ], + )); + } +} diff --git a/lib/features/forgotPassword/presentation/pages/restore_password_phone_verification_layout.dart b/lib/features/forgotPassword/presentation/pages/restore_password_phone_verification_layout.dart new file mode 100644 index 0000000..1cb2d9c --- /dev/null +++ b/lib/features/forgotPassword/presentation/pages/restore_password_phone_verification_layout.dart @@ -0,0 +1,23 @@ +import 'package:flutter/material.dart'; +import 'package:gap/gap.dart'; +import 'package:tanami_app/features/forgotPassword/presentation/widgets/restore_password_phone_verification_bottom_section.dart'; +import 'package:tanami_app/features/forgotPassword/presentation/widgets/restore_password_phone_verification_top_section.dart'; + +import '../widgets/restore_password_phone_verification_form.dart'; + +class RestorePasswordPhoneVerificationLayout extends StatelessWidget { + const RestorePasswordPhoneVerificationLayout({super.key}); + + @override + Widget build(BuildContext context) { + return Scaffold( + body: ListView( + children: const [ + RestorePasswordPhoneVerificationTopSection(), + RestorePasswordPhoneVerificationForm(), + Gap(150), + RestorePasswordPhoneVerificationBottomSection(), + ], + )); + } +} diff --git a/lib/features/forgotPassword/presentation/pages/restore_password_phone_verification_screen.dart b/lib/features/forgotPassword/presentation/pages/restore_password_phone_verification_screen.dart new file mode 100644 index 0000000..8abd903 --- /dev/null +++ b/lib/features/forgotPassword/presentation/pages/restore_password_phone_verification_screen.dart @@ -0,0 +1,26 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:tanami_app/features/forgotPassword/presentation/pages/restore_password_phone_verification_layout.dart'; + +import '../bloc/restore_password_phone_verification_bloc.dart'; + +class RestorePasswordPhoneVerificationScreen extends StatelessWidget { + const RestorePasswordPhoneVerificationScreen({super.key}); + + @override + Widget build(BuildContext context) { + return Scaffold( + resizeToAvoidBottomInset: true, + body: MultiBlocProvider( + // Define the providers for the OnboardingBloc and other blocs + providers: [ + BlocProvider( + // Create an instance of the OnboardingBloc + create: (context) => RestorePasswordPhoneVerificationBloc(), + ), + ], + child: const RestorePasswordPhoneVerificationLayout(), + ), + ); + } +} diff --git a/lib/features/forgotPassword/presentation/pages/restore_password_screen.dart b/lib/features/forgotPassword/presentation/pages/restore_password_screen.dart new file mode 100644 index 0000000..8052e8f --- /dev/null +++ b/lib/features/forgotPassword/presentation/pages/restore_password_screen.dart @@ -0,0 +1,26 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; + +import '../bloc/restore_password_bloc.dart'; +import 'restore_password_layout.dart'; + +class RestorePasswordScreen extends StatelessWidget { + const RestorePasswordScreen({super.key}); + + @override + Widget build(BuildContext context) { + return Scaffold( + resizeToAvoidBottomInset: true, + body: MultiBlocProvider( + // Define the providers for the OnboardingBloc and other blocs + providers: [ + BlocProvider( + // Create an instance of the OnboardingBloc + create: (context) => RestorePasswordBloc(), + ), + ], + child: const RestorePasswordLayout(), + ), + ); + } +} diff --git a/lib/features/forgotPassword/presentation/widgets/restore_password_bottom_section.dart b/lib/features/forgotPassword/presentation/widgets/restore_password_bottom_section.dart new file mode 100644 index 0000000..5942f4a --- /dev/null +++ b/lib/features/forgotPassword/presentation/widgets/restore_password_bottom_section.dart @@ -0,0 +1,97 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:gap/gap.dart'; +import 'package:tanami_app/shared/components/loader.dart'; +import 'package:tanami_app/shared/components/toast_message.dart'; + +import '../../../../core/routes/route_name.dart'; +import '../../../../core/routes/routes.dart'; +import '../../../../core/styles/app_color.dart'; +import '../../../../core/styles/app_text.dart'; +import '../../../../shared/components/button_widget.dart'; +import '../../../../shared/components/text_widget.dart'; +import '../../../countrySelection/presentation/bloc/choose_country_bloc.dart'; +import '../bloc/restore_password_bloc.dart'; +import '../bloc/restore_password_event.dart'; +import '../bloc/restore_password_state.dart'; + +class RestorePasswordBottomSection extends StatelessWidget { + const RestorePasswordBottomSection({ + super.key, + }); + + @override + Widget build(BuildContext context) { + final radioBloc = context.read(); + return Column( + children: [ + BlocConsumer( + listener: (context, state) { + if (state is RestorePasswordLoading) { + Loader.loader(context); + } else if (state is RestorePasswordSuccess) { + successToastMessage(context, "Password resetted successful !"); + goRouter.pop(); + radioBloc.resetSelection(); + goRouter.goNamed(RouteName.loginScreen, pathParameters: { + "fromScreen": "registerStep", + }); + } else if (state is RestorePasswordFailure) { + goRouter.pop(); + errorToastMessage( + context, + state.error, + ); + } + }, + builder: (context, state) { + bool isButtonEnabled = false; + if (state is RestorePasswordFieldsState) { + isButtonEnabled = state.areFieldsFilled; + } else if (state is RestorePasswordSuccess || + state is RestorePasswordFailure) { + isButtonEnabled = true; + } + return Container( + margin: const EdgeInsets.symmetric( + horizontal: 16, + ), + width: 1.sw, + height: 56.h, + child: ButtonWidget().elevatedBtn( + txtClr: isButtonEnabled + ? AppColor.plainWhite + : AppColor.inactiveBtnTxtColor, + function: () { + isButtonEnabled + ? context.read().add( + RestorePasswordSubmitted( + context + .read() + .passwordTextField + .text, + ""), + ) + : null; + }, + text: AppText.submitText, + clr: isButtonEnabled + ? AppColor.primaryColor2 + : AppColor.inactiveBtnColor, + ), + ); + }, + ), + const Gap(5), + ButtonWidget().textBtn( + function: () { + goRouter.pop(); + }, + text: TextWidget().text14W700(AppText.backText, + clr: AppColor.textLabelColor, + textDecoration: TextDecoration.underline)), + ], + ); + } +} diff --git a/lib/features/forgotPassword/presentation/widgets/restore_password_form.dart b/lib/features/forgotPassword/presentation/widgets/restore_password_form.dart new file mode 100644 index 0000000..d6be1e7 --- /dev/null +++ b/lib/features/forgotPassword/presentation/widgets/restore_password_form.dart @@ -0,0 +1,58 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:gap/gap.dart'; +import 'package:tanami_app/core/styles/app_text.dart'; +import 'package:tanami_app/features/forgotPassword/presentation/bloc/restore_password_bloc.dart'; + +import '../../../../shared/components/bloc/password_field/password_visibility_bloc.dart'; +import '../../../../shared/components/form_label_textfield.dart'; + +class RestorePasswordForm extends StatelessWidget { + const RestorePasswordForm({super.key}); + + @override + Widget build(BuildContext context) { + final restorePasswordBloc = context.read(); + + // Reset fields when the screen is built + restorePasswordBloc.resetFields(); + + return Form( + key: restorePasswordBloc.formKey, + child: Padding( + padding: const EdgeInsets.symmetric( + horizontal: 14, + ), + child: Align( + alignment: Alignment.topLeft, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const Gap(50), + BlocProvider( + create: (_) => PasswordVisibilityBloc(), + child: FormLabelTextField( + hintText: AppText.enterPassword, + title: AppText.password, + type: AppText.password.toLowerCase(), + textEditingController: restorePasswordBloc.passwordTextField, + ), + ), + const Gap(12), + BlocProvider( + create: (_) => PasswordVisibilityBloc(), + child: FormLabelTextField( + hintText: AppText.repeatPasswordText, + title: AppText.repeatPasswordText, + type: AppText.password.toLowerCase(), + textEditingController: + restorePasswordBloc.repeatPasswordTextField, + ), + ), + ], + ), + ), + ), + ); + } +} diff --git a/lib/features/forgotPassword/presentation/widgets/restore_password_phone_verification_bottom_section.dart b/lib/features/forgotPassword/presentation/widgets/restore_password_phone_verification_bottom_section.dart new file mode 100644 index 0000000..4b18b50 --- /dev/null +++ b/lib/features/forgotPassword/presentation/widgets/restore_password_phone_verification_bottom_section.dart @@ -0,0 +1,100 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:gap/gap.dart'; +import 'package:tanami_app/shared/components/loader.dart'; +import 'package:tanami_app/shared/components/toast_message.dart'; + +import '../../../../core/routes/route_name.dart'; +import '../../../../core/routes/routes.dart'; +import '../../../../core/styles/app_color.dart'; +import '../../../../core/styles/app_text.dart'; +import '../../../../shared/components/button_widget.dart'; +import '../../../../shared/components/text_widget.dart'; +import '../../../countrySelection/presentation/bloc/choose_country_bloc.dart'; +import '../bloc/restore_password_phone_verification_bloc.dart'; +import '../bloc/restore_password_phone_verification_event.dart'; +import '../bloc/restore_password_phone_verification_state.dart'; + +class RestorePasswordPhoneVerificationBottomSection extends StatelessWidget { + const RestorePasswordPhoneVerificationBottomSection({ + super.key, + }); + + @override + Widget build(BuildContext context) { + final radioBloc = context.read(); + return Column( + children: [ + BlocConsumer( + listener: (context, state) { + if (state is RestorePasswordPhoneVerificationLoading) { + Loader.loader(context); + } else if (state is RestorePasswordPhoneVerificationSuccess) { + successToastMessage(context, "OTP send successful !"); + goRouter.pop(); + radioBloc.resetSelection(); + goRouter.pushNamed(RouteName.otpScreen, + pathParameters: {"fromScreen": "forgot-password"}); + } else if (state is RestorePasswordPhoneVerificationFailure) { + goRouter.pop(); + errorToastMessage( + context, + state.error, + ); + } + }, + builder: (context, state) { + bool isButtonEnabled = false; + if (state is RestorePasswordPhoneVerificationFieldsState) { + isButtonEnabled = state.areFieldsFilled; + } else if (state is RestorePasswordPhoneVerificationSuccess || + state is RestorePasswordPhoneVerificationFailure) { + isButtonEnabled = true; + } + return Container( + margin: const EdgeInsets.symmetric( + horizontal: 16, + ), + width: 1.sw, + height: 56.h, + child: ButtonWidget().elevatedBtn( + txtClr: isButtonEnabled + ? AppColor.plainWhite + : AppColor.inactiveBtnTxtColor, + function: () { + isButtonEnabled + ? context + .read() + .add( + RestorePasswordPhoneVerificationSubmitted( + context + .read< + RestorePasswordPhoneVerificationBloc>() + .phoneNumberTextField + .text, + ""), + ) + : null; + }, + text: AppText.nextText, + clr: isButtonEnabled + ? AppColor.primaryColor2 + : AppColor.inactiveBtnColor, + ), + ); + }, + ), + const Gap(5), + ButtonWidget().textBtn( + function: () { + goRouter.pop(); + }, + text: TextWidget().text14W700(AppText.backText, + clr: AppColor.textLabelColor, + textDecoration: TextDecoration.underline)), + ], + ); + } +} diff --git a/lib/features/forgotPassword/presentation/widgets/restore_password_phone_verification_form.dart b/lib/features/forgotPassword/presentation/widgets/restore_password_phone_verification_form.dart new file mode 100644 index 0000000..fa12f76 --- /dev/null +++ b/lib/features/forgotPassword/presentation/widgets/restore_password_phone_verification_form.dart @@ -0,0 +1,83 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:gap/gap.dart'; +import 'package:tanami_app/core/styles/app_text.dart'; +import 'package:tanami_app/core/utils/constant/country_flag_data.dart'; + +import '../../../../shared/components/form_label_textfield.dart'; +import '../../../countrySelection/presentation/bloc/choose_country_bloc.dart'; +import '../../../countrySelection/presentation/bloc/choose_country_state.dart'; +import '../bloc/restore_password_phone_verification_bloc.dart'; + +class RestorePasswordPhoneVerificationForm extends StatelessWidget { + const RestorePasswordPhoneVerificationForm({super.key}); + + @override + Widget build(BuildContext context) { + final restorePasswordBloc = + context.read(); + + // Reset fields when the screen is built + restorePasswordBloc.resetFields(); + + return BlocConsumer(listener: (context, state) { + int selectedCountry = -1; + if (state is RadioSelectionChanged) { + selectedCountry = state.selectedIndex; + restorePasswordBloc.countrySelectionTextField.text = + countryName[selectedCountry]; + } + }, builder: (context, state) { + int selectedCountry = -1; + if (state is RadioSelectionChanged) { + selectedCountry = state.selectedIndex; + } + return Form( + key: restorePasswordBloc.formKey, + child: Padding( + padding: const EdgeInsets.symmetric( + horizontal: 14, + ), + child: Align( + alignment: Alignment.topLeft, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const Gap(50), + FormLabelTextField( + prefixWidget: selectedCountry == -1 + ? null + : Image.asset( + countryFlag[selectedCountry], + width: 20, + height: 20, + ), + hintText: AppText.chooseCountry, + title: AppText.countryOfResidence, + type: "country selection", + textEditingController: + restorePasswordBloc.countrySelectionTextField, + ), + const Gap(20), + FormLabelTextField( + prefixWidget: selectedCountry == -1 + ? null + : Image.asset( + countryFlag[selectedCountry], + width: 20, + height: 20, + ), + hintText: "+0 (000) 000 00 00", + title: AppText.phoneNumber, + type: "phone number", + textEditingController: + restorePasswordBloc.phoneNumberTextField, + ), + ], + ), + ), + ), + ); + }); + } +} diff --git a/lib/features/forgotPassword/presentation/widgets/restore_password_phone_verification_top_section.dart b/lib/features/forgotPassword/presentation/widgets/restore_password_phone_verification_top_section.dart new file mode 100644 index 0000000..28c2dfb --- /dev/null +++ b/lib/features/forgotPassword/presentation/widgets/restore_password_phone_verification_top_section.dart @@ -0,0 +1,41 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_svg/flutter_svg.dart'; +import 'package:gap/gap.dart'; +import 'package:tanami_app/core/styles/app_color.dart'; +import 'package:tanami_app/core/styles/app_images.dart'; +import 'package:tanami_app/core/styles/app_text.dart'; +import 'package:tanami_app/shared/components/text_widget.dart'; + +class RestorePasswordPhoneVerificationTopSection extends StatelessWidget { + const RestorePasswordPhoneVerificationTopSection({ + super.key, + }); + + @override + Widget build(BuildContext context) { + return Column( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + const Gap(85), + Center( + child: SvgPicture.asset( + AppImages.weclomeLogo, + ), + ), + const Gap(60), + TextWidget().text20W700( + AppText.restorePasswordText, + clr: AppColor.charcoalColor, + ), + const Gap(10), + Padding( + padding: const EdgeInsets.symmetric(horizontal: 75), + child: TextWidget().text14W500( + AppText.toRestorePasswordPleaseEnterPhoneNumber, + clr: AppColor.smokeGrayColor, + ), + ), + ], + ); + } +} diff --git a/lib/features/forgotPassword/presentation/widgets/restore_password_top_section.dart b/lib/features/forgotPassword/presentation/widgets/restore_password_top_section.dart new file mode 100644 index 0000000..4340449 --- /dev/null +++ b/lib/features/forgotPassword/presentation/widgets/restore_password_top_section.dart @@ -0,0 +1,41 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_svg/flutter_svg.dart'; +import 'package:gap/gap.dart'; +import 'package:tanami_app/core/styles/app_color.dart'; +import 'package:tanami_app/core/styles/app_images.dart'; +import 'package:tanami_app/core/styles/app_text.dart'; +import 'package:tanami_app/shared/components/text_widget.dart'; + +class RestorePasswordTopSection extends StatelessWidget { + const RestorePasswordTopSection({ + super.key, + }); + + @override + Widget build(BuildContext context) { + return Column( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + const Gap(85), + Center( + child: SvgPicture.asset( + AppImages.weclomeLogo, + ), + ), + const Gap(60), + TextWidget().text20W700( + AppText.restorePasswordText, + clr: AppColor.charcoalColor, + ), + const Gap(10), + Padding( + padding: const EdgeInsets.symmetric(horizontal: 75), + child: TextWidget().text14W500( + AppText.createNewPasswordText, + clr: AppColor.smokeGrayColor, + ), + ), + ], + ); + } +} diff --git a/lib/features/login/presentation/pages/login_layout.dart b/lib/features/login/presentation/pages/login_layout.dart index ea53361..ed06f02 100644 --- a/lib/features/login/presentation/pages/login_layout.dart +++ b/lib/features/login/presentation/pages/login_layout.dart @@ -5,17 +5,25 @@ import '../widgets/bottom_section.dart'; import '../widgets/top_section.dart'; class LoginLayout extends StatelessWidget { - const LoginLayout({super.key}); + final String fromScreen; + const LoginLayout({ + super.key, + required this.fromScreen, + }); @override Widget build(BuildContext context) { return Scaffold( body: ListView( // - children: const [ - TopSection(), - LoginForm(), - BottomSection(), + children: [ + TopSection( + fromScreen: fromScreen, + ), + const LoginForm(), + BottomSection( + fromScreen: fromScreen, + ), ], )); } diff --git a/lib/features/login/presentation/pages/login_screen.dart b/lib/features/login/presentation/pages/login_screen.dart index dbf1805..fe2d6c1 100644 --- a/lib/features/login/presentation/pages/login_screen.dart +++ b/lib/features/login/presentation/pages/login_screen.dart @@ -15,7 +15,9 @@ class LoginScreen extends StatelessWidget { final radioBloc = context.read(); return WillPopScope( onWillPop: () async { - if (fromScreen == "welcome" || fromScreen == "registerStep") { + if (fromScreen == "welcome" || + fromScreen == "registerStep" || + fromScreen == "forgot-pin") { exitAppDialog(context); return false; } else { @@ -33,7 +35,9 @@ class LoginScreen extends StatelessWidget { create: (context) => LoginBloc(), ), ], - child: const LoginLayout(), + child: LoginLayout( + fromScreen: fromScreen, + ), ), ), ); diff --git a/lib/features/login/presentation/widgets/bottom_section.dart b/lib/features/login/presentation/widgets/bottom_section.dart index 1940cc4..c192af8 100644 --- a/lib/features/login/presentation/widgets/bottom_section.dart +++ b/lib/features/login/presentation/widgets/bottom_section.dart @@ -17,7 +17,11 @@ import '../bloc/login_event.dart'; import '../bloc/login_state.dart'; class BottomSection extends StatelessWidget { - const BottomSection({super.key}); + final String fromScreen; + const BottomSection({ + super.key, + required this.fromScreen, + }); @override Widget build(BuildContext context) { @@ -29,7 +33,8 @@ class BottomSection extends StatelessWidget { alignment: Alignment.topRight, child: ButtonWidget().textBtn( function: () { - goRouter.goNamed(RouteName.loginScreen); + goRouter + .pushNamed(RouteName.forgotPasswordPhoneVerificationScreen); }, text: TextWidget().text15W400(AppText.forgorPassword, clr: AppColor.forgotPassButtonColor, @@ -44,10 +49,11 @@ class BottomSection extends StatelessWidget { goRouter.goNamed('mainScreen'); successToastMessage(context, "login successful !"); goRouter.pop(); - - goRouter.goNamed(RouteName.pinScreen, pathParameters: { - "fromScreen": "login", - }); + radioBloc.resetSelection(); + goRouter.pushNamed(RouteName.pinScreen, pathParameters: { + "fromScreen": + fromScreen == "forgot-pin" ? "forgot-pin" : "login", + }); //Push Or GO Need to Check } else if (state is LoginFailure) { goRouter.pop(); errorToastMessage( @@ -101,11 +107,17 @@ class BottomSection extends StatelessWidget { ButtonWidget().textBtn( function: () { radioBloc.resetSelection(); - goRouter.pushNamed(RouteName.registerStepScreen, pathParameters: { - "fromScreentype": "login", - }); + fromScreen == "forgot-pin" + ? goRouter.pop() + : goRouter + .pushNamed(RouteName.registerStepScreen, pathParameters: { + "fromScreentype": "login", + }); }, - text: TextWidget().text14W700(AppText.signUpText, + text: TextWidget().text14W700( + fromScreen == "forgot-pin" + ? AppText.backText + : AppText.signUpText, clr: AppColor.textLabelColor, textDecoration: TextDecoration.underline)), ], diff --git a/lib/features/login/presentation/widgets/top_section.dart b/lib/features/login/presentation/widgets/top_section.dart index e447561..8ca31e8 100644 --- a/lib/features/login/presentation/widgets/top_section.dart +++ b/lib/features/login/presentation/widgets/top_section.dart @@ -7,7 +7,11 @@ import 'package:tanami_app/core/styles/app_text.dart'; import 'package:tanami_app/shared/components/text_widget.dart'; class TopSection extends StatelessWidget { - const TopSection({super.key}); + final String fromScreen; + const TopSection({ + super.key, + required this.fromScreen, + }); @override Widget build(BuildContext context) { @@ -29,7 +33,9 @@ class TopSection extends StatelessWidget { Padding( padding: const EdgeInsets.symmetric(horizontal: 75), child: TextWidget().text14W500( - AppText.pleaseEnterLoginDetails, + fromScreen == "forgot-pin" + ? AppText.toGetYourAccountPleaseSetYourInfo + : AppText.pleaseEnterLoginDetails, clr: AppColor.smokeGrayColor, ), ), diff --git a/lib/features/otpVerification/presentation/pages/otp_layout.dart b/lib/features/otpVerification/presentation/pages/otp_layout.dart index 658a4e3..7bd3166 100644 --- a/lib/features/otpVerification/presentation/pages/otp_layout.dart +++ b/lib/features/otpVerification/presentation/pages/otp_layout.dart @@ -5,16 +5,22 @@ import '../widgets/otp_top_section.dart'; import '../widgets/resend_otp_section.dart'; class OtpLayout extends StatelessWidget { - const OtpLayout({super.key}); + final String fromScreen; + const OtpLayout({ + super.key, + required this.fromScreen, + }); @override Widget build(BuildContext context) { return Scaffold( body: ListView( - children: const [ - OtpTopSection(), - OtpFillSection(), - ResendOtpSection(), + children: [ + const OtpTopSection(), + OtpFillSection( + fromScreen: fromScreen, + ), + const ResendOtpSection(), ], )); } diff --git a/lib/features/otpVerification/presentation/pages/otp_screen.dart b/lib/features/otpVerification/presentation/pages/otp_screen.dart index 7a9e994..55f58fd 100644 --- a/lib/features/otpVerification/presentation/pages/otp_screen.dart +++ b/lib/features/otpVerification/presentation/pages/otp_screen.dart @@ -8,7 +8,11 @@ import '../bloc/timer/timer_bloc.dart'; import '../bloc/timer/timer_event.dart'; class OtpScreen extends StatelessWidget { - const OtpScreen({super.key}); + final String fromScreen; + const OtpScreen({ + super.key, + required this.fromScreen, + }); @override Widget build(BuildContext context) { @@ -25,7 +29,7 @@ class OtpScreen extends StatelessWidget { TimerBloc()..add(StartTimer()), // Start the timer here ), ], - child: const OtpLayout(), + child: OtpLayout(fromScreen: fromScreen), ), ); } diff --git a/lib/features/otpVerification/presentation/widgets/otp_fill_section.dart b/lib/features/otpVerification/presentation/widgets/otp_fill_section.dart index 417e1f9..07cdf96 100644 --- a/lib/features/otpVerification/presentation/widgets/otp_fill_section.dart +++ b/lib/features/otpVerification/presentation/widgets/otp_fill_section.dart @@ -14,7 +14,11 @@ import '../bloc/otp_event.dart'; import '../bloc/otp_state.dart'; class OtpFillSection extends StatelessWidget { - const OtpFillSection({super.key}); + final String fromScreen; + const OtpFillSection({ + super.key, + required this.fromScreen, + }); @override Widget build(BuildContext context) { @@ -28,7 +32,10 @@ class OtpFillSection extends StatelessWidget { context, AppText.otpVerifiedSucessfully, ); - goRouter.pushNamed(RouteName.registerUserDetailsScreen); + + fromScreen == "forgot-password" + ? goRouter.pushNamed(RouteName.forgotPasswordScreen) + : goRouter.pushNamed(RouteName.registerUserDetailsScreen); } else if (state is OtpSubmissionFailure) { goRouter.pop(); errorToastMessage( diff --git a/lib/features/register/presentation/widgets/register_bottom_section.dart b/lib/features/register/presentation/widgets/register_bottom_section.dart index a3b6b51..1ec667f 100644 --- a/lib/features/register/presentation/widgets/register_bottom_section.dart +++ b/lib/features/register/presentation/widgets/register_bottom_section.dart @@ -41,7 +41,8 @@ class RegisterBottomSection extends StatelessWidget { successToastMessage(context, "successful !"); goRouter.pop(); - goRouter.pushNamed(RouteName.otpScreen); + goRouter.pushNamed(RouteName.otpScreen, + pathParameters: {"fromScreen": "register"}); } else if (state is RegisterFailure) { goRouter.pop(); errorToastMessage( diff --git a/lib/features/securePin/presentation/widgets/forgot_pin_dialog.dart b/lib/features/securePin/presentation/widgets/forgot_pin_dialog.dart index 87b93ff..b8d5a37 100644 --- a/lib/features/securePin/presentation/widgets/forgot_pin_dialog.dart +++ b/lib/features/securePin/presentation/widgets/forgot_pin_dialog.dart @@ -1,6 +1,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_screenutil/flutter_screenutil.dart'; import 'package:gap/gap.dart'; +import 'package:tanami_app/core/routes/route_name.dart'; import 'package:tanami_app/core/routes/routes.dart'; import 'package:tanami_app/core/styles/app_color.dart'; import 'package:tanami_app/core/styles/app_text.dart'; @@ -46,6 +47,9 @@ void forgotPinDialog(BuildContext context) { text: AppText.allowText, clr: AppColor.primaryColor2, function: () { + goRouter.pushNamed(RouteName.loginScreen, pathParameters: { + "fromScreen": "forgot-pin", + }); goRouter.pop(); }, ), diff --git a/lib/features/securePin/presentation/widgets/pin_keypad_section.dart b/lib/features/securePin/presentation/widgets/pin_keypad_section.dart index 5ebfc9d..a7a632d 100644 --- a/lib/features/securePin/presentation/widgets/pin_keypad_section.dart +++ b/lib/features/securePin/presentation/widgets/pin_keypad_section.dart @@ -58,8 +58,10 @@ class PinKey extends StatelessWidget { ); }, ), - fromScreen == "login" ? const Gap(20) : const Gap(0), - fromScreen == "login" + (fromScreen == "login" || fromScreen == "forgot-pin") + ? const Gap(20) + : const Gap(0), + (fromScreen == "login") ? InkWell( onTap: () { forgotPinDialog(context); diff --git a/lib/features/securePin/presentation/widgets/pin_top_section.dart b/lib/features/securePin/presentation/widgets/pin_top_section.dart index 6c3b7a7..228bfc7 100644 --- a/lib/features/securePin/presentation/widgets/pin_top_section.dart +++ b/lib/features/securePin/presentation/widgets/pin_top_section.dart @@ -49,10 +49,15 @@ class PinTopSection extends StatelessWidget { ) ], ) - : TextWidget().text14W500( - AppText.createPinCode, - clr: AppColor.textLabelColor, - ) + : fromScreen == "forgot-pin" + ? TextWidget().text14W500( + AppText.changePinCode, + clr: AppColor.textLabelColor, + ) + : TextWidget().text14W500( + AppText.createPinCode, + clr: AppColor.textLabelColor, + ) ], ); } diff --git a/lib/shared/components/text_widget.dart b/lib/shared/components/text_widget.dart index 4b259a3..80dbd91 100644 --- a/lib/shared/components/text_widget.dart +++ b/lib/shared/components/text_widget.dart @@ -3,6 +3,15 @@ import 'package:google_fonts/google_fonts.dart'; import 'package:tanami_app/core/styles/app_color.dart'; class TextWidget { + //Text Size 11 + Widget text11W400(String text, {Color? clr}) { + return Text(text, + style: GoogleFonts.dmSans( + fontSize: 11, + fontWeight: FontWeight.w400, + color: clr ?? AppColor.plainWhite)); + } + //Text Size 12 Widget text12W400(String text, {Color? clr}) { return Text(text, @@ -12,6 +21,21 @@ class TextWidget { color: clr ?? AppColor.plainWhite)); } + Widget text12W500(String text, {Color? clr}) { + return Text(text, + style: GoogleFonts.dmSans( + fontSize: 12, + fontWeight: FontWeight.w500, + color: clr ?? AppColor.plainWhite)); + } + + Widget text12W700(String text, {Color? clr}) { + return Text(text, + style: GoogleFonts.dmSans( + fontSize: 12, + fontWeight: FontWeight.w700, + color: clr ?? AppColor.plainWhite)); + } //Text Size 14 Widget text14W400( @@ -120,4 +144,13 @@ class TextWidget { fontWeight: FontWeight.w700, color: clr ?? AppColor.plainWhite)); } + + //Text Size 28 + Widget text28W700(String text, {Color? clr}) { + return Text(text, + style: GoogleFonts.dmSans( + fontSize: 28, + fontWeight: FontWeight.w700, + color: clr ?? AppColor.plainWhite)); + } } diff --git a/pubspec.lock b/pubspec.lock index e5d578e..441a282 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -81,6 +81,54 @@ packages: url: "https://pub.dev" source: hosted version: "1.1.1" + build_daemon: + dependency: transitive + description: + name: build_daemon + sha256: "0343061a33da9c5810b2d6cee51945127d8f4c060b7fbdd9d54917f0a3feaaa1" + url: "https://pub.dev" + source: hosted + version: "4.0.1" + build_resolvers: + dependency: transitive + description: + name: build_resolvers + sha256: "339086358431fa15d7eca8b6a36e5d783728cf025e559b834f4609a1fcfb7b0a" + url: "https://pub.dev" + source: hosted + version: "2.4.2" + build_runner: + dependency: "direct dev" + description: + name: build_runner + sha256: "3ac61a79bfb6f6cc11f693591063a7f19a7af628dc52f141743edac5c16e8c22" + url: "https://pub.dev" + source: hosted + version: "2.4.9" + build_runner_core: + dependency: transitive + description: + name: build_runner_core + sha256: "4ae8ffe5ac758da294ecf1802f2aff01558d8b1b00616aa7538ea9a8a5d50799" + url: "https://pub.dev" + source: hosted + version: "7.3.0" + built_collection: + dependency: transitive + description: + name: built_collection + sha256: "376e3dd27b51ea877c28d525560790aee2e6fbb5f20e2f85d5081027d94e2100" + url: "https://pub.dev" + source: hosted + version: "5.1.1" + built_value: + dependency: transitive + description: + name: built_value + sha256: c7913a9737ee4007efedaffc968c049fd0f3d0e49109e778edc10de9426005cb + url: "https://pub.dev" + source: hosted + version: "8.9.2" cached_network_image: dependency: "direct main" description: @@ -137,6 +185,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.1.1" + code_builder: + dependency: transitive + description: + name: code_builder + sha256: f692079e25e7869c14132d39f223f8eec9830eb76131925143b2129c4bb01b37 + url: "https://pub.dev" + source: hosted + version: "4.10.0" collection: dependency: transitive description: @@ -424,6 +480,14 @@ packages: description: flutter source: sdk version: "0.0.0" + frontend_server_client: + dependency: transitive + description: + name: frontend_server_client + sha256: f64a0333a82f30b0cca061bc3d143813a486dc086b574bfb233b7c1372427694 + url: "https://pub.dev" + source: hosted + version: "4.0.0" gap: dependency: "direct main" description: @@ -456,6 +520,14 @@ packages: url: "https://pub.dev" source: hosted version: "6.2.1" + graphs: + dependency: transitive + description: + name: graphs + sha256: aedc5a15e78fc65a6e23bcd927f24c64dd995062bcd1ca6eda65a3cff92a4d19 + url: "https://pub.dev" + source: hosted + version: "2.3.1" html: dependency: transitive description: @@ -472,6 +544,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.2.1" + http_multi_server: + dependency: transitive + description: + name: http_multi_server + sha256: "97486f20f9c2f7be8f514851703d0119c3596d14ea63227af6f7a481ef2b2f8b" + url: "https://pub.dev" + source: hosted + version: "3.2.1" http_parser: dependency: transitive description: @@ -496,6 +576,14 @@ packages: url: "https://pub.dev" source: hosted version: "0.19.0" + io: + dependency: transitive + description: + name: io + sha256: "2ec25704aba361659e10e3e5f5d672068d332fc8ac516421d483a11e5cbd061e" + url: "https://pub.dev" + source: hosted + version: "1.0.4" js: dependency: transitive description: @@ -505,7 +593,7 @@ packages: source: hosted version: "0.6.7" json_annotation: - dependency: transitive + dependency: "direct main" description: name: json_annotation sha256: "1ce844379ca14835a50d2f019a3099f419082cfdd231cd86a142af94dd5c6bb1" @@ -513,7 +601,7 @@ packages: source: hosted version: "4.9.0" json_serializable: - dependency: "direct main" + dependency: "direct dev" description: name: json_serializable sha256: ea1432d167339ea9b5bb153f0571d0039607a873d6e04e0117af043f14a1fd4b @@ -632,6 +720,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.11.0" + mime: + dependency: transitive + description: + name: mime + sha256: "2e123074287cc9fd6c09de8336dae606d1ddb88d9ac47358826db698c176a1f2" + url: "https://pub.dev" + source: hosted + version: "1.0.5" nested: dependency: transitive description: @@ -840,6 +936,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.1.8" + pool: + dependency: transitive + description: + name: pool + sha256: "20fe868b6314b322ea036ba325e6fc0711a22948856475e2c2b6306e8ab39c2a" + url: "https://pub.dev" + source: hosted + version: "1.5.1" provider: dependency: transitive description: @@ -936,6 +1040,22 @@ packages: url: "https://pub.dev" source: hosted version: "2.3.2" + shelf: + dependency: transitive + description: + name: shelf + sha256: ad29c505aee705f41a4d8963641f91ac4cee3c8fad5947e033390a7bd8180fa4 + url: "https://pub.dev" + source: hosted + version: "1.4.1" + shelf_web_socket: + dependency: transitive + description: + name: shelf_web_socket + sha256: "9ca081be41c60190ebcb4766b2486a7d50261db7bd0f5d9615f2d653637a84c1" + url: "https://pub.dev" + source: hosted + version: "1.0.4" shimmer: dependency: "direct main" description: @@ -1029,6 +1149,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.1.2" + stream_transform: + dependency: transitive + description: + name: stream_transform + sha256: "14a00e794c7c11aa145a170587321aedce29769c08d7f58b1d141da75e3b1c6f" + url: "https://pub.dev" + source: hosted + version: "2.1.0" string_scanner: dependency: transitive description: @@ -1061,6 +1189,14 @@ packages: url: "https://pub.dev" source: hosted version: "0.6.1" + timing: + dependency: transitive + description: + name: timing + sha256: "70a3b636575d4163c477e6de42f247a23b315ae20e86442bebe32d3cabf61c32" + url: "https://pub.dev" + source: hosted + version: "1.0.1" toastification: dependency: "direct main" description: @@ -1221,6 +1357,14 @@ packages: url: "https://pub.dev" source: hosted version: "0.5.1" + web_socket_channel: + dependency: transitive + description: + name: web_socket_channel + sha256: "58c6666b342a38816b2e7e50ed0f1e261959630becd4c879c4f26bfa14aa5a42" + url: "https://pub.dev" + source: hosted + version: "2.4.5" win32: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 8cedd73..dedb1bd 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -69,7 +69,7 @@ dependencies: go_router: ^14.1.3 # Json Annotation - json_serializable: ^6.8.0 + json_annotation: ^4.9.0 #Style control_style: ^0.1.0 @@ -81,6 +81,8 @@ dependencies: dev_dependencies: flutter_test: sdk: flutter + json_serializable: + build_runner: flutter_lints: ^3.0.0