change_password

This commit is contained in:
mohammad
2024-12-30 16:51:35 +03:00
parent 33d2bbc91f
commit 8be05fbd91
16 changed files with 864 additions and 33 deletions

View File

@ -0,0 +1,3 @@
<svg width="17" height="8" viewBox="0 0 17 8" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M15.1551 2.33183C15.4564 2.0013 15.6902 1.64372 15.8445 1.26438C15.9791 0.93308 15.7945 0.564752 15.4321 0.441634C15.0698 0.318517 14.6666 0.487195 14.5319 0.818495C14.2555 1.49824 13.533 2.13013 12.4974 2.59786C11.3781 3.10342 9.9584 3.38183 8.50002 3.38183C7.04165 3.38183 5.62204 3.10342 4.50267 2.59786C3.4671 2.13017 2.74455 1.49829 2.46811 0.818581C2.33341 0.487323 1.93051 0.318601 1.56797 0.441762C1.20557 0.56488 1.02099 0.933251 1.15569 1.26455C1.30993 1.64384 1.54371 2.00138 1.84496 2.33187L0.205067 3.83074C-0.0683557 4.08065 -0.0683557 4.48579 0.205067 4.73569C0.341779 4.86065 0.520918 4.9231 0.700103 4.9231C0.879288 4.9231 1.05847 4.86065 1.19514 4.73569L2.87865 3.19694C3.33414 3.49453 3.85618 3.75417 4.43008 3.96875L3.77375 6.02617C3.66551 6.36545 3.87875 6.72068 4.24993 6.81961C4.31534 6.83706 4.38129 6.84533 4.44619 6.84533C4.74941 6.84533 5.02883 6.66386 5.11798 6.38439L5.76259 4.36365C6.40253 4.50861 7.08297 4.60375 7.78919 4.64214V6.76146C7.78919 7.11486 8.10266 7.40137 8.48931 7.40137C8.87597 7.40137 9.18944 7.11486 9.18944 6.76146V4.64338C9.90329 4.60575 10.5911 4.51011 11.2375 4.3637L11.8821 6.38443C11.9713 6.6639 12.2507 6.84538 12.5539 6.84538C12.6188 6.84538 12.6848 6.83706 12.7502 6.81965C13.1214 6.72072 13.3346 6.36549 13.2263 6.02621L12.57 3.96879C13.1438 3.75421 13.6659 3.49458 14.1214 3.19698L15.8049 4.73574C15.9416 4.86069 16.1207 4.92314 16.2999 4.92314C16.479 4.92314 16.6582 4.86065 16.7949 4.73574C17.0684 4.48583 17.0684 4.08069 16.7949 3.83078L15.1551 2.33183Z" fill="#D5D5D5"/>
</svg>

After

Width:  |  Height:  |  Size: 1.6 KiB

View 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

View File

@ -0,0 +1,10 @@
<svg width="107" height="100" viewBox="0 0 107 100" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M36.8625 65.1775C39.1819 62.9069 40.625 59.7444 40.625 56.25C40.625 49.3575 35.0175 43.75 28.125 43.75C21.2325 43.75 15.625 49.3575 15.625 56.25C15.625 59.7444 17.0681 62.9069 19.3875 65.1775C15.2348 67.9887 12.5 72.7435 12.5 78.125V84.375C12.5 86.1008 13.8992 87.5 15.625 87.5H40.625C42.3508 87.5 43.75 86.1008 43.75 84.375V78.125C43.75 72.7435 41.0152 67.9887 36.8625 65.1775ZM28.125 50C31.5712 50 34.375 52.8038 34.375 56.25C34.375 59.6962 31.5712 62.5 28.125 62.5C24.6788 62.5 21.875 59.6962 21.875 56.25C21.875 52.8038 24.6788 50 28.125 50ZM37.5 81.25H18.75V78.125C18.75 72.9556 22.9556 68.75 28.125 68.75C33.2944 68.75 37.5 72.9556 37.5 78.125V81.25Z" fill="#D5D5D5"/>
<path d="M72.291 62.5C74.0169 62.5 75.416 61.1009 75.416 59.375C75.416 57.6491 74.0169 56.25 72.291 56.25C70.5651 56.25 69.166 57.6491 69.166 59.375C69.166 61.1009 70.5651 62.5 72.291 62.5Z" fill="#D5D5D5"/>
<path d="M84.791 62.5C86.5169 62.5 87.916 61.1009 87.916 59.375C87.916 57.6491 86.5169 56.25 84.791 56.25C83.0651 56.25 81.666 57.6491 81.666 59.375C81.666 61.1009 83.0651 62.5 84.791 62.5Z" fill="#D5D5D5"/>
<path d="M59.791 62.5C61.5169 62.5 62.916 61.1009 62.916 59.375C62.916 57.6491 61.5169 56.25 59.791 56.25C58.0651 56.25 56.666 57.6491 56.666 59.375C56.666 61.1009 58.0651 62.5 59.791 62.5Z" fill="#D5D5D5"/>
<path d="M84.791 68.75H59.791C58.0652 68.75 56.666 70.1492 56.666 71.875C56.666 73.6008 58.0652 75 59.791 75H84.791C86.5168 75 87.916 73.6008 87.916 71.875C87.916 70.1492 86.5168 68.75 84.791 68.75Z" fill="#D5D5D5"/>
<path d="M84.791 81.25H59.791C58.0652 81.25 56.666 82.6492 56.666 84.375C56.666 86.1008 58.0652 87.5 59.791 87.5H84.791C86.5168 87.5 87.916 86.1008 87.916 84.375C87.916 82.6492 86.5168 81.25 84.791 81.25Z" fill="#D5D5D5"/>
<path d="M93.2507 16.5405C92.0305 15.3199 90.0517 15.3199 88.8315 16.5403L78.5411 26.8307L74.5007 22.7903C73.2805 21.5699 71.3017 21.5699 70.0813 22.7903C68.8609 24.0107 68.8609 25.9892 70.0813 27.2097L76.3313 33.4597C76.9415 34.0701 77.7413 34.3751 78.5411 34.3751C79.3409 34.3751 80.1407 34.0701 80.7507 33.4599L93.2507 20.9599C94.4711 19.7395 94.4711 17.7609 93.2507 16.5405Z" fill="#D5D5D5"/>
<path d="M81.6667 0C67.8817 0 56.6667 11.215 56.6667 25H9.375C4.20563 25 0 29.2056 0 34.375V90.625C0 95.7944 4.20563 100 9.375 100H91.0417C96.211 100 100.417 95.7944 100.417 90.625V41.5165C104.304 37.1085 106.667 31.3254 106.667 25C106.667 11.215 95.4517 0 81.6667 0ZM94.1667 90.625C94.1667 92.3481 92.7648 93.75 91.0417 93.75H9.375C7.65188 93.75 6.25 92.3481 6.25 90.625V34.375C6.25 32.6519 7.65188 31.25 9.375 31.25H57.4577C60.24 42.0206 70.039 50 81.6667 50C86.2173 50 90.4873 48.7773 94.1667 46.644V90.625ZM81.6667 43.75C71.3279 43.75 62.9167 35.3387 62.9167 25C62.9167 14.6613 71.3279 6.25 81.6667 6.25C92.0054 6.25 100.417 14.6613 100.417 25C100.417 35.3387 92.0054 43.75 81.6667 43.75Z" fill="#D5D5D5"/>
</svg>

After

Width:  |  Height:  |  Size: 2.9 KiB

View File

@ -204,6 +204,7 @@ class AuthCubit extends Cubit<AuthState> {
const FlutterSecureStorage().write( const FlutterSecureStorage().write(
key: UserModel.userUuidKey, key: UserModel.userUuidKey,
value: Token.decodeToken(token.accessToken)['uuid'].toString()); value: Token.decodeToken(token.accessToken)['uuid'].toString());
user = UserModel.fromToken(token); user = UserModel.fromToken(token);
emailController.clear(); emailController.clear();
passwordController.clear(); passwordController.clear();

View File

@ -14,6 +14,7 @@ class UserModel {
final bool? isEmailVerified; final bool? isEmailVerified;
final String? regionName; final String? regionName;
final String? timeZone; final String? timeZone;
final String? regionUuid;
final bool? isAgreementAccepted; final bool? isAgreementAccepted;
UserModel({ UserModel({
@ -24,10 +25,10 @@ class UserModel {
required this.profilePicture, required this.profilePicture,
required this.phoneNumber, required this.phoneNumber,
required this.isEmailVerified, required this.isEmailVerified,
required this.regionUuid,
required this.isAgreementAccepted, required this.isAgreementAccepted,
required this.regionName, // Add this line required this.regionName, // Add this line
required this.timeZone, // Add this line required this.timeZone, // Add this line
}); });
factory UserModel.fromJson(Map<String, dynamic> json) { factory UserModel.fromJson(Map<String, dynamic> json) {
@ -42,21 +43,23 @@ class UserModel {
isAgreementAccepted: json['isAgreementAccepted'], isAgreementAccepted: json['isAgreementAccepted'],
regionName: json['region']?['regionName'], // Extract regionName regionName: json['region']?['regionName'], // Extract regionName
timeZone: json['timeZone']?['timeZoneOffset'], // Extract regionName timeZone: json['timeZone']?['timeZoneOffset'], // Extract regionName
regionUuid: json['region']?['uuid'],
); );
} }
//uuid to json //uuid to json
//from token //from token
factory UserModel.fromToken(Token token) { factory UserModel.fromToken(Token token) {
Map<String, dynamic> tempJson = Token.decodeToken(token.accessToken); Map<String, dynamic> tempJson = Token.decodeToken(token.accessToken);
return UserModel( return UserModel(
uuid: tempJson['uuid'].toString(), uuid: tempJson['uuid'].toString(),
email: tempJson['email'], email: tempJson['email'],
lastName: tempJson['lastName'], lastName: tempJson['lastName'],
firstName:tempJson['firstName'] , firstName: tempJson['firstName'],
profilePicture: UserModel.decodeBase64Image(tempJson['profilePicture']), profilePicture: UserModel.decodeBase64Image(tempJson['profilePicture']),
phoneNumber: null, phoneNumber: null,
isEmailVerified: null, isEmailVerified: null,
isAgreementAccepted: null, isAgreementAccepted: null,
regionUuid: null,
regionName: tempJson['region']?['regionName'], regionName: tempJson['region']?['regionName'],
timeZone: tempJson['timezone']?['timeZoneOffset'], timeZone: tempJson['timezone']?['timeZoneOffset'],
); );

View File

@ -0,0 +1,207 @@
import 'dart:async';
import 'package:dio/dio.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:syncrow_app/features/app_layout/bloc/home_cubit.dart';
import 'package:syncrow_app/features/menu/view/widgets/securty/bloc/security_event.dart';
import 'package:syncrow_app/features/menu/view/widgets/securty/bloc/security_state.dart';
import 'package:syncrow_app/services/api/authentication_api.dart';
class SecurityBloc extends Bloc<SecurityEvent, SecurityState> {
bool _isPasswordVisible = false;
String otpCode = '';
String validate = '';
SecurityBloc() : super(PasswordVisibilityState(false)) {
on<SetPassword>(_onSetPassword);
on<TogglePasswordVisibility>(_onTogglePasswordVisibility);
on<StartTimerEvent>(_onStartTimer);
on<StopTimerEvent>(_onStopTimer);
on<UpdateTimerEvent>(_onUpdateTimer);
on<VerifyPassCodeEvent>(verifyCode);
on<ChangePasswordEvent>(changePassword);
}
void _onSetPassword(SetPassword event, Emitter<SecurityState> emit) {
if (event.password.length < 6) {
emit(PasswordErrorState('Password must be at least 6 characters long.'));
} else {
emit(PasswordSetState('Password successfully set.'));
}
}
void _onTogglePasswordVisibility(
TogglePasswordVisibility event, Emitter<SecurityState> emit) {
_isPasswordVisible = !_isPasswordVisible;
emit(PasswordVisibilityState(_isPasswordVisible));
}
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;
}
TextEditingController newPassword = TextEditingController();
Timer? _timer;
int _remainingTime = 0;
Future _onStartTimer(
StartTimerEvent event, Emitter<SecurityState> emit) async {
if (_timer != null && _timer!.isActive) {
return;
}
_remainingTime = 1;
add(UpdateTimerEvent(
remainingTime: _remainingTime, isButtonEnabled: false));
try {
_remainingTime = 30;
var res = (await AuthenticationAPI.sendOtp(body: {
'email': HomeCubit.user!.email,
'type': 'VERIFICATION',
if (HomeCubit.user!.regionUuid != null)
'regionUuid': HomeCubit.user!.regionUuid
}));
_remainingTime = res['cooldown'];
} on DioException catch (e) {
if (e.response!.statusCode == 400) {
final errorData = e.response!.data;
String errorMessage = errorData['message'];
if (errorMessage == 'User not found') {
validate = 'Invalid Credential';
emit(AuthInitialState());
return 1;
} else {
validate = '';
_remainingTime = errorData['data']['cooldown'] ?? 1;
emit(AuthInitialState());
}
} else {
emit(AuthInitialState());
return 1;
}
emit(AuthInitialState());
} catch (e) {
emit(AuthInitialState());
return 1;
}
_timer = Timer.periodic(const Duration(seconds: 1), (timer) {
_remainingTime--;
if (_remainingTime <= 0) {
_timer?.cancel();
add(UpdateTimerEvent(remainingTime: 0, isButtonEnabled: true));
} else {
add(UpdateTimerEvent(
remainingTime: _remainingTime, isButtonEnabled: false));
}
});
}
void _onStopTimer(StopTimerEvent event, Emitter<SecurityState> emit) {
_timer?.cancel();
emit(TimerState(isButtonEnabled: true, remainingTime: 0));
}
void _onUpdateTimer(UpdateTimerEvent event, Emitter<SecurityState> emit) {
emit(TimerState(
isButtonEnabled: event.isButtonEnabled,
remainingTime: event.remainingTime));
}
String formattedTime(int time) {
final int days = (time / 86400).floor(); // 86400 seconds in a day
final int hours = ((time % 86400) / 3600).floor();
final int minutes = (((time % 86400) % 3600) / 60).floor();
final int seconds = (((time % 86400) % 3600) % 60).floor();
final String formattedTime = [
if (days > 0) '${days}d', // Append 'd' for days
if (days > 0 || hours > 0)
hours
.toString()
.padLeft(2, '0'), // Show hours if there are days or hours
minutes.toString().padLeft(2, '0'),
seconds.toString().padLeft(2, '0'),
].join(':');
return formattedTime;
}
Future<void> verifyCode(
VerifyPassCodeEvent event, Emitter<SecurityState> emit) async {
emit(LoadingForgetState());
try {
final response = await AuthenticationAPI.verifyPassCode(body: {
'email': HomeCubit.user!.email!,
'type': 'VERIFICATION',
'otpCode': otpCode
});
//929971
print('response=-=-=-${response}');
if (response['statusCode'] == 200) {
_timer?.cancel();
emit(SuccessForgetState());
}
} on DioException catch (e) {
final errorData = e.response!.data;
String errorMessage =
errorData['error']['message'] ?? 'something went wrong';
validate = errorMessage;
emit(AuthInitialState());
}
}
Future<void> changePassword(
ChangePasswordEvent event, Emitter<SecurityState> emit) async {
emit(LoadingForgetState());
try {
print('response=-=-=-${event.otpCode}');
final response = await AuthenticationAPI.forgetPassword(
email: HomeCubit.user!.email!,
otpCode: event.otpCode,
password: newPassword.text,
);
//537580
print('response=-=-=-${response}');
// if (response['statusCode'] == 200) {
// _timer?.cancel();
// emit(ChangedPassState());
// }
emit(ChangedPassState());
} on DioException catch (e) {
final errorData = e.response!.data;
String errorMessage =
errorData['error']['message'] ?? 'something went wrong';
validate = errorMessage;
emit(AuthInitialState());
}
}
}

View File

@ -0,0 +1,31 @@
abstract class SecurityEvent {}
class SetPassword extends SecurityEvent {
final String password;
SetPassword(this.password);
}
class TogglePasswordVisibility extends SecurityEvent {}
class SubmitEvent extends SecurityEvent {}
class StartTimerEvent extends SecurityEvent {}
class StopTimerEvent extends SecurityEvent {}
class UpdateTimerEvent extends SecurityEvent {
final int remainingTime;
final bool isButtonEnabled;
UpdateTimerEvent(
{required this.remainingTime, required this.isButtonEnabled});
}
class ChangePasswordEvent extends SecurityEvent {
final String otpCode;
ChangePasswordEvent({
required this.otpCode,
});
}
class VerifyPassCodeEvent extends SecurityEvent {}

View File

@ -0,0 +1,44 @@
abstract class SecurityState {}
class InitialState extends SecurityState {}
class PasswordVisibilityState extends SecurityState {
final bool isVisible;
PasswordVisibilityState(this.isVisible);
}
class PasswordSetState extends SecurityState {
final String message;
PasswordSetState(this.message);
}
class PasswordErrorState extends SecurityState {
final String error;
PasswordErrorState(this.error);
}
class AuthTokenLoading extends SecurityState {}
class AuthLoading extends SecurityState {}
class AuthInitialState extends SecurityState {}
class TimerState extends SecurityState {
final bool isButtonEnabled;
final int remainingTime;
TimerState({required this.isButtonEnabled, required this.remainingTime});
@override
List<Object> get props => [isButtonEnabled, remainingTime];
}
class InitialForgetState extends SecurityState {}
class LoadingForgetState extends SecurityState {}
class SuccessForgetState extends SecurityState {}
class ChangedPassState extends SecurityState {}

View File

@ -0,0 +1,88 @@
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_svg/flutter_svg.dart';
import 'package:syncrow_app/features/menu/view/widgets/securty/bloc/security_bloc.dart';
import 'package:syncrow_app/features/menu/view/widgets/securty/view/verification_code_page.dart';
import 'package:syncrow_app/features/shared_widgets/default_scaffold.dart';
import 'package:syncrow_app/features/shared_widgets/text_widgets/body_medium.dart';
import 'package:syncrow_app/generated/assets.dart';
import 'package:syncrow_app/utils/resource_manager/color_manager.dart';
class ChangePasswordPage extends StatelessWidget {
const ChangePasswordPage({super.key});
@override
Widget build(BuildContext context) {
return DefaultScaffold(
title: 'Change Password',
bottomNavBar: SizedBox(
height: 150,
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
mainAxisAlignment: MainAxisAlignment.center,
children: [
InkWell(
onTap: () {
// In your parent widget or navigator
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => BlocProvider(
create: (_) => SecurityBloc(), // Provide the Bloc
child: const VerificationCodePage(),
),
),
);
},
child: Container(
height: 50,
margin: const EdgeInsets.only(right: 20, left: 20),
decoration: const BoxDecoration(
color: ColorsManager.blueColor,
borderRadius: BorderRadius.all(Radius.circular(20))),
child: const Center(
child: Text(
'Get Verification Code',
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w700,
color: ColorsManager.onPrimaryColor),
)),
),
),
],
),
),
child: Column(
children: [
Padding(
padding: const EdgeInsets.only(top: 80, bottom: 30),
child: SvgPicture.asset(Assets.verificationIcon),
),
const Padding(
padding: EdgeInsets.all(8.0),
child: BodyMedium(
text: 'Account Verification',
fontWeight: FontWeight.w700,
fontSize: 18,
),
),
const BodyMedium(
text: 'Click here to send a verification ',
fontWeight: FontWeight.w400,
fontSize: 16,
),
const SizedBox(
height: 4,
),
const BodyMedium(
text: 'code to your email: test@test.com',
fontWeight: FontWeight.w400,
fontSize: 16,
),
],
),
);
}
}

View File

@ -1,4 +1,5 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:syncrow_app/features/menu/view/widgets/securty/view/change_password_page.dart';
import 'package:syncrow_app/features/shared_widgets/default_container.dart'; import 'package:syncrow_app/features/shared_widgets/default_container.dart';
import 'package:syncrow_app/features/shared_widgets/default_scaffold.dart'; import 'package:syncrow_app/features/shared_widgets/default_scaffold.dart';
import 'package:syncrow_app/features/shared_widgets/text_widgets/body_medium.dart'; import 'package:syncrow_app/features/shared_widgets/text_widgets/body_medium.dart';
@ -23,7 +24,11 @@ class SecurtyView extends StatelessWidget {
mainAxisAlignment: MainAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.start,
children: [ children: [
InkWell( InkWell(
onTap: () {}, onTap: () {
Navigator.of(context).push(MaterialPageRoute(
builder: (context) => const ChangePasswordPage(),
));
},
child: const Column( child: const Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,

View File

@ -0,0 +1,239 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_svg/svg.dart';
import 'package:syncrow_app/features/auth/bloc/auth_cubit.dart';
import 'package:syncrow_app/features/devices/view/device_settings/update_dialog.dart';
import 'package:syncrow_app/features/menu/view/widgets/securty/bloc/security_bloc.dart';
import 'package:syncrow_app/features/menu/view/widgets/securty/bloc/security_event.dart';
import 'package:syncrow_app/features/menu/view/widgets/securty/bloc/security_state.dart';
import 'package:syncrow_app/features/shared_widgets/default_scaffold.dart';
import 'package:syncrow_app/features/shared_widgets/text_widgets/body_large.dart';
import 'package:syncrow_app/features/shared_widgets/text_widgets/body_medium.dart';
import 'package:syncrow_app/generated/assets.dart';
import 'package:syncrow_app/utils/resource_manager/color_manager.dart';
class SetPasswordPage extends StatelessWidget {
String? otpCode;
SetPasswordPage({super.key, this.otpCode});
@override
Widget build(BuildContext context) {
return BlocProvider<SecurityBloc>(
create: (context) => SecurityBloc(),
child: BlocConsumer<SecurityBloc, SecurityState>(
listener: (context, state) {
if (state is SuccessForgetState) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('Change Password Successfully '),
),
);
Navigator.of(context).pop();
}
if (state is ChangedPassState) {
showDialog(
context: context,
barrierDismissible: false,
builder: (context) {
return AlertDialog(
contentPadding: EdgeInsets.zero,
content: SizedBox(
height: MediaQuery.of(context).size.height * 0.2,
child: Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
const SizedBox(
height: 10,
),
BodyLarge(
text: 'Password Changed',
fontWeight: FontWeight.w700,
fontColor:
ColorsManager.switchButton.withOpacity(0.6),
fontSize: 16,
),
const Padding(
padding: EdgeInsets.only(left: 15, right: 15),
child: Divider(
color: ColorsManager.textGray,
),
),
const Padding(
padding: EdgeInsets.only(
left: 15, right: 20, top: 15, bottom: 20),
child: Column(
children: [
Center(
child: Text(
'Your password has been',
textAlign: TextAlign.center,
)),
Center(
child: Text(
'successfully updated.',
textAlign: TextAlign.center,
)),
],
),
),
Container(
decoration: const BoxDecoration(
border: Border(
left: BorderSide(
color: ColorsManager.textGray,
width: 0.5,
),
top: BorderSide(
color: ColorsManager.textGray,
width: 1.0,
),
)),
child: InkWell(
onTap: () {
AuthCubit.get(context).logout();
},
child: Padding(
padding:
const EdgeInsets.only(top: 15, bottom: 15),
child: Center(
child: Text(
'Done',
style: TextStyle(
color: ColorsManager.switchButton
.withOpacity(0.6),
fontSize: 14,
fontWeight: FontWeight.w400),
),
),
)),
)
],
),
),
);
},
);
}
}, builder: (context, state) {
final _bloc = BlocProvider.of<SecurityBloc>(context);
return DefaultScaffold(
title: 'Change Password',
child: ListView(
shrinkWrap: true,
children: [
const SizedBox(height: 55),
const Center(
child: Padding(
padding: EdgeInsets.all(8.0),
child: BodyMedium(
text: 'Set Password',
fontWeight: FontWeight.w700,
fontSize: 18,
),
),
),
const Center(
child: BodyMedium(
text: 'Secure your account with a',
fontWeight: FontWeight.w400,
fontSize: 16,
),
),
const SizedBox(height: 4),
const Center(
child: BodyMedium(
text: 'strong password',
fontWeight: FontWeight.w400,
fontSize: 14,
),
),
const SizedBox(height: 40),
BlocBuilder<SecurityBloc, SecurityState>(
builder: (context, state) {
if (state is PasswordErrorState) {
return Center(
child: Text(
state.error,
style: const TextStyle(color: Colors.red),
),
);
} else if (state is PasswordSetState) {
return Center(
child: Text(
state.message,
style: const TextStyle(color: Colors.green),
),
);
}
return const SizedBox.shrink();
},
),
PasswordInputField(controller: _bloc.newPassword),
const SizedBox(height: 55),
InkWell(
onTap: () {
_bloc.add(ChangePasswordEvent(otpCode: otpCode.toString()));
},
child: Container(
padding: const EdgeInsets.only(
right: 20, left: 20, top: 15, bottom: 15),
decoration: const BoxDecoration(
color: ColorsManager.blueColor,
borderRadius: BorderRadius.all(Radius.circular(20)),
),
child: const Center(
child: Text(
"Done",
style: TextStyle(
fontSize: 16,
color: Colors.white,
fontWeight: FontWeight.w700,
),
),
),
),
)
],
),
);
}));
}
}
class PasswordInputField extends StatelessWidget {
PasswordInputField({this.controller});
TextEditingController? controller = TextEditingController();
@override
Widget build(BuildContext context) {
return BlocBuilder<SecurityBloc, SecurityState>(
builder: (context, state) {
bool isPasswordVisible = false;
if (state is PasswordVisibilityState) {
isPasswordVisible = state.isVisible;
}
return TextField(
controller: controller,
obscureText: !isPasswordVisible,
decoration: InputDecoration(
hintText: 'Password',
suffixIcon: IconButton(
icon: SvgPicture.asset(isPasswordVisible
? Assets.passwordVisibility
: Assets.passwordUnvisibility),
onPressed: () {
context.read<SecurityBloc>().add(TogglePasswordVisibility());
},
),
),
);
},
);
}
void dispose() {
controller!.dispose();
}
}

View File

@ -0,0 +1,181 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:syncrow_app/features/app_layout/bloc/home_cubit.dart';
import 'package:syncrow_app/features/menu/view/widgets/securty/bloc/security_bloc.dart';
import 'package:syncrow_app/features/menu/view/widgets/securty/bloc/security_event.dart';
import 'package:syncrow_app/features/menu/view/widgets/securty/bloc/security_state.dart';
import 'package:syncrow_app/features/menu/view/widgets/securty/view/set_password_page.dart';
import 'package:syncrow_app/features/shared_widgets/default_scaffold.dart';
import 'package:syncrow_app/features/shared_widgets/text_widgets/body_medium.dart';
import 'package:syncrow_app/utils/resource_manager/color_manager.dart';
import 'package:pin_code_fields/pin_code_fields.dart';
class VerificationCodePage extends StatelessWidget {
const VerificationCodePage({super.key});
@override
Widget build(BuildContext context) {
String otp = '';
return BlocProvider<SecurityBloc>(
create: (context) => SecurityBloc()..add(StartTimerEvent()),
child: BlocConsumer<SecurityBloc, SecurityState>(
listener: (context, state) {
if (state is SuccessForgetState) {
Navigator.of(context).push(MaterialPageRoute(
builder: (context) => SetPasswordPage(
otpCode: otp,
),
));
}
},
builder: (context, state) {
final _bloc = BlocProvider.of<SecurityBloc>(context);
return DefaultScaffold(
title: 'Change Password',
child: Column(
children: [
const SizedBox(height: 55),
const Padding(
padding: EdgeInsets.all(8.0),
child: BodyMedium(
text: 'Verification Code',
fontWeight: FontWeight.w700,
fontSize: 18,
),
),
const BodyMedium(
text: 'We have sent the verification code',
fontWeight: FontWeight.w400,
fontSize: 16,
),
const SizedBox(height: 4),
Row(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.center,
children: [
const BodyMedium(
text: 'to ',
fontWeight: FontWeight.w400,
fontSize: 14,
),
BodyMedium(
text: HomeCubit.user!.email!,
fontWeight: FontWeight.w700,
fontSize: 14,
),
],
),
const SizedBox(height: 40),
BlocBuilder<SecurityBloc, SecurityState>(
builder: (context, state) {
return Center(
child: PinCodeTextField(
hintCharacter: '0',
appContext: context,
length: 6,
mainAxisAlignment: MainAxisAlignment.center,
backgroundColor: Colors.transparent,
enableActiveFill: true,
pinTheme: PinTheme(
shape: PinCodeFieldShape.box,
activeColor: Colors.white,
selectedColor: Colors.white,
inactiveColor: Colors.white,
inactiveFillColor: Colors.white70,
selectedFillColor: Colors.white70,
activeFillColor: Colors.white,
errorBorderColor: Colors.white,
fieldHeight: 55.0,
fieldWidth: 55.0,
fieldOuterPadding: const EdgeInsets.only(right: 8),
borderRadius: BorderRadius.circular(17),
borderWidth: 1,
),
cursorWidth: 1,
keyboardType: TextInputType.number,
onChanged: (value) {
// Update OTP code in the bloc when user enters a pin
_bloc.otpCode = value;
otp = value;
},
),
);
},
),
const SizedBox(height: 55),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Expanded(
child: InkWell(
onTap: state is TimerState &&
!state.isButtonEnabled &&
state.remainingTime != 1
? null
: () {
_bloc.add(StartTimerEvent());
},
child: Container(
padding: const EdgeInsets.only(
right: 20, left: 20, top: 15, bottom: 15),
decoration: BoxDecoration(
color: state is TimerState && !state.isButtonEnabled
? ColorsManager.blueButton
: ColorsManager.blueColor,
borderRadius:
BorderRadius.all(Radius.circular(20))),
child: Center(
child: Center(
child: Text(
'${state is TimerState && !state.isButtonEnabled && state.remainingTime != 1 ? "${_bloc.formattedTime(state.remainingTime)} " : "Resend"}',
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w700,
color: state is TimerState &&
!state.isButtonEnabled
? Colors.white
: ColorsManager.onPrimaryColor,
),
),
),
),
),
)),
const SizedBox(width: 20),
Expanded(
child: InkWell(
onTap: () {
context
.read<SecurityBloc>()
.add(VerifyPassCodeEvent());
},
child: Container(
padding: const EdgeInsets.only(
right: 20, left: 20, top: 15, bottom: 15),
decoration: const BoxDecoration(
color: ColorsManager.blueColor,
borderRadius:
BorderRadius.all(Radius.circular(20))),
child: const Center(
child: Text(
"Verify",
style: TextStyle(
fontSize: 16,
color: Colors.white,
fontWeight: FontWeight.w700),
),
),
),
),
),
],
),
],
),
);
},
),
);
}
}

View File

@ -1125,4 +1125,11 @@ class Assets {
"assets/icons/edit_device_name_icon.svg"; "assets/icons/edit_device_name_icon.svg";
static const String sosHomeIcon = "assets/icons/sos_home_icon.svg"; static const String sosHomeIcon = "assets/icons/sos_home_icon.svg";
static const String editNameSetting = "assets/icons/edit_name_setting.svg"; static const String editNameSetting = "assets/icons/edit_name_setting.svg";
static const String verificationIcon = "assets/icons/verification_icon.svg";
static const String passwordUnvisibility =
"assets/icons/password_unvisibility.svg";
static const String passwordVisibility =
"assets/icons/password_visibility.svg";
} }

View File

@ -1,4 +1,5 @@
import 'dart:async'; import 'dart:async';
import 'dart:developer';
import 'package:syncrow_app/features/app_layout/bloc/home_cubit.dart'; import 'package:syncrow_app/features/app_layout/bloc/home_cubit.dart';
import 'package:syncrow_app/features/auth/model/user_model.dart'; import 'package:syncrow_app/features/auth/model/user_model.dart';
import 'package:syncrow_app/features/menu/model/region_model.dart'; import 'package:syncrow_app/features/menu/model/region_model.dart';
@ -9,14 +10,15 @@ import 'package:syncrow_app/services/api/http_service.dart';
class ProfileApi { class ProfileApi {
static final HTTPService _httpService = HTTPService(); static final HTTPService _httpService = HTTPService();
static Future<Map<String, dynamic>> saveName({String? firstName, String? lastName,}) async { static Future<Map<String, dynamic>> saveName({
String? firstName,
String? lastName,
}) async {
try { try {
final response = await _httpService.put( final response = await _httpService.put(
path: ApiEndpoints.saveName.replaceAll('{userUuid}', HomeCubit.user!.uuid!), path: ApiEndpoints.saveName
body: { .replaceAll('{userUuid}', HomeCubit.user!.uuid!),
"firstName": firstName, body: {"firstName": firstName, "lastName": lastName},
"lastName": lastName
},
expectedResponseModel: (json) { expectedResponseModel: (json) {
return json; return json;
}, },
@ -27,10 +29,13 @@ class ProfileApi {
} }
} }
static Future saveRegion({String? regionUuid,}) async { static Future saveRegion({
String? regionUuid,
}) async {
try { try {
final response = await _httpService.put( final response = await _httpService.put(
path: ApiEndpoints.saveRegion.replaceAll('{userUuid}', HomeCubit.user!.uuid!), path: ApiEndpoints.saveRegion
.replaceAll('{userUuid}', HomeCubit.user!.uuid!),
body: { body: {
"regionUuid": regionUuid, "regionUuid": regionUuid,
}, },
@ -44,10 +49,13 @@ class ProfileApi {
} }
} }
static Future saveTimeZone({String? regionUuid,}) async { static Future saveTimeZone({
String? regionUuid,
}) async {
try { try {
final response = await _httpService.put( final response = await _httpService.put(
path: ApiEndpoints.saveTimeZone.replaceAll('{userUuid}', HomeCubit.user!.uuid!), path: ApiEndpoints.saveTimeZone
.replaceAll('{userUuid}', HomeCubit.user!.uuid!),
body: { body: {
"timezoneUuid": regionUuid, "timezoneUuid": regionUuid,
}, },
@ -64,10 +72,9 @@ class ProfileApi {
static Future<Map<String, dynamic>> saveImage(String image) async { static Future<Map<String, dynamic>> saveImage(String image) async {
try { try {
final response = await _httpService.put( final response = await _httpService.put(
path: ApiEndpoints.sendPicture.replaceAll('{userUuid}', HomeCubit.user!.uuid!), path: ApiEndpoints.sendPicture
body: { .replaceAll('{userUuid}', HomeCubit.user!.uuid!),
"profilePicture": 'data:image/png;base64,$image' body: {"profilePicture": 'data:image/png;base64,$image'},
},
expectedResponseModel: (json) { expectedResponseModel: (json) {
return json; return json;
}, },
@ -78,14 +85,14 @@ class ProfileApi {
} }
} }
Future fetchUserInfo(userId) async { Future fetchUserInfo(userId) async {
final response = await _httpService.get( final response = await _httpService.get(
path: ApiEndpoints.getUser.replaceAll('{userUuid}', userId!), path: ApiEndpoints.getUser.replaceAll('{userUuid}', userId!),
showServerMessage: true, showServerMessage: true,
expectedResponseModel: (json) { expectedResponseModel: (json) {
return UserModel.fromJson(json); log('user json =$json');
} return UserModel.fromJson(json);
); });
return response; return response;
} }
@ -94,9 +101,10 @@ class ProfileApi {
path: ApiEndpoints.getRegion, path: ApiEndpoints.getRegion,
showServerMessage: true, showServerMessage: true,
expectedResponseModel: (json) { expectedResponseModel: (json) {
return (json as List).map((zone) => RegionModel.fromJson(zone)).toList(); return (json as List)
} .map((zone) => RegionModel.fromJson(zone))
); .toList();
});
return response as List<RegionModel>; return response as List<RegionModel>;
} }
@ -106,9 +114,7 @@ class ProfileApi {
showServerMessage: true, showServerMessage: true,
expectedResponseModel: (json) { expectedResponseModel: (json) {
return (json as List).map((zone) => TimeZone.fromJson(zone)).toList(); return (json as List).map((zone) => TimeZone.fromJson(zone)).toList();
} });
);
return response as List<TimeZone>; return response as List<TimeZone>;
} }
} }

View File

@ -34,5 +34,8 @@ abstract class ColorsManager {
static const Color blueColor = Color(0xff5481F3); static const Color blueColor = Color(0xff5481F3);
static const Color blueColor1 = Color(0xff0A7AFF); static const Color blueColor1 = Color(0xff0A7AFF);
static const Color grayButtonColors = Color(0xffCCCCCC); static const Color grayButtonColors = Color(0xffCCCCCC);
static const Color blueButton = Color(0xff85BDFF);
} }
//background: #F5F5F5;023DFE //background: #F5F5F5;background: #85BDFF;

View File

@ -5,7 +5,7 @@ import 'package:syncrow_app/features/devices/model/function_model.dart';
import 'package:syncrow_app/features/menu/view/widgets/join_home/join_home_view.dart'; import 'package:syncrow_app/features/menu/view/widgets/join_home/join_home_view.dart';
import 'package:syncrow_app/features/menu/view/widgets/manage_home/manage_home_view.dart'; import 'package:syncrow_app/features/menu/view/widgets/manage_home/manage_home_view.dart';
import 'package:syncrow_app/features/menu/view/widgets/privacy/privacy_view.dart'; import 'package:syncrow_app/features/menu/view/widgets/privacy/privacy_view.dart';
import 'package:syncrow_app/features/menu/view/widgets/securty/securty_view.dart'; import 'package:syncrow_app/features/menu/view/widgets/securty/view/securty_view.dart';
import 'package:syncrow_app/generated/assets.dart'; import 'package:syncrow_app/generated/assets.dart';
import 'package:syncrow_app/utils/resource_manager/color_manager.dart'; import 'package:syncrow_app/utils/resource_manager/color_manager.dart';