Blocs Project Document
Project File :
1. PubSpec
2. Configs:
3. Model
4. Repositories
5. Services
6. Utilities
7. Blocs
8. View
9. Widgets
PubSpec:
iconsax_plus: ^1.0.0
http: ^1.2.1
bloc: ^8.1.4
flutter_bloc: ^8.1.5
equatable: ^2.0.5
shared_preferences: ^2.2.3
logger: ^2.3.0
pinput: ^4.0.0
lottie: ^3.1.2
flutter_in_store_app_version_checker: ^1.6.1
flutter_sliding_toast: ^1.0.2
timezone: ^0.9.3
Configs:
app_colors.dart:
class AppColors{
static const Color seed = Color(0xff339966);
static Color systemBg(context) => Theme.of(context).scaffoldBackgroundColor;
static Color error(context) => Theme.of(context).colorScheme.error;
//avatar bg
static LinearGradient avatarBG = LinearGradient(
begin: Alignment.topLeft,
end: const Alignment(0.80, 0.80),
colors: [
Colors.pink.shade50,
Colors.blue.shade50,
]);
}
static const Color seed = Color(0xff339966);
static Color systemBg(context) => Theme.of(context).scaffoldBackgroundColor;
static Color error(context) => Theme.of(context).colorScheme.error;
//avatar bg
static LinearGradient avatarBG = LinearGradient(
begin: Alignment.topLeft,
end: const Alignment(0.80, 0.80),
colors: [
Colors.pink.shade50,
Colors.blue.shade50,
]);
}
app_routes.dart:
class AppRoutes{
static pushReplacement(context,page)=> Navigator.pushReplacement(context, CupertinoPageRoute(builder: (_)=> page));
static push(context,page)=> Navigator.push(context, CupertinoPageRoute(builder: (_)=> page));
static pushAndRemoveUntil(context,page)=> Navigator.pushAndRemoveUntil(context, CupertinoPageRoute(builder: (_)=> page), (route) => false);
}
static pushReplacement(context,page)=> Navigator.pushReplacement(context, CupertinoPageRoute(builder: (_)=> page));
static push(context,page)=> Navigator.push(context, CupertinoPageRoute(builder: (_)=> page));
static pushAndRemoveUntil(context,page)=> Navigator.pushAndRemoveUntil(context, CupertinoPageRoute(builder: (_)=> page), (route) => false);
}
app_size.dart:
class AppSizes{
static const double bodyPadding = 15;
static const double normalText = 13;
static const double subText = 12;
static double height(context) => MediaQuery.sizeOf(context).height;
static double width(context) => MediaQuery.sizeOf(context).width;
}
static const double bodyPadding = 15;
static const double normalText = 13;
static const double subText = 12;
static double height(context) => MediaQuery.sizeOf(context).height;
static double width(context) => MediaQuery.sizeOf(context).width;
}
app_url.dart:
class AppUrls{
static const String playStore = "https://play.google.com/store/apps/details?id=com.m360ict.toab_admin&hl=en&gl=US";
static const String appStore = "https://apps.apple.com/pk/app/toab-admin/id6503280976";
static const String baseUrl = "https://toab.services/api/v1";
static const String imageBaseUrl = "https://m360ict.s3.ap-south-1.amazonaws.com/toab-storage";
static const String login = "$baseUrl/auth/admin/login";
}
Logger logger = Logger();
static const String playStore = "https://play.google.com/store/apps/details?id=com.m360ict.toab_admin&hl=en&gl=US";
static const String appStore = "https://apps.apple.com/pk/app/toab-admin/id6503280976";
static const String baseUrl = "https://toab.services/api/v1";
static const String imageBaseUrl = "https://m360ict.s3.ap-south-1.amazonaws.com/toab-storage";
static const String login = "$baseUrl/auth/admin/login";
}
Logger logger = Logger();
Model:
All Api Model
Repositories:
import 'package:http/http.dart' as http;
Future<String> getResponse({
required String url,
required String token,
}) async {
Uri uriUrl = Uri.parse(url);
final Map<String, String> header = {
"Content-Type": "application/json",
'Authorization': 'Bearer $token'
};
try {
final response = await http
.get(uriUrl, headers: header)
.timeout(const Duration(seconds: 15));
logger.i("getResponse body: ${response.body}");
return response.body;
} on TimeoutException {
return '''
{
"success": false,
"title": "Timeout",
"message": "The request timed out. Please try again later.",
"data": null
}
''';
} on SocketException {
return '''
{
"success": false,
"title": "Connection Failed",
"message": "Unable to connect to the server. Please check your network connection and try again.",
"data": null
}
''';
} catch (e) {
return '''
{
"success": false,
"title": "Failed",
"message": "An error occurred while communicating with the server",
"data": null
}
''';
}
}
required String token,
}) async {
Uri uriUrl = Uri.parse(url);
final Map<String, String> header = {
"Content-Type": "application/json",
'Authorization': 'Bearer $token'
};
try {
final response = await http
.get(uriUrl, headers: header)
.timeout(const Duration(seconds: 15));
logger.i("getResponse body: ${response.body}");
return response.body;
} on TimeoutException {
return '''
{
"success": false,
"title": "Timeout",
"message": "The request timed out. Please try again later.",
"data": null
}
''';
} on SocketException {
return '''
{
"success": false,
"title": "Connection Failed",
"message": "Unable to connect to the server. Please check your network connection and try again.",
"data": null
}
''';
} catch (e) {
return '''
{
"success": false,
"title": "Failed",
"message": "An error occurred while communicating with the server",
"data": null
}
''';
}
}
Future<String> postResponse(
{required String url,
Map<String, String>? payload,
String? token}) async {
Uri uriUrl = Uri.parse(url);
final Map<String, String> header = {
"Content-Type": "application/json",
if (token != null) "Authorization": "Bearer $token"
};
try {
final response = await http
.post(uriUrl, body: payload == null ? null : jsonEncode(payload), headers: header)
.timeout(const Duration(seconds: 15));
logger.i("postResponse body: ${response.body}");
return response.body;
} on TimeoutException {
return '''
{
"success": false,
"title": "Timeout",
"message": "The request timed out. Please try again later.",
"data": null
}
''';
} on SocketException {
return '''
{
"success": false,
"title": "Connection Failed",
"message": "Unable to connect to the server. Please check your network connection and try again.",
"data": null
}
''';
} catch (e) {
return '''
{
"success": false,
"title": "Failed",
"message": "An error occurred while communicating with the server",
"data": null
}
''';
}
}
{required String url,
Map<String, String>? payload,
String? token}) async {
Uri uriUrl = Uri.parse(url);
final Map<String, String> header = {
"Content-Type": "application/json",
if (token != null) "Authorization": "Bearer $token"
};
try {
final response = await http
.post(uriUrl, body: payload == null ? null : jsonEncode(payload), headers: header)
.timeout(const Duration(seconds: 15));
logger.i("postResponse body: ${response.body}");
return response.body;
} on TimeoutException {
return '''
{
"success": false,
"title": "Timeout",
"message": "The request timed out. Please try again later.",
"data": null
}
''';
} on SocketException {
return '''
{
"success": false,
"title": "Connection Failed",
"message": "Unable to connect to the server. Please check your network connection and try again.",
"data": null
}
''';
} catch (e) {
return '''
{
"success": false,
"title": "Failed",
"message": "An error occurred while communicating with the server",
"data": null
}
''';
}
}
Future<String> patchImageResponse(
{required String url,
required Map<String, String> payload,
required String? photoPath,
required String token}) async {
Uri uriUrl = Uri.parse(url);
final Map<String, String> header = {'Authorization': 'Bearer $token'};
try {
var request = http.MultipartRequest('PATCH', uriUrl);
request.fields.addAll(payload);
if (photoPath != null) {
request.files.add(await http.MultipartFile.fromPath('avatar', photoPath));
}
request.headers.addAll(header);
http.StreamedResponse response = await request.send();
final respStr = await response.stream.bytesToString();
logger.i("patchImageResponse respStr: $respStr");
return respStr;
} on TimeoutException {
return '''
{
"success": false,
"title": "Timeout",
"message": "The request timed out. Please try again later.",
"data": null
}
''';
} on SocketException {
return '''
{
"success": false,
"title": "Connection Failed",
"message": "Unable to connect to the server. Please check your network connection and try again.",
"data": null
}
''';
} catch (e) {
return '''
{
"success": false,
"title": "Failed",
"message": "An error occurred while communicating with the server",
"data": null
}
''';
}
}
{required String url,
required Map<String, String> payload,
required String? photoPath,
required String token}) async {
Uri uriUrl = Uri.parse(url);
final Map<String, String> header = {'Authorization': 'Bearer $token'};
try {
var request = http.MultipartRequest('PATCH', uriUrl);
request.fields.addAll(payload);
if (photoPath != null) {
request.files.add(await http.MultipartFile.fromPath('avatar', photoPath));
}
request.headers.addAll(header);
http.StreamedResponse response = await request.send();
final respStr = await response.stream.bytesToString();
logger.i("patchImageResponse respStr: $respStr");
return respStr;
} on TimeoutException {
return '''
{
"success": false,
"title": "Timeout",
"message": "The request timed out. Please try again later.",
"data": null
}
''';
} on SocketException {
return '''
{
"success": false,
"title": "Connection Failed",
"message": "Unable to connect to the server. Please check your network connection and try again.",
"data": null
}
''';
} catch (e) {
return '''
{
"success": false,
"title": "Failed",
"message": "An error occurred while communicating with the server",
"data": null
}
''';
}
}
Services:
import 'package:shared_preferences/shared_preferences.dart';
class LocalDB {
//for sign in info
static Future<void> putEmail(String email)async{
final SharedPreferences prefs = await SharedPreferences.getInstance();
await prefs.setString('email', email);
}
static Future<void> putPassword(String password)async{
final SharedPreferences prefs = await SharedPreferences.getInstance();
await prefs.setString('password', password);
}
static Future<SignInDBModel?> getSignInInfo()async{
final SharedPreferences prefs = await SharedPreferences.getInstance();
final String? email = prefs.getString('email');
final String? password = prefs.getString('password');
if(email==null || password == null){
return null;
}else{
return SignInDBModel(email: email, password: password);
}
}
static Future<void> deleteSignInInfo()async{
final SharedPreferences prefs = await SharedPreferences.getInstance();
await prefs.remove('email');
await prefs.remove('password');
}
//for add member count
static Future<void> putCountAddMember(int count)async{
final SharedPreferences prefs = await SharedPreferences.getInstance();
await prefs.setInt('count', count);
}
static Future<int> getCountAddMember()async{
final SharedPreferences prefs = await SharedPreferences.getInstance();
final int? count = prefs.getInt('count');
return count ?? 0;
}
}
//end for sign in info
class SignInDBModel{
final String email,password;
const SignInDBModel({required this.email,required this.password});
}
class LocalDB {
//for sign in info
static Future<void> putEmail(String email)async{
final SharedPreferences prefs = await SharedPreferences.getInstance();
await prefs.setString('email', email);
}
static Future<void> putPassword(String password)async{
final SharedPreferences prefs = await SharedPreferences.getInstance();
await prefs.setString('password', password);
}
static Future<SignInDBModel?> getSignInInfo()async{
final SharedPreferences prefs = await SharedPreferences.getInstance();
final String? email = prefs.getString('email');
final String? password = prefs.getString('password');
if(email==null || password == null){
return null;
}else{
return SignInDBModel(email: email, password: password);
}
}
static Future<void> deleteSignInInfo()async{
final SharedPreferences prefs = await SharedPreferences.getInstance();
await prefs.remove('email');
await prefs.remove('password');
}
//for add member count
static Future<void> putCountAddMember(int count)async{
final SharedPreferences prefs = await SharedPreferences.getInstance();
await prefs.setInt('count', count);
}
static Future<int> getCountAddMember()async{
final SharedPreferences prefs = await SharedPreferences.getInstance();
final int? count = prefs.getInt('count');
return count ?? 0;
}
}
//end for sign in info
class SignInDBModel{
final String email,password;
const SignInDBModel({required this.email,required this.password});
}
Utilities:
app_pars_json.dart
ApiResponse<T> appParseJson<T>(String jsonString, Function fromJsonT) {
try{
final jsonData = json.decode(jsonString);
return ApiResponse.fromJson(jsonData, fromJsonT);
}on FormatException catch (e) {
return ApiResponse(success: false, message: "Invalid JSON format: $e", title: "JSON Decoding Error");
}
catch(e){
return ApiResponse(success: false, message: "Failed to parse JSON data: $e", title: "JSON Decoding Error");
}
}
try{
final jsonData = json.decode(jsonString);
return ApiResponse.fromJson(jsonData, fromJsonT);
}on FormatException catch (e) {
return ApiResponse(success: false, message: "Invalid JSON format: $e", title: "JSON Decoding Error");
}
catch(e){
return ApiResponse(success: false, message: "Failed to parse JSON data: $e", title: "JSON Decoding Error");
}
}
app_validator.dart
class AppValidators {
RegExp emailRegex = RegExp(
r"[a-zA-Z0-9.a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+@[a-zA-Z0-9-]+\.[a-zA-Z]+");
String? validateEmail(String? email) {
if (email == null || email.isEmpty) {
return 'Please enter your email address.';
} else if (!emailRegex.hasMatch(email)) {
return 'Please enter valid email address.';
}
return null; // Email is valid
}
static String? validate(String? value, {required String label}) {
if (value == null || value.isEmpty) {
return 'Please enter your $label.';
}
return null; // Email is valid
}
}
RegExp emailRegex = RegExp(
r"[a-zA-Z0-9.a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+@[a-zA-Z0-9-]+\.[a-zA-Z]+");
String? validateEmail(String? email) {
if (email == null || email.isEmpty) {
return 'Please enter your email address.';
} else if (!emailRegex.hasMatch(email)) {
return 'Please enter valid email address.';
}
return null; // Email is valid
}
static String? validate(String? value, {required String label}) {
if (value == null || value.isEmpty) {
return 'Please enter your $label.';
}
return null; // Email is valid
}
}
Blocs:
auth_event.dart
sealed class AuthEvent extends Equatable {
const AuthEvent();
@override
List<Object> get props => [];
}
//for sign in
class DoSignInEvent extends AuthEvent{
final Map<String, String> payload;
const DoSignInEvent({required this.payload});
}
//end sign in
class DoRoute extends AuthEvent{}
class DoUpdateProfileEvent extends AuthEvent{
final Map<String, String> payload;
final String token;
final String? photoPath;
const DoUpdateProfileEvent({required this.payload, required this.token, required this.photoPath});
}
class DoSendOtpEvent extends AuthEvent{
final Map<String, String> payload;
const DoSendOtpEvent({required this.payload});
}
const AuthEvent();
@override
List<Object> get props => [];
}
//for sign in
class DoSignInEvent extends AuthEvent{
final Map<String, String> payload;
const DoSignInEvent({required this.payload});
}
//end sign in
class DoRoute extends AuthEvent{}
class DoUpdateProfileEvent extends AuthEvent{
final Map<String, String> payload;
final String token;
final String? photoPath;
const DoUpdateProfileEvent({required this.payload, required this.token, required this.photoPath});
}
class DoSendOtpEvent extends AuthEvent{
final Map<String, String> payload;
const DoSendOtpEvent({required this.payload});
}
app_state.dart
sealed class AuthState extends Equatable {
const AuthState();
@override
List<Object> get props => [];
}
final class AuthInitial extends AuthState {}
//for login
final class SignInLoading extends AuthState {}
final class SignInSuccess extends AuthState {}
final class SignInFailed extends AuthState {
final String title, message;
const SignInFailed({required this.title, required this.message});
}
//for login
//for route
final class RouteLogin extends AuthState {}
final class RouteHome extends AuthState {}
//end for route
//for update profile
final class UpdateProfileLoading extends AuthState {}
final class UpdateProfileFailed extends AuthState {
final String title, message;
const UpdateProfileFailed({required this.title, required this.message});
}
final class UpdateProfileSuccess extends AuthState {
final String title, message;
const UpdateProfileSuccess({required this.title, required this.message});
}
//end for update profile
//for send otp
final class SendOtpLoading extends AuthState {}
final class SendOtpFailed extends AuthState {
final String title, message;
const SendOtpFailed({required this.title, required this.message});
}
final class SendOtpSuccess extends AuthState {}
//end for send otp
const AuthState();
@override
List<Object> get props => [];
}
final class AuthInitial extends AuthState {}
//for login
final class SignInLoading extends AuthState {}
final class SignInSuccess extends AuthState {}
final class SignInFailed extends AuthState {
final String title, message;
const SignInFailed({required this.title, required this.message});
}
//for login
//for route
final class RouteLogin extends AuthState {}
final class RouteHome extends AuthState {}
//end for route
//for update profile
final class UpdateProfileLoading extends AuthState {}
final class UpdateProfileFailed extends AuthState {
final String title, message;
const UpdateProfileFailed({required this.title, required this.message});
}
final class UpdateProfileSuccess extends AuthState {
final String title, message;
const UpdateProfileSuccess({required this.title, required this.message});
}
//end for update profile
//for send otp
final class SendOtpLoading extends AuthState {}
final class SendOtpFailed extends AuthState {
final String title, message;
const SendOtpFailed({required this.title, required this.message});
}
final class SendOtpSuccess extends AuthState {}
//end for send otp
auth_bloc.dart
import 'package:bloc/bloc.dart';
import 'package:equatable/equatable.dart';
class AuthBloc extends Bloc<AuthEvent, AuthState> {
String? token;
LoginModel? loginData;
AuthBloc() : super(AuthInitial()) {
//for sign in
on<DoSignInEvent>((event, emit) async {
logger.f("message DoALoginEvent");
emit(SignInLoading());
final responseStr =
await postResponse(url: AppUrls.login, payload: event.payload);
ApiResponse<LoginModel> response = appParseJson<LoginModel>(
responseStr,
(data) => LoginModel.fromJson(data),
);
if (response.success != true) {
emit(SignInFailed(
title: response.title ?? 'Failed',
message:
response.message ?? 'Something went wrong. Please try again.'));
return;
}
token = response.token;
loginData = response.data;
emit(SignInSuccess());
//save all local value here
LocalDB.putEmail(event.payload['email'] ?? '');
LocalDB.putPassword(event.payload['password'] ?? '');
});
//end for sign in
//for route
on<DoRoute>((event, emit) async {
logger.f("message DoRoute");
final authInfo = await LocalDB.getSignInInfo();
if (authInfo == null) {
emit(RouteLogin());
return;
}
Map<String, String> payload = {
"email": authInfo.email,
"password": authInfo.password
};
final responseStr =
await postResponse(url: AppUrls.login, payload: payload);
ApiResponse<LoginModel> response = appParseJson<LoginModel>(
responseStr,
(data) => LoginModel.fromJson(data),
);
if (response.success != true) {
emit(RouteLogin());
return;
}
token = response.token;
loginData = response.data;
emit(RouteHome());
});
//end for route
//for update profile
on<DoUpdateProfileEvent>((event, emit) async {
logger.f("message DoUpdateProfileEvent");
emit(UpdateProfileLoading());
final responseStr = await patchImageResponse(
url: AppUrls.profile,
payload: event.payload,
photoPath: event.photoPath,
token: event.token);
ApiResponse response = appParseJson(responseStr, (data) => data);
if (response.success != true) {
emit(UpdateProfileFailed(
title: response.title ?? "Failed",
message: response.message ?? "Failed to update profile"));
return;
}
final profileData =
await getResponse(url: AppUrls.profile, token: event.token);
ApiResponse<LoginModel> response2 = appParseJson<LoginModel>(
profileData, (data) => LoginModel.fromJson(data));
loginData = response2.data;
emit(UpdateProfileSuccess(
title: response.title ?? "Success",
message: response.message ?? "Profile updated successfully"));
LocalDB.putEmail(event.payload['email'] ?? '');
});
//end for update profile
//for send otp
on<DoSendOtpEvent>((event, emit) async {
logger.f("message DoSendOtpEvent");
emit(SendOtpLoading());
final responseStr =
await postResponse(payload: event.payload, url: AppUrls.sendEmailOtp);
ApiResponse response = appParseJson(responseStr, (data) => data);
if (response.success != true) {
emit(SendOtpFailed(
title: response.title ?? "Failed",
message: response.message ?? "Failed to send OTP code"));
return;
}
emit(SendOtpSuccess());
});
//for end send otp
}
}
import 'package:equatable/equatable.dart';
class AuthBloc extends Bloc<AuthEvent, AuthState> {
String? token;
LoginModel? loginData;
AuthBloc() : super(AuthInitial()) {
//for sign in
on<DoSignInEvent>((event, emit) async {
logger.f("message DoALoginEvent");
emit(SignInLoading());
final responseStr =
await postResponse(url: AppUrls.login, payload: event.payload);
ApiResponse<LoginModel> response = appParseJson<LoginModel>(
responseStr,
(data) => LoginModel.fromJson(data),
);
if (response.success != true) {
emit(SignInFailed(
title: response.title ?? 'Failed',
message:
response.message ?? 'Something went wrong. Please try again.'));
return;
}
token = response.token;
loginData = response.data;
emit(SignInSuccess());
//save all local value here
LocalDB.putEmail(event.payload['email'] ?? '');
LocalDB.putPassword(event.payload['password'] ?? '');
});
//end for sign in
//for route
on<DoRoute>((event, emit) async {
logger.f("message DoRoute");
final authInfo = await LocalDB.getSignInInfo();
if (authInfo == null) {
emit(RouteLogin());
return;
}
Map<String, String> payload = {
"email": authInfo.email,
"password": authInfo.password
};
final responseStr =
await postResponse(url: AppUrls.login, payload: payload);
ApiResponse<LoginModel> response = appParseJson<LoginModel>(
responseStr,
(data) => LoginModel.fromJson(data),
);
if (response.success != true) {
emit(RouteLogin());
return;
}
token = response.token;
loginData = response.data;
emit(RouteHome());
});
//end for route
//for update profile
on<DoUpdateProfileEvent>((event, emit) async {
logger.f("message DoUpdateProfileEvent");
emit(UpdateProfileLoading());
final responseStr = await patchImageResponse(
url: AppUrls.profile,
payload: event.payload,
photoPath: event.photoPath,
token: event.token);
ApiResponse response = appParseJson(responseStr, (data) => data);
if (response.success != true) {
emit(UpdateProfileFailed(
title: response.title ?? "Failed",
message: response.message ?? "Failed to update profile"));
return;
}
final profileData =
await getResponse(url: AppUrls.profile, token: event.token);
ApiResponse<LoginModel> response2 = appParseJson<LoginModel>(
profileData, (data) => LoginModel.fromJson(data));
loginData = response2.data;
emit(UpdateProfileSuccess(
title: response.title ?? "Success",
message: response.message ?? "Profile updated successfully"));
LocalDB.putEmail(event.payload['email'] ?? '');
});
//end for update profile
//for send otp
on<DoSendOtpEvent>((event, emit) async {
logger.f("message DoSendOtpEvent");
emit(SendOtpLoading());
final responseStr =
await postResponse(payload: event.payload, url: AppUrls.sendEmailOtp);
ApiResponse response = appParseJson(responseStr, (data) => data);
if (response.success != true) {
emit(SendOtpFailed(
title: response.title ?? "Failed",
message: response.message ?? "Failed to send OTP code"));
return;
}
emit(SendOtpSuccess());
});
//for end send otp
}
}
View:
sign_in_screen.dart
class SignInScreen extends StatefulWidget {
const SignInScreen({super.key});
@override
State<SignInScreen> createState() => _SignInScreenState();
}
class _SignInScreenState extends State<SignInScreen> {
final GlobalKey<FormState> formKey = GlobalKey<FormState>();
TextEditingController emailCon = TextEditingController();
TextEditingController passwordCon = TextEditingController();
bool hidePassword = true;
@override
void initState() {
super.initState();
appUpdateAlert(context);
}
@override
Widget build(BuildContext context) {
return BlocListener<AuthBloc, AuthState>(
listener: (context, state) {
if(state is SignInLoading){
appLoader(context);
}else if(state is SignInSuccess){
Navigator.pop(context);
AppRoutes.pushAndRemoveUntil(context, const HomeScreen());
}else if(state is SignInFailed){
Navigator.pop(context);
appAlertDialog(context, state.message,title: state.title);
}
},
child: Scaffold(
appBar: AppBar(),
body: Center(
child: Form(
key: formKey,
child: ListView(
shrinkWrap: true,
padding:
const EdgeInsets.all(AppSizes.bodyPadding),
children: [
SizedBox(
height: AppSizes.height(context) * 0.1,
),
Image.asset(
"assets/images/building.png",
height: 70,
width: 70,
),
SizedBox(
height: AppSizes.height(context) * 0.1,
),
const Text(
"Welcome,",
style: TextStyle(
color: AppColors.grey, fontWeight: FontWeight.w500),
),
const Text("Sign in Now", style: TextStyle(
fontSize: 22,
fontWeight: FontWeight.w500
),),
const SizedBox(
height: AppSizes.bodyPadding * 2,
),
AppTextFormField(
labelText: "Email Address",
hintText: "Enter email address",
controller: emailCon,
validator: AppValidators().validateEmail,
textInputAction: TextInputAction.next,
keyboardType: TextInputType.emailAddress,
),
AppTextFormField(
labelText: "Password",
hintText: "Enter password",
controller: passwordCon,
obscureText: hidePassword,
validator: (v) {
return AppValidators.validate(v, label: "password");
},
suffixIcon: IconButton(
alignment: Alignment.bottomCenter,
onPressed: () {
setState(() {
hidePassword = !hidePassword;
});
},
icon: Icon(hidePassword
? IconsaxPlusLinear.eye
: IconsaxPlusLinear.eye_slash, size: 18,),
),
),
Align(
alignment: Alignment.centerRight,
child: InkWell(
onTap: () =>AppRoutes.push(context, const ForgotPasswordScreen()),
child: const Text("Forgot Password?", style: TextStyle(
color: AppColors.seed,
fontWeight: FontWeight.w500),)),
),
const SizedBox(
height: AppSizes.bodyPadding * 4,
),
ElevatedButton(
onPressed: () {
final currentState = formKey.currentState;
if (currentState != null && currentState.validate()) {
final Map<String, String> payload = {
"email": emailCon.text.trim(),
"password": passwordCon.text.trim()
};
context
.read<AuthBloc>()
.add(DoSignInEvent(payload: payload));
}
},
child: const Text("Continue")
),
SizedBox(
height: AppSizes.height(context) * 0.1,
),
],
)
),
),
),
);
}
}
const SignInScreen({super.key});
@override
State<SignInScreen> createState() => _SignInScreenState();
}
class _SignInScreenState extends State<SignInScreen> {
final GlobalKey<FormState> formKey = GlobalKey<FormState>();
TextEditingController emailCon = TextEditingController();
TextEditingController passwordCon = TextEditingController();
bool hidePassword = true;
@override
void initState() {
super.initState();
appUpdateAlert(context);
}
@override
Widget build(BuildContext context) {
return BlocListener<AuthBloc, AuthState>(
listener: (context, state) {
if(state is SignInLoading){
appLoader(context);
}else if(state is SignInSuccess){
Navigator.pop(context);
AppRoutes.pushAndRemoveUntil(context, const HomeScreen());
}else if(state is SignInFailed){
Navigator.pop(context);
appAlertDialog(context, state.message,title: state.title);
}
},
child: Scaffold(
appBar: AppBar(),
body: Center(
child: Form(
key: formKey,
child: ListView(
shrinkWrap: true,
padding:
const EdgeInsets.all(AppSizes.bodyPadding),
children: [
SizedBox(
height: AppSizes.height(context) * 0.1,
),
Image.asset(
"assets/images/building.png",
height: 70,
width: 70,
),
SizedBox(
height: AppSizes.height(context) * 0.1,
),
const Text(
"Welcome,",
style: TextStyle(
color: AppColors.grey, fontWeight: FontWeight.w500),
),
const Text("Sign in Now", style: TextStyle(
fontSize: 22,
fontWeight: FontWeight.w500
),),
const SizedBox(
height: AppSizes.bodyPadding * 2,
),
AppTextFormField(
labelText: "Email Address",
hintText: "Enter email address",
controller: emailCon,
validator: AppValidators().validateEmail,
textInputAction: TextInputAction.next,
keyboardType: TextInputType.emailAddress,
),
AppTextFormField(
labelText: "Password",
hintText: "Enter password",
controller: passwordCon,
obscureText: hidePassword,
validator: (v) {
return AppValidators.validate(v, label: "password");
},
suffixIcon: IconButton(
alignment: Alignment.bottomCenter,
onPressed: () {
setState(() {
hidePassword = !hidePassword;
});
},
icon: Icon(hidePassword
? IconsaxPlusLinear.eye
: IconsaxPlusLinear.eye_slash, size: 18,),
),
),
Align(
alignment: Alignment.centerRight,
child: InkWell(
onTap: () =>AppRoutes.push(context, const ForgotPasswordScreen()),
child: const Text("Forgot Password?", style: TextStyle(
color: AppColors.seed,
fontWeight: FontWeight.w500),)),
),
const SizedBox(
height: AppSizes.bodyPadding * 4,
),
ElevatedButton(
onPressed: () {
final currentState = formKey.currentState;
if (currentState != null && currentState.validate()) {
final Map<String, String> payload = {
"email": emailCon.text.trim(),
"password": passwordCon.text.trim()
};
context
.read<AuthBloc>()
.add(DoSignInEvent(payload: payload));
}
},
child: const Text("Continue")
),
SizedBox(
height: AppSizes.height(context) * 0.1,
),
],
)
),
),
),
);
}
}
forgot_password.scr.dart
class ForgotPasswordScreen extends StatelessWidget {
const ForgotPasswordScreen({super.key});
@override
Widget build(BuildContext context) {
final GlobalKey<FormState> forgotFormKey = GlobalKey<FormState>();
final TextEditingController emailCon = TextEditingController();
Map<String, String> payload = {};
return Scaffold(
appBar: AppBar(),
body: BlocListener<AuthBloc, AuthState>(
listener: (context, state) {
if(state is SendOtpLoading){
appLoader(context);
}else if(state is SendOtpSuccess){
Navigator.pop(context);
AppRoutes.push(context, OtpScreen(payload: payload));
}else if (state is SendOtpFailed){
Navigator.pop(context);
appAlertDialog(context, state.message, title: state.title);
}
},
child: Form(
key: forgotFormKey,
child: ListView(
padding: const EdgeInsets.all(AppSizes.bodyPadding),
children: [
const Text(
'Forgot Password?',
style: TextStyle(fontSize: 20, fontWeight: FontWeight.w500),
),
const Text(
"Don't worry! it occurs. Please enter the email address linked with your account.",
style: TextStyle(fontSize: 12, color: AppColors.grey),
),
SizedBox(
height: AppSizes.height(context) * 0.1,
),
AppTextFormField(
hintText: "Enter your email",
controller: emailCon,
labelText: 'Email',
validator: (v) {
return AppValidators().validateEmail(v);
},
),
SizedBox(
height: AppSizes.height(context) * 0.1,
),
ElevatedButton(
child: const Text("Send Code"),
onPressed: () {
final currentState = forgotFormKey.currentState;
if (currentState != null && currentState.validate()) {
payload = {
"email": emailCon.text.trim(),
"type": AppUrls.type
};
context
.read<AuthBloc>()
.add(DoSendOtpEvent(payload: payload));
}
})
],
)),
),
);
}
}
const ForgotPasswordScreen({super.key});
@override
Widget build(BuildContext context) {
final GlobalKey<FormState> forgotFormKey = GlobalKey<FormState>();
final TextEditingController emailCon = TextEditingController();
Map<String, String> payload = {};
return Scaffold(
appBar: AppBar(),
body: BlocListener<AuthBloc, AuthState>(
listener: (context, state) {
if(state is SendOtpLoading){
appLoader(context);
}else if(state is SendOtpSuccess){
Navigator.pop(context);
AppRoutes.push(context, OtpScreen(payload: payload));
}else if (state is SendOtpFailed){
Navigator.pop(context);
appAlertDialog(context, state.message, title: state.title);
}
},
child: Form(
key: forgotFormKey,
child: ListView(
padding: const EdgeInsets.all(AppSizes.bodyPadding),
children: [
const Text(
'Forgot Password?',
style: TextStyle(fontSize: 20, fontWeight: FontWeight.w500),
),
const Text(
"Don't worry! it occurs. Please enter the email address linked with your account.",
style: TextStyle(fontSize: 12, color: AppColors.grey),
),
SizedBox(
height: AppSizes.height(context) * 0.1,
),
AppTextFormField(
hintText: "Enter your email",
controller: emailCon,
labelText: 'Email',
validator: (v) {
return AppValidators().validateEmail(v);
},
),
SizedBox(
height: AppSizes.height(context) * 0.1,
),
ElevatedButton(
child: const Text("Send Code"),
onPressed: () {
final currentState = forgotFormKey.currentState;
if (currentState != null && currentState.validate()) {
payload = {
"email": emailCon.text.trim(),
"type": AppUrls.type
};
context
.read<AuthBloc>()
.add(DoSendOtpEvent(payload: payload));
}
})
],
)),
),
);
}
}
otp_screen.dart
class OtpScreen extends StatefulWidget {
const OtpScreen({super.key, required this.payload});
final Map<String, String> payload;
@override
State<OtpScreen> createState() => _OtpScreenState();
}
class _OtpScreenState extends State<OtpScreen> {
bool timerDone = false;
CountdownController countCon = CountdownController(autoStart: true);
@override
void dispose() {
super.dispose();
countCon.isCompleted;
}
@override
Widget build(BuildContext context) {
final defaultPinTheme = PinTheme(
width: 56,
height: 56,
textStyle: const TextStyle(fontSize: 20, color: Color.fromRGBO(30, 60, 87, 1), fontWeight: FontWeight.w600),
decoration: BoxDecoration(
border: Border.all(color: AppColors.seed.withOpacity(0.2)),
borderRadius: BorderRadius.circular(15),
),
);
final focusedPinTheme = defaultPinTheme.copyDecorationWith(
border: Border.all(color: AppColors.seed),
borderRadius: BorderRadius.circular(10),
);
final submittedPinTheme = defaultPinTheme.copyWith(
decoration: defaultPinTheme.decoration?.copyWith(
color: AppColors.seed.withOpacity(0.1),
),
);
return Scaffold(
appBar: AppBar(),
body: ListView(
padding: const EdgeInsets.all(AppSizes.bodyPadding),
children: [
const Text(
'OTP Verification',
style: TextStyle(fontSize: 20, fontWeight: FontWeight.w500),
),
Text(
"Enter the verification code we just sent on your email address (${widget.payload['email']})",
style: const TextStyle(fontSize: 12, color: AppColors.grey),
),
SizedBox(height: AppSizes.height(context) * 0.1,),
//for otp
Pinput(
length: 6,
defaultPinTheme: defaultPinTheme,
focusedPinTheme: focusedPinTheme,
submittedPinTheme: submittedPinTheme,
pinputAutovalidateMode: PinputAutovalidateMode.onSubmit,
showCursor: true,
autofocus: true,
cursor: const Text("|",style: TextStyle(fontWeight: FontWeight.w500, fontSize: 20, color: AppColors.seed),),
onCompleted: (pin) {
appLoader(context);
Map<String, String> matchPayload = {...widget.payload,"otp": pin};
postResponse(payload: matchPayload, url: AppUrls.matchEmailOtp).then((value) {
ApiResponse response = appParseJson(value, (data)=>data);
if(response.success != true){
Navigator.pop(context);
appAlertDialog(context, response.message??'Failed to match otp code', title: response.title);
}else{
Navigator.pop(context);
AppRoutes.pushReplacement(context, ResetPasswordScreen(token: response.token ?? '', matchOtpPayload: matchPayload));
}
});
},
),
//end for otp
SizedBox(height: AppSizes.height(context) * 0.1,),
//for timer & resend code
timerDone
?
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Text("Didn't received code?"),
TextButton(onPressed: (){
appLoader(context);
postResponse(payload: widget.payload, url: AppUrls.matchEmailOtp).then((value) {
ApiResponse response = appParseJson(value, (data)=>data);
if(response.success != true){
Navigator.pop(context);
appAlertDialog(context, response.message??'Send to failed otp code', title: response.title);
}else{
Navigator.pop(context);
appSnackBar(context, response.message??'OTP code sent successfully');
timerDone = false;
countCon.start();
setState(() {});
}
});
}, child: const Text("Resend"))
],
):Countdown(
seconds: 180,
controller: countCon,
build: (BuildContext context, double sec) {
final duration =
Duration(seconds: sec.toInt()).toString().split(':');
final seconds = duration[2].split('.')[0];
final minutes = duration[1];
return Text(
"OTP code will expire at $minutes:$seconds minutes",
textAlign: TextAlign.center,
);
},
interval: const Duration(seconds: 1),
onFinished: () {
timerDone = true;
setState(() {});
},
),
//end for timer & resend code
],
),
);
}
}
const OtpScreen({super.key, required this.payload});
final Map<String, String> payload;
@override
State<OtpScreen> createState() => _OtpScreenState();
}
class _OtpScreenState extends State<OtpScreen> {
bool timerDone = false;
CountdownController countCon = CountdownController(autoStart: true);
@override
void dispose() {
super.dispose();
countCon.isCompleted;
}
@override
Widget build(BuildContext context) {
final defaultPinTheme = PinTheme(
width: 56,
height: 56,
textStyle: const TextStyle(fontSize: 20, color: Color.fromRGBO(30, 60, 87, 1), fontWeight: FontWeight.w600),
decoration: BoxDecoration(
border: Border.all(color: AppColors.seed.withOpacity(0.2)),
borderRadius: BorderRadius.circular(15),
),
);
final focusedPinTheme = defaultPinTheme.copyDecorationWith(
border: Border.all(color: AppColors.seed),
borderRadius: BorderRadius.circular(10),
);
final submittedPinTheme = defaultPinTheme.copyWith(
decoration: defaultPinTheme.decoration?.copyWith(
color: AppColors.seed.withOpacity(0.1),
),
);
return Scaffold(
appBar: AppBar(),
body: ListView(
padding: const EdgeInsets.all(AppSizes.bodyPadding),
children: [
const Text(
'OTP Verification',
style: TextStyle(fontSize: 20, fontWeight: FontWeight.w500),
),
Text(
"Enter the verification code we just sent on your email address (${widget.payload['email']})",
style: const TextStyle(fontSize: 12, color: AppColors.grey),
),
SizedBox(height: AppSizes.height(context) * 0.1,),
//for otp
Pinput(
length: 6,
defaultPinTheme: defaultPinTheme,
focusedPinTheme: focusedPinTheme,
submittedPinTheme: submittedPinTheme,
pinputAutovalidateMode: PinputAutovalidateMode.onSubmit,
showCursor: true,
autofocus: true,
cursor: const Text("|",style: TextStyle(fontWeight: FontWeight.w500, fontSize: 20, color: AppColors.seed),),
onCompleted: (pin) {
appLoader(context);
Map<String, String> matchPayload = {...widget.payload,"otp": pin};
postResponse(payload: matchPayload, url: AppUrls.matchEmailOtp).then((value) {
ApiResponse response = appParseJson(value, (data)=>data);
if(response.success != true){
Navigator.pop(context);
appAlertDialog(context, response.message??'Failed to match otp code', title: response.title);
}else{
Navigator.pop(context);
AppRoutes.pushReplacement(context, ResetPasswordScreen(token: response.token ?? '', matchOtpPayload: matchPayload));
}
});
},
),
//end for otp
SizedBox(height: AppSizes.height(context) * 0.1,),
//for timer & resend code
timerDone
?
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Text("Didn't received code?"),
TextButton(onPressed: (){
appLoader(context);
postResponse(payload: widget.payload, url: AppUrls.matchEmailOtp).then((value) {
ApiResponse response = appParseJson(value, (data)=>data);
if(response.success != true){
Navigator.pop(context);
appAlertDialog(context, response.message??'Send to failed otp code', title: response.title);
}else{
Navigator.pop(context);
appSnackBar(context, response.message??'OTP code sent successfully');
timerDone = false;
countCon.start();
setState(() {});
}
});
}, child: const Text("Resend"))
],
):Countdown(
seconds: 180,
controller: countCon,
build: (BuildContext context, double sec) {
final duration =
Duration(seconds: sec.toInt()).toString().split(':');
final seconds = duration[2].split('.')[0];
final minutes = duration[1];
return Text(
"OTP code will expire at $minutes:$seconds minutes",
textAlign: TextAlign.center,
);
},
interval: const Duration(seconds: 1),
onFinished: () {
timerDone = true;
setState(() {});
},
),
//end for timer & resend code
],
),
);
}
}
reset_password_screen.dart
class ResetPasswordScreen extends StatefulWidget {
const ResetPasswordScreen({super.key, required this.token, required this.matchOtpPayload});
final String token;
final Map<String, String> matchOtpPayload;
@override
State<ResetPasswordScreen> createState() => _ResetPasswordScreenState();
}
class _ResetPasswordScreenState extends State<ResetPasswordScreen> {
final GlobalKey<FormState> formKey = GlobalKey<FormState>();
final TextEditingController passwordCon1 = TextEditingController(),
passwordCon2 = TextEditingController();
bool hidePassword1 = true,hidePassword2 = true;
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(),
body: Form(
key: formKey,
child: ListView(
padding: const EdgeInsets.all(AppSizes.bodyPadding),
children: [
const Text(
'Reset Password',
style: TextStyle(fontSize: 20, fontWeight: FontWeight.w500),
),
const Text(
"Your identity has been verified. Set your new Password",
style: TextStyle(fontSize: 12, color: AppColors.grey),
),
SizedBox(
height: AppSizes.height(context) * 0.1,
),
AppTextFormField(
labelText: 'New Password',
hintText: 'Enter new password',
textInputAction: TextInputAction.next,
validator: (value) {
return value!.isEmpty ? 'Please enter new password' : null;
},
controller: passwordCon1,
obscureText: hidePassword1,
suffixIcon: IconButton(
onPressed: () {
setState(() {
hidePassword1 = !hidePassword1;
});
},
icon: Icon(
hidePassword1
? IconsaxPlusLinear.eye_slash
: IconsaxPlusLinear.eye,
color: AppColors.grey,
size: 18,
)),
),
AppTextFormField(
labelText: 'Re-type Password',
hintText: 'Enter re-type password',
textInputAction: TextInputAction.done,
validator: (value) {
return value!.isEmpty
? 'Please enter re-type password'
: value.trim() != passwordCon1.text.trim()
? "Retype password does not match"
: null;
},
controller: passwordCon2,
obscureText: hidePassword2,
suffixIcon: IconButton(
onPressed: () {
setState(() {
hidePassword2 = !hidePassword2;
});
},
icon: Icon(
hidePassword2
? IconsaxPlusLinear.eye_slash
: IconsaxPlusLinear.eye,
color: AppColors.grey,
size: 18,
)),
),
SizedBox(
height: AppSizes.height(context) * 0.1,
),
ElevatedButton(child: const Text("Reset Password"), onPressed: (){
final currentState = formKey.currentState;
if(currentState != null && currentState.validate()){
appLoader(context);
final Map<String, String> payload = {
"token": widget.token,
"email": widget.matchOtpPayload['email']??'',
"password": passwordCon2.text.trim()
};
postResponse(payload: payload, url: AppUrls.forgetPassword).then((value) {
ApiResponse response = appParseJson(value, (data)=>data);
if(response.success != true){
Navigator.pop(context);
appAlertDialog(context, response.message??'Failed to reset password', title: response.title);
}else{
Navigator.pop(context);
Navigator.pop(context);
Navigator.pop(context);
appSnackBar(context, response.message??'Password reset successfully');
LocalDB.putPassword(passwordCon2.text.trim());
}
});
}
})
],
)),
);
}
}
const ResetPasswordScreen({super.key, required this.token, required this.matchOtpPayload});
final String token;
final Map<String, String> matchOtpPayload;
@override
State<ResetPasswordScreen> createState() => _ResetPasswordScreenState();
}
class _ResetPasswordScreenState extends State<ResetPasswordScreen> {
final GlobalKey<FormState> formKey = GlobalKey<FormState>();
final TextEditingController passwordCon1 = TextEditingController(),
passwordCon2 = TextEditingController();
bool hidePassword1 = true,hidePassword2 = true;
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(),
body: Form(
key: formKey,
child: ListView(
padding: const EdgeInsets.all(AppSizes.bodyPadding),
children: [
const Text(
'Reset Password',
style: TextStyle(fontSize: 20, fontWeight: FontWeight.w500),
),
const Text(
"Your identity has been verified. Set your new Password",
style: TextStyle(fontSize: 12, color: AppColors.grey),
),
SizedBox(
height: AppSizes.height(context) * 0.1,
),
AppTextFormField(
labelText: 'New Password',
hintText: 'Enter new password',
textInputAction: TextInputAction.next,
validator: (value) {
return value!.isEmpty ? 'Please enter new password' : null;
},
controller: passwordCon1,
obscureText: hidePassword1,
suffixIcon: IconButton(
onPressed: () {
setState(() {
hidePassword1 = !hidePassword1;
});
},
icon: Icon(
hidePassword1
? IconsaxPlusLinear.eye_slash
: IconsaxPlusLinear.eye,
color: AppColors.grey,
size: 18,
)),
),
AppTextFormField(
labelText: 'Re-type Password',
hintText: 'Enter re-type password',
textInputAction: TextInputAction.done,
validator: (value) {
return value!.isEmpty
? 'Please enter re-type password'
: value.trim() != passwordCon1.text.trim()
? "Retype password does not match"
: null;
},
controller: passwordCon2,
obscureText: hidePassword2,
suffixIcon: IconButton(
onPressed: () {
setState(() {
hidePassword2 = !hidePassword2;
});
},
icon: Icon(
hidePassword2
? IconsaxPlusLinear.eye_slash
: IconsaxPlusLinear.eye,
color: AppColors.grey,
size: 18,
)),
),
SizedBox(
height: AppSizes.height(context) * 0.1,
),
ElevatedButton(child: const Text("Reset Password"), onPressed: (){
final currentState = formKey.currentState;
if(currentState != null && currentState.validate()){
appLoader(context);
final Map<String, String> payload = {
"token": widget.token,
"email": widget.matchOtpPayload['email']??'',
"password": passwordCon2.text.trim()
};
postResponse(payload: payload, url: AppUrls.forgetPassword).then((value) {
ApiResponse response = appParseJson(value, (data)=>data);
if(response.success != true){
Navigator.pop(context);
appAlertDialog(context, response.message??'Failed to reset password', title: response.title);
}else{
Navigator.pop(context);
Navigator.pop(context);
Navigator.pop(context);
appSnackBar(context, response.message??'Password reset successfully');
LocalDB.putPassword(passwordCon2.text.trim());
}
});
}
})
],
)),
);
}
}
Comments
Post a Comment