Compare commits

..

8 Commits

Author SHA1 Message Date
7750290be4 Fix device status display in Control modal to reflect actual status 2025-07-02 10:14:54 +03:00
423ad6e687 Enhance navigation buttons in SmartPowerDeviceControl for better user… (#293)
… experience

<!--
  Thanks for contributing!

Provide a description of your changes below and a general summary in the
title

Please look at the following checklist to ensure that your PR can be
accepted quickly:
-->



## Description

<!--- Describe your changes in detail -->
Enhance navigation buttons in SmartPowerDeviceControl 

## Type of Change

<!--- Put an `x` in all the boxes that apply: -->

- [ ]  New feature (non-breaking change which adds functionality)
- [x] 🛠️ Bug fix (non-breaking change which fixes an issue)
- [ ]  Breaking change (fix or feature that would cause existing
functionality to change)
- [ ] 🧹 Code refactor
- [ ]  Build configuration change
- [ ] 📝 Documentation
- [ ] 🗑️ Chore
2025-06-25 09:13:33 +03:00
932e50f518 sp:1677 [FE] Device status in Control modal always shows "Online" regardless of actual status (#287)
<!--
  Thanks for contributing!

Provide a description of your changes below and a general summary in the
title

Please look at the following checklist to ensure that your PR can be
accepted quickly:
-->

## Jira Ticket
[SP-1677](https://syncrow.atlassian.net/browse/SP-1677)

## Description

status depend on the real status of the device afterit was static 

## Type of Change

<!--- Put an `x` in all the boxes that apply: -->

- [ ]  New feature (non-breaking change which adds functionality)
- [x] 🛠️ Bug fix (non-breaking change which fixes an issue)
- [ ]  Breaking change (fix or feature that would cause existing
functionality to change)
- [ ] 🧹 Code refactor
- [ ]  Build configuration change
- [ ] 📝 Documentation
- [ ] 🗑️ Chore 


[SP-1677]:
https://syncrow.atlassian.net/browse/SP-1677?atlOrigin=eyJpIjoiNWRkNTljNzYxNjVmNDY3MDlhMDU5Y2ZhYzA5YTRkZjUiLCJwIjoiZ2l0aHViLWNvbS1KU1cifQ
2025-06-25 08:18:10 +03:00
c46cfb04a7 Add countdown seconds to schedule management (#291)
<!--
  Thanks for contributing!

Provide a description of your changes below and a general summary in the
title

Please look at the following checklist to ensure that your PR can be
accepted quickly:
-->


## Description

<!--- Describe your changes in detail -->
Add countdown seconds to schedule management
## Type of Change

<!--- Put an `x` in all the boxes that apply: -->

- [ ]  New feature (non-breaking change which adds functionality)
- [x] 🛠️ Bug fix (non-breaking change which fixes an issue)
- [ ]  Breaking change (fix or feature that would cause existing
functionality to change)
- [ ] 🧹 Code refactor
- [ ]  Build configuration change
- [ ] 📝 Documentation
- [ ] 🗑️ Chore
2025-06-24 16:21:43 +03:00
8754960713 Cancel-button-on-"Create-Visitor-Password"-modal-unnecessarily-triggers-visitor-passwords-API (#292)
… on pop

<!--
  Thanks for contributing!

Provide a description of your changes below and a general summary in the
title

Please look at the following checklist to ensure that your PR can be
accepted quickly:
-->



## Description

<!--- Describe your changes in detail -->

## Type of Change

<!--- Put an `x` in all the boxes that apply: -->

- [ ]  New feature (non-breaking change which adds functionality)
- [x] 🛠️ Bug fix (non-breaking change which fixes an issue)
- [ ]  Breaking change (fix or feature that would cause existing
functionality to change)
- [ ] 🧹 Code refactor
- [ ]  Build configuration change
- [ ] 📝 Documentation
- [ ] 🗑️ Chore
2025-06-24 16:21:03 +03:00
c6e98fa245 Refactor visitor password dialog navigation to return specific values on pop 2025-06-24 16:06:53 +03:00
277a9ce4f0 Add countdown seconds to schedule management 2025-06-24 15:38:16 +03:00
95d6e1ecda if online go green with online status else red with offline status 2025-06-23 16:33:45 +03:00
8 changed files with 307 additions and 176 deletions

View File

@ -50,6 +50,9 @@ class _DynamicTableState extends State<DynamicTable> {
bool _selectAll = false; bool _selectAll = false;
final ScrollController _verticalScrollController = ScrollController(); final ScrollController _verticalScrollController = ScrollController();
final ScrollController _horizontalScrollController = ScrollController(); final ScrollController _horizontalScrollController = ScrollController();
static const double _fixedRowHeight = 60;
static const double _checkboxColumnWidth = 50;
static const double _settingsColumnWidth = 100;
@override @override
void initState() { void initState() {
@ -67,7 +70,6 @@ class _DynamicTableState extends State<DynamicTable> {
bool _compareListOfLists( bool _compareListOfLists(
List<List<dynamic>> oldList, List<List<dynamic>> newList) { List<List<dynamic>> oldList, List<List<dynamic>> newList) {
// Check if the old and new lists are the same
if (oldList.length != newList.length) return false; if (oldList.length != newList.length) return false;
for (int i = 0; i < oldList.length; i++) { for (int i = 0; i < oldList.length; i++) {
@ -104,73 +106,130 @@ class _DynamicTableState extends State<DynamicTable> {
context.read<DeviceManagementBloc>().add(UpdateSelection(_selectedRows)); context.read<DeviceManagementBloc>().add(UpdateSelection(_selectedRows));
} }
double get _totalTableWidth {
final hasSettings = widget.headers.contains('Settings');
final base = (widget.withCheckBox ? _checkboxColumnWidth : 0) +
(hasSettings ? _settingsColumnWidth : 0);
final regularCount = widget.headers.length - (hasSettings ? 1 : 0);
final regularWidth = (widget.size.width - base) / regularCount;
return base + regularCount * regularWidth;
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Container( return Container(
width: widget.size.width,
height: widget.size.height,
decoration: widget.cellDecoration, decoration: widget.cellDecoration,
child: ScrollConfiguration(
behavior: const ScrollBehavior().copyWith(scrollbars: false),
child: Scrollbar( child: Scrollbar(
controller: _verticalScrollController,
thumbVisibility: true,
trackVisibility: true,
child: Scrollbar(
//fixed the horizontal scrollbar issue
controller: _horizontalScrollController, controller: _horizontalScrollController,
thumbVisibility: true, thumbVisibility: true,
trackVisibility: true, trackVisibility: true,
notificationPredicate: (notif) => notif.depth == 1, notificationPredicate: (notif) =>
child: SingleChildScrollView( notif.metrics.axis == Axis.horizontal,
controller: _verticalScrollController,
child: SingleChildScrollView( child: SingleChildScrollView(
controller: _horizontalScrollController, controller: _horizontalScrollController,
scrollDirection: Axis.horizontal, scrollDirection: Axis.horizontal,
child: SizedBox( child: SizedBox(
width: widget.size.width, width: _totalTableWidth,
child: Column( child: Column(
children: [ children: [
Container( Container(
height: _fixedRowHeight,
decoration: widget.headerDecoration ?? decoration: widget.headerDecoration ??
const BoxDecoration( const BoxDecoration(color: ColorsManager.boxColor),
color: ColorsManager.boxColor,
),
child: Row( child: Row(
children: [ children: [
if (widget.withCheckBox) _buildSelectAllCheckbox(), if (widget.withCheckBox)
...List.generate(widget.headers.length, (index) { _buildSelectAllCheckbox(_checkboxColumnWidth),
return _buildTableHeaderCell( for (var i = 0; i < widget.headers.length; i++)
widget.headers[index], index); _buildTableHeaderCell(
}) widget.headers[i],
//...widget.headers.map((header) => _buildTableHeaderCell(header)), widget.headers[i] == 'Settings'
? _settingsColumnWidth
: (_totalTableWidth -
(widget.withCheckBox
? _checkboxColumnWidth
: 0) -
(widget.headers.contains('Settings')
? _settingsColumnWidth
: 0)) /
(widget.headers.length -
(widget.headers.contains('Settings')
? 1
: 0)),
),
], ],
), ),
), ),
SizedBox(
width: widget.size.width, Expanded(
child: widget.isEmpty child: widget.isEmpty
? _buildEmptyState() ? _buildEmptyState()
: Column( : Scrollbar(
children: controller: _verticalScrollController,
List.generate(widget.data.length, (rowIndex) { thumbVisibility: true,
trackVisibility: true,
notificationPredicate: (notif) =>
notif.metrics.axis == Axis.vertical,
child: ListView.builder(
controller: _verticalScrollController,
itemCount: widget.data.length,
itemBuilder: (_, rowIndex) {
final row = widget.data[rowIndex]; final row = widget.data[rowIndex];
return Row( return SizedBox(
height: _fixedRowHeight,
child: Row(
children: [ children: [
if (widget.withCheckBox) if (widget.withCheckBox)
_buildRowCheckbox( _buildRowCheckbox(
rowIndex, widget.size.height * 0.08), rowIndex,
...row.asMap().entries.map((entry) { _checkboxColumnWidth,
return _buildTableCell( ),
entry.value.toString(), for (var colIndex = 0;
widget.size.height * 0.08, colIndex < row.length;
colIndex++)
widget.headers[colIndex] == 'Settings'
? buildSettingsIcon(
width: _settingsColumnWidth,
onTap: () => widget
.onSettingsPressed
?.call(rowIndex),
)
: _buildTableCell(
row[colIndex].toString(),
width: widget.headers[
colIndex] ==
'Settings'
? _settingsColumnWidth
: (_totalTableWidth -
(widget.withCheckBox
? _checkboxColumnWidth
: 0) -
(widget.headers
.contains(
'Settings')
? _settingsColumnWidth
: 0)) /
(widget.headers.length -
(widget.headers
.contains(
'Settings')
? 1
: 0)),
rowIndex: rowIndex, rowIndex: rowIndex,
columnIndex: entry.key, columnIndex: colIndex,
);
}).toList(),
],
);
}),
),
), ),
], ],
), ),
);
},
),
),
),
],
), ),
), ),
), ),
@ -210,9 +269,10 @@ class _DynamicTableState extends State<DynamicTable> {
], ],
), ),
); );
Widget _buildSelectAllCheckbox() {
Widget _buildSelectAllCheckbox(double width) {
return Container( return Container(
width: 50, width: width,
decoration: const BoxDecoration( decoration: const BoxDecoration(
border: Border.symmetric( border: Border.symmetric(
vertical: BorderSide(color: ColorsManager.boxDivider), vertical: BorderSide(color: ColorsManager.boxDivider),
@ -227,11 +287,11 @@ class _DynamicTableState extends State<DynamicTable> {
); );
} }
Widget _buildRowCheckbox(int index, double size) { Widget _buildRowCheckbox(int index, double width) {
return Container( return Container(
width: 50, width: width,
padding: const EdgeInsets.all(8.0), padding: const EdgeInsets.all(8.0),
height: size, height: _fixedRowHeight,
decoration: const BoxDecoration( decoration: const BoxDecoration(
border: Border( border: Border(
bottom: BorderSide( bottom: BorderSide(
@ -253,20 +313,18 @@ class _DynamicTableState extends State<DynamicTable> {
); );
} }
Widget _buildTableHeaderCell(String title, int index) { Widget _buildTableHeaderCell(String title, double width) {
return Expanded( return Container(
child: Container( width: width,
decoration: const BoxDecoration( decoration: const BoxDecoration(
border: Border.symmetric( border: Border.symmetric(
vertical: BorderSide(color: ColorsManager.boxDivider), vertical: BorderSide(color: ColorsManager.boxDivider),
), ),
), ),
constraints: const BoxConstraints.expand(height: 40), constraints: BoxConstraints(minHeight: 40, maxHeight: _fixedRowHeight),
alignment: Alignment.centerLeft, alignment: Alignment.centerLeft,
child: Padding( child: Padding(
padding: EdgeInsets.symmetric( padding: const EdgeInsets.symmetric(horizontal: 8.0, vertical: 4),
horizontal: index == widget.headers.length - 1 ? 12 : 8.0,
vertical: 4),
child: Text( child: Text(
title, title,
style: context.textTheme.titleSmall!.copyWith( style: context.textTheme.titleSmall!.copyWith(
@ -275,28 +333,27 @@ class _DynamicTableState extends State<DynamicTable> {
fontWeight: FontWeight.w400, fontWeight: FontWeight.w400,
), ),
maxLines: 2, maxLines: 2,
), overflow: TextOverflow.ellipsis,
), ),
), ),
); );
} }
Widget _buildTableCell(String content, double size, Widget _buildTableCell(String content,
{required int rowIndex, required int columnIndex}) { {required double width,
required int rowIndex,
required int columnIndex}) {
bool isBatteryLevel = content.endsWith('%'); bool isBatteryLevel = content.endsWith('%');
double? batteryLevel; double? batteryLevel;
if (isBatteryLevel) { if (isBatteryLevel) {
batteryLevel = double.tryParse(content.replaceAll('%', '').trim()); batteryLevel = double.tryParse(content.replaceAll('%', '').trim());
} }
bool isSettingsColumn = widget.headers[columnIndex] == 'Settings'; bool isSettingsColumn = widget.headers[columnIndex] == 'Settings';
if (isSettingsColumn) { if (isSettingsColumn) {
return buildSettingsIcon( return buildSettingsIcon(
width: 120, width: width, onTap: () => widget.onSettingsPressed?.call(rowIndex));
height: 60,
iconSize: 40,
onTap: () => widget.onSettingsPressed?.call(rowIndex),
);
} }
Color? statusColor; Color? statusColor;
@ -320,10 +377,10 @@ class _DynamicTableState extends State<DynamicTable> {
statusColor = Colors.black; statusColor = Colors.black;
} }
return Expanded( return Container(
child: Container( width: width,
height: size, height: _fixedRowHeight,
padding: const EdgeInsets.all(5.0), padding: const EdgeInsets.symmetric(horizontal: 8.0, vertical: 4),
decoration: const BoxDecoration( decoration: const BoxDecoration(
border: Border( border: Border(
bottom: BorderSide( bottom: BorderSide(
@ -343,23 +400,19 @@ class _DynamicTableState extends State<DynamicTable> {
? ColorsManager.green ? ColorsManager.green
: statusColor, : statusColor,
fontSize: 13, fontSize: 13,
fontWeight: FontWeight.w400), fontWeight: FontWeight.w400,
maxLines: 2,
), ),
maxLines: 2,
overflow: TextOverflow.ellipsis,
), ),
); );
} }
Widget buildSettingsIcon( Widget buildSettingsIcon({required double width, VoidCallback? onTap}) {
{double width = 120, return Container(
double height = 60, width: width,
double iconSize = 40, height: _fixedRowHeight,
VoidCallback? onTap}) { padding: const EdgeInsets.only(left: 15, top: 10, bottom: 10),
return Column(
children: [
Container(
padding: const EdgeInsets.only(top: 10, bottom: 15, left: 10),
margin: const EdgeInsets.only(right: 15),
decoration: const BoxDecoration( decoration: const BoxDecoration(
color: ColorsManager.whiteColors, color: ColorsManager.whiteColors,
border: Border( border: Border(
@ -369,17 +422,13 @@ class _DynamicTableState extends State<DynamicTable> {
), ),
), ),
), ),
width: width, child: Align(
child: Padding( alignment: Alignment.centerLeft,
padding: const EdgeInsets.only(
right: 16.0,
left: 17.0,
),
child: Container( child: Container(
width: 50, width: 50,
decoration: BoxDecoration( decoration: BoxDecoration(
color: const Color(0xFFF7F8FA), color: const Color(0xFFF7F8FA),
borderRadius: BorderRadius.circular(height / 2), borderRadius: BorderRadius.circular(20),
boxShadow: [ boxShadow: [
BoxShadow( BoxShadow(
color: Colors.black.withOpacity(0.17), color: Colors.black.withOpacity(0.17),
@ -391,12 +440,12 @@ class _DynamicTableState extends State<DynamicTable> {
child: InkWell( child: InkWell(
onTap: onTap, onTap: onTap,
child: Padding( child: Padding(
padding: const EdgeInsets.all(8.0), padding: EdgeInsets.all(8.0),
child: Center( child: Center(
child: SvgPicture.asset( child: SvgPicture.asset(
Assets.settings, Assets.settings,
width: 40, width: 40,
height: 22, height: 20,
color: ColorsManager.primaryColor, color: ColorsManager.primaryColor,
), ),
), ),
@ -404,8 +453,6 @@ class _DynamicTableState extends State<DynamicTable> {
), ),
), ),
), ),
),
],
); );
} }
} }

View File

@ -83,6 +83,12 @@ class ScheduleBloc extends Bloc<ScheduleEvent, ScheduleState> {
emit(currentState.copyWith( emit(currentState.copyWith(
scheduleMode: event.scheduleMode, scheduleMode: event.scheduleMode,
countdownRemaining: Duration.zero, countdownRemaining: Duration.zero,
countdownHours: 0,
countdownMinutes: 0,
inchingHours: 0,
inchingMinutes: 0,
isCountdownActive: false,
isInchingActive: false,
)); ));
} }
} }
@ -94,6 +100,7 @@ class ScheduleBloc extends Bloc<ScheduleEvent, ScheduleState> {
if (state is ScheduleLoaded) { if (state is ScheduleLoaded) {
final currentState = state as ScheduleLoaded; final currentState = state as ScheduleLoaded;
emit(currentState.copyWith( emit(currentState.copyWith(
countdownSeconds: event.seconds,
countdownHours: event.hours, countdownHours: event.hours,
countdownMinutes: event.minutes, countdownMinutes: event.minutes,
inchingHours: 0, inchingHours: 0,
@ -113,6 +120,7 @@ class ScheduleBloc extends Bloc<ScheduleEvent, ScheduleState> {
inchingHours: event.hours, inchingHours: event.hours,
inchingMinutes: event.minutes, inchingMinutes: event.minutes,
countdownRemaining: Duration.zero, countdownRemaining: Duration.zero,
inchingSeconds: 0, // Add this
)); ));
} }
} }
@ -424,6 +432,7 @@ class ScheduleBloc extends Bloc<ScheduleEvent, ScheduleState> {
countdownMinutes: countdownDuration.inMinutes % 60, countdownMinutes: countdownDuration.inMinutes % 60,
countdownRemaining: countdownDuration, countdownRemaining: countdownDuration,
isCountdownActive: true, isCountdownActive: true,
countdownSeconds: countdownDuration.inSeconds,
), ),
); );
@ -437,6 +446,7 @@ class ScheduleBloc extends Bloc<ScheduleEvent, ScheduleState> {
countdownMinutes: 0, countdownMinutes: 0,
countdownRemaining: Duration.zero, countdownRemaining: Duration.zero,
isCountdownActive: false, isCountdownActive: false,
countdownSeconds: 0,
), ),
); );
} }
@ -448,6 +458,7 @@ class ScheduleBloc extends Bloc<ScheduleEvent, ScheduleState> {
inchingMinutes: inchingDuration.inMinutes % 60, inchingMinutes: inchingDuration.inMinutes % 60,
isInchingActive: true, isInchingActive: true,
countdownRemaining: inchingDuration, countdownRemaining: inchingDuration,
countdownSeconds: inchingDuration.inSeconds,
), ),
); );
} }
@ -574,8 +585,7 @@ class ScheduleBloc extends Bloc<ScheduleEvent, ScheduleState> {
} }
String extractTime(String isoDateTime) { String extractTime(String isoDateTime) {
// Example input: "2025-06-19T15:45:00.000" return isoDateTime.split('T')[1].split('.')[0];
return isoDateTime.split('T')[1].split('.')[0]; // gives "15:45:00"
} }
int? getTimeStampWithoutSeconds(DateTime? dateTime) { int? getTimeStampWithoutSeconds(DateTime? dateTime) {

View File

@ -146,14 +146,16 @@ class UpdateScheduleModeEvent extends ScheduleEvent {
class UpdateCountdownTimeEvent extends ScheduleEvent { class UpdateCountdownTimeEvent extends ScheduleEvent {
final int hours; final int hours;
final int minutes; final int minutes;
final int seconds;
const UpdateCountdownTimeEvent({ const UpdateCountdownTimeEvent({
required this.hours, required this.hours,
required this.minutes, required this.minutes,
required this.seconds,
}); });
@override @override
List<Object> get props => [hours, minutes]; List<Object> get props => [hours, minutes, seconds];
} }
class UpdateInchingTimeEvent extends ScheduleEvent { class UpdateInchingTimeEvent extends ScheduleEvent {

View File

@ -26,11 +26,15 @@ class ScheduleLoaded extends ScheduleState {
final bool isCountdownActive; final bool isCountdownActive;
final int inchingHours; final int inchingHours;
final int inchingMinutes; final int inchingMinutes;
final int inchingSeconds;
final bool isInchingActive; final bool isInchingActive;
final ScheduleModes scheduleMode; final ScheduleModes scheduleMode;
final Duration? countdownRemaining; final Duration? countdownRemaining;
final int? countdownSeconds;
const ScheduleLoaded({ const ScheduleLoaded({
this.countdownSeconds = 0,
this.inchingSeconds = 0,
required this.schedules, required this.schedules,
this.selectedTime, this.selectedTime,
required this.selectedDays, required this.selectedDays,
@ -61,6 +65,9 @@ class ScheduleLoaded extends ScheduleState {
bool? isInchingActive, bool? isInchingActive,
ScheduleModes? scheduleMode, ScheduleModes? scheduleMode,
Duration? countdownRemaining, Duration? countdownRemaining,
String? deviceId,
int? countdownSeconds,
int? inchingSeconds,
}) { }) {
return ScheduleLoaded( return ScheduleLoaded(
schedules: schedules ?? this.schedules, schedules: schedules ?? this.schedules,
@ -68,7 +75,7 @@ class ScheduleLoaded extends ScheduleState {
selectedDays: selectedDays ?? this.selectedDays, selectedDays: selectedDays ?? this.selectedDays,
functionOn: functionOn ?? this.functionOn, functionOn: functionOn ?? this.functionOn,
isEditing: isEditing ?? this.isEditing, isEditing: isEditing ?? this.isEditing,
deviceId: deviceId, deviceId: deviceId ?? this.deviceId,
countdownHours: countdownHours ?? this.countdownHours, countdownHours: countdownHours ?? this.countdownHours,
countdownMinutes: countdownMinutes ?? this.countdownMinutes, countdownMinutes: countdownMinutes ?? this.countdownMinutes,
isCountdownActive: isCountdownActive ?? this.isCountdownActive, isCountdownActive: isCountdownActive ?? this.isCountdownActive,
@ -77,6 +84,8 @@ class ScheduleLoaded extends ScheduleState {
isInchingActive: isInchingActive ?? this.isInchingActive, isInchingActive: isInchingActive ?? this.isInchingActive,
scheduleMode: scheduleMode ?? this.scheduleMode, scheduleMode: scheduleMode ?? this.scheduleMode,
countdownRemaining: countdownRemaining ?? this.countdownRemaining, countdownRemaining: countdownRemaining ?? this.countdownRemaining,
countdownSeconds: countdownSeconds ?? this.countdownSeconds,
inchingSeconds: inchingSeconds ?? this.inchingSeconds,
); );
} }
@ -96,6 +105,8 @@ class ScheduleLoaded extends ScheduleState {
isInchingActive, isInchingActive,
scheduleMode, scheduleMode,
countdownRemaining, countdownRemaining,
countdownSeconds,
inchingSeconds,
]; ];
} }

View File

@ -6,7 +6,8 @@ import 'package:syncrow_web/utils/color_manager.dart';
import 'package:syncrow_web/utils/extension/build_context_x.dart'; import 'package:syncrow_web/utils/extension/build_context_x.dart';
class CountdownInchingView extends StatefulWidget { class CountdownInchingView extends StatefulWidget {
const CountdownInchingView({super.key}); final String deviceId;
const CountdownInchingView({super.key, required this.deviceId});
@override @override
State<CountdownInchingView> createState() => _CountdownInchingViewState(); State<CountdownInchingView> createState() => _CountdownInchingViewState();
@ -15,25 +16,30 @@ class CountdownInchingView extends StatefulWidget {
class _CountdownInchingViewState extends State<CountdownInchingView> { class _CountdownInchingViewState extends State<CountdownInchingView> {
late FixedExtentScrollController _hoursController; late FixedExtentScrollController _hoursController;
late FixedExtentScrollController _minutesController; late FixedExtentScrollController _minutesController;
late FixedExtentScrollController _secondsController;
int _lastHours = -1; int _lastHours = -1;
int _lastMinutes = -1; int _lastMinutes = -1;
int _lastSeconds = -1;
@override @override
void initState() { void initState() {
super.initState(); super.initState();
_hoursController = FixedExtentScrollController(); _hoursController = FixedExtentScrollController();
_minutesController = FixedExtentScrollController(); _minutesController = FixedExtentScrollController();
_secondsController = FixedExtentScrollController();
} }
@override @override
void dispose() { void dispose() {
_hoursController.dispose(); _hoursController.dispose();
_minutesController.dispose(); _minutesController.dispose();
_secondsController.dispose();
super.dispose(); super.dispose();
} }
void _updateControllers(int displayHours, int displayMinutes) { void _updateControllers(
int displayHours, int displayMinutes, int displaySeconds) {
if (_lastHours != displayHours) { if (_lastHours != displayHours) {
WidgetsBinding.instance.addPostFrameCallback((_) { WidgetsBinding.instance.addPostFrameCallback((_) {
if (_hoursController.hasClients) { if (_hoursController.hasClients) {
@ -50,6 +56,15 @@ class _CountdownInchingViewState extends State<CountdownInchingView> {
}); });
_lastMinutes = displayMinutes; _lastMinutes = displayMinutes;
} }
// Update seconds controller
if (_lastSeconds != displaySeconds) {
WidgetsBinding.instance.addPostFrameCallback((_) {
if (_secondsController.hasClients) {
_secondsController.jumpToItem(displaySeconds);
}
});
_lastSeconds = displaySeconds;
}
} }
@override @override
@ -57,7 +72,6 @@ class _CountdownInchingViewState extends State<CountdownInchingView> {
return BlocBuilder<ScheduleBloc, ScheduleState>( return BlocBuilder<ScheduleBloc, ScheduleState>(
builder: (context, state) { builder: (context, state) {
if (state is! ScheduleLoaded) return const SizedBox.shrink(); if (state is! ScheduleLoaded) return const SizedBox.shrink();
final isCountDown = state.scheduleMode == ScheduleModes.countdown; final isCountDown = state.scheduleMode == ScheduleModes.countdown;
final isActive = final isActive =
isCountDown ? state.isCountdownActive : state.isInchingActive; isCountDown ? state.isCountdownActive : state.isInchingActive;
@ -67,8 +81,21 @@ class _CountdownInchingViewState extends State<CountdownInchingView> {
final displayMinutes = isActive && state.countdownRemaining != null final displayMinutes = isActive && state.countdownRemaining != null
? state.countdownRemaining!.inMinutes.remainder(60) ? state.countdownRemaining!.inMinutes.remainder(60)
: (isCountDown ? state.countdownMinutes : state.inchingMinutes); : (isCountDown ? state.countdownMinutes : state.inchingMinutes);
final displaySeconds = isActive && state.countdownRemaining != null
? state.countdownRemaining!.inSeconds.remainder(60)
: (isCountDown ? state.countdownSeconds : state.inchingSeconds);
_updateControllers(displayHours, displayMinutes, displaySeconds!);
if (displayHours == 0 && displayMinutes == 0 && displaySeconds == 0) {
context.read<ScheduleBloc>().add(
StopScheduleEvent(
mode: ScheduleModes.countdown,
deviceId: widget.deviceId,
),
);
}
_updateControllers(displayHours, displayMinutes);
return Column( return Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
@ -100,7 +127,10 @@ class _CountdownInchingViewState extends State<CountdownInchingView> {
(value) { (value) {
if (!isActive) { if (!isActive) {
context.read<ScheduleBloc>().add(UpdateCountdownTimeEvent( context.read<ScheduleBloc>().add(UpdateCountdownTimeEvent(
hours: value, minutes: displayMinutes)); hours: value,
minutes: displayMinutes,
seconds: displaySeconds,
));
} }
}, },
isActive: isActive, isActive: isActive,
@ -115,7 +145,31 @@ class _CountdownInchingViewState extends State<CountdownInchingView> {
(value) { (value) {
if (!isActive) { if (!isActive) {
context.read<ScheduleBloc>().add(UpdateCountdownTimeEvent( context.read<ScheduleBloc>().add(UpdateCountdownTimeEvent(
hours: displayHours, minutes: value)); hours: displayHours,
minutes: value,
seconds: displaySeconds,
));
}
},
isActive: isActive,
),
const SizedBox(width: 10),
if (isActive)
_buildPickerColumn(
context,
's',
displaySeconds,
60,
_secondsController,
(value) {
if (!isActive) {
context
.read<ScheduleBloc>()
.add(UpdateCountdownTimeEvent(
hours: displayHours,
minutes: displayMinutes,
seconds: value,
));
} }
}, },
isActive: isActive, isActive: isActive,

View File

@ -74,7 +74,9 @@ class BuildScheduleView extends StatelessWidget {
), ),
if (state.scheduleMode == ScheduleModes.countdown || if (state.scheduleMode == ScheduleModes.countdown ||
state.scheduleMode == ScheduleModes.inching) state.scheduleMode == ScheduleModes.inching)
const CountdownInchingView(), CountdownInchingView(
deviceId: deviceUuid,
),
const SizedBox(height: 20), const SizedBox(height: 20),
if (state.scheduleMode == ScheduleModes.countdown) if (state.scheduleMode == ScheduleModes.countdown)
CountdownModeButtons( CountdownModeButtons(

View File

@ -79,6 +79,7 @@ class DeviceControlDialog extends StatelessWidget with RouteControlsBasedCode {
} }
Widget _buildDeviceInfoSection() { Widget _buildDeviceInfoSection() {
final isOnlineDevice = device.online != null && device.online!;
return Padding( return Padding(
padding: const EdgeInsets.symmetric(vertical: 25, horizontal: 50), padding: const EdgeInsets.symmetric(vertical: 25, horizontal: 50),
child: Table( child: Table(
@ -107,7 +108,7 @@ class DeviceControlDialog extends StatelessWidget with RouteControlsBasedCode {
'Installation Date and Time:', 'Installation Date and Time:',
formatDateTime( formatDateTime(
DateTime.fromMillisecondsSinceEpoch( DateTime.fromMillisecondsSinceEpoch(
((device.createTime ?? 0) * 1000), (device.createTime ?? 0) * 1000,
), ),
), ),
), ),
@ -126,12 +127,16 @@ class DeviceControlDialog extends StatelessWidget with RouteControlsBasedCode {
), ),
TableRow( TableRow(
children: [ children: [
_buildInfoRow('Status:', 'Online', statusColor: Colors.green), _buildInfoRow(
'Status:',
isOnlineDevice ? 'Online' : 'offline',
statusColor: isOnlineDevice ? Colors.green : Colors.red,
),
_buildInfoRow( _buildInfoRow(
'Last Offline Date and Time:', 'Last Offline Date and Time:',
formatDateTime( formatDateTime(
DateTime.fromMillisecondsSinceEpoch( DateTime.fromMillisecondsSinceEpoch(
((device.updateTime ?? 0) * 1000), (device.updateTime ?? 0) * 1000,
), ),
), ),
), ),

View File

@ -102,7 +102,7 @@ class VisitorPasswordDialog extends StatelessWidget {
], ],
)) ))
.then((v) { .then((v) {
Navigator.of(context).pop(true); Navigator.of(context).pop(v);
}); });
} else if (state is FailedState) { } else if (state is FailedState) {
visitorBloc.stateDialog( visitorBloc.stateDialog(
@ -476,7 +476,7 @@ class VisitorPasswordDialog extends StatelessWidget {
child: DefaultButton( child: DefaultButton(
borderRadius: 8, borderRadius: 8,
onPressed: () { onPressed: () {
Navigator.of(context).pop(true); Navigator.of(context).pop(null);
}, },
backgroundColor: Colors.white, backgroundColor: Colors.white,
child: Text( child: Text(
@ -651,7 +651,7 @@ class VisitorPasswordDialog extends StatelessWidget {
child: DefaultButton( child: DefaultButton(
borderRadius: 8, borderRadius: 8,
onPressed: () { onPressed: () {
Navigator.of(context).pop(); Navigator.of(context).pop(null);
}, },
backgroundColor: Colors.white, backgroundColor: Colors.white,
child: Text( child: Text(