From 1d226742e6fb1f64a6d340ae7bfca8defd1dd43f Mon Sep 17 00:00:00 2001 From: mohammad Date: Thu, 8 Aug 2024 16:54:02 +0300 Subject: [PATCH 01/32] get user info and access management page --- lib/main.dart | 12 +- .../access_management/access_management.dart | 142 +++++++++++++++ lib/pages/auth/model/user_model.dart | 19 +- lib/pages/home/bloc/home_bloc.dart | 96 +++++++++- lib/pages/home/bloc/home_state.dart | 6 + .../home/home_model/home_item_model.dart | 22 +++ lib/pages/home/view/home_page_web.dart | 164 +++++++----------- lib/services/home_api.dart | 18 ++ lib/utils/color_manager.dart | 1 + lib/utils/constants/api_const.dart | 1 + lib/web_layout/web_app_bar.dart | 10 +- pubspec.lock | 8 + pubspec.yaml | 1 + 13 files changed, 385 insertions(+), 115 deletions(-) create mode 100644 lib/pages/access_management/access_management.dart create mode 100644 lib/pages/home/home_model/home_item_model.dart create mode 100644 lib/services/home_api.dart diff --git a/lib/main.dart b/lib/main.dart index d52c6541..14d48c3e 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,7 +1,9 @@ import 'package:flutter/gestures.dart'; import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:syncrow_web/pages/auth/bloc/auth_bloc.dart'; import 'package:syncrow_web/pages/auth/view/login_page.dart'; +import 'package:syncrow_web/pages/home/bloc/home_bloc.dart'; import 'package:syncrow_web/pages/home/view/home_page.dart'; import 'package:syncrow_web/services/locator.dart'; import 'package:syncrow_web/utils/color_manager.dart'; @@ -23,7 +25,13 @@ class MyApp extends StatelessWidget { }); @override Widget build(BuildContext context) { - return MaterialApp( + return MultiBlocProvider( + providers: [ + BlocProvider(create: (context) => HomeBloc()), + ], + child: + + MaterialApp( debugShowCheckedModeBanner: false, // Hide debug banner scrollBehavior: const MaterialScrollBehavior().copyWith( dragDevices: { @@ -51,6 +59,6 @@ class MyApp extends StatelessWidget { useMaterial3: true, // Enable Material 3 ), home: isLoggedIn == 'Success' ? const HomePage() : const LoginPage(), - ); + )); } } diff --git a/lib/pages/access_management/access_management.dart b/lib/pages/access_management/access_management.dart new file mode 100644 index 00000000..f7fee005 --- /dev/null +++ b/lib/pages/access_management/access_management.dart @@ -0,0 +1,142 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_svg/svg.dart'; +import 'package:syncrow_web/utils/color_manager.dart'; +import 'package:syncrow_web/utils/constants/assets.dart'; +import 'package:syncrow_web/web_layout/web_scaffold.dart'; + +class AccessManagementPage extends StatelessWidget { + const AccessManagementPage({super.key}); + + @override + Widget build(BuildContext context) { + Size size = MediaQuery.of(context).size; + + return WebScaffold( + enableMenuSideba: false, + appBarTitle: Row( + children: [ + Text( + 'Access Management', + style: Theme.of(context).textTheme.headlineLarge, + ) + ], + ), + appBarBody: [ + Text( + 'Physical Access', + style: Theme.of(context) + .textTheme + .headlineMedium! + .copyWith(color: Colors.white), + ), + Text( + 'App Access', + style: Theme.of(context) + .textTheme + .headlineMedium! + .copyWith(color: Colors.white), + ) + ], + scaffoldBody: Container( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Container( + width:size.width*0.3, + height: size.height*0.05, + decoration: BoxDecoration( + boxShadow: [ + BoxShadow( + color: Colors.grey.withOpacity(0.5), + spreadRadius: 5, + blurRadius: 7, + offset: Offset(0, 3), // changes position of shadow + ), + ], + color: ColorsManager.boxColor, + borderRadius: BorderRadius.all(Radius.circular(10))), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceAround, + children: [ + Text('All'), + Text('To Be Effective (0)'), + Text('Effective (0)'), + Text('Expired'), + ], + ), + ), + SizedBox(height: 10,), + Row( + children: [ + Container( + width:size.width*0.08, + height: size.height*0.05, + decoration: BoxDecoration( + boxShadow: [ + BoxShadow( + color: Colors.grey.withOpacity(0.5), + spreadRadius: 5, + blurRadius: 7, + offset: Offset(0, 3), // changes position of shadow + ), + ], + color: ColorsManager.boxColor, + borderRadius: BorderRadius.all(Radius.circular(10))), + child: TextFormField() + ), + Container( + width:size.width*0.08, + height: size.height*0.05, + decoration: BoxDecoration( + boxShadow: [ + BoxShadow( + color: Colors.grey.withOpacity(0.5), + spreadRadius: 5, + blurRadius: 7, + offset: Offset(0, 3), // changes position of shadow + ), + ], + color: ColorsManager.boxColor, + borderRadius: BorderRadius.all(Radius.circular(10))), + child: TextFormField() + ), + Container( + width:size.width*0.08, + height: size.height*0.05, + decoration: BoxDecoration( + boxShadow: [ + BoxShadow( + color: Colors.grey.withOpacity(0.5), + spreadRadius: 5, + blurRadius: 7, + offset: Offset(0, 3), // changes position of shadow + ), + ], + color: ColorsManager.boxColor, + borderRadius: BorderRadius.all(Radius.circular(10))), + child: TextFormField() + ), + Container( + width:size.width*0.08, + height: size.height*0.05, + decoration: BoxDecoration( + boxShadow: [ + BoxShadow( + color: Colors.grey.withOpacity(0.5), + spreadRadius: 5, + blurRadius: 7, + offset: Offset(0, 3), // changes position of shadow + ), + ], + color: ColorsManager.boxColor, + borderRadius: BorderRadius.all(Radius.circular(10))), + child: TextFormField() + ) + ], + ), + + ], + ), + )); + } +} diff --git a/lib/pages/auth/model/user_model.dart b/lib/pages/auth/model/user_model.dart index 674eee9a..cbae385a 100644 --- a/lib/pages/auth/model/user_model.dart +++ b/lib/pages/auth/model/user_model.dart @@ -6,19 +6,17 @@ class UserModel { static String userUuidKey = 'userUuid'; final String? uuid; final String? email; - final String? name; + final String? firstName; + final String? lastName; final String? photoUrl; - final String? phoneNumber; - final bool? isEmailVerified; - final bool? isAgreementAccepted; - UserModel({ required this.uuid, required this.email, - required this.name, + required this.firstName, + required this.lastName, required this.photoUrl, required this.phoneNumber, required this.isEmailVerified, @@ -29,7 +27,8 @@ class UserModel { return UserModel( uuid: json['id'], email: json['email'], - name: json['name'], + firstName: json['firstName'], + lastName: json['lastName'], photoUrl: json['photoUrl'], phoneNumber: json['phoneNumber'], isEmailVerified: json['isEmailVerified'], @@ -46,7 +45,8 @@ class UserModel { return UserModel( uuid: tempJson['uuid'].toString(), email: tempJson['email'], - name: null, + firstName: null, + lastName: null, photoUrl: null, phoneNumber: null, isEmailVerified: null, @@ -58,7 +58,8 @@ class UserModel { return { 'id': uuid, 'email': email, - 'name': name, + 'firstName': firstName, + 'lastName': lastName, 'photoUrl': photoUrl, 'phoneNumber': phoneNumber, 'isEmailVerified': isEmailVerified, diff --git a/lib/pages/home/bloc/home_bloc.dart b/lib/pages/home/bloc/home_bloc.dart index a1475532..f261c39a 100644 --- a/lib/pages/home/bloc/home_bloc.dart +++ b/lib/pages/home/bloc/home_bloc.dart @@ -1,23 +1,34 @@ +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:flutter_secure_storage/flutter_secure_storage.dart'; import 'package:graphview/GraphView.dart'; +import 'package:syncrow_web/pages/access_management/access_management.dart'; +import 'package:syncrow_web/pages/auth/model/user_model.dart'; import 'package:syncrow_web/pages/home/bloc/home_event.dart'; import 'package:syncrow_web/pages/home/bloc/home_state.dart'; +import 'package:syncrow_web/pages/home/home_model/home_item_model.dart'; +import 'package:syncrow_web/services/home_api.dart'; +import 'package:syncrow_web/utils/color_manager.dart'; +import 'package:syncrow_web/utils/constants/assets.dart'; class HomeBloc extends Bloc { final Graph graph = Graph()..isTree = true; final BuchheimWalkerConfiguration builder = BuchheimWalkerConfiguration(); List sourcesList = []; List destinationsList = []; + static UserModel? user; HomeBloc() : super((HomeInitial())) { on(_createNode); + fetchUserInfo(); + } void _createNode(CreateNewNode event, Emitter emit) async { emit(HomeInitial()); sourcesList.add(event.sourceNode); destinationsList.add(event.destinationNode); - for (int i = 0; i < sourcesList.length; i++) { graph.addEdge(sourcesList[i], destinationsList[i]); } @@ -30,4 +41,87 @@ class HomeBloc extends Bloc { emit(HomeUpdateTree(graph: graph, builder: builder)); } + + + + Future fetchUserInfo() async { + try { + var uuid = + await const FlutterSecureStorage().read(key: UserModel.userUuidKey); + user = await HomeApi().fetchUserInfo(uuid); + emit(HomeUserInfoLoaded(user!)); // Emit state after fetching user info + } catch (e) { + return; + } + } + + List homeItems = [ + HomeItemModel( + title: 'Access', + icon: Assets.accessIcon, + active: true, + onPress: (context) { + Navigator.of(context).push( + MaterialPageRoute(builder: (context) => AccessManagementPage()), + ); + }, + color: null, + ), + HomeItemModel( + title: 'Space\nManagement', + icon: Assets.spaseManagementIcon, + active: true, + onPress: (context) { + }, + color: ColorsManager.primaryColor, + ), + HomeItemModel( + title: 'Devices', + icon: Assets.devicesIcon, + active: true, + onPress: (context) { + }, + color: ColorsManager.primaryColor, + ), + HomeItemModel( + title: 'Move in', + icon: Assets.moveinIcon, + active: false, + onPress: (context) { + }, + color: ColorsManager.primaryColor, + ), + HomeItemModel( + title: 'Construction', + icon: Assets.constructionIcon, + active: false, + onPress: (context) { + }, + color: ColorsManager.primaryColor, + ), + HomeItemModel( + title: 'Energy', + icon: Assets.energyIcon, + active: false, + onPress: (context) { + }, + color: ColorsManager.slidingBlueColor.withOpacity(0.2), + ), + HomeItemModel( + title: 'Integrations', + icon: Assets.integrationsIcon, + active: false, + onPress: (context) { + }, + color: ColorsManager.slidingBlueColor.withOpacity(0.2), + ), + HomeItemModel( + title: 'Asset', + icon: Assets.assetIcon, + active: false, + onPress: (context) { + }, + color: ColorsManager.slidingBlueColor.withOpacity(0.2), + ), + ]; } diff --git a/lib/pages/home/bloc/home_state.dart b/lib/pages/home/bloc/home_state.dart index 10c50486..7a1be0be 100644 --- a/lib/pages/home/bloc/home_state.dart +++ b/lib/pages/home/bloc/home_state.dart @@ -1,5 +1,6 @@ import 'package:equatable/equatable.dart'; import 'package:graphview/GraphView.dart'; +import 'package:syncrow_web/pages/auth/model/user_model.dart'; abstract class HomeState extends Equatable { const HomeState(); @@ -24,3 +25,8 @@ class HomeUpdateTree extends HomeState { @override List get props => [graph, builder]; } +class HomeUserInfoLoaded extends HomeState { + final UserModel user; + + HomeUserInfoLoaded(this.user); +} diff --git a/lib/pages/home/home_model/home_item_model.dart b/lib/pages/home/home_model/home_item_model.dart new file mode 100644 index 00000000..5626cd3f --- /dev/null +++ b/lib/pages/home/home_model/home_item_model.dart @@ -0,0 +1,22 @@ + + + + +import 'package:flutter/cupertino.dart'; + +class HomeItemModel { + final String? title; + final String? icon; + final Color? color; + final bool? active; + final void Function(BuildContext context) onPress; + + + HomeItemModel({ + this.title, + this.icon, + this.color, + this.active, + required this.onPress, + }); +} \ No newline at end of file diff --git a/lib/pages/home/view/home_page_web.dart b/lib/pages/home/view/home_page_web.dart index f7c4e988..0124acad 100644 --- a/lib/pages/home/view/home_page_web.dart +++ b/lib/pages/home/view/home_page_web.dart @@ -7,113 +7,75 @@ import 'package:syncrow_web/utils/color_manager.dart'; import 'package:syncrow_web/utils/constants/assets.dart'; import 'package:syncrow_web/web_layout/web_scaffold.dart'; +import '../bloc/home_state.dart'; + class HomeWebPage extends StatelessWidget { HomeWebPage({super.key}); @override Widget build(BuildContext context) { Size size = MediaQuery.of(context).size; return WebScaffold( - enableMenuSideba:false , - appBarTitle: Row( - children: [ - SvgPicture.asset( - Assets.loginLogo, - width: 150, - ), - ], - ), - scaffoldBody: BlocProvider( - create: (context) => HomeBloc(), - child: SizedBox( - height: size.height, - width: size.width, - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - SizedBox(height: size.height * 0.1), - const Text( - 'ACCESS YOUR APPS', - style: TextStyle(fontSize: 40, fontWeight: FontWeight.w700), - ), - const SizedBox(height: 30), - Expanded( - flex: 4, - child: SizedBox( - height: size.height * 0.6, - width: size.width * 0.68, - child: GridView.builder( - itemCount: 8, - gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount( - crossAxisCount: 4, - crossAxisSpacing: 20.0, - mainAxisSpacing: 20.0, - childAspectRatio: 1.5, - ), - itemBuilder: (context, index) { - return HomeCard( - index:index, - active:ceilingSensorButtons[index]['active'], - name: ceilingSensorButtons[index]['title'], - img:ceilingSensorButtons[index]['icon'] , - onTap: () { - - },); - }, - ), - ), - ), - ], - ), + enableMenuSideba: false, + appBarTitle: Row( + children: [ + SvgPicture.asset( + Assets.loginLogo, + width: 150, + ), + ], ), - ), - ); + scaffoldBody: BlocProvider( + create: (context) => HomeBloc(), + child: BlocConsumer( + listener: (BuildContext context, state) {}, + builder: (context, state) { + final homeBloc = BlocProvider.of(context); + return SizedBox( + height: size.height, + width: size.width, + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + SizedBox(height: size.height * 0.1), + Text( + 'ACCESS YOUR APPS', + style: Theme.of(context) + .textTheme.headlineLarge! + .copyWith(color: Colors.black, fontSize: 40), + ), + const SizedBox(height: 30), + Expanded( + flex: 4, + child: SizedBox( + height: size.height * 0.6, + width: size.width * 0.68, + child: GridView.builder( + itemCount: 8, + gridDelegate: + const SliverGridDelegateWithFixedCrossAxisCount( + crossAxisCount: 4, + crossAxisSpacing: 20.0, + mainAxisSpacing: 20.0, + childAspectRatio: 1.5, + ), + itemBuilder: (context, index) { + return HomeCard( + index: index, + active: homeBloc.homeItems[index].active!, + name: homeBloc.homeItems[index].title!, + img: homeBloc.homeItems[index].icon!, + onTap: () => homeBloc.homeItems[index].onPress(context), + ); + }, + ), + ), + ), + ], + ), + ); + }, + ), + )); } - - dynamic ceilingSensorButtons = - [ - { - 'title': 'Access', - 'icon': Assets.accessIcon, - 'active': true, - }, - { - 'title': 'Space\nManagement', - 'icon': Assets.spaseManagementIcon, - 'color': ColorsManager.primaryColor, - 'active': true, - }, - { - 'title': 'Devices', - 'icon':Assets.devicesIcon, - 'active': true, - }, - { - 'title': 'Move in', - 'icon': Assets.moveinIcon, - 'active': false, - }, - { - 'title': 'Construction', - 'icon': Assets.constructionIcon, - 'active': false, - }, - { - 'title': 'Energy', - 'icon': Assets.energyIcon, - 'color': ColorsManager.slidingBlueColor.withOpacity(0.2), - 'active': false, - }, - { - 'title': 'Integrations', - 'icon': Assets.integrationsIcon, - 'color': ColorsManager.slidingBlueColor.withOpacity(0.2), - 'active': false, - }, { - 'title': 'Asset', - 'icon': Assets.assetIcon, - 'color': ColorsManager.slidingBlueColor.withOpacity(0.2), - 'active': false, - }, - ]; } diff --git a/lib/services/home_api.dart b/lib/services/home_api.dart new file mode 100644 index 00000000..29ad30ab --- /dev/null +++ b/lib/services/home_api.dart @@ -0,0 +1,18 @@ + import 'package:syncrow_web/pages/auth/model/user_model.dart'; +import 'package:syncrow_web/services/api/http_service.dart'; +import 'package:syncrow_web/utils/constants/api_const.dart'; + +class HomeApi{ + + Future fetchUserInfo(userId) async { + final response = await HTTPService().get( + path: ApiEndpoints.getUser.replaceAll('{userUuid}', userId!), + showServerMessage: true, + expectedResponseModel: (json) { + print('user=$json'); + return UserModel.fromJson(json); + } + ); + return response; + } + } \ No newline at end of file diff --git a/lib/utils/color_manager.dart b/lib/utils/color_manager.dart index 46cb4383..492e5f8a 100644 --- a/lib/utils/color_manager.dart +++ b/lib/utils/color_manager.dart @@ -29,4 +29,5 @@ abstract class ColorsManager { static const Color textGray = Color(0xffD5D5D5); static const Color btnColor = Color(0xFF00008B); static const Color blueColor = Color(0xFF0036E6); + static const Color boxColor = Color(0xFFF5F6F7); } diff --git a/lib/utils/constants/api_const.dart b/lib/utils/constants/api_const.dart index 855d74e5..4eb6ceed 100644 --- a/lib/utils/constants/api_const.dart +++ b/lib/utils/constants/api_const.dart @@ -9,4 +9,5 @@ abstract class ApiEndpoints { static const String sendOtp = '$baseUrl/authentication/user/send-otp'; static const String verifyOtp = '$baseUrl/authentication/user/verify-otp'; static const String getRegion = '$baseUrl/region'; + static const String getUser = '$baseUrl/user/{userUuid}'; } diff --git a/lib/web_layout/web_app_bar.dart b/lib/web_layout/web_app_bar.dart index 4163e168..f9316f05 100644 --- a/lib/web_layout/web_app_bar.dart +++ b/lib/web_layout/web_app_bar.dart @@ -1,4 +1,7 @@ import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:syncrow_web/pages/home/bloc/home_bloc.dart'; +import 'package:syncrow_web/pages/home/bloc/home_state.dart'; import 'package:syncrow_web/utils/color_manager.dart'; class WebAppBar extends StatelessWidget { @@ -8,7 +11,8 @@ class WebAppBar extends StatelessWidget { @override Widget build(BuildContext context) { - return Container( + return BlocBuilder(builder: (context, state) { + return Container( height: 120, decoration: const BoxDecoration(color: ColorsManager.secondaryColor), padding: const EdgeInsets.all(10), @@ -48,8 +52,9 @@ class WebAppBar extends StatelessWidget { const SizedBox( width: 10, ), + if(HomeBloc.user!=null) Text( - 'mohamamd alnemer ', + '${HomeBloc.user!.firstName.toString() ?? ''} ${HomeBloc.user!.lastName.toString() ?? ''} ', style: Theme.of(context).textTheme.bodyLarge, ), ], @@ -58,5 +63,6 @@ class WebAppBar extends StatelessWidget { ), ), ); + }); } } diff --git a/pubspec.lock b/pubspec.lock index ba0d48bf..d22f2d41 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -65,6 +65,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.0.8" + data_table_2: + dependency: "direct main" + description: + name: data_table_2 + sha256: f02ec9b24f44420816a87370ff4f4e533e15b274f6267e4c9a88a585ad1a0473 + url: "https://pub.dev" + source: hosted + version: "2.5.15" dio: dependency: "direct main" description: diff --git a/pubspec.yaml b/pubspec.yaml index 7f55b18e..b4c3f411 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -43,6 +43,7 @@ dependencies: get_it: ^7.6.7 flutter_secure_storage: ^9.2.2 shared_preferences: ^2.3.0 + data_table_2: ^2.5.15 dev_dependencies: flutter_test: From cb0ebcca3742cd38e000326ef5546f3af1e89a6b Mon Sep 17 00:00:00 2001 From: mohammad Date: Mon, 12 Aug 2024 14:36:46 +0300 Subject: [PATCH 02/32] region and access_management ui --- assets/dome.json | 88 +++++ assets/images/calendar_icon.svg | 13 + lib/main.dart | 6 +- .../access_management/access_management.dart | 142 --------- .../access_management/bloc/access_bloc.dart | 26 ++ .../access_management/bloc/access_event.dart | 11 + .../access_management/bloc/access_state.dart | 31 ++ .../model/access_manag_model.dart | 37 +++ .../view/access_management.dart | 301 ++++++++++++++++++ lib/pages/auth/bloc/auth_bloc.dart | 50 ++- lib/pages/auth/bloc/auth_event.dart | 14 +- lib/pages/auth/bloc/auth_state.dart | 10 + .../auth/model/login_with_email_model.dart | 4 + .../auth/view/forget_password_web_page.dart | 19 +- lib/pages/auth/view/login_mobile_page.dart | 16 +- lib/pages/auth/view/login_web_page.dart | 15 +- lib/pages/common/default_button.dart | 6 - lib/pages/home/bloc/home_bloc.dart | 3 +- lib/services/access_mang_api.dart | 36 +++ lib/services/api/http_interceptor.dart | 3 +- lib/services/auth_api.dart | 56 +++- lib/services/home_api.dart | 2 - lib/utils/color_manager.dart | 1 + lib/utils/constants/assets.dart | 1 + lib/utils/style.dart | 13 +- lib/web_layout/web_app_bar.dart | 2 +- lib/web_layout/web_scaffold.dart | 2 +- pubspec.yaml | 1 + 28 files changed, 703 insertions(+), 206 deletions(-) create mode 100644 assets/dome.json create mode 100644 assets/images/calendar_icon.svg delete mode 100644 lib/pages/access_management/access_management.dart create mode 100644 lib/pages/access_management/bloc/access_bloc.dart create mode 100644 lib/pages/access_management/bloc/access_event.dart create mode 100644 lib/pages/access_management/bloc/access_state.dart create mode 100644 lib/pages/access_management/model/access_manag_model.dart create mode 100644 lib/pages/access_management/view/access_management.dart create mode 100644 lib/services/access_mang_api.dart diff --git a/assets/dome.json b/assets/dome.json new file mode 100644 index 00000000..39e6ee7c --- /dev/null +++ b/assets/dome.json @@ -0,0 +1,88 @@ +[ + { + "accessUser": "Ali Doe", + "accessType": "Admin", + "accessPeriod": "2023-08-01", + "accessibleDevice": "Smart Door", + "authorizationSource": "System", + "authorizer": "Jane Smith", + "authorizationTime": "2023-08-01 10:00 AM", + "accessStatus": "Granted", + "actions": "View" + }, { + "accessUser": "oamr Doe", + "accessType": "Admin", + "accessPeriod": "2023-08-01", + "accessibleDevice": "Smart Door", + "authorizationSource": "System", + "authorizer": "Jane Smith", + "authorizationTime": "2023-08-01 10:00 AM", + "accessStatus": "Granted", + "actions": "View" + }, { + "accessUser": "John Doe", + "accessType": "Admin", + "accessPeriod": "2023-08-01", + "accessibleDevice": "Smart Door", + "authorizationSource": "System", + "authorizer": "Jane Smith", + "authorizationTime": "2023-08-01 10:00 AM", + "accessStatus": "Granted", + "actions": "View" + }, + + { + "accessUser": "John Doe", + "accessType": "Admin", + "accessPeriod": "2023-08-01", + "accessibleDevice": "Smart Door", + "authorizationSource": "System", + "authorizer": "Jane Smith", + "authorizationTime": "2023-08-01 10:00 AM", + "accessStatus": "Granted", + "actions": "View" + }, + { + "accessUser": "John Doe", + "accessType": "Admin", + "accessPeriod": "2023-08-01", + "accessibleDevice": "Smart Door", + "authorizationSource": "System", + "authorizer": "Jane Smith", + "authorizationTime": "2023-08-01 10:00 AM", + "accessStatus": "Granted", + "actions": "View" + }, + { + "accessUser": "John Doe", + "accessType": "Admin", + "accessPeriod": "2023-08-01 ", + "accessibleDevice": "Smart Door", + "authorizationSource": "System", + "authorizer": "Jane Smith", + "authorizationTime": "2023-08-01 10:00 AM", + "accessStatus": "Granted", + "actions": "View" + }, { + "accessUser": "John Doe", + "accessType": "Admin", + "accessPeriod": "2023-08-01 ", + "accessibleDevice": "Smart Door", + "authorizationSource": "System", + "authorizer": "Jane Smith", + "authorizationTime": "2023-08-01 10:00 AM", + "accessStatus": "Granted", + "actions": "View" + }, + { + "accessUser": "Alice Johnson", + "accessType": "User", + "accessPeriod": "2023-08-01 to 2023-08-31", + "accessibleDevice": "Smart Lock", + "authorizationSource": "Admin", + "authorizer": "John Doe", + "authorizationTime": "2023-08-02 11:00 AM", + "accessStatus": "Pending", + "actions": "Approve" + } +] diff --git a/assets/images/calendar_icon.svg b/assets/images/calendar_icon.svg new file mode 100644 index 00000000..bdf23bee --- /dev/null +++ b/assets/images/calendar_icon.svg @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/lib/main.dart b/lib/main.dart index 14d48c3e..615851eb 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -29,9 +29,7 @@ class MyApp extends StatelessWidget { providers: [ BlocProvider(create: (context) => HomeBloc()), ], - child: - - MaterialApp( + child: MaterialApp( debugShowCheckedModeBanner: false, // Hide debug banner scrollBehavior: const MaterialScrollBehavior().copyWith( dragDevices: { @@ -58,7 +56,7 @@ class MyApp extends StatelessWidget { colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple), // Set up color scheme useMaterial3: true, // Enable Material 3 ), - home: isLoggedIn == 'Success' ? const HomePage() : const LoginPage(), + home: isLoggedIn == 'Success' ? const HomePage() : const LoginPage(), )); } } diff --git a/lib/pages/access_management/access_management.dart b/lib/pages/access_management/access_management.dart deleted file mode 100644 index f7fee005..00000000 --- a/lib/pages/access_management/access_management.dart +++ /dev/null @@ -1,142 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:flutter_svg/svg.dart'; -import 'package:syncrow_web/utils/color_manager.dart'; -import 'package:syncrow_web/utils/constants/assets.dart'; -import 'package:syncrow_web/web_layout/web_scaffold.dart'; - -class AccessManagementPage extends StatelessWidget { - const AccessManagementPage({super.key}); - - @override - Widget build(BuildContext context) { - Size size = MediaQuery.of(context).size; - - return WebScaffold( - enableMenuSideba: false, - appBarTitle: Row( - children: [ - Text( - 'Access Management', - style: Theme.of(context).textTheme.headlineLarge, - ) - ], - ), - appBarBody: [ - Text( - 'Physical Access', - style: Theme.of(context) - .textTheme - .headlineMedium! - .copyWith(color: Colors.white), - ), - Text( - 'App Access', - style: Theme.of(context) - .textTheme - .headlineMedium! - .copyWith(color: Colors.white), - ) - ], - scaffoldBody: Container( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Container( - width:size.width*0.3, - height: size.height*0.05, - decoration: BoxDecoration( - boxShadow: [ - BoxShadow( - color: Colors.grey.withOpacity(0.5), - spreadRadius: 5, - blurRadius: 7, - offset: Offset(0, 3), // changes position of shadow - ), - ], - color: ColorsManager.boxColor, - borderRadius: BorderRadius.all(Radius.circular(10))), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceAround, - children: [ - Text('All'), - Text('To Be Effective (0)'), - Text('Effective (0)'), - Text('Expired'), - ], - ), - ), - SizedBox(height: 10,), - Row( - children: [ - Container( - width:size.width*0.08, - height: size.height*0.05, - decoration: BoxDecoration( - boxShadow: [ - BoxShadow( - color: Colors.grey.withOpacity(0.5), - spreadRadius: 5, - blurRadius: 7, - offset: Offset(0, 3), // changes position of shadow - ), - ], - color: ColorsManager.boxColor, - borderRadius: BorderRadius.all(Radius.circular(10))), - child: TextFormField() - ), - Container( - width:size.width*0.08, - height: size.height*0.05, - decoration: BoxDecoration( - boxShadow: [ - BoxShadow( - color: Colors.grey.withOpacity(0.5), - spreadRadius: 5, - blurRadius: 7, - offset: Offset(0, 3), // changes position of shadow - ), - ], - color: ColorsManager.boxColor, - borderRadius: BorderRadius.all(Radius.circular(10))), - child: TextFormField() - ), - Container( - width:size.width*0.08, - height: size.height*0.05, - decoration: BoxDecoration( - boxShadow: [ - BoxShadow( - color: Colors.grey.withOpacity(0.5), - spreadRadius: 5, - blurRadius: 7, - offset: Offset(0, 3), // changes position of shadow - ), - ], - color: ColorsManager.boxColor, - borderRadius: BorderRadius.all(Radius.circular(10))), - child: TextFormField() - ), - Container( - width:size.width*0.08, - height: size.height*0.05, - decoration: BoxDecoration( - boxShadow: [ - BoxShadow( - color: Colors.grey.withOpacity(0.5), - spreadRadius: 5, - blurRadius: 7, - offset: Offset(0, 3), // changes position of shadow - ), - ], - color: ColorsManager.boxColor, - borderRadius: BorderRadius.all(Radius.circular(10))), - child: TextFormField() - ) - ], - ), - - ], - ), - )); - } -} diff --git a/lib/pages/access_management/bloc/access_bloc.dart b/lib/pages/access_management/bloc/access_bloc.dart new file mode 100644 index 00000000..8938fc36 --- /dev/null +++ b/lib/pages/access_management/bloc/access_bloc.dart @@ -0,0 +1,26 @@ +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:syncrow_web/pages/access_management/bloc/access_event.dart'; +import 'package:syncrow_web/pages/access_management/bloc/access_state.dart'; +import 'package:syncrow_web/pages/access_management/model/access_manag_model.dart'; +import 'package:syncrow_web/services/access_mang_api.dart'; + +class AccessBloc extends Bloc { + AccessBloc() : super((AccessInitial())) { + on(_onFetchTableData); + } + String startTime = 'Start Time'; + String endTime = 'End Time'; + + Future _onFetchTableData( + FetchTableData event, Emitter emit) async { + try { + emit(AccessLoaded()); + List data = await AccessMangApi().fetchInfo(); + print('objectwww888888${data[0].accessPeriod}'); + + emit(TableLoaded(data)); + } catch (e) { + emit(FailedState(e.toString())); + } + } +} diff --git a/lib/pages/access_management/bloc/access_event.dart b/lib/pages/access_management/bloc/access_event.dart new file mode 100644 index 00000000..60512387 --- /dev/null +++ b/lib/pages/access_management/bloc/access_event.dart @@ -0,0 +1,11 @@ + +import 'package:equatable/equatable.dart'; + +abstract class AccessEvent extends Equatable { + const AccessEvent(); + + @override + List get props => []; +} +class FetchTableData extends AccessEvent {} + diff --git a/lib/pages/access_management/bloc/access_state.dart b/lib/pages/access_management/bloc/access_state.dart new file mode 100644 index 00000000..94bbc90c --- /dev/null +++ b/lib/pages/access_management/bloc/access_state.dart @@ -0,0 +1,31 @@ +import 'package:equatable/equatable.dart'; +import 'package:syncrow_web/pages/access_management/model/access_manag_model.dart'; + +abstract class AccessState extends Equatable { + const AccessState(); + + @override + List get props => []; +} + +class AccessInitial extends AccessState {} + +class AccessLoaded extends AccessState {} +class FailedState extends AccessState { + final String message; + + FailedState(this.message); + + @override + List get props => [message]; +} + +class TableLoaded extends AccessState { + final List data; + + const TableLoaded(this.data); + + @override + List get props => [data]; +} + diff --git a/lib/pages/access_management/model/access_manag_model.dart b/lib/pages/access_management/model/access_manag_model.dart new file mode 100644 index 00000000..ef468c8e --- /dev/null +++ b/lib/pages/access_management/model/access_manag_model.dart @@ -0,0 +1,37 @@ +class AccessManagModel { + final String accessUser; + final String accessType; + final String accessPeriod; + final String accessibleDevice; + final String authorizationSource; + final String authorizer; + final String authorizationTime; + final String accessStatus; + final String actions; + + AccessManagModel({ + required this.accessUser, + required this.accessType, + required this.accessPeriod, + required this.accessibleDevice, + required this.authorizationSource, + required this.authorizer, + required this.authorizationTime, + required this.accessStatus, + required this.actions, + }); + + factory AccessManagModel.fromJson(Map json) { + return AccessManagModel( + accessUser: json['accessUser'], + accessType: json['accessType'], + accessPeriod: json['accessPeriod'], + accessibleDevice: json['accessibleDevice'], + authorizationSource: json['authorizationSource'], + authorizer: json['authorizer'], + authorizationTime: json['authorizationTime'], + accessStatus: json['accessStatus'], + actions: json['actions'], + ); + } +} diff --git a/lib/pages/access_management/view/access_management.dart b/lib/pages/access_management/view/access_management.dart new file mode 100644 index 00000000..00856a99 --- /dev/null +++ b/lib/pages/access_management/view/access_management.dart @@ -0,0 +1,301 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:flutter_svg/flutter_svg.dart'; +import 'package:syncrow_web/pages/access_management/bloc/access_bloc.dart'; +import 'package:syncrow_web/pages/access_management/bloc/access_event.dart'; +import 'package:syncrow_web/pages/access_management/bloc/access_state.dart'; +import 'package:syncrow_web/pages/common/default_button.dart'; +import 'package:syncrow_web/utils/color_manager.dart'; +import 'package:syncrow_web/utils/constants/assets.dart'; +import 'package:syncrow_web/utils/style.dart'; +import 'package:syncrow_web/web_layout/web_scaffold.dart'; + +class AccessManagementPage extends StatelessWidget { + const AccessManagementPage({super.key}); + @override + Widget build(BuildContext context) { + Size size = MediaQuery.of(context).size; + return WebScaffold( + enableMenuSideba: false, + appBarTitle: Row( + children: [ + Text( + 'Access Management', + style: Theme.of(context).textTheme.headlineLarge, + ) + ], + ), + appBarBody: [ + Text( + 'Physical Access', + style: Theme.of(context) + .textTheme + .headlineMedium! + .copyWith(color: Colors.white), + ), + ], + scaffoldBody: BlocProvider( + create: (BuildContext context) => AccessBloc()..add(FetchTableData() ), + child: BlocConsumer( + listener: (context, state) { + if (state is FailedState) { + // CustomSnackBar.displaySnackBar( + // state.errorMessage + // ); + } + }, builder: (context, state) { + return Container( + padding: EdgeInsets.all(30), + height: size.height, + width: size.width, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Container( + width: size.width * 0.3, + height: size.height * 0.05, + decoration: containerDecoration, + child: const Row( + mainAxisAlignment: MainAxisAlignment.spaceAround, + children: [ + Text('All'), + Text('To Be Effective (0)'), + Text('Effective (0)'), + Text('Expired'), + ], + ), + ), + const SizedBox( + height: 20, + ), + Wrap( + children: [ + Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.start, + children: [ + Text('User Name'), + Container( + width: size.width*0.15, + decoration: containerDecoration, + child: TextFormField( + decoration: textBoxDecoration()! + .copyWith(hintText: 'Please enter'), + )), + ], + ), + const SizedBox(width: 15,), + Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.start, + children: [ + const Text('Email Address'), + Container( + width: size.width*0.15, + decoration: containerDecoration, + child: TextFormField( + decoration: textBoxDecoration()! + .copyWith(hintText: 'Please enter'), + )), + ], + ), + const SizedBox(width: 15,), + Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.start, + children: [ + const Text('Access Time'), + Container( + width: size.width*0.18, + padding: EdgeInsets.all(10), + decoration: containerDecoration, + child: Column( + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + InkWell(child: Text(BlocProvider.of(context).startTime)), + const Icon(Icons.arrow_right_alt), + InkWell(child: Text(BlocProvider.of(context).endTime)), + SvgPicture.asset( + Assets.calendarIcon, + ), + ], + ), + ], + )), + ], + ), + const SizedBox(width: 15,), + Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.start, + children: [ + const Text('Authorization Source'), + Container( + width: size.width*0.18, + decoration: containerDecoration, + child: TextFormField( + decoration: textBoxDecoration(), + )), + ], + ), + const SizedBox(width: 15,), + SizedBox( + width: size.width*0.06, + child: Column( + children: [ + Text(''), + Container( + decoration: containerDecoration, + + child: DefaultButton(child: Text('Search'),borderRadius: 9)), + ], + ), + ), + const SizedBox(width: 10,), + SizedBox( + width: size.width*0.06, + child: Column( + children: [ + Text(''), + Container( + decoration: containerDecoration, + child: DefaultButton( + backgroundColor: ColorsManager.whiteColors,borderRadius: 9, + child: Text('Reset' + ,style: Theme.of(context).textTheme.bodySmall!.copyWith(color: Colors.black),) + ,), + ), + ], + ), + ), + ], + ), + const SizedBox( + height: 20, + ), + Wrap(children: [ + Container( + width: size.width*0.15, + decoration: containerDecoration, + child: const DefaultButton( + borderRadius: 8, + child: Text('+ Create Visitor Password ')), + ), + const SizedBox(width: 10,), + Container( + width: size.width*0.12, + decoration: containerDecoration, + child: DefaultButton( + borderRadius: 8, + backgroundColor: ColorsManager.whiteColors, + child: Text('Admin Password' + ,style: Theme.of(context).textTheme.bodySmall!.copyWith(color: Colors.black),) + )) + ],), + const SizedBox( + height: 20, + ), + Expanded( + child:state is TableLoaded? + Container( + decoration: containerDecoration, + width: size.width, + child: Padding( + padding: const EdgeInsets.all(10.0), + child: ListView( + scrollDirection: Axis.horizontal, + children: [ + Container( + width: size.width, + height:size.height , + child: Column( + children: [ + + Container( + color: ColorsManager.boxColor, + child: Row( + children: [ + _buildTableHeaderCell('Access User'), + _buildTableHeaderCell('Access Type'), + _buildTableHeaderCell('Access Period'), + _buildTableHeaderCell('Accessible Device'), + _buildTableHeaderCell('Authorization Source'), + _buildTableHeaderCell('Authorizer'), + _buildTableHeaderCell('Authorization Time'), + _buildTableHeaderCell('Access Status'), + _buildTableHeaderCell('Actions'), + ], + ), + ), + + Expanded( + child: Container( + width: size.width, + color: ColorsManager.whiteColors, + child: ListView( + shrinkWrap: true, + children: [ + Column( + children: state.data.map((item) { + return Row( + children: [ + _buildTableCell(item.accessUser), + _buildTableCell(item.accessType), + _buildTableCell(item.accessPeriod), + _buildTableCell(item.accessibleDevice), + _buildTableCell(item.authorizationSource), + _buildTableCell(item.authorizer), + _buildTableCell(item.authorizationTime), + _buildTableCell(item.accessStatus), + _buildTableCell(item.actions), + ], + ); + }).toList(), + ), + ], + ), + ), + ), + ], + ), + ), + ], + ), + ), + ):const Center(child: CircularProgressIndicator()) + ) + ], + ), + ); + }))); + } +} +Widget _buildTableHeaderCell(String title) { + return Expanded( + child: Container( + decoration: const BoxDecoration( + border: Border.symmetric(vertical: BorderSide(color: ColorsManager.boxDivider)) + ), + alignment: Alignment.centerLeft, + child: Padding( + padding: const EdgeInsets.all(8.0), + child: Text(title, style: TextStyle(fontWeight: FontWeight.bold)), + ), + ), + ); +} + +Widget _buildTableCell(String content) { + return Expanded( + child: Container( + padding: const EdgeInsets.all(20.0), + decoration: const BoxDecoration( + border: Border.symmetric(horizontal: BorderSide(color: ColorsManager.boxDivider)) + ), + alignment: Alignment.centerLeft, + child: Text(content,style: TextStyle(color: Colors.black,fontSize: 12),), + ), + ); +} diff --git a/lib/pages/auth/bloc/auth_bloc.dart b/lib/pages/auth/bloc/auth_bloc.dart index 00f6227f..7e57f7ab 100644 --- a/lib/pages/auth/bloc/auth_bloc.dart +++ b/lib/pages/auth/bloc/auth_bloc.dart @@ -23,6 +23,7 @@ class AuthBloc extends Bloc { on(_onUpdateTimer); on(_passwordVisible); on(_fetchRegion); + on(selectRegion); } ////////////////////////////// forget password ////////////////////////////////// @@ -32,7 +33,7 @@ class AuthBloc extends Bloc { final forgetFormKey = GlobalKey(); Timer? _timer; int _remainingTime = 0; - List? regionList; + List? regionList=[RegionModel(name: 'name', id: 'id')]; Future _onStartTimer(StartTimerEvent event, Emitter emit) async { if (_validateInputs(emit)) return; @@ -42,7 +43,8 @@ class AuthBloc extends Bloc { _remainingTime = 60; add(UpdateTimerEvent( remainingTime: _remainingTime, isButtonEnabled: false)); - await AuthenticationAPI.sendOtp(email: forgetEmailController.text); + debugPrint('_remainingTime=$_remainingTime'); + _remainingTime = (await AuthenticationAPI.sendOtp(email: forgetEmailController.text,regionUuid: regionUuid))!; _timer = Timer.periodic(const Duration(seconds: 1), (timer) { _remainingTime--; if (_remainingTime <= 0) { @@ -99,6 +101,7 @@ class AuthBloc extends Bloc { String maskedEmail = ''; String otpCode = ''; String validate = ''; + String regionUuid = ''; static Token token = Token.emptyConstructor(); static UserModel? user; bool showValidationMessage = false; @@ -116,6 +119,7 @@ class AuthBloc extends Bloc { model: LoginWithEmailModel( email: event.username, password: event.password, + regionUuid: event.regionUuid ), ); } catch (failure) { @@ -274,15 +278,7 @@ class AuthBloc extends Bloc { return '$maskedLocalPart@$domainPart'; } - final List regions = [ - 'North America', - 'South America', - 'Europe', - 'Asia', - 'Africa', - 'Australia', - 'Antarctica', - ]; + static Future getTokenAndValidate() async { @@ -317,8 +313,8 @@ class AuthBloc extends Bloc { void _fetchRegion(RegionInitialEvent event, Emitter emit) async { try { emit(AuthLoading()); - regionList = await AuthenticationAPI.fetchRegion(); - emit(LoginSuccess()); + regionList = await AuthenticationAPI.fetchRegion(); + emit(AuthInitialState()); } catch (e) { emit( LoginFailure(error: e.toString())); @@ -326,6 +322,34 @@ class AuthBloc extends Bloc { } + Future selectRegion(SelectRegionEvent event, Emitter emit) async { + try { + emit(AuthLoading()); + regionUuid= event.val; + emit(AuthInitialState()); + } catch (e) { + emit(FailureForgetState(error: e.toString())); + return; + } + } + + String formattedTime(int time) { + final int days = (time / 86400).floor(); // 86400 seconds in a day + final int hours = ((time % 86400) / 3600).floor(); + final int minutes = (((time % 86400) % 3600) / 60).floor(); + final int seconds = (((time % 86400) % 3600) % 60).floor(); + + final String formattedTime = [ + if (days > 0) '${days}d', // Append 'd' for days + if (days > 0 || hours > 0) hours.toString().padLeft(2, '0'), // Show hours if there are days or hours + minutes.toString().padLeft(2, '0'), + seconds.toString().padLeft(2, '0'), + ].join(':'); + + return formattedTime; + } + + } diff --git a/lib/pages/auth/bloc/auth_event.dart b/lib/pages/auth/bloc/auth_event.dart index 8a410555..50cd5c56 100644 --- a/lib/pages/auth/bloc/auth_event.dart +++ b/lib/pages/auth/bloc/auth_event.dart @@ -1,4 +1,5 @@ import 'package:equatable/equatable.dart'; +import 'package:flutter/material.dart'; abstract class AuthEvent extends Equatable { const AuthEvent(); @@ -10,11 +11,12 @@ abstract class AuthEvent extends Equatable { class LoginButtonPressed extends AuthEvent { final String username; final String password; + final String regionUuid; - const LoginButtonPressed({required this.username, required this.password}); + const LoginButtonPressed({required this.username, required this.password, required this.regionUuid}); @override - List get props => [username, password]; + List get props => [username, password,regionUuid]; } class CheckBoxEvent extends AuthEvent { @@ -52,4 +54,10 @@ class PasswordVisibleEvent extends AuthEvent{ class RegionInitialEvent extends AuthEvent {} -class SelectRegionEvent extends AuthEvent {} +class SelectRegionEvent extends AuthEvent { + final String val; + const SelectRegionEvent({required this.val}); + @override + List get props => [val]; +} + diff --git a/lib/pages/auth/bloc/auth_state.dart b/lib/pages/auth/bloc/auth_state.dart index 4b6359eb..973ee400 100644 --- a/lib/pages/auth/bloc/auth_state.dart +++ b/lib/pages/auth/bloc/auth_state.dart @@ -12,6 +12,7 @@ class LoginInitial extends AuthState {} class AuthTokenLoading extends AuthState {} class AuthLoading extends AuthState {} +class AuthInitialState extends AuthState {} class LoginSuccess extends AuthState {} @@ -73,3 +74,12 @@ class AuthTokenError extends AuthError { class AuthSuccess extends AuthState {} class AuthTokenSuccess extends AuthSuccess {} +class TimerUpdated extends AuthState { + final String formattedTime; + final bool isButtonEnabled; + + TimerUpdated({ + required this.formattedTime, + required this.isButtonEnabled, + }); +} diff --git a/lib/pages/auth/model/login_with_email_model.dart b/lib/pages/auth/model/login_with_email_model.dart index c387b0d2..88be9808 100644 --- a/lib/pages/auth/model/login_with_email_model.dart +++ b/lib/pages/auth/model/login_with_email_model.dart @@ -1,16 +1,19 @@ class LoginWithEmailModel { final String email; final String password; + final String regionUuid; LoginWithEmailModel({ required this.email, required this.password, + required this.regionUuid, }); factory LoginWithEmailModel.fromJson(Map json) { return LoginWithEmailModel( email: json['email'], password: json['password'], + regionUuid: json['regionUuid'], ); } @@ -18,6 +21,7 @@ class LoginWithEmailModel { return { 'email': email, 'password': password, + 'regionUuid': regionUuid, }; } } diff --git a/lib/pages/auth/view/forget_password_web_page.dart b/lib/pages/auth/view/forget_password_web_page.dart index d24691b6..0ae96d02 100644 --- a/lib/pages/auth/view/forget_password_web_page.dart +++ b/lib/pages/auth/view/forget_password_web_page.dart @@ -4,6 +4,7 @@ import 'package:flutter_svg/flutter_svg.dart'; import 'package:syncrow_web/pages/auth/bloc/auth_bloc.dart'; import 'package:syncrow_web/pages/auth/bloc/auth_event.dart'; import 'package:syncrow_web/pages/auth/bloc/auth_state.dart'; +import 'package:syncrow_web/pages/auth/model/region_model.dart'; import 'package:syncrow_web/pages/common/default_button.dart'; import 'package:syncrow_web/pages/common/first_layer.dart'; import 'package:syncrow_web/utils/color_manager.dart'; @@ -13,9 +14,10 @@ class ForgetPasswordWebPage extends StatelessWidget { const ForgetPasswordWebPage({super.key}); @override Widget build(BuildContext context) { + return Scaffold( body: BlocProvider( - create: (context) => AuthBloc(), + create: (context) => AuthBloc()..add(RegionInitialEvent()), child: BlocConsumer( listener: (context, state) { if (state is SuccessForgetState){ @@ -30,7 +32,7 @@ class ForgetPasswordWebPage extends StatelessWidget { } }, builder: (context, state) { - if (state is LoadingForgetState) { + if (state is AuthLoading) { return const Center(child: CircularProgressIndicator()); } else { return _buildForm(context, state); @@ -147,14 +149,14 @@ class ForgetPasswordWebPage extends StatelessWidget { ), isDense: true, style: const TextStyle(color: Colors.black), - items: forgetBloc.regions.map((String region) { + items:forgetBloc.regionList!.map((RegionModel region) { return DropdownMenuItem( - value: region, - child: Text(region), + value: region.id, + child: Text(region.name), ); }).toList(), onChanged: (String? value) { - print(value); + forgetBloc.add(SelectRegionEvent(val: value!,)); }, ), ) @@ -201,8 +203,9 @@ class ForgetPasswordWebPage extends StatelessWidget { onTap: () { BlocProvider.of(context).add(StartTimerEvent()); }, - child: Text( - 'Get Code ${state is TimerState && !state.isButtonEnabled ? "(${state.remainingTime.toString()})" : ""}', + child: + Text( + 'Get Code ${state is TimerState && !state.isButtonEnabled ? "(${ BlocProvider.of(context).formattedTime(state.remainingTime)})" : ""}', style: TextStyle( color: state is TimerState && !state.isButtonEnabled ? Colors.grey diff --git a/lib/pages/auth/view/login_mobile_page.dart b/lib/pages/auth/view/login_mobile_page.dart index a2877bf5..50684bab 100644 --- a/lib/pages/auth/view/login_mobile_page.dart +++ b/lib/pages/auth/view/login_mobile_page.dart @@ -6,6 +6,7 @@ import 'package:flutter_svg/svg.dart'; import 'package:syncrow_web/pages/auth/bloc/auth_bloc.dart'; import 'package:syncrow_web/pages/auth/bloc/auth_event.dart'; import 'package:syncrow_web/pages/auth/bloc/auth_state.dart'; +import 'package:syncrow_web/pages/auth/model/region_model.dart'; import 'package:syncrow_web/pages/auth/view/forget_password_page.dart'; import 'package:syncrow_web/pages/common/default_button.dart'; import 'package:syncrow_web/utils/color_manager.dart'; @@ -145,13 +146,13 @@ class LoginMobilePage extends StatelessWidget { textAlign: TextAlign.center, ), ), + isDense: true, style: const TextStyle(color: Colors.black), - items: - loginBloc.regions.map((String region) { + items:loginBloc.regionList!.map((RegionModel region) { return DropdownMenuItem( - value: region, - child: Text(region), + value: region.name, + child: Text(region.name), ); }).toList(), onChanged: (String? value) { @@ -300,10 +301,9 @@ class LoginMobilePage extends StatelessWidget { .validate()) { loginBloc.add( LoginButtonPressed( - username: - loginBloc.loginEmailController.text, - password: - loginBloc.loginPasswordController.text, + regionUuid:'' , + username: loginBloc.loginEmailController.text, + password: loginBloc.loginPasswordController.text, ), ); } diff --git a/lib/pages/auth/view/login_web_page.dart b/lib/pages/auth/view/login_web_page.dart index 1a473df7..bd2259fa 100644 --- a/lib/pages/auth/view/login_web_page.dart +++ b/lib/pages/auth/view/login_web_page.dart @@ -7,6 +7,7 @@ import 'package:flutter_svg/svg.dart'; import 'package:syncrow_web/pages/auth/bloc/auth_bloc.dart'; import 'package:syncrow_web/pages/auth/bloc/auth_event.dart'; import 'package:syncrow_web/pages/auth/bloc/auth_state.dart'; +import 'package:syncrow_web/pages/auth/model/region_model.dart'; import 'package:syncrow_web/pages/auth/view/forget_password_page.dart'; import 'package:syncrow_web/pages/common/default_button.dart'; import 'package:syncrow_web/pages/common/first_layer.dart'; @@ -28,7 +29,7 @@ class _LoginWebPageState extends State { Widget build(BuildContext context) { return Scaffold( body: BlocProvider( - create: (BuildContext context) => AuthBloc(), + create: (BuildContext context) => AuthBloc()..add(RegionInitialEvent()), child: BlocConsumer( listener: (context, state) { if (state is LoginSuccess) { @@ -153,13 +154,16 @@ class _LoginWebPageState extends State { ), isDense: true, style: const TextStyle(color: Colors.black), - items:loginBloc.regions.map((String region) { + items:loginBloc.regionList!.map((RegionModel region) { return DropdownMenuItem( - value: region, - child: Text(region), + value: region.id, + child: Text(region.name), ); }).toList(), - onChanged: (String? value) {}, + onChanged: (String? value) { + + loginBloc.add(SelectRegionEvent(val: value!,)); + }, ), ) ], @@ -310,6 +314,7 @@ class _LoginWebPageState extends State { onPressed: () { if (loginBloc.loginFormKey.currentState!.validate()) { loginBloc.add(LoginButtonPressed( + regionUuid:loginBloc.regionUuid , username: loginBloc.loginEmailController.text, password: loginBloc.loginPasswordController.text, ), diff --git a/lib/pages/common/default_button.dart b/lib/pages/common/default_button.dart index d588acb9..02254828 100644 --- a/lib/pages/common/default_button.dart +++ b/lib/pages/common/default_button.dart @@ -18,7 +18,6 @@ class DefaultButton extends StatelessWidget { this.height, this.padding, }); - final void Function()? onPressed; final Widget child; final double? height; @@ -28,15 +27,10 @@ class DefaultButton extends StatelessWidget { final double? padding; final bool isDone; final bool isLoading; - final TextStyle? customTextStyle; - final ButtonStyle? customButtonStyle; - final Color? backgroundColor; - final Color? foregroundColor; - @override Widget build(BuildContext context) { return ElevatedButton( diff --git a/lib/pages/home/bloc/home_bloc.dart b/lib/pages/home/bloc/home_bloc.dart index f261c39a..db233d3c 100644 --- a/lib/pages/home/bloc/home_bloc.dart +++ b/lib/pages/home/bloc/home_bloc.dart @@ -3,7 +3,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_secure_storage/flutter_secure_storage.dart'; import 'package:graphview/GraphView.dart'; -import 'package:syncrow_web/pages/access_management/access_management.dart'; +import 'package:syncrow_web/pages/access_management/view/access_management.dart'; import 'package:syncrow_web/pages/auth/model/user_model.dart'; import 'package:syncrow_web/pages/home/bloc/home_event.dart'; import 'package:syncrow_web/pages/home/bloc/home_state.dart'; @@ -38,7 +38,6 @@ class HomeBloc extends Bloc { ..levelSeparation = (150) ..subtreeSeparation = (150) ..orientation = (BuchheimWalkerConfiguration.ORIENTATION_TOP_BOTTOM); - emit(HomeUpdateTree(graph: graph, builder: builder)); } diff --git a/lib/services/access_mang_api.dart b/lib/services/access_mang_api.dart new file mode 100644 index 00000000..0882e98b --- /dev/null +++ b/lib/services/access_mang_api.dart @@ -0,0 +1,36 @@ +import 'dart:convert'; + +import 'package:flutter/services.dart'; +import 'package:syncrow_web/pages/access_management/model/access_manag_model.dart'; +import 'package:syncrow_web/pages/auth/model/user_model.dart'; +import 'package:syncrow_web/services/api/http_service.dart'; +import 'package:syncrow_web/utils/constants/api_const.dart'; + +class AccessMangApi{ + + // Future> fetchInfo() async { + // final response = await HTTPService().get( + // path: '/Users/mohammad/StudioProjects/web_auth/assets/demo.json', + // showServerMessage: true, + // expectedResponseModel: (json) { + // print('fetchInfo=$json'); + // return (json as List).map((item) => AccessManagModel.fromJson(item)).toList(); + // }, + // ); + // return response; + // } + + Future> fetchInfo() async { + // Load the JSON file + final jsonString = await rootBundle.loadString('assets/dome.json'); + + // Parse the JSON string + final List jsonList = json.decode(jsonString); + print('jsonList=${jsonList.runtimeType}'); + print('jsonList=${jsonList}'); + // Convert the list of JSON objects to a list of AccessManagModel instances + final List accessList = jsonList.map((item) => AccessManagModel.fromJson(item)).toList(); + + return accessList; + } +} \ No newline at end of file diff --git a/lib/services/api/http_interceptor.dart b/lib/services/api/http_interceptor.dart index ff3fc0d4..cece39a7 100644 --- a/lib/services/api/http_interceptor.dart +++ b/lib/services/api/http_interceptor.dart @@ -13,7 +13,8 @@ class HTTPInterceptor extends InterceptorsWrapper { List headerExclusionListOfAddedParameters = [ ApiEndpoints.login, - ApiEndpoints.getRegion + ApiEndpoints.getRegion, + ApiEndpoints.sendOtp ]; @override diff --git a/lib/services/auth_api.dart b/lib/services/auth_api.dart index 58a705ff..3c7f3fbf 100644 --- a/lib/services/auth_api.dart +++ b/lib/services/auth_api.dart @@ -1,5 +1,7 @@ import 'dart:convert'; +import 'package:dio/dio.dart'; +import 'package:flutter/foundation.dart'; import 'package:syncrow_web/pages/auth/model/region_model.dart'; import 'package:syncrow_web/pages/auth/model/token.dart'; import 'package:syncrow_web/services/api/http_service.dart'; @@ -8,6 +10,7 @@ import 'package:syncrow_web/utils/constants/api_const.dart'; class AuthenticationAPI { static Future loginWithEmail({required var model}) async { + print('model=$model'); final response = await HTTPService().post( path: ApiEndpoints.login, body: model.toJson(), @@ -19,22 +22,57 @@ class AuthenticationAPI { } static Future forgetPassword( - {required var email, required var password}) async { + {required var email, required var password,}) async { final response = await HTTPService().post( path: ApiEndpoints.forgetPassword, - body: {"email": email, "password": password}, + body: { + "email": email, + "password": password + }, showServerMessage: true, expectedResponseModel: (json) {}); return response; } - static Future sendOtp({required var email}) async { - final response = await HTTPService().post( - path: ApiEndpoints.sendOtp, - body: {"email": email, "type": "VERIFICATION"}, - showServerMessage: true, - expectedResponseModel: (json) {}); - return response; + + static Future sendOtp({required String email, required String regionUuid}) async { + try { + final response = await HTTPService().post( + path: ApiEndpoints.sendOtp, + body: { + "email": email, + "type": "PASSWORD", + "regionUuid": regionUuid + }, + showServerMessage: true, + options: Options(), + expectedResponseModel: (json) { + Map parsedJson = jsonDecode(json); + int cooldown = parsedJson['data']['cooldown']; + debugPrint('Cooldown: $cooldown seconds'); + return cooldown; + } + ); + return response; + } on DioError catch (e) { + if (e.response != null) { + if (e.response!.statusCode == 400) { + // Handle 400 Bad Request + final errorData = e.response!.data; + String errorMessage = errorData['message'] ?? 'Unknown error'; + int cooldown = errorData['data']['cooldown'] ?? 0; + return cooldown; + } else { + debugPrint('Error: ${e.response!.statusCode} - ${e.response!.statusMessage}'); + } + } else { + debugPrint('Error: ${e.message}'); + } + return null; + } catch (e) { + debugPrint('Unexpected Error: $e'); + return null; + } } static Future verifyOtp( diff --git a/lib/services/home_api.dart b/lib/services/home_api.dart index 29ad30ab..42e732b4 100644 --- a/lib/services/home_api.dart +++ b/lib/services/home_api.dart @@ -3,13 +3,11 @@ import 'package:syncrow_web/services/api/http_service.dart'; import 'package:syncrow_web/utils/constants/api_const.dart'; class HomeApi{ - Future fetchUserInfo(userId) async { final response = await HTTPService().get( path: ApiEndpoints.getUser.replaceAll('{userUuid}', userId!), showServerMessage: true, expectedResponseModel: (json) { - print('user=$json'); return UserModel.fromJson(json); } ); diff --git a/lib/utils/color_manager.dart b/lib/utils/color_manager.dart index 492e5f8a..01e539dc 100644 --- a/lib/utils/color_manager.dart +++ b/lib/utils/color_manager.dart @@ -30,4 +30,5 @@ abstract class ColorsManager { static const Color btnColor = Color(0xFF00008B); static const Color blueColor = Color(0xFF0036E6); static const Color boxColor = Color(0xFFF5F6F7); + static const Color boxDivider = Color(0xFFE0E0E0); } diff --git a/lib/utils/constants/assets.dart b/lib/utils/constants/assets.dart index 0d0b3924..d53f82a7 100644 --- a/lib/utils/constants/assets.dart +++ b/lib/utils/constants/assets.dart @@ -23,4 +23,5 @@ class Assets { static const String energyIcon = "assets/images/energy_icon.svg"; static const String integrationsIcon = "assets/images/Integrations_icon.svg"; static const String assetIcon = "assets/images/asset_icon.svg"; + static const String calendarIcon = "assets/images/calendar_icon.svg"; } diff --git a/lib/utils/style.dart b/lib/utils/style.dart index 7260e78d..5edcdfad 100644 --- a/lib/utils/style.dart +++ b/lib/utils/style.dart @@ -23,4 +23,15 @@ InputDecoration? textBoxDecoration({bool suffixIcon = false}) => InputDecoration -Decoration containerDecoration = const BoxDecoration(color: Colors.white,borderRadius: BorderRadius.all(Radius.circular(20))); \ No newline at end of file +Decoration containerDecoration = BoxDecoration( + boxShadow: [ + BoxShadow( + color: Colors.grey.withOpacity(0.5), + spreadRadius: 5, + blurRadius: 7, + offset: Offset(0, + 3), // changes position of shadow + ), + ], + color: ColorsManager.boxColor, + borderRadius: BorderRadius.all(Radius.circular(10))); \ No newline at end of file diff --git a/lib/web_layout/web_app_bar.dart b/lib/web_layout/web_app_bar.dart index f9316f05..7b1ee1a7 100644 --- a/lib/web_layout/web_app_bar.dart +++ b/lib/web_layout/web_app_bar.dart @@ -13,7 +13,7 @@ class WebAppBar extends StatelessWidget { Widget build(BuildContext context) { return BlocBuilder(builder: (context, state) { return Container( - height: 120, + height: 100, decoration: const BoxDecoration(color: ColorsManager.secondaryColor), padding: const EdgeInsets.all(10), child: Expanded( diff --git a/lib/web_layout/web_scaffold.dart b/lib/web_layout/web_scaffold.dart index e190dac1..567710bd 100644 --- a/lib/web_layout/web_scaffold.dart +++ b/lib/web_layout/web_scaffold.dart @@ -29,7 +29,7 @@ class WebScaffold extends StatelessWidget { crossAxisAlignment: CrossAxisAlignment.stretch, children: [ Opacity( - opacity: 0.6, + opacity: 0.7, child: WebAppBar( title: appBarTitle, body: appBarBody, diff --git a/pubspec.yaml b/pubspec.yaml index b4c3f411..c1fd3f66 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -70,6 +70,7 @@ flutter: # To add assets to your application, add an assets section, like this: assets: - assets/images/ + - assets/ # An image asset can refer to one or more resolution-specific "variants", see From 3f1fdf4f930c7f40bbbe896db93ef3a18840fae4 Mon Sep 17 00:00:00 2001 From: mohammad Date: Mon, 12 Aug 2024 15:23:45 +0300 Subject: [PATCH 03/32] region and access_management ui --- lib/pages/auth/bloc/auth_bloc.dart | 9 +- .../auth/view/forget_password_web_page.dart | 498 ++++++++++-------- lib/services/auth_api.dart | 29 +- 3 files changed, 287 insertions(+), 249 deletions(-) diff --git a/lib/pages/auth/bloc/auth_bloc.dart b/lib/pages/auth/bloc/auth_bloc.dart index 7e57f7ab..0a3b0d24 100644 --- a/lib/pages/auth/bloc/auth_bloc.dart +++ b/lib/pages/auth/bloc/auth_bloc.dart @@ -40,10 +40,8 @@ class AuthBloc extends Bloc { if (_timer != null && _timer!.isActive) { return; } - _remainingTime = 60; - add(UpdateTimerEvent( - remainingTime: _remainingTime, isButtonEnabled: false)); - debugPrint('_remainingTime=$_remainingTime'); + _remainingTime = 1; + add(UpdateTimerEvent(remainingTime: _remainingTime, isButtonEnabled: false)); _remainingTime = (await AuthenticationAPI.sendOtp(email: forgetEmailController.text,regionUuid: regionUuid))!; _timer = Timer.periodic(const Duration(seconds: 1), (timer) { _remainingTime--; @@ -125,7 +123,6 @@ class AuthBloc extends Bloc { } catch (failure) { validate='Something went wrong'; emit(const LoginFailure(error: 'Something went wrong')); - // emit(LoginFailure(error: failure.toString())); return; } if (token.accessTokenIsNotEmpty) { @@ -187,6 +184,8 @@ class AuthBloc extends Bloc { return 'Email is required'; } else if (!RegExp(r'^[^@]+@[^@]+\.[^@]+').hasMatch(value)) { return 'Enter a valid email address'; + }else if (regionUuid=='') { + return 'Please select your region'; } return null; } diff --git a/lib/pages/auth/view/forget_password_web_page.dart b/lib/pages/auth/view/forget_password_web_page.dart index 0ae96d02..d87b277f 100644 --- a/lib/pages/auth/view/forget_password_web_page.dart +++ b/lib/pages/auth/view/forget_password_web_page.dart @@ -10,20 +10,19 @@ import 'package:syncrow_web/pages/common/first_layer.dart'; import 'package:syncrow_web/utils/color_manager.dart'; import 'package:syncrow_web/utils/constants/assets.dart'; import 'package:syncrow_web/utils/style.dart'; + class ForgetPasswordWebPage extends StatelessWidget { const ForgetPasswordWebPage({super.key}); @override Widget build(BuildContext context) { - return Scaffold( body: BlocProvider( create: (context) => AuthBloc()..add(RegionInitialEvent()), child: BlocConsumer( listener: (context, state) { - if (state is SuccessForgetState){ + if (state is SuccessForgetState) { Navigator.of(context).pop(); - } - else if (state is FailureForgetState) { + } else if (state is FailureForgetState) { ScaffoldMessenger.of(context).showSnackBar( SnackBar( content: Text(state.error), @@ -47,264 +46,299 @@ class ForgetPasswordWebPage extends StatelessWidget { late ScrollController _scrollController; _scrollController = ScrollController(); void _scrollToCenter() { - final double middlePosition = _scrollController.position.maxScrollExtent / 2; + final double middlePosition = + _scrollController.position.maxScrollExtent / 2; _scrollController.animateTo( middlePosition, duration: const Duration(seconds: 1), curve: Curves.easeInOut, ); } + WidgetsBinding.instance.addPostFrameCallback((_) { _scrollToCenter(); }); final forgetBloc = BlocProvider.of(context); - Size size = MediaQuery.of(context).size; + Size size = MediaQuery.of(context).size; return FirstLayer( - second: Center( - child: ListView( - shrinkWrap: true, - controller: _scrollController, - children: [ - Container( - padding: EdgeInsets.all(size.width*0.02), - margin: EdgeInsets.all(size.width*0.09), - decoration: BoxDecoration( - color: Colors.black.withOpacity(0.3), - borderRadius: const BorderRadius.all(Radius.circular(20)), - ), - child: Center( - child: Row( - crossAxisAlignment: CrossAxisAlignment.center, - mainAxisAlignment: MainAxisAlignment.center, - children: [ - const Spacer(), - Expanded( - flex: 3, - child: SvgPicture.asset( - Assets.loginLogo, - ), + second: Center( + child: ListView( + shrinkWrap: true, + controller: _scrollController, + children: [ + Container( + padding: EdgeInsets.all(size.width * 0.02), + margin: EdgeInsets.all(size.width * 0.09), + decoration: BoxDecoration( + color: Colors.black.withOpacity(0.3), + borderRadius: const BorderRadius.all(Radius.circular(20)), + ), + child: Center( + child: Row( + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + const Spacer(), + Expanded( + flex: 3, + child: SvgPicture.asset( + Assets.loginLogo, ), - const Spacer(), - Expanded( - flex: 3, - child: Container( - decoration: BoxDecoration( - color: Colors.white.withOpacity(0.1), - borderRadius: const BorderRadius.all(Radius.circular(30)), - border: Border.all(color: ColorsManager.graysColor.withOpacity(0.2)), - ), - child: Form( - key: forgetBloc.forgetFormKey, - child: Padding( - padding: EdgeInsets.symmetric( - horizontal: size.width*0.02, - vertical: size.width*0.003), - child: Column( - mainAxisAlignment: MainAxisAlignment.spaceEvenly, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - const SizedBox(height: 10), - const Text( - 'Forget Password', - style: TextStyle( - color: Colors.white, - fontSize: 24, - fontWeight: FontWeight.bold), - ), - const SizedBox(height: 10), - Text( - 'Please fill in your account information to\nretrieve your password', - style: Theme.of(context).textTheme.bodySmall, - ), - const SizedBox(height: 10), - Column( - crossAxisAlignment: CrossAxisAlignment.start, - mainAxisAlignment: MainAxisAlignment.start, - children: [ - Text( - "Country/Region", - style: Theme.of(context).textTheme.bodySmall, - ), - const SizedBox(height: 10), - SizedBox( - child: DropdownButtonFormField( - validator: forgetBloc.validateRegion, - icon: const Icon( - Icons.keyboard_arrow_down_outlined, - ), - decoration: textBoxDecoration()!.copyWith( - hintText: null, - ), - hint: SizedBox( - width: size.width * 0.11, - child: const Align( - alignment: Alignment.centerLeft, - child: Text( - 'Select your region/country', - textAlign: TextAlign.center, - overflow: TextOverflow.ellipsis, - - ), + ), + const Spacer(), + Expanded( + flex: 3, + child: Container( + decoration: BoxDecoration( + color: Colors.white.withOpacity(0.1), + borderRadius: + const BorderRadius.all(Radius.circular(30)), + border: Border.all( + color: ColorsManager.graysColor.withOpacity(0.2)), + ), + child: Form( + key: forgetBloc.forgetFormKey, + child: Padding( + padding: EdgeInsets.symmetric( + horizontal: size.width * 0.02, + vertical: size.width * 0.003), + child: Column( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const SizedBox(height: 10), + const Text( + 'Forget Password', + style: TextStyle( + color: Colors.white, + fontSize: 24, + fontWeight: FontWeight.bold), + ), + const SizedBox(height: 10), + Text( + 'Please fill in your account information to\nretrieve your password', + style: Theme.of(context).textTheme.bodySmall, + ), + const SizedBox(height: 10), + Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.start, + children: [ + Text( + "Country/Region", + style: + Theme.of(context).textTheme.bodySmall, + ), + const SizedBox(height: 10), + SizedBox( + child: DropdownButtonFormField( + validator: forgetBloc.validateRegion, + icon: const Icon( + Icons.keyboard_arrow_down_outlined, + ), + decoration: textBoxDecoration()!.copyWith( + hintText: null, + ), + hint: SizedBox( + width: size.width * 0.11, + child: const Align( + alignment: Alignment.centerLeft, + child: Text( + 'Select your region/country', + textAlign: TextAlign.center, + overflow: TextOverflow.ellipsis, ), ), - isDense: true, - style: const TextStyle(color: Colors.black), - items:forgetBloc.regionList!.map((RegionModel region) { - return DropdownMenuItem( - value: region.id, - child: Text(region.name), - ); - }).toList(), - onChanged: (String? value) { - forgetBloc.add(SelectRegionEvent(val: value!,)); - }, ), - ) - ], - ), - const SizedBox(height: 20), - Column( - crossAxisAlignment: CrossAxisAlignment.start, - mainAxisAlignment: MainAxisAlignment.start, - children: [ - Text("Account", - style: Theme.of(context).textTheme.bodySmall, + isDense: true, + style: + const TextStyle(color: Colors.black), + items: forgetBloc.regionList! + .map((RegionModel region) { + return DropdownMenuItem( + value: region.id, + child: Text(region.name), + ); + }).toList(), + onChanged: (String? value) { + forgetBloc.add(SelectRegionEvent( + val: value!, + )); + }, ), - const SizedBox(height: 10), - SizedBox( - child: TextFormField( - validator: forgetBloc.validateEmail, - controller: forgetBloc.forgetEmailController, - decoration: textBoxDecoration()!.copyWith(hintText: 'Enter your email'), - style: const TextStyle(color: Colors.black), - ), + ) + ], + ), + const SizedBox(height: 20), + Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.start, + children: [ + Text( + "Account", + style: + Theme.of(context).textTheme.bodySmall, + ), + const SizedBox(height: 10), + SizedBox( + child: TextFormField( + validator: forgetBloc.validateEmail, + controller: + forgetBloc.forgetEmailController, + decoration: textBoxDecoration()!.copyWith( + hintText: 'Enter your email'), + style: + const TextStyle(color: Colors.black), ), - ], - ), - const SizedBox(height: 20.0), - Column( - crossAxisAlignment: CrossAxisAlignment.start, - mainAxisAlignment: MainAxisAlignment.start, - children: [ - Text("One Time Password", - style: Theme.of(context).textTheme.bodySmall,), - const SizedBox(height: 10), - SizedBox( - child: TextFormField( - validator: forgetBloc.validateCode, - keyboardType: TextInputType.visiblePassword, - controller: forgetBloc.forgetOtp, - decoration: textBoxDecoration()!.copyWith( - hintText: 'Enter Code', - suffixIcon: SizedBox( - width: 100, - child: Center( - child: InkWell( - onTap: () { - BlocProvider.of(context).add(StartTimerEvent()); - }, - child: - Text( - 'Get Code ${state is TimerState && !state.isButtonEnabled ? "(${ BlocProvider.of(context).formattedTime(state.remainingTime)})" : ""}', - style: TextStyle( - color: state is TimerState && !state.isButtonEnabled - ? Colors.grey - : ColorsManager.btnColor, - ), + ), + ], + ), + const SizedBox(height: 20.0), + Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.start, + children: [ + Text( + "One Time Password", + style: + Theme.of(context).textTheme.bodySmall, + ), + const SizedBox(height: 10), + SizedBox( + child: TextFormField( + validator: forgetBloc.validateCode, + keyboardType: + TextInputType.visiblePassword, + controller: forgetBloc.forgetOtp, + decoration: textBoxDecoration()!.copyWith( + hintText: 'Enter Code', + suffixIcon: SizedBox( + width: 100, + child: Center( + child: InkWell( + onTap: () { + BlocProvider.of( + context) + .add(StartTimerEvent()); + }, + child: Text( + 'Get Code ${state is TimerState && !state.isButtonEnabled ? "(${BlocProvider.of(context).formattedTime(state.remainingTime)}) " : ""}', + style: TextStyle( + color: state is TimerState && + !state.isButtonEnabled + ? Colors.grey + : ColorsManager.btnColor, ), ), ), ), ), - style: const TextStyle(color: Colors.black), ), + style: + const TextStyle(color: Colors.black), ), - ], - ), - const SizedBox(height: 20.0), - Column( - crossAxisAlignment: CrossAxisAlignment.start, - mainAxisAlignment: MainAxisAlignment.start, - children: [ - Text("Password", - style: Theme.of(context).textTheme.bodySmall,), - const SizedBox(height: 10), - SizedBox( - child: TextFormField( - validator: forgetBloc.passwordValidator, - keyboardType: TextInputType.visiblePassword, - controller: forgetBloc.forgetPasswordController, - decoration: textBoxDecoration()!.copyWith( - hintText: 'At least 8 characters', - ), - style: const TextStyle(color: Colors.black), - ), - ), - ], - ), - const SizedBox( - height: 10, - ), - const SizedBox(height: 20.0), - Row( - crossAxisAlignment: CrossAxisAlignment.center, - mainAxisAlignment: MainAxisAlignment.center, - children: [ - SizedBox( - width: size.width * 0.2, - child: DefaultButton( - backgroundColor: ColorsManager.btnColor, - child: const Text('Submit'), - onPressed: () { - if (forgetBloc.forgetFormKey.currentState!.validate()) { - forgetBloc.add(ChangePasswordEvent()); - } - }, - ), - ), - ], - ), - const SizedBox(height: 10.0), - SizedBox(child: Text(forgetBloc.validate, - style: const TextStyle(fontWeight: FontWeight.w700,color: ColorsManager.red ),),), - SizedBox(height: 10,), - SizedBox( - width: size.width * 0.2, - child: Row( - mainAxisAlignment: MainAxisAlignment.center, - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - const Flexible( - child: Text( - "Do you have an account? ", - style: TextStyle(color: Colors.white), - )), - InkWell( - onTap: () { - Navigator.pop(context); - }, - child: const Flexible( - child: Text( - "Sign in", - )), - ), - ], ), - ) - ], - ), + ], + ), + const SizedBox(height: 20.0), + Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.start, + children: [ + Text( + "Password", + style: + Theme.of(context).textTheme.bodySmall, + ), + const SizedBox(height: 10), + SizedBox( + child: TextFormField( + validator: forgetBloc.passwordValidator, + keyboardType: + TextInputType.visiblePassword, + controller: + forgetBloc.forgetPasswordController, + decoration: textBoxDecoration()!.copyWith( + hintText: 'At least 8 characters', + ), + style: + const TextStyle(color: Colors.black), + ), + ), + ], + ), + const SizedBox( + height: 10, + ), + const SizedBox(height: 20.0), + Row( + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + SizedBox( + width: size.width * 0.2, + child: DefaultButton( + backgroundColor: ColorsManager.btnColor, + child: const Text('Submit'), + onPressed: () { + if (forgetBloc + .forgetFormKey.currentState! + .validate()) { + forgetBloc.add(ChangePasswordEvent()); + } + }, + ), + ), + ], + ), + const SizedBox(height: 10.0), + SizedBox( + child: Text( + forgetBloc.validate, + style: const TextStyle( + fontWeight: FontWeight.w700, + color: ColorsManager.red), + ), + ), + SizedBox( + height: 10, + ), + SizedBox( + width: size.width * 0.2, + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + const Flexible( + child: Text( + "Do you have an account? ", + style: TextStyle(color: Colors.white), + )), + InkWell( + onTap: () { + Navigator.pop(context); + }, + child: const Flexible( + child: Text( + "Sign in", + )), + ), + ], + ), + ) + ], ), ), ), ), - const Spacer(), - ], - ), + ), + const Spacer(), + ], ), ), - ], - ), - ) - ); + ), + ], + ), + )); } } diff --git a/lib/services/auth_api.dart b/lib/services/auth_api.dart index 3c7f3fbf..a0c0b87f 100644 --- a/lib/services/auth_api.dart +++ b/lib/services/auth_api.dart @@ -45,33 +45,38 @@ class AuthenticationAPI { "regionUuid": regionUuid }, showServerMessage: true, - options: Options(), expectedResponseModel: (json) { - Map parsedJson = jsonDecode(json); - int cooldown = parsedJson['data']['cooldown']; - debugPrint('Cooldown: $cooldown seconds'); - return cooldown; + return 30; } ); - return response; + return 30; } on DioError catch (e) { + if (e.response != null) { if (e.response!.statusCode == 400) { // Handle 400 Bad Request final errorData = e.response!.data; - String errorMessage = errorData['message'] ?? 'Unknown error'; - int cooldown = errorData['data']['cooldown'] ?? 0; - return cooldown; + String errorMessage = errorData['message']; + debugPrint('Unexpected Error: $errorMessage'); + if(errorMessage=='User not found'){ + return 1; + }else{ + int cooldown = errorData['data']['cooldown'] ?? 1; + return cooldown; + } + } else { debugPrint('Error: ${e.response!.statusCode} - ${e.response!.statusMessage}'); + return 1; } } else { debugPrint('Error: ${e.message}'); + return 1; } - return null; + return 1; } catch (e) { debugPrint('Unexpected Error: $e'); - return null; + return 1; } } @@ -79,7 +84,7 @@ class AuthenticationAPI { {required String email, required String otpCode}) async { final response = await HTTPService().post( path: ApiEndpoints.verifyOtp, - body: {"email": email, "type": "VERIFICATION", "otpCode": otpCode}, + body: {"email": email, "type": "PASSWORD", "otpCode": otpCode}, showServerMessage: true, expectedResponseModel: (json) { if (json['message'] == 'Otp Verified Successfully') { From 350888c9da25483e6fcdcd8df7a0892b9ada71ac Mon Sep 17 00:00:00 2001 From: mohammad Date: Mon, 12 Aug 2024 15:51:21 +0300 Subject: [PATCH 04/32] region and access_management ui --- lib/pages/access_management/view/access_management.dart | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/pages/access_management/view/access_management.dart b/lib/pages/access_management/view/access_management.dart index 00856a99..6893b205 100644 --- a/lib/pages/access_management/view/access_management.dart +++ b/lib/pages/access_management/view/access_management.dart @@ -7,6 +7,7 @@ import 'package:syncrow_web/pages/access_management/bloc/access_state.dart'; import 'package:syncrow_web/pages/common/default_button.dart'; import 'package:syncrow_web/utils/color_manager.dart'; import 'package:syncrow_web/utils/constants/assets.dart'; +import 'package:syncrow_web/utils/snack_bar.dart'; import 'package:syncrow_web/utils/style.dart'; import 'package:syncrow_web/web_layout/web_scaffold.dart'; @@ -272,6 +273,7 @@ class AccessManagementPage extends StatelessWidget { }))); } } + Widget _buildTableHeaderCell(String title) { return Expanded( child: Container( From 2d0f85bded31299d037f1197b90184ebf42f4bc1 Mon Sep 17 00:00:00 2001 From: mohammad Date: Tue, 13 Aug 2024 17:08:18 +0300 Subject: [PATCH 05/32] visitor password --- assets/dome.json | 26 +- lib/main.dart | 5 +- .../access_management/bloc/access_bloc.dart | 165 ++++++- .../access_management/bloc/access_event.dart | 29 ++ .../access_management/bloc/access_state.dart | 13 +- .../model/access_manag_model.dart | 37 -- .../model/password_model.dart | 54 ++ .../view/access_management.dart | 467 ++++++++++-------- .../bloc/visitor_password_bloc.dart | 13 + .../bloc/visitor_password_event.dart | 23 + .../bloc/visitor_password_state.dart | 23 + .../view/visitor_password_dialog.dart | 290 +++++++++++ lib/services/access_mang_api.dart | 51 +- lib/services/auth_api.dart | 5 +- lib/utils/constants/api_const.dart | 1 + 15 files changed, 920 insertions(+), 282 deletions(-) delete mode 100644 lib/pages/access_management/model/access_manag_model.dart create mode 100644 lib/pages/access_management/model/password_model.dart create mode 100644 lib/pages/visitor_password/bloc/visitor_password_bloc.dart create mode 100644 lib/pages/visitor_password/bloc/visitor_password_event.dart create mode 100644 lib/pages/visitor_password/bloc/visitor_password_state.dart create mode 100644 lib/pages/visitor_password/view/visitor_password_dialog.dart diff --git a/assets/dome.json b/assets/dome.json index 39e6ee7c..91186608 100644 --- a/assets/dome.json +++ b/assets/dome.json @@ -2,7 +2,8 @@ { "accessUser": "Ali Doe", "accessType": "Admin", - "accessPeriod": "2023-08-01", + "startTime": "2023-08-01", + "endTime": "2023-08-02", "accessibleDevice": "Smart Door", "authorizationSource": "System", "authorizer": "Jane Smith", @@ -12,8 +13,9 @@ }, { "accessUser": "oamr Doe", "accessType": "Admin", - "accessPeriod": "2023-08-01", - "accessibleDevice": "Smart Door", + "startTime": "2023-08-01", + "endTime": "2023-08-05", + "accessibleDevice": "Smart Door", "authorizationSource": "System", "authorizer": "Jane Smith", "authorizationTime": "2023-08-01 10:00 AM", @@ -22,7 +24,8 @@ }, { "accessUser": "John Doe", "accessType": "Admin", - "accessPeriod": "2023-08-01", + "startTime": "2023-08-01", + "endTime": "2023-08-10", "accessibleDevice": "Smart Door", "authorizationSource": "System", "authorizer": "Jane Smith", @@ -34,7 +37,8 @@ { "accessUser": "John Doe", "accessType": "Admin", - "accessPeriod": "2023-08-01", + "startTime": "2023-08-01", + "endTime": "2023-10-10", "accessibleDevice": "Smart Door", "authorizationSource": "System", "authorizer": "Jane Smith", @@ -45,7 +49,8 @@ { "accessUser": "John Doe", "accessType": "Admin", - "accessPeriod": "2023-08-01", + "startTime": "2023-03-01", + "endTime": "2023-05-10", "accessibleDevice": "Smart Door", "authorizationSource": "System", "authorizer": "Jane Smith", @@ -56,7 +61,8 @@ { "accessUser": "John Doe", "accessType": "Admin", - "accessPeriod": "2023-08-01 ", + "startTime": "2023-07-01", + "endTime": "2023-08-10", "accessibleDevice": "Smart Door", "authorizationSource": "System", "authorizer": "Jane Smith", @@ -66,7 +72,8 @@ }, { "accessUser": "John Doe", "accessType": "Admin", - "accessPeriod": "2023-08-01 ", + "startTime": "2023-01-01", + "endTime": "2023-09-05", "accessibleDevice": "Smart Door", "authorizationSource": "System", "authorizer": "Jane Smith", @@ -77,7 +84,8 @@ { "accessUser": "Alice Johnson", "accessType": "User", - "accessPeriod": "2023-08-01 to 2023-08-31", + "startTime": "2023-08-01", + "endTime": "2023-08-10", "accessibleDevice": "Smart Lock", "authorizationSource": "Admin", "authorizer": "John Doe", diff --git a/lib/main.dart b/lib/main.dart index 615851eb..ab12a5fa 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,10 +1,12 @@ import 'package:flutter/gestures.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:syncrow_web/pages/access_management/view/access_management.dart'; import 'package:syncrow_web/pages/auth/bloc/auth_bloc.dart'; import 'package:syncrow_web/pages/auth/view/login_page.dart'; import 'package:syncrow_web/pages/home/bloc/home_bloc.dart'; import 'package:syncrow_web/pages/home/view/home_page.dart'; +import 'package:syncrow_web/pages/visitor_password/view/visitor_password_dialog.dart'; import 'package:syncrow_web/services/locator.dart'; import 'package:syncrow_web/utils/color_manager.dart'; @@ -56,7 +58,8 @@ class MyApp extends StatelessWidget { colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple), // Set up color scheme useMaterial3: true, // Enable Material 3 ), - home: isLoggedIn == 'Success' ? const HomePage() : const LoginPage(), + home: VisitorPasswordDialog() + // isLoggedIn == 'Success' ? const HomePage() : const LoginPage(), )); } } diff --git a/lib/pages/access_management/bloc/access_bloc.dart b/lib/pages/access_management/bloc/access_bloc.dart index 8938fc36..e8be33f4 100644 --- a/lib/pages/access_management/bloc/access_bloc.dart +++ b/lib/pages/access_management/bloc/access_bloc.dart @@ -1,26 +1,181 @@ +import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:syncrow_web/pages/access_management/bloc/access_event.dart'; import 'package:syncrow_web/pages/access_management/bloc/access_state.dart'; -import 'package:syncrow_web/pages/access_management/model/access_manag_model.dart'; +import 'package:syncrow_web/pages/access_management/model/password_model.dart'; import 'package:syncrow_web/services/access_mang_api.dart'; +import 'package:syncrow_web/utils/color_manager.dart'; +import 'package:syncrow_web/utils/snack_bar.dart'; class AccessBloc extends Bloc { AccessBloc() : super((AccessInitial())) { on(_onFetchTableData); + on(selectFilterTap); + on(selectTime); + on(_filterData); + on(resetSearch); } String startTime = 'Start Time'; String endTime = 'End Time'; + int? effectiveTimeTimeStamp; + int? expirationTimeTimeStamp; + TextEditingController passwordName= TextEditingController(); + List filteredData = []; // To store filtered data + List data=[]; + Future _onFetchTableData( FetchTableData event, Emitter emit) async { try { emit(AccessLoaded()); - List data = await AccessMangApi().fetchInfo(); - print('objectwww888888${data[0].accessPeriod}'); - - emit(TableLoaded(data)); + data = await AccessMangApi().fetchVisitorPassword(); + emit(TableLoaded(data)); } catch (e) { emit(FailedState(e.toString())); } } + + int selectedIndex = 0; + + final List tabs = [ + 'All', + 'To Be Effective (0)', + 'Effective (0)', + 'Expired' + ]; + + + Future selectFilterTap(TabChangedEvent event, Emitter emit) async { + try { + emit(AccessLoaded()); + selectedIndex= event.selectedIndex; + emit(AccessInitial()); + emit(TableLoaded(data)); + + } catch (e) { + emit(FailedState( e.toString())); + return; + } + } + + + Future selectTime(SelectTime event, Emitter emit) async { + final DateTime? picked = await showDatePicker( + context: event.context, + initialDate: DateTime.now(), + firstDate: DateTime(2015, 8), + lastDate: DateTime(2101), + ); + if (picked != null) { + final TimeOfDay? timePicked = await showTimePicker( + context: event.context, + initialTime: TimeOfDay.now(), + + builder: (context, child) { + return Theme( + data: ThemeData.light().copyWith( + colorScheme: const ColorScheme.light( + primary: ColorsManager.primaryColor, + onSurface: Colors.black, + ), + buttonTheme: const ButtonThemeData( + colorScheme: ColorScheme.light( + primary: Colors.green, + ), + ), + ), + child: child!, + ); + }, + ); + if (timePicked != null) { + final selectedDateTime = DateTime( + picked.year, + picked.month, + picked.day, + timePicked.hour, + timePicked.minute, + ); + final selectedTimestamp = DateTime( + selectedDateTime.year, + selectedDateTime.month, + selectedDateTime.day, + selectedDateTime.hour, + selectedDateTime.minute, + ).millisecondsSinceEpoch ~/ 1000; // Divide by 1000 to remove milliseconds + if (event.isStart) { + if (expirationTimeTimeStamp != null && selectedTimestamp > expirationTimeTimeStamp!) { + CustomSnackBar.displaySnackBar('Effective Time cannot be later than Expiration Time.'); + } else { + startTime = selectedDateTime.toString().split('.').first; // Remove seconds and milliseconds + effectiveTimeTimeStamp = selectedTimestamp; + } + } else { + if (effectiveTimeTimeStamp != null && selectedTimestamp < effectiveTimeTimeStamp!) { + CustomSnackBar.displaySnackBar('Expiration Time cannot be earlier than Effective Time.'); + } else { + endTime = selectedDateTime.toString().split('.').first; // Remove seconds and milliseconds + expirationTimeTimeStamp = selectedTimestamp; + } + } + } + } + emit(AccessInitial()); + emit(TableLoaded(data)); + } + + Future _filterData(FilterDataEvent event, Emitter emit) async { + emit(AccessLoaded()); + try { + // Filter the data based on the provided criteria + filteredData = data.where((item) { + bool matchesCriteria = true; + // Check if the password name should be used for filtering + if (event.passwordName != null && event.passwordName!.isNotEmpty) { + final bool matchesName = item.passwodName != null && + item.passwodName.contains(event.passwordName!); + if (!matchesName) { + matchesCriteria = false; + } + } + // Check if the time range should be used for filtering + if (event.startTime != null && event.endTime != null) { + // Ensure effectiveTime and invalidTime are treated as integers + final int? effectiveTime = int.tryParse(item.effectiveTime.toString()); + final int? invalidTime = int.tryParse(item.invalidTime.toString()); + if (effectiveTime == null || invalidTime == null) { + matchesCriteria = false; + } else { + final bool matchesStartTime = effectiveTime >= event.startTime!; + final bool matchesEndTime = invalidTime <= event.endTime!; + if (!matchesStartTime || !matchesEndTime) { + matchesCriteria = false; + } + } + } + return matchesCriteria; + }).toList(); + print('Filtered data: $filteredData'); // Print to debug filtered data + emit(TableLoaded(filteredData)); + } catch (e) { + print('Error occurred during filtering: $e'); + } + } + // ResetSearch + resetSearch(ResetSearch event, Emitter emit) async{ + emit(AccessLoaded()); + startTime = 'Start Time'; + endTime = 'End Time'; + passwordName.clear(); + add(FetchTableData()); + + } + + + DateTime timestampToDateTime(dynamic timestamp) { + return DateTime.fromMillisecondsSinceEpoch(int.parse(timestamp) * 1000); + } + + + } diff --git a/lib/pages/access_management/bloc/access_event.dart b/lib/pages/access_management/bloc/access_event.dart index 60512387..3a64a11a 100644 --- a/lib/pages/access_management/bloc/access_event.dart +++ b/lib/pages/access_management/bloc/access_event.dart @@ -1,5 +1,6 @@ import 'package:equatable/equatable.dart'; +import 'package:flutter/material.dart'; abstract class AccessEvent extends Equatable { const AccessEvent(); @@ -8,4 +9,32 @@ abstract class AccessEvent extends Equatable { List get props => []; } class FetchTableData extends AccessEvent {} +class ResetSearch extends AccessEvent {} +class TabChangedEvent extends AccessEvent { + final int selectedIndex; + + const TabChangedEvent(this.selectedIndex); +} + + +class SelectTime extends AccessEvent { + final BuildContext context; + final bool isStart; + const SelectTime({required this.context,required this.isStart}); + @override + List get props => [context,isStart]; +} + + +class FilterDataEvent extends AccessEvent { + final String? passwordName; + final int? startTime; + final int? endTime; + + const FilterDataEvent({ + this.passwordName, + this.startTime, + this.endTime, + }); +} diff --git a/lib/pages/access_management/bloc/access_state.dart b/lib/pages/access_management/bloc/access_state.dart index 94bbc90c..11253d1f 100644 --- a/lib/pages/access_management/bloc/access_state.dart +++ b/lib/pages/access_management/bloc/access_state.dart @@ -1,5 +1,5 @@ import 'package:equatable/equatable.dart'; -import 'package:syncrow_web/pages/access_management/model/access_manag_model.dart'; +import 'package:syncrow_web/pages/access_management/model/password_model.dart'; abstract class AccessState extends Equatable { const AccessState(); @@ -21,7 +21,7 @@ class FailedState extends AccessState { } class TableLoaded extends AccessState { - final List data; + final List data; const TableLoaded(this.data); @@ -29,3 +29,12 @@ class TableLoaded extends AccessState { List get props => [data]; } +class TabState extends AccessState { + final int selectedIndex; + + const TabState({required this.selectedIndex}); +} + +class ChangeTimeState extends AccessState {} + +class TimeSelectedState extends AccessState {} diff --git a/lib/pages/access_management/model/access_manag_model.dart b/lib/pages/access_management/model/access_manag_model.dart deleted file mode 100644 index ef468c8e..00000000 --- a/lib/pages/access_management/model/access_manag_model.dart +++ /dev/null @@ -1,37 +0,0 @@ -class AccessManagModel { - final String accessUser; - final String accessType; - final String accessPeriod; - final String accessibleDevice; - final String authorizationSource; - final String authorizer; - final String authorizationTime; - final String accessStatus; - final String actions; - - AccessManagModel({ - required this.accessUser, - required this.accessType, - required this.accessPeriod, - required this.accessibleDevice, - required this.authorizationSource, - required this.authorizer, - required this.authorizationTime, - required this.accessStatus, - required this.actions, - }); - - factory AccessManagModel.fromJson(Map json) { - return AccessManagModel( - accessUser: json['accessUser'], - accessType: json['accessType'], - accessPeriod: json['accessPeriod'], - accessibleDevice: json['accessibleDevice'], - authorizationSource: json['authorizationSource'], - authorizer: json['authorizer'], - authorizationTime: json['authorizationTime'], - accessStatus: json['accessStatus'], - actions: json['actions'], - ); - } -} diff --git a/lib/pages/access_management/model/password_model.dart b/lib/pages/access_management/model/password_model.dart new file mode 100644 index 00000000..0377bfb7 --- /dev/null +++ b/lib/pages/access_management/model/password_model.dart @@ -0,0 +1,54 @@ +class PasswordModel { + final dynamic passwordId; + final dynamic invalidTime; + final dynamic effectiveTime; + final dynamic passwordCreated; + final dynamic createdTime; + final dynamic passwodName; // New field + final dynamic passwordStatus; + final dynamic passwordType; + final dynamic deviceUuid; + + PasswordModel({ + this.passwordId, + this.invalidTime, + this.effectiveTime, + this.passwordCreated, + this.createdTime, + this.passwodName, // New field + this.passwordStatus, + this.passwordType, + this.deviceUuid, + }); + + factory PasswordModel.fromJson(Map json) { + return PasswordModel( + passwordId: json['passwordId'], + invalidTime: json['invalidTime'], + effectiveTime: json['effectiveTime'], + passwordCreated: json['passwordCreated'], + createdTime: json['createdTime'], + passwodName: json['passwodName']??'No name', // New field + passwordStatus: json['passwordStatus'], + passwordType: json['passwordType'], + deviceUuid: json['deviceUuid'], + ); + } + + Map toJson() { + return { + 'passwordId': passwordId, + 'invalidTime': invalidTime, + 'effectiveTime': effectiveTime, + 'passwordCreated': passwordCreated, + 'createdTime': createdTime, + 'passwodName': passwodName, // New field + 'passwordStatus': passwordStatus, + 'passwordType': passwordType, + 'deviceUuid': deviceUuid, + }; + } + List parsePasswordList(List jsonList) { + return jsonList.map((json) => PasswordModel.fromJson(json)).toList(); + } +} diff --git a/lib/pages/access_management/view/access_management.dart b/lib/pages/access_management/view/access_management.dart index 6893b205..943849d8 100644 --- a/lib/pages/access_management/view/access_management.dart +++ b/lib/pages/access_management/view/access_management.dart @@ -5,9 +5,9 @@ import 'package:syncrow_web/pages/access_management/bloc/access_bloc.dart'; import 'package:syncrow_web/pages/access_management/bloc/access_event.dart'; import 'package:syncrow_web/pages/access_management/bloc/access_state.dart'; import 'package:syncrow_web/pages/common/default_button.dart'; +import 'package:syncrow_web/pages/visitor_password/view/visitor_password_dialog.dart'; import 'package:syncrow_web/utils/color_manager.dart'; import 'package:syncrow_web/utils/constants/assets.dart'; -import 'package:syncrow_web/utils/snack_bar.dart'; import 'package:syncrow_web/utils/style.dart'; import 'package:syncrow_web/web_layout/web_scaffold.dart'; @@ -35,16 +35,15 @@ class AccessManagementPage extends StatelessWidget { .copyWith(color: Colors.white), ), ], - scaffoldBody: BlocProvider( - create: (BuildContext context) => AccessBloc()..add(FetchTableData() ), - child: BlocConsumer( - listener: (context, state) { + scaffoldBody: BlocProvider(create: (BuildContext context) => AccessBloc()..add(FetchTableData()), + child: BlocConsumer(listener: (context, state) { if (state is FailedState) { // CustomSnackBar.displaySnackBar( // state.errorMessage // ); } }, builder: (context, state) { + final accessBloc = BlocProvider.of(context); return Container( padding: EdgeInsets.all(30), height: size.height, @@ -53,233 +52,294 @@ class AccessManagementPage extends StatelessWidget { crossAxisAlignment: CrossAxisAlignment.start, children: [ Container( - width: size.width * 0.3, height: size.height * 0.05, + width:size.width * 0.26 , decoration: containerDecoration, - child: const Row( - mainAxisAlignment: MainAxisAlignment.spaceAround, - children: [ - Text('All'), - Text('To Be Effective (0)'), - Text('Effective (0)'), - Text('Expired'), - ], + child: Center( + child: ListView.builder( + scrollDirection: Axis.horizontal, + itemCount: BlocProvider.of(context).tabs.length, + itemBuilder: (context, index) { + final isSelected = index == + BlocProvider.of(context).selectedIndex; + return InkWell( + onTap: () { + BlocProvider.of(context).add(TabChangedEvent(index)); + }, + child: Container( + decoration: BoxDecoration( + color: ColorsManager.boxColor, + border: Border.all( + color: isSelected ? Colors.blue : Colors.transparent, + width: 2.0,), + borderRadius: index == 0 + ? const BorderRadius.only( + topLeft: Radius.circular(10), + bottomLeft: Radius.circular(10)) + : index == 3 + ? const BorderRadius.only( + topRight: Radius.circular(10), + bottomRight: Radius.circular(10)) + : null), + padding: const EdgeInsets.only(left: 10,right: 10), + child: Center( + child: Text( + BlocProvider.of(context).tabs[index], + style: TextStyle( + color: isSelected + ? Colors.blue + : Colors.black, + ), + ), + ), + ), + ); + }, + ), ), ), const SizedBox( height: 20, ), Wrap( - children: [ - Column( - crossAxisAlignment: CrossAxisAlignment.start, - mainAxisAlignment: MainAxisAlignment.start, - children: [ - Text('User Name'), - Container( - width: size.width*0.15, - decoration: containerDecoration, - child: TextFormField( - decoration: textBoxDecoration()! - .copyWith(hintText: 'Please enter'), - )), - ], - ), - const SizedBox(width: 15,), - Column( - crossAxisAlignment: CrossAxisAlignment.start, - mainAxisAlignment: MainAxisAlignment.start, - children: [ - const Text('Email Address'), - Container( - width: size.width*0.15, - decoration: containerDecoration, - child: TextFormField( - decoration: textBoxDecoration()! - .copyWith(hintText: 'Please enter'), - )), - ], - ), - const SizedBox(width: 15,), - Column( - crossAxisAlignment: CrossAxisAlignment.start, - mainAxisAlignment: MainAxisAlignment.start, - children: [ - const Text('Access Time'), - Container( - width: size.width*0.18, - padding: EdgeInsets.all(10), - decoration: containerDecoration, - child: Column( - children: [ - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - InkWell(child: Text(BlocProvider.of(context).startTime)), - const Icon(Icons.arrow_right_alt), - InkWell(child: Text(BlocProvider.of(context).endTime)), - SvgPicture.asset( - Assets.calendarIcon, - ), - ], - ), - ], - )), - ], - ), - const SizedBox(width: 15,), - Column( - crossAxisAlignment: CrossAxisAlignment.start, - mainAxisAlignment: MainAxisAlignment.start, - children: [ - const Text('Authorization Source'), - Container( - width: size.width*0.18, - decoration: containerDecoration, - child: TextFormField( - decoration: textBoxDecoration(), - )), - ], - ), - const SizedBox(width: 15,), - SizedBox( - width: size.width*0.06, - child: Column( + children: [ + Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.start, children: [ - Text(''), + const Text('Password Name'), Container( + width: size.width * 0.15, decoration: containerDecoration, - - child: DefaultButton(child: Text('Search'),borderRadius: 9)), + child: TextFormField( + controller: accessBloc.passwordName, + style: TextStyle(color: Colors.black), + decoration: textBoxDecoration()! + .copyWith(hintText: 'Please enter'), + )), ], ), - ), - const SizedBox(width: 10,), - SizedBox( - width: size.width*0.06, - child: Column( + const SizedBox( + width: 15, + ), + Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.start, children: [ - Text(''), + const Text('Access Time'), Container( - decoration: containerDecoration, - child: DefaultButton( - backgroundColor: ColorsManager.whiteColors,borderRadius: 9, - child: Text('Reset' - ,style: Theme.of(context).textTheme.bodySmall!.copyWith(color: Colors.black),) - ,), - ), - ], - ), - ), - ], - ), - const SizedBox( - height: 20, - ), - Wrap(children: [ - Container( - width: size.width*0.15, - decoration: containerDecoration, - child: const DefaultButton( - borderRadius: 8, - child: Text('+ Create Visitor Password ')), - ), - const SizedBox(width: 10,), - Container( - width: size.width*0.12, - decoration: containerDecoration, - child: DefaultButton( - borderRadius: 8, - backgroundColor: ColorsManager.whiteColors, - child: Text('Admin Password' - ,style: Theme.of(context).textTheme.bodySmall!.copyWith(color: Colors.black),) - )) - ],), - const SizedBox( - height: 20, - ), - Expanded( - child:state is TableLoaded? - Container( - decoration: containerDecoration, - width: size.width, - child: Padding( - padding: const EdgeInsets.all(10.0), - child: ListView( - scrollDirection: Axis.horizontal, - children: [ - Container( - width: size.width, - height:size.height , + width: size.width * 0.25, + padding: EdgeInsets.all(10), + decoration: containerDecoration, child: Column( children: [ - - Container( - color: ColorsManager.boxColor, - child: Row( - children: [ - _buildTableHeaderCell('Access User'), - _buildTableHeaderCell('Access Type'), - _buildTableHeaderCell('Access Period'), - _buildTableHeaderCell('Accessible Device'), - _buildTableHeaderCell('Authorization Source'), - _buildTableHeaderCell('Authorizer'), - _buildTableHeaderCell('Authorization Time'), - _buildTableHeaderCell('Access Status'), - _buildTableHeaderCell('Actions'), - ], - ), - ), - - Expanded( - child: Container( - width: size.width, - color: ColorsManager.whiteColors, - child: ListView( - shrinkWrap: true, - children: [ - Column( - children: state.data.map((item) { - return Row( - children: [ - _buildTableCell(item.accessUser), - _buildTableCell(item.accessType), - _buildTableCell(item.accessPeriod), - _buildTableCell(item.accessibleDevice), - _buildTableCell(item.authorizationSource), - _buildTableCell(item.authorizer), - _buildTableCell(item.authorizationTime), - _buildTableCell(item.accessStatus), - _buildTableCell(item.actions), - ], - ); - }).toList(), - ), - ], + Row( + mainAxisAlignment: + MainAxisAlignment.spaceBetween, + children: [ + InkWell( + onTap: () { + accessBloc.add(SelectTime(context: context, isStart: true)); + }, + child: Text(BlocProvider.of(context).startTime) ), - ), + const Icon(Icons.arrow_right_alt), + InkWell( + onTap: () { + accessBloc.add(SelectTime(context: context, isStart: false)); + }, + child: Text(BlocProvider.of(context).endTime)), + SvgPicture.asset( + Assets.calendarIcon, + ), + ], ), ], + )), + ], + ), + const SizedBox( + width: 15, + ), + + SizedBox( + width: size.width * 0.06, + child: Column( + children: [ + Text(''), + Container( + decoration: containerDecoration, + child: DefaultButton( + onPressed: () { + accessBloc.add(FilterDataEvent( + passwordName: accessBloc.passwordName.text, + startTime: accessBloc.effectiveTimeTimeStamp, + endTime: accessBloc.expirationTimeTimeStamp + )); + }, borderRadius: 9, + child: const Text('Search'))), + ], + ), + ), + const SizedBox( + width: 10, + ), + SizedBox( + width: size.width * 0.06, + child: Column( + children: [ + Text(''), + Container( + decoration: containerDecoration, + child: DefaultButton( + onPressed: () { + accessBloc.add(ResetSearch()); + }, + backgroundColor: ColorsManager.whiteColors, + borderRadius: 9, + child: Text( + 'Reset', + style: Theme.of(context) + .textTheme + .bodySmall! + .copyWith(color: Colors.black), + ), ), ), ], ), ), - ):const Center(child: CircularProgressIndicator()) - ) + ], + ), + const SizedBox( + height: 20, + ), + Wrap( + children: [ + Container( + width: size.width * 0.15, + decoration: containerDecoration, + child: DefaultButton( + onPressed: () { + showDialog( + context: context, + barrierDismissible: false, + builder: (BuildContext context) { + return const VisitorPasswordDialog(); + }, + ); + }, + borderRadius: 8, + child: Text('+ Create Visitor Password ')), + ), + const SizedBox( + width: 10, + ), + Container( + width: size.width * 0.12, + decoration: containerDecoration, + child: DefaultButton( + borderRadius: 8, + backgroundColor: ColorsManager.whiteColors, + child: Text( + 'Admin Password', + style: Theme.of(context) + .textTheme + .bodySmall! + .copyWith(color: Colors.black), + ))) + ], + ), + const SizedBox( + height: 20, + ), + Expanded( + child: state is TableLoaded + ? TableWidget(size, state,accessBloc) + : const Center(child: CircularProgressIndicator())) ], ), ); }))); } + + Container TableWidget(Size size, TableLoaded state,AccessBloc accessBloc) { + return Container( + decoration: containerDecoration, + width: size.width, + child: Padding( + padding: const EdgeInsets.all(10.0), + child: ListView( + scrollDirection: Axis.horizontal, + children: [ + Container( + width: size.width, + height: size.height, + child: Column( + children: [ + Container( + color: ColorsManager.boxColor, + child: Row( + children: [ + _buildTableHeaderCell('Password name'), + _buildTableHeaderCell(' Password Type'), + _buildTableHeaderCell('Start Time'), + _buildTableHeaderCell('End Time'), + _buildTableHeaderCell('Device Id'), + // _buildTableHeaderCell('Authorization Source'), + // _buildTableHeaderCell('Authorizer'), + _buildTableHeaderCell('Password Created'), + // _buildTableHeaderCell('Access Status'), + _buildTableHeaderCell('Password Status'), + ], + ), + ), + Expanded( + child: Container( + width: size.width, + color: ColorsManager.whiteColors, + child: ListView( + shrinkWrap: true, + children: [ + Column( + children: state.data.map((item) { + return Row( + children: [ + _buildTableCell(item.passwodName), + _buildTableCell(item.passwordType), + + _buildTableCell(accessBloc.timestampToDateTime(item.effectiveTime).toString()), + _buildTableCell(accessBloc.timestampToDateTime(item.invalidTime).toString()), + _buildTableCell(item.deviceUuid.toString()), + // _buildTableCell(item.authorizationSource), + // _buildTableCell(item.authorizer), + _buildTableCell(item.passwordCreated!=null?accessBloc.timestampToDateTime(item.passwordCreated).toString():'no data'), + // _buildTableCell(item.accessStatus), + _buildTableCell(item.passwordStatus.toString()), + ], + ); + }).toList(), + ), + ], + ), + ), + ), + ], + ), + ), + ], + ), + ), + ); + } } Widget _buildTableHeaderCell(String title) { return Expanded( child: Container( decoration: const BoxDecoration( - border: Border.symmetric(vertical: BorderSide(color: ColorsManager.boxDivider)) - ), + border: Border.symmetric( + vertical: BorderSide(color: ColorsManager.boxDivider))), alignment: Alignment.centerLeft, child: Padding( padding: const EdgeInsets.all(8.0), @@ -292,12 +352,21 @@ Widget _buildTableHeaderCell(String title) { Widget _buildTableCell(String content) { return Expanded( child: Container( + height: 80, padding: const EdgeInsets.all(20.0), - decoration: const BoxDecoration( - border: Border.symmetric(horizontal: BorderSide(color: ColorsManager.boxDivider)) + decoration: BoxDecoration( + border: Border( + bottom: BorderSide( // <--- right side + color: ColorsManager.boxDivider, + width: 1.0, + ), + ) ), alignment: Alignment.centerLeft, - child: Text(content,style: TextStyle(color: Colors.black,fontSize: 12),), + child: Text( + content, + style: TextStyle(color: Colors.black, fontSize: 12), + ), ), ); } diff --git a/lib/pages/visitor_password/bloc/visitor_password_bloc.dart b/lib/pages/visitor_password/bloc/visitor_password_bloc.dart new file mode 100644 index 00000000..41510a8d --- /dev/null +++ b/lib/pages/visitor_password/bloc/visitor_password_bloc.dart @@ -0,0 +1,13 @@ +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:syncrow_web/pages/visitor_password/bloc/visitor_password_event.dart'; +import 'package:syncrow_web/pages/visitor_password/bloc/visitor_password_state.dart'; + +// Define the BLoC +class VisitorPasswordBloc extends Bloc { + VisitorPasswordBloc() : super(VisitorPasswordInitial()) { + on((event, emit) { + // Handle the event and emit the new state + emit(PasswordTypeSelected(event.type)); + }); + } +} diff --git a/lib/pages/visitor_password/bloc/visitor_password_event.dart b/lib/pages/visitor_password/bloc/visitor_password_event.dart new file mode 100644 index 00000000..37083997 --- /dev/null +++ b/lib/pages/visitor_password/bloc/visitor_password_event.dart @@ -0,0 +1,23 @@ + + + + +import 'package:equatable/equatable.dart'; + +abstract class VisitorPasswordEvent extends Equatable { + const VisitorPasswordEvent( + + ); + + @override + List get props => []; +} + +class SelectPasswordType extends VisitorPasswordEvent { + final String type; + + const SelectPasswordType(this.type); + + @override + List get props => [type]; +} diff --git a/lib/pages/visitor_password/bloc/visitor_password_state.dart b/lib/pages/visitor_password/bloc/visitor_password_state.dart new file mode 100644 index 00000000..c13d27db --- /dev/null +++ b/lib/pages/visitor_password/bloc/visitor_password_state.dart @@ -0,0 +1,23 @@ + + +import 'package:equatable/equatable.dart'; + +abstract class VisitorPasswordState extends Equatable { + const VisitorPasswordState(); + + @override + List get props => []; +} + +class VisitorPasswordInitial extends VisitorPasswordState {} + + + +class PasswordTypeSelected extends VisitorPasswordState { + final String selectedType; + + PasswordTypeSelected(this.selectedType); + + @override + List get props => [selectedType]; +} \ No newline at end of file diff --git a/lib/pages/visitor_password/view/visitor_password_dialog.dart b/lib/pages/visitor_password/view/visitor_password_dialog.dart new file mode 100644 index 00000000..ef9639c6 --- /dev/null +++ b/lib/pages/visitor_password/view/visitor_password_dialog.dart @@ -0,0 +1,290 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:syncrow_web/pages/visitor_password/bloc/visitor_password_bloc.dart'; +import 'package:syncrow_web/pages/visitor_password/bloc/visitor_password_event.dart'; +import 'package:syncrow_web/pages/visitor_password/bloc/visitor_password_state.dart'; +import 'package:syncrow_web/utils/color_manager.dart'; +import 'package:syncrow_web/utils/style.dart'; + +class VisitorPasswordDialog extends StatelessWidget { + const VisitorPasswordDialog({super.key}); + + @override + Widget build(BuildContext context) { + Size size = MediaQuery.of(context).size; + + return BlocProvider( + create: (context) => VisitorPasswordBloc(), + child: BlocBuilder( + builder: (context, state) { + return AlertDialog( + title: const Text('Create visitor password'), + content: SingleChildScrollView( + child: ListBody( + children: [ + Row( + children: [ + Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.start, + children: [ + Row( + children: [ + Text( + '* ', + style: Theme.of(context) + .textTheme + .bodyMedium! + .copyWith(color: Colors.red), + ), + const Text('User Name'), + ], + ), + Container( + width: size.width * 0.15, + decoration: containerDecoration, + child: TextFormField( + style: TextStyle(color: Colors.black), + decoration: textBoxDecoration()! + .copyWith(hintText: 'Please enter'), + ), + ), + ], + ), + SizedBox(width: size.width * 0.05), // Add spacing between columns + Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.start, + children: [ + Row( + children: [ + Text( + '* ', + style: Theme.of(context) + .textTheme + .bodyMedium! + .copyWith(color: Colors.red), + ), + const Text('Email Address'), + ], + ), + Container( + width: size.width * 0.15, + decoration: containerDecoration, + child: TextFormField( + style: TextStyle(color: Colors.black), + decoration: textBoxDecoration()! + .copyWith(hintText: 'Please enter'), + ), + ), + ], + ), + ], + ), + SizedBox(height: size.height * 0.02), // Add spacing + Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.start, + children: [ + Row( + children: [ + Text( + '* ', + style: Theme.of(context) + .textTheme + .bodyMedium! + .copyWith(color: Colors.red), + ), + const Text('Access Type'), + ], + ), + Row( + children: [ + SizedBox( + width: 200, + child: RadioListTile( + title: Text('Offline Password'), + value: 'Offline Password', + groupValue: (state is PasswordTypeSelected) + ? state.selectedType + : 'Offline Password', + onChanged: (String? value) { + if (value != null) { + context + .read() + .add(SelectPasswordType(value)); + } + }, + ), + ), + SizedBox( + width: 200, + + child: RadioListTile( + title: Text('Online Password'), + value: 'Online Password', + groupValue: (state is PasswordTypeSelected) + ? state.selectedType + : 'Offline Password', + onChanged: (String? value) { + if (value != null) { + context + .read() + .add(SelectPasswordType(value)); + } + }, + ), + ), + SizedBox( + width: 200, + + child: RadioListTile( + title: Text('Dynamic Password'), + value: 'Dynamic Password', + groupValue: (state is PasswordTypeSelected) + ? state.selectedType + : 'Offline Password', + onChanged: (String? value) { + if (value != null) { + context + .read() + .add(SelectPasswordType(value)); + } + }, + ), + ), + ], + ) + ], + ), + Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.start, + children: [ + Row( + children: [ + Text( + '* ', + style: Theme.of(context) + .textTheme + .bodyMedium! + .copyWith(color: Colors.red), + ), + const Text('Usage Frequency'), + ], + ), + Row( + children: [ + SizedBox( + width: 200, + child: RadioListTile( + title: const Text('One-Time'), + value: 'One-Time', + groupValue: (state is PasswordTypeSelected) + ? state.selectedType + : 'One-Time', + onChanged: (String? value) { + if (value != null) { + context + .read() + .add(SelectPasswordType(value)); + } + }, + ), + ), + SizedBox( + width: 200, + + child: RadioListTile( + title: Text('Periodic'), + value: 'Periodic', + groupValue: (state is PasswordTypeSelected) + ? state.selectedType + : 'Periodic', + onChanged: (String? value) { + if (value != null) { + context + .read() + .add(SelectPasswordType(value)); + } + }, + ), + ), + ], + ) + ], + ), + Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.start, + children: [ + Row( + children: [ + Text( + '* ', + style: Theme.of(context) + .textTheme + .bodyMedium! + .copyWith(color: Colors.red), + ), + const Text('Access Period'), + ], + ), + Row( + children: [ + SizedBox( + width: 200, + child: RadioListTile( + title: const Text('One-Time'), + value: 'One-Time', + groupValue: (state is PasswordTypeSelected) + ? state.selectedType + : 'One-Time', + onChanged: (String? value) { + if (value != null) { + context + .read() + .add(SelectPasswordType(value)); + } + }, + ), + ), + SizedBox( + width: 200, + + child: RadioListTile( + title: Text('Periodic'), + value: 'Periodic', + groupValue: (state is PasswordTypeSelected) + ? state.selectedType + : 'Periodic', + onChanged: (String? value) { + if (value != null) { + context + .read() + .add(SelectPasswordType(value)); + } + }, + ), + ), + ], + ) + ], + ), + ], + ), + ), + actions: [ + TextButton( + child: const Text('Approve'), + onPressed: () { + Navigator.of(context).pop(); + }, + ), + ], + ); + }, + ), + ); + } +} diff --git a/lib/services/access_mang_api.dart b/lib/services/access_mang_api.dart index 0882e98b..49870143 100644 --- a/lib/services/access_mang_api.dart +++ b/lib/services/access_mang_api.dart @@ -1,36 +1,37 @@ import 'dart:convert'; -import 'package:flutter/services.dart'; -import 'package:syncrow_web/pages/access_management/model/access_manag_model.dart'; -import 'package:syncrow_web/pages/auth/model/user_model.dart'; +import 'package:dio/dio.dart'; +import 'package:flutter/cupertino.dart'; +import 'package:syncrow_web/pages/access_management/model/password_model.dart'; import 'package:syncrow_web/services/api/http_service.dart'; import 'package:syncrow_web/utils/constants/api_const.dart'; class AccessMangApi{ - // Future> fetchInfo() async { - // final response = await HTTPService().get( - // path: '/Users/mohammad/StudioProjects/web_auth/assets/demo.json', - // showServerMessage: true, - // expectedResponseModel: (json) { - // print('fetchInfo=$json'); - // return (json as List).map((item) => AccessManagModel.fromJson(item)).toList(); - // }, - // ); - // return response; - // } - Future> fetchInfo() async { - // Load the JSON file - final jsonString = await rootBundle.loadString('assets/dome.json'); - // Parse the JSON string - final List jsonList = json.decode(jsonString); - print('jsonList=${jsonList.runtimeType}'); - print('jsonList=${jsonList}'); - // Convert the list of JSON objects to a list of AccessManagModel instances - final List accessList = jsonList.map((item) => AccessManagModel.fromJson(item)).toList(); - - return accessList; + Future> fetchVisitorPassword() async { + try { + final response = await HTTPService().get( + path: ApiEndpoints.visitorPassword, + showServerMessage: true, + expectedResponseModel: (json) { + List jsonData = json; + print('Password List: $json'); + List passwordList = jsonData.map((jsonItem) { + return PasswordModel.fromJson(jsonItem); + }).toList(); + return passwordList; + }, + ); + return response; + } catch (e) { + debugPrint('Error fetching visitor passwords: $e'); + return []; + } } + + + + } \ No newline at end of file diff --git a/lib/services/auth_api.dart b/lib/services/auth_api.dart index a0c0b87f..7be7f328 100644 --- a/lib/services/auth_api.dart +++ b/lib/services/auth_api.dart @@ -50,8 +50,7 @@ class AuthenticationAPI { } ); return 30; - } on DioError catch (e) { - + } on DioException catch (e) { if (e.response != null) { if (e.response!.statusCode == 400) { // Handle 400 Bad Request @@ -64,7 +63,6 @@ class AuthenticationAPI { int cooldown = errorData['data']['cooldown'] ?? 1; return cooldown; } - } else { debugPrint('Error: ${e.response!.statusCode} - ${e.response!.statusMessage}'); return 1; @@ -73,7 +71,6 @@ class AuthenticationAPI { debugPrint('Error: ${e.message}'); return 1; } - return 1; } catch (e) { debugPrint('Unexpected Error: $e'); return 1; diff --git a/lib/utils/constants/api_const.dart b/lib/utils/constants/api_const.dart index 4eb6ceed..d689ce79 100644 --- a/lib/utils/constants/api_const.dart +++ b/lib/utils/constants/api_const.dart @@ -9,5 +9,6 @@ abstract class ApiEndpoints { static const String sendOtp = '$baseUrl/authentication/user/send-otp'; static const String verifyOtp = '$baseUrl/authentication/user/verify-otp'; static const String getRegion = '$baseUrl/region'; + static const String visitorPassword = '$baseUrl/visitor-password'; static const String getUser = '$baseUrl/user/{userUuid}'; } From 6efcc6081d860c084f8b740476805873afa47f59 Mon Sep 17 00:00:00 2001 From: mohammad Date: Wed, 14 Aug 2024 12:37:41 +0300 Subject: [PATCH 06/32] visitor password --- lib/main.dart | 7 +- .../view/access_management.dart | 53 ++--- lib/pages/common/custom_web_textfield.dart | 67 ++++++ lib/pages/common/date_time_widget.dart | 74 ++++++ .../bloc/visitor_password_bloc.dart | 103 +++++++- .../bloc/visitor_password_event.dart | 27 ++- .../bloc/visitor_password_state.dart | 11 +- .../view/visitor_password_dialog.dart | 224 +++++++----------- lib/utils/style.dart | 2 +- 9 files changed, 380 insertions(+), 188 deletions(-) create mode 100644 lib/pages/common/custom_web_textfield.dart create mode 100644 lib/pages/common/date_time_widget.dart diff --git a/lib/main.dart b/lib/main.dart index ab12a5fa..91173eb5 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -43,8 +43,7 @@ class MyApp extends StatelessWidget { ), theme: ThemeData( textTheme: const TextTheme( - bodySmall: TextStyle( - fontSize: 13, color: ColorsManager.whiteColors, fontWeight: FontWeight.bold), + bodySmall: TextStyle(fontSize: 13, color: ColorsManager.whiteColors, fontWeight: FontWeight.bold), bodyMedium: TextStyle(color: Colors.black87, fontSize: 14), bodyLarge: TextStyle(fontSize: 16, color: Colors.white), headlineSmall: TextStyle(color: Colors.black87, fontSize: 18), @@ -58,8 +57,8 @@ class MyApp extends StatelessWidget { colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple), // Set up color scheme useMaterial3: true, // Enable Material 3 ), - home: VisitorPasswordDialog() - // isLoggedIn == 'Success' ? const HomePage() : const LoginPage(), + // home: VisitorPasswordDialog() + home:isLoggedIn == 'Success' ? const HomePage() : const LoginPage(), )); } } diff --git a/lib/pages/access_management/view/access_management.dart b/lib/pages/access_management/view/access_management.dart index 943849d8..a0d89948 100644 --- a/lib/pages/access_management/view/access_management.dart +++ b/lib/pages/access_management/view/access_management.dart @@ -1,13 +1,12 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; -import 'package:flutter_svg/flutter_svg.dart'; import 'package:syncrow_web/pages/access_management/bloc/access_bloc.dart'; import 'package:syncrow_web/pages/access_management/bloc/access_event.dart'; import 'package:syncrow_web/pages/access_management/bloc/access_state.dart'; +import 'package:syncrow_web/pages/common/date_time_widget.dart'; import 'package:syncrow_web/pages/common/default_button.dart'; import 'package:syncrow_web/pages/visitor_password/view/visitor_password_dialog.dart'; import 'package:syncrow_web/utils/color_manager.dart'; -import 'package:syncrow_web/utils/constants/assets.dart'; import 'package:syncrow_web/utils/style.dart'; import 'package:syncrow_web/web_layout/web_scaffold.dart'; @@ -122,42 +121,19 @@ class AccessManagementPage extends StatelessWidget { const SizedBox( width: 15, ), - Column( - crossAxisAlignment: CrossAxisAlignment.start, - mainAxisAlignment: MainAxisAlignment.start, - children: [ - const Text('Access Time'), - Container( - width: size.width * 0.25, - padding: EdgeInsets.all(10), - decoration: containerDecoration, - child: Column( - children: [ - Row( - mainAxisAlignment: - MainAxisAlignment.spaceBetween, - children: [ - InkWell( - onTap: () { - accessBloc.add(SelectTime(context: context, isStart: true)); - }, - child: Text(BlocProvider.of(context).startTime) - ), - const Icon(Icons.arrow_right_alt), - InkWell( - onTap: () { - accessBloc.add(SelectTime(context: context, isStart: false)); - }, - child: Text(BlocProvider.of(context).endTime)), - SvgPicture.asset( - Assets.calendarIcon, - ), - ], - ), - ], - )), - ], - ), + DateTimeWebWidget( + isRequired: false, + title: 'Access Time', + size: size, + endTime: () { + accessBloc.add(SelectTime(context: context, isStart: false)); + }, + startTime: () { + accessBloc.add(SelectTime(context: context, isStart: true)); + }, + firstString:BlocProvider.of(context).startTime , + secondString:BlocProvider.of(context).endTime , + ) , const SizedBox( width: 15, ), @@ -334,6 +310,7 @@ class AccessManagementPage extends StatelessWidget { } } + Widget _buildTableHeaderCell(String title) { return Expanded( child: Container( diff --git a/lib/pages/common/custom_web_textfield.dart b/lib/pages/common/custom_web_textfield.dart new file mode 100644 index 00000000..003ca88d --- /dev/null +++ b/lib/pages/common/custom_web_textfield.dart @@ -0,0 +1,67 @@ + +import 'package:flutter/material.dart'; +import 'package:syncrow_web/utils/color_manager.dart'; +import 'package:syncrow_web/utils/style.dart'; +class CustomWebTextField extends StatelessWidget { + const CustomWebTextField({ + super.key, + required this.isRequired, + required this.textFieldName, + required this.controller, + this.description, + }); + + final bool isRequired; + final String textFieldName; + final String? description; + final TextEditingController? controller; + + + @override + Widget build(BuildContext context) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.start, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + if(isRequired) + Row( + children: [ + Text( + '* ', + style: Theme.of(context) + .textTheme + .bodyMedium! + .copyWith(color: Colors.red), + ), + Text(textFieldName), + ], + ), + Text( + description??'', // ' The password will be sent to the visitor’s email address.', + style: Theme.of(context) + .textTheme + .bodySmall! + .copyWith( + fontSize: 9, + fontWeight: FontWeight.w400, + color: ColorsManager.textGray), + ), + ], + ), + const SizedBox(height: 7,), + Container( + decoration: containerDecoration, + child: TextFormField( + controller: controller, + style: const TextStyle(color: Colors.black), + decoration: textBoxDecoration()! + .copyWith(hintText: 'Please enter'), + ), + ), + ], + ); + } +} diff --git a/lib/pages/common/date_time_widget.dart b/lib/pages/common/date_time_widget.dart new file mode 100644 index 00000000..ace2cf8d --- /dev/null +++ b/lib/pages/common/date_time_widget.dart @@ -0,0 +1,74 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_svg/flutter_svg.dart'; +import 'package:syncrow_web/utils/constants/assets.dart'; +import 'package:syncrow_web/utils/style.dart'; + +class DateTimeWebWidget extends StatelessWidget { + const DateTimeWebWidget({ + super.key, + required this.size, + required this.isRequired, + required this.title, + required this.startTime, + required this.endTime, + required this.firstString, + required this.secondString, + }); + + final Size size; + final String title; + final bool isRequired; + final String firstString; + final String secondString; + final Function()? startTime; + final Function()? endTime; + + @override + Widget build(BuildContext context) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.start, + children: [ + Row( + children: [ + if(isRequired) + Text( + '* ', + style: Theme.of(context) + .textTheme + .bodyMedium! + .copyWith(color: Colors.red), + ), + Text(title??''), + ], + ), + SizedBox(height: 8,), + Container( + width: size.width * 0.25, + padding: EdgeInsets.all(10), + decoration: containerDecoration, + child: Column( + children: [ + Row( + mainAxisAlignment: + MainAxisAlignment.spaceBetween, + children: [ + InkWell( + onTap: startTime, + child: Text(firstString) + ), + const Icon(Icons.arrow_right_alt), + InkWell( + onTap:endTime, + child: Text(secondString)), + SvgPicture.asset( + Assets.calendarIcon, + ), + ], + ), + ], + )), + ], + ); + } +} diff --git a/lib/pages/visitor_password/bloc/visitor_password_bloc.dart b/lib/pages/visitor_password/bloc/visitor_password_bloc.dart index 41510a8d..f12e67f1 100644 --- a/lib/pages/visitor_password/bloc/visitor_password_bloc.dart +++ b/lib/pages/visitor_password/bloc/visitor_password_bloc.dart @@ -1,13 +1,108 @@ +import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:syncrow_web/pages/visitor_password/bloc/visitor_password_event.dart'; import 'package:syncrow_web/pages/visitor_password/bloc/visitor_password_state.dart'; +import 'package:syncrow_web/utils/color_manager.dart'; +import 'package:syncrow_web/utils/snack_bar.dart'; // Define the BLoC class VisitorPasswordBloc extends Bloc { VisitorPasswordBloc() : super(VisitorPasswordInitial()) { - on((event, emit) { - // Handle the event and emit the new state - emit(PasswordTypeSelected(event.type)); - }); + on(selectUsageFrequency); + on(selectAccessType); + on(selectTimeVisitorPassword); } + final TextEditingController userNameController = TextEditingController(); + final TextEditingController emailController = TextEditingController(); + + String accessTypeSelected=''; + String usageFrequencySelected=''; + + + int? effectiveTimeTimeStamp; + int? expirationTimeTimeStamp; + + String startTime = 'Start Time'; + String endTime = 'End Time'; + + selectAccessType(SelectPasswordType event, Emitter emit) { + accessTypeSelected=event.type; + print(accessTypeSelected); + + emit(PasswordTypeSelected(event.type)); + } + + selectUsageFrequency(SelectUsageFrequency event, Emitter emit) { + usageFrequencySelected=event.usageType; + print(usageFrequencySelected); + emit(UsageFrequencySelected(event.usageType)); + } + + + Future selectTimeVisitorPassword(SelectTimeVisitorPassword event, Emitter emit) async { + final DateTime? picked = await showDatePicker( + context: event.context, + initialDate: DateTime.now(), + firstDate: DateTime(2015, 8), + lastDate: DateTime(2101), + ); + if (picked != null) { + final TimeOfDay? timePicked = await showTimePicker( + context: event.context, + initialTime: TimeOfDay.now(), + + builder: (context, child) { + return Theme( + data: ThemeData.light().copyWith( + colorScheme: const ColorScheme.light( + primary: ColorsManager.primaryColor, + onSurface: Colors.black, + ), + buttonTheme: const ButtonThemeData( + colorScheme: ColorScheme.light( + primary: Colors.green, + ), + ), + ), + child: child!, + ); + }, + ); + if (timePicked != null) { + final selectedDateTime = DateTime( + picked.year, + picked.month, + picked.day, + timePicked.hour, + timePicked.minute, + ); + final selectedTimestamp = DateTime( + selectedDateTime.year, + selectedDateTime.month, + selectedDateTime.day, + selectedDateTime.hour, + selectedDateTime.minute, + ).millisecondsSinceEpoch ~/ 1000; // Divide by 1000 to remove milliseconds + if (event.isStart) { + if (expirationTimeTimeStamp != null && selectedTimestamp > expirationTimeTimeStamp!) { + CustomSnackBar.displaySnackBar('Effective Time cannot be later than Expiration Time.'); + } else { + startTime = selectedDateTime.toString().split('.').first; // Remove seconds and milliseconds + effectiveTimeTimeStamp = selectedTimestamp; + } + } else { + if (effectiveTimeTimeStamp != null && selectedTimestamp < effectiveTimeTimeStamp!) { + CustomSnackBar.displaySnackBar('Expiration Time cannot be earlier than Effective Time.'); + } else { + endTime = selectedDateTime.toString().split('.').first; // Remove seconds and milliseconds + expirationTimeTimeStamp = selectedTimestamp; + } + } + } + } + // emit(AccessInitial()); + // emit(TableLoaded(data)); + } + + } diff --git a/lib/pages/visitor_password/bloc/visitor_password_event.dart b/lib/pages/visitor_password/bloc/visitor_password_event.dart index 37083997..a01a702c 100644 --- a/lib/pages/visitor_password/bloc/visitor_password_event.dart +++ b/lib/pages/visitor_password/bloc/visitor_password_event.dart @@ -1,13 +1,8 @@ - - - - import 'package:equatable/equatable.dart'; +import 'package:flutter/cupertino.dart'; abstract class VisitorPasswordEvent extends Equatable { - const VisitorPasswordEvent( - - ); + const VisitorPasswordEvent(); @override List get props => []; @@ -21,3 +16,21 @@ class SelectPasswordType extends VisitorPasswordEvent { @override List get props => [type]; } + +class SelectUsageFrequency extends VisitorPasswordEvent { + final String usageType; + + const SelectUsageFrequency(this.usageType); + + @override + List get props => [usageType]; +} +class SelectTimeVisitorPassword extends VisitorPasswordEvent { + final BuildContext context; + final bool isStart; + + const SelectTimeVisitorPassword({ required this.context,required this.isStart}); + + @override + List get props => [context,isStart]; +} diff --git a/lib/pages/visitor_password/bloc/visitor_password_state.dart b/lib/pages/visitor_password/bloc/visitor_password_state.dart index c13d27db..b12232f4 100644 --- a/lib/pages/visitor_password/bloc/visitor_password_state.dart +++ b/lib/pages/visitor_password/bloc/visitor_password_state.dart @@ -16,8 +16,17 @@ class VisitorPasswordInitial extends VisitorPasswordState {} class PasswordTypeSelected extends VisitorPasswordState { final String selectedType; - PasswordTypeSelected(this.selectedType); + const PasswordTypeSelected(this.selectedType); @override List get props => [selectedType]; +} + +class UsageFrequencySelected extends VisitorPasswordState { + final String selectedFrequency; + + const UsageFrequencySelected(this.selectedFrequency); + + @override + List get props => [selectedFrequency]; } \ No newline at end of file diff --git a/lib/pages/visitor_password/view/visitor_password_dialog.dart b/lib/pages/visitor_password/view/visitor_password_dialog.dart index ef9639c6..7fc6fbd3 100644 --- a/lib/pages/visitor_password/view/visitor_password_dialog.dart +++ b/lib/pages/visitor_password/view/visitor_password_dialog.dart @@ -1,86 +1,54 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:syncrow_web/pages/common/custom_web_textfield.dart'; +import 'package:syncrow_web/pages/common/date_time_widget.dart'; +import 'package:syncrow_web/pages/common/default_button.dart'; import 'package:syncrow_web/pages/visitor_password/bloc/visitor_password_bloc.dart'; import 'package:syncrow_web/pages/visitor_password/bloc/visitor_password_event.dart'; import 'package:syncrow_web/pages/visitor_password/bloc/visitor_password_state.dart'; -import 'package:syncrow_web/utils/color_manager.dart'; import 'package:syncrow_web/utils/style.dart'; + class VisitorPasswordDialog extends StatelessWidget { const VisitorPasswordDialog({super.key}); @override Widget build(BuildContext context) { Size size = MediaQuery.of(context).size; - return BlocProvider( create: (context) => VisitorPasswordBloc(), child: BlocBuilder( builder: (context, state) { + final visitorBloc = BlocProvider.of(context); return AlertDialog( title: const Text('Create visitor password'), content: SingleChildScrollView( child: ListBody( children: [ - Row( + Row( children: [ - Column( - crossAxisAlignment: CrossAxisAlignment.start, - mainAxisAlignment: MainAxisAlignment.start, - children: [ - Row( - children: [ - Text( - '* ', - style: Theme.of(context) - .textTheme - .bodyMedium! - .copyWith(color: Colors.red), - ), - const Text('User Name'), - ], - ), - Container( - width: size.width * 0.15, - decoration: containerDecoration, - child: TextFormField( - style: TextStyle(color: Colors.black), - decoration: textBoxDecoration()! - .copyWith(hintText: 'Please enter'), - ), - ), - ], + Expanded( + flex: 2, + child: CustomWebTextField( + controller:visitorBloc.userNameController , + isRequired: true, + textFieldName: 'User Name', + description: '', + ), ), - SizedBox(width: size.width * 0.05), // Add spacing between columns - Column( - crossAxisAlignment: CrossAxisAlignment.start, - mainAxisAlignment: MainAxisAlignment.start, - children: [ - Row( - children: [ - Text( - '* ', - style: Theme.of(context) - .textTheme - .bodyMedium! - .copyWith(color: Colors.red), - ), - const Text('Email Address'), - ], - ), - Container( - width: size.width * 0.15, - decoration: containerDecoration, - child: TextFormField( - style: TextStyle(color: Colors.black), - decoration: textBoxDecoration()! - .copyWith(hintText: 'Please enter'), - ), - ), - ], + const Spacer(), + Expanded( + flex: 2, + child: CustomWebTextField( + controller:visitorBloc.emailController , + isRequired: true, + textFieldName: 'Email Address', + description: 'The password will be sent to the visitor’s email address.', + ), ), ], ), + SizedBox(height: size.height * 0.02), // Add spacing Column( crossAxisAlignment: CrossAxisAlignment.start, @@ -101,25 +69,22 @@ class VisitorPasswordDialog extends StatelessWidget { Row( children: [ SizedBox( - width: 200, + width: size.width*0.15, child: RadioListTile( - title: Text('Offline Password'), + title: const Text('Offline Password'), value: 'Offline Password', groupValue: (state is PasswordTypeSelected) ? state.selectedType : 'Offline Password', onChanged: (String? value) { if (value != null) { - context - .read() - .add(SelectPasswordType(value)); + context.read().add(SelectPasswordType(value)); } }, ), ), SizedBox( - width: 200, - + width: size.width*0.15, child: RadioListTile( title: Text('Online Password'), value: 'Online Password', @@ -136,10 +101,9 @@ class VisitorPasswordDialog extends StatelessWidget { ), ), SizedBox( - width: 200, - + width: size.width*0.15, child: RadioListTile( - title: Text('Dynamic Password'), + title: const Text('Dynamic Password'), value: 'Dynamic Password', groupValue: (state is PasswordTypeSelected) ? state.selectedType @@ -180,32 +144,29 @@ class VisitorPasswordDialog extends StatelessWidget { child: RadioListTile( title: const Text('One-Time'), value: 'One-Time', - groupValue: (state is PasswordTypeSelected) - ? state.selectedType + groupValue: (state is UsageFrequencySelected) + ? state.selectedFrequency : 'One-Time', onChanged: (String? value) { if (value != null) { - context - .read() - .add(SelectPasswordType(value)); + context.read() + .add(SelectUsageFrequency(value)); } }, ), ), SizedBox( width: 200, - child: RadioListTile( - title: Text('Periodic'), + title: const Text('Periodic'), value: 'Periodic', - groupValue: (state is PasswordTypeSelected) - ? state.selectedType - : 'Periodic', + groupValue: (state is UsageFrequencySelected) + ? state.selectedFrequency + : 'Periodic1', onChanged: (String? value) { if (value != null) { - context - .read() - .add(SelectPasswordType(value)); + context.read() + .add(SelectUsageFrequency(value)); } }, ), @@ -214,73 +175,69 @@ class VisitorPasswordDialog extends StatelessWidget { ) ], ), + DateTimeWebWidget( + isRequired: true, + title: 'Access Period', + size: size, + endTime: () { + visitorBloc.add(SelectTimeVisitorPassword(context: context, isStart: false)); + }, + startTime: () { + visitorBloc.add(SelectTimeVisitorPassword(context: context, isStart: true)); + }, + firstString:BlocProvider.of(context).startTime , + secondString:BlocProvider.of(context).endTime , + ) , + Column( - crossAxisAlignment: CrossAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( children: [ - Text( - '* ', - style: Theme.of(context) - .textTheme - .bodyMedium! - .copyWith(color: Colors.red), - ), - const Text('Access Period'), + Text( + '* ', + style: Theme.of(context) + .textTheme + .bodyMedium! + .copyWith(color: Colors.red), + ), + const Text('Access Devices'), ], ), - Row( - children: [ - SizedBox( - width: 200, - child: RadioListTile( - title: const Text('One-Time'), - value: 'One-Time', - groupValue: (state is PasswordTypeSelected) - ? state.selectedType - : 'One-Time', - onChanged: (String? value) { - if (value != null) { - context - .read() - .add(SelectPasswordType(value)); - } - }, - ), - ), - SizedBox( - width: 200, + const Text('Within the validity period, each device can be unlocked only once.'), + Container( + decoration: containerDecoration, + width: size.width*0.2, + child: const DefaultButton( + borderRadius: 8, + child:Text('+ Add Device'),)), - child: RadioListTile( - title: Text('Periodic'), - value: 'Periodic', - groupValue: (state is PasswordTypeSelected) - ? state.selectedType - : 'Periodic', - onChanged: (String? value) { - if (value != null) { - context - .read() - .add(SelectPasswordType(value)); - } - }, - ), - ), - ], - ) ], - ), + ) ], ), ), + actionsAlignment: MainAxisAlignment.center, actions: [ - TextButton( - child: const Text('Approve'), - onPressed: () { - Navigator.of(context).pop(); - }, - ), + Container( + decoration: containerDecoration, + width: size.width*0.2, + child: DefaultButton( + borderRadius: 8, + onPressed:() {}, + backgroundColor: Colors.white, + child:Text('Cancel',style: Theme.of(context) + .textTheme + .bodyMedium!,),)), + + Container( + decoration: containerDecoration, + width: size.width*0.2, + child: const DefaultButton( + borderRadius: 8, + child:Text('Ok'),)), + ], ); }, @@ -288,3 +245,4 @@ class VisitorPasswordDialog extends StatelessWidget { ); } } + diff --git a/lib/utils/style.dart b/lib/utils/style.dart index 5edcdfad..41599fad 100644 --- a/lib/utils/style.dart +++ b/lib/utils/style.dart @@ -28,7 +28,7 @@ Decoration containerDecoration = BoxDecoration( BoxShadow( color: Colors.grey.withOpacity(0.5), spreadRadius: 5, - blurRadius: 7, + blurRadius: 8, offset: Offset(0, 3), // changes position of shadow ), From de46bc98721094a477404f558f0a960111d009f4 Mon Sep 17 00:00:00 2001 From: mohammad Date: Thu, 15 Aug 2024 09:51:10 +0300 Subject: [PATCH 07/32] login changes --- lib/pages/auth/bloc/auth_bloc.dart | 105 ++-- lib/pages/auth/bloc/auth_event.dart | 4 +- .../auth/view/forget_password_web_page.dart | 3 +- lib/pages/auth/view/login_mobile_page.dart | 3 +- lib/pages/auth/view/login_page.dart | 3 +- lib/pages/auth/view/login_web_page.dart | 571 ++++++++++-------- lib/pages/common/default_button.dart | 2 +- lib/utils/style.dart | 14 +- 8 files changed, 391 insertions(+), 314 deletions(-) diff --git a/lib/pages/auth/bloc/auth_bloc.dart b/lib/pages/auth/bloc/auth_bloc.dart index 0a3b0d24..5898f2dd 100644 --- a/lib/pages/auth/bloc/auth_bloc.dart +++ b/lib/pages/auth/bloc/auth_bloc.dart @@ -24,25 +24,33 @@ class AuthBloc extends Bloc { on(_passwordVisible); on(_fetchRegion); on(selectRegion); + on(checkEnable); + on(changeValidate); } - ////////////////////////////// forget password ////////////////////////////////// + ////////////////////////////// forget password ////////////////////////////////// final TextEditingController forgetEmailController = TextEditingController(); - final TextEditingController forgetPasswordController = TextEditingController(); + final TextEditingController forgetPasswordController = + TextEditingController(); final TextEditingController forgetOtp = TextEditingController(); final forgetFormKey = GlobalKey(); + late bool checkValidate = false; + Timer? _timer; int _remainingTime = 0; - List? regionList=[RegionModel(name: 'name', id: 'id')]; + List? regionList = [RegionModel(name: 'name', id: 'id')]; - Future _onStartTimer(StartTimerEvent event, Emitter emit) async { + Future _onStartTimer( + StartTimerEvent event, Emitter emit) async { if (_validateInputs(emit)) return; if (_timer != null && _timer!.isActive) { return; } _remainingTime = 1; - add(UpdateTimerEvent(remainingTime: _remainingTime, isButtonEnabled: false)); - _remainingTime = (await AuthenticationAPI.sendOtp(email: forgetEmailController.text,regionUuid: regionUuid))!; + add(UpdateTimerEvent( + remainingTime: _remainingTime, isButtonEnabled: false)); + _remainingTime = (await AuthenticationAPI.sendOtp( + email: forgetEmailController.text, regionUuid: regionUuid))!; _timer = Timer.periodic(const Duration(seconds: 1), (timer) { _remainingTime--; if (_remainingTime <= 0) { @@ -85,10 +93,6 @@ class AuthBloc extends Bloc { remainingTime: event.remainingTime)); } - - - - ///////////////////////////////////// login ///////////////////////////////////// final TextEditingController loginEmailController = TextEditingController(); final TextEditingController loginPasswordController = TextEditingController(); @@ -106,6 +110,7 @@ class AuthBloc extends Bloc { void _login(LoginButtonPressed event, Emitter emit) async { emit(AuthLoading()); + if (isChecked) { try { if (event.username.isEmpty || event.password.isEmpty) { @@ -115,14 +120,14 @@ class AuthBloc extends Bloc { } token = await AuthenticationAPI.loginWithEmail( model: LoginWithEmailModel( - email: event.username, - password: event.password, - regionUuid: event.regionUuid - ), + email: event.username, + password: event.password, + regionUuid: event.regionUuid), ); } catch (failure) { - validate='Something went wrong'; - emit(const LoginFailure(error: 'Something went wrong')); + validate = 'Invalid Credentials!'; + emit(AuthInitialState()); + // emit(const LoginFailure(error: 'Something went wrong')); return; } if (token.accessTokenIsNotEmpty) { @@ -137,9 +142,11 @@ class AuthBloc extends Bloc { loginPasswordController.clear(); emit(LoginSuccess()); } else { + emit(const LoginFailure(error: 'Something went wrong')); } } else { + emit(const LoginFailure(error: 'Accept terms and condition')); } } @@ -147,13 +154,13 @@ class AuthBloc extends Bloc { checkBoxToggle(CheckBoxEvent event, Emitter emit,) { emit(AuthLoading()); isChecked = event.newValue!; + add(CheckEnableEvent()); emit(LoginInitial()); } checkOtpCode(ChangePasswordEvent event, Emitter emit,) async { emit(LoadingForgetState()); - await AuthenticationAPI.verifyOtp( - email: forgetEmailController.text, otpCode: forgetOtp.text); + await AuthenticationAPI.verifyOtp(email: forgetEmailController.text, otpCode: forgetOtp.text); emit(SuccessForgetState()); } @@ -163,30 +170,32 @@ class AuthBloc extends Bloc { emit(PasswordVisibleState()); } - void launchURL(String url) { - - } - - + void launchURL(String url) {} /////////////////////////////////////VALIDATORS///////////////////////////////////// String? validatePassword(String? value) { if (value == null || value.isEmpty) { - return 'Password is required'; - } else if (value.length < 8) { - return 'Password must be at least 8 characters'; + return ''; } return null; } String? validateEmail(String? value) { + if (value == null || value.isEmpty) { return 'Email is required'; } else if (!RegExp(r'^[^@]+@[^@]+\.[^@]+').hasMatch(value)) { return 'Enter a valid email address'; - }else if (regionUuid=='') { + } else if (regionUuid == '') { return 'Please select your region'; } + validate=''; + return null; + } + String? loginValidateEmail(String? value) { + if (!RegExp(r'^[^@]+@[^@]+\.[^@]+').hasMatch(value!)) { + return ''; + } return null; } @@ -277,17 +286,17 @@ class AuthBloc extends Bloc { return '$maskedLocalPart@$domainPart'; } - - - - static Future getTokenAndValidate() async { + static Future getTokenAndValidate() async { try { const storage = FlutterSecureStorage(); - final firstLaunch = await SharedPreferencesHelper.readBoolFromSP(StringsManager.firstLaunch) ?? true; + final firstLaunch = await SharedPreferencesHelper.readBoolFromSP( + StringsManager.firstLaunch) ?? + true; if (firstLaunch) { storage.deleteAll(); } - await SharedPreferencesHelper.saveBoolToSP(StringsManager.firstLaunch, false); + await SharedPreferencesHelper.saveBoolToSP( + StringsManager.firstLaunch, false); final value = await storage.read(key: Token.loginAccessTokenKey) ?? ''; if (value.isEmpty) { return 'Token not found'; @@ -312,19 +321,17 @@ class AuthBloc extends Bloc { void _fetchRegion(RegionInitialEvent event, Emitter emit) async { try { emit(AuthLoading()); - regionList = await AuthenticationAPI.fetchRegion(); + regionList = await AuthenticationAPI.fetchRegion(); emit(AuthInitialState()); } catch (e) { - emit( LoginFailure(error: e.toString())); - + emit(LoginFailure(error: e.toString())); } } - Future selectRegion(SelectRegionEvent event, Emitter emit) async { try { emit(AuthLoading()); - regionUuid= event.val; + regionUuid = event.val; emit(AuthInitialState()); } catch (e) { emit(FailureForgetState(error: e.toString())); @@ -340,7 +347,8 @@ class AuthBloc extends Bloc { final String formattedTime = [ if (days > 0) '${days}d', // Append 'd' for days - if (days > 0 || hours > 0) hours.toString().padLeft(2, '0'), // Show hours if there are days or hours + if (days > 0 || hours > 0) + hours.toString().padLeft(2, '0'), // Show hours if there are days or hours minutes.toString().padLeft(2, '0'), seconds.toString().padLeft(2, '0'), ].join(':'); @@ -348,8 +356,19 @@ class AuthBloc extends Bloc { return formattedTime; } - + bool checkEnable( CheckEnableEvent event, Emitter emit,) { + emit(AuthLoading()); + checkValidate = isChecked==true && + loginPasswordController.text.isNotEmpty && + loginEmailController.text.isNotEmpty && + regionUuid != ''; + emit(LoginInitial()); + return checkValidate; + } + changeValidate(ChangeValidateEvent event, Emitter emit,){ + emit(AuthLoading()); + validate=''; + print('validate'); + emit(LoginInitial()); + } } - - - diff --git a/lib/pages/auth/bloc/auth_event.dart b/lib/pages/auth/bloc/auth_event.dart index 50cd5c56..4f80a2db 100644 --- a/lib/pages/auth/bloc/auth_event.dart +++ b/lib/pages/auth/bloc/auth_event.dart @@ -13,7 +13,7 @@ class LoginButtonPressed extends AuthEvent { final String password; final String regionUuid; - const LoginButtonPressed({required this.username, required this.password, required this.regionUuid}); + const LoginButtonPressed({required this.username, required this.password, required this.regionUuid, }); @override List get props => [username, password,regionUuid]; @@ -53,6 +53,8 @@ class PasswordVisibleEvent extends AuthEvent{ } class RegionInitialEvent extends AuthEvent {} +class CheckEnableEvent extends AuthEvent {} +class ChangeValidateEvent extends AuthEvent {} class SelectRegionEvent extends AuthEvent { final String val; diff --git a/lib/pages/auth/view/forget_password_web_page.dart b/lib/pages/auth/view/forget_password_web_page.dart index d87b277f..b0e17374 100644 --- a/lib/pages/auth/view/forget_password_web_page.dart +++ b/lib/pages/auth/view/forget_password_web_page.dart @@ -254,8 +254,7 @@ class ForgetPasswordWebPage extends StatelessWidget { SizedBox( child: TextFormField( validator: forgetBloc.passwordValidator, - keyboardType: - TextInputType.visiblePassword, + keyboardType: TextInputType.visiblePassword, controller: forgetBloc.forgetPasswordController, decoration: textBoxDecoration()!.copyWith( diff --git a/lib/pages/auth/view/login_mobile_page.dart b/lib/pages/auth/view/login_mobile_page.dart index 50684bab..98402f59 100644 --- a/lib/pages/auth/view/login_mobile_page.dart +++ b/lib/pages/auth/view/login_mobile_page.dart @@ -297,8 +297,7 @@ class LoginMobilePage extends StatelessWidget { : ColorsManager.grayColor, child: const Text('Sign in'), onPressed: () { - if (loginBloc.loginFormKey.currentState! - .validate()) { + if (loginBloc.loginFormKey.currentState!.validate()) { loginBloc.add( LoginButtonPressed( regionUuid:'' , diff --git a/lib/pages/auth/view/login_page.dart b/lib/pages/auth/view/login_page.dart index 58b07299..92487fe4 100644 --- a/lib/pages/auth/view/login_page.dart +++ b/lib/pages/auth/view/login_page.dart @@ -1,7 +1,6 @@ import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:flutter/widgets.dart'; -import 'package:syncrow_web/pages/auth/view/login_mobile_page.dart'; import 'package:syncrow_web/pages/auth/view/login_web_page.dart'; import 'package:syncrow_web/utils/responsive_layout.dart'; @@ -11,7 +10,7 @@ class LoginPage extends StatelessWidget { Widget build(BuildContext context) { return const ResponsiveLayout( desktopBody: LoginWebPage(), - mobileBody:LoginMobilePage() + mobileBody:LoginWebPage() ); } } diff --git a/lib/pages/auth/view/login_web_page.dart b/lib/pages/auth/view/login_web_page.dart index bd2259fa..10ed36a6 100644 --- a/lib/pages/auth/view/login_web_page.dart +++ b/lib/pages/auth/view/login_web_page.dart @@ -48,11 +48,7 @@ class _LoginWebPageState extends State { } }, builder: (context, state) { - if (state is AuthLoading) { - return const Center(child: CircularProgressIndicator()); - } else { - return _buildLoginForm(context,state); - } + return _buildLoginForm(context,state); }, ), ), @@ -75,272 +71,327 @@ class _LoginWebPageState extends State { WidgetsBinding.instance.addPostFrameCallback((_) { _scrollToCenter(); }); - return FirstLayer( - second: Center( - child: ListView( - controller: _scrollController, - shrinkWrap: true, - children: [ - Container( - padding: EdgeInsets.all(size.width*0.02) , - margin: EdgeInsets.all(size.width*0.09), - decoration: BoxDecoration( - color: Colors.black.withOpacity(0.3), - borderRadius: const BorderRadius.all(Radius.circular(20)), - ), - child: Center( - child:Row( - crossAxisAlignment: CrossAxisAlignment.center, - mainAxisAlignment: MainAxisAlignment.center, - children: [ - const Spacer(), - Expanded( - flex: 3, - child: SvgPicture.asset( - Assets.loginLogo, - ), - ), - const Spacer(), - Expanded( - flex: 3, - child: Container( - decoration: BoxDecoration( - color: Colors.white.withOpacity(0.1), - borderRadius: const BorderRadius.all(Radius.circular(30)), - border: Border.all(color: ColorsManager.graysColor.withOpacity(0.2))), - child: Form( - key: loginBloc.loginFormKey, - child: Padding( - padding: EdgeInsets.symmetric( - horizontal: size.width*0.02, - vertical: size.width*0.003), - child: Column( - mainAxisAlignment: MainAxisAlignment.spaceEvenly, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - const SizedBox(height: 40), - Text( - 'Login', - style:Theme.of(context).textTheme.headlineLarge), - SizedBox(height: size.height*0.03), - Column( + return Stack( + children: [ + FirstLayer( + second: Center( + child: ListView( + controller: _scrollController, + shrinkWrap: true, + children: [ + Container( + padding: EdgeInsets.all(size.width*0.02) , + margin: EdgeInsets.all(size.width*0.09), + decoration: BoxDecoration( + color: Colors.black.withOpacity(0.3), + borderRadius: const BorderRadius.all(Radius.circular(20)), + ), + child: Center( + child:Row( + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + const Spacer(), + Expanded( + flex: 3, + child: SvgPicture.asset( + Assets.loginLogo, + ), + ), + const Spacer(), + Expanded( + flex: 3, + child: Container( + decoration: BoxDecoration( + color: Colors.white.withOpacity(0.1), + borderRadius: const BorderRadius.all(Radius.circular(30)), + border: Border.all(color: ColorsManager.graysColor.withOpacity(0.2))), + child: Form( + key: loginBloc.loginFormKey, + child: Padding( + padding: EdgeInsets.symmetric( + horizontal: size.width*0.02, + vertical: size.width*0.003), + child: Column( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, crossAxisAlignment: CrossAxisAlignment.start, - mainAxisAlignment: MainAxisAlignment.start, - children: [ - Text( - "Country/Region", - style: Theme.of(context).textTheme.bodySmall, - ), - const SizedBox(height: 10,), - SizedBox( - child: DropdownButtonFormField( - validator:loginBloc.validateRegion , - icon: const Icon( - Icons.keyboard_arrow_down_outlined, + children: [ + const SizedBox(height: 40), + Text( + 'Login', + style:Theme.of(context).textTheme.headlineLarge), + SizedBox(height: size.height*0.03), + Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.start, + children: [ + Text( + "Country/Region", + style: Theme.of(context).textTheme.bodySmall!.copyWith(fontSize: 14,fontWeight: FontWeight.w400), ), - decoration: textBoxDecoration()!.copyWith( - hintText: null,), - hint: SizedBox( - width: size.width * 0.11, - child: const Align( - alignment: Alignment.centerLeft, + const SizedBox(height: 10,), + + SizedBox( + child: DropdownButtonFormField( + padding: EdgeInsets.zero, + value: loginBloc.regionList!.any((region) => region.id == loginBloc.regionUuid) + ? loginBloc.regionUuid + : null, + + validator: loginBloc.validateRegion, + icon: const Icon( + Icons.keyboard_arrow_down_outlined, + ), + decoration: textBoxDecoration()!.copyWith( + errorStyle: const TextStyle(height: 0), + hintText: null, + ), + hint: SizedBox( + width: size.width * 0.12, + child: Align( + alignment: Alignment.centerLeft, + child: Text( + 'Select your region/country', + textAlign: TextAlign.center, + style: Theme.of(context).textTheme.bodySmall!.copyWith( + color: ColorsManager.grayColor, + fontWeight: FontWeight.w400), + overflow: TextOverflow.ellipsis, + ), + ), + ), + isDense: true, + style: const TextStyle(color: Colors.black), + items: loginBloc.regionList!.map((RegionModel region) { + return DropdownMenuItem( + value: region.id, + child: SizedBox( + width: size.width*0.06, + + child: Text(region.name)), + ); + }).toList(), + onChanged: (String? value) { + loginBloc.add(CheckEnableEvent()); + loginBloc.add(SelectRegionEvent(val: value!)); + }, + ), + ) + + + ], + ), + const SizedBox(height: 20.0), + Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.start, + children: [ + Text("Email", + style: Theme.of(context).textTheme.bodySmall!.copyWith(fontSize: 14,fontWeight: FontWeight.w400), + ), + const SizedBox( + height: 10, + ), + SizedBox( + child: TextFormField( + onChanged: (value) { + loginBloc.add(CheckEnableEvent()); + // print(loginBloc.checkEnable()); + }, + validator:loginBloc.loginValidateEmail , + controller:loginBloc.loginEmailController, + decoration: textBoxDecoration()!.copyWith( + errorStyle: const TextStyle(height: 0), // Hide the error text space + hintText: 'Enter your email address', + hintStyle: Theme.of(context).textTheme.bodySmall!.copyWith( + color: ColorsManager.grayColor, + fontWeight: FontWeight.w400) + ), + style: const TextStyle(color: Colors.black), + ), + ), + ], + ), + const SizedBox(height: 20.0), + Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.start, + children: [ + Text("Password", + style: Theme.of(context).textTheme.bodySmall!.copyWith(fontSize: 14,fontWeight: FontWeight.w400), + ), + const SizedBox( + height: 10, + ), + SizedBox( + child: TextFormField( + onChanged: (value) { + loginBloc.add(CheckEnableEvent()); + }, + validator:loginBloc.validatePassword, + obscureText:loginBloc.obscureText, + keyboardType: TextInputType.visiblePassword, + controller:loginBloc.loginPasswordController, + decoration: textBoxDecoration()!.copyWith( + hintText: 'At least 8 characters', + hintStyle: Theme.of(context).textTheme.bodySmall!.copyWith( + color: ColorsManager.grayColor, + fontWeight: FontWeight.w400), + suffixIcon: IconButton(onPressed: () { + loginBloc.add(PasswordVisibleEvent(newValue: loginBloc.obscureText)); + }, + icon: SizedBox( + child: SvgPicture.asset( + loginBloc.obscureText? + Assets.visiblePassword : + Assets.invisiblePassword, + height: 15, + width: 15, + ), + ), + ), + errorStyle: const TextStyle(height: 0), // Hide the error text space + ), + style: const TextStyle(color: Colors.black), + ), + ), + ], + ), + const SizedBox( + height: 20, + ), + SizedBox( + child: Row( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + InkWell( + onTap: () { + Navigator.of(context).push(MaterialPageRoute(builder: (context) => const ForgetPasswordPage(),)); + }, child: Text( - 'Select your region/country', - textAlign: TextAlign.center, - style: TextStyle(fontSize: 14), - overflow: TextOverflow.ellipsis, + "Forgot Password?", + style: Theme.of(context).textTheme.bodySmall!.copyWith(color: Colors.black,fontSize: 14,fontWeight: FontWeight.w400), + ), + ), + ], + ), + ), + const SizedBox( + height: 20, + ), + Row( + children: [ + Transform.scale( + scale: 1.2, // Adjust the scale as needed + child: Checkbox( + fillColor: MaterialStateProperty.all(Colors.white), + activeColor: Colors.white, + value:loginBloc.isChecked, + checkColor: Colors.black, + shape: const CircleBorder(), + onChanged: (bool? newValue) { + loginBloc.add(CheckBoxEvent(newValue: newValue)); + }, + ), + ), + SizedBox( + width:size.width * 0.14, + child: RichText( + text: TextSpan( + text: 'Agree to ', + style: const TextStyle(color: Colors.white), + children: [ + TextSpan( + text: '(Terms of Service)', + style: const TextStyle( + color: Colors.black,), + recognizer: TapGestureRecognizer() + ..onTap = () { + loginBloc.launchURL( + 'https://example.com/terms'); + }, + ), + TextSpan( + text: ' (Legal Statement)', + style: const TextStyle(color: Colors.black), + recognizer: TapGestureRecognizer() + ..onTap = () { + loginBloc.launchURL( + 'https://example.com/legal'); + }, + ), + TextSpan( + text: ' (Privacy Statement)', + style: const TextStyle( + color: Colors.black), + recognizer: TapGestureRecognizer() + ..onTap = () { + loginBloc.launchURL( + 'https://example.com/privacy'); + }, + ), + ], ), ), ), - isDense: true, - style: const TextStyle(color: Colors.black), - items:loginBloc.regionList!.map((RegionModel region) { - return DropdownMenuItem( - value: region.id, - child: Text(region.name), - ); - }).toList(), - onChanged: (String? value) { + ], + ), + const SizedBox(height: 20.0), + Row( + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + SizedBox( + width:size.width * 0.2, + child: DefaultButton( + enabled: loginBloc.checkValidate, + child:Text('Sign in', + style: Theme.of(context).textTheme.labelLarge !.copyWith( + fontSize: 14, + color: + loginBloc.checkValidate ? + ColorsManager.whiteColors:ColorsManager.whiteColors.withOpacity(0.2), + ) + ), + onPressed: () { - loginBloc.add(SelectRegionEvent(val: value!,)); - }, - ), - ) + if(loginBloc.loginFormKey.currentState!.validate() ){ + loginBloc.add(LoginButtonPressed( + regionUuid:loginBloc.regionUuid, + username: loginBloc.loginEmailController.text, + password: loginBloc.loginPasswordController.text, + )); + }else{ + loginBloc.add(ChangeValidateEvent()); + } + + }, + ), + ), + ], + ), + const SizedBox(height: 15.0), + Row( + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisAlignment: MainAxisAlignment.center, + children: [ SizedBox(child: Text(loginBloc.validate, + style: const TextStyle(fontWeight: FontWeight.w700,color: ColorsManager.red ),),)],) ], ), - const SizedBox(height: 20.0), - Column( - crossAxisAlignment: CrossAxisAlignment.start, - mainAxisAlignment: MainAxisAlignment.start, - children: [ - Text("Email", - style: Theme.of(context).textTheme.bodySmall, - ), - const SizedBox( - height: 10, - ), - SizedBox( - child: TextFormField( - validator:loginBloc.validateEmail , - controller:loginBloc.loginEmailController, - decoration: textBoxDecoration()!.copyWith(hintText: 'Enter your email'), - style: const TextStyle(color: Colors.black), - ), - ), - ], - ), - const SizedBox(height: 20.0), - Column( - crossAxisAlignment: CrossAxisAlignment.start, - mainAxisAlignment: MainAxisAlignment.start, - children: [ - Text("Password", style: Theme.of(context).textTheme.bodySmall,), - const SizedBox( - height: 10, - ), - SizedBox( - child: TextFormField( - validator:loginBloc.validatePassword, - obscureText:loginBloc.obscureText, - keyboardType: TextInputType.visiblePassword, - controller:loginBloc.loginPasswordController, - decoration: textBoxDecoration()!.copyWith( - hintText: 'At least 8 characters', - suffixIcon: IconButton(onPressed: () { - loginBloc.add(PasswordVisibleEvent(newValue: loginBloc.obscureText)); - }, - icon: SizedBox( - child: SvgPicture.asset( - loginBloc.obscureText? - Assets.visiblePassword : - Assets.invisiblePassword, - height: 15, - width: 15, - ), - ), - ) - ), - style: const TextStyle(color: Colors.black), - ), - ), - ], - ), - const SizedBox( - height: 20, - ), - SizedBox( - child: Row( - mainAxisAlignment: MainAxisAlignment.end, - children: [ - InkWell( - onTap: () { - Navigator.of(context).push(MaterialPageRoute(builder: (context) => const ForgetPasswordPage(),)); - }, - child: Text( - "Forgot Password?", - style: Theme.of(context).textTheme.bodySmall, - ), - ), - ], - ), - ), - const SizedBox( - height: 20, - ), - Row( - children: [ - Transform.scale( - scale: 1.2, // Adjust the scale as needed - child: Checkbox( - fillColor: MaterialStateProperty.all(Colors.white), - activeColor: Colors.white, - value:loginBloc.isChecked, - checkColor: Colors.black, - shape: const CircleBorder(), - onChanged: (bool? newValue) { - loginBloc.add(CheckBoxEvent(newValue: newValue)); - }, - ), - ), - SizedBox( - width:size.width * 0.14, - child: RichText( - text: TextSpan( - text: 'Agree to ', - style: const TextStyle(color: Colors.white), - children: [ - TextSpan( - text: '(Terms of Service)', - style: const TextStyle( - color: Colors.black,), - recognizer: TapGestureRecognizer() - ..onTap = () { - loginBloc.launchURL( - 'https://example.com/terms'); - }, - ), - TextSpan( - text: ' (Legal Statement)', - style: const TextStyle(color: Colors.black), - recognizer: TapGestureRecognizer() - ..onTap = () { - loginBloc.launchURL( - 'https://example.com/legal'); - }, - ), - TextSpan( - text: ' (Privacy Statement)', - style: const TextStyle( - color: Colors.black), - recognizer: TapGestureRecognizer() - ..onTap = () { - loginBloc.launchURL( - 'https://example.com/privacy'); - }, - ), - ], - ), - ), - ), - ], - ), - const SizedBox(height: 20.0), - SizedBox( - width:size.width * 0.2, - child: DefaultButton( - backgroundColor: loginBloc.isChecked? - ColorsManager.btnColor:ColorsManager.grayColor, - child: const Text('Sign in'), - onPressed: () { - if (loginBloc.loginFormKey.currentState!.validate()) { - loginBloc.add(LoginButtonPressed( - regionUuid:loginBloc.regionUuid , - username: loginBloc.loginEmailController.text, - password: loginBloc.loginPasswordController.text, - ), - ); - } - }, - ), - ), - const SizedBox(height: 15.0), - Row( - crossAxisAlignment: CrossAxisAlignment.center, - mainAxisAlignment: MainAxisAlignment.center, - children: [ SizedBox(child: Text(loginBloc.validate, - style: const TextStyle(fontWeight: FontWeight.w700,color: ColorsManager.red ),),)],) - ], - ), - ), - ) - )), - const Spacer(), - ], - ),), + ), + ) + )), + const Spacer(), + ], + ),), + ), + ], ), - ], + ), ), - ), + if (state is AuthLoading) + const Center(child: CircularProgressIndicator()) + ], ); } } diff --git a/lib/pages/common/default_button.dart b/lib/pages/common/default_button.dart index 02254828..181e093a 100644 --- a/lib/pages/common/default_button.dart +++ b/lib/pages/common/default_button.dart @@ -59,7 +59,7 @@ class DefaultButton extends StatelessWidget { (Set states) { return enabled ? backgroundColor ?? ColorsManager.primaryColor - : Colors.grey; + : Colors.black.withOpacity(0.2); }), shape: MaterialStateProperty.all( RoundedRectangleBorder( diff --git a/lib/utils/style.dart b/lib/utils/style.dart index 41599fad..6a31ca8c 100644 --- a/lib/utils/style.dart +++ b/lib/utils/style.dart @@ -8,17 +8,25 @@ InputDecoration? textBoxDecoration({bool suffixIcon = false}) => InputDecoration filled: true, // Enable background filling fillColor: Colors.grey.shade200, // Set the background color border: OutlineInputBorder( - borderRadius: BorderRadius.circular(15), // Add border radius + borderRadius: BorderRadius.circular(20), // Add border radius borderSide: BorderSide.none, // Remove the underline ), enabledBorder: OutlineInputBorder( - borderRadius: BorderRadius.circular(15), // Add border radius + borderRadius: BorderRadius.circular(20), // Add border radius borderSide: BorderSide.none, // Remove the underline ), focusedBorder: OutlineInputBorder( - borderRadius: BorderRadius.circular(15), // Add border radius + borderRadius: BorderRadius.circular(20), // Add border radius borderSide: BorderSide.none, // Remove the underline ), + errorBorder: OutlineInputBorder( + borderSide: BorderSide(color: Colors.red, width: 2), + borderRadius: BorderRadius.circular(20), + ), + focusedErrorBorder: OutlineInputBorder( + borderSide: BorderSide(color: Colors.red, width: 2), + borderRadius: BorderRadius.circular(20), + ), ); From f4cb117464e86e5a98aca9ae439a8d97cde8c884 Mon Sep 17 00:00:00 2001 From: mohammad Date: Thu, 15 Aug 2024 10:04:11 +0300 Subject: [PATCH 08/32] login changes --- .../auth/view/forget_password_web_page.dart | 29 +++++++++++-------- lib/pages/auth/view/login_web_page.dart | 2 -- 2 files changed, 17 insertions(+), 14 deletions(-) diff --git a/lib/pages/auth/view/forget_password_web_page.dart b/lib/pages/auth/view/forget_password_web_page.dart index b0e17374..cb137c6b 100644 --- a/lib/pages/auth/view/forget_password_web_page.dart +++ b/lib/pages/auth/view/forget_password_web_page.dart @@ -117,7 +117,7 @@ class ForgetPasswordWebPage extends StatelessWidget { const SizedBox(height: 10), Text( 'Please fill in your account information to\nretrieve your password', - style: Theme.of(context).textTheme.bodySmall, + style: Theme.of(context).textTheme.bodySmall!.copyWith(fontSize: 14,fontWeight: FontWeight.w400), ), const SizedBox(height: 10), Column( @@ -126,8 +126,8 @@ class ForgetPasswordWebPage extends StatelessWidget { children: [ Text( "Country/Region", - style: - Theme.of(context).textTheme.bodySmall, + style: Theme.of(context).textTheme.bodySmall!.copyWith(fontSize: 14,fontWeight: FontWeight.w400), + ), const SizedBox(height: 10), SizedBox( @@ -140,7 +140,7 @@ class ForgetPasswordWebPage extends StatelessWidget { hintText: null, ), hint: SizedBox( - width: size.width * 0.11, + width: size.width * 0.12, child: const Align( alignment: Alignment.centerLeft, child: Text( @@ -157,7 +157,10 @@ class ForgetPasswordWebPage extends StatelessWidget { .map((RegionModel region) { return DropdownMenuItem( value: region.id, - child: Text(region.name), + child: SizedBox( + width: size.width*0.06, + + child: Text(region.name)), ); }).toList(), onChanged: (String? value) { @@ -176,8 +179,8 @@ class ForgetPasswordWebPage extends StatelessWidget { children: [ Text( "Account", - style: - Theme.of(context).textTheme.bodySmall, + style: Theme.of(context).textTheme.bodySmall!.copyWith(fontSize: 14,fontWeight: FontWeight.w400), + ), const SizedBox(height: 10), SizedBox( @@ -200,8 +203,8 @@ class ForgetPasswordWebPage extends StatelessWidget { children: [ Text( "One Time Password", - style: - Theme.of(context).textTheme.bodySmall, + style: Theme.of(context).textTheme.bodySmall!.copyWith(fontSize: 14,fontWeight: FontWeight.w400), + ), const SizedBox(height: 10), SizedBox( @@ -247,8 +250,8 @@ class ForgetPasswordWebPage extends StatelessWidget { children: [ Text( "Password", - style: - Theme.of(context).textTheme.bodySmall, + style: Theme.of(context).textTheme.bodySmall!.copyWith(fontSize: 14,fontWeight: FontWeight.w400), + ), const SizedBox(height: 10), SizedBox( @@ -324,7 +327,9 @@ class ForgetPasswordWebPage extends StatelessWidget { ), ], ), - ) + ), + const SizedBox(height: 15.0), + ], ), ), diff --git a/lib/pages/auth/view/login_web_page.dart b/lib/pages/auth/view/login_web_page.dart index 10ed36a6..b819669e 100644 --- a/lib/pages/auth/view/login_web_page.dart +++ b/lib/pages/auth/view/login_web_page.dart @@ -354,7 +354,6 @@ class _LoginWebPageState extends State { ) ), onPressed: () { - if(loginBloc.loginFormKey.currentState!.validate() ){ loginBloc.add(LoginButtonPressed( regionUuid:loginBloc.regionUuid, @@ -364,7 +363,6 @@ class _LoginWebPageState extends State { }else{ loginBloc.add(ChangeValidateEvent()); } - }, ), ), From 562bce8c4fea4e8c553ff441f0bcaa305738f8c9 Mon Sep 17 00:00:00 2001 From: mohammad Date: Thu, 15 Aug 2024 12:34:17 +0300 Subject: [PATCH 09/32] forget password changes --- lib/pages/auth/bloc/auth_bloc.dart | 13 ++++++++++- lib/pages/auth/view/forget_password_page.dart | 2 +- .../auth/view/forget_password_web_page.dart | 22 ++++++++----------- 3 files changed, 22 insertions(+), 15 deletions(-) diff --git a/lib/pages/auth/bloc/auth_bloc.dart b/lib/pages/auth/bloc/auth_bloc.dart index 5898f2dd..e09feea6 100644 --- a/lib/pages/auth/bloc/auth_bloc.dart +++ b/lib/pages/auth/bloc/auth_bloc.dart @@ -83,7 +83,10 @@ class AuthBloc extends Bloc { } emit(SuccessForgetState()); } catch (failure) { - emit(FailureForgetState(error: failure.toString())); + forgetValidate='Invalid Credentials!'; + emit(AuthInitialState()); + + // emit(FailureForgetState(error: failure.toString())); } } @@ -103,6 +106,7 @@ class AuthBloc extends Bloc { String maskedEmail = ''; String otpCode = ''; String validate = ''; + String forgetValidate = ''; String regionUuid = ''; static Token token = Token.emptyConstructor(); static UserModel? user; @@ -365,10 +369,17 @@ class AuthBloc extends Bloc { emit(LoginInitial()); return checkValidate; } + changeValidate(ChangeValidateEvent event, Emitter emit,){ emit(AuthLoading()); validate=''; print('validate'); emit(LoginInitial()); } + changeForgetValidate(ChangeValidateEvent event, Emitter emit,){ + emit(AuthLoading()); + forgetValidate=''; + print('validate'); + emit(LoginInitial()); + } } diff --git a/lib/pages/auth/view/forget_password_page.dart b/lib/pages/auth/view/forget_password_page.dart index da09a888..f90b2ef4 100644 --- a/lib/pages/auth/view/forget_password_page.dart +++ b/lib/pages/auth/view/forget_password_page.dart @@ -12,7 +12,7 @@ class ForgetPasswordPage extends StatelessWidget { Widget build(BuildContext context) { return const ResponsiveLayout( desktopBody: ForgetPasswordWebPage(), - mobileBody:ForgetPasswordMobilePage() + mobileBody:ForgetPasswordWebPage() ); } } diff --git a/lib/pages/auth/view/forget_password_web_page.dart b/lib/pages/auth/view/forget_password_web_page.dart index cb137c6b..07b59495 100644 --- a/lib/pages/auth/view/forget_password_web_page.dart +++ b/lib/pages/auth/view/forget_password_web_page.dart @@ -204,7 +204,6 @@ class ForgetPasswordWebPage extends StatelessWidget { Text( "One Time Password", style: Theme.of(context).textTheme.bodySmall!.copyWith(fontSize: 14,fontWeight: FontWeight.w400), - ), const SizedBox(height: 10), SizedBox( @@ -220,9 +219,7 @@ class ForgetPasswordWebPage extends StatelessWidget { child: Center( child: InkWell( onTap: () { - BlocProvider.of( - context) - .add(StartTimerEvent()); + BlocProvider.of(context).add(StartTimerEvent()); }, child: Text( 'Get Code ${state is TimerState && !state.isButtonEnabled ? "(${BlocProvider.of(context).formattedTime(state.remainingTime)}) " : ""}', @@ -251,20 +248,17 @@ class ForgetPasswordWebPage extends StatelessWidget { Text( "Password", style: Theme.of(context).textTheme.bodySmall!.copyWith(fontSize: 14,fontWeight: FontWeight.w400), - ), const SizedBox(height: 10), SizedBox( child: TextFormField( validator: forgetBloc.passwordValidator, keyboardType: TextInputType.visiblePassword, - controller: - forgetBloc.forgetPasswordController, + controller: forgetBloc.forgetPasswordController, decoration: textBoxDecoration()!.copyWith( hintText: 'At least 8 characters', ), - style: - const TextStyle(color: Colors.black), + style: const TextStyle(color: Colors.black), ), ), ], @@ -283,9 +277,7 @@ class ForgetPasswordWebPage extends StatelessWidget { backgroundColor: ColorsManager.btnColor, child: const Text('Submit'), onPressed: () { - if (forgetBloc - .forgetFormKey.currentState! - .validate()) { + if (forgetBloc.forgetFormKey.currentState!.validate()) { forgetBloc.add(ChangePasswordEvent()); } }, @@ -329,7 +321,11 @@ class ForgetPasswordWebPage extends StatelessWidget { ), ), const SizedBox(height: 15.0), - + Row( + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisAlignment: MainAxisAlignment.center, + children: [ SizedBox(child: Text(forgetBloc.forgetValidate, + style: const TextStyle(fontWeight: FontWeight.w700,color: ColorsManager.red ),),)],) ], ), ), From cd372402f7d7cee0c3da532c606e8196eb9546de Mon Sep 17 00:00:00 2001 From: mohammad Date: Thu, 15 Aug 2024 12:50:05 +0300 Subject: [PATCH 10/32] forget password changes --- lib/pages/auth/bloc/auth_bloc.dart | 6 ++---- lib/pages/auth/view/forget_password_web_page.dart | 8 +++++--- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/lib/pages/auth/bloc/auth_bloc.dart b/lib/pages/auth/bloc/auth_bloc.dart index e09feea6..0761ae19 100644 --- a/lib/pages/auth/bloc/auth_bloc.dart +++ b/lib/pages/auth/bloc/auth_bloc.dart @@ -58,7 +58,8 @@ class AuthBloc extends Bloc { add(const UpdateTimerEvent(remainingTime: 0, isButtonEnabled: true)); } else { add(UpdateTimerEvent( - remainingTime: _remainingTime, isButtonEnabled: false)); + remainingTime: _remainingTime, + isButtonEnabled: false)); } }); } @@ -185,7 +186,6 @@ class AuthBloc extends Bloc { } String? validateEmail(String? value) { - if (value == null || value.isEmpty) { return 'Email is required'; } else if (!RegExp(r'^[^@]+@[^@]+\.[^@]+').hasMatch(value)) { @@ -373,13 +373,11 @@ class AuthBloc extends Bloc { changeValidate(ChangeValidateEvent event, Emitter emit,){ emit(AuthLoading()); validate=''; - print('validate'); emit(LoginInitial()); } changeForgetValidate(ChangeValidateEvent event, Emitter emit,){ emit(AuthLoading()); forgetValidate=''; - print('validate'); emit(LoginInitial()); } } diff --git a/lib/pages/auth/view/forget_password_web_page.dart b/lib/pages/auth/view/forget_password_web_page.dart index 07b59495..0cb6b0e3 100644 --- a/lib/pages/auth/view/forget_password_web_page.dart +++ b/lib/pages/auth/view/forget_password_web_page.dart @@ -219,10 +219,10 @@ class ForgetPasswordWebPage extends StatelessWidget { child: Center( child: InkWell( onTap: () { - BlocProvider.of(context).add(StartTimerEvent()); + forgetBloc.add(StartTimerEvent()); }, child: Text( - 'Get Code ${state is TimerState && !state.isButtonEnabled ? "(${BlocProvider.of(context).formattedTime(state.remainingTime)}) " : ""}', + 'Get Code ${state is TimerState && !state.isButtonEnabled && state.remainingTime!=1? "(${forgetBloc.formattedTime(state.remainingTime)}) " : ""}', style: TextStyle( color: state is TimerState && !state.isButtonEnabled @@ -325,7 +325,9 @@ class ForgetPasswordWebPage extends StatelessWidget { crossAxisAlignment: CrossAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center, children: [ SizedBox(child: Text(forgetBloc.forgetValidate, - style: const TextStyle(fontWeight: FontWeight.w700,color: ColorsManager.red ),),)],) + style: const TextStyle(fontWeight: FontWeight.w700,color: ColorsManager.red ),),) + ], + ) ], ), ), From 75d5c7a4ac1b28003511f8b5843e217d3439859a Mon Sep 17 00:00:00 2001 From: mohammad Date: Thu, 15 Aug 2024 13:04:18 +0300 Subject: [PATCH 11/32] forget password changes --- lib/pages/auth/view/forget_password_web_page.dart | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/lib/pages/auth/view/forget_password_web_page.dart b/lib/pages/auth/view/forget_password_web_page.dart index 0cb6b0e3..f86cdf60 100644 --- a/lib/pages/auth/view/forget_password_web_page.dart +++ b/lib/pages/auth/view/forget_password_web_page.dart @@ -117,7 +117,9 @@ class ForgetPasswordWebPage extends StatelessWidget { const SizedBox(height: 10), Text( 'Please fill in your account information to\nretrieve your password', - style: Theme.of(context).textTheme.bodySmall!.copyWith(fontSize: 14,fontWeight: FontWeight.w400), + style: Theme.of(context).textTheme.bodySmall!.copyWith( + fontSize: 14, + fontWeight: FontWeight.w400), ), const SizedBox(height: 10), Column( @@ -126,7 +128,9 @@ class ForgetPasswordWebPage extends StatelessWidget { children: [ Text( "Country/Region", - style: Theme.of(context).textTheme.bodySmall!.copyWith(fontSize: 14,fontWeight: FontWeight.w400), + style: Theme.of(context).textTheme.bodySmall!.copyWith( + fontSize: 14, + fontWeight: FontWeight.w400), ), const SizedBox(height: 10), @@ -310,6 +314,7 @@ class ForgetPasswordWebPage extends StatelessWidget { )), InkWell( onTap: () { + forgetBloc.add(StopTimerEvent()); Navigator.pop(context); }, child: const Flexible( From 5cce050013924b2c862f6c74963708d8fd12646b Mon Sep 17 00:00:00 2001 From: mohammad Date: Thu, 15 Aug 2024 13:17:35 +0300 Subject: [PATCH 12/32] forget password changes --- .../view/access_management.dart | 2 +- .../auth/view/forget_password_web_page.dart | 502 +++++++++--------- .../bloc/visitor_password_bloc.dart | 1 - .../view/visitor_password_dialog.dart | 2 +- lib/utils/style.dart | 10 +- 5 files changed, 259 insertions(+), 258 deletions(-) diff --git a/lib/pages/access_management/view/access_management.dart b/lib/pages/access_management/view/access_management.dart index a0d89948..d2dd72b9 100644 --- a/lib/pages/access_management/view/access_management.dart +++ b/lib/pages/access_management/view/access_management.dart @@ -100,7 +100,7 @@ class AccessManagementPage extends StatelessWidget { const SizedBox( height: 20, ), - Wrap( + Row( children: [ Column( crossAxisAlignment: CrossAxisAlignment.start, diff --git a/lib/pages/auth/view/forget_password_web_page.dart b/lib/pages/auth/view/forget_password_web_page.dart index f86cdf60..8485f976 100644 --- a/lib/pages/auth/view/forget_password_web_page.dart +++ b/lib/pages/auth/view/forget_password_web_page.dart @@ -31,11 +31,7 @@ class ForgetPasswordWebPage extends StatelessWidget { } }, builder: (context, state) { - if (state is AuthLoading) { - return const Center(child: CircularProgressIndicator()); - } else { return _buildForm(context, state); - } }, ), ), @@ -62,287 +58,293 @@ class ForgetPasswordWebPage extends StatelessWidget { Size size = MediaQuery.of(context).size; return FirstLayer( second: Center( - child: ListView( - shrinkWrap: true, - controller: _scrollController, + child: Stack( children: [ - Container( - padding: EdgeInsets.all(size.width * 0.02), - margin: EdgeInsets.all(size.width * 0.09), - decoration: BoxDecoration( - color: Colors.black.withOpacity(0.3), - borderRadius: const BorderRadius.all(Radius.circular(20)), - ), - child: Center( - child: Row( - crossAxisAlignment: CrossAxisAlignment.center, - mainAxisAlignment: MainAxisAlignment.center, - children: [ - const Spacer(), - Expanded( - flex: 3, - child: SvgPicture.asset( - Assets.loginLogo, - ), - ), - const Spacer(), - Expanded( - flex: 3, - child: Container( - decoration: BoxDecoration( - color: Colors.white.withOpacity(0.1), - borderRadius: - const BorderRadius.all(Radius.circular(30)), - border: Border.all( - color: ColorsManager.graysColor.withOpacity(0.2)), + if (state is AuthLoading) + const Center(child: CircularProgressIndicator()), + ListView( + shrinkWrap: true, + controller: _scrollController, + children: [ + Container( + padding: EdgeInsets.all(size.width * 0.02), + margin: EdgeInsets.all(size.width * 0.09), + decoration: BoxDecoration( + color: Colors.black.withOpacity(0.3), + borderRadius: const BorderRadius.all(Radius.circular(20)), + ), + child: Center( + child: Row( + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + const Spacer(), + Expanded( + flex: 3, + child: SvgPicture.asset( + Assets.loginLogo, + ), ), - child: Form( - key: forgetBloc.forgetFormKey, - child: Padding( - padding: EdgeInsets.symmetric( - horizontal: size.width * 0.02, - vertical: size.width * 0.003), - child: Column( - mainAxisAlignment: MainAxisAlignment.spaceEvenly, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - const SizedBox(height: 10), - const Text( - 'Forget Password', - style: TextStyle( - color: Colors.white, - fontSize: 24, - fontWeight: FontWeight.bold), - ), - const SizedBox(height: 10), - Text( - 'Please fill in your account information to\nretrieve your password', - style: Theme.of(context).textTheme.bodySmall!.copyWith( - fontSize: 14, - fontWeight: FontWeight.w400), - ), - const SizedBox(height: 10), - Column( + const Spacer(), + Expanded( + flex: 3, + child: Container( + decoration: BoxDecoration( + color: Colors.white.withOpacity(0.1), + borderRadius: + const BorderRadius.all(Radius.circular(30)), + border: Border.all( + color: ColorsManager.graysColor.withOpacity(0.2)), + ), + child: Form( + key: forgetBloc.forgetFormKey, + child: Padding( + padding: EdgeInsets.symmetric( + horizontal: size.width * 0.02, + vertical: size.width * 0.003), + child: Column( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, crossAxisAlignment: CrossAxisAlignment.start, - mainAxisAlignment: MainAxisAlignment.start, - children: [ + children: [ + const SizedBox(height: 10), + const Text( + 'Forget Password', + style: TextStyle( + color: Colors.white, + fontSize: 24, + fontWeight: FontWeight.bold), + ), + const SizedBox(height: 10), Text( - "Country/Region", + 'Please fill in your account information to\nretrieve your password', style: Theme.of(context).textTheme.bodySmall!.copyWith( fontSize: 14, fontWeight: FontWeight.w400), - ), const SizedBox(height: 10), - SizedBox( - child: DropdownButtonFormField( - validator: forgetBloc.validateRegion, - icon: const Icon( - Icons.keyboard_arrow_down_outlined, + Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.start, + children: [ + Text( + "Country/Region", + style: Theme.of(context).textTheme.bodySmall!.copyWith( + fontSize: 14, + fontWeight: FontWeight.w400), + ), - decoration: textBoxDecoration()!.copyWith( - hintText: null, - ), - hint: SizedBox( - width: size.width * 0.12, - child: const Align( - alignment: Alignment.centerLeft, - child: Text( - 'Select your region/country', - textAlign: TextAlign.center, - overflow: TextOverflow.ellipsis, + const SizedBox(height: 10), + SizedBox( + child: DropdownButtonFormField( + validator: forgetBloc.validateRegion, + icon: const Icon( + Icons.keyboard_arrow_down_outlined, ), + decoration: textBoxDecoration()!.copyWith( + hintText: null, + ), + hint: SizedBox( + width: size.width * 0.12, + child: const Align( + alignment: Alignment.centerLeft, + child: Text( + 'Select your region/country', + textAlign: TextAlign.center, + overflow: TextOverflow.ellipsis, + ), + ), + ), + isDense: true, + style: + const TextStyle(color: Colors.black), + items: forgetBloc.regionList! + .map((RegionModel region) { + return DropdownMenuItem( + value: region.id, + child: SizedBox( + width: size.width*0.06, + + child: Text(region.name)), + ); + }).toList(), + onChanged: (String? value) { + forgetBloc.add(SelectRegionEvent( + val: value!, + )); + }, + ), + ) + ], + ), + const SizedBox(height: 20), + Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.start, + children: [ + Text( + "Account", + style: Theme.of(context).textTheme.bodySmall!.copyWith(fontSize: 14,fontWeight: FontWeight.w400), + + ), + const SizedBox(height: 10), + SizedBox( + child: TextFormField( + validator: forgetBloc.validateEmail, + controller: + forgetBloc.forgetEmailController, + decoration: textBoxDecoration()!.copyWith( + hintText: 'Enter your email'), + style: + const TextStyle(color: Colors.black), ), ), - isDense: true, - style: - const TextStyle(color: Colors.black), - items: forgetBloc.regionList! - .map((RegionModel region) { - return DropdownMenuItem( - value: region.id, - child: SizedBox( - width: size.width*0.06, - - child: Text(region.name)), - ); - }).toList(), - onChanged: (String? value) { - forgetBloc.add(SelectRegionEvent( - val: value!, - )); - }, - ), - ) - ], - ), - const SizedBox(height: 20), - Column( - crossAxisAlignment: CrossAxisAlignment.start, - mainAxisAlignment: MainAxisAlignment.start, - children: [ - Text( - "Account", - style: Theme.of(context).textTheme.bodySmall!.copyWith(fontSize: 14,fontWeight: FontWeight.w400), - + ], ), - const SizedBox(height: 10), - SizedBox( - child: TextFormField( - validator: forgetBloc.validateEmail, - controller: - forgetBloc.forgetEmailController, - decoration: textBoxDecoration()!.copyWith( - hintText: 'Enter your email'), - style: - const TextStyle(color: Colors.black), - ), - ), - ], - ), - const SizedBox(height: 20.0), - Column( - crossAxisAlignment: CrossAxisAlignment.start, - mainAxisAlignment: MainAxisAlignment.start, - children: [ - Text( - "One Time Password", - style: Theme.of(context).textTheme.bodySmall!.copyWith(fontSize: 14,fontWeight: FontWeight.w400), - ), - const SizedBox(height: 10), - SizedBox( - child: TextFormField( - validator: forgetBloc.validateCode, - keyboardType: - TextInputType.visiblePassword, - controller: forgetBloc.forgetOtp, - decoration: textBoxDecoration()!.copyWith( - hintText: 'Enter Code', - suffixIcon: SizedBox( - width: 100, - child: Center( - child: InkWell( - onTap: () { - forgetBloc.add(StartTimerEvent()); - }, - child: Text( - 'Get Code ${state is TimerState && !state.isButtonEnabled && state.remainingTime!=1? "(${forgetBloc.formattedTime(state.remainingTime)}) " : ""}', - style: TextStyle( - color: state is TimerState && - !state.isButtonEnabled - ? Colors.grey - : ColorsManager.btnColor, + const SizedBox(height: 20.0), + Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.start, + children: [ + Text( + "One Time Password", + style: Theme.of(context).textTheme.bodySmall!.copyWith(fontSize: 14,fontWeight: FontWeight.w400), + ), + const SizedBox(height: 10), + SizedBox( + child: TextFormField( + validator: forgetBloc.validateCode, + keyboardType: + TextInputType.visiblePassword, + controller: forgetBloc.forgetOtp, + decoration: textBoxDecoration()!.copyWith( + hintText: 'Enter Code', + suffixIcon: SizedBox( + width: 100, + child: Center( + child: InkWell( + onTap: () { + forgetBloc.add(StartTimerEvent()); + }, + child: Text( + 'Get Code ${state is TimerState && !state.isButtonEnabled && state.remainingTime!=1? "(${forgetBloc.formattedTime(state.remainingTime)}) " : ""}', + style: TextStyle( + color: state is TimerState && + !state.isButtonEnabled + ? Colors.grey + : ColorsManager.btnColor, + ), + ), ), ), ), ), + style: + const TextStyle(color: Colors.black), ), ), - style: - const TextStyle(color: Colors.black), - ), + ], ), - ], - ), - const SizedBox(height: 20.0), - Column( - crossAxisAlignment: CrossAxisAlignment.start, - mainAxisAlignment: MainAxisAlignment.start, - children: [ - Text( - "Password", - style: Theme.of(context).textTheme.bodySmall!.copyWith(fontSize: 14,fontWeight: FontWeight.w400), - ), - const SizedBox(height: 10), - SizedBox( - child: TextFormField( - validator: forgetBloc.passwordValidator, - keyboardType: TextInputType.visiblePassword, - controller: forgetBloc.forgetPasswordController, - decoration: textBoxDecoration()!.copyWith( - hintText: 'At least 8 characters', + const SizedBox(height: 20.0), + Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.start, + children: [ + Text( + "Password", + style: Theme.of(context).textTheme.bodySmall!.copyWith(fontSize: 14,fontWeight: FontWeight.w400), ), - style: const TextStyle(color: Colors.black), + const SizedBox(height: 10), + SizedBox( + child: TextFormField( + validator: forgetBloc.passwordValidator, + keyboardType: TextInputType.visiblePassword, + controller: forgetBloc.forgetPasswordController, + decoration: textBoxDecoration()!.copyWith( + hintText: 'At least 8 characters', + ), + style: const TextStyle(color: Colors.black), + ), + ), + ], + ), + const SizedBox( + height: 10, + ), + const SizedBox(height: 20.0), + Row( + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + SizedBox( + width: size.width * 0.2, + child: DefaultButton( + backgroundColor: ColorsManager.btnColor, + child: const Text('Submit'), + onPressed: () { + if (forgetBloc.forgetFormKey.currentState!.validate()) { + forgetBloc.add(ChangePasswordEvent()); + } + }, + ), + ), + ], + ), + const SizedBox(height: 10.0), + SizedBox( + child: Text( + forgetBloc.validate, + style: const TextStyle( + fontWeight: FontWeight.w700, + color: ColorsManager.red), ), ), - ], - ), - const SizedBox( - height: 10, - ), - const SizedBox(height: 20.0), - Row( - crossAxisAlignment: CrossAxisAlignment.center, - mainAxisAlignment: MainAxisAlignment.center, - children: [ + SizedBox( + height: 10, + ), SizedBox( width: size.width * 0.2, - child: DefaultButton( - backgroundColor: ColorsManager.btnColor, - child: const Text('Submit'), - onPressed: () { - if (forgetBloc.forgetFormKey.currentState!.validate()) { - forgetBloc.add(ChangePasswordEvent()); - } - }, + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + const Flexible( + child: Text( + "Do you have an account? ", + style: TextStyle(color: Colors.white), + )), + InkWell( + onTap: () { + forgetBloc.add(StopTimerEvent()); + Navigator.pop(context); + }, + child: const Flexible( + child: Text( + "Sign in", + )), + ), + ], ), ), + const SizedBox(height: 15.0), + Row( + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisAlignment: MainAxisAlignment.center, + children: [ SizedBox(child: Text(forgetBloc.forgetValidate, + style: const TextStyle(fontWeight: FontWeight.w700,color: ColorsManager.red ),),) + ], + ) ], ), - const SizedBox(height: 10.0), - SizedBox( - child: Text( - forgetBloc.validate, - style: const TextStyle( - fontWeight: FontWeight.w700, - color: ColorsManager.red), - ), - ), - SizedBox( - height: 10, - ), - SizedBox( - width: size.width * 0.2, - child: Row( - mainAxisAlignment: MainAxisAlignment.center, - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - const Flexible( - child: Text( - "Do you have an account? ", - style: TextStyle(color: Colors.white), - )), - InkWell( - onTap: () { - forgetBloc.add(StopTimerEvent()); - Navigator.pop(context); - }, - child: const Flexible( - child: Text( - "Sign in", - )), - ), - ], - ), - ), - const SizedBox(height: 15.0), - Row( - crossAxisAlignment: CrossAxisAlignment.center, - mainAxisAlignment: MainAxisAlignment.center, - children: [ SizedBox(child: Text(forgetBloc.forgetValidate, - style: const TextStyle(fontWeight: FontWeight.w700,color: ColorsManager.red ),),) - ], - ) - ], + ), ), ), ), - ), + const Spacer(), + ], ), - const Spacer(), - ], + ), ), - ), + ], ), ], ), diff --git a/lib/pages/visitor_password/bloc/visitor_password_bloc.dart b/lib/pages/visitor_password/bloc/visitor_password_bloc.dart index f12e67f1..cb4c5a01 100644 --- a/lib/pages/visitor_password/bloc/visitor_password_bloc.dart +++ b/lib/pages/visitor_password/bloc/visitor_password_bloc.dart @@ -28,7 +28,6 @@ class VisitorPasswordBloc extends Bloc emit) { accessTypeSelected=event.type; print(accessTypeSelected); - emit(PasswordTypeSelected(event.type)); } diff --git a/lib/pages/visitor_password/view/visitor_password_dialog.dart b/lib/pages/visitor_password/view/visitor_password_dialog.dart index 7fc6fbd3..160ab9b7 100644 --- a/lib/pages/visitor_password/view/visitor_password_dialog.dart +++ b/lib/pages/visitor_password/view/visitor_password_dialog.dart @@ -86,7 +86,7 @@ class VisitorPasswordDialog extends StatelessWidget { SizedBox( width: size.width*0.15, child: RadioListTile( - title: Text('Online Password'), + title: const Text('Online Password'), value: 'Online Password', groupValue: (state is PasswordTypeSelected) ? state.selectedType diff --git a/lib/utils/style.dart b/lib/utils/style.dart index 6a31ca8c..8c174468 100644 --- a/lib/utils/style.dart +++ b/lib/utils/style.dart @@ -8,24 +8,24 @@ InputDecoration? textBoxDecoration({bool suffixIcon = false}) => InputDecoration filled: true, // Enable background filling fillColor: Colors.grey.shade200, // Set the background color border: OutlineInputBorder( - borderRadius: BorderRadius.circular(20), // Add border radius + borderRadius: BorderRadius.circular(8), // Add border radius borderSide: BorderSide.none, // Remove the underline ), enabledBorder: OutlineInputBorder( - borderRadius: BorderRadius.circular(20), // Add border radius + borderRadius: BorderRadius.circular(8), // Add border radius borderSide: BorderSide.none, // Remove the underline ), focusedBorder: OutlineInputBorder( - borderRadius: BorderRadius.circular(20), // Add border radius + borderRadius: BorderRadius.circular(8), // Add border radius borderSide: BorderSide.none, // Remove the underline ), errorBorder: OutlineInputBorder( borderSide: BorderSide(color: Colors.red, width: 2), - borderRadius: BorderRadius.circular(20), + borderRadius: BorderRadius.circular(8), ), focusedErrorBorder: OutlineInputBorder( borderSide: BorderSide(color: Colors.red, width: 2), - borderRadius: BorderRadius.circular(20), + borderRadius: BorderRadius.circular(8), ), ); From 20ed718c644005e7991eaf566694dce1eea75b3a Mon Sep 17 00:00:00 2001 From: mohammad Date: Thu, 15 Aug 2024 14:23:38 +0300 Subject: [PATCH 13/32] forget password changes --- lib/main.dart | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/main.dart b/lib/main.dart index 91173eb5..92b8684c 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,7 +1,6 @@ import 'package:flutter/gestures.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; -import 'package:syncrow_web/pages/access_management/view/access_management.dart'; import 'package:syncrow_web/pages/auth/bloc/auth_bloc.dart'; import 'package:syncrow_web/pages/auth/view/login_page.dart'; import 'package:syncrow_web/pages/home/bloc/home_bloc.dart'; From 213fa487418ba7cf3bf927c9b96be6a44bb5750d Mon Sep 17 00:00:00 2001 From: mohammad Date: Thu, 15 Aug 2024 17:01:15 +0300 Subject: [PATCH 14/32] create visitor password --- lib/main.dart | 4 +- .../auth/view/forget_password_web_page.dart | 9 +- .../bloc/visitor_password_bloc.dart | 42 ++++- .../bloc/visitor_password_event.dart | 13 ++ .../bloc/visitor_password_state.dart | 14 +- .../visitor_password/view/repeat_widget.dart | 81 +++++++++ .../view/visitor_password_dialog.dart | 164 ++++++++++-------- 7 files changed, 241 insertions(+), 86 deletions(-) create mode 100644 lib/pages/visitor_password/view/repeat_widget.dart diff --git a/lib/main.dart b/lib/main.dart index 92b8684c..e5d2c22b 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -56,8 +56,8 @@ class MyApp extends StatelessWidget { colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple), // Set up color scheme useMaterial3: true, // Enable Material 3 ), - // home: VisitorPasswordDialog() - home:isLoggedIn == 'Success' ? const HomePage() : const LoginPage(), + home: VisitorPasswordDialog() + // home:isLoggedIn == 'Success' ? const HomePage() : const LoginPage(), )); } } diff --git a/lib/pages/auth/view/forget_password_web_page.dart b/lib/pages/auth/view/forget_password_web_page.dart index 8485f976..5b97684c 100644 --- a/lib/pages/auth/view/forget_password_web_page.dart +++ b/lib/pages/auth/view/forget_password_web_page.dart @@ -222,7 +222,7 @@ class ForgetPasswordWebPage extends StatelessWidget { width: 100, child: Center( child: InkWell( - onTap: () { + onTap:state is TimerState && !state.isButtonEnabled && state.remainingTime!=1?null: () { forgetBloc.add(StartTimerEvent()); }, child: Text( @@ -326,13 +326,6 @@ class ForgetPasswordWebPage extends StatelessWidget { ), ), const SizedBox(height: 15.0), - Row( - crossAxisAlignment: CrossAxisAlignment.center, - mainAxisAlignment: MainAxisAlignment.center, - children: [ SizedBox(child: Text(forgetBloc.forgetValidate, - style: const TextStyle(fontWeight: FontWeight.w700,color: ColorsManager.red ),),) - ], - ) ], ), ), diff --git a/lib/pages/visitor_password/bloc/visitor_password_bloc.dart b/lib/pages/visitor_password/bloc/visitor_password_bloc.dart index cb4c5a01..668d082e 100644 --- a/lib/pages/visitor_password/bloc/visitor_password_bloc.dart +++ b/lib/pages/visitor_password/bloc/visitor_password_bloc.dart @@ -11,13 +11,17 @@ class VisitorPasswordBloc extends Bloc(selectUsageFrequency); on(selectAccessType); on(selectTimeVisitorPassword); + on(toggleRepeat); + on(toggleDaySelection); + } final TextEditingController userNameController = TextEditingController(); final TextEditingController emailController = TextEditingController(); - String accessTypeSelected=''; - String usageFrequencySelected=''; + String accessTypeSelected='Offline Password'; + String usageFrequencySelected='One-Time'; + bool repeat = false; int? effectiveTimeTimeStamp; int? expirationTimeTimeStamp; @@ -27,13 +31,11 @@ class VisitorPasswordBloc extends Bloc emit) { accessTypeSelected=event.type; - print(accessTypeSelected); emit(PasswordTypeSelected(event.type)); } selectUsageFrequency(SelectUsageFrequency event, Emitter emit) { usageFrequencySelected=event.usageType; - print(usageFrequencySelected); emit(UsageFrequencySelected(event.usageType)); } @@ -104,4 +106,36 @@ class VisitorPasswordBloc extends Bloc emit) { + emit(LoadingInitialState()); + repeat = !repeat; + emit(IsRepeatState(repeat: repeat)); + return repeat; + } + + + + List> days = [ + {"day": "Sun", "key": "Sun"}, + {"day": "Mon", "key": "Mon"}, + {"day": "Tue", "key": "Tue"}, + {"day": "Wed", "key": "Wed"}, + {"day": "Thu", "key": "Thu"}, + {"day": "Fri", "key": "Fri"}, + {"day": "Sat", "key": "Sat"}, + ]; + + List selectedDays = []; + + Future toggleDaySelection(ToggleDaySelectionEvent event, Emitter emit,)async { + emit(LoadingInitialState()); + if (selectedDays.contains(event.key)) { + selectedDays.remove(event.key); + } else { + selectedDays.add(event.key); + } + emit(ChangeTimeState()); + } + + } diff --git a/lib/pages/visitor_password/bloc/visitor_password_event.dart b/lib/pages/visitor_password/bloc/visitor_password_event.dart index a01a702c..b0a51ca0 100644 --- a/lib/pages/visitor_password/bloc/visitor_password_event.dart +++ b/lib/pages/visitor_password/bloc/visitor_password_event.dart @@ -34,3 +34,16 @@ class SelectTimeVisitorPassword extends VisitorPasswordEvent { @override List get props => [context,isStart]; } + + + +class ToggleDaySelectionEvent extends VisitorPasswordEvent { + final String key; + + const ToggleDaySelectionEvent({required this.key}); + @override + List get props => [key]; +} + + +class ToggleRepeatEvent extends VisitorPasswordEvent {} diff --git a/lib/pages/visitor_password/bloc/visitor_password_state.dart b/lib/pages/visitor_password/bloc/visitor_password_state.dart index b12232f4..8a5525d3 100644 --- a/lib/pages/visitor_password/bloc/visitor_password_state.dart +++ b/lib/pages/visitor_password/bloc/visitor_password_state.dart @@ -29,4 +29,16 @@ class UsageFrequencySelected extends VisitorPasswordState { @override List get props => [selectedFrequency]; -} \ No newline at end of file +} + +class IsRepeatState extends VisitorPasswordState { + final bool repeat; + const IsRepeatState({required this.repeat}); + + @override + List get props => [repeat]; + +} + +class LoadingInitialState extends VisitorPasswordState {} +class ChangeTimeState extends VisitorPasswordState {} diff --git a/lib/pages/visitor_password/view/repeat_widget.dart b/lib/pages/visitor_password/view/repeat_widget.dart new file mode 100644 index 00000000..24199b65 --- /dev/null +++ b/lib/pages/visitor_password/view/repeat_widget.dart @@ -0,0 +1,81 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:syncrow_web/pages/common/date_time_widget.dart'; +import 'package:syncrow_web/pages/visitor_password/bloc/visitor_password_bloc.dart'; +import 'package:syncrow_web/pages/visitor_password/bloc/visitor_password_event.dart'; +import 'package:syncrow_web/pages/visitor_password/bloc/visitor_password_state.dart'; +import 'package:syncrow_web/utils/color_manager.dart'; + +class RepeatWidget extends StatelessWidget { + const RepeatWidget({ + super.key, + }); + + @override + Widget build(BuildContext context) { + Size size = MediaQuery.of(context).size; + + return BlocBuilder( + builder: (context, state) { + final smartDoorBloc = BlocProvider.of(context); + return Column( + children: [ + Padding( + padding: const EdgeInsets.all(8.0), + child: DateTimeWebWidget( + isRequired: true, + title: 'Access Period', + size: size, + endTime: () { + smartDoorBloc.add(SelectTimeVisitorPassword(context: context, isStart: false)); + }, + startTime: () { + smartDoorBloc.add(SelectTimeVisitorPassword(context: context, isStart: true)); + }, + firstString: smartDoorBloc.startTime, + secondString: smartDoorBloc.endTime, + ), + ), + const Divider( + color: ColorsManager.graysColor, + ), + const Divider( + color: ColorsManager.graysColor, + ), + const SizedBox(height: 20), + Container( + width: size.width * 0.8, + height: size.height * 0.06, // Adjust height as needed + child: ListView( + scrollDirection: Axis.horizontal, + children: smartDoorBloc.days.map((day) { + return Container( + width: size.width* 0.09, + child: CheckboxListTile( + title: Text( + day['day']!, + style: TextStyle( + fontSize: 18, + color: smartDoorBloc.selectedDays.contains(day['key']) + ? Colors.black + : ColorsManager.grayColor, + ), + ), + value: smartDoorBloc.selectedDays.contains(day['key']), + onChanged: (bool? value) { + if (value != null) { + smartDoorBloc.add(ToggleDaySelectionEvent(key: day['key']!)); + } + }, + ), + ); + }).toList(), + ), + ), + const SizedBox(height: 20), + + ], + ); + }); + } +} diff --git a/lib/pages/visitor_password/view/visitor_password_dialog.dart b/lib/pages/visitor_password/view/visitor_password_dialog.dart index 160ab9b7..140ded0b 100644 --- a/lib/pages/visitor_password/view/visitor_password_dialog.dart +++ b/lib/pages/visitor_password/view/visitor_password_dialog.dart @@ -1,3 +1,4 @@ +import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:syncrow_web/pages/common/custom_web_textfield.dart'; @@ -6,9 +7,10 @@ import 'package:syncrow_web/pages/common/default_button.dart'; import 'package:syncrow_web/pages/visitor_password/bloc/visitor_password_bloc.dart'; import 'package:syncrow_web/pages/visitor_password/bloc/visitor_password_event.dart'; import 'package:syncrow_web/pages/visitor_password/bloc/visitor_password_state.dart'; +import 'package:syncrow_web/pages/visitor_password/view/repeat_widget.dart'; +import 'package:syncrow_web/utils/snack_bar.dart'; import 'package:syncrow_web/utils/style.dart'; - class VisitorPasswordDialog extends StatelessWidget { const VisitorPasswordDialog({super.key}); @@ -18,19 +20,20 @@ class VisitorPasswordDialog extends StatelessWidget { return BlocProvider( create: (context) => VisitorPasswordBloc(), child: BlocBuilder( - builder: (context, state) { + builder: (BuildContext context, VisitorPasswordState state) { final visitorBloc = BlocProvider.of(context); + bool isRepeat = state is IsRepeatState ? state.repeat : visitorBloc.repeat; return AlertDialog( title: const Text('Create visitor password'), content: SingleChildScrollView( child: ListBody( children: [ - Row( + Row( children: [ Expanded( flex: 2, child: CustomWebTextField( - controller:visitorBloc.userNameController , + controller: visitorBloc.userNameController, isRequired: true, textFieldName: 'User Name', description: '', @@ -40,7 +43,7 @@ class VisitorPasswordDialog extends StatelessWidget { Expanded( flex: 2, child: CustomWebTextField( - controller:visitorBloc.emailController , + controller: visitorBloc.emailController, isRequired: true, textFieldName: 'Email Address', description: 'The password will be sent to the visitor’s email address.', @@ -48,11 +51,9 @@ class VisitorPasswordDialog extends StatelessWidget { ), ], ), - SizedBox(height: size.height * 0.02), // Add spacing Column( crossAxisAlignment: CrossAxisAlignment.start, - mainAxisAlignment: MainAxisAlignment.start, children: [ Row( children: [ @@ -69,13 +70,13 @@ class VisitorPasswordDialog extends StatelessWidget { Row( children: [ SizedBox( - width: size.width*0.15, + width: size.width * 0.15, child: RadioListTile( title: const Text('Offline Password'), value: 'Offline Password', groupValue: (state is PasswordTypeSelected) ? state.selectedType - : 'Offline Password', + : visitorBloc.accessTypeSelected, onChanged: (String? value) { if (value != null) { context.read().add(SelectPasswordType(value)); @@ -84,46 +85,43 @@ class VisitorPasswordDialog extends StatelessWidget { ), ), SizedBox( - width: size.width*0.15, + width: size.width * 0.15, child: RadioListTile( title: const Text('Online Password'), value: 'Online Password', groupValue: (state is PasswordTypeSelected) ? state.selectedType - : 'Offline Password', + : visitorBloc.accessTypeSelected, onChanged: (String? value) { if (value != null) { - context - .read() - .add(SelectPasswordType(value)); + context.read().add(SelectPasswordType(value)); } }, ), ), SizedBox( - width: size.width*0.15, + width: size.width * 0.15, child: RadioListTile( title: const Text('Dynamic Password'), value: 'Dynamic Password', groupValue: (state is PasswordTypeSelected) ? state.selectedType - : 'Offline Password', + : visitorBloc.accessTypeSelected, onChanged: (String? value) { if (value != null) { - context - .read() - .add(SelectPasswordType(value)); + context.read().add(SelectPasswordType(value)); } }, ), ), ], - ) + ), ], ), + visitorBloc.accessTypeSelected=='Dynamic Password' ? + SizedBox(): Column( crossAxisAlignment: CrossAxisAlignment.start, - mainAxisAlignment: MainAxisAlignment.start, children: [ Row( children: [ @@ -146,11 +144,10 @@ class VisitorPasswordDialog extends StatelessWidget { value: 'One-Time', groupValue: (state is UsageFrequencySelected) ? state.selectedFrequency - : 'One-Time', + : visitorBloc.usageFrequencySelected, onChanged: (String? value) { if (value != null) { - context.read() - .add(SelectUsageFrequency(value)); + context.read().add(SelectUsageFrequency(value)); } }, ), @@ -162,59 +159,80 @@ class VisitorPasswordDialog extends StatelessWidget { value: 'Periodic', groupValue: (state is UsageFrequencySelected) ? state.selectedFrequency - : 'Periodic1', + : visitorBloc.usageFrequencySelected, onChanged: (String? value) { if (value != null) { - context.read() - .add(SelectUsageFrequency(value)); + context.read().add(SelectUsageFrequency(value)); } }, ), ), ], - ) + ), ], ), + visitorBloc.accessTypeSelected=='Dynamic Password' ? + SizedBox(): + DateTimeWebWidget( isRequired: true, - title: 'Access Period', - size: size, - endTime: () { - visitorBloc.add(SelectTimeVisitorPassword(context: context, isStart: false)); - }, - startTime: () { - visitorBloc.add(SelectTimeVisitorPassword(context: context, isStart: true)); - }, - firstString:BlocProvider.of(context).startTime , - secondString:BlocProvider.of(context).endTime , - ) , - + title: 'Access Period', + size: size, + endTime: () { + visitorBloc.add(SelectTimeVisitorPassword(context: context, isStart: false)); + }, + startTime: () { + visitorBloc.add(SelectTimeVisitorPassword(context: context, isStart: true)); + }, + firstString: visitorBloc.startTime, + secondString: visitorBloc.endTime, + ), Column( - mainAxisAlignment: MainAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( children: [ - Text( - '* ', - style: Theme.of(context) - .textTheme - .bodyMedium! - .copyWith(color: Colors.red), - ), + Text( + '* ', + style: Theme.of(context) + .textTheme + .bodyMedium! + .copyWith(color: Colors.red), + ), const Text('Access Devices'), ], ), const Text('Within the validity period, each device can be unlocked only once.'), + visitorBloc.usageFrequencySelected=='Periodic'&&visitorBloc.accessTypeSelected=='Offline Password'? + SizedBox( + width: 100, + child: ListTile( + contentPadding: EdgeInsets.zero, + leading: const Text('Repeat'), + trailing: Transform.scale( + scale: .8, + child: CupertinoSwitch( + value: visitorBloc.repeat, + onChanged: (value) { + visitorBloc.add(ToggleRepeatEvent()); + }, + applyTheme: true, + ), + ), + ), + ):const SizedBox(), + isRepeat ? const RepeatWidget() : const SizedBox(), Container( - decoration: containerDecoration, - width: size.width*0.2, - child: const DefaultButton( - borderRadius: 8, - child:Text('+ Add Device'),)), - + decoration: containerDecoration, + width: size.width * 0.2, + child: const DefaultButton( + borderRadius: 8, + child: Text('+ Add Device'), + ), + ), ], - ) + ), + ], ), ), @@ -222,22 +240,27 @@ class VisitorPasswordDialog extends StatelessWidget { actions: [ Container( decoration: containerDecoration, - width: size.width*0.2, - child: DefaultButton( - borderRadius: 8, - onPressed:() {}, - backgroundColor: Colors.white, - child:Text('Cancel',style: Theme.of(context) - .textTheme - .bodyMedium!,),)), - + width: size.width * 0.2, + child: DefaultButton( + borderRadius: 8, + onPressed: () { + Navigator.of(context).pop(); // Close the dialog + }, + backgroundColor: Colors.white, + child: Text( + 'Cancel', + style: Theme.of(context).textTheme.bodyMedium!, + ), + ), + ), Container( - decoration: containerDecoration, - width: size.width*0.2, - child: const DefaultButton( - borderRadius: 8, - child:Text('Ok'),)), - + decoration: containerDecoration, + width: size.width * 0.2, + child: const DefaultButton( + borderRadius: 8, + child: Text('Ok'), + ), + ), ], ); }, @@ -245,4 +268,3 @@ class VisitorPasswordDialog extends StatelessWidget { ); } } - From e610f7335dd2ea914ad96040f7ee630a79ea4268 Mon Sep 17 00:00:00 2001 From: mohammad Date: Thu, 15 Aug 2024 17:01:49 +0300 Subject: [PATCH 15/32] create visitor password --- lib/main.dart | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/main.dart b/lib/main.dart index e5d2c22b..92b8684c 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -56,8 +56,8 @@ class MyApp extends StatelessWidget { colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple), // Set up color scheme useMaterial3: true, // Enable Material 3 ), - home: VisitorPasswordDialog() - // home:isLoggedIn == 'Success' ? const HomePage() : const LoginPage(), + // home: VisitorPasswordDialog() + home:isLoggedIn == 'Success' ? const HomePage() : const LoginPage(), )); } } From 869a10f92c2d2a862cfb338be7686c826ea95780 Mon Sep 17 00:00:00 2001 From: mohammad Date: Sun, 18 Aug 2024 17:08:36 +0300 Subject: [PATCH 16/32] create visitor password --- assets/images/device_note.svg | 5 + lib/main.dart | 5 +- .../access_management/bloc/access_bloc.dart | 7 +- .../model/password_model.dart | 8 +- .../view/access_management.dart | 293 ++++++------ lib/pages/common/custom_table.dart | 103 +++++ lib/pages/common/custom_web_textfield.dart | 16 +- .../bloc/visitor_password_bloc.dart | 29 +- .../bloc/visitor_password_event.dart | 1 + .../bloc/visitor_password_state.dart | 18 + .../visitor_password/model/device_model.dart | 99 +++++ .../view/add_device_dialog.dart | 210 +++++++++ .../view/visitor_password_dialog.dart | 418 ++++++++++-------- lib/services/access_mang_api.dart | 23 + lib/services/auth_api.dart | 2 + lib/utils/color_manager.dart | 1 + lib/utils/constants/api_const.dart | 1 + lib/utils/constants/assets.dart | 1 + lib/utils/constants/const.dart | 44 ++ lib/utils/constants/string_const.dart | 5 - lib/utils/style.dart | 11 +- 21 files changed, 932 insertions(+), 368 deletions(-) create mode 100644 assets/images/device_note.svg create mode 100644 lib/pages/common/custom_table.dart create mode 100644 lib/pages/visitor_password/model/device_model.dart create mode 100644 lib/pages/visitor_password/view/add_device_dialog.dart create mode 100644 lib/utils/constants/const.dart delete mode 100644 lib/utils/constants/string_const.dart diff --git a/assets/images/device_note.svg b/assets/images/device_note.svg new file mode 100644 index 00000000..f0b94043 --- /dev/null +++ b/assets/images/device_note.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/lib/main.dart b/lib/main.dart index 92b8684c..e57db364 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -5,6 +5,7 @@ import 'package:syncrow_web/pages/auth/bloc/auth_bloc.dart'; import 'package:syncrow_web/pages/auth/view/login_page.dart'; import 'package:syncrow_web/pages/home/bloc/home_bloc.dart'; import 'package:syncrow_web/pages/home/view/home_page.dart'; +import 'package:syncrow_web/pages/visitor_password/view/add_device_dialog.dart'; import 'package:syncrow_web/pages/visitor_password/view/visitor_password_dialog.dart'; import 'package:syncrow_web/services/locator.dart'; import 'package:syncrow_web/utils/color_manager.dart'; @@ -40,6 +41,7 @@ class MyApp extends StatelessWidget { PointerDeviceKind.unknown, }, ), + theme: ThemeData( textTheme: const TextTheme( bodySmall: TextStyle(fontSize: 13, color: ColorsManager.whiteColors, fontWeight: FontWeight.bold), @@ -53,10 +55,11 @@ class MyApp extends StatelessWidget { fontWeight: FontWeight.bold, ), ), + colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple), // Set up color scheme useMaterial3: true, // Enable Material 3 ), - // home: VisitorPasswordDialog() + // home: AddDeviceDialog() home:isLoggedIn == 'Success' ? const HomePage() : const LoginPage(), )); } diff --git a/lib/pages/access_management/bloc/access_bloc.dart b/lib/pages/access_management/bloc/access_bloc.dart index e8be33f4..aef9508c 100644 --- a/lib/pages/access_management/bloc/access_bloc.dart +++ b/lib/pages/access_management/bloc/access_bloc.dart @@ -171,11 +171,10 @@ class AccessBloc extends Bloc { } - - DateTime timestampToDateTime(dynamic timestamp) { - return DateTime.fromMillisecondsSinceEpoch(int.parse(timestamp) * 1000); + String timestampToDate(dynamic timestamp) { + DateTime dateTime = DateTime.fromMillisecondsSinceEpoch(int.parse(timestamp) * 1000); + return "${dateTime.year}/${dateTime.month.toString().padLeft(2, '0')}/${dateTime.day.toString().padLeft(2, '0')}"; } - } diff --git a/lib/pages/access_management/model/password_model.dart b/lib/pages/access_management/model/password_model.dart index 0377bfb7..acd3e13a 100644 --- a/lib/pages/access_management/model/password_model.dart +++ b/lib/pages/access_management/model/password_model.dart @@ -1,3 +1,5 @@ +import 'package:syncrow_web/utils/constants/const.dart'; + class PasswordModel { final dynamic passwordId; final dynamic invalidTime; @@ -6,7 +8,7 @@ class PasswordModel { final dynamic createdTime; final dynamic passwodName; // New field final dynamic passwordStatus; - final dynamic passwordType; + final AccessType passwordType; final dynamic deviceUuid; PasswordModel({ @@ -17,7 +19,7 @@ class PasswordModel { this.createdTime, this.passwodName, // New field this.passwordStatus, - this.passwordType, + required this.passwordType, this.deviceUuid, }); @@ -30,7 +32,7 @@ class PasswordModel { createdTime: json['createdTime'], passwodName: json['passwodName']??'No name', // New field passwordStatus: json['passwordStatus'], - passwordType: json['passwordType'], + passwordType:AccessTypeExtension.fromString(json['passwordType']) , deviceUuid: json['deviceUuid'], ); } diff --git a/lib/pages/access_management/view/access_management.dart b/lib/pages/access_management/view/access_management.dart index d2dd72b9..265c96c0 100644 --- a/lib/pages/access_management/view/access_management.dart +++ b/lib/pages/access_management/view/access_management.dart @@ -3,10 +3,12 @@ import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:syncrow_web/pages/access_management/bloc/access_bloc.dart'; import 'package:syncrow_web/pages/access_management/bloc/access_event.dart'; import 'package:syncrow_web/pages/access_management/bloc/access_state.dart'; +import 'package:syncrow_web/pages/common/custom_table.dart'; import 'package:syncrow_web/pages/common/date_time_widget.dart'; import 'package:syncrow_web/pages/common/default_button.dart'; import 'package:syncrow_web/pages/visitor_password/view/visitor_password_dialog.dart'; import 'package:syncrow_web/utils/color_manager.dart'; +import 'package:syncrow_web/utils/constants/const.dart'; import 'package:syncrow_web/utils/style.dart'; import 'package:syncrow_web/web_layout/web_scaffold.dart'; @@ -51,68 +53,69 @@ class AccessManagementPage extends StatelessWidget { crossAxisAlignment: CrossAxisAlignment.start, children: [ Container( - height: size.height * 0.05, - width:size.width * 0.26 , decoration: containerDecoration, - child: Center( - child: ListView.builder( - scrollDirection: Axis.horizontal, - itemCount: BlocProvider.of(context).tabs.length, - itemBuilder: (context, index) { - final isSelected = index == - BlocProvider.of(context).selectedIndex; - return InkWell( - onTap: () { - BlocProvider.of(context).add(TabChangedEvent(index)); - }, - child: Container( - decoration: BoxDecoration( - color: ColorsManager.boxColor, - border: Border.all( - color: isSelected ? Colors.blue : Colors.transparent, - width: 2.0,), - borderRadius: index == 0 - ? const BorderRadius.only( - topLeft: Radius.circular(10), - bottomLeft: Radius.circular(10)) - : index == 3 - ? const BorderRadius.only( - topRight: Radius.circular(10), - bottomRight: Radius.circular(10)) - : null), - padding: const EdgeInsets.only(left: 10,right: 10), - child: Center( - child: Text( - BlocProvider.of(context).tabs[index], - style: TextStyle( - color: isSelected - ? Colors.blue - : Colors.black, + height: size.height * 0.05, + child: Flexible( + child: ListView.builder( + scrollDirection: Axis.horizontal, + itemCount: BlocProvider.of(context).tabs.length, + shrinkWrap: true, + itemBuilder: (context, index) { + final isSelected = index == + BlocProvider.of(context).selectedIndex; + return InkWell( + onTap: () { + BlocProvider.of(context).add(TabChangedEvent(index)); + }, + child: Container( + decoration: BoxDecoration( + color: ColorsManager.boxColor, + border: Border.all( + color: isSelected ? Colors.blue : Colors.transparent, + width: 2.0,), + borderRadius: index == 0 + ? const BorderRadius.only( + topLeft: Radius.circular(10), + bottomLeft: Radius.circular(10)) + : index == 3 + ? const BorderRadius.only( + topRight: Radius.circular(10), + bottomRight: Radius.circular(10)) + : null), + padding: const EdgeInsets.only(left: 10,right: 10), + child: Center( + child: Text( + BlocProvider.of(context).tabs[index], + style: TextStyle( + color: isSelected + ? Colors.blue + : Colors.black, + ), ), ), ), - ), - ); - }, + ); + }, + ), ), ), - ), const SizedBox( height: 20, ), Row( children: [ Column( + crossAxisAlignment: CrossAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.start, children: [ - const Text('Password Name'), + const Text('Name'), Container( width: size.width * 0.15, decoration: containerDecoration, child: TextFormField( controller: accessBloc.passwordName, - style: TextStyle(color: Colors.black), + style: const TextStyle(color: Colors.black), decoration: textBoxDecoration()! .copyWith(hintText: 'Please enter'), )), @@ -206,7 +209,7 @@ class AccessManagementPage extends StatelessWidget { ); }, borderRadius: 8, - child: Text('+ Create Visitor Password ')), + child: const Text('+ Create Visitor Password ')), ), const SizedBox( width: 10, @@ -230,120 +233,110 @@ class AccessManagementPage extends StatelessWidget { height: 20, ), Expanded( - child: state is TableLoaded - ? TableWidget(size, state,accessBloc) - : const Center(child: CircularProgressIndicator())) + child: state is TableLoaded + ? DynamicTable( + size: size, + cellDecoration: containerDecoration, + headers: const [ + 'Name', + 'Access Type', + 'Access Period', + 'Device Id', + 'Authorizer', + 'Authorization Date & Time', + 'Access Status' + ], + data: state.data.map((item) { + return [ + item.passwodName.toString(), + item.passwordType.value, + ('${accessBloc.timestampToDate(item.effectiveTime)} - ${accessBloc.timestampToDate(item.invalidTime)}'), + item.deviceUuid.toString(), + '', + '', + '' + ]; + }).toList(), + ) + : const Center(child: CircularProgressIndicator()), + ) + ], ), ); }))); } - Container TableWidget(Size size, TableLoaded state,AccessBloc accessBloc) { - return Container( - decoration: containerDecoration, - width: size.width, - child: Padding( - padding: const EdgeInsets.all(10.0), - child: ListView( - scrollDirection: Axis.horizontal, - children: [ - Container( - width: size.width, - height: size.height, - child: Column( - children: [ - Container( - color: ColorsManager.boxColor, - child: Row( - children: [ - _buildTableHeaderCell('Password name'), - _buildTableHeaderCell(' Password Type'), - _buildTableHeaderCell('Start Time'), - _buildTableHeaderCell('End Time'), - _buildTableHeaderCell('Device Id'), - // _buildTableHeaderCell('Authorization Source'), - // _buildTableHeaderCell('Authorizer'), - _buildTableHeaderCell('Password Created'), - // _buildTableHeaderCell('Access Status'), - _buildTableHeaderCell('Password Status'), - ], - ), - ), - Expanded( - child: Container( - width: size.width, - color: ColorsManager.whiteColors, - child: ListView( - shrinkWrap: true, - children: [ - Column( - children: state.data.map((item) { - return Row( - children: [ - _buildTableCell(item.passwodName), - _buildTableCell(item.passwordType), - _buildTableCell(accessBloc.timestampToDateTime(item.effectiveTime).toString()), - _buildTableCell(accessBloc.timestampToDateTime(item.invalidTime).toString()), - _buildTableCell(item.deviceUuid.toString()), - // _buildTableCell(item.authorizationSource), - // _buildTableCell(item.authorizer), - _buildTableCell(item.passwordCreated!=null?accessBloc.timestampToDateTime(item.passwordCreated).toString():'no data'), - // _buildTableCell(item.accessStatus), - _buildTableCell(item.passwordStatus.toString()), - ], - ); - }).toList(), - ), - ], - ), - ), - ), - ], - ), - ), - ], - ), - ), - ); - } + // Container TableWidget(Size size, TableLoaded state,AccessBloc accessBloc) { + // return Container( + // decoration: containerDecoration, + // width: size.width, + // child: Padding( + // padding: const EdgeInsets.all(10.0), + // child: ListView( + // scrollDirection: Axis.horizontal, + // children: [ + // Container( + // width: size.width, + // height: size.height, + // child: Column( + // children: [ + // Container( + // color: ColorsManager.boxColor, + // child: Row( + // children: [ + // _buildTableHeaderCell('Password name'), + // _buildTableHeaderCell(' Password Type'), + // _buildTableHeaderCell('Start Time'), + // _buildTableHeaderCell('End Time'), + // _buildTableHeaderCell('Device Id'), + // // _buildTableHeaderCell('Authorization Source'), + // // _buildTableHeaderCell('Authorizer'), + // _buildTableHeaderCell('Password Created'), + // // _buildTableHeaderCell('Access Status'), + // _buildTableHeaderCell('Password Status'), + // ], + // ), + // ), + // Expanded( + // child: Container( + // width: size.width, + // color: ColorsManager.whiteColors, + // child: ListView( + // shrinkWrap: true, + // children: [ + // Column( + // children: state.data.map((item) { + // return Row( + // children: [ + // _buildTableCell(item.passwodName), + // _buildTableCell(item.passwordType), + // + // _buildTableCell(accessBloc.timestampToDateTime(item.effectiveTime).toString()), + // _buildTableCell(accessBloc.timestampToDateTime(item.invalidTime).toString()), + // _buildTableCell(item.deviceUuid.toString()), + // // _buildTableCell(item.authorizationSource), + // // _buildTableCell(item.authorizer), + // _buildTableCell(item.passwordCreated!=null?accessBloc.timestampToDateTime(item.passwordCreated).toString():'no data'), + // // _buildTableCell(item.accessStatus), + // _buildTableCell(item.passwordStatus.toString()), + // ], + // ); + // }).toList(), + // ), + // ], + // ), + // ), + // ), + // ], + // ), + // ), + // ], + // ), + // ), + // ); + // } } -Widget _buildTableHeaderCell(String title) { - return Expanded( - child: Container( - decoration: const BoxDecoration( - border: Border.symmetric( - vertical: BorderSide(color: ColorsManager.boxDivider))), - alignment: Alignment.centerLeft, - child: Padding( - padding: const EdgeInsets.all(8.0), - child: Text(title, style: TextStyle(fontWeight: FontWeight.bold)), - ), - ), - ); -} - -Widget _buildTableCell(String content) { - return Expanded( - child: Container( - height: 80, - padding: const EdgeInsets.all(20.0), - decoration: BoxDecoration( - border: Border( - bottom: BorderSide( // <--- right side - color: ColorsManager.boxDivider, - width: 1.0, - ), - ) - ), - alignment: Alignment.centerLeft, - child: Text( - content, - style: TextStyle(color: Colors.black, fontSize: 12), - ), - ), - ); -} diff --git a/lib/pages/common/custom_table.dart b/lib/pages/common/custom_table.dart new file mode 100644 index 00000000..25c821fb --- /dev/null +++ b/lib/pages/common/custom_table.dart @@ -0,0 +1,103 @@ +import 'package:flutter/material.dart'; +import 'package:syncrow_web/utils/color_manager.dart'; + +class DynamicTable extends StatelessWidget { + final List headers; + final List> data; + final BoxDecoration? headerDecoration; + final BoxDecoration? cellDecoration; + final Size size; + + const DynamicTable({ + Key? key, + required this.headers, + required this.data, + required this.size, + this.headerDecoration, + this.cellDecoration, + }) : super(key: key); + + @override + Widget build(BuildContext context) { + return Container( + + decoration: cellDecoration, + child: Padding( + padding: const EdgeInsets.all(10.0), + child: ListView( + scrollDirection: Axis.horizontal, + children: [ + Container( + width:size.width, + child: Column( + children: [ + Container( + decoration: headerDecoration ?? + BoxDecoration(color: Colors.grey[200]), + child: Row( + children: headers.map((header) => + _buildTableHeaderCell(header)).toList(), + ), + ), + Expanded( + child: Container( + color: Colors.white, + child: ListView( + shrinkWrap: true, + children: data.map((row) { + return Row( + children: row.map((cell) => + _buildTableCell(cell.toString())).toList(), + ); + }).toList(), + ), + ), + ), + ], + ), + ), + ], + ), + ), + ); + } +} +Widget _buildTableHeaderCell(String title) { + return Expanded( + child: Container( + decoration: const BoxDecoration( + border: Border.symmetric( + vertical: BorderSide(color: ColorsManager.boxDivider))), + alignment: Alignment.centerLeft, + child: Padding( + padding: const EdgeInsets.all(8.0), + child: Text(title, style: TextStyle(fontWeight: FontWeight.bold)), + ), + ), + ); +} + +Widget _buildTableCell(String content) { + return Expanded( + child: Container( + height: 80, + padding: const EdgeInsets.all(20.0), + decoration: const BoxDecoration( + border: Border( + bottom: BorderSide( // <--- right side + color: ColorsManager.boxDivider, + width: 1.0, + ), + ) + ), + alignment: Alignment.centerLeft, + child: Text( + content, + style: TextStyle(color: Colors.black, fontSize: 12), + ), + ), + ); +} + + + diff --git a/lib/pages/common/custom_web_textfield.dart b/lib/pages/common/custom_web_textfield.dart index 003ca88d..0662199b 100644 --- a/lib/pages/common/custom_web_textfield.dart +++ b/lib/pages/common/custom_web_textfield.dart @@ -29,8 +29,7 @@ class CustomWebTextField extends StatelessWidget { if(isRequired) Row( children: [ - Text( - '* ', + Text('* ', style: Theme.of(context) .textTheme .bodyMedium! @@ -39,6 +38,7 @@ class CustomWebTextField extends StatelessWidget { Text(textFieldName), ], ), + const SizedBox(width: 10,), Text( description??'', // ' The password will be sent to the visitor’s email address.', style: Theme.of(context) @@ -53,7 +53,17 @@ class CustomWebTextField extends StatelessWidget { ), const SizedBox(height: 7,), Container( - decoration: containerDecoration, + height: MediaQuery.of(context).size.height*0.05, + decoration: containerDecoration.copyWith( + color: const Color(0xFFF5F6F7), + boxShadow: [ + BoxShadow( + color: Colors.grey.withOpacity(0.3), + spreadRadius:2, + blurRadius: 3, + offset: Offset(1, 1), // changes position of shadow + ), ] + ), child: TextFormField( controller: controller, style: const TextStyle(color: Colors.black), diff --git a/lib/pages/visitor_password/bloc/visitor_password_bloc.dart b/lib/pages/visitor_password/bloc/visitor_password_bloc.dart index 668d082e..c6ce5188 100644 --- a/lib/pages/visitor_password/bloc/visitor_password_bloc.dart +++ b/lib/pages/visitor_password/bloc/visitor_password_bloc.dart @@ -2,6 +2,8 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:syncrow_web/pages/visitor_password/bloc/visitor_password_event.dart'; import 'package:syncrow_web/pages/visitor_password/bloc/visitor_password_state.dart'; +import 'package:syncrow_web/pages/visitor_password/model/device_model.dart'; +import 'package:syncrow_web/services/access_mang_api.dart'; import 'package:syncrow_web/utils/color_manager.dart'; import 'package:syncrow_web/utils/snack_bar.dart'; @@ -9,15 +11,29 @@ import 'package:syncrow_web/utils/snack_bar.dart'; class VisitorPasswordBloc extends Bloc { VisitorPasswordBloc() : super(VisitorPasswordInitial()) { on(selectUsageFrequency); + on(_onFetchDevice); + on(selectAccessType); on(selectTimeVisitorPassword); on(toggleRepeat); on(toggleDaySelection); - } final TextEditingController userNameController = TextEditingController(); final TextEditingController emailController = TextEditingController(); + + + final TextEditingController deviceNameController = TextEditingController(); + final TextEditingController deviceIdController = TextEditingController(); + final TextEditingController unitNameController = TextEditingController(); + final TextEditingController virtualAddressController = TextEditingController(); + + List data=[]; + + + + + String accessTypeSelected='Offline Password'; String usageFrequencySelected='One-Time'; @@ -136,6 +152,17 @@ class VisitorPasswordBloc extends Bloc _onFetchDevice( + FetchDevice event, Emitter emit) async { + try { + emit(DeviceLoaded()); + data = await AccessMangApi().fetchDevices(); + emit(TableLoaded(data)); + } catch (e) { + emit(FailedState(e.toString())); + } + } } diff --git a/lib/pages/visitor_password/bloc/visitor_password_event.dart b/lib/pages/visitor_password/bloc/visitor_password_event.dart index b0a51ca0..8195e155 100644 --- a/lib/pages/visitor_password/bloc/visitor_password_event.dart +++ b/lib/pages/visitor_password/bloc/visitor_password_event.dart @@ -47,3 +47,4 @@ class ToggleDaySelectionEvent extends VisitorPasswordEvent { class ToggleRepeatEvent extends VisitorPasswordEvent {} +class FetchDevice extends VisitorPasswordEvent {} diff --git a/lib/pages/visitor_password/bloc/visitor_password_state.dart b/lib/pages/visitor_password/bloc/visitor_password_state.dart index 8a5525d3..88b14ec9 100644 --- a/lib/pages/visitor_password/bloc/visitor_password_state.dart +++ b/lib/pages/visitor_password/bloc/visitor_password_state.dart @@ -1,6 +1,7 @@ import 'package:equatable/equatable.dart'; +import 'package:syncrow_web/pages/visitor_password/model/device_model.dart'; abstract class VisitorPasswordState extends Equatable { const VisitorPasswordState(); @@ -42,3 +43,20 @@ class IsRepeatState extends VisitorPasswordState { class LoadingInitialState extends VisitorPasswordState {} class ChangeTimeState extends VisitorPasswordState {} +class DeviceLoaded extends VisitorPasswordState {} +class FailedState extends VisitorPasswordState { + final String message; + + FailedState(this.message); + + @override + List get props => [message]; +} +class TableLoaded extends VisitorPasswordState { + final List data; + + const TableLoaded(this.data); + + @override + List get props => [data]; +} \ No newline at end of file diff --git a/lib/pages/visitor_password/model/device_model.dart b/lib/pages/visitor_password/model/device_model.dart new file mode 100644 index 00000000..0b37d70d --- /dev/null +++ b/lib/pages/visitor_password/model/device_model.dart @@ -0,0 +1,99 @@ + + +class DeviceModel { + dynamic productUuid; + dynamic productType; + dynamic activeTime; + dynamic category; + dynamic categoryName; + dynamic createTime; + dynamic gatewayId; + dynamic icon; + dynamic ip; + dynamic lat; + dynamic localKey; + dynamic lon; + dynamic model; + dynamic name; + dynamic online; + dynamic ownerId; + dynamic sub; + dynamic timeZone; + dynamic updateTime; + dynamic uuid; + + DeviceModel({ + required this.productUuid, + required this.productType, + required this.activeTime, + required this.category, + required this.categoryName, + required this.createTime, + required this.gatewayId, + required this.icon, + required this.ip, + required this.lat, + required this.localKey, + required this.lon, + required this.model, + required this.name, + required this.online, + required this.ownerId, + required this.sub, + required this.timeZone, + required this.updateTime, + required this.uuid, + }); + + // Deserialize from JSON + factory DeviceModel.fromJson(Map json) { + return DeviceModel( + productUuid: json['productUuid'] , + productType: json['productType'], + activeTime: json['activeTime'], + category: json['category'] , + categoryName: json['categoryName'] , + createTime: json['createTime'] , + gatewayId: json['gatewayId'], + icon: json['icon'], + ip: json['ip'] , + lat: json['lat'] , + localKey: json['localKey'] , + lon: json['lon'] , + model: json['model'] , + name: json['name'], + online: json['online'], + ownerId: json['ownerId'] , + sub: json['sub'], + timeZone: json['timeZone'], + updateTime: json['updateTime'] , + uuid: json['uuid'], + ); + } + + // Serialize to JSON + Map toJson() { + return { + 'productUuid': productUuid, + 'productType': productType, + 'activeTime': activeTime, + 'category': category, + 'categoryName': categoryName, + 'createTime': createTime, + 'gatewayId': gatewayId, + 'icon': icon, + 'ip': ip, + 'lat': lat, + 'localKey': localKey, + 'lon': lon, + 'model': model, + 'name': name, + 'online': online, + 'ownerId': ownerId, + 'sub': sub, + 'timeZone': timeZone, + 'updateTime': updateTime, + 'uuid': uuid, + }; + } +} diff --git a/lib/pages/visitor_password/view/add_device_dialog.dart b/lib/pages/visitor_password/view/add_device_dialog.dart new file mode 100644 index 00000000..d987621e --- /dev/null +++ b/lib/pages/visitor_password/view/add_device_dialog.dart @@ -0,0 +1,210 @@ + +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:flutter_svg/flutter_svg.dart'; +import 'package:syncrow_web/pages/common/custom_web_textfield.dart'; +import 'package:syncrow_web/pages/common/default_button.dart'; +import 'package:syncrow_web/pages/visitor_password/bloc/visitor_password_bloc.dart'; +import 'package:syncrow_web/pages/visitor_password/bloc/visitor_password_event.dart'; +import 'package:syncrow_web/pages/visitor_password/bloc/visitor_password_state.dart'; +import 'package:syncrow_web/utils/color_manager.dart'; +import 'package:syncrow_web/utils/constants/assets.dart'; +import 'package:syncrow_web/utils/style.dart'; +import '../../common/custom_table.dart'; + + +class AddDeviceDialog extends StatelessWidget { + const AddDeviceDialog({super.key}); + + @override + Widget build(BuildContext context) { + Size size = MediaQuery.of(context).size; + return BlocProvider( + create: (context) => VisitorPasswordBloc()..add(FetchDevice()), + child: BlocBuilder( + builder: (BuildContext context, VisitorPasswordState state) { + final visitorBloc = BlocProvider.of(context); + return AlertDialog( + backgroundColor: Colors.white, + title: const Text('Add Accessible Device'), + content: Container( + height: MediaQuery.of(context).size.height/1.7, + width: MediaQuery.of(context).size.width/2, + child: Padding( + padding: const EdgeInsets.all(10.0), + child: Column( + children: [ + Container( + width: size.width, + padding: EdgeInsets.all(15), + decoration:containerDecoration.copyWith( + color: ColorsManager.worningColor, + border: Border.all(color: Color(0xffFFD22F)), + boxShadow: [] + ), + child: Row( + children: [ + SizedBox( + child: SvgPicture.asset( + Assets.deviceNoteIcon, + height: 15, + width: 15, + ), + ), + SizedBox(width: 10,), + Text('Only online accessible devices can be added'), + ], + ) + ), + SizedBox(height: 20,), + Row( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Expanded( + flex: 2, + child: CustomWebTextField( + controller: visitorBloc.deviceNameController, + isRequired: true, + textFieldName: 'Device Name', + description: '', + ), + ), + const SizedBox(width: 10), + Expanded( + flex: 2, + child: CustomWebTextField( + controller: visitorBloc.deviceNameController, + isRequired: true, + textFieldName: 'Device ID', + description: '', + ), + ), + const SizedBox(width: 10), + Expanded( + flex: 2, + child: CustomWebTextField( + controller: visitorBloc.unitNameController, + isRequired: true, + textFieldName: 'Unit Name', + description: '', + ), + ), + + const SizedBox(width: 10), + Column( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + const SizedBox(height: 25), + Center( + child: Container( + height: 43, + width: 100, + decoration: containerDecoration, + child: Center( + child: DefaultButton( + onPressed: () { + // Your search function here + }, + borderRadius: 9, + child: const Text('Search'), + ), + ), + ), + ), + + ], + ), + const SizedBox(width: 10), + Column( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + const SizedBox(height: 25), + + Center( + child: Container( + height: 43, + width: 100, + decoration: containerDecoration, + child: Center( + child: DefaultButton( + backgroundColor: ColorsManager.whiteColors, + borderRadius: 9, + child: Text( + 'Reset', + style: Theme.of(context).textTheme.bodySmall!.copyWith(color: Colors.black), + ), + ), + ), + ), + ), + + ], + ), + + ], + ), + SizedBox(height: 20), + Container( + child: Expanded( + child: state is TableLoaded + ? Container( + decoration: containerDecoration, + child: DynamicTable( + size: size*0.5, + headers: ['Device Name', 'Device ID', 'Access Type', 'Unit Name', 'Status'], + data: state.data.map((item) { + return [ + item.name.toString(), + item.uuid.toString(), + item.productType.toString(), + '', + item.online.toString(), + // item.categoryName.toString(), + // accessBloc.timestampToDateTime(item.effectiveTime).toString(), + // accessBloc.timestampToDateTime(item.invalidTime).toString(), + // item.deviceUuid.toString(), + // item.passwordCreated != null ? accessBloc.timestampToDateTime(item.passwordCreated).toString() : 'no data', + // item.passwordStatus.toString(), + ]; + }).toList(), + ), + ) + // TableWidget(size: size, headers: ['Device Name', 'Device ID', 'Access Type', 'Unit Name', 'Status', 'Virtual Address',], data: [], bloc: bloc) + : const Center(child: CircularProgressIndicator())), + ) + ], + ), + ), + ), + actionsAlignment: MainAxisAlignment.center, + actions: [ + Container( + decoration: containerDecoration, + width: size.width * 0.2, + child: DefaultButton( + borderRadius: 8, + onPressed: () { + Navigator.of(context).pop(); // Close the dialog + }, + backgroundColor: Colors.white, + child: Text( + 'Cancel', + style: Theme.of(context).textTheme.bodyMedium!, + ), + ), + ), + Container( + decoration: containerDecoration, + width: size.width * 0.2, + child: const DefaultButton( + borderRadius: 8, + child: Text('Ok'), + ), + ), + ], + ); + }, + ), + ); } +} diff --git a/lib/pages/visitor_password/view/visitor_password_dialog.dart b/lib/pages/visitor_password/view/visitor_password_dialog.dart index 140ded0b..0693f724 100644 --- a/lib/pages/visitor_password/view/visitor_password_dialog.dart +++ b/lib/pages/visitor_password/view/visitor_password_dialog.dart @@ -7,8 +7,8 @@ import 'package:syncrow_web/pages/common/default_button.dart'; import 'package:syncrow_web/pages/visitor_password/bloc/visitor_password_bloc.dart'; import 'package:syncrow_web/pages/visitor_password/bloc/visitor_password_event.dart'; import 'package:syncrow_web/pages/visitor_password/bloc/visitor_password_state.dart'; +import 'package:syncrow_web/pages/visitor_password/view/add_device_dialog.dart'; import 'package:syncrow_web/pages/visitor_password/view/repeat_widget.dart'; -import 'package:syncrow_web/utils/snack_bar.dart'; import 'package:syncrow_web/utils/style.dart'; class VisitorPasswordDialog extends StatelessWidget { @@ -24,216 +24,242 @@ class VisitorPasswordDialog extends StatelessWidget { final visitorBloc = BlocProvider.of(context); bool isRepeat = state is IsRepeatState ? state.repeat : visitorBloc.repeat; return AlertDialog( + backgroundColor: Colors.white, title: const Text('Create visitor password'), content: SingleChildScrollView( - child: ListBody( - children: [ - Row( - children: [ - Expanded( - flex: 2, - child: CustomWebTextField( - controller: visitorBloc.userNameController, - isRequired: true, - textFieldName: 'User Name', - description: '', - ), - ), - const Spacer(), - Expanded( - flex: 2, - child: CustomWebTextField( - controller: visitorBloc.emailController, - isRequired: true, - textFieldName: 'Email Address', - description: 'The password will be sent to the visitor’s email address.', - ), - ), - ], - ), - SizedBox(height: size.height * 0.02), // Add spacing - Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Row( + child: Padding( + padding: const EdgeInsets.all(10.0), + child: ListBody( + children: [ + Container( + child: Row( children: [ - Text( - '* ', - style: Theme.of(context) - .textTheme - .bodyMedium! - .copyWith(color: Colors.red), + Expanded( + flex: 2, + child: CustomWebTextField( + controller: visitorBloc.userNameController, + isRequired: true, + textFieldName: 'Name', + description: '', + ), ), - const Text('Access Type'), + const Spacer(), + Expanded( + flex: 2, + child: CustomWebTextField( + controller: visitorBloc.emailController, + isRequired: true, + textFieldName: 'Email Address', + description: 'The password will be sent to the visitor’s email address.', + ), + ), + const Spacer(), ], ), - Row( - children: [ - SizedBox( - width: size.width * 0.15, - child: RadioListTile( - title: const Text('Offline Password'), - value: 'Offline Password', - groupValue: (state is PasswordTypeSelected) - ? state.selectedType - : visitorBloc.accessTypeSelected, - onChanged: (String? value) { - if (value != null) { - context.read().add(SelectPasswordType(value)); - } - }, + ), + SizedBox(height: 20,), + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + Text( + '* ', + style: Theme.of(context) + .textTheme + .bodyMedium! + .copyWith(color: Colors.red), ), - ), - SizedBox( - width: size.width * 0.15, - child: RadioListTile( - title: const Text('Online Password'), - value: 'Online Password', - groupValue: (state is PasswordTypeSelected) - ? state.selectedType - : visitorBloc.accessTypeSelected, - onChanged: (String? value) { - if (value != null) { - context.read().add(SelectPasswordType(value)); - } - }, + const Text('Access Type'), + ], + ), + Row( + children: [ + SizedBox( + width: size.width * 0.15, + child: RadioListTile( + title: const Text('Offline Password'), + value: 'Offline Password', + groupValue: (state is PasswordTypeSelected) + ? state.selectedType + : visitorBloc.accessTypeSelected, + onChanged: (String? value) { + if (value != null) { + context.read().add(SelectPasswordType(value)); + } + }, + ), ), - ), - SizedBox( - width: size.width * 0.15, - child: RadioListTile( - title: const Text('Dynamic Password'), - value: 'Dynamic Password', - groupValue: (state is PasswordTypeSelected) - ? state.selectedType - : visitorBloc.accessTypeSelected, - onChanged: (String? value) { - if (value != null) { - context.read().add(SelectPasswordType(value)); - } - }, + SizedBox( + width: size.width * 0.15, + child: RadioListTile( + title: const Text('Online Password'), + value: 'Online Password', + groupValue: (state is PasswordTypeSelected) + ? state.selectedType + : visitorBloc.accessTypeSelected, + onChanged: (String? value) { + if (value != null) { + context.read().add(SelectPasswordType(value)); + } + }, + ), ), - ), - ], - ), - ], - ), - visitorBloc.accessTypeSelected=='Dynamic Password' ? - SizedBox(): - Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Row( - children: [ - Text( - '* ', - style: Theme.of(context) - .textTheme - .bodyMedium! - .copyWith(color: Colors.red), - ), - const Text('Usage Frequency'), - ], - ), - Row( - children: [ - SizedBox( - width: 200, - child: RadioListTile( - title: const Text('One-Time'), - value: 'One-Time', - groupValue: (state is UsageFrequencySelected) - ? state.selectedFrequency - : visitorBloc.usageFrequencySelected, - onChanged: (String? value) { - if (value != null) { - context.read().add(SelectUsageFrequency(value)); - } - }, + SizedBox( + width: size.width * 0.15, + child: RadioListTile( + title: const Text('Dynamic Password'), + value: 'Dynamic Password', + groupValue: (state is PasswordTypeSelected) + ? state.selectedType + : visitorBloc.accessTypeSelected, + onChanged: (String? value) { + if (value != null) { + context.read().add(SelectPasswordType(value)); + } + }, + ), ), - ), - SizedBox( - width: 200, - child: RadioListTile( - title: const Text('Periodic'), - value: 'Periodic', - groupValue: (state is UsageFrequencySelected) - ? state.selectedFrequency - : visitorBloc.usageFrequencySelected, - onChanged: (String? value) { - if (value != null) { - context.read().add(SelectUsageFrequency(value)); - } - }, + ], + ), + const Text('Only currently online devices can be selected. It is recommended to use when the device network is stable, and the system randomly generates a digital password'), + SizedBox(height: 20,) + ], + ), + visitorBloc.accessTypeSelected=='Dynamic Password' ? + SizedBox(): + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + Text( + '* ', + style: Theme.of(context) + .textTheme + .bodyMedium! + .copyWith(color: Colors.red), ), - ), - ], - ), - ], - ), - visitorBloc.accessTypeSelected=='Dynamic Password' ? - SizedBox(): + const Text('Usage Frequency'), + ], + ), + Row( + children: [ + SizedBox( + width: 200, + child: RadioListTile( + title: const Text('One-Time'), + value: 'One-Time', + groupValue: (state is UsageFrequencySelected) + ? state.selectedFrequency + : visitorBloc.usageFrequencySelected, + onChanged: (String? value) { + if (value != null) { + context.read().add(SelectUsageFrequency(value)); + } + }, + ), + ), + SizedBox( + width: 200, + child: RadioListTile( + title: const Text('Periodic'), + value: 'Periodic', + groupValue: (state is UsageFrequencySelected) + ? state.selectedFrequency + : visitorBloc.usageFrequencySelected, + onChanged: (String? value) { + if (value != null) { + context.read().add(SelectUsageFrequency(value)); + } + }, + ), + ), + ], + ), - DateTimeWebWidget( - isRequired: true, - title: 'Access Period', - size: size, - endTime: () { - visitorBloc.add(SelectTimeVisitorPassword(context: context, isStart: false)); - }, - startTime: () { - visitorBloc.add(SelectTimeVisitorPassword(context: context, isStart: true)); - }, - firstString: visitorBloc.startTime, - secondString: visitorBloc.endTime, - ), - Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Row( - children: [ - Text( - '* ', - style: Theme.of(context) - .textTheme - .bodyMedium! - .copyWith(color: Colors.red), - ), - const Text('Access Devices'), - ], - ), - const Text('Within the validity period, each device can be unlocked only once.'), - visitorBloc.usageFrequencySelected=='Periodic'&&visitorBloc.accessTypeSelected=='Offline Password'? - SizedBox( - width: 100, - child: ListTile( - contentPadding: EdgeInsets.zero, - leading: const Text('Repeat'), - trailing: Transform.scale( - scale: .8, - child: CupertinoSwitch( - value: visitorBloc.repeat, - onChanged: (value) { - visitorBloc.add(ToggleRepeatEvent()); - }, - applyTheme: true, + Text('Within the validity period, each device can be unlocked only once.') + ], + ), + + const SizedBox(height: 20,), + + visitorBloc.accessTypeSelected=='Dynamic Password' ? + SizedBox(): + DateTimeWebWidget( + isRequired: true, + title: 'Access Period', + size: size, + endTime: () { + visitorBloc.add(SelectTimeVisitorPassword(context: context, isStart: false)); + }, + startTime: () { + visitorBloc.add(SelectTimeVisitorPassword(context: context, isStart: true)); + }, + firstString: visitorBloc.startTime, + secondString: visitorBloc.endTime, + ), + const SizedBox(height: 20,), + + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + Text( + '* ', + style: Theme.of(context) + .textTheme + .bodyMedium! + .copyWith(color: Colors.red), + ), + const Text('Access Devices'), + ], + ), + const Text('Within the validity period, each device can be unlocked only once.'), + const SizedBox(height: 20,), + visitorBloc.usageFrequencySelected=='Periodic'&&visitorBloc.accessTypeSelected=='Offline Password'? + SizedBox( + width: 100, + child: ListTile( + contentPadding: EdgeInsets.zero, + leading: const Text('Repeat'), + trailing: Transform.scale( + scale: .8, + child: CupertinoSwitch( + value: visitorBloc.repeat, + onChanged: (value) { + visitorBloc.add(ToggleRepeatEvent()); + }, + applyTheme: true, + ), ), ), - ), - ):const SizedBox(), - isRepeat ? const RepeatWidget() : const SizedBox(), - Container( - decoration: containerDecoration, - width: size.width * 0.2, - child: const DefaultButton( - borderRadius: 8, - child: Text('+ Add Device'), - ), - ), - ], - ), + ):const SizedBox(), + isRepeat ? const RepeatWidget() : const SizedBox(), + Container( + decoration: containerDecoration, + width: size.width * 0.1, + child: DefaultButton( + onPressed: () { + showDialog( + context: context, + barrierDismissible: false, + builder: (BuildContext context) { + return const AddDeviceDialog(); - ], + }, + ); + }, + borderRadius: 8, + child: Text('+ Add Device'), + ), + ), + ], + ), + + ], + ), ), ), actionsAlignment: MainAxisAlignment.center, diff --git a/lib/services/access_mang_api.dart b/lib/services/access_mang_api.dart index 49870143..de14f4ea 100644 --- a/lib/services/access_mang_api.dart +++ b/lib/services/access_mang_api.dart @@ -6,6 +6,8 @@ import 'package:syncrow_web/pages/access_management/model/password_model.dart'; import 'package:syncrow_web/services/api/http_service.dart'; import 'package:syncrow_web/utils/constants/api_const.dart'; +import '../pages/visitor_password/model/device_model.dart'; + class AccessMangApi{ @@ -31,6 +33,27 @@ class AccessMangApi{ } } + Future fetchDevices() async { + try { + final response = await HTTPService().get( + path: ApiEndpoints.getDevices, + showServerMessage: true, + expectedResponseModel: (json) { + List jsonData = json; + print('fetchDevices List: $json'); + List passwordList = jsonData.map((jsonItem) { + return DeviceModel.fromJson(jsonItem); + }).toList(); + return passwordList; + }, + ); + return response; + } catch (e) { + debugPrint('Error fetching $e'); + return []; + } + } + diff --git a/lib/services/auth_api.dart b/lib/services/auth_api.dart index 7be7f328..0942b47b 100644 --- a/lib/services/auth_api.dart +++ b/lib/services/auth_api.dart @@ -46,8 +46,10 @@ class AuthenticationAPI { }, showServerMessage: true, expectedResponseModel: (json) { + print('object==$json'); return 30; } + ); return 30; } on DioException catch (e) { diff --git a/lib/utils/color_manager.dart b/lib/utils/color_manager.dart index 01e539dc..9b5b6d44 100644 --- a/lib/utils/color_manager.dart +++ b/lib/utils/color_manager.dart @@ -18,6 +18,7 @@ abstract class ColorsManager { static const Color dozeColor = Color(0xFFFEC258); static const Color relaxColor = Color(0xFFFBD288); static const Color readingColor = Color(0xFFF7D69C); + static const Color worningColor = Color(0xFFFFF3C8); static const Color energizingColor = Color(0xFFEDEDED); static const Color dividerColor = Color(0xFFEBEBEB); static const Color slidingBlueColor = Color(0x99023DFE); diff --git a/lib/utils/constants/api_const.dart b/lib/utils/constants/api_const.dart index d689ce79..b9ee2f5e 100644 --- a/lib/utils/constants/api_const.dart +++ b/lib/utils/constants/api_const.dart @@ -10,5 +10,6 @@ abstract class ApiEndpoints { static const String verifyOtp = '$baseUrl/authentication/user/verify-otp'; static const String getRegion = '$baseUrl/region'; static const String visitorPassword = '$baseUrl/visitor-password'; + static const String getDevices = '$baseUrl/visitor-password/devices'; static const String getUser = '$baseUrl/user/{userUuid}'; } diff --git a/lib/utils/constants/assets.dart b/lib/utils/constants/assets.dart index d53f82a7..2c2a6e43 100644 --- a/lib/utils/constants/assets.dart +++ b/lib/utils/constants/assets.dart @@ -24,4 +24,5 @@ class Assets { static const String integrationsIcon = "assets/images/Integrations_icon.svg"; static const String assetIcon = "assets/images/asset_icon.svg"; static const String calendarIcon = "assets/images/calendar_icon.svg"; + static const String deviceNoteIcon = "assets/images/device_note.svg"; } diff --git a/lib/utils/constants/const.dart b/lib/utils/constants/const.dart new file mode 100644 index 00000000..3d48dce6 --- /dev/null +++ b/lib/utils/constants/const.dart @@ -0,0 +1,44 @@ + + + +enum AccessType { + onlineOnetime, + onlineMultiple, + offlineOnetime, + offlineMultiple, +} + +extension AccessTypeExtension on AccessType { + String get value { + switch (this) { + case AccessType.onlineOnetime: + return "Online Password"; + case AccessType.onlineMultiple: + return "online Multiple Password"; + case AccessType.offlineOnetime: + return "Offline Onetime Password"; + case AccessType.offlineMultiple: + return "Offline Multiple Password"; + } + } + + static AccessType fromString(String value) { + switch (value) { + case "ONLINE_ONETIME": + return AccessType.onlineOnetime; + case "ONLINE_MULTIPLE": + return AccessType.onlineMultiple; + case "OFFLINE_ONETIME": + return AccessType.offlineOnetime; + case "OFFLINE_MULTIPLE": + return AccessType.offlineMultiple; + default: + throw ArgumentError("Invalid access type: $value"); + } + } +} + + + + + diff --git a/lib/utils/constants/string_const.dart b/lib/utils/constants/string_const.dart deleted file mode 100644 index 3f2ff2d6..00000000 --- a/lib/utils/constants/string_const.dart +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/lib/utils/style.dart b/lib/utils/style.dart index 8c174468..24747880 100644 --- a/lib/utils/style.dart +++ b/lib/utils/style.dart @@ -6,7 +6,7 @@ InputDecoration? textBoxDecoration({bool suffixIcon = false}) => InputDecoration suffixIcon:suffixIcon? const Icon(Icons.search):null, hintText: 'Search', filled: true, // Enable background filling - fillColor: Colors.grey.shade200, // Set the background color + fillColor: const Color(0xffF5F6F7), // Set the background color border: OutlineInputBorder( borderRadius: BorderRadius.circular(8), // Add border radius borderSide: BorderSide.none, // Remove the underline @@ -30,16 +30,17 @@ InputDecoration? textBoxDecoration({bool suffixIcon = false}) => InputDecoration ); - -Decoration containerDecoration = BoxDecoration( +BoxDecoration containerDecoration = BoxDecoration( boxShadow: [ BoxShadow( color: Colors.grey.withOpacity(0.5), spreadRadius: 5, blurRadius: 8, - offset: Offset(0, + offset: const Offset(0, 3), // changes position of shadow ), ], color: ColorsManager.boxColor, - borderRadius: BorderRadius.all(Radius.circular(10))); \ No newline at end of file + borderRadius: const BorderRadius.all(Radius.circular(10))); + + From 753aa29a8a2a2a65da1ba63aeb66f6917a9984f1 Mon Sep 17 00:00:00 2001 From: mohammad Date: Mon, 19 Aug 2024 09:49:58 +0300 Subject: [PATCH 17/32] create visitor password --- lib/pages/auth/bloc/auth_bloc.dart | 22 ++++++---- .../auth/view/forget_password_web_page.dart | 16 +++++++ lib/services/auth_api.dart | 43 +++++++++++++------ 3 files changed, 60 insertions(+), 21 deletions(-) diff --git a/lib/pages/auth/bloc/auth_bloc.dart b/lib/pages/auth/bloc/auth_bloc.dart index 0761ae19..a4b9ec68 100644 --- a/lib/pages/auth/bloc/auth_bloc.dart +++ b/lib/pages/auth/bloc/auth_bloc.dart @@ -73,7 +73,7 @@ class AuthBloc extends Bloc { ChangePasswordEvent event, Emitter emit) async { try { emit(LoadingForgetState()); - bool response = await AuthenticationAPI.verifyOtp( + var response = await AuthenticationAPI.verifyOtp( email: forgetEmailController.text, otpCode: forgetOtp.text); if (response == true) { await AuthenticationAPI.forgetPassword( @@ -81,8 +81,11 @@ class AuthBloc extends Bloc { email: forgetEmailController.text); _timer?.cancel(); emit(const TimerState(isButtonEnabled: true, remainingTime: 0)); + emit(SuccessForgetState()); + } else if (response == "You entered wrong otp") { + forgetValidate = response; // Set the validation message + emit(AuthInitialState()); } - emit(SuccessForgetState()); } catch (failure) { forgetValidate='Invalid Credentials!'; emit(AuthInitialState()); @@ -91,6 +94,13 @@ class AuthBloc extends Bloc { } } + String? validateCode(String? value) { + if (value == null || value.isEmpty) { + return 'Code is required'; + } + return null; + } + void _onUpdateTimer(UpdateTimerEvent event, Emitter emit) { emit(TimerState( isButtonEnabled: event.isButtonEnabled, @@ -196,6 +206,7 @@ class AuthBloc extends Bloc { validate=''; return null; } + String? loginValidateEmail(String? value) { if (!RegExp(r'^[^@]+@[^@]+\.[^@]+').hasMatch(value!)) { return ''; @@ -203,12 +214,7 @@ class AuthBloc extends Bloc { return null; } - String? validateCode(String? value) { - if (value == null || value.isEmpty) { - return 'Code is required'; - } - return null; - } + bool _validateInputs(Emitter emit) { emit(LoadingForgetState()); diff --git a/lib/pages/auth/view/forget_password_web_page.dart b/lib/pages/auth/view/forget_password_web_page.dart index 5b97684c..1b65423a 100644 --- a/lib/pages/auth/view/forget_password_web_page.dart +++ b/lib/pages/auth/view/forget_password_web_page.dart @@ -21,6 +21,11 @@ class ForgetPasswordWebPage extends StatelessWidget { child: BlocConsumer( listener: (context, state) { if (state is SuccessForgetState) { + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar( + content: Text('Change Password Successfully '), + ), + ); Navigator.of(context).pop(); } else if (state is FailureForgetState) { ScaffoldMessenger.of(context).showSnackBar( @@ -242,6 +247,17 @@ class ForgetPasswordWebPage extends StatelessWidget { const TextStyle(color: Colors.black), ), ), + if (forgetBloc.forgetValidate != '') // Check if there is a validation message + Padding( + padding: const EdgeInsets.only(top: 8.0), + child: Text( + forgetBloc.forgetValidate, + style: const TextStyle( + color: ColorsManager.red, + fontSize: 12, + ), + ), + ), ], ), const SizedBox(height: 20.0), diff --git a/lib/services/auth_api.dart b/lib/services/auth_api.dart index 0942b47b..55584055 100644 --- a/lib/services/auth_api.dart +++ b/lib/services/auth_api.dart @@ -79,20 +79,37 @@ class AuthenticationAPI { } } - static Future verifyOtp( + static Future verifyOtp( {required String email, required String otpCode}) async { - final response = await HTTPService().post( - path: ApiEndpoints.verifyOtp, - body: {"email": email, "type": "PASSWORD", "otpCode": otpCode}, - showServerMessage: true, - expectedResponseModel: (json) { - if (json['message'] == 'Otp Verified Successfully') { - return true; - } else { - return false; - } - }); - return response; + try{ + final response = await HTTPService().post( + path: ApiEndpoints.verifyOtp, + body: {"email": email, "type": "PASSWORD", "otpCode": otpCode}, + showServerMessage: true, + expectedResponseModel: (json) { + print('json=$json'); + + if (json['message'] == 'Otp Verified Successfully') { + return true; + } else { + return false; + } + }); + return response; + }on DioException catch (e){ + if (e.response != null) { + if (e.response!.statusCode == 400) { + // Handle 400 Bad Request + final errorData = e.response!.data; + String errorMessage = errorData['message']; + debugPrint('Unexpected Error: $errorMessage'); + return errorMessage; + + } + } else { + debugPrint('Error: ${e.message}'); + } + } } static Future> fetchRegion() async { From 21f036388a72e86ae81f9ab94ab48de9fa097dc6 Mon Sep 17 00:00:00 2001 From: mohammad Date: Mon, 19 Aug 2024 09:59:25 +0300 Subject: [PATCH 18/32] create visitor password --- lib/pages/auth/bloc/auth_bloc.dart | 5 ++--- lib/pages/auth/view/forget_password_web_page.dart | 3 ++- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/pages/auth/bloc/auth_bloc.dart b/lib/pages/auth/bloc/auth_bloc.dart index a4b9ec68..bfd42eaf 100644 --- a/lib/pages/auth/bloc/auth_bloc.dart +++ b/lib/pages/auth/bloc/auth_bloc.dart @@ -83,13 +83,12 @@ class AuthBloc extends Bloc { emit(const TimerState(isButtonEnabled: true, remainingTime: 0)); emit(SuccessForgetState()); } else if (response == "You entered wrong otp") { - forgetValidate = response; // Set the validation message + forgetValidate = 'Wrong one time password.'; emit(AuthInitialState()); } } catch (failure) { - forgetValidate='Invalid Credentials!'; + // forgetValidate='Invalid Credentials!'; emit(AuthInitialState()); - // emit(FailureForgetState(error: failure.toString())); } } diff --git a/lib/pages/auth/view/forget_password_web_page.dart b/lib/pages/auth/view/forget_password_web_page.dart index 1b65423a..a2a939e3 100644 --- a/lib/pages/auth/view/forget_password_web_page.dart +++ b/lib/pages/auth/view/forget_password_web_page.dart @@ -254,7 +254,8 @@ class ForgetPasswordWebPage extends StatelessWidget { forgetBloc.forgetValidate, style: const TextStyle( color: ColorsManager.red, - fontSize: 12, + fontSize: 10, + fontWeight: FontWeight.w700 ), ), ), From 5b9a6197e642853e5182590969061e6b02b7ce8a Mon Sep 17 00:00:00 2001 From: mohammad Date: Tue, 20 Aug 2024 09:10:59 +0300 Subject: [PATCH 19/32] visitor password api --- ios/Runner.xcodeproj/project.pbxproj | 112 +++++ .../contents.xcworkspacedata | 3 + lib/main.dart | 4 +- .../access_management/bloc/access_bloc.dart | 2 +- .../view/access_management.dart | 3 +- lib/pages/common/custom_table.dart | 167 +++++-- lib/pages/common/custom_web_textfield.dart | 19 +- .../bloc/visitor_password_bloc.dart | 176 +++++-- .../bloc/visitor_password_event.dart | 28 ++ .../bloc/visitor_password_state.dart | 6 + .../model/schedule_model.dart | 27 + .../view/add_device_dialog.dart | 69 +-- .../view/visitor_password_dialog.dart | 467 ++++++++++-------- lib/services/access_mang_api.dart | 75 ++- lib/utils/constants/api_const.dart | 6 + pubspec.lock | 8 + pubspec.yaml | 2 +- 17 files changed, 825 insertions(+), 349 deletions(-) create mode 100644 lib/pages/visitor_password/model/schedule_model.dart diff --git a/ios/Runner.xcodeproj/project.pbxproj b/ios/Runner.xcodeproj/project.pbxproj index 6094b4c6..dffe452f 100644 --- a/ios/Runner.xcodeproj/project.pbxproj +++ b/ios/Runner.xcodeproj/project.pbxproj @@ -14,6 +14,8 @@ 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; }; 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; }; 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; }; + E44A9405B1EB1B638DD05A58 /* Pods_RunnerTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 7ABF0EC746A2D686A0ED574F /* Pods_RunnerTests.framework */; }; + FF49F60EC38658783D8D66DA /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 2AFAE479A87ECDEBD5D6EB30 /* Pods_Runner.framework */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -42,12 +44,19 @@ /* Begin PBXFileReference section */ 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = ""; }; 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = ""; }; + 22428D486F110EE0B969469D /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = ""; }; + 253C5EA6840355311DB030EA /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = ""; }; + 2AFAE479A87ECDEBD5D6EB30 /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 2C0D722D2ED971BF672D18D5 /* Pods-RunnerTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.debug.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.debug.xcconfig"; sourceTree = ""; }; 331C807B294A618700263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = ""; }; 331C8081294A63A400263BE5 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; }; + 544621C7727C798253BAB2C8 /* Pods-RunnerTests.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.profile.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.profile.xcconfig"; sourceTree = ""; }; 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = ""; }; 74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; + 7ABF0EC746A2D686A0ED574F /* Pods_RunnerTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_RunnerTests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; }; + 877FDC97D8B87080E35B3EB7 /* Pods-RunnerTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.release.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.release.xcconfig"; sourceTree = ""; }; 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = ""; }; 9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = ""; }; 97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -55,19 +64,52 @@ 97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + D3AD250AADBF93406007C9EB /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ + 759A57780A409ED209817654 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + E44A9405B1EB1B638DD05A58 /* Pods_RunnerTests.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; 97C146EB1CF9000F007C117D /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + FF49F60EC38658783D8D66DA /* Pods_Runner.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ + 1454C118FFCECEEDF59152D2 /* Pods */ = { + isa = PBXGroup; + children = ( + 253C5EA6840355311DB030EA /* Pods-Runner.debug.xcconfig */, + 22428D486F110EE0B969469D /* Pods-Runner.release.xcconfig */, + D3AD250AADBF93406007C9EB /* Pods-Runner.profile.xcconfig */, + 2C0D722D2ED971BF672D18D5 /* Pods-RunnerTests.debug.xcconfig */, + 877FDC97D8B87080E35B3EB7 /* Pods-RunnerTests.release.xcconfig */, + 544621C7727C798253BAB2C8 /* Pods-RunnerTests.profile.xcconfig */, + ); + name = Pods; + path = Pods; + sourceTree = ""; + }; + 20A3C64D2B1CFED5A81C3251 /* Frameworks */ = { + isa = PBXGroup; + children = ( + 2AFAE479A87ECDEBD5D6EB30 /* Pods_Runner.framework */, + 7ABF0EC746A2D686A0ED574F /* Pods_RunnerTests.framework */, + ); + name = Frameworks; + sourceTree = ""; + }; 331C8082294A63A400263BE5 /* RunnerTests */ = { isa = PBXGroup; children = ( @@ -94,6 +136,8 @@ 97C146F01CF9000F007C117D /* Runner */, 97C146EF1CF9000F007C117D /* Products */, 331C8082294A63A400263BE5 /* RunnerTests */, + 1454C118FFCECEEDF59152D2 /* Pods */, + 20A3C64D2B1CFED5A81C3251 /* Frameworks */, ); sourceTree = ""; }; @@ -128,8 +172,10 @@ isa = PBXNativeTarget; buildConfigurationList = 331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */; buildPhases = ( + B9A66CAAF434B6A1BD8C4E09 /* [CP] Check Pods Manifest.lock */, 331C807D294A63A400263BE5 /* Sources */, 331C807F294A63A400263BE5 /* Resources */, + 759A57780A409ED209817654 /* Frameworks */, ); buildRules = ( ); @@ -145,12 +191,14 @@ isa = PBXNativeTarget; buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */; buildPhases = ( + C1C48B0232C0B26BFF405512 /* [CP] Check Pods Manifest.lock */, 9740EEB61CF901F6004384FC /* Run Script */, 97C146EA1CF9000F007C117D /* Sources */, 97C146EB1CF9000F007C117D /* Frameworks */, 97C146EC1CF9000F007C117D /* Resources */, 9705A1C41CF9048500538489 /* Embed Frameworks */, 3B06AD1E1E4923F5004D2608 /* Thin Binary */, + 33590C9CD073D3D5EBA02CDE /* [CP] Embed Pods Frameworks */, ); buildRules = ( ); @@ -222,6 +270,23 @@ /* End PBXResourcesBuildPhase section */ /* Begin PBXShellScriptBuildPhase section */ + 33590C9CD073D3D5EBA02CDE /* [CP] Embed Pods Frameworks */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist", + ); + name = "[CP] Embed Pods Frameworks"; + outputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n"; + showEnvVarsInLog = 0; + }; 3B06AD1E1E4923F5004D2608 /* Thin Binary */ = { isa = PBXShellScriptBuildPhase; alwaysOutOfDate = 1; @@ -253,6 +318,50 @@ shellPath = /bin/sh; shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build"; }; + B9A66CAAF434B6A1BD8C4E09 /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-RunnerTests-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; + C1C48B0232C0B26BFF405512 /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; /* End PBXShellScriptBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ @@ -378,6 +487,7 @@ }; 331C8088294A63A400263BE5 /* Debug */ = { isa = XCBuildConfiguration; + baseConfigurationReference = 2C0D722D2ED971BF672D18D5 /* Pods-RunnerTests.debug.xcconfig */; buildSettings = { BUNDLE_LOADER = "$(TEST_HOST)"; CODE_SIGN_STYLE = Automatic; @@ -395,6 +505,7 @@ }; 331C8089294A63A400263BE5 /* Release */ = { isa = XCBuildConfiguration; + baseConfigurationReference = 877FDC97D8B87080E35B3EB7 /* Pods-RunnerTests.release.xcconfig */; buildSettings = { BUNDLE_LOADER = "$(TEST_HOST)"; CODE_SIGN_STYLE = Automatic; @@ -410,6 +521,7 @@ }; 331C808A294A63A400263BE5 /* Profile */ = { isa = XCBuildConfiguration; + baseConfigurationReference = 544621C7727C798253BAB2C8 /* Pods-RunnerTests.profile.xcconfig */; buildSettings = { BUNDLE_LOADER = "$(TEST_HOST)"; CODE_SIGN_STYLE = Automatic; diff --git a/ios/Runner.xcworkspace/contents.xcworkspacedata b/ios/Runner.xcworkspace/contents.xcworkspacedata index 1d526a16..21a3cc14 100644 --- a/ios/Runner.xcworkspace/contents.xcworkspacedata +++ b/ios/Runner.xcworkspace/contents.xcworkspacedata @@ -4,4 +4,7 @@ + + diff --git a/lib/main.dart b/lib/main.dart index e57db364..ad7b1260 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -59,8 +59,8 @@ class MyApp extends StatelessWidget { colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple), // Set up color scheme useMaterial3: true, // Enable Material 3 ), - // home: AddDeviceDialog() - home:isLoggedIn == 'Success' ? const HomePage() : const LoginPage(), + home: VisitorPasswordDialog() + // home:isLoggedIn == 'Success' ? const HomePage() : const LoginPage(), )); } } diff --git a/lib/pages/access_management/bloc/access_bloc.dart b/lib/pages/access_management/bloc/access_bloc.dart index aef9508c..7c57bfa4 100644 --- a/lib/pages/access_management/bloc/access_bloc.dart +++ b/lib/pages/access_management/bloc/access_bloc.dart @@ -155,7 +155,7 @@ class AccessBloc extends Bloc { } return matchesCriteria; }).toList(); - print('Filtered data: $filteredData'); // Print to debug filtered data + print('Filtered data: $filteredData'); emit(TableLoaded(filteredData)); } catch (e) { print('Error occurred during filtering: $e'); diff --git a/lib/pages/access_management/view/access_management.dart b/lib/pages/access_management/view/access_management.dart index 265c96c0..2e543b09 100644 --- a/lib/pages/access_management/view/access_management.dart +++ b/lib/pages/access_management/view/access_management.dart @@ -235,8 +235,9 @@ class AccessManagementPage extends StatelessWidget { Expanded( child: state is TableLoaded ? DynamicTable( + withCheckBox: false, size: size, - cellDecoration: containerDecoration, + // cellDecoration: containerDecoration, headers: const [ 'Name', 'Access Type', diff --git a/lib/pages/common/custom_table.dart b/lib/pages/common/custom_table.dart index 25c821fb..ca1ff664 100644 --- a/lib/pages/common/custom_table.dart +++ b/lib/pages/common/custom_table.dart @@ -1,55 +1,107 @@ import 'package:flutter/material.dart'; import 'package:syncrow_web/utils/color_manager.dart'; -class DynamicTable extends StatelessWidget { +class DynamicTable extends StatefulWidget { final List headers; final List> data; final BoxDecoration? headerDecoration; final BoxDecoration? cellDecoration; final Size size; + final bool withCheckBox; + final void Function(bool?)? onChanged; + final void Function(bool?)? selectAll; + final void Function(int, bool?)? onRowCheckboxChanged; const DynamicTable({ Key? key, required this.headers, required this.data, required this.size, + required this.withCheckBox, this.headerDecoration, this.cellDecoration, + this.onChanged, + this.selectAll, + this.onRowCheckboxChanged, }) : super(key: key); + @override + _DynamicTableState createState() => _DynamicTableState(); +} + +class _DynamicTableState extends State { + late List _selected; + bool _selectAll = false; + + @override + void initState() { + super.initState(); + _selected = List.filled(widget.data.length, false); + } + + void _toggleSelectAll(bool? value) { + setState(() { + _selectAll = value ?? false; + _selected = List.filled(widget.data.length, _selectAll); + if (widget.selectAll != null) { + widget.selectAll!(_selectAll); + } + }); + } + + void _toggleRowSelection(int index, bool? value) { + setState(() { + _selected[index] = value ?? false; + _selectAll = _selected.every((element) => element == true); + if (widget.onRowCheckboxChanged != null) { + widget.onRowCheckboxChanged!(index, _selected[index]); + } + }); + } + @override Widget build(BuildContext context) { return Container( - - decoration: cellDecoration, + decoration: widget.cellDecoration, child: Padding( padding: const EdgeInsets.all(10.0), child: ListView( scrollDirection: Axis.horizontal, children: [ - Container( - width:size.width, + SizedBox( + width: widget.size.width, child: Column( children: [ Container( - decoration: headerDecoration ?? + decoration: widget.headerDecoration ?? BoxDecoration(color: Colors.grey[200]), child: Row( - children: headers.map((header) => - _buildTableHeaderCell(header)).toList(), + children: [ + if (widget.withCheckBox) + _buildSelectAllCheckbox(), + ...widget.headers + .map((header) => _buildTableHeaderCell(header)) + .toList(), + ], ), ), Expanded( child: Container( color: Colors.white, - child: ListView( + child: ListView.builder( shrinkWrap: true, - children: data.map((row) { + itemCount: widget.data.length, + itemBuilder: (context, index) { + final row = widget.data[index]; return Row( - children: row.map((cell) => - _buildTableCell(cell.toString())).toList(), + children: [ + if (widget.withCheckBox) + _buildRowCheckbox(index), + ...row.map((cell) => + _buildTableCell(cell.toString())).toList(), + ], ); - }).toList(), + }, ), ), ), @@ -61,43 +113,62 @@ class DynamicTable extends StatelessWidget { ), ); } -} -Widget _buildTableHeaderCell(String title) { - return Expanded( - child: Container( - decoration: const BoxDecoration( - border: Border.symmetric( - vertical: BorderSide(color: ColorsManager.boxDivider))), - alignment: Alignment.centerLeft, - child: Padding( - padding: const EdgeInsets.all(8.0), - child: Text(title, style: TextStyle(fontWeight: FontWeight.bold)), + + Widget _buildSelectAllCheckbox() { + return SizedBox( + width: 50, + child: Checkbox( + value: _selectAll, + onChanged: _toggleSelectAll, ), - ), - ); -} + ); + } -Widget _buildTableCell(String content) { - return Expanded( - child: Container( - height: 80, - padding: const EdgeInsets.all(20.0), - decoration: const BoxDecoration( - border: Border( - bottom: BorderSide( // <--- right side - color: ColorsManager.boxDivider, - width: 1.0, - ), - ) + Widget _buildRowCheckbox(int index) { + return SizedBox( + width: 50, + child: Checkbox( + value: _selected[index], + onChanged: (bool? value) { + _toggleRowSelection(index, value); + }, ), - alignment: Alignment.centerLeft, - child: Text( - content, - style: TextStyle(color: Colors.black, fontSize: 12), + ); + } + + Widget _buildTableHeaderCell(String title) { + return Expanded( + child: Container( + decoration: const BoxDecoration( + border: Border.symmetric( + vertical: BorderSide(color: ColorsManager.boxDivider))), + alignment: Alignment.centerLeft, + child: Padding( + padding: const EdgeInsets.all(8.0), + child: Text(title, style: const TextStyle(fontWeight: FontWeight.bold)), + ), ), - ), - ); + ); + } + + Widget _buildTableCell(String content) { + return Expanded( + child: Container( + height: 80, + padding: const EdgeInsets.all(20.0), + decoration: const BoxDecoration( + border: Border( + bottom: BorderSide( + color: ColorsManager.boxDivider, + width: 1.0, + ), + )), + alignment: Alignment.centerLeft, + child: Text( + content, + style: const TextStyle(color: Colors.black, fontSize: 12), + ), + ), + ); + } } - - - diff --git a/lib/pages/common/custom_web_textfield.dart b/lib/pages/common/custom_web_textfield.dart index 0662199b..0190ccfb 100644 --- a/lib/pages/common/custom_web_textfield.dart +++ b/lib/pages/common/custom_web_textfield.dart @@ -9,12 +9,14 @@ class CustomWebTextField extends StatelessWidget { required this.textFieldName, required this.controller, this.description, + this.validator, }); final bool isRequired; final String textFieldName; final String? description; final TextEditingController? controller; + final String? Function(String?)? validator; @override @@ -53,7 +55,6 @@ class CustomWebTextField extends StatelessWidget { ), const SizedBox(height: 7,), Container( - height: MediaQuery.of(context).size.height*0.05, decoration: containerDecoration.copyWith( color: const Color(0xFFF5F6F7), boxShadow: [ @@ -62,13 +63,17 @@ class CustomWebTextField extends StatelessWidget { spreadRadius:2, blurRadius: 3, offset: Offset(1, 1), // changes position of shadow - ), ] + ), + ] ), - child: TextFormField( - controller: controller, - style: const TextStyle(color: Colors.black), - decoration: textBoxDecoration()! - .copyWith(hintText: 'Please enter'), + child: Container( + child: TextFormField( + validator: validator, + controller: controller, + style: const TextStyle(color: Colors.black), + decoration: textBoxDecoration()! + .copyWith(hintText: 'Please enter'), + ), ), ), ], diff --git a/lib/pages/visitor_password/bloc/visitor_password_bloc.dart b/lib/pages/visitor_password/bloc/visitor_password_bloc.dart index c6ce5188..0b4b3388 100644 --- a/lib/pages/visitor_password/bloc/visitor_password_bloc.dart +++ b/lib/pages/visitor_password/bloc/visitor_password_bloc.dart @@ -1,14 +1,19 @@ +import 'dart:math'; + import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:intl/intl.dart'; import 'package:syncrow_web/pages/visitor_password/bloc/visitor_password_event.dart'; import 'package:syncrow_web/pages/visitor_password/bloc/visitor_password_state.dart'; import 'package:syncrow_web/pages/visitor_password/model/device_model.dart'; +import 'package:syncrow_web/pages/visitor_password/model/schedule_model.dart'; import 'package:syncrow_web/services/access_mang_api.dart'; import 'package:syncrow_web/utils/color_manager.dart'; import 'package:syncrow_web/utils/snack_bar.dart'; // Define the BLoC -class VisitorPasswordBloc extends Bloc { +class VisitorPasswordBloc + extends Bloc { VisitorPasswordBloc() : super(VisitorPasswordInitial()) { on(selectUsageFrequency); on(_onFetchDevice); @@ -17,25 +22,25 @@ class VisitorPasswordBloc extends Bloc(selectTimeVisitorPassword); on(toggleRepeat); on(toggleDaySelection); + on(selectDevice); + on(postOnlineOneTimePassword); + on(postOnlineMultipleTimePassword); } final TextEditingController userNameController = TextEditingController(); final TextEditingController emailController = TextEditingController(); - - final TextEditingController deviceNameController = TextEditingController(); final TextEditingController deviceIdController = TextEditingController(); final TextEditingController unitNameController = TextEditingController(); final TextEditingController virtualAddressController = TextEditingController(); - List data=[]; + List data = []; + List selectedDeviceIds = ['909e052c-6934-48f3-b22d-67ee04ad7265']; + final forgetFormKey = GlobalKey(); - - - - - String accessTypeSelected='Offline Password'; - String usageFrequencySelected='One-Time'; + String accessTypeSelected = 'Online Password'; + String usageFrequencySelected = 'One-Time'; + String passwordController = ''; bool repeat = false; @@ -45,18 +50,24 @@ class VisitorPasswordBloc extends Bloc emit) { - accessTypeSelected=event.type; - emit(PasswordTypeSelected(event.type)); + DateTime? repeatStartTime=DateTime.now(); + DateTime? repeatEndTime; + + selectAccessType( + SelectPasswordType event, Emitter emit) { + accessTypeSelected = event.type; + emit(PasswordTypeSelected(event.type)); } - selectUsageFrequency(SelectUsageFrequency event, Emitter emit) { - usageFrequencySelected=event.usageType; - emit(UsageFrequencySelected(event.usageType)); + selectUsageFrequency( + SelectUsageFrequency event, Emitter emit) { + usageFrequencySelected = event.usageType; + emit(UsageFrequencySelected(event.usageType)); } - - Future selectTimeVisitorPassword(SelectTimeVisitorPassword event, Emitter emit) async { + Future selectTimeVisitorPassword( + SelectTimeVisitorPassword event, + Emitter emit) async { final DateTime? picked = await showDatePicker( context: event.context, initialDate: DateTime.now(), @@ -67,7 +78,6 @@ class VisitorPasswordBloc extends Bloc expirationTimeTimeStamp!) { - CustomSnackBar.displaySnackBar('Effective Time cannot be later than Expiration Time.'); + if (expirationTimeTimeStamp != null && + selectedTimestamp > expirationTimeTimeStamp!) { + CustomSnackBar.displaySnackBar( + 'Effective Time cannot be later than Expiration Time.'); } else { - startTime = selectedDateTime.toString().split('.').first; // Remove seconds and milliseconds + startTime = selectedDateTime + .toString() + .split('.') + .first; // Remove seconds and milliseconds effectiveTimeTimeStamp = selectedTimestamp; } + emit(ChangeTimeState()); + } else { - if (effectiveTimeTimeStamp != null && selectedTimestamp < effectiveTimeTimeStamp!) { - CustomSnackBar.displaySnackBar('Expiration Time cannot be earlier than Effective Time.'); + if (effectiveTimeTimeStamp != null && + selectedTimestamp < effectiveTimeTimeStamp!) { + CustomSnackBar.displaySnackBar( + 'Expiration Time cannot be earlier than Effective Time.'); } else { - endTime = selectedDateTime.toString().split('.').first; // Remove seconds and milliseconds + endTime = selectedDateTime + .toString() + .split('.') + .first; // Remove seconds and milliseconds expirationTimeTimeStamp = selectedTimestamp; } + emit(VisitorPasswordInitial()); + } } } + // emit(AccessInitial()); // emit(TableLoaded(data)); } - - bool toggleRepeat(ToggleRepeatEvent event, Emitter emit) { + bool toggleRepeat( + ToggleRepeatEvent event, Emitter emit) { emit(LoadingInitialState()); repeat = !repeat; emit(IsRepeatState(repeat: repeat)); return repeat; } - - List> days = [ {"day": "Sun", "key": "Sun"}, {"day": "Mon", "key": "Mon"}, @@ -143,7 +167,10 @@ class VisitorPasswordBloc extends Bloc selectedDays = []; - Future toggleDaySelection(ToggleDaySelectionEvent event, Emitter emit,)async { + Future toggleDaySelection( + ToggleDaySelectionEvent event, + Emitter emit, + ) async { emit(LoadingInitialState()); if (selectedDays.contains(event.key)) { selectedDays.remove(event.key); @@ -152,17 +179,90 @@ class VisitorPasswordBloc extends Bloc _onFetchDevice( FetchDevice event, Emitter emit) async { try { emit(DeviceLoaded()); - data = await AccessMangApi().fetchDevices(); + data = await AccessMangApi().fetchDevices(); emit(TableLoaded(data)); } catch (e) { emit(FailedState(e.toString())); } } + Future postOnlineOneTimePassword( + OnlineOneTimePasswordEvent event, + Emitter emit) async { + try { + // emit(DeviceLoaded()); + await AccessMangApi().postOnlineOneTime( + email: event.email, + devicesUuid: selectedDeviceIds, + passwordName: event.passwordName); + // emit(TableLoaded(data)); + } catch (e) { + emit(FailedState(e.toString())); + } + } + Future postOnlineMultipleTimePassword( + OnlineMultipleTimePasswordEvent event, + Emitter emit) async { + try { + generate7DigitNumber(); + // emit(DeviceLoaded()); + await AccessMangApi().postOnlineMultipleTime( + scheduleList:[ + if (repeat) + Schedule( + effectiveTime: getTimeOnly(repeatStartTime), + invalidTime: getTimeOnly(repeatEndTime).toString(), + workingDay: selectedDays, + ), + ] , + password: passwordController, + invalidTime:event.invalidTime , + effectiveTime:event.effectiveTime , + email: event.email, + devicesUuid: selectedDeviceIds, + passwordName: event.passwordName + ); + // emit(TableLoaded(data)); + } catch (e) { + emit(FailedState(e.toString())); + } + } + + void selectDevice( + SelectDeviceEvent event, Emitter emit) { + if (selectedDeviceIds.contains(event.deviceId)) { + selectedDeviceIds.remove(event.deviceId); + } else { + selectedDeviceIds.add(event.deviceId); + } + print(selectedDeviceIds); + } + + String? validate(String? value) { + if (value == null || value.isEmpty) { + return 'Field is required'; + } + return null; + } + + + Future generate7DigitNumber() async { + emit(LoadingInitialState()); + passwordController=''; + Random random = Random(); + int min = 1000000; + int max = 9999999; + passwordController = (min + random.nextInt(max - min + 1)).toString(); + emit(GeneratePasswordState()); + return passwordController; + } + String getTimeOnly(DateTime? dateTime) { + if (dateTime == null) return ''; + return DateFormat('HH:mm').format(dateTime); + } } diff --git a/lib/pages/visitor_password/bloc/visitor_password_event.dart b/lib/pages/visitor_password/bloc/visitor_password_event.dart index 8195e155..40fe2542 100644 --- a/lib/pages/visitor_password/bloc/visitor_password_event.dart +++ b/lib/pages/visitor_password/bloc/visitor_password_event.dart @@ -47,4 +47,32 @@ class ToggleDaySelectionEvent extends VisitorPasswordEvent { class ToggleRepeatEvent extends VisitorPasswordEvent {} +class GeneratePasswordEvent extends VisitorPasswordEvent {} + class FetchDevice extends VisitorPasswordEvent {} + +class OnlineOneTimePasswordEvent extends VisitorPasswordEvent { + final String? email; + final String? passwordName; + + const OnlineOneTimePasswordEvent({this.email,this.passwordName}); + + @override + List get props => [email!,passwordName!,]; +} +class OnlineMultipleTimePasswordEvent extends VisitorPasswordEvent { + final String? email; + final String? passwordName; + final String? invalidTime; + final String? effectiveTime; + + const OnlineMultipleTimePasswordEvent({this.email,this.passwordName,this.invalidTime,this.effectiveTime}); + + @override + List get props => [email!,passwordName!,invalidTime!,effectiveTime!]; +} + +class SelectDeviceEvent extends VisitorPasswordEvent { + final String deviceId; + const SelectDeviceEvent(this.deviceId); +} \ No newline at end of file diff --git a/lib/pages/visitor_password/bloc/visitor_password_state.dart b/lib/pages/visitor_password/bloc/visitor_password_state.dart index 88b14ec9..9ec77f4a 100644 --- a/lib/pages/visitor_password/bloc/visitor_password_state.dart +++ b/lib/pages/visitor_password/bloc/visitor_password_state.dart @@ -44,6 +44,7 @@ class IsRepeatState extends VisitorPasswordState { class LoadingInitialState extends VisitorPasswordState {} class ChangeTimeState extends VisitorPasswordState {} class DeviceLoaded extends VisitorPasswordState {} +class GeneratePasswordState extends VisitorPasswordState {} class FailedState extends VisitorPasswordState { final String message; @@ -59,4 +60,9 @@ class TableLoaded extends VisitorPasswordState { @override List get props => [data]; +} + +class DeviceSelectionUpdated extends VisitorPasswordState { + final List selectedDeviceIds; + const DeviceSelectionUpdated(this.selectedDeviceIds); } \ No newline at end of file diff --git a/lib/pages/visitor_password/model/schedule_model.dart b/lib/pages/visitor_password/model/schedule_model.dart new file mode 100644 index 00000000..efeeff35 --- /dev/null +++ b/lib/pages/visitor_password/model/schedule_model.dart @@ -0,0 +1,27 @@ +class Schedule { + final String effectiveTime; + final String invalidTime; + final List workingDay; + + Schedule({ + required this.effectiveTime, + required this.invalidTime, + required this.workingDay, + }); + + factory Schedule.fromJson(Map json) { + return Schedule( + effectiveTime: json['effectiveTime'], + invalidTime: json['invalidTime'], + workingDay: List.from(json['workingDay']), + ); + } + + Map toJson() { + return { + 'effectiveTime': effectiveTime, + 'invalidTime': invalidTime, + 'workingDay': workingDay, + }; + } +} \ No newline at end of file diff --git a/lib/pages/visitor_password/view/add_device_dialog.dart b/lib/pages/visitor_password/view/add_device_dialog.dart index d987621e..1b237759 100644 --- a/lib/pages/visitor_password/view/add_device_dialog.dart +++ b/lib/pages/visitor_password/view/add_device_dialog.dart @@ -15,7 +15,6 @@ import '../../common/custom_table.dart'; class AddDeviceDialog extends StatelessWidget { const AddDeviceDialog({super.key}); - @override Widget build(BuildContext context) { Size size = MediaQuery.of(context).size; @@ -138,41 +137,42 @@ class AddDeviceDialog extends StatelessWidget { ), ), ), - ], ), ], ), - SizedBox(height: 20), - Container( - child: Expanded( - child: state is TableLoaded - ? Container( - decoration: containerDecoration, - child: DynamicTable( - size: size*0.5, - headers: ['Device Name', 'Device ID', 'Access Type', 'Unit Name', 'Status'], - data: state.data.map((item) { - return [ - item.name.toString(), - item.uuid.toString(), - item.productType.toString(), - '', - item.online.toString(), - // item.categoryName.toString(), - // accessBloc.timestampToDateTime(item.effectiveTime).toString(), - // accessBloc.timestampToDateTime(item.invalidTime).toString(), - // item.deviceUuid.toString(), - // item.passwordCreated != null ? accessBloc.timestampToDateTime(item.passwordCreated).toString() : 'no data', - // item.passwordStatus.toString(), - ]; - }).toList(), - ), - ) - // TableWidget(size: size, headers: ['Device Name', 'Device ID', 'Access Type', 'Unit Name', 'Status', 'Virtual Address',], data: [], bloc: bloc) - : const Center(child: CircularProgressIndicator())), - ) + const SizedBox(height: 20), + Expanded( + child: state is TableLoaded + ? Container( + decoration: containerDecoration, + child: DynamicTable( + selectAll: (p0) { + visitorBloc.selectedDeviceIds.clear(); + for (var item in state.data) { + visitorBloc.add(SelectDeviceEvent(item.uuid)); + } + }, + onRowCheckboxChanged: (index, isSelected) { + final deviceId = state.data[index].uuid; // Adjust as per your actual data structure + visitorBloc.add(SelectDeviceEvent(deviceId)); + }, + withCheckBox: true, + size: size*0.5, + headers: const [ 'Device Name', 'Device ID', 'Access Type', 'Unit Name', 'Status'], + data: state.data.map((item) { + return [ + item.name.toString(), + item.uuid.toString(), + item.productType.toString(), + '', + item.online.toString(), + ]; + }).toList(), + ), + ) + : const Center(child: CircularProgressIndicator())) ], ), ), @@ -195,9 +195,14 @@ class AddDeviceDialog extends StatelessWidget { ), ), Container( + decoration: containerDecoration, width: size.width * 0.2, - child: const DefaultButton( + child: DefaultButton( + onPressed: () { + Navigator.of(context).pop(); // Close the dialog + + }, borderRadius: 8, child: Text('Ok'), ), diff --git a/lib/pages/visitor_password/view/visitor_password_dialog.dart b/lib/pages/visitor_password/view/visitor_password_dialog.dart index 0693f724..b752c3a9 100644 --- a/lib/pages/visitor_password/view/visitor_password_dialog.dart +++ b/lib/pages/visitor_password/view/visitor_password_dialog.dart @@ -27,238 +27,246 @@ class VisitorPasswordDialog extends StatelessWidget { backgroundColor: Colors.white, title: const Text('Create visitor password'), content: SingleChildScrollView( - child: Padding( - padding: const EdgeInsets.all(10.0), - child: ListBody( - children: [ - Container( - child: Row( + child: Form( + key: visitorBloc.forgetFormKey, + child: Padding( + padding: const EdgeInsets.all(10.0), + child: ListBody( + children: [ + Container( + child: Row( + children: [ + Expanded( + flex: 2, + child: CustomWebTextField( + validator: visitorBloc.validate, + controller: visitorBloc.userNameController, + isRequired: true, + textFieldName: 'Name', + description: '', + ), + ), + const Spacer(), + Expanded( + flex: 2, + child: CustomWebTextField( + validator: visitorBloc.validate, + controller: visitorBloc.emailController, + isRequired: true, + textFieldName: 'Email Address', + description: 'The password will be sent to the visitor’s email address.', + ), + ), + const Spacer(), + ], + ), + ), + SizedBox(height: 20,), + Column( + crossAxisAlignment: CrossAxisAlignment.start, children: [ - Expanded( - flex: 2, - child: CustomWebTextField( - controller: visitorBloc.userNameController, - isRequired: true, - textFieldName: 'Name', - description: '', - ), + Row( + children: [ + Text( + '* ', + style: Theme.of(context) + .textTheme + .bodyMedium! + .copyWith(color: Colors.red), + ), + const Text('Access Type'), + ], ), - const Spacer(), - Expanded( - flex: 2, - child: CustomWebTextField( - controller: visitorBloc.emailController, - isRequired: true, - textFieldName: 'Email Address', - description: 'The password will be sent to the visitor’s email address.', - ), + Row( + children: [ + + SizedBox( + width: size.width * 0.15, + child: RadioListTile( + title: const Text('Online Password'), + value: 'Online Password', + groupValue: (state is PasswordTypeSelected) + ? state.selectedType + : visitorBloc.accessTypeSelected, + onChanged: (String? value) { + if (value != null) { + context.read().add(SelectPasswordType(value)); + } + }, + ), + ), + + SizedBox( + width: size.width * 0.15, + child: RadioListTile( + title: const Text('Offline Password'), + value: 'Offline Password', + groupValue: (state is PasswordTypeSelected) + ? state.selectedType + : visitorBloc.accessTypeSelected, + onChanged: (String? value) { + if (value != null) { + context.read().add(SelectPasswordType(value)); + } + }, + ), + ), + + SizedBox( + width: size.width * 0.15, + child: RadioListTile( + title: const Text('Dynamic Password'), + value: 'Dynamic Password', + groupValue: (state is PasswordTypeSelected) + ? state.selectedType + : visitorBloc.accessTypeSelected, + onChanged: (String? value) { + if (value != null) { + context.read().add(SelectPasswordType(value)); + } + }, + ), + ), + ], ), - const Spacer(), + const Text('Only currently online devices can be selected. It is recommended to use when the device network is stable, and the system randomly generates a digital password'), + SizedBox(height: 20,) ], ), - ), - SizedBox(height: 20,), - Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Row( - children: [ - Text( - '* ', - style: Theme.of(context) - .textTheme - .bodyMedium! - .copyWith(color: Colors.red), - ), - const Text('Access Type'), - ], - ), - Row( - children: [ - SizedBox( - width: size.width * 0.15, - child: RadioListTile( - title: const Text('Offline Password'), - value: 'Offline Password', - groupValue: (state is PasswordTypeSelected) - ? state.selectedType - : visitorBloc.accessTypeSelected, - onChanged: (String? value) { - if (value != null) { - context.read().add(SelectPasswordType(value)); - } - }, + visitorBloc.accessTypeSelected=='Dynamic Password' ? + SizedBox(): + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + Text( + '* ', + style: Theme.of(context) + .textTheme + .bodyMedium! + .copyWith(color: Colors.red), ), - ), - SizedBox( - width: size.width * 0.15, - child: RadioListTile( - title: const Text('Online Password'), - value: 'Online Password', - groupValue: (state is PasswordTypeSelected) - ? state.selectedType - : visitorBloc.accessTypeSelected, - onChanged: (String? value) { - if (value != null) { - context.read().add(SelectPasswordType(value)); - } - }, + const Text('Usage Frequency'), + ], + ), + Row( + children: [ + SizedBox( + width: 200, + child: RadioListTile( + title: const Text('One-Time'), + value: 'One-Time', + groupValue: (state is UsageFrequencySelected) + ? state.selectedFrequency + : visitorBloc.usageFrequencySelected, + onChanged: (String? value) { + if (value != null) { + context.read().add(SelectUsageFrequency(value)); + } + }, + ), ), - ), - SizedBox( - width: size.width * 0.15, - child: RadioListTile( - title: const Text('Dynamic Password'), - value: 'Dynamic Password', - groupValue: (state is PasswordTypeSelected) - ? state.selectedType - : visitorBloc.accessTypeSelected, - onChanged: (String? value) { - if (value != null) { - context.read().add(SelectPasswordType(value)); - } - }, + SizedBox( + width: 200, + child: RadioListTile( + title: const Text('Periodic'), + value: 'Periodic', + groupValue: (state is UsageFrequencySelected) + ? state.selectedFrequency + : visitorBloc.usageFrequencySelected, + onChanged: (String? value) { + if (value != null) { + context.read().add(SelectUsageFrequency(value)); + } + }, + ), ), - ), - ], - ), - const Text('Only currently online devices can be selected. It is recommended to use when the device network is stable, and the system randomly generates a digital password'), - SizedBox(height: 20,) - ], - ), - visitorBloc.accessTypeSelected=='Dynamic Password' ? - SizedBox(): - Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Row( - children: [ - Text( - '* ', - style: Theme.of(context) - .textTheme - .bodyMedium! - .copyWith(color: Colors.red), - ), - const Text('Usage Frequency'), - ], - ), - Row( - children: [ - SizedBox( - width: 200, - child: RadioListTile( - title: const Text('One-Time'), - value: 'One-Time', - groupValue: (state is UsageFrequencySelected) - ? state.selectedFrequency - : visitorBloc.usageFrequencySelected, - onChanged: (String? value) { - if (value != null) { - context.read().add(SelectUsageFrequency(value)); - } - }, - ), - ), - SizedBox( - width: 200, - child: RadioListTile( - title: const Text('Periodic'), - value: 'Periodic', - groupValue: (state is UsageFrequencySelected) - ? state.selectedFrequency - : visitorBloc.usageFrequencySelected, - onChanged: (String? value) { - if (value != null) { - context.read().add(SelectUsageFrequency(value)); - } - }, - ), - ), - ], - ), + ], + ), - Text('Within the validity period, each device can be unlocked only once.') - ], - ), + Text('Within the validity period, each device can be unlocked only once.') + ], + ), - const SizedBox(height: 20,), + const SizedBox(height: 20,), - visitorBloc.accessTypeSelected=='Dynamic Password' ? - SizedBox(): - DateTimeWebWidget( - isRequired: true, - title: 'Access Period', - size: size, - endTime: () { - visitorBloc.add(SelectTimeVisitorPassword(context: context, isStart: false)); - }, - startTime: () { - visitorBloc.add(SelectTimeVisitorPassword(context: context, isStart: true)); - }, - firstString: visitorBloc.startTime, - secondString: visitorBloc.endTime, - ), - const SizedBox(height: 20,), + visitorBloc.accessTypeSelected=='Dynamic Password' ? + SizedBox(): + DateTimeWebWidget( + isRequired: true, + title: 'Access Period', + size: size, + endTime: () { + visitorBloc.add(SelectTimeVisitorPassword(context: context, isStart: false)); + }, + startTime: () { + visitorBloc.add(SelectTimeVisitorPassword(context: context, isStart: true)); + }, + firstString: visitorBloc.startTime, + secondString: visitorBloc.endTime, + ), + const SizedBox(height: 20,), - Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Row( - children: [ - Text( - '* ', - style: Theme.of(context) - .textTheme - .bodyMedium! - .copyWith(color: Colors.red), - ), - const Text('Access Devices'), - ], - ), - const Text('Within the validity period, each device can be unlocked only once.'), - const SizedBox(height: 20,), - visitorBloc.usageFrequencySelected=='Periodic'&&visitorBloc.accessTypeSelected=='Offline Password'? - SizedBox( - width: 100, - child: ListTile( - contentPadding: EdgeInsets.zero, - leading: const Text('Repeat'), - trailing: Transform.scale( - scale: .8, - child: CupertinoSwitch( - value: visitorBloc.repeat, - onChanged: (value) { - visitorBloc.add(ToggleRepeatEvent()); - }, - applyTheme: true, + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + Text( + '* ', + style: Theme.of(context) + .textTheme + .bodyMedium! + .copyWith(color: Colors.red), + ), + const Text('Access Devices'), + ], + ), + const Text('Within the validity period, each device can be unlocked only once.'), + const SizedBox(height: 20,), + if(visitorBloc.usageFrequencySelected=='Periodic'&&visitorBloc.accessTypeSelected=='Online Password') + SizedBox( + width: 100, + child: ListTile( + contentPadding: EdgeInsets.zero, + leading: const Text('Repeat'), + trailing: Transform.scale( + scale: .8, + child: CupertinoSwitch( + value: visitorBloc.repeat, + onChanged: (value) { + visitorBloc.add(ToggleRepeatEvent()); + }, + applyTheme: true, + ), ), ), ), - ):const SizedBox(), - isRepeat ? const RepeatWidget() : const SizedBox(), - Container( - decoration: containerDecoration, - width: size.width * 0.1, - child: DefaultButton( - onPressed: () { - showDialog( - context: context, - barrierDismissible: false, - builder: (BuildContext context) { - return const AddDeviceDialog(); + isRepeat ? const RepeatWidget() : const SizedBox(), + Container( + decoration: containerDecoration, + width: size.width * 0.1, + child: DefaultButton( + onPressed: () { + showDialog( + context: context, + barrierDismissible: false, + builder: (BuildContext context) { + return const AddDeviceDialog(); - }, - ); - }, - borderRadius: 8, - child: Text('+ Add Device'), + }, + ); + }, + borderRadius: 8, + child: Text('+ Add Device'), + ), ), - ), - ], - ), + ], + ), - ], + ], + ), ), ), ), @@ -282,7 +290,36 @@ class VisitorPasswordDialog extends StatelessWidget { Container( decoration: containerDecoration, width: size.width * 0.2, - child: const DefaultButton( + child: DefaultButton( + onPressed: () { + if(visitorBloc.forgetFormKey.currentState!.validate()){ + if(visitorBloc.usageFrequencySelected=='One-Time'&&visitorBloc.accessTypeSelected=='Online Password'){ + visitorBloc.add(OnlineOneTimePasswordEvent( + passwordName:visitorBloc.userNameController.text , + email: visitorBloc.emailController.text + ) + ); + }else if(visitorBloc.usageFrequencySelected=='Periodic'&&visitorBloc.accessTypeSelected=='Online Password') { + visitorBloc.add(OnlineMultipleTimePasswordEvent( + passwordName:visitorBloc.userNameController.text , + email: visitorBloc.emailController.text, + effectiveTime:visitorBloc.effectiveTimeTimeStamp.toString() , + invalidTime:visitorBloc.expirationTimeTimeStamp.toString() + ) + ); + } + else if(visitorBloc.usageFrequencySelected=='Periodic'&&visitorBloc.accessTypeSelected=='Online Password') { + visitorBloc.add(OnlineMultipleTimePasswordEvent( + passwordName:visitorBloc.userNameController.text , + email: visitorBloc.emailController.text, + effectiveTime:visitorBloc.effectiveTimeTimeStamp.toString() , + invalidTime:visitorBloc.expirationTimeTimeStamp.toString() + ) + ); + } + } + + }, borderRadius: 8, child: Text('Ok'), ), diff --git a/lib/services/access_mang_api.dart b/lib/services/access_mang_api.dart index de14f4ea..f755209d 100644 --- a/lib/services/access_mang_api.dart +++ b/lib/services/access_mang_api.dart @@ -1,8 +1,7 @@ import 'dart:convert'; - -import 'package:dio/dio.dart'; import 'package:flutter/cupertino.dart'; import 'package:syncrow_web/pages/access_management/model/password_model.dart'; +import 'package:syncrow_web/pages/visitor_password/model/schedule_model.dart'; import 'package:syncrow_web/services/api/http_service.dart'; import 'package:syncrow_web/utils/constants/api_const.dart'; @@ -10,8 +9,6 @@ import '../pages/visitor_password/model/device_model.dart'; class AccessMangApi{ - - Future> fetchVisitorPassword() async { try { final response = await HTTPService().get( @@ -54,6 +51,76 @@ class AccessMangApi{ } } + Future postOnlineOneTime({String? email,String? passwordName,List? devicesUuid}) async { + try { + + print('postOfflineOneTime List: ${ + { + "email": email, + "passwordName": passwordName, + "devicesUuid": devicesUuid + } + }'); + + final response = await HTTPService().post( + path: ApiEndpoints.sendOfflineOneTime, + body: jsonEncode({ + "email": email, + "passwordName": passwordName, + "devicesUuid": devicesUuid + }), + showServerMessage: true, + expectedResponseModel: (json) { + List jsonData = json; + print('postOfflineOneTime List: $json'); + }, + ); + return response; + } catch (e) { + debugPrint('Error fetching $e'); + return []; + } + } + + Future postOnlineMultipleTime({ + String? effectiveTime, + String? invalidTime, + String? email, + String? password, + String? passwordName, + List? scheduleList, + List? devicesUuid}) async { + try { + Map body = { + "email": email, + "devicesUuid": devicesUuid, + "passwordName": passwordName, + "password": password, + "effectiveTime": effectiveTime, + "invalidTime": invalidTime, + }; + print('createPassword =${scheduleList![0].workingDay}'); + if (scheduleList != null) { + body["scheduleList"] = scheduleList.map((schedule) => schedule.toJson()).toList(); + } + print('createPassword =$body'); + + final response = await HTTPService().post( + path: ApiEndpoints.sendOfflineMultipleTime, + body: jsonEncode(body), + showServerMessage: true, + expectedResponseModel: (json) { + List jsonData = json; + print('postOfflineOneTime List: $json'); + }, + ); + return response; + } catch (e) { + debugPrint('Error fetching $e'); + return []; + } + } + diff --git a/lib/utils/constants/api_const.dart b/lib/utils/constants/api_const.dart index b9ee2f5e..b2db133a 100644 --- a/lib/utils/constants/api_const.dart +++ b/lib/utils/constants/api_const.dart @@ -11,5 +11,11 @@ abstract class ApiEndpoints { static const String getRegion = '$baseUrl/region'; static const String visitorPassword = '$baseUrl/visitor-password'; static const String getDevices = '$baseUrl/visitor-password/devices'; + + + static const String sendOfflineOneTime = '$baseUrl/visitor-password/temporary-password/online/one-time'; + static const String sendOfflineMultipleTime = '$baseUrl/visitor-password/temporary-password/online/multiple-time'; + + static const String getUser = '$baseUrl/user/{userUuid}'; } diff --git a/pubspec.lock b/pubspec.lock index d22f2d41..b8984d5a 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -240,6 +240,14 @@ packages: url: "https://pub.dev" source: hosted version: "4.0.2" + intl: + dependency: "direct main" + description: + name: intl + sha256: d6f56758b7d3014a48af9701c085700aac781a92a87a62b1333b46d8879661cf + url: "https://pub.dev" + source: hosted + version: "0.19.0" js: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index c1fd3f66..faaa7ddb 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -44,7 +44,7 @@ dependencies: flutter_secure_storage: ^9.2.2 shared_preferences: ^2.3.0 data_table_2: ^2.5.15 - + intl: ^0.19.0 dev_dependencies: flutter_test: sdk: flutter From 0cf5053f8b735777b89f1d5ab99b9f12a4ceb783 Mon Sep 17 00:00:00 2001 From: mohammad Date: Tue, 20 Aug 2024 10:44:15 +0300 Subject: [PATCH 20/32] visitor password api --- lib/main.dart | 4 +- .../view/access_management.dart | 2 +- .../bloc/visitor_password_bloc.dart | 62 ++++++++++++++-- .../bloc/visitor_password_event.dart | 23 +++++- .../view/add_device_dialog.dart | 4 +- .../view/visitor_password_dialog.dart | 11 ++- lib/services/access_mang_api.dart | 73 ++++++++++++++++++- lib/utils/constants/api_const.dart | 8 +- 8 files changed, 170 insertions(+), 17 deletions(-) diff --git a/lib/main.dart b/lib/main.dart index ad7b1260..776847e7 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -59,8 +59,8 @@ class MyApp extends StatelessWidget { colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple), // Set up color scheme useMaterial3: true, // Enable Material 3 ), - home: VisitorPasswordDialog() - // home:isLoggedIn == 'Success' ? const HomePage() : const LoginPage(), + // home: VisitorPasswordDialog() + home:isLoggedIn == 'Success' ? const HomePage() : const LoginPage(), )); } } diff --git a/lib/pages/access_management/view/access_management.dart b/lib/pages/access_management/view/access_management.dart index 2e543b09..b7a678f2 100644 --- a/lib/pages/access_management/view/access_management.dart +++ b/lib/pages/access_management/view/access_management.dart @@ -234,7 +234,7 @@ class AccessManagementPage extends StatelessWidget { ), Expanded( child: state is TableLoaded - ? DynamicTable( + ? DynamicTable( withCheckBox: false, size: size, // cellDecoration: containerDecoration, diff --git a/lib/pages/visitor_password/bloc/visitor_password_bloc.dart b/lib/pages/visitor_password/bloc/visitor_password_bloc.dart index 0b4b3388..c84d6498 100644 --- a/lib/pages/visitor_password/bloc/visitor_password_bloc.dart +++ b/lib/pages/visitor_password/bloc/visitor_password_bloc.dart @@ -10,6 +10,7 @@ import 'package:syncrow_web/pages/visitor_password/model/schedule_model.dart'; import 'package:syncrow_web/services/access_mang_api.dart'; import 'package:syncrow_web/utils/color_manager.dart'; import 'package:syncrow_web/utils/snack_bar.dart'; + List selectedDevices = []; // Define the BLoC class VisitorPasswordBloc @@ -17,14 +18,18 @@ class VisitorPasswordBloc VisitorPasswordBloc() : super(VisitorPasswordInitial()) { on(selectUsageFrequency); on(_onFetchDevice); - on(selectAccessType); on(selectTimeVisitorPassword); on(toggleRepeat); on(toggleDaySelection); on(selectDevice); + on(postOnlineOneTimePassword); on(postOnlineMultipleTimePassword); + + on(postOfflineMultipleTimePassword); + on(postOfflineOneTimePassword); + } final TextEditingController userNameController = TextEditingController(); final TextEditingController emailController = TextEditingController(); @@ -35,7 +40,10 @@ class VisitorPasswordBloc final TextEditingController virtualAddressController = TextEditingController(); List data = []; - List selectedDeviceIds = ['909e052c-6934-48f3-b22d-67ee04ad7265']; + List selectedDeviceIds = []; + + + final forgetFormKey = GlobalKey(); String accessTypeSelected = 'Online Password'; @@ -191,20 +199,24 @@ class VisitorPasswordBloc } } + //online password + Future postOnlineOneTimePassword( OnlineOneTimePasswordEvent event, Emitter emit) async { try { + print('selectedDevices$selectedDevices'); // emit(DeviceLoaded()); await AccessMangApi().postOnlineOneTime( email: event.email, - devicesUuid: selectedDeviceIds, + devicesUuid: selectedDevices, passwordName: event.passwordName); // emit(TableLoaded(data)); } catch (e) { emit(FailedState(e.toString())); } } + Future postOnlineMultipleTimePassword( OnlineMultipleTimePasswordEvent event, Emitter emit) async { @@ -224,7 +236,7 @@ class VisitorPasswordBloc invalidTime:event.invalidTime , effectiveTime:event.effectiveTime , email: event.email, - devicesUuid: selectedDeviceIds, + devicesUuid: selectedDevices, passwordName: event.passwordName ); // emit(TableLoaded(data)); @@ -233,6 +245,45 @@ class VisitorPasswordBloc } } + + //offline password + Future postOfflineOneTimePassword( + OfflineOneTimePasswordEvent event, + Emitter emit) async { + try { + generate7DigitNumber(); + // emit(DeviceLoaded()); + await AccessMangApi().postOffLineOneTime( + email: event.email, + devicesUuid: selectedDevices, + passwordName: event.passwordName + ); + // emit(TableLoaded(data)); + } catch (e) { + emit(FailedState(e.toString())); + } + } + + Future postOfflineMultipleTimePassword( + OfflineMultipleTimePasswordEvent event, + Emitter emit) async { + try { + generate7DigitNumber(); + // emit(DeviceLoaded()); + await AccessMangApi().postOffLineMultipleTime( + email: event.email, + devicesUuid: selectedDevices, + passwordName: event.passwordName, + invalidTime:event.invalidTime , + effectiveTime:event.effectiveTime + ); + // emit(TableLoaded(data)); + } catch (e) { + emit(FailedState(e.toString())); + } + } + + void selectDevice( SelectDeviceEvent event, Emitter emit) { if (selectedDeviceIds.contains(event.deviceId)) { @@ -240,7 +291,8 @@ class VisitorPasswordBloc } else { selectedDeviceIds.add(event.deviceId); } - print(selectedDeviceIds); + selectedDevices=selectedDeviceIds; + print(selectedDevices); } String? validate(String? value) { diff --git a/lib/pages/visitor_password/bloc/visitor_password_event.dart b/lib/pages/visitor_password/bloc/visitor_password_event.dart index 40fe2542..da57735a 100644 --- a/lib/pages/visitor_password/bloc/visitor_password_event.dart +++ b/lib/pages/visitor_password/bloc/visitor_password_event.dart @@ -51,6 +51,7 @@ class GeneratePasswordEvent extends VisitorPasswordEvent {} class FetchDevice extends VisitorPasswordEvent {} +//online password class OnlineOneTimePasswordEvent extends VisitorPasswordEvent { final String? email; final String? passwordName; @@ -65,13 +66,33 @@ class OnlineMultipleTimePasswordEvent extends VisitorPasswordEvent { final String? passwordName; final String? invalidTime; final String? effectiveTime; - const OnlineMultipleTimePasswordEvent({this.email,this.passwordName,this.invalidTime,this.effectiveTime}); + @override + List get props => [email!,passwordName!,invalidTime!,effectiveTime!]; +} + +//offline password +class OfflineOneTimePasswordEvent extends VisitorPasswordEvent { + final String? email; + final String? passwordName; + const OfflineOneTimePasswordEvent({this.email,this.passwordName}); + @override + List get props => [email!,passwordName!,]; +} + +class OfflineMultipleTimePasswordEvent extends VisitorPasswordEvent { + final String? email; + final String? passwordName; + final String? invalidTime; + final String? effectiveTime; + + const OfflineMultipleTimePasswordEvent({this.email,this.passwordName,this.invalidTime,this.effectiveTime}); @override List get props => [email!,passwordName!,invalidTime!,effectiveTime!]; } + class SelectDeviceEvent extends VisitorPasswordEvent { final String deviceId; const SelectDeviceEvent(this.deviceId); diff --git a/lib/pages/visitor_password/view/add_device_dialog.dart b/lib/pages/visitor_password/view/add_device_dialog.dart index 1b237759..e71cfb0b 100644 --- a/lib/pages/visitor_password/view/add_device_dialog.dart +++ b/lib/pages/visitor_password/view/add_device_dialog.dart @@ -2,6 +2,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_svg/flutter_svg.dart'; +import 'package:syncrow_web/pages/common/custom_table.dart'; import 'package:syncrow_web/pages/common/custom_web_textfield.dart'; import 'package:syncrow_web/pages/common/default_button.dart'; import 'package:syncrow_web/pages/visitor_password/bloc/visitor_password_bloc.dart'; @@ -10,7 +11,6 @@ import 'package:syncrow_web/pages/visitor_password/bloc/visitor_password_state.d import 'package:syncrow_web/utils/color_manager.dart'; import 'package:syncrow_web/utils/constants/assets.dart'; import 'package:syncrow_web/utils/style.dart'; -import '../../common/custom_table.dart'; class AddDeviceDialog extends StatelessWidget { @@ -155,7 +155,7 @@ class AddDeviceDialog extends StatelessWidget { } }, onRowCheckboxChanged: (index, isSelected) { - final deviceId = state.data[index].uuid; // Adjust as per your actual data structure + final deviceId = state.data[index].uuid; visitorBloc.add(SelectDeviceEvent(deviceId)); }, withCheckBox: true, diff --git a/lib/pages/visitor_password/view/visitor_password_dialog.dart b/lib/pages/visitor_password/view/visitor_password_dialog.dart index b752c3a9..4e94116c 100644 --- a/lib/pages/visitor_password/view/visitor_password_dialog.dart +++ b/lib/pages/visitor_password/view/visitor_password_dialog.dart @@ -308,8 +308,15 @@ class VisitorPasswordDialog extends StatelessWidget { ) ); } - else if(visitorBloc.usageFrequencySelected=='Periodic'&&visitorBloc.accessTypeSelected=='Online Password') { - visitorBloc.add(OnlineMultipleTimePasswordEvent( + else if(visitorBloc.usageFrequencySelected=='One-Time'&&visitorBloc.accessTypeSelected=='Offline Password') { + visitorBloc.add(OfflineOneTimePasswordEvent( + passwordName:visitorBloc.userNameController.text , + email: visitorBloc.emailController.text, + ) + ); + } + else if(visitorBloc.usageFrequencySelected=='Periodic'&&visitorBloc.accessTypeSelected=='Offline Password') { + visitorBloc.add(OfflineMultipleTimePasswordEvent( passwordName:visitorBloc.userNameController.text , email: visitorBloc.emailController.text, effectiveTime:visitorBloc.effectiveTimeTimeStamp.toString() , diff --git a/lib/services/access_mang_api.dart b/lib/services/access_mang_api.dart index f755209d..38730553 100644 --- a/lib/services/access_mang_api.dart +++ b/lib/services/access_mang_api.dart @@ -63,7 +63,7 @@ class AccessMangApi{ }'); final response = await HTTPService().post( - path: ApiEndpoints.sendOfflineOneTime, + path: ApiEndpoints.sendOnlineOneTime, body: jsonEncode({ "email": email, "passwordName": passwordName, @@ -106,7 +106,7 @@ class AccessMangApi{ print('createPassword =$body'); final response = await HTTPService().post( - path: ApiEndpoints.sendOfflineMultipleTime, + path: ApiEndpoints.sendOnlineMultipleTime, body: jsonEncode(body), showServerMessage: true, expectedResponseModel: (json) { @@ -121,7 +121,76 @@ class AccessMangApi{ } } +// OffLine One Time Password + Future postOffLineOneTime({String? email,String? passwordName,List? devicesUuid}) async { + try { + print('postOfflineOneTime List: ${ + { + "email": email, + "passwordName": passwordName, + "devicesUuid": devicesUuid + } + }'); + final response = await HTTPService().post( + path: ApiEndpoints.sendOffLineOneTime, + body: jsonEncode({ + "email": email, + "passwordName": passwordName, + "devicesUuid": devicesUuid + }), + showServerMessage: true, + expectedResponseModel: (json) { + List jsonData = json; + print('postOfflineOneTime List: $json'); + }, + ); + return response; + } catch (e) { + debugPrint('Error fetching $e'); + return []; + } + } + + Future postOffLineMultipleTime({ + String? email, + String? passwordName, + String? effectiveTime, + String? invalidTime, + List? devicesUuid + + }) async { + try { + + print('postOfflineOneTime List: ${ + { + "email": email, + "passwordName": passwordName, + "devicesUuid": devicesUuid + } + }'); + + final response = await HTTPService().post( + path: ApiEndpoints.sendOffLineOneTime, + body: jsonEncode({ + "email": email, + "devicesUuid":devicesUuid, + "passwordName": passwordName, + "effectiveTime": effectiveTime, + "invalidTime": invalidTime + }), + showServerMessage: true, + expectedResponseModel: (json) { + List jsonData = json; + print('postOfflineOneTime List: $json'); + }, + ); + return response; + } catch (e) { + debugPrint('Error fetching $e'); + return []; + } + } } \ No newline at end of file diff --git a/lib/utils/constants/api_const.dart b/lib/utils/constants/api_const.dart index b2db133a..f9581cb6 100644 --- a/lib/utils/constants/api_const.dart +++ b/lib/utils/constants/api_const.dart @@ -13,8 +13,12 @@ abstract class ApiEndpoints { static const String getDevices = '$baseUrl/visitor-password/devices'; - static const String sendOfflineOneTime = '$baseUrl/visitor-password/temporary-password/online/one-time'; - static const String sendOfflineMultipleTime = '$baseUrl/visitor-password/temporary-password/online/multiple-time'; + static const String sendOnlineOneTime = '$baseUrl/visitor-password/temporary-password/online/one-time'; + static const String sendOnlineMultipleTime = '$baseUrl/visitor-password/temporary-password/online/multiple-time'; + +//offline Password + static const String sendOffLineOneTime = '$baseUrl/visitor-password/temporary-password/offline/one-time'; + static const String sendOffLineMultipleTime = '$baseUrl/visitor-password/temporary-password/offline/multiple-time'; static const String getUser = '$baseUrl/user/{userUuid}'; From 1204563c553cefbd18f55a133a83ee00a3c6e55b Mon Sep 17 00:00:00 2001 From: mohammad Date: Tue, 20 Aug 2024 16:36:05 +0300 Subject: [PATCH 21/32] add device filter and select time repeat widget --- lib/main.dart | 2 +- .../view/access_management.dart | 2 +- lib/pages/common/custom_table.dart | 2 +- lib/pages/common/custom_web_textfield.dart | 5 +- lib/pages/common/info_dialog.dart | 74 ++++++++++ .../bloc/visitor_password_bloc.dart | 95 +++++++++---- .../bloc/visitor_password_event.dart | 23 ++- .../bloc/visitor_password_state.dart | 10 +- .../visitor_password/model/device_model.dart | 6 +- .../view/add_device_dialog.dart | 37 ++--- .../visitor_password/view/repeat_widget.dart | 46 +++--- .../view/visitor_password_dialog.dart | 133 ++++++++++++------ lib/utils/constants/const.dart | 35 +++++ 13 files changed, 346 insertions(+), 124 deletions(-) create mode 100644 lib/pages/common/info_dialog.dart diff --git a/lib/main.dart b/lib/main.dart index 776847e7..dcabeaa2 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -59,7 +59,7 @@ class MyApp extends StatelessWidget { colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple), // Set up color scheme useMaterial3: true, // Enable Material 3 ), - // home: VisitorPasswordDialog() + // home: AddDeviceDialog() home:isLoggedIn == 'Success' ? const HomePage() : const LoginPage(), )); } diff --git a/lib/pages/access_management/view/access_management.dart b/lib/pages/access_management/view/access_management.dart index b7a678f2..55570324 100644 --- a/lib/pages/access_management/view/access_management.dart +++ b/lib/pages/access_management/view/access_management.dart @@ -237,7 +237,7 @@ class AccessManagementPage extends StatelessWidget { ? DynamicTable( withCheckBox: false, size: size, - // cellDecoration: containerDecoration, + cellDecoration: containerDecoration, headers: const [ 'Name', 'Access Type', diff --git a/lib/pages/common/custom_table.dart b/lib/pages/common/custom_table.dart index ca1ff664..2b312152 100644 --- a/lib/pages/common/custom_table.dart +++ b/lib/pages/common/custom_table.dart @@ -155,7 +155,7 @@ class _DynamicTableState extends State { return Expanded( child: Container( height: 80, - padding: const EdgeInsets.all(20.0), + padding: const EdgeInsets.all(15.0), decoration: const BoxDecoration( border: Border( bottom: BorderSide( diff --git a/lib/pages/common/custom_web_textfield.dart b/lib/pages/common/custom_web_textfield.dart index 0190ccfb..4ec0aa81 100644 --- a/lib/pages/common/custom_web_textfield.dart +++ b/lib/pages/common/custom_web_textfield.dart @@ -72,7 +72,10 @@ class CustomWebTextField extends StatelessWidget { controller: controller, style: const TextStyle(color: Colors.black), decoration: textBoxDecoration()! - .copyWith(hintText: 'Please enter'), + .copyWith( + errorStyle: const TextStyle(height: 0), // Hide the error text space + + hintText: 'Please enter'), ), ), ), diff --git a/lib/pages/common/info_dialog.dart b/lib/pages/common/info_dialog.dart new file mode 100644 index 00000000..4465ced6 --- /dev/null +++ b/lib/pages/common/info_dialog.dart @@ -0,0 +1,74 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_svg/svg.dart'; +import 'package:syncrow_web/utils/color_manager.dart'; +import 'package:syncrow_web/utils/constants/assets.dart'; + +class InfoDialog extends StatelessWidget { + final String title; + final String content; + final Size? size; + final List? actions; + + InfoDialog({ + required this.title, + required this.content, + this.actions, + this.size, + }); + + @override + Widget build(BuildContext context) { + return AlertDialog( + alignment: Alignment.center, + content: SizedBox( + height: size!.height * 0.25, + child: Column( + children: [ + Column( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + SizedBox( + child: SvgPicture.asset( + Assets.deviceNoteIcon, + height: 35, + width: 35, + ), + ), + + Text( + title, + style: Theme.of(context).textTheme.headlineLarge!.copyWith( + fontSize: 30, + fontWeight: FontWeight.w400, + color: Colors.black), + ), + ], + ), + const SizedBox( + width: 15, + ), + Text( + content, + textAlign: TextAlign.center, + style: Theme.of(context).textTheme.bodyMedium!.copyWith( + color: ColorsManager.grayColor, + fontWeight: FontWeight.w400, + fontSize: 18), + ), + ], + ), + ), + actionsAlignment: MainAxisAlignment.center, + actions: actions ?? + [ + TextButton( + onPressed: () { + Navigator.of(context).pop(); + }, + child: Text('OK'), + ), + ], + ); + } +} diff --git a/lib/pages/visitor_password/bloc/visitor_password_bloc.dart b/lib/pages/visitor_password/bloc/visitor_password_bloc.dart index c84d6498..d9f49e56 100644 --- a/lib/pages/visitor_password/bloc/visitor_password_bloc.dart +++ b/lib/pages/visitor_password/bloc/visitor_password_bloc.dart @@ -23,6 +23,7 @@ class VisitorPasswordBloc on(toggleRepeat); on(toggleDaySelection); on(selectDevice); + on(_onUpdateFilteredDevices); on(postOnlineOneTimePassword); on(postOnlineMultipleTimePassword); @@ -39,6 +40,8 @@ class VisitorPasswordBloc final TextEditingController unitNameController = TextEditingController(); final TextEditingController virtualAddressController = TextEditingController(); + + List data = []; List selectedDeviceIds = []; @@ -55,11 +58,18 @@ class VisitorPasswordBloc int? effectiveTimeTimeStamp; int? expirationTimeTimeStamp; + int? repeatEffectiveTimeTimeStamp; + int? repeatExpirationTimeTimeStamp; + String startTime = 'Start Time'; String endTime = 'End Time'; - DateTime? repeatStartTime=DateTime.now(); - DateTime? repeatEndTime; + + String repeatStartTime = 'Start Time'; + String repeatEndTime = 'End Time'; + + // DateTime? repeatStartTime=DateTime.now(); + // DateTime? repeatEndTime; selectAccessType( SelectPasswordType event, Emitter emit) { @@ -80,7 +90,7 @@ class VisitorPasswordBloc context: event.context, initialDate: DateTime.now(), firstDate: DateTime(2015, 8), - lastDate: DateTime(2101), + lastDate: DateTime(3101), ); if (picked != null) { final TimeOfDay? timePicked = await showTimePicker( @@ -120,33 +130,34 @@ class VisitorPasswordBloc ).millisecondsSinceEpoch ~/ 1000; // Divide by 1000 to remove milliseconds if (event.isStart) { - if (expirationTimeTimeStamp != null && - selectedTimestamp > expirationTimeTimeStamp!) { - CustomSnackBar.displaySnackBar( - 'Effective Time cannot be later than Expiration Time.'); + if (expirationTimeTimeStamp != null && selectedTimestamp >expirationTimeTimeStamp!) { + CustomSnackBar.displaySnackBar('Effective Time cannot be later than Expiration Time.'); } else { - startTime = selectedDateTime - .toString() - .split('.') - .first; // Remove seconds and milliseconds + if(event.isRepeat==true) + {repeatStartTime = selectedDateTime.toString().split('.').first;} + + else // Remove seconds and milliseconds + {startTime = selectedDateTime.toString().split('.').first;} effectiveTimeTimeStamp = selectedTimestamp; + emit(ChangeTimeState()); } emit(ChangeTimeState()); - } else { if (effectiveTimeTimeStamp != null && selectedTimestamp < effectiveTimeTimeStamp!) { - CustomSnackBar.displaySnackBar( - 'Expiration Time cannot be earlier than Effective Time.'); + CustomSnackBar.displaySnackBar('Expiration Time cannot be earlier than Effective Time.'); } else { - endTime = selectedDateTime - .toString() - .split('.') - .first; // Remove seconds and milliseconds + if(event.isRepeat==true) + {repeatEndTime = selectedDateTime.toString().split('.').first;} + else + {endTime = selectedDateTime.toString().split('.').first;} expirationTimeTimeStamp = selectedTimestamp; - } - emit(VisitorPasswordInitial()); + emit(ChangeTimeState()); + } + emit(ChangeTimeState()); + + emit(VisitorPasswordInitial()); } } } @@ -225,12 +236,12 @@ class VisitorPasswordBloc // emit(DeviceLoaded()); await AccessMangApi().postOnlineMultipleTime( scheduleList:[ - if (repeat) - Schedule( - effectiveTime: getTimeOnly(repeatStartTime), - invalidTime: getTimeOnly(repeatEndTime).toString(), - workingDay: selectedDays, - ), + // if (repeat) + // Schedule( + // effectiveTime: getTimeOnly(repeatStartTime), + // invalidTime: getTimeOnly(repeatEndTime).toString(), + // workingDay: selectedDays, + // ), ] , password: passwordController, invalidTime:event.invalidTime , @@ -297,7 +308,7 @@ class VisitorPasswordBloc String? validate(String? value) { if (value == null || value.isEmpty) { - return 'Field is required'; + return ''; } return null; } @@ -317,4 +328,34 @@ class VisitorPasswordBloc if (dateTime == null) return ''; return DateFormat('HH:mm').format(dateTime); } + + + void filterDevices() { + final deviceName = deviceNameController.text.toLowerCase(); + final deviceId = deviceIdController.text.toLowerCase(); + final unitName = unitNameController.text.toLowerCase(); + + final filteredData = data.where((device) { + final matchesDeviceName = device.name.toLowerCase().contains(deviceName); + final matchesDeviceId = device.uuid.toLowerCase().contains(deviceId); + // final matchesUnitName = device.unitName.toLowerCase().contains(unitName); // Assuming unitName is a property of the device + + return matchesDeviceName && matchesDeviceId ; + }).toList(); + // emit(TableLoaded(filteredData)); + + add(UpdateFilteredDevicesEvent(filteredData)); + } + @override + Stream mapEventToState(VisitorPasswordEvent event) async* { + if (event is FetchDevice) { + // Fetching logic... + } else if (event is UpdateFilteredDevicesEvent) { + yield TableLoaded(event.filteredData); + } + } + + void _onUpdateFilteredDevices(UpdateFilteredDevicesEvent event, Emitter emit) { + emit(TableLoaded(event.filteredData)); + } } diff --git a/lib/pages/visitor_password/bloc/visitor_password_event.dart b/lib/pages/visitor_password/bloc/visitor_password_event.dart index da57735a..9990ad9a 100644 --- a/lib/pages/visitor_password/bloc/visitor_password_event.dart +++ b/lib/pages/visitor_password/bloc/visitor_password_event.dart @@ -1,5 +1,6 @@ import 'package:equatable/equatable.dart'; import 'package:flutter/cupertino.dart'; +import 'package:syncrow_web/pages/visitor_password/model/device_model.dart'; abstract class VisitorPasswordEvent extends Equatable { const VisitorPasswordEvent(); @@ -28,11 +29,12 @@ class SelectUsageFrequency extends VisitorPasswordEvent { class SelectTimeVisitorPassword extends VisitorPasswordEvent { final BuildContext context; final bool isStart; + final bool isRepeat; - const SelectTimeVisitorPassword({ required this.context,required this.isStart}); + const SelectTimeVisitorPassword({ required this.context,required this.isStart,required this.isRepeat}); @override - List get props => [context,isStart]; + List get props => [context,isStart,isRepeat]; } @@ -96,4 +98,21 @@ class OfflineMultipleTimePasswordEvent extends VisitorPasswordEvent { class SelectDeviceEvent extends VisitorPasswordEvent { final String deviceId; const SelectDeviceEvent(this.deviceId); +} + +class FilterDataEvent extends VisitorPasswordEvent { + final String? passwordName; + final int? startTime; + final int? endTime; + + const FilterDataEvent({ + this.passwordName, + this.startTime, + this.endTime, + }); +} +class UpdateFilteredDevicesEvent extends VisitorPasswordEvent { + final List filteredData; + + UpdateFilteredDevicesEvent(this.filteredData); } \ No newline at end of file diff --git a/lib/pages/visitor_password/bloc/visitor_password_state.dart b/lib/pages/visitor_password/bloc/visitor_password_state.dart index 9ec77f4a..374433af 100644 --- a/lib/pages/visitor_password/bloc/visitor_password_state.dart +++ b/lib/pages/visitor_password/bloc/visitor_password_state.dart @@ -16,9 +16,7 @@ class VisitorPasswordInitial extends VisitorPasswordState {} class PasswordTypeSelected extends VisitorPasswordState { final String selectedType; - const PasswordTypeSelected(this.selectedType); - @override List get props => [selectedType]; } @@ -45,19 +43,17 @@ class LoadingInitialState extends VisitorPasswordState {} class ChangeTimeState extends VisitorPasswordState {} class DeviceLoaded extends VisitorPasswordState {} class GeneratePasswordState extends VisitorPasswordState {} + class FailedState extends VisitorPasswordState { final String message; - - FailedState(this.message); - + const FailedState(this.message); @override List get props => [message]; } + class TableLoaded extends VisitorPasswordState { final List data; - const TableLoaded(this.data); - @override List get props => [data]; } diff --git a/lib/pages/visitor_password/model/device_model.dart b/lib/pages/visitor_password/model/device_model.dart index 0b37d70d..2c3ce8d9 100644 --- a/lib/pages/visitor_password/model/device_model.dart +++ b/lib/pages/visitor_password/model/device_model.dart @@ -1,5 +1,7 @@ +import 'package:syncrow_web/utils/constants/const.dart'; + class DeviceModel { dynamic productUuid; dynamic productType; @@ -15,7 +17,7 @@ class DeviceModel { dynamic lon; dynamic model; dynamic name; - dynamic online; + DeviseStatus online; dynamic ownerId; dynamic sub; dynamic timeZone; @@ -62,7 +64,7 @@ class DeviceModel { lon: json['lon'] , model: json['model'] , name: json['name'], - online: json['online'], + online: OnlineTypeExtension.fromString(json['online']), ownerId: json['ownerId'] , sub: json['sub'], timeZone: json['timeZone'], diff --git a/lib/pages/visitor_password/view/add_device_dialog.dart b/lib/pages/visitor_password/view/add_device_dialog.dart index e71cfb0b..d50ec164 100644 --- a/lib/pages/visitor_password/view/add_device_dialog.dart +++ b/lib/pages/visitor_password/view/add_device_dialog.dart @@ -1,4 +1,3 @@ - import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_svg/flutter_svg.dart'; @@ -10,6 +9,7 @@ import 'package:syncrow_web/pages/visitor_password/bloc/visitor_password_event.d import 'package:syncrow_web/pages/visitor_password/bloc/visitor_password_state.dart'; import 'package:syncrow_web/utils/color_manager.dart'; import 'package:syncrow_web/utils/constants/assets.dart'; +import 'package:syncrow_web/utils/constants/const.dart'; import 'package:syncrow_web/utils/style.dart'; @@ -34,13 +34,13 @@ class AddDeviceDialog extends StatelessWidget { child: Column( children: [ Container( - width: size.width, - padding: EdgeInsets.all(15), - decoration:containerDecoration.copyWith( - color: ColorsManager.worningColor, - border: Border.all(color: Color(0xffFFD22F)), - boxShadow: [] - ), + width: size.width, + padding: EdgeInsets.all(15), + decoration:containerDecoration.copyWith( + color: ColorsManager.worningColor, + border: Border.all(color: Color(0xffFFD22F)), + boxShadow: [] + ), child: Row( children: [ SizedBox( @@ -73,7 +73,7 @@ class AddDeviceDialog extends StatelessWidget { Expanded( flex: 2, child: CustomWebTextField( - controller: visitorBloc.deviceNameController, + controller: visitorBloc.deviceIdController, isRequired: true, textFieldName: 'Device ID', description: '', @@ -103,7 +103,7 @@ class AddDeviceDialog extends StatelessWidget { child: Center( child: DefaultButton( onPressed: () { - // Your search function here + visitorBloc.filterDevices(); // Call filter function }, borderRadius: 9, child: const Text('Search'), @@ -133,6 +133,12 @@ class AddDeviceDialog extends StatelessWidget { 'Reset', style: Theme.of(context).textTheme.bodySmall!.copyWith(color: Colors.black), ), + onPressed: () { + visitorBloc.deviceNameController.clear(); + visitorBloc.deviceIdController.clear(); + visitorBloc.unitNameController.clear(); + visitorBloc.add(FetchDevice()); // Reset to original list + }, ), ), ), @@ -145,7 +151,7 @@ class AddDeviceDialog extends StatelessWidget { const SizedBox(height: 20), Expanded( child: state is TableLoaded - ? Container( + ? Container( decoration: containerDecoration, child: DynamicTable( selectAll: (p0) { @@ -160,14 +166,14 @@ class AddDeviceDialog extends StatelessWidget { }, withCheckBox: true, size: size*0.5, - headers: const [ 'Device Name', 'Device ID', 'Access Type', 'Unit Name', 'Status'], + headers: const [ 'Device Name', 'Device ID', 'Access Type', 'Unit Name', 'Status'], data: state.data.map((item) { return [ item.name.toString(), item.uuid.toString(), item.productType.toString(), '', - item.online.toString(), + item.online.value.toString(), ]; }).toList(), ), @@ -195,13 +201,11 @@ class AddDeviceDialog extends StatelessWidget { ), ), Container( - decoration: containerDecoration, width: size.width * 0.2, child: DefaultButton( onPressed: () { Navigator.of(context).pop(); // Close the dialog - }, borderRadius: 8, child: Text('Ok'), @@ -211,5 +215,6 @@ class AddDeviceDialog extends StatelessWidget { ); }, ), - ); } + ); + } } diff --git a/lib/pages/visitor_password/view/repeat_widget.dart b/lib/pages/visitor_password/view/repeat_widget.dart index 24199b65..ecbd2b89 100644 --- a/lib/pages/visitor_password/view/repeat_widget.dart +++ b/lib/pages/visitor_password/view/repeat_widget.dart @@ -20,29 +20,6 @@ class RepeatWidget extends StatelessWidget { final smartDoorBloc = BlocProvider.of(context); return Column( children: [ - Padding( - padding: const EdgeInsets.all(8.0), - child: DateTimeWebWidget( - isRequired: true, - title: 'Access Period', - size: size, - endTime: () { - smartDoorBloc.add(SelectTimeVisitorPassword(context: context, isStart: false)); - }, - startTime: () { - smartDoorBloc.add(SelectTimeVisitorPassword(context: context, isStart: true)); - }, - firstString: smartDoorBloc.startTime, - secondString: smartDoorBloc.endTime, - ), - ), - const Divider( - color: ColorsManager.graysColor, - ), - const Divider( - color: ColorsManager.graysColor, - ), - const SizedBox(height: 20), Container( width: size.width * 0.8, height: size.height * 0.06, // Adjust height as needed @@ -72,6 +49,29 @@ class RepeatWidget extends StatelessWidget { }).toList(), ), ), + Padding( + padding: const EdgeInsets.all(8.0), + child: DateTimeWebWidget( + isRequired: false, + title: '', + size: size, + endTime: () { + smartDoorBloc.add(SelectTimeVisitorPassword( + isRepeat: true, + context: context, isStart: false + )); + }, + startTime: () { + smartDoorBloc.add(SelectTimeVisitorPassword( + isRepeat: true, + context: context, isStart: true + )); + }, + firstString: smartDoorBloc.repeatStartTime.toString(), + secondString: smartDoorBloc.repeatEndTime.toString(), + ), + ), + const SizedBox(height: 20), ], diff --git a/lib/pages/visitor_password/view/visitor_password_dialog.dart b/lib/pages/visitor_password/view/visitor_password_dialog.dart index 4e94116c..eadee6dc 100644 --- a/lib/pages/visitor_password/view/visitor_password_dialog.dart +++ b/lib/pages/visitor_password/view/visitor_password_dialog.dart @@ -4,6 +4,7 @@ import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:syncrow_web/pages/common/custom_web_textfield.dart'; import 'package:syncrow_web/pages/common/date_time_widget.dart'; import 'package:syncrow_web/pages/common/default_button.dart'; +import 'package:syncrow_web/pages/common/info_dialog.dart'; import 'package:syncrow_web/pages/visitor_password/bloc/visitor_password_bloc.dart'; import 'package:syncrow_web/pages/visitor_password/bloc/visitor_password_event.dart'; import 'package:syncrow_web/pages/visitor_password/bloc/visitor_password_state.dart'; @@ -79,7 +80,6 @@ class VisitorPasswordDialog extends StatelessWidget { ), Row( children: [ - SizedBox( width: size.width * 0.15, child: RadioListTile( @@ -90,6 +90,7 @@ class VisitorPasswordDialog extends StatelessWidget { : visitorBloc.accessTypeSelected, onChanged: (String? value) { if (value != null) { + print(value); context.read().add(SelectPasswordType(value)); } }, @@ -106,6 +107,8 @@ class VisitorPasswordDialog extends StatelessWidget { : visitorBloc.accessTypeSelected, onChanged: (String? value) { if (value != null) { + print(value); + context.read().add(SelectPasswordType(value)); } }, @@ -123,6 +126,7 @@ class VisitorPasswordDialog extends StatelessWidget { onChanged: (String? value) { if (value != null) { context.read().add(SelectPasswordType(value)); + visitorBloc.usageFrequencySelected=''; } }, ), @@ -130,7 +134,7 @@ class VisitorPasswordDialog extends StatelessWidget { ], ), const Text('Only currently online devices can be selected. It is recommended to use when the device network is stable, and the system randomly generates a digital password'), - SizedBox(height: 20,) + const SizedBox(height: 20,) ], ), visitorBloc.accessTypeSelected=='Dynamic Password' ? @@ -162,6 +166,8 @@ class VisitorPasswordDialog extends StatelessWidget { : visitorBloc.usageFrequencySelected, onChanged: (String? value) { if (value != null) { + print(value); + context.read().add(SelectUsageFrequency(value)); } }, @@ -185,27 +191,28 @@ class VisitorPasswordDialog extends StatelessWidget { ], ), - Text('Within the validity period, each device can be unlocked only once.') + const Text('Within the validity period, each device can be unlocked only once.') ], ), - const SizedBox(height: 20,), - - visitorBloc.accessTypeSelected=='Dynamic Password' ? - SizedBox(): + if((visitorBloc.usageFrequencySelected!='One-Time'||visitorBloc.accessTypeSelected!='Offline Password')&&(visitorBloc.usageFrequencySelected!='')) DateTimeWebWidget( + isRequired: true, title: 'Access Period', size: size, endTime: () { - visitorBloc.add(SelectTimeVisitorPassword(context: context, isStart: false)); + visitorBloc.add(SelectTimeVisitorPassword( + context: context, isStart: false,isRepeat:false)); }, startTime: () { - visitorBloc.add(SelectTimeVisitorPassword(context: context, isStart: true)); + visitorBloc.add(SelectTimeVisitorPassword( + context: context, isStart: true,isRepeat:false)); }, firstString: visitorBloc.startTime, secondString: visitorBloc.endTime, ), + const SizedBox(height: 20,), Column( @@ -243,7 +250,8 @@ class VisitorPasswordDialog extends StatelessWidget { ), ), ), - isRepeat ? const RepeatWidget() : const SizedBox(), + if(visitorBloc.usageFrequencySelected=='Periodic'&&visitorBloc.accessTypeSelected=='Online Password') + isRepeat ? const RepeatWidget() : const SizedBox(), Container( decoration: containerDecoration, width: size.width * 0.1, @@ -254,7 +262,6 @@ class VisitorPasswordDialog extends StatelessWidget { barrierDismissible: false, builder: (BuildContext context) { return const AddDeviceDialog(); - }, ); }, @@ -293,39 +300,79 @@ class VisitorPasswordDialog extends StatelessWidget { child: DefaultButton( onPressed: () { if(visitorBloc.forgetFormKey.currentState!.validate()){ - if(visitorBloc.usageFrequencySelected=='One-Time'&&visitorBloc.accessTypeSelected=='Online Password'){ - visitorBloc.add(OnlineOneTimePasswordEvent( - passwordName:visitorBloc.userNameController.text , - email: visitorBloc.emailController.text - ) - ); - }else if(visitorBloc.usageFrequencySelected=='Periodic'&&visitorBloc.accessTypeSelected=='Online Password') { - visitorBloc.add(OnlineMultipleTimePasswordEvent( - passwordName:visitorBloc.userNameController.text , - email: visitorBloc.emailController.text, - effectiveTime:visitorBloc.effectiveTimeTimeStamp.toString() , - invalidTime:visitorBloc.expirationTimeTimeStamp.toString() - ) - ); - } - else if(visitorBloc.usageFrequencySelected=='One-Time'&&visitorBloc.accessTypeSelected=='Offline Password') { - visitorBloc.add(OfflineOneTimePasswordEvent( - passwordName:visitorBloc.userNameController.text , - email: visitorBloc.emailController.text, - ) - ); - } - else if(visitorBloc.usageFrequencySelected=='Periodic'&&visitorBloc.accessTypeSelected=='Offline Password') { - visitorBloc.add(OfflineMultipleTimePasswordEvent( - passwordName:visitorBloc.userNameController.text , - email: visitorBloc.emailController.text, - effectiveTime:visitorBloc.effectiveTimeTimeStamp.toString() , - invalidTime:visitorBloc.expirationTimeTimeStamp.toString() - ) - ); - } - } + showDialog( + context: context, + barrierDismissible: false, + builder: (BuildContext context) { + return InfoDialog( + size: size, + title: 'Set Password', + content: 'This action will update all of the selected\n door locks passwords in the property.\n\nAre you sure you want to continue?', + actions: [ + Container( + decoration: containerDecoration, + width: size.width * 0.1, + child: DefaultButton( + borderRadius: 8, + onPressed: () { + Navigator.of(context).pop(); // Close the dialog + }, + backgroundColor: Colors.white, + child: Text( + 'Cancel', + style: Theme.of(context).textTheme.bodyMedium!, + ), + ), + ), + Container( + decoration: containerDecoration, + width: size.width * 0.1, + child: DefaultButton( + borderRadius: 8, + onPressed: () { + if(visitorBloc.usageFrequencySelected=='One-Time'&&visitorBloc.accessTypeSelected=='Online Password'){ + visitorBloc.add(OnlineOneTimePasswordEvent( + passwordName:visitorBloc.userNameController.text , + email: visitorBloc.emailController.text + ) + ); + } + else if(visitorBloc.usageFrequencySelected=='Periodic'&&visitorBloc.accessTypeSelected=='Online Password') { + visitorBloc.add(OnlineMultipleTimePasswordEvent( + passwordName:visitorBloc.userNameController.text , + email: visitorBloc.emailController.text, + effectiveTime:visitorBloc.effectiveTimeTimeStamp.toString() , + invalidTime:visitorBloc.expirationTimeTimeStamp.toString() + ) + ); + } + else if(visitorBloc.usageFrequencySelected=='One-Time'&&visitorBloc.accessTypeSelected=='Offline Password') { + visitorBloc.add(OfflineOneTimePasswordEvent( + passwordName:visitorBloc.userNameController.text , + email: visitorBloc.emailController.text, + ) + ); + } + else if(visitorBloc.usageFrequencySelected=='Periodic'&&visitorBloc.accessTypeSelected=='Offline Password') { + visitorBloc.add(OfflineMultipleTimePasswordEvent( + passwordName:visitorBloc.userNameController.text , + email: visitorBloc.emailController.text, + effectiveTime:visitorBloc.effectiveTimeTimeStamp.toString() , + invalidTime:visitorBloc.expirationTimeTimeStamp.toString() + ) + ); + } + }, + child: const Text( + 'Ok', + ), + ), + ), + ],); + }, + ); + } }, borderRadius: 8, child: Text('Ok'), diff --git a/lib/utils/constants/const.dart b/lib/utils/constants/const.dart index 3d48dce6..7afa399d 100644 --- a/lib/utils/constants/const.dart +++ b/lib/utils/constants/const.dart @@ -42,3 +42,38 @@ extension AccessTypeExtension on AccessType { + + + +enum DeviseStatus { + online, + offline, +} + +extension OnlineTypeExtension on DeviseStatus { + String get value { + switch (this) { + case DeviseStatus.online: + return "Online"; + case DeviseStatus.offline: + return "Offline"; + + } + } + + static DeviseStatus fromString(bool value) { + switch (value) { + case false: + return DeviseStatus.offline; + case true: + return DeviseStatus.online; + default: + throw ArgumentError("Invalid access type: $value"); + } + } +} + + + + + From bb959bcc61f0686bc779f3ba7af3cb59a1c72da1 Mon Sep 17 00:00:00 2001 From: mohammad Date: Tue, 20 Aug 2024 16:40:51 +0300 Subject: [PATCH 22/32] add device filter and select time repeat widget --- lib/pages/common/custom_table.dart | 2 - .../view/add_device_dialog.dart | 52 +++++++++---------- 2 files changed, 25 insertions(+), 29 deletions(-) diff --git a/lib/pages/common/custom_table.dart b/lib/pages/common/custom_table.dart index 2b312152..c2a83273 100644 --- a/lib/pages/common/custom_table.dart +++ b/lib/pages/common/custom_table.dart @@ -8,7 +8,6 @@ class DynamicTable extends StatefulWidget { final BoxDecoration? cellDecoration; final Size size; final bool withCheckBox; - final void Function(bool?)? onChanged; final void Function(bool?)? selectAll; final void Function(int, bool?)? onRowCheckboxChanged; @@ -20,7 +19,6 @@ class DynamicTable extends StatefulWidget { required this.withCheckBox, this.headerDecoration, this.cellDecoration, - this.onChanged, this.selectAll, this.onRowCheckboxChanged, }) : super(key: key); diff --git a/lib/pages/visitor_password/view/add_device_dialog.dart b/lib/pages/visitor_password/view/add_device_dialog.dart index d50ec164..58e49182 100644 --- a/lib/pages/visitor_password/view/add_device_dialog.dart +++ b/lib/pages/visitor_password/view/add_device_dialog.dart @@ -151,33 +151,31 @@ class AddDeviceDialog extends StatelessWidget { const SizedBox(height: 20), Expanded( child: state is TableLoaded - ? Container( - decoration: containerDecoration, - child: DynamicTable( - selectAll: (p0) { - visitorBloc.selectedDeviceIds.clear(); - for (var item in state.data) { - visitorBloc.add(SelectDeviceEvent(item.uuid)); - } - }, - onRowCheckboxChanged: (index, isSelected) { - final deviceId = state.data[index].uuid; - visitorBloc.add(SelectDeviceEvent(deviceId)); - }, - withCheckBox: true, - size: size*0.5, - headers: const [ 'Device Name', 'Device ID', 'Access Type', 'Unit Name', 'Status'], - data: state.data.map((item) { - return [ - item.name.toString(), - item.uuid.toString(), - item.productType.toString(), - '', - item.online.value.toString(), - ]; - }).toList(), - ), - ) + ? DynamicTable( + cellDecoration: containerDecoration, + selectAll: (p0) { + visitorBloc.selectedDeviceIds.clear(); + for (var item in state.data) { + visitorBloc.add(SelectDeviceEvent(item.uuid)); + } + }, + onRowCheckboxChanged: (index, isSelected) { + final deviceId = state.data[index].uuid; + visitorBloc.add(SelectDeviceEvent(deviceId)); + }, + withCheckBox: true, + size: size*0.5, + headers: const [ 'Device Name', 'Device ID', 'Access Type', 'Unit Name', 'Status'], + data: state.data.map((item) { + return [ + item.name.toString(), + item.uuid.toString(), + item.productType.toString(), + '', + item.online.value.toString(), + ]; + }).toList(), + ) : const Center(child: CircularProgressIndicator())) ], ), From 50ef283b520b1f48391f112d7ac1e8a849eb965b Mon Sep 17 00:00:00 2001 From: mohammad Date: Wed, 21 Aug 2024 09:22:31 +0300 Subject: [PATCH 23/32] password expired. --- .../access_management/bloc/access_bloc.dart | 2 +- .../view/access_management.dart | 2 +- lib/pages/auth/bloc/auth_bloc.dart | 5 ++++- .../view/add_device_dialog.dart | 6 +++++- .../visitor_password/view/repeat_widget.dart | 18 +++++++++--------- .../view/visitor_password_dialog.dart | 6 +++++- lib/utils/color_manager.dart | 2 +- 7 files changed, 26 insertions(+), 15 deletions(-) diff --git a/lib/pages/access_management/bloc/access_bloc.dart b/lib/pages/access_management/bloc/access_bloc.dart index 7c57bfa4..f586c56f 100644 --- a/lib/pages/access_management/bloc/access_bloc.dart +++ b/lib/pages/access_management/bloc/access_bloc.dart @@ -133,7 +133,7 @@ class AccessBloc extends Bloc { // Check if the password name should be used for filtering if (event.passwordName != null && event.passwordName!.isNotEmpty) { final bool matchesName = item.passwodName != null && - item.passwodName.contains(event.passwordName!); + item.passwodName.contains(event.passwordName); if (!matchesName) { matchesCriteria = false; } diff --git a/lib/pages/access_management/view/access_management.dart b/lib/pages/access_management/view/access_management.dart index 55570324..e2f79429 100644 --- a/lib/pages/access_management/view/access_management.dart +++ b/lib/pages/access_management/view/access_management.dart @@ -151,7 +151,7 @@ class AccessManagementPage extends StatelessWidget { child: DefaultButton( onPressed: () { accessBloc.add(FilterDataEvent( - passwordName: accessBloc.passwordName.text, + passwordName: accessBloc.passwordName.text.toLowerCase(), startTime: accessBloc.effectiveTimeTimeStamp, endTime: accessBloc.expirationTimeTimeStamp )); diff --git a/lib/pages/auth/bloc/auth_bloc.dart b/lib/pages/auth/bloc/auth_bloc.dart index bfd42eaf..2cc1a033 100644 --- a/lib/pages/auth/bloc/auth_bloc.dart +++ b/lib/pages/auth/bloc/auth_bloc.dart @@ -85,6 +85,9 @@ class AuthBloc extends Bloc { } else if (response == "You entered wrong otp") { forgetValidate = 'Wrong one time password.'; emit(AuthInitialState()); + }else if (response == "OTP expired") { + forgetValidate = 'One time password has been expired.'; + emit(AuthInitialState()); } } catch (failure) { // forgetValidate='Invalid Credentials!'; @@ -92,7 +95,7 @@ class AuthBloc extends Bloc { // emit(FailureForgetState(error: failure.toString())); } } - +//925207 String? validateCode(String? value) { if (value == null || value.isEmpty) { return 'Code is required'; diff --git a/lib/pages/visitor_password/view/add_device_dialog.dart b/lib/pages/visitor_password/view/add_device_dialog.dart index 58e49182..7dc5407d 100644 --- a/lib/pages/visitor_password/view/add_device_dialog.dart +++ b/lib/pages/visitor_password/view/add_device_dialog.dart @@ -25,7 +25,11 @@ class AddDeviceDialog extends StatelessWidget { final visitorBloc = BlocProvider.of(context); return AlertDialog( backgroundColor: Colors.white, - title: const Text('Add Accessible Device'), + title: Text('Add Accessible Device', + style: Theme.of(context).textTheme.headlineLarge!.copyWith( + fontWeight: FontWeight.w400, + fontSize: 24, + color: Colors.black),), content: Container( height: MediaQuery.of(context).size.height/1.7, width: MediaQuery.of(context).size.width/2, diff --git a/lib/pages/visitor_password/view/repeat_widget.dart b/lib/pages/visitor_password/view/repeat_widget.dart index ecbd2b89..b04614fe 100644 --- a/lib/pages/visitor_password/view/repeat_widget.dart +++ b/lib/pages/visitor_password/view/repeat_widget.dart @@ -17,7 +17,7 @@ class RepeatWidget extends StatelessWidget { return BlocBuilder( builder: (context, state) { - final smartDoorBloc = BlocProvider.of(context); + final visitorBloc = BlocProvider.of(context); return Column( children: [ Container( @@ -25,7 +25,7 @@ class RepeatWidget extends StatelessWidget { height: size.height * 0.06, // Adjust height as needed child: ListView( scrollDirection: Axis.horizontal, - children: smartDoorBloc.days.map((day) { + children: visitorBloc.days.map((day) { return Container( width: size.width* 0.09, child: CheckboxListTile( @@ -33,15 +33,15 @@ class RepeatWidget extends StatelessWidget { day['day']!, style: TextStyle( fontSize: 18, - color: smartDoorBloc.selectedDays.contains(day['key']) + color: visitorBloc.selectedDays.contains(day['key']) ? Colors.black : ColorsManager.grayColor, ), ), - value: smartDoorBloc.selectedDays.contains(day['key']), + value: visitorBloc.selectedDays.contains(day['key']), onChanged: (bool? value) { if (value != null) { - smartDoorBloc.add(ToggleDaySelectionEvent(key: day['key']!)); + visitorBloc.add(ToggleDaySelectionEvent(key: day['key']!)); } }, ), @@ -56,19 +56,19 @@ class RepeatWidget extends StatelessWidget { title: '', size: size, endTime: () { - smartDoorBloc.add(SelectTimeVisitorPassword( + visitorBloc.add(SelectTimeVisitorPassword( isRepeat: true, context: context, isStart: false )); }, startTime: () { - smartDoorBloc.add(SelectTimeVisitorPassword( + visitorBloc.add(SelectTimeVisitorPassword( isRepeat: true, context: context, isStart: true )); }, - firstString: smartDoorBloc.repeatStartTime.toString(), - secondString: smartDoorBloc.repeatEndTime.toString(), + firstString: visitorBloc.repeatStartTime.toString(), + secondString: visitorBloc.repeatEndTime.toString(), ), ), diff --git a/lib/pages/visitor_password/view/visitor_password_dialog.dart b/lib/pages/visitor_password/view/visitor_password_dialog.dart index eadee6dc..e91ab834 100644 --- a/lib/pages/visitor_password/view/visitor_password_dialog.dart +++ b/lib/pages/visitor_password/view/visitor_password_dialog.dart @@ -26,7 +26,11 @@ class VisitorPasswordDialog extends StatelessWidget { bool isRepeat = state is IsRepeatState ? state.repeat : visitorBloc.repeat; return AlertDialog( backgroundColor: Colors.white, - title: const Text('Create visitor password'), + title: Text('Create visitor password', + style: Theme.of(context).textTheme.headlineLarge!.copyWith( + fontWeight: FontWeight.w400, + fontSize: 24, + color: Colors.black),), content: SingleChildScrollView( child: Form( key: visitorBloc.forgetFormKey, diff --git a/lib/utils/color_manager.dart b/lib/utils/color_manager.dart index 9b5b6d44..2ee12718 100644 --- a/lib/utils/color_manager.dart +++ b/lib/utils/color_manager.dart @@ -25,7 +25,7 @@ abstract class ColorsManager { static const Color blackColor = Color(0xFF000000); static const Color lightGreen = Color(0xFF00FF0A); static const Color grayColor = Color(0xFF999999); - static const Color red = Colors.red; + static const Color red = Color(0xFFFF0000); static const Color graysColor = Color(0xffEBEBEB); static const Color textGray = Color(0xffD5D5D5); static const Color btnColor = Color(0xFF00008B); From 4b7567a6fe01e241ec923f382397160f76dee0d5 Mon Sep 17 00:00:00 2001 From: mohammad Date: Wed, 21 Aug 2024 16:02:08 +0300 Subject: [PATCH 24/32] padding and text them --- lib/main.dart | 2 +- lib/pages/common/custom_web_textfield.dart | 24 +- lib/pages/common/date_time_widget.dart | 10 +- lib/pages/common/hour_picker_dialog.dart | 92 +++ .../bloc/visitor_password_bloc.dart | 269 ++++++--- .../bloc/visitor_password_event.dart | 22 +- .../bloc/visitor_password_state.dart | 1 + .../view/add_device_dialog.dart | 1 + .../visitor_password/view/repeat_widget.dart | 27 +- .../view/visitor_password_dialog.dart | 534 +++++++++++------- lib/services/access_mang_api.dart | 56 +- 11 files changed, 720 insertions(+), 318 deletions(-) create mode 100644 lib/pages/common/hour_picker_dialog.dart diff --git a/lib/main.dart b/lib/main.dart index dcabeaa2..c00a8da7 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -59,7 +59,7 @@ class MyApp extends StatelessWidget { colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple), // Set up color scheme useMaterial3: true, // Enable Material 3 ), - // home: AddDeviceDialog() + // home: VisitorPasswordDialog() home:isLoggedIn == 'Success' ? const HomePage() : const LoginPage(), )); } diff --git a/lib/pages/common/custom_web_textfield.dart b/lib/pages/common/custom_web_textfield.dart index 4ec0aa81..82fb86c9 100644 --- a/lib/pages/common/custom_web_textfield.dart +++ b/lib/pages/common/custom_web_textfield.dart @@ -37,19 +37,23 @@ class CustomWebTextField extends StatelessWidget { .bodyMedium! .copyWith(color: Colors.red), ), - Text(textFieldName), + Text(textFieldName, + style: Theme.of(context).textTheme.bodySmall!.copyWith( + color: Colors.black,fontSize: 13),), ], ), const SizedBox(width: 10,), - Text( - description??'', // ' The password will be sent to the visitor’s email address.', - style: Theme.of(context) - .textTheme - .bodySmall! - .copyWith( - fontSize: 9, - fontWeight: FontWeight.w400, - color: ColorsManager.textGray), + Expanded( + child: Text( + description??'', + style: Theme.of(context) + .textTheme + .bodySmall! + .copyWith( + fontSize: 9, + fontWeight: FontWeight.w400, + color: ColorsManager.textGray), + ), ), ], ), diff --git a/lib/pages/common/date_time_widget.dart b/lib/pages/common/date_time_widget.dart index ace2cf8d..f5034342 100644 --- a/lib/pages/common/date_time_widget.dart +++ b/lib/pages/common/date_time_widget.dart @@ -1,5 +1,6 @@ import 'package:flutter/material.dart'; import 'package:flutter_svg/flutter_svg.dart'; +import 'package:syncrow_web/utils/color_manager.dart'; import 'package:syncrow_web/utils/constants/assets.dart'; import 'package:syncrow_web/utils/style.dart'; @@ -39,7 +40,8 @@ class DateTimeWebWidget extends StatelessWidget { .bodyMedium! .copyWith(color: Colors.red), ), - Text(title??''), + Text(title??'' , style: Theme.of(context).textTheme.bodySmall!.copyWith( + color: Colors.black,fontSize: 13),), ], ), SizedBox(height: 8,), @@ -55,12 +57,14 @@ class DateTimeWebWidget extends StatelessWidget { children: [ InkWell( onTap: startTime, - child: Text(firstString) + child: Text(firstString, style: Theme.of(context).textTheme.bodySmall!.copyWith( + color: ColorsManager.grayColor,fontSize: 12,fontWeight: FontWeight.w400),) ), const Icon(Icons.arrow_right_alt), InkWell( onTap:endTime, - child: Text(secondString)), + child: Text(secondString, style: Theme.of(context).textTheme.bodySmall!.copyWith( + color: ColorsManager.grayColor,fontSize: 12,fontWeight: FontWeight.w400),)), SvgPicture.asset( Assets.calendarIcon, ), diff --git a/lib/pages/common/hour_picker_dialog.dart b/lib/pages/common/hour_picker_dialog.dart new file mode 100644 index 00000000..63e95e21 --- /dev/null +++ b/lib/pages/common/hour_picker_dialog.dart @@ -0,0 +1,92 @@ + + +import 'package:flutter/material.dart'; + +class HourPickerDialog extends StatefulWidget { + final TimeOfDay initialTime; + HourPickerDialog({required this.initialTime}); + + @override + _HourPickerDialogState createState() => _HourPickerDialogState(); +} + +class _HourPickerDialogState extends State { + late int _selectedHour; + bool _isPm = false; + + @override + void initState() { + super.initState(); + _selectedHour = widget.initialTime.hour > 12 ? widget.initialTime.hour - 12 : widget.initialTime.hour; + _isPm = widget.initialTime.period == DayPeriod.pm; + } + + @override + Widget build(BuildContext context) { + return AlertDialog( + title: Text('Select Hour'), + content: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + DropdownButton( + value: _selectedHour, + items: List.generate(12, (index) { + int displayHour = index + 1; + return DropdownMenuItem( + value: displayHour, + child: Text(displayHour.toString()), + ); + }), + onChanged: (value) { + setState(() { + _selectedHour = value!; + }); + }, + ), + SizedBox(width: 16.0), + DropdownButton( + value: _isPm, + items: [ + DropdownMenuItem( + value: false, + child: Text('AM'), + ), + DropdownMenuItem( + value: true, + child: Text('PM'), + ), + ], + onChanged: (value) { + setState(() { + _isPm = value!; + }); + }, + ), + ], + ), + actions: [ + TextButton( + onPressed: () => Navigator.of(context).pop(null), + child: Text('Cancel'), + ), + TextButton( + onPressed: () { + int hour = _isPm ? _selectedHour + 12 : _selectedHour; + Navigator.of(context).pop(TimeOfDay(hour: hour, minute: 0)); + }, + child: Text('OK'), + ), + ], + ); + } +} + +Future showHourPicker({ + required BuildContext context, + required TimeOfDay initialTime, +}) { + return showDialog( + context: context, + builder: (context) => HourPickerDialog(initialTime: initialTime), + ); +} diff --git a/lib/pages/visitor_password/bloc/visitor_password_bloc.dart b/lib/pages/visitor_password/bloc/visitor_password_bloc.dart index d9f49e56..e620cc59 100644 --- a/lib/pages/visitor_password/bloc/visitor_password_bloc.dart +++ b/lib/pages/visitor_password/bloc/visitor_password_bloc.dart @@ -1,8 +1,8 @@ import 'dart:math'; - import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:intl/intl.dart'; +import 'package:syncrow_web/pages/common/hour_picker_dialog.dart'; import 'package:syncrow_web/pages/visitor_password/bloc/visitor_password_event.dart'; import 'package:syncrow_web/pages/visitor_password/bloc/visitor_password_state.dart'; import 'package:syncrow_web/pages/visitor_password/model/device_model.dart'; @@ -10,7 +10,8 @@ import 'package:syncrow_web/pages/visitor_password/model/schedule_model.dart'; import 'package:syncrow_web/services/access_mang_api.dart'; import 'package:syncrow_web/utils/color_manager.dart'; import 'package:syncrow_web/utils/snack_bar.dart'; - List selectedDevices = []; + +List selectedDevices = []; // Define the BLoC class VisitorPasswordBloc @@ -30,7 +31,8 @@ class VisitorPasswordBloc on(postOfflineMultipleTimePassword); on(postOfflineOneTimePassword); - + on(selectTimeOfLinePassword); + on(changeTime); } final TextEditingController userNameController = TextEditingController(); final TextEditingController emailController = TextEditingController(); @@ -38,14 +40,14 @@ class VisitorPasswordBloc final TextEditingController deviceNameController = TextEditingController(); final TextEditingController deviceIdController = TextEditingController(); final TextEditingController unitNameController = TextEditingController(); - final TextEditingController virtualAddressController = TextEditingController(); - - + final TextEditingController virtualAddressController = + TextEditingController(); List data = []; List selectedDeviceIds = []; + String effectiveTime = 'Select Time'; - + String expirationTime = 'Select Time'; final forgetFormKey = GlobalKey(); @@ -61,12 +63,11 @@ class VisitorPasswordBloc int? repeatEffectiveTimeTimeStamp; int? repeatExpirationTimeTimeStamp; - String startTime = 'Start Time'; - String endTime = 'End Time'; + DateTime? startTime = DateTime.now(); + DateTime? endTime; - - String repeatStartTime = 'Start Time'; - String repeatEndTime = 'End Time'; + String startTimeAccess = 'Start Time'; + String endTimeAccess = 'End Time'; // DateTime? repeatStartTime=DateTime.now(); // DateTime? repeatEndTime; @@ -85,13 +86,15 @@ class VisitorPasswordBloc Future selectTimeVisitorPassword( SelectTimeVisitorPassword event, - Emitter emit) async { + Emitter emit, + ) async { final DateTime? picked = await showDatePicker( context: event.context, initialDate: DateTime.now(), firstDate: DateTime(2015, 8), lastDate: DateTime(3101), ); + if (picked != null) { final TimeOfDay? timePicked = await showTimePicker( context: event.context, @@ -113,6 +116,7 @@ class VisitorPasswordBloc ); }, ); + if (timePicked != null) { final selectedDateTime = DateTime( picked.year, @@ -121,49 +125,39 @@ class VisitorPasswordBloc timePicked.hour, timePicked.minute, ); - final selectedTimestamp = DateTime( - selectedDateTime.year, - selectedDateTime.month, - selectedDateTime.day, - selectedDateTime.hour, - selectedDateTime.minute, - ).millisecondsSinceEpoch ~/ - 1000; // Divide by 1000 to remove milliseconds - if (event.isStart) { - if (expirationTimeTimeStamp != null && selectedTimestamp >expirationTimeTimeStamp!) { - CustomSnackBar.displaySnackBar('Effective Time cannot be later than Expiration Time.'); - } else { - if(event.isRepeat==true) - {repeatStartTime = selectedDateTime.toString().split('.').first;} - else // Remove seconds and milliseconds - {startTime = selectedDateTime.toString().split('.').first;} - effectiveTimeTimeStamp = selectedTimestamp; - emit(ChangeTimeState()); + final selectedTimestamp = selectedDateTime.millisecondsSinceEpoch ~/ 1000; + + if (event.isStart) { + if (expirationTimeTimeStamp != null && + selectedTimestamp > expirationTimeTimeStamp!) { + CustomSnackBar.displaySnackBar( + 'Effective Time cannot be later than Expiration Time.', + ); + return; } - emit(ChangeTimeState()); + effectiveTimeTimeStamp = selectedTimestamp; + + startTimeAccess = selectedDateTime.toString().split('.').first; + } else { if (effectiveTimeTimeStamp != null && selectedTimestamp < effectiveTimeTimeStamp!) { - CustomSnackBar.displaySnackBar('Expiration Time cannot be earlier than Effective Time.'); - } else { - if(event.isRepeat==true) - {repeatEndTime = selectedDateTime.toString().split('.').first;} - else - {endTime = selectedDateTime.toString().split('.').first;} - expirationTimeTimeStamp = selectedTimestamp; - emit(ChangeTimeState()); - + CustomSnackBar.displaySnackBar( + 'Expiration Time cannot be earlier than Effective Time.', + ); + return; } - emit(ChangeTimeState()); + expirationTimeTimeStamp = selectedTimestamp; + + endTimeAccess = selectedDateTime.toString().split('.').first; - emit(VisitorPasswordInitial()); } + + emit(ChangeTimeState()); + emit(VisitorPasswordInitial()); } } - - // emit(AccessInitial()); - // emit(TableLoaded(data)); } bool toggleRepeat( @@ -212,17 +206,24 @@ class VisitorPasswordBloc //online password - Future postOnlineOneTimePassword( - OnlineOneTimePasswordEvent event, + Future postOnlineOneTimePassword(OnlineOneTimePasswordEvent event, Emitter emit) async { try { + generate7DigitNumber(); + print('selectedDevices$selectedDevices'); // emit(DeviceLoaded()); - await AccessMangApi().postOnlineOneTime( + bool res = await AccessMangApi().postOnlineOneTime( email: event.email, + password: passwordController, devicesUuid: selectedDevices, - passwordName: event.passwordName); - // emit(TableLoaded(data)); + passwordName: event.passwordName, + effectiveTime: effectiveTimeTimeStamp.toString(), + invalidTime: expirationTimeTimeStamp.toString()); + if (res = true) { + Navigator.pop(event.context!); + } + emit(TableLoaded(data)); } catch (e) { emit(FailedState(e.toString())); } @@ -233,33 +234,31 @@ class VisitorPasswordBloc Emitter emit) async { try { generate7DigitNumber(); - // emit(DeviceLoaded()); - await AccessMangApi().postOnlineMultipleTime( - scheduleList:[ - // if (repeat) - // Schedule( - // effectiveTime: getTimeOnly(repeatStartTime), - // invalidTime: getTimeOnly(repeatEndTime).toString(), - // workingDay: selectedDays, - // ), - ] , + bool res = await AccessMangApi().postOnlineMultipleTime( + scheduleList: [ + if (repeat) + Schedule( + effectiveTime: getTimeFromDateTimeString(expirationTime), + invalidTime: getTimeFromDateTimeString(effectiveTime).toString(), + workingDay: selectedDays, + ), + ], password: passwordController, - invalidTime:event.invalidTime , - effectiveTime:event.effectiveTime , + invalidTime: expirationTimeTimeStamp.toString(), + effectiveTime: effectiveTimeTimeStamp.toString(), email: event.email, devicesUuid: selectedDevices, - passwordName: event.passwordName - ); - // emit(TableLoaded(data)); + passwordName: event.passwordName); + if(res==true){ + Navigator.pop(event.context!); + } } catch (e) { emit(FailedState(e.toString())); } } - //offline password - Future postOfflineOneTimePassword( - OfflineOneTimePasswordEvent event, + Future postOfflineOneTimePassword(OfflineOneTimePasswordEvent event, Emitter emit) async { try { generate7DigitNumber(); @@ -267,8 +266,7 @@ class VisitorPasswordBloc await AccessMangApi().postOffLineOneTime( email: event.email, devicesUuid: selectedDevices, - passwordName: event.passwordName - ); + passwordName: event.passwordName); // emit(TableLoaded(data)); } catch (e) { emit(FailedState(e.toString())); @@ -285,16 +283,14 @@ class VisitorPasswordBloc email: event.email, devicesUuid: selectedDevices, passwordName: event.passwordName, - invalidTime:event.invalidTime , - effectiveTime:event.effectiveTime - ); + invalidTime: event.invalidTime, + effectiveTime: event.effectiveTime); // emit(TableLoaded(data)); } catch (e) { emit(FailedState(e.toString())); } } - void selectDevice( SelectDeviceEvent event, Emitter emit) { if (selectedDeviceIds.contains(event.deviceId)) { @@ -302,8 +298,6 @@ class VisitorPasswordBloc } else { selectedDeviceIds.add(event.deviceId); } - selectedDevices=selectedDeviceIds; - print(selectedDevices); } String? validate(String? value) { @@ -313,10 +307,9 @@ class VisitorPasswordBloc return null; } - Future generate7DigitNumber() async { emit(LoadingInitialState()); - passwordController=''; + passwordController = ''; Random random = Random(); int min = 1000000; int max = 9999999; @@ -324,12 +317,12 @@ class VisitorPasswordBloc emit(GeneratePasswordState()); return passwordController; } + String getTimeOnly(DateTime? dateTime) { if (dateTime == null) return ''; return DateFormat('HH:mm').format(dateTime); } - void filterDevices() { final deviceName = deviceNameController.text.toLowerCase(); final deviceId = deviceIdController.text.toLowerCase(); @@ -340,14 +333,16 @@ class VisitorPasswordBloc final matchesDeviceId = device.uuid.toLowerCase().contains(deviceId); // final matchesUnitName = device.unitName.toLowerCase().contains(unitName); // Assuming unitName is a property of the device - return matchesDeviceName && matchesDeviceId ; + return matchesDeviceName && matchesDeviceId; }).toList(); // emit(TableLoaded(filteredData)); - add(UpdateFilteredDevicesEvent(filteredData)); + add(UpdateFilteredDevicesEvent(filteredData)); } + @override - Stream mapEventToState(VisitorPasswordEvent event) async* { + Stream mapEventToState( + VisitorPasswordEvent event) async* { if (event is FetchDevice) { // Fetching logic... } else if (event is UpdateFilteredDevicesEvent) { @@ -355,7 +350,113 @@ class VisitorPasswordBloc } } - void _onUpdateFilteredDevices(UpdateFilteredDevicesEvent event, Emitter emit) { + void _onUpdateFilteredDevices( + UpdateFilteredDevicesEvent event, Emitter emit) { emit(TableLoaded(event.filteredData)); } + + addDeviceToList() { + selectedDevices = selectedDeviceIds; + print(selectedDevices); + } + + Future selectTimeOfLinePassword(SelectTimeEvent event, Emitter emit) async { + emit(ChangeTimeState()); + final DateTime? picked = await showDatePicker( + context: event.context, + initialDate: DateTime.now(), + firstDate: DateTime.now(), + lastDate: DateTime(3101), + ); + if (picked != null) { + final TimeOfDay? timePicked = await showHourPicker( + context: event.context, + initialTime: TimeOfDay.now(), + ); + if (timePicked != null) { + final selectedDateTime = DateTime( + picked.year, + picked.month, + picked.day, + timePicked.hour, + 0, + ); + final selectedTimestamp = DateTime( + selectedDateTime.year, + selectedDateTime.month, + selectedDateTime.day, + selectedDateTime.hour, + selectedDateTime.minute, + ).millisecondsSinceEpoch ~/ + 1000; // Divide by 1000 to remove milliseconds + if (event.isEffective) { + if (expirationTimeTimeStamp != null && + selectedTimestamp > expirationTimeTimeStamp!) { + CustomSnackBar.displaySnackBar( + 'Effective Time cannot be later than Expiration Time.'); + } else { + effectiveTime = selectedDateTime + .toString() + .split('.') + .first; // Remove seconds and milliseconds + effectiveTimeTimeStamp = selectedTimestamp; + } + } else { + if (effectiveTimeTimeStamp != null && + selectedTimestamp < effectiveTimeTimeStamp!) { + CustomSnackBar.displaySnackBar( + 'Expiration Time cannot be earlier than Effective Time.'); + } else { + expirationTime = selectedDateTime + .toString() + .split('.') + .first; // Remove seconds and milliseconds + expirationTimeTimeStamp = selectedTimestamp; + } + } + print('effectiveTime=$effectiveTime'); + print('expirationTime=$expirationTime'); + + print('expirationTimeTimeStamp=$expirationTimeTimeStamp'); + print('effectiveTimeTimeStamp=$effectiveTimeTimeStamp'); + emit(TimeSelectedState()); + } + } + } + + changeTime(ChangeTimeEvent event, Emitter emit) { + if (event.isStartEndTime == true) { + startTime = event.val; + } else { + endTime = event.val; + } + } + DateTime? convertStringToDateTime(String dateTimeString) { + try { + // Define the input format. Adjust this pattern based on your input string format. + final DateFormat inputFormat = DateFormat('yyyy-MM-dd HH:mm:ss'); + // Convert the string to a DateTime object. + DateTime dateTime = inputFormat.parse(dateTimeString); + return dateTime; + } catch (e) { + print("Error parsing date: $e"); + return null; + } + } + + String getTimeFromDateTimeString(String dateTimeString) { + DateTime? dateTime = convertStringToDateTime(dateTimeString); + if (dateTime == null) return ''; + + // Extract the time component from the DateTime object. + return DateFormat('HH:mm').format(dateTime); + } + + String? validateEmail(String? value) { + if (!RegExp(r'^[^@]+@[^@]+\.[^@]+').hasMatch(value!)) { + return ''; + } + return null; + } + } diff --git a/lib/pages/visitor_password/bloc/visitor_password_event.dart b/lib/pages/visitor_password/bloc/visitor_password_event.dart index 9990ad9a..723ac052 100644 --- a/lib/pages/visitor_password/bloc/visitor_password_event.dart +++ b/lib/pages/visitor_password/bloc/visitor_password_event.dart @@ -57,8 +57,9 @@ class FetchDevice extends VisitorPasswordEvent {} class OnlineOneTimePasswordEvent extends VisitorPasswordEvent { final String? email; final String? passwordName; + final BuildContext? context; - const OnlineOneTimePasswordEvent({this.email,this.passwordName}); + const OnlineOneTimePasswordEvent({this.email,this.passwordName,this.context}); @override List get props => [email!,passwordName!,]; @@ -68,9 +69,10 @@ class OnlineMultipleTimePasswordEvent extends VisitorPasswordEvent { final String? passwordName; final String? invalidTime; final String? effectiveTime; - const OnlineMultipleTimePasswordEvent({this.email,this.passwordName,this.invalidTime,this.effectiveTime}); + final BuildContext? context; + const OnlineMultipleTimePasswordEvent({this.email,this.passwordName,this.invalidTime,this.effectiveTime,this.context}); @override - List get props => [email!,passwordName!,invalidTime!,effectiveTime!]; + List get props => [email!,passwordName!,invalidTime!,effectiveTime!,context!]; } //offline password @@ -115,4 +117,18 @@ class UpdateFilteredDevicesEvent extends VisitorPasswordEvent { final List filteredData; UpdateFilteredDevicesEvent(this.filteredData); +}class SelectTimeEvent extends VisitorPasswordEvent { + final BuildContext context; + final bool isEffective; + const SelectTimeEvent({required this.context,required this.isEffective}); + @override + List get props => [context,isEffective]; +} +class ChangeTimeEvent extends VisitorPasswordEvent { + final dynamic val; + final bool isStartEndTime; + + const ChangeTimeEvent({required this.val,required this.isStartEndTime}); + @override + List get props => [val,isStartEndTime]; } \ No newline at end of file diff --git a/lib/pages/visitor_password/bloc/visitor_password_state.dart b/lib/pages/visitor_password/bloc/visitor_password_state.dart index 374433af..27008a30 100644 --- a/lib/pages/visitor_password/bloc/visitor_password_state.dart +++ b/lib/pages/visitor_password/bloc/visitor_password_state.dart @@ -41,6 +41,7 @@ class IsRepeatState extends VisitorPasswordState { class LoadingInitialState extends VisitorPasswordState {} class ChangeTimeState extends VisitorPasswordState {} +class TimeSelectedState extends VisitorPasswordState {} class DeviceLoaded extends VisitorPasswordState {} class GeneratePasswordState extends VisitorPasswordState {} diff --git a/lib/pages/visitor_password/view/add_device_dialog.dart b/lib/pages/visitor_password/view/add_device_dialog.dart index 7dc5407d..844e0ca9 100644 --- a/lib/pages/visitor_password/view/add_device_dialog.dart +++ b/lib/pages/visitor_password/view/add_device_dialog.dart @@ -207,6 +207,7 @@ class AddDeviceDialog extends StatelessWidget { width: size.width * 0.2, child: DefaultButton( onPressed: () { + visitorBloc.addDeviceToList(); Navigator.of(context).pop(); // Close the dialog }, borderRadius: 8, diff --git a/lib/pages/visitor_password/view/repeat_widget.dart b/lib/pages/visitor_password/view/repeat_widget.dart index b04614fe..73770100 100644 --- a/lib/pages/visitor_password/view/repeat_widget.dart +++ b/lib/pages/visitor_password/view/repeat_widget.dart @@ -56,19 +56,26 @@ class RepeatWidget extends StatelessWidget { title: '', size: size, endTime: () { - visitorBloc.add(SelectTimeVisitorPassword( - isRepeat: true, - context: context, isStart: false - )); + print('sadasd'); + visitorBloc.add(SelectTimeEvent( + context: context, + isEffective: false)); + new Future.delayed(const Duration(milliseconds: 500), () { + visitorBloc.add(ChangeTimeEvent(val: visitorBloc.endTime, isStartEndTime: true)); + + }); + + }, startTime: () { - visitorBloc.add(SelectTimeVisitorPassword( - isRepeat: true, - context: context, isStart: true - )); + new Future.delayed(const Duration(milliseconds: 500), () { + visitorBloc.add(ChangeTimeEvent(val: visitorBloc.endTime, isStartEndTime: true)); + }); + visitorBloc.add(SelectTimeEvent(context: context, isEffective: true)); + }, - firstString: visitorBloc.repeatStartTime.toString(), - secondString: visitorBloc.repeatEndTime.toString(), + firstString:visitorBloc.effectiveTime , + secondString: visitorBloc.expirationTime , ), ), diff --git a/lib/pages/visitor_password/view/visitor_password_dialog.dart b/lib/pages/visitor_password/view/visitor_password_dialog.dart index e91ab834..2299877f 100644 --- a/lib/pages/visitor_password/view/visitor_password_dialog.dart +++ b/lib/pages/visitor_password/view/visitor_password_dialog.dart @@ -1,6 +1,7 @@ import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:flutter_svg/flutter_svg.dart'; import 'package:syncrow_web/pages/common/custom_web_textfield.dart'; import 'package:syncrow_web/pages/common/date_time_widget.dart'; import 'package:syncrow_web/pages/common/default_button.dart'; @@ -10,6 +11,8 @@ import 'package:syncrow_web/pages/visitor_password/bloc/visitor_password_event.d import 'package:syncrow_web/pages/visitor_password/bloc/visitor_password_state.dart'; import 'package:syncrow_web/pages/visitor_password/view/add_device_dialog.dart'; import 'package:syncrow_web/pages/visitor_password/view/repeat_widget.dart'; +import 'package:syncrow_web/utils/color_manager.dart'; +import 'package:syncrow_web/utils/constants/assets.dart'; import 'package:syncrow_web/utils/style.dart'; class VisitorPasswordDialog extends StatelessWidget { @@ -23,19 +26,22 @@ class VisitorPasswordDialog extends StatelessWidget { child: BlocBuilder( builder: (BuildContext context, VisitorPasswordState state) { final visitorBloc = BlocProvider.of(context); - bool isRepeat = state is IsRepeatState ? state.repeat : visitorBloc.repeat; + bool isRepeat = + state is IsRepeatState ? state.repeat : visitorBloc.repeat; return AlertDialog( backgroundColor: Colors.white, - title: Text('Create visitor password', - style: Theme.of(context).textTheme.headlineLarge!.copyWith( - fontWeight: FontWeight.w400, - fontSize: 24, - color: Colors.black),), + title: Text( + 'Create visitor password', + style: Theme.of(context).textTheme.headlineLarge!.copyWith( + fontWeight: FontWeight.w400, + fontSize: 24, + color: Colors.black), + ), content: SingleChildScrollView( child: Form( key: visitorBloc.forgetFormKey, child: Padding( - padding: const EdgeInsets.all(10.0), + padding: const EdgeInsets.all(5.0), child: ListBody( children: [ Container( @@ -55,18 +61,21 @@ class VisitorPasswordDialog extends StatelessWidget { Expanded( flex: 2, child: CustomWebTextField( - validator: visitorBloc.validate, + validator: visitorBloc.validateEmail, controller: visitorBloc.emailController, isRequired: true, textFieldName: 'Email Address', - description: 'The password will be sent to the visitor’s email address.', + description: + 'The password will be sent to the visitor’s email address.', ), ), const Spacer(), ], ), ), - SizedBox(height: 20,), + const SizedBox( + height: 15, + ), Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ @@ -79,7 +88,9 @@ class VisitorPasswordDialog extends StatelessWidget { .bodyMedium! .copyWith(color: Colors.red), ), - const Text('Access Type'), + Text('Access Type', + style: Theme.of(context).textTheme.bodySmall!.copyWith( + color: Colors.black,fontSize: 13),), ], ), Row( @@ -87,7 +98,10 @@ class VisitorPasswordDialog extends StatelessWidget { SizedBox( width: size.width * 0.15, child: RadioListTile( - title: const Text('Online Password'), + title: Text('Online Password', + style: Theme.of(context).textTheme.bodySmall!.copyWith( + color: Colors.black,fontSize: 13), + ), value: 'Online Password', groupValue: (state is PasswordTypeSelected) ? state.selectedType @@ -95,16 +109,19 @@ class VisitorPasswordDialog extends StatelessWidget { onChanged: (String? value) { if (value != null) { print(value); - context.read().add(SelectPasswordType(value)); + context + .read() + .add(SelectPasswordType(value)); } }, ), ), - SizedBox( width: size.width * 0.15, child: RadioListTile( - title: const Text('Offline Password'), + title: Text('Offline Password', + style: Theme.of(context).textTheme.bodySmall!.copyWith( + color: Colors.black,fontSize: 13),), value: 'Offline Password', groupValue: (state is PasswordTypeSelected) ? state.selectedType @@ -113,36 +130,146 @@ class VisitorPasswordDialog extends StatelessWidget { if (value != null) { print(value); - context.read().add(SelectPasswordType(value)); + context + .read() + .add(SelectPasswordType(value)); } }, ), ), - SizedBox( width: size.width * 0.15, child: RadioListTile( - title: const Text('Dynamic Password'), + title: Text('Dynamic Password', + style: Theme.of(context).textTheme.bodySmall!.copyWith( + color: Colors.black,fontSize: 13),), value: 'Dynamic Password', groupValue: (state is PasswordTypeSelected) ? state.selectedType : visitorBloc.accessTypeSelected, onChanged: (String? value) { if (value != null) { - context.read().add(SelectPasswordType(value)); - visitorBloc.usageFrequencySelected=''; + context + .read() + .add(SelectPasswordType(value)); + visitorBloc.usageFrequencySelected = ''; } }, ), ), ], ), - const Text('Only currently online devices can be selected. It is recommended to use when the device network is stable, and the system randomly generates a digital password'), - const SizedBox(height: 20,) + Text( + 'Only currently online devices can be selected. It is recommended to use when the device network is stable, and the system randomly generates a digital password', + style: Theme.of(context).textTheme.bodySmall!.copyWith( + fontWeight: FontWeight.w400, + color: ColorsManager.grayColor,fontSize: 9),), + const SizedBox( + height: 20, + ) ], ), - visitorBloc.accessTypeSelected=='Dynamic Password' ? - SizedBox(): + visitorBloc.accessTypeSelected == 'Dynamic Password' + ? SizedBox() + : Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + Text( + '* ', + style: Theme.of(context) + .textTheme + .bodyMedium! + .copyWith(color: Colors.red), + ), + Text('Usage Frequency',style: Theme.of(context).textTheme.bodySmall!.copyWith( + color: Colors.black,fontSize: 13),), + ], + ), + Row( + children: [ + SizedBox( + width: 200, + child: RadioListTile( + title: Text('One-Time',style: Theme.of(context).textTheme.bodySmall!.copyWith( + color: Colors.black,fontSize: 13),), + value: 'One-Time', + groupValue: + (state is UsageFrequencySelected) + ? state.selectedFrequency + : visitorBloc + .usageFrequencySelected, + onChanged: (String? value) { + if (value != null) { + print(value); + + context + .read() + .add(SelectUsageFrequency( + value)); + } + }, + ), + ), + SizedBox( + width: 200, + child: RadioListTile( + title: Text('Periodic', + style: Theme.of(context).textTheme.bodySmall!.copyWith( + color: Colors.black,fontSize: 13),), + value: 'Periodic', + groupValue: + (state is UsageFrequencySelected) + ? state.selectedFrequency + : visitorBloc + .usageFrequencySelected, + onChanged: (String? value) { + if (value != null) { + context + .read() + .add(SelectUsageFrequency( + value)); + } + }, + ), + ), + ], + ), + Text('Within the validity period, each device can be unlocked only once.', + style: Theme.of(context).textTheme.bodySmall!.copyWith( + color: ColorsManager.grayColor,fontSize: 9),) + ], + ), + const SizedBox( + height: 20, + ), + if ((visitorBloc.usageFrequencySelected != 'One-Time' || + visitorBloc.accessTypeSelected != + 'Offline Password') && + (visitorBloc.usageFrequencySelected != '')) + DateTimeWebWidget( + isRequired: true, + title: 'Access Period', + size: size, + endTime: () { + visitorBloc.add(SelectTimeVisitorPassword( + context: context, + isStart: false, + isRepeat: false)); + }, + startTime: () { + visitorBloc.add(SelectTimeVisitorPassword( + context: context, + isStart: true, + isRepeat: false)); + }, + firstString: visitorBloc.startTimeAccess.toString(), + secondString: visitorBloc.endTimeAccess.toString(), + ), + const SizedBox( + height: 20, + ), Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ @@ -155,111 +282,47 @@ class VisitorPasswordDialog extends StatelessWidget { .bodyMedium! .copyWith(color: Colors.red), ), - const Text('Usage Frequency'), + Text('Access Devices', style: Theme.of(context).textTheme.bodySmall!.copyWith( + color: Colors.black,fontSize: 13),), ], ), - Row( - children: [ - SizedBox( - width: 200, - child: RadioListTile( - title: const Text('One-Time'), - value: 'One-Time', - groupValue: (state is UsageFrequencySelected) - ? state.selectedFrequency - : visitorBloc.usageFrequencySelected, - onChanged: (String? value) { - if (value != null) { - print(value); - - context.read().add(SelectUsageFrequency(value)); - } - }, - ), - ), - SizedBox( - width: 200, - child: RadioListTile( - title: const Text('Periodic'), - value: 'Periodic', - groupValue: (state is UsageFrequencySelected) - ? state.selectedFrequency - : visitorBloc.usageFrequencySelected, - onChanged: (String? value) { - if (value != null) { - context.read().add(SelectUsageFrequency(value)); - } - }, - ), - ), - ], + Text( + 'Within the validity period, each device can be unlocked only once.',style: Theme.of(context).textTheme.bodySmall!.copyWith( + fontWeight: FontWeight.w400, + color: ColorsManager.grayColor,fontSize: 9),), + const SizedBox( + height: 20, ), - - const Text('Within the validity period, each device can be unlocked only once.') - ], - ), - const SizedBox(height: 20,), - if((visitorBloc.usageFrequencySelected!='One-Time'||visitorBloc.accessTypeSelected!='Offline Password')&&(visitorBloc.usageFrequencySelected!='')) - DateTimeWebWidget( - - isRequired: true, - title: 'Access Period', - size: size, - endTime: () { - visitorBloc.add(SelectTimeVisitorPassword( - context: context, isStart: false,isRepeat:false)); - }, - startTime: () { - visitorBloc.add(SelectTimeVisitorPassword( - context: context, isStart: true,isRepeat:false)); - }, - firstString: visitorBloc.startTime, - secondString: visitorBloc.endTime, - ), - - const SizedBox(height: 20,), - - Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Row( - children: [ - Text( - '* ', - style: Theme.of(context) - .textTheme - .bodyMedium! - .copyWith(color: Colors.red), - ), - const Text('Access Devices'), - ], - ), - const Text('Within the validity period, each device can be unlocked only once.'), - const SizedBox(height: 20,), - if(visitorBloc.usageFrequencySelected=='Periodic'&&visitorBloc.accessTypeSelected=='Online Password') - SizedBox( - width: 100, - child: ListTile( - contentPadding: EdgeInsets.zero, - leading: const Text('Repeat'), - trailing: Transform.scale( - scale: .8, - child: CupertinoSwitch( - value: visitorBloc.repeat, - onChanged: (value) { - visitorBloc.add(ToggleRepeatEvent()); - }, - applyTheme: true, + if (visitorBloc.usageFrequencySelected == + 'Periodic' && + visitorBloc.accessTypeSelected == + 'Online Password') + SizedBox( + width: 100, + child: ListTile( + contentPadding: EdgeInsets.zero, + leading: const Text('Repeat'), + trailing: Transform.scale( + scale: .8, + child: CupertinoSwitch( + value: visitorBloc.repeat, + onChanged: (value) { + visitorBloc.add(ToggleRepeatEvent()); + }, + applyTheme: true, + ), ), ), ), - ), - if(visitorBloc.usageFrequencySelected=='Periodic'&&visitorBloc.accessTypeSelected=='Online Password') + if (visitorBloc.usageFrequencySelected == + 'Periodic' && + visitorBloc.accessTypeSelected == + 'Online Password') isRepeat ? const RepeatWidget() : const SizedBox(), Container( decoration: containerDecoration, - width: size.width * 0.1, - child: DefaultButton( + width: size.width * 0.08, + child: DefaultButton( onPressed: () { showDialog( context: context, @@ -270,12 +333,13 @@ class VisitorPasswordDialog extends StatelessWidget { ); }, borderRadius: 8, - child: Text('+ Add Device'), + child: Text('+ Add Device',style: Theme.of(context).textTheme.bodySmall!.copyWith( + fontWeight: FontWeight.w400, + color: ColorsManager.whiteColors,fontSize: 12),), ), ), ], ), - ], ), ), @@ -294,88 +358,176 @@ class VisitorPasswordDialog extends StatelessWidget { backgroundColor: Colors.white, child: Text( 'Cancel', - style: Theme.of(context).textTheme.bodyMedium!, + style: Theme.of(context).textTheme.bodySmall!.copyWith( + fontWeight: FontWeight.w400, + color: ColorsManager.blackColor,fontSize: 16), ), ), ), Container( decoration: containerDecoration, width: size.width * 0.2, - child: DefaultButton( + child: DefaultButton( onPressed: () { - if(visitorBloc.forgetFormKey.currentState!.validate()){ - showDialog( - context: context, - barrierDismissible: false, - builder: (BuildContext context) { - return InfoDialog( - size: size, - title: 'Set Password', - content: 'This action will update all of the selected\n door locks passwords in the property.\n\nAre you sure you want to continue?', + print(selectedDevices); + if (visitorBloc.forgetFormKey.currentState!.validate()) { + if (selectedDevices.toString() != '[]') { + showDialog( + context: context, + barrierDismissible: false, + builder: (BuildContext context) { + return InfoDialog( + size: size, + title: 'Set Password', + content: + 'This action will update all of the selected\n door locks passwords in the property.\n\nAre you sure you want to continue?', + actions: [ + Container( + decoration: containerDecoration, + width: size.width * 0.1, + child: DefaultButton( + borderRadius: 8, + onPressed: () { + Navigator.of(context) + .pop(); // Close the dialog + }, + backgroundColor: Colors.white, + child: Text( + 'Cancel', + style: Theme.of(context).textTheme.bodySmall!.copyWith( + fontWeight: FontWeight.w400, + color: ColorsManager.blackColor,fontSize: 16), + ), + ), + ), + Container( + decoration: containerDecoration, + width: size.width * 0.1, + child: DefaultButton( + borderRadius: 8, + onPressed: () { + if (visitorBloc.usageFrequencySelected == + 'One-Time' && + visitorBloc.accessTypeSelected == + 'Online Password') { + visitorBloc.add( + OnlineOneTimePasswordEvent( + context: context, + passwordName: visitorBloc + .userNameController.text, + email: visitorBloc + .emailController.text)); + } else if (visitorBloc + .usageFrequencySelected == + 'Periodic' && + visitorBloc.accessTypeSelected == + 'Online Password') { + visitorBloc.add( + OnlineMultipleTimePasswordEvent( + passwordName: + visitorBloc + .userNameController + .text, + email: + visitorBloc + .emailController.text, + effectiveTime: visitorBloc + .effectiveTimeTimeStamp + .toString(), + invalidTime: visitorBloc + .expirationTimeTimeStamp + .toString())); + } else if (visitorBloc + .usageFrequencySelected == + 'One-Time' && + visitorBloc.accessTypeSelected == + 'Offline Password') { + visitorBloc + .add(OfflineOneTimePasswordEvent( + passwordName: visitorBloc + .userNameController.text, + email: + visitorBloc.emailController.text, + )); + } else if (visitorBloc + .usageFrequencySelected == + 'Periodic' && + visitorBloc.accessTypeSelected == + 'Offline Password') { + visitorBloc.add( + OfflineMultipleTimePasswordEvent( + passwordName: visitorBloc + .userNameController.text, + email: visitorBloc + .emailController.text, + effectiveTime: visitorBloc + .effectiveTimeTimeStamp + .toString(), + invalidTime: visitorBloc + .expirationTimeTimeStamp + .toString())); + } + }, + child: const Text( + 'Ok', + ), + ), + ), + ], + ); + }, + ); + } else { + showDialog( + context: context, + barrierDismissible: false, + builder: (BuildContext context) { + return AlertDialog( + alignment: Alignment.center, + content: SizedBox( + height: size!.height * 0.15, + child: Column( + children: [ + SizedBox( + child: SvgPicture.asset( + Assets.deviceNoteIcon, + height: 35, + width: 35, + ), + ), - actions: [ - Container( - decoration: containerDecoration, - width: size.width * 0.1, - child: DefaultButton( - borderRadius: 8, - onPressed: () { - Navigator.of(context).pop(); // Close the dialog - }, - backgroundColor: Colors.white, - child: Text( - 'Cancel', - style: Theme.of(context).textTheme.bodyMedium!, - ), + const SizedBox( + width: 15, + ), + Text( + 'Please select devices to continue', + style: Theme.of(context) + .textTheme + .headlineLarge! + .copyWith( + fontSize: 20, + fontWeight: FontWeight.w400, + color: Colors.black), + ), + ], ), ), - Container( - decoration: containerDecoration, - width: size.width * 0.1, - child: DefaultButton( - borderRadius: 8, + actionsAlignment: MainAxisAlignment.center, + actions: [ + TextButton( onPressed: () { - if(visitorBloc.usageFrequencySelected=='One-Time'&&visitorBloc.accessTypeSelected=='Online Password'){ - visitorBloc.add(OnlineOneTimePasswordEvent( - passwordName:visitorBloc.userNameController.text , - email: visitorBloc.emailController.text - ) - ); - } - else if(visitorBloc.usageFrequencySelected=='Periodic'&&visitorBloc.accessTypeSelected=='Online Password') { - visitorBloc.add(OnlineMultipleTimePasswordEvent( - passwordName:visitorBloc.userNameController.text , - email: visitorBloc.emailController.text, - effectiveTime:visitorBloc.effectiveTimeTimeStamp.toString() , - invalidTime:visitorBloc.expirationTimeTimeStamp.toString() - ) - ); - } - else if(visitorBloc.usageFrequencySelected=='One-Time'&&visitorBloc.accessTypeSelected=='Offline Password') { - visitorBloc.add(OfflineOneTimePasswordEvent( - passwordName:visitorBloc.userNameController.text , - email: visitorBloc.emailController.text, - ) - ); - } - else if(visitorBloc.usageFrequencySelected=='Periodic'&&visitorBloc.accessTypeSelected=='Offline Password') { - visitorBloc.add(OfflineMultipleTimePasswordEvent( - passwordName:visitorBloc.userNameController.text , - email: visitorBloc.emailController.text, - effectiveTime:visitorBloc.effectiveTimeTimeStamp.toString() , - invalidTime:visitorBloc.expirationTimeTimeStamp.toString() - ) - ); - } + Navigator.of(context).pop(); }, - child: const Text( - 'Ok', - ), + child: Text('OK', + style: Theme.of(context).textTheme.bodySmall!.copyWith( + fontWeight: FontWeight.w400, + color: ColorsManager.whiteColors,fontSize: 16),), ), - ), - ],); - }, - ); + ], + ); + }, + ); + } } }, borderRadius: 8, diff --git a/lib/services/access_mang_api.dart b/lib/services/access_mang_api.dart index 38730553..26d7eeea 100644 --- a/lib/services/access_mang_api.dart +++ b/lib/services/access_mang_api.dart @@ -1,4 +1,5 @@ import 'dart:convert'; +import 'package:dio/dio.dart'; import 'package:flutter/cupertino.dart'; import 'package:syncrow_web/pages/access_management/model/password_model.dart'; import 'package:syncrow_web/pages/visitor_password/model/schedule_model.dart'; @@ -51,15 +52,24 @@ class AccessMangApi{ } } - Future postOnlineOneTime({String? email,String? passwordName,List? devicesUuid}) async { + Future postOnlineOneTime({ + String? email, + String? passwordName, + String? password, + String? effectiveTime, + String? invalidTime, + List? devicesUuid}) async { try { print('postOfflineOneTime List: ${ - { + jsonEncode({ "email": email, "passwordName": passwordName, - "devicesUuid": devicesUuid - } + "password": password, + "devicesUuid": devicesUuid, + "effectiveTime":effectiveTime , + "invalidTime": invalidTime + }) }'); final response = await HTTPService().post( @@ -67,18 +77,27 @@ class AccessMangApi{ body: jsonEncode({ "email": email, "passwordName": passwordName, - "devicesUuid": devicesUuid + "password": password, + "devicesUuid": devicesUuid, + "effectiveTime":effectiveTime , + "invalidTime": invalidTime }), showServerMessage: true, expectedResponseModel: (json) { - List jsonData = json; print('postOfflineOneTime List: $json'); + if(json['statusCode'].toString()=='201'){ + return true; + }else{ + return false; + } }, ); return response; - } catch (e) { - debugPrint('Error fetching $e'); - return []; + } on DioException catch (e) { + debugPrint('Error: ${e.message}'); + + debugPrint('Error fetching ${e.response!.statusMessage}'); + return false; } } @@ -99,25 +118,30 @@ class AccessMangApi{ "effectiveTime": effectiveTime, "invalidTime": invalidTime, }; - print('createPassword =${scheduleList![0].workingDay}'); if (scheduleList != null) { body["scheduleList"] = scheduleList.map((schedule) => schedule.toJson()).toList(); } - print('createPassword =$body'); + print('createPassword =${jsonEncode(body)}'); final response = await HTTPService().post( path: ApiEndpoints.sendOnlineMultipleTime, body: jsonEncode(body), showServerMessage: true, expectedResponseModel: (json) { - List jsonData = json; - print('postOfflineOneTime List: $json'); + print('createPassword =${json}'); + + if(json['data']['successOperations'][0]['success'].toString()=='true'){ + return true; + }else{ + return false; + } }, ); return response; - } catch (e) { - debugPrint('Error fetching $e'); - return []; + } on DioException catch (e){ + debugPrint('Error fetching ${e.type.name}'); + debugPrint('Error fetching ${e.response!.statusMessage}'); + return false; } } From e4f8924e93635e97c782db302aaed2ac145c7ac4 Mon Sep 17 00:00:00 2001 From: mohammad Date: Wed, 21 Aug 2024 16:58:28 +0300 Subject: [PATCH 25/32] padding and text them --- assets/images/time_icon.svg | 4 + lib/main.dart | 1 - .../view/access_management.dart | 1 + lib/pages/common/custom_table.dart | 2 +- lib/pages/common/custom_web_textfield.dart | 3 +- lib/pages/common/date_time_widget.dart | 4 + .../bloc/visitor_password_bloc.dart | 17 +-- .../view/add_device_dialog.dart | 6 +- .../visitor_password/view/repeat_widget.dart | 33 ++--- .../view/visitor_password_dialog.dart | 140 +++++++----------- lib/utils/constants/assets.dart | 1 + 11 files changed, 93 insertions(+), 119 deletions(-) create mode 100644 assets/images/time_icon.svg diff --git a/assets/images/time_icon.svg b/assets/images/time_icon.svg new file mode 100644 index 00000000..a8f06677 --- /dev/null +++ b/assets/images/time_icon.svg @@ -0,0 +1,4 @@ + + + + diff --git a/lib/main.dart b/lib/main.dart index c00a8da7..af74170d 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -59,7 +59,6 @@ class MyApp extends StatelessWidget { colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple), // Set up color scheme useMaterial3: true, // Enable Material 3 ), - // home: VisitorPasswordDialog() home:isLoggedIn == 'Success' ? const HomePage() : const LoginPage(), )); } diff --git a/lib/pages/access_management/view/access_management.dart b/lib/pages/access_management/view/access_management.dart index e2f79429..e202194f 100644 --- a/lib/pages/access_management/view/access_management.dart +++ b/lib/pages/access_management/view/access_management.dart @@ -125,6 +125,7 @@ class AccessManagementPage extends StatelessWidget { width: 15, ), DateTimeWebWidget( + isTime: false, isRequired: false, title: 'Access Time', size: size, diff --git a/lib/pages/common/custom_table.dart b/lib/pages/common/custom_table.dart index c2a83273..86f34124 100644 --- a/lib/pages/common/custom_table.dart +++ b/lib/pages/common/custom_table.dart @@ -143,7 +143,7 @@ class _DynamicTableState extends State { alignment: Alignment.centerLeft, child: Padding( padding: const EdgeInsets.all(8.0), - child: Text(title, style: const TextStyle(fontWeight: FontWeight.bold)), + child: Text(title, style: const TextStyle(fontWeight: FontWeight.w400,fontSize: 13,color: Color(0xFF999999))), ), ), ); diff --git a/lib/pages/common/custom_web_textfield.dart b/lib/pages/common/custom_web_textfield.dart index 82fb86c9..daaa2c13 100644 --- a/lib/pages/common/custom_web_textfield.dart +++ b/lib/pages/common/custom_web_textfield.dart @@ -37,8 +37,7 @@ class CustomWebTextField extends StatelessWidget { .bodyMedium! .copyWith(color: Colors.red), ), - Text(textFieldName, - style: Theme.of(context).textTheme.bodySmall!.copyWith( + Text(textFieldName, style: Theme.of(context).textTheme.bodySmall!.copyWith( color: Colors.black,fontSize: 13),), ], ), diff --git a/lib/pages/common/date_time_widget.dart b/lib/pages/common/date_time_widget.dart index f5034342..39dc582c 100644 --- a/lib/pages/common/date_time_widget.dart +++ b/lib/pages/common/date_time_widget.dart @@ -12,6 +12,7 @@ class DateTimeWebWidget extends StatelessWidget { required this.title, required this.startTime, required this.endTime, + required this.isTime, required this.firstString, required this.secondString, }); @@ -19,6 +20,7 @@ class DateTimeWebWidget extends StatelessWidget { final Size size; final String title; final bool isRequired; + final bool isTime; final String firstString; final String secondString; final Function()? startTime; @@ -66,6 +68,8 @@ class DateTimeWebWidget extends StatelessWidget { child: Text(secondString, style: Theme.of(context).textTheme.bodySmall!.copyWith( color: ColorsManager.grayColor,fontSize: 12,fontWeight: FontWeight.w400),)), SvgPicture.asset( + isTime? + Assets.timeIcon: Assets.calendarIcon, ), ], diff --git a/lib/pages/visitor_password/bloc/visitor_password_bloc.dart b/lib/pages/visitor_password/bloc/visitor_password_bloc.dart index e620cc59..65d82dda 100644 --- a/lib/pages/visitor_password/bloc/visitor_password_bloc.dart +++ b/lib/pages/visitor_password/bloc/visitor_password_bloc.dart @@ -262,12 +262,10 @@ class VisitorPasswordBloc Emitter emit) async { try { generate7DigitNumber(); - // emit(DeviceLoaded()); await AccessMangApi().postOffLineOneTime( email: event.email, devicesUuid: selectedDevices, passwordName: event.passwordName); - // emit(TableLoaded(data)); } catch (e) { emit(FailedState(e.toString())); } @@ -278,14 +276,14 @@ class VisitorPasswordBloc Emitter emit) async { try { generate7DigitNumber(); - // emit(DeviceLoaded()); await AccessMangApi().postOffLineMultipleTime( email: event.email, devicesUuid: selectedDevices, passwordName: event.passwordName, - invalidTime: event.invalidTime, - effectiveTime: event.effectiveTime); - // emit(TableLoaded(data)); + invalidTime: expirationTimeTimeStamp.toString(), + effectiveTime: effectiveTimeTimeStamp.toString(), + + ); } catch (e) { emit(FailedState(e.toString())); } @@ -335,8 +333,6 @@ class VisitorPasswordBloc return matchesDeviceName && matchesDeviceId; }).toList(); - // emit(TableLoaded(filteredData)); - add(UpdateFilteredDevicesEvent(filteredData)); } @@ -414,11 +410,6 @@ class VisitorPasswordBloc expirationTimeTimeStamp = selectedTimestamp; } } - print('effectiveTime=$effectiveTime'); - print('expirationTime=$expirationTime'); - - print('expirationTimeTimeStamp=$expirationTimeTimeStamp'); - print('effectiveTimeTimeStamp=$effectiveTimeTimeStamp'); emit(TimeSelectedState()); } } diff --git a/lib/pages/visitor_password/view/add_device_dialog.dart b/lib/pages/visitor_password/view/add_device_dialog.dart index 844e0ca9..7f9ca94e 100644 --- a/lib/pages/visitor_password/view/add_device_dialog.dart +++ b/lib/pages/visitor_password/view/add_device_dialog.dart @@ -55,7 +55,11 @@ class AddDeviceDialog extends StatelessWidget { ), ), SizedBox(width: 10,), - Text('Only online accessible devices can be added'), + Text('Only online accessible devices can be added', + style: Theme.of(context).textTheme.bodySmall!.copyWith( + fontWeight: FontWeight.w400, + fontSize: 12, + color: ColorsManager.grayColor),), ], ) ), diff --git a/lib/pages/visitor_password/view/repeat_widget.dart b/lib/pages/visitor_password/view/repeat_widget.dart index 73770100..31bedb05 100644 --- a/lib/pages/visitor_password/view/repeat_widget.dart +++ b/lib/pages/visitor_password/view/repeat_widget.dart @@ -14,28 +14,29 @@ class RepeatWidget extends StatelessWidget { @override Widget build(BuildContext context) { Size size = MediaQuery.of(context).size; - return BlocBuilder( builder: (context, state) { final visitorBloc = BlocProvider.of(context); return Column( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, children: [ + Container( - width: size.width * 0.8, - height: size.height * 0.06, // Adjust height as needed - child: ListView( - scrollDirection: Axis.horizontal, + height: size.height * 0.05, // Adjust height as needed + child: Wrap( children: visitorBloc.days.map((day) { return Container( - width: size.width* 0.09, + width: size.width * 0.05, child: CheckboxListTile( + contentPadding: EdgeInsets.zero, title: Text( day['day']!, style: TextStyle( - fontSize: 18, + fontSize: 12, color: visitorBloc.selectedDays.contains(day['key']) ? Colors.black - : ColorsManager.grayColor, + : ColorsManager.blackColor, ), ), value: visitorBloc.selectedDays.contains(day['key']), @@ -52,27 +53,23 @@ class RepeatWidget extends StatelessWidget { Padding( padding: const EdgeInsets.all(8.0), child: DateTimeWebWidget( + isTime: true, isRequired: false, title: '', size: size, endTime: () { - print('sadasd'); - visitorBloc.add(SelectTimeEvent( - context: context, - isEffective: false)); - new Future.delayed(const Duration(milliseconds: 500), () { + visitorBloc.add(SelectTimeEvent( + context: context, + isEffective: false)); + Future.delayed(const Duration(milliseconds: 500), () { visitorBloc.add(ChangeTimeEvent(val: visitorBloc.endTime, isStartEndTime: true)); - }); - - }, startTime: () { - new Future.delayed(const Duration(milliseconds: 500), () { + Future.delayed(const Duration(milliseconds: 500), () { visitorBloc.add(ChangeTimeEvent(val: visitorBloc.endTime, isStartEndTime: true)); }); visitorBloc.add(SelectTimeEvent(context: context, isEffective: true)); - }, firstString:visitorBloc.effectiveTime , secondString: visitorBloc.expirationTime , diff --git a/lib/pages/visitor_password/view/visitor_password_dialog.dart b/lib/pages/visitor_password/view/visitor_password_dialog.dart index 2299877f..9998ec3e 100644 --- a/lib/pages/visitor_password/view/visitor_password_dialog.dart +++ b/lib/pages/visitor_password/view/visitor_password_dialog.dart @@ -245,10 +245,10 @@ class VisitorPasswordDialog extends StatelessWidget { height: 20, ), if ((visitorBloc.usageFrequencySelected != 'One-Time' || - visitorBloc.accessTypeSelected != - 'Offline Password') && - (visitorBloc.usageFrequencySelected != '')) + visitorBloc.accessTypeSelected != 'Offline Password') && + (visitorBloc.usageFrequencySelected != '')) DateTimeWebWidget( + isTime: false, isRequired: true, title: 'Access Period', size: size, @@ -282,42 +282,44 @@ class VisitorPasswordDialog extends StatelessWidget { .bodyMedium! .copyWith(color: Colors.red), ), - Text('Access Devices', style: Theme.of(context).textTheme.bodySmall!.copyWith( + Text('Access Devices', + style: Theme.of(context).textTheme.bodySmall!.copyWith( color: Colors.black,fontSize: 13),), ], ), Text( - 'Within the validity period, each device can be unlocked only once.',style: Theme.of(context).textTheme.bodySmall!.copyWith( + 'Within the validity period, each device can be unlocked only once.', + style: Theme.of(context).textTheme.bodySmall!.copyWith( fontWeight: FontWeight.w400, color: ColorsManager.grayColor,fontSize: 9),), const SizedBox( height: 20, ), - if (visitorBloc.usageFrequencySelected == - 'Periodic' && - visitorBloc.accessTypeSelected == - 'Online Password') + if (visitorBloc.usageFrequencySelected == 'Periodic' && + visitorBloc.accessTypeSelected == 'Online Password') SizedBox( width: 100, - child: ListTile( - contentPadding: EdgeInsets.zero, - leading: const Text('Repeat'), - trailing: Transform.scale( - scale: .8, - child: CupertinoSwitch( - value: visitorBloc.repeat, - onChanged: (value) { - visitorBloc.add(ToggleRepeatEvent()); - }, - applyTheme: true, - ), - ), + child: Column( + children: [ + Text('Repeat', + style: Theme.of(context).textTheme.bodySmall!.copyWith( + color: Colors.black,fontSize: 13),), + Transform.scale( + scale: .8, + child: CupertinoSwitch( + value: visitorBloc.repeat, + onChanged: (value) { + visitorBloc.add(ToggleRepeatEvent()); + }, + applyTheme: true, + ), + ), + ], + ), ), - if (visitorBloc.usageFrequencySelected == - 'Periodic' && - visitorBloc.accessTypeSelected == - 'Online Password') + if (visitorBloc.usageFrequencySelected == 'Periodic' && + visitorBloc.accessTypeSelected == 'Online Password') isRepeat ? const RepeatWidget() : const SizedBox(), Container( decoration: containerDecoration, @@ -406,70 +408,40 @@ class VisitorPasswordDialog extends StatelessWidget { child: DefaultButton( borderRadius: 8, onPressed: () { - if (visitorBloc.usageFrequencySelected == - 'One-Time' && - visitorBloc.accessTypeSelected == - 'Online Password') { + if (visitorBloc.usageFrequencySelected == 'One-Time' && + visitorBloc.accessTypeSelected == 'Online Password') { visitorBloc.add( OnlineOneTimePasswordEvent( context: context, - passwordName: visitorBloc - .userNameController.text, - email: visitorBloc - .emailController.text)); - } else if (visitorBloc - .usageFrequencySelected == - 'Periodic' && - visitorBloc.accessTypeSelected == - 'Online Password') { - visitorBloc.add( - OnlineMultipleTimePasswordEvent( - passwordName: - visitorBloc - .userNameController - .text, - email: - visitorBloc - .emailController.text, - effectiveTime: visitorBloc - .effectiveTimeTimeStamp - .toString(), - invalidTime: visitorBloc - .expirationTimeTimeStamp - .toString())); - } else if (visitorBloc - .usageFrequencySelected == - 'One-Time' && - visitorBloc.accessTypeSelected == - 'Offline Password') { - visitorBloc - .add(OfflineOneTimePasswordEvent( - passwordName: visitorBloc - .userNameController.text, - email: - visitorBloc.emailController.text, + passwordName: visitorBloc.userNameController.text, + email: visitorBloc.emailController.text)); + } else if (visitorBloc.usageFrequencySelected == 'Periodic' && + visitorBloc.accessTypeSelected == 'Online Password') { + visitorBloc.add(OnlineMultipleTimePasswordEvent( + passwordName: visitorBloc.userNameController.text, + email: visitorBloc.emailController.text, + effectiveTime: visitorBloc.effectiveTimeTimeStamp.toString(), + invalidTime: visitorBloc.expirationTimeTimeStamp.toString())); + } else if (visitorBloc.usageFrequencySelected == 'One-Time' && + visitorBloc.accessTypeSelected == 'Offline Password') { + visitorBloc.add(OfflineOneTimePasswordEvent( + passwordName: visitorBloc.userNameController.text, + email: visitorBloc.emailController.text, )); - } else if (visitorBloc - .usageFrequencySelected == - 'Periodic' && - visitorBloc.accessTypeSelected == - 'Offline Password') { + } else if (visitorBloc.usageFrequencySelected == 'Periodic' && + visitorBloc.accessTypeSelected == 'Offline Password') { visitorBloc.add( OfflineMultipleTimePasswordEvent( - passwordName: visitorBloc - .userNameController.text, - email: visitorBloc - .emailController.text, - effectiveTime: visitorBloc - .effectiveTimeTimeStamp - .toString(), - invalidTime: visitorBloc - .expirationTimeTimeStamp - .toString())); + passwordName: visitorBloc.userNameController.text, + email: visitorBloc.emailController.text, + effectiveTime: visitorBloc.effectiveTimeTimeStamp.toString(), + invalidTime: visitorBloc.expirationTimeTimeStamp.toString())); } }, - child: const Text( - 'Ok', + child: Text('Ok', + style: Theme.of(context).textTheme.bodySmall!.copyWith( + fontWeight: FontWeight.w400, + color: ColorsManager.whiteColors,fontSize: 16), ), ), ), @@ -531,7 +503,9 @@ class VisitorPasswordDialog extends StatelessWidget { } }, borderRadius: 8, - child: Text('Ok'), + child: Text('Ok', style: Theme.of(context).textTheme.bodySmall!.copyWith( + fontWeight: FontWeight.w400, + color: ColorsManager.whiteColors,fontSize: 16),), ), ), ], diff --git a/lib/utils/constants/assets.dart b/lib/utils/constants/assets.dart index 2c2a6e43..62f991fe 100644 --- a/lib/utils/constants/assets.dart +++ b/lib/utils/constants/assets.dart @@ -25,4 +25,5 @@ class Assets { static const String assetIcon = "assets/images/asset_icon.svg"; static const String calendarIcon = "assets/images/calendar_icon.svg"; static const String deviceNoteIcon = "assets/images/device_note.svg"; + static const String timeIcon = "assets/images/time_icon.svg"; } From f5a7441b3c2af02525bff97868d42363a70320db Mon Sep 17 00:00:00 2001 From: mohammad Date: Thu, 22 Aug 2024 16:52:41 +0300 Subject: [PATCH 26/32] tap filter --- assets/images/empty_table.svg | 23 ++ .../access_management/bloc/access_bloc.dart | 97 +++++-- .../access_management/bloc/access_event.dart | 6 + .../model/password_model.dart | 20 +- .../view/access_management.dart | 147 +++-------- lib/pages/auth/bloc/auth_bloc.dart | 1 - lib/pages/auth/view/login_web_page.dart | 3 +- lib/pages/common/custom_table.dart | 32 ++- lib/pages/common/date_time_widget.dart | 5 +- lib/pages/home/bloc/home_bloc.dart | 2 +- lib/pages/home/view/home_card.dart | 18 +- .../bloc/visitor_password_bloc.dart | 18 +- .../view/add_device_dialog.dart | 4 +- .../view/visitor_password_dialog.dart | 236 +++++++----------- lib/utils/constants/assets.dart | 1 + lib/utils/constants/const.dart | 38 ++- 16 files changed, 333 insertions(+), 318 deletions(-) create mode 100644 assets/images/empty_table.svg diff --git a/assets/images/empty_table.svg b/assets/images/empty_table.svg new file mode 100644 index 00000000..24ac359d --- /dev/null +++ b/assets/images/empty_table.svg @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/lib/pages/access_management/bloc/access_bloc.dart b/lib/pages/access_management/bloc/access_bloc.dart index f586c56f..5b406add 100644 --- a/lib/pages/access_management/bloc/access_bloc.dart +++ b/lib/pages/access_management/bloc/access_bloc.dart @@ -5,23 +5,25 @@ import 'package:syncrow_web/pages/access_management/bloc/access_state.dart'; import 'package:syncrow_web/pages/access_management/model/password_model.dart'; import 'package:syncrow_web/services/access_mang_api.dart'; import 'package:syncrow_web/utils/color_manager.dart'; +import 'package:syncrow_web/utils/constants/const.dart'; import 'package:syncrow_web/utils/snack_bar.dart'; class AccessBloc extends Bloc { AccessBloc() : super((AccessInitial())) { on(_onFetchTableData); - on(selectFilterTap); + // on(selectFilterTap); on(selectTime); on(_filterData); on(resetSearch); + on(onTabChanged); } - String startTime = 'Start Time'; - String endTime = 'End Time'; + String startTime = 'Start Date'; + String endTime = 'End Date'; int? effectiveTimeTimeStamp; int? expirationTimeTimeStamp; TextEditingController passwordName= TextEditingController(); - List filteredData = []; // To store filtered data + List filteredData = []; List data=[]; Future _onFetchTableData( @@ -29,11 +31,26 @@ class AccessBloc extends Bloc { try { emit(AccessLoaded()); data = await AccessMangApi().fetchVisitorPassword(); - emit(TableLoaded(data)); + filteredData= data; + updateTabsCount(); + emit(TableLoaded(data)); } catch (e) { emit(FailedState(e.toString())); } } + void updateTabsCount() { + // Count occurrences based on the type field + int toBeEffectiveCount = data.where((item) => item.passwordStatus.value== 'To Be Effective').length; + int effectiveCount = data.where((item) => item.passwordStatus.value == 'Effective').length; + int expiredCount = data.where((item) => item.passwordStatus.value == 'Expired').length; + + // Update tab labels with counts + tabs[1] = 'To Be Effective ($toBeEffectiveCount)'; + tabs[2] = 'Effective ($effectiveCount)'; + tabs[3] = 'Expired ($expiredCount)'; + } + + int selectedIndex = 0; @@ -49,9 +66,8 @@ class AccessBloc extends Bloc { try { emit(AccessLoaded()); selectedIndex= event.selectedIndex; - emit(AccessInitial()); + emit(AccessInitial()); emit(TableLoaded(data)); - } catch (e) { emit(FailedState( e.toString())); return; @@ -60,6 +76,7 @@ class AccessBloc extends Bloc { Future selectTime(SelectTime event, Emitter emit) async { + final DateTime? picked = await showDatePicker( context: event.context, initialDate: DateTime.now(), @@ -120,27 +137,27 @@ class AccessBloc extends Bloc { } } } - emit(AccessInitial()); - emit(TableLoaded(data)); + emit(ChangeTimeState()); } + Future _filterData(FilterDataEvent event, Emitter emit) async { emit(AccessLoaded()); try { - // Filter the data based on the provided criteria filteredData = data.where((item) { bool matchesCriteria = true; - // Check if the password name should be used for filtering + + // Filter by password name if provided if (event.passwordName != null && event.passwordName!.isNotEmpty) { - final bool matchesName = item.passwodName != null && - item.passwodName.contains(event.passwordName); + final bool matchesName = item.passwordName != null && + item.passwordName.contains(event.passwordName); if (!matchesName) { matchesCriteria = false; } } - // Check if the time range should be used for filtering + + // Filter by date range if provided if (event.startTime != null && event.endTime != null) { - // Ensure effectiveTime and invalidTime are treated as integers final int? effectiveTime = int.tryParse(item.effectiveTime.toString()); final int? invalidTime = int.tryParse(item.invalidTime.toString()); if (effectiveTime == null || invalidTime == null) { @@ -153,22 +170,31 @@ class AccessBloc extends Bloc { } } } + + // Filter by tab selection + if (event.selectedTabIndex == 1 && item.passwordStatus.value != 'To Be Effective') { + matchesCriteria = false; + } else if (event.selectedTabIndex == 2 && item.passwordStatus.value != 'Effective') { + matchesCriteria = false; + } else if (event.selectedTabIndex == 3 && item.passwordStatus.value != 'Expired') { + matchesCriteria = false; + } + return matchesCriteria; }).toList(); - print('Filtered data: $filteredData'); emit(TableLoaded(filteredData)); } catch (e) { - print('Error occurred during filtering: $e'); + emit(FailedState(e.toString())); } } - // ResetSearch + resetSearch(ResetSearch event, Emitter emit) async{ emit(AccessLoaded()); startTime = 'Start Time'; endTime = 'End Time'; passwordName.clear(); + selectedIndex=0; add(FetchTableData()); - } String timestampToDate(dynamic timestamp) { @@ -176,5 +202,38 @@ class AccessBloc extends Bloc { return "${dateTime.year}/${dateTime.month.toString().padLeft(2, '0')}/${dateTime.day.toString().padLeft(2, '0')}"; } + Future onTabChanged(TabChangedEvent event, Emitter emit) async { + try { + emit(AccessLoaded()); + selectedIndex = event.selectedIndex; + // Apply filtering based on selected tab + switch (selectedIndex) { + case 0: // All + filteredData = data; + break; + case 1: // To Be Effective + filteredData = data.where((item) => item.passwordStatus.value == "To Be Effective").toList(); + break; + case 2: // Effective + filteredData = data.where((item) => item.passwordStatus.value == "Effective").toList(); + + break; + case 3: // Expired + filteredData = data.where((item) => item.passwordStatus.value == "Expired").toList(); + break; + default: + filteredData = data; + } + add(FilterDataEvent( + selectedTabIndex: selectedIndex, // Pass the selected tab index + passwordName: passwordName.text.toLowerCase(), + startTime: effectiveTimeTimeStamp, + endTime: expirationTimeTimeStamp + )); + emit(TableLoaded(filteredData)); + } catch (e) { + emit(FailedState(e.toString())); + } + } } diff --git a/lib/pages/access_management/bloc/access_event.dart b/lib/pages/access_management/bloc/access_event.dart index 3a64a11a..f2f631b4 100644 --- a/lib/pages/access_management/bloc/access_event.dart +++ b/lib/pages/access_management/bloc/access_event.dart @@ -31,10 +31,16 @@ class FilterDataEvent extends AccessEvent { final String? passwordName; final int? startTime; final int? endTime; + final int selectedTabIndex; // Add this field const FilterDataEvent({ this.passwordName, this.startTime, this.endTime, + required this.selectedTabIndex, // Initialize this field + }); } + + + diff --git a/lib/pages/access_management/model/password_model.dart b/lib/pages/access_management/model/password_model.dart index acd3e13a..584e9b7e 100644 --- a/lib/pages/access_management/model/password_model.dart +++ b/lib/pages/access_management/model/password_model.dart @@ -6,8 +6,8 @@ class PasswordModel { final dynamic effectiveTime; final dynamic passwordCreated; final dynamic createdTime; - final dynamic passwodName; // New field - final dynamic passwordStatus; + final dynamic passwordName; // New field + final AccessStatus passwordStatus; final AccessType passwordType; final dynamic deviceUuid; @@ -17,8 +17,8 @@ class PasswordModel { this.effectiveTime, this.passwordCreated, this.createdTime, - this.passwodName, // New field - this.passwordStatus, + this.passwordName, // New field + required this.passwordStatus, required this.passwordType, this.deviceUuid, }); @@ -30,9 +30,9 @@ class PasswordModel { effectiveTime: json['effectiveTime'], passwordCreated: json['passwordCreated'], createdTime: json['createdTime'], - passwodName: json['passwodName']??'No name', // New field - passwordStatus: json['passwordStatus'], - passwordType:AccessTypeExtension.fromString(json['passwordType']) , + passwordName: json['passwordName']??'No name', // New field + passwordStatus:AccessStatusExtension.fromString(json['passwordStatus']), + passwordType:AccessTypeExtension.fromString(json['passwordType']), deviceUuid: json['deviceUuid'], ); } @@ -44,13 +44,11 @@ class PasswordModel { 'effectiveTime': effectiveTime, 'passwordCreated': passwordCreated, 'createdTime': createdTime, - 'passwodName': passwodName, // New field + 'passwodName': passwordName, // New field 'passwordStatus': passwordStatus, 'passwordType': passwordType, 'deviceUuid': deviceUuid, }; } - List parsePasswordList(List jsonList) { - return jsonList.map((json) => PasswordModel.fromJson(json)).toList(); - } + } diff --git a/lib/pages/access_management/view/access_management.dart b/lib/pages/access_management/view/access_management.dart index e202194f..38fb545e 100644 --- a/lib/pages/access_management/view/access_management.dart +++ b/lib/pages/access_management/view/access_management.dart @@ -38,14 +38,13 @@ class AccessManagementPage extends StatelessWidget { ], scaffoldBody: BlocProvider(create: (BuildContext context) => AccessBloc()..add(FetchTableData()), child: BlocConsumer(listener: (context, state) { - if (state is FailedState) { - // CustomSnackBar.displaySnackBar( - // state.errorMessage - // ); - } + }, builder: (context, state) { final accessBloc = BlocProvider.of(context); - return Container( + final filteredData = accessBloc.filteredData; + return state is AccessLoaded? + const Center(child: CircularProgressIndicator()): + Container( padding: EdgeInsets.all(30), height: size.height, width: size.width, @@ -61,35 +60,34 @@ class AccessManagementPage extends StatelessWidget { itemCount: BlocProvider.of(context).tabs.length, shrinkWrap: true, itemBuilder: (context, index) { - final isSelected = index == - BlocProvider.of(context).selectedIndex; + final isSelected = index == BlocProvider.of(context).selectedIndex; return InkWell( onTap: () { BlocProvider.of(context).add(TabChangedEvent(index)); }, child: Container( decoration: BoxDecoration( - color: ColorsManager.boxColor, - border: Border.all( - color: isSelected ? Colors.blue : Colors.transparent, - width: 2.0,), - borderRadius: index == 0 - ? const BorderRadius.only( - topLeft: Radius.circular(10), - bottomLeft: Radius.circular(10)) - : index == 3 - ? const BorderRadius.only( - topRight: Radius.circular(10), - bottomRight: Radius.circular(10)) - : null), - padding: const EdgeInsets.only(left: 10,right: 10), + color: ColorsManager.boxColor, + border: Border.all( + color: isSelected ? Colors.blue : Colors.transparent, + width: 2.0, + ), + borderRadius: index == 0 + ? const BorderRadius.only( + topLeft: Radius.circular(10), + bottomLeft: Radius.circular(10)) + : index == 3 + ? const BorderRadius.only( + topRight: Radius.circular(10), + bottomRight: Radius.circular(10)) + : null, + ), + padding: const EdgeInsets.only(left: 10, right: 10), child: Center( child: Text( BlocProvider.of(context).tabs[index], style: TextStyle( - color: isSelected - ? Colors.blue - : Colors.black, + color: isSelected ? Colors.blue : Colors.black, ), ), ), @@ -105,12 +103,15 @@ class AccessManagementPage extends StatelessWidget { Row( children: [ Column( - crossAxisAlignment: CrossAxisAlignment.start, - mainAxisAlignment: MainAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - const Text('Name'), + Text('Name', + style: Theme.of(context).textTheme.bodySmall!.copyWith( + color: Colors.black,fontSize: 13),), + const SizedBox(height: 5,), Container( + height:size.height * 0.053, width: size.width * 0.15, decoration: containerDecoration, child: TextFormField( @@ -118,7 +119,8 @@ class AccessManagementPage extends StatelessWidget { style: const TextStyle(color: Colors.black), decoration: textBoxDecoration()! .copyWith(hintText: 'Please enter'), - )), + ) + ), ], ), const SizedBox( @@ -152,6 +154,7 @@ class AccessManagementPage extends StatelessWidget { child: DefaultButton( onPressed: () { accessBloc.add(FilterDataEvent( + selectedTabIndex: BlocProvider.of(context).selectedIndex, // Pass the selected tab index passwordName: accessBloc.passwordName.text.toLowerCase(), startTime: accessBloc.effectiveTimeTimeStamp, endTime: accessBloc.expirationTimeTimeStamp @@ -234,8 +237,8 @@ class AccessManagementPage extends StatelessWidget { height: 20, ), Expanded( - child: state is TableLoaded - ? DynamicTable( + child: DynamicTable( + isEmpty: filteredData.isEmpty , withCheckBox: false, size: size, cellDecoration: containerDecoration, @@ -243,102 +246,30 @@ class AccessManagementPage extends StatelessWidget { 'Name', 'Access Type', 'Access Period', - 'Device Id', + 'Accessible Device', 'Authorizer', 'Authorization Date & Time', 'Access Status' ], - data: state.data.map((item) { + data: filteredData.map((item) { return [ - item.passwodName.toString(), + item.passwordName.toString(), item.passwordType.value, ('${accessBloc.timestampToDate(item.effectiveTime)} - ${accessBloc.timestampToDate(item.invalidTime)}'), item.deviceUuid.toString(), '', '', - '' + item.passwordStatus.value ]; }).toList(), ) - : const Center(child: CircularProgressIndicator()), + // : const Center(child: CircularProgressIndicator()), ) - ], ), ); }))); } - - - // Container TableWidget(Size size, TableLoaded state,AccessBloc accessBloc) { - // return Container( - // decoration: containerDecoration, - // width: size.width, - // child: Padding( - // padding: const EdgeInsets.all(10.0), - // child: ListView( - // scrollDirection: Axis.horizontal, - // children: [ - // Container( - // width: size.width, - // height: size.height, - // child: Column( - // children: [ - // Container( - // color: ColorsManager.boxColor, - // child: Row( - // children: [ - // _buildTableHeaderCell('Password name'), - // _buildTableHeaderCell(' Password Type'), - // _buildTableHeaderCell('Start Time'), - // _buildTableHeaderCell('End Time'), - // _buildTableHeaderCell('Device Id'), - // // _buildTableHeaderCell('Authorization Source'), - // // _buildTableHeaderCell('Authorizer'), - // _buildTableHeaderCell('Password Created'), - // // _buildTableHeaderCell('Access Status'), - // _buildTableHeaderCell('Password Status'), - // ], - // ), - // ), - // Expanded( - // child: Container( - // width: size.width, - // color: ColorsManager.whiteColors, - // child: ListView( - // shrinkWrap: true, - // children: [ - // Column( - // children: state.data.map((item) { - // return Row( - // children: [ - // _buildTableCell(item.passwodName), - // _buildTableCell(item.passwordType), - // - // _buildTableCell(accessBloc.timestampToDateTime(item.effectiveTime).toString()), - // _buildTableCell(accessBloc.timestampToDateTime(item.invalidTime).toString()), - // _buildTableCell(item.deviceUuid.toString()), - // // _buildTableCell(item.authorizationSource), - // // _buildTableCell(item.authorizer), - // _buildTableCell(item.passwordCreated!=null?accessBloc.timestampToDateTime(item.passwordCreated).toString():'no data'), - // // _buildTableCell(item.accessStatus), - // _buildTableCell(item.passwordStatus.toString()), - // ], - // ); - // }).toList(), - // ), - // ], - // ), - // ), - // ), - // ], - // ), - // ), - // ], - // ), - // ), - // ); - // } } diff --git a/lib/pages/auth/bloc/auth_bloc.dart b/lib/pages/auth/bloc/auth_bloc.dart index 2cc1a033..125c2817 100644 --- a/lib/pages/auth/bloc/auth_bloc.dart +++ b/lib/pages/auth/bloc/auth_bloc.dart @@ -159,7 +159,6 @@ class AuthBloc extends Bloc { loginPasswordController.clear(); emit(LoginSuccess()); } else { - emit(const LoginFailure(error: 'Something went wrong')); } } else { diff --git a/lib/pages/auth/view/login_web_page.dart b/lib/pages/auth/view/login_web_page.dart index b819669e..d8b22d38 100644 --- a/lib/pages/auth/view/login_web_page.dart +++ b/lib/pages/auth/view/login_web_page.dart @@ -166,8 +166,7 @@ class _LoginWebPageState extends State { return DropdownMenuItem( value: region.id, child: SizedBox( - width: size.width*0.06, - + width: size.width*0.08, child: Text(region.name)), ); }).toList(), diff --git a/lib/pages/common/custom_table.dart b/lib/pages/common/custom_table.dart index 86f34124..b7aa71a5 100644 --- a/lib/pages/common/custom_table.dart +++ b/lib/pages/common/custom_table.dart @@ -1,5 +1,7 @@ import 'package:flutter/material.dart'; +import 'package:flutter_svg/flutter_svg.dart'; import 'package:syncrow_web/utils/color_manager.dart'; +import 'package:syncrow_web/utils/constants/assets.dart'; class DynamicTable extends StatefulWidget { final List headers; @@ -8,6 +10,7 @@ class DynamicTable extends StatefulWidget { final BoxDecoration? cellDecoration; final Size size; final bool withCheckBox; + final bool isEmpty; final void Function(bool?)? selectAll; final void Function(int, bool?)? onRowCheckboxChanged; @@ -16,6 +19,7 @@ class DynamicTable extends StatefulWidget { required this.headers, required this.data, required this.size, + required this.isEmpty, required this.withCheckBox, this.headerDecoration, this.cellDecoration, @@ -63,7 +67,8 @@ class _DynamicTableState extends State { decoration: widget.cellDecoration, child: Padding( padding: const EdgeInsets.all(10.0), - child: ListView( + child: + ListView( scrollDirection: Axis.horizontal, children: [ SizedBox( @@ -83,6 +88,29 @@ class _DynamicTableState extends State { ], ), ), + widget.isEmpty? + Expanded( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Row( + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Column( + children: [ + SvgPicture.asset( + Assets.emptyTable + ), + SizedBox(height: 15,), + Text('No Passwords',style: Theme.of(context).textTheme.bodySmall!.copyWith(color:ColorsManager.grayColor ),) + ], + ), + ], + ), + ], + ), + ): Expanded( child: Container( color: Colors.white, @@ -164,7 +192,7 @@ class _DynamicTableState extends State { alignment: Alignment.centerLeft, child: Text( content, - style: const TextStyle(color: Colors.black, fontSize: 12), + style: const TextStyle(color: Colors.black, fontSize: 10,fontWeight: FontWeight.w400), ), ), ); diff --git a/lib/pages/common/date_time_widget.dart b/lib/pages/common/date_time_widget.dart index 39dc582c..55d5b9a9 100644 --- a/lib/pages/common/date_time_widget.dart +++ b/lib/pages/common/date_time_widget.dart @@ -42,11 +42,12 @@ class DateTimeWebWidget extends StatelessWidget { .bodyMedium! .copyWith(color: Colors.red), ), - Text(title??'' , style: Theme.of(context).textTheme.bodySmall!.copyWith( + Text(title??'' , + style: Theme.of(context).textTheme.bodySmall!.copyWith( color: Colors.black,fontSize: 13),), ], ), - SizedBox(height: 8,), + const SizedBox(height: 8,), Container( width: size.width * 0.25, padding: EdgeInsets.all(10), diff --git a/lib/pages/home/bloc/home_bloc.dart b/lib/pages/home/bloc/home_bloc.dart index db233d3c..ebcc6b4e 100644 --- a/lib/pages/home/bloc/home_bloc.dart +++ b/lib/pages/home/bloc/home_bloc.dart @@ -67,7 +67,7 @@ class HomeBloc extends Bloc { color: null, ), HomeItemModel( - title: 'Space\nManagement', + title: 'Space Management', icon: Assets.spaseManagementIcon, active: true, onPress: (context) { diff --git a/lib/pages/home/view/home_card.dart b/lib/pages/home/view/home_card.dart index 287bb4f4..ce152c1c 100644 --- a/lib/pages/home/view/home_card.dart +++ b/lib/pages/home/view/home_card.dart @@ -37,14 +37,16 @@ class HomeCard extends StatelessWidget { child: Row( mainAxisAlignment: MainAxisAlignment.start, children: [ - FittedBox( - fit: BoxFit.scaleDown, - child: Text( - name, - style: const TextStyle( - fontSize: 20, - color: Colors.white, - fontWeight: FontWeight.bold, + Flexible( + child: FittedBox( + fit: BoxFit.scaleDown, + child: Text( + name, + style: const TextStyle( + fontSize: 20, + color: Colors.white, + fontWeight: FontWeight.bold, + ), ), ), ), diff --git a/lib/pages/visitor_password/bloc/visitor_password_bloc.dart b/lib/pages/visitor_password/bloc/visitor_password_bloc.dart index 65d82dda..ff3b57e4 100644 --- a/lib/pages/visitor_password/bloc/visitor_password_bloc.dart +++ b/lib/pages/visitor_password/bloc/visitor_password_bloc.dart @@ -11,9 +11,7 @@ import 'package:syncrow_web/services/access_mang_api.dart'; import 'package:syncrow_web/utils/color_manager.dart'; import 'package:syncrow_web/utils/snack_bar.dart'; -List selectedDevices = []; -// Define the BLoC class VisitorPasswordBloc extends Bloc { VisitorPasswordBloc() : super(VisitorPasswordInitial()) { @@ -42,12 +40,12 @@ class VisitorPasswordBloc final TextEditingController unitNameController = TextEditingController(); final TextEditingController virtualAddressController = TextEditingController(); + List selectedDevices = []; List data = []; List selectedDeviceIds = []; - String effectiveTime = 'Select Time'; - - String expirationTime = 'Select Time'; + String effectiveTime = 'Start Time'; + String expirationTime = 'End Time'; final forgetFormKey = GlobalKey(); @@ -69,8 +67,6 @@ class VisitorPasswordBloc String startTimeAccess = 'Start Time'; String endTimeAccess = 'End Time'; - // DateTime? repeatStartTime=DateTime.now(); - // DateTime? repeatEndTime; selectAccessType( SelectPasswordType event, Emitter emit) { @@ -210,9 +206,6 @@ class VisitorPasswordBloc Emitter emit) async { try { generate7DigitNumber(); - - print('selectedDevices$selectedDevices'); - // emit(DeviceLoaded()); bool res = await AccessMangApi().postOnlineOneTime( email: event.email, password: passwordController, @@ -225,6 +218,7 @@ class VisitorPasswordBloc } emit(TableLoaded(data)); } catch (e) { + emit(FailedState(e.toString())); } } @@ -351,9 +345,9 @@ class VisitorPasswordBloc emit(TableLoaded(event.filteredData)); } - addDeviceToList() { + addDeviceToList(context) { selectedDevices = selectedDeviceIds; - print(selectedDevices); + Navigator.of(context).pop(selectedDevices); // Close the dialog } Future selectTimeOfLinePassword(SelectTimeEvent event, Emitter emit) async { diff --git a/lib/pages/visitor_password/view/add_device_dialog.dart b/lib/pages/visitor_password/view/add_device_dialog.dart index 7f9ca94e..9d7be636 100644 --- a/lib/pages/visitor_password/view/add_device_dialog.dart +++ b/lib/pages/visitor_password/view/add_device_dialog.dart @@ -161,6 +161,7 @@ class AddDeviceDialog extends StatelessWidget { child: state is TableLoaded ? DynamicTable( cellDecoration: containerDecoration, + isEmpty:visitorBloc.data.isEmpty, selectAll: (p0) { visitorBloc.selectedDeviceIds.clear(); for (var item in state.data) { @@ -211,8 +212,7 @@ class AddDeviceDialog extends StatelessWidget { width: size.width * 0.2, child: DefaultButton( onPressed: () { - visitorBloc.addDeviceToList(); - Navigator.of(context).pop(); // Close the dialog + visitorBloc.addDeviceToList(context); }, borderRadius: 8, child: Text('Ok'), diff --git a/lib/pages/visitor_password/view/visitor_password_dialog.dart b/lib/pages/visitor_password/view/visitor_password_dialog.dart index 9998ec3e..1f7e3a14 100644 --- a/lib/pages/visitor_password/view/visitor_password_dialog.dart +++ b/lib/pages/visitor_password/view/visitor_password_dialog.dart @@ -128,10 +128,7 @@ class VisitorPasswordDialog extends StatelessWidget { : visitorBloc.accessTypeSelected, onChanged: (String? value) { if (value != null) { - print(value); - - context - .read() + context.read() .add(SelectPasswordType(value)); } }, @@ -192,22 +189,18 @@ class VisitorPasswordDialog extends StatelessWidget { SizedBox( width: 200, child: RadioListTile( - title: Text('One-Time',style: Theme.of(context).textTheme.bodySmall!.copyWith( + title: Text('One-Time', + style: Theme.of(context).textTheme.bodySmall!.copyWith( color: Colors.black,fontSize: 13),), value: 'One-Time', groupValue: (state is UsageFrequencySelected) ? state.selectedFrequency - : visitorBloc - .usageFrequencySelected, + : visitorBloc.usageFrequencySelected, onChanged: (String? value) { if (value != null) { - print(value); - - context - .read() - .add(SelectUsageFrequency( - value)); + context.read() + .add(SelectUsageFrequency(value)); } }, ), @@ -219,17 +212,14 @@ class VisitorPasswordDialog extends StatelessWidget { style: Theme.of(context).textTheme.bodySmall!.copyWith( color: Colors.black,fontSize: 13),), value: 'Periodic', - groupValue: - (state is UsageFrequencySelected) + groupValue: (state is UsageFrequencySelected) ? state.selectedFrequency - : visitorBloc - .usageFrequencySelected, + : visitorBloc.usageFrequencySelected, onChanged: (String? value) { if (value != null) { context .read() - .add(SelectUsageFrequency( - value)); + .add(SelectUsageFrequency(value)); } }, ), @@ -238,7 +228,8 @@ class VisitorPasswordDialog extends StatelessWidget { ), Text('Within the validity period, each device can be unlocked only once.', style: Theme.of(context).textTheme.bodySmall!.copyWith( - color: ColorsManager.grayColor,fontSize: 9),) + color: ColorsManager.grayColor,fontSize: 9), + ) ], ), const SizedBox( @@ -288,7 +279,7 @@ class VisitorPasswordDialog extends StatelessWidget { ], ), Text( - 'Within the validity period, each device can be unlocked only once.', + 'Within the validity period, each device can be unlocked only once.', style: Theme.of(context).textTheme.bodySmall!.copyWith( fontWeight: FontWeight.w400, color: ColorsManager.grayColor,fontSize: 9),), @@ -326,13 +317,20 @@ class VisitorPasswordDialog extends StatelessWidget { width: size.width * 0.08, child: DefaultButton( onPressed: () { - showDialog( + showDialog( context: context, barrierDismissible: false, builder: (BuildContext context) { return const AddDeviceDialog(); }, - ); + ).then((listDevice) { + if(listDevice!=null){ + print('selectedDevices==$listDevice'); + visitorBloc.selectedDevices = listDevice; + + } + }); + }, borderRadius: 8, child: Text('+ Add Device',style: Theme.of(context).textTheme.bodySmall!.copyWith( @@ -371,135 +369,81 @@ class VisitorPasswordDialog extends StatelessWidget { width: size.width * 0.2, child: DefaultButton( onPressed: () { - print(selectedDevices); if (visitorBloc.forgetFormKey.currentState!.validate()) { - if (selectedDevices.toString() != '[]') { - showDialog( - context: context, - barrierDismissible: false, - builder: (BuildContext context) { - return InfoDialog( - size: size, - title: 'Set Password', - content: - 'This action will update all of the selected\n door locks passwords in the property.\n\nAre you sure you want to continue?', - actions: [ - Container( - decoration: containerDecoration, - width: size.width * 0.1, - child: DefaultButton( - borderRadius: 8, - onPressed: () { - Navigator.of(context) - .pop(); // Close the dialog - }, - backgroundColor: Colors.white, - child: Text( - 'Cancel', - style: Theme.of(context).textTheme.bodySmall!.copyWith( - fontWeight: FontWeight.w400, - color: ColorsManager.blackColor,fontSize: 16), - ), - ), - ), - Container( - decoration: containerDecoration, - width: size.width * 0.1, - child: DefaultButton( - borderRadius: 8, - onPressed: () { - if (visitorBloc.usageFrequencySelected == 'One-Time' && - visitorBloc.accessTypeSelected == 'Online Password') { - visitorBloc.add( - OnlineOneTimePasswordEvent( - context: context, - passwordName: visitorBloc.userNameController.text, - email: visitorBloc.emailController.text)); - } else if (visitorBloc.usageFrequencySelected == 'Periodic' && - visitorBloc.accessTypeSelected == 'Online Password') { - visitorBloc.add(OnlineMultipleTimePasswordEvent( - passwordName: visitorBloc.userNameController.text, - email: visitorBloc.emailController.text, - effectiveTime: visitorBloc.effectiveTimeTimeStamp.toString(), - invalidTime: visitorBloc.expirationTimeTimeStamp.toString())); - } else if (visitorBloc.usageFrequencySelected == 'One-Time' && - visitorBloc.accessTypeSelected == 'Offline Password') { - visitorBloc.add(OfflineOneTimePasswordEvent( - passwordName: visitorBloc.userNameController.text, - email: visitorBloc.emailController.text, - )); - } else if (visitorBloc.usageFrequencySelected == 'Periodic' && - visitorBloc.accessTypeSelected == 'Offline Password') { - visitorBloc.add( - OfflineMultipleTimePasswordEvent( - passwordName: visitorBloc.userNameController.text, - email: visitorBloc.emailController.text, - effectiveTime: visitorBloc.effectiveTimeTimeStamp.toString(), - invalidTime: visitorBloc.expirationTimeTimeStamp.toString())); - } - }, - child: Text('Ok', - style: Theme.of(context).textTheme.bodySmall!.copyWith( - fontWeight: FontWeight.w400, - color: ColorsManager.whiteColors,fontSize: 16), - ), - ), - ), - ], - ); - }, - ); - } else { - showDialog( - context: context, - barrierDismissible: false, - builder: (BuildContext context) { - return AlertDialog( - alignment: Alignment.center, - content: SizedBox( - height: size!.height * 0.15, - child: Column( - children: [ - SizedBox( - child: SvgPicture.asset( - Assets.deviceNoteIcon, - height: 35, - width: 35, - ), - ), - - const SizedBox( - width: 15, - ), - Text( - 'Please select devices to continue', - style: Theme.of(context) - .textTheme - .headlineLarge! - .copyWith( - fontSize: 20, - fontWeight: FontWeight.w400, - color: Colors.black), - ), - ], - ), - ), - actionsAlignment: MainAxisAlignment.center, - actions: [ - TextButton( + showDialog( + context: context, + barrierDismissible: false, + builder: (BuildContext context) { + return InfoDialog( + size: size, + title: 'Set Password', + content: + 'This action will update all of the selected\n door locks passwords in the property.\n\nAre you sure you want to continue?', + actions: [ + Container( + decoration: containerDecoration, + width: size.width * 0.1, + child: DefaultButton( + borderRadius: 8, onPressed: () { Navigator.of(context).pop(); }, - child: Text('OK', + backgroundColor: Colors.white, + child: Text( + 'Cancel', style: Theme.of(context).textTheme.bodySmall!.copyWith( - fontWeight: FontWeight.w400, - color: ColorsManager.whiteColors,fontSize: 16),), + fontWeight: FontWeight.w400, + color: ColorsManager.blackColor,fontSize: 16), + ), ), - ], - ); - }, - ); - } + ), + Container( + decoration: containerDecoration, + width: size.width * 0.1, + child: DefaultButton( + borderRadius: 8, + onPressed: () { + if (visitorBloc.usageFrequencySelected == 'One-Time' && + visitorBloc.accessTypeSelected == 'Online Password') { + visitorBloc.add( + OnlineOneTimePasswordEvent( + context: context, + passwordName: visitorBloc.userNameController.text, + email: visitorBloc.emailController.text)); + } else if (visitorBloc.usageFrequencySelected == 'Periodic' && + visitorBloc.accessTypeSelected == 'Online Password') { + visitorBloc.add(OnlineMultipleTimePasswordEvent( + passwordName: visitorBloc.userNameController.text, + email: visitorBloc.emailController.text, + effectiveTime: visitorBloc.effectiveTimeTimeStamp.toString(), + invalidTime: visitorBloc.expirationTimeTimeStamp.toString())); + } else if (visitorBloc.usageFrequencySelected == 'One-Time' && + visitorBloc.accessTypeSelected == 'Offline Password') { + visitorBloc.add(OfflineOneTimePasswordEvent( + passwordName: visitorBloc.userNameController.text, + email: visitorBloc.emailController.text, + )); + } else if (visitorBloc.usageFrequencySelected == 'Periodic' && + visitorBloc.accessTypeSelected == 'Offline Password') { + visitorBloc.add( + OfflineMultipleTimePasswordEvent( + passwordName: visitorBloc.userNameController.text, + email: visitorBloc.emailController.text, + effectiveTime: visitorBloc.effectiveTimeTimeStamp.toString(), + invalidTime: visitorBloc.expirationTimeTimeStamp.toString())); + } + }, + child: Text('Ok', + style: Theme.of(context).textTheme.bodySmall!.copyWith( + fontWeight: FontWeight.w400, + color: ColorsManager.whiteColors,fontSize: 16), + ), + ), + ), + ], + ); + }, + ); } }, borderRadius: 8, diff --git a/lib/utils/constants/assets.dart b/lib/utils/constants/assets.dart index 62f991fe..f4a2859e 100644 --- a/lib/utils/constants/assets.dart +++ b/lib/utils/constants/assets.dart @@ -26,4 +26,5 @@ class Assets { static const String calendarIcon = "assets/images/calendar_icon.svg"; static const String deviceNoteIcon = "assets/images/device_note.svg"; static const String timeIcon = "assets/images/time_icon.svg"; + static const String emptyTable = "assets/images/empty_table.svg"; } diff --git a/lib/utils/constants/const.dart b/lib/utils/constants/const.dart index 7afa399d..77beff69 100644 --- a/lib/utils/constants/const.dart +++ b/lib/utils/constants/const.dart @@ -1,6 +1,4 @@ - - enum AccessType { onlineOnetime, onlineMultiple, @@ -43,8 +41,6 @@ extension AccessTypeExtension on AccessType { - - enum DeviseStatus { online, offline, @@ -74,6 +70,40 @@ extension OnlineTypeExtension on DeviseStatus { } +enum AccessStatus { + expired , + effective , + toBeEffective, +} + +extension AccessStatusExtension on AccessStatus { + String get value { + switch (this) { + case AccessStatus.expired: + return "Expired"; + case AccessStatus.effective: + return "Effective" ; + case AccessStatus.toBeEffective: + return "To be effective"; + + } + } + + static AccessStatus fromString(String value) { + switch (value) { + case "EXPIRED" : + return AccessStatus.expired; + case "EFFECTIVE" : + return AccessStatus.effective; + case "TO_BE_EFFECTIVE": + return AccessStatus.toBeEffective; + default: + throw ArgumentError("Invalid access type: $value"); + } + } +} + + From cf1a21e121eab6a0399be3a12b70345dc7fd7ede Mon Sep 17 00:00:00 2001 From: mohammad Date: Fri, 23 Aug 2024 22:16:48 +0300 Subject: [PATCH 27/32] Success dialog and Failed dialog changes with VisitorPasswordDialog --- assets/dome.json | 96 -- lib/main.dart | 5 +- .../access_management/bloc/access_bloc.dart | 16 +- .../view/access_management.dart | 82 +- lib/pages/auth/view/login_mobile_page.dart | 2 - lib/pages/common/custom_dialog.dart | 75 ++ lib/pages/common/custom_table.dart | 47 +- lib/pages/common/custom_web_textfield.dart | 29 +- lib/pages/common/hour_picker_dialog.dart | 10 +- lib/pages/common/info_dialog.dart | 2 +- lib/pages/home/view/home_page_web.dart | 4 +- .../bloc/visitor_password_bloc.dart | 120 ++- .../bloc/visitor_password_event.dart | 10 +- .../bloc/visitor_password_state.dart | 2 +- .../view/add_device_dialog.dart | 97 +- .../view/visitor_password_dialog.dart | 897 ++++++++++-------- lib/services/access_mang_api.dart | 69 +- lib/services/auth_api.dart | 12 - lib/web_layout/web_scaffold.dart | 2 - 19 files changed, 792 insertions(+), 785 deletions(-) delete mode 100644 assets/dome.json create mode 100644 lib/pages/common/custom_dialog.dart diff --git a/assets/dome.json b/assets/dome.json deleted file mode 100644 index 91186608..00000000 --- a/assets/dome.json +++ /dev/null @@ -1,96 +0,0 @@ -[ - { - "accessUser": "Ali Doe", - "accessType": "Admin", - "startTime": "2023-08-01", - "endTime": "2023-08-02", - "accessibleDevice": "Smart Door", - "authorizationSource": "System", - "authorizer": "Jane Smith", - "authorizationTime": "2023-08-01 10:00 AM", - "accessStatus": "Granted", - "actions": "View" - }, { - "accessUser": "oamr Doe", - "accessType": "Admin", - "startTime": "2023-08-01", - "endTime": "2023-08-05", - "accessibleDevice": "Smart Door", - "authorizationSource": "System", - "authorizer": "Jane Smith", - "authorizationTime": "2023-08-01 10:00 AM", - "accessStatus": "Granted", - "actions": "View" - }, { - "accessUser": "John Doe", - "accessType": "Admin", - "startTime": "2023-08-01", - "endTime": "2023-08-10", - "accessibleDevice": "Smart Door", - "authorizationSource": "System", - "authorizer": "Jane Smith", - "authorizationTime": "2023-08-01 10:00 AM", - "accessStatus": "Granted", - "actions": "View" - }, - - { - "accessUser": "John Doe", - "accessType": "Admin", - "startTime": "2023-08-01", - "endTime": "2023-10-10", - "accessibleDevice": "Smart Door", - "authorizationSource": "System", - "authorizer": "Jane Smith", - "authorizationTime": "2023-08-01 10:00 AM", - "accessStatus": "Granted", - "actions": "View" - }, - { - "accessUser": "John Doe", - "accessType": "Admin", - "startTime": "2023-03-01", - "endTime": "2023-05-10", - "accessibleDevice": "Smart Door", - "authorizationSource": "System", - "authorizer": "Jane Smith", - "authorizationTime": "2023-08-01 10:00 AM", - "accessStatus": "Granted", - "actions": "View" - }, - { - "accessUser": "John Doe", - "accessType": "Admin", - "startTime": "2023-07-01", - "endTime": "2023-08-10", - "accessibleDevice": "Smart Door", - "authorizationSource": "System", - "authorizer": "Jane Smith", - "authorizationTime": "2023-08-01 10:00 AM", - "accessStatus": "Granted", - "actions": "View" - }, { - "accessUser": "John Doe", - "accessType": "Admin", - "startTime": "2023-01-01", - "endTime": "2023-09-05", - "accessibleDevice": "Smart Door", - "authorizationSource": "System", - "authorizer": "Jane Smith", - "authorizationTime": "2023-08-01 10:00 AM", - "accessStatus": "Granted", - "actions": "View" - }, - { - "accessUser": "Alice Johnson", - "accessType": "User", - "startTime": "2023-08-01", - "endTime": "2023-08-10", - "accessibleDevice": "Smart Lock", - "authorizationSource": "Admin", - "authorizer": "John Doe", - "authorizationTime": "2023-08-02 11:00 AM", - "accessStatus": "Pending", - "actions": "Approve" - } -] diff --git a/lib/main.dart b/lib/main.dart index af74170d..01911d05 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -5,8 +5,7 @@ import 'package:syncrow_web/pages/auth/bloc/auth_bloc.dart'; import 'package:syncrow_web/pages/auth/view/login_page.dart'; import 'package:syncrow_web/pages/home/bloc/home_bloc.dart'; import 'package:syncrow_web/pages/home/view/home_page.dart'; -import 'package:syncrow_web/pages/visitor_password/view/add_device_dialog.dart'; -import 'package:syncrow_web/pages/visitor_password/view/visitor_password_dialog.dart'; +import 'package:syncrow_web/pages/visitor_password/bloc/visitor_password_bloc.dart'; import 'package:syncrow_web/services/locator.dart'; import 'package:syncrow_web/utils/color_manager.dart'; @@ -30,6 +29,8 @@ class MyApp extends StatelessWidget { return MultiBlocProvider( providers: [ BlocProvider(create: (context) => HomeBloc()), + BlocProvider( + create: (context) => VisitorPasswordBloc(),) ], child: MaterialApp( debugShowCheckedModeBanner: false, // Hide debug banner diff --git a/lib/pages/access_management/bloc/access_bloc.dart b/lib/pages/access_management/bloc/access_bloc.dart index 5b406add..4ed61291 100644 --- a/lib/pages/access_management/bloc/access_bloc.dart +++ b/lib/pages/access_management/bloc/access_bloc.dart @@ -39,12 +39,9 @@ class AccessBloc extends Bloc { } } void updateTabsCount() { - // Count occurrences based on the type field int toBeEffectiveCount = data.where((item) => item.passwordStatus.value== 'To Be Effective').length; int effectiveCount = data.where((item) => item.passwordStatus.value == 'Effective').length; int expiredCount = data.where((item) => item.passwordStatus.value == 'Expired').length; - - // Update tab labels with counts tabs[1] = 'To Be Effective ($toBeEffectiveCount)'; tabs[2] = 'Effective ($effectiveCount)'; tabs[3] = 'Expired ($expiredCount)'; @@ -53,7 +50,6 @@ class AccessBloc extends Bloc { int selectedIndex = 0; - final List tabs = [ 'All', 'To Be Effective (0)', @@ -76,7 +72,6 @@ class AccessBloc extends Bloc { Future selectTime(SelectTime event, Emitter emit) async { - final DateTime? picked = await showDatePicker( context: event.context, initialDate: DateTime.now(), @@ -146,8 +141,6 @@ class AccessBloc extends Bloc { try { filteredData = data.where((item) { bool matchesCriteria = true; - - // Filter by password name if provided if (event.passwordName != null && event.passwordName!.isNotEmpty) { final bool matchesName = item.passwordName != null && item.passwordName.contains(event.passwordName); @@ -155,8 +148,6 @@ class AccessBloc extends Bloc { matchesCriteria = false; } } - - // Filter by date range if provided if (event.startTime != null && event.endTime != null) { final int? effectiveTime = int.tryParse(item.effectiveTime.toString()); final int? invalidTime = int.tryParse(item.invalidTime.toString()); @@ -170,8 +161,6 @@ class AccessBloc extends Bloc { } } } - - // Filter by tab selection if (event.selectedTabIndex == 1 && item.passwordStatus.value != 'To Be Effective') { matchesCriteria = false; } else if (event.selectedTabIndex == 2 && item.passwordStatus.value != 'Effective') { @@ -179,7 +168,6 @@ class AccessBloc extends Bloc { } else if (event.selectedTabIndex == 3 && item.passwordStatus.value != 'Expired') { matchesCriteria = false; } - return matchesCriteria; }).toList(); emit(TableLoaded(filteredData)); @@ -206,7 +194,6 @@ class AccessBloc extends Bloc { try { emit(AccessLoaded()); selectedIndex = event.selectedIndex; - // Apply filtering based on selected tab switch (selectedIndex) { case 0: // All filteredData = data; @@ -216,7 +203,6 @@ class AccessBloc extends Bloc { break; case 2: // Effective filteredData = data.where((item) => item.passwordStatus.value == "Effective").toList(); - break; case 3: // Expired filteredData = data.where((item) => item.passwordStatus.value == "Expired").toList(); @@ -225,7 +211,7 @@ class AccessBloc extends Bloc { filteredData = data; } add(FilterDataEvent( - selectedTabIndex: selectedIndex, // Pass the selected tab index + selectedTabIndex: selectedIndex, passwordName: passwordName.text.toLowerCase(), startTime: effectiveTimeTimeStamp, endTime: expirationTimeTimeStamp diff --git a/lib/pages/access_management/view/access_management.dart b/lib/pages/access_management/view/access_management.dart index 38fb545e..6245dcda 100644 --- a/lib/pages/access_management/view/access_management.dart +++ b/lib/pages/access_management/view/access_management.dart @@ -21,17 +21,14 @@ class AccessManagementPage extends StatelessWidget { enableMenuSideba: false, appBarTitle: Row( children: [ - Text( - 'Access Management', + Text('Access Management', style: Theme.of(context).textTheme.headlineLarge, ) ], ), appBarBody: [ - Text( - 'Physical Access', - style: Theme.of(context) - .textTheme + Text('Physical Access', + style: Theme.of(context).textTheme .headlineMedium! .copyWith(color: Colors.white), ), @@ -101,7 +98,9 @@ class AccessManagementPage extends StatelessWidget { height: 20, ), Row( - children: [ + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.end, + textBaseline: TextBaseline.ideographic, children: [ Column( crossAxisAlignment: CrossAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.spaceBetween, @@ -111,7 +110,7 @@ class AccessManagementPage extends StatelessWidget { color: Colors.black,fontSize: 13),), const SizedBox(height: 5,), Container( - height:size.height * 0.053, + height:43, width: size.width * 0.15, decoration: containerDecoration, child: TextFormField( @@ -145,51 +144,44 @@ class AccessManagementPage extends StatelessWidget { ), SizedBox( + height:45, + width: size.width * 0.06, - child: Column( - children: [ - Text(''), - Container( - decoration: containerDecoration, - child: DefaultButton( - onPressed: () { - accessBloc.add(FilterDataEvent( - selectedTabIndex: BlocProvider.of(context).selectedIndex, // Pass the selected tab index - passwordName: accessBloc.passwordName.text.toLowerCase(), - startTime: accessBloc.effectiveTimeTimeStamp, - endTime: accessBloc.expirationTimeTimeStamp - )); - }, borderRadius: 9, - child: const Text('Search'))), - ], - ), + child:Container( + decoration: containerDecoration, + child: DefaultButton( + onPressed: () { + accessBloc.add(FilterDataEvent( + selectedTabIndex: BlocProvider.of(context).selectedIndex, // Pass the selected tab index + passwordName: accessBloc.passwordName.text.toLowerCase(), + startTime: accessBloc.effectiveTimeTimeStamp, + endTime: accessBloc.expirationTimeTimeStamp + )); + }, borderRadius: 9, + child: const Text('Search'))), ), const SizedBox( width: 10, ), SizedBox( + height:45, width: size.width * 0.06, - child: Column( - children: [ - Text(''), - Container( - decoration: containerDecoration, - child: DefaultButton( - onPressed: () { - accessBloc.add(ResetSearch()); - }, - backgroundColor: ColorsManager.whiteColors, - borderRadius: 9, - child: Text( - 'Reset', - style: Theme.of(context) - .textTheme - .bodySmall! - .copyWith(color: Colors.black), - ), - ), + child: Container( + decoration: containerDecoration, + child: DefaultButton( + onPressed: () { + accessBloc.add(ResetSearch()); + }, + backgroundColor: ColorsManager.whiteColors, + borderRadius: 9, + child: Text( + 'Reset', + style: Theme.of(context) + .textTheme + .bodySmall! + .copyWith(color: Colors.black), ), - ], + ), ), ), ], diff --git a/lib/pages/auth/view/login_mobile_page.dart b/lib/pages/auth/view/login_mobile_page.dart index 98402f59..d6544a98 100644 --- a/lib/pages/auth/view/login_mobile_page.dart +++ b/lib/pages/auth/view/login_mobile_page.dart @@ -146,7 +146,6 @@ class LoginMobilePage extends StatelessWidget { textAlign: TextAlign.center, ), ), - isDense: true, style: const TextStyle(color: Colors.black), items:loginBloc.regionList!.map((RegionModel region) { @@ -156,7 +155,6 @@ class LoginMobilePage extends StatelessWidget { ); }).toList(), onChanged: (String? value) { - print(value); }, ), ) diff --git a/lib/pages/common/custom_dialog.dart b/lib/pages/common/custom_dialog.dart new file mode 100644 index 00000000..3b667811 --- /dev/null +++ b/lib/pages/common/custom_dialog.dart @@ -0,0 +1,75 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_svg/flutter_svg.dart'; +import 'package:syncrow_web/utils/color_manager.dart'; + +Future showCustomDialog({ + required BuildContext context, + required String message, + String? title, + String? iconPath, + double? dialogHeight, + double? iconHeight, + double? iconWidth, + VoidCallback? onOkPressed, + bool barrierDismissible = false, required actions, +}) { + return showDialog( + context: context, + barrierDismissible: barrierDismissible, + builder: (BuildContext context) { + final size = MediaQuery.of(context).size; + return AlertDialog( + alignment: Alignment.center, + content: SizedBox( + height: dialogHeight ?? size.height * 0.15, + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + if (iconPath != null) + SvgPicture.asset( + iconPath, + height: iconHeight ?? 35, + width: iconWidth ?? 35, + ), + if (title != null) + Padding( + padding: const EdgeInsets.only(top: 8.0), + child: Text( + title, + style: Theme.of(context).textTheme.headlineLarge!.copyWith( + fontSize: 20, + fontWeight: FontWeight.w400, + color: Colors.black), + ), + ), + Padding( + padding: const EdgeInsets.only(top: 8.0), + child: Text( + message, + style: Theme.of(context) + .textTheme + .bodyMedium! + .copyWith(color: Colors.black), + textAlign: TextAlign.center, + ), + ), + ], + ), + ), + actionsAlignment: MainAxisAlignment.center, + actions: [ + TextButton( + onPressed: onOkPressed ?? () => Navigator.of(context).pop(), + child: Text( + 'OK', + style: Theme.of(context).textTheme.bodySmall!.copyWith( + fontWeight: FontWeight.w400, + color: ColorsManager.blackColor, + fontSize: 16), + ), + ), + ], + ); + }, + ); +} diff --git a/lib/pages/common/custom_table.dart b/lib/pages/common/custom_table.dart index b7aa71a5..a691b673 100644 --- a/lib/pages/common/custom_table.dart +++ b/lib/pages/common/custom_table.dart @@ -15,7 +15,7 @@ class DynamicTable extends StatefulWidget { final void Function(int, bool?)? onRowCheckboxChanged; const DynamicTable({ - Key? key, + super.key, required this.headers, required this.data, required this.size, @@ -25,7 +25,7 @@ class DynamicTable extends StatefulWidget { this.cellDecoration, this.selectAll, this.onRowCheckboxChanged, - }) : super(key: key); + }); @override _DynamicTableState createState() => _DynamicTableState(); @@ -66,7 +66,7 @@ class _DynamicTableState extends State { return Container( decoration: widget.cellDecoration, child: Padding( - padding: const EdgeInsets.all(10.0), + padding: const EdgeInsets.all(2.0), child: ListView( scrollDirection: Axis.horizontal, @@ -102,7 +102,7 @@ class _DynamicTableState extends State { SvgPicture.asset( Assets.emptyTable ), - SizedBox(height: 15,), + const SizedBox(height: 15,), Text('No Passwords',style: Theme.of(context).textTheme.bodySmall!.copyWith(color:ColorsManager.grayColor ),) ], ), @@ -122,9 +122,9 @@ class _DynamicTableState extends State { return Row( children: [ if (widget.withCheckBox) - _buildRowCheckbox(index), + _buildRowCheckbox(index,widget.size.height*0.10), ...row.map((cell) => - _buildTableCell(cell.toString())).toList(), + _buildTableCell(cell.toString(),widget.size.height*0.10)).toList(), ], ); }, @@ -141,8 +141,12 @@ class _DynamicTableState extends State { } Widget _buildSelectAllCheckbox() { - return SizedBox( - width: 50, + return Container( + padding: const EdgeInsets.all(8.0), + + decoration: const BoxDecoration( + border: Border.symmetric( + vertical: BorderSide(color: ColorsManager.boxDivider))), child: Checkbox( value: _selectAll, onChanged: _toggleSelectAll, @@ -150,16 +154,27 @@ class _DynamicTableState extends State { ); } - Widget _buildRowCheckbox(int index) { - return SizedBox( - width: 50, - child: Checkbox( + Widget _buildRowCheckbox(int index,size) { + return Container( + padding: const EdgeInsets.all(8.0), + + height:size , + decoration: const BoxDecoration( + border: Border( + bottom: BorderSide( + color: ColorsManager.boxDivider, + width: 1.0, + ), + )), + alignment: Alignment.centerLeft, + child: Center(child: Checkbox( value: _selected[index], onChanged: (bool? value) { _toggleRowSelection(index, value); }, - ), + ),) ); + } Widget _buildTableHeaderCell(String title) { @@ -177,11 +192,11 @@ class _DynamicTableState extends State { ); } - Widget _buildTableCell(String content) { + Widget _buildTableCell(String content,size) { return Expanded( child: Container( - height: 80, - padding: const EdgeInsets.all(15.0), + height:size , + padding: const EdgeInsets.all(5.0), decoration: const BoxDecoration( border: Border( bottom: BorderSide( diff --git a/lib/pages/common/custom_web_textfield.dart b/lib/pages/common/custom_web_textfield.dart index daaa2c13..363a1994 100644 --- a/lib/pages/common/custom_web_textfield.dart +++ b/lib/pages/common/custom_web_textfield.dart @@ -33,8 +33,7 @@ class CustomWebTextField extends StatelessWidget { children: [ Text('* ', style: Theme.of(context) - .textTheme - .bodyMedium! + .textTheme.bodyMedium! .copyWith(color: Colors.red), ), Text(textFieldName, style: Theme.of(context).textTheme.bodySmall!.copyWith( @@ -46,10 +45,8 @@ class CustomWebTextField extends StatelessWidget { child: Text( description??'', style: Theme.of(context) - .textTheme - .bodySmall! - .copyWith( - fontSize: 9, + .textTheme.bodySmall! + .copyWith(fontSize: 9, fontWeight: FontWeight.w400, color: ColorsManager.textGray), ), @@ -65,21 +62,19 @@ class CustomWebTextField extends StatelessWidget { color: Colors.grey.withOpacity(0.3), spreadRadius:2, blurRadius: 3, - offset: Offset(1, 1), // changes position of shadow + offset: const Offset(1, 1), // changes position of shadow ), ] ), - child: Container( - child: TextFormField( - validator: validator, - controller: controller, - style: const TextStyle(color: Colors.black), - decoration: textBoxDecoration()! - .copyWith( - errorStyle: const TextStyle(height: 0), // Hide the error text space + child: TextFormField( + validator: validator, + controller: controller, + style: const TextStyle(color: Colors.black), + decoration: textBoxDecoration()! + .copyWith( + errorStyle: const TextStyle(height: 0), // Hide the error text space - hintText: 'Please enter'), - ), + hintText: 'Please enter'), ), ), ], diff --git a/lib/pages/common/hour_picker_dialog.dart b/lib/pages/common/hour_picker_dialog.dart index 63e95e21..718f1ebf 100644 --- a/lib/pages/common/hour_picker_dialog.dart +++ b/lib/pages/common/hour_picker_dialog.dart @@ -4,7 +4,7 @@ import 'package:flutter/material.dart'; class HourPickerDialog extends StatefulWidget { final TimeOfDay initialTime; - HourPickerDialog({required this.initialTime}); + const HourPickerDialog({super.key, required this.initialTime}); @override _HourPickerDialogState createState() => _HourPickerDialogState(); @@ -24,7 +24,7 @@ class _HourPickerDialogState extends State { @override Widget build(BuildContext context) { return AlertDialog( - title: Text('Select Hour'), + title: const Text('Select Hour'), content: Row( mainAxisAlignment: MainAxisAlignment.center, children: [ @@ -46,7 +46,7 @@ class _HourPickerDialogState extends State { SizedBox(width: 16.0), DropdownButton( value: _isPm, - items: [ + items: const [ DropdownMenuItem( value: false, child: Text('AM'), @@ -67,14 +67,14 @@ class _HourPickerDialogState extends State { actions: [ TextButton( onPressed: () => Navigator.of(context).pop(null), - child: Text('Cancel'), + child: const Text('Cancel'), ), TextButton( onPressed: () { int hour = _isPm ? _selectedHour + 12 : _selectedHour; Navigator.of(context).pop(TimeOfDay(hour: hour, minute: 0)); }, - child: Text('OK'), + child: const Text('OK'), ), ], ); diff --git a/lib/pages/common/info_dialog.dart b/lib/pages/common/info_dialog.dart index 4465ced6..cfd2cbd4 100644 --- a/lib/pages/common/info_dialog.dart +++ b/lib/pages/common/info_dialog.dart @@ -66,7 +66,7 @@ class InfoDialog extends StatelessWidget { onPressed: () { Navigator.of(context).pop(); }, - child: Text('OK'), + child: const Text('OK'), ), ], ); diff --git a/lib/pages/home/view/home_page_web.dart b/lib/pages/home/view/home_page_web.dart index 0124acad..39a79a57 100644 --- a/lib/pages/home/view/home_page_web.dart +++ b/lib/pages/home/view/home_page_web.dart @@ -2,13 +2,11 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_svg/svg.dart'; import 'package:syncrow_web/pages/home/bloc/home_bloc.dart'; +import 'package:syncrow_web/pages/home/bloc/home_state.dart'; import 'package:syncrow_web/pages/home/view/home_card.dart'; -import 'package:syncrow_web/utils/color_manager.dart'; import 'package:syncrow_web/utils/constants/assets.dart'; import 'package:syncrow_web/web_layout/web_scaffold.dart'; -import '../bloc/home_state.dart'; - class HomeWebPage extends StatelessWidget { HomeWebPage({super.key}); @override diff --git a/lib/pages/visitor_password/bloc/visitor_password_bloc.dart b/lib/pages/visitor_password/bloc/visitor_password_bloc.dart index ff3b57e4..ee40de87 100644 --- a/lib/pages/visitor_password/bloc/visitor_password_bloc.dart +++ b/lib/pages/visitor_password/bloc/visitor_password_bloc.dart @@ -2,6 +2,7 @@ import 'dart:math'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:intl/intl.dart'; +import 'package:syncrow_web/pages/common/custom_dialog.dart'; import 'package:syncrow_web/pages/common/hour_picker_dialog.dart'; import 'package:syncrow_web/pages/visitor_password/bloc/visitor_password_event.dart'; import 'package:syncrow_web/pages/visitor_password/bloc/visitor_password_state.dart'; @@ -9,9 +10,9 @@ import 'package:syncrow_web/pages/visitor_password/model/device_model.dart'; import 'package:syncrow_web/pages/visitor_password/model/schedule_model.dart'; import 'package:syncrow_web/services/access_mang_api.dart'; import 'package:syncrow_web/utils/color_manager.dart'; +import 'package:syncrow_web/utils/constants/assets.dart'; import 'package:syncrow_web/utils/snack_bar.dart'; - class VisitorPasswordBloc extends Bloc { VisitorPasswordBloc() : super(VisitorPasswordInitial()) { @@ -23,10 +24,8 @@ class VisitorPasswordBloc on(toggleDaySelection); on(selectDevice); on(_onUpdateFilteredDevices); - on(postOnlineOneTimePassword); on(postOnlineMultipleTimePassword); - on(postOfflineMultipleTimePassword); on(postOfflineOneTimePassword); on(selectTimeOfLinePassword); @@ -58,16 +57,12 @@ class VisitorPasswordBloc int? effectiveTimeTimeStamp; int? expirationTimeTimeStamp; - int? repeatEffectiveTimeTimeStamp; - int? repeatExpirationTimeTimeStamp; - DateTime? startTime = DateTime.now(); DateTime? endTime; String startTimeAccess = 'Start Time'; String endTimeAccess = 'End Time'; - selectAccessType( SelectPasswordType event, Emitter emit) { accessTypeSelected = event.type; @@ -81,9 +76,9 @@ class VisitorPasswordBloc } Future selectTimeVisitorPassword( - SelectTimeVisitorPassword event, - Emitter emit, - ) async { + SelectTimeVisitorPassword event, + Emitter emit, + ) async { final DateTime? picked = await showDatePicker( context: event.context, initialDate: DateTime.now(), @@ -122,7 +117,8 @@ class VisitorPasswordBloc timePicked.minute, ); - final selectedTimestamp = selectedDateTime.millisecondsSinceEpoch ~/ 1000; + final selectedTimestamp = + selectedDateTime.millisecondsSinceEpoch ~/ 1000; if (event.isStart) { if (expirationTimeTimeStamp != null && @@ -133,9 +129,7 @@ class VisitorPasswordBloc return; } effectiveTimeTimeStamp = selectedTimestamp; - - startTimeAccess = selectedDateTime.toString().split('.').first; - + startTimeAccess = selectedDateTime.toString().split('.').first; } else { if (effectiveTimeTimeStamp != null && selectedTimestamp < effectiveTimeTimeStamp!) { @@ -145,11 +139,8 @@ class VisitorPasswordBloc return; } expirationTimeTimeStamp = selectedTimestamp; - - endTimeAccess = selectedDateTime.toString().split('.').first; - + endTimeAccess = selectedDateTime.toString().split('.').first; } - emit(ChangeTimeState()); emit(VisitorPasswordInitial()); } @@ -201,10 +192,10 @@ class VisitorPasswordBloc } //online password - Future postOnlineOneTimePassword(OnlineOneTimePasswordEvent event, Emitter emit) async { try { + emit(LoadingInitialState()); generate7DigitNumber(); bool res = await AccessMangApi().postOnlineOneTime( email: event.email, @@ -213,27 +204,37 @@ class VisitorPasswordBloc passwordName: event.passwordName, effectiveTime: effectiveTimeTimeStamp.toString(), invalidTime: expirationTimeTimeStamp.toString()); - if (res = true) { - Navigator.pop(event.context!); + if (res == true) { + emit(SuccessState()); + } else { + throw Exception('Failed to create password'); } emit(TableLoaded(data)); } catch (e) { - emit(FailedState(e.toString())); + Navigator.pop(event.context!); + stateDialog( + context: event.context!, + message: e.toString(), + title: 'Something Wrong'); } } + Future postOnlineMultipleTimePassword( OnlineMultipleTimePasswordEvent event, Emitter emit) async { try { - generate7DigitNumber(); - bool res = await AccessMangApi().postOnlineMultipleTime( + emit(LoadingInitialState()); + + await generate7DigitNumber(); + bool res = await AccessMangApi().postOnlineMultipleTime( scheduleList: [ if (repeat) Schedule( effectiveTime: getTimeFromDateTimeString(expirationTime), - invalidTime: getTimeFromDateTimeString(effectiveTime).toString(), + invalidTime: + getTimeFromDateTimeString(effectiveTime).toString(), workingDay: selectedDays, ), ], @@ -243,9 +244,11 @@ class VisitorPasswordBloc email: event.email, devicesUuid: selectedDevices, passwordName: event.passwordName); - if(res==true){ - Navigator.pop(event.context!); + if (res == true) { + emit(SuccessState()); + emit(TableLoaded(data)); } + } catch (e) { emit(FailedState(e.toString())); } @@ -255,11 +258,16 @@ class VisitorPasswordBloc Future postOfflineOneTimePassword(OfflineOneTimePasswordEvent event, Emitter emit) async { try { - generate7DigitNumber(); - await AccessMangApi().postOffLineOneTime( + emit(LoadingInitialState()); + await generate7DigitNumber(); + bool res = await AccessMangApi().postOffLineOneTime( email: event.email, devicesUuid: selectedDevices, passwordName: event.passwordName); + if (res == true) { + emit(SuccessState()); + emit(TableLoaded(data)); + } } catch (e) { emit(FailedState(e.toString())); } @@ -269,15 +277,19 @@ class VisitorPasswordBloc OfflineMultipleTimePasswordEvent event, Emitter emit) async { try { - generate7DigitNumber(); - await AccessMangApi().postOffLineMultipleTime( - email: event.email, - devicesUuid: selectedDevices, - passwordName: event.passwordName, + emit(LoadingInitialState()); + await generate7DigitNumber(); + bool res = await AccessMangApi().postOffLineMultipleTime( + email: event.email, + devicesUuid: selectedDevices, + passwordName: event.passwordName, invalidTime: expirationTimeTimeStamp.toString(), effectiveTime: effectiveTimeTimeStamp.toString(), - ); + if (res == true) { + emit(SuccessState()); + emit(TableLoaded(data)); + } } catch (e) { emit(FailedState(e.toString())); } @@ -300,13 +312,11 @@ class VisitorPasswordBloc } Future generate7DigitNumber() async { - emit(LoadingInitialState()); passwordController = ''; Random random = Random(); int min = 1000000; int max = 9999999; passwordController = (min + random.nextInt(max - min + 1)).toString(); - emit(GeneratePasswordState()); return passwordController; } @@ -319,12 +329,10 @@ class VisitorPasswordBloc final deviceName = deviceNameController.text.toLowerCase(); final deviceId = deviceIdController.text.toLowerCase(); final unitName = unitNameController.text.toLowerCase(); - final filteredData = data.where((device) { final matchesDeviceName = device.name.toLowerCase().contains(deviceName); final matchesDeviceId = device.uuid.toLowerCase().contains(deviceId); // final matchesUnitName = device.unitName.toLowerCase().contains(unitName); // Assuming unitName is a property of the device - return matchesDeviceName && matchesDeviceId; }).toList(); add(UpdateFilteredDevicesEvent(filteredData)); @@ -334,7 +342,6 @@ class VisitorPasswordBloc Stream mapEventToState( VisitorPasswordEvent event) async* { if (event is FetchDevice) { - // Fetching logic... } else if (event is UpdateFilteredDevicesEvent) { yield TableLoaded(event.filteredData); } @@ -347,10 +354,11 @@ class VisitorPasswordBloc addDeviceToList(context) { selectedDevices = selectedDeviceIds; - Navigator.of(context).pop(selectedDevices); // Close the dialog + Navigator.of(context).pop(selectedDevices); } - Future selectTimeOfLinePassword(SelectTimeEvent event, Emitter emit) async { + Future selectTimeOfLinePassword( + SelectTimeEvent event, Emitter emit) async { emit(ChangeTimeState()); final DateTime? picked = await showDatePicker( context: event.context, @@ -416,11 +424,10 @@ class VisitorPasswordBloc endTime = event.val; } } + DateTime? convertStringToDateTime(String dateTimeString) { try { - // Define the input format. Adjust this pattern based on your input string format. final DateFormat inputFormat = DateFormat('yyyy-MM-dd HH:mm:ss'); - // Convert the string to a DateTime object. DateTime dateTime = inputFormat.parse(dateTimeString); return dateTime; } catch (e) { @@ -432,8 +439,6 @@ class VisitorPasswordBloc String getTimeFromDateTimeString(String dateTimeString) { DateTime? dateTime = convertStringToDateTime(dateTimeString); if (dateTime == null) return ''; - - // Extract the time component from the DateTime object. return DateFormat('HH:mm').format(dateTime); } @@ -444,4 +449,27 @@ class VisitorPasswordBloc return null; } + Future stateDialog({ + BuildContext? context, + String? message, + String? title, + dynamic actions, + }) { + return showCustomDialog( + context: context!, + message: message!, + iconPath: Assets.deviceNoteIcon, + title: title, + dialogHeight: 150, + actions: actions ?? + [ + TextButton( + onPressed: () { + Navigator.of(context).pop(); + }, + child: const Text('OK'), + ), + ], + ); + } } diff --git a/lib/pages/visitor_password/bloc/visitor_password_event.dart b/lib/pages/visitor_password/bloc/visitor_password_event.dart index 723ac052..05ad73d1 100644 --- a/lib/pages/visitor_password/bloc/visitor_password_event.dart +++ b/lib/pages/visitor_password/bloc/visitor_password_event.dart @@ -77,11 +77,12 @@ class OnlineMultipleTimePasswordEvent extends VisitorPasswordEvent { //offline password class OfflineOneTimePasswordEvent extends VisitorPasswordEvent { + final BuildContext? context; final String? email; final String? passwordName; - const OfflineOneTimePasswordEvent({this.email,this.passwordName}); + const OfflineOneTimePasswordEvent({this.email,this.passwordName,this.context}); @override - List get props => [email!,passwordName!,]; + List get props => [email!,passwordName!,context!,]; } class OfflineMultipleTimePasswordEvent extends VisitorPasswordEvent { @@ -89,11 +90,12 @@ class OfflineMultipleTimePasswordEvent extends VisitorPasswordEvent { final String? passwordName; final String? invalidTime; final String? effectiveTime; + final BuildContext? context; - const OfflineMultipleTimePasswordEvent({this.email,this.passwordName,this.invalidTime,this.effectiveTime}); + const OfflineMultipleTimePasswordEvent({this.context,this.email,this.passwordName,this.invalidTime,this.effectiveTime}); @override - List get props => [email!,passwordName!,invalidTime!,effectiveTime!]; + List get props => [email!,passwordName!,invalidTime!,effectiveTime!,context!]; } diff --git a/lib/pages/visitor_password/bloc/visitor_password_state.dart b/lib/pages/visitor_password/bloc/visitor_password_state.dart index 27008a30..279c9809 100644 --- a/lib/pages/visitor_password/bloc/visitor_password_state.dart +++ b/lib/pages/visitor_password/bloc/visitor_password_state.dart @@ -43,7 +43,7 @@ class LoadingInitialState extends VisitorPasswordState {} class ChangeTimeState extends VisitorPasswordState {} class TimeSelectedState extends VisitorPasswordState {} class DeviceLoaded extends VisitorPasswordState {} -class GeneratePasswordState extends VisitorPasswordState {} +class SuccessState extends VisitorPasswordState {} class FailedState extends VisitorPasswordState { final String message; diff --git a/lib/pages/visitor_password/view/add_device_dialog.dart b/lib/pages/visitor_password/view/add_device_dialog.dart index 9d7be636..eccdfd78 100644 --- a/lib/pages/visitor_password/view/add_device_dialog.dart +++ b/lib/pages/visitor_password/view/add_device_dialog.dart @@ -12,7 +12,6 @@ import 'package:syncrow_web/utils/constants/assets.dart'; import 'package:syncrow_web/utils/constants/const.dart'; import 'package:syncrow_web/utils/style.dart'; - class AddDeviceDialog extends StatelessWidget { const AddDeviceDialog({super.key}); @override @@ -54,7 +53,7 @@ class AddDeviceDialog extends StatelessWidget { width: 15, ), ), - SizedBox(width: 10,), + const SizedBox(width: 10,), Text('Only online accessible devices can be added', style: Theme.of(context).textTheme.bodySmall!.copyWith( fontWeight: FontWeight.w400, @@ -63,13 +62,15 @@ class AddDeviceDialog extends StatelessWidget { ], ) ), - SizedBox(height: 20,), + const SizedBox(height: 20,), Row( - mainAxisAlignment: MainAxisAlignment.center, - crossAxisAlignment: CrossAxisAlignment.center, + mainAxisAlignment: MainAxisAlignment.end, + crossAxisAlignment: CrossAxisAlignment.end, + textBaseline: TextBaseline.alphabetic, + children: [ Expanded( - flex: 2, + flex: 4, child: CustomWebTextField( controller: visitorBloc.deviceNameController, isRequired: true, @@ -79,7 +80,7 @@ class AddDeviceDialog extends StatelessWidget { ), const SizedBox(width: 10), Expanded( - flex: 2, + flex: 4, child: CustomWebTextField( controller: visitorBloc.deviceIdController, isRequired: true, @@ -89,7 +90,7 @@ class AddDeviceDialog extends StatelessWidget { ), const SizedBox(width: 10), Expanded( - flex: 2, + flex: 4, child: CustomWebTextField( controller: visitorBloc.unitNameController, isRequired: true, @@ -97,67 +98,49 @@ class AddDeviceDialog extends StatelessWidget { description: '', ), ), - const SizedBox(width: 10), - Column( - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - const SizedBox(height: 25), - Center( - child: Container( - height: 43, - width: 100, - decoration: containerDecoration, - child: Center( - child: DefaultButton( - onPressed: () { - visitorBloc.filterDevices(); // Call filter function - }, - borderRadius: 9, - child: const Text('Search'), - ), + Expanded( + flex: 2, + child: Container( + child: SizedBox( + width: size.width * 0.06, + child: Center( + child: DefaultButton( + onPressed: () { + visitorBloc.filterDevices(); + }, + borderRadius: 9, + child: const Text('Search'), ), ), ), - - ], + ), ), const SizedBox(width: 10), - Column( - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - const SizedBox(height: 25), - - Center( - child: Container( - height: 43, - width: 100, - decoration: containerDecoration, - child: Center( - child: DefaultButton( - backgroundColor: ColorsManager.whiteColors, - borderRadius: 9, - child: Text( - 'Reset', - style: Theme.of(context).textTheme.bodySmall!.copyWith(color: Colors.black), - ), - onPressed: () { - visitorBloc.deviceNameController.clear(); - visitorBloc.deviceIdController.clear(); - visitorBloc.unitNameController.clear(); - visitorBloc.add(FetchDevice()); // Reset to original list - }, - ), - ), + Expanded( + flex: 2, + child: Container( + width: size.width * 0.06, + child: DefaultButton( + backgroundColor: ColorsManager.whiteColors, + borderRadius: 9, + child: Text('Reset', + style: Theme.of(context).textTheme.bodySmall!.copyWith(color: Colors.black), ), + onPressed: () { + visitorBloc.deviceNameController.clear(); + visitorBloc.deviceIdController.clear(); + visitorBloc.unitNameController.clear(); + visitorBloc.add(FetchDevice()); // Reset to original list + }, ), - ], - ), - + ), + ) ], ), const SizedBox(height: 20), Expanded( + flex: 3, child: state is TableLoaded ? DynamicTable( cellDecoration: containerDecoration, diff --git a/lib/pages/visitor_password/view/visitor_password_dialog.dart b/lib/pages/visitor_password/view/visitor_password_dialog.dart index 1f7e3a14..0aa63638 100644 --- a/lib/pages/visitor_password/view/visitor_password_dialog.dart +++ b/lib/pages/visitor_password/view/visitor_password_dialog.dart @@ -1,11 +1,10 @@ import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; -import 'package:flutter_svg/flutter_svg.dart'; +import 'package:flutter_svg/svg.dart'; import 'package:syncrow_web/pages/common/custom_web_textfield.dart'; import 'package:syncrow_web/pages/common/date_time_widget.dart'; import 'package:syncrow_web/pages/common/default_button.dart'; -import 'package:syncrow_web/pages/common/info_dialog.dart'; import 'package:syncrow_web/pages/visitor_password/bloc/visitor_password_bloc.dart'; import 'package:syncrow_web/pages/visitor_password/bloc/visitor_password_event.dart'; import 'package:syncrow_web/pages/visitor_password/bloc/visitor_password_state.dart'; @@ -21,441 +20,517 @@ class VisitorPasswordDialog extends StatelessWidget { @override Widget build(BuildContext context) { Size size = MediaQuery.of(context).size; + var text = Theme.of(context).textTheme.bodySmall!.copyWith( + color: Colors.black,fontSize: 13); return BlocProvider( create: (context) => VisitorPasswordBloc(), - child: BlocBuilder( - builder: (BuildContext context, VisitorPasswordState state) { + child: BlocListener( + listener: (context, state) { final visitorBloc = BlocProvider.of(context); - bool isRepeat = - state is IsRepeatState ? state.repeat : visitorBloc.repeat; - return AlertDialog( - backgroundColor: Colors.white, - title: Text( - 'Create visitor password', - style: Theme.of(context).textTheme.headlineLarge!.copyWith( - fontWeight: FontWeight.w400, - fontSize: 24, - color: Colors.black), - ), - content: SingleChildScrollView( - child: Form( - key: visitorBloc.forgetFormKey, - child: Padding( - padding: const EdgeInsets.all(5.0), - child: ListBody( - children: [ - Container( - child: Row( - children: [ - Expanded( - flex: 2, - child: CustomWebTextField( - validator: visitorBloc.validate, - controller: visitorBloc.userNameController, - isRequired: true, - textFieldName: 'Name', - description: '', - ), - ), - const Spacer(), - Expanded( - flex: 2, - child: CustomWebTextField( - validator: visitorBloc.validateEmail, - controller: visitorBloc.emailController, - isRequired: true, - textFieldName: 'Email Address', - description: - 'The password will be sent to the visitor’s email address.', - ), - ), - const Spacer(), - ], - ), - ), - const SizedBox( - height: 15, - ), - Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Row( + if (state is SuccessState) { + visitorBloc.stateDialog( + context: context, + message: 'Password Created Successfully', + title: 'Send Success', + ); + } else if (state is FailedState) { + visitorBloc.stateDialog( + context: context, + message: state.message, + title: 'Something Wrong', + ); + } + }, + child: BlocBuilder( + builder: (BuildContext context, VisitorPasswordState state) { + final visitorBloc = BlocProvider.of(context); + bool isRepeat = state is IsRepeatState ? state.repeat : visitorBloc.repeat; + return AlertDialog( + backgroundColor: Colors.white, + title: Text( + 'Create visitor password', + style: Theme.of(context).textTheme.headlineLarge!.copyWith( + fontWeight: FontWeight.w400, + fontSize: 24, + color: Colors.black), + ), + content: + state is LoadingInitialState ?const Center(child: CircularProgressIndicator()): + SingleChildScrollView( + child: Form( + key: visitorBloc.forgetFormKey, + child: Padding( + padding: const EdgeInsets.all(5.0), + child: ListBody( + children: [ + Container( + child: Row( children: [ - Text( - '* ', - style: Theme.of(context) - .textTheme - .bodyMedium! - .copyWith(color: Colors.red), + Expanded( + flex: 2, + child: CustomWebTextField( + validator: visitorBloc.validate, + controller: visitorBloc.userNameController, + isRequired: true, + textFieldName: 'Name', + description: '', + ), ), - Text('Access Type', - style: Theme.of(context).textTheme.bodySmall!.copyWith( - color: Colors.black,fontSize: 13),), + const Spacer(), + Expanded( + flex: 2, + child: CustomWebTextField( + validator: visitorBloc.validateEmail, + controller: visitorBloc.emailController, + isRequired: true, + textFieldName: 'Email Address', + description: + 'The password will be sent to the visitor’s email address.', + ), + ), + const Spacer(), ], ), - Row( - children: [ - SizedBox( - width: size.width * 0.15, - child: RadioListTile( - title: Text('Online Password', - style: Theme.of(context).textTheme.bodySmall!.copyWith( - color: Colors.black,fontSize: 13), - ), - value: 'Online Password', - groupValue: (state is PasswordTypeSelected) - ? state.selectedType - : visitorBloc.accessTypeSelected, - onChanged: (String? value) { - if (value != null) { - print(value); - context - .read() - .add(SelectPasswordType(value)); - } - }, - ), - ), - SizedBox( - width: size.width * 0.15, - child: RadioListTile( - title: Text('Offline Password', - style: Theme.of(context).textTheme.bodySmall!.copyWith( - color: Colors.black,fontSize: 13),), - value: 'Offline Password', - groupValue: (state is PasswordTypeSelected) - ? state.selectedType - : visitorBloc.accessTypeSelected, - onChanged: (String? value) { - if (value != null) { - context.read() - .add(SelectPasswordType(value)); - } - }, - ), - ), - SizedBox( - width: size.width * 0.15, - child: RadioListTile( - title: Text('Dynamic Password', - style: Theme.of(context).textTheme.bodySmall!.copyWith( - color: Colors.black,fontSize: 13),), - value: 'Dynamic Password', - groupValue: (state is PasswordTypeSelected) - ? state.selectedType - : visitorBloc.accessTypeSelected, - onChanged: (String? value) { - if (value != null) { - context - .read() - .add(SelectPasswordType(value)); - visitorBloc.usageFrequencySelected = ''; - } - }, - ), - ), - ], - ), - Text( - 'Only currently online devices can be selected. It is recommended to use when the device network is stable, and the system randomly generates a digital password', - style: Theme.of(context).textTheme.bodySmall!.copyWith( - fontWeight: FontWeight.w400, - color: ColorsManager.grayColor,fontSize: 9),), - const SizedBox( - height: 20, - ) - ], - ), - visitorBloc.accessTypeSelected == 'Dynamic Password' - ? SizedBox() - : Column( - crossAxisAlignment: CrossAxisAlignment.start, + ), + const SizedBox( + height: 15, + ), + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( children: [ - Row( - children: [ - Text( - '* ', - style: Theme.of(context) - .textTheme - .bodyMedium! - .copyWith(color: Colors.red), - ), - Text('Usage Frequency',style: Theme.of(context).textTheme.bodySmall!.copyWith( - color: Colors.black,fontSize: 13),), - ], + Text( + '* ', + style: Theme.of(context).textTheme + .bodyMedium!.copyWith(color: Colors.red), ), - Row( - children: [ - SizedBox( - width: 200, - child: RadioListTile( - title: Text('One-Time', - style: Theme.of(context).textTheme.bodySmall!.copyWith( - color: Colors.black,fontSize: 13),), - value: 'One-Time', - groupValue: - (state is UsageFrequencySelected) - ? state.selectedFrequency - : visitorBloc.usageFrequencySelected, - onChanged: (String? value) { - if (value != null) { - context.read() - .add(SelectUsageFrequency(value)); - } - }, - ), - ), - SizedBox( - width: 200, - child: RadioListTile( - title: Text('Periodic', - style: Theme.of(context).textTheme.bodySmall!.copyWith( - color: Colors.black,fontSize: 13),), - value: 'Periodic', - groupValue: (state is UsageFrequencySelected) - ? state.selectedFrequency - : visitorBloc.usageFrequencySelected, - onChanged: (String? value) { - if (value != null) { - context - .read() - .add(SelectUsageFrequency(value)); - } - }, - ), - ), - ], - ), - Text('Within the validity period, each device can be unlocked only once.', - style: Theme.of(context).textTheme.bodySmall!.copyWith( - color: ColorsManager.grayColor,fontSize: 9), - ) + Text('Access Type', + style:text ), ], ), - const SizedBox( - height: 20, - ), - if ((visitorBloc.usageFrequencySelected != 'One-Time' || - visitorBloc.accessTypeSelected != 'Offline Password') && - (visitorBloc.usageFrequencySelected != '')) - DateTimeWebWidget( - isTime: false, - isRequired: true, - title: 'Access Period', - size: size, - endTime: () { - visitorBloc.add(SelectTimeVisitorPassword( - context: context, - isStart: false, - isRepeat: false)); - }, - startTime: () { - visitorBloc.add(SelectTimeVisitorPassword( - context: context, - isStart: true, - isRepeat: false)); - }, - firstString: visitorBloc.startTimeAccess.toString(), - secondString: visitorBloc.endTimeAccess.toString(), + Row( + children: [ + SizedBox( + width: size.width * 0.15, + child: RadioListTile( + title: Text('Online Password', + style: text, + ), + value: 'Online Password', + groupValue: (state is PasswordTypeSelected) + ? state.selectedType + : visitorBloc.accessTypeSelected, + onChanged: (String? value) { + if (value != null) { + context.read() + .add(SelectPasswordType(value)); + } + }, + ), + ), + SizedBox( + width: size.width * 0.15, + child: RadioListTile( + title: Text('Offline Password', + style:text ), + value: 'Offline Password', + groupValue: (state is PasswordTypeSelected) + ? state.selectedType + : visitorBloc.accessTypeSelected, + onChanged: (String? value) { + if (value != null) { + context.read().add(SelectPasswordType(value)); + } + }, + ), + ), + SizedBox( + width: size.width * 0.15, + child: RadioListTile( + title: Text('Dynamic Password', + style: text,), + value: 'Dynamic Password', + groupValue: (state is PasswordTypeSelected) + ? state.selectedType + : visitorBloc.accessTypeSelected, + onChanged: (String? value) { + if (value != null) { + context.read() + .add(SelectPasswordType(value)); + visitorBloc.usageFrequencySelected = ''; + } + }, + ), + ), + ], + ), + Text( + 'Only currently online devices can be selected. It is recommended to use when the device network is stable, and the system randomly generates a digital password', + style: Theme.of(context).textTheme.bodySmall!.copyWith( + fontWeight: FontWeight.w400, + color: ColorsManager.grayColor,fontSize: 9),), + const SizedBox( + height: 20, + ) + ], ), - const SizedBox( - height: 20, - ), - Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Row( - children: [ - Text( - '* ', - style: Theme.of(context) - .textTheme - .bodyMedium! - .copyWith(color: Colors.red), - ), - Text('Access Devices', - style: Theme.of(context).textTheme.bodySmall!.copyWith( - color: Colors.black,fontSize: 13),), - ], + visitorBloc.accessTypeSelected == 'Dynamic Password' + ? const SizedBox() + : Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + Text('* ', + style: Theme.of(context).textTheme.bodyMedium! + .copyWith(color: Colors.red), + ), + Text('Usage Frequency',style:text ,), + ], + ), + Row( + children: [ + SizedBox( + width: 200, + child: RadioListTile( + title: Text('One-Time', + style:text ,), + value: 'One-Time', + groupValue: + (state is UsageFrequencySelected) + ? state.selectedFrequency + : visitorBloc.usageFrequencySelected, + onChanged: (String? value) { + if (value != null) { + context.read() + .add(SelectUsageFrequency(value)); + } + }, + ), + ), + SizedBox( + width: 200, + child: RadioListTile( + title: Text('Periodic', + style: text), + value: 'Periodic', + groupValue: (state is UsageFrequencySelected) + ? state.selectedFrequency + : visitorBloc.usageFrequencySelected, + onChanged: (String? value) { + if (value != null) { + context.read() + .add(SelectUsageFrequency(value)); + } + }, + ), + ), + ], + ), + Text('Within the validity period, each device can be unlocked only once.', + style: Theme.of(context).textTheme.bodySmall!.copyWith( + color: ColorsManager.grayColor,fontSize: 9), + ) + ], + ), + const SizedBox( + height: 20, + ), + if ((visitorBloc.usageFrequencySelected != 'One-Time' || + visitorBloc.accessTypeSelected != 'Offline Password') && + (visitorBloc.usageFrequencySelected != '')) + DateTimeWebWidget( + isTime: false, + isRequired: true, + title: 'Access Period', + size: size, + endTime: () { + visitorBloc.add(SelectTimeVisitorPassword( + context: context, + isStart: false, + isRepeat: false)); + }, + startTime: () { + visitorBloc.add(SelectTimeVisitorPassword( + context: context, + isStart: true, + isRepeat: false)); + }, + firstString: visitorBloc.startTimeAccess.toString(), + secondString: visitorBloc.endTimeAccess.toString(), ), - Text( - 'Within the validity period, each device can be unlocked only once.', - style: Theme.of(context).textTheme.bodySmall!.copyWith( - fontWeight: FontWeight.w400, - color: ColorsManager.grayColor,fontSize: 9),), - const SizedBox( - height: 20, - ), - if (visitorBloc.usageFrequencySelected == 'Periodic' && - visitorBloc.accessTypeSelected == 'Online Password') - SizedBox( - width: 100, - child: Column( - children: [ - Text('Repeat', - style: Theme.of(context).textTheme.bodySmall!.copyWith( - color: Colors.black,fontSize: 13),), - Transform.scale( - scale: .8, - child: CupertinoSwitch( - value: visitorBloc.repeat, - onChanged: (value) { - visitorBloc.add(ToggleRepeatEvent()); - }, - applyTheme: true, - ), - ), - ], + const SizedBox( + height: 20, + ), + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + Text('* ', + style: Theme.of(context).textTheme.bodyMedium! + .copyWith(color: Colors.red), + ), + Text('Access Devices', + style:text ,), + ], + ), + Text( + 'Within the validity period, each device can be unlocked only once.', + style: Theme.of(context).textTheme.bodySmall!.copyWith( + fontWeight: FontWeight.w400, + color: ColorsManager.grayColor,fontSize: 9),), + const SizedBox( + height: 20, + ), + if (visitorBloc.usageFrequencySelected == 'Periodic' && + visitorBloc.accessTypeSelected == 'Online Password') + SizedBox( + width: 100, + child: Column( + children: [ + Text('Repeat', + style:text), + Transform.scale( + scale: .8, + child: CupertinoSwitch( + value: visitorBloc.repeat, + onChanged: (value) { + visitorBloc.add(ToggleRepeatEvent()); + }, + applyTheme: true, + ), + ), + ], + ), + ), + if (visitorBloc.usageFrequencySelected == 'Periodic' && + visitorBloc.accessTypeSelected == 'Online Password') + isRepeat ? const RepeatWidget() : const SizedBox(), + Container( + decoration: containerDecoration, + width: size.width * 0.08, + child: DefaultButton( + onPressed: () { + showDialog( + context: context, + barrierDismissible: false, + builder: (BuildContext context) { + return const AddDeviceDialog(); + }, + ).then((listDevice) { + if(listDevice!=null){ + visitorBloc.selectedDevices = listDevice; + } + }); + }, + borderRadius: 8, + child: Text('+ Add Device',style: Theme.of(context).textTheme.bodySmall!.copyWith( + fontWeight: FontWeight.w400, + color: ColorsManager.whiteColors,fontSize: 12),), ), ), - if (visitorBloc.usageFrequencySelected == 'Periodic' && - visitorBloc.accessTypeSelected == 'Online Password') - isRepeat ? const RepeatWidget() : const SizedBox(), - Container( - decoration: containerDecoration, - width: size.width * 0.08, - child: DefaultButton( - onPressed: () { - showDialog( - context: context, - barrierDismissible: false, - builder: (BuildContext context) { - return const AddDeviceDialog(); - }, - ).then((listDevice) { - if(listDevice!=null){ - print('selectedDevices==$listDevice'); - visitorBloc.selectedDevices = listDevice; + ], + ), + ], + ), + ), + ), + ), + actionsAlignment: MainAxisAlignment.center, + actions: [ + Container( + decoration: containerDecoration, + width: size.width * 0.2, + child: DefaultButton( + borderRadius: 8, + onPressed: () { + Navigator.of(context).pop(); // Close the dialog + }, + backgroundColor: Colors.white, + child: Text( + 'Cancel', + style: Theme.of(context).textTheme.bodySmall!.copyWith( + fontWeight: FontWeight.w400, + color: ColorsManager.blackColor,fontSize: 16), + ), + ), + ), + Container( + decoration: containerDecoration, + width: size.width * 0.2, + child: DefaultButton( + onPressed: () { + if (visitorBloc.forgetFormKey.currentState!.validate()) { + if(visitorBloc.selectedDevices.isNotEmpty){ + if(visitorBloc.effectiveTimeTimeStamp!=null&&visitorBloc.expirationTimeTimeStamp!=null) { + setPasswordFunction(context, size, visitorBloc); + } + else{ + visitorBloc.stateDialog(context: + context,message: 'Please select Access Period to continue',title: 'Access Period'); + } + }else{ + visitorBloc.stateDialog(context: + context,message: 'Please select devices to continue',title: 'Select Devices'); + } + } + }, + borderRadius: 8, + child: Text('Ok', style: Theme.of(context).textTheme.bodySmall!.copyWith( + fontWeight: FontWeight.w400, + color: ColorsManager.whiteColors,fontSize: 16),), + ), + ), + ], + ); }, + ), + ), - } - }); + ); + } - }, - borderRadius: 8, - child: Text('+ Add Device',style: Theme.of(context).textTheme.bodySmall!.copyWith( - fontWeight: FontWeight.w400, - color: ColorsManager.whiteColors,fontSize: 12),), + Future setPasswordFunction( + BuildContext context, + Size size, + VisitorPasswordBloc visitorBloc, + ) { + return showDialog( + context: context, + barrierDismissible: false, + builder: (BuildContext context) { + return BlocBuilder( + builder: (context, state) { + if (state is LoadingInitialState) { + // Show loading indicator while loading + return AlertDialog( + alignment: Alignment.center, + content: SizedBox( + height: size.height * 0.25, + child: Center( + child: CircularProgressIndicator(), // Display a loading spinner + ), + ), + ); + }else{ + return AlertDialog( + alignment: Alignment.center, + content: SizedBox( + height: size.height * 0.25, + child: Column( + children: [ + Column( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + SizedBox( + child: SvgPicture.asset( + Assets.deviceNoteIcon, + height: 35, + width: 35, + ), + ), + Text( + 'Set Password', + style: Theme.of(context).textTheme.headlineLarge!.copyWith( + fontSize: 30, + fontWeight: FontWeight.w400, + color: Colors.black, ), ), ], ), + const SizedBox(width: 15), + Text( + 'This action will update all of the selected\n door locks passwords in the property.\n\nAre you sure you want to continue?', + textAlign: TextAlign.center, + style: Theme.of(context).textTheme.bodyMedium!.copyWith( + color: ColorsManager.grayColor, + fontWeight: FontWeight.w400, + fontSize: 18, + ), + ), ], ), ), - ), - ), - actionsAlignment: MainAxisAlignment.center, - actions: [ - Container( - decoration: containerDecoration, - width: size.width * 0.2, - child: DefaultButton( - borderRadius: 8, - onPressed: () { - Navigator.of(context).pop(); // Close the dialog - }, - backgroundColor: Colors.white, - child: Text( - 'Cancel', - style: Theme.of(context).textTheme.bodySmall!.copyWith( - fontWeight: FontWeight.w400, - color: ColorsManager.blackColor,fontSize: 16), + actionsAlignment: MainAxisAlignment.center, + actions: [ + Container( + decoration: containerDecoration, + width: size.width * 0.1, + child: DefaultButton( + borderRadius: 8, + onPressed: () { + Navigator.of(context).pop(); + }, + backgroundColor: Colors.white, + child: Text( + 'Cancel', + style: Theme.of(context).textTheme.bodySmall!.copyWith( + fontWeight: FontWeight.w400, + color: ColorsManager.blackColor, + fontSize: 16, + ), + ), + ), ), - ), - ), - Container( - decoration: containerDecoration, - width: size.width * 0.2, - child: DefaultButton( - onPressed: () { - if (visitorBloc.forgetFormKey.currentState!.validate()) { - showDialog( - context: context, - barrierDismissible: false, - builder: (BuildContext context) { - return InfoDialog( - size: size, - title: 'Set Password', - content: - 'This action will update all of the selected\n door locks passwords in the property.\n\nAre you sure you want to continue?', - actions: [ - Container( - decoration: containerDecoration, - width: size.width * 0.1, - child: DefaultButton( - borderRadius: 8, - onPressed: () { - Navigator.of(context).pop(); - }, - backgroundColor: Colors.white, - child: Text( - 'Cancel', - style: Theme.of(context).textTheme.bodySmall!.copyWith( - fontWeight: FontWeight.w400, - color: ColorsManager.blackColor,fontSize: 16), - ), - ), - ), - Container( - decoration: containerDecoration, - width: size.width * 0.1, - child: DefaultButton( - borderRadius: 8, - onPressed: () { - if (visitorBloc.usageFrequencySelected == 'One-Time' && - visitorBloc.accessTypeSelected == 'Online Password') { - visitorBloc.add( - OnlineOneTimePasswordEvent( - context: context, - passwordName: visitorBloc.userNameController.text, - email: visitorBloc.emailController.text)); - } else if (visitorBloc.usageFrequencySelected == 'Periodic' && - visitorBloc.accessTypeSelected == 'Online Password') { - visitorBloc.add(OnlineMultipleTimePasswordEvent( - passwordName: visitorBloc.userNameController.text, - email: visitorBloc.emailController.text, - effectiveTime: visitorBloc.effectiveTimeTimeStamp.toString(), - invalidTime: visitorBloc.expirationTimeTimeStamp.toString())); - } else if (visitorBloc.usageFrequencySelected == 'One-Time' && - visitorBloc.accessTypeSelected == 'Offline Password') { - visitorBloc.add(OfflineOneTimePasswordEvent( - passwordName: visitorBloc.userNameController.text, - email: visitorBloc.emailController.text, - )); - } else if (visitorBloc.usageFrequencySelected == 'Periodic' && - visitorBloc.accessTypeSelected == 'Offline Password') { - visitorBloc.add( - OfflineMultipleTimePasswordEvent( - passwordName: visitorBloc.userNameController.text, - email: visitorBloc.emailController.text, - effectiveTime: visitorBloc.effectiveTimeTimeStamp.toString(), - invalidTime: visitorBloc.expirationTimeTimeStamp.toString())); - } - }, - child: Text('Ok', - style: Theme.of(context).textTheme.bodySmall!.copyWith( - fontWeight: FontWeight.w400, - color: ColorsManager.whiteColors,fontSize: 16), - ), - ), - ), - ], - ); - }, - ); - } - }, - borderRadius: 8, - child: Text('Ok', style: Theme.of(context).textTheme.bodySmall!.copyWith( - fontWeight: FontWeight.w400, - color: ColorsManager.whiteColors,fontSize: 16),), - ), - ), - ], - ); - }, - ), + Container( + decoration: containerDecoration, + width: size.width * 0.1, + child: DefaultButton( + borderRadius: 8, + onPressed: () { + Navigator.pop(context); + if (visitorBloc.usageFrequencySelected == 'One-Time' && + visitorBloc.accessTypeSelected == 'Online Password') { + visitorBloc.add(OnlineOneTimePasswordEvent( + context: context, + passwordName: visitorBloc.userNameController.text, + email: visitorBloc.emailController.text, + )); + } else if (visitorBloc.usageFrequencySelected == 'Periodic' && + visitorBloc.accessTypeSelected == 'Online Password') { + visitorBloc.add(OnlineMultipleTimePasswordEvent( + passwordName: visitorBloc.userNameController.text, + email: visitorBloc.emailController.text, + effectiveTime: visitorBloc.effectiveTimeTimeStamp.toString(), + invalidTime: visitorBloc.expirationTimeTimeStamp.toString(), + )); + } else if (visitorBloc.usageFrequencySelected == 'One-Time' && + visitorBloc.accessTypeSelected == 'Offline Password') { + visitorBloc.add(OfflineOneTimePasswordEvent( + context: context, + passwordName: visitorBloc.userNameController.text, + email: visitorBloc.emailController.text, + )); + } else if (visitorBloc.usageFrequencySelected == 'Periodic' && + visitorBloc.accessTypeSelected == 'Offline Password') { + visitorBloc.add(OfflineMultipleTimePasswordEvent( + passwordName: visitorBloc.userNameController.text, + email: visitorBloc.emailController.text, + effectiveTime: visitorBloc.effectiveTimeTimeStamp.toString(), + invalidTime: visitorBloc.expirationTimeTimeStamp.toString(), + )); + } + }, + child: Text( + 'Ok', + style: Theme.of(context).textTheme.bodySmall!.copyWith( + fontWeight: FontWeight.w400, + color: ColorsManager.whiteColors, + fontSize: 16, + ), + ), + ), + ), + ], + ); + } + }, + ); + }, ); } } diff --git a/lib/services/access_mang_api.dart b/lib/services/access_mang_api.dart index 26d7eeea..6a2cf40d 100644 --- a/lib/services/access_mang_api.dart +++ b/lib/services/access_mang_api.dart @@ -2,12 +2,11 @@ import 'dart:convert'; import 'package:dio/dio.dart'; import 'package:flutter/cupertino.dart'; import 'package:syncrow_web/pages/access_management/model/password_model.dart'; +import 'package:syncrow_web/pages/visitor_password/model/device_model.dart'; import 'package:syncrow_web/pages/visitor_password/model/schedule_model.dart'; import 'package:syncrow_web/services/api/http_service.dart'; import 'package:syncrow_web/utils/constants/api_const.dart'; -import '../pages/visitor_password/model/device_model.dart'; - class AccessMangApi{ Future> fetchVisitorPassword() async { @@ -17,7 +16,6 @@ class AccessMangApi{ showServerMessage: true, expectedResponseModel: (json) { List jsonData = json; - print('Password List: $json'); List passwordList = jsonData.map((jsonItem) { return PasswordModel.fromJson(jsonItem); }).toList(); @@ -38,7 +36,6 @@ class AccessMangApi{ showServerMessage: true, expectedResponseModel: (json) { List jsonData = json; - print('fetchDevices List: $json'); List passwordList = jsonData.map((jsonItem) { return DeviceModel.fromJson(jsonItem); }).toList(); @@ -60,18 +57,6 @@ class AccessMangApi{ String? invalidTime, List? devicesUuid}) async { try { - - print('postOfflineOneTime List: ${ - jsonEncode({ - "email": email, - "passwordName": passwordName, - "password": password, - "devicesUuid": devicesUuid, - "effectiveTime":effectiveTime , - "invalidTime": invalidTime - }) - }'); - final response = await HTTPService().post( path: ApiEndpoints.sendOnlineOneTime, body: jsonEncode({ @@ -84,7 +69,6 @@ class AccessMangApi{ }), showServerMessage: true, expectedResponseModel: (json) { - print('postOfflineOneTime List: $json'); if(json['statusCode'].toString()=='201'){ return true; }else{ @@ -93,9 +77,8 @@ class AccessMangApi{ }, ); return response; - } on DioException catch (e) { + } on DioException catch (e) { debugPrint('Error: ${e.message}'); - debugPrint('Error fetching ${e.response!.statusMessage}'); return false; } @@ -121,15 +104,11 @@ class AccessMangApi{ if (scheduleList != null) { body["scheduleList"] = scheduleList.map((schedule) => schedule.toJson()).toList(); } - print('createPassword =${jsonEncode(body)}'); - final response = await HTTPService().post( path: ApiEndpoints.sendOnlineMultipleTime, body: jsonEncode(body), showServerMessage: true, expectedResponseModel: (json) { - print('createPassword =${json}'); - if(json['data']['successOperations'][0]['success'].toString()=='true'){ return true; }else{ @@ -138,7 +117,7 @@ class AccessMangApi{ }, ); return response; - } on DioException catch (e){ + } on DioException catch (e){ debugPrint('Error fetching ${e.type.name}'); debugPrint('Error fetching ${e.response!.statusMessage}'); return false; @@ -149,15 +128,6 @@ class AccessMangApi{ Future postOffLineOneTime({String? email,String? passwordName,List? devicesUuid}) async { try { - - print('postOfflineOneTime List: ${ - { - "email": email, - "passwordName": passwordName, - "devicesUuid": devicesUuid - } - }'); - final response = await HTTPService().post( path: ApiEndpoints.sendOffLineOneTime, body: jsonEncode({ @@ -166,10 +136,14 @@ class AccessMangApi{ "devicesUuid": devicesUuid }), showServerMessage: true, - expectedResponseModel: (json) { - List jsonData = json; - print('postOfflineOneTime List: $json'); - }, + expectedResponseModel: (json) { + if (json['data']['successOperations'][0]['success'].toString() == + 'true') { + return true; + } else { + return false; + } + } ); return response; } catch (e) { @@ -187,15 +161,6 @@ class AccessMangApi{ }) async { try { - - print('postOfflineOneTime List: ${ - { - "email": email, - "passwordName": passwordName, - "devicesUuid": devicesUuid - } - }'); - final response = await HTTPService().post( path: ApiEndpoints.sendOffLineOneTime, body: jsonEncode({ @@ -206,10 +171,14 @@ class AccessMangApi{ "invalidTime": invalidTime }), showServerMessage: true, - expectedResponseModel: (json) { - List jsonData = json; - print('postOfflineOneTime List: $json'); - }, + expectedResponseModel: (json) { + if (json['data']['successOperations'][0]['success'].toString() == + 'true') { + return true; + } else { + return false; + } + } ); return response; } catch (e) { diff --git a/lib/services/auth_api.dart b/lib/services/auth_api.dart index 55584055..3bb8f7e7 100644 --- a/lib/services/auth_api.dart +++ b/lib/services/auth_api.dart @@ -1,4 +1,3 @@ -import 'dart:convert'; import 'package:dio/dio.dart'; import 'package:flutter/foundation.dart'; @@ -8,9 +7,7 @@ import 'package:syncrow_web/services/api/http_service.dart'; import 'package:syncrow_web/utils/constants/api_const.dart'; class AuthenticationAPI { - static Future loginWithEmail({required var model}) async { - print('model=$model'); final response = await HTTPService().post( path: ApiEndpoints.login, body: model.toJson(), @@ -46,19 +43,15 @@ class AuthenticationAPI { }, showServerMessage: true, expectedResponseModel: (json) { - print('object==$json'); return 30; } - ); return 30; } on DioException catch (e) { if (e.response != null) { if (e.response!.statusCode == 400) { - // Handle 400 Bad Request final errorData = e.response!.data; String errorMessage = errorData['message']; - debugPrint('Unexpected Error: $errorMessage'); if(errorMessage=='User not found'){ return 1; }else{ @@ -87,8 +80,6 @@ class AuthenticationAPI { body: {"email": email, "type": "PASSWORD", "otpCode": otpCode}, showServerMessage: true, expectedResponseModel: (json) { - print('json=$json'); - if (json['message'] == 'Otp Verified Successfully') { return true; } else { @@ -99,12 +90,9 @@ class AuthenticationAPI { }on DioException catch (e){ if (e.response != null) { if (e.response!.statusCode == 400) { - // Handle 400 Bad Request final errorData = e.response!.data; String errorMessage = errorData['message']; - debugPrint('Unexpected Error: $errorMessage'); return errorMessage; - } } else { debugPrint('Error: ${e.message}'); diff --git a/lib/web_layout/web_scaffold.dart b/lib/web_layout/web_scaffold.dart index 567710bd..31b8d958 100644 --- a/lib/web_layout/web_scaffold.dart +++ b/lib/web_layout/web_scaffold.dart @@ -3,14 +3,12 @@ import 'package:flutter_svg/svg.dart'; import 'package:syncrow_web/utils/constants/assets.dart'; import 'package:syncrow_web/web_layout/web_app_bar.dart'; import 'menu_sidebar.dart'; - class WebScaffold extends StatelessWidget { final bool enableMenuSideba; final Widget? appBarTitle; final List? appBarBody; final Widget? scaffoldBody; const WebScaffold({super.key,this.appBarTitle,this.appBarBody,this.scaffoldBody,this.enableMenuSideba=true}); - @override Widget build(BuildContext context) { return Scaffold( From 820da1ecfb9033ef6558f563992226de54b58c4d Mon Sep 17 00:00:00 2001 From: mohammad Date: Fri, 23 Aug 2024 22:25:09 +0300 Subject: [PATCH 28/32] Success dialog and Failed dialog changes with VisitorPasswordDialog --- .../bloc/visitor_password_bloc.dart | 31 ++++++++++++++++--- 1 file changed, 26 insertions(+), 5 deletions(-) diff --git a/lib/pages/visitor_password/bloc/visitor_password_bloc.dart b/lib/pages/visitor_password/bloc/visitor_password_bloc.dart index ee40de87..dc4b8a5f 100644 --- a/lib/pages/visitor_password/bloc/visitor_password_bloc.dart +++ b/lib/pages/visitor_password/bloc/visitor_password_bloc.dart @@ -246,12 +246,18 @@ class VisitorPasswordBloc passwordName: event.passwordName); if (res == true) { emit(SuccessState()); - emit(TableLoaded(data)); + }else { + throw Exception('Failed to create password'); } + emit(TableLoaded(data)); } catch (e) { emit(FailedState(e.toString())); - } + Navigator.pop(event.context!); + stateDialog( + context: event.context!, + message: e.toString(), + title: 'Something Wrong'); } } //offline password @@ -266,10 +272,18 @@ class VisitorPasswordBloc passwordName: event.passwordName); if (res == true) { emit(SuccessState()); - emit(TableLoaded(data)); + }else { + throw Exception('Failed to create password'); } + emit(TableLoaded(data)); + } catch (e) { emit(FailedState(e.toString())); + Navigator.pop(event.context!); + stateDialog( + context: event.context!, + message: e.toString(), + title: 'Something Wrong'); } } @@ -288,11 +302,18 @@ class VisitorPasswordBloc ); if (res == true) { emit(SuccessState()); - emit(TableLoaded(data)); + }else { + throw Exception('Failed to create password'); } + emit(TableLoaded(data)); + } catch (e) { emit(FailedState(e.toString())); - } + Navigator.pop(event.context!); + stateDialog( + context: event.context!, + message: e.toString(), + title: 'Something Wrong'); } } void selectDevice( From 13ae978908f1cdecdccbc63c4f57c3695edf3be7 Mon Sep 17 00:00:00 2001 From: mohammad Date: Sat, 24 Aug 2024 09:26:37 +0300 Subject: [PATCH 29/32] check if row in the table checked --- lib/pages/common/custom_table.dart | 8 +++++++- .../bloc/visitor_password_event.dart | 5 ++++- .../visitor_password/view/add_device_dialog.dart | 15 +++++++++++++-- .../view/visitor_password_dialog.dart | 2 +- 4 files changed, 25 insertions(+), 5 deletions(-) diff --git a/lib/pages/common/custom_table.dart b/lib/pages/common/custom_table.dart index a691b673..76e3b663 100644 --- a/lib/pages/common/custom_table.dart +++ b/lib/pages/common/custom_table.dart @@ -13,6 +13,7 @@ class DynamicTable extends StatefulWidget { final bool isEmpty; final void Function(bool?)? selectAll; final void Function(int, bool?)? onRowCheckboxChanged; + final List? initialSelectedIds; const DynamicTable({ super.key, @@ -25,6 +26,7 @@ class DynamicTable extends StatefulWidget { this.cellDecoration, this.selectAll, this.onRowCheckboxChanged, + this.initialSelectedIds, }); @override @@ -38,7 +40,11 @@ class _DynamicTableState extends State { @override void initState() { super.initState(); - _selected = List.filled(widget.data.length, false); + _selected = List.generate(widget.data.length, (index) { + return widget.initialSelectedIds != null && + widget.initialSelectedIds!.contains(widget.data[index][1]); + }); + _selectAll = _selected.every((element) => element == true); } void _toggleSelectAll(bool? value) { diff --git a/lib/pages/visitor_password/bloc/visitor_password_event.dart b/lib/pages/visitor_password/bloc/visitor_password_event.dart index 05ad73d1..b0151757 100644 --- a/lib/pages/visitor_password/bloc/visitor_password_event.dart +++ b/lib/pages/visitor_password/bloc/visitor_password_event.dart @@ -51,7 +51,10 @@ class ToggleDaySelectionEvent extends VisitorPasswordEvent { class ToggleRepeatEvent extends VisitorPasswordEvent {} class GeneratePasswordEvent extends VisitorPasswordEvent {} -class FetchDevice extends VisitorPasswordEvent {} +class FetchDevice extends VisitorPasswordEvent { + final List? list; + const FetchDevice({this.list}); +} //online password class OnlineOneTimePasswordEvent extends VisitorPasswordEvent { diff --git a/lib/pages/visitor_password/view/add_device_dialog.dart b/lib/pages/visitor_password/view/add_device_dialog.dart index eccdfd78..73098976 100644 --- a/lib/pages/visitor_password/view/add_device_dialog.dart +++ b/lib/pages/visitor_password/view/add_device_dialog.dart @@ -13,15 +13,25 @@ import 'package:syncrow_web/utils/constants/const.dart'; import 'package:syncrow_web/utils/style.dart'; class AddDeviceDialog extends StatelessWidget { - const AddDeviceDialog({super.key}); + final List? selectedDeviceIds; + const AddDeviceDialog({super.key,this.selectedDeviceIds }); @override Widget build(BuildContext context) { Size size = MediaQuery.of(context).size; return BlocProvider( - create: (context) => VisitorPasswordBloc()..add(FetchDevice()), + create: (context) => VisitorPasswordBloc()..add(FetchDevice(list: selectedDeviceIds)), child: BlocBuilder( builder: (BuildContext context, VisitorPasswordState state) { final visitorBloc = BlocProvider.of(context); + if (state is TableLoaded) { + print('ooooooooooo${selectedDeviceIds}'); + for (var device in selectedDeviceIds!) { + if (selectedDeviceIds!.contains(device)) { + visitorBloc.add(SelectDeviceEvent(device)); + } + } + } + return AlertDialog( backgroundColor: Colors.white, title: Text('Add Accessible Device', @@ -143,6 +153,7 @@ class AddDeviceDialog extends StatelessWidget { flex: 3, child: state is TableLoaded ? DynamicTable( + initialSelectedIds:selectedDeviceIds , cellDecoration: containerDecoration, isEmpty:visitorBloc.data.isEmpty, selectAll: (p0) { diff --git a/lib/pages/visitor_password/view/visitor_password_dialog.dart b/lib/pages/visitor_password/view/visitor_password_dialog.dart index 0aa63638..8206d42c 100644 --- a/lib/pages/visitor_password/view/visitor_password_dialog.dart +++ b/lib/pages/visitor_password/view/visitor_password_dialog.dart @@ -318,7 +318,7 @@ class VisitorPasswordDialog extends StatelessWidget { context: context, barrierDismissible: false, builder: (BuildContext context) { - return const AddDeviceDialog(); + return AddDeviceDialog(selectedDeviceIds: visitorBloc.selectedDevices,); }, ).then((listDevice) { if(listDevice!=null){ From 9d21b32607235078c6072830b92d9b72a0f4b422 Mon Sep 17 00:00:00 2001 From: mohammad Date: Sat, 24 Aug 2024 09:28:29 +0300 Subject: [PATCH 30/32] check if row in the table checked --- lib/pages/visitor_password/bloc/visitor_password_bloc.dart | 3 ++- lib/pages/visitor_password/bloc/visitor_password_event.dart | 2 -- lib/pages/visitor_password/view/add_device_dialog.dart | 3 +-- 3 files changed, 3 insertions(+), 5 deletions(-) diff --git a/lib/pages/visitor_password/bloc/visitor_password_bloc.dart b/lib/pages/visitor_password/bloc/visitor_password_bloc.dart index dc4b8a5f..2a3e486f 100644 --- a/lib/pages/visitor_password/bloc/visitor_password_bloc.dart +++ b/lib/pages/visitor_password/bloc/visitor_password_bloc.dart @@ -257,7 +257,8 @@ class VisitorPasswordBloc stateDialog( context: event.context!, message: e.toString(), - title: 'Something Wrong'); } + title: 'Something Wrong'); + } } //offline password diff --git a/lib/pages/visitor_password/bloc/visitor_password_event.dart b/lib/pages/visitor_password/bloc/visitor_password_event.dart index b0151757..9526bf54 100644 --- a/lib/pages/visitor_password/bloc/visitor_password_event.dart +++ b/lib/pages/visitor_password/bloc/visitor_password_event.dart @@ -52,8 +52,6 @@ class ToggleRepeatEvent extends VisitorPasswordEvent {} class GeneratePasswordEvent extends VisitorPasswordEvent {} class FetchDevice extends VisitorPasswordEvent { - final List? list; - const FetchDevice({this.list}); } //online password diff --git a/lib/pages/visitor_password/view/add_device_dialog.dart b/lib/pages/visitor_password/view/add_device_dialog.dart index 73098976..e5e4853c 100644 --- a/lib/pages/visitor_password/view/add_device_dialog.dart +++ b/lib/pages/visitor_password/view/add_device_dialog.dart @@ -19,12 +19,11 @@ class AddDeviceDialog extends StatelessWidget { Widget build(BuildContext context) { Size size = MediaQuery.of(context).size; return BlocProvider( - create: (context) => VisitorPasswordBloc()..add(FetchDevice(list: selectedDeviceIds)), + create: (context) => VisitorPasswordBloc()..add(FetchDevice()), child: BlocBuilder( builder: (BuildContext context, VisitorPasswordState state) { final visitorBloc = BlocProvider.of(context); if (state is TableLoaded) { - print('ooooooooooo${selectedDeviceIds}'); for (var device in selectedDeviceIds!) { if (selectedDeviceIds!.contains(device)) { visitorBloc.add(SelectDeviceEvent(device)); From 6d64408360d44a93683ad2622a410b038bb394fb Mon Sep 17 00:00:00 2001 From: mohammad Date: Sat, 24 Aug 2024 10:35:42 +0300 Subject: [PATCH 31/32] change some padding and when bake to AccessManagementPage get table --- .../view/access_management.dart | 9 ++- lib/pages/common/date_time_widget.dart | 71 +++++++++++-------- .../visitor_password/view/repeat_widget.dart | 35 ++++----- .../view/visitor_password_dialog.dart | 32 +++++---- 4 files changed, 83 insertions(+), 64 deletions(-) diff --git a/lib/pages/access_management/view/access_management.dart b/lib/pages/access_management/view/access_management.dart index 6245dcda..e357216d 100644 --- a/lib/pages/access_management/view/access_management.dart +++ b/lib/pages/access_management/view/access_management.dart @@ -8,6 +8,7 @@ import 'package:syncrow_web/pages/common/date_time_widget.dart'; import 'package:syncrow_web/pages/common/default_button.dart'; import 'package:syncrow_web/pages/visitor_password/view/visitor_password_dialog.dart'; import 'package:syncrow_web/utils/color_manager.dart'; +import 'package:syncrow_web/utils/constants/assets.dart'; import 'package:syncrow_web/utils/constants/const.dart'; import 'package:syncrow_web/utils/style.dart'; import 'package:syncrow_web/web_layout/web_scaffold.dart'; @@ -126,7 +127,7 @@ class AccessManagementPage extends StatelessWidget { width: 15, ), DateTimeWebWidget( - isTime: false, + icon: Assets.calendarIcon, isRequired: false, title: 'Access Time', size: size, @@ -202,7 +203,11 @@ class AccessManagementPage extends StatelessWidget { builder: (BuildContext context) { return const VisitorPasswordDialog(); }, - ); + ).then((v){ + if(v!=null){ + accessBloc.add(FetchTableData()); + } + }); }, borderRadius: 8, child: const Text('+ Create Visitor Password ')), diff --git a/lib/pages/common/date_time_widget.dart b/lib/pages/common/date_time_widget.dart index 55d5b9a9..b2103ab2 100644 --- a/lib/pages/common/date_time_widget.dart +++ b/lib/pages/common/date_time_widget.dart @@ -12,17 +12,17 @@ class DateTimeWebWidget extends StatelessWidget { required this.title, required this.startTime, required this.endTime, - required this.isTime, required this.firstString, required this.secondString, + required this.icon, }); final Size size; final String title; final bool isRequired; - final bool isTime; final String firstString; final String secondString; + final String icon; final Function()? startTime; final Function()? endTime; @@ -49,34 +49,45 @@ class DateTimeWebWidget extends StatelessWidget { ), const SizedBox(height: 8,), Container( - width: size.width * 0.25, - padding: EdgeInsets.all(10), - decoration: containerDecoration, - child: Column( - children: [ - Row( - mainAxisAlignment: - MainAxisAlignment.spaceBetween, - children: [ - InkWell( - onTap: startTime, - child: Text(firstString, style: Theme.of(context).textTheme.bodySmall!.copyWith( - color: ColorsManager.grayColor,fontSize: 12,fontWeight: FontWeight.w400),) - ), - const Icon(Icons.arrow_right_alt), - InkWell( - onTap:endTime, - child: Text(secondString, style: Theme.of(context).textTheme.bodySmall!.copyWith( - color: ColorsManager.grayColor,fontSize: 12,fontWeight: FontWeight.w400),)), - SvgPicture.asset( - isTime? - Assets.timeIcon: - Assets.calendarIcon, - ), - ], - ), - ], - )), + height:size.height * 0.055 , + padding: EdgeInsets.only(top: 10,bottom: 10,right: 30,left: 30), + decoration: containerDecoration, + child: FittedBox( + child: Column( + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceAround, + children: [ + InkWell( + onTap: startTime, + child: FittedBox( + child: Text(firstString, + style: Theme.of(context).textTheme.bodySmall!.copyWith( + color: ColorsManager.grayColor,fontSize: 12,fontWeight: FontWeight.w400),), + ) + ), + SizedBox(width: 10,), + const Icon(Icons.arrow_right_alt), + SizedBox(width: 10,), + + InkWell( + onTap:endTime, + child: FittedBox( + child: Text(secondString, + style: Theme.of(context).textTheme.bodySmall!.copyWith( + color: ColorsManager.grayColor,fontSize: 12,fontWeight: FontWeight.w400), + ), + )), + SizedBox(width: 10,), + + SvgPicture.asset( + icon, + ), + ], + ), + ], + )), + ), ], ); } diff --git a/lib/pages/visitor_password/view/repeat_widget.dart b/lib/pages/visitor_password/view/repeat_widget.dart index 31bedb05..ae37b1e3 100644 --- a/lib/pages/visitor_password/view/repeat_widget.dart +++ b/lib/pages/visitor_password/view/repeat_widget.dart @@ -5,6 +5,7 @@ import 'package:syncrow_web/pages/visitor_password/bloc/visitor_password_bloc.da import 'package:syncrow_web/pages/visitor_password/bloc/visitor_password_event.dart'; import 'package:syncrow_web/pages/visitor_password/bloc/visitor_password_state.dart'; import 'package:syncrow_web/utils/color_manager.dart'; +import 'package:syncrow_web/utils/constants/assets.dart'; class RepeatWidget extends StatelessWidget { const RepeatWidget({ @@ -21,19 +22,20 @@ class RepeatWidget extends StatelessWidget { mainAxisAlignment: MainAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start, children: [ - - Container( - height: size.height * 0.05, // Adjust height as needed - child: Wrap( + // Wrap the Row in a SingleChildScrollView to handle overflow + SingleChildScrollView( + scrollDirection: Axis.horizontal, + child: Row( children: visitorBloc.days.map((day) { return Container( - width: size.width * 0.05, + width: 70, // Adjust width as needed + margin: EdgeInsets.all(5), child: CheckboxListTile( contentPadding: EdgeInsets.zero, title: Text( day['day']!, style: TextStyle( - fontSize: 12, + fontSize: 10, color: visitorBloc.selectedDays.contains(day['key']) ? Colors.black : ColorsManager.blackColor, @@ -53,33 +55,32 @@ class RepeatWidget extends StatelessWidget { Padding( padding: const EdgeInsets.all(8.0), child: DateTimeWebWidget( - isTime: true, + icon: Assets.timeIcon, isRequired: false, title: '', size: size, endTime: () { - visitorBloc.add(SelectTimeEvent( - context: context, - isEffective: false)); - Future.delayed(const Duration(milliseconds: 500), () { + visitorBloc.add(SelectTimeEvent( + context: context, + isEffective: false)); + Future.delayed(const Duration(milliseconds: 500), () { visitorBloc.add(ChangeTimeEvent(val: visitorBloc.endTime, isStartEndTime: true)); }); }, startTime: () { - Future.delayed(const Duration(milliseconds: 500), () { + Future.delayed(const Duration(milliseconds: 500), () { visitorBloc.add(ChangeTimeEvent(val: visitorBloc.endTime, isStartEndTime: true)); }); visitorBloc.add(SelectTimeEvent(context: context, isEffective: true)); }, - firstString:visitorBloc.effectiveTime , - secondString: visitorBloc.expirationTime , + firstString: visitorBloc.effectiveTime, + secondString: visitorBloc.expirationTime, ), ), - const SizedBox(height: 20), - ], ); - }); + } + ); } } diff --git a/lib/pages/visitor_password/view/visitor_password_dialog.dart b/lib/pages/visitor_password/view/visitor_password_dialog.dart index 8206d42c..ff3d5a04 100644 --- a/lib/pages/visitor_password/view/visitor_password_dialog.dart +++ b/lib/pages/visitor_password/view/visitor_password_dialog.dart @@ -100,20 +100,20 @@ class VisitorPasswordDialog extends StatelessWidget { children: [ Row( children: [ - Text( - '* ', + Text('* ', style: Theme.of(context).textTheme .bodyMedium!.copyWith(color: Colors.red), ), Text('Access Type', style:text ), ], + ), Row( children: [ - SizedBox( - width: size.width * 0.15, + Flexible( child: RadioListTile( + contentPadding: EdgeInsets.zero, title: Text('Online Password', style: text, ), @@ -129,9 +129,10 @@ class VisitorPasswordDialog extends StatelessWidget { }, ), ), - SizedBox( - width: size.width * 0.15, + Flexible( child: RadioListTile( + contentPadding: EdgeInsets.zero, + title: Text('Offline Password', style:text ), value: 'Offline Password', @@ -145,9 +146,10 @@ class VisitorPasswordDialog extends StatelessWidget { }, ), ), - SizedBox( - width: size.width * 0.15, + Flexible( child: RadioListTile( + contentPadding: EdgeInsets.zero, + title: Text('Dynamic Password', style: text,), value: 'Dynamic Password', @@ -191,9 +193,9 @@ class VisitorPasswordDialog extends StatelessWidget { ), Row( children: [ - SizedBox( - width: 200, + Flexible( child: RadioListTile( + contentPadding: EdgeInsets.zero, title: Text('One-Time', style:text ,), value: 'One-Time', @@ -209,9 +211,9 @@ class VisitorPasswordDialog extends StatelessWidget { }, ), ), - SizedBox( - width: 200, + Flexible( child: RadioListTile( + contentPadding: EdgeInsets.zero, title: Text('Periodic', style: text), value: 'Periodic', @@ -241,7 +243,6 @@ class VisitorPasswordDialog extends StatelessWidget { visitorBloc.accessTypeSelected != 'Offline Password') && (visitorBloc.usageFrequencySelected != '')) DateTimeWebWidget( - isTime: false, isRequired: true, title: 'Access Period', size: size, @@ -259,6 +260,7 @@ class VisitorPasswordDialog extends StatelessWidget { }, firstString: visitorBloc.startTimeAccess.toString(), secondString: visitorBloc.endTimeAccess.toString(), + icon: Assets.calendarIcon ), const SizedBox( height: 20, @@ -311,7 +313,7 @@ class VisitorPasswordDialog extends StatelessWidget { isRepeat ? const RepeatWidget() : const SizedBox(), Container( decoration: containerDecoration, - width: size.width * 0.08, + width: size.width / 9, child: DefaultButton( onPressed: () { showDialog( @@ -347,7 +349,7 @@ class VisitorPasswordDialog extends StatelessWidget { child: DefaultButton( borderRadius: 8, onPressed: () { - Navigator.of(context).pop(); // Close the dialog + Navigator.of(context).pop(true); }, backgroundColor: Colors.white, child: Text( From 4e1c2ab5ceae018d58c88ba4e790701a06a66a55 Mon Sep 17 00:00:00 2001 From: mohammad Date: Sat, 24 Aug 2024 10:49:08 +0300 Subject: [PATCH 32/32] change some padding and when bake to AccessManagementPage get table --- lib/pages/access_management/bloc/access_bloc.dart | 4 ++++ .../access_management/view/access_management.dart | 1 - lib/pages/common/date_time_widget.dart | 10 +++++----- 3 files changed, 9 insertions(+), 6 deletions(-) diff --git a/lib/pages/access_management/bloc/access_bloc.dart b/lib/pages/access_management/bloc/access_bloc.dart index 4ed61291..47f865ef 100644 --- a/lib/pages/access_management/bloc/access_bloc.dart +++ b/lib/pages/access_management/bloc/access_bloc.dart @@ -72,6 +72,8 @@ class AccessBloc extends Bloc { Future selectTime(SelectTime event, Emitter emit) async { + emit(AccessLoaded()); + final DateTime? picked = await showDatePicker( context: event.context, initialDate: DateTime.now(), @@ -182,6 +184,8 @@ class AccessBloc extends Bloc { endTime = 'End Time'; passwordName.clear(); selectedIndex=0; + effectiveTimeTimeStamp=null; + expirationTimeTimeStamp=null; add(FetchTableData()); } diff --git a/lib/pages/access_management/view/access_management.dart b/lib/pages/access_management/view/access_management.dart index e357216d..d17e774b 100644 --- a/lib/pages/access_management/view/access_management.dart +++ b/lib/pages/access_management/view/access_management.dart @@ -36,7 +36,6 @@ class AccessManagementPage extends StatelessWidget { ], scaffoldBody: BlocProvider(create: (BuildContext context) => AccessBloc()..add(FetchTableData()), child: BlocConsumer(listener: (context, state) { - }, builder: (context, state) { final accessBloc = BlocProvider.of(context); final filteredData = accessBloc.filteredData; diff --git a/lib/pages/common/date_time_widget.dart b/lib/pages/common/date_time_widget.dart index b2103ab2..d8fbfe51 100644 --- a/lib/pages/common/date_time_widget.dart +++ b/lib/pages/common/date_time_widget.dart @@ -50,13 +50,13 @@ class DateTimeWebWidget extends StatelessWidget { const SizedBox(height: 8,), Container( height:size.height * 0.055 , - padding: EdgeInsets.only(top: 10,bottom: 10,right: 30,left: 30), + padding: EdgeInsets.only(top: 10,bottom: 10,right: 30,left: 10), decoration: containerDecoration, child: FittedBox( child: Column( children: [ Row( - mainAxisAlignment: MainAxisAlignment.spaceAround, + mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ InkWell( onTap: startTime, @@ -66,9 +66,9 @@ class DateTimeWebWidget extends StatelessWidget { color: ColorsManager.grayColor,fontSize: 12,fontWeight: FontWeight.w400),), ) ), - SizedBox(width: 10,), + SizedBox(width: 30,), const Icon(Icons.arrow_right_alt), - SizedBox(width: 10,), + SizedBox(width: 30,), InkWell( onTap:endTime, @@ -78,7 +78,7 @@ class DateTimeWebWidget extends StatelessWidget { color: ColorsManager.grayColor,fontSize: 12,fontWeight: FontWeight.w400), ), )), - SizedBox(width: 10,), + SizedBox(width: 30,), SvgPicture.asset( icon,