mirror of
https://github.com/SyncrowIOT/web.git
synced 2025-07-09 22:57:21 +00:00
3
assets/images/Password_invisible.svg
Normal file
3
assets/images/Password_invisible.svg
Normal file
@ -0,0 +1,3 @@
|
||||
<svg width="17" height="13" viewBox="0 0 17 13" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M15.1551 5.18632C15.4564 4.85579 15.6902 4.49821 15.8445 4.11887C15.9791 3.78757 15.7945 3.41924 15.4321 3.29613C15.0698 3.17301 14.6666 3.34169 14.5319 3.67299C14.2555 4.35274 13.533 4.98462 12.4974 5.45235C11.3781 5.95792 9.9584 6.23632 8.50002 6.23632C7.04165 6.23632 5.62204 5.95792 4.50267 5.45235C3.4671 4.98466 2.74455 4.35278 2.46811 3.67307C2.33341 3.34182 1.93051 3.17309 1.56797 3.29625C1.20557 3.41937 1.02099 3.78774 1.15569 4.11904C1.30993 4.49834 1.54371 4.85587 1.84496 5.18636L0.205067 6.68523C-0.0683557 6.93514 -0.0683557 7.34028 0.205067 7.59019C0.341779 7.71514 0.520918 7.77759 0.700103 7.77759C0.879288 7.77759 1.05847 7.71514 1.19514 7.59019L2.87865 6.05143C3.33414 6.34903 3.85618 6.60866 4.43008 6.82324L3.77375 8.88066C3.66551 9.21994 3.87875 9.57517 4.24993 9.6741C4.31534 9.69155 4.38129 9.69982 4.44619 9.69982C4.74941 9.69982 5.02883 9.51835 5.11798 9.23888L5.76259 7.21815C6.40253 7.36311 7.08297 7.45824 7.78919 7.49663V9.61595C7.78919 9.96935 8.10266 10.2559 8.48931 10.2559C8.87597 10.2559 9.18944 9.96935 9.18944 9.61595V7.49787C9.90329 7.46024 10.5911 7.3646 11.2375 7.21819L11.8821 9.23892C11.9713 9.51839 12.2507 9.69987 12.5539 9.69987C12.6188 9.69987 12.6848 9.69155 12.7502 9.67414C13.1214 9.57521 13.3346 9.21998 13.2263 8.8807L12.57 6.82328C13.1438 6.6087 13.6659 6.34907 14.1214 6.05147L15.8049 7.59023C15.9416 7.71518 16.1207 7.77764 16.2999 7.77764C16.479 7.77764 16.6582 7.71514 16.7949 7.59023C17.0684 7.34033 17.0684 6.93518 16.7949 6.68528L15.1551 5.18632Z" fill="#D5D5D5"/>
|
||||
</svg>
|
After Width: | Height: | Size: 1.6 KiB |
4
assets/images/facebook.svg
Normal file
4
assets/images/facebook.svg
Normal 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
8
assets/images/google.svg
Normal 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
BIN
assets/images/lift_line.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 179 B |
3
assets/images/password_visible.svg
Normal file
3
assets/images/password_visible.svg
Normal file
@ -0,0 +1,3 @@
|
||||
<svg width="15" height="13" viewBox="0 0 15 13" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M14.6809 10.3829C14.394 10.5298 14.0409 10.4175 13.8928 10.1291H13.8928C12.9943 8.37829 10.609 5.86828 7.49997 5.86828C4.71173 5.86828 2.23389 7.93348 1.10719 10.1291C0.958976 10.4164 0.606008 10.5299 0.318459 10.3821C0.0309393 10.2344 -0.0828497 9.88216 0.0644257 9.5944C1.35894 7.06664 4.20568 4.69641 7.49997 4.69641C11.257 4.69641 13.9478 7.6657 14.9355 9.5944C15.083 9.88245 14.969 10.2355 14.6809 10.3829ZM0.227668 4.08996L1.53267 5.26723C1.77437 5.48528 2.14474 5.46346 2.36022 5.22463C2.57695 4.98434 2.55791 4.61385 2.31762 4.39708L1.01262 3.21981C0.772414 3.00311 0.401926 3.02209 0.18507 3.26241C-0.0316681 3.5027 -0.0126251 3.87319 0.227668 4.08996ZM13.4672 5.26723L14.7722 4.08996C15.0125 3.87319 15.0316 3.5027 14.8148 3.26241C14.598 3.02215 14.2276 3.00305 13.9873 3.21981L12.6823 4.39708C12.442 4.61382 12.423 4.98434 12.6397 5.22463C12.8554 5.46372 13.2258 5.48502 13.4672 5.26723ZM7.49994 2.93127C7.82356 2.93127 8.08588 2.66892 8.08588 2.34533V0.585937C8.08588 0.262354 7.82356 0 7.49994 0C7.17633 0 6.914 0.262354 6.914 0.585937V2.34533C6.91403 2.66895 7.17636 2.93127 7.49994 2.93127ZM10.3784 9.86136C10.3784 11.4509 9.0871 12.7441 7.49997 12.7441C5.91284 12.7441 4.62158 11.4509 4.62158 9.86136C4.62158 8.2718 5.91284 6.97857 7.49997 6.97857C9.0871 6.97857 10.3784 8.2718 10.3784 9.86136ZM9.20651 9.86136C9.20651 8.91797 8.44099 8.15045 7.5 8.15045C6.55901 8.15045 5.79346 8.91797 5.79346 9.86136C5.79346 10.8047 6.55899 11.5723 7.49997 11.5723C8.44096 11.5723 9.20651 10.8047 9.20651 9.86136Z" fill="#D5D5D5"/>
|
||||
</svg>
|
After Width: | Height: | Size: 1.6 KiB |
BIN
assets/images/right_line.png
Normal file
BIN
assets/images/right_line.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 188 B |
@ -1,33 +1,58 @@
|
||||
import 'package:flutter/gestures.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:syncrow_web/pages/auth/bloc/auth_bloc.dart';
|
||||
import 'package:syncrow_web/pages/auth/view/login_page.dart';
|
||||
import 'package:syncrow_web/services/locator.dart';
|
||||
import 'package:syncrow_web/utils/color_manager.dart';
|
||||
|
||||
void main() {
|
||||
Future<void> main() async {
|
||||
WidgetsFlutterBinding.ensureInitialized();
|
||||
initialSetup();
|
||||
runApp(const MyApp());
|
||||
initialSetup(); // Perform initial setup, e.g., dependency injection
|
||||
String checkToken = await AuthBloc.getTokenAndValidate();
|
||||
runApp(MyApp(
|
||||
isLoggedIn: checkToken,
|
||||
));
|
||||
}
|
||||
|
||||
class MyApp extends StatelessWidget {
|
||||
const MyApp({super.key});
|
||||
final dynamic isLoggedIn;
|
||||
const MyApp({
|
||||
super.key,
|
||||
required this.isLoggedIn,
|
||||
});
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return MaterialApp(
|
||||
debugShowCheckedModeBanner: false,
|
||||
scrollBehavior: const MaterialScrollBehavior().copyWith(
|
||||
debugShowCheckedModeBanner: false, // Hide debug banner
|
||||
scrollBehavior: const MaterialScrollBehavior().copyWith(
|
||||
dragDevices: {
|
||||
PointerDeviceKind.mouse,
|
||||
PointerDeviceKind.touch,
|
||||
PointerDeviceKind.stylus,
|
||||
PointerDeviceKind.unknown
|
||||
PointerDeviceKind.unknown,
|
||||
},
|
||||
),
|
||||
theme: ThemeData(
|
||||
colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
|
||||
useMaterial3: true,
|
||||
textTheme: const TextTheme(
|
||||
bodySmall: TextStyle(
|
||||
fontSize: 13,
|
||||
color: ColorsManager.whiteColors,
|
||||
fontWeight: FontWeight.bold),
|
||||
bodyMedium: TextStyle(color: Colors.black87, fontSize: 14),
|
||||
bodyLarge: TextStyle(fontSize: 16,color: Colors.white),
|
||||
headlineSmall: TextStyle(color: Colors.black87, fontSize: 18),
|
||||
headlineMedium: TextStyle(color: Colors.black87, fontSize: 20),
|
||||
headlineLarge: TextStyle(
|
||||
color: Colors.white,
|
||||
fontSize: 24,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
colorScheme: ColorScheme.fromSeed(
|
||||
seedColor: Colors.deepPurple), // Set up color scheme
|
||||
useMaterial3: true, // Enable Material 3
|
||||
),
|
||||
home: const LoginPage(),
|
||||
home:LoginPage(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
332
lib/pages/auth/bloc/auth_bloc.dart
Normal file
332
lib/pages/auth/bloc/auth_bloc.dart
Normal file
@ -0,0 +1,332 @@
|
||||
import 'dart:async';
|
||||
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/auth_event.dart';
|
||||
import 'package:syncrow_web/pages/auth/bloc/auth_state.dart';
|
||||
import 'package:syncrow_web/pages/auth/model/login_with_email_model.dart';
|
||||
import 'package:syncrow_web/pages/auth/model/region_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/services/auth_api.dart';
|
||||
import 'package:syncrow_web/utils/constants/strings_manager.dart';
|
||||
import 'package:syncrow_web/utils/helpers/shared_preferences_helper.dart';
|
||||
import 'package:syncrow_web/utils/snack_bar.dart';
|
||||
|
||||
class AuthBloc extends Bloc<AuthEvent, AuthState> {
|
||||
AuthBloc() : super(LoginInitial()) {
|
||||
on<LoginButtonPressed>(_login);
|
||||
on<CheckBoxEvent>(checkBoxToggle);
|
||||
on<ChangePasswordEvent>(changePassword);
|
||||
on<StartTimerEvent>(_onStartTimer);
|
||||
on<StopTimerEvent>(_onStopTimer);
|
||||
on<UpdateTimerEvent>(_onUpdateTimer);
|
||||
on<PasswordVisibleEvent>(_passwordVisible);
|
||||
on<RegionInitialEvent>(_fetchRegion);
|
||||
}
|
||||
|
||||
////////////////////////////// forget password //////////////////////////////////
|
||||
final TextEditingController forgetEmailController = TextEditingController();
|
||||
final TextEditingController forgetPasswordController = TextEditingController();
|
||||
final TextEditingController forgetOtp = TextEditingController();
|
||||
final forgetFormKey = GlobalKey<FormState>();
|
||||
Timer? _timer;
|
||||
int _remainingTime = 0;
|
||||
List<RegionModel>? regionList;
|
||||
|
||||
Future<void> _onStartTimer(StartTimerEvent event, Emitter<AuthState> emit) async {
|
||||
if (_validateInputs(emit)) return;
|
||||
if (_timer != null && _timer!.isActive) {
|
||||
return;
|
||||
}
|
||||
_remainingTime = 60;
|
||||
add(UpdateTimerEvent(
|
||||
remainingTime: _remainingTime, isButtonEnabled: false));
|
||||
await AuthenticationAPI.sendOtp(email: forgetEmailController.text);
|
||||
_timer = Timer.periodic(const Duration(seconds: 1), (timer) {
|
||||
_remainingTime--;
|
||||
if (_remainingTime <= 0) {
|
||||
_timer?.cancel();
|
||||
add(const UpdateTimerEvent(remainingTime: 0, isButtonEnabled: true));
|
||||
} else {
|
||||
add(UpdateTimerEvent(
|
||||
remainingTime: _remainingTime, isButtonEnabled: false));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void _onStopTimer(StopTimerEvent event, Emitter<AuthState> emit) {
|
||||
_timer?.cancel();
|
||||
emit(const TimerState(isButtonEnabled: true, remainingTime: 0));
|
||||
}
|
||||
|
||||
Future<void> changePassword(
|
||||
ChangePasswordEvent event, Emitter<AuthState> emit) async {
|
||||
try {
|
||||
emit(LoadingForgetState());
|
||||
bool response = await AuthenticationAPI.verifyOtp(
|
||||
email: forgetEmailController.text, otpCode: forgetOtp.text);
|
||||
if (response == true) {
|
||||
await AuthenticationAPI.forgetPassword(
|
||||
password: forgetPasswordController.text,
|
||||
email: forgetEmailController.text);
|
||||
_timer?.cancel();
|
||||
emit(const TimerState(isButtonEnabled: true, remainingTime: 0));
|
||||
}
|
||||
emit(SuccessForgetState());
|
||||
} catch (failure) {
|
||||
emit(FailureForgetState(error: failure.toString()));
|
||||
}
|
||||
}
|
||||
|
||||
void _onUpdateTimer(UpdateTimerEvent event, Emitter<AuthState> emit) {
|
||||
emit(TimerState(
|
||||
isButtonEnabled: event.isButtonEnabled,
|
||||
remainingTime: event.remainingTime));
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
///////////////////////////////////// login /////////////////////////////////////
|
||||
final TextEditingController loginEmailController = TextEditingController();
|
||||
final TextEditingController loginPasswordController = TextEditingController();
|
||||
final loginFormKey = GlobalKey<FormState>();
|
||||
bool isChecked = false;
|
||||
bool obscureText = true;
|
||||
String newPassword = '';
|
||||
String maskedEmail = '';
|
||||
String otpCode = '';
|
||||
String validate = '';
|
||||
static Token token = Token.emptyConstructor();
|
||||
static UserModel? user;
|
||||
bool showValidationMessage = false;
|
||||
|
||||
void _login(LoginButtonPressed event, Emitter<AuthState> emit) async {
|
||||
emit(AuthLoading());
|
||||
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) {
|
||||
validate='Something went wrong';
|
||||
emit(const LoginFailure(error: 'Something went wrong'));
|
||||
// emit(LoginFailure(error: failure.toString()));
|
||||
return;
|
||||
}
|
||||
if (token.accessTokenIsNotEmpty) {
|
||||
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);
|
||||
loginEmailController.clear();
|
||||
loginPasswordController.clear();
|
||||
emit(LoginSuccess());
|
||||
} else {
|
||||
emit(const LoginFailure(error: 'Something went wrong'));
|
||||
}
|
||||
} else {
|
||||
emit(const LoginFailure(error: 'Accept terms and condition'));
|
||||
}
|
||||
}
|
||||
|
||||
checkBoxToggle(CheckBoxEvent event, Emitter<AuthState> emit,) {
|
||||
emit(AuthLoading());
|
||||
isChecked = event.newValue!;
|
||||
emit(LoginInitial());
|
||||
}
|
||||
|
||||
checkOtpCode(ChangePasswordEvent event, Emitter<AuthState> emit,) async {
|
||||
emit(LoadingForgetState());
|
||||
await AuthenticationAPI.verifyOtp(
|
||||
email: forgetEmailController.text, otpCode: forgetOtp.text);
|
||||
emit(SuccessForgetState());
|
||||
}
|
||||
|
||||
void _passwordVisible(PasswordVisibleEvent event, Emitter<AuthState> emit) {
|
||||
emit(AuthLoading());
|
||||
obscureText = !event.newValue!;
|
||||
emit(PasswordVisibleState());
|
||||
}
|
||||
|
||||
void launchURL(String url) {
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
/////////////////////////////////////VALIDATORS/////////////////////////////////////
|
||||
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? validateCode(String? value) {
|
||||
if (value == null || value.isEmpty) {
|
||||
return 'Code is required';
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
bool _validateInputs(Emitter<AuthState> emit) {
|
||||
emit(LoadingForgetState());
|
||||
final nameError = validateEmail(forgetEmailController.text);
|
||||
if (nameError != null) {
|
||||
emit(FailureForgetState(error: nameError));
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
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';
|
||||
final withoutExtraSpaces = value.replaceAll(RegExp(r"\s+"), ' ').trim();
|
||||
if (withoutExtraSpaces.length < 2 || withoutExtraSpaces.length > 30) {
|
||||
return 'Full name must be between 2 and 30 characters long';
|
||||
}
|
||||
if (RegExp(r"/[^ a-zA-Z0-9-\']/").hasMatch(withoutExtraSpaces)) {
|
||||
return 'Only alphanumeric characters, space, dash and single quote are allowed';
|
||||
}
|
||||
final parts = withoutExtraSpaces.split(' ');
|
||||
if (parts.length < 2) return 'Full name must contain first and last names';
|
||||
if (parts.length > 3) return 'Full name can at most contain 3 parts';
|
||||
if (parts.any((part) => part.length < 2 || part.length > 30)) {
|
||||
return 'Full name parts must be between 2 and 30 characters long';
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
String maskEmail(String email) {
|
||||
final emailParts = email.split('@');
|
||||
if (emailParts.length != 2) return email;
|
||||
|
||||
final localPart = emailParts[0];
|
||||
final domainPart = emailParts[1];
|
||||
|
||||
if (localPart.length < 3) return email;
|
||||
|
||||
final start = localPart.substring(0, 2);
|
||||
final end = localPart.substring(localPart.length - 1);
|
||||
|
||||
final maskedLocalPart = '$start******$end';
|
||||
return '$maskedLocalPart@$domainPart';
|
||||
}
|
||||
|
||||
final List<String> regions = [
|
||||
'North America',
|
||||
'South America',
|
||||
'Europe',
|
||||
'Asia',
|
||||
'Africa',
|
||||
'Australia',
|
||||
'Antarctica',
|
||||
];
|
||||
|
||||
|
||||
static Future<String> getTokenAndValidate() async {
|
||||
try {
|
||||
const storage = FlutterSecureStorage();
|
||||
final firstLaunch = await SharedPreferencesHelper.readBoolFromSP(StringsManager.firstLaunch) ?? true;
|
||||
if (firstLaunch) {
|
||||
storage.deleteAll();
|
||||
}
|
||||
await SharedPreferencesHelper.saveBoolToSP(StringsManager.firstLaunch, false);
|
||||
final value = await storage.read(key: Token.loginAccessTokenKey) ?? '';
|
||||
if (value.isEmpty) {
|
||||
return 'Token not found';
|
||||
}
|
||||
final tokenData = Token.decodeToken(value);
|
||||
if (tokenData.containsKey('exp')) {
|
||||
final exp = tokenData['exp'] ?? 0;
|
||||
final currentTime = DateTime.now().millisecondsSinceEpoch ~/ 1000;
|
||||
if (currentTime < exp) {
|
||||
return 'Success';
|
||||
} else {
|
||||
return 'expired';
|
||||
}
|
||||
} else {
|
||||
return 'Something went wrong';
|
||||
}
|
||||
} catch (_) {
|
||||
return 'Something went wrong';
|
||||
}
|
||||
}
|
||||
|
||||
void _fetchRegion(RegionInitialEvent event, Emitter<AuthState> emit) async {
|
||||
try {
|
||||
emit(AuthLoading());
|
||||
regionList = await AuthenticationAPI.fetchRegion();
|
||||
emit(LoginSuccess());
|
||||
} catch (e) {
|
||||
emit( LoginFailure(error: e.toString()));
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
55
lib/pages/auth/bloc/auth_event.dart
Normal file
55
lib/pages/auth/bloc/auth_event.dart
Normal file
@ -0,0 +1,55 @@
|
||||
import 'package:equatable/equatable.dart';
|
||||
|
||||
abstract class AuthEvent extends Equatable {
|
||||
const AuthEvent();
|
||||
|
||||
@override
|
||||
List<Object> get props => [];
|
||||
}
|
||||
|
||||
class LoginButtonPressed extends AuthEvent {
|
||||
final String username;
|
||||
final String password;
|
||||
|
||||
const LoginButtonPressed({required this.username, required this.password});
|
||||
|
||||
@override
|
||||
List<Object> get props => [username, password];
|
||||
}
|
||||
|
||||
class CheckBoxEvent extends AuthEvent {
|
||||
final bool? newValue;
|
||||
|
||||
const CheckBoxEvent({required this.newValue,});
|
||||
|
||||
@override
|
||||
List<Object> get props => [newValue!,];
|
||||
}
|
||||
|
||||
class GetCodeEvent extends AuthEvent{}
|
||||
|
||||
class SubmitEvent extends AuthEvent{}
|
||||
|
||||
class StartTimerEvent extends AuthEvent{}
|
||||
|
||||
class StopTimerEvent extends AuthEvent{}
|
||||
|
||||
class UpdateTimerEvent extends AuthEvent {
|
||||
final int remainingTime;
|
||||
final bool isButtonEnabled;
|
||||
const UpdateTimerEvent({required this.remainingTime, required this.isButtonEnabled});
|
||||
}
|
||||
|
||||
class ChangePasswordEvent extends AuthEvent{}
|
||||
|
||||
class SendOtpEvent extends AuthEvent{}
|
||||
|
||||
class PasswordVisibleEvent extends AuthEvent{
|
||||
final bool? newValue;
|
||||
|
||||
const PasswordVisibleEvent({required this.newValue,});
|
||||
}
|
||||
|
||||
class RegionInitialEvent extends AuthEvent {}
|
||||
|
||||
class SelectRegionEvent extends AuthEvent {}
|
76
lib/pages/auth/bloc/auth_state.dart
Normal file
76
lib/pages/auth/bloc/auth_state.dart
Normal file
@ -0,0 +1,76 @@
|
||||
import 'package:equatable/equatable.dart';
|
||||
|
||||
abstract class AuthState extends Equatable {
|
||||
const AuthState();
|
||||
|
||||
@override
|
||||
List<Object> get props => [];
|
||||
}
|
||||
|
||||
class LoginInitial extends AuthState {}
|
||||
|
||||
class AuthTokenLoading extends AuthState {}
|
||||
|
||||
class AuthLoading extends AuthState {}
|
||||
|
||||
class LoginSuccess extends AuthState {}
|
||||
|
||||
class LoginFailure extends AuthState {
|
||||
final String error;
|
||||
|
||||
const LoginFailure({required this.error});
|
||||
|
||||
@override
|
||||
List<Object> get props => [error];
|
||||
}
|
||||
|
||||
class LoginValid extends AuthState {}
|
||||
|
||||
class LoginInvalid extends AuthState {
|
||||
final String error;
|
||||
|
||||
const LoginInvalid({required this.error});
|
||||
|
||||
@override
|
||||
List<Object> get props => [error];
|
||||
}
|
||||
|
||||
class InitialForgetState extends AuthState{}
|
||||
|
||||
class LoadingForgetState extends AuthState{}
|
||||
|
||||
class SuccessForgetState extends AuthState{}
|
||||
|
||||
class PasswordVisibleState extends AuthState{}
|
||||
|
||||
class FailureForgetState extends AuthState {
|
||||
final String error;
|
||||
const FailureForgetState({required this.error});
|
||||
@override
|
||||
List<Object> get props => [error];
|
||||
}
|
||||
|
||||
class TimerState extends AuthState {
|
||||
final bool isButtonEnabled ;
|
||||
final int remainingTime;
|
||||
|
||||
const TimerState({required this.isButtonEnabled, required this.remainingTime});
|
||||
|
||||
@override
|
||||
List<Object> get props => [isButtonEnabled, remainingTime];
|
||||
}
|
||||
|
||||
|
||||
class AuthError extends AuthState {
|
||||
final String message;
|
||||
String? code;
|
||||
AuthError({required this.message, this.code});
|
||||
}
|
||||
|
||||
class AuthTokenError extends AuthError {
|
||||
AuthTokenError({required super.message, super.code});
|
||||
}
|
||||
class AuthSuccess extends AuthState {}
|
||||
|
||||
class AuthTokenSuccess extends AuthSuccess {}
|
||||
|
@ -1,201 +0,0 @@
|
||||
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/services/auth_api.dart';
|
||||
import 'package:syncrow_web/utils/snack_bar.dart';
|
||||
|
||||
class LoginBloc extends Bloc<LoginEvent, LoginState> {
|
||||
LoginBloc() : super(LoginInitial()) {
|
||||
on<LoginButtonPressed>(_login);
|
||||
}
|
||||
|
||||
|
||||
final TextEditingController emailController = TextEditingController();
|
||||
final TextEditingController passwordController = TextEditingController();
|
||||
|
||||
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';
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
String? fullNameValidator(String? value) {
|
||||
if (value == null) return 'Full name is required';
|
||||
|
||||
final withoutExtraSpaces = value.replaceAll(RegExp(r"\s+"), ' ').trim();
|
||||
|
||||
if (withoutExtraSpaces.length < 2 || withoutExtraSpaces.length > 30) {
|
||||
return 'Full name must be between 2 and 30 characters long';
|
||||
}
|
||||
|
||||
// Test if it contains anything but alphanumeric spaces and single quote
|
||||
|
||||
if (RegExp(r"/[^ a-zA-Z0-9-\']/").hasMatch(withoutExtraSpaces)) {
|
||||
return 'Only alphanumeric characters, space, dash and single quote are allowed';
|
||||
}
|
||||
|
||||
final parts = withoutExtraSpaces.split(' ');
|
||||
|
||||
if (parts.length < 2) return 'Full name must contain first and last names';
|
||||
|
||||
if (parts.length > 3) return 'Full name can at most contain 3 parts';
|
||||
|
||||
if (parts.any((part) => part.length < 2 || part.length > 30)) {
|
||||
return 'Full name parts must be between 2 and 30 characters long';
|
||||
}
|
||||
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);
|
||||
if (newPassword == value) {
|
||||
return null;
|
||||
} else {
|
||||
return 'Passwords do not match';
|
||||
}
|
||||
}
|
||||
|
||||
String? emailAddressValidator(String? value) {
|
||||
if (value != null && value.isNotEmpty && value != "") {
|
||||
if (checkValidityOfEmail(value)) {
|
||||
return null;
|
||||
} else {
|
||||
return 'Please enter a valid email';
|
||||
}
|
||||
} else {
|
||||
return 'Email address is required';
|
||||
}
|
||||
}
|
||||
|
||||
bool checkValidityOfEmail(String? email) {
|
||||
if (email != null) {
|
||||
return RegExp(
|
||||
r"^[a-zA-Z0-9]+([.!#$%&'*+/=?^_`{|}~-]?[a-zA-Z0-9]+)*@[a-zA-Z0-9]+([.-]?[a-zA-Z0-9]+)*\.[a-zA-Z0-9]{2,}$")
|
||||
.hasMatch(email);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
String maskEmail(String email) {
|
||||
final emailParts = email.split('@');
|
||||
if (emailParts.length != 2) return email;
|
||||
|
||||
final localPart = emailParts[0];
|
||||
final domainPart = emailParts[1];
|
||||
|
||||
if (localPart.length < 3) return email;
|
||||
|
||||
final start = localPart.substring(0, 2);
|
||||
final end = localPart.substring(localPart.length - 1);
|
||||
|
||||
final maskedLocalPart = '$start******$end';
|
||||
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'));
|
||||
}
|
||||
}
|
||||
|
||||
// signUp() async {
|
||||
// emit(LoginLoading());
|
||||
// final response;
|
||||
// try {
|
||||
// List<String> userFullName = fullName.split(' ');
|
||||
// response = await AuthenticationAPI.signUp(
|
||||
// model: SignUpModel(
|
||||
// email: email.toLowerCase(),
|
||||
// password: signUpPassword,
|
||||
// firstName: userFullName[0],
|
||||
// lastName: userFullName[1]),
|
||||
// );
|
||||
// } catch (failure) {
|
||||
// emit(AuthLoginError(message: failure.toString()));
|
||||
// return;
|
||||
// }
|
||||
// if (response) {
|
||||
// maskedEmail = maskEmail(email);
|
||||
// await sendOtp();
|
||||
// } else {
|
||||
// emit(AuthLoginError(message: 'Something went wrong'));
|
||||
// }
|
||||
// }
|
||||
|
||||
}
|
@ -1,18 +0,0 @@
|
||||
import 'package:equatable/equatable.dart';
|
||||
|
||||
abstract class LoginEvent extends Equatable {
|
||||
const LoginEvent();
|
||||
|
||||
@override
|
||||
List<Object> get props => [];
|
||||
}
|
||||
|
||||
class LoginButtonPressed extends LoginEvent {
|
||||
final String username;
|
||||
final String password;
|
||||
|
||||
const LoginButtonPressed({required this.username, required this.password});
|
||||
|
||||
@override
|
||||
List<Object> get props => [username, password];
|
||||
}
|
@ -1,23 +0,0 @@
|
||||
import 'package:equatable/equatable.dart';
|
||||
|
||||
abstract class LoginState extends Equatable {
|
||||
const LoginState();
|
||||
|
||||
@override
|
||||
List<Object> get props => [];
|
||||
}
|
||||
|
||||
class LoginInitial extends LoginState {}
|
||||
|
||||
class LoginLoading extends LoginState {}
|
||||
|
||||
class LoginSuccess extends LoginState {}
|
||||
|
||||
class LoginFailure extends LoginState {
|
||||
final String error;
|
||||
|
||||
const LoginFailure({required this.error});
|
||||
|
||||
@override
|
||||
List<Object> get props => [error];
|
||||
}
|
25
lib/pages/auth/model/region_model.dart
Normal file
25
lib/pages/auth/model/region_model.dart
Normal file
@ -0,0 +1,25 @@
|
||||
|
||||
|
||||
class RegionModel {
|
||||
final String name;
|
||||
final String id;
|
||||
|
||||
RegionModel({
|
||||
required this.name,
|
||||
required this.id,
|
||||
});
|
||||
|
||||
factory RegionModel.fromJson(Map<String, dynamic> json) {
|
||||
return RegionModel(
|
||||
name: json['regionName'],
|
||||
id: json['uuid'].toString(), // Ensure id is a String
|
||||
);
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
return {
|
||||
'regionName': name,
|
||||
'uuid': id,
|
||||
};
|
||||
}
|
||||
}
|
@ -1,3 +1,5 @@
|
||||
|
||||
|
||||
import 'package:syncrow_web/pages/auth/model/token.dart';
|
||||
|
||||
class UserModel {
|
||||
|
11
lib/pages/auth/view/forget_password_mobile_page.dart
Normal file
11
lib/pages/auth/view/forget_password_mobile_page.dart
Normal 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();
|
||||
}
|
||||
}
|
19
lib/pages/auth/view/forget_password_page.dart
Normal file
19
lib/pages/auth/view/forget_password_page.dart
Normal file
@ -0,0 +1,19 @@
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:syncrow_web/pages/auth/view/forget_password_mobile_page.dart';
|
||||
import 'package:syncrow_web/pages/auth/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()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
307
lib/pages/auth/view/forget_password_web_page.dart
Normal file
307
lib/pages/auth/view/forget_password_web_page.dart
Normal file
@ -0,0 +1,307 @@
|
||||
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/bloc/auth_bloc.dart';
|
||||
import 'package:syncrow_web/pages/auth/bloc/auth_event.dart';
|
||||
import 'package:syncrow_web/pages/auth/bloc/auth_state.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) => AuthBloc(),
|
||||
child: BlocConsumer<AuthBloc, AuthState>(
|
||||
listener: (context, state) {
|
||||
if (state is SuccessForgetState){
|
||||
Navigator.of(context).pop();
|
||||
}
|
||||
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, AuthState state) {
|
||||
late ScrollController _scrollController;
|
||||
_scrollController = ScrollController();
|
||||
void _scrollToCenter() {
|
||||
final double middlePosition = _scrollController.position.maxScrollExtent / 2;
|
||||
_scrollController.animateTo(
|
||||
middlePosition,
|
||||
duration: const Duration(seconds: 1),
|
||||
curve: Curves.easeInOut,
|
||||
);
|
||||
}
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
_scrollToCenter();
|
||||
});
|
||||
final forgetBloc = BlocProvider.of<AuthBloc>(context);
|
||||
Size size = MediaQuery.of(context).size;
|
||||
return FirstLayer(
|
||||
second: Center(
|
||||
child: ListView(
|
||||
shrinkWrap: true,
|
||||
controller: _scrollController,
|
||||
children: [
|
||||
Container(
|
||||
padding: EdgeInsets.all(size.width*0.02),
|
||||
margin: EdgeInsets.all(size.width*0.09),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.black.withOpacity(0.3),
|
||||
borderRadius: const BorderRadius.all(Radius.circular(20)),
|
||||
),
|
||||
child: Center(
|
||||
child: Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
const Spacer(),
|
||||
Expanded(
|
||||
flex: 3,
|
||||
child: SvgPicture.asset(
|
||||
Assets.loginLogo,
|
||||
),
|
||||
),
|
||||
const Spacer(),
|
||||
Expanded(
|
||||
flex: 3,
|
||||
child: 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.forgetFormKey,
|
||||
child: Padding(
|
||||
padding: EdgeInsets.symmetric(
|
||||
horizontal: size.width*0.02,
|
||||
vertical: size.width*0.003),
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: <Widget>[
|
||||
const SizedBox(height: 10),
|
||||
const Text(
|
||||
'Forget Password',
|
||||
style: TextStyle(
|
||||
color: Colors.white,
|
||||
fontSize: 24,
|
||||
fontWeight: FontWeight.bold),
|
||||
),
|
||||
const SizedBox(height: 10),
|
||||
Text(
|
||||
'Please fill in your account information to\nretrieve your password',
|
||||
style: Theme.of(context).textTheme.bodySmall,
|
||||
),
|
||||
const SizedBox(height: 10),
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
"Country/Region",
|
||||
style: Theme.of(context).textTheme.bodySmall,
|
||||
),
|
||||
const SizedBox(height: 10),
|
||||
SizedBox(
|
||||
child: DropdownButtonFormField<String>(
|
||||
validator: forgetBloc.validateRegion,
|
||||
icon: const Icon(
|
||||
Icons.keyboard_arrow_down_outlined,
|
||||
),
|
||||
decoration: textBoxDecoration()!.copyWith(
|
||||
hintText: null,
|
||||
),
|
||||
hint: SizedBox(
|
||||
width: size.width * 0.11,
|
||||
child: const Align(
|
||||
alignment: Alignment.centerLeft,
|
||||
child: Text(
|
||||
'Select your region/country',
|
||||
textAlign: TextAlign.center,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
|
||||
),
|
||||
),
|
||||
),
|
||||
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),
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
children: [
|
||||
Text("Account",
|
||||
style: Theme.of(context).textTheme.bodySmall,
|
||||
),
|
||||
const SizedBox(height: 10),
|
||||
SizedBox(
|
||||
child: TextFormField(
|
||||
validator: forgetBloc.validateEmail,
|
||||
controller: forgetBloc.forgetEmailController,
|
||||
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: Theme.of(context).textTheme.bodySmall,),
|
||||
const SizedBox(height: 10),
|
||||
SizedBox(
|
||||
child: TextFormField(
|
||||
validator: forgetBloc.validateCode,
|
||||
keyboardType: TextInputType.visiblePassword,
|
||||
controller: forgetBloc.forgetOtp,
|
||||
decoration: textBoxDecoration()!.copyWith(
|
||||
hintText: 'Enter Code',
|
||||
suffixIcon: SizedBox(
|
||||
width: 100,
|
||||
child: Center(
|
||||
child: InkWell(
|
||||
onTap: () {
|
||||
BlocProvider.of<AuthBloc>(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: Theme.of(context).textTheme.bodySmall,),
|
||||
const SizedBox(height: 10),
|
||||
SizedBox(
|
||||
child: TextFormField(
|
||||
validator: forgetBloc.passwordValidator,
|
||||
keyboardType: TextInputType.visiblePassword,
|
||||
controller: forgetBloc.forgetPasswordController,
|
||||
decoration: textBoxDecoration()!.copyWith(
|
||||
hintText: 'At least 8 characters',
|
||||
),
|
||||
style: const TextStyle(color: Colors.black),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(
|
||||
height: 10,
|
||||
),
|
||||
const SizedBox(height: 20.0),
|
||||
Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
SizedBox(
|
||||
width: size.width * 0.2,
|
||||
child: DefaultButton(
|
||||
backgroundColor: ColorsManager.btnColor,
|
||||
child: const Text('Submit'),
|
||||
onPressed: () {
|
||||
if (forgetBloc.forgetFormKey.currentState!.validate()) {
|
||||
forgetBloc.add(ChangePasswordEvent());
|
||||
}
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 10.0),
|
||||
SizedBox(child: Text(forgetBloc.validate,
|
||||
style: const TextStyle(fontWeight: FontWeight.w700,color: ColorsManager.red ),),),
|
||||
SizedBox(height: 10,),
|
||||
SizedBox(
|
||||
width: size.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,22 +1,26 @@
|
||||
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/bloc/auth_bloc.dart';
|
||||
import 'package:syncrow_web/pages/auth/bloc/auth_event.dart';
|
||||
import 'package:syncrow_web/pages/auth/bloc/auth_state.dart';
|
||||
import 'package:syncrow_web/pages/auth/view/forget_password_page.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/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 LoginMobilePage extends StatelessWidget {
|
||||
const LoginMobilePage({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
body: BlocProvider(
|
||||
create: (context) => LoginBloc(),
|
||||
child: BlocConsumer<LoginBloc, LoginState>(
|
||||
create: (context) => AuthBloc(),
|
||||
child: BlocConsumer<AuthBloc, AuthState>(
|
||||
listener: (context, state) {
|
||||
if (state is LoginSuccess) {
|
||||
// Navigate to home screen after successful login
|
||||
@ -34,7 +38,7 @@ class LoginMobilePage extends StatelessWidget {
|
||||
}
|
||||
},
|
||||
builder: (context, state) {
|
||||
if (state is LoginLoading) {
|
||||
if (state is AuthLoading) {
|
||||
return const Center(child: CircularProgressIndicator());
|
||||
} else {
|
||||
return _buildLoginForm(context);
|
||||
@ -42,12 +46,11 @@ class LoginMobilePage extends StatelessWidget {
|
||||
},
|
||||
),
|
||||
),
|
||||
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildLoginForm(BuildContext context) {
|
||||
final loginBloc = BlocProvider.of<LoginBloc>(context);
|
||||
final loginBloc = BlocProvider.of<AuthBloc>(context);
|
||||
final TextEditingController _usernameController = TextEditingController();
|
||||
final TextEditingController _passwordController = TextEditingController();
|
||||
return Center(
|
||||
@ -94,121 +97,239 @@ class LoginMobilePage extends StatelessWidget {
|
||||
),
|
||||
),
|
||||
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,
|
||||
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.loginFormKey,
|
||||
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: Theme.of(context).textTheme.bodySmall,
|
||||
),
|
||||
);
|
||||
},
|
||||
child: const Text('Login'),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
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: Theme.of(context).textTheme.bodySmall,
|
||||
),
|
||||
SizedBox(
|
||||
child: TextFormField(
|
||||
validator: loginBloc.validateEmail,
|
||||
controller: loginBloc.loginEmailController,
|
||||
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: Theme.of(context).textTheme.bodySmall,
|
||||
),
|
||||
SizedBox(
|
||||
child: TextFormField(
|
||||
validator: loginBloc.validatePassword,
|
||||
obscureText: loginBloc.obscureText,
|
||||
keyboardType: TextInputType.visiblePassword,
|
||||
controller: loginBloc.loginPasswordController,
|
||||
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: Theme.of(context).textTheme.bodySmall,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
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.loginFormKey.currentState!
|
||||
.validate()) {
|
||||
loginBloc.add(
|
||||
LoginButtonPressed(
|
||||
username:
|
||||
loginBloc.loginEmailController.text,
|
||||
password:
|
||||
loginBloc.loginPasswordController.text,
|
||||
),
|
||||
);
|
||||
}
|
||||
},
|
||||
),
|
||||
),
|
||||
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",
|
||||
),
|
||||
],
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
)),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
@ -7,7 +7,6 @@ import 'package:syncrow_web/utils/responsive_layout.dart';
|
||||
|
||||
class LoginPage extends StatelessWidget {
|
||||
const LoginPage({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return const ResponsiveLayout(
|
||||
|
@ -1,22 +1,35 @@
|
||||
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/bloc/auth_bloc.dart';
|
||||
import 'package:syncrow_web/pages/auth/bloc/auth_event.dart';
|
||||
import 'package:syncrow_web/pages/auth/bloc/auth_state.dart';
|
||||
import 'package:syncrow_web/pages/auth/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/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 {
|
||||
class LoginWebPage extends StatefulWidget {
|
||||
const LoginWebPage({super.key});
|
||||
|
||||
@override
|
||||
State<LoginWebPage> createState() => _LoginWebPageState();
|
||||
}
|
||||
|
||||
class _LoginWebPageState extends State<LoginWebPage> {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
body: BlocProvider(
|
||||
create: (context) => LoginBloc(),
|
||||
child: BlocConsumer<LoginBloc, LoginState>(
|
||||
create: (BuildContext context) => AuthBloc(),
|
||||
child: BlocConsumer<AuthBloc, AuthState>(
|
||||
listener: (context, state) {
|
||||
if (state is LoginSuccess) {
|
||||
// Navigate to home screen after successful login
|
||||
@ -34,10 +47,10 @@ class LoginWebPage extends StatelessWidget {
|
||||
}
|
||||
},
|
||||
builder: (context, state) {
|
||||
if (state is LoginLoading) {
|
||||
if (state is AuthLoading) {
|
||||
return const Center(child: CircularProgressIndicator());
|
||||
} else {
|
||||
return _buildLoginForm(context);
|
||||
return _buildLoginForm(context,state);
|
||||
}
|
||||
},
|
||||
),
|
||||
@ -45,149 +58,283 @@ class LoginWebPage extends StatelessWidget {
|
||||
);
|
||||
}
|
||||
|
||||
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),
|
||||
Widget _buildLoginForm(BuildContext context,AuthState state) {
|
||||
final loginBloc = BlocProvider.of<AuthBloc>(context);
|
||||
Size size = MediaQuery.of(context).size;
|
||||
late ScrollController _scrollController;
|
||||
_scrollController = ScrollController();
|
||||
void _scrollToCenter() {
|
||||
final double middlePosition = _scrollController.position.maxScrollExtent / 2;
|
||||
_scrollController.animateTo(
|
||||
middlePosition,
|
||||
duration: const Duration(seconds: 1),
|
||||
curve: Curves.easeInOut,
|
||||
);
|
||||
}
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
_scrollToCenter();
|
||||
});
|
||||
return FirstLayer(
|
||||
second: Center(
|
||||
child: ListView(
|
||||
controller: _scrollController,
|
||||
shrinkWrap: true,
|
||||
children: [
|
||||
Container(
|
||||
padding: EdgeInsets.all(size.width*0.02) ,
|
||||
margin: EdgeInsets.all(size.width*0.09),
|
||||
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,
|
||||
),
|
||||
child: Center(
|
||||
child:Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
const Spacer(),
|
||||
Expanded(
|
||||
flex: 3,
|
||||
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 Spacer(),
|
||||
Expanded(
|
||||
flex: 3,
|
||||
child: 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.loginFormKey,
|
||||
child: Padding(
|
||||
padding: EdgeInsets.symmetric(
|
||||
horizontal: size.width*0.02,
|
||||
vertical: size.width*0.003),
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: <Widget>[
|
||||
const SizedBox(height: 40),
|
||||
Text(
|
||||
'Login',
|
||||
style:Theme.of(context).textTheme.headlineLarge),
|
||||
SizedBox(height: size.height*0.03),
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
"Country/Region",
|
||||
style: Theme.of(context).textTheme.bodySmall,
|
||||
),
|
||||
const SizedBox(height: 10,),
|
||||
SizedBox(
|
||||
child: DropdownButtonFormField<String>(
|
||||
validator:loginBloc.validateRegion ,
|
||||
icon: const Icon(
|
||||
Icons.keyboard_arrow_down_outlined,
|
||||
),
|
||||
decoration: textBoxDecoration()!.copyWith(
|
||||
hintText: null,),
|
||||
hint: SizedBox(
|
||||
width: size.width * 0.11,
|
||||
child: const Align(
|
||||
alignment: Alignment.centerLeft,
|
||||
child: Text(
|
||||
'Select your region/country',
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(fontSize: 14),
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
),
|
||||
),
|
||||
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) {},
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
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),
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
children: [
|
||||
Text("Email",
|
||||
style: Theme.of(context).textTheme.bodySmall,
|
||||
),
|
||||
const SizedBox(
|
||||
height: 10,
|
||||
),
|
||||
SizedBox(
|
||||
child: TextFormField(
|
||||
validator:loginBloc.validateEmail ,
|
||||
controller:loginBloc.loginEmailController,
|
||||
decoration: textBoxDecoration()!.copyWith(hintText: 'Enter your email'),
|
||||
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,
|
||||
const SizedBox(height: 20.0),
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
children: [
|
||||
Text("Password", style: Theme.of(context).textTheme.bodySmall,),
|
||||
const SizedBox(
|
||||
height: 10,
|
||||
),
|
||||
SizedBox(
|
||||
child: TextFormField(
|
||||
validator:loginBloc.validatePassword,
|
||||
obscureText:loginBloc.obscureText,
|
||||
keyboardType: TextInputType.visiblePassword,
|
||||
controller:loginBloc.loginPasswordController,
|
||||
decoration: textBoxDecoration()!.copyWith(
|
||||
hintText: 'At least 8 characters',
|
||||
suffixIcon: IconButton(onPressed: () {
|
||||
loginBloc.add(PasswordVisibleEvent(newValue: loginBloc.obscureText));
|
||||
},
|
||||
icon: SizedBox(
|
||||
child: SvgPicture.asset(
|
||||
loginBloc.obscureText?
|
||||
Assets.visiblePassword :
|
||||
Assets.invisiblePassword,
|
||||
height: 15,
|
||||
width: 15,
|
||||
),
|
||||
),
|
||||
)
|
||||
),
|
||||
style: const TextStyle(color: Colors.black),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
child: const Text('Login'),
|
||||
const SizedBox(
|
||||
height: 20,
|
||||
),
|
||||
SizedBox(
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.end,
|
||||
children: [
|
||||
InkWell(
|
||||
onTap: () {
|
||||
Navigator.of(context).push(MaterialPageRoute(builder: (context) => const ForgetPasswordPage(),));
|
||||
},
|
||||
child: Text(
|
||||
"Forgot Password?",
|
||||
style: Theme.of(context).textTheme.bodySmall,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
const SizedBox(
|
||||
height: 20,
|
||||
),
|
||||
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:size.width * 0.14,
|
||||
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:size.width * 0.2,
|
||||
child: DefaultButton(
|
||||
backgroundColor: loginBloc.isChecked?
|
||||
ColorsManager.btnColor:ColorsManager.grayColor,
|
||||
child: const Text('Sign in'),
|
||||
onPressed: () {
|
||||
if (loginBloc.loginFormKey.currentState!.validate()) {
|
||||
loginBloc.add(LoginButtonPressed(
|
||||
username: loginBloc.loginEmailController.text,
|
||||
password: loginBloc.loginPasswordController.text,
|
||||
),
|
||||
);
|
||||
}
|
||||
},
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 15.0),
|
||||
Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [ SizedBox(child: Text(loginBloc.validate,
|
||||
style: const TextStyle(fontWeight: FontWeight.w700,color: ColorsManager.red ),),)],)
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
const Spacer(),
|
||||
],
|
||||
),
|
||||
],
|
||||
)),
|
||||
)
|
||||
)),
|
||||
const Spacer(),
|
||||
],
|
||||
),),
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
105
lib/pages/common/default_button.dart
Normal file
105
lib/pages/common/default_button.dart
Normal file
@ -0,0 +1,105 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:syncrow_web/utils/color_manager.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
|
||||
?? Theme.of(context).textTheme.bodySmall!.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/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';
|
||||
|
||||
@ -12,6 +13,7 @@ class HTTPInterceptor extends InterceptorsWrapper {
|
||||
|
||||
List<String> headerExclusionListOfAddedParameters = [
|
||||
ApiEndpoints.login,
|
||||
ApiEndpoints.getRegion
|
||||
];
|
||||
|
||||
@override
|
||||
@ -30,7 +32,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,29 +1,67 @@
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:syncrow_web/pages/auth/model/region_model.dart';
|
||||
import 'package:syncrow_web/pages/auth/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 {
|
||||
|
||||
|
||||
static Future<Token> loginWithEmail({required var model}) async {
|
||||
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) {});
|
||||
return response;
|
||||
}
|
||||
|
||||
static Future<bool> verifyOtp(
|
||||
{required String email, required String otpCode}) async {
|
||||
final response = await HTTPService().post(
|
||||
path: ApiEndpoints.verifyOtp,
|
||||
body: {"email": email, "type": "VERIFICATION", "otpCode": otpCode},
|
||||
showServerMessage: true,
|
||||
expectedResponseModel: (json) {
|
||||
if (json['message'] == 'Otp Verified Successfully') {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
});
|
||||
return response;
|
||||
}
|
||||
|
||||
static Future<List<RegionModel>> fetchRegion() async {
|
||||
final response = await HTTPService().get(
|
||||
path: ApiEndpoints.getRegion,
|
||||
showServerMessage: true,
|
||||
expectedResponseModel: (json) {
|
||||
return (json as List).map((zone) => RegionModel.fromJson(zone)).toList();
|
||||
}
|
||||
);
|
||||
return response as List<RegionModel>;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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,12 @@
|
||||
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';
|
||||
static const String verifyOtp = '$baseUrl/authentication/user/verify-otp';
|
||||
static const String getRegion = '$baseUrl/region';
|
||||
}
|
||||
|
@ -9,4 +9,10 @@ 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";
|
||||
static const String invisiblePassword = "assets/images/Password_invisible.svg";
|
||||
static const String visiblePassword = "assets/images/Password_visible.svg";
|
||||
}
|
||||
|
43
lib/utils/constants/strings_manager.dart
Normal file
43
lib/utils/constants/strings_manager.dart
Normal file
@ -0,0 +1,43 @@
|
||||
class StringsManager {
|
||||
static const noRouteFound = 'No route found';
|
||||
static const noInternetConnection = 'No internet connection';
|
||||
static const String dashboard = 'Dashboard';
|
||||
static const String devices = 'Devices';
|
||||
static const String routine = 'Routines';
|
||||
static const String tapToRunRoutine = 'Tap to run routine';
|
||||
static const String wizard = 'Wizard';
|
||||
static const String active = 'Active';
|
||||
static const String current = 'Current';
|
||||
static const String frequency = 'Frequency';
|
||||
static const String energyUsage = 'Energy Usage';
|
||||
static const String totalConsumption = 'Total Consumption';
|
||||
static const String ACConsumption = 'AC Consumption';
|
||||
static const String units = 'Units';
|
||||
static const String emissions = 'Emissions';
|
||||
static const String reductions = 'Reductions';
|
||||
static const String winter = 'Winter';
|
||||
static const String winterMode = 'Winter Mode';
|
||||
static const String summer = 'Summer';
|
||||
static const String summerMode = 'Summer Mode';
|
||||
static const String on = 'ON';
|
||||
static const String off = 'OFF';
|
||||
static const String timer = 'Timer';
|
||||
static const String dimmerAndColor = "Dimmer & color";
|
||||
static const String recentlyUsed = "Recently used colors";
|
||||
static const String lightingModes = "Lighting modes";
|
||||
static const String doze = "Doze";
|
||||
static const String relax = "Relax";
|
||||
static const String reading = "Reading";
|
||||
static const String energizing = "Energizing";
|
||||
static const String createScene = 'Create Scene';
|
||||
static const String tapToRun = 'Launch: Tap - To - Run';
|
||||
static const String turnOffAllLights =
|
||||
'Example: turn off all lights in the with one tap.';
|
||||
static const String whenDeviceStatusChanges = 'When device status changes';
|
||||
static const String whenUnusualActivityIsDetected =
|
||||
'Example: when an unusual activity is detected.';
|
||||
static const String functions = "Functions";
|
||||
static const String firstLaunch = "firstLaunch";
|
||||
static const String deleteScene = 'Delete Scene';
|
||||
static const String deleteAutomation = 'Delete Automation';
|
||||
}
|
58
lib/utils/helpers/shared_preferences_helper.dart
Normal file
58
lib/utils/helpers/shared_preferences_helper.dart
Normal file
@ -0,0 +1,58 @@
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
|
||||
class SharedPreferencesHelper {
|
||||
static saveStringToSP(String key, String value) async {
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
await prefs.setString(key, value);
|
||||
}
|
||||
|
||||
static saveBoolToSP(String key, bool value) async {
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
await prefs.setBool(key, value);
|
||||
}
|
||||
|
||||
static saveIntToSP(String key, int value) async {
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
await prefs.setInt(key, value);
|
||||
}
|
||||
|
||||
static saveDoubleToSP(String key, double value) async {
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
await prefs.setDouble(key, value);
|
||||
}
|
||||
|
||||
static saveStringListToSP(String key, List<String> value) async {
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
await prefs.setStringList(key, value);
|
||||
}
|
||||
|
||||
static Future<String> readStringFromSP(String key) async {
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
String value = prefs.getString(key) ?? '';
|
||||
return value;
|
||||
}
|
||||
|
||||
static Future<bool?> readBoolFromSP(String key) async {
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
bool? value = prefs.getBool(key);
|
||||
return value;
|
||||
}
|
||||
|
||||
static Future<int> readIntFromSP(String key) async {
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
int value = prefs.getInt(key) ?? 0;
|
||||
return value;
|
||||
}
|
||||
|
||||
static Future<List<String>> readStringListFromSP(String key) async {
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
List<String>? value = prefs.getStringList(key) ?? [];
|
||||
return value;
|
||||
}
|
||||
|
||||
static Future<bool> removeValueFromSP(String key) async {
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
await prefs.remove(key);
|
||||
return true;
|
||||
}
|
||||
}
|
@ -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: [
|
||||
|
@ -22,5 +22,5 @@ InputDecoration? textBoxDecoration({bool suffixIcon = false}) => InputDecoration
|
||||
);
|
||||
|
||||
|
||||
TextStyle appBarTextStyle =
|
||||
const TextStyle(fontSize: 20, color: ColorsManager.whiteColors);
|
||||
|
||||
Decoration containerDecoration = const BoxDecoration(color: Colors.white,borderRadius: BorderRadius.all(Radius.circular(20)));
|
@ -18,9 +18,8 @@ class WebAppBar extends StatelessWidget {
|
||||
children: [
|
||||
Expanded(
|
||||
child: Text(
|
||||
title!,style: const TextStyle(
|
||||
fontSize: 30,
|
||||
color: Colors.white),)
|
||||
title!,
|
||||
style: Theme.of(context).textTheme.headlineLarge,)
|
||||
),
|
||||
if (body != null)
|
||||
Expanded(
|
||||
@ -49,7 +48,7 @@ class WebAppBar extends StatelessWidget {
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 10,),
|
||||
const Text('mohamamd alnemer ',style: TextStyle(fontSize: 16,color: Colors.white),),
|
||||
Text('mohamamd alnemer ',style:Theme.of(context).textTheme.bodyLarge ,),
|
||||
],
|
||||
)
|
||||
],
|
||||
|
@ -7,8 +7,10 @@ import Foundation
|
||||
|
||||
import flutter_secure_storage_macos
|
||||
import path_provider_foundation
|
||||
import shared_preferences_foundation
|
||||
|
||||
func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
|
||||
FlutterSecureStoragePlugin.register(with: registry.registrar(forPlugin: "FlutterSecureStoragePlugin"))
|
||||
PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin"))
|
||||
SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin"))
|
||||
}
|
||||
|
72
pubspec.lock
72
pubspec.lock
@ -105,6 +105,14 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.1.2"
|
||||
file:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: file
|
||||
sha256: "5fc22d7c25582e38ad9a8515372cd9a93834027aacf1801cf01164dac0ffa08c"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "7.0.0"
|
||||
flutter:
|
||||
dependency: "direct main"
|
||||
description: flutter
|
||||
@ -324,10 +332,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:
|
||||
@ -392,6 +400,62 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "6.1.2"
|
||||
shared_preferences:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: shared_preferences
|
||||
sha256: c3f888ba2d659f3e75f4686112cc1e71f46177f74452d40d8307edc332296ead
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.3.0"
|
||||
shared_preferences_android:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: shared_preferences_android
|
||||
sha256: "041be4d9d2dc6079cf342bc8b761b03787e3b71192d658220a56cac9c04a0294"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.3.0"
|
||||
shared_preferences_foundation:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: shared_preferences_foundation
|
||||
sha256: "671e7a931f55a08aa45be2a13fe7247f2a41237897df434b30d2012388191833"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.5.0"
|
||||
shared_preferences_linux:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: shared_preferences_linux
|
||||
sha256: "2ba0510d3017f91655b7543e9ee46d48619de2a2af38e5c790423f7007c7ccc1"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.4.0"
|
||||
shared_preferences_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: shared_preferences_platform_interface
|
||||
sha256: "57cbf196c486bc2cf1f02b85784932c6094376284b3ad5779d1b1c6c6a816b80"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.4.1"
|
||||
shared_preferences_web:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: shared_preferences_web
|
||||
sha256: "3a293170d4d9403c3254ee05b84e62e8a9b3c5808ebd17de6a33fe9ea6457936"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.4.0"
|
||||
shared_preferences_windows:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: shared_preferences_windows
|
||||
sha256: "398084b47b7f92110683cac45c6dc4aae853db47e470e5ddcd52cab7f7196ab2"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.4.0"
|
||||
sky_engine:
|
||||
dependency: transitive
|
||||
description: flutter
|
||||
@ -497,10 +561,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: web
|
||||
sha256: d43c1d6b787bf0afad444700ae7f4db8827f701bc61c255ac8d328c6f4d52062
|
||||
sha256: "97da13628db363c635202ad97068d47c5b8aa555808e7a9411963c533b449b27"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.0.0"
|
||||
version: "0.5.1"
|
||||
win32:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -42,7 +42,7 @@ dependencies:
|
||||
dio: ^5.5.0+1
|
||||
get_it: ^7.6.7
|
||||
flutter_secure_storage: ^9.2.2
|
||||
|
||||
shared_preferences: ^2.3.0
|
||||
|
||||
dev_dependencies:
|
||||
flutter_test:
|
||||
|
@ -1,30 +0,0 @@
|
||||
// This is a basic Flutter widget test.
|
||||
//
|
||||
// To perform an interaction with a widget in your test, use the WidgetTester
|
||||
// utility in the flutter_test package. For example, you can send tap and scroll
|
||||
// gestures. You can also use WidgetTester to find child widgets in the widget
|
||||
// tree, read text, and verify that the values of widget properties are correct.
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
|
||||
import 'package:syncrow_web/main.dart';
|
||||
|
||||
void main() {
|
||||
testWidgets('Counter increments smoke test', (WidgetTester tester) async {
|
||||
// Build our app and trigger a frame.
|
||||
await tester.pumpWidget(const MyApp());
|
||||
|
||||
// Verify that our counter starts at 0.
|
||||
expect(find.text('0'), findsOneWidget);
|
||||
expect(find.text('1'), findsNothing);
|
||||
|
||||
// Tap the '+' icon and trigger a frame.
|
||||
await tester.tap(find.byIcon(Icons.add));
|
||||
await tester.pump();
|
||||
|
||||
// Verify that our counter has incremented.
|
||||
expect(find.text('0'), findsNothing);
|
||||
expect(find.text('1'), findsOneWidget);
|
||||
});
|
||||
}
|
||||
|
Reference in New Issue
Block a user