Compare commits

..

28 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
c649044a1f Enhance navigation buttons in SmartPowerDeviceControl for better user experience 2025-06-24 16:40:42 +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
db9e856bca Sp 1711 fe implement blank state (#288)
<!--
  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-1711](https://syncrow.atlassian.net/browse/SP-1711)
[SP-1712](https://syncrow.atlassian.net/browse/SP-1712)

## Description

1. Shows tooltip on space cell.
2. navigates to space when a new space is selected
3. Fixed a thrown Exception because of an Expanded widget
4. Adjusted connections between spaces to select from and to nodes.
5. Created a `SpaceDetailsDialog` and a helper class to show it, because
a space can be created through different widgets, and this was done to
unify the logic and remove code duplication.

## Type of Change

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

- [x]  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-1711]:
https://syncrow.atlassian.net/browse/SP-1711?atlOrigin=eyJpIjoiNWRkNTljNzYxNjVmNDY3MDlhMDU5Y2ZhYzA5YTRkZjUiLCJwIjoiZ2l0aHViLWNvbS1KU1cifQ

[SP-1712]:
https://syncrow.atlassian.net/browse/SP-1712?atlOrigin=eyJpIjoiNWRkNTljNzYxNjVmNDY3MDlhMDU5Y2ZhYzA5YTRkZjUiLCJwIjoiZ2l0aHViLWNvbS1KU1cifQ
2025-06-24 12:29:25 +03:00
07435ec89e On access management page Create visitor password dialog is not responsive (#289)
<!--
  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-1474](https://syncrow.atlassian.net/browse/SP-1474)

## Description

<!--- Describe your changes in detail -->
Add responsive input fields and radio groups for visitor password setup

## 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-1474]:
https://syncrow.atlassian.net/browse/SP-1474?atlOrigin=eyJpIjoiNWRkNTljNzYxNjVmNDY3MDlhMDU5Y2ZhYzA5YTRkZjUiLCJwIjoiZ2l0aHViLWNvbS1KU1cifQ
2025-06-24 12:27:39 +03:00
2a2fb7ffca Add responsive input fields and radio groups for visitor password setup 2025-06-24 11:36:50 +03:00
5a2299ea2f navigates to initial create space dialog from the respective buttons. 2025-06-24 10:47:48 +03:00
90f8305aa1 shows tooltip on SpaceCell. 2025-06-24 10:45:13 +03:00
329b2ba472 selects the space from and to connection when selecting a space. 2025-06-24 10:36:13 +03:00
0fb9149613 Selects all children of a space when selecting a parent. 2025-06-24 10:30:56 +03:00
87b45fff1d removed expanded widget that caused a size exception. 2025-06-24 10:21:25 +03:00
95ae50d12d navigates to selected space when changed on sidebar in space management canvas. 2025-06-24 10:16:03 +03:00
95d6e1ecda if online go green with online status else red with offline status 2025-06-23 16:33:45 +03:00
479aa4a091 Sp 1713 implement empty state (#285)
<!--
  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-1713](https://syncrow.atlassian.net/browse/SP-1713)

## Description

Implemented non selected space state
Implemented an initial version of the canvas.

## Type of Change

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

- [x]  New feature (non-breaking change which adds functionality)
- [ ] 🛠️ 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-1713]:
https://syncrow.atlassian.net/browse/SP-1713?atlOrigin=eyJpIjoiNWRkNTljNzYxNjVmNDY3MDlhMDU5Y2ZhYzA5YTRkZjUiLCJwIjoiZ2l0aHViLWNvbS1KU1cifQ
2025-06-23 16:27:42 +03:00
75efc595b4 reverted to old import to avoid confusion with QA team. 2025-06-23 16:22:11 +03:00
8bc7a3daa2 Implemented space management canvas. 2025-06-23 15:45:49 +03:00
ada7daf179 Switched from using Text to SelectableText in CreateCommunityDialog. 2025-06-23 10:13:30 +03:00
4bdb487094 doesnt show a snackbar when creating a community fails, since we show the error message in the dialog itself. 2025-06-23 10:11:23 +03:00
f8e4c89cdb uses correct error message that the api sends in RemoteCreateCommunityService. 2025-06-23 10:11:03 +03:00
7d4cdba0ef Connected templates view into SpaceManagementBody, while applying the correct UI principals if what to show what when? 2025-06-23 10:06:59 +03:00
a78b5993a9 Created SpaceManagementTemplatesView widget. 2025-06-23 10:05:53 +03:00
0e7109a19e Created CommunityTemplateCell widget. 2025-06-23 10:02:15 +03:00
ff3d5cd996 Created a helper class to show create community dialog, since this dialog can be shown from two different widgets. 2025-06-23 10:02:02 +03:00
34 changed files with 1707 additions and 591 deletions

View File

@ -50,6 +50,9 @@ class _DynamicTableState extends State<DynamicTable> {
bool _selectAll = false;
final ScrollController _verticalScrollController = ScrollController();
final ScrollController _horizontalScrollController = ScrollController();
static const double _fixedRowHeight = 60;
static const double _checkboxColumnWidth = 50;
static const double _settingsColumnWidth = 100;
@override
void initState() {
@ -67,7 +70,6 @@ class _DynamicTableState extends State<DynamicTable> {
bool _compareListOfLists(
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;
for (int i = 0; i < oldList.length; i++) {
@ -104,73 +106,130 @@ class _DynamicTableState extends State<DynamicTable> {
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
Widget build(BuildContext context) {
return Container(
width: widget.size.width,
height: widget.size.height,
decoration: widget.cellDecoration,
child: Scrollbar(
controller: _verticalScrollController,
thumbVisibility: true,
trackVisibility: true,
child: ScrollConfiguration(
behavior: const ScrollBehavior().copyWith(scrollbars: false),
child: Scrollbar(
//fixed the horizontal scrollbar issue
controller: _horizontalScrollController,
thumbVisibility: true,
trackVisibility: true,
notificationPredicate: (notif) => notif.depth == 1,
notificationPredicate: (notif) =>
notif.metrics.axis == Axis.horizontal,
child: SingleChildScrollView(
controller: _verticalScrollController,
child: SingleChildScrollView(
controller: _horizontalScrollController,
scrollDirection: Axis.horizontal,
child: SizedBox(
width: widget.size.width,
child: Column(
children: [
Container(
decoration: widget.headerDecoration ??
const BoxDecoration(
color: ColorsManager.boxColor,
controller: _horizontalScrollController,
scrollDirection: Axis.horizontal,
child: SizedBox(
width: _totalTableWidth,
child: Column(
children: [
Container(
height: _fixedRowHeight,
decoration: widget.headerDecoration ??
const BoxDecoration(color: ColorsManager.boxColor),
child: Row(
children: [
if (widget.withCheckBox)
_buildSelectAllCheckbox(_checkboxColumnWidth),
for (var i = 0; i < widget.headers.length; i++)
_buildTableHeaderCell(
widget.headers[i],
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)),
),
child: Row(
children: [
if (widget.withCheckBox) _buildSelectAllCheckbox(),
...List.generate(widget.headers.length, (index) {
return _buildTableHeaderCell(
widget.headers[index], index);
})
//...widget.headers.map((header) => _buildTableHeaderCell(header)),
],
),
],
),
SizedBox(
width: widget.size.width,
child: widget.isEmpty
? _buildEmptyState()
: Column(
children:
List.generate(widget.data.length, (rowIndex) {
),
Expanded(
child: widget.isEmpty
? _buildEmptyState()
: Scrollbar(
controller: _verticalScrollController,
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];
return Row(
children: [
if (widget.withCheckBox)
_buildRowCheckbox(
rowIndex, widget.size.height * 0.08),
...row.asMap().entries.map((entry) {
return _buildTableCell(
entry.value.toString(),
widget.size.height * 0.08,
rowIndex: rowIndex,
columnIndex: entry.key,
);
}).toList(),
],
return SizedBox(
height: _fixedRowHeight,
child: Row(
children: [
if (widget.withCheckBox)
_buildRowCheckbox(
rowIndex,
_checkboxColumnWidth,
),
for (var colIndex = 0;
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,
columnIndex: colIndex,
),
],
),
);
}),
},
),
),
],
),
),
),
],
),
),
),
@ -210,9 +269,10 @@ class _DynamicTableState extends State<DynamicTable> {
],
),
);
Widget _buildSelectAllCheckbox() {
Widget _buildSelectAllCheckbox(double width) {
return Container(
width: 50,
width: width,
decoration: const BoxDecoration(
border: Border.symmetric(
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(
width: 50,
width: width,
padding: const EdgeInsets.all(8.0),
height: size,
height: _fixedRowHeight,
decoration: const BoxDecoration(
border: Border(
bottom: BorderSide(
@ -253,50 +313,47 @@ class _DynamicTableState extends State<DynamicTable> {
);
}
Widget _buildTableHeaderCell(String title, int index) {
return Expanded(
child: Container(
decoration: const BoxDecoration(
border: Border.symmetric(
vertical: BorderSide(color: ColorsManager.boxDivider),
),
Widget _buildTableHeaderCell(String title, double width) {
return Container(
width: width,
decoration: const BoxDecoration(
border: Border.symmetric(
vertical: BorderSide(color: ColorsManager.boxDivider),
),
constraints: const BoxConstraints.expand(height: 40),
alignment: Alignment.centerLeft,
child: Padding(
padding: EdgeInsets.symmetric(
horizontal: index == widget.headers.length - 1 ? 12 : 8.0,
vertical: 4),
child: Text(
title,
style: context.textTheme.titleSmall!.copyWith(
color: ColorsManager.grayColor,
fontSize: 12,
fontWeight: FontWeight.w400,
),
maxLines: 2,
),
constraints: BoxConstraints(minHeight: 40, maxHeight: _fixedRowHeight),
alignment: Alignment.centerLeft,
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 8.0, vertical: 4),
child: Text(
title,
style: context.textTheme.titleSmall!.copyWith(
color: ColorsManager.grayColor,
fontSize: 12,
fontWeight: FontWeight.w400,
),
maxLines: 2,
overflow: TextOverflow.ellipsis,
),
),
);
}
Widget _buildTableCell(String content, double size,
{required int rowIndex, required int columnIndex}) {
Widget _buildTableCell(String content,
{required double width,
required int rowIndex,
required int columnIndex}) {
bool isBatteryLevel = content.endsWith('%');
double? batteryLevel;
if (isBatteryLevel) {
batteryLevel = double.tryParse(content.replaceAll('%', '').trim());
}
bool isSettingsColumn = widget.headers[columnIndex] == 'Settings';
if (isSettingsColumn) {
return buildSettingsIcon(
width: 120,
height: 60,
iconSize: 40,
onTap: () => widget.onSettingsPressed?.call(rowIndex),
);
width: width, onTap: () => widget.onSettingsPressed?.call(rowIndex));
}
Color? statusColor;
@ -320,92 +377,82 @@ class _DynamicTableState extends State<DynamicTable> {
statusColor = Colors.black;
}
return Expanded(
child: Container(
height: size,
padding: const EdgeInsets.all(5.0),
decoration: const BoxDecoration(
border: Border(
bottom: BorderSide(
color: ColorsManager.boxDivider,
width: 1.0,
),
return Container(
width: width,
height: _fixedRowHeight,
padding: const EdgeInsets.symmetric(horizontal: 8.0, vertical: 4),
decoration: const BoxDecoration(
border: Border(
bottom: BorderSide(
color: ColorsManager.boxDivider,
width: 1.0,
),
color: Colors.white,
),
alignment: Alignment.centerLeft,
child: Text(
content,
style: TextStyle(
color: (batteryLevel != null && batteryLevel < 20)
? ColorsManager.red
: (batteryLevel != null && batteryLevel > 20)
? ColorsManager.green
: statusColor,
fontSize: 13,
fontWeight: FontWeight.w400),
maxLines: 2,
color: Colors.white,
),
alignment: Alignment.centerLeft,
child: Text(
content,
style: TextStyle(
color: (batteryLevel != null && batteryLevel < 20)
? ColorsManager.red
: (batteryLevel != null && batteryLevel > 20)
? ColorsManager.green
: statusColor,
fontSize: 13,
fontWeight: FontWeight.w400,
),
maxLines: 2,
overflow: TextOverflow.ellipsis,
),
);
}
Widget buildSettingsIcon(
{double width = 120,
double height = 60,
double iconSize = 40,
VoidCallback? onTap}) {
return Column(
children: [
Container(
padding: const EdgeInsets.only(top: 10, bottom: 15, left: 10),
margin: const EdgeInsets.only(right: 15),
decoration: const BoxDecoration(
color: ColorsManager.whiteColors,
border: Border(
bottom: BorderSide(
color: ColorsManager.boxDivider,
width: 1.0,
),
),
Widget buildSettingsIcon({required double width, VoidCallback? onTap}) {
return Container(
width: width,
height: _fixedRowHeight,
padding: const EdgeInsets.only(left: 15, top: 10, bottom: 10),
decoration: const BoxDecoration(
color: ColorsManager.whiteColors,
border: Border(
bottom: BorderSide(
color: ColorsManager.boxDivider,
width: 1.0,
),
width: width,
child: Padding(
padding: const EdgeInsets.only(
right: 16.0,
left: 17.0,
),
child: Container(
width: 50,
decoration: BoxDecoration(
color: const Color(0xFFF7F8FA),
borderRadius: BorderRadius.circular(height / 2),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.17),
blurRadius: 14,
offset: const Offset(0, 4),
),
],
),
),
child: Align(
alignment: Alignment.centerLeft,
child: Container(
width: 50,
decoration: BoxDecoration(
color: const Color(0xFFF7F8FA),
borderRadius: BorderRadius.circular(20),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.17),
blurRadius: 14,
offset: const Offset(0, 4),
),
child: InkWell(
onTap: onTap,
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Center(
child: SvgPicture.asset(
Assets.settings,
width: 40,
height: 22,
color: ColorsManager.primaryColor,
),
),
],
),
child: InkWell(
onTap: onTap,
child: Padding(
padding: EdgeInsets.all(8.0),
child: Center(
child: SvgPicture.asset(
Assets.settings,
width: 40,
height: 20,
color: ColorsManager.primaryColor,
),
),
),
),
),
],
),
);
}
}

View File

@ -12,7 +12,8 @@ import 'package:syncrow_web/utils/constants/assets.dart';
import 'package:syncrow_web/utils/helpers/responsice_layout_helper/responsive_layout_helper.dart';
//Smart Power Clamp
class SmartPowerDeviceControl extends StatelessWidget with HelperResponsiveLayout {
class SmartPowerDeviceControl extends StatelessWidget
with HelperResponsiveLayout {
final String deviceId;
const SmartPowerDeviceControl({super.key, required this.deviceId});
@ -145,13 +146,16 @@ class SmartPowerDeviceControl extends StatelessWidget with HelperResponsiveLayou
children: [
IconButton(
icon: const Icon(Icons.arrow_left),
onPressed: () {
blocProvider.add(SmartPowerArrowPressedEvent(-1));
pageController.previousPage(
duration: const Duration(milliseconds: 300),
curve: Curves.easeInOut,
);
},
onPressed: blocProvider.currentPage <= 0
? null
: () {
blocProvider
.add(SmartPowerArrowPressedEvent(-1));
pageController.previousPage(
duration: const Duration(milliseconds: 300),
curve: Curves.easeInOut,
);
},
),
Text(
currentPage == 0
@ -165,13 +169,16 @@ class SmartPowerDeviceControl extends StatelessWidget with HelperResponsiveLayou
),
IconButton(
icon: const Icon(Icons.arrow_right),
onPressed: () {
blocProvider.add(SmartPowerArrowPressedEvent(1));
pageController.nextPage(
duration: const Duration(milliseconds: 300),
curve: Curves.easeInOut,
);
},
onPressed: blocProvider.currentPage >= 3
? null
: () {
blocProvider
.add(SmartPowerArrowPressedEvent(1));
pageController.nextPage(
duration: const Duration(milliseconds: 300),
curve: Curves.easeInOut,
);
},
),
],
),
@ -195,8 +202,8 @@ class SmartPowerDeviceControl extends StatelessWidget with HelperResponsiveLayou
blocProvider.add(SelectDateEvent(context: context));
blocProvider.add(FilterRecordsByDateEvent(
selectedDate: blocProvider.dateTime!,
viewType:
blocProvider.views[blocProvider.currentIndex]));
viewType: blocProvider
.views[blocProvider.currentIndex]));
},
widget: blocProvider.dateSwitcher(),
chartData: blocProvider.energyDataList.isNotEmpty

View File

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

View File

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

View File

@ -26,11 +26,15 @@ class ScheduleLoaded extends ScheduleState {
final bool isCountdownActive;
final int inchingHours;
final int inchingMinutes;
final int inchingSeconds;
final bool isInchingActive;
final ScheduleModes scheduleMode;
final Duration? countdownRemaining;
final int? countdownSeconds;
const ScheduleLoaded({
this.countdownSeconds = 0,
this.inchingSeconds = 0,
required this.schedules,
this.selectedTime,
required this.selectedDays,
@ -61,6 +65,9 @@ class ScheduleLoaded extends ScheduleState {
bool? isInchingActive,
ScheduleModes? scheduleMode,
Duration? countdownRemaining,
String? deviceId,
int? countdownSeconds,
int? inchingSeconds,
}) {
return ScheduleLoaded(
schedules: schedules ?? this.schedules,
@ -68,7 +75,7 @@ class ScheduleLoaded extends ScheduleState {
selectedDays: selectedDays ?? this.selectedDays,
functionOn: functionOn ?? this.functionOn,
isEditing: isEditing ?? this.isEditing,
deviceId: deviceId,
deviceId: deviceId ?? this.deviceId,
countdownHours: countdownHours ?? this.countdownHours,
countdownMinutes: countdownMinutes ?? this.countdownMinutes,
isCountdownActive: isCountdownActive ?? this.isCountdownActive,
@ -77,6 +84,8 @@ class ScheduleLoaded extends ScheduleState {
isInchingActive: isInchingActive ?? this.isInchingActive,
scheduleMode: scheduleMode ?? this.scheduleMode,
countdownRemaining: countdownRemaining ?? this.countdownRemaining,
countdownSeconds: countdownSeconds ?? this.countdownSeconds,
inchingSeconds: inchingSeconds ?? this.inchingSeconds,
);
}
@ -96,6 +105,8 @@ class ScheduleLoaded extends ScheduleState {
isInchingActive,
scheduleMode,
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';
class CountdownInchingView extends StatefulWidget {
const CountdownInchingView({super.key});
final String deviceId;
const CountdownInchingView({super.key, required this.deviceId});
@override
State<CountdownInchingView> createState() => _CountdownInchingViewState();
@ -15,25 +16,30 @@ class CountdownInchingView extends StatefulWidget {
class _CountdownInchingViewState extends State<CountdownInchingView> {
late FixedExtentScrollController _hoursController;
late FixedExtentScrollController _minutesController;
late FixedExtentScrollController _secondsController;
int _lastHours = -1;
int _lastMinutes = -1;
int _lastSeconds = -1;
@override
void initState() {
super.initState();
_hoursController = FixedExtentScrollController();
_minutesController = FixedExtentScrollController();
_secondsController = FixedExtentScrollController();
}
@override
void dispose() {
_hoursController.dispose();
_minutesController.dispose();
_secondsController.dispose();
super.dispose();
}
void _updateControllers(int displayHours, int displayMinutes) {
void _updateControllers(
int displayHours, int displayMinutes, int displaySeconds) {
if (_lastHours != displayHours) {
WidgetsBinding.instance.addPostFrameCallback((_) {
if (_hoursController.hasClients) {
@ -50,6 +56,15 @@ class _CountdownInchingViewState extends State<CountdownInchingView> {
});
_lastMinutes = displayMinutes;
}
// Update seconds controller
if (_lastSeconds != displaySeconds) {
WidgetsBinding.instance.addPostFrameCallback((_) {
if (_secondsController.hasClients) {
_secondsController.jumpToItem(displaySeconds);
}
});
_lastSeconds = displaySeconds;
}
}
@override
@ -57,7 +72,6 @@ class _CountdownInchingViewState extends State<CountdownInchingView> {
return BlocBuilder<ScheduleBloc, ScheduleState>(
builder: (context, state) {
if (state is! ScheduleLoaded) return const SizedBox.shrink();
final isCountDown = state.scheduleMode == ScheduleModes.countdown;
final isActive =
isCountDown ? state.isCountdownActive : state.isInchingActive;
@ -67,8 +81,21 @@ class _CountdownInchingViewState extends State<CountdownInchingView> {
final displayMinutes = isActive && state.countdownRemaining != null
? state.countdownRemaining!.inMinutes.remainder(60)
: (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(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
@ -100,7 +127,10 @@ class _CountdownInchingViewState extends State<CountdownInchingView> {
(value) {
if (!isActive) {
context.read<ScheduleBloc>().add(UpdateCountdownTimeEvent(
hours: value, minutes: displayMinutes));
hours: value,
minutes: displayMinutes,
seconds: displaySeconds,
));
}
},
isActive: isActive,
@ -115,11 +145,35 @@ class _CountdownInchingViewState extends State<CountdownInchingView> {
(value) {
if (!isActive) {
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,
),
],
),
],

View File

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

View File

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

View File

@ -1,9 +1,7 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_svg/flutter_svg.dart';
import 'package:syncrow_web/pages/auth/model/user_model.dart';
import 'package:syncrow_web/pages/common/bloc/project_manager.dart';
import 'package:syncrow_web/pages/roles_and_permission/model/roles_user_model.dart';
import 'package:syncrow_web/pages/roles_and_permission/users_page/add_user_dialog/bloc/users_bloc.dart';
import 'package:syncrow_web/pages/roles_and_permission/users_page/add_user_dialog/bloc/users_event.dart';
import 'package:syncrow_web/pages/roles_and_permission/users_page/add_user_dialog/bloc/users_status.dart';
@ -14,11 +12,8 @@ import 'package:syncrow_web/utils/color_manager.dart';
import 'package:syncrow_web/utils/constants/assets.dart';
class EditUserDialog extends StatefulWidget {
final RolesUserModel? user;
const EditUserDialog({
super.key,
this.user,
});
final String? userId;
const EditUserDialog({super.key, this.userId});
@override
_EditUserDialogState createState() => _EditUserDialogState();
@ -33,11 +28,10 @@ class _EditUserDialogState extends State<EditUserDialog> {
create: (BuildContext context) => UsersBloc()
// ..add(const LoadCommunityAndSpacesEvent())
..add(const RoleEvent())
..add(GetUserByIdEvent(uuid: widget.user!.uuid)),
..add(GetUserByIdEvent(uuid: widget.userId)),
child: BlocConsumer<UsersBloc, UsersState>(listener: (context, state) {
if (state is SpacesLoadedState) {
BlocProvider.of<UsersBloc>(context)
.add(GetUserByIdEvent(uuid: widget.user!.uuid));
BlocProvider.of<UsersBloc>(context).add(GetUserByIdEvent(uuid: widget.userId));
}
}, builder: (context, state) {
final _blocRole = BlocProvider.of<UsersBloc>(context);
@ -45,8 +39,7 @@ class _EditUserDialogState extends State<EditUserDialog> {
return Dialog(
child: Container(
decoration: const BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.all(Radius.circular(20))),
color: Colors.white, borderRadius: BorderRadius.all(Radius.circular(20))),
width: 900,
child: Column(
children: [
@ -75,8 +68,7 @@ class _EditUserDialogState extends State<EditUserDialog> {
children: [
_buildStep1Indicator(1, "Basics", _blocRole),
_buildStep2Indicator(2, "Spaces", _blocRole),
_buildStep3Indicator(
3, "Role & Permissions", _blocRole),
_buildStep3Indicator(3, "Role & Permissions", _blocRole),
],
),
),
@ -94,7 +86,7 @@ class _EditUserDialogState extends State<EditUserDialog> {
children: [
const SizedBox(height: 10),
Expanded(
child: _getFormContent(widget.user!),
child: _getFormContent(widget.userId),
),
const SizedBox(height: 20),
],
@ -124,14 +116,13 @@ class _EditUserDialogState extends State<EditUserDialog> {
if (currentStep < 3) {
currentStep++;
if (currentStep == 2) {
_blocRole
.add(CheckStepStatus(isEditUser: true));
_blocRole.add(CheckStepStatus(isEditUser: true));
} else if (currentStep == 3) {
_blocRole.add(const CheckSpacesStepStatus());
}
} else {
_blocRole.add(EditInviteUsers(
context: context, userId: widget.user!.uuid));
_blocRole
.add(EditInviteUsers(context: context, userId: widget.userId!));
}
});
},
@ -140,8 +131,7 @@ class _EditUserDialogState extends State<EditUserDialog> {
style: TextStyle(
color: (_blocRole.isCompleteSpaces == false ||
_blocRole.isCompleteBasics == false ||
_blocRole.isCompleteRolePermissions ==
false) &&
_blocRole.isCompleteRolePermissions == false) &&
currentStep == 3
? ColorsManager.grayColor
: ColorsManager.secondaryColor),
@ -156,15 +146,15 @@ class _EditUserDialogState extends State<EditUserDialog> {
}));
}
Widget _getFormContent(RolesUserModel user) {
Widget _getFormContent(userid) {
switch (currentStep) {
case 1:
return BasicsView(
userId: user.uuid,
userId: userid,
);
case 2:
return SpacesAccessView(
userId: user.uuid,
userId: userid,
);
case 3:
return const RolesAndPermission();
@ -176,7 +166,6 @@ class _EditUserDialogState extends State<EditUserDialog> {
int step3 = 0;
Widget _buildStep1Indicator(int step, String label, UsersBloc bloc) {
final isCurrentStep = currentStep == step;
return GestureDetector(
onTap: () {
setState(() {
@ -200,7 +189,7 @@ class _EditUserDialogState extends State<EditUserDialog> {
child: Row(
children: [
SvgPicture.asset(
isCurrentStep
currentStep == step
? Assets.currentProcessIcon
: bloc.isCompleteBasics == false
? Assets.wrongProcessIcon
@ -215,11 +204,8 @@ class _EditUserDialogState extends State<EditUserDialog> {
label,
style: TextStyle(
fontSize: 16,
color: isCurrentStep
? ColorsManager.blackColor
: ColorsManager.greyColor,
fontWeight:
isCurrentStep ? FontWeight.bold : FontWeight.normal,
color: currentStep == step ? ColorsManager.blackColor : ColorsManager.greyColor,
fontWeight: currentStep == step ? FontWeight.bold : FontWeight.normal,
),
),
],
@ -243,7 +229,6 @@ class _EditUserDialogState extends State<EditUserDialog> {
}
Widget _buildStep2Indicator(int step, String label, UsersBloc bloc) {
final isCurrentStep = currentStep == step;
return GestureDetector(
onTap: () {
setState(() {
@ -263,7 +248,7 @@ class _EditUserDialogState extends State<EditUserDialog> {
child: Row(
children: [
SvgPicture.asset(
isCurrentStep
currentStep == step
? Assets.currentProcessIcon
: bloc.isCompleteSpaces == false
? Assets.wrongProcessIcon
@ -278,11 +263,8 @@ class _EditUserDialogState extends State<EditUserDialog> {
label,
style: TextStyle(
fontSize: 16,
color: isCurrentStep
? ColorsManager.blackColor
: ColorsManager.greyColor,
fontWeight:
isCurrentStep ? FontWeight.bold : FontWeight.normal,
color: currentStep == step ? ColorsManager.blackColor : ColorsManager.greyColor,
fontWeight: currentStep == step ? FontWeight.bold : FontWeight.normal,
),
),
],
@ -306,7 +288,6 @@ class _EditUserDialogState extends State<EditUserDialog> {
}
Widget _buildStep3Indicator(int step, String label, UsersBloc bloc) {
final isCurrentStep = currentStep == step;
return GestureDetector(
onTap: () {
setState(() {
@ -325,7 +306,7 @@ class _EditUserDialogState extends State<EditUserDialog> {
child: Row(
children: [
SvgPicture.asset(
isCurrentStep
currentStep == step
? Assets.currentProcessIcon
: bloc.isCompleteRolePermissions == false
? Assets.wrongProcessIcon
@ -340,11 +321,8 @@ class _EditUserDialogState extends State<EditUserDialog> {
label,
style: TextStyle(
fontSize: 16,
color: isCurrentStep
? ColorsManager.blackColor
: ColorsManager.greyColor,
fontWeight:
isCurrentStep ? FontWeight.bold : FontWeight.normal,
color: currentStep == step ? ColorsManager.blackColor : ColorsManager.greyColor,
fontWeight: currentStep == step ? FontWeight.bold : FontWeight.normal,
),
),
],

View File

@ -19,7 +19,6 @@ import 'package:syncrow_web/utils/color_manager.dart';
import 'package:syncrow_web/utils/constants/assets.dart';
import 'package:syncrow_web/utils/extension/build_context_x.dart';
import 'package:syncrow_web/utils/style.dart';
class UsersPage extends StatelessWidget {
UsersPage({super.key});
@ -452,31 +451,33 @@ class UsersPage extends StatelessWidget {
),
Row(
children: [
if (user.isEnabled != false)
actionButton(
isActive: true,
title: "Edit",
onTap: () {
context
.read<SpaceTreeBloc>()
.add(ClearCachedData());
showDialog(
context: context,
barrierDismissible: false,
builder: (BuildContext context) {
return EditUserDialog(user: user);
user.isEnabled != false
? actionButton(
isActive: true,
title: "Edit",
onTap: () {
context
.read<SpaceTreeBloc>()
.add(ClearCachedData());
showDialog(
context: context,
barrierDismissible: false,
builder: (BuildContext context) {
return EditUserDialog(
userId: user.uuid);
},
).then((v) {
if (v != null) {
if (v != null) {
_blocRole.add(const GetUsers());
}
}
});
},
).then((v) {
if (v != null) {
_blocRole.add(const GetUsers());
}
});
},
)
else
actionButton(
title: "Edit",
),
)
: actionButton(
title: "Edit",
),
actionButton(
title: "Delete",
onTap: () {

View File

@ -0,0 +1,6 @@
class SpaceConnectionModel {
final String from;
final String to;
const SpaceConnectionModel({required this.from, required this.to});
}

View File

@ -0,0 +1,61 @@
import 'package:flutter/material.dart';
import 'package:syncrow_web/pages/space_management_v2/main_module/models/space_connection_model.dart';
import 'package:syncrow_web/utils/color_manager.dart';
class SpacesConnectionsArrowPainter extends CustomPainter {
final List<SpaceConnectionModel> connections;
final Map<String, Offset> positions;
final double cardWidth = 150.0;
final double cardHeight = 90.0;
final Set<String> highlightedUuids;
SpacesConnectionsArrowPainter({
required this.connections,
required this.positions,
required this.highlightedUuids,
});
@override
void paint(Canvas canvas, Size size) {
for (final connection in connections) {
final isSelected = highlightedUuids.contains(connection.from) ||
highlightedUuids.contains(connection.to);
final paint = Paint()
..color = isSelected
? ColorsManager.blackColor
: ColorsManager.blackColor.withValues(alpha: 0.5)
..strokeWidth = 2.0
..style = PaintingStyle.stroke;
final from = positions[connection.from];
final to = positions[connection.to];
if (from != null && to != null) {
final startPoint =
Offset(from.dx + cardWidth / 2, from.dy + cardHeight - 10);
final endPoint = Offset(to.dx + cardWidth / 2, to.dy);
final path = Path()..moveTo(startPoint.dx, startPoint.dy);
final controlPoint1 = Offset(startPoint.dx, startPoint.dy + 20);
final controlPoint2 = Offset(endPoint.dx, endPoint.dy - 60);
path.cubicTo(controlPoint1.dx, controlPoint1.dy, controlPoint2.dx,
controlPoint2.dy, endPoint.dx, endPoint.dy);
canvas.drawPath(path, paint);
final circlePaint = Paint()
..color = isSelected
? ColorsManager.blackColor
: ColorsManager.blackColor.withValues(alpha: 0.5)
..style = PaintingStyle.fill
..blendMode = BlendMode.srcIn;
canvas.drawCircle(endPoint, 4, circlePaint);
}
}
}
@override
bool shouldRepaint(covariant CustomPainter oldDelegate) => true;
}

View File

@ -0,0 +1,24 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:syncrow_web/pages/space_management_v2/modules/communities/presentation/bloc/communities_bloc.dart';
import 'package:syncrow_web/pages/space_management_v2/modules/communities/presentation/communities_tree_selection_bloc/communities_tree_selection_bloc.dart';
import 'package:syncrow_web/pages/space_management_v2/modules/create_community/presentation/create_community_dialog.dart';
abstract final class SpaceManagementCommunityDialogHelper {
static void showCreateDialog(BuildContext context) {
showDialog<void>(
context: context,
builder: (_) => CreateCommunityDialog(
title: const SelectableText('Community Name'),
onCreateCommunity: (community) {
context.read<CommunitiesBloc>().add(
InsertCommunity(community),
);
context.read<CommunitiesTreeSelectionBloc>().add(
SelectCommunityEvent(community: community),
);
},
),
);
}
}

View File

@ -0,0 +1,280 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:syncrow_web/pages/space_management_v2/main_module/models/space_connection_model.dart';
import 'package:syncrow_web/pages/space_management_v2/main_module/painters/spaces_connections_arrow_painter.dart';
import 'package:syncrow_web/pages/space_management_v2/main_module/widgets/space_card_widget.dart';
import 'package:syncrow_web/pages/space_management_v2/main_module/widgets/space_cell.dart';
import 'package:syncrow_web/pages/space_management_v2/modules/communities/domain/models/community_model.dart';
import 'package:syncrow_web/pages/space_management_v2/modules/communities/domain/models/space_model.dart';
import 'package:syncrow_web/pages/space_management_v2/modules/communities/presentation/communities_tree_selection_bloc/communities_tree_selection_bloc.dart';
import 'package:syncrow_web/pages/space_management_v2/modules/space_details/presentation/helpers/space_details_dialog_helper.dart';
class CommunityStructureCanvas extends StatefulWidget {
const CommunityStructureCanvas({
required this.community,
required this.selectedSpace,
super.key,
});
final CommunityModel community;
final SpaceModel? selectedSpace;
@override
State<CommunityStructureCanvas> createState() => _CommunityStructureCanvasState();
}
class _CommunityStructureCanvasState extends State<CommunityStructureCanvas>
with SingleTickerProviderStateMixin {
final Map<String, Offset> _positions = {};
final double _cardWidth = 150.0;
final double _cardHeight = 90.0;
final double _horizontalSpacing = 150.0;
final double _verticalSpacing = 120.0;
late TransformationController _transformationController;
late AnimationController _animationController;
@override
void initState() {
_transformationController = TransformationController();
_animationController = AnimationController(
vsync: this,
duration: const Duration(milliseconds: 150),
);
super.initState();
}
@override
void didUpdateWidget(covariant CommunityStructureCanvas oldWidget) {
super.didUpdateWidget(oldWidget);
if (widget.selectedSpace?.uuid != oldWidget.selectedSpace?.uuid) {
WidgetsBinding.instance.addPostFrameCallback((_) {
if (mounted) {
_animateToSpace(widget.selectedSpace);
}
});
}
}
@override
void dispose() {
_transformationController.dispose();
_animationController.dispose();
super.dispose();
}
Set<String> _getAllDescendantUuids(SpaceModel space) {
final uuids = <String>{};
for (final child in space.children) {
uuids.add(child.uuid);
uuids.addAll(_getAllDescendantUuids(child));
}
return uuids;
}
void _runAnimation(Matrix4 target) {
final animation = Matrix4Tween(
begin: _transformationController.value,
end: target,
).animate(_animationController);
void listener() {
_transformationController.value = animation.value;
}
animation.addListener(listener);
_animationController.forward(from: 0).whenCompleteOrCancel(() {
animation.removeListener(listener);
});
}
void _animateToSpace(SpaceModel? space) {
if (space == null) {
_runAnimation(Matrix4.identity());
return;
}
final position = _positions[space.uuid];
if (position == null) return;
const scale = 1.5;
final viewSize = context.size;
if (viewSize == null) return;
final x = -position.dx * scale + (viewSize.width / 2) - (_cardWidth * scale / 2);
final y =
-position.dy * scale + (viewSize.height / 2) - (_cardHeight * scale / 2);
final matrix = Matrix4.identity()
..translate(x, y)
..scale(scale);
_runAnimation(matrix);
}
void _onSpaceTapped(SpaceModel? space) {
context.read<CommunitiesTreeSelectionBloc>().add(
SelectSpaceEvent(community: widget.community, space: space),
);
}
void _resetSelectionAndZoom() {
context.read<CommunitiesTreeSelectionBloc>().add(
SelectSpaceEvent(
community: widget.community,
space: null,
),
);
}
void _calculateLayout(
List<SpaceModel> spaces,
int depth,
Map<int, double> levelXOffset,
) {
for (final space in spaces) {
double childSubtreeWidth = 0;
if (space.children.isNotEmpty) {
_calculateLayout(space.children, depth + 1, levelXOffset);
final firstChildPos = _positions[space.children.first.uuid];
final lastChildPos = _positions[space.children.last.uuid];
if (firstChildPos != null && lastChildPos != null) {
childSubtreeWidth = (lastChildPos.dx + _cardWidth) - firstChildPos.dx;
}
}
final currentX = levelXOffset.putIfAbsent(depth, () => 0.0);
double? x;
if (space.children.isNotEmpty) {
final firstChildPos = _positions[space.children.first.uuid]!;
x = firstChildPos.dx + (childSubtreeWidth - _cardWidth) / 2;
} else {
x = currentX;
}
if (x < currentX) {
final shiftX = currentX - x;
_shiftSubtree(space, shiftX);
final keysToShift = levelXOffset.keys.where((d) => d > depth).toList();
for (final key in keysToShift) {
levelXOffset[key] = levelXOffset[key]! + shiftX;
}
x += shiftX;
}
final y = depth * (_verticalSpacing + _cardHeight);
_positions[space.uuid] = Offset(x, y);
levelXOffset[depth] = x + _cardWidth + _horizontalSpacing;
}
}
void _shiftSubtree(SpaceModel space, double shiftX) {
if (_positions.containsKey(space.uuid)) {
_positions[space.uuid] = _positions[space.uuid]!.translate(shiftX, 0);
}
for (final child in space.children) {
_shiftSubtree(child, shiftX);
}
}
List<Widget> _buildTreeWidgets() {
_positions.clear();
final community = widget.community;
_calculateLayout(community.spaces, 0, {});
final selectedSpace = widget.selectedSpace;
final highlightedUuids = <String>{};
if (selectedSpace != null) {
highlightedUuids.add(selectedSpace.uuid);
highlightedUuids.addAll(_getAllDescendantUuids(selectedSpace));
}
final widgets = <Widget>[];
final connections = <SpaceConnectionModel>[];
_generateWidgets(community.spaces, widgets, connections, highlightedUuids);
return [
CustomPaint(
painter: SpacesConnectionsArrowPainter(
connections: connections,
positions: _positions,
highlightedUuids: highlightedUuids,
),
child: Stack(alignment: AlignmentDirectional.center, children: widgets),
),
];
}
void _generateWidgets(
List<SpaceModel> spaces,
List<Widget> widgets,
List<SpaceConnectionModel> connections,
Set<String> highlightedUuids,
) {
for (final space in spaces) {
final position = _positions[space.uuid];
if (position == null) continue;
final isHighlighted = highlightedUuids.contains(space.uuid);
final hasNoSelectedSpace = widget.selectedSpace == null;
widgets.add(
Positioned(
left: position.dx,
top: position.dy,
width: _cardWidth,
height: _cardHeight,
child: SpaceCardWidget(
buildSpaceContainer: () {
return Opacity(
opacity: hasNoSelectedSpace || isHighlighted ? 1.0 : 0.5,
child: Tooltip(
message: space.spaceName,
preferBelow: false,
child: SpaceCell(
onTap: () => _onSpaceTapped(space),
icon: space.icon,
name: space.spaceName,
),
),
);
},
onTap: () => SpaceDetailsDialogHelper.showCreate(context),
),
),
);
for (final child in space.children) {
connections.add(
SpaceConnectionModel(from: space.uuid, to: child.uuid),
);
}
_generateWidgets(space.children, widgets, connections, highlightedUuids);
}
}
@override
Widget build(BuildContext context) {
final treeWidgets = _buildTreeWidgets();
return InteractiveViewer(
transformationController: _transformationController,
boundaryMargin: EdgeInsets.symmetric(
horizontal: MediaQuery.sizeOf(context).width * 0.3,
vertical: MediaQuery.sizeOf(context).height * 0.3,
),
minScale: 0.5,
maxScale: 3.0,
constrained: false,
child: GestureDetector(
onTap: _resetSelectionAndZoom,
child: SizedBox(
width: MediaQuery.sizeOf(context).width * 5,
height: MediaQuery.sizeOf(context).height * 5,
child: Stack(children: treeWidgets),
),
),
);
}
}

View File

@ -0,0 +1,50 @@
import 'package:flutter/material.dart';
import 'package:syncrow_web/utils/color_manager.dart';
import 'package:syncrow_web/utils/extension/build_context_x.dart';
class CommunityTemplateCell extends StatelessWidget {
const CommunityTemplateCell({
super.key,
required this.onTap,
required this.title,
});
final void Function() onTap;
final Widget title;
@override
Widget build(BuildContext context) {
return GestureDetector(
onTap: onTap,
child: Column(
spacing: 8,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Expanded(
child: AspectRatio(
aspectRatio: 2.0,
child: Container(
decoration: ShapeDecoration(
shape: RoundedRectangleBorder(
side: const BorderSide(
width: 4,
strokeAlign: BorderSide.strokeAlignOutside,
color: ColorsManager.borderColor,
),
borderRadius: BorderRadius.circular(5),
),
),
),
),
),
DefaultTextStyle(
style: context.textTheme.bodyLarge!.copyWith(
color: ColorsManager.blackColor,
),
child: title,
),
],
),
);
}
}

View File

@ -0,0 +1,43 @@
import 'package:flutter/material.dart';
import 'package:syncrow_web/pages/space_management_v2/modules/space_details/presentation/helpers/space_details_dialog_helper.dart';
import 'package:syncrow_web/utils/color_manager.dart';
class CreateSpaceButton extends StatelessWidget {
const CreateSpaceButton({super.key});
@override
Widget build(BuildContext context) {
return GestureDetector(
onTap: () => SpaceDetailsDialogHelper.showCreate(context),
child: Container(
height: 60,
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(20),
boxShadow: [
BoxShadow(
color: Colors.grey.withValues(alpha: 0.5),
spreadRadius: 5,
blurRadius: 7,
offset: const Offset(0, 3),
),
],
),
child: Center(
child: Container(
width: 40,
height: 40,
decoration: const BoxDecoration(
color: ColorsManager.boxColor,
shape: BoxShape.circle,
),
child: const Icon(
Icons.add,
color: Colors.blue,
),
),
),
),
);
}
}

View File

@ -0,0 +1,33 @@
import 'package:flutter/material.dart';
import 'package:syncrow_web/utils/color_manager.dart';
class PlusButtonWidget extends StatelessWidget {
final Offset offset;
final void Function() onButtonTap;
const PlusButtonWidget({
super.key,
required this.offset,
required this.onButtonTap,
});
@override
Widget build(BuildContext context) {
return GestureDetector(
onTap: onButtonTap,
child: Container(
width: 30,
height: 30,
decoration: const BoxDecoration(
color: ColorsManager.spaceColor,
shape: BoxShape.circle,
),
child: const Icon(
Icons.add,
color: ColorsManager.whiteColors,
size: 20,
),
),
);
}
}

View File

@ -0,0 +1,44 @@
import 'package:flutter/material.dart';
import 'package:syncrow_web/pages/space_management_v2/main_module/widgets/plus_button_widget.dart';
class SpaceCardWidget extends StatefulWidget {
final void Function() onTap;
final Widget Function() buildSpaceContainer;
const SpaceCardWidget({
required this.onTap,
required this.buildSpaceContainer,
super.key,
});
@override
State<SpaceCardWidget> createState() => _SpaceCardWidgetState();
}
class _SpaceCardWidgetState extends State<SpaceCardWidget> {
bool isHovered = false;
@override
Widget build(BuildContext context) {
return MouseRegion(
onEnter: (_) => setState(() => isHovered = true),
onExit: (_) => setState(() => isHovered = false),
child: SizedBox(
child: Stack(
clipBehavior: Clip.none,
alignment: Alignment.center,
children: [
widget.buildSpaceContainer(),
if (isHovered)
Positioned(
bottom: 0,
child: PlusButtonWidget(
offset: Offset.zero,
onButtonTap: widget.onTap,
),
),
],
),
),
);
}
}

View File

@ -0,0 +1,85 @@
import 'package:flutter/material.dart';
import 'package:flutter_svg/svg.dart';
import 'package:syncrow_web/utils/color_manager.dart';
import 'package:syncrow_web/utils/extension/build_context_x.dart';
class SpaceCell extends StatelessWidget {
final String icon;
final String name;
final VoidCallback? onTap;
const SpaceCell({
super.key,
required this.icon,
required this.name,
required this.onTap,
});
@override
Widget build(BuildContext context) {
return GestureDetector(
onTap: onTap,
child: Container(
width: 150,
height: 70,
decoration: _containerDecoration(),
child: Row(
children: [
_buildIconContainer(),
const SizedBox(width: 10),
Expanded(
child: Text(
name,
style: context.textTheme.bodyLarge?.copyWith(
fontWeight: FontWeight.bold,
color: ColorsManager.blackColor,
),
overflow: TextOverflow.ellipsis,
),
),
],
),
),
);
}
Widget _buildIconContainer() {
return Container(
width: 40,
height: double.infinity,
decoration: const BoxDecoration(
color: ColorsManager.spaceColor,
borderRadius: BorderRadius.only(
topLeft: Radius.circular(15),
bottomLeft: Radius.circular(15),
),
),
child: Center(
child: SvgPicture.asset(
icon,
colorFilter: const ColorFilter.mode(
ColorsManager.whiteColors,
BlendMode.srcIn,
),
width: 24,
height: 24,
),
),
);
}
BoxDecoration _containerDecoration() {
return BoxDecoration(
color: ColorsManager.whiteColors,
borderRadius: BorderRadius.circular(15),
boxShadow: [
BoxShadow(
color: ColorsManager.lightGrayColor.withValues(alpha: 0.5),
spreadRadius: 2,
blurRadius: 5,
offset: const Offset(0, 3),
),
],
);
}
}

View File

@ -1,4 +1,8 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:syncrow_web/pages/space_management_v2/main_module/widgets/space_management_community_structure.dart';
import 'package:syncrow_web/pages/space_management_v2/main_module/widgets/space_management_templates_view.dart';
import 'package:syncrow_web/pages/space_management_v2/modules/communities/presentation/communities_tree_selection_bloc/communities_tree_selection_bloc.dart';
import 'package:syncrow_web/pages/space_management_v2/modules/communities/presentation/widgets/space_management_communities_tree.dart';
class SpaceManagementBody extends StatelessWidget {
@ -6,9 +10,21 @@ class SpaceManagementBody extends StatelessWidget {
@override
Widget build(BuildContext context) {
return const Row(
return Row(
children: [
SpaceManagementCommunitiesTree(),
const SpaceManagementCommunitiesTree(),
Expanded(
child: BlocBuilder<CommunitiesTreeSelectionBloc,
CommunitiesTreeSelectionState>(
buildWhen: (previous, current) =>
previous.selectedCommunity != current.selectedCommunity,
builder: (context, state) => Visibility(
visible: state.selectedCommunity == null,
replacement: const SpaceManagementCommunityStructure(),
child: const SpaceManagementTemplatesView(),
),
),
),
],
);
}

View File

@ -0,0 +1,27 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:syncrow_web/pages/space_management_v2/main_module/widgets/community_structure_canvas.dart';
import 'package:syncrow_web/pages/space_management_v2/main_module/widgets/create_space_button.dart';
import 'package:syncrow_web/pages/space_management_v2/modules/communities/presentation/communities_tree_selection_bloc/communities_tree_selection_bloc.dart';
class SpaceManagementCommunityStructure extends StatelessWidget {
const SpaceManagementCommunityStructure({super.key});
@override
Widget build(BuildContext context) {
final selectionBloc = context.watch<CommunitiesTreeSelectionBloc>().state;
final selectedCommunity = selectionBloc.selectedCommunity;
final selectedSpace = selectionBloc.selectedSpace;
const spacer = Spacer(flex: 10);
return Visibility(
visible: selectedCommunity!.spaces.isNotEmpty,
replacement: const Row(
children: [spacer, Expanded(child: CreateSpaceButton()), spacer],
),
child: CommunityStructureCanvas(
community: selectedCommunity,
selectedSpace: selectedSpace,
),
);
}
}

View File

@ -0,0 +1,50 @@
import 'package:flutter/material.dart';
import 'package:syncrow_web/pages/space_management_v2/main_module/shared/helpers/space_management_community_dialog_helper.dart';
import 'package:syncrow_web/pages/space_management_v2/main_module/widgets/community_template_cell.dart';
import 'package:syncrow_web/utils/color_manager.dart';
class SpaceManagementTemplatesView extends StatelessWidget {
const SpaceManagementTemplatesView({super.key});
@override
Widget build(BuildContext context) {
return ColoredBox(
color: ColorsManager.whiteColors,
child: GridView.builder(
padding: const EdgeInsets.symmetric(horizontal: 40, vertical: 20),
gridDelegate: const SliverGridDelegateWithMaxCrossAxisExtent(
maxCrossAxisExtent: 400,
mainAxisSpacing: 10,
crossAxisSpacing: 10,
childAspectRatio: 2.0,
),
itemCount: _gridItems(context).length,
itemBuilder: (context, index) {
final model = _gridItems(context)[index];
return CommunityTemplateCell(
onTap: model.onTap,
title: model.title,
);
},
),
);
}
List<_CommunityTemplateModel> _gridItems(BuildContext context) {
return [
_CommunityTemplateModel(
title: const Text('Blank'),
onTap: () => SpaceManagementCommunityDialogHelper.showCreateDialog(context),
),
];
}
}
class _CommunityTemplateModel {
final Widget title;
final void Function() onTap;
_CommunityTemplateModel({
required this.title,
required this.onTap,
});
}

View File

@ -32,7 +32,7 @@ class CommunitiesTreeSelectionBloc
) {
emit(
CommunitiesTreeSelectionState(
selectedCommunity: null,
selectedCommunity: event.community,
selectedSpace: event.space,
),
);

View File

@ -8,7 +8,7 @@ sealed class CommunitiesTreeSelectionEvent extends Equatable {
}
final class SelectCommunityEvent extends CommunitiesTreeSelectionEvent {
final CommunityModel? community;
final CommunityModel community;
const SelectCommunityEvent({required this.community});
@override
@ -17,8 +17,9 @@ final class SelectCommunityEvent extends CommunitiesTreeSelectionEvent {
final class SelectSpaceEvent extends CommunitiesTreeSelectionEvent {
final SpaceModel? space;
final CommunityModel community;
const SelectSpaceEvent({required this.space});
const SelectSpaceEvent({required this.space, required this.community});
@override
List<Object?> get props => [space];

View File

@ -30,7 +30,7 @@ class SpaceManagementCommunitiesTreeSpaceTile extends StatelessWidget {
initiallyExpanded: spaceIsExpanded,
onExpansionChanged: (expanded) {},
onItemSelected: () => context.read<CommunitiesTreeSelectionBloc>().add(
SelectSpaceEvent(space: space),
SelectSpaceEvent(community: community, space: space),
),
children: space.children
.map(

View File

@ -1,9 +1,8 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:syncrow_web/pages/space_management_v2/modules/communities/presentation/bloc/communities_bloc.dart';
import 'package:syncrow_web/pages/space_management_v2/main_module/shared/helpers/space_management_community_dialog_helper.dart';
import 'package:syncrow_web/pages/space_management_v2/modules/communities/presentation/communities_tree_selection_bloc/communities_tree_selection_bloc.dart';
import 'package:syncrow_web/pages/space_management_v2/modules/communities/presentation/widgets/space_management_sidebar_add_community_button.dart';
import 'package:syncrow_web/pages/space_management_v2/modules/create_community/presentation/create_community_dialog.dart';
import 'package:syncrow_web/utils/color_manager.dart';
import 'package:syncrow_web/utils/extension/build_context_x.dart';
import 'package:syncrow_web/utils/style.dart';
@ -41,7 +40,7 @@ class SpaceManagementSidebarHeader extends StatelessWidget {
if (isSelected) {
_clearSelection(context);
} else {
_showCreateCommunityDialog(context);
SpaceManagementCommunityDialogHelper.showCreateDialog(context);
}
}
@ -50,19 +49,4 @@ class SpaceManagementSidebarHeader extends StatelessWidget {
const ClearCommunitiesTreeSelectionEvent(),
);
}
void _showCreateCommunityDialog(BuildContext context) => showDialog<void>(
context: context,
builder: (_) => CreateCommunityDialog(
title: const Text('Community Name'),
onCreateCommunity: (community) {
context.read<CommunitiesBloc>().add(
InsertCommunity(community),
);
context.read<CommunitiesTreeSelectionBloc>().add(
SelectCommunityEvent(community: community),
);
},
),
);
}

View File

@ -53,7 +53,7 @@ class RemoteCreateCommunityService implements CreateCommunityService {
return _defaultErrorMessage;
}
final error = body['error'] as Map<String, dynamic>?;
final errorMessage = error?['error'] as String? ?? '';
final errorMessage = error?['message'] as String? ?? '';
return errorMessage;
}

View File

@ -41,11 +41,8 @@ class CreateCommunityDialog extends StatelessWidget {
);
onCreateCommunity.call(community);
break;
case CreateCommunityFailure(:final message):
case CreateCommunityFailure():
Navigator.of(context).pop();
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text(message)),
);
break;
default:
break;

View File

@ -0,0 +1,11 @@
import 'package:flutter/material.dart';
import 'package:syncrow_web/pages/space_management_v2/modules/space_details/presentation/widgets/space_details_dialog.dart';
abstract final class SpaceDetailsDialogHelper {
static void showCreate(BuildContext context) {
showDialog<void>(
context: context,
builder: (context) => const SpaceDetailsDialog(),
);
}
}

View File

@ -0,0 +1,12 @@
import 'package:flutter/material.dart';
class SpaceDetailsDialog extends StatelessWidget {
const SpaceDetailsDialog({super.key});
@override
Widget build(BuildContext context) {
return const Dialog(
child: Text('Create Space'),
);
}
}

View File

@ -0,0 +1,120 @@
import 'package:flutter/material.dart';
import 'package:syncrow_web/pages/visitor_password/bloc/visitor_password_bloc.dart';
class AccessTypeRadioGroup extends StatelessWidget {
final String? selectedType;
final String? accessTypeSelected;
final Function(String) onTypeSelected;
final VisitorPasswordBloc visitorBloc;
const AccessTypeRadioGroup({
super.key,
required this.selectedType,
required this.accessTypeSelected,
required this.onTypeSelected,
required this.visitorBloc,
});
@override
Widget build(BuildContext context) {
Size size = MediaQuery.of(context).size;
final text = Theme.of(context)
.textTheme
.bodySmall!
.copyWith(color: Colors.black, fontSize: 13);
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Text(
'* ',
style: Theme.of(context)
.textTheme
.bodyMedium!
.copyWith(color: Colors.red),
),
Text('Access Type', style: text),
],
),
const SizedBox(height: 8),
if (size.width < 800)
Column(
children: [
_buildRadioTile(
context,
'Online Password',
selectedType ?? accessTypeSelected,
onTypeSelected,
),
const SizedBox(height: 8),
_buildRadioTile(
context,
'Offline Password',
selectedType ?? accessTypeSelected,
onTypeSelected,
),
],
)
else
Row(
children: [
Expanded(
flex: 2,
child: Row(
children: [
_buildRadioTile(
context,
'Online Password',
selectedType ?? accessTypeSelected,
onTypeSelected,
width: size.width * 0.12,
),
_buildRadioTile(
context,
'Offline Password',
selectedType ?? accessTypeSelected,
onTypeSelected,
width: size.width * 0.12,
),
],
),
),
const Spacer(flex: 2),
],
),
],
);
}
Widget _buildRadioTile(
BuildContext context,
String value,
String? groupValue,
Function(String) onChanged, {
double? width,
}) {
return SizedBox(
width: width,
child: RadioListTile<String>(
contentPadding: EdgeInsets.zero,
title: Text(value,
style: Theme.of(context).textTheme.bodySmall!.copyWith(
color: Colors.black,
fontSize: 13,
)),
value: value,
groupValue: groupValue,
onChanged: (value) {
if (value != null) {
onChanged(value);
if (value == 'Dynamic Password') {
visitorBloc.usageFrequencySelected = '';
}
}
},
),
);
}
}

View File

@ -0,0 +1,73 @@
import 'package:flutter/material.dart';
import 'package:syncrow_web/pages/common/text_field/custom_web_textfield.dart';
class NameAndEmailFields extends StatelessWidget {
final TextEditingController nameController;
final TextEditingController emailController;
final String? Function(String?)? nameValidator;
final String? Function(String?)? emailValidator;
const NameAndEmailFields({
super.key,
required this.nameController,
required this.emailController,
required this.nameValidator,
required this.emailValidator,
});
@override
Widget build(BuildContext context) {
Size size = MediaQuery.of(context).size;
return Container(
width: size.width,
child: size.width < 800
? Column(
children: [
CustomWebTextField(
validator: nameValidator,
controller: nameController,
isRequired: true,
textFieldName: 'Name',
description: '',
),
const SizedBox(height: 15),
CustomWebTextField(
validator: emailValidator,
controller: emailController,
isRequired: true,
textFieldName: 'Email Address',
description:
'The password will be sent to the visitors email address.',
),
],
)
: Row(
children: [
Expanded(
flex: 2,
child: CustomWebTextField(
validator: nameValidator,
controller: nameController,
isRequired: true,
textFieldName: 'Name',
description: '',
),
),
const Spacer(),
Expanded(
flex: 2,
child: CustomWebTextField(
validator: emailValidator,
controller: emailController,
isRequired: true,
textFieldName: 'Email Address',
description:
'The password will be sent to the visitors email address.',
),
),
const Spacer(),
],
),
);
}
}

View File

@ -0,0 +1,91 @@
import 'package:flutter/material.dart';
class UsageFrequencyRadioGroup extends StatelessWidget {
final String? selectedFrequency;
final String? usageFrequencySelected;
final Function(String) onFrequencySelected;
const UsageFrequencyRadioGroup({
super.key,
required this.selectedFrequency,
required this.usageFrequencySelected,
required this.onFrequencySelected,
});
@override
Widget build(BuildContext context) {
Size size = MediaQuery.of(context).size;
final text = Theme.of(context)
.textTheme
.bodySmall!
.copyWith(color: Colors.black, fontSize: 13);
return size.width < 600
? Column(
children: [
_buildRadioTile(
context,
'One-Time',
selectedFrequency ?? usageFrequencySelected,
onFrequencySelected,
text: text,
fullWidth: true,
),
const SizedBox(height: 8),
_buildRadioTile(
context,
'Periodic',
selectedFrequency ?? usageFrequencySelected,
onFrequencySelected,
text: text,
fullWidth: true,
),
],
)
: Row(
children: [
_buildRadioTile(
context,
'One-Time',
selectedFrequency ?? usageFrequencySelected,
onFrequencySelected,
width: size.width * 0.12,
text: text,
),
_buildRadioTile(
context,
'Periodic',
selectedFrequency ?? usageFrequencySelected,
onFrequencySelected,
width: size.width * 0.12,
text: text,
),
],
);
}
Widget _buildRadioTile(
BuildContext context,
String value,
String? groupValue,
Function(String) onChanged, {
double? width,
required TextStyle text,
bool fullWidth = false,
}) {
return SizedBox(
width: fullWidth ? double.infinity : width,
child: RadioListTile<String>(
contentPadding: EdgeInsets.zero,
title: Text(value, style: text),
value: value,
groupValue: groupValue,
onChanged: (String? value) {
if (value != null) {
onChanged(value);
}
},
),
);
}
}

View File

@ -9,8 +9,11 @@ import 'package:syncrow_web/pages/common/text_field/custom_web_textfield.dart';
import 'package:syncrow_web/pages/visitor_password/bloc/visitor_password_bloc.dart';
import 'package:syncrow_web/pages/visitor_password/bloc/visitor_password_event.dart';
import 'package:syncrow_web/pages/visitor_password/bloc/visitor_password_state.dart';
import 'package:syncrow_web/pages/visitor_password/view/access_type_radio_group.dart';
import 'package:syncrow_web/pages/visitor_password/view/add_device_dialog.dart';
import 'package:syncrow_web/pages/visitor_password/view/repeat_widget.dart';
import 'package:syncrow_web/pages/visitor_password/view/responsive_fields_row.dart';
import 'package:syncrow_web/pages/visitor_password/view/usage_frequency_radio_group.dart';
import 'package:syncrow_web/utils/color_manager.dart';
import 'package:syncrow_web/utils/constants/assets.dart';
import 'package:syncrow_web/utils/style.dart';
@ -21,7 +24,10 @@ class VisitorPasswordDialog extends StatelessWidget {
@override
Widget build(BuildContext context) {
Size size = MediaQuery.of(context).size;
var text = Theme.of(context).textTheme.bodySmall!.copyWith(color: Colors.black, fontSize: 13);
var text = Theme.of(context)
.textTheme
.bodySmall!
.copyWith(color: Colors.black, fontSize: 13);
return BlocProvider(
create: (context) => VisitorPasswordBloc(),
child: BlocListener<VisitorPasswordBloc, VisitorPasswordState>(
@ -35,7 +41,8 @@ class VisitorPasswordDialog extends StatelessWidget {
title: 'Sent Successfully',
widgeta: Column(
children: [
if (visitorBloc.passwordStatus!.failedOperations.isNotEmpty)
if (visitorBloc
.passwordStatus!.failedOperations.isNotEmpty)
Column(
children: [
const Text('Failed Devices'),
@ -45,7 +52,8 @@ class VisitorPasswordDialog extends StatelessWidget {
child: ListView.builder(
scrollDirection: Axis.horizontal,
shrinkWrap: true,
itemCount: visitorBloc.passwordStatus!.failedOperations.length,
itemCount: visitorBloc
.passwordStatus!.failedOperations.length,
itemBuilder: (context, index) {
return Container(
margin: EdgeInsets.all(5),
@ -53,14 +61,17 @@ class VisitorPasswordDialog extends StatelessWidget {
height: 45,
child: Center(
child: Text(visitorBloc
.passwordStatus!.failedOperations[index].deviceUuid)),
.passwordStatus!
.failedOperations[index]
.deviceUuid)),
);
},
),
),
],
),
if (visitorBloc.passwordStatus!.successOperations.isNotEmpty)
if (visitorBloc
.passwordStatus!.successOperations.isNotEmpty)
Column(
children: [
const Text('Success Devices'),
@ -70,15 +81,18 @@ class VisitorPasswordDialog extends StatelessWidget {
child: ListView.builder(
scrollDirection: Axis.horizontal,
shrinkWrap: true,
itemCount: visitorBloc.passwordStatus!.successOperations.length,
itemCount: visitorBloc
.passwordStatus!.successOperations.length,
itemBuilder: (context, index) {
return Container(
margin: EdgeInsets.all(5),
decoration: containerDecoration,
height: 45,
child: Center(
child: Text(visitorBloc.passwordStatus!
.successOperations[index].deviceUuid)),
child: Text(visitorBloc
.passwordStatus!
.successOperations[index]
.deviceUuid)),
);
},
),
@ -88,8 +102,7 @@ class VisitorPasswordDialog extends StatelessWidget {
],
))
.then((v) {
Navigator.of(context).pop(true);
Navigator.of(context).pop(v);
});
} else if (state is FailedState) {
visitorBloc.stateDialog(
@ -102,15 +115,16 @@ class VisitorPasswordDialog extends StatelessWidget {
child: BlocBuilder<VisitorPasswordBloc, VisitorPasswordState>(
builder: (BuildContext context, VisitorPasswordState state) {
final visitorBloc = BlocProvider.of<VisitorPasswordBloc>(context);
bool isRepeat = state is IsRepeatState ? state.repeat : visitorBloc.repeat;
bool isRepeat =
state is IsRepeatState ? state.repeat : visitorBloc.repeat;
return AlertDialog(
backgroundColor: Colors.white,
title: Text(
'Create visitor password',
style: Theme.of(context)
.textTheme
.headlineLarge!
.copyWith(fontWeight: FontWeight.w400, fontSize: 24, color: Colors.black),
style: Theme.of(context).textTheme.headlineLarge!.copyWith(
fontWeight: FontWeight.w400,
fontSize: 24,
color: Colors.black),
),
content: state is LoadingInitialState
? const Center(child: CircularProgressIndicator())
@ -121,34 +135,11 @@ class VisitorPasswordDialog extends StatelessWidget {
padding: const EdgeInsets.all(5.0),
child: ListBody(
children: <Widget>[
Container(
child: Row(
children: [
Expanded(
flex: 2,
child: CustomWebTextField(
validator: visitorBloc.validate,
controller: visitorBloc.userNameController,
isRequired: true,
textFieldName: 'Name',
description: '',
),
),
const Spacer(),
Expanded(
flex: 2,
child: CustomWebTextField(
validator: visitorBloc.validateEmail,
controller: visitorBloc.emailController,
isRequired: true,
textFieldName: 'Email Address',
description:
'The password will be sent to the visitors email address.',
),
),
const Spacer(),
],
),
NameAndEmailFields(
nameController: visitorBloc.userNameController,
emailController: visitorBloc.emailController,
nameValidator: visitorBloc.validate,
emailValidator: visitorBloc.validateEmail,
),
const SizedBox(
height: 15,
@ -156,107 +147,43 @@ class VisitorPasswordDialog extends StatelessWidget {
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Text(
'* ',
style: Theme.of(context)
.textTheme
.bodyMedium!
.copyWith(color: Colors.red),
),
Text('Access Type', style: text),
],
AccessTypeRadioGroup(
selectedType: state is PasswordTypeSelected
? state.selectedType
: null,
accessTypeSelected:
visitorBloc.accessTypeSelected,
onTypeSelected: (value) {
context
.read<VisitorPasswordBloc>()
.add(SelectPasswordType(value));
},
visitorBloc: visitorBloc,
),
Row(
children: <Widget>[
Expanded(
flex: 2,
child: Row(
children: [
SizedBox(
width: size.width * 0.12,
child: RadioListTile<String>(
contentPadding: EdgeInsets.zero,
title: Text(
'Online Password',
style: text,
),
value: 'Online Password',
groupValue: (state is PasswordTypeSelected)
? state.selectedType
: visitorBloc.accessTypeSelected,
onChanged: (String? value) {
if (value != null) {
context
.read<VisitorPasswordBloc>()
.add(SelectPasswordType(value));
}
},
),
),
SizedBox(
width: size.width * 0.12,
child: RadioListTile<String>(
contentPadding: EdgeInsets.zero,
title: Text('Offline Password', style: text),
value: 'Offline Password',
groupValue: (state is PasswordTypeSelected)
? state.selectedType
: visitorBloc.accessTypeSelected,
onChanged: (String? value) {
if (value != null) {
context
.read<VisitorPasswordBloc>()
.add(SelectPasswordType(value));
}
},
),
),
// SizedBox(
// width: size.width * 0.12,
// child: RadioListTile<String>(
// contentPadding: EdgeInsets.zero,
// title: Text(
// 'Dynamic Password',
// style: text,
// ),
// value: 'Dynamic Password',
// groupValue: (state is PasswordTypeSelected)
// ? state.selectedType
// : visitorBloc.accessTypeSelected,
// onChanged: (String? value) {
// if (value != null) {
// context
// .read<VisitorPasswordBloc>()
// .add(SelectPasswordType(value));
// visitorBloc.usageFrequencySelected = '';
// }
// },
// ),
// ),
],
)),
const Spacer(
flex: 2,
),
],
),
if (visitorBloc.accessTypeSelected == 'Online Password')
if (visitorBloc.accessTypeSelected ==
'Online Password')
Text(
'Only currently online devices can be selected. It is recommended to use when the device network is stable, and the system randomly generates a digital password',
style: Theme.of(context).textTheme.bodySmall!.copyWith(
fontWeight: FontWeight.w400,
color: ColorsManager.grayColor,
fontSize: 9),
style: Theme.of(context)
.textTheme
.bodySmall!
.copyWith(
fontWeight: FontWeight.w400,
color: ColorsManager.grayColor,
fontSize: 9),
),
if (visitorBloc.accessTypeSelected == 'Offline Password')
if (visitorBloc.accessTypeSelected ==
'Offline Password')
Text(
'Unaffected by the online status of the device, you can select online or offline device, and the system randomly generates a digital password',
style: Theme.of(context).textTheme.bodySmall!.copyWith(
fontWeight: FontWeight.w400,
color: ColorsManager.grayColor,
fontSize: 9),
style: Theme.of(context)
.textTheme
.bodySmall!
.copyWith(
fontWeight: FontWeight.w400,
color: ColorsManager.grayColor,
fontSize: 9),
),
// if (visitorBloc.accessTypeSelected == 'Dynamic Password')
// Text(
@ -271,143 +198,170 @@ class VisitorPasswordDialog extends StatelessWidget {
)
],
),
visitorBloc.accessTypeSelected == 'Dynamic Password'
? const SizedBox()
: Column(
crossAxisAlignment: CrossAxisAlignment.start,
if (visitorBloc.accessTypeSelected ==
'Dynamic Password')
const SizedBox()
else
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Row(
children: [
Text(
'* ',
style: Theme.of(context)
.textTheme
.bodyMedium!
.copyWith(color: Colors.red),
),
Text(
'Usage Frequency',
style: text,
),
],
Text(
'* ',
style: Theme.of(context)
.textTheme
.bodyMedium!
.copyWith(color: Colors.red),
),
Row(
children: <Widget>[
SizedBox(
width: size.width * 0.12,
child: RadioListTile<String>(
contentPadding: EdgeInsets.zero,
title: Text(
'One-Time',
style: text,
),
value: 'One-Time',
groupValue: (state is UsageFrequencySelected)
? state.selectedFrequency
: visitorBloc.usageFrequencySelected,
onChanged: (String? value) {
if (value != null) {
context
.read<VisitorPasswordBloc>()
.add(SelectUsageFrequency(value));
}
},
),
),
SizedBox(
width: size.width * 0.12,
child: RadioListTile<String>(
contentPadding: EdgeInsets.zero,
title: Text('Periodic', style: text),
value: 'Periodic',
groupValue: (state is UsageFrequencySelected)
? state.selectedFrequency
: visitorBloc.usageFrequencySelected,
onChanged: (String? value) {
if (value != null) {
context.read<VisitorPasswordBloc>()
.add(SelectUsageFrequency(value));
}
},
),
),
],
Text(
'Usage Frequency',
style: text,
),
//One-Time
if (visitorBloc.usageFrequencySelected == 'One-Time' &&
visitorBloc.accessTypeSelected == 'Online Password')
Text(
'Within the validity period, each device can be unlocked only once.',
style: Theme.of(context).textTheme.bodySmall!.copyWith(
color: ColorsManager.grayColor, fontSize: 9),
),
if (visitorBloc.usageFrequencySelected == 'One-Time' &&
visitorBloc.accessTypeSelected == 'Offline Password')
Text(
'Within the validity period, each device can be unlocked only once, and the maximum validity period is 6 hours',
style: Theme.of(context).textTheme.bodySmall!.copyWith(
color: ColorsManager.grayColor, fontSize: 9),
),
// Periodic
if (visitorBloc.usageFrequencySelected == 'Periodic' &&
visitorBloc.accessTypeSelected == 'Offline Password')
Text(
'Within the validity period, there is no limit to the number of times each device can be unlocked, and it should be used at least once within 24 hours after the entry into force.',
style: Theme.of(context).textTheme.bodySmall!.copyWith(
color: ColorsManager.grayColor, fontSize: 9),
),
if (visitorBloc.usageFrequencySelected == 'Periodic' &&
visitorBloc.accessTypeSelected == 'Online Password')
Text(
'Within the validity period, there is no limit to the number of times each device can be unlocked.',
style: Theme.of(context).textTheme.bodySmall!.copyWith(
color: ColorsManager.grayColor, fontSize: 9),
),
],
),
UsageFrequencyRadioGroup(
selectedFrequency:
state is UsageFrequencySelected
? state.selectedFrequency
: null,
usageFrequencySelected:
visitorBloc.usageFrequencySelected,
onFrequencySelected: (value) {
context
.read<VisitorPasswordBloc>()
.add(SelectUsageFrequency(value));
},
),
//One-Time
if (visitorBloc.usageFrequencySelected ==
'One-Time' &&
visitorBloc.accessTypeSelected ==
'Online Password')
Text(
'Within the validity period, each device can be unlocked only once.',
style: Theme.of(context)
.textTheme
.bodySmall!
.copyWith(
color: ColorsManager.grayColor,
fontSize: 9),
),
if (visitorBloc.usageFrequencySelected ==
'One-Time' &&
visitorBloc.accessTypeSelected ==
'Offline Password')
Text(
'Within the validity period, each device can be unlocked only once, and the maximum validity period is 6 hours',
style: Theme.of(context)
.textTheme
.bodySmall!
.copyWith(
color: ColorsManager.grayColor,
fontSize: 9),
),
// Periodic
if (visitorBloc.usageFrequencySelected ==
'Periodic' &&
visitorBloc.accessTypeSelected ==
'Offline Password')
Text(
'Within the validity period, there is no limit to the number of times each device can be unlocked, and it should be used at least once within 24 hours after the entry into force.',
style: Theme.of(context)
.textTheme
.bodySmall!
.copyWith(
color: ColorsManager.grayColor,
fontSize: 9),
),
if (visitorBloc.usageFrequencySelected ==
'Periodic' &&
visitorBloc.accessTypeSelected ==
'Online Password')
Text(
'Within the validity period, there is no limit to the number of times each device can be unlocked.',
style: Theme.of(context)
.textTheme
.bodySmall!
.copyWith(
color: ColorsManager.grayColor,
fontSize: 9),
),
],
),
const SizedBox(
height: 20,
),
if ((visitorBloc.usageFrequencySelected != 'One-Time' ||
visitorBloc.accessTypeSelected != 'Offline Password') &&
if ((visitorBloc.usageFrequencySelected !=
'One-Time' ||
visitorBloc.accessTypeSelected !=
'Offline Password') &&
(visitorBloc.usageFrequencySelected != ''))
DateTimeWebWidget(
isRequired: true,
title: 'Access Period',
size: size,
endTime: () {
if (visitorBloc.usageFrequencySelected == 'Periodic' &&
visitorBloc.accessTypeSelected == 'Offline Password') {
visitorBloc.add(SelectTimeEvent(context: context, isEffective: false));
if (visitorBloc.usageFrequencySelected ==
'Periodic' &&
visitorBloc.accessTypeSelected ==
'Offline Password') {
visitorBloc.add(SelectTimeEvent(
context: context,
isEffective: false));
} else {
visitorBloc.add(SelectTimeVisitorPassword(context: context, isStart: false, isRepeat: false));
visitorBloc.add(
SelectTimeVisitorPassword(
context: context,
isStart: false,
isRepeat: false));
}
},
startTime: () {
if (visitorBloc.usageFrequencySelected == 'Periodic' &&
visitorBloc.accessTypeSelected == 'Offline Password') {
visitorBloc.add(
SelectTimeEvent(context: context, isEffective: true));
if (visitorBloc.usageFrequencySelected ==
'Periodic' &&
visitorBloc.accessTypeSelected ==
'Offline Password') {
visitorBloc.add(SelectTimeEvent(
context: context,
isEffective: true));
} else {
visitorBloc.add(SelectTimeVisitorPassword(
context: context, isStart: true, isRepeat: false));
visitorBloc.add(
SelectTimeVisitorPassword(
context: context,
isStart: true,
isRepeat: false));
}
},
firstString: (visitorBloc.usageFrequencySelected ==
'Periodic' && visitorBloc.accessTypeSelected == 'Offline Password')
firstString: (visitorBloc
.usageFrequencySelected ==
'Periodic' &&
visitorBloc.accessTypeSelected ==
'Offline Password')
? visitorBloc.effectiveTime
: visitorBloc.startTimeAccess.toString(),
secondString: (visitorBloc.usageFrequencySelected ==
'Periodic' && visitorBloc.accessTypeSelected == 'Offline Password')
: visitorBloc.startTimeAccess
.toString(),
secondString: (visitorBloc
.usageFrequencySelected ==
'Periodic' &&
visitorBloc.accessTypeSelected ==
'Offline Password')
? visitorBloc.expirationTime
: visitorBloc.endTimeAccess.toString(),
icon: Assets.calendarIcon),
const SizedBox(height: 10,),
Text(visitorBloc.accessPeriodValidate,
style: Theme.of(context).textTheme.bodyMedium!.copyWith(color: ColorsManager.red),),
const SizedBox(
height: 10,
),
Text(
visitorBloc.accessPeriodValidate,
style: Theme.of(context)
.textTheme
.bodyMedium!
.copyWith(color: ColorsManager.red),
),
const SizedBox(
height: 20,
),
@ -431,16 +385,21 @@ class VisitorPasswordDialog extends StatelessWidget {
),
Text(
'Within the validity period, each device can be unlocked only once.',
style: Theme.of(context).textTheme.bodySmall!.copyWith(
fontWeight: FontWeight.w400,
color: ColorsManager.grayColor,
fontSize: 9),
style: Theme.of(context)
.textTheme
.bodySmall!
.copyWith(
fontWeight: FontWeight.w400,
color: ColorsManager.grayColor,
fontSize: 9),
),
const SizedBox(
height: 20,
),
if (visitorBloc.usageFrequencySelected == 'Periodic' &&
visitorBloc.accessTypeSelected == 'Online Password')
if (visitorBloc.usageFrequencySelected ==
'Periodic' &&
visitorBloc.accessTypeSelected ==
'Online Password')
SizedBox(
width: 100,
child: Column(
@ -451,7 +410,8 @@ class VisitorPasswordDialog extends StatelessWidget {
child: CupertinoSwitch(
value: visitorBloc.repeat,
onChanged: (value) {
visitorBloc.add(ToggleRepeatEvent());
visitorBloc
.add(ToggleRepeatEvent());
},
applyTheme: true,
),
@ -459,12 +419,16 @@ class VisitorPasswordDialog extends StatelessWidget {
],
),
),
if (visitorBloc.usageFrequencySelected == 'Periodic' &&
visitorBloc.accessTypeSelected == 'Online Password')
isRepeat ? const RepeatWidget() : const SizedBox(),
if (visitorBloc.usageFrequencySelected ==
'Periodic' &&
visitorBloc.accessTypeSelected ==
'Online Password')
isRepeat
? const RepeatWidget()
: const SizedBox(),
Container(
decoration: containerDecoration,
width: size.width / 9,
width: size.width / 6,
child: DefaultButton(
onPressed: () {
showDialog(
@ -472,22 +436,28 @@ class VisitorPasswordDialog extends StatelessWidget {
barrierDismissible: false,
builder: (BuildContext context) {
return AddDeviceDialog(
selectedDeviceIds: visitorBloc.selectedDevices,
selectedDeviceIds:
visitorBloc.selectedDevices,
);
},
).then((listDevice) {
if (listDevice != null) {
visitorBloc.selectedDevices = listDevice;
visitorBloc.selectedDevices =
listDevice;
}
});
},
borderRadius: 8,
child: Text(
'+ Add Device',
style: Theme.of(context).textTheme.bodySmall!.copyWith(
fontWeight: FontWeight.w400,
color: ColorsManager.whiteColors,
fontSize: 12),
style: Theme.of(context)
.textTheme
.bodySmall!
.copyWith(
fontWeight: FontWeight.w400,
color:
ColorsManager.whiteColors,
fontSize: 12),
),
),
),
@ -506,7 +476,7 @@ class VisitorPasswordDialog extends StatelessWidget {
child: DefaultButton(
borderRadius: 8,
onPressed: () {
Navigator.of(context).pop(true);
Navigator.of(context).pop(null);
},
backgroundColor: Colors.white,
child: Text(
@ -525,30 +495,37 @@ class VisitorPasswordDialog extends StatelessWidget {
onPressed: () {
if (visitorBloc.forgetFormKey.currentState!.validate()) {
if (visitorBloc.selectedDevices.isNotEmpty) {
if (visitorBloc.usageFrequencySelected == 'One-Time' &&
visitorBloc.accessTypeSelected == 'Offline Password') {
if (visitorBloc.usageFrequencySelected ==
'One-Time' &&
visitorBloc.accessTypeSelected ==
'Offline Password') {
setPasswordFunction(context, size, visitorBloc);
} else if (visitorBloc.usageFrequencySelected == 'Periodic' &&
visitorBloc.accessTypeSelected == 'Offline Password') {
} else if (visitorBloc.usageFrequencySelected ==
'Periodic' &&
visitorBloc.accessTypeSelected ==
'Offline Password') {
if (visitorBloc.expirationTime != 'End Time' &&
visitorBloc.effectiveTime != 'Start Time' ) {
visitorBloc.effectiveTime != 'Start Time') {
setPasswordFunction(context, size, visitorBloc);
}else{
} else {
visitorBloc.stateDialog(
context: context,
message: 'Please select Access Period to continue',
message:
'Please select Access Period to continue',
title: 'Access Period');
}
} else if(
visitorBloc.endTimeAccess.toString()!='End Time'
&&visitorBloc.startTimeAccess.toString()!='Start Time') {
} else if (visitorBloc.endTimeAccess.toString() !=
'End Time' &&
visitorBloc.startTimeAccess.toString() !=
'Start Time') {
if (visitorBloc.effectiveTimeTimeStamp != null &&
visitorBloc.expirationTimeTimeStamp != null) {
if (isRepeat == true) {
if (visitorBloc.expirationTime != 'End Time' &&
visitorBloc.effectiveTime != 'Start Time' &&
visitorBloc.selectedDays.isNotEmpty) {
setPasswordFunction(context, size, visitorBloc);
setPasswordFunction(
context, size, visitorBloc);
} else {
visitorBloc.stateDialog(
context: context,
@ -562,14 +539,16 @@ class VisitorPasswordDialog extends StatelessWidget {
} else {
visitorBloc.stateDialog(
context: context,
message: 'Please select Access Period to continue',
message:
'Please select Access Period to continue',
title: 'Access Period');
}
}else{
visitorBloc.stateDialog(
context: context,
message: 'Please select Access Period to continue',
title: 'Access Period');
} else {
visitorBloc.stateDialog(
context: context,
message:
'Please select Access Period to continue',
title: 'Access Period');
}
} else {
visitorBloc.stateDialog(
@ -615,7 +594,8 @@ class VisitorPasswordDialog extends StatelessWidget {
content: SizedBox(
height: size.height * 0.25,
child: Center(
child: CircularProgressIndicator(), // Display a loading spinner
child:
CircularProgressIndicator(), // Display a loading spinner
),
),
);
@ -639,7 +619,10 @@ class VisitorPasswordDialog extends StatelessWidget {
),
Text(
'Set Password',
style: Theme.of(context).textTheme.headlineLarge!.copyWith(
style: Theme.of(context)
.textTheme
.headlineLarge!
.copyWith(
fontSize: 30,
fontWeight: FontWeight.w400,
color: Colors.black,
@ -668,7 +651,7 @@ class VisitorPasswordDialog extends StatelessWidget {
child: DefaultButton(
borderRadius: 8,
onPressed: () {
Navigator.of(context).pop();
Navigator.of(context).pop(null);
},
backgroundColor: Colors.white,
child: Text(
@ -689,37 +672,45 @@ class VisitorPasswordDialog extends StatelessWidget {
onPressed: () {
Navigator.pop(context);
if (visitorBloc.usageFrequencySelected == 'One-Time' &&
visitorBloc.accessTypeSelected == 'Online Password') {
visitorBloc.accessTypeSelected ==
'Online Password') {
visitorBloc.add(OnlineOneTimePasswordEvent(
context: context,
passwordName: visitorBloc.userNameController.text,
email: visitorBloc.emailController.text,
));
}
else if (visitorBloc.usageFrequencySelected == 'Periodic' &&
visitorBloc.accessTypeSelected == 'Online Password') {
} else if (visitorBloc.usageFrequencySelected ==
'Periodic' &&
visitorBloc.accessTypeSelected ==
'Online Password') {
visitorBloc.add(OnlineMultipleTimePasswordEvent(
passwordName: visitorBloc.userNameController.text,
email: visitorBloc.emailController.text,
effectiveTime: visitorBloc.effectiveTimeTimeStamp.toString(),
invalidTime: visitorBloc.expirationTimeTimeStamp.toString(),
effectiveTime:
visitorBloc.effectiveTimeTimeStamp.toString(),
invalidTime:
visitorBloc.expirationTimeTimeStamp.toString(),
));
}
else if (visitorBloc.usageFrequencySelected == 'One-Time' &&
visitorBloc.accessTypeSelected == 'Offline Password') {
} else if (visitorBloc.usageFrequencySelected ==
'One-Time' &&
visitorBloc.accessTypeSelected ==
'Offline Password') {
visitorBloc.add(OfflineOneTimePasswordEvent(
context: context,
passwordName: visitorBloc.userNameController.text,
email: visitorBloc.emailController.text,
));
}
else if (visitorBloc.usageFrequencySelected == 'Periodic' &&
visitorBloc.accessTypeSelected == 'Offline Password') {
} else if (visitorBloc.usageFrequencySelected ==
'Periodic' &&
visitorBloc.accessTypeSelected ==
'Offline Password') {
visitorBloc.add(OfflineMultipleTimePasswordEvent(
passwordName: visitorBloc.userNameController.text,
email: visitorBloc.emailController.text,
effectiveTime: visitorBloc.effectiveTimeTimeStamp.toString(),
invalidTime: visitorBloc.expirationTimeTimeStamp.toString(),
effectiveTime:
visitorBloc.effectiveTimeTimeStamp.toString(),
invalidTime:
visitorBloc.expirationTimeTimeStamp.toString(),
));
}
},