diff --git a/assets/images/Password_invisible.svg b/assets/images/Password_invisible.svg new file mode 100644 index 00000000..bb190eb3 --- /dev/null +++ b/assets/images/Password_invisible.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/images/facebook.svg b/assets/images/facebook.svg new file mode 100644 index 00000000..929d3861 --- /dev/null +++ b/assets/images/facebook.svg @@ -0,0 +1,4 @@ + + + + diff --git a/assets/images/google.svg b/assets/images/google.svg new file mode 100644 index 00000000..27788e84 --- /dev/null +++ b/assets/images/google.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/assets/images/lift_line.png b/assets/images/lift_line.png new file mode 100644 index 00000000..8dacf6d7 Binary files /dev/null and b/assets/images/lift_line.png differ diff --git a/assets/images/password_visible.svg b/assets/images/password_visible.svg new file mode 100644 index 00000000..25c55434 --- /dev/null +++ b/assets/images/password_visible.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/images/right_line.png b/assets/images/right_line.png new file mode 100644 index 00000000..805d1c0a Binary files /dev/null and b/assets/images/right_line.png differ diff --git a/lib/main.dart b/lib/main.dart index 09a1c6ab..c7b31392 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -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 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(), ); } } diff --git a/lib/pages/auth/bloc/auth_bloc.dart b/lib/pages/auth/bloc/auth_bloc.dart new file mode 100644 index 00000000..00f6227f --- /dev/null +++ b/lib/pages/auth/bloc/auth_bloc.dart @@ -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 { + AuthBloc() : super(LoginInitial()) { + on(_login); + on(checkBoxToggle); + on(changePassword); + on(_onStartTimer); + on(_onStopTimer); + on(_onUpdateTimer); + on(_passwordVisible); + on(_fetchRegion); + } + + ////////////////////////////// forget password ////////////////////////////////// + final TextEditingController forgetEmailController = TextEditingController(); + final TextEditingController forgetPasswordController = TextEditingController(); + final TextEditingController forgetOtp = TextEditingController(); + final forgetFormKey = GlobalKey(); + Timer? _timer; + int _remainingTime = 0; + List? regionList; + + Future _onStartTimer(StartTimerEvent event, Emitter 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 emit) { + _timer?.cancel(); + emit(const TimerState(isButtonEnabled: true, remainingTime: 0)); + } + + Future changePassword( + ChangePasswordEvent event, Emitter 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 emit) { + emit(TimerState( + isButtonEnabled: event.isButtonEnabled, + remainingTime: event.remainingTime)); + } + + + + + + ///////////////////////////////////// login ///////////////////////////////////// + final TextEditingController loginEmailController = TextEditingController(); + final TextEditingController loginPasswordController = TextEditingController(); + final loginFormKey = GlobalKey(); + 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 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 emit,) { + emit(AuthLoading()); + isChecked = event.newValue!; + emit(LoginInitial()); + } + + checkOtpCode(ChangePasswordEvent event, Emitter emit,) async { + emit(LoadingForgetState()); + await AuthenticationAPI.verifyOtp( + email: forgetEmailController.text, otpCode: forgetOtp.text); + emit(SuccessForgetState()); + } + + void _passwordVisible(PasswordVisibleEvent event, Emitter 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 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 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 regions = [ + 'North America', + 'South America', + 'Europe', + 'Asia', + 'Africa', + 'Australia', + 'Antarctica', + ]; + + + static Future 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 emit) async { + try { + emit(AuthLoading()); + regionList = await AuthenticationAPI.fetchRegion(); + emit(LoginSuccess()); + } catch (e) { + emit( LoginFailure(error: e.toString())); + + } + } + + +} + + + diff --git a/lib/pages/auth/bloc/auth_event.dart b/lib/pages/auth/bloc/auth_event.dart new file mode 100644 index 00000000..8a410555 --- /dev/null +++ b/lib/pages/auth/bloc/auth_event.dart @@ -0,0 +1,55 @@ +import 'package:equatable/equatable.dart'; + +abstract class AuthEvent extends Equatable { + const AuthEvent(); + + @override + List get props => []; +} + +class LoginButtonPressed extends AuthEvent { + final String username; + final String password; + + const LoginButtonPressed({required this.username, required this.password}); + + @override + List get props => [username, password]; +} + +class CheckBoxEvent extends AuthEvent { + final bool? newValue; + + const CheckBoxEvent({required this.newValue,}); + + @override + List 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 {} diff --git a/lib/pages/auth/bloc/auth_state.dart b/lib/pages/auth/bloc/auth_state.dart new file mode 100644 index 00000000..9814955a --- /dev/null +++ b/lib/pages/auth/bloc/auth_state.dart @@ -0,0 +1,76 @@ +import 'package:equatable/equatable.dart'; + +abstract class AuthState extends Equatable { + const AuthState(); + + @override + List 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 get props => [error]; +} + +class LoginValid extends AuthState {} + +class LoginInvalid extends AuthState { + final String error; + + const LoginInvalid({required this.error}); + + @override + List 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 get props => [error]; +} + +class TimerState extends AuthState { + final bool isButtonEnabled ; + final int remainingTime; + + const TimerState({required this.isButtonEnabled, required this.remainingTime}); + + @override + List 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 {} + diff --git a/lib/pages/auth/bloc/login_bloc.dart b/lib/pages/auth/bloc/login_bloc.dart deleted file mode 100644 index 83443eea..00000000 --- a/lib/pages/auth/bloc/login_bloc.dart +++ /dev/null @@ -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 { - LoginBloc() : super(LoginInitial()) { - on(_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(); - final signUpFormKey = GlobalKey(); - final checkEmailFormKey = GlobalKey(); - final createNewPasswordKey = GlobalKey(); - 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 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 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')); - // } - // } - -} diff --git a/lib/pages/auth/bloc/login_event.dart b/lib/pages/auth/bloc/login_event.dart deleted file mode 100644 index 8c46f607..00000000 --- a/lib/pages/auth/bloc/login_event.dart +++ /dev/null @@ -1,18 +0,0 @@ -import 'package:equatable/equatable.dart'; - -abstract class LoginEvent extends Equatable { - const LoginEvent(); - - @override - List get props => []; -} - -class LoginButtonPressed extends LoginEvent { - final String username; - final String password; - - const LoginButtonPressed({required this.username, required this.password}); - - @override - List get props => [username, password]; -} diff --git a/lib/pages/auth/bloc/login_state.dart b/lib/pages/auth/bloc/login_state.dart deleted file mode 100644 index 6d5cff8a..00000000 --- a/lib/pages/auth/bloc/login_state.dart +++ /dev/null @@ -1,23 +0,0 @@ -import 'package:equatable/equatable.dart'; - -abstract class LoginState extends Equatable { - const LoginState(); - - @override - List 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 get props => [error]; -} diff --git a/lib/pages/auth/model/region_model.dart b/lib/pages/auth/model/region_model.dart new file mode 100644 index 00000000..fd6306a2 --- /dev/null +++ b/lib/pages/auth/model/region_model.dart @@ -0,0 +1,25 @@ + + +class RegionModel { + final String name; + final String id; + + RegionModel({ + required this.name, + required this.id, + }); + + factory RegionModel.fromJson(Map json) { + return RegionModel( + name: json['regionName'], + id: json['uuid'].toString(), // Ensure id is a String + ); + } + + Map toJson() { + return { + 'regionName': name, + 'uuid': id, + }; + } +} diff --git a/lib/pages/auth/model/user_model.dart b/lib/pages/auth/model/user_model.dart index 5ca08d1c..674eee9a 100644 --- a/lib/pages/auth/model/user_model.dart +++ b/lib/pages/auth/model/user_model.dart @@ -1,3 +1,5 @@ + + import 'package:syncrow_web/pages/auth/model/token.dart'; class UserModel { diff --git a/lib/pages/auth/view/forget_password_mobile_page.dart b/lib/pages/auth/view/forget_password_mobile_page.dart new file mode 100644 index 00000000..2aab5894 --- /dev/null +++ b/lib/pages/auth/view/forget_password_mobile_page.dart @@ -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(); + } +} diff --git a/lib/pages/auth/view/forget_password_page.dart b/lib/pages/auth/view/forget_password_page.dart new file mode 100644 index 00000000..da09a888 --- /dev/null +++ b/lib/pages/auth/view/forget_password_page.dart @@ -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() + ); + } +} + diff --git a/lib/pages/auth/view/forget_password_web_page.dart b/lib/pages/auth/view/forget_password_web_page.dart new file mode 100644 index 00000000..d24691b6 --- /dev/null +++ b/lib/pages/auth/view/forget_password_web_page.dart @@ -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( + 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(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: [ + 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( + 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( + 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(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(), + ], + ), + ), + ), + ], + ), + ) + ); + } +} diff --git a/lib/pages/auth/view/login_mobile_page.dart b/lib/pages/auth/view/login_mobile_page.dart index ccccb916..4cb00cf5 100644 --- a/lib/pages/auth/view/login_mobile_page.dart +++ b/lib/pages/auth/view/login_mobile_page.dart @@ -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( + create: (context) => AuthBloc(), + child: BlocConsumer( 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(context); + final loginBloc = BlocProvider.of(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: [ - 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: [ + 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( + 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( + 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( + 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", + ), + ], + ), + ) + ], + ), + )), ], ), ), diff --git a/lib/pages/auth/view/login_page.dart b/lib/pages/auth/view/login_page.dart index d5644b5e..58b07299 100644 --- a/lib/pages/auth/view/login_page.dart +++ b/lib/pages/auth/view/login_page.dart @@ -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( diff --git a/lib/pages/auth/view/login_web_page.dart b/lib/pages/auth/view/login_web_page.dart index 92f24028..401b4b2d 100644 --- a/lib/pages/auth/view/login_web_page.dart +++ b/lib/pages/auth/view/login_web_page.dart @@ -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 createState() => _LoginWebPageState(); +} + +class _LoginWebPageState extends State { + @override Widget build(BuildContext context) { return Scaffold( body: BlocProvider( - create: (context) => LoginBloc(), - child: BlocConsumer( + create: (BuildContext context) => AuthBloc(), + child: BlocConsumer( 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(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(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: [ - 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: [ + 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( + 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( + 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(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(), + ], + ),), ), - ], + ], + ), ), ); } diff --git a/lib/pages/common/default_button.dart b/lib/pages/common/default_button.dart new file mode 100644 index 00000000..d588acb9 --- /dev/null +++ b/lib/pages/common/default_button.dart @@ -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( + (Set 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, + ), + ), + ); + } +} diff --git a/lib/pages/common/first_layer.dart b/lib/pages/common/first_layer.dart new file mode 100644 index 00000000..a566f6d9 --- /dev/null +++ b/lib/pages/common/first_layer.dart @@ -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! + ], + ), + ); + } +} diff --git a/lib/services/api/http_interceptor.dart b/lib/services/api/http_interceptor.dart index 71fbaba3..ff3fc0d4 100644 --- a/lib/services/api/http_interceptor.dart +++ b/lib/services/api/http_interceptor.dart @@ -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 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); } diff --git a/lib/services/auth_api.dart b/lib/services/auth_api.dart index 4829f527..58a705ff 100644 --- a/lib/services/auth_api.dart +++ b/lib/services/auth_api.dart @@ -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 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 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 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> 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; + } } diff --git a/lib/utils/color_manager.dart b/lib/utils/color_manager.dart index 36e5b0fc..96e7a43a 100644 --- a/lib/utils/color_manager.dart +++ b/lib/utils/color_manager.dart @@ -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); } diff --git a/lib/utils/constants/api_const.dart b/lib/utils/constants/api_const.dart index e188f4e6..855d74e5 100644 --- a/lib/utils/constants/api_const.dart +++ b/lib/utils/constants/api_const.dart @@ -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'; } diff --git a/lib/utils/constants/assets.dart b/lib/utils/constants/assets.dart index 1e5e0d00..0758fa9a 100644 --- a/lib/utils/constants/assets.dart +++ b/lib/utils/constants/assets.dart @@ -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"; } diff --git a/lib/utils/constants/strings_manager.dart b/lib/utils/constants/strings_manager.dart new file mode 100644 index 00000000..f3edde6e --- /dev/null +++ b/lib/utils/constants/strings_manager.dart @@ -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'; +} diff --git a/lib/utils/helpers/shared_preferences_helper.dart b/lib/utils/helpers/shared_preferences_helper.dart new file mode 100644 index 00000000..b9c7e0f4 --- /dev/null +++ b/lib/utils/helpers/shared_preferences_helper.dart @@ -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 value) async { + final prefs = await SharedPreferences.getInstance(); + await prefs.setStringList(key, value); + } + + static Future readStringFromSP(String key) async { + final prefs = await SharedPreferences.getInstance(); + String value = prefs.getString(key) ?? ''; + return value; + } + + static Future readBoolFromSP(String key) async { + final prefs = await SharedPreferences.getInstance(); + bool? value = prefs.getBool(key); + return value; + } + + static Future readIntFromSP(String key) async { + final prefs = await SharedPreferences.getInstance(); + int value = prefs.getInt(key) ?? 0; + return value; + } + + static Future> readStringListFromSP(String key) async { + final prefs = await SharedPreferences.getInstance(); + List? value = prefs.getStringList(key) ?? []; + return value; + } + + static Future removeValueFromSP(String key) async { + final prefs = await SharedPreferences.getInstance(); + await prefs.remove(key); + return true; + } +} diff --git a/lib/utils/snack_bar.dart b/lib/utils/snack_bar.dart index d50a4250..11e46828 100644 --- a/lib/utils/snack_bar.dart +++ b/lib/utils/snack_bar.dart @@ -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: [ diff --git a/lib/utils/style.dart b/lib/utils/style.dart index 8e8eb3f6..7260e78d 100644 --- a/lib/utils/style.dart +++ b/lib/utils/style.dart @@ -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))); \ No newline at end of file diff --git a/lib/web_layout/web_app_bar.dart b/lib/web_layout/web_app_bar.dart index c7655eef..7d28032d 100644 --- a/lib/web_layout/web_app_bar.dart +++ b/lib/web_layout/web_app_bar.dart @@ -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 ,), ], ) ], diff --git a/macos/Flutter/GeneratedPluginRegistrant.swift b/macos/Flutter/GeneratedPluginRegistrant.swift index 15a1671b..37af1fe0 100644 --- a/macos/Flutter/GeneratedPluginRegistrant.swift +++ b/macos/Flutter/GeneratedPluginRegistrant.swift @@ -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")) } diff --git a/pubspec.lock b/pubspec.lock index 19cd83d0..ba0d48bf 100644 --- a/pubspec.lock +++ b/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: diff --git a/pubspec.yaml b/pubspec.yaml index 84c96f79..7f55b18e 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -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: diff --git a/test/widget_test.dart b/test/widget_test.dart index 69f36050..e69de29b 100644 --- a/test/widget_test.dart +++ b/test/widget_test.dart @@ -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); - }); -}