From 56d4c34321f0f56f3b988a1f37e5dcc41026f161 Mon Sep 17 00:00:00 2001 From: mohammad Date: Thu, 12 Dec 2024 19:00:51 +0300 Subject: [PATCH] roles_and_permission --- assets/icons/active_user.svg | 18 + assets/icons/box_checked.png | Bin 0 -> 754 bytes assets/icons/compleate_process_icon.svg | 9 + assets/icons/current_process_icon.svg | 3 + assets/icons/deactive_user.svg | 18 + assets/icons/empty_box.png | Bin 0 -> 425 bytes assets/icons/invited_icon.svg | 7 + assets/icons/rectangle_check_box.png | Bin 0 -> 523 bytes assets/icons/search_icon_user.svg | 4 + assets/icons/uncompleate_process_icon.svg | 3 + assets/icons/wrong_process_icon.svg | 10 + lib/pages/home/bloc/home_bloc.dart | 9 +- .../bloc/roles_permission_bloc.dart | 42 ++ .../bloc/roles_permission_event.dart | 41 ++ .../bloc/roles_permission_state.dart | 77 +++ .../model/role_model.dart | 6 + .../model/roles_user_model.dart | 22 + .../users_page/bloc/users_bloc.dart | 136 +++++ .../users_page/bloc/users_event.dart | 35 ++ .../users_page/bloc/users_status.dart | 57 ++ .../users_page/view/add_user_dialog.dart | 214 ++++++++ .../users_page/view/basics_view.dart | 279 ++++++++++ .../users_page/view/roles_and_permission.dart | 498 ++++++++++++++++++ .../users_page/view/spaces_access_view.dart | 338 ++++++++++++ .../users_page/view/user_table.dart | 256 +++++++++ .../users_page/view/users_page.dart | 241 +++++++++ .../view/create_role_card.dart | 0 .../roles_and_permission/view/role_card.dart | 57 ++ .../view/roles_and_permission_page.dart | 96 ++++ .../roles_and_permission/view/roles_page.dart | 69 +++ lib/utils/app_routes.dart | 6 +- lib/utils/color_manager.dart | 13 +- lib/utils/constants/assets.dart | 158 ++++-- lib/utils/constants/routes_const.dart | 1 + lib/utils/style.dart | 79 ++- pubspec.lock | 24 +- 36 files changed, 2739 insertions(+), 87 deletions(-) create mode 100644 assets/icons/active_user.svg create mode 100644 assets/icons/box_checked.png create mode 100644 assets/icons/compleate_process_icon.svg create mode 100644 assets/icons/current_process_icon.svg create mode 100644 assets/icons/deactive_user.svg create mode 100644 assets/icons/empty_box.png create mode 100644 assets/icons/invited_icon.svg create mode 100644 assets/icons/rectangle_check_box.png create mode 100644 assets/icons/search_icon_user.svg create mode 100644 assets/icons/uncompleate_process_icon.svg create mode 100644 assets/icons/wrong_process_icon.svg create mode 100644 lib/pages/roles_and_permission/bloc/roles_permission_bloc.dart create mode 100644 lib/pages/roles_and_permission/bloc/roles_permission_event.dart create mode 100644 lib/pages/roles_and_permission/bloc/roles_permission_state.dart create mode 100644 lib/pages/roles_and_permission/model/role_model.dart create mode 100644 lib/pages/roles_and_permission/model/roles_user_model.dart create mode 100644 lib/pages/roles_and_permission/users_page/bloc/users_bloc.dart create mode 100644 lib/pages/roles_and_permission/users_page/bloc/users_event.dart create mode 100644 lib/pages/roles_and_permission/users_page/bloc/users_status.dart create mode 100644 lib/pages/roles_and_permission/users_page/view/add_user_dialog.dart create mode 100644 lib/pages/roles_and_permission/users_page/view/basics_view.dart create mode 100644 lib/pages/roles_and_permission/users_page/view/roles_and_permission.dart create mode 100644 lib/pages/roles_and_permission/users_page/view/spaces_access_view.dart create mode 100644 lib/pages/roles_and_permission/users_page/view/user_table.dart create mode 100644 lib/pages/roles_and_permission/users_page/view/users_page.dart create mode 100644 lib/pages/roles_and_permission/view/create_role_card.dart create mode 100644 lib/pages/roles_and_permission/view/role_card.dart create mode 100644 lib/pages/roles_and_permission/view/roles_and_permission_page.dart create mode 100644 lib/pages/roles_and_permission/view/roles_page.dart diff --git a/assets/icons/active_user.svg b/assets/icons/active_user.svg new file mode 100644 index 00000000..5e0806e0 --- /dev/null +++ b/assets/icons/active_user.svg @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + + diff --git a/assets/icons/box_checked.png b/assets/icons/box_checked.png new file mode 100644 index 0000000000000000000000000000000000000000..d93b9d76d9df6c22695ef154f7b27f151dc63a79 GIT binary patch literal 754 zcmV#?`b=x}-yKsn#)Vx;QBaZaH++eUHK6j0|-3R)(Zx zVRaOSP9)Yi6PfZm?+51;vwnwUA1{kLszV?46N#Mg@dwDToF(Sb$}CBbACq`IS;Fa) zz~Mszx>#7^n0n&J>Q83N3p~j@QF&2Fag(Hr4@+8w+u7Iz1}AzfIb-srM+h-Y3qE(N zDt$EXMf#No`2s^H1OpPWNEvtIrWza8YK}~GgPtRgl7^_$$OT)PdQMU=bU+uLe(goD z7f6hn>K1OTW|-G^e(}}=-8d7WSv&AXSE-wPVdpsx&Ga9YczXXYLaJ?rRxwGRO_y+P zK)_IKE4enYBkez8KS0#Zs5fO_#r0AmzsWIY_~BZ7{*>Iabp02{SkjN|Dsv}J#7v~8 zi&m>s+EPri#C2Op?P#P{K5(MBbnSQhSF)*?s?By%w@pjtNR1ApEyXFri~0ApRm@P! zK76)fNrM6R(w1Uk*6`u;_iC45Ky4tColDz_sno@j-PYiZa4T&qcH}qI;PrA^iEH=V zNp~eC*45x7cZxkp5GDeMsxbN};$bw$fpRqDH9$J=5} zF4D<{+9n4l>})L{(!BDgE2ENTj!sbSU0Bi^_DHz6T$NIwhx(K`*iV=()K>+!1AYj4 kSrLMNK@o58iCJanAKq9oum;g}8UO$Q07*qoM6N<$g1q%wMF0Q* literal 0 HcmV?d00001 diff --git a/assets/icons/compleate_process_icon.svg b/assets/icons/compleate_process_icon.svg new file mode 100644 index 00000000..a4159de2 --- /dev/null +++ b/assets/icons/compleate_process_icon.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/assets/icons/current_process_icon.svg b/assets/icons/current_process_icon.svg new file mode 100644 index 00000000..967928e3 --- /dev/null +++ b/assets/icons/current_process_icon.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/icons/deactive_user.svg b/assets/icons/deactive_user.svg new file mode 100644 index 00000000..7011f5fb --- /dev/null +++ b/assets/icons/deactive_user.svg @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + + diff --git a/assets/icons/empty_box.png b/assets/icons/empty_box.png new file mode 100644 index 0000000000000000000000000000000000000000..71e798759d2913e747487d2b5ffe9cbe32b5831f GIT binary patch literal 425 zcmeAS@N?(olHy`uVBq!ia0vp^av;pX1|+Qw)-3{3oCO|{#S9E$svykh8Km+7D9BhG zZ8s34L5V* z+7c~3fABHB^!;!DB8~9lg?8_Yx$F`pq*5c}DlRTyedxRN_uqeqx4jMNsJ&m`KBe4e zS>~m@?aFeOV?{OS3ZJUzxNeel6?|IhSIndsuBl`uQ?PK# zPwm84yY_wX@1ABbA#eNb|3c6BR&^O3w7*x(yh*?BujG-RKY$_2 N;OXk;vd$@?2>^L{tuFuo literal 0 HcmV?d00001 diff --git a/assets/icons/invited_icon.svg b/assets/icons/invited_icon.svg new file mode 100644 index 00000000..5563de14 --- /dev/null +++ b/assets/icons/invited_icon.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/assets/icons/rectangle_check_box.png b/assets/icons/rectangle_check_box.png new file mode 100644 index 0000000000000000000000000000000000000000..3404c79cabb9ea8574f45e1698a32f58dc98cafc GIT binary patch literal 523 zcmV+m0`&cfP)%D|6&E zzQc@&oE1{6ku-PM&?@{)xeCl}4BK+X%q8mQD5dcp6Lh=(m~ zubS%Fuun6V8UnpXhLQ$pQf$VN?wyj<3mwqH(^p>jM}g#uss6(DX@>as$!9kPbko`h z?Yaf;wo2VpYCp#~`h+WX#je;DyJA=T55$x!y7Bw)yO?5(NGw{=O+0L(o480{3Xjej zsErZ}rs|~LcTUep#!Wl+2+G_Ko|z|NEvxEVa`LwRrHgb@u5y*PPFo9zG;h9nvZ^#o zG(g>($?J_h66OjWDFr>$Ri?jJm^SKG!JWVlLN7%j_yg*=#yzu}^BwrGqlvs0I6VLW N002ovPDHLkV1k%v=!XCR literal 0 HcmV?d00001 diff --git a/assets/icons/search_icon_user.svg b/assets/icons/search_icon_user.svg new file mode 100644 index 00000000..61eca62d --- /dev/null +++ b/assets/icons/search_icon_user.svg @@ -0,0 +1,4 @@ + + + + diff --git a/assets/icons/uncompleate_process_icon.svg b/assets/icons/uncompleate_process_icon.svg new file mode 100644 index 00000000..4ede6757 --- /dev/null +++ b/assets/icons/uncompleate_process_icon.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/icons/wrong_process_icon.svg b/assets/icons/wrong_process_icon.svg new file mode 100644 index 00000000..de5b475c --- /dev/null +++ b/assets/icons/wrong_process_icon.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/lib/pages/home/bloc/home_bloc.dart b/lib/pages/home/bloc/home_bloc.dart index c837e40a..04c35295 100644 --- a/lib/pages/home/bloc/home_bloc.dart +++ b/lib/pages/home/bloc/home_bloc.dart @@ -42,7 +42,8 @@ class HomeBloc extends Bloc { Future _fetchUserInfo(FetchUserInfo event, Emitter emit) async { try { - var uuid = await const FlutterSecureStorage().read(key: UserModel.userUuidKey); + var uuid = + await const FlutterSecureStorage().read(key: UserModel.userUuidKey); user = await HomeApi().fetchUserInfo(uuid); emit(HomeInitial()); } catch (e) { @@ -93,8 +94,10 @@ class HomeBloc extends Bloc { HomeItemModel( title: 'Move in', icon: Assets.moveinIcon, - active: false, - onPress: (context) {}, + active: true, + onPress: (context) { + context.go(RoutesConst.rolesAndPermissions); + }, color: ColorsManager.primaryColor, ), HomeItemModel( diff --git a/lib/pages/roles_and_permission/bloc/roles_permission_bloc.dart b/lib/pages/roles_and_permission/bloc/roles_permission_bloc.dart new file mode 100644 index 00000000..4f4988b3 --- /dev/null +++ b/lib/pages/roles_and_permission/bloc/roles_permission_bloc.dart @@ -0,0 +1,42 @@ +import 'dart:async'; +import 'package:bloc/bloc.dart'; +import 'package:syncrow_web/pages/roles_and_permission/bloc/roles_permission_event.dart'; +import 'package:syncrow_web/pages/roles_and_permission/bloc/roles_permission_state.dart'; +import 'package:syncrow_web/pages/roles_and_permission/model/role_model.dart'; + +class RolesPermissionBloc + extends Bloc { + RolesPermissionBloc() : super(RolesInitial()) { + on(_getRoles); + on(changeTapSelected); + } + List roleModel = []; + + FutureOr _getRoles( + GetRoles event, Emitter emit) async { + emit(UsersLoadingState()); + try { + roleModel = [ + RoleModel(roleId: '1', roleImage: '', roleName: 'Admin'), + RoleModel(roleId: '2', roleImage: '', roleName: 'Security'), + RoleModel(roleId: '2', roleImage: '', roleName: 'Reception'), + ]; + emit(UsersLoadedState()); + } catch (e) { + emit(ErrorState(e.toString())); + } + } + + bool tapSelect = true; + + changeTapSelected( + ChangeTapSelected event, Emitter emit) { + try { + emit(RolesLoadingState()); + tapSelect = event.selected; + emit(ChangeTapStatus(select: !tapSelect)); + } catch (e) { + emit(ErrorState(e.toString())); + } + } +} diff --git a/lib/pages/roles_and_permission/bloc/roles_permission_event.dart b/lib/pages/roles_and_permission/bloc/roles_permission_event.dart new file mode 100644 index 00000000..d5dce346 --- /dev/null +++ b/lib/pages/roles_and_permission/bloc/roles_permission_event.dart @@ -0,0 +1,41 @@ +import 'package:equatable/equatable.dart'; + +sealed class RolesPermissionEvent extends Equatable { + const RolesPermissionEvent(); +} + +class GetRoles extends RolesPermissionEvent { + const GetRoles(); + @override + List get props => []; +} + +class GetBatchStatus extends RolesPermissionEvent { + final List uuids; + const GetBatchStatus(this.uuids); + @override + List get props => [uuids]; +} + +class GetDeviceRecords extends RolesPermissionEvent { + final String uuid; + + const GetDeviceRecords(this.uuid); + @override + List get props => [uuid]; +} + +class GetDeviceAutomationRecords extends RolesPermissionEvent { + final String uuid; + const GetDeviceAutomationRecords(this.uuid); + @override + List get props => [uuid]; +} + +class ChangeTapSelected extends RolesPermissionEvent { + final bool selected; + const ChangeTapSelected(this.selected); + + @override + List get props => [selected]; +} diff --git a/lib/pages/roles_and_permission/bloc/roles_permission_state.dart b/lib/pages/roles_and_permission/bloc/roles_permission_state.dart new file mode 100644 index 00000000..55c2a8cb --- /dev/null +++ b/lib/pages/roles_and_permission/bloc/roles_permission_state.dart @@ -0,0 +1,77 @@ +import 'package:equatable/equatable.dart'; + +sealed class RolesPermissionState extends Equatable { + const RolesPermissionState(); +} + +final class RolesInitial extends RolesPermissionState { + @override + List get props => []; +} + +final class RolesLoadingState extends RolesPermissionState { + @override + List get props => []; +} +final class UsersLoadingState extends RolesPermissionState { + @override + List get props => []; +} + +final class RolesLoadedState extends RolesPermissionState { + @override + List get props => []; +} +final class UsersLoadedState extends RolesPermissionState { + @override + List get props => []; +} + +final class ErrorState extends RolesPermissionState { + final String message; + + const ErrorState(this.message); + + @override + List get props => [message]; +} + +/// report state +final class SosReportLoadingState extends RolesPermissionState { + @override + List get props => []; +} + +final class RolesErrorState extends RolesPermissionState { + final String message; + + const RolesErrorState(this.message); + + @override + List get props => [message]; +} + +/// automation reports + +final class SosAutomationReportLoadingState extends RolesPermissionState { + @override + List get props => []; +} + +final class SosAutomationReportErrorState extends RolesPermissionState { + final String message; + + const SosAutomationReportErrorState(this.message); + + @override + List get props => [message]; +} + +final class ChangeTapStatus extends RolesPermissionState { + bool select = true; + + ChangeTapStatus({required this.select}); + + @override + List get props => [select]; +} diff --git a/lib/pages/roles_and_permission/model/role_model.dart b/lib/pages/roles_and_permission/model/role_model.dart new file mode 100644 index 00000000..3d139904 --- /dev/null +++ b/lib/pages/roles_and_permission/model/role_model.dart @@ -0,0 +1,6 @@ +class RoleModel { + String? roleId; + String? roleName; + String? roleImage; + RoleModel({this.roleId, this.roleName, this.roleImage}); +} diff --git a/lib/pages/roles_and_permission/model/roles_user_model.dart b/lib/pages/roles_and_permission/model/roles_user_model.dart new file mode 100644 index 00000000..244c58de --- /dev/null +++ b/lib/pages/roles_and_permission/model/roles_user_model.dart @@ -0,0 +1,22 @@ +class RolesUserModel { + String? id; + String? userName; + String? userEmail; + String? userRole; + String? creationDate; + String? creationTime; + String? createdBy; + String? status; + String? action; + RolesUserModel( + {this.id, + this.userName, + this.userEmail, + this.userRole, + this.creationDate, + this.creationTime, + this.status, + this.action, + this.createdBy, + }); +} diff --git a/lib/pages/roles_and_permission/users_page/bloc/users_bloc.dart b/lib/pages/roles_and_permission/users_page/bloc/users_bloc.dart new file mode 100644 index 00000000..5de74e4b --- /dev/null +++ b/lib/pages/roles_and_permission/users_page/bloc/users_bloc.dart @@ -0,0 +1,136 @@ +import 'package:bloc/bloc.dart'; +import 'package:flutter/material.dart'; +import 'package:syncrow_web/pages/roles_and_permission/model/roles_user_model.dart'; +import 'package:syncrow_web/pages/roles_and_permission/users_page/bloc/users_event.dart'; +import 'package:syncrow_web/pages/roles_and_permission/users_page/bloc/users_status.dart'; + +class UsersBloc extends Bloc { + UsersBloc() : super(UsersInitial()) { + on(_getUsers); + on(_changeUserStatus); + on(isCompleteBasicsFun); + } + + List users = []; + + Future _getUsers(GetUsers event, Emitter emit) async { + emit(UsersLoadingState()); + try { + users = [ + RolesUserModel( + id: '1', + userName: 'user 1', + userEmail: 'test1@test.com', + action: '', + createdBy: 'Admin', + creationDate: '25/10/2024', + creationTime: '10:30 AM', + status: 'Invited', + ), + RolesUserModel( + id: '2', + userName: 'user 2', + userEmail: 'test2@test.com', + action: '', + createdBy: 'Admin', + creationDate: '25/10/2024', + creationTime: '10:30 AM', + status: 'Active', + ), + RolesUserModel( + id: '3', + userName: 'user 3', + userEmail: 'test3@test.com', + action: '', + createdBy: 'Admin', + creationDate: '25/10/2024', + creationTime: '10:30 AM', + status: 'Disabled', + ), + ]; + emit(UsersLoadedState(users: users)); + } catch (e) { + emit(ErrorState(e.toString())); + } + } + + void _changeUserStatus(ChangeUserStatus event, Emitter emit) { + try { + // Update the user's status + users = users.map((user) { + if (user.id == event.userId) { + return RolesUserModel( + id: user.id, + userName: user.userName, + userEmail: user.userEmail, + createdBy: user.createdBy, + creationDate: user.creationDate, + creationTime: user.creationTime, + status: event.newStatus, + action: user.action, + ); + } + return user; + }).toList(); + + emit(UsersLoadedState(users: users)); + } catch (e) { + emit(ErrorState(e.toString())); + } + } + + final formKey = GlobalKey(); + final TextEditingController firstNameController = TextEditingController(); + final TextEditingController lastNameController = TextEditingController(); + final TextEditingController emailController = TextEditingController(); + final TextEditingController phoneController = TextEditingController(); + final TextEditingController jobTitleController = TextEditingController(); + + bool isCompleteBasics = false; + bool isCompleteRolePermissions = false; + bool isCompleteSpaces = false; + + int numberBasics = 0; + int numberSpaces = 0; + int numberRole = 0; + + isCompleteBasicsFun(CheckStepStatus event, Emitter emit) { + emit(UsersLoadingState()); + isCompleteBasics = firstNameController.text.isNotEmpty && + lastNameController.text.isNotEmpty && + emailController.text.isNotEmpty && + phoneController.text.isNotEmpty && + jobTitleController.text.isNotEmpty; + emit(ChangeStatusSteps()); + } + + // void checkStatus(CheckStepStatus event, Emitter emit) { + // try { + // // Check if basic fields are completed + // isCompleteBasics = firstNameController.text.isNotEmpty && + // lastNameController.text.isNotEmpty && + // emailController.text.isNotEmpty && + // phoneController.text.isNotEmpty && + // jobTitleController.text.isNotEmpty; + // // Emit the updated state + // if (isCompleteBasics && isCompleteRolePermissions && isCompleteSpaces) { + // } else { + // // emit(IncompleteState( + // // isCompleteBasics, isCompleteRolePermissions, isCompleteSpaces)); + // } + // } catch (e) { + // emit(ErrorState(e.toString())); + // } + // } + +// Example placeholder methods for role permissions and spaces + bool checkRolePermissions() { + // Add logic to check if role permissions are completed + return true; // Replace with actual logic + } + + bool checkSpaces() { + // Add logic to check if spaces are completed + return true; // Replace with actual logic + } +} diff --git a/lib/pages/roles_and_permission/users_page/bloc/users_event.dart b/lib/pages/roles_and_permission/users_page/bloc/users_event.dart new file mode 100644 index 00000000..3b679a7e --- /dev/null +++ b/lib/pages/roles_and_permission/users_page/bloc/users_event.dart @@ -0,0 +1,35 @@ +import 'package:equatable/equatable.dart'; + +sealed class UsersEvent extends Equatable { + const UsersEvent(); +} + +class GetUsers extends UsersEvent { + const GetUsers(); + @override + List get props => []; +} + +class GetBatchStatus extends UsersEvent { + final List uuids; + const GetBatchStatus(this.uuids); + @override + List get props => [uuids]; +} + +class ChangeUserStatus extends UsersEvent { + final String userId; + final String newStatus; + + const ChangeUserStatus({required this.userId, required this.newStatus}); + + @override + List get props => [userId, newStatus]; +} + +class CheckStepStatus extends UsersEvent { + final int? steps; + const CheckStepStatus({this.steps}); + @override + List get props => [steps]; +} diff --git a/lib/pages/roles_and_permission/users_page/bloc/users_status.dart b/lib/pages/roles_and_permission/users_page/bloc/users_status.dart new file mode 100644 index 00000000..b9937f77 --- /dev/null +++ b/lib/pages/roles_and_permission/users_page/bloc/users_status.dart @@ -0,0 +1,57 @@ +import 'package:equatable/equatable.dart'; +import 'package:syncrow_web/pages/roles_and_permission/model/roles_user_model.dart'; + +sealed class UsersState extends Equatable { + const UsersState(); +} + +final class UsersInitial extends UsersState { + @override + List get props => []; +} + +final class ChangeStatusSteps extends UsersState { + @override + List get props => []; +} + +final class UsersLoadingState extends UsersState { + @override + List get props => []; +} + +final class UsersLoadedState extends UsersState { + List users = []; + UsersLoadedState({required this.users}); + @override + List get props => [users]; +} + +final class ErrorState extends UsersState { + final String message; + + const ErrorState(this.message); + + @override + List get props => [message]; +} + +final class RolesErrorState extends UsersState { + final String message; + + const RolesErrorState(this.message); + + @override + List get props => [message]; +} + +/// automation reports + +final class ChangeTapStatus extends UsersState { + bool select = true; + + ChangeTapStatus({required this.select}); + + @override + List get props => [select]; +} diff --git a/lib/pages/roles_and_permission/users_page/view/add_user_dialog.dart b/lib/pages/roles_and_permission/users_page/view/add_user_dialog.dart new file mode 100644 index 00000000..b2ee8cf5 --- /dev/null +++ b/lib/pages/roles_and_permission/users_page/view/add_user_dialog.dart @@ -0,0 +1,214 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:flutter_svg/flutter_svg.dart'; +import 'package:syncrow_web/pages/roles_and_permission/users_page/bloc/users_bloc.dart'; +import 'package:syncrow_web/pages/roles_and_permission/users_page/bloc/users_status.dart'; +import 'package:syncrow_web/pages/roles_and_permission/users_page/view/basics_view.dart'; +import 'package:syncrow_web/pages/roles_and_permission/users_page/view/roles_and_permission.dart'; +import 'package:syncrow_web/pages/roles_and_permission/users_page/view/spaces_access_view.dart'; +import 'package:syncrow_web/utils/color_manager.dart'; +import 'package:syncrow_web/utils/constants/assets.dart'; + +class AddNewUserDialog extends StatefulWidget { + const AddNewUserDialog({super.key}); + + @override + _AddNewUserDialogState createState() => _AddNewUserDialogState(); +} + +class _AddNewUserDialogState extends State { + int currentStep = 1; + + @override + Widget build(BuildContext context) { + return BlocProvider( + create: (BuildContext context) => UsersBloc(), + child: BlocConsumer( + listener: (context, state) {}, + builder: (context, state) { + final _blocRole = BlocProvider.of(context); + + return Dialog( + child: Container( + decoration: const BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.all(Radius.circular(20))), + width: 900, + child: Column( + children: [ + // Title + const Padding( + padding: EdgeInsets.all(8.0), + child: SizedBox( + child: Text( + "Add New User", + style: TextStyle( + fontSize: 18, fontWeight: FontWeight.bold), + ), + ), + ), + const Divider(), + Expanded( + child: Row( + children: [ + // Sidebar for Steps + Expanded( + child: Container( + padding: const EdgeInsets.all(20), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + _buildStepIndicator(1, "Basics", _blocRole), + _buildStepIndicator(2, "Spaces", _blocRole), + _buildStepIndicator( + 3, "Role & Permissions", _blocRole), + ], + ), + ), + ), + Container( + width: 1, + color: ColorsManager.grayBorder, + ), + // Main content (Form) + Expanded( + flex: 2, + child: Padding( + padding: const EdgeInsets.all(20.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const SizedBox(height: 10), + Expanded( + child: _getFormContent(), + ), + const SizedBox(height: 20), + Row( + mainAxisAlignment: + MainAxisAlignment.spaceBetween, + children: [ + TextButton( + onPressed: () { + Navigator.of(context).pop(); + }, + child: const Text("Cancel"), + ), + ElevatedButton( + onPressed: () { + if (_blocRole.formKey.currentState + ?.validate() ?? + false) { + // Proceed to next step or finish + setState(() { + if (currentStep < 3) { + currentStep++; + } else { + Navigator.of(context).pop(); + } + }); + } + }, + child: Text(currentStep < 3 + ? "Next" + : "Finish"), + ), + ], + ), + ], + ), + ), + ), + ], + ), + ), + ], + ), + )); + })); + } + + // Method to get the form content based on the current step + Widget _getFormContent() { + switch (currentStep) { + case 1: + return const BasicsView(); + case 2: + return const SpacesAccessView(); + case 3: + return const RolesAndPermission(); + default: + return Container(); + } + } + + // Helper method to build step indicators + Widget _buildStepIndicator(int step, String label, UsersBloc bloc) { + return GestureDetector( + onTap: () { + // if (bloc.formKey.currentState?.validate() ?? false) { + setState(() { + currentStep = step; + if (currentStep == 1) { + bloc.numberBasics = 1; + } else if (currentStep == 2) { + bloc.numberSpaces = 2; + } else if (currentStep == 3) { + bloc.numberRole = 3; + } + }); + // } + }, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.start, + children: [ + Padding( + padding: const EdgeInsets.all(5.0), + child: Row( + children: [ + SvgPicture.asset( + currentStep == step + ? Assets.currentProcessIcon + : bloc.isCompleteBasics == false + && (bloc.numberBasics != 0 || + bloc.numberRole != 0 || + bloc.numberSpaces != 0) + ? Assets.wrongProcessIcon + : Assets.uncomplete_ProcessIcon, + + width: 25, + height: 25, + ), + const SizedBox(width: 10), + Text( + label, + style: TextStyle( + fontSize: 16, + color: currentStep == step + ? ColorsManager.blackColor + : ColorsManager.greyColor, + fontWeight: currentStep == step + ? FontWeight.bold + : FontWeight.normal, + ), + ), + ], + ), + ), + if (step != 3) + Padding( + padding: const EdgeInsets.all(5.0), + child: Padding( + padding: const EdgeInsets.only(left: 12), + child: Container( + height: 60, + width: 1, + color: Colors.grey, + ), + ), + ) + ], + ), + ); + } +} diff --git a/lib/pages/roles_and_permission/users_page/view/basics_view.dart b/lib/pages/roles_and_permission/users_page/view/basics_view.dart new file mode 100644 index 00000000..d64d837d --- /dev/null +++ b/lib/pages/roles_and_permission/users_page/view/basics_view.dart @@ -0,0 +1,279 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; // Import Bloc package +import 'package:syncrow_web/pages/roles_and_permission/users_page/bloc/users_bloc.dart'; +import 'package:syncrow_web/pages/roles_and_permission/users_page/bloc/users_status.dart'; +import 'package:syncrow_web/utils/color_manager.dart'; +import 'package:syncrow_web/utils/extension/build_context_x.dart'; +import 'package:syncrow_web/utils/style.dart'; + +class BasicsView extends StatelessWidget { + const BasicsView({super.key}); + @override + Widget build(BuildContext context) { + return BlocBuilder(builder: (context, state) { + final _blocRole = BlocProvider.of(context); + return Form( + key: _blocRole.formKey, + child: ListView( + shrinkWrap: true, + children: [ + Text( + 'Set up the basics', + style: context.textTheme.bodyLarge?.copyWith( + fontWeight: FontWeight.w700, + fontSize: 20, + color: Colors.black), + ), + const SizedBox( + height: 80, + ), + Text( + 'To get started, fill out some basic information about who you’re adding as a user.', + style: context.textTheme.bodyMedium?.copyWith( + fontWeight: FontWeight.w400, + fontSize: 14, + ), + ), + const SizedBox( + height: 25, + ), + Row( + children: [ + // First Name + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SizedBox( + child: Row( + children: [ + Text( + "*", + style: TextStyle(color: ColorsManager.red), + ), + Text( + 'First Name', + style: context.textTheme.bodyMedium?.copyWith( + fontWeight: FontWeight.w400, + fontSize: 13, + ), + ), + ], + )), + Padding( + padding: const EdgeInsets.all(8.0), + child: TextFormField( + style: TextStyle(color: Colors.black), + controller: _blocRole.firstNameController, + decoration: inputTextFormDeco( + hintText: "Enter first name", + ).copyWith( + hintStyle: context.textTheme.bodyMedium?.copyWith( + fontWeight: FontWeight.w400, + fontSize: 12, + color: ColorsManager.textGray), + ), + validator: (value) { + if (value == null || value.isEmpty) { + return 'Enter first name'; + } + return null; + }, + ), + ), + ], + ), + ), + + SizedBox(width: 10), + + // Last Name + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SizedBox( + child: Row( + children: [ + const Text( + "*", + style: TextStyle(color: ColorsManager.red), + ), + Text('Last Name', + style: context.textTheme.bodyMedium?.copyWith( + fontWeight: FontWeight.w400, + fontSize: 13, + )), + ], + )), + Padding( + padding: const EdgeInsets.all(8.0), + child: TextFormField( + controller: _blocRole.lastNameController, + style: TextStyle(color: Colors.black), + decoration: + inputTextFormDeco(hintText: "Enter last name") + .copyWith( + hintStyle: context + .textTheme.bodyMedium + ?.copyWith( + fontWeight: FontWeight.w400, + fontSize: 12, + color: ColorsManager.textGray)), + validator: (value) { + if (value == null || value.isEmpty) { + return 'Enter last name'; + } + return null; + }, + ), + ), + ], + ), + ), + ], + ), + SizedBox(height: 10), + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SizedBox( + child: Row( + children: [ + Text( + "*", + style: TextStyle(color: ColorsManager.red), + ), + Text( + 'Email Address', + style: context.textTheme.bodyMedium?.copyWith( + fontWeight: FontWeight.w400, + fontSize: 13, + ), + ), + ], + )), + Padding( + padding: const EdgeInsets.all(8.0), + child: TextFormField( + controller: _blocRole.emailController, + style: TextStyle(color: Colors.black), + decoration: inputTextFormDeco(hintText: "name@example.com") + .copyWith( + hintStyle: context.textTheme.bodyMedium?.copyWith( + fontWeight: FontWeight.w400, + fontSize: 12, + color: ColorsManager.textGray), + ), + validator: (value) { + if (value == null || value.isEmpty) { + return 'Enter last name'; + } + return null; + }, + ), + ), + ], + ), + SizedBox(height: 10), + Row( + children: [ + // Phone Number + + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SizedBox( + child: Row( + children: [ + Text( + "*", + style: TextStyle(color: ColorsManager.red), + ), + Text( + 'Mobile Number', + style: context.textTheme.bodyMedium?.copyWith( + fontWeight: FontWeight.w400, + fontSize: 13, + ), + ), + ], + )), + Padding( + padding: const EdgeInsets.all(8.0), + child: TextFormField( + style: const TextStyle(color: Colors.black), + controller: _blocRole.phoneController, + decoration: inputTextFormDeco( + hintText: "05x xxx xxxx", + ).copyWith( + hintStyle: context.textTheme.bodyMedium?.copyWith( + fontWeight: FontWeight.w400, + fontSize: 12, + color: ColorsManager.textGray), + ), + validator: (value) { + if (value == null || value.isEmpty) { + return 'Please enter a phone number'; + } + return null; + }, + keyboardType: TextInputType.phone, + ), + ), + ], + ), + ), + + SizedBox(width: 10), + + // Job Title + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SizedBox( + child: Row( + children: [ + Text( + "*", + style: TextStyle(color: ColorsManager.red), + ), + Text( + 'Job Title', + style: context.textTheme.bodyMedium?.copyWith( + fontWeight: FontWeight.w400, + fontSize: 13, + ), + ), + ], + )), + Padding( + padding: const EdgeInsets.all(8.0), + child: TextFormField( + controller: _blocRole.jobTitleController, + style: TextStyle(color: Colors.black), + decoration: inputTextFormDeco( + hintText: "Job Title (Optional)") + .copyWith( + hintStyle: context.textTheme.bodyMedium?.copyWith( + fontWeight: FontWeight.w400, + fontSize: 12, + color: ColorsManager.textGray), + ), + keyboardType: TextInputType.phone, + ), + ), + ], + ), + ), + ], + ), + SizedBox(height: 20), + ], + ), + ); + }); + } +} diff --git a/lib/pages/roles_and_permission/users_page/view/roles_and_permission.dart b/lib/pages/roles_and_permission/users_page/view/roles_and_permission.dart new file mode 100644 index 00000000..c9b58581 --- /dev/null +++ b/lib/pages/roles_and_permission/users_page/view/roles_and_permission.dart @@ -0,0 +1,498 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; // Import Bloc package +import 'package:flutter_svg/flutter_svg.dart'; +import 'package:syncrow_web/pages/roles_and_permission/users_page/bloc/users_bloc.dart'; +import 'package:syncrow_web/pages/roles_and_permission/users_page/bloc/users_status.dart'; +import 'package:syncrow_web/utils/color_manager.dart'; +import 'package:syncrow_web/utils/constants/assets.dart'; +import 'package:syncrow_web/utils/extension/build_context_x.dart'; +import 'package:syncrow_web/utils/style.dart'; + +class RolesAndPermission extends StatelessWidget { + const RolesAndPermission({super.key}); + @override + Widget build(BuildContext context) { + final size = MediaQuery.of(context).size; + + return BlocBuilder(builder: (context, state) { + final _blocRole = BlocProvider.of(context); + return Container( + color: Colors.white, + child: Form( + key: _blocRole.formKey, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.start, + children: [ + Text( + 'Role & Permissions', + style: context.textTheme.bodyLarge?.copyWith( + fontWeight: FontWeight.w700, + fontSize: 20, + color: Colors.black), + ), + const SizedBox( + height: 15, + ), + const SizedBox(width: 300, height: 110, child: DropdownExample()), + const SizedBox(height: 10), + Expanded( + child: SizedBox( + child: Column( + children: [ + Expanded( + flex: 2, + child: Container( + decoration: const BoxDecoration( + color: ColorsManager.CircleRolesBackground, + borderRadius: BorderRadius.only( + topRight: Radius.circular(20), + topLeft: Radius.circular(20)), + ), + child: Padding( + padding: const EdgeInsets.all(8.0), + child: Row( + children: [ + Expanded( + child: Container( + decoration: BoxDecoration( + borderRadius: const BorderRadius.all( + Radius.circular(20)), + border: Border.all( + color: ColorsManager.grayBorder)), + child: TextFormField( + style: + const TextStyle(color: Colors.black), + controller: _blocRole.firstNameController, + decoration: textBoxDecoration(radios: 20)! + .copyWith( + fillColor: Colors.white, + suffixIcon: Padding( + padding: + const EdgeInsets.only(right: 16), + child: SvgPicture.asset( + Assets.textFieldSearch, + width: 24, + height: 24, + ), + ), + hintStyle: context.textTheme.bodyMedium + ?.copyWith( + fontWeight: FontWeight.w400, + fontSize: 12, + color: ColorsManager.textGray), + ), + ), + ), + ), + ], + ), + ), + ), + ), + Expanded( + flex: 7, + child: Container( + color: ColorsManager.CircleRolesBackground, + padding: const EdgeInsets.all(8.0), + child: Container( + color: ColorsManager.whiteColors, + child: const DeviceManagement()))) + ], + ), + ), + ), + ], + ), + ), + ); + }); + } +} + +class DropdownExample extends StatefulWidget { + const DropdownExample({super.key}); + + @override + _DropdownExampleState createState() => _DropdownExampleState(); +} + +class _DropdownExampleState extends State { + String? selectedRole; + List roles = ['Admin', 'User', 'Guest', 'Moderator']; + + @override + Widget build(BuildContext context) { + return Padding( + padding: const EdgeInsets.all(10.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const Text( + "Role", + style: TextStyle( + fontWeight: FontWeight.bold, + fontSize: 16, + color: Colors.black, + ), + ), + const SizedBox(height: 8), + SizedBox( + child: DropdownButtonFormField( + alignment: Alignment.center, + focusColor: ColorsManager.whiteColors, + autofocus: true, + value: selectedRole, + items: roles.map((role) { + return DropdownMenuItem( + value: role, + child: Text(role), + ); + }).toList(), + onChanged: (value) { + setState(() { + selectedRole = value; + }); + }, + padding: EdgeInsets.zero, + icon: const SizedBox.shrink(), + borderRadius: const BorderRadius.all(Radius.circular(10)), + hint: const Padding( + padding: EdgeInsets.only(left: 20), + child: Text("Please Select"), + ), + decoration: inputTextFormDeco().copyWith( + contentPadding: EdgeInsets.zero, + suffixIcon: Container( + padding: EdgeInsets.zero, + width: 70, + height: 50, + decoration: BoxDecoration( + color: ColorsManager.graysColor, + borderRadius: const BorderRadius.only( + bottomRight: Radius.circular(10), + topRight: Radius.circular(10), + ), + border: Border.all( + color: ColorsManager.grayBorder, + width: 1.0, + ), + ), + child: const Center( + child: Icon(Icons.keyboard_arrow_down), + ), + ), + ), + ), + ), + ], + ), + ); + } +} + +class DeviceManagement extends StatefulWidget { + const DeviceManagement({Key? key}) : super(key: key); + + @override + _DeviceManagementState createState() => _DeviceManagementState(); +} + +class _DeviceManagementState extends State { + final List options = [ + MainRoleOption( + id: '1', + title: "Device Management", + subOptions: [ + SubRoleOption( + id: '11', + title: "Manage devices in private spaces", + children: [ + ChildRoleOption(id: '111', title: "Control"), + ChildRoleOption(id: '112', title: "Assign device"), + ChildRoleOption(id: '113', title: "View"), + ], + ), + SubRoleOption( + id: '12', + title: "Manage", + children: [ + ChildRoleOption(id: '121', title: "cc"), + ChildRoleOption(id: '122', title: "Assign"), + ChildRoleOption(id: '123', title: "s"), + ], + ), + ], + ), + MainRoleOption( + id: '2', + title: "Device Management", + subOptions: [ + SubRoleOption( + id: '22', + title: "Manage devices in private spaces", + children: [ + ChildRoleOption(id: '211', title: "Control"), + ChildRoleOption(id: '212', title: "Assign device"), + ChildRoleOption(id: '213', title: "View"), + ], + ), + ], + ), + ]; + + void toggleOptionById(String id) { + setState(() { + for (var mainOption in options) { + if (mainOption.id == id) { + final isChecked = + checkifOneOfthemChecked(mainOption) == CheckState.all; + mainOption.isChecked = !isChecked; + + for (var subOption in mainOption.subOptions) { + subOption.isChecked = !isChecked; + for (var child in subOption.children) { + child.isChecked = !isChecked; + } + } + return; + } + + for (var subOption in mainOption.subOptions) { + if (subOption.id == id) { + subOption.isChecked = !subOption.isChecked; + for (var child in subOption.children) { + child.isChecked = subOption.isChecked; + } + mainOption.isChecked = + mainOption.subOptions.every((sub) => sub.isChecked); + return; + } + + for (var child in subOption.children) { + if (child.id == id) { + child.isChecked = !child.isChecked; + subOption.isChecked = + subOption.children.every((child) => child.isChecked); + mainOption.isChecked = + mainOption.subOptions.every((sub) => sub.isChecked); + return; + } + } + } + } + }); + } + + CheckState checkifOneOfthemChecked(MainRoleOption mainOption) { + bool allSelected = true; + bool someSelected = false; + + for (var subOption in mainOption.subOptions) { + if (subOption.isChecked) { + someSelected = true; + } else { + allSelected = false; + } + + for (var child in subOption.children) { + if (child.isChecked) { + someSelected = true; + } else { + allSelected = false; + } + } + } + + if (allSelected) { + return CheckState.all; + } else if (someSelected) { + return CheckState.some; + } else { + return CheckState.none; + } + } + + @override + Widget build(BuildContext context) { + return ListView.builder( + padding: const EdgeInsets.all(8), + itemCount: options.length, + itemBuilder: (context, index) { + final option = options[index]; + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + InkWell( + onTap: () => toggleOptionById(option.id), + child: Builder( + builder: (context) { + final checkState = checkifOneOfthemChecked(option); + + if (checkState == CheckState.all) { + return Image.asset( + Assets.CheckBoxChecked, + width: 20, + height: 20, + ); + } else if (checkState == CheckState.some) { + return Image.asset( + Assets.rectangleCheckBox, + width: 20, + height: 20, + ); + } else { + return Image.asset( + Assets.emptyBox, + width: 20, + height: 20, + ); + } + }, + ), + ), + const SizedBox(width: 8), + Text( + option.title, + style: context.textTheme.bodyMedium?.copyWith( + fontWeight: FontWeight.w700, + fontSize: 12, + color: ColorsManager.blackColor), + ), + ], + ), + const SizedBox( + height: 10, + ), + ...option.subOptions.map((subOption) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + InkWell( + onTap: () => toggleOptionById(subOption.id), + child: Builder( + builder: (context) { + final checkState = + checkifOneOfthemChecked(MainRoleOption( + id: subOption.id, + title: subOption.title, + subOptions: [subOption], + )); + + if (checkState == CheckState.all) { + return Image.asset( + Assets.CheckBoxChecked, + width: 20, + height: 20, + ); + } else if (checkState == CheckState.some) { + return Image.asset( + Assets.rectangleCheckBox, + width: 20, + height: 20, + ); + } else { + return Image.asset( + Assets.emptyBox, + width: 20, + height: 20, + ); + } + }, + ), + ), + const SizedBox(width: 8), + Text( + subOption.title, + style: context.textTheme.bodyMedium?.copyWith( + fontWeight: FontWeight.w700, + fontSize: 12, + color: ColorsManager.lightGreyColor), + ), + ], + ), + Padding( + padding: const EdgeInsets.only(left: 50.0), + child: GridView.builder( + shrinkWrap: true, + physics: const NeverScrollableScrollPhysics(), + gridDelegate: + const SliverGridDelegateWithFixedCrossAxisCount( + crossAxisCount: 2, // 2 items per row + mainAxisSpacing: 2.0, // Space between rows + crossAxisSpacing: 0.2, // Space between columns + childAspectRatio: 5, // Adjust aspect ratio as needed + ), + itemCount: subOption.children.length, + itemBuilder: (context, index) { + final child = subOption.children[index]; + return CheckboxListTile( + dense: true, + controlAffinity: ListTileControlAffinity.leading, + title: Text( + child.title, + style: context.textTheme.bodyMedium?.copyWith( + fontWeight: FontWeight.w400, + fontSize: 12, + color: ColorsManager.lightGreyColor), + ), + value: child.isChecked, + onChanged: (value) => toggleOptionById(child.id), + ); + }, + ), + ) + ], + ); + }).toList(), + ], + ); + }, + ); + } +} + +class MainRoleOption { + String id; + String title; + bool isChecked; + List subOptions; + MainRoleOption({ + required this.id, + required this.title, + this.isChecked = false, + this.subOptions = const [], + }); +} + +class SubRoleOption { + String id; + String title; + bool isChecked; + List children; + + SubRoleOption({ + required this.id, + required this.title, + this.isChecked = false, + this.children = const [], + }); +} + +class ChildRoleOption { + String id; + String title; + bool isChecked; + + ChildRoleOption({ + required this.id, + required this.title, + this.isChecked = false, + }); +} + +enum CheckState { none, some, all } diff --git a/lib/pages/roles_and_permission/users_page/view/spaces_access_view.dart b/lib/pages/roles_and_permission/users_page/view/spaces_access_view.dart new file mode 100644 index 00000000..9dc35a89 --- /dev/null +++ b/lib/pages/roles_and_permission/users_page/view/spaces_access_view.dart @@ -0,0 +1,338 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:flutter_svg/svg.dart'; +import 'package:syncrow_web/pages/roles_and_permission/users_page/bloc/users_bloc.dart'; +import 'package:syncrow_web/pages/roles_and_permission/users_page/bloc/users_status.dart'; +import 'package:syncrow_web/utils/color_manager.dart'; +import 'package:syncrow_web/utils/constants/assets.dart'; +import 'package:syncrow_web/utils/extension/build_context_x.dart'; +import 'package:syncrow_web/utils/style.dart'; + +class SpacesAccessView extends StatelessWidget { + const SpacesAccessView({super.key}); + @override + Widget build(BuildContext context) { + final size = MediaQuery.of(context).size; + + return BlocBuilder(builder: (context, state) { + final _blocRole = BlocProvider.of(context); + return Container( + color: Colors.white, + child: Form( + key: _blocRole.formKey, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.start, + children: [ + Text( + 'Spaces access', + style: context.textTheme.bodyLarge?.copyWith( + fontWeight: FontWeight.w700, + fontSize: 20, + color: Colors.black), + ), + const SizedBox( + height: 35, + ), + const SizedBox( + child: Text( + 'Select the spaces you would like to grant access to for the user you are adding'), + ), + const SizedBox( + height: 25, + ), + Expanded( + child: SizedBox( + child: Column( + children: [ + Expanded( + flex: 2, + child: Container( + decoration: const BoxDecoration( + color: ColorsManager.CircleRolesBackground, + borderRadius: BorderRadius.only( + topRight: Radius.circular(20), + topLeft: Radius.circular(20)), + ), + child: Padding( + padding: const EdgeInsets.all(8.0), + child: Row( + children: [ + Expanded( + child: Container( + decoration: BoxDecoration( + borderRadius: const BorderRadius.all( + Radius.circular(20)), + border: Border.all( + color: ColorsManager.grayBorder)), + child: TextFormField( + style: + const TextStyle(color: Colors.black), + controller: _blocRole.firstNameController, + decoration: textBoxDecoration(radios: 20)! + .copyWith( + fillColor: Colors.white, + suffixIcon: Padding( + padding: + const EdgeInsets.only(right: 16), + child: SvgPicture.asset( + Assets.textFieldSearch, + width: 24, + height: 24, + ), + ), + hintStyle: context.textTheme.bodyMedium + ?.copyWith( + fontWeight: FontWeight.w400, + fontSize: 12, + color: ColorsManager.textGray), + ), + ), + ), + ), + ], + ), + ), + ), + ), + Expanded( + flex: 7, + child: Container( + color: ColorsManager.CircleRolesBackground, + padding: const EdgeInsets.all(8.0), + child: Container( + color: ColorsManager.whiteColors, + child: TreeView()))) + ], + ), + ), + ), + ], + ), + ), + ); + }); + } +} + +class TreeNode { + String title; + bool isChecked; + bool isHighlighted; + bool isExpanded; // New property to manage expansion + List children; + + TreeNode({ + required this.title, + this.isChecked = false, + this.isHighlighted = false, + this.isExpanded = false, // Default to collapsed + this.children = const [], + }); +} + +class TreeView extends StatefulWidget { + @override + _TreeViewState createState() => _TreeViewState(); +} + +class _TreeViewState extends State { + List treeData = [ + TreeNode( + title: 'Downtown Dubai', + ), + TreeNode(title: 'Dubai Creek Harbour', isHighlighted: true), + TreeNode( + title: 'Dubai Hills Estate', + isHighlighted: true, + children: [ + TreeNode(title: 'North Side'), + TreeNode( + title: 'South Side', + isHighlighted: true, + children: [ + TreeNode(title: 'Hills Business Park'), + TreeNode(title: 'Park Point'), + TreeNode(title: 'Acacia'), + TreeNode( + title: 'Executive Residence', + children: [ + TreeNode(title: 'Residence I'), + TreeNode( + title: 'Residence II', + children: [ + TreeNode(title: 'Ground Floor', isHighlighted: true), + TreeNode(title: '1st Floor', isHighlighted: true), + TreeNode(title: 'Pool', isHighlighted: true), + TreeNode(title: 'Gym', isHighlighted: true), + ], + ), + ], + ), + ], + ), + TreeNode( + title: 'South Side', + isHighlighted: true, + children: [ + TreeNode(title: 'Hills Business Park'), + TreeNode(title: 'Park Point'), + TreeNode(title: 'Acacia'), + TreeNode( + title: 'Executive Residence', + children: [ + TreeNode(title: 'Residence I'), + TreeNode( + title: 'Residence II', + children: [ + TreeNode(title: 'Ground Floor', isHighlighted: true), + TreeNode(title: '1st Floor', isHighlighted: true), + TreeNode(title: 'Pool', isHighlighted: true), + TreeNode(title: 'Gym', isHighlighted: true), + ], + ), + ], + ), + ], + ), + ], + ), + ]; + + Widget _buildTree(List nodes, {int level = 0}) { + return Column( + children: nodes.map((node) => _buildNode(node, level: level)).toList(), + ); + } + + Widget _buildNode(TreeNode node, {int level = 0}) { + return Container( + color: node.isHighlighted ? Colors.blue.shade100 : Colors.transparent, + child: Padding( + padding: EdgeInsets.only(left: level * 8.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + GestureDetector( + onTap: () { + setState(() { + node.isExpanded = !node.isExpanded; // Toggle expansion + }); + }, + child: Icon( + node.children.isNotEmpty + ? (node.isExpanded + ? Icons.arrow_drop_down + : Icons.arrow_right) + : Icons.arrow_right, + color: node.children.isNotEmpty + ? Colors.black + : Colors.transparent, + ), + ), + GestureDetector( + onTap: () { + setState(() { + node.isChecked = !node.isChecked; + _updateChildrenCheckStatus(node, node.isChecked); + _updateParentCheckStatus(node); + }); + }, + child: Image.asset( + _getCheckBoxImage(node), + width: 20, + height: 20, + ), + ), + Expanded( + child: Text( + node.title, + style: TextStyle( + fontSize: 16, + color: Colors.black87, + ), + ), + ), + ], + ), + if (node.isExpanded && node.children.isNotEmpty) + Padding( + padding: const EdgeInsets.only(left: 24.0), + child: _buildTree(node.children, level: level + 1), + ), + ], + ), + ), + ); + } + + // Determine the appropriate image based on the check state + String _getCheckBoxImage(TreeNode node) { + if (node.children.isEmpty) { + return node.isChecked ? Assets.CheckBoxChecked : Assets.emptyBox; + } + if (_areAllChildrenChecked(node)) { + return Assets.CheckBoxChecked; + } else if (_areSomeChildrenChecked(node)) { + return Assets.rectangleCheckBox; + } else { + return Assets.emptyBox; + } + } + + // Helper to determine if all children are checked + bool _areAllChildrenChecked(TreeNode node) { + return node.children.isNotEmpty && + node.children.every((child) => + child.isChecked && + (child.children.isEmpty || _areAllChildrenChecked(child))); + } + + // Helper to determine if some children are checked + bool _areSomeChildrenChecked(TreeNode node) { + return node.children.isNotEmpty && + node.children.any((child) => + child.isChecked || + (child.children.isNotEmpty && _areSomeChildrenChecked(child))); + } + + // Update the checkbox state for all children + void _updateChildrenCheckStatus(TreeNode node, bool isChecked) { + for (var child in node.children) { + child.isChecked = isChecked; + _updateChildrenCheckStatus(child, isChecked); + } + } + + // Update the checkbox state for parent nodes + void _updateParentCheckStatus(TreeNode node) { + TreeNode? parent = _findParent(treeData, node); + if (parent != null) { + setState(() { + parent.isChecked = _areAllChildrenChecked(parent); + _updateParentCheckStatus(parent); // Recursively update ancestors + }); + } + } + + // Helper to find a node's parent + TreeNode? _findParent(List nodes, TreeNode target) { + for (var node in nodes) { + if (node.children.contains(target)) { + return node; + } + var parent = _findParent(node.children, target); + if (parent != null) return parent; + } + return null; + } + + @override + Widget build(BuildContext context) { + return SingleChildScrollView( + child: _buildTree(treeData), + ); + } +} diff --git a/lib/pages/roles_and_permission/users_page/view/user_table.dart b/lib/pages/roles_and_permission/users_page/view/user_table.dart new file mode 100644 index 00000000..f951f06e --- /dev/null +++ b/lib/pages/roles_and_permission/users_page/view/user_table.dart @@ -0,0 +1,256 @@ +import 'package:flutter/material.dart'; +import 'package:syncrow_web/utils/color_manager.dart'; + +class DynamicTableScreen extends StatefulWidget { + final List titles; + final List> rows; + + DynamicTableScreen({required this.titles, required this.rows}); + + @override + _DynamicTableScreenState createState() => _DynamicTableScreenState(); +} + +class _DynamicTableScreenState extends State + with WidgetsBindingObserver { + late List columnWidths; + + // @override + // void initState() { + // super.initState(); + // // Initialize column widths with default sizes proportional to the screen width + // // Assigning placeholder values here. The actual sizes will be updated in `build`. + // } + @override + void initState() { + super.initState(); + columnWidths = List.filled(widget.titles.length, 150.0); + + WidgetsBinding.instance.addObserver(this); + } + + @override + void dispose() { + WidgetsBinding.instance.removeObserver(this); + super.dispose(); + } + + @override + void didChangeMetrics() { + super.didChangeMetrics(); + // Screen size might have changed + final newScreenWidth = MediaQuery.of(context).size.width; + setState(() { + columnWidths = List.generate(widget.titles.length, (index) { + if (index == 1) { + return newScreenWidth * + 0.12; // 20% of screen width for the second column + } else if (index == 9) { + return newScreenWidth * + 0.2; // 25% of screen width for the tenth column + } + return newScreenWidth * + 0.09; // Default to 10% of screen width for other columns + }); + }); + } + + @override + Widget build(BuildContext context) { + final screenWidth = MediaQuery.of(context).size.width; + + // Initialize column widths if they are still set to placeholder values + if (columnWidths.every((width) => width == 150.0)) { + columnWidths = List.generate(widget.titles.length, (index) { + if (index == 1) { + return screenWidth * + 0.12; // 20% of screen width for the second column + } else if (index == 9) { + return screenWidth * 0.2; // 25% of screen width for the tenth column + } + return screenWidth * + 0.09; // Default to 10% of screen width for other columns + }); + setState(() {}); + } + return SizedBox( + child: SingleChildScrollView( + scrollDirection: Axis.horizontal, + child: FittedBox( + child: Column( + children: [ + // Header Row with Resizable Columns + Container( + color: ColorsManager.CircleRolesBackground, + child: Row( + children: List.generate(widget.titles.length, (index) { + return Row( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + FittedBox( + child: Container( + padding: const EdgeInsets.only(left: 10, right: 10), + width: columnWidths[index], + child: Text( + widget.titles[index], + style: const TextStyle( + fontWeight: FontWeight.w400, + fontSize: 13, + color: ColorsManager.grayColor, + ), + ), + ), + ), + GestureDetector( + onHorizontalDragUpdate: (details) { + setState(() { + columnWidths[index] = (columnWidths[index] + + details.delta.dx) + .clamp( + 150.0, 300.0); // Minimum & Maximum size + }); + }, + child: MouseRegion( + cursor: SystemMouseCursors + .resizeColumn, // Set the cursor to resize + child: Container( + color: Colors.green, + child: Container( + color: ColorsManager.boxDivider, + width: 1, + height: 50, // Height of the header cell + ), + ), + ), + ), + ], + ); + }), + ), + ), + // Data Rows with Dividers + Container( + color: ColorsManager.whiteColors, + child: Column( + children: widget.rows.map((row) { + int rowIndex = widget.rows.indexOf(row); + return Column( + children: [ + Container( + child: Padding( + padding: const EdgeInsets.only( + left: 5, top: 10, right: 5, bottom: 10), + child: Row( + children: List.generate(row.length, (index) { + return SizedBox( + width: columnWidths[index], + child: SizedBox( + child: Padding( + padding: const EdgeInsets.only( + left: 15, right: 10), + child: row[index], + ), + ), + ); + }), + ), + ), + ), + if (rowIndex < widget.rows.length - 1) + Row( + children: + List.generate(widget.titles.length, (index) { + return SizedBox( + width: columnWidths[index], + child: const Divider( + color: ColorsManager.boxDivider, + thickness: 1, + height: 1, + ), + ); + })) + // Add a Divider below each row except the last one + ], + ); + }).toList(), + ), + ), + ], + ), + ), + ), + ); + } +} + + + + // Widget build(BuildContext context) { + // return Scaffold( + // body: SingleChildScrollView( + // scrollDirection: Axis.horizontal, + // child: SingleChildScrollView( + // scrollDirection: Axis.vertical, + // child: Column( + // children: [ + // // Header Row with Resizable Columns + // Container( + // color: Colors.green, + // child: Row( + // children: List.generate(widget.titles.length, (index) { + // return Row( + // children: [ + // Container( + // width: columnWidths[index], + // decoration: const BoxDecoration( + // color: Colors.green, + // ), + // child: Text( + // widget.titles[index], + // style: TextStyle(fontWeight: FontWeight.bold), + // textAlign: TextAlign.center, + // ), + // ), + // GestureDetector( + // onHorizontalDragUpdate: (details) { + // setState(() { + // columnWidths[index] = (columnWidths[index] + + // details.delta.dx) + // .clamp(50.0, 300.0); // Minimum & Maximum size + // }); + // }, + // child: MouseRegion( + // cursor: SystemMouseCursors + // .resizeColumn, // Set the cursor to resize + // child: Container( + // color: Colors.green, + // child: Container( + // color: Colors.black, + // width: 1, + // height: 50, // Height of the header cell + // ), + // ), + // ), + // ), + // ], + // ); + // }), + // ), + // ), + // // Data Rows + // ...widget.rows.map((row) { + // return Row( + // children: List.generate(row.length, (index) { + // return Container( + // width: columnWidths[index], + // child: row[index], + // ); + // }), + // ); + // }).toList(), + // ], + // ), + // ), + // ), + // ); + // } \ No newline at end of file diff --git a/lib/pages/roles_and_permission/users_page/view/users_page.dart b/lib/pages/roles_and_permission/users_page/view/users_page.dart new file mode 100644 index 00000000..273b8a34 --- /dev/null +++ b/lib/pages/roles_and_permission/users_page/view/users_page.dart @@ -0,0 +1,241 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; // Import Bloc package +import 'package:flutter_svg/flutter_svg.dart'; +import 'package:syncrow_web/pages/roles_and_permission/users_page/bloc/users_bloc.dart'; +import 'package:syncrow_web/pages/roles_and_permission/users_page/bloc/users_event.dart'; +import 'package:syncrow_web/pages/roles_and_permission/users_page/bloc/users_status.dart'; +import 'package:syncrow_web/pages/roles_and_permission/users_page/view/add_user_dialog.dart'; +import 'package:syncrow_web/pages/roles_and_permission/users_page/view/user_table.dart'; +import 'package:syncrow_web/utils/color_manager.dart'; +import 'package:syncrow_web/utils/constants/assets.dart'; +import 'package:syncrow_web/utils/extension/build_context_x.dart'; +import 'package:syncrow_web/utils/style.dart'; + +class UsersPage extends StatelessWidget { + const UsersPage({super.key}); + @override + Widget build(BuildContext context) { + final TextEditingController searchController = TextEditingController(); + + Widget actionButton({required String title, required Function()? onTap}) { + return InkWell( + onTap: onTap, + child: Padding( + padding: const EdgeInsets.only(left: 8, right: 8), + child: Text( + title, + style: Theme.of(context).textTheme.bodySmall?.copyWith( + color: title == "Delete" + ? ColorsManager.red + : ColorsManager.spaceColor, + fontWeight: FontWeight.w400, + ), + ), + ), + ); + } + + Widget status({required String status}) { + return Center( + child: Container( + padding: const EdgeInsets.only(left: 5, right: 5, bottom: 5, top: 5), + decoration: BoxDecoration( + borderRadius: const BorderRadius.all(Radius.circular(20)), + color: status == "Invited" + ? ColorsManager.invitedOrange.withOpacity(0.5) + : status == "Active" + ? ColorsManager.activeGreen.withOpacity(0.5) + : ColorsManager.disabledPink.withOpacity(0.5), + ), + child: Padding( + padding: + const EdgeInsets.only(left: 10, right: 10, bottom: 5, top: 5), + child: Row( + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text( + status, + style: Theme.of(context).textTheme.bodySmall?.copyWith( + color: status == "Invited" + ? ColorsManager.invitedOrangeText + : status == "Active" + ? ColorsManager.activeGreenText + : ColorsManager.disabledRedText, + fontWeight: FontWeight.w400, + ), + ), + ], + ), + ), + ), + ); + } + + Widget changeIconStatus( + {required String userId, + required String status, + required Function()? onTap}) { + return Center( + child: InkWell( + onTap: () { + final newStatus = status == 'Active' + ? 'Disabled' + : status == 'Disabled' + ? 'Invited' + : 'Active'; + context + .read() + .add(ChangeUserStatus(userId: userId, newStatus: newStatus)); + }, + child: Padding( + padding: + const EdgeInsets.only(left: 5, right: 5, bottom: 5, top: 5), + child: SvgPicture.asset( + status == "Invited" + ? Assets.invitedIcon + : status == "Active" + ? Assets.activeUser + : Assets.deActiveUser, + height: 35, + ), + ), + ), + ); + } + +// return RolesAndPermission(); +// } +// } + return BlocBuilder( + builder: (context, state) { + final screenSize = MediaQuery.of(context).size; + + if (state is UsersLoadingState) { + return const Center(child: CircularProgressIndicator()); + } else if (state is UsersLoadedState) { + return Padding( + padding: const EdgeInsets.all(20), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + Container( + decoration: containerDecoration.copyWith( + borderRadius: const BorderRadius.all( + Radius.circular(20), + ), + ), + width: screenSize.width * 0.4, + child: TextFormField( + controller: searchController, + style: const TextStyle(color: Colors.black), + decoration: textBoxDecoration(radios: 15)!.copyWith( + fillColor: ColorsManager.whiteColors, + errorStyle: const TextStyle(height: 0), + hintStyle: context.textTheme.titleSmall?.copyWith( + color: Colors.grey, + fontSize: 12, + ), + hintText: 'Search', + suffixIcon: SizedBox( + child: SvgPicture.asset( + Assets.searchIconUser, + fit: BoxFit.none, + ), + ), + ), + ), + ), + const SizedBox(width: 20), + InkWell( + onTap: () { + showDialog( + context: context, + barrierDismissible: false, + builder: (BuildContext context) { + return const AddNewUserDialog(); + }, + ).then((listDevice) { + if (listDevice != null) {} + }); + }, + child: Container( + decoration: containerWhiteDecoration, + width: screenSize.width * 0.18, + height: 50, + child: const Center( + child: Text( + 'Add New User', + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.w700, + color: ColorsManager.blueColor, + ), + ), + ), + ), + ), + ], + ), + const SizedBox(height: 25), + DynamicTableScreen( + titles: const [ + "Full Name", + "Email Address", + "Job Title", + "Role", + "Creation Date", + "Creation Time", + "Created By", + "Status", + "De/Activate", + "Action" + ], + rows: state.users.map((user) { + return [ + Text(user.userName!), + Text(user.userEmail!), + const Text("Test"), + const Text("Member"), + Text(user.creationDate!), + Text(user.creationTime!), + Text(user.createdBy!), + changeIconStatus( + status: user.status!, + userId: user.id!, + onTap: () {}, + ), + status(status: user.status!), + Row( + children: [ + actionButton( + title: "Activity Log", + onTap: () {}, + ), + actionButton( + title: "Edit", + onTap: () {}, + ), + actionButton( + title: "Delete", + onTap: () {}, + ), + ], + ), + ]; + }).toList(), + ), + ], + ), + ); + } else if (state is ErrorState) { + return Center(child: Text(state.message)); + } else { + return const Center(child: Text('No data available.')); + } + }, + ); + } +} diff --git a/lib/pages/roles_and_permission/view/create_role_card.dart b/lib/pages/roles_and_permission/view/create_role_card.dart new file mode 100644 index 00000000..e69de29b diff --git a/lib/pages/roles_and_permission/view/role_card.dart b/lib/pages/roles_and_permission/view/role_card.dart new file mode 100644 index 00000000..b3f59ee9 --- /dev/null +++ b/lib/pages/roles_and_permission/view/role_card.dart @@ -0,0 +1,57 @@ +import 'package:flutter/material.dart'; +import 'package:syncrow_web/utils/color_manager.dart'; + +class RoleCard extends StatelessWidget { + final String name; + const RoleCard({super.key, required this.name}); + + @override + Widget build(BuildContext context) { + return Container( + decoration: BoxDecoration( + color: ColorsManager.whiteColors, // Card background color + borderRadius: BorderRadius.circular(20), // Rounded corners + boxShadow: [ + BoxShadow( + color: ColorsManager.blackColor.withOpacity(0.2), // Shadow color + blurRadius: 20, // Spread of the shadow + offset: const Offset(2, 2), // No directional bias + spreadRadius: 1, // Ensures the shadow is more noticeable + ), + ], + ), + child: Container( + decoration: BoxDecoration( + color: ColorsManager.whiteColors, + borderRadius: BorderRadius.circular(20), + ), + child: Column( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + const CircleAvatar( + backgroundColor: ColorsManager.neutralGray, + radius: 65, + child: CircleAvatar( + backgroundColor: ColorsManager.CircleRolesBackground, + radius: 62, + child: Icon( + Icons.admin_panel_settings, + size: 40, + color: Colors.blue, + ), + ), + ), + Text( + name, + style: const TextStyle( + fontSize: 18, + fontWeight: FontWeight.bold, + color: Colors.black87, + ), + ), + ], + ), + ), + ); + } +} diff --git a/lib/pages/roles_and_permission/view/roles_and_permission_page.dart b/lib/pages/roles_and_permission/view/roles_and_permission_page.dart new file mode 100644 index 00000000..05f924ca --- /dev/null +++ b/lib/pages/roles_and_permission/view/roles_and_permission_page.dart @@ -0,0 +1,96 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:syncrow_web/pages/device_managment/shared/navigate_home_grid_view.dart'; +import 'package:syncrow_web/pages/roles_and_permission/bloc/roles_permission_bloc.dart'; +import 'package:syncrow_web/pages/roles_and_permission/bloc/roles_permission_event.dart'; +import 'package:syncrow_web/pages/roles_and_permission/bloc/roles_permission_state.dart'; +import 'package:syncrow_web/pages/roles_and_permission/users_page/bloc/users_bloc.dart'; +import 'package:syncrow_web/pages/roles_and_permission/users_page/bloc/users_event.dart'; +import 'package:syncrow_web/pages/roles_and_permission/users_page/view/users_page.dart'; +import 'package:syncrow_web/utils/color_manager.dart'; +import 'package:syncrow_web/utils/extension/build_context_x.dart'; +import 'package:syncrow_web/web_layout/web_scaffold.dart'; + +class RolesAndPermissionPage extends StatelessWidget { + const RolesAndPermissionPage({super.key}); + + @override + Widget build(BuildContext context) { + return BlocProvider( + create: (BuildContext context) => + RolesPermissionBloc()..add(const GetRoles()), + child: BlocConsumer( + listener: (context, state) {}, + builder: (context, state) { + final _blocRole = BlocProvider.of(context); + + return state is RolesLoadingState + ? const Center(child: CircularProgressIndicator()) + : WebScaffold( + enableMenuSidebar: false, + appBarTitle: FittedBox( + child: Text( + 'Roles & Permissions', + style: Theme.of(context).textTheme.headlineLarge, + ), + ), + rightBody: const NavigateHomeGridView(), + centerBody: Row( + children: [ + // TextButton( + // style: TextButton.styleFrom( + // backgroundColor: null, + // ), + // onPressed: () { + // _blocRole.add(const ChangeTapSelected(true)); + // }, + // child: Text( + // 'Roles', + // style: context.textTheme.titleMedium?.copyWith( + // color: (_blocRole.tapSelect == true) + // ? ColorsManager.whiteColors + // : ColorsManager.grayColor, + // fontWeight: (_blocRole.tapSelect == true) + // ? FontWeight.w700 + // : FontWeight.w400, + // ), + // ), + // ), + TextButton( + style: TextButton.styleFrom( + backgroundColor: null, + ), + onPressed: () { + // _blocRole.add(const ChangeTapSelected(false)); + }, + child: Text( + 'Users', + style: context.textTheme.titleMedium?.copyWith( + color: (_blocRole.tapSelect == true) + ? ColorsManager.whiteColors + : ColorsManager.grayColor, + fontWeight: (_blocRole.tapSelect == true) + ? FontWeight.w700 + : FontWeight.w400, + ), + ), + ), + ], + ), + scaffoldBody: BlocProvider( + create: (context) => UsersBloc()..add(const GetUsers()), + child: const UsersPage(), + ) + // _blocRole.tapSelect == false + // ? UsersPage( + // blocRole: _blocRole, + // ) + // : RolesPage( + // blocRole: _blocRole, + // ) + ); + }, + ), + ); + } +} diff --git a/lib/pages/roles_and_permission/view/roles_page.dart b/lib/pages/roles_and_permission/view/roles_page.dart new file mode 100644 index 00000000..9c8ef0cd --- /dev/null +++ b/lib/pages/roles_and_permission/view/roles_page.dart @@ -0,0 +1,69 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_svg/flutter_svg.dart'; +import 'package:syncrow_web/pages/roles_and_permission/bloc/roles_permission_bloc.dart'; +import 'package:syncrow_web/pages/roles_and_permission/view/role_card.dart'; +import 'package:syncrow_web/utils/color_manager.dart'; +import 'package:syncrow_web/utils/constants/assets.dart'; +import 'package:syncrow_web/utils/extension/build_context_x.dart'; +import 'package:syncrow_web/utils/style.dart'; + +class RolesPage extends StatelessWidget { + final RolesPermissionBloc blocRole; + const RolesPage({super.key, required this.blocRole}); + + @override + Widget build(BuildContext context) { + final TextEditingController searchController = TextEditingController(); + double screenWidth = MediaQuery.of(context).size.width; + + int crossAxisCount = (screenWidth ~/ 200).clamp(1, 6); + + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Container( + decoration: containerDecoration.copyWith( + borderRadius: const BorderRadius.all( + Radius.circular(20), + ), + ), + width: 250, + child: TextFormField( + controller: searchController, + style: const TextStyle(color: Colors.black), + decoration: textBoxDecoration(radios: 15)!.copyWith( + fillColor: ColorsManager.whiteColors, + errorStyle: const TextStyle(height: 0), + hintStyle: context.textTheme.titleSmall?.copyWith( + color: Colors.grey, + fontSize: 12, + ), + hintText: 'Search', + suffixIcon: SvgPicture.asset(Assets.searchIconUser)), + ), + ), + Expanded( + child: GridView.builder( + padding: const EdgeInsets.all(10), + gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( + crossAxisCount: crossAxisCount, + crossAxisSpacing: 16, + mainAxisSpacing: 16, + childAspectRatio: 2 / 2.5, + ), + itemCount: blocRole.roleModel.length ?? 0, + itemBuilder: (context, index) { + final role = blocRole.roleModel[index]; + if (role == null) { + return const SizedBox.shrink(); + } + return RoleCard( + name: role.roleName ?? 'Unknown', + ); + }, + ), + ), + ], + ); + } +} diff --git a/lib/utils/app_routes.dart b/lib/utils/app_routes.dart index 20a89e21..246e269e 100644 --- a/lib/utils/app_routes.dart +++ b/lib/utils/app_routes.dart @@ -3,6 +3,7 @@ import 'package:syncrow_web/pages/access_management/view/access_management.dart' import 'package:syncrow_web/pages/auth/view/login_page.dart'; import 'package:syncrow_web/pages/device_managment/all_devices/view/device_managment_page.dart'; import 'package:syncrow_web/pages/home/view/home_page.dart'; +import 'package:syncrow_web/pages/roles_and_permission/view/roles_and_permission_page.dart'; import 'package:syncrow_web/pages/spaces_management/view/spaces_management_page.dart'; import 'package:syncrow_web/pages/visitor_password/view/visitor_password_dialog.dart'; import 'package:syncrow_web/utils/constants/routes_const.dart'; @@ -32,7 +33,10 @@ class AppRoutes { ), GoRoute( path: RoutesConst.spacesManagementPage, - builder: (context, state) => SpaceManagementPage()), + builder: (context, state) => const SpaceManagementPage()), + GoRoute( + path: RoutesConst.rolesAndPermissions, + builder: (context, state) => const RolesAndPermissionPage()), ]; } } diff --git a/lib/utils/color_manager.dart b/lib/utils/color_manager.dart index 95d0f214..5549b566 100644 --- a/lib/utils/color_manager.dart +++ b/lib/utils/color_manager.dart @@ -5,7 +5,8 @@ abstract class ColorsManager { static const Color switchOffColor = Color(0x7F8D99AE); static const Color primaryColor = Color(0xFF0030CB); //023DFE static const Color secondaryTextColor = Color(0xFF848484); - static Color primaryColorWithOpacity = const Color(0xFF023DFE).withOpacity(0.6); + static Color primaryColorWithOpacity = + const Color(0xFF023DFE).withOpacity(0.6); static const Color whiteColors = Colors.white; static const Color secondaryColor = Color(0xFF023DFE); static const Color onSecondaryColor = Color(0xFF023DFE); @@ -54,5 +55,13 @@ abstract class ColorsManager { static const Color warningRed = Color(0xFFFF6465); static const Color borderColor = Color(0xFFE5E5E5); static const Color CircleImageBackground = Color(0xFFF4F4F4); + static const Color CircleRolesBackground = Color(0xFFF8F8F8); + static const Color activeGreen = Color(0xFF99FF93); + static const Color activeGreenText = Color(0xFF008905); + static const Color disabledPink = Color(0xFFFF9395); + static const Color disabledRedText = Color(0xFF890002); + static const Color invitedOrange = Color(0xFFFFE193); + static const Color invitedOrangeText = Color(0xFFFFBF00); + //background: #F8F8F8; + } -//background: #background: #5D5D5D; diff --git a/lib/utils/constants/assets.dart b/lib/utils/constants/assets.dart index 958c2c1c..4e35e84f 100644 --- a/lib/utils/constants/assets.dart +++ b/lib/utils/constants/assets.dart @@ -13,10 +13,12 @@ class Assets { 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 invisiblePassword = + "assets/images/Password_invisible.svg"; static const String visiblePassword = "assets/images/password_visible.svg"; static const String accessIcon = "assets/images/access_icon.svg"; - static const String spaseManagementIcon = "assets/images/spase_management_icon.svg"; + static const String spaseManagementIcon = + "assets/images/spase_management_icon.svg"; static const String devicesIcon = "assets/images/devices_icon.svg"; static const String moveinIcon = "assets/images/movein_icon.svg"; static const String constructionIcon = "assets/images/construction_icon.svg"; @@ -29,13 +31,15 @@ class Assets { static const String emptyTable = "assets/images/empty_table.svg"; // General assets - static const String motionlessDetection = "assets/icons/motionless_detection.svg"; + static const String motionlessDetection = + "assets/icons/motionless_detection.svg"; static const String acHeating = "assets/icons/ac_heating.svg"; static const String acPowerOff = "assets/icons/ac_power_off.svg"; static const String acFanMiddle = "assets/icons/ac_fan_middle.svg"; static const String switchAlarmSound = "assets/icons/switch_alarm_sound.svg"; static const String resetOff = "assets/icons/reset_off.svg"; - static const String sensitivityOperationIcon = "assets/icons/sesitivity_operation_icon.svg"; + static const String sensitivityOperationIcon = + "assets/icons/sesitivity_operation_icon.svg"; static const String motionDetection = "assets/icons/motion_detection.svg"; static const String freezing = "assets/icons/freezing.svg"; static const String indicator = "assets/icons/indicator.svg"; @@ -56,7 +60,8 @@ class Assets { static const String celsiusDegrees = "assets/icons/celsius_degrees.svg"; static const String masterState = "assets/icons/master_state.svg"; static const String acPower = "assets/icons/ac_power.svg"; - static const String farDetectionFunction = "assets/icons/far_detection_function.svg"; + static const String farDetectionFunction = + "assets/icons/far_detection_function.svg"; static const String nobodyTime = "assets/icons/nobody_time.svg"; // Automation functions @@ -64,33 +69,47 @@ class Assets { "assets/icons/automation_functions/temp_password_unlock.svg"; static const String doorlockNormalOpen = "assets/icons/automation_functions/doorlock_normal_open.svg"; - static const String doorbell = "assets/icons/automation_functions/doorbell.svg"; + static const String doorbell = + "assets/icons/automation_functions/doorbell.svg"; static const String remoteUnlockViaApp = "assets/icons/automation_functions/remote_unlock_via_app.svg"; - static const String doubleLock = "assets/icons/automation_functions/double_lock.svg"; - static const String selfTestResult = "assets/icons/automation_functions/self_test_result.svg"; - static const String lockAlarm = "assets/icons/automation_functions/lock_alarm.svg"; - static const String presenceState = "assets/icons/automation_functions/presence_state.svg"; - static const String currentTemp = "assets/icons/automation_functions/current_temp.svg"; - static const String presence = "assets/icons/automation_functions/presence.svg"; + static const String doubleLock = + "assets/icons/automation_functions/double_lock.svg"; + static const String selfTestResult = + "assets/icons/automation_functions/self_test_result.svg"; + static const String lockAlarm = + "assets/icons/automation_functions/lock_alarm.svg"; + static const String presenceState = + "assets/icons/automation_functions/presence_state.svg"; + static const String currentTemp = + "assets/icons/automation_functions/current_temp.svg"; + static const String presence = + "assets/icons/automation_functions/presence.svg"; static const String residualElectricity = "assets/icons/automation_functions/residual_electricity.svg"; - static const String hijackAlarm = "assets/icons/automation_functions/hijack_alarm.svg"; - static const String passwordUnlock = "assets/icons/automation_functions/password_unlock.svg"; + static const String hijackAlarm = + "assets/icons/automation_functions/hijack_alarm.svg"; + static const String passwordUnlock = + "assets/icons/automation_functions/password_unlock.svg"; static const String remoteUnlockRequest = "assets/icons/automation_functions/remote_unlock_req.svg"; - static const String cardUnlock = "assets/icons/automation_functions/card_unlock.svg"; + static const String cardUnlock = + "assets/icons/automation_functions/card_unlock.svg"; static const String motion = "assets/icons/automation_functions/motion.svg"; static const String fingerprintUnlock = "assets/icons/automation_functions/fingerprint_unlock.svg"; // Presence Sensor Assets static const String sensorMotionIcon = "assets/icons/sensor_motion_ic.svg"; - static const String sensorPresenceIcon = "assets/icons/sensor_presence_ic.svg"; + static const String sensorPresenceIcon = + "assets/icons/sensor_presence_ic.svg"; static const String sensorVacantIcon = "assets/icons/sensor_vacant_ic.svg"; - static const String illuminanceRecordIcon = "assets/icons/illuminance_record_ic.svg"; - static const String presenceRecordIcon = "assets/icons/presence_record_ic.svg"; - static const String helpDescriptionIcon = "assets/icons/help_description_ic.svg"; + static const String illuminanceRecordIcon = + "assets/icons/illuminance_record_ic.svg"; + static const String presenceRecordIcon = + "assets/icons/presence_record_ic.svg"; + static const String helpDescriptionIcon = + "assets/icons/help_description_ic.svg"; static const String lightPulp = "assets/icons/light_pulb.svg"; static const String acDevice = "assets/icons/ac_device.svg"; @@ -140,10 +159,12 @@ class Assets { static const String unit = 'assets/icons/unit_icon.svg'; static const String villa = 'assets/icons/villa_icon.svg'; static const String iconEdit = 'assets/icons/icon_edit_icon.svg'; - static const String textFieldSearch = 'assets/icons/textfield_search_icon.svg'; + static const String textFieldSearch = + 'assets/icons/textfield_search_icon.svg'; static const String roundedAddIcon = 'assets/icons/rounded_add_icon.svg'; static const String addIcon = 'assets/icons/add_icon.svg'; - static const String smartThermostatIcon = 'assets/icons/smart_thermostat_icon.svg'; + static const String smartThermostatIcon = + 'assets/icons/smart_thermostat_icon.svg'; static const String smartLightIcon = 'assets/icons/smart_light_icon.svg'; static const String presenceSensor = 'assets/icons/presence_sensor.svg'; static const String Gang3SwitchIcon = 'assets/icons/3_Gang_switch_icon.svg'; @@ -191,7 +212,8 @@ class Assets { //assets/icons/water_leak_normal.svg static const String waterLeakNormal = 'assets/icons/water_leak_normal.svg'; //assets/icons/water_leak_detected.svg - static const String waterLeakDetected = 'assets/icons/water_leak_detected.svg'; + static const String waterLeakDetected = + 'assets/icons/water_leak_detected.svg'; //assets/icons/automation_records.svg static const String automationRecords = 'assets/icons/automation_records.svg'; @@ -256,40 +278,64 @@ class Assets { static const String delay = 'assets/icons/routine/delay.svg'; // Assets for functions_icons - static const String assetsSensitivityFunction = "assets/icons/functions_icons/sensitivity.svg"; + static const String assetsSensitivityFunction = + "assets/icons/functions_icons/sensitivity.svg"; static const String assetsSensitivityOperationIcon = "assets/icons/functions_icons/sesitivity_operation_icon.svg"; - static const String assetsAcPower = "assets/icons/functions_icons/ac_power.svg"; - static const String assetsAcPowerOFF = "assets/icons/functions_icons/ac_power_off.svg"; - static const String assetsChildLock = "assets/icons/functions_icons/child_lock.svg"; - static const String assetsFreezing = "assets/icons/functions_icons/freezing.svg"; - static const String assetsFanSpeed = "assets/icons/functions_icons/fan_speed.svg"; - static const String assetsAcCooling = "assets/icons/functions_icons/ac_cooling.svg"; - static const String assetsAcHeating = "assets/icons/functions_icons/ac_heating.svg"; - static const String assetsCelsiusDegrees = "assets/icons/functions_icons/celsius_degrees.svg"; - static const String assetsTempreture = "assets/icons/functions_icons/tempreture.svg"; - static const String assetsAcFanLow = "assets/icons/functions_icons/ac_fan_low.svg"; - static const String assetsAcFanMiddle = "assets/icons/functions_icons/ac_fan_middle.svg"; - static const String assetsAcFanHigh = "assets/icons/functions_icons/ac_fan_high.svg"; - static const String assetsAcFanAuto = "assets/icons/functions_icons/ac_fan_auto.svg"; - static const String assetsSceneChildLock = "assets/icons/functions_icons/scene_child_lock.svg"; + static const String assetsAcPower = + "assets/icons/functions_icons/ac_power.svg"; + static const String assetsAcPowerOFF = + "assets/icons/functions_icons/ac_power_off.svg"; + static const String assetsChildLock = + "assets/icons/functions_icons/child_lock.svg"; + static const String assetsFreezing = + "assets/icons/functions_icons/freezing.svg"; + static const String assetsFanSpeed = + "assets/icons/functions_icons/fan_speed.svg"; + static const String assetsAcCooling = + "assets/icons/functions_icons/ac_cooling.svg"; + static const String assetsAcHeating = + "assets/icons/functions_icons/ac_heating.svg"; + static const String assetsCelsiusDegrees = + "assets/icons/functions_icons/celsius_degrees.svg"; + static const String assetsTempreture = + "assets/icons/functions_icons/tempreture.svg"; + static const String assetsAcFanLow = + "assets/icons/functions_icons/ac_fan_low.svg"; + static const String assetsAcFanMiddle = + "assets/icons/functions_icons/ac_fan_middle.svg"; + static const String assetsAcFanHigh = + "assets/icons/functions_icons/ac_fan_high.svg"; + static const String assetsAcFanAuto = + "assets/icons/functions_icons/ac_fan_auto.svg"; + static const String assetsSceneChildLock = + "assets/icons/functions_icons/scene_child_lock.svg"; static const String assetsSceneChildUnlock = "assets/icons/functions_icons/scene_child_unlock.svg"; - static const String assetsSceneRefresh = "assets/icons/functions_icons/scene_refresh.svg"; - static const String assetsLightCountdown = "assets/icons/functions_icons/light_countdown.svg"; - static const String assetsFarDetection = "assets/icons/functions_icons/far_detection.svg"; + static const String assetsSceneRefresh = + "assets/icons/functions_icons/scene_refresh.svg"; + static const String assetsLightCountdown = + "assets/icons/functions_icons/light_countdown.svg"; + static const String assetsFarDetection = + "assets/icons/functions_icons/far_detection.svg"; static const String assetsFarDetectionFunction = "assets/icons/functions_icons/far_detection_function.svg"; - static const String assetsIndicator = "assets/icons/functions_icons/indicator.svg"; - static const String assetsMotionDetection = "assets/icons/functions_icons/motion_detection.svg"; + static const String assetsIndicator = + "assets/icons/functions_icons/indicator.svg"; + static const String assetsMotionDetection = + "assets/icons/functions_icons/motion_detection.svg"; static const String assetsMotionlessDetection = "assets/icons/functions_icons/motionless_detection.svg"; - static const String assetsNobodyTime = "assets/icons/functions_icons/nobody_time.svg"; - static const String assetsFactoryReset = "assets/icons/functions_icons/factory_reset.svg"; - static const String assetsMasterState = "assets/icons/functions_icons/master_state.svg"; + static const String assetsNobodyTime = + "assets/icons/functions_icons/nobody_time.svg"; + static const String assetsFactoryReset = + "assets/icons/functions_icons/factory_reset.svg"; + static const String assetsMasterState = + "assets/icons/functions_icons/master_state.svg"; static const String assetsSwitchAlarmSound = "assets/icons/functions_icons/switch_alarm_sound.svg"; - static const String assetsResetOff = "assets/icons/functions_icons/reset_off.svg"; + static const String assetsResetOff = + "assets/icons/functions_icons/reset_off.svg"; // Assets for automation_functions static const String assetsCardUnlock = @@ -320,11 +366,29 @@ class Assets { "assets/icons/functions_icons/automation_functions/self_test_result.svg"; static const String assetsPresence = "assets/icons/functions_icons/automation_functions/presence.svg"; - static const String assetsMotion = "assets/icons/functions_icons/automation_functions/motion.svg"; + static const String assetsMotion = + "assets/icons/functions_icons/automation_functions/motion.svg"; static const String assetsCurrentTemp = "assets/icons/functions_icons/automation_functions/current_temp.svg"; static const String assetsPresenceState = "assets/icons/functions_icons/automation_functions/presence_state.svg"; //assets/icons/routine/automation.svg static const String automation = 'assets/icons/routine/automation.svg'; + static const String searchIconUser = 'assets/icons/search_icon_user.svg'; + static const String searchIcoUser = 'assets/icons/search_icon_user.svg'; + static const String activeUser = 'assets/icons/active_user.svg'; + static const String deActiveUser = 'assets/icons/deactive_user.svg'; + static const String invitedIcon = 'assets/icons/invited_icon.svg'; + static const String rectangleCheckBox = + 'assets/icons/rectangle_check_box.png'; + static const String CheckBoxChecked = 'assets/icons/box_checked.png'; + static const String emptyBox = 'assets/icons/empty_box.png'; + static const String completeProcessIcon = + 'assets/icons/compleate_process_icon.svg'; + static const String currentProcessIcon = + 'assets/icons/current_process_icon.svg'; + static const String uncomplete_ProcessIcon = + 'assets/icons/uncompleate_process_icon.svg'; + static const String wrongProcessIcon = + 'assets/icons/wrong_process_icon.svg'; } diff --git a/lib/utils/constants/routes_const.dart b/lib/utils/constants/routes_const.dart index 094787d4..8a65e9ae 100644 --- a/lib/utils/constants/routes_const.dart +++ b/lib/utils/constants/routes_const.dart @@ -5,4 +5,5 @@ class RoutesConst { static const String accessManagementPage = '/access-management-page'; static const String deviceManagementPage = '/device-management-page'; static const String spacesManagementPage = '/spaces_management-page'; + static const String rolesAndPermissions = '/roles_and_Permissions-page'; } diff --git a/lib/utils/style.dart b/lib/utils/style.dart index a80c68d6..b5ea59ee 100644 --- a/lib/utils/style.dart +++ b/lib/utils/style.dart @@ -2,51 +2,59 @@ import 'package:flutter/material.dart'; import 'color_manager.dart'; -InputDecoration? textBoxDecoration({bool suffixIcon = false}) => InputDecoration( +InputDecoration? textBoxDecoration( + {bool suffixIcon = false, double radios = 8}) => + InputDecoration( focusColor: ColorsManager.grayColor, suffixIcon: suffixIcon ? const Icon(Icons.search) : null, hintText: 'Search', filled: true, // Enable background filling fillColor: const Color(0xffF5F6F7), // Set the background color border: OutlineInputBorder( - borderRadius: BorderRadius.circular(8), // Add border radius + borderRadius: BorderRadius.circular(radios), // Add border radius borderSide: BorderSide.none, // Remove the underline ), enabledBorder: OutlineInputBorder( - borderRadius: BorderRadius.circular(8), // Add border radius + borderRadius: BorderRadius.circular(radios), // Add border radius borderSide: BorderSide.none, // Remove the underline ), focusedBorder: OutlineInputBorder( - borderRadius: BorderRadius.circular(8), // Add border radius + borderRadius: BorderRadius.circular(radios), // Add border radius borderSide: BorderSide.none, // Remove the underline ), errorBorder: OutlineInputBorder( borderSide: const BorderSide(color: Colors.red, width: 2), - borderRadius: BorderRadius.circular(8), + borderRadius: BorderRadius.circular(radios), ), focusedErrorBorder: OutlineInputBorder( borderSide: const BorderSide(color: Colors.red, width: 2), - borderRadius: BorderRadius.circular(8), + borderRadius: BorderRadius.circular(radios), ), ); -BoxDecoration containerDecoration = BoxDecoration(boxShadow: [ - BoxShadow( - color: Colors.grey.withOpacity(0.3), - spreadRadius: 2, - blurRadius: 4, - offset: const Offset(0, 5), // changes position of shadow - ), -], color: ColorsManager.boxColor, borderRadius: const BorderRadius.all(Radius.circular(10))); +BoxDecoration containerDecoration = BoxDecoration( + boxShadow: [ + BoxShadow( + color: Colors.grey.withOpacity(0.3), + spreadRadius: 2, + blurRadius: 4, + offset: const Offset(0, 5), // changes position of shadow + ), + ], + color: ColorsManager.boxColor, + borderRadius: const BorderRadius.all(Radius.circular(10))); -BoxDecoration containerWhiteDecoration = BoxDecoration(boxShadow: [ - BoxShadow( - color: Colors.grey.withOpacity(0.3), - spreadRadius: 2, - blurRadius: 4, - offset: const Offset(0, 5), // changes position of shadow - ), -], color: ColorsManager.whiteColors, borderRadius: const BorderRadius.all(Radius.circular(15))); +BoxDecoration containerWhiteDecoration = BoxDecoration( + boxShadow: [ + BoxShadow( + color: Colors.grey.withOpacity(0.3), + spreadRadius: 2, + blurRadius: 4, + offset: const Offset(0, 5), // changes position of shadow + ), + ], + color: ColorsManager.whiteColors, + borderRadius: const BorderRadius.all(Radius.circular(15))); BoxDecoration subSectionContainerDecoration = BoxDecoration( color: ColorsManager.whiteColors, @@ -59,3 +67,30 @@ BoxDecoration subSectionContainerDecoration = BoxDecoration( ), ], ); + +InputDecoration inputTextFormDeco({hintText}) => InputDecoration( + hintText: hintText, + border: const OutlineInputBorder( + + borderSide: BorderSide( + width: 1, + color: ColorsManager.textGray, // Border color for unfocused state + ), + borderRadius: BorderRadius.all(Radius.circular(10)), + ), + focusedBorder: const OutlineInputBorder( + borderSide: BorderSide( + width: 2, + color: ColorsManager.textGray, // Border color when focused + ), + borderRadius: BorderRadius.all(Radius.circular(10)), + ), + enabledBorder: const OutlineInputBorder( + borderSide: BorderSide( + width: 1, + color: ColorsManager + .textGray // Border color for enabled (but unfocused) state + ), + borderRadius: BorderRadius.all(Radius.circular(10)), + ), + ); diff --git a/pubspec.lock b/pubspec.lock index 192106d7..ea2315d7 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -316,18 +316,18 @@ packages: dependency: transitive description: name: leak_tracker - sha256: "3f87a60e8c63aecc975dda1ceedbc8f24de75f09e4856ea27daf8958f2f0ce05" + sha256: "7f0df31977cb2c0b88585095d168e689669a2cc9b97c309665e3386f3e9d341a" url: "https://pub.dev" source: hosted - version: "10.0.5" + version: "10.0.4" leak_tracker_flutter_testing: dependency: transitive description: name: leak_tracker_flutter_testing - sha256: "932549fb305594d82d7183ecd9fa93463e9914e1b67cacc34bc40906594a1806" + sha256: "06e98f569d004c1315b991ded39924b21af84cf14cc94791b8aea337d25b57f8" url: "https://pub.dev" source: hosted - version: "3.0.5" + version: "3.0.3" leak_tracker_testing: dependency: transitive description: @@ -364,18 +364,18 @@ packages: dependency: transitive description: name: material_color_utilities - sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec + sha256: "0e0a020085b65b6083975e499759762399b4475f766c21668c4ecca34ea74e5a" url: "https://pub.dev" source: hosted - version: "0.11.1" + version: "0.8.0" meta: dependency: transitive description: name: meta - sha256: bdb68674043280c3428e9ec998512fb681678676b3c54e773629ffe74419f8c7 + sha256: "7687075e408b093f36e6bbf6c91878cc0d4cd10f409506f7bc996f68220b9136" url: "https://pub.dev" source: hosted - version: "1.15.0" + version: "1.12.0" nested: dependency: transitive description: @@ -593,10 +593,10 @@ packages: dependency: transitive description: name: test_api - sha256: "5b8a98dafc4d5c4c9c72d8b31ab2b23fc13422348d2997120294d3bac86b4ddb" + sha256: "9955ae474176f7ac8ee4e989dadfb411a58c30415bcfb648fa04b2b8a03afa7f" url: "https://pub.dev" source: hosted - version: "0.7.2" + version: "0.7.0" time_picker_spinner: dependency: "direct main" description: @@ -657,10 +657,10 @@ packages: dependency: transitive description: name: vm_service - sha256: f652077d0bdf60abe4c1f6377448e8655008eef28f128bc023f7b5e8dfeb48fc + sha256: "3923c89304b715fb1eb6423f017651664a03bf5f4b29983627c4da791f74a4ec" url: "https://pub.dev" source: hosted - version: "14.2.4" + version: "14.2.1" web: dependency: transitive description: