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 ),