mirror of
https://github.com/SyncrowIOT/web.git
synced 2025-07-10 07:07:19 +00:00
auth UI and Api
This commit is contained in:
@ -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() {
|
||||
|
195
lib/pages/auth/forget_password/bloc/forget_password_bloc.dart
Normal file
195
lib/pages/auth/forget_password/bloc/forget_password_bloc.dart
Normal 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'));
|
||||
// }
|
||||
// }
|
@ -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{}
|
@ -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];
|
||||
}
|
@ -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();
|
||||
}
|
||||
}
|
@ -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()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -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(),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
@ -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> {
|
||||
// }
|
||||
// }
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
@ -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!,];
|
||||
}
|
@ -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 {}
|
@ -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';
|
426
lib/pages/auth/login/view/login_mobile_page.dart
Normal file
426
lib/pages/auth/login/view/login_mobile_page.dart
Normal 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",
|
||||
),
|
||||
],
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
)),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
@ -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 {
|
393
lib/pages/auth/login/view/login_web_page.dart
Normal file
393
lib/pages/auth/login/view/login_web_page.dart
Normal 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(),
|
||||
],
|
||||
),
|
||||
],
|
||||
)),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
@ -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'),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
@ -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(),
|
||||
],
|
||||
),
|
||||
],
|
||||
)),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
106
lib/pages/common/default_button.dart
Normal file
106
lib/pages/common/default_button.dart
Normal 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,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
38
lib/pages/common/first_layer.dart
Normal file
38
lib/pages/common/first_layer.dart
Normal 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!
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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';
|
||||
}
|
||||
|
@ -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";
|
||||
}
|
||||
|
@ -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: [
|
||||
|
@ -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)));
|
Reference in New Issue
Block a user