forgot password screen, portoflio screen ui code reformating

This commit is contained in:
jayesh
2024-06-04 17:56:52 +05:30
parent 5510e72c6e
commit 154f28b3cc
46 changed files with 1699 additions and 385 deletions

View File

@@ -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';
}

View File

@@ -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(

View File

@@ -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);
}

View File

@@ -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';
}

View File

@@ -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,7 +88,6 @@ class AppText {
//Academy
static const String videosTitle = "Videos";
//Dialog
static const String exitText = "Exit";
static const String cancelText = "Cancel";
@@ -104,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";
@@ -116,4 +118,12 @@ 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";
}

View File

@@ -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';

View File

@@ -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<String, dynamic> json) =>
_$PortfolioModelFromJson(json);
Map<String, dynamic> toJson() => _$PortfolioModelToJson(this);
}
List<PortfolioModel> 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%",
)
];

View File

@@ -0,0 +1,31 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'portfolio_model.dart';
// **************************************************************************
// JsonSerializableGenerator
// **************************************************************************
PortfolioModel _$PortfolioModelFromJson(Map<String, dynamic> 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<String, dynamic> _$PortfolioModelToJson(PortfolioModel instance) =>
<String, dynamic>{
'date': instance.date,
'title': instance.title,
'status': instance.status,
'investmentAmountSAR': instance.investmentAmountSAR,
'investmentAmountUSD': instance.investmentAmountUSD,
'currentValuationSAR': instance.currentValuationSAR,
'currentValuationUSD': instance.currentValuationUSD,
'totalReturnPercentage': instance.totalReturnPercentage,
};

View File

@@ -1,341 +0,0 @@
import 'package:flutter/material.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:google_fonts/google_fonts.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_text.dart';
class PortfolioScreen extends StatefulWidget {
const PortfolioScreen({super.key});
@override
State<PortfolioScreen> createState() => _PortfolioScreenState();
}
class _PortfolioScreenState extends State<PortfolioScreen> {
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.white,
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: [
Text(
AppText.portfolio,
style: GoogleFonts.dmSans(
color: const Color(0xFF888888),
fontSize: 12.sp,
fontWeight: FontWeight.w700,
),
),
Text(
'SAR 178,000',
style: GoogleFonts.dmSans(
color: Colors.black,
fontSize: 14.sp,
fontWeight: FontWeight.w700,
),
),
],
),
),
background: Stack(
fit: StackFit.expand,
children: [
Image.asset(
'assets/images/portfolio_screen/bg.png', // Replace with your image asset
fit: BoxFit.cover,
),
Padding(
padding: const EdgeInsets.only(left: 40.0),
child: Opacity(
opacity: percentage,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
AppText.portfolio,
style: GoogleFonts.dmSans(
color: const Color(0xFFC9D9CB),
fontSize: 14.sp,
fontWeight: FontWeight.w700,
),
),
SizedBox(
height: 8.h,
),
Text(
'SAR 178,000',
style: GoogleFonts.dmSans(
color: Colors.white,
fontSize: 28.sp,
fontWeight: FontWeight.w700,
),
),
],
),
),
),
],
),
);
}),
backgroundColor: Colors.white,
),
SliverList(
delegate: SliverChildBuilderDelegate(
(context, index) => Padding(
padding: const EdgeInsets.all(10.0),
child: GestureDetector(
onTap: () {
goRouter.pushNamed(RouteName.porfolioDetails);
},
child: Container(
decoration: BoxDecoration(
color: Colors.white,
borderRadius:
const BorderRadius.all(Radius.circular(20.0)),
boxShadow: [
BoxShadow(
color: Colors.black.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(
(index == 2)
? 'assets/images/portfolio_screen/clock_off.png'
: 'assets/images/portfolio_screen/clock.png',
height: 25.sp,
),
SizedBox(
width: 5.w,
),
Text(
'Mar 01 2024',
style: GoogleFonts.dmSans(
color: const Color(0xFF004717),
fontSize: 12.sp,
fontWeight: FontWeight.w500,
),
)
],
),
Text(
(index == 2) ? 'Exited' : 'Pending',
style: GoogleFonts.dmSans(
color: (index == 2)
? const Color(0xFF8D8D8D)
: const Color(0xFF0FA4A4),
fontSize: 14.sp,
fontWeight: FontWeight.w700,
),
)
],
),
SizedBox(
height: 8.h,
),
Text(
(index == 2)
? 'Real Estate III'
: 'Private equity portfolio I',
style: GoogleFonts.dmSans(
color: Colors.black,
fontSize: 17.sp,
fontWeight: FontWeight.w700,
),
),
],
),
),
Container(
decoration: const BoxDecoration(
color: Color(0xFFF8F8F8),
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: [
Text(
AppText.investmentamount,
style: GoogleFonts.dmSans(
color: (index == 2)
? const Color(0xFF8D8D8D)
: const Color(0xFF535353),
fontSize: 14.sp,
fontWeight: FontWeight.w500,
),
),
Column(
crossAxisAlignment:
CrossAxisAlignment.end,
children: [
Text(
'SAR 100,000',
style: GoogleFonts.dmSans(
color: (index == 2)
? const Color(0xFF8D8D8D)
: Colors.black,
fontSize: 14.sp,
fontWeight: FontWeight.w700,
),
),
Text(
' \$ 26,700',
style: GoogleFonts.dmSans(
color: (index == 2)
? const Color(0xFF8D8D8D)
: Colors.black,
fontSize: 11.sp,
fontWeight: FontWeight.w400,
),
),
],
)
],
),
SizedBox(
height: 8.h,
),
Row(
mainAxisAlignment:
MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
AppText.currentval,
style: GoogleFonts.dmSans(
color: (index == 2)
? const Color(0xFF8D8D8D)
: const Color(0xFF535353),
fontSize: 14.sp,
fontWeight: FontWeight.w500,
),
),
Column(
crossAxisAlignment:
CrossAxisAlignment.end,
children: [
Text(
'SAR 100,000',
style: GoogleFonts.dmSans(
color: (index == 2)
? const Color(0xFF8D8D8D)
: Colors.black,
fontSize: 14.sp,
fontWeight: FontWeight.w700,
),
),
Text(
' \$ 26,700',
style: GoogleFonts.dmSans(
color: (index == 2)
? const Color(0xFF8D8D8D)
: Colors.black,
fontSize: 11.sp,
fontWeight: FontWeight.w400,
),
),
],
)
],
),
SizedBox(
height: 8.h,
),
Row(
mainAxisAlignment:
MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
AppText.totalreturn,
style: GoogleFonts.dmSans(
color: (index == 2)
? const Color(0xFF8D8D8D)
: const Color(0xFF535353),
fontSize: 14.sp,
fontWeight: FontWeight.w500,
),
),
Text(
(index == 2) ? '- 20.0%' : '+ 20.0%',
style: GoogleFonts.dmSans(
color: (index == 2)
? const Color(0xFFde9595)
: const Color(0xFF066123),
fontSize: 14.sp,
fontWeight: FontWeight.w700,
),
)
],
)
],
),
),
)
],
),
),
),
),
childCount: 3,
), //SliverChildBuildDelegate
),
],
),
);
}
}

View File

@@ -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,
),
),
],
),
);
}
}

View File

@@ -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<PortfolioScreen> createState() => _PortfolioScreenState();
}
class _PortfolioScreenState extends State<PortfolioScreen> {
@override
Widget build(BuildContext context) {
return const Scaffold(body: PortfolioLayout());
}
}

View File

@@ -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,
),
],
)
],
),
),
)
],
),
),
),
);
}
}

View File

@@ -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),
),
],
)
],
),
),
)
],
),
),
),
);
}
}

View File

@@ -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<RestorePasswordEvent, RestorePasswordState> {
final GlobalKey<FormState> formKey = GlobalKey<FormState>();
final TextEditingController passwordTextField = TextEditingController();
final TextEditingController repeatPasswordTextField = TextEditingController();
GlobalKey<FormState> getFormKey() {
return formKey;
}
RestorePasswordBloc() : super(RestorePasswordInitial()) {
passwordTextField.addListener(_onFormFieldChanged);
repeatPasswordTextField.addListener(_onFormFieldChanged);
on<RestorePasswordFormChanged>(_onLoginFormChanged);
on<RestorePasswordSubmitted>((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<RestorePasswordState> 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<bool> _mockLoginApi(
String phoneNumber,
) async {
return true;
}
@override
Future<void> close() {
passwordTextField.dispose();
repeatPasswordTextField.dispose();
return super.close();
}
}

View File

@@ -0,0 +1,31 @@
import 'package:equatable/equatable.dart';
abstract class RestorePasswordEvent extends Equatable {
const RestorePasswordEvent();
@override
List<Object> get props => [];
}
class RestorePasswordSubmitted extends RestorePasswordEvent {
final String password;
final String repeatPassword;
const RestorePasswordSubmitted(
this.password,
this.repeatPassword,
);
@override
List<Object> get props => [password, repeatPassword];
}
class RestorePasswordFormChanged extends RestorePasswordEvent {
final String password;
final String repeatPassword;
const RestorePasswordFormChanged(this.password, this.repeatPassword);
@override
List<Object> get props => [password, repeatPassword];
}

View File

@@ -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<FormState> formKey = GlobalKey<FormState>();
final TextEditingController phoneNumberTextField = TextEditingController();
final TextEditingController countrySelectionTextField =
TextEditingController();
GlobalKey<FormState> getFormKey() {
return formKey;
}
RestorePasswordPhoneVerificationBloc()
: super(RestorePasswordPhoneVerificationInitial()) {
phoneNumberTextField.addListener(_onFormFieldChanged);
countrySelectionTextField.addListener(_onFormFieldChanged);
on<RestorePasswordPhoneVerificationFormChanged>(_onLoginFormChanged);
on<RestorePasswordPhoneVerificationSubmitted>((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<RestorePasswordPhoneVerificationState> 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<bool> _mockLoginApi(
String phoneNumber,
) async {
return phoneNumber == "1234567891";
}
@override
Future<void> close() {
phoneNumberTextField.dispose();
countrySelectionTextField.dispose();
return super.close();
}
}

View File

@@ -0,0 +1,34 @@
import 'package:equatable/equatable.dart';
abstract class RestorePasswordPhoneVerificationEvent extends Equatable {
const RestorePasswordPhoneVerificationEvent();
@override
List<Object> get props => [];
}
class RestorePasswordPhoneVerificationSubmitted
extends RestorePasswordPhoneVerificationEvent {
final String phoneNumber;
final String countryResidence;
const RestorePasswordPhoneVerificationSubmitted(
this.phoneNumber,
this.countryResidence,
);
@override
List<Object> get props => [phoneNumber, countryResidence];
}
class RestorePasswordPhoneVerificationFormChanged
extends RestorePasswordPhoneVerificationEvent {
final String phoneNumber;
final String country;
const RestorePasswordPhoneVerificationFormChanged(
this.phoneNumber, this.country);
@override
List<Object> get props => [phoneNumber, country];
}

View File

@@ -0,0 +1,37 @@
import 'package:equatable/equatable.dart';
abstract class RestorePasswordPhoneVerificationState extends Equatable {
const RestorePasswordPhoneVerificationState();
@override
List<Object> 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<Object> get props => [error];
}
class RestorePasswordPhoneVerificationFieldsState
extends RestorePasswordPhoneVerificationState {
final bool areFieldsFilled;
const RestorePasswordPhoneVerificationFieldsState(this.areFieldsFilled);
@override
List<Object> get props => [areFieldsFilled];
}

View File

@@ -0,0 +1,32 @@
import 'package:equatable/equatable.dart';
abstract class RestorePasswordState extends Equatable {
const RestorePasswordState();
@override
List<Object> 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<Object> get props => [error];
}
class RestorePasswordFieldsState extends RestorePasswordState {
final bool areFieldsFilled;
const RestorePasswordFieldsState(this.areFieldsFilled);
@override
List<Object> get props => [areFieldsFilled];
}

View File

@@ -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(),
],
));
}
}

View File

@@ -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(),
],
));
}
}

View File

@@ -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(),
),
);
}
}

View File

@@ -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(),
),
);
}
}

View File

@@ -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<RadioBloc>();
return Column(
children: [
BlocConsumer<RestorePasswordBloc, RestorePasswordState>(
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<RestorePasswordBloc>().add(
RestorePasswordSubmitted(
context
.read<RestorePasswordBloc>()
.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)),
],
);
}
}

View File

@@ -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<RestorePasswordBloc>();
// 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,
),
),
],
),
),
),
);
}
}

View File

@@ -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<RadioBloc>();
return Column(
children: [
BlocConsumer<RestorePasswordPhoneVerificationBloc,
RestorePasswordPhoneVerificationState>(
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<RestorePasswordPhoneVerificationBloc>()
.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)),
],
);
}
}

View File

@@ -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<RestorePasswordPhoneVerificationBloc>();
// Reset fields when the screen is built
restorePasswordBloc.resetFields();
return BlocConsumer<RadioBloc, RadioState>(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,
),
],
),
),
),
);
});
}
}

View File

@@ -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,
),
),
],
);
}
}

View File

@@ -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,
),
),
],
);
}
}

View File

@@ -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,
),
],
));
}

View File

@@ -15,7 +15,9 @@ class LoginScreen extends StatelessWidget {
final radioBloc = context.read<RadioBloc>();
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,
),
),
),
);

View File

@@ -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)),
],

View File

@@ -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,
),
),

View File

@@ -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(),
],
));
}

View File

@@ -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),
),
);
}

View File

@@ -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(

View File

@@ -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(

View File

@@ -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();
},
),

View File

@@ -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);

View File

@@ -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,
)
],
);
}

View File

@@ -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));
}
}

View File

@@ -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:

View File

@@ -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