portfolio done pics2

This commit is contained in:
meet2711
2024-05-31 11:06:22 +05:30
57 changed files with 1388 additions and 137 deletions

View File

@@ -9,6 +9,10 @@ class RouteName {
//Login
static const String loginScreen = 'loginScreen';
//Register
static const String registerStepScreen = 'registerStepScreen';
static const String registerScreen = 'registerScreen';
//No Internet
static const String noInternetScreen = 'noInternet';
@@ -23,4 +27,11 @@ class RouteName {
//Portfolio details
static const String porfolioDetails = 'porfolioDetails';
//Biometric
static const String biometricScreen = 'biometricScreen';
//Biometric
static const String otpScreen = 'otpScreen';
}

View File

@@ -3,8 +3,15 @@
import 'package:go_router/go_router.dart';
import 'package:tanami_app/core/routes/route_name.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/biometric/presentation/pages/biometric_screen.dart';
import 'package:tanami_app/features/countrySelection/presentation/pages/choose_country_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/welcome/presentation/pages/weclome_screen.dart';
import '../../features/login/presentation/pages/login_screen.dart';
@@ -20,55 +27,80 @@ final goRouter = GoRouter(
//errorBuilder: (context, state) => ErrorScreen(state.error),
routes: [
GoRoute(
name: "splash",
path: RouteName.splashScreen,
builder: (context, state) {
return const SplashScreen();
},
// redirect: (context, state) {
// if (true) {
// return "/login";
// }
// return "/";
// },
routes: [
GoRoute(
name: RouteName.loginScreen,
path: RouteName.loginScreen,
builder: (context, state) {
return const LoginScreen();
},
),
GoRoute(
name: RouteName.welcomeScreen,
path: RouteName.welcomeScreen,
builder: (context, state) {
return const WelcomeScreen();
},
),
GoRoute(
name: RouteName.chooseCountryScreen,
path: RouteName.chooseCountryScreen,
builder: (context, state) {
return const ChooseCountryScreen();
},
),
GoRoute(
name: RouteName.mainScreen,
path: RouteName.mainScreen,
builder: (context, state) {
return const MainScreen();
},
),
GoRoute(
name: "splash",
path: RouteName.splashScreen,
builder: (context, state) {
return const SplashScreen();
},
routes: [
GoRoute(
name: RouteName.loginScreen,
path: "${RouteName.loginScreen}/:fromScreen",
builder: (context, state) {
return LoginScreen(
fromScreen: state.pathParameters["fromScreen"]!,
);
},
),
GoRoute(
name: RouteName.registerStepScreen,
path: "${RouteName.registerStepScreen}/:fromScreentype",
builder: (context, state) {
return RegisterStepScreen(
fromScreen: state.pathParameters["fromScreentype"]!,
);
},
),
GoRoute(
name: RouteName.welcomeScreen,
path: RouteName.welcomeScreen,
builder: (context, state) {
return const WelcomeScreen();
},
),
GoRoute(
name: RouteName.chooseCountryScreen,
path: RouteName.chooseCountryScreen,
builder: (context, state) {
return const ChooseCountryScreen();
},
),
GoRoute(
name: RouteName.mainScreen,
path: RouteName.mainScreen,
builder: (context, state) {
return const MainScreen();
},
),
GoRoute(
name: RouteName.biometricScreen,
path: RouteName.biometricScreen,
builder: (context, state) {
return const BiometricScreen();
},
),
GoRoute(
name: RouteName.registerScreen,
path: RouteName.registerScreen,
builder: (context, state) {
return const RegisterScreen();
},
),
GoRoute(
name: RouteName.otpScreen,
path: RouteName.otpScreen,
builder: (context, state) {
return const OtpScreen();
},
),
GoRoute(
name: RouteName.porfolioDetails,
path: RouteName.porfolioDetails,
builder: (context, state) {
return const DetailsScreen();
},
),
],
),
]),
// GoRoute(
// path: '/profile/:userId',

View File

@@ -36,4 +36,9 @@ class AppColor {
//Radio Color
static const Color radioActiveColor = Color(0xFF0B8933);
//Otp Color
static const Color strokeColor = Color(0xFFB4B4B4);
static const Color otpTextColor = Color(0xFF191B1E);
static const Color fillColor = Color(0xFFF6F6F6);
}

View File

@@ -22,6 +22,16 @@ class AppImages {
"assets/images/auth_screen/svg/hide_password.svg";
static const String showPassword =
"assets/images/auth_screen/svg/show_password.svg";
static const String step1Image =
"assets/images/auth_screen/svg/first_step.svg";
static const String step2Image =
"assets/images/auth_screen/svg/second_step.svg";
static const String step3Image =
"assets/images/auth_screen/svg/third_step.svg";
static const String stage1Image =
"assets/images/auth_screen/png/stage_one.png";
static const String stage2Image =
"assets/images/auth_screen/png/stage_two.png";
//Country Flag
static const String bahrainFlag =
@@ -35,4 +45,15 @@ class AppImages {
"assets/images/country_flag/png/saudi_arabia_flag.png";
static const String unitedArabEmiratesFlag =
"assets/images/country_flag/png/uae_flag.png";
//Biometric Screen
static const String biometricBg =
"assets/images/biometric_screen/svg/biometric_bg.svg";
static const String biometricFingerprint =
"assets/images/biometric_screen/png/biometric_fingerprint.png";
static const String biometricFace =
"assets/images/biometric_screen/png/biomertric_face.png";
//Dialog
static const String exitAppIcon = "assets/images/dialog/svg/exit_icon.svg";
}

View File

@@ -29,6 +29,26 @@ class AppText {
static const String invalidPassword = "Invalid Password";
static const String forgorPassword = "Forgot Password";
//Register
static const String getStartedToday = "Get started today";
static const String setupYourTanamiAccountToBegin =
"Setup your Tanami account to begin investing in a few simple steps";
static const String step1 = "Step 1";
static const String step2 = "Step 2";
static const String step3 = "Step 3";
static const String enterYourCountryOfResidence =
"Enter your country of residence and mobile number";
static const String enterNameEmailPassword =
"Enter your name, email and password";
static const String enableBiometricAuthentication =
"Enable biometric authentication and select a unique pin code for easy access";
static const String getStarted = "Get started";
static const String welcome = "Welcome!";
static const String selectYourCountryOfResidence =
"Select your country of residence and enter your mobile number";
static const String nextText = "Next";
static const String backText = "Back";
//Country Name
static const String bahrainCountryText = "Bahrain";
static const String kuwaitCountryText = "Kuwait";
@@ -45,4 +65,17 @@ class AppText {
static const String totalreturn = "Total return";
static const String disttodate = "Distributions to date";
static const String includeddocs = "Included Documents";
//Dialog
static const String exitText = "Exit";
static const String cancelText = "Cancel";
static const String areYouSureYouWantToExitText =
"Are you sure you want to Exit?";
//OTP
static const String checkYourMessages = "Check your messages";
static const String referToSameOtpMessage =
"Please refer to the same OTP message shown below";
static const String resendSms = "Resend SMS";
}

View File

@@ -0,0 +1,20 @@
import 'package:tanami_app/core/styles/app_images.dart';
import 'package:tanami_app/core/styles/app_text.dart';
List<String> title = [
AppText.step1,
AppText.step2,
AppText.step3,
];
List<String> description = [
AppText.enterYourCountryOfResidence,
AppText.enterNameEmailPassword,
AppText.enableBiometricAuthentication,
];
List<String> stepImage = [
AppImages.step1Image,
AppImages.step2Image,
AppImages.step3Image,
];

View File

@@ -0,0 +1,49 @@
import 'dart:io';
import 'package:flutter/material.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:flutter_svg/flutter_svg.dart';
import '../../../../core/styles/app_images.dart';
class BiometricLayout extends StatelessWidget {
const BiometricLayout({super.key});
@override
Widget build(BuildContext context) {
String biometricImage = "";
if (Platform.isIOS) {
biometricImage = AppImages.biometricFace;
} else {
biometricImage = AppImages.biometricFingerprint;
}
return Scaffold(
body: SizedBox(
width: 1.sw,
height: 1.sh,
child: Stack(
children: [
Positioned.fill(
child: SvgPicture.asset(
height: 1.sh,
width: 1.sw,
AppImages.biometricBg,
fit: BoxFit.cover,
),
),
Positioned.fill(
child: Align(
alignment: Alignment.center,
child: Image.asset(
biometricImage,
width: 133,
height: 155,
),
),
),
],
),
),
);
}
}

View File

@@ -0,0 +1,14 @@
import 'package:flutter/material.dart';
import 'package:tanami_app/features/biometric/presentation/pages/biometric_layout.dart';
class BiometricScreen extends StatelessWidget {
const BiometricScreen({super.key});
@override
Widget build(BuildContext context) {
return const Scaffold(
// resizeToAvoidBottomInset: true,
body: BiometricLayout(),
);
}
}

View File

@@ -6,12 +6,22 @@ import 'choose_country_state.dart';
class RadioBloc extends Bloc<RadioEvent, RadioState> {
RadioBloc() : super(RadioInitial()) {
on<RadioSelected>(_onRadioSelected);
on<ResetRadioSelection>(_onResetRadioSelection);
}
void _onRadioSelected(RadioSelected event, Emitter<RadioState> emit) {
emit(RadioSelectionChanged(event.selectedIndex));
}
void _onResetRadioSelection(
ResetRadioSelection event, Emitter<RadioState> emit) {
emit(RadioInitial());
}
void resetSelection() {
add(ResetRadioSelection());
}
int get selectedCountry {
if (state is RadioSelectionChanged) {
return (state as RadioSelectionChanged).selectedIndex;

View File

@@ -10,8 +10,10 @@ abstract class RadioEvent extends Equatable {
class RadioSelected extends RadioEvent {
final int selectedIndex;
const RadioSelected(this.selectedIndex);
const RadioSelected([this.selectedIndex = 0]);
@override
List<Object> get props => [selectedIndex];
}
class ResetRadioSelection extends RadioEvent {}

View File

@@ -24,7 +24,6 @@ class ChooseCountryLayout extends StatelessWidget {
child: ButtonWidget().elevatedBtn(
txtClr: AppColor.plainWhite,
function: () {
// radioBloc.add(const BackPressed(true));
goRouter.pop();
},
text: AppText.confirmSelectionText,

View File

@@ -17,7 +17,7 @@ class CountrySelectionList extends StatelessWidget {
final radioBloc = context.read<RadioBloc>();
return BlocBuilder<RadioBloc, RadioState>(
builder: (context, state) {
int selectedIndex = 0;
int selectedIndex = -1;
if (state is RadioSelectionChanged) {
selectedIndex = state.selectedIndex;
}

View File

@@ -56,13 +56,20 @@ class LoginBloc extends Bloc<LoginEvent, LoginState> {
emit(LoginFieldsState(areFieldsFilled));
}
// Method to reset text fields
void resetFields() {
phoneNumberTextField.clear();
passwordTextField.clear();
countrySelectionTextField.clear();
}
// Mock API function, replace with actual API call
Future<bool> _mockLoginApi(
String email,
String phoneNumber,
String password,
String countryResidence,
) async {
return email == "1234567891" && password == "123456";
return phoneNumber == "1234567891" && password == "123456";
}
@override

View File

@@ -1,29 +1,40 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import '../../../../shared/components/bloc/password_field/password_visibility_bloc.dart';
import '../../../../shared/components/exit_app_dialog.dart';
import '../../../countrySelection/presentation/bloc/choose_country_bloc.dart';
import '../bloc/login_bloc.dart';
import 'login_layout.dart';
class LoginScreen extends StatelessWidget {
const LoginScreen({super.key});
final String fromScreen;
const LoginScreen({super.key, required this.fromScreen});
@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) => LoginBloc(),
),
BlocProvider(
create: (context) => PasswordVisibilityBloc(),
),
],
child: const LoginLayout(),
final radioBloc = context.read<RadioBloc>();
return WillPopScope(
onWillPop: () async {
if (fromScreen == "welcome" || fromScreen == "registerStep") {
exitAppDialog(context);
return false;
} else {
radioBloc.resetSelection();
return true;
}
},
child: Scaffold(
resizeToAvoidBottomInset: true,
body: MultiBlocProvider(
// Define the providers for the OnboardingBloc and other blocs
providers: [
BlocProvider(
// Create an instance of the OnboardingBloc
create: (context) => LoginBloc(),
),
],
child: const LoginLayout(),
),
),
);
}

View File

@@ -11,6 +11,7 @@ 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/login_bloc.dart';
import '../bloc/login_event.dart';
import '../bloc/login_state.dart';
@@ -20,6 +21,7 @@ class BottomSection extends StatelessWidget {
@override
Widget build(BuildContext context) {
final radioBloc = context.read<RadioBloc>();
return Column(
children: [
const Gap(12),
@@ -39,9 +41,11 @@ class BottomSection extends StatelessWidget {
if (state is LoginLoading) {
Loader.loader(context);
} else if (state is LoginSuccess) {
goRouter.pop();
goRouter.goNamed('mainScreen');
successToastMessage(context, "login successful !");
goRouter.pop();
goRouter.goNamed(RouteName.biometricScreen);
} else if (state is LoginFailure) {
goRouter.pop();
errorToastMessage(
@@ -94,7 +98,10 @@ class BottomSection extends StatelessWidget {
const Gap(5),
ButtonWidget().textBtn(
function: () {
goRouter.goNamed(RouteName.loginScreen);
radioBloc.resetSelection();
goRouter.pushNamed(RouteName.registerStepScreen, pathParameters: {
"fromScreentype": "login",
});
},
text: TextWidget().tex14W700(AppText.signUpText,
clr: AppColor.textLabelColor,

View File

@@ -16,6 +16,9 @@ class LoginForm extends StatelessWidget {
Widget build(BuildContext context) {
final loginBloc = context.read<LoginBloc>();
// Reset fields when the screen is built
loginBloc.resetFields();
return BlocConsumer<RadioBloc, RadioState>(listener: (context, state) {
int selectedCountry = -1;
if (state is RadioSelectionChanged) {

View File

@@ -0,0 +1,53 @@
// otp_bloc.dart
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:sms_autofill/sms_autofill.dart';
import 'otp_event.dart';
import 'otp_state.dart';
class OtpBloc extends Bloc<OtpEvent, OtpState> {
final TextEditingController otpController = TextEditingController();
OtpBloc() : super(OtpInitial()) {
on<StartListeningForOtp>(_onStartListeningForOtp);
on<OtpCodeChanged>(_onOtpCodeChanged);
on<OtpSubmit>(_onOtpSubmit);
}
void _onStartListeningForOtp(
StartListeningForOtp event, Emitter<OtpState> emit) {
_startListening();
}
void _onOtpCodeChanged(OtpCodeChanged event, Emitter<OtpState> emit) {
emit(OtpCodeReceived(event.code));
if (event.code.length == 6) {
add(OtpSubmit(event.code));
}
}
void _onOtpSubmit(OtpSubmit event, Emitter<OtpState> emit) async {
emit(OtpSubmitting());
try {
// Add your OTP verification logic here
await Future.delayed(const Duration(seconds: 2));
if (otpController.text == "123456") {
emit(OtpSubmissionSuccess());
} else {
emit(const OtpSubmissionFailure("Otp Invalid !"));
} // Simulate network call
} catch (e) {
emit(OtpSubmissionFailure(e.toString()));
}
}
void _startListening() {
SmsAutoFill().listenForCode(); // Correctly call listenForCode
}
@override
Future<void> close() {
otpController.dispose();
SmsAutoFill().unregisterListener();
return super.close();
}
}

View File

@@ -0,0 +1,29 @@
// otp_event.dart
import 'package:equatable/equatable.dart';
abstract class OtpEvent extends Equatable {
const OtpEvent();
@override
List<Object> get props => [];
}
class OtpCodeChanged extends OtpEvent {
final String code;
const OtpCodeChanged(this.code);
@override
List<Object> get props => [code];
}
class OtpSubmit extends OtpEvent {
final String code;
const OtpSubmit(this.code);
@override
List<Object> get props => [code];
}
class StartListeningForOtp extends OtpEvent {}

View File

@@ -0,0 +1,33 @@
// otp_state.dart
import 'package:equatable/equatable.dart';
abstract class OtpState extends Equatable {
const OtpState();
@override
List<Object> get props => [];
}
class OtpInitial extends OtpState {}
class OtpCodeReceived extends OtpState {
final String code;
const OtpCodeReceived(this.code);
@override
List<Object> get props => [code];
}
class OtpSubmitting extends OtpState {}
class OtpSubmissionSuccess extends OtpState {}
class OtpSubmissionFailure extends OtpState {
final String error;
const OtpSubmissionFailure(this.error);
@override
List<Object> get props => [error];
}

View File

@@ -0,0 +1,19 @@
import 'package:flutter/material.dart';
import 'package:tanami_app/features/otpVerification/presentation/widgets/otp_fill_section.dart';
import '../widgets/otp_top_section.dart';
class OtpLayout extends StatelessWidget {
const OtpLayout({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
body: ListView(
children: const [
OtpTopSection(),
OtpFillSection(),
],
));
}
}

View File

@@ -0,0 +1,26 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:tanami_app/features/otpVerification/presentation/bloc/otp_bloc.dart';
import 'package:tanami_app/features/otpVerification/presentation/pages/otp_layout.dart';
import '../bloc/otp_event.dart';
class OtpScreen extends StatelessWidget {
const OtpScreen({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
resizeToAvoidBottomInset: true,
body: MultiBlocProvider(
providers: [
BlocProvider(
// Create an instance of the OnboardingBloc
create: (context) => OtpBloc()..add(StartListeningForOtp()),
),
],
child: const OtpLayout(),
),
);
}
}

View File

@@ -0,0 +1,68 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:google_fonts/google_fonts.dart';
import 'package:sms_autofill/sms_autofill.dart';
import 'package:tanami_app/core/routes/routes.dart';
import 'package:tanami_app/core/styles/app_color.dart';
import 'package:tanami_app/shared/components/loader.dart';
import 'package:tanami_app/shared/components/toast_message.dart';
import '../bloc/otp_bloc.dart';
import '../bloc/otp_event.dart';
import '../bloc/otp_state.dart';
class OtpFillSection extends StatelessWidget {
const OtpFillSection({super.key});
@override
Widget build(BuildContext context) {
return BlocConsumer<OtpBloc, OtpState>(
listener: (context, state) {
if (state is OtpSubmitting) {
Loader.loader(context);
} else if (state is OtpSubmissionSuccess) {
goRouter.pop();
successToastMessage(context, 'OTP Verified Successfully!');
} else if (state is OtpSubmissionFailure) {
goRouter.pop();
errorToastMessage(context, 'OTP Verification Failed: ${state.error}');
}
},
builder: (context, state) {
final otpBloc = context.read<OtpBloc>();
return Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
PinFieldAutoFill(
controller: otpBloc.otpController,
currentCode: otpBloc.otpController.text,
decoration: BoxLooseDecoration(
textStyle: GoogleFonts.dmSans(
fontSize: 22,
color: AppColor.otpTextColor,
fontWeight: FontWeight.w700,
),
bgColorBuilder: const FixedColorBuilder(AppColor.fillColor),
radius: const Radius.circular(12),
strokeColorBuilder: const FixedColorBuilder(
AppColor.strokeColor,
)),
codeLength: 6,
onCodeChanged: (code) {
if (code != null) {
otpBloc.otpController.text = code;
otpBloc.add(OtpCodeChanged(code));
}
},
autoFocus: true,
),
],
),
);
},
);
}
}

View File

@@ -0,0 +1,40 @@
import 'package:flutter/material.dart';
import 'package:flutter_svg/flutter_svg.dart';
import 'package:gap/gap.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 OtpTopSection extends StatelessWidget {
const OtpTopSection({super.key});
@override
Widget build(BuildContext context) {
return Column(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
const Gap(85),
Center(
child: SvgPicture.asset(
AppImages.weclomeLogo,
),
),
const Gap(125),
TextWidget().tex20W700(
AppText.checkYourMessages,
clr: AppColor.charcoalColor,
),
const Gap(25),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 35),
child: TextWidget().tex14W500(
AppText.referToSameOtpMessage,
clr: AppColor.smokeGrayColor,
),
),
],
);
}
}

View File

@@ -0,0 +1,74 @@
import 'package:bloc/bloc.dart';
import 'package:flutter/material.dart';
import 'register_event.dart';
import 'register_state.dart';
class RegisterBloc extends Bloc<RegisterEvent, RegisterState> {
final GlobalKey<FormState> formKey = GlobalKey<FormState>();
final TextEditingController countrySelectionTextField =
TextEditingController();
final TextEditingController phoneNumberTextField = TextEditingController();
final TextEditingController passwordTextField = TextEditingController();
GlobalKey<FormState> getFormKey() {
return formKey;
}
RegisterBloc() : super(RegisterInitial()) {
phoneNumberTextField.addListener(_onFormFieldChanged);
passwordTextField.addListener(_onFormFieldChanged);
countrySelectionTextField.addListener(_onFormFieldChanged);
on<RegisterFormChanged>(_onLoginFormChanged);
on<RegisterSubmitted>((event, emit) async {
if (!formKey.currentState!.validate()) {
return;
}
emit(RegisterLoading());
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, event.password, event.countryResidence);
if (isSuccess) {
emit(RegisterSuccess());
} else {
emit(const RegisterFailure(
"Register failed. Please check your credentials."));
}
} catch (e) {
emit(RegisterFailure(e.toString()));
}
});
}
void _onFormFieldChanged() {
add(RegisterFormChanged(
phoneNumberTextField.text,
countrySelectionTextField.text,
));
}
void _onLoginFormChanged(
RegisterFormChanged event, Emitter<RegisterState> emit) {
final areFieldsFilled =
event.phoneNumber.isNotEmpty && event.country.isNotEmpty;
emit(RegisterFieldsState(areFieldsFilled));
}
// Mock API function, replace with actual API call
Future<bool> _mockLoginApi(
String phoneNumber,
String password,
String countryResidence,
) async {
return true;
}
@override
Future<void> close() {
phoneNumberTextField.dispose();
passwordTextField.dispose();
countrySelectionTextField.dispose();
return super.close();
}
}

View File

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

View File

@@ -0,0 +1,32 @@
import 'package:equatable/equatable.dart';
abstract class RegisterState extends Equatable {
const RegisterState();
@override
List<Object> get props => [];
}
class RegisterInitial extends RegisterState {}
class RegisterLoading extends RegisterState {}
class RegisterSuccess extends RegisterState {}
class RegisterFailure extends RegisterState {
final String error;
const RegisterFailure(this.error);
@override
List<Object> get props => [error];
}
class RegisterFieldsState extends RegisterState {
final bool areFieldsFilled;
const RegisterFieldsState(this.areFieldsFilled);
@override
List<Object> get props => [areFieldsFilled];
}

View File

@@ -0,0 +1,21 @@
import 'package:flutter/material.dart';
import 'package:tanami_app/features/register/presentation/widgets/register_bottom_section.dart';
import 'package:tanami_app/features/register/presentation/widgets/register_form.dart';
import 'package:tanami_app/features/register/presentation/widgets/register_top_section.dart';
class RegisterLayout extends StatelessWidget {
const RegisterLayout({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
body: ListView(
//
children: const [
RegisterTopSection(),
RegisterForm(),
RegisterBottomSection(),
],
));
}
}

View File

@@ -0,0 +1,33 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:tanami_app/features/register/presentation/bloc/register_bloc.dart';
import '../../../countrySelection/presentation/bloc/choose_country_bloc.dart';
import 'register_layout.dart';
class RegisterScreen extends StatelessWidget {
const RegisterScreen({super.key});
@override
Widget build(BuildContext context) {
final radioBloc = context.read<RadioBloc>();
return WillPopScope(
onWillPop: () async {
radioBloc.resetSelection();
return true; // Allow the pop to happen
},
child: Scaffold(
resizeToAvoidBottomInset: true,
body: MultiBlocProvider(
providers: [
BlocProvider(
// Create an instance of the OnboardingBloc
create: (context) => RegisterBloc(),
),
],
child: const RegisterLayout(),
),
),
);
}
}

View File

@@ -0,0 +1,23 @@
import 'package:flutter/material.dart';
import 'package:gap/gap.dart';
import 'package:tanami_app/features/register/presentation/widgets/register_step_count.dart';
import '../widgets/register_step_bottom_section.dart';
import '../widgets/register_step_top_section.dart';
class RegisterStepLayout extends StatelessWidget {
const RegisterStepLayout({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
body: ListView(
children: const [
RegisterStepTopSection(),
Gap(20),
RegisterStepCount(),
RegisterStepBottomSection(),
],
));
}
}

View File

@@ -0,0 +1,30 @@
import 'package:flutter/material.dart';
import '../../../../shared/components/exit_app_dialog.dart';
import 'register_step_layout.dart';
class RegisterStepScreen extends StatelessWidget {
final String fromScreen;
const RegisterStepScreen({
super.key,
required this.fromScreen,
});
@override
Widget build(BuildContext context) {
return WillPopScope(
onWillPop: () async {
if (fromScreen == "welcome" || fromScreen == "login") {
exitAppDialog(context);
return false;
} else {
return true;
}
},
child: const Scaffold(
resizeToAvoidBottomInset: true,
body: RegisterStepLayout(),
),
);
}
}

View File

@@ -0,0 +1,106 @@
import 'package:flutter/material.dart';
import 'package:flutter/widgets.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:gap/gap.dart';
import 'package:tanami_app/core/styles/app_images.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/register_bloc.dart';
import '../bloc/register_event.dart';
import '../bloc/register_state.dart';
class RegisterBottomSection extends StatelessWidget {
const RegisterBottomSection({super.key});
@override
Widget build(BuildContext context) {
final radioBloc = context.read<RadioBloc>();
return Column(
children: [
const Gap(90),
Image.asset(
AppImages.stage1Image,
width: 75,
height: 12,
),
const Gap(36),
BlocConsumer<RegisterBloc, RegisterState>(
listener: (context, state) {
if (state is RegisterLoading) {
Loader.loader(context);
} else if (state is RegisterSuccess) {
successToastMessage(context, "successful !");
goRouter.pop();
goRouter.pushNamed(RouteName.otpScreen);
} else if (state is RegisterFailure) {
goRouter.pop();
errorToastMessage(
context,
state.error,
);
}
},
builder: (context, state) {
bool isButtonEnabled = false;
if (state is RegisterFieldsState) {
isButtonEnabled = state.areFieldsFilled;
} else if (state is RegisterSuccess || state is RegisterFailure) {
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<RegisterBloc>().add(
RegisterSubmitted(
context
.read<RegisterBloc>()
.phoneNumberTextField
.text,
context
.read<RegisterBloc>()
.passwordTextField
.text,
""),
)
: null;
},
text: AppText.nextText,
clr: isButtonEnabled
? AppColor.primaryColor2
: AppColor.inactiveBtnColor,
),
);
},
),
const Gap(5),
ButtonWidget().textBtn(
function: () {
radioBloc.resetSelection();
goRouter.pop();
},
text: TextWidget().tex14W700(AppText.backText,
clr: AppColor.textLabelColor,
textDecoration: TextDecoration.underline)),
],
);
}
}

View File

@@ -0,0 +1,76 @@
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/register_bloc.dart';
class RegisterForm extends StatelessWidget {
const RegisterForm({super.key});
@override
Widget build(BuildContext context) {
final loginBloc = context.read<RegisterBloc>();
int selectedCountry = -1;
return BlocConsumer<RadioBloc, RadioState>(listener: (context, state) {
if (state is RadioSelectionChanged) {
selectedCountry = state.selectedIndex;
loginBloc.countrySelectionTextField.text = countryName[selectedCountry];
}
}, builder: (context, state) {
if (state is RadioSelectionChanged) {
selectedCountry = state.selectedIndex;
} else {
selectedCountry = -1;
}
return Form(
key: loginBloc.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: loginBloc.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: loginBloc.phoneNumberTextField,
),
],
),
),
),
);
});
}
}

View File

@@ -0,0 +1,54 @@
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/shared/components/button_widget.dart';
import '../../../../core/styles/app_text.dart';
import '../../../../shared/components/text_widget.dart';
class RegisterStepBottomSection extends StatelessWidget {
const RegisterStepBottomSection({super.key});
@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.symmetric(vertical: 40.0),
child: Column(
children: [
Container(
margin: const EdgeInsets.symmetric(
horizontal: 16,
),
width: 1.sw,
height: 56.h,
child: ButtonWidget().elevatedBtn(
function: () {
goRouter.pushNamed(
RouteName.registerScreen,
);
},
text: AppText.getStarted,
txtClr: AppColor.plainWhite,
clr: AppColor.primaryColor2,
),
),
const Gap(16),
ButtonWidget().textBtn(
function: () {
goRouter.goNamed(RouteName.loginScreen, pathParameters: {
"fromScreen": "registerStep",
});
},
text: TextWidget().tex14W700(
AppText.loginText,
clr: AppColor.darkGreyColor,
textDecoration: TextDecoration.underline,
))
],
),
);
}
}

View File

@@ -0,0 +1,39 @@
import 'package:flutter/material.dart';
import 'package:flutter_svg/flutter_svg.dart';
import 'package:tanami_app/core/styles/app_color.dart';
import 'package:tanami_app/core/utils/constant/register_step_data.dart';
import 'package:tanami_app/shared/components/text_widget.dart';
class RegisterStepCount extends StatelessWidget {
const RegisterStepCount({super.key});
@override
Widget build(BuildContext context) {
return ListView.builder(
shrinkWrap: true,
itemCount: 3,
itemBuilder: (context, index) {
return Container(
margin: const EdgeInsets.symmetric(
horizontal: 27,
vertical: 5,
),
child: ListTile(
isThreeLine: true,
leading: SvgPicture.asset(stepImage[index]),
title: TextWidget().tex14W700(
title[index],
clr: AppColor.textLabelColor,
txtAlign: TextAlign.start,
),
subtitle: TextWidget().tex14W500(
description[index],
clr: AppColor.textLabelColor,
txtAlign: TextAlign.start,
),
),
);
},
);
}
}

View File

@@ -0,0 +1,39 @@
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 RegisterStepTopSection extends StatelessWidget {
const RegisterStepTopSection({super.key});
@override
Widget build(BuildContext context) {
return Column(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
const Gap(85),
Center(
child: SvgPicture.asset(
AppImages.weclomeLogo,
),
),
const Gap(30),
TextWidget().tex20W700(
AppText.getStarted,
clr: AppColor.charcoalColor,
),
const Gap(10),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 75),
child: TextWidget().tex14W500(
AppText.setupYourTanamiAccountToBegin,
clr: AppColor.smokeGrayColor,
),
),
],
);
}
}

View File

@@ -0,0 +1,39 @@
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 RegisterTopSection extends StatelessWidget {
const RegisterTopSection({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().tex20W700(
AppText.welcome,
clr: AppColor.charcoalColor,
),
const Gap(10),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 75),
child: TextWidget().tex14W500(
AppText.selectYourCountryOfResidence,
clr: AppColor.smokeGrayColor,
),
),
],
);
}
}

View File

@@ -25,7 +25,11 @@ class LoginSignUpButton extends StatelessWidget {
width: 1.sw,
height: 56.h,
child: ButtonWidget().elevatedBtn(
function: () {},
function: () {
goRouter.goNamed(RouteName.registerStepScreen, pathParameters: {
"fromScreentype": "welcome",
});
},
text: AppText.signUpText,
txtClr: AppColor.plainWhite,
clr: AppColor.primaryColor,
@@ -34,11 +38,15 @@ class LoginSignUpButton extends StatelessWidget {
const Gap(16),
ButtonWidget().textBtn(
function: () {
goRouter.goNamed(RouteName.loginScreen);
goRouter.goNamed(RouteName.loginScreen, pathParameters: {
"fromScreen": "welcome",
});
},
text: TextWidget().tex14W700(AppText.loginText,
clr: AppColor.darkGreyColor,
textDecoration: TextDecoration.underline))
text: TextWidget().tex14W700(
AppText.loginText,
clr: AppColor.darkGreyColor,
textDecoration: TextDecoration.underline,
))
],
),
);

View File

@@ -6,6 +6,7 @@ import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'core/routes/routes.dart';
import 'core/utils/connectivity/network_connectivity.dart';
import 'features/countrySelection/presentation/bloc/choose_country_bloc.dart';
import 'shared/components/bloc/password_field/password_visibility_bloc.dart';
/* CREATED BY - JAYESH JAIN
DATE - 24-05-2024
@@ -53,7 +54,10 @@ class _MyAppState extends State<MyApp> with WidgetsBindingObserver {
providers: [
BlocProvider(
create: (context) => RadioBloc(),
)
),
BlocProvider(
create: (context) => PasswordVisibilityBloc(),
),
],
child: ScreenUtilInit(
builder: (BuildContext context, Widget? child) => MaterialApp.router(

View File

@@ -0,0 +1,119 @@
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_screenutil/flutter_screenutil.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';
exitAppDialog(
context,
) {
return showDialog(
context: context,
builder: (context) => Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
AlertDialog(
insetPadding: const EdgeInsets.symmetric(horizontal: 16),
backgroundColor: const Color(0XFFFFFFFF),
//contentPadding: EdgeInsets.fromLTRB(96, 32, 96, 28),
shape: const RoundedRectangleBorder(
borderRadius: BorderRadius.all(Radius.circular(20)),
side: BorderSide(color: Color(0XFFFFFFFF)),
),
content: Column(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
//sizedBoxHeight(32.h),
Align(
alignment: Alignment.center,
child: CircleAvatar(
radius: 25,
backgroundColor: AppColor.inactiveBtnColor,
child: Center(
child: SvgPicture.asset(
AppImages.exitAppIcon,
width: 30,
height: 30,
fit: BoxFit.contain,
color: AppColor.primaryColor,
),
),
),
),
SizedBox(
height: 22.h,
),
Align(
alignment: Alignment.center,
child: Text(
AppText.areYouSureYouWantToExitText,
textAlign: TextAlign.center,
style: TextStyle(
color: Colors.black,
fontSize: 22.sp,
//fontWeight: FontWeight.w600,
),
),
),
Gap(21.h),
Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
InkWell(
onTap: () {
Navigator.pop(context);
},
child: Container(
height: 48.h,
width: 140.w,
decoration: BoxDecoration(
border: Border.all(
color: AppColor.primaryColor,
),
borderRadius: BorderRadius.circular(10.h),
color: AppColor.plainWhite,
),
child: Center(
child: Text(
AppText.cancelText,
style: TextStyle(
color: AppColor.primaryColor, fontSize: 18.sp),
),
),
),
),
Gap(15.w),
InkWell(
onTap: () {
SystemNavigator.pop();
Navigator.pop(context);
},
child: Container(
height: 48.h,
width: 140.w,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(10.h),
color: AppColor.primaryColor),
child: Center(
child: Text(
AppText.exitText,
style: TextStyle(
color: AppColor.plainWhite, fontSize: 18.sp),
),
),
),
),
],
),
],
),
),
],
),
);
}

View File

@@ -13,23 +13,15 @@ class TextWidget {
}
//Text Size 14
Widget tex14W700(String text, {Color? clr, TextDecoration? textDecoration}) {
return Text(text,
textAlign: TextAlign.center,
style: GoogleFonts.dmSans(
fontSize: 14,
fontWeight: FontWeight.w700,
decoration: textDecoration ?? TextDecoration.none,
color: clr ?? AppColor.plainWhite));
}
Widget tex14W500(
String text, {
Color? clr,
TextDecoration? textDecoration,
TextAlign? txtAlign,
}) {
return Text(text,
textAlign: TextAlign.center,
textAlign: txtAlign ?? TextAlign.center,
style: GoogleFonts.dmSans(
fontSize: 14,
fontWeight: FontWeight.w500,
@@ -37,6 +29,17 @@ class TextWidget {
color: clr ?? AppColor.plainWhite));
}
Widget tex14W700(String text,
{Color? clr, TextDecoration? textDecoration, TextAlign? txtAlign}) {
return Text(text,
textAlign: txtAlign ?? TextAlign.center,
style: GoogleFonts.dmSans(
fontSize: 14,
fontWeight: FontWeight.w700,
decoration: textDecoration ?? TextDecoration.none,
color: clr ?? AppColor.plainWhite));
}
//Text Size 15
Widget tex15W500(
String text, {