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 811953d..cdaab9d 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,11 +1,13 @@ import 'dart:math'; -import 'package:day_picker/model/day_in_week.dart'; +import 'package:flutter/cupertino.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/offline_password_model.dart'; +import 'package:syncrow_app/features/devices/model/offline_temporary_password.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'; @@ -17,12 +19,13 @@ import 'package:syncrow_app/utils/resource_manager/color_manager.dart'; class SmartDoorBloc extends Bloc { final String deviceId; late SmartDoorModel deviceStatus; - static String pageType = ''; bool isSavingPassword = false; SmartDoorBloc({required this.deviceId}) : super(InitialState()) { on(_fetchSmartDoorStatus); on(getTemporaryPasswords); + on(getOneTimePasswords); + on(getTimeLimitPasswords); on(_updateLock); on(savePassword); on(toggleRepeat); @@ -31,7 +34,56 @@ class SmartDoorBloc extends Bloc { on(generate7DigitNumber); on(selectTime); on(deletePassword); + on(generateAndSavePasswordTimeLimited); + on(generateAndSavePasswordOneTime); + on(toggleDaySelection); + on(_renamePassword); } + + TextEditingController passwordController = TextEditingController(); + TextEditingController passwordNameController = TextEditingController(); + String effectiveTime = 'Select Time'; + String passwordId = ''; + int? effectiveTimeTimeStamp; + String expirationTime = 'Select Time'; + int? expirationTimeTimeStamp; + bool repeat = false; + bool isStartEndTime = true; + DateTime? startTime; + DateTime? endTime; + List? temporaryPasswords = []; + List? oneTimePasswords = []; + List? timeLimitPasswords = []; + + Future 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()); + return passwordController.text; + } + + + Future generateAndSavePasswordOneTime (GenerateAndSavePasswordOneTimeEvent event, Emitter emit) async { + try { + if (isSavingPassword) return; + isSavingPassword = true; + emit(LoadingInitialState()); + var res = await DevicesAPI.generateOneTimePassword(deviceId: deviceId); + ApiResponse pass= ApiResponse.fromJson(res); + passwordController.text =pass.data.offlineTempPassword; + passwordId=pass.data.offlineTempPasswordId; + emit(const GeneratePasswordOneTimestate(generated: true)); + } catch (_) { + emit(FailedState(errorMessage: _.toString())); + }finally { + isSavingPassword = false; + } + } + void _fetchSmartDoorStatus(InitialEvent event, Emitter emit) async { try { emit(LoadingInitialState()); @@ -48,18 +100,29 @@ class SmartDoorBloc extends Bloc { } } + void _renamePassword(RenamePasswordEvent event, Emitter emit) async { + try { + emit(LoadingInitialState()); + var response = await DevicesAPI.renamePass( + name:passwordNameController.text , + doorLockUuid:deviceId , + passwordId:passwordId + ); + emit(UpdateState(smartDoorModel: deviceStatus)); + } catch (e) { + emit(FailedState(errorMessage: e.toString())); + return; + } + } + void getTemporaryPasswords(InitialPasswordsPage event, Emitter emit) async { try { emit(LoadingInitialState()); - pageType = event.type!; - var response = await DevicesAPI.getTemporaryPasswords(deviceId, pageType); + var response = await DevicesAPI.getTemporaryPasswords(deviceId, ); 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"); + temporaryPasswords = (response['data'] as List).map((item) => TemporaryPassword.fromJson(item)).toList(); } emit(TemporaryPasswordsLoadedState(temporaryPassword: temporaryPasswords!)); } catch (e) { @@ -67,41 +130,51 @@ class SmartDoorBloc extends Bloc { } } - 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 = []; + void getOneTimePasswords(InitialOneTimePassword event, Emitter emit) async { + try { + emit(LoadingInitialState()); + var response = await DevicesAPI.getOneTimePasswords(deviceId); + if (response is List) { + oneTimePasswords = response.map((item) => OfflinePasswordModel.fromJson(item)).toList(); + } + emit(TemporaryPasswordsLoadedState(temporaryPassword: temporaryPasswords!)); + } catch (e) { + emit(FailedState(errorMessage: e.toString())); + } + } + + void getTimeLimitPasswords(InitialTimeLimitPassword event, Emitter emit) async { + try { + emit(LoadingInitialState()); + var response = await DevicesAPI.getTimeLimitPasswords(deviceId); + if (response is List) { + timeLimitPasswords = response.map((item) => OfflinePasswordModel.fromJson(item)).toList(); + } + emit(TemporaryPasswordsLoadedState(temporaryPassword: temporaryPasswords!)); + } catch (e) { + emit(FailedState(errorMessage: e.toString())); + } + } 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()); + emit(IsRepeatState(repeat: repeat)); return repeat; } bool setStartEndTime(SetStartEndTimeEvent event, Emitter emit) { emit(LoadingInitialState()); isStartEndTime = event.val; - emit(IsStartEndState()); + emit(IsStartEndState(isStartEndTime:isStartEndTime)); return isStartEndTime; } @@ -119,16 +192,6 @@ class SmartDoorBloc extends Bloc { 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 { emit(ChangeTimeState()); final DateTime? picked = await showDatePicker( @@ -166,14 +229,13 @@ class SmartDoorBloc extends Bloc { timePicked.hour, timePicked.minute, ); - // Convert selectedDateTime to a timestamp without seconds and milliseconds final selectedTimestamp = DateTime( - selectedDateTime.year, - selectedDateTime.month, - selectedDateTime.day, - selectedDateTime.hour, - selectedDateTime.minute, - ).millisecondsSinceEpoch ~/ + 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!) { @@ -203,7 +265,6 @@ class SmartDoorBloc extends Bloc { isSavingPassword = true; emit(LoadingSaveState()); var res = await DevicesAPI.createPassword( - pageType: pageType, deviceId: deviceId, effectiveTime: effectiveTimeTimeStamp.toString(), invalidTime: expirationTimeTimeStamp.toString(), @@ -214,24 +275,48 @@ class SmartDoorBloc extends Bloc { Schedule( effectiveTime: getTimeOnly(startTime), invalidTime: getTimeOnly(endTime).toString(), - workingDay: selectedDay!, + workingDay: selectedDays, ), ], ); Navigator.of(event.context).pop(true); CustomSnackBar.displaySnackBar('Save Successfully'); emit(SaveState()); - } catch (_) {}finally { + } catch (_) { + + }finally { isSavingPassword = false; } } + + Future generateAndSavePasswordTimeLimited (GenerateAndSavePasswordTimeLimitEvent event, Emitter emit) async { + if (timeLimitValidate() || isSavingPassword) return; + try { + isSavingPassword = true; + emit(LoadingInitialState()); + var res = await DevicesAPI.generateMultiTimePassword(deviceId: deviceId, + effectiveTime: effectiveTimeTimeStamp.toString(), + invalidTime: expirationTimeTimeStamp.toString(), ); + ApiResponse pass= ApiResponse.fromJson(res); + passwordController.text =pass.data.offlineTempPassword; + passwordId=pass.data.offlineTempPasswordId; + CustomSnackBar.displaySnackBar('Save Successfully'); + emit(const GeneratePasswordOneTimestate(generated: true)); + } catch (_) { + emit(FailedState(errorMessage: e.toString())); + } + finally { + isSavingPassword = false; + } + } + 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)); + await DevicesAPI.deletePassword(deviceId: deviceId, passwordId: event.passwordId) + .then((value) async { + add(InitialPasswordsPage()); }); } catch (e) { emit(FailedState(errorMessage: e.toString())); @@ -239,7 +324,7 @@ class SmartDoorBloc extends Bloc { } bool _validateInputs() { - if (passwordController.text.length < 7) { + if (passwordController.text.length < 7 ) { CustomSnackBar.displaySnackBar('Password less than 7'); return true; } @@ -247,7 +332,7 @@ class SmartDoorBloc extends Bloc { CustomSnackBar.displaySnackBar('Password required'); return true; } - if (passwordNameController.text.isEmpty) { + if (passwordNameController.text.isEmpty ) { CustomSnackBar.displaySnackBar('Password name required'); return true; } @@ -259,25 +344,51 @@ class SmartDoorBloc extends Bloc { CustomSnackBar.displaySnackBar('Select expiration time'); return true; } - if (repeat == true && (endTime == null || startTime == null || selectedDay == null)) { + if (repeat == true && (endTime == null || startTime == null || selectedDays == null)) { CustomSnackBar.displaySnackBar('Start Time and End time and the days required '); return true; } return false; } + bool timeLimitValidate() { - 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'), + 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; + } + return false; + } + + 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()); + } + 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 df138ae..b73a381 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 @@ -9,11 +9,9 @@ abstract class SmartDoorEvent extends Equatable { } class InitialEvent extends SmartDoorEvent {} -class InitialPasswordsPage extends SmartDoorEvent { - final String? type; - const InitialPasswordsPage({ this.type}); -} +class InitialPasswordsPage extends SmartDoorEvent {} class InitialOneTimePassword extends SmartDoorEvent {} +class InitialTimeLimitPassword extends SmartDoorEvent {} class UpdateLockEvent extends SmartDoorEvent { final bool value; @@ -27,9 +25,26 @@ class SavePasswordEvent extends SmartDoorEvent { const SavePasswordEvent({required this.context}); @override List get props => [context]; +} +class GenerateAndSavePasswordTimeLimitEvent extends SmartDoorEvent { + final BuildContext context; + const GenerateAndSavePasswordTimeLimitEvent({required this.context}); + @override + List get props => [context]; +} + +class GenerateAndSavePasswordOneTimeEvent extends SmartDoorEvent { + final BuildContext context; + const GenerateAndSavePasswordOneTimeEvent({required this.context}); + @override + List get props => [context]; +} + +class GeneratePasswordEvent extends SmartDoorEvent { + + } -class GeneratePasswordEvent extends SmartDoorEvent {} class SelectTimeEvent extends SmartDoorEvent { final BuildContext context; final bool isEffective; @@ -41,7 +56,6 @@ class SelectTimeEvent extends SmartDoorEvent { class ToggleRepeatEvent extends SmartDoorEvent {} class SetStartEndTimeEvent extends SmartDoorEvent { final bool val; - const SetStartEndTimeEvent({required this.val}); @override List get props => [val]; @@ -55,6 +69,14 @@ class DeletePasswordEvent extends SmartDoorEvent { List get props => [passwordId]; } +class ToggleDaySelectionEvent extends SmartDoorEvent { + final String key; + + const ToggleDaySelectionEvent({required this.key}); + @override + List get props => [key]; +} + class ChangeTimeEvent extends SmartDoorEvent { final dynamic val; final bool isStartEndTime; @@ -64,4 +86,7 @@ class ChangeTimeEvent extends SmartDoorEvent { List get props => [val,isStartEndTime]; } +class RenamePasswordEvent extends SmartDoorEvent { +} + 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 29b1d94..7741361 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 @@ -38,13 +38,27 @@ class FailedState extends SmartDoorState { List get props => [errorMessage]; } -class GeneratePasswordState extends SmartDoorState {} +class GeneratePasswordState extends SmartDoorState { +} class TimeSelectedState extends SmartDoorState {} -class IsRepeatState extends SmartDoorState {} +class IsRepeatState extends SmartDoorState { + final bool repeat; + const IsRepeatState({required this.repeat}); -class IsStartEndState extends SmartDoorState {} + @override + List get props => [repeat]; + +} + +class IsStartEndState extends SmartDoorState { + final bool isStartEndTime; + const IsStartEndState({required this.isStartEndTime}); + + @override + List get props => [isStartEndTime]; +} class ChangeStartTimeState extends SmartDoorState {} @@ -56,6 +70,12 @@ class SaveState extends SmartDoorState {} class LoadingSaveState extends SmartDoorState {} +class GeneratePasswordOneTimestate extends SmartDoorState { + final bool generated; + const GeneratePasswordOneTimestate({required this.generated}); + List get props => [generated]; +} + class TemporaryPasswordsLoadedState extends SmartDoorState { final List temporaryPassword; const TemporaryPasswordsLoadedState({required this.temporaryPassword}); diff --git a/lib/features/devices/model/offline_password_model.dart b/lib/features/devices/model/offline_password_model.dart new file mode 100644 index 0000000..17d8faa --- /dev/null +++ b/lib/features/devices/model/offline_password_model.dart @@ -0,0 +1,63 @@ + + +class OfflinePasswordModel { + final dynamic gmtCreate; + final dynamic gmtExpired; + final dynamic gmtStart; + final bool hasClearPwd; + final String optUid; + final String pwd; + final dynamic pwdId; + final String pwdName; + final String pwdTypeCode; + final String revokedPwdName; + final dynamic status; + + OfflinePasswordModel({ + required this.gmtCreate, + required this.gmtExpired, + required this.gmtStart, + required this.hasClearPwd, + required this.optUid, + required this.pwd, + required this.pwdId, + required this.pwdName, + required this.pwdTypeCode, + required this.revokedPwdName, + required this.status, + }); + + // Factory method to create a Password from a JSON map + factory OfflinePasswordModel.fromJson(Map json) { + return OfflinePasswordModel( + gmtCreate: json['gmtCreate'], + gmtExpired: json['gmtExpired'], + gmtStart: json['gmtStart'], + hasClearPwd: json['hasClearPwd'], + optUid: json['optUid'], + pwd: json['pwd'], + pwdId: json['pwdId'], + pwdName: json['pwdName'], + pwdTypeCode: json['pwdTypeCode'], + revokedPwdName: json['revokedPwdName'], + status: json['status'], + ); + } + + // Method to convert a Password object to a JSON map + Map toJson() { + return { + 'gmtCreate': gmtCreate, + 'gmtExpired': gmtExpired, + 'gmtStart': gmtStart, + 'hasClearPwd': hasClearPwd, + 'optUid': optUid, + 'pwd': pwd, + 'pwdId': pwdId, + 'pwdName': pwdName, + 'pwdTypeCode': pwdTypeCode, + 'revokedPwdName': revokedPwdName, + 'status': status, + }; + } +} \ No newline at end of file diff --git a/lib/features/devices/model/offline_temporary_password.dart b/lib/features/devices/model/offline_temporary_password.dart new file mode 100644 index 0000000..33ed4fe --- /dev/null +++ b/lib/features/devices/model/offline_temporary_password.dart @@ -0,0 +1,69 @@ +class OfflineTemporaryPassword { + dynamic effectiveTime; + dynamic invalidTime; + dynamic offlineTempPassword; + dynamic offlineTempPasswordId; + dynamic offlineTempPasswordName; + + OfflineTemporaryPassword({ + required this.effectiveTime, + required this.invalidTime, + required this.offlineTempPassword, + required this.offlineTempPasswordId, + required this.offlineTempPasswordName, + }); + + factory OfflineTemporaryPassword.fromJson(Map json) { + return OfflineTemporaryPassword( + effectiveTime: json['effective_time'], + invalidTime: json['invalid_time'], + offlineTempPassword: json['offline_temp_password'], + offlineTempPasswordId: json['offline_temp_password_id'], + offlineTempPasswordName: json['offline_temp_password_name'], + ); + } + + Map toJson() { + return { + 'effective_time': effectiveTime, + 'invalid_time': invalidTime, + 'offline_temp_password': offlineTempPassword, + 'offline_temp_password_id': offlineTempPasswordId, + 'offline_temp_password_name': offlineTempPasswordName, + }; + } +} + +class ApiResponse { + int statusCode; + bool success; + String message; + OfflineTemporaryPassword data; + + ApiResponse({ + required this.statusCode, + required this.success, + required this.message, + required this.data, + }); + + factory ApiResponse.fromJson(Map json) { + return ApiResponse( + statusCode: json['statusCode'], + success: json['success'], + message: json['message'], + data: OfflineTemporaryPassword.fromJson(json['data']['result']), + ); + } + + Map toJson() { + return { + 'statusCode': statusCode, + 'success': success, + 'message': message, + 'data': { + 'result': data.toJson(), + }, + }; + } +} diff --git a/lib/features/devices/view/widgets/name_time_widget.dart b/lib/features/devices/view/widgets/name_time_widget.dart new file mode 100644 index 0000000..d54ecf4 --- /dev/null +++ b/lib/features/devices/view/widgets/name_time_widget.dart @@ -0,0 +1,116 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.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/shared_widgets/default_container.dart'; +import 'package:syncrow_app/features/shared_widgets/text_widgets/body_medium.dart'; +import 'package:syncrow_app/utils/resource_manager/color_manager.dart'; + +class NameTimeWidget extends StatelessWidget { + const NameTimeWidget({super.key}); + + @override + Widget build(BuildContext context) { + return DefaultContainer( + padding: const EdgeInsets.all(20), + child: Column( + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Expanded( + child: Container( + padding: const EdgeInsets.all(10.0), + child: const BodyMedium( + text: 'Password Name', + fontWeight: FontWeight.normal, + ), + ), + ), + SizedBox( + width: MediaQuery.of(context).size.width / 2.6, + child: TextFormField( + controller: BlocProvider.of(context) + .passwordNameController, + decoration: const InputDecoration( + hintText: 'Enter The Name', + hintStyle: TextStyle( + fontSize: 14, color: ColorsManager.textGray)), + )), + ], + ), + Column( + children: [ + const Divider( + color: ColorsManager.graysColor, + ), + Padding( + padding: const EdgeInsets.all(10.0), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + const Expanded( + child: BodyMedium( + text: 'Effective Time', + fontWeight: FontWeight.normal, + ), + ), + SizedBox( + width: MediaQuery.of(context).size.width / 3.5, + child: InkWell( + onTap: () { + + BlocProvider.of(context).add(SelectTimeEvent(context: context, isEffective: true)); + }, + child: Text( + BlocProvider.of(context).effectiveTime, + style: TextStyle(fontSize: 14, + color: BlocProvider.of(context).effectiveTime == + 'Select Time' + ? ColorsManager.textGray + : null), + ), + )), + ],), + ), + const Divider( + color: ColorsManager.graysColor, + ), + Padding( + padding: const EdgeInsets.all(10.0), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + const Expanded( + child: BodyMedium( + text: 'Expiration Time', + fontWeight: FontWeight.normal, + ), + ), + SizedBox( + width: MediaQuery.of(context).size.width / 3.5, + child: InkWell( + onTap: () { + BlocProvider.of(context).add( + SelectTimeEvent( + context: context, isEffective: false)); + }, + child: Text( + BlocProvider.of(context).expirationTime, + style: TextStyle( + fontSize: 14, + color: BlocProvider.of(context).expirationTime == 'Select Time' + ? ColorsManager.textGray + : null), + ), + ), + ), + ], + ), + ), + ], + ), + ], + )); + } +} diff --git a/lib/features/devices/view/widgets/offline_one_time_password_page.dart b/lib/features/devices/view/widgets/offline_one_time_password_page.dart new file mode 100644 index 0000000..8f8ddec --- /dev/null +++ b/lib/features/devices/view/widgets/offline_one_time_password_page.dart @@ -0,0 +1,228 @@ + +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_bloc/flutter_bloc.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/devices/view/widgets/smart_door/repeat_widget.dart'; +import 'package:syncrow_app/features/shared_widgets/default_button.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/door_lock_button.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'; + +class OfflineOneTimePasswordPage extends StatelessWidget { + final String? deviceId; + final String? type; + const OfflineOneTimePasswordPage({super.key, this.deviceId, this.type}); + @override + Widget build(BuildContext context) { + bool isRepeat = false; + bool generated = false; + 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, + ), + ); + } + if (state is IsRepeatState){ + isRepeat = state.repeat; + } + if (state is GeneratePasswordOneTimestate ){ + generated = state.generated; + } + }, builder: (context, state) { + final smartDoorBloc = BlocProvider.of(context); + return DefaultScaffold( + appBar: AppBar( + backgroundColor: Colors.transparent, + centerTitle: true, + title: const BodyLarge( + text: 'Create One-Time Password', + fontColor: ColorsManager.primaryColor, + fontWeight: FontsManager.bold, + ), + leading: IconButton( + onPressed: () { + Navigator.of(context).pop(true); + }, + icon: const Icon(Icons.arrow_back) + ), + ), + 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: Column( + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + mainAxisSize: MainAxisSize.max, + children: [ + Flexible( + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children:smartDoorBloc.passwordController.text.isEmpty? + List.generate(10, (index) { + return const Padding( + padding: EdgeInsets.symmetric(horizontal: 4.0,vertical: 15), + child: Icon( + Icons.circle, + size: 20.0, + color: Colors.black, + ), + ); + }) :[ + Expanded( + child: Row( + children: [ + Expanded( + child: BodyLarge( + style: const TextStyle( + color: ColorsManager.primaryColor, + fontWeight: FontWeight.bold, + letterSpacing: 8.0 , + fontSize: 25, + wordSpacing: 2), + textAlign: TextAlign.center, + text: smartDoorBloc.passwordController.text, + fontSize: 25, + ),), + IconButton( + onPressed: () async { + await Clipboard.setData(ClipboardData( + text: smartDoorBloc.passwordController.text)); + }, + icon: const Icon(Icons.copy) + ), + ], + ), + ), + ], + )), + const SizedBox( + width: 10, + ), + + ], + ), + if(smartDoorBloc.passwordController.text.isNotEmpty) + Column( + children: [ + const Divider( + color: ColorsManager.graysColor, + ), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Expanded( + child: Container( + padding: const EdgeInsets.all(10.0), + child: const BodyMedium( + text: 'Password Name', + fontWeight: FontWeight.normal, + ), + ), + ), + SizedBox( + width: MediaQuery.of(context).size.width / 2.6, + child: TextFormField( + controller: BlocProvider.of(context).passwordNameController, + decoration: const InputDecoration( + hintText: 'Enter The Name', + hintStyle: TextStyle( + fontSize: 14, color: ColorsManager.textGray)), + )), + ], + ), + + ], + ), + + ], + ), + ), + const SizedBox( + height: 20, + ), + const BodyMedium( + textAlign: TextAlign.center, + text: 'Save the password immediately. The password is not displayed in the app.', + fontWeight: FontWeight.normal, + fontColor: ColorsManager.grayColor, + ), + + // NameTimeWidget(type:type!), + const SizedBox( + height: 20, + ), + Center( + child: SizedBox( + width: MediaQuery.of(context).size.width/1.5, + child: DoorLockButton( + isDone: generated, + isLoading: smartDoorBloc.isSavingPassword , + borderRadius: 30, + backgroundColor:ColorsManager.primaryColor , + onPressed: () async { + if(generated==false){ + smartDoorBloc.add(GenerateAndSavePasswordOneTimeEvent(context: context)); + await Clipboard.setData( + ClipboardData(text: smartDoorBloc.passwordController.text) + ); + }else{ + if(smartDoorBloc.passwordNameController.text.isNotEmpty){ + smartDoorBloc.add(RenamePasswordEvent()); + } + Navigator.of(context).pop(true); + } + }, + child: const BodyMedium( + text: 'Obtain Password', + fontWeight: FontWeight.bold, + fontColor: Colors.white, + ),), + ), + ), + const SizedBox( + height: 20, + ), + isRepeat? const RepeatWidget():const SizedBox(), + const SizedBox( + height: 40, + ) + ], + ), + ), + ); + })); + } +} 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 index 0cf50f7..7d2b807 100644 --- a/lib/features/devices/view/widgets/smart_door/create_temporary_password.dart +++ b/lib/features/devices/view/widgets/smart_door/create_temporary_password.dart @@ -1,4 +1,3 @@ -import 'package:day_picker/day_picker.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; @@ -7,6 +6,8 @@ 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/devices/view/widgets/name_time_widget.dart'; +import 'package:syncrow_app/features/devices/view/widgets/smart_door/repeat_widget.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'; @@ -14,15 +15,15 @@ import 'package:syncrow_app/features/shared_widgets/text_widgets/body_medium.dar 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'; -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) { + bool isRepeat = false; + bool generated = false; return BlocProvider( create: (BuildContext context) => SmartDoorBloc(deviceId: deviceId!), child: BlocConsumer(listener: (context, state) { @@ -34,7 +35,14 @@ class CreateTemporaryPassword extends StatelessWidget { ), ); } + if (state is IsRepeatState){ + isRepeat = state.repeat; + } + if (state is GeneratePasswordOneTimestate ){ + generated = state.generated; + } }, builder: (context, state) { + final smartDoorBloc = BlocProvider.of(context); return DefaultScaffold( appBar: AppBar( backgroundColor: Colors.transparent, @@ -46,360 +54,143 @@ class CreateTemporaryPassword extends StatelessWidget { ), leading: IconButton( onPressed: () { - Navigator.of(context).pop('UpdatePage'); + Navigator.of(context).pop(true); }, icon: const Icon(Icons.arrow_back) ), - actions: [ + actions: + type == 'Online Password'?[ TextButton( onPressed: () { - BlocProvider.of(context) - .add(SavePasswordEvent(context: context)); + smartDoorBloc.add(SavePasswordEvent(context: context)); }, child: const Text('Save') ) - ], + ]:null, ), 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( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - mainAxisSize: MainAxisSize.max, - children: [ - Flexible( - flex: 2, - child: PinCodeTextField( - onCompleted: (value) { - if (value.split('').every((char) => char == '1')) { - BlocProvider.of(context).passwordController.clear(); - CustomSnackBar.displaySnackBar('All characters cannot be 1.'); - } - }, - 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: const Duration(milliseconds: 300), - backgroundColor: Colors.white, - enableActiveFill: false, - controller: BlocProvider.of(context).passwordController, - appContext: context, - )), - const SizedBox( - width: 10, - ), - Flexible( - child: 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: 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: Padding( + padding: EdgeInsets.symmetric(horizontal: type == 'Online Password'?0:25), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + mainAxisSize: MainAxisSize.max, + children: [ + Flexible( + flex: 2, + child: PinCodeTextField( + onCompleted: (value) { + if (value.split('').every((char) => char == '1')) { + smartDoorBloc.passwordController.clear(); + CustomSnackBar.displaySnackBar('All characters cannot be 1.'); + } }, - child: const Text('Copy')) - : const SizedBox(), - const SizedBox( - height: 20, - ), - DefaultContainer( - padding: const EdgeInsets.all(20), - child: Column( - children: [ - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Expanded( - child: Container( - padding: const EdgeInsets.all(10.0), - child: const BodyMedium( - text: 'Password Name', - fontWeight: FontWeight.normal, - ), - ), - ), - SizedBox( - width: MediaQuery.of(context).size.width / 2.6, - child: TextFormField( - controller: BlocProvider.of(context) - .passwordNameController, - decoration: const InputDecoration( - hintText: 'Enter The Name', - hintStyle: TextStyle( - fontSize: 14, color: ColorsManager.textGray)), - )), - ], - ), - const Divider( - color: ColorsManager.graysColor, - ), - Padding( - padding: const EdgeInsets.all(10.0), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - const Expanded( - child: BodyMedium( - text: 'Effective Time', - fontWeight: FontWeight.normal, - ), - ), - SizedBox( - width: MediaQuery.of(context).size.width / 3.5, - child: InkWell( - onTap: () { - BlocProvider.of(context).add( - SelectTimeEvent( - context: context, isEffective: true)); - }, - child: Text( - BlocProvider.of(context).effectiveTime, - style: TextStyle( - fontSize: 14, - color: BlocProvider.of(context) - .effectiveTime == - 'Select Time' - ? ColorsManager.textGray - : null), - ), - )), - ], - ), - ), - const Divider( - color: ColorsManager.graysColor, - ), - Padding( - padding: const EdgeInsets.all(10.0), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - const Expanded( - child: BodyMedium( - text: 'Expiration Time', - fontWeight: FontWeight.normal, - ), - ), - SizedBox( - width: MediaQuery.of(context).size.width / 3.5, - child: InkWell( - onTap: () { - BlocProvider.of(context).add( - SelectTimeEvent( - context: context, isEffective: false)); - }, - child: Text( - BlocProvider.of(context).expirationTime, - style: TextStyle( - fontSize: 14, - color: BlocProvider.of(context) - .expirationTime == - 'Select Time' - ? ColorsManager.textGray - : null), - ), - ), - ), - ], - ), - ), - ], - )), - const SizedBox( - height: 20, - ), - if (type == 'Online Password') - DefaultContainer( - padding: const EdgeInsets.symmetric(horizontal: 10, 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, - )), - ), + autoDisposeControllers: false, + keyboardType: TextInputType.phone, + length: 7, + // enabled:type == 'Online Password'? true:false, + 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: const Duration(milliseconds: 300), + backgroundColor: Colors.white, + enableActiveFill: false, + controller: smartDoorBloc.passwordController, + appContext: context, + )), + const SizedBox( + width: 10, ), - 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: ColorsManager.graysColor, - ), - Container( - height: 110, - child: BlocProvider.of(context).isStartEndTime - ? TimePickerSpinner( - time: - BlocProvider.of(context).startTime, - is24HourMode: false, - itemHeight: 40, - normalTextStyle: const TextStyle( - color: Colors.grey, - fontSize: 24, - ), - highlightedTextStyle: - const TextStyle(fontSize: 30, color: Colors.blue), - onTimeChange: (time) { - 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( - color: Colors.grey, - fontSize: 24, - ), - highlightedTextStyle: const TextStyle( - fontSize: 30, color: Colors.blue), - onTimeChange: (time) { - BlocProvider.of(context).add( - ChangeTimeEvent( - val: time, - isStartEndTime: - BlocProvider.of( - context) - .isStartEndTime)); - }, - ), - ), - ), - const Divider( - color: ColorsManager.graysColor, - ), - 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; - }, - ), - ], - )) - : const SizedBox(), - const SizedBox( - height: 40, - ) - ], + if(type == 'Online Password') + Flexible( + child: InkWell( + onTap: () { + smartDoorBloc.add(GeneratePasswordEvent()); + }, + child: const BodyMedium( + text: 'Generate Randomly', + fontWeight: FontWeight.bold, + fontColor: ColorsManager.primaryColor, + )), + ) + ], + ), ), ), + if(smartDoorBloc.passwordController.text.isNotEmpty) + TextButton( + onPressed: () async { + await Clipboard.setData(ClipboardData( + text: smartDoorBloc.passwordController.text)); + }, + child: const Text('Copy') + ), + const SizedBox( + height: 20, + ), + NameTimeWidget(), + const SizedBox( + height: 20, + ), + DefaultContainer( + padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 5), + child: ListTile( + contentPadding: EdgeInsets.zero, + leading: const BodyMedium( + text: 'Repeat', + fontWeight: FontWeight.normal, + ), + trailing: Transform.scale( + scale: .8, + child: CupertinoSwitch( + value: smartDoorBloc.repeat, + onChanged: (value) { + smartDoorBloc.add(ToggleRepeatEvent()); + }, + applyTheme: true, + )), + ), + ) , + const SizedBox( + height: 20, + ), + isRepeat? const RepeatWidget():const 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 index 39d83b9..b1a672d 100644 --- a/lib/features/devices/view/widgets/smart_door/door_dialog.dart +++ b/lib/features/devices/view/widgets/smart_door/door_dialog.dart @@ -1,5 +1,6 @@ import 'package:flutter/material.dart'; import 'package:intl/intl.dart'; +import 'package:syncrow_app/features/devices/model/offline_password_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/utils/context_extension.dart'; @@ -7,13 +8,15 @@ 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; + final String? title; + final TemporaryPassword? temporaryPassword; + final OfflinePasswordModel? offline; const DoorDialog({ super.key, - required this.title, - required this.value, + this.title, + this.offline, + this.temporaryPassword, }); @override @@ -28,16 +31,18 @@ class DoorDialogState extends State { @override Widget build(BuildContext context) { + final effectiveTime = widget.temporaryPassword?.effectiveTime ??int.parse( widget.offline?.gmtStart); + final invalidTime = widget.temporaryPassword?.invalidTime ?? int.parse(widget.offline?.gmtExpired); + final DateTime effectiveDateTime = - DateTime.fromMillisecondsSinceEpoch(widget.value.effectiveTime * 1000, isUtc: false); + DateTime.fromMillisecondsSinceEpoch(effectiveTime! * 1000, isUtc: false); String formattedDateEffectiveTime = DateFormat('yyyy-MM-dd').format(effectiveDateTime); - String formattedTimeEffectiveTime = DateFormat('HH:mm:ss').format(effectiveDateTime); + String formattedTimeEffectiveTime = DateFormat('hh:mm a').format(effectiveDateTime); final DateTime expiredDateTime = - DateTime.fromMillisecondsSinceEpoch(widget.value.invalidTime * 1000, isUtc: false); + DateTime.fromMillisecondsSinceEpoch(invalidTime! * 1000, isUtc: false); String formattedDateExpiredDateTime = DateFormat('yyyy-MM-dd').format(expiredDateTime); - String formattedTimeExpiredDateTime = DateFormat('HH:mm:ss').format(expiredDateTime); - + String formattedTimeExpiredDateTime = DateFormat('hh:mm a').format(expiredDateTime); return Dialog( child: Container( decoration: BoxDecoration( @@ -49,7 +54,7 @@ class DoorDialogState extends State { mainAxisSize: MainAxisSize.min, children: [ BodyMedium( - text: widget.title, + text: widget.title ?? '', style: context.bodyMedium.copyWith( color: ColorsManager.primaryColorWithOpacity, fontWeight: FontsManager.extraBold, @@ -111,6 +116,7 @@ class DoorDialogState extends State { width: double.infinity, color: ColorsManager.greyColor, ), + widget.temporaryPassword?.effectiveTime!=null? Row( mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: [ @@ -142,6 +148,27 @@ class DoorDialogState extends State { ), ), ], + ): + Row( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + Container( + height: 50, + child: InkWell( + onTap: () { + Navigator.pop(context); + }, + child: Center( + child: BodyMedium( + text: 'Cancel', + style: context.bodyMedium.copyWith(color: ColorsManager.greyColor), + ), + ), + ), + + ), + + ], ) ], ), diff --git a/lib/features/devices/view/widgets/smart_door/offline_timeLimit_password_page.dart b/lib/features/devices/view/widgets/smart_door/offline_timeLimit_password_page.dart new file mode 100644 index 0000000..9cb26da --- /dev/null +++ b/lib/features/devices/view/widgets/smart_door/offline_timeLimit_password_page.dart @@ -0,0 +1,295 @@ +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_bloc/flutter_bloc.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/devices/view/widgets/smart_door/repeat_widget.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/door_lock_button.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'; + +class CreateOfflineTimeLimitPasswordPage extends StatelessWidget { + final String? deviceId; + final String? type; + const CreateOfflineTimeLimitPasswordPage({super.key, this.deviceId, this.type}); + @override + Widget build(BuildContext context) { + bool isRepeat = false; + bool generated = false; + 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, + ), + ); + } + if (state is IsRepeatState) { + isRepeat = state.repeat; + } + if (state is GeneratePasswordOneTimestate) { + generated = state.generated; + } + }, builder: (context, state) { + final smartDoorBloc = BlocProvider.of(context); + return DefaultScaffold( + appBar: AppBar( + backgroundColor: Colors.transparent, + centerTitle: true, + title: const BodyLarge( + text: 'Create Time-Limited Password', + fontColor: ColorsManager.primaryColor, + fontWeight: FontsManager.bold, + ), + leading: IconButton( + onPressed: () { + Navigator.of(context).pop(true); + }, + icon: const Icon(Icons.arrow_back)), + ), + 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, + ), + + DefaultContainer( + padding: const EdgeInsets.symmetric( + horizontal: 10, vertical: 15), + child: Column( + children: [ + Row( + mainAxisAlignment: + MainAxisAlignment.spaceBetween, + mainAxisSize: MainAxisSize.max, + children: [ + Flexible( + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: smartDoorBloc.passwordController.text.isEmpty ? + List.generate(10, (index) { + return const Padding( + padding: EdgeInsets.symmetric( + horizontal: 4.0, + vertical: 15), + child: Icon( + Icons.circle, + size: 20.0, + color: Colors.black, + ), + ); + }) : [ + Expanded( + child: Row( + children: [ + Expanded( + child: BodyLarge( + style: const TextStyle( + color: ColorsManager.primaryColor, + fontWeight: FontWeight.bold, + letterSpacing: 8.0, + fontSize: 25, + wordSpacing: 2), + textAlign: TextAlign.center, + text: smartDoorBloc.passwordController.text, + fontSize: 25, + ), + ), + IconButton( + onPressed: () async { + await Clipboard.setData( + ClipboardData(text: smartDoorBloc.passwordController.text) + ); + }, + icon: const Icon(Icons.copy)), + ], + ), + ), + ], + )), + const SizedBox( + width: 10, + ), + ], + ), + DefaultContainer( + padding: const EdgeInsets.all(20), + child: Column( + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Expanded( + child: Container( + padding: const EdgeInsets.all(10.0), + child: const BodyMedium( + text: 'Password Name', + fontWeight: FontWeight.normal, + ), + ), + ), + SizedBox( + width: MediaQuery.of(context).size.width / 2.6, + child: TextFormField( + controller: BlocProvider.of(context).passwordNameController, + decoration: + const InputDecoration( + hintText: 'Enter The Name', + hintStyle: TextStyle( + fontSize: 14, + color: ColorsManager.textGray) + ), + )), + ], + ), + Column( + children: [ + const Divider(color: ColorsManager.graysColor,), + Padding( + padding: const EdgeInsets.all(10.0), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + const Expanded( + child: BodyMedium( + text: 'Effective Time', + fontWeight: FontWeight.normal, + ), + ), + SizedBox( + width: MediaQuery.of(context).size.width / 3.5, + child: InkWell( + onTap: () { + BlocProvider.of(context).add(SelectTimeEvent(context: context, isEffective: true)); + }, + child: Text( + BlocProvider.of(context).effectiveTime, + style: TextStyle( + fontSize: 14, + color: BlocProvider.of(context).effectiveTime == + 'Select Time' ? ColorsManager.textGray : null), + ), + )),], + ), + ), + const Divider( + color: ColorsManager.graysColor, + ), + Padding( + padding: const EdgeInsets.all(10.0), + child: Row(mainAxisAlignment: + MainAxisAlignment.spaceBetween, + children: [ + const Expanded( + child: BodyMedium( + text: 'Expiration Time', + fontWeight: FontWeight.normal, + ), + ), + SizedBox( + width: MediaQuery.of(context).size.width / 3.5, + child: InkWell( + onTap: () { + BlocProvider.of(context).add(SelectTimeEvent( + context: context, + isEffective: false)); + }, + child: Text( + BlocProvider.of(context).expirationTime, + style: TextStyle( + fontSize: 14, + color: BlocProvider.of(context) + .expirationTime == 'Select Time' ? ColorsManager + .textGray : null), + ), + ), + ), + ], + ), + ), + ], + ), + ], + )), + ], + ), + ), + const SizedBox( + height: 20, + ), + const BodyMedium( + textAlign: TextAlign.center, + text: + 'Use the time-limited password at least once within 24 hours after the password takes effect. Otherwise, the password becomes invalid.', + fontWeight: FontWeight.normal, + fontColor: ColorsManager.grayColor, + ), + // NameTimeWidget(type:type!), + const SizedBox( + height: 20, + ), + Center( + child: SizedBox( + width: MediaQuery.of(context).size.width / 1.5, + child: DoorLockButton( + isDone: generated, + isLoading: smartDoorBloc.isSavingPassword, + borderRadius: 30, + backgroundColor: ColorsManager.primaryColor, + onPressed: () async { + if (generated == false) { + smartDoorBloc.add( + GenerateAndSavePasswordTimeLimitEvent(context: context)); + await Clipboard.setData( + ClipboardData(text: smartDoorBloc.passwordController.text) + ); + } else { + if(smartDoorBloc.passwordNameController.text.isNotEmpty){ + smartDoorBloc.add(RenamePasswordEvent()); + } + Navigator.of(context).pop(true); + } + }, + child: const BodyMedium( + text: 'Obtain Password', + fontWeight: FontWeight.bold, + fontColor: Colors.white, + ), + ), + ), + ), + const SizedBox( + height: 20, + ), + isRepeat ? const RepeatWidget() : const SizedBox(), + const SizedBox( + height: 40, + ) + ], + ), + ), + ); + })); + } +} diff --git a/lib/features/devices/view/widgets/smart_door/onetime_password_page.dart b/lib/features/devices/view/widgets/smart_door/onetime_password_page.dart new file mode 100644 index 0000000..ba54fc5 --- /dev/null +++ b/lib/features/devices/view/widgets/smart_door/onetime_password_page.dart @@ -0,0 +1,111 @@ +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/devices/view/widgets/offline_one_time_password_page.dart'; +import 'package:syncrow_app/features/devices/view/widgets/smart_door/door_dialog.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'; + +class OnetimePasswordPage extends StatelessWidget { + final String? deviceId; + const OnetimePasswordPage({super.key, this.deviceId,}); + + @override + Widget build(BuildContext context) { + return BlocProvider( + create: (BuildContext context) => SmartDoorBloc(deviceId: deviceId!)..add(InitialOneTimePassword( )), + 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) => OfflineOneTimePasswordPage(deviceId: deviceId, ) + )).then((result) { + if(result!=null){ + smartDoorBloc.add(InitialOneTimePassword()); + smartDoorBloc.add(InitialOneTimePassword()); + } + }); + }, + icon: const Icon(Icons.add) + ) + ], + child: Builder( + builder: (context) { + return state is LoadingInitialState + ? const Center(child: CircularProgressIndicator()) + : Center( + child: smartDoorBloc.oneTimePasswords!.isNotEmpty + ? ListView.builder( + itemCount: smartDoorBloc.oneTimePasswords!.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.oneTimePasswords![index].pwdName}', + fontWeight: FontWeight.normal, + ), + onTap: () async { + final result = await showDialog( + context: context, + builder: (context) { + return DoorDialog( + title: 'Password Information', + offline: smartDoorBloc.oneTimePasswords![index], + ); + }, + ); + + }, + 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/devices/view/widgets/smart_door/repeat_widget.dart b/lib/features/devices/view/widgets/smart_door/repeat_widget.dart new file mode 100644 index 0000000..b35b320 --- /dev/null +++ b/lib/features/devices/view/widgets/smart_door/repeat_widget.dart @@ -0,0 +1,125 @@ +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.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/text_widgets/body_medium.dart'; +import 'package:syncrow_app/utils/resource_manager/color_manager.dart'; + +class RepeatWidget extends StatelessWidget { + const RepeatWidget({ + super.key, + }); + + @override + Widget build(BuildContext context) { + + return BlocBuilder( + builder: (context, state) { + final smartDoorBloc = BlocProvider.of(context); + return 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: () { + smartDoorBloc.add(const SetStartEndTimeEvent(val: true)); + }, + child: BodyMedium( + text: 'Start', + fontColor: smartDoorBloc.isStartEndTime == false + ? Colors.black + : Colors.blue, + fontSize: 18, + ), + ), + InkWell( + onTap: () { + smartDoorBloc.add(const SetStartEndTimeEvent(val: false)); + }, + child: BodyMedium( + text: 'End', + fontColor: smartDoorBloc.isStartEndTime + ? Colors.black + : Colors.blue, + fontSize: 18, + ), + ) + ], + ), + ), + const Divider( + color: ColorsManager.graysColor, + ), + smartDoorBloc.isStartEndTime + ? Container( + height: 110, + child: CupertinoDatePicker( + mode: CupertinoDatePickerMode.time, + initialDateTime:smartDoorBloc.startTime, + onDateTimeChanged: (startTime) { + smartDoorBloc.add(ChangeTimeEvent(val: startTime, isStartEndTime: true)); + }, + ) + ):SizedBox( + height: 110, + child: CupertinoDatePicker( + mode: CupertinoDatePickerMode.time, + initialDateTime:smartDoorBloc.endTime, + onDateTimeChanged: (endTime) { + smartDoorBloc.add(ChangeTimeEvent(val: endTime, isStartEndTime: false)); + }, + ) + ), + const Divider( + color: ColorsManager.graysColor, + ), + const SizedBox(height: 20), + SizedBox( + height: MediaQuery.of(context).size.height *0.10, + child: ListView( + scrollDirection: Axis.horizontal, + children: smartDoorBloc.days.map((day) { + bool isSelected = smartDoorBloc.selectedDays.contains(day['key']); + return Padding( + padding: const EdgeInsets.all(8.0), + child: InkWell( + onTap: () { + smartDoorBloc.add(ToggleDaySelectionEvent(key:day['key']!)); + }, + child: Container( + width: 70, + padding: EdgeInsets.symmetric(vertical: 8, horizontal: 8), + decoration: BoxDecoration( + border: Border.all( + color: isSelected? + Colors.black:ColorsManager.grayColor + ), + color: Colors.transparent, + borderRadius: BorderRadius.circular(55), + ), + child: Center( + child: Text( + day['day']!, + style: TextStyle( + fontSize: 18, + color: isSelected ? Colors.black : ColorsManager.grayColor, + ), + ), + ), + ), + ), + ); + }).toList(), + ) + ) + ])); + }); + } +} 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 index 3b9718f..3544fe0 100644 --- a/lib/features/devices/view/widgets/smart_door/temporary_password_page.dart +++ b/lib/features/devices/view/widgets/smart_door/temporary_password_page.dart @@ -1,6 +1,8 @@ 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/onetime_password_page.dart'; +import 'package:syncrow_app/features/devices/view/widgets/smart_door/timelimited_password_page.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'; @@ -10,7 +12,7 @@ import 'package:syncrow_app/generated/assets.dart'; import 'package:syncrow_app/utils/resource_manager/color_manager.dart'; class TemporaryPasswordPage extends StatelessWidget { - final String? deviceId; + final String? deviceId; const TemporaryPasswordPage({super.key,this.deviceId}); @override @@ -40,13 +42,17 @@ class TemporaryPasswordPage extends StatelessWidget { child:ListTile( contentPadding: EdgeInsets.zero, leading: SvgPicture.asset( - Assets.timeLimitedPasswordIcon), + 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'),)); + Navigator.of(context).push(MaterialPageRoute(builder: (context) => + ViewTemporaryPassword( + deviceId:deviceId, + type:'Online Password'), + )); }, trailing: const Icon( Icons.arrow_forward_ios, @@ -57,7 +63,6 @@ class TemporaryPasswordPage extends StatelessWidget { ), ], ), - const SizedBox(height: 10), Column( mainAxisAlignment: MainAxisAlignment.start, @@ -75,46 +80,45 @@ class TemporaryPasswordPage extends StatelessWidget { ), 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(color:ColorsManager.graysColor,), - 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, - ), - ), - ],) + 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) => OnetimePasswordPage(deviceId:deviceId,),)); + }, + trailing: const Icon( + Icons.arrow_forward_ios, + color: ColorsManager.greyColor, + size: 15, + ), + ), + const Divider(color:ColorsManager.graysColor,), + 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) => TimeLimitedPasswordPage(deviceId:deviceId,),)); + }, + trailing: const Icon( + Icons.arrow_forward_ios, + color: ColorsManager.greyColor, + size: 15, + ), + ), + ],) ), ], ), diff --git a/lib/features/devices/view/widgets/smart_door/timelimited_password_page.dart b/lib/features/devices/view/widgets/smart_door/timelimited_password_page.dart new file mode 100644 index 0000000..644c075 --- /dev/null +++ b/lib/features/devices/view/widgets/smart_door/timelimited_password_page.dart @@ -0,0 +1,109 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:flutter_svg/flutter_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/devices/view/widgets/smart_door/offline_timeLimit_password_page.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 'door_dialog.dart'; + +class TimeLimitedPasswordPage extends StatelessWidget { + final String? deviceId; + const TimeLimitedPasswordPage({super.key, this.deviceId}); + @override + Widget build(BuildContext context) { + return BlocProvider( + create: (BuildContext context) => SmartDoorBloc(deviceId: deviceId!)..add(InitialTimeLimitPassword()), + 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) => CreateOfflineTimeLimitPasswordPage(deviceId: deviceId,) + )).then((result) { + if (result != null) { + smartDoorBloc.add(InitialTimeLimitPassword()); + smartDoorBloc.add(InitialTimeLimitPassword()); + } + }); + }, + icon: const Icon(Icons.add) + ) + ], + child: Builder( + builder: (context) { + return state is LoadingInitialState + ? const Center(child: CircularProgressIndicator()) + : Center( + child: smartDoorBloc.timeLimitPasswords!.isNotEmpty + ? ListView.builder( + itemCount: smartDoorBloc.timeLimitPasswords!.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.timeLimitPasswords![index].pwdName}', + fontWeight: FontWeight.normal, + ), + onTap: () async { + final result = await showDialog( + context: context, + builder: (context) { + return DoorDialog( + title: 'Password Information', + offline: smartDoorBloc.timeLimitPasswords![index], + ); + }, + ); + }, + 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/devices/view/widgets/smart_door/view_temporary_password.dart b/lib/features/devices/view/widgets/smart_door/view_temporary_password.dart index 75350e4..03bf22f 100644 --- a/lib/features/devices/view/widgets/smart_door/view_temporary_password.dart +++ b/lib/features/devices/view/widgets/smart_door/view_temporary_password.dart @@ -20,8 +20,7 @@ class ViewTemporaryPassword extends StatelessWidget { @override Widget build(BuildContext context) { return BlocProvider( - create: (BuildContext context) => - SmartDoorBloc(deviceId: deviceId!)..add(InitialPasswordsPage(type:type )), + create: (BuildContext context) => SmartDoorBloc(deviceId: deviceId!)..add(InitialPasswordsPage( )), child: BlocConsumer( listener: (context, state) { if (state is FailedState) { @@ -32,84 +31,86 @@ class ViewTemporaryPassword extends StatelessWidget { ), ); } - }, 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, - ), - ), - ), - ); - }, + }, + 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()); + smartDoorBloc.add(InitialPasswordsPage()); + } + }); + }, + icon: const Icon(Icons.add) ) - : Column( - crossAxisAlignment: CrossAxisAlignment.center, - mainAxisAlignment: MainAxisAlignment.center, - children: [ - SvgPicture.asset(Assets.noValidPasswords), - const SizedBox( - height: 10, + ], + 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', + temporaryPassword: 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') + ], ), - const BodyMedium(text: 'No Valid Passwords') - ], - ), - ); - }, - )); - })); + ); + }, + )); + }) + ); } } diff --git a/lib/features/shared_widgets/door_lock_button.dart b/lib/features/shared_widgets/door_lock_button.dart new file mode 100644 index 0000000..a178ea1 --- /dev/null +++ b/lib/features/shared_widgets/door_lock_button.dart @@ -0,0 +1,99 @@ +import 'package:flutter/material.dart'; +import 'package:syncrow_app/utils/context_extension.dart'; +import 'package:syncrow_app/utils/resource_manager/color_manager.dart'; + +class DoorLockButton extends StatelessWidget { + const DoorLockButton({ + super.key, + this.enabled = true, + this.onPressed, + required this.child, + this.isSecondary = false, + this.isLoading = false, + this.isDone = false, + this.customTextStyle, + this.customButtonStyle, + this.backgroundColor, + this.foregroundColor, + this.borderRadius, + this.height, + this.padding, + }); + + final void Function()? onPressed; + final Widget child; + final double? height; + final bool isSecondary; + final double? borderRadius; + final bool enabled; + 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( + onPressed: enabled ? onPressed : null, + style: isSecondary + ? null + : customButtonStyle ?? + ButtonStyle( + textStyle: MaterialStateProperty.all( + customTextStyle ?? + context.bodyMedium.copyWith( + fontSize: 16, + color: foregroundColor, + ), + ), + foregroundColor: MaterialStateProperty.all( + isSecondary + ? Colors.black + : enabled + ? foregroundColor ?? Colors.white + : Colors.black, + ), + backgroundColor: MaterialStateProperty.resolveWith( + (Set states) { + return enabled + ? backgroundColor ?? ColorsManager.primaryColor + : Colors.grey; + }), + shape: MaterialStateProperty.all( + RoundedRectangleBorder( + borderRadius: BorderRadius.circular(borderRadius ?? 20), + ), + ), + fixedSize: MaterialStateProperty.all( + const Size.fromHeight(50), + ), + padding: MaterialStateProperty.all( + EdgeInsets.all(padding ?? 10), + ), + minimumSize: MaterialStateProperty.all( + const Size.fromHeight(50), + ), + ), + child: SizedBox( + height: height ?? 50, + child: Center( + child: isLoading + ? const SizedBox.square( + dimension: 24, + child: CircularProgressIndicator( + color: Colors.white, + ), + ) + : isDone + ? const Text('Done') :child , + ), + ), + ); + } +} diff --git a/lib/services/api/api_links_endpoints.dart b/lib/services/api/api_links_endpoints.dart index c3c2e8c..2a2af8c 100644 --- a/lib/services/api/api_links_endpoints.dart +++ b/lib/services/api/api_links_endpoints.dart @@ -160,15 +160,7 @@ abstract class ApiEndpoints { static const String getOneTimeTemporaryPassword = '/door-lock/temporary-password/offline/one-time/{doorLockUuid}'; - //multiple-time offline - static const String addMultipleTimeTemporaryPassword = - '/door-lock/temporary-password/offline/multiple-time/{doorLockUuid}'; - static const String getMultipleTimeTemporaryPassword = - '/door-lock/temporary-password/offline/multiple-time/{doorLockUuid}'; - //multiple-time offline - static const String deleteTemporaryPassword = - '/door-lock/temporary-password/{doorLockUuid}/{passwordId}'; //user @@ -179,4 +171,17 @@ abstract class ApiEndpoints { static const String sendPicture = '/user/profile-picture/{userUuid}'; static const String getRegion = '/region'; static const String getTimezone = '/timezone'; + + //multiple-time offline + static const String addMultipleTimeTemporaryPassword = + '/door-lock/temporary-password/offline/multiple-time/{doorLockUuid}'; + static const String getMultipleTimeTemporaryPassword = + '/door-lock/temporary-password/offline/multiple-time/{doorLockUuid}'; + + static const String renamePassword = + '/door-lock/temporary-password/{doorLockUuid}/offline/{passwordId}'; + + //multiple-time offline + static const String deleteTemporaryPassword = + '/door-lock/temporary-password/online/{doorLockUuid}/{passwordId}'; } diff --git a/lib/services/api/devices_api.dart b/lib/services/api/devices_api.dart index caad97b..e379ba7 100644 --- a/lib/services/api/devices_api.dart +++ b/lib/services/api/devices_api.dart @@ -69,6 +69,24 @@ class DevicesAPI { return response; } + static Future> renamePass({ + required String name, + required String doorLockUuid, + required String passwordId}) async { + final response = await _httpService.put( + path: ApiEndpoints.renamePassword + .replaceAll('{doorLockUuid}', doorLockUuid) + .replaceAll('{passwordId}', passwordId), + body: { + "name": name + }, + expectedResponseModel: (json) { + return json; + }, + ); + return response; + } + /// Get Device Functions static Future deviceFunctions(String deviceId) async { final response = await _httpService.get( @@ -138,14 +156,9 @@ class DevicesAPI { return response; } - static Future getTemporaryPasswords(String deviceId, pageType) async { + static Future getTemporaryPasswords(String deviceId, ) 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), + path: ApiEndpoints.getTemporaryPassword.replaceAll('{doorLockUuid}', deviceId), showServerMessage: false, expectedResponseModel: (json) { return json; @@ -154,37 +167,48 @@ class DevicesAPI { return response; } -//create password + static Future getOneTimePasswords(String deviceId) async { + final response = await _httpService.get( + path: ApiEndpoints.getOneTimeTemporaryPassword.replaceAll('{doorLockUuid}', deviceId), + showServerMessage: false, + expectedResponseModel: (json) { + return json; + }, + ); + return response; + } + + static Future getTimeLimitPasswords(String deviceId) async { + final response = await _httpService.get( + path: 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); - } + List? scheduleList,}) async { + Map body = { "name": name, "password": password, "effectiveTime": effectiveTime, "invalidTime": invalidTime, }; - if (pageType == 'Online Password' && scheduleList != null) { + if (scheduleList != null) { body["scheduleList"] = scheduleList.map((schedule) => schedule.toJson()).toList(); } final response = await _httpService.post( - path: endpointPath, + path: ApiEndpoints.addTemporaryPassword.replaceAll('{doorLockUuid}', deviceId), body: body, showServerMessage: false, expectedResponseModel: (json) => json, @@ -192,6 +216,40 @@ class DevicesAPI { return response; } + static Future generateOneTimePassword({deviceId}) async { + try { + final response = await _httpService.post( + path: ApiEndpoints.addOneTimeTemporaryPassword.replaceAll('{doorLockUuid}', deviceId), + showServerMessage: false, + expectedResponseModel: (json) { + return json; + }, + ); + return response; + } catch (e) { + rethrow; + } + } + + static Future generateMultiTimePassword({deviceId,effectiveTime,invalidTime}) async { + try { + final response = await _httpService.post( + path: ApiEndpoints.addMultipleTimeTemporaryPassword.replaceAll('{doorLockUuid}', deviceId), + showServerMessage: false, + body: { + "effectiveTime": effectiveTime, + "invalidTime": invalidTime + }, + expectedResponseModel: (json) { + return json; + }, + ); + return response; + } catch (e) { + rethrow; + } + } + static Future> deletePassword( {required String passwordId, required String deviceId}) async { final response = await _httpService.delete( @@ -205,4 +263,5 @@ class DevicesAPI { ); return response; } + }