diff --git a/assets/icons/noValidPasswords.svg b/assets/icons/noValidPasswords.svg new file mode 100644 index 0000000..1a660df --- /dev/null +++ b/assets/icons/noValidPasswords.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/assets/icons/oneTimePassword.svg b/assets/icons/oneTimePassword.svg new file mode 100644 index 0000000..06bd9b5 --- /dev/null +++ b/assets/icons/oneTimePassword.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/assets/icons/timeLimitedPassword.svg b/assets/icons/timeLimitedPassword.svg new file mode 100644 index 0000000..be4fdbc --- /dev/null +++ b/assets/icons/timeLimitedPassword.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/assets/icons/timeLimitedPasswordIcon.svg b/assets/icons/timeLimitedPasswordIcon.svg new file mode 100644 index 0000000..60cc3d6 --- /dev/null +++ b/assets/icons/timeLimitedPasswordIcon.svg @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/lib/features/devices/bloc/smart_door_bloc/smart_door_bloc.dart b/lib/features/devices/bloc/smart_door_bloc/smart_door_bloc.dart index de0b4c7..0259855 100644 --- a/lib/features/devices/bloc/smart_door_bloc/smart_door_bloc.dart +++ b/lib/features/devices/bloc/smart_door_bloc/smart_door_bloc.dart @@ -1,23 +1,39 @@ +import 'dart:math'; +import 'package:day_picker/model/day_in_week.dart'; +import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:intl/intl.dart'; import 'package:syncrow_app/features/devices/bloc/smart_door_bloc/smart_door_event.dart'; import 'package:syncrow_app/features/devices/bloc/smart_door_bloc/smart_door_state.dart'; import 'package:syncrow_app/features/devices/model/device_control_model.dart'; import 'package:syncrow_app/features/devices/model/smart_door_model.dart'; import 'package:syncrow_app/features/devices/model/status_model.dart'; +import 'package:syncrow_app/features/devices/model/create_temporary_password_model.dart'; +import 'package:syncrow_app/features/devices/model/temporary_password_model.dart'; import 'package:syncrow_app/services/api/devices_api.dart'; +import 'package:syncrow_app/utils/helpers/snack_bar.dart'; class SmartDoorBloc extends Bloc { final String deviceId; late SmartDoorModel deviceStatus; + static String pageType = ''; SmartDoorBloc({required this.deviceId}) : super(InitialState()) { on(_fetchSmartDoorStatus); + on(getTemporaryPasswords); on(_updateLock); + on(savePassword); + on(toggleRepeat); + on(setStartEndTime); + on(changeTime); + on(generate7DigitNumber); + on(selectTime); + on(deletePassword); } - - void _fetchSmartDoorStatus(InitialEvent event, Emitter emit) async { - emit(LoadingInitialState()); + void _fetchSmartDoorStatus( + InitialEvent event, Emitter emit) async { try { + emit(LoadingInitialState()); var response = await DevicesAPI.getDeviceStatus(deviceId); List statusModelList = []; for (var status in response['status']) { @@ -31,11 +47,76 @@ class SmartDoorBloc extends Bloc { } } + void getTemporaryPasswords( + InitialPasswordsPage event, Emitter emit) async { + try { + emit(LoadingInitialState()); + pageType = event.type!; + var response = await DevicesAPI.getTemporaryPasswords(deviceId, pageType); + if (response is List) { + temporaryPasswords = + response.map((item) => TemporaryPassword.fromJson(item)).toList(); + } else if (response is Map && response.containsKey('data')) { + temporaryPasswords = (response['data'] as List) + .map((item) => TemporaryPassword.fromJson(item)) + .toList(); + } else { + throw Exception("Unexpected response format"); + } + emit(TemporaryPasswordsLoadedState( + temporaryPassword: temporaryPasswords!)); + } catch (e) { + emit(FailedState(errorMessage: e.toString())); + } + } + + TextEditingController passwordController = TextEditingController(); + TextEditingController passwordNameController = TextEditingController(); + String effectiveTime = 'Select Time'; + int? effectiveTimeTimeStamp; + String expirationTime = 'Select Time'; + int? expirationTimeTimeStamp; + bool repeat = false; + bool isStartEndTime = true; + List? selectedDay; + DateTime? startTime; + DateTime? endTime; + List? temporaryPasswords = []; + List? oneTimePasswords = []; + + changeTime(ChangeTimeEvent event, Emitter emit) { + emit(LoadingInitialState()); + if (event.isStartEndTime == true) { + startTime = event.val; + } else { + endTime = event.val; + } + emit(changeTimeState()); + } + + bool toggleRepeat(ToggleRepeatEvent event, Emitter emit) { + emit(LoadingInitialState()); + repeat = !repeat; + emit(IsRepeatState()); + return repeat; + } + + + bool setStartEndTime(SetStartEndTimeEvent event, Emitter emit) { + emit(LoadingInitialState()); + isStartEndTime = event.val; + emit(IsStartEndState()); + return isStartEndTime; + } + void _updateLock(UpdateLockEvent event, Emitter emit) async { emit(LoadingNewSate(smartDoorModel: deviceStatus)); try { final response = await DevicesAPI.controlDevice( - DeviceControlModel(deviceId: deviceId, code: 'normal_open_switch', value: !event.value), + DeviceControlModel( + deviceId: deviceId, + code: 'normal_open_switch', + value: !event.value), deviceId); if (response['success'] ?? false) { @@ -44,4 +125,146 @@ class SmartDoorBloc extends Bloc { } catch (_) {} emit(UpdateState(smartDoorModel: deviceStatus)); } + + void generate7DigitNumber(GeneratePasswordEvent event, Emitter emit) async { + emit(LoadingInitialState()); + passwordController.clear(); + Random random = Random(); + int min = 1000000; + int max = 9999999; + passwordController.text = (min + random.nextInt(max - min + 1)).toString(); + emit(GeneratePasswordState()); + } + + Future selectTime(SelectTimeEvent 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(), + ); + if (timePicked != null) { + final selectedDateTime = DateTime( + picked.year, + picked.month, + picked.day, + timePicked.hour, + timePicked.minute, + ); + if (event.isEffective) { + if (expirationTimeTimeStamp != null && + selectedDateTime.millisecondsSinceEpoch > + expirationTimeTimeStamp!) { + CustomSnackBar.displaySnackBar('Effective Time cannot be later than Expiration Time.'); + } else { + effectiveTime = selectedDateTime.toString(); + effectiveTimeTimeStamp = selectedDateTime.millisecondsSinceEpoch; + } + } else { + if (effectiveTimeTimeStamp != null && + selectedDateTime.millisecondsSinceEpoch < + effectiveTimeTimeStamp!) { + CustomSnackBar.displaySnackBar('Expiration Time cannot be earlier than Effective Time.'); + } else { + expirationTime = selectedDateTime.toString(); + expirationTimeTimeStamp = selectedDateTime.millisecondsSinceEpoch; + } + } + emit(TimeSelectedState()); + } + } + } + + Future savePassword(SavePasswordEvent event, Emitter emit) async { + if (_validateInputs()) return; + try { + emit(LoadingSaveState()); + var res = await DevicesAPI.createPassword( + pageType: pageType, + deviceId: deviceId, + effectiveTime: effectiveTimeTimeStamp.toString(), + invalidTime: expirationTimeTimeStamp.toString(), + name: passwordNameController.text, + password: passwordController.text, + scheduleList: [ + if (repeat) + Schedule( + effectiveTime: getTimeOnly(startTime), + invalidTime: getTimeOnly(endTime).toString(), + workingDay: selectedDay!, + ), + ], + ); + Navigator.of(event.context).pop(true); + CustomSnackBar.displaySnackBar('Save Successfully'); + emit(SaveState()); + + } catch (_) { + } + } + + Future deletePassword(DeletePasswordEvent event, Emitter emit) async { + try { + emit(LoadingInitialState()); + var response = await DevicesAPI.deletePassword( + deviceId: deviceId, passwordId: event.passwordId) + .then((value) async { + add(InitialPasswordsPage(type: pageType)); + }); + } catch (e) { + emit(FailedState(errorMessage: e.toString())); + } + } + + bool _validateInputs() { + if (passwordController.text.length<7) { + CustomSnackBar.displaySnackBar('Password less than 7'); + return true; + } + if (passwordController.text.isEmpty) { + CustomSnackBar.displaySnackBar('Password required'); + return true; + } + if (passwordNameController.text.isEmpty) { + CustomSnackBar.displaySnackBar('Password name required'); + return true; + } + if (effectiveTime == 'Select Time' || effectiveTimeTimeStamp == null) { + CustomSnackBar.displaySnackBar('Select effective time'); + return true; + } + if (expirationTime == 'Select Time' || expirationTimeTimeStamp == null) { + CustomSnackBar.displaySnackBar('Select expiration time'); + return true; + } + if (repeat == true && + (endTime == null || startTime == null || selectedDay == null)) { + CustomSnackBar.displaySnackBar('Start Time and End time and the days required '); + return true; + } + return false; + } + + + + List days = [ + DayInWeek("S", dayKey: 'Sun'), + DayInWeek("M", dayKey: 'Mon'), + DayInWeek("T", dayKey: 'Tue'), + DayInWeek("W", dayKey: 'Wed'), + DayInWeek("T", dayKey: 'Thu'), + DayInWeek("F", dayKey: 'Fri'), + DayInWeek("S", dayKey: 'Sat'), + ]; + + String getTimeOnly(DateTime? dateTime) { + if (dateTime == null) return ''; + return DateFormat('HH:mm').format(dateTime); + } + } diff --git a/lib/features/devices/bloc/smart_door_bloc/smart_door_event.dart b/lib/features/devices/bloc/smart_door_bloc/smart_door_event.dart index a266390..df138ae 100644 --- a/lib/features/devices/bloc/smart_door_bloc/smart_door_event.dart +++ b/lib/features/devices/bloc/smart_door_bloc/smart_door_event.dart @@ -1,4 +1,5 @@ import 'package:equatable/equatable.dart'; +import 'package:flutter/cupertino.dart'; abstract class SmartDoorEvent extends Equatable { const SmartDoorEvent(); @@ -8,6 +9,11 @@ abstract class SmartDoorEvent extends Equatable { } class InitialEvent extends SmartDoorEvent {} +class InitialPasswordsPage extends SmartDoorEvent { + final String? type; + const InitialPasswordsPage({ this.type}); +} +class InitialOneTimePassword extends SmartDoorEvent {} class UpdateLockEvent extends SmartDoorEvent { final bool value; @@ -15,3 +21,47 @@ class UpdateLockEvent extends SmartDoorEvent { @override List get props => [value]; } + +class SavePasswordEvent extends SmartDoorEvent { + final BuildContext context; + const SavePasswordEvent({required this.context}); + @override + List get props => [context]; +} + +class GeneratePasswordEvent extends SmartDoorEvent {} +class SelectTimeEvent extends SmartDoorEvent { + final BuildContext context; + final bool isEffective; + const SelectTimeEvent({required this.context,required this.isEffective}); + @override + List get props => [context,isEffective]; +} + +class ToggleRepeatEvent extends SmartDoorEvent {} +class SetStartEndTimeEvent extends SmartDoorEvent { + final bool val; + + const SetStartEndTimeEvent({required this.val}); + @override + List get props => [val]; +} + +class DeletePasswordEvent extends SmartDoorEvent { + final String passwordId; + + const DeletePasswordEvent({required this.passwordId}); + @override + List get props => [passwordId]; +} + +class ChangeTimeEvent extends SmartDoorEvent { + final dynamic val; + final bool isStartEndTime; + + const ChangeTimeEvent({required this.val,required this.isStartEndTime}); + @override + List get props => [val,isStartEndTime]; +} + + diff --git a/lib/features/devices/bloc/smart_door_bloc/smart_door_state.dart b/lib/features/devices/bloc/smart_door_bloc/smart_door_state.dart index 12cab76..473a768 100644 --- a/lib/features/devices/bloc/smart_door_bloc/smart_door_state.dart +++ b/lib/features/devices/bloc/smart_door_bloc/smart_door_state.dart @@ -1,5 +1,6 @@ import 'package:equatable/equatable.dart'; import 'package:syncrow_app/features/devices/model/smart_door_model.dart'; +import 'package:syncrow_app/features/devices/model/temporary_password_model.dart'; class SmartDoorState extends Equatable { const SmartDoorState(); @@ -36,3 +37,17 @@ class FailedState extends SmartDoorState { @override List get props => [errorMessage]; } + +class GeneratePasswordState extends SmartDoorState{} +class TimeSelectedState extends SmartDoorState{} +class IsRepeatState extends SmartDoorState{} +class IsStartEndState extends SmartDoorState{} +class changeStartTimeState extends SmartDoorState{} +class changeEndTimeState extends SmartDoorState{} +class changeTimeState extends SmartDoorState{} +class SaveState extends SmartDoorState{} +class LoadingSaveState extends SmartDoorState{} +class TemporaryPasswordsLoadedState extends SmartDoorState{ + final List temporaryPassword; + const TemporaryPasswordsLoadedState({required this.temporaryPassword}); +} diff --git a/lib/features/devices/model/create_temporary_password_model.dart b/lib/features/devices/model/create_temporary_password_model.dart new file mode 100644 index 0000000..fe94079 --- /dev/null +++ b/lib/features/devices/model/create_temporary_password_model.dart @@ -0,0 +1,68 @@ + + + +class CreateTemporaryPasswordModel{ + final String name; + late final String password; + late final String effectiveTime; + late final String invalidTime; + List? scheduleList; + + CreateTemporaryPasswordModel({ + required this.name, + required this.password, + required this.effectiveTime, + required this.invalidTime, + this.scheduleList, + }); + + factory CreateTemporaryPasswordModel.fromJson(Map json) { + return CreateTemporaryPasswordModel( + name: json['name'], + password: json['password'], + effectiveTime: json['effectiveTime'], + invalidTime: json['invalidTime'], + scheduleList: (json['scheduleList'] as List) + .map((i) => Schedule.fromJson(i)) + .toList(), + ); + } + + Map toJson() { + return { + 'name': name, + 'password': password, + 'effectiveTime': effectiveTime, + 'invalidTime': invalidTime, + 'scheduleList': scheduleList!.map((i) => i.toJson()).toList(), + }; + } +} + +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, + }; + } +} diff --git a/lib/features/devices/model/temporary_password_model.dart b/lib/features/devices/model/temporary_password_model.dart new file mode 100644 index 0000000..40cda2c --- /dev/null +++ b/lib/features/devices/model/temporary_password_model.dart @@ -0,0 +1,37 @@ +class TemporaryPassword { + final int effectiveTime; + final int id; + final int invalidTime; + final String name; + final int phase; + final String phone; + final int sn; + final String timeZone; + final int type; + + TemporaryPassword({ + required this.effectiveTime, + required this.id, + required this.invalidTime, + required this.name, + required this.phase, + required this.phone, + required this.sn, + required this.timeZone, + required this.type, + }); + + factory TemporaryPassword.fromJson(Map json) { + return TemporaryPassword( + effectiveTime: json['effectiveTime'], + id: json['id'], + invalidTime: json['invalidTime'], + name: json['name'], + phase: json['phase'], + phone: json['phone'] ?? '', + sn: json['sn'], + timeZone: json['timeZone'] ?? '', + type: json['type'], + ); + } +} diff --git a/lib/features/devices/view/widgets/ceiling_sensor/ceiling_sensor_interface.dart b/lib/features/devices/view/widgets/ceiling_sensor/ceiling_sensor_interface.dart index 5b2db52..bfc050f 100644 --- a/lib/features/devices/view/widgets/ceiling_sensor/ceiling_sensor_interface.dart +++ b/lib/features/devices/view/widgets/ceiling_sensor/ceiling_sensor_interface.dart @@ -240,8 +240,7 @@ class CeilingSensorInterface extends StatelessWidget { padding: const EdgeInsets.symmetric(vertical: 12, horizontal: 20), onTap: () async { - if (ceilingSensorButtons()[index]['title'] == - 'Sensitivity') { + if (ceilingSensorButtons()[index]['title'] == 'Sensitivity') { final result = await showDialog( context: context, builder: (context) { diff --git a/lib/features/devices/view/widgets/smart_door/create_temporary_password.dart b/lib/features/devices/view/widgets/smart_door/create_temporary_password.dart new file mode 100644 index 0000000..fd2af90 --- /dev/null +++ b/lib/features/devices/view/widgets/smart_door/create_temporary_password.dart @@ -0,0 +1,325 @@ +import 'package:day_picker/day_picker.dart'; +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:pin_code_fields/pin_code_fields.dart'; +import 'package:syncrow_app/features/devices/bloc/smart_door_bloc/smart_door_bloc.dart'; +import 'package:syncrow_app/features/devices/bloc/smart_door_bloc/smart_door_event.dart'; +import 'package:syncrow_app/features/devices/bloc/smart_door_bloc/smart_door_state.dart'; +import 'package:syncrow_app/features/shared_widgets/default_container.dart'; +import 'package:syncrow_app/features/shared_widgets/default_scaffold.dart'; +import 'package:syncrow_app/features/shared_widgets/text_widgets/body_large.dart'; +import 'package:syncrow_app/features/shared_widgets/text_widgets/body_medium.dart'; +import 'package:syncrow_app/utils/resource_manager/color_manager.dart'; +import 'package:syncrow_app/utils/resource_manager/font_manager.dart'; +import 'package:time_picker_spinner/time_picker_spinner.dart'; + +class CreateTemporaryPassword extends StatelessWidget { + final String? deviceId; + final String? type; + + const CreateTemporaryPassword({super.key,this.deviceId, this.type}); + @override + Widget build(BuildContext context) { + return BlocProvider( + create: (BuildContext context) => SmartDoorBloc(deviceId: deviceId!), + child: BlocConsumer( + listener: (context, state) { + if (state is FailedState) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text(state.errorMessage), + backgroundColor: Colors.red, + ), + ); + } + }, + builder: (context, state) { + return DefaultScaffold( + appBar: AppBar( + backgroundColor: Colors.transparent, + centerTitle: true, + title: const BodyLarge( + text: 'Create Password' , + fontColor: ColorsManager.primaryColor, + fontWeight: FontsManager.bold, + ), + leading: IconButton(onPressed: () { + Navigator.of(context).pop('UpdatePage'); + }, icon: const Icon(Icons.arrow_back)), + actions: [ + TextButton( + onPressed: () { + BlocProvider.of(context).add(SavePasswordEvent(context: context)); + }, + child: const Text('Save')) + ], + ), + + child: state is LoadingInitialState? + const Center(child: CircularProgressIndicator()): SingleChildScrollView( + child: + Column( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const BodyMedium( + text: + 'Save the password immediately. The password is not displayed in the app.', + fontWeight: FontWeight.normal, + fontColor: ColorsManager.grayColor, + ), + const SizedBox( + height: 20, + ), + const BodyMedium( + text: '7-Digit Password', + fontWeight: FontWeight.normal, + fontColor: ColorsManager.grayColor, + ), + DefaultContainer( + padding: const EdgeInsets.symmetric(horizontal: 15, vertical: 15), + child: Row( + children: [ + Expanded( + child: PinCodeTextField( + autoDisposeControllers: false, + keyboardType: TextInputType.phone, + length: 7, + enabled: true, + obscureText: false, + animationType: AnimationType.fade, + pinTheme: PinTheme( + shape: PinCodeFieldShape.underline, + fieldHeight: 45, + fieldWidth: 20, + activeFillColor: Colors.white, + disabledColor: Colors.grey, + activeColor: Colors.grey, + errorBorderColor: Colors.grey, + inactiveColor: Colors.grey, + inactiveFillColor: Colors.grey, + selectedColor: Colors.grey + ), + animationDuration: Duration(milliseconds: 300), + backgroundColor: Colors.white, + enableActiveFill: false, + controller: BlocProvider.of(context).passwordController, + appContext: context, + )), + const SizedBox( + width: 10, + ), + InkWell( + onTap: () { + BlocProvider.of(context).add(GeneratePasswordEvent()); + }, + child: const BodyMedium( + text: 'Generate Randomly', + fontWeight: FontWeight.bold, + fontColor: ColorsManager.primaryColor, + )) + ], + ), + ), + BlocProvider.of(context).passwordController.text.isNotEmpty? + TextButton(onPressed: () async { + await Clipboard.setData(ClipboardData(text: BlocProvider.of(context).passwordController.text)); + }, + child: const Text('Copy')):SizedBox(), + const SizedBox( + height: 20, + ), + DefaultContainer( + padding: const EdgeInsets.all(20), + child: Column( + children: [ + ListTile( + contentPadding: EdgeInsets.zero, + leading: const BodyMedium( + text: 'Password Name', + fontWeight: FontWeight.normal, + ), + trailing: SizedBox( + width: MediaQuery.of(context).size.width / 2, + child: TextFormField( + controller: + BlocProvider.of(context).passwordNameController, + decoration: const InputDecoration( + labelText: 'Enter The Name'), + )), + ), + const Divider(color:Color(0xffEBEBEB),), + ListTile( + contentPadding: EdgeInsets.zero, + leading: const BodyMedium( + text: 'Effective Time', + fontWeight: FontWeight.normal, + ), + trailing: SizedBox( + width: MediaQuery.of(context).size.width / 2, + child: InkWell( + onTap: () { + BlocProvider.of(context)..add(SelectTimeEvent(context: context, isEffective: true)); + // .selectTime(context, isEffective: true); + }, + child: Text( + BlocProvider.of(context).effectiveTime + ), + )), + ), + const Divider(color:Color(0xffEBEBEB),), + ListTile( + contentPadding: EdgeInsets.zero, + leading: const BodyMedium( + text: 'Expiration Time', + fontWeight: FontWeight.normal, + ), + trailing: SizedBox( + width: MediaQuery.of(context).size.width / 2, + child: SizedBox( + width: MediaQuery.of(context).size.width / 2, + child: InkWell( + onTap: () { + BlocProvider.of(context)..add(SelectTimeEvent(context: context, isEffective: false)); + // BlocProvider.of(context) + // .selectTime(context, + // isEffective: false); + }, + child: Text( + BlocProvider.of(context) + .expirationTime), + )), + ), + ), + ], + )), + const SizedBox( + height: 20, + ), + if(type=='Online Password') + DefaultContainer( + padding: + const EdgeInsets.symmetric(horizontal: 5, vertical: 5), + child: ListTile( + contentPadding: EdgeInsets.zero, + leading: const BodyMedium( + text: 'Repeat', + fontWeight: FontWeight.normal, + ), + trailing: Transform.scale( + scale: .8, + child: CupertinoSwitch( + value: + BlocProvider.of(context).repeat, + onChanged: (value) { + BlocProvider.of(context).add(ToggleRepeatEvent()); + }, + applyTheme: true, + )), + ), + ), + const SizedBox( + height: 20, + ), + BlocProvider.of(context).repeat + ? DefaultContainer( + padding: const EdgeInsets.symmetric( + horizontal: 20, vertical: 5), + child: Column( + children: [ + Padding( + padding: const EdgeInsets.all(8.0), + child: Row( + mainAxisAlignment: + MainAxisAlignment.spaceBetween, + children: [ + InkWell( + onTap: () { + BlocProvider.of(context).add(const SetStartEndTimeEvent(val: true)); + }, + child: BodyMedium(text: 'Start',fontColor:BlocProvider.of(context).isStartEndTime==false? Colors.black:Colors.blue,fontSize: 18,), + ), + + InkWell( + onTap: () { + BlocProvider.of(context).add(const SetStartEndTimeEvent(val: false)); + + }, + child: BodyMedium(text: 'End',fontColor:BlocProvider.of(context).isStartEndTime? Colors.black:Colors.blue,fontSize: 18,), + ) + ], + ), + ), + const Divider(color:Color(0xffEBEBEB),), + Container( + height: 80, + child: + Container( + height: 90, + child:BlocProvider.of(context).isStartEndTime? TimePickerSpinner( + time: + BlocProvider.of(context).startTime, + is24HourMode: false, + itemHeight: 40, + normalTextStyle: const TextStyle( + fontSize: 24, + ), + highlightedTextStyle: const TextStyle( + fontSize: 30, color: Colors.blue), + onTimeChange: (time) { + + // BlocProvider.of(context).changeTime(time, BlocProvider.of(context).isStartEndTime); + BlocProvider.of(context).add(ChangeTimeEvent(val: time, isStartEndTime: BlocProvider.of(context).isStartEndTime)); + }, + ): Container( + child: TimePickerSpinner( + time: BlocProvider.of(context).endTime, + is24HourMode: false, + itemHeight: 40, + normalTextStyle: const TextStyle( + fontSize: 24, + ), + highlightedTextStyle: const TextStyle( + fontSize: 30, color: Colors.blue), + onTimeChange: (time) { + BlocProvider.of(context).add(ChangeTimeEvent(val: time, isStartEndTime: BlocProvider.of(context).isStartEndTime)); + + // BlocProvider.of(context).changeTime(time, BlocProvider.of(context).isStartEndTime); + }, + ), + ), + ) + ), + const Divider(color:Color(0xffEBEBEB),), + const SizedBox(height: 20), + SelectWeekDays( + width: MediaQuery.of(context).size.width / 1, + fontSize: 18, + fontWeight: FontWeight.w600, + days: BlocProvider.of(context) + .days, + border: false, + selectedDayTextColor: Colors.black, + unSelectedDayTextColor: Colors.grey, + boxDecoration: BoxDecoration( + borderRadius: BorderRadius.circular(30.0), + color: Colors.white), + onSelect: (values) { + BlocProvider.of(context).selectedDay = values; + }, + ), + ], + )) + : SizedBox(), + const SizedBox( + height: 40, + ) + ], + ), + ), + ); + })); + } +} diff --git a/lib/features/devices/view/widgets/smart_door/door_dialog.dart b/lib/features/devices/view/widgets/smart_door/door_dialog.dart new file mode 100644 index 0000000..022a20e --- /dev/null +++ b/lib/features/devices/view/widgets/smart_door/door_dialog.dart @@ -0,0 +1,135 @@ +import 'package:flutter/material.dart'; +import 'package:intl/intl.dart'; +import 'package:syncrow_app/features/devices/model/device_model.dart'; +import 'package:syncrow_app/features/devices/model/temporary_password_model.dart'; +import 'package:syncrow_app/features/shared_widgets/text_widgets/body_medium.dart'; +import 'package:syncrow_app/features/shared_widgets/text_widgets/title_medium.dart'; +import 'package:syncrow_app/utils/context_extension.dart'; +import 'package:syncrow_app/utils/helpers/snack_bar.dart'; +import 'package:syncrow_app/utils/resource_manager/color_manager.dart'; +import 'package:syncrow_app/utils/resource_manager/font_manager.dart'; + +class DoorDialog extends StatefulWidget { + final String title; + final TemporaryPassword value; + + const DoorDialog({ + super.key, + required this.title, + required this.value, + }); + + @override + DoorDialogState createState() => DoorDialogState(); +} + +class DoorDialogState extends State { + @override + void initState() { + super.initState(); + } + + @override + Widget build(BuildContext context) { + final DateTime effectiveDateTime = + DateTime.fromMillisecondsSinceEpoch(widget.value.effectiveTime); + final DateTime expiredDateTime = + DateTime.fromMillisecondsSinceEpoch(widget.value.invalidTime); + final DateFormat formatter = DateFormat('HH:mm'); + return Dialog( + child: Container( + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(20), + ), + padding: const EdgeInsets.only(top: 20), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + BodyMedium( + text: widget.title, + style: context.bodyMedium.copyWith( + color: ColorsManager.primaryColorWithOpacity, + fontWeight: FontsManager.extraBold, + ), + ), + Padding( + padding: const EdgeInsets.symmetric( + vertical: 15, + horizontal: 50, + ), + child: Container( + height: 1, + width: double.infinity, + color: ColorsManager.greyColor, + ), + ), + Padding( + padding: const EdgeInsets.all(15.0), + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + const Text('Effective Time :'), + Text(formatter.format(effectiveDateTime)), + ], + ), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + const Text('Expired Time :'), + Text(formatter.format(expiredDateTime)), + ], + ), + ], + ), + ), + Container( + height: 1, + width: double.infinity, + color: ColorsManager.greyColor, + ), + Row( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + InkWell( + onTap: () { + Navigator.pop(context); + }, + child: Center( + child: BodyMedium( + text: 'Cancel', + style: context.bodyMedium + .copyWith(color: ColorsManager.greyColor), + ), + ), + ), + Container( + height: 50, + width: 1, + color: ColorsManager.greyColor, + ), + InkWell( + onTap: () { + Navigator.pop(context, 'delete'); + + }, + child: Center( + child: BodyMedium( + text: 'Delete Password', + style: context.bodyMedium.copyWith( + color: ColorsManager.primaryColorWithOpacity), + ), + ), + ), + ], + ) + ], + ), + ), + ); + } +} diff --git a/lib/features/devices/view/widgets/smart_door/door_grid.dart b/lib/features/devices/view/widgets/smart_door/door_grid.dart index fc33595..fc5c290 100644 --- a/lib/features/devices/view/widgets/smart_door/door_grid.dart +++ b/lib/features/devices/view/widgets/smart_door/door_grid.dart @@ -3,15 +3,16 @@ import 'package:flutter/widgets.dart'; import 'package:flutter_svg/flutter_svg.dart'; import 'package:syncrow_app/features/devices/view/widgets/smart_door/members_management_view.dart'; import 'package:syncrow_app/features/devices/view/widgets/smart_door/smart_linkage_view.dart'; +import 'package:syncrow_app/features/devices/view/widgets/smart_door/temporary_password_page.dart'; import 'package:syncrow_app/features/devices/view/widgets/smart_door/unlocking_records_view.dart'; import 'package:syncrow_app/features/shared_widgets/default_container.dart'; import 'package:syncrow_app/features/shared_widgets/text_widgets/body_small.dart'; import 'package:syncrow_app/generated/assets.dart'; class DoorLockGrid extends StatelessWidget { - const DoorLockGrid({ - super.key, - }); + String uuid; + DoorLockGrid({ + super.key,required this.uuid}); @override Widget build(BuildContext context) { @@ -28,10 +29,10 @@ class DoorLockGrid extends StatelessWidget { itemBuilder: (context, index) => DefaultContainer( onTap: () { //TODO: remove checking after adding the pages - doorLockButtons[index]['page'] != null + doorLockButtons()[index]['page'] != null ? Navigator.of(context).push( MaterialPageRoute( - builder: (context) => doorLockButtons[index]['page'] as Widget, + builder: (context) => doorLockButtons(val: uuid)[index]['page'] as Widget, ), ) : null; @@ -43,7 +44,7 @@ class DoorLockGrid extends StatelessWidget { ConstrainedBox( constraints: const BoxConstraints(maxHeight: 46, maxWidth: 50), child: SvgPicture.asset( - doorLockButtons[index]['image'] as String, + doorLockButtons()[index]['image'] as String, ), ), const SizedBox( @@ -52,7 +53,7 @@ class DoorLockGrid extends StatelessWidget { Flexible( child: FittedBox( child: BodySmall( - text: doorLockButtons[index]['title'] as String, + text: doorLockButtons()[index]['title'] as String, // doorLockButtons.keys.elementAt(index), textAlign: TextAlign.center, ), @@ -65,7 +66,7 @@ class DoorLockGrid extends StatelessWidget { } } -List> doorLockButtons = [ +List> doorLockButtons({val}) => [ { 'title': 'Unlocking Records', 'image': Assets.assetsIconsDoorlockAssetsUnlockingRecords, @@ -79,7 +80,7 @@ List> doorLockButtons = [ { 'title': 'Temporary Password', 'image': Assets.assetsIconsDoorlockAssetsTemporaryPassword, - 'page': null, + 'page': TemporaryPasswordPage(deviceId:val) , }, { 'title': 'Smart Linkage', diff --git a/lib/features/devices/view/widgets/smart_door/door_interface.dart b/lib/features/devices/view/widgets/smart_door/door_interface.dart index d950bc1..b78378f 100644 --- a/lib/features/devices/view/widgets/smart_door/door_interface.dart +++ b/lib/features/devices/view/widgets/smart_door/door_interface.dart @@ -36,7 +36,6 @@ class DoorInterface extends StatelessWidget { ), ); } - }, builder: (context, state) { SmartDoorModel smartDoorModel = SmartDoorModel( unlockFingerprint: 0, @@ -112,7 +111,8 @@ class DoorInterface extends StatelessWidget { doorLock: doorLock, smartDoorModel: smartDoorModel, ), - const DoorLockGrid(), + DoorLockGrid( uuid: doorLock.uuid!, ), + ], ) ], diff --git a/lib/features/devices/view/widgets/smart_door/temporary_password_page.dart b/lib/features/devices/view/widgets/smart_door/temporary_password_page.dart new file mode 100644 index 0000000..ae9ad6b --- /dev/null +++ b/lib/features/devices/view/widgets/smart_door/temporary_password_page.dart @@ -0,0 +1,126 @@ +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_svg/svg.dart'; +import 'package:syncrow_app/features/devices/view/widgets/smart_door/view_temporary_password.dart'; +import 'package:syncrow_app/features/shared_widgets/default_container.dart'; +import 'package:syncrow_app/features/shared_widgets/default_scaffold.dart'; +import 'package:syncrow_app/features/shared_widgets/text_widgets/body_large.dart'; +import 'package:syncrow_app/features/shared_widgets/text_widgets/body_medium.dart'; +import 'package:syncrow_app/generated/assets.dart'; +import 'package:syncrow_app/utils/resource_manager/color_manager.dart'; + +class TemporaryPasswordPage extends StatelessWidget { + final String? deviceId; + const TemporaryPasswordPage({super.key,this.deviceId}); + + @override + Widget build(BuildContext context) { + return DefaultScaffold( + title: 'Temporary Password', + child: Column( + children: [ + const SizedBox(height: 10), + Column( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const BodyLarge( + text: 'Online Password', + fontWeight: FontWeight.normal, + fontColor: Color(0xfff999999), + ), + const BodyMedium( + text: 'We recommend that you use the online password when the Wi-Fi network is stable.', + fontWeight: FontWeight.normal, + fontColor: ColorsManager.grayColor, + ), + const SizedBox(height: 10), + DefaultContainer( + padding: const EdgeInsets.symmetric(horizontal: 15, vertical: 10), + child:ListTile( + contentPadding: EdgeInsets.zero, + leading: SvgPicture.asset( + Assets.timeLimitedPasswordIcon), + title: const BodyMedium( + text: 'Time-Limited Password', + fontWeight: FontWeight.normal, + ), + onTap: () { + Navigator.of(context).push(MaterialPageRoute(builder: (context) => ViewTemporaryPassword(deviceId:deviceId,type:'Online Password'),)); + }, + trailing: const Icon( + Icons.arrow_forward_ios, + color: ColorsManager.greyColor, + size: 15, + ), + ), + ), + ], + ), + + const SizedBox(height: 10), + Column( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const BodyLarge( + text: 'Offline Password', + fontWeight: FontWeight.normal, + fontColor: Color(0xfff999999), + ), + const BodyMedium( + text: 'WWe recommend that you use the offline password when the Wi-Fi is disconnected or in an unstable Wi-Fi environment.', + fontWeight: FontWeight.normal, + fontColor: ColorsManager.grayColor, + ), + const SizedBox(height: 10), + DefaultContainer( + padding: const EdgeInsets.symmetric(horizontal: 15, vertical: 20), + child: Column(children: [ + ListTile( + contentPadding: EdgeInsets.zero, + leading: SvgPicture.asset(Assets.oneTimePassword), + title: + const BodyMedium( + text: 'One-Time Password', + fontWeight: FontWeight.normal, + ), + onTap: () { + Navigator.of(context).push(MaterialPageRoute(builder: (context) => ViewTemporaryPassword(deviceId:deviceId,type:'One-Time'),)); + + }, + trailing: const Icon( + Icons.arrow_forward_ios, + color: ColorsManager.greyColor, + size: 15, + ), + ), + + const Divider( ), + ListTile( + contentPadding: EdgeInsets.zero, + leading: SvgPicture.asset( + Assets.timeLimitedPassword), + title: const BodyMedium( + text: 'Time-Limited Password', + fontWeight: FontWeight.normal, + ), + onTap: () { + Navigator.of(context).push(MaterialPageRoute(builder: (context) => ViewTemporaryPassword(deviceId:deviceId,type:'Time-Limited'),)); + }, + trailing: const Icon( + Icons.arrow_forward_ios, + color: ColorsManager.greyColor, + size: 15, + ), + ), + ],) + ), + ], + ), + const Spacer(), + ], + ), + ); + } +} diff --git a/lib/features/devices/view/widgets/smart_door/view_temporary_password.dart b/lib/features/devices/view/widgets/smart_door/view_temporary_password.dart new file mode 100644 index 0000000..75350e4 --- /dev/null +++ b/lib/features/devices/view/widgets/smart_door/view_temporary_password.dart @@ -0,0 +1,115 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:flutter_svg/svg.dart'; +import 'package:syncrow_app/features/devices/bloc/smart_door_bloc/smart_door_bloc.dart'; +import 'package:syncrow_app/features/devices/bloc/smart_door_bloc/smart_door_event.dart'; +import 'package:syncrow_app/features/devices/bloc/smart_door_bloc/smart_door_state.dart'; +import 'package:syncrow_app/features/shared_widgets/default_container.dart'; +import 'package:syncrow_app/features/shared_widgets/default_scaffold.dart'; +import 'package:syncrow_app/features/shared_widgets/text_widgets/body_medium.dart'; +import 'package:syncrow_app/generated/assets.dart'; +import 'package:syncrow_app/utils/resource_manager/color_manager.dart'; +import 'create_temporary_password.dart'; +import 'door_dialog.dart'; + +class ViewTemporaryPassword extends StatelessWidget { + final String? deviceId; + final String? type; + const ViewTemporaryPassword({super.key, this.deviceId, this.type}); + + @override + Widget build(BuildContext context) { + return BlocProvider( + create: (BuildContext context) => + SmartDoorBloc(deviceId: deviceId!)..add(InitialPasswordsPage(type:type )), + child: BlocConsumer( + listener: (context, state) { + if (state is FailedState) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text(state.errorMessage), + backgroundColor: Colors.red, + ), + ); + } + }, builder: (context, state) { + final smartDoorBloc = BlocProvider.of(context); + return DefaultScaffold( + title: 'Passwords', + actions: [ + IconButton( + onPressed: () { + Navigator.of(context).push(MaterialPageRoute( + builder: (context) => CreateTemporaryPassword(deviceId: deviceId, type: type), + )).then((result) { + if (result != null && result) { + smartDoorBloc.add(InitialPasswordsPage(type:type )); + } + }); + }, + icon: const Icon(Icons.add)) + ], + child: Builder( + builder: (context) { + return state is LoadingInitialState + ? const Center(child: CircularProgressIndicator()) + : Center( + child: smartDoorBloc.temporaryPasswords!.isNotEmpty + ? ListView.builder( + itemCount: smartDoorBloc.temporaryPasswords!.length, + itemBuilder: (context, index) { + return Padding( + padding: const EdgeInsets.all(5.0), + child: DefaultContainer( + padding: const EdgeInsets.symmetric( + horizontal: 15, vertical: 10), + child: ListTile( + contentPadding: EdgeInsets.zero, + leading: SvgPicture.asset( + Assets.timeLimitedPasswordIcon), + title: BodyMedium( + text: + 'Password Name: ${smartDoorBloc.temporaryPasswords![index].name}', + fontWeight: FontWeight.normal, + ), + onTap: () async { + final result = await showDialog( + context: context, + builder: (context) { + return DoorDialog( + title: 'Password Information', + value: smartDoorBloc.temporaryPasswords![index], + ); + }, + ); + if(result=='delete'){ + smartDoorBloc.add(DeletePasswordEvent(passwordId: smartDoorBloc.temporaryPasswords![index].id.toString())); + } + }, + trailing: const Icon( + Icons.arrow_forward_ios, + color: ColorsManager.greyColor, + size: 15, + ), + ), + ), + ); + }, + ) + : Column( + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + SvgPicture.asset(Assets.noValidPasswords), + const SizedBox( + height: 10, + ), + const BodyMedium(text: 'No Valid Passwords') + ], + ), + ); + }, + )); + })); + } +} diff --git a/lib/features/menu/view/widgets/manage_home/manage_home_view.dart b/lib/features/menu/view/widgets/manage_home/manage_home_view.dart index eeb59ce..7704659 100644 --- a/lib/features/menu/view/widgets/manage_home/manage_home_view.dart +++ b/lib/features/menu/view/widgets/manage_home/manage_home_view.dart @@ -30,7 +30,8 @@ class ManageHomeView extends StatelessWidget { child: Column( mainAxisSize: MainAxisSize.min, mainAxisAlignment: MainAxisAlignment.start, - children: List.generate( + children: + List.generate( spaces.length, (index) { if (index == spaces.length - 1) { diff --git a/lib/generated/assets.dart b/lib/generated/assets.dart index d39855d..7e427ae 100644 --- a/lib/generated/assets.dart +++ b/lib/generated/assets.dart @@ -378,6 +378,29 @@ class Assets { static const String assetsIconsLinkageIconsDoorLockAlarm = "assets/icons/linkageIcons/doorLockAlarm.svg"; + + /// Assets for assetsIconsLinkTimeLimitedPasswordIcon + /// assets/icons/timeLimitedPasswordIcon.svg + static const String timeLimitedPasswordIcon = + "assets/icons/timeLimitedPasswordIcon.svg"; + + + /// Assets for assetsIconsoneTimePassword + /// assets/icons/oneTimePassword.svg + static const String oneTimePassword = + "assets/icons/oneTimePassword.svg"; + + /// Assets for assetsIconsTimeLimitedPassword + /// assets/icons/timeLimitedPassword.svg + static const String timeLimitedPassword = + "assets/icons/timeLimitedPassword.svg"; + + + /// Assets for assetsIconsNoValidPasswords + /// assets/icons/noValidPasswords.svg + static const String noValidPasswords = + "assets/icons/noValidPasswords.svg"; + /// Assets for assetsIconsLinkageIconsFamilyHome /// assets/icons/linkageIcons/familyHome.svg static const String assetsIconsLinkageIconsFamilyHome = diff --git a/lib/services/api/api_links_endpoints.dart b/lib/services/api/api_links_endpoints.dart index 3f6476d..9c50dc6 100644 --- a/lib/services/api/api_links_endpoints.dart +++ b/lib/services/api/api_links_endpoints.dart @@ -111,4 +111,24 @@ abstract class ApiEndpoints { static const String editDevicePermission = '$baseUrl/device-permission/edit/{userId}'; static const String assignDeviceToRoom = '$baseUrl/device/room'; + + + //////////////////////Door Lock ////////////////////// + //online + static const String addTemporaryPassword = '$baseUrl/door-lock/temporary-password/online/{doorLockUuid}'; + static const String getTemporaryPassword = '$baseUrl/door-lock/temporary-password/online/{doorLockUuid}'; + + + //one-time offline + static const String addOneTimeTemporaryPassword = '$baseUrl/door-lock/temporary-password/offline/one-time/{doorLockUuid}'; + static const String getOneTimeTemporaryPassword = '$baseUrl/door-lock/temporary-password/offline/one-time/{doorLockUuid}'; + + //multiple-time offline + static const String addMultipleTimeTemporaryPassword = '$baseUrl/door-lock/temporary-password/offline/multiple-time/{doorLockUuid}'; + static const String getMultipleTimeTemporaryPassword = '$baseUrl/door-lock/temporary-password/offline/multiple-time/{doorLockUuid}'; + + //multiple-time offline + static const String deleteTemporaryPassword = '$baseUrl/door-lock/temporary-password/{doorLockUuid}/{passwordId}'; + + } diff --git a/lib/services/api/devices_api.dart b/lib/services/api/devices_api.dart index aebbe94..0cd1862 100644 --- a/lib/services/api/devices_api.dart +++ b/lib/services/api/devices_api.dart @@ -1,4 +1,5 @@ import 'dart:async'; +import 'dart:convert'; import 'package:syncrow_app/features/devices/model/device_category_model.dart'; import 'package:syncrow_app/features/devices/model/device_control_model.dart'; @@ -6,6 +7,8 @@ import 'package:syncrow_app/features/devices/model/device_model.dart'; import 'package:syncrow_app/services/api/api_links_endpoints.dart'; import 'package:syncrow_app/services/api/http_service.dart'; +import '../../features/devices/model/create_temporary_password_model.dart'; + class DevicesAPI { static final HTTPService _httpService = HTTPService(); @@ -58,7 +61,8 @@ class DevicesAPI { static Future> getDeviceStatus(String deviceId) async { final response = await _httpService.get( - path: ApiEndpoints.deviceFunctionsStatus.replaceAll('{deviceUuid}', deviceId), + path: ApiEndpoints.deviceFunctionsStatus + .replaceAll('{deviceUuid}', deviceId), showServerMessage: false, expectedResponseModel: (json) { return json; @@ -67,7 +71,8 @@ class DevicesAPI { return response; } - static Future> getDeviceByGroupName(String unitId, String groupName) async { + static Future> getDeviceByGroupName( + String unitId, String groupName) async { final response = await _httpService.get( path: ApiEndpoints.devicesByGroupName .replaceAll('{unitUuid}', unitId) @@ -106,7 +111,8 @@ class DevicesAPI { return response; } - static Future> getDevicesByGatewayId(String gatewayId) async { + static Future> getDevicesByGatewayId( + String gatewayId) async { final response = await _httpService.get( path: ApiEndpoints.gatewayApi.replaceAll('{gatewayUuid}', gatewayId), showServerMessage: false, @@ -123,4 +129,79 @@ class DevicesAPI { ); return response; } + + static Future getTemporaryPasswords(String deviceId, pageType) async { + final response = await _httpService.get( + path: pageType == 'One-Time' + ? ApiEndpoints.getOneTimeTemporaryPassword + .replaceAll('{doorLockUuid}', deviceId) + : pageType == 'Online Password' + ? ApiEndpoints.getTemporaryPassword + .replaceAll('{doorLockUuid}', deviceId) + : ApiEndpoints.getMultipleTimeTemporaryPassword + .replaceAll('{doorLockUuid}', deviceId), + showServerMessage: false, + expectedResponseModel: (json) { + return json; + }, + ); + return response; + } + +//create password + static Future createPassword({ + required String name, + required String password, + required String effectiveTime, + required String invalidTime, + required String deviceId, + required String pageType, + List? scheduleList, + }) async { + + String endpointPath; + if (pageType == 'Online Password') { + endpointPath = ApiEndpoints.addTemporaryPassword + .replaceAll('{doorLockUuid}', deviceId); + } else if (pageType == 'One-Time') { + endpointPath = ApiEndpoints.addOneTimeTemporaryPassword + .replaceAll('{doorLockUuid}', deviceId); + } else { + endpointPath = ApiEndpoints.addMultipleTimeTemporaryPassword + .replaceAll('{doorLockUuid}', deviceId); + } + Map body = { + "name": name, + "password": password, + "effectiveTime": effectiveTime, + "invalidTime": invalidTime, + }; + if (pageType == 'Online Password' && scheduleList != null) { + body["scheduleList"] = + scheduleList.map((schedule) => schedule.toJson()).toList(); + } + final response = await _httpService.post( + path: endpointPath, + body: body, + showServerMessage: false, + expectedResponseModel: (json) => json, + ); + return response; + + } + + + static Future> deletePassword( + {required String passwordId, required String deviceId}) async { + final response = await _httpService.delete( + path: ApiEndpoints.deleteTemporaryPassword + .replaceAll('{doorLockUuid}', deviceId) + .replaceAll('{passwordId}', passwordId), + showServerMessage: false, + expectedResponseModel: (json) { + return json; + }, + ); + return response; + } } diff --git a/lib/utils/resource_manager/color_manager.dart b/lib/utils/resource_manager/color_manager.dart index 05a1069..7815e27 100644 --- a/lib/utils/resource_manager/color_manager.dart +++ b/lib/utils/resource_manager/color_manager.dart @@ -21,4 +21,5 @@ abstract class ColorsManager { static const Color slidingBlueColor = Color(0x99023DFE); static const Color blackColor = Color(0xFF000000); static const Color lightGreen = Color(0xFF00FF0A); + static const Color grayColor = Color(0xFF999999); } diff --git a/pubspec.lock b/pubspec.lock index 75ce64d..8b82f21 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -121,6 +121,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.0.6" + day_picker: + dependency: "direct main" + description: + name: day_picker + sha256: a0f73716d688b3643769db39e7626cae54026bec24c33a8ebf5a6f8c618a05f2 + url: "https://pub.dev" + source: hosted + version: "2.2.0" dio: dependency: "direct main" description: @@ -834,6 +842,14 @@ packages: url: "https://pub.dev" source: hosted version: "0.7.0" + time_picker_spinner: + dependency: "direct main" + description: + name: time_picker_spinner + sha256: "53d824801d108890d22756501e7ade9db48b53dac1ec41580499dd4ebd128e3c" + url: "https://pub.dev" + source: hosted + version: "1.0.0" typed_data: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 7a25e93..9a8f724 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -20,7 +20,7 @@ dependencies: flutter_svg: ^2.0.10+1 fl_chart: ^0.66.2 sleek_circular_slider: ^2.0.1 - + day_picker: 2.2.0 # Utility Packages flutter_secure_storage: ^9.0.0 cached_network_image: ^3.3.1 @@ -42,6 +42,7 @@ dependencies: permission_handler: ^11.3.1 pin_code_fields: ^8.0.1 share_plus: ^9.0.0 + time_picker_spinner: ^1.0.0 dev_dependencies: flutter_test: