portfolio done pics2
This commit is contained in:
@@ -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,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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(),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
@@ -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 {}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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(),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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) {
|
||||
|
||||
53
lib/features/otpVerification/presentation/bloc/otp_bloc.dart
Normal file
53
lib/features/otpVerification/presentation/bloc/otp_bloc.dart
Normal 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();
|
||||
}
|
||||
}
|
||||
@@ -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 {}
|
||||
@@ -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];
|
||||
}
|
||||
@@ -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(),
|
||||
],
|
||||
));
|
||||
}
|
||||
}
|
||||
@@ -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(),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
74
lib/features/register/presentation/bloc/register_bloc.dart
Normal file
74
lib/features/register/presentation/bloc/register_bloc.dart
Normal 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();
|
||||
}
|
||||
}
|
||||
33
lib/features/register/presentation/bloc/register_event.dart
Normal file
33
lib/features/register/presentation/bloc/register_event.dart
Normal 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];
|
||||
}
|
||||
32
lib/features/register/presentation/bloc/register_state.dart
Normal file
32
lib/features/register/presentation/bloc/register_state.dart
Normal 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];
|
||||
}
|
||||
@@ -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(),
|
||||
],
|
||||
));
|
||||
}
|
||||
}
|
||||
@@ -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(),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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(),
|
||||
],
|
||||
));
|
||||
}
|
||||
}
|
||||
@@ -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(),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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)),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
))
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
))
|
||||
],
|
||||
),
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user