auth UI and Api

This commit is contained in:
mohammad
2024-07-30 16:36:12 +03:00
parent 3eb3ed10c9
commit 7e9b24a95b
34 changed files with 1773 additions and 510 deletions

View File

@ -0,0 +1,4 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M24 12C24 17.9897 19.6116 22.9542 13.875 23.8542V15.4688H16.6711L17.2031 12H13.875V9.74906C13.875 8.79984 14.34 7.875 15.8306 7.875H17.3438V4.92188C17.3438 4.92188 15.9703 4.6875 14.6573 4.6875C11.9166 4.6875 10.125 6.34875 10.125 9.35625V12H7.07812V15.4688H10.125V23.8542C4.38844 22.9542 0 17.9897 0 12C0 5.37281 5.37281 0 12 0C18.6272 0 24 5.37281 24 12Z" fill="#1877F2"/>
<path d="M16.6711 15.4688L17.2031 12H13.875V9.74902C13.875 8.80003 14.3399 7.875 15.8306 7.875H17.3438V4.92188C17.3438 4.92188 15.9705 4.6875 14.6576 4.6875C11.9165 4.6875 10.125 6.34875 10.125 9.35625V12H7.07812V15.4688H10.125V23.8542C10.736 23.95 11.3621 24 12 24C12.6379 24 13.264 23.95 13.875 23.8542V15.4688H16.6711Z" fill="white"/>
</svg>

After

Width:  |  Height:  |  Size: 825 B

8
assets/images/google.svg Normal file
View File

@ -0,0 +1,8 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M23.0938 9.91351L13.3045 9.91304C12.8722 9.91304 12.5218 10.2634 12.5218 10.6957V13.8229C12.5218 14.2551 12.8722 14.6055 13.3044 14.6055H18.8172C18.2135 16.1721 17.0869 17.4841 15.6494 18.3177L18 22.3869C21.7707 20.2061 24 16.3798 24 12.0965C24 11.4866 23.955 11.0506 23.8651 10.5597C23.7968 10.1867 23.473 9.91351 23.0938 9.91351Z" fill="#167EE6"/>
<path d="M11.9999 19.3043C9.30209 19.3043 6.94691 17.8303 5.68199 15.649L1.61298 17.9944C3.68367 21.5832 7.56275 23.9999 11.9999 23.9999C14.1767 23.9999 16.2306 23.4138 17.9999 22.3925V22.3869L15.6493 18.3177C14.5741 18.9413 13.3298 19.3043 11.9999 19.3043Z" fill="#12B347"/>
<path d="M17.9999 22.3925V22.3869L15.6493 18.3177C14.5741 18.9413 13.3299 19.3043 12 19.3043V23.9999C14.1767 23.9999 16.2307 23.4138 17.9999 22.3925Z" fill="#0F993E"/>
<path d="M4.69564 12C4.69564 10.6702 5.05854 9.42607 5.68203 8.3509L1.61301 6.00557C0.586029 7.76933 0 9.81767 0 12C0 14.1823 0.586029 16.2306 1.61301 17.9944L5.68203 15.649C5.05854 14.5739 4.69564 13.3298 4.69564 12Z" fill="#FFD500"/>
<path d="M11.9999 4.69564C13.7592 4.69564 15.3751 5.32076 16.6373 6.36059C16.9487 6.61709 17.4013 6.59857 17.6865 6.31334L19.9023 4.09756C20.2259 3.77394 20.2029 3.24421 19.8572 2.9443C17.7424 1.10967 14.9909 0 11.9999 0C7.56275 0 3.68367 2.41673 1.61298 6.00556L5.68199 8.35089C6.94691 6.16967 9.30209 4.69564 11.9999 4.69564Z" fill="#FF4B26"/>
<path d="M16.6374 6.36059C16.9488 6.61709 17.4014 6.59857 17.6866 6.31334L19.9023 4.09756C20.2259 3.77394 20.2029 3.24421 19.8572 2.9443C17.7424 1.10962 14.9909 0 12 0V4.69564C13.7592 4.69564 15.3752 5.32076 16.6374 6.36059Z" fill="#D93F21"/>
</svg>

After

Width:  |  Height:  |  Size: 1.7 KiB

BIN
assets/images/lift_line.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 179 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 188 B

View File

@ -1,6 +1,6 @@
import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';
import 'package:syncrow_web/pages/auth/view/login_page.dart';
import 'package:syncrow_web/pages/auth/login/view/login_page.dart';
import 'package:syncrow_web/services/locator.dart';
void main() {

View File

@ -0,0 +1,195 @@
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:syncrow_web/pages/auth/forget_password/bloc/forget_password_event.dart';
import 'package:syncrow_web/pages/auth/forget_password/bloc/forget_password_state.dart';
import 'package:syncrow_web/services/auth_api.dart';
class ForgetPasswordBloc
extends Bloc<ForgetPasswordEvent, ForgetPasswordState> {
ForgetPasswordBloc() : super(InitialForgetState()) {
on<ChangePasswordEvent>(changePassword);
on<StartTimerEvent>(_onStartTimer);
on<StopTimerEvent>(_onStopTimer);
on<UpdateTimerEvent>(_onUpdateTimer);
}
void _onUpdateTimer(UpdateTimerEvent event, Emitter<ForgetPasswordState> emit) {
emit(TimerState(
isButtonEnabled: event.isButtonEnabled,
remainingTime: event.remainingTime));
}
final TextEditingController emailController = TextEditingController();
final TextEditingController passwordController = TextEditingController();
final TextEditingController otp = TextEditingController();
final formKey = GlobalKey<FormState>();
Timer? _timer;
int _remainingTime = 0;
Future<void> _onStartTimer(
StartTimerEvent event, Emitter<ForgetPasswordState> emit) async {
if (_validateInputs(emit)) return;
print("StartTimerEvent received");
if (_timer != null && _timer!.isActive) {
print("Timer is already active");
return;
}
_remainingTime = 60;
add(UpdateTimerEvent(
remainingTime: _remainingTime, isButtonEnabled: false));
print("Timer started, initial remaining time: $_remainingTime");
await AuthenticationAPI.sendOtp(email: emailController.text);
_timer = Timer.periodic(const Duration(seconds: 1), (timer) {
_remainingTime--;
print("Timer tick, remaining time: $_remainingTime"); // Debug print
if (_remainingTime <= 0) {
_timer?.cancel();
add(const UpdateTimerEvent(remainingTime: 0, isButtonEnabled: true));
print("Timer finished"); // Debug print
} else {
add(UpdateTimerEvent(remainingTime: _remainingTime, isButtonEnabled: false));
}
});
}
void _onStopTimer(StopTimerEvent event, Emitter<ForgetPasswordState> emit) {
_timer?.cancel();
emit(TimerState(isButtonEnabled: true, remainingTime: 0));
}
Future<void> changePassword(
ChangePasswordEvent event, Emitter<ForgetPasswordState> emit) async {
try {
emit(LoadingForgetState());
await AuthenticationAPI.forgetPassword(
password: passwordController.text, email: emailController.text);
emit(SuccessForgetState());
} catch (failure) {
print(failure);
emit(FailureForgetState(error: failure.toString()));
}
}
String? validateEmail(String? value) {
if (value == null || value.isEmpty) {
return 'Email is required';
} else if (!RegExp(r'^[^@]+@[^@]+\.[^@]+').hasMatch(value)) {
return 'Enter a valid email address';
}
return null;
}
String? validateRegion(String? value) {
if (value == null || value.isEmpty) {
return 'Please select a region';
}
return null;
}
String? otpValidate(String? value) {
if (value == null || value.isEmpty) {
return 'Please select a region';
}
return null;
}
String? passwordValidator(String? value) {
if (value == null || value.isEmpty) {
return 'Please enter your password';
}
List<String> validationErrors = [];
if (!RegExp(r'^(?=.*[a-z])').hasMatch(value)) {
validationErrors.add(' - one lowercase letter');
}
if (!RegExp(r'^(?=.*[A-Z])').hasMatch(value)) {
validationErrors.add(' - one uppercase letter');
}
if (!RegExp(r'^(?=.*\d)').hasMatch(value)) {
validationErrors.add(' - one number');
}
if (!RegExp(r'^(?=.*[@$!%*?&])').hasMatch(value)) {
validationErrors.add(' - one special character');
}
if (value.length < 8) {
validationErrors.add(' - minimum 8 characters');
}
if (validationErrors.isNotEmpty) {
return 'Password must contain at least:\n${validationErrors.join('\n')}';
}
return null;
}
final List<String> regions = [
'North America',
'South America',
'Europe',
'Asia',
'Africa',
'Australia',
'Antarctica',
];
bool _validateInputs(Emitter<ForgetPasswordState> emit) {
emit(LoadingForgetState());
final nameError = validateEmail(emailController.text);
if (nameError != null) {
emit(FailureForgetState(error:nameError )) ;
// CustomSnackBar.displaySnackBar(nameError);
return true;
}
return false;
}
}
//
//
// _login(LoginButtonPressed event, Emitter<LoginState> emit) async {
// emit(LoginLoading());
// if(isChecked) {
// try {
// if (event.username.isEmpty || event.password.isEmpty) {
// CustomSnackBar.displaySnackBar('Please enter your credentials');
// emit(const LoginFailure(error: 'Something went wrong'));
// return;
// }
// token = await AuthenticationAPI.loginWithEmail(
// model: LoginWithEmailModel(
// email: event.username,
// password: event.password,
// ),
// );
// }
// catch (failure) {
// emit(const LoginFailure(error: 'Something went wrong'));
// // emit(LoginFailure(error: failure.toString()));
// return;
// }
// if (token.accessTokenIsNotEmpty) {
// debugPrint('token: ${token.accessToken}');
// FlutterSecureStorage storage = const FlutterSecureStorage();
// await storage.write(
// key: Token.loginAccessTokenKey, value: token.accessToken);
//
// const FlutterSecureStorage().write(
// key: UserModel.userUuidKey,
// value: Token.decodeToken(token.accessToken)['uuid'].toString()
// );
// user = UserModel.fromToken(token);
// emailController.clear();
// passwordController.clear();
// emit(LoginSuccess());
// } else {
// emit(const LoginFailure(error: 'Something went wrong'));
// }
// }
// else{
// emit(const LoginFailure(error: 'Accept terms and condition'));
// }
// }

View File

@ -0,0 +1,26 @@
import 'package:equatable/equatable.dart';
abstract class ForgetPasswordEvent extends Equatable {
const ForgetPasswordEvent();
@override
List<Object> get props => [];
}
class GetCodeEvent extends ForgetPasswordEvent{}
class SubmitEvent extends ForgetPasswordEvent{}
class StartTimerEvent extends ForgetPasswordEvent{}
class StopTimerEvent extends ForgetPasswordEvent{}
class UpdateTimerEvent extends ForgetPasswordEvent {
final int remainingTime;
final bool isButtonEnabled;
const UpdateTimerEvent({required this.remainingTime, required this.isButtonEnabled});
}
class ChangePasswordEvent extends ForgetPasswordEvent{}
class SendOtpEvent extends ForgetPasswordEvent{}

View File

@ -0,0 +1,35 @@
import 'package:equatable/equatable.dart';
class ForgetPasswordState extends Equatable{
const ForgetPasswordState();
@override
List<Object> get props => [];
}
class InitialForgetState extends ForgetPasswordState{}
class LoadingForgetState extends ForgetPasswordState{}
class SuccessForgetState extends ForgetPasswordState{}
class FailureForgetState extends ForgetPasswordState {
final String error;
const FailureForgetState({required this.error});
@override
List<Object> get props => [error];
}
class TimerState extends ForgetPasswordState {
final bool isButtonEnabled ;
final int remainingTime;
const TimerState({required this.isButtonEnabled, required this.remainingTime});
@override
List<Object> get props => [isButtonEnabled, remainingTime];
}

View File

@ -0,0 +1,11 @@
import 'package:flutter/material.dart';
class ForgetPasswordMobilePage extends StatelessWidget {
const ForgetPasswordMobilePage({super.key});
@override
Widget build(BuildContext context) {
return const Placeholder();
}
}

View File

@ -0,0 +1,20 @@
import 'package:flutter/material.dart';
import 'package:syncrow_web/pages/auth/forget_password/view/forget_password_mobile_page.dart';
import 'package:syncrow_web/pages/auth/forget_password/view/forget_password_web_page.dart';
import 'package:syncrow_web/utils/responsive_layout.dart';
class ForgetPasswordPage extends StatelessWidget {
const ForgetPasswordPage({super.key});
@override
Widget build(BuildContext context) {
return const ResponsiveLayout(
desktopBody: ForgetPasswordWebPage(),
mobileBody:ForgetPasswordMobilePage()
);
}
}

View File

@ -0,0 +1,271 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_svg/flutter_svg.dart';
import 'package:syncrow_web/pages/auth/forget_password/bloc/forget_password_bloc.dart';
import 'package:syncrow_web/pages/auth/forget_password/bloc/forget_password_event.dart';
import 'package:syncrow_web/pages/auth/forget_password/bloc/forget_password_state.dart';
import 'package:syncrow_web/pages/auth/login/view/login_page.dart';
import 'package:syncrow_web/pages/common/default_button.dart';
import 'package:syncrow_web/pages/common/first_layer.dart';
import 'package:syncrow_web/utils/color_manager.dart';
import 'package:syncrow_web/utils/constants/assets.dart';
import 'package:syncrow_web/utils/style.dart';
class ForgetPasswordWebPage extends StatelessWidget {
const ForgetPasswordWebPage({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
body: BlocProvider(
create: (context) => ForgetPasswordBloc(),
child: BlocConsumer<ForgetPasswordBloc, ForgetPasswordState>(
listener: (context, state) {
if (state is LoadingForgetState) {
} else if (state is FailureForgetState) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(state.error),
),
);
}
},
builder: (context, state) {
if (state is LoadingForgetState) {
return const Center(child: CircularProgressIndicator());
} else {
return _buildForm(context, state);
}
},
),
),
);
}
Widget _buildForm(BuildContext context, ForgetPasswordState state) {
final forgetBloc = BlocProvider.of<ForgetPasswordBloc>(context);
return FirstLayer(
second: Container(
margin: const EdgeInsets.all(50),
decoration: BoxDecoration(
color: Colors.black.withOpacity(0.3),
borderRadius: const BorderRadius.all(Radius.circular(20)),
),
child: Center(
child: ListView(
shrinkWrap: true,
children: [
Row(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Spacer(),
Expanded(
flex: 2,
child: SvgPicture.asset(
Assets.loginLogo,
),
),
const Spacer(),
Container(
decoration: BoxDecoration(
color: Colors.white.withOpacity(0.1),
borderRadius: const BorderRadius.all(Radius.circular(30)),
border: Border.all(color: ColorsManager.graysColor.withOpacity(0.2)),
),
child: Form(
key: forgetBloc.formKey,
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 50, vertical: 20),
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
const SizedBox(height: 15),
const Text(
'Forget Password',
style: TextStyle(
color: Colors.white,
fontSize: 24,
fontWeight: FontWeight.bold),
),
Text(
'Please fill in your account information to retrieve your password',
style: smallTextStyle,
),
const SizedBox(height: 30),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.start,
children: [
Text(
"Country/Region",
style: smallTextStyle,
),
SizedBox(
width: MediaQuery.of(context).size.width * 0.2,
child: DropdownButtonFormField<String>(
validator: forgetBloc.validateRegion,
icon: const Icon(
Icons.keyboard_arrow_down_outlined,
),
decoration: textBoxDecoration()!.copyWith(
hintText: null,
),
hint: const Align(
alignment: Alignment.centerLeft,
child: Text(
'Select your region/country',
textAlign: TextAlign.center,
),
),
isDense: true,
style: const TextStyle(color: Colors.black),
items: forgetBloc.regions.map((String region) {
return DropdownMenuItem<String>(
value: region,
child: Text(region),
);
}).toList(),
onChanged: (String? value) {
print(value);
},
),
)
],
),
const SizedBox(height: 20.0),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.start,
children: [
Text(
"Account",
style: smallTextStyle,
),
SizedBox(
width: MediaQuery.sizeOf(context).width * 0.2,
child: TextFormField(
validator: forgetBloc.validateEmail,
controller: forgetBloc.emailController,
decoration: textBoxDecoration()!.copyWith(hintText: 'Enter your email'),
style: const TextStyle(color: Colors.black),
),
),
],
),
const SizedBox(height: 20.0),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.start,
children: [
Text("One Time Password",
style: smallTextStyle,),
SizedBox(
width: MediaQuery.sizeOf(context).width * 0.2,
child: TextFormField(
validator: forgetBloc.otpValidate,
keyboardType: TextInputType.visiblePassword,
controller: forgetBloc.otp,
decoration: textBoxDecoration()!.copyWith(
hintText: 'Enter Code',
suffixIcon: SizedBox(
width: 100,
child: Center(
child: InkWell(
onTap: () {
BlocProvider.of<ForgetPasswordBloc>(context).add(StartTimerEvent());
},
child: Text(
'Get Code ${state is TimerState && !state.isButtonEnabled ? "(${state.remainingTime.toString()})" : ""}',
style: TextStyle(
color: state is TimerState && !state.isButtonEnabled
? Colors.grey
: ColorsManager.btnColor,
),
),
),
),
),
),
style: const TextStyle(color: Colors.black),
),
),
],
),
const SizedBox(height: 20.0),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.start,
children: [
Text("Password",
style: smallTextStyle,),
SizedBox(
width: MediaQuery.sizeOf(context).width * 0.2,
child: TextFormField(
validator: forgetBloc.passwordValidator,
keyboardType: TextInputType.visiblePassword,
controller: forgetBloc.passwordController,
decoration: textBoxDecoration()!.copyWith(
hintText: 'At least 8 characters',
),
style: const TextStyle(color: Colors.black),
),
),
],
),
const SizedBox(
height: 10,
),
const SizedBox(height: 20.0),
SizedBox(
width: MediaQuery.sizeOf(context).width * 0.2,
child: DefaultButton(
backgroundColor: ColorsManager.btnColor,
child: const Text('Submit'),
onPressed: () {
if (forgetBloc.formKey.currentState!.validate()) {
forgetBloc.add(ChangePasswordEvent());
}
},
),
),
SizedBox(height: 10,),
SizedBox(
width: MediaQuery.sizeOf(context).width * 0.2,
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
const Flexible(
child: Text(
"Do you have an account? ",
style: TextStyle(color: Colors.white),
)),
InkWell(
onTap: () {
Navigator.pop(context);
},
child: const Flexible(
child: Text(
"Sign in",
)),
),
],
),
)
],
),
),
),
),
const Spacer(),
],
),
],
),
),
),
);
}
}

View File

@ -1,58 +1,156 @@
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
import 'package:syncrow_web/pages/auth/bloc/login_event.dart';
import 'package:syncrow_web/pages/auth/bloc/login_state.dart';
import 'package:syncrow_web/pages/auth/model/login_with_email_model.dart';
import 'package:syncrow_web/pages/auth/model/token.dart';
import 'package:syncrow_web/pages/auth/model/user_model.dart';
import 'package:syncrow_web/pages/auth/login/bloc/login_event.dart';
import 'package:syncrow_web/pages/auth/login/bloc/login_state.dart';
import 'package:syncrow_web/pages/auth/login/model/login_with_email_model.dart';
import 'package:syncrow_web/pages/auth/login/model/token.dart';
import 'package:syncrow_web/pages/auth/login/model/user_model.dart';
import 'package:syncrow_web/services/auth_api.dart';
import 'package:syncrow_web/utils/snack_bar.dart';
class LoginBloc extends Bloc<LoginEvent, LoginState> {
LoginBloc() : super(LoginInitial()) {
on<LoginButtonPressed>(_login);
on<CheckBoxEvent>(checkBoxTgl);
}
final TextEditingController emailController = TextEditingController();
final TextEditingController passwordController = TextEditingController();
final formKey = GlobalKey<FormState>();
bool isChecked = false;
bool obscureText = true;
_login(LoginButtonPressed event, Emitter<LoginState> emit) async {
emit(LoginLoading());
if(isChecked) {
try {
if (event.username.isEmpty || event.password.isEmpty) {
CustomSnackBar.displaySnackBar('Please enter your credentials');
emit(const LoginFailure(error: 'Something went wrong'));
return;
}
token = await AuthenticationAPI.loginWithEmail(
model: LoginWithEmailModel(
email: event.username,
password: event.password,
),
);
}
catch (failure) {
emit(const LoginFailure(error: 'Something went wrong'));
// emit(LoginFailure(error: failure.toString()));
return;
}
if (token.accessTokenIsNotEmpty) {
debugPrint('token: ${token.accessToken}');
FlutterSecureStorage storage = const FlutterSecureStorage();
await storage.write(
key: Token.loginAccessTokenKey, value: token.accessToken);
const FlutterSecureStorage().write(
key: UserModel.userUuidKey,
value: Token.decodeToken(token.accessToken)['uuid'].toString()
);
user = UserModel.fromToken(token);
emailController.clear();
passwordController.clear();
emit(LoginSuccess());
} else {
emit(const LoginFailure(error: 'Something went wrong'));
}
}
else{
emit(const LoginFailure(error: 'Accept terms and condition'));
}
}
checkBoxTgl(CheckBoxEvent event, Emitter<LoginState> emit,){
emit(LoginLoading());
isChecked = event.newValue!;
emit(LoginInitial());
}
void launchURL(String url) {
if (kDebugMode) {
print('Launching URL: $url');
}
}
String fullName = '';
String email = '';
String forgetPasswordEmail = '';
String signUpPassword = '';
String newPassword = '';
String maskedEmail = '';
String otpCode = '';
final loginFormKey = GlobalKey<FormState>();
final signUpFormKey = GlobalKey<FormState>();
final checkEmailFormKey = GlobalKey<FormState>();
final createNewPasswordKey = GlobalKey<FormState>();
static Token token = Token.emptyConstructor();
static UserModel? user;
bool isPasswordVisible = false;
bool showValidationMessage = false;
/////////////////////////////////////VALIDATORS/////////////////////////////////////
String? passwordValidator(String? value) {
if (value != null) {
if (value.isEmpty) {
return 'Please enter your password';
}
if (value.isNotEmpty) {
if (!RegExp(r'^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[@$!%*?&])[A-Za-z\d@$!%*?&]{8,}$')
.hasMatch(value)) {
return 'Password must contain at least:\n - one uppercase letter.\n - one lowercase letter.\n - one number. \n - special character';
}
}
String? validatePassword(String? value) {
if (value == null || value.isEmpty) {
return 'Password is required';
} else if (value.length < 8) {
return 'Password must be at least 8 characters';
}
return null;
}
String? validateEmail(String? value) {
if (value == null || value.isEmpty) {
return 'Email is required';
} else if (!RegExp(r'^[^@]+@[^@]+\.[^@]+').hasMatch(value)) {
return 'Enter a valid email address';
}
return null;
}
String? validateRegion(String? value) {
if (value == null || value.isEmpty) {
return 'Please select a region';
}
return null;
}
String? passwordValidator(String? value) {
if (value == null || value.isEmpty) {
return 'Please enter your password';
}
List<String> validationErrors = [];
if (!RegExp(r'^(?=.*[a-z])').hasMatch(value)) {
validationErrors.add(' - one lowercase letter');
}
if (!RegExp(r'^(?=.*[A-Z])').hasMatch(value)) {
validationErrors.add(' - one uppercase letter');
}
if (!RegExp(r'^(?=.*\d)').hasMatch(value)) {
validationErrors.add(' - one number');
}
if (!RegExp(r'^(?=.*[@$!%*?&])').hasMatch(value)) {
validationErrors.add(' - one special character');
}
if (value.length < 8) {
validationErrors.add(' - minimum 8 characters');
}
if (validationErrors.isNotEmpty) {
return 'Password must contain at least:\n' + validationErrors.join('\n');
}
return null;
}
String? fullNameValidator(String? value) {
if (value == null) return 'Full name is required';
@ -80,14 +178,7 @@ class LoginBloc extends Bloc<LoginEvent, LoginState> {
return null;
}
String? reEnterPasswordCheck(String? value) {
passwordValidator(value);
if (signUpPassword == value) {
return null;
} else {
return 'Passwords do not match';
}
}
String? reEnterPasswordCheckForgetPass(String? value) {
passwordValidator(value);
@ -136,43 +227,16 @@ class LoginBloc extends Bloc<LoginEvent, LoginState> {
return '$maskedLocalPart@$domainPart';
}
_login(LoginButtonPressed event, Emitter<LoginState> emit) async {
emit(LoginLoading());
try {
if (event.username.isEmpty ||event.password.isEmpty) {
CustomSnackBar.displaySnackBar('Please enter your credentials');
emit(const LoginFailure(error: 'Something went wrong'));
return;
}
token = await AuthenticationAPI.loginWithEmail(
model: LoginWithEmailModel(
email: event.username,
password: event.password,
),
);
} catch (failure) {
emit( LoginFailure(error: failure.toString()));
return;
}
if (token.accessTokenIsNotEmpty) {
debugPrint('token: ${token.accessToken}');
FlutterSecureStorage storage = const FlutterSecureStorage();
await storage.write(key: Token.loginAccessTokenKey, value: token.accessToken);
const FlutterSecureStorage().write(
key: UserModel.userUuidKey,
value: Token.decodeToken(token.accessToken)['uuid'].toString()
);
user = UserModel.fromToken(token);
emailController.clear();
passwordController.clear();
emit(LoginSuccess());
} else {
emit(const LoginFailure(error: 'Something went wrong'));
}
}
final List<String> regions = [
'North America',
'South America',
'Europe',
'Asia',
'Africa',
'Australia',
'Antarctica',
];
// signUp() async {
// emit(LoginLoading());
@ -198,4 +262,7 @@ class LoginBloc extends Bloc<LoginEvent, LoginState> {
// }
// }
}

View File

@ -16,3 +16,13 @@ class LoginButtonPressed extends LoginEvent {
@override
List<Object> get props => [username, password];
}
class CheckBoxEvent extends LoginEvent {
final bool? newValue;
const CheckBoxEvent({required this.newValue,});
@override
List<Object> get props => [newValue!,];
}

View File

@ -21,3 +21,19 @@ class LoginFailure extends LoginState {
@override
List<Object> get props => [error];
}
class LoginValid extends LoginState {}
class LoginInvalid extends LoginState {
final String error;
const LoginInvalid({required this.error});
@override
List<Object> get props => [error];
}
// class LoginState extends LoginState {}

View File

@ -1,4 +1,5 @@
import 'package:syncrow_web/pages/auth/model/token.dart';
import 'package:syncrow_web/pages/auth/login/model/token.dart';
class UserModel {
static String userUuidKey = 'userUuid';

View File

@ -0,0 +1,426 @@
import 'dart:ui';
import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_svg/svg.dart';
import 'package:syncrow_web/pages/auth/forget_password/view/forget_password_page.dart';
import 'package:syncrow_web/pages/auth/login/bloc/login_bloc.dart';
import 'package:syncrow_web/pages/auth/login/bloc/login_event.dart';
import 'package:syncrow_web/pages/auth/login/bloc/login_state.dart';
import 'package:syncrow_web/pages/common/default_button.dart';
import 'package:syncrow_web/utils/color_manager.dart';
import 'package:syncrow_web/utils/constants/assets.dart';
import 'package:syncrow_web/pages/home/view/home_page.dart';
import 'package:syncrow_web/utils/style.dart';
class LoginMobilePage extends StatelessWidget {
const LoginMobilePage({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
body: BlocProvider(
create: (context) => LoginBloc(),
child: BlocConsumer<LoginBloc, LoginState>(
listener: (context, state) {
if (state is LoginSuccess) {
// Navigate to home screen after successful login
Navigator.pushReplacement(
context,
MaterialPageRoute(builder: (context) => const HomePage()),
);
} else if (state is LoginFailure) {
// Show error message
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(state.error),
),
);
}
},
builder: (context, state) {
if (state is LoginLoading) {
return const Center(child: CircularProgressIndicator());
} else {
return _buildLoginForm(context);
}
},
),
),
);
}
Widget _buildLoginForm(BuildContext context) {
final loginBloc = BlocProvider.of<LoginBloc>(context);
final TextEditingController _usernameController = TextEditingController();
final TextEditingController _passwordController = TextEditingController();
return Center(
child: Stack(
children: [
Container(
width: MediaQuery.sizeOf(context).width,
height: MediaQuery.sizeOf(context).height,
decoration: const BoxDecoration(
image: DecorationImage(
image: AssetImage(
Assets.background,
),
fit: BoxFit.cover,
),
),
),
Container(
width: MediaQuery.sizeOf(context).width,
height: MediaQuery.sizeOf(context).height,
decoration: const BoxDecoration(
image: DecorationImage(
image: AssetImage(Assets.vector),
fit: BoxFit.cover,
opacity: 0.9,
),
),
),
SingleChildScrollView(
child: Container(
margin: const EdgeInsets.all(50),
decoration: BoxDecoration(
color: Colors.black.withOpacity(0.3),
borderRadius: const BorderRadius.all(Radius.circular(20))),
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.center,
children: [
Padding(
padding: const EdgeInsets.all(15.0),
child: SvgPicture.asset(
Assets.loginLogo,
),
),
Container(
margin: EdgeInsets.all(30),
padding: EdgeInsets.all(20),
decoration: BoxDecoration(
color: Colors.white.withOpacity(0.1),
borderRadius:
const BorderRadius.all(Radius.circular(30)),
border: Border.all(
color:
ColorsManager.graysColor.withOpacity(0.2))),
child: Form(
key: loginBloc.formKey,
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
const SizedBox(height: 15),
const Text(
'Login',
style: TextStyle(
color: Colors.white,
fontSize: 24,
fontWeight: FontWeight.bold),
),
const SizedBox(height: 30),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.start,
children: [
Text(
"Country/Region",
style: smallTextStyle,
),
SizedBox(
child: DropdownButtonFormField<String>(
validator: loginBloc.validateRegion,
icon: const Icon(
Icons.keyboard_arrow_down_outlined,
),
decoration: textBoxDecoration()!.copyWith(
hintText: null,
),
hint: const Align(
alignment: Alignment.centerLeft,
child: Text(
'Select your region/country',
textAlign: TextAlign.center,
),
),
isDense: true,
style: const TextStyle(color: Colors.black),
items:
loginBloc.regions.map((String region) {
return DropdownMenuItem<String>(
value: region,
child: Text(region),
);
}).toList(),
onChanged: (String? value) {
print(value);
},
),
)
],
),
const SizedBox(height: 20.0),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.start,
children: [
Text(
"Email",
style: smallTextStyle,
),
SizedBox(
child: TextFormField(
validator: loginBloc.validateEmail,
controller: loginBloc.emailController,
decoration: textBoxDecoration()!
.copyWith(hintText: 'Enter your email'),
style: const TextStyle(color: Colors.black),
),
),
],
),
const SizedBox(height: 20.0),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.start,
children: [
Text(
"Password",
style: smallTextStyle,
),
SizedBox(
child: TextFormField(
validator: loginBloc.validatePassword,
obscureText: loginBloc.obscureText,
keyboardType: TextInputType.visiblePassword,
controller: loginBloc.passwordController,
decoration: textBoxDecoration()!.copyWith(
hintText: 'At least 8 characters',
),
style: const TextStyle(color: Colors.black),
),
),
],
),
const SizedBox(
height: 10,
),
SizedBox(
child: Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [
InkWell(
onTap: () {
Navigator.of(context)
.push(MaterialPageRoute(
builder: (context) =>
const ForgetPasswordPage(),
));
},
child: Text(
"Forgot Password?",
style: smallTextStyle,
),
),
],
),
),
Row(
children: [
Transform.scale(
scale: 1.2, // Adjust the scale as needed
child: Checkbox(
fillColor: MaterialStateProperty.all<Color>(
Colors.white),
activeColor: Colors.white,
value: loginBloc.isChecked,
checkColor: Colors.black,
shape: const CircleBorder(),
onChanged: (bool? newValue) {
loginBloc.add(
CheckBoxEvent(newValue: newValue));
},
),
),
SizedBox(
width: MediaQuery.sizeOf(context).width * 0.5,
child: RichText(
text: TextSpan(
text: 'Agree to ',
style:
const TextStyle(color: Colors.white),
children: [
TextSpan(
text: '(Terms of Service)',
style: const TextStyle(
color: Colors.black),
recognizer: TapGestureRecognizer()
..onTap = () {
loginBloc.launchURL(
'https://example.com/terms');
},
),
TextSpan(
text: ' (Legal Statement)',
style: const TextStyle(
color: Colors.black),
recognizer: TapGestureRecognizer()
..onTap = () {
loginBloc.launchURL(
'https://example.com/legal');
},
),
TextSpan(
text: ' (Privacy Statement)',
style: const TextStyle(
color: Colors.black),
recognizer: TapGestureRecognizer()
..onTap = () {
loginBloc.launchURL(
'https://example.com/privacy');
},
),
],
),
),
),
],
),
const SizedBox(height: 20.0),
SizedBox(
child: DefaultButton(
backgroundColor: loginBloc.isChecked
? ColorsManager.btnColor
: ColorsManager.grayColor,
child: const Text('Sign in'),
onPressed: () {
if (loginBloc.formKey.currentState!
.validate()) {
loginBloc.add(
LoginButtonPressed(
username:
loginBloc.emailController.text,
password:
loginBloc.passwordController.text,
),
);
}
},
),
),
// Padding(
// padding: const EdgeInsets.all(5.0),
// child: SizedBox(
// width: MediaQuery.sizeOf(context).width * 0.2,
// child: Row(
// mainAxisAlignment: MainAxisAlignment.center,
// crossAxisAlignment: CrossAxisAlignment.center,
// children: [
// Expanded(child: Image.asset(Assets.liftLine)),
// Expanded(
// child: Padding(
// padding: const EdgeInsets.all(5.0),
// child: Text('Or sign in with',
// style: smallTextStyle.copyWith(fontSize: 10),
// ),
// )
// ),
// Expanded(child: Image.asset(Assets.rightLine)),
// ],
// ),
// ),
// ),
// SizedBox(
// width: MediaQuery.sizeOf(context).width * 0.2,
// child: Row(
// crossAxisAlignment: CrossAxisAlignment.center,
// children: [
// Expanded(
// child: Container(
// decoration: containerDecoration,
// child:InkWell(
// child: Padding(
// padding: const EdgeInsets.all(8.0),
// child: Row(
// mainAxisAlignment: MainAxisAlignment.spaceAround,
// crossAxisAlignment: CrossAxisAlignment.center,
// children: [
// SvgPicture.asset(
// Assets.google,
// fit: BoxFit.cover,
// ),
// const Flexible(
// child: Text('Google',
// style: TextStyle(color: Colors.black),
// ),
// ),
// ],
// ),
// ),
// onTap: () {},
// ),
// ),
// ),
// SizedBox(width: 10,),
// Expanded(
// child: Container(
// decoration: containerDecoration,
// child:InkWell(
// child: Padding(
// padding: const EdgeInsets.all(8.0),
// child: Row(
// mainAxisAlignment: MainAxisAlignment.spaceAround,
// crossAxisAlignment: CrossAxisAlignment.center,
// children: [
// SvgPicture.asset(
// Assets.facebook,
// fit: BoxFit.cover,
// ),
// const Flexible(
// child: Text('Facebook',
// style: TextStyle(color: Colors.black),
// ),
// ),
// ],
// ),
// ),
// onTap: () {},
// ),
// ),
// ),
//
// ],
// ),
// ),
const SizedBox(
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Flexible(
child: Text(
"Don't you have an account? ",
style: TextStyle(
color: Colors.white, fontSize: 13),
)),
Text(
"Sign up",
),
],
),
)
],
),
)),
],
),
),
),
],
),
);
}
}

View File

@ -1,8 +1,8 @@
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter/widgets.dart';
import 'package:syncrow_web/pages/auth/view/login_mobile_page.dart';
import 'package:syncrow_web/pages/auth/view/login_web_page.dart';
import 'package:syncrow_web/pages/auth/login/view/login_mobile_page.dart';
import 'package:syncrow_web/pages/auth/login/view/login_web_page.dart';
import 'package:syncrow_web/utils/responsive_layout.dart';
class LoginPage extends StatelessWidget {

View File

@ -0,0 +1,393 @@
import 'package:flutter/cupertino.dart';
import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';
import 'package:flutter/widgets.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_svg/svg.dart';
import 'package:syncrow_web/pages/auth/login/bloc/login_bloc.dart';
import 'package:syncrow_web/pages/auth/login/bloc/login_event.dart';
import 'package:syncrow_web/pages/auth/login/bloc/login_state.dart';
import 'package:syncrow_web/pages/auth/forget_password/view/forget_password_page.dart';
import 'package:syncrow_web/pages/common/default_button.dart';
import 'package:syncrow_web/pages/common/first_layer.dart';
import 'package:syncrow_web/utils/color_manager.dart';
import 'package:syncrow_web/utils/constants/assets.dart';
import 'package:syncrow_web/pages/home/view/home_page.dart';
import 'package:syncrow_web/utils/style.dart';
class LoginWebPage extends StatelessWidget {
const LoginWebPage({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
body: BlocProvider(
create: (context) => LoginBloc(),
child: BlocConsumer<LoginBloc, LoginState>(
listener: (context, state) {
if (state is LoginSuccess) {
// Navigate to home screen after successful login
Navigator.pushReplacement(
context,
MaterialPageRoute(builder: (context) => const HomePage()),
);
} else if (state is LoginFailure) {
// Show error message
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(state.error),
),
);
}
},
builder: (context, state) {
if (state is LoginLoading) {
return const Center(child: CircularProgressIndicator());
} else {
return _buildLoginForm(context);
}
},
),
),
);
}
Widget _buildLoginForm(BuildContext context) {
final loginBloc = BlocProvider.of<LoginBloc>(context);
return FirstLayer(
second: Container(
margin: const EdgeInsets.all(50),
decoration: BoxDecoration(
color: Colors.black.withOpacity(0.3),
borderRadius: const BorderRadius.all(Radius.circular(20)),
),
child: Center(
child: ListView(
shrinkWrap: true,
children: [
Row(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Spacer(),
Expanded(
flex: 2,
child: SvgPicture.asset(
Assets.loginLogo,
),
),
const Spacer(),
Container(
decoration: BoxDecoration(
color: Colors.white.withOpacity(0.1),
borderRadius: const BorderRadius.all(Radius.circular(30)),
border: Border.all(color: ColorsManager.graysColor.withOpacity(0.2))),
child: Form(
key: loginBloc.formKey,
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 50, vertical: 20),
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
const SizedBox(height: 15),
const Text(
'Login',
style: TextStyle(
color: Colors.white,
fontSize: 24,
fontWeight: FontWeight.bold),
),
const SizedBox(height: 30),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.start,
children: [
Text(
"Country/Region",
style: smallTextStyle,
),
SizedBox(
width: MediaQuery.of(context).size.width * 0.2,
child: DropdownButtonFormField<String>(
validator:loginBloc.validateRegion ,
icon: const Icon(
Icons.keyboard_arrow_down_outlined,
),
decoration: textBoxDecoration()!.copyWith(
hintText: null,
),
hint: const Align(
alignment: Alignment.centerLeft,
child: Text(
'Select your region/country',
textAlign: TextAlign.center,
),
),
isDense: true,
style: const TextStyle(color: Colors.black),
items:loginBloc.regions.map((String region) {
return DropdownMenuItem<String>(
value: region,
child: Text(region),
);
}).toList(),
onChanged: (String? value) {
print(value);
},
),
)
],
),
const SizedBox(height: 20.0),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.start,
children: [
Text("Email",
style: smallTextStyle,
),
SizedBox(
width: MediaQuery.sizeOf(context).width * 0.2,
child: TextFormField(
validator:loginBloc.validateEmail ,
controller:loginBloc.emailController,
decoration: textBoxDecoration()!.copyWith(hintText: 'Enter your email'),
style: const TextStyle(color: Colors.black),
),
),
],
),
const SizedBox(height: 20.0),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.start,
children: [
Text("Password",
style: smallTextStyle,),
SizedBox(
width: MediaQuery.sizeOf(context).width * 0.2,
child: TextFormField(
validator:loginBloc.validatePassword ,
obscureText:loginBloc.obscureText,
keyboardType: TextInputType.visiblePassword,
controller:loginBloc.passwordController,
decoration: textBoxDecoration()!.copyWith(
hintText: 'At least 8 characters',
),
style: const TextStyle(color: Colors.black),
),
),
],
),
const SizedBox(
height: 10,
),
SizedBox(
width: MediaQuery.of(context).size.width * 0.2,
child: Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [
InkWell(
onTap: () {
Navigator.of(context).push(MaterialPageRoute(builder: (context) => const ForgetPasswordPage(),));
},
child: Text(
"Forgot Password?",
style: smallTextStyle,
),
),
],
),
),
Row(
children: [
Transform.scale(
scale: 1.2, // Adjust the scale as needed
child: Checkbox(
fillColor: MaterialStateProperty.all<Color>(Colors.white),
activeColor: Colors.white,
value:loginBloc.isChecked,
checkColor: Colors.black,
shape: const CircleBorder(),
onChanged: (bool? newValue) {
loginBloc.add(CheckBoxEvent(newValue: newValue));
},
),
),
SizedBox(
width: MediaQuery.sizeOf(context).width * 0.2,
child: RichText(
text: TextSpan(
text: 'Agree to ',
style: const TextStyle(color: Colors.white),
children: [
TextSpan(
text: '(Terms of Service)',
style: const TextStyle(
color: Colors.black),
recognizer: TapGestureRecognizer()
..onTap = () {
loginBloc.launchURL(
'https://example.com/terms');
},
),
TextSpan(
text: ' (Legal Statement)',
style: const TextStyle(color: Colors.black),
recognizer: TapGestureRecognizer()
..onTap = () {
loginBloc.launchURL(
'https://example.com/legal');
},
),
TextSpan(
text: ' (Privacy Statement)',
style: const TextStyle(
color: Colors.black),
recognizer: TapGestureRecognizer()
..onTap = () {
loginBloc.launchURL(
'https://example.com/privacy');
},
),
],
),
),
),
],
),
const SizedBox(height: 20.0),
SizedBox(
width: MediaQuery.sizeOf(context).width * 0.2,
child: DefaultButton(
backgroundColor: loginBloc.isChecked?
ColorsManager.btnColor:ColorsManager.grayColor,
child: const Text('Sign in'),
onPressed: () {
if (loginBloc.formKey.currentState!.validate()) {
loginBloc.add(LoginButtonPressed(
username: loginBloc.emailController.text,
password: loginBloc.passwordController.text,
),
);
}
},
),
),
// Padding(
// padding: const EdgeInsets.all(5.0),
// child: SizedBox(
// width: MediaQuery.sizeOf(context).width * 0.2,
// child: Row(
// mainAxisAlignment: MainAxisAlignment.center,
// crossAxisAlignment: CrossAxisAlignment.center,
// children: [
// Expanded(child: Image.asset(Assets.liftLine)),
// Expanded(
// child: Padding(
// padding: const EdgeInsets.all(5.0),
// child: Text('Or sign in with',
// style: smallTextStyle.copyWith(fontSize: 10),
// ),
// )
// ),
// Expanded(child: Image.asset(Assets.rightLine)),
// ],
// ),
// ),
// ),
// SizedBox(
// width: MediaQuery.sizeOf(context).width * 0.2,
// child: Row(
// crossAxisAlignment: CrossAxisAlignment.center,
// children: [
// Expanded(
// child: Container(
// decoration: containerDecoration,
// child:InkWell(
// child: Padding(
// padding: const EdgeInsets.all(8.0),
// child: Row(
// mainAxisAlignment: MainAxisAlignment.spaceAround,
// crossAxisAlignment: CrossAxisAlignment.center,
// children: [
// SvgPicture.asset(
// Assets.google,
// fit: BoxFit.cover,
// ),
// const Flexible(
// child: Text('Google',
// style: TextStyle(color: Colors.black),
// ),
// ),
// ],
// ),
// ),
// onTap: () {},
// ),
// ),
// ),
// SizedBox(width: 10,),
// Expanded(
// child: Container(
// decoration: containerDecoration,
// child:InkWell(
// child: Padding(
// padding: const EdgeInsets.all(8.0),
// child: Row(
// mainAxisAlignment: MainAxisAlignment.spaceAround,
// crossAxisAlignment: CrossAxisAlignment.center,
// children: [
// SvgPicture.asset(
// Assets.facebook,
// fit: BoxFit.cover,
// ),
// const Flexible(
// child: Text('Facebook',
// style: TextStyle(color: Colors.black),
// ),
// ),
// ],
// ),
// ),
// onTap: () {},
// ),
// ),
// ),
//
// ],
// ),
// ),
SizedBox(
width: MediaQuery.sizeOf(context).width * 0.2,
child: const Row(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Flexible(
child: Text(
"Don't you have an account? ",
style: TextStyle(color: Colors.white),
)),
Flexible(
child: Text(
"Sign up",
)),
],
),
)
],
),
),
)
),
const Spacer(),
],
),
],
)),
),
);
}
}

View File

@ -1,220 +0,0 @@
import 'dart:ui';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_svg/svg.dart';
import 'package:syncrow_web/utils/constants/assets.dart';
import 'package:syncrow_web/pages/auth/bloc/login_bloc.dart';
import 'package:syncrow_web/pages/auth/bloc/login_event.dart';
import 'package:syncrow_web/pages/auth/bloc/login_state.dart';
import 'package:syncrow_web/pages/home/view/home_page.dart';
class LoginMobilePage extends StatelessWidget {
const LoginMobilePage({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
body: BlocProvider(
create: (context) => LoginBloc(),
child: BlocConsumer<LoginBloc, LoginState>(
listener: (context, state) {
if (state is LoginSuccess) {
// Navigate to home screen after successful login
Navigator.pushReplacement(
context,
MaterialPageRoute(builder: (context) => const HomePage()),
);
} else if (state is LoginFailure) {
// Show error message
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(state.error),
),
);
}
},
builder: (context, state) {
if (state is LoginLoading) {
return const Center(child: CircularProgressIndicator());
} else {
return _buildLoginForm(context);
}
},
),
),
);
}
Widget _buildLoginForm(BuildContext context) {
final loginBloc = BlocProvider.of<LoginBloc>(context);
final TextEditingController _usernameController = TextEditingController();
final TextEditingController _passwordController = TextEditingController();
return Center(
child: Stack(
children: [
Container(
width: MediaQuery.sizeOf(context).width,
height: MediaQuery.sizeOf(context).height,
decoration: const BoxDecoration(
image: DecorationImage(
image: AssetImage(
Assets.background,
),
fit: BoxFit.cover,
),
),
),
Container(
width: MediaQuery.sizeOf(context).width,
height: MediaQuery.sizeOf(context).height,
decoration: const BoxDecoration(
image: DecorationImage(
image: AssetImage(Assets.vector),
fit: BoxFit.cover,
opacity: 0.9,
),
),
),
SingleChildScrollView(
child: Container(
margin: const EdgeInsets.all(50),
decoration: BoxDecoration(
color: Colors.black.withOpacity(0.3),
borderRadius: const BorderRadius.all(Radius.circular(20))),
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.center,
children: [
Padding(
padding: const EdgeInsets.all(15.0),
child: SvgPicture.asset(
Assets.loginLogo,
),
),
Container(
margin: const EdgeInsets.all(15),
decoration: BoxDecoration(
color: Colors.white.withOpacity(0.3),
borderRadius: const BorderRadius.all(Radius.circular(30))),
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 50, vertical: 20),
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
const SizedBox(height: 15),
const Text(
'Login',
style: TextStyle(
color: Colors.white,
fontSize: 24,
fontWeight: FontWeight.bold),
),
const SizedBox(height: 15),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.start,
children: [
const Text(
"Email",
style: TextStyle(
color: Colors.white,
fontSize: 13,
fontWeight: FontWeight.bold),
),
SizedBox(
child: TextFormField(
decoration: InputDecoration(
labelText: 'Email',
labelStyle: TextStyle(color: Colors.white),
hintText: 'username@gmail.com',
hintStyle: TextStyle(color: Colors.grey),
enabledBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(8.0),
borderSide: BorderSide(color: Colors.white),
),
focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(8.0),
borderSide: BorderSide(color: Colors.white),
),
filled: true,
fillColor: Colors.white,
contentPadding: EdgeInsets.symmetric(
vertical: 16.0, horizontal: 12.0),
),
style: TextStyle(color: Colors.black),
)),
],
),
const SizedBox(height: 15.0),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.start,
children: [
const Text(
"Password",
style: TextStyle(
color: Colors.white,
fontSize: 13,
fontWeight: FontWeight.bold),
),
SizedBox(
child: TextFormField(
decoration: InputDecoration(
labelText: 'Password',
labelStyle: TextStyle(color: Colors.white),
hintText: 'Password',
hintStyle: TextStyle(color: Colors.grey),
enabledBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(8.0),
borderSide: BorderSide(color: Colors.white),
),
focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(8.0),
borderSide: BorderSide(color: Colors.white),
),
filled: true,
fillColor: Colors.white,
contentPadding: EdgeInsets.symmetric(
vertical: 16.0, horizontal: 12.0),
),
style: TextStyle(color: Colors.black),
)),
const SizedBox(height: 20.0),
const Text(
"Forgot Password?",
style: TextStyle(
color: Colors.white,
fontSize: 13,
fontWeight: FontWeight.bold),
),
],
),
const SizedBox(height: 20.0),
ElevatedButton(
onPressed: () async {
// Trigger login event
loginBloc.add(
LoginButtonPressed(
username: _usernameController.text,
password: _passwordController.text,
),
);
},
child: const Text('Login'),
),
],
),
),
),
],
),
),
),
],
),
);
}
}

View File

@ -1,194 +0,0 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_svg/svg.dart';
import 'package:syncrow_web/utils/constants/assets.dart';
import 'package:syncrow_web/pages/auth/bloc/login_bloc.dart';
import 'package:syncrow_web/pages/auth/bloc/login_event.dart';
import 'package:syncrow_web/pages/auth/bloc/login_state.dart';
import 'package:syncrow_web/pages/home/view/home_page.dart';
import 'package:syncrow_web/utils/style.dart';
class LoginWebPage extends StatelessWidget {
const LoginWebPage({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
body: BlocProvider(
create: (context) => LoginBloc(),
child: BlocConsumer<LoginBloc, LoginState>(
listener: (context, state) {
if (state is LoginSuccess) {
// Navigate to home screen after successful login
Navigator.pushReplacement(
context,
MaterialPageRoute(builder: (context) => const HomePage()),
);
} else if (state is LoginFailure) {
// Show error message
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(state.error),
),
);
}
},
builder: (context, state) {
if (state is LoginLoading) {
return const Center(child: CircularProgressIndicator());
} else {
return _buildLoginForm(context);
}
},
),
),
);
}
Widget _buildLoginForm(BuildContext context) {
final loginBloc = BlocProvider.of<LoginBloc>(context);
final TextEditingController _usernameController = TextEditingController();
final TextEditingController _passwordController = TextEditingController();
return Center(
child: Stack(
children: [
Container(
width: MediaQuery.sizeOf(context).width,
height: MediaQuery.sizeOf(context).height,
child: SvgPicture.asset(
Assets.webBackground,
fit: BoxFit.cover,
),
),
Container(
width: MediaQuery.sizeOf(context).width,
height: MediaQuery.sizeOf(context).height,
decoration: const BoxDecoration(
image: DecorationImage(
image: AssetImage(Assets.vector),
fit: BoxFit.cover,
opacity: 0.9,
),
),
),
Container(
margin: const EdgeInsets.all(50),
decoration: BoxDecoration(
color: Colors.black.withOpacity(0.3),
borderRadius: const BorderRadius.all(Radius.circular(20)),
),
child:Center(
child: ListView(
shrinkWrap: true,
children: [
Row(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Spacer(),
Expanded(
flex: 2,
child: SvgPicture.asset(
Assets.loginLogo,
),
),
const Spacer(),
Container(
decoration: BoxDecoration(
color: Colors.white.withOpacity(0.3),
borderRadius: const BorderRadius.all(Radius.circular(30)),
),
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 50, vertical: 20),
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
const SizedBox(height: 15),
const Text(
'Login',
style: TextStyle(
color: Colors.white,
fontSize: 24,
fontWeight: FontWeight.bold),
),
const SizedBox(height: 30),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.start,
children: [
const Text(
"Email",
style: TextStyle(
color: Colors.white,
fontSize: 13,
fontWeight: FontWeight.bold),
),
SizedBox(
width: MediaQuery.sizeOf(context).width * 0.2,
child: TextFormField(
controller: _usernameController,
decoration: textBoxDecoration()!.copyWith(hintText: 'Email'),
style: const TextStyle(color: Colors.black),
),
),
],
),
const SizedBox(height: 20.0),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.start,
children: [
const Text(
"Password",
style: TextStyle(
color: Colors.white,
fontSize: 13,
fontWeight: FontWeight.bold),
),
SizedBox(
width: MediaQuery.sizeOf(context).width * 0.2,
child: TextFormField(
controller: _passwordController,
decoration: textBoxDecoration()!.copyWith(hintText: 'Password' ,),
style: const TextStyle(color: Colors.black),
),
),
const SizedBox(height: 20.0),
const Text(
"Forgot Password?",
style: TextStyle(
color: Colors.white,
fontSize: 13,
fontWeight: FontWeight.bold),
),
],
),
const SizedBox(height: 20.0),
ElevatedButton(
onPressed: () async {
// Trigger login event
loginBloc.add(
LoginButtonPressed(
username: _usernameController.text,
password: _passwordController.text,
),
);
},
child: const Text('Login'),
),
],
),
),
),
const Spacer(),
],
),
],
)),
),
],
),
);
}
}

View File

@ -0,0 +1,106 @@
import 'package:flutter/material.dart';
import 'package:syncrow_web/utils/color_manager.dart';
import 'package:syncrow_web/utils/style.dart';
class DefaultButton extends StatelessWidget {
const DefaultButton({
super.key,
this.enabled = true,
this.onPressed,
required this.child,
this.isSecondary = false,
this.isLoading = false,
this.isDone = false,
this.customTextStyle,
this.customButtonStyle,
this.backgroundColor,
this.foregroundColor,
this.borderRadius,
this.height,
this.padding,
});
final void Function()? onPressed;
final Widget child;
final double? height;
final bool isSecondary;
final double? borderRadius;
final bool enabled;
final double? padding;
final bool isDone;
final bool isLoading;
final TextStyle? customTextStyle;
final ButtonStyle? customButtonStyle;
final Color? backgroundColor;
final Color? foregroundColor;
@override
Widget build(BuildContext context) {
return ElevatedButton(
onPressed: enabled ? onPressed : null,
style: isSecondary
? null
: customButtonStyle ??
ButtonStyle(
textStyle: MaterialStateProperty.all(
customTextStyle
?? smallTextStyle.copyWith(
fontSize: 13,
color: foregroundColor,
fontWeight: FontWeight.normal
),
),
foregroundColor: MaterialStateProperty.all(
isSecondary
? Colors.black
: enabled
? foregroundColor ?? Colors.white
: Colors.black,
),
backgroundColor: MaterialStateProperty.resolveWith<Color>(
(Set<MaterialState> states) {
return enabled
? backgroundColor ?? ColorsManager.primaryColor
: Colors.grey;
}),
shape: MaterialStateProperty.all(
RoundedRectangleBorder(
borderRadius: BorderRadius.circular(borderRadius ?? 20),
),
),
fixedSize: MaterialStateProperty.all(
const Size.fromHeight(50),
),
padding: MaterialStateProperty.all(
EdgeInsets.all(padding ?? 10),
),
minimumSize: MaterialStateProperty.all(
const Size.fromHeight(50),
),
),
child: SizedBox(
height: height ?? 50,
child: Center(
child: isLoading
? const SizedBox.square(
dimension: 24,
child: CircularProgressIndicator(
color: Colors.white,
),
)
: isDone
? const Icon(
Icons.check_circle_outline,
color: Colors.white,
)
: child,
),
),
);
}
}

View File

@ -0,0 +1,38 @@
import 'package:flutter/material.dart';
import 'package:flutter_svg/svg.dart';
import 'package:syncrow_web/utils/constants/assets.dart';
class FirstLayer extends StatelessWidget {
final Widget? second;
const FirstLayer({super.key,this.second});
@override
Widget build(BuildContext context) {
return Center(
child: Stack(
children: [
SizedBox(
width: MediaQuery.sizeOf(context).width,
height: MediaQuery.sizeOf(context).height,
child: SvgPicture.asset(
Assets.webBackground,
fit: BoxFit.cover,
),
),
Container(
width: MediaQuery.sizeOf(context).width,
height: MediaQuery.sizeOf(context).height,
decoration: const BoxDecoration(
image: DecorationImage(
image: AssetImage(Assets.vector),
fit: BoxFit.cover,
opacity: 0.9,
),
),
),
second!
],
),
);
}
}

View File

@ -1,9 +1,10 @@
import 'dart:io';
import 'package:dio/dio.dart';
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
import 'package:syncrow_web/pages/auth/model/token.dart';
import 'dart:async';
import 'package:syncrow_web/pages/auth/login/model/token.dart';
import 'package:syncrow_web/services/api/network_exception.dart';
import 'dart:async';
import 'package:syncrow_web/utils/constants/api_const.dart';
import 'package:syncrow_web/utils/snack_bar.dart';
@ -30,7 +31,7 @@ class HTTPInterceptor extends InterceptorsWrapper {
if (checkHeaderExclusionListOfAddedParameters(options.path)) {
options.headers.putIfAbsent(HttpHeaders.authorizationHeader, () => "Bearer $token");
}
options.headers['Authorization'] = 'Bearer ${token!}';
// options.headers['Authorization'] = 'Bearer ${'${token!}123'}';
super.onRequest(options, handler);
}

View File

@ -1,6 +1,8 @@
import 'package:syncrow_web/pages/auth/model/token.dart';
import 'package:syncrow_web/pages/auth/login/model/token.dart';
import 'package:syncrow_web/services/api/http_service.dart';
import 'package:syncrow_web/utils/constants/api_const.dart';
import 'api/http_service.dart';
class AuthenticationAPI {
@ -9,21 +11,54 @@ class AuthenticationAPI {
final response = await HTTPService().post(
path: ApiEndpoints.login,
body: model.toJson(),
showServerMessage: false,
expectedResponseModel: (json) => Token.fromJson(json['data']));
showServerMessage: true,
expectedResponseModel: (json) {
return Token.fromJson(json['data']);
});
return response;
}
// static Future<bool> signUp({required SignUpModel model}) async {
// final response = await HTTPService().post(
// path: ApiEndpoints.signUp,
// body: model.toJson(),
// showServerMessage: false,
// expectedResponseModel: (json) => json['statusCode'] == 201);
// return response;
// }
static Future forgetPassword({ required var email, required var password}) async {
final response = await HTTPService().post(
path: ApiEndpoints.forgetPassword,
body: {
"email": email,
"password": password
},
showServerMessage: true,
expectedResponseModel: (json) {
});
return response;
}
static Future sendOtp({ required var email}) async {
final response = await HTTPService().post(
path: ApiEndpoints.sendOtp,
body: {
"email": email,
"type": "VERIFICATION"
},
showServerMessage: true,
expectedResponseModel: (json) {
print('json===$json');
});
return response;
}
static Future checkOtp({ required var email}) async {
final response = await HTTPService().post(
path: ApiEndpoints.sendOtp,
body: {
"email": email,
"type": "VERIFICATION"
},
showServerMessage: true,
expectedResponseModel: (json) {
});
return response;
}
}

View File

@ -27,4 +27,5 @@ abstract class ColorsManager {
static const Color red = Colors.red;
static const Color graysColor = Color(0xffEBEBEB);
static const Color textGray = Color(0xffD5D5D5);
static const Color btnColor = Color(0xFF00008B);
}

View File

@ -1,8 +1,10 @@
abstract class ApiEndpoints {
static const String baseUrl = 'https://syncrow-staging.azurewebsites.net';
static const String baseUrl = 'https://syncrow-dev.azurewebsites.net';
// static const String baseUrl = 'http://100.107.182.63:4001'; //Localhost
//https://syncrow-staging.azurewebsites.net
////////////////////////////////////// Authentication ///////////////////////////////
static const String signUp = 'authentication/user/signup';
static const String login = 'authentication/user/login';
static const String signUp = '$baseUrl/authentication/user/signup';
static const String login = '$baseUrl/authentication/user/login';
static const String forgetPassword = '$baseUrl/authentication/user/forget-password';
static const String sendOtp = '$baseUrl/authentication/user/send-otp';
}

View File

@ -9,4 +9,8 @@ class Assets {
static const String loginLogo = "assets/images/login_logo.svg";
static const String whiteLogo = "assets/images/white-logo.png";
static const String window = "assets/images/Window.png";
static const String liftLine = "assets/images/lift_line.png";
static const String rightLine = "assets/images/right_line.png";
static const String google = "assets/images/google.svg";
static const String facebook = "assets/images/facebook.svg";
}

View File

@ -16,6 +16,7 @@ class CustomSnackBar {
BuildContext? currentContext = key?.currentContext;
if (key != null && currentContext != null) {
final snackBar = SnackBar(
padding: const EdgeInsets.all(16),
backgroundColor: Colors.green,
content: Row(mainAxisAlignment: MainAxisAlignment.center, children: [

View File

@ -23,4 +23,10 @@ InputDecoration? textBoxDecoration({bool suffixIcon = false}) => InputDecoration
TextStyle appBarTextStyle =
const TextStyle(fontSize: 20, color: ColorsManager.whiteColors);
const TextStyle(fontSize: 20, color: ColorsManager.whiteColors);
TextStyle smallTextStyle =
const TextStyle(fontSize: 13, color: ColorsManager.whiteColors,fontWeight: FontWeight.bold);
Decoration containerDecoration = const BoxDecoration(color: Colors.white,borderRadius: BorderRadius.all(Radius.circular(20)));

View File

@ -324,10 +324,10 @@ packages:
dependency: transitive
description:
name: path_provider_android
sha256: "30c5aa827a6ae95ce2853cdc5fe3971daaac00f6f081c419c013f7f57bff2f5e"
sha256: e84c8a53fe1510ef4582f118c7b4bdf15b03002b51d7c2b66983c65843d61193
url: "https://pub.dev"
source: hosted
version: "2.2.7"
version: "2.2.8"
path_provider_foundation:
dependency: transitive
description: