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 d749fb3..9553676 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 @@ -149,7 +149,6 @@ class SmartDoorBloc extends Bloc { _streamSubscription = null; return super.close(); } - _doorLockUpdated(DoorLockUpdated event, Emitter emit) { unlockRequest = deviceStatus.unlockRequest; @@ -254,18 +253,42 @@ class SmartDoorBloc extends Bloc { } void _updateLock(UpdateLockEvent event, Emitter emit) async { - emit(LoadingNewSate(smartDoorModel: deviceStatus)); + final oldValue = deviceStatus.normalOpenSwitch; + deviceStatus = deviceStatus.copyWith(normalOpenSwitch: !oldValue); + emit(UpdateState(smartDoorModel: deviceStatus)); try { - // final response = await DevicesAPI.controlDevice( - // DeviceControlModel(deviceId: deviceId, code: 'normal_open_switch', value: !event.value), - // deviceId); - final response = await DevicesAPI.openDoorLock(deviceId); - if (response) { - deviceStatus.normalOpenSwitch = !event.value; + if (!response) { + _revertValueAndEmit(deviceId, 'normal_open_switch', oldValue, emit); } - } catch (_) {} + } catch (_) { + _revertValueAndEmit(deviceId, 'normal_open_switch', oldValue, emit); + } + } + + void _revertValueAndEmit(String deviceId, String code, dynamic oldValue, + Emitter emit) { + _updateLocalValue(code, oldValue); + emit(UpdateState(smartDoorModel: deviceStatus)); + emit(const FailedState(errorMessage: 'Failed to control the device.')); + } + + void _updateLocalValue(String code, dynamic value) { + switch (code) { + case 'normal_open_switch': + if (value is bool) { + deviceStatus = deviceStatus.copyWith(normalOpenSwitch: value); + } + break; + case 'reverse_lock': + if (value is bool) { + deviceStatus = deviceStatus.copyWith(reverseLock: value); + } + break; + default: + break; + } emit(UpdateState(smartDoorModel: deviceStatus)); } @@ -331,6 +354,7 @@ class SmartDoorBloc extends Bloc { Future selectTimeOnlinePassword( SelectTimeOnlinePasswordEvent event, Emitter emit) async { + effectiveTimeTimeStamp ??= DateTime.now().millisecondsSinceEpoch ~/ 1000; emit(ChangeTimeState()); final DateTime? picked = await showDatePicker( context: event.context, @@ -375,7 +399,13 @@ class SmartDoorBloc extends Bloc { selectedDateTime.minute, ).millisecondsSinceEpoch ~/ 1000; // Divide by 1000 to remove milliseconds + final currentTimestamp = DateTime.now().millisecondsSinceEpoch ~/ 1000; if (event.isEffective) { + if (selectedTimestamp < currentTimestamp) { + CustomSnackBar.displaySnackBar( + 'Effective Time cannot be later than Expiration Time.'); + return; + } if (expirationTimeTimeStamp != null && selectedTimestamp > expirationTimeTimeStamp!) { CustomSnackBar.displaySnackBar( diff --git a/lib/features/devices/model/smart_door_model.dart b/lib/features/devices/model/smart_door_model.dart index a215d0c..8a324c6 100644 --- a/lib/features/devices/model/smart_door_model.dart +++ b/lib/features/devices/model/smart_door_model.dart @@ -113,4 +113,44 @@ class SmartDoorModel { remoteNoDpKey: _remoteNoDpKey, normalOpenSwitch: _normalOpenSwitch); } + SmartDoorModel copyWith({ + String? uuid, + int? unlockFingerprint, + int? unlockPassword, + int? unlockTemporary, + int? unlockCard, + String? alarmLock, + int? unlockRequest, + int? residualElectricity, + bool? reverseLock, + int? unlockApp, + bool? hijack, + bool? doorbell, + String? unlockOfflinePd, + String? unlockOfflineClear, + String? unlockDoubleKit, + String? remoteNoPdSetkey, + String? remoteNoDpKey, + bool? normalOpenSwitch, + }) { + return SmartDoorModel( + unlockAlarm: alarmLock ?? unlockAlarm, + unlockFingerprint: unlockFingerprint ?? this.unlockFingerprint, + unlockPassword: unlockPassword ?? this.unlockPassword, + unlockTemporary: unlockTemporary ?? this.unlockTemporary, + unlockCard: unlockCard ?? this.unlockCard, + unlockRequest: unlockRequest ?? this.unlockRequest, + residualElectricity: residualElectricity ?? this.residualElectricity, + reverseLock: reverseLock ?? this.reverseLock, + unlockApp: unlockApp ?? this.unlockApp, + hijack: hijack ?? this.hijack, + doorbell: doorbell ?? this.doorbell, + unlockOfflinePd: unlockOfflinePd ?? this.unlockOfflinePd, + unlockOfflineClear: unlockOfflineClear ?? this.unlockOfflineClear, + unlockDoubleKit: unlockDoubleKit ?? this.unlockDoubleKit, + remoteNoPdSetkey: remoteNoPdSetkey ?? this.remoteNoPdSetkey, + remoteNoDpKey: remoteNoDpKey ?? this.remoteNoDpKey, + normalOpenSwitch: normalOpenSwitch ?? this.normalOpenSwitch, + ); + } } diff --git a/lib/features/devices/view/widgets/name_time_widget.dart b/lib/features/devices/view/widgets/name_time_widget.dart index 5adbe2e..6c9815b 100644 --- a/lib/features/devices/view/widgets/name_time_widget.dart +++ b/lib/features/devices/view/widgets/name_time_widget.dart @@ -11,6 +11,9 @@ class NameTimeWidget extends StatelessWidget { @override Widget build(BuildContext context) { + DateTime now = DateTime.now(); + DateTime cleaned = + DateTime(now.year, now.month, now.day, now.hour, now.minute); return DefaultContainer( padding: const EdgeInsets.all(20), child: Column( @@ -59,19 +62,22 @@ class NameTimeWidget extends StatelessWidget { width: MediaQuery.of(context).size.width / 3.5, child: InkWell( onTap: () { - - BlocProvider.of(context).add(SelectTimeOnlinePasswordEvent(context: context, isEffective: true)); + BlocProvider.of(context).add( + SelectTimeOnlinePasswordEvent( + context: context, isEffective: true)); }, child: Text( - BlocProvider.of(context).effectiveTime, - style: TextStyle(fontSize: 14, - color: BlocProvider.of(context).effectiveTime == + BlocProvider.of(context) + .effectiveTime == 'Select Time' - ? ColorsManager.textGray - : null), + ? cleaned.toString() + : BlocProvider.of(context) + .effectiveTime, + style: TextStyle(fontSize: 14), ), )), - ],), + ], + ), ), const Divider( color: ColorsManager.graysColor, @@ -96,10 +102,13 @@ class NameTimeWidget extends StatelessWidget { context: context, isEffective: false)); }, child: Text( - BlocProvider.of(context).expirationTime, + BlocProvider.of(context) + .expirationTime, style: TextStyle( fontSize: 14, - color: BlocProvider.of(context).expirationTime == 'Select Time' + color: BlocProvider.of(context) + .expirationTime == + 'Select Time' ? ColorsManager.textGray : null), ), diff --git a/lib/features/devices/view/widgets/smart_door/door_button.dart b/lib/features/devices/view/widgets/smart_door/door_button.dart index 92e924d..1034b95 100644 --- a/lib/features/devices/view/widgets/smart_door/door_button.dart +++ b/lib/features/devices/view/widgets/smart_door/door_button.dart @@ -6,10 +6,9 @@ import 'package:syncrow_app/features/devices/bloc/smart_door_bloc/smart_door_eve import 'package:syncrow_app/features/devices/model/device_model.dart'; import 'package:syncrow_app/features/devices/model/smart_door_model.dart'; import 'package:syncrow_app/generated/assets.dart'; -import 'package:syncrow_app/utils/context_extension.dart'; import 'package:syncrow_app/utils/resource_manager/color_manager.dart'; -class DoorLockButton extends StatefulWidget { +class DoorLockButton extends StatelessWidget { const DoorLockButton({ super.key, required this.doorLock, @@ -18,157 +17,79 @@ class DoorLockButton extends StatefulWidget { final DeviceModel doorLock; final SmartDoorModel smartDoorModel; - @override - State createState() => _DoorLockButtonState(smartDoorModel: smartDoorModel); -} -class _DoorLockButtonState extends State with SingleTickerProviderStateMixin { - late AnimationController _animationController; - late Animation _animation; - SmartDoorModel smartDoorModel; - _DoorLockButtonState({required this.smartDoorModel}); - - @override - void initState() { - super.initState(); - - _animationController = AnimationController( - vsync: this, - value: context.read().unlockRequest > 0 ? 1 : 0, - duration: Duration(seconds: context.read().unlockRequest), - ); - if (context.read().unlockRequest > 0) { - _animationController.reverse(); - } - - _animation = Tween(begin: 0, end: 1).animate(_animationController) - ..addListener(() { - setState(() {}); - }); - } - - @override - void didUpdateWidget(DoorLockButton oldWidget) { - super.didUpdateWidget(oldWidget); - - if (_animationController.status == AnimationStatus.dismissed) { - if (context.read().unlockRequest > 0) { - _animationController.value = 1; - _animationController.duration = - Duration(seconds: context.read().unlockRequest); - _animationController.reverse(); - } - } - } - - @override - void dispose() { - _animationController.dispose(); - super.dispose(); + double _calculateProgress() { + final value = smartDoorModel.unlockRequest; + if (value <= 0 || value > 30) return 0; + return value / 30.0; } @override Widget build(BuildContext context) { - return Padding( - padding: EdgeInsets.only( - right: context.width * 0.25 / 2, - left: context.width * 0.25 / 2, - bottom: context.width * 0.2 / 2, - ), + final progress = _calculateProgress(); + final isEnabled = smartDoorModel.unlockRequest > 0; + + return SizedBox( + width: 255, + height: 255, child: InkWell( - overlayColor: - WidgetStateProperty.all(ColorsManager.primaryColorWithOpacity.withOpacity(0.1)), - borderRadius: BorderRadius.circular(999), - onTapDown: (details) { - // if (_animationController.status == AnimationStatus.dismissed) { - // _animationController.forward(); - // } else if (_animationController.status == AnimationStatus.completed) { - // _animationController.reverse(); - // } else if (_animationController.status == AnimationStatus.forward) { - // _animationController.reverse(); - // } else if (_animationController.status == AnimationStatus.reverse) { - // _animationController.forward(); - // } - if (context.read().unlockRequest > 0) { - BlocProvider.of(context) - .add(UpdateLockEvent(value: smartDoorModel.normalOpenSwitch)); - } - }, - onTapUp: (details) { - // if (_animationController.status == AnimationStatus.forward) { - // _animationController.reverse(); - // } else if (_animationController.status == AnimationStatus.reverse) { - // _animationController.forward(); - // } - }, + onTap: isEnabled + ? () { + BlocProvider.of(context).add( + UpdateLockEvent(value: !smartDoorModel.normalOpenSwitch), + ); + } + : null, child: Container( - width: context.width * 06, - height: context.width * 0.6, - margin: const EdgeInsets.all(10), - decoration: const BoxDecoration( + width: 255, + height: 255, + decoration: BoxDecoration( + color: const Color(0xFFEBECED), + shape: BoxShape.circle, boxShadow: [ BoxShadow( - color: Colors.grey, + color: Colors.grey.withOpacity(0.5), blurRadius: 18, - // offset: Offset(6, 7), blurStyle: BlurStyle.outer, ), ], - color: Color(0xFFEBECED), - borderRadius: BorderRadius.all(Radius.circular(999)), ), - child: Padding( - padding: const EdgeInsets.all(25), - child: Stack( - alignment: Alignment.center, - children: [ - Container( - margin: const EdgeInsets.all(8), - decoration: BoxDecoration( - color: Colors.white, - borderRadius: BorderRadius.circular(999), - boxShadow: [ - BoxShadow( - color: Colors.white.withOpacity(0.5), - blurRadius: 30, - offset: const Offset(-5, -5), - blurStyle: BlurStyle.outer, - ), - BoxShadow( - color: Colors.black.withOpacity(0.14), - blurRadius: 25, - offset: const Offset(5, 5), - blurStyle: BlurStyle.outer, - ), - BoxShadow( - color: Colors.black.withOpacity(0.14), - blurRadius: 30, - offset: const Offset(5, 5), - blurStyle: BlurStyle.inner, - ), - ], - ), - child: Center( - child: SvgPicture.asset( - smartDoorModel.normalOpenSwitch - ? Assets.doorUnlockIcon - : Assets.assetsIconsDoorlockAssetsLockIcon, - ), + child: Stack( + alignment: Alignment.center, + children: [ + Container( + margin: const EdgeInsets.all(30), + decoration: const BoxDecoration( + color: Color(0xFFF9F9F9), + shape: BoxShape.circle, + ), + child: Center( + child: SvgPicture.asset( + smartDoorModel.normalOpenSwitch + ? Assets.doorUnlockIcon + : Assets.assetsIconsDoorlockAssetsLockIcon, + width: 60, + height: 60, ), ), - SizedBox.expand( + ), + if (progress > 0) + Container( + decoration: BoxDecoration(shape: BoxShape.circle), + height: 250, + width: 250, child: CircularProgressIndicator( - value: _animation.value, - strokeWidth: 15, + value: progress, + strokeWidth: 8, backgroundColor: Colors.transparent, - valueColor: const AlwaysStoppedAnimation(ColorsManager.primaryColor), + valueColor: const AlwaysStoppedAnimation( + ColorsManager.primaryColor), ), - ) - ], - ), + ), + ], ), ), ), ); } -} +} \ No newline at end of file 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 index 9a55e82..a75db32 100644 --- 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 @@ -18,7 +18,8 @@ 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}); + const CreateOfflineTimeLimitPasswordPage( + {super.key, this.deviceId, this.type}); @override Widget build(BuildContext context) { bool isRepeat = false; @@ -28,9 +29,7 @@ class CreateOfflineTimeLimitPasswordPage extends StatelessWidget { child: BlocConsumer( listener: (context, state) { if (state is FailedState) { - CustomSnackBar.displaySnackBar( - state.errorMessage - ); + CustomSnackBar.displaySnackBar(state.errorMessage); } if (state is IsRepeatState) { isRepeat = state.repeat; @@ -39,6 +38,10 @@ class CreateOfflineTimeLimitPasswordPage extends StatelessWidget { generated = state.generated; } }, builder: (context, state) { + DateTime now = DateTime.now(); + DateTime cleaned = + DateTime(now.year, now.month, now.day, now.hour, now.minute); + final smartDoorBloc = BlocProvider.of(context); return DefaultScaffold( appBar: AppBar( @@ -85,46 +88,56 @@ class CreateOfflineTimeLimitPasswordPage extends StatelessWidget { 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: [ + 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: 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, + 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)), + ], ), ), - IconButton( - onPressed: () async { - await Clipboard.setData( - ClipboardData(text: smartDoorBloc.passwordController.text) - ); - }, - icon: const Icon(Icons.copy)), ], - ), - ), - ], )), const SizedBox( width: 10, @@ -135,91 +148,142 @@ class CreateOfflineTimeLimitPasswordPage extends StatelessWidget { 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, + 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 / 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), + SizedBox( + width: MediaQuery.of(context) + .size + .width / + 2.6, + child: TextFormField( + controller: BlocProvider.of< + SmartDoorBloc>(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< + SmartDoorBloc>( + context) + .add( + SelectTimeEvent( + context: + context, + isEffective: + true)); + }, + child: Text( + BlocProvider.of( + context) + .effectiveTime == + 'Select Time' + ? cleaned.toString() + : BlocProvider.of< + SmartDoorBloc>( + context) + .effectiveTime, + style: TextStyle( + fontSize: 14, + ), + ), + )), + ], + ), + ), + 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< + SmartDoorBloc>( + context) + .add(SelectTimeEvent( + context: context, + isEffective: + false)); + }, + child: Text( + BlocProvider.of< + SmartDoorBloc>( + context) + .expirationTime, + style: TextStyle( + fontSize: 14, + color: BlocProvider.of< + SmartDoorBloc>( + context) + .expirationTime == + 'Select Time' + ? ColorsManager + .textGray + : null), ), ), ), @@ -238,7 +302,8 @@ class CreateOfflineTimeLimitPasswordPage extends StatelessWidget { ), 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.', + 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, ), @@ -256,10 +321,13 @@ class CreateOfflineTimeLimitPasswordPage extends StatelessWidget { backgroundColor: ColorsManager.primaryColor, onPressed: () async { if (generated == false) { - smartDoorBloc.add(GenerateAndSavePasswordTimeLimitEvent(context: context)); + smartDoorBloc.add( + GenerateAndSavePasswordTimeLimitEvent( + context: context)); } else { - if(smartDoorBloc.passwordNameController.text.isNotEmpty){ - smartDoorBloc.add(RenamePasswordEvent()); + if (smartDoorBloc + .passwordNameController.text.isNotEmpty) { + smartDoorBloc.add(RenamePasswordEvent()); } Navigator.of(context).pop(true); }