Compare commits

..

9 Commits

Author SHA1 Message Date
72af55ef98 Add booking page and related routes, icons, and button widget 2025-07-02 15:50:46 +03:00
b888f516e2 Fix device status display in Control modal to reflect actual status (#337)
<!--
  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 -->
table enhancement

## 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-07-02 11:28:50 +03:00
c1e61ee61d [FE] Community and Space Dialog Redesign in the routine tab (#336)
<!--
  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-1601](https://syncrow.atlassian.net/browse/SP-1601)

## Description

i made the fixes requested by Yazan

## 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-1601]:
https://syncrow.atlassian.net/browse/SP-1601?atlOrigin=eyJpIjoiNWRkNTljNzYxNjVmNDY3MDlhMDU5Y2ZhYzA5YTRkZjUiLCJwIjoiZ2l0aHViLWNvbS1KU1cifQ
2025-07-02 11:21:34 +03:00
7750290be4 Fix device status display in Control modal to reflect actual status 2025-07-02 10:14:54 +03:00
7f26c773a7 [FE] Preferences & Calibration (#332)
<!--
  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-1707](https://syncrow.atlassian.net/browse/SP-1707)

## Description

change the color of completed dialog

## 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-1707]:
https://syncrow.atlassian.net/browse/SP-1707?atlOrigin=eyJpIjoiNWRkNTljNzYxNjVmNDY3MDlhMDU5Y2ZhYzA5YTRkZjUiLCJwIjoiZ2l0aHViLWNvbS1KU1cifQ
2025-07-02 09:05:44 +03:00
1adbae6735 Clarification on Default Value for Start Date in Door Lock Online Tile Limited Password repeat section (#331)
…t with dialog information showing the error and if the init start date
is null fill it with the needed value

<!--
  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-368](https://syncrow.atlassian.net/browse/SP-368)

## Description

now if user change end time into value before start time it prevent it
and give init start date value to start date

## 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-368]:
https://syncrow.atlassian.net/browse/SP-368?atlOrigin=eyJpIjoiNWRkNTljNzYxNjVmNDY3MDlhMDU5Y2ZhYzA5YTRkZjUiLCJwIjoiZ2l0aHViLWNvbS1KU1cifQ
2025-07-02 08:58:47 +03:00
0847cb8a41 fix UI 2025-07-02 08:19:56 +03:00
818bdee745 change calibration completed dialog color 2025-07-01 15:04:50 +03:00
f33b3e8bd2 now if user change end time into value before start time it prevent it with dialog information showing the error and if the init start date is null fill it with the needed value 2025-07-01 11:19:35 +03:00
22 changed files with 1029 additions and 813 deletions

View File

@ -0,0 +1,15 @@
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_9717_7433)">
<path d="M17.1131 10.6766H15.5664C15.7241 11.1083 15.8102 11.5741 15.8102 12.0596V17.9053C15.8102 18.1077 15.775 18.302 15.7109 18.4827H18.2679C19.2231 18.4827 20.0002 17.7056 20.0002 16.7505V13.5637C20.0002 11.9718 18.7051 10.6766 17.1131 10.6766Z" fill="#023DFE" fill-opacity="0.7"/>
<path d="M4.19005 12.0596C4.19005 11.5741 4.27618 11.1083 4.43384 10.6766H2.88712C1.29516 10.6766 0 11.9718 0 13.5637V16.7505C0 17.7057 0.777072 18.4828 1.73227 18.4828H4.28938C4.22528 18.302 4.19005 18.1077 4.19005 17.9053V12.0596Z" fill="#023DFE" fill-opacity="0.7"/>
<path d="M11.7679 9.17249H8.23184C6.63989 9.17249 5.34473 10.4676 5.34473 12.0596V17.9053C5.34473 18.2242 5.60324 18.4827 5.92215 18.4827H14.0776C14.3965 18.4827 14.655 18.2242 14.655 17.9053V12.0596C14.655 10.4676 13.3598 9.17249 11.7679 9.17249Z" fill="#023DFE" fill-opacity="0.7"/>
<path d="M9.99995 1.51721C8.08541 1.51721 6.52783 3.07479 6.52783 4.98937C6.52783 6.288 7.24459 7.42218 8.30311 8.01765C8.80518 8.30008 9.38401 8.46148 9.99995 8.46148C10.6159 8.46148 11.1947 8.30008 11.6968 8.01765C12.7553 7.42218 13.4721 6.28796 13.4721 4.98937C13.4721 3.07483 11.9145 1.51721 9.99995 1.51721Z" fill="#023DFE" fill-opacity="0.7"/>
<path d="M3.90284 4.75354C2.471 4.75354 1.30615 5.91839 1.30615 7.35022C1.30615 8.78206 2.471 9.94691 3.90284 9.94691C4.26604 9.94691 4.6119 9.87168 4.92608 9.73644C5.46929 9.50257 5.91718 9.08859 6.19433 8.57003C6.38886 8.20609 6.49952 7.79089 6.49952 7.35022C6.49952 5.91843 5.33468 4.75354 3.90284 4.75354Z" fill="#023DFE" fill-opacity="0.7"/>
<path d="M16.0972 4.75354C14.6653 4.75354 13.5005 5.91839 13.5005 7.35022C13.5005 7.79093 13.6112 8.20612 13.8057 8.57003C14.0828 9.08863 14.5307 9.50261 15.0739 9.73644C15.3881 9.87168 15.734 9.94691 16.0972 9.94691C17.529 9.94691 18.6939 8.78206 18.6939 7.35022C18.6939 5.91839 17.529 4.75354 16.0972 4.75354Z" fill="#023DFE" fill-opacity="0.7"/>
</g>
<defs>
<clipPath id="clip0_9717_7433">
<rect width="20" height="20" fill="white"/>
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 2.1 KiB

View File

@ -0,0 +1,4 @@
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M10.0002 5.97498L3.12109 11.2683V18.3601H8.64871V13.163H11.5852V18.3601H16.8794V11.2683L10.0002 5.97498Z" fill="#023DFE" fill-opacity="0.7"/>
<path d="M17.1673 7.15356V3.52759H14.2702V4.92485L10 1.63989L0 9.33274L1.38043 11.1271L10 4.49458L18.6196 11.1272L20 9.33278L17.1673 7.15356Z" fill="#023DFE" fill-opacity="0.7"/>
</svg>

After

Width:  |  Height:  |  Size: 433 B

View File

@ -0,0 +1,52 @@
import 'package:flutter/material.dart';
import 'package:syncrow_web/pages/access_management/booking_system/view/widgets/icon_text_button.dart';
import 'package:syncrow_web/utils/constants/assets.dart';
class BookingPage extends StatelessWidget {
const BookingPage({super.key});
@override
Widget build(BuildContext context) {
return Container(
child: Row(
children: [
Expanded(
child: Container(
color: Colors.blueGrey[100],
child: const Center(
child: Text(
'Side bar',
style: TextStyle(fontSize: 24, fontWeight: FontWeight.bold),
),
),
)),
Expanded(
flex: 4,
child: Padding(
padding: const EdgeInsets.all(20.0),
child: SizedBox(
child: Column(
children: [
Row(
mainAxisAlignment: MainAxisAlignment.start,
children: [
SvgTextButton(
svgAsset: Assets.homeIcon,
label: 'Manage Bookable Spaces',
onPressed: () {}),
SizedBox(width: 20),
SvgTextButton(
svgAsset: Assets.groupIcon,
label: 'Manage Users',
onPressed: () {})
],
)
],
),
),
))
],
),
);
}
}

View File

@ -0,0 +1,73 @@
import 'package:flutter/material.dart';
import 'package:flutter_svg/flutter_svg.dart';
import 'package:syncrow_web/utils/color_manager.dart';
class SvgTextButton extends StatelessWidget {
final String svgAsset;
final String label;
final VoidCallback onPressed;
final Color backgroundColor;
final Color svgColor;
final Color labelColor;
final double borderRadius;
final List<BoxShadow> boxShadow;
final double svgSize;
const SvgTextButton({
super.key,
required this.svgAsset,
required this.label,
required this.onPressed,
this.backgroundColor = ColorsManager.circleRolesBackground,
this.svgColor = const Color(0xFF496EFF),
this.labelColor = Colors.black87,
this.borderRadius = 10.0,
this.boxShadow = const [
BoxShadow(
color: ColorsManager.textGray,
blurRadius: 12,
offset: Offset(0, 4),
),
],
this.svgSize = 24.0,
});
@override
Widget build(BuildContext context) {
return Material(
color: Colors.transparent,
child: InkWell(
borderRadius: BorderRadius.circular(borderRadius),
onTap: onPressed,
child: Container(
padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 12),
decoration: BoxDecoration(
color: backgroundColor,
borderRadius: BorderRadius.circular(borderRadius),
boxShadow: boxShadow,
),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
SvgPicture.asset(
svgAsset,
width: svgSize,
height: svgSize,
color: svgColor,
),
const SizedBox(width: 12),
Text(
label,
style: TextStyle(
color: labelColor,
fontSize: 16,
fontWeight: FontWeight.w500,
),
),
],
),
),
),
);
}
}

View File

@ -2,302 +2,86 @@ import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:syncrow_web/pages/access_management/bloc/access_bloc.dart'; import 'package:syncrow_web/pages/access_management/bloc/access_bloc.dart';
import 'package:syncrow_web/pages/access_management/bloc/access_event.dart'; import 'package:syncrow_web/pages/access_management/bloc/access_event.dart';
import 'package:syncrow_web/pages/access_management/bloc/access_state.dart'; import 'package:syncrow_web/pages/access_management/booking_system/view/booking_page.dart';
import 'package:syncrow_web/pages/common/buttons/default_button.dart'; import 'package:syncrow_web/pages/access_management/view/access_overview_content.dart';
import 'package:syncrow_web/pages/common/buttons/search_reset_buttons.dart';
import 'package:syncrow_web/pages/common/custom_table.dart';
import 'package:syncrow_web/pages/common/date_time_widget.dart';
import 'package:syncrow_web/pages/common/filter/filter_widget.dart';
import 'package:syncrow_web/pages/common/text_field/custom_web_textfield.dart';
import 'package:syncrow_web/pages/device_managment/shared/navigate_home_grid_view.dart'; import 'package:syncrow_web/pages/device_managment/shared/navigate_home_grid_view.dart';
import 'package:syncrow_web/pages/visitor_password/view/visitor_password_dialog.dart';
// import 'package:syncrow_web/utils/color_manager.dart';
import 'package:syncrow_web/utils/constants/app_enum.dart';
import 'package:syncrow_web/utils/constants/assets.dart';
import 'package:syncrow_web/utils/extension/build_context_x.dart'; import 'package:syncrow_web/utils/extension/build_context_x.dart';
import 'package:syncrow_web/utils/helpers/responsice_layout_helper/responsive_layout_helper.dart'; import 'package:syncrow_web/utils/helpers/responsice_layout_helper/responsive_layout_helper.dart';
import 'package:syncrow_web/utils/style.dart';
import 'package:syncrow_web/utils/theme/responsive_text_theme.dart'; import 'package:syncrow_web/utils/theme/responsive_text_theme.dart';
import 'package:syncrow_web/web_layout/web_scaffold.dart'; import 'package:syncrow_web/web_layout/web_scaffold.dart';
class AccessManagementPage extends StatelessWidget with HelperResponsiveLayout { class AccessManagementPage extends StatefulWidget {
const AccessManagementPage({super.key}); const AccessManagementPage({super.key});
@override @override
Widget build(BuildContext context) { State<AccessManagementPage> createState() => _AccessManagementPageState();
final isLargeScreen = isLargeScreenSize(context); }
final isSmallScreen = isSmallScreenSize(context);
final isHalfMediumScreen = isHafMediumScreenSize(context);
final padding =
isLargeScreen ? const EdgeInsets.all(30) : const EdgeInsets.all(15);
return WebScaffold( class _AccessManagementPageState extends State<AccessManagementPage>
with HelperResponsiveLayout {
final PageController _pageController = PageController(initialPage: 0);
int _currentPageIndex = 0;
@override
void dispose() {
_pageController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return BlocProvider(
create: (BuildContext context) => AccessBloc()..add(FetchTableData()),
child: WebScaffold(
enableMenuSidebar: false, enableMenuSidebar: false,
appBarTitle: Text( appBarTitle: Text(
'Access Management', 'Access Management',
style: ResponsiveTextTheme.of(context).deviceManagementTitle, style: ResponsiveTextTheme.of(context).deviceManagementTitle,
), ),
rightBody: const NavigateHomeGridView(), centerBody: Row(
scaffoldBody: BlocProvider( mainAxisSize: MainAxisSize.min,
create: (BuildContext context) => children: [
AccessBloc()..add(FetchTableData()), TextButton(
child: BlocConsumer<AccessBloc, AccessState>( onPressed: () => _switchPage(0),
listener: (context, state) {},
builder: (context, state) {
final accessBloc = BlocProvider.of<AccessBloc>(context);
final filteredData = accessBloc.filteredData;
return state is AccessLoaded
? const Center(child: CircularProgressIndicator())
: Container(
padding: padding,
height: MediaQuery.of(context).size.height,
width: MediaQuery.of(context).size.width,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
FilterWidget(
size: MediaQuery.of(context).size,
tabs: accessBloc.tabs,
selectedIndex: accessBloc.selectedIndex,
onTabChanged: (index) {
accessBloc.add(TabChangedEvent(index));
},
),
const SizedBox(height: 20),
if (isSmallScreen || isHalfMediumScreen)
_buildSmallSearchFilters(context, accessBloc)
else
_buildNormalSearchWidgets(context, accessBloc),
const SizedBox(height: 20),
_buildVisitorAdminPasswords(context, accessBloc),
const SizedBox(height: 20),
Expanded(
child: DynamicTable(
tableName: 'AccessManagement',
uuidIndex: 1,
withSelectAll: true,
isEmpty: filteredData.isEmpty,
withCheckBox: false,
size: MediaQuery.of(context).size,
cellDecoration: containerDecoration,
headers: const [
'Name',
'Access Type',
'Access Start',
'Access End',
'Accessible Device',
'Authorizer',
'Authorization Date & Time',
'Access Status'
],
data: filteredData.map((item) {
return [
item.passwordName,
item.passwordType.value,
accessBloc
.timestampToDate(item.effectiveTime),
accessBloc
.timestampToDate(item.invalidTime),
item.deviceName.toString(),
item.authorizerEmail.toString(),
accessBloc
.timestampToDate(item.invalidTime),
item.passwordStatus.value,
];
}).toList(),
)),
],
),
);
})));
}
Wrap _buildVisitorAdminPasswords(
BuildContext context, AccessBloc accessBloc) {
return Wrap(
spacing: 10,
runSpacing: 10,
children: [
Container(
width: 205,
height: 42,
decoration: containerDecoration,
child: DefaultButton(
onPressed: () {
showDialog(
context: context,
barrierDismissible: false,
builder: (BuildContext context) {
return const VisitorPasswordDialog();
},
).then((v) {
if (v != null) {
accessBloc.add(FetchTableData());
}
});
},
borderRadius: 8,
child: Text( child: Text(
'Create Visitor Password ', 'Access Overview',
style: context.textTheme.titleSmall! style: context.textTheme.titleMedium?.copyWith(
.copyWith(color: Colors.white, fontSize: 12), color: _currentPageIndex == 0 ? Colors.white : Colors.grey,
)), fontWeight: _currentPageIndex == 0
? FontWeight.w700
: FontWeight.w400,
),
),
),
TextButton(
onPressed: () => _switchPage(1),
child: Text(
'Booking System',
style: context.textTheme.titleMedium?.copyWith(
color: _currentPageIndex == 1 ? Colors.white : Colors.grey,
fontWeight: _currentPageIndex == 1
? FontWeight.w700
: FontWeight.w400,
),
),
),
],
), ),
// Container( rightBody: const NavigateHomeGridView(),
// width: 133, scaffoldBody: PageView(
// height: 42, controller: _pageController,
// decoration: containerDecoration, physics: const NeverScrollableScrollPhysics(),
// child: DefaultButton( children: const [
// borderRadius: 8, AccessOverviewContent(),
// backgroundColor: ColorsManager.whiteColors, BookingPage(),
// child: Text( ],
// 'Admin Password', ),
// style: context.textTheme.titleSmall! ),
// .copyWith(color: Colors.black, fontSize: 12),
// )),
// ),
],
); );
} }
Row _buildNormalSearchWidgets(BuildContext context, AccessBloc accessBloc) { void _switchPage(int index) {
// TimeOfDay _selectedTime = TimeOfDay.now(); setState(() => _currentPageIndex = index);
_pageController.jumpToPage(index);
return Row(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.end,
textBaseline: TextBaseline.ideographic,
children: [
SizedBox(
width: 250,
child: CustomWebTextField(
controller: accessBloc.passwordName,
height: 43,
isRequired: false,
textFieldName: 'Name',
description: '',
onSubmitted: (value) {
accessBloc.add(FilterDataEvent(
emailAuthorizer:
accessBloc.emailAuthorizer.text.toLowerCase(),
selectedTabIndex:
BlocProvider.of<AccessBloc>(context).selectedIndex,
passwordName: accessBloc.passwordName.text.toLowerCase(),
startTime: accessBloc.effectiveTimeTimeStamp,
endTime: accessBloc.expirationTimeTimeStamp));
},
),
),
const SizedBox(width: 15),
SizedBox(
width: 250,
child: CustomWebTextField(
controller: accessBloc.emailAuthorizer,
height: 43,
isRequired: false,
textFieldName: 'Authorizer',
description: '',
onSubmitted: (value) {
accessBloc.add(FilterDataEvent(
emailAuthorizer:
accessBloc.emailAuthorizer.text.toLowerCase(),
selectedTabIndex:
BlocProvider.of<AccessBloc>(context).selectedIndex,
passwordName: accessBloc.passwordName.text.toLowerCase(),
startTime: accessBloc.effectiveTimeTimeStamp,
endTime: accessBloc.expirationTimeTimeStamp));
},
),
),
const SizedBox(width: 15),
SizedBox(
child: DateTimeWebWidget(
icon: Assets.calendarIcon,
isRequired: false,
title: 'Access Time',
size: MediaQuery.of(context).size,
endTime: () {
accessBloc.add(SelectTime(context: context, isStart: false));
},
startTime: () {
accessBloc.add(SelectTime(context: context, isStart: true));
},
firstString: BlocProvider.of<AccessBloc>(context).startTime,
secondString: BlocProvider.of<AccessBloc>(context).endTime,
),
),
const SizedBox(width: 15),
SearchResetButtons(
onSearch: () {
accessBloc.add(FilterDataEvent(
emailAuthorizer: accessBloc.emailAuthorizer.text.toLowerCase(),
selectedTabIndex:
BlocProvider.of<AccessBloc>(context).selectedIndex,
passwordName: accessBloc.passwordName.text.toLowerCase(),
startTime: accessBloc.effectiveTimeTimeStamp,
endTime: accessBloc.expirationTimeTimeStamp));
},
onReset: () {
accessBloc.add(ResetSearch());
},
),
],
);
}
Widget _buildSmallSearchFilters(BuildContext context, AccessBloc accessBloc) {
return Wrap(
spacing: 20,
runSpacing: 10,
children: [
SizedBox(
width: 300,
child: CustomWebTextField(
controller: accessBloc.passwordName,
isRequired: true,
height: 40,
textFieldName: 'Name',
description: '',
onSubmitted: (value) {
accessBloc.add(FilterDataEvent(
emailAuthorizer:
accessBloc.emailAuthorizer.text.toLowerCase(),
selectedTabIndex:
BlocProvider.of<AccessBloc>(context).selectedIndex,
passwordName: accessBloc.passwordName.text.toLowerCase(),
startTime: accessBloc.effectiveTimeTimeStamp,
endTime: accessBloc.expirationTimeTimeStamp));
}),
),
DateTimeWebWidget(
icon: Assets.calendarIcon,
isRequired: false,
title: 'Access Time',
size: MediaQuery.of(context).size,
endTime: () {
accessBloc.add(SelectTime(context: context, isStart: false));
},
startTime: () {
accessBloc.add(SelectTime(context: context, isStart: true));
},
firstString: BlocProvider.of<AccessBloc>(context).startTime,
secondString: BlocProvider.of<AccessBloc>(context).endTime,
),
SearchResetButtons(
onSearch: () {
accessBloc.add(FilterDataEvent(
emailAuthorizer: accessBloc.emailAuthorizer.text.toLowerCase(),
selectedTabIndex:
BlocProvider.of<AccessBloc>(context).selectedIndex,
passwordName: accessBloc.passwordName.text.toLowerCase(),
startTime: accessBloc.effectiveTimeTimeStamp,
endTime: accessBloc.expirationTimeTimeStamp));
},
onReset: () {
accessBloc.add(ResetSearch());
},
),
],
);
} }
} }

View File

@ -0,0 +1,289 @@
import 'package:syncrow_web/pages/common/buttons/default_button.dart';
import 'package:syncrow_web/pages/common/buttons/search_reset_buttons.dart';
import 'package:syncrow_web/pages/common/custom_table.dart';
import 'package:syncrow_web/pages/common/date_time_widget.dart';
import 'package:syncrow_web/pages/common/filter/filter_widget.dart';
import 'package:syncrow_web/pages/common/text_field/custom_web_textfield.dart';
import 'package:flutter/material.dart';
import 'package:syncrow_web/pages/access_management/bloc/access_state.dart';
import 'package:syncrow_web/pages/visitor_password/view/visitor_password_dialog.dart';
import 'package:syncrow_web/utils/constants/app_enum.dart';
import 'package:syncrow_web/utils/constants/assets.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:syncrow_web/pages/access_management/bloc/access_bloc.dart';
import 'package:syncrow_web/pages/access_management/bloc/access_event.dart';
import 'package:syncrow_web/utils/extension/build_context_x.dart';
import 'package:syncrow_web/utils/helpers/responsice_layout_helper/responsive_layout_helper.dart';
import 'package:syncrow_web/utils/style.dart';
class AccessOverviewContent extends StatelessWidget
with HelperResponsiveLayout {
const AccessOverviewContent({super.key});
@override
Widget build(BuildContext context) {
final isLargeScreen = isLargeScreenSize(context);
final isSmallScreen = isSmallScreenSize(context);
final isHalfMediumScreen = isHafMediumScreenSize(context);
final padding =
isLargeScreen ? const EdgeInsets.all(30) : const EdgeInsets.all(15);
return BlocProvider(
create: (BuildContext context) => AccessBloc()..add(FetchTableData()),
child: BlocConsumer<AccessBloc, AccessState>(
listener: (context, state) {},
builder: (context, state) {
final accessBloc = BlocProvider.of<AccessBloc>(context);
final filteredData = accessBloc.filteredData;
return state is AccessLoaded
? const Center(child: CircularProgressIndicator())
: Container(
padding: padding,
height: MediaQuery.of(context).size.height,
width: MediaQuery.of(context).size.width,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
FilterWidget(
size: MediaQuery.of(context).size,
tabs: accessBloc.tabs,
selectedIndex: accessBloc.selectedIndex,
onTabChanged: (index) {
accessBloc.add(TabChangedEvent(index));
},
),
const SizedBox(height: 20),
if (isSmallScreen || isHalfMediumScreen)
_buildSmallSearchFilters(context, accessBloc)
else
_buildNormalSearchWidgets(context, accessBloc),
const SizedBox(height: 20),
_buildVisitorAdminPasswords(context, accessBloc),
const SizedBox(height: 20),
Expanded(
child: DynamicTable(
tableName: 'AccessManagement',
uuidIndex: 1,
withSelectAll: true,
isEmpty: filteredData.isEmpty,
withCheckBox: false,
size: MediaQuery.of(context).size,
cellDecoration: containerDecoration,
headers: const [
'Name',
'Access Type',
'Access Start',
'Access End',
'Accessible Device',
'Authorizer',
'Authorization Date & Time',
'Access Status'
],
data: filteredData.map((item) {
return [
item.passwordName,
item.passwordType.value,
accessBloc.timestampToDate(item.effectiveTime),
accessBloc.timestampToDate(item.invalidTime),
item.deviceName.toString(),
item.authorizerEmail.toString(),
accessBloc.timestampToDate(item.invalidTime),
item.passwordStatus.value,
];
}).toList(),
)),
],
),
);
}));
}
Wrap _buildVisitorAdminPasswords(
BuildContext context, AccessBloc accessBloc) {
return Wrap(
spacing: 10,
runSpacing: 10,
children: [
Container(
width: 205,
height: 42,
decoration: containerDecoration,
child: DefaultButton(
onPressed: () {
showDialog(
context: context,
barrierDismissible: false,
builder: (BuildContext context) {
return const VisitorPasswordDialog();
},
).then((v) {
if (v != null) {
accessBloc.add(FetchTableData());
}
});
},
borderRadius: 8,
child: Text(
'Create Visitor Password ',
style: context.textTheme.titleSmall!
.copyWith(color: Colors.white, fontSize: 12),
)),
),
// Container(
// width: 133,
// height: 42,
// decoration: containerDecoration,
// child: DefaultButton(
// borderRadius: 8,
// backgroundColor: ColorsManager.whiteColors,
// child: Text(
// 'Admin Password',
// style: context.textTheme.titleSmall!
// .copyWith(color: Colors.black, fontSize: 12),
// )),
// ),
],
);
}
Row _buildNormalSearchWidgets(BuildContext context, AccessBloc accessBloc) {
// TimeOfDay _selectedTime = TimeOfDay.now();
return Row(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.end,
textBaseline: TextBaseline.ideographic,
children: [
SizedBox(
width: 250,
child: CustomWebTextField(
controller: accessBloc.passwordName,
height: 43,
isRequired: false,
textFieldName: 'Name',
description: '',
onSubmitted: (value) {
accessBloc.add(FilterDataEvent(
emailAuthorizer:
accessBloc.emailAuthorizer.text.toLowerCase(),
selectedTabIndex:
BlocProvider.of<AccessBloc>(context).selectedIndex,
passwordName: accessBloc.passwordName.text.toLowerCase(),
startTime: accessBloc.effectiveTimeTimeStamp,
endTime: accessBloc.expirationTimeTimeStamp));
},
),
),
const SizedBox(width: 15),
SizedBox(
width: 250,
child: CustomWebTextField(
controller: accessBloc.emailAuthorizer,
height: 43,
isRequired: false,
textFieldName: 'Authorizer',
description: '',
onSubmitted: (value) {
accessBloc.add(FilterDataEvent(
emailAuthorizer:
accessBloc.emailAuthorizer.text.toLowerCase(),
selectedTabIndex:
BlocProvider.of<AccessBloc>(context).selectedIndex,
passwordName: accessBloc.passwordName.text.toLowerCase(),
startTime: accessBloc.effectiveTimeTimeStamp,
endTime: accessBloc.expirationTimeTimeStamp));
},
),
),
const SizedBox(width: 15),
SizedBox(
child: DateTimeWebWidget(
icon: Assets.calendarIcon,
isRequired: false,
title: 'Access Time',
size: MediaQuery.of(context).size,
endTime: () {
accessBloc.add(SelectTime(context: context, isStart: false));
},
startTime: () {
accessBloc.add(SelectTime(context: context, isStart: true));
},
firstString: BlocProvider.of<AccessBloc>(context).startTime,
secondString: BlocProvider.of<AccessBloc>(context).endTime,
),
),
const SizedBox(width: 15),
SearchResetButtons(
onSearch: () {
accessBloc.add(FilterDataEvent(
emailAuthorizer: accessBloc.emailAuthorizer.text.toLowerCase(),
selectedTabIndex:
BlocProvider.of<AccessBloc>(context).selectedIndex,
passwordName: accessBloc.passwordName.text.toLowerCase(),
startTime: accessBloc.effectiveTimeTimeStamp,
endTime: accessBloc.expirationTimeTimeStamp));
},
onReset: () {
accessBloc.add(ResetSearch());
},
),
],
);
}
Widget _buildSmallSearchFilters(BuildContext context, AccessBloc accessBloc) {
return Wrap(
spacing: 20,
runSpacing: 10,
children: [
SizedBox(
width: 300,
child: CustomWebTextField(
controller: accessBloc.passwordName,
isRequired: true,
height: 40,
textFieldName: 'Name',
description: '',
onSubmitted: (value) {
accessBloc.add(FilterDataEvent(
emailAuthorizer:
accessBloc.emailAuthorizer.text.toLowerCase(),
selectedTabIndex:
BlocProvider.of<AccessBloc>(context).selectedIndex,
passwordName: accessBloc.passwordName.text.toLowerCase(),
startTime: accessBloc.effectiveTimeTimeStamp,
endTime: accessBloc.expirationTimeTimeStamp));
}),
),
DateTimeWebWidget(
icon: Assets.calendarIcon,
isRequired: false,
title: 'Access Time',
size: MediaQuery.of(context).size,
endTime: () {
accessBloc.add(SelectTime(context: context, isStart: false));
},
startTime: () {
accessBloc.add(SelectTime(context: context, isStart: true));
},
firstString: BlocProvider.of<AccessBloc>(context).startTime,
secondString: BlocProvider.of<AccessBloc>(context).endTime,
),
SearchResetButtons(
onSearch: () {
accessBloc.add(FilterDataEvent(
emailAuthorizer: accessBloc.emailAuthorizer.text.toLowerCase(),
selectedTabIndex:
BlocProvider.of<AccessBloc>(context).selectedIndex,
passwordName: accessBloc.passwordName.text.toLowerCase(),
startTime: accessBloc.effectiveTimeTimeStamp,
endTime: accessBloc.expirationTimeTimeStamp));
},
onReset: () {
accessBloc.add(ResetSearch());
},
),
],
);
}
}

View File

@ -22,9 +22,7 @@ import 'package:syncrow_web/pages/analytics/services/analytics_devices/remote_en
import 'package:syncrow_web/pages/analytics/services/analytics_devices/remote_occupancy_analytics_devices_service.dart'; import 'package:syncrow_web/pages/analytics/services/analytics_devices/remote_occupancy_analytics_devices_service.dart';
import 'package:syncrow_web/pages/analytics/services/device_location/device_location_details_service_decorator.dart'; import 'package:syncrow_web/pages/analytics/services/device_location/device_location_details_service_decorator.dart';
import 'package:syncrow_web/pages/analytics/services/device_location/remote_device_location_service.dart'; import 'package:syncrow_web/pages/analytics/services/device_location/remote_device_location_service.dart';
import 'package:syncrow_web/pages/analytics/services/energy_consumption_by_phases/energy_consumption_by_phases_value_divider_decorator.dart';
import 'package:syncrow_web/pages/analytics/services/energy_consumption_by_phases/remote_energy_consumption_by_phases_service.dart'; import 'package:syncrow_web/pages/analytics/services/energy_consumption_by_phases/remote_energy_consumption_by_phases_service.dart';
import 'package:syncrow_web/pages/analytics/services/energy_consumption_per_device/energy_consumption_per_device_value_divider_decorator.dart';
import 'package:syncrow_web/pages/analytics/services/energy_consumption_per_device/remote_energy_consumption_per_device_service.dart'; import 'package:syncrow_web/pages/analytics/services/energy_consumption_per_device/remote_energy_consumption_per_device_service.dart';
import 'package:syncrow_web/pages/analytics/services/occupacy/remote_occupancy_service.dart'; import 'package:syncrow_web/pages/analytics/services/occupacy/remote_occupancy_service.dart';
import 'package:syncrow_web/pages/analytics/services/occupancy_heat_map/remote_occupancy_heat_map_service.dart'; import 'package:syncrow_web/pages/analytics/services/occupancy_heat_map/remote_occupancy_heat_map_service.dart';
@ -64,23 +62,17 @@ class _AnalyticsPageState extends State<AnalyticsPage> {
), ),
BlocProvider( BlocProvider(
create: (context) => TotalEnergyConsumptionBloc( create: (context) => TotalEnergyConsumptionBloc(
DividedTotalEnergyConsumptionDecorator( RemoteTotalEnergyConsumptionService(_httpService),
RemoteTotalEnergyConsumptionService(_httpService),
),
), ),
), ),
BlocProvider( BlocProvider(
create: (context) => EnergyConsumptionByPhasesBloc( create: (context) => EnergyConsumptionByPhasesBloc(
EnergyConsumptionByPhasesValueDividerDecorator( RemoteEnergyConsumptionByPhasesService(_httpService),
RemoteEnergyConsumptionByPhasesService(_httpService),
),
), ),
), ),
BlocProvider( BlocProvider(
create: (context) => EnergyConsumptionPerDeviceBloc( create: (context) => EnergyConsumptionPerDeviceBloc(
EnergyConsumptionPerDeviceValueDividerDecorator( RemoteEnergyConsumptionPerDeviceService(_httpService),
RemoteEnergyConsumptionPerDeviceService(_httpService),
),
), ),
), ),
BlocProvider( BlocProvider(
@ -153,7 +145,6 @@ class _AnalyticsPageFormState extends State<AnalyticsPageForm> {
context.read<SpaceTreeBloc>().add(InitialEvent()); context.read<SpaceTreeBloc>().add(InitialEvent());
super.initState(); super.initState();
} }
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return WebScaffold( return WebScaffold(

View File

@ -13,15 +13,15 @@ class EnergyConsumptionByPhasesChart extends StatelessWidget {
}); });
final List<PhasesEnergyConsumption> energyData; final List<PhasesEnergyConsumption> energyData;
static const _kLeftTitlesInterval = 4.0;
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return BarChart( return BarChart(
BarChartData( BarChartData(
gridData: EnergyManagementChartsHelper.gridData().copyWith( gridData: EnergyManagementChartsHelper.gridData().copyWith(
checkToShowHorizontalLine: (value) => true, checkToShowHorizontalLine: (value) => true,
horizontalInterval: _kLeftTitlesInterval, horizontalInterval: 250,
), ),
borderData: EnergyManagementChartsHelper.borderData(), borderData: EnergyManagementChartsHelper.borderData(),
barTouchData: _barTouchData(context), barTouchData: _barTouchData(context),
@ -100,11 +100,11 @@ class EnergyConsumptionByPhasesChart extends StatelessWidget {
}) { }) {
final data = energyData; final data = energyData;
final date = DateFormat('dd/MM/yyyy').format(data[group.x].date); final date = DateFormat('dd/MM/yyyy').format(data[group.x.toInt()].date);
final phaseA = data[group.x].energyConsumedA; final phaseA = data[group.x.toInt()].energyConsumedA;
final phaseB = data[group.x].energyConsumedB; final phaseB = data[group.x.toInt()].energyConsumedB;
final phaseC = data[group.x].energyConsumedC; final phaseC = data[group.x.toInt()].energyConsumedC;
final total = data[group.x].energyConsumedKw; final total = data[group.x.toInt()].energyConsumedKw;
return BarTooltipItem( return BarTooltipItem(
'$date\n', '$date\n',
@ -149,7 +149,7 @@ class EnergyConsumptionByPhasesChart extends StatelessWidget {
FlTitlesData _titlesData(BuildContext context) { FlTitlesData _titlesData(BuildContext context) {
final titlesData = EnergyManagementChartsHelper.titlesData( final titlesData = EnergyManagementChartsHelper.titlesData(
context, context,
leftTitlesInterval: _kLeftTitlesInterval, leftTitlesInterval: 250,
); );
final leftTitles = titlesData.leftTitles.copyWith( final leftTitles = titlesData.leftTitles.copyWith(

View File

@ -7,7 +7,6 @@ class EnergyConsumptionPerDeviceChart extends StatelessWidget {
const EnergyConsumptionPerDeviceChart({super.key, required this.chartData}); const EnergyConsumptionPerDeviceChart({super.key, required this.chartData});
final List<DeviceEnergyDataModel> chartData; final List<DeviceEnergyDataModel> chartData;
static const _kLeftTitlesInterval = 2.5;
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
@ -16,11 +15,12 @@ class EnergyConsumptionPerDeviceChart extends StatelessWidget {
clipData: const FlClipData.vertical(), clipData: const FlClipData.vertical(),
titlesData: EnergyManagementChartsHelper.titlesData( titlesData: EnergyManagementChartsHelper.titlesData(
context, context,
leftTitlesInterval: _kLeftTitlesInterval, leftTitlesInterval: 250,
), ),
gridData: EnergyManagementChartsHelper.gridData().copyWith( gridData: EnergyManagementChartsHelper.gridData().copyWith(
checkToShowHorizontalLine: (value) => true, checkToShowHorizontalLine: (value) => true,
horizontalInterval: _kLeftTitlesInterval, horizontalInterval: 250,
), ),
borderData: EnergyManagementChartsHelper.borderData(), borderData: EnergyManagementChartsHelper.borderData(),
lineTouchData: EnergyManagementChartsHelper.lineTouchData(), lineTouchData: EnergyManagementChartsHelper.lineTouchData(),

View File

@ -9,24 +9,22 @@ class TotalEnergyConsumptionChart extends StatelessWidget {
final List<EnergyDataModel> chartData; final List<EnergyDataModel> chartData;
static const _leftTitlesInterval = 5.0;
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Expanded( return Expanded(
child: LineChart( child: LineChart(
LineChartData( LineChartData(
maxY: chartData.isNotEmpty maxY: chartData.isEmpty
? chartData.map((e) => e.value).reduce((a, b) => a > b ? a : b) + ? null
_leftTitlesInterval : chartData.map((e) => e.value).reduce((a, b) => a > b ? a : b) + 250,
: null,
clipData: const FlClipData.vertical(), clipData: const FlClipData.vertical(),
titlesData: EnergyManagementChartsHelper.titlesData( titlesData: EnergyManagementChartsHelper.titlesData(
context, context,
leftTitlesInterval: _leftTitlesInterval, leftTitlesInterval: 500,
), ),
gridData: EnergyManagementChartsHelper.gridData().copyWith( gridData: EnergyManagementChartsHelper.gridData().copyWith(
checkToShowHorizontalLine: (value) => true, checkToShowHorizontalLine: (value) => true,
horizontalInterval: _leftTitlesInterval, horizontalInterval: 500,
), ),
borderData: EnergyManagementChartsHelper.borderData(), borderData: EnergyManagementChartsHelper.borderData(),
lineTouchData: EnergyManagementChartsHelper.lineTouchData(), lineTouchData: EnergyManagementChartsHelper.lineTouchData(),

View File

@ -1,34 +0,0 @@
import 'package:syncrow_web/pages/analytics/models/phases_energy_consumption.dart';
import 'package:syncrow_web/pages/analytics/params/get_energy_consumption_by_phases_param.dart';
import 'package:syncrow_web/pages/analytics/services/energy_consumption_by_phases/energy_consumption_by_phases_service.dart';
import 'package:syncrow_web/utils/helpers/safe_division_helper.dart';
class EnergyConsumptionByPhasesValueDividerDecorator
implements EnergyConsumptionByPhasesService {
const EnergyConsumptionByPhasesValueDividerDecorator(
this._decoratee, {
this.divider = 100,
});
final EnergyConsumptionByPhasesService _decoratee;
final double divider;
@override
Future<List<PhasesEnergyConsumption>> load(
GetEnergyConsumptionByPhasesParam param) async {
final result = await _decoratee.load(param);
return result.map((e) {
return PhasesEnergyConsumption(
date: e.date,
energyConsumedA: SafeDivisionHelper.divide(e.energyConsumedA, divider),
energyConsumedB: SafeDivisionHelper.divide(e.energyConsumedB, divider),
energyConsumedC: SafeDivisionHelper.divide(e.energyConsumedC, divider),
energyConsumedKw: SafeDivisionHelper.divide(e.energyConsumedKw, divider),
uuid: e.uuid,
createdAt: e.createdAt,
updatedAt: e.updatedAt,
deviceUuid: e.deviceUuid,
);
}).toList();
}
}

View File

@ -1,36 +0,0 @@
import 'package:syncrow_web/pages/analytics/models/device_energy_data_model.dart';
import 'package:syncrow_web/pages/analytics/models/energy_data_model.dart';
import 'package:syncrow_web/pages/analytics/params/get_energy_consumption_per_device_param.dart';
import 'package:syncrow_web/pages/analytics/services/energy_consumption_per_device/energy_consumption_per_device_service.dart';
import 'package:syncrow_web/utils/helpers/safe_division_helper.dart';
class EnergyConsumptionPerDeviceValueDividerDecorator
implements EnergyConsumptionPerDeviceService {
const EnergyConsumptionPerDeviceValueDividerDecorator(
this._decoratee, {
this.divider = 100,
});
final EnergyConsumptionPerDeviceService _decoratee;
final double divider;
@override
Future<List<DeviceEnergyDataModel>> load(
GetEnergyConsumptionPerDeviceParam param,
) async {
final result = await _decoratee.load(param);
return result.map((device) {
return DeviceEnergyDataModel(
deviceId: device.deviceId,
deviceName: device.deviceName,
color: device.color,
energy: device.energy.map((e) {
return EnergyDataModel(
date: e.date,
value: SafeDivisionHelper.divide(e.value, divider),
);
}).toList(),
);
}).toList();
}
}

View File

@ -1,26 +0,0 @@
import 'package:syncrow_web/pages/analytics/models/energy_data_model.dart';
import 'package:syncrow_web/pages/analytics/params/get_total_energy_consumption_param.dart';
import 'package:syncrow_web/pages/analytics/services/total_energy_consumption/total_energy_consumption_service.dart';
import 'package:syncrow_web/utils/helpers/safe_division_helper.dart';
class DividedTotalEnergyConsumptionDecorator
implements TotalEnergyConsumptionService {
const DividedTotalEnergyConsumptionDecorator(
this._decoratee, {
this.divider = 100,
});
final TotalEnergyConsumptionService _decoratee;
final double divider;
@override
Future<List<EnergyDataModel>> load(GetTotalEnergyConsumptionParam param) async {
final result = await _decoratee.load(param);
return result.map((e) {
return EnergyDataModel(
date: e.date,
value: SafeDivisionHelper.divide(e.value, divider),
);
}).toList();
}
}

View File

@ -59,23 +59,3 @@ abstract final class _TotalEnergyConsumptionResponseMapper {
}).toList(); }).toList();
} }
} }
class DividedTotalEnergyConsumptionDecorator
implements TotalEnergyConsumptionService {
const DividedTotalEnergyConsumptionDecorator(this._decoratee);
final TotalEnergyConsumptionService _decoratee;
@override
Future<List<EnergyDataModel>> load(
GetTotalEnergyConsumptionParam param,
) async {
final result = await _decoratee.load(param);
final dividedResult = result.map((e) {
return EnergyDataModel(
date: e.date,
value: e.value / 100,
);
});
return dividedResult.toList();
}
}

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: Scrollbar( child: ScrollConfiguration(
controller: _verticalScrollController, behavior: const ScrollBehavior().copyWith(scrollbars: false),
thumbVisibility: true,
trackVisibility: true,
child: Scrollbar( 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) =>
notif.metrics.axis == Axis.horizontal,
child: SingleChildScrollView( child: SingleChildScrollView(
controller: _verticalScrollController, controller: _horizontalScrollController,
child: SingleChildScrollView( scrollDirection: Axis.horizontal,
controller: _horizontalScrollController, child: SizedBox(
scrollDirection: Axis.horizontal, width: _totalTableWidth,
child: SizedBox( child: Column(
width: widget.size.width, children: [
child: Column( Container(
children: [ height: _fixedRowHeight,
Container( decoration: widget.headerDecoration ??
decoration: widget.headerDecoration ?? const BoxDecoration(color: ColorsManager.boxColor),
const BoxDecoration( child: Row(
color: ColorsManager.boxColor, 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 Expanded(
? _buildEmptyState() child: widget.isEmpty
: Column( ? _buildEmptyState()
children: : Scrollbar(
List.generate(widget.data.length, (rowIndex) { 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]; final row = widget.data[rowIndex];
return Row( return SizedBox(
children: [ height: _fixedRowHeight,
if (widget.withCheckBox) child: Row(
_buildRowCheckbox( children: [
rowIndex, widget.size.height * 0.08), if (widget.withCheckBox)
...row.asMap().entries.map((entry) { _buildRowCheckbox(
return _buildTableCell( rowIndex,
entry.value.toString(), _checkboxColumnWidth,
widget.size.height * 0.08, ),
rowIndex: rowIndex, for (var colIndex = 0;
columnIndex: entry.key, colIndex < row.length;
); colIndex++)
}).toList(), 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( 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,50 +313,47 @@ 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), ),
alignment: Alignment.centerLeft, constraints: BoxConstraints(minHeight: 40, maxHeight: _fixedRowHeight),
child: Padding( alignment: Alignment.centerLeft,
padding: EdgeInsets.symmetric( child: Padding(
horizontal: index == widget.headers.length - 1 ? 12 : 8.0, padding: const EdgeInsets.symmetric(horizontal: 8.0, vertical: 4),
vertical: 4), child: Text(
child: Text( title,
title, style: context.textTheme.titleSmall!.copyWith(
style: context.textTheme.titleSmall!.copyWith( color: ColorsManager.grayColor,
color: ColorsManager.grayColor, fontSize: 12,
fontSize: 12, 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,92 +377,82 @@ 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(
color: ColorsManager.boxDivider, color: ColorsManager.boxDivider,
width: 1.0, width: 1.0,
),
), ),
color: Colors.white,
), ),
alignment: Alignment.centerLeft, color: Colors.white,
child: Text( ),
content, alignment: Alignment.centerLeft,
style: TextStyle( child: Text(
color: (batteryLevel != null && batteryLevel < 20) content,
? ColorsManager.red style: TextStyle(
: (batteryLevel != null && batteryLevel > 20) color: (batteryLevel != null && batteryLevel < 20)
? ColorsManager.green ? ColorsManager.red
: statusColor, : (batteryLevel != null && batteryLevel > 20)
fontSize: 13, ? ColorsManager.green
fontWeight: FontWeight.w400), : statusColor,
maxLines: 2, fontSize: 13,
fontWeight: FontWeight.w400,
), ),
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( decoration: const BoxDecoration(
children: [ color: ColorsManager.whiteColors,
Container( border: Border(
padding: const EdgeInsets.only(top: 10, bottom: 15, left: 10), bottom: BorderSide(
margin: const EdgeInsets.only(right: 15), color: ColorsManager.boxDivider,
decoration: const BoxDecoration( width: 1.0,
color: ColorsManager.whiteColors,
border: Border(
bottom: BorderSide(
color: ColorsManager.boxDivider,
width: 1.0,
),
),
), ),
width: width, ),
child: Padding( ),
padding: const EdgeInsets.only( child: Align(
right: 16.0, alignment: Alignment.centerLeft,
left: 17.0, child: Container(
), width: 50,
child: Container( decoration: BoxDecoration(
width: 50, color: const Color(0xFFF7F8FA),
decoration: BoxDecoration( borderRadius: BorderRadius.circular(20),
color: const Color(0xFFF7F8FA), boxShadow: [
borderRadius: BorderRadius.circular(height / 2), BoxShadow(
boxShadow: [ color: Colors.black.withOpacity(0.17),
BoxShadow( blurRadius: 14,
color: Colors.black.withOpacity(0.17), offset: const Offset(0, 4),
blurRadius: 14,
offset: const Offset(0, 4),
),
],
), ),
child: InkWell( ],
onTap: onTap, ),
child: Padding( child: InkWell(
padding: const EdgeInsets.all(8.0), onTap: onTap,
child: Center( child: Padding(
child: SvgPicture.asset( padding: EdgeInsets.all(8.0),
Assets.settings, child: Center(
width: 40, child: SvgPicture.asset(
height: 22, Assets.settings,
color: ColorsManager.primaryColor, width: 40,
), height: 20,
), color: ColorsManager.primaryColor,
), ),
), ),
), ),
), ),
), ),
], ),
); );
} }
} }

View File

@ -17,6 +17,7 @@ class CalibrateCompletedDialog extends StatelessWidget {
@override @override
Widget build(_) { Widget build(_) {
return AlertDialog( return AlertDialog(
backgroundColor: ColorsManager.whiteColors,
contentPadding: EdgeInsets.zero, contentPadding: EdgeInsets.zero,
content: SizedBox( content: SizedBox(
height: 250, height: 250,

View File

@ -105,7 +105,7 @@ class HomeBloc extends Bloc<HomeEvent, HomeState> {
color: const Color(0xFF0026A2), color: const Color(0xFF0026A2),
), ),
HomeItemModel( HomeItemModel(
title: 'Device Management', title: 'Devices Management',
icon: Assets.devicesIcon, icon: Assets.devicesIcon,
active: true, active: true,
onPress: (context) { onPress: (context) {

View File

@ -32,113 +32,114 @@ class SpaceDropdown extends StatelessWidget {
color: ColorsManager.blackColor, color: ColorsManager.blackColor,
), ),
), ),
SizedBox( DropdownButton2<String>(
child: Container( underline: const SizedBox(),
buttonStyleData: ButtonStyleData(
decoration:
BoxDecoration(borderRadius: BorderRadius.circular(12)),
),
value: selectedValue,
items: spaces.map((space) {
return DropdownMenuItem<String>(
value: space.uuid,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
' ${space.name}',
style: Theme.of(context).textTheme.bodyMedium!.copyWith(
fontSize: 16,
fontWeight: FontWeight.bold,
color: selectedValue == space.uuid
? ColorsManager.dialogBlueTitle
: ColorsManager.blackColor,
),
),
Text(
' ${space.lastThreeParents}',
style: Theme.of(context).textTheme.bodyMedium!.copyWith(
fontSize: 12,
color: selectedValue == space.uuid
? ColorsManager.dialogBlueTitle
: ColorsManager.blackColor,
),
),
],
),
);
}).toList(),
onChanged: onChanged,
style: TextStyle(
color: Colors.black,
fontSize: 13,
),
hint: Padding(
padding: const EdgeInsets.only(left: 10),
child: Text(
hintMessage,
style: Theme.of(context).textTheme.bodySmall!.copyWith(
color: ColorsManager.textGray,
),
),
),
customButton: Container(
height: 40, height: 40,
decoration: BoxDecoration(
border: Border.all(color: ColorsManager.textGray, width: 1.0),
borderRadius: BorderRadius.circular(10),
),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Expanded(
flex: 8,
child: Padding(
padding: const EdgeInsets.only(left: 10),
child: Text(
selectedValue != null
? spaces
.firstWhere((e) => e.uuid == selectedValue)
.name
: hintMessage,
style: Theme.of(context).textTheme.bodySmall!.copyWith(
fontSize: 13,
color: selectedValue != null
? Colors.black
: ColorsManager.textGray,
),
overflow: TextOverflow.ellipsis,
),
),
),
Expanded(
child: Container(
decoration: BoxDecoration(
color: Colors.grey[200],
borderRadius: const BorderRadius.only(
topRight: Radius.circular(10),
bottomRight: Radius.circular(10),
),
),
height: 45,
child: const Icon(
Icons.keyboard_arrow_down,
color: ColorsManager.textGray,
),
),
),
],
),
),
dropdownStyleData: DropdownStyleData(
maxHeight: MediaQuery.of(context).size.height * 0.4,
decoration: BoxDecoration( decoration: BoxDecoration(
color: ColorsManager.whiteColors, color: ColorsManager.whiteColors,
borderRadius: BorderRadius.circular(10), borderRadius: BorderRadius.circular(10),
), ),
child: DropdownButton2<String>( ),
underline: const SizedBox(), menuItemStyleData: const MenuItemStyleData(
value: selectedValue, height: 60,
items: spaces.map((space) {
return DropdownMenuItem<String>(
value: space.uuid,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
' ${space.name}',
style:
Theme.of(context).textTheme.bodyMedium!.copyWith(
fontSize: 12,
color: ColorsManager.blackColor,
),
),
Text(
' ${space.lastThreeParents}',
style:
Theme.of(context).textTheme.bodyMedium!.copyWith(
fontSize: 12,
),
),
],
),
);
}).toList(),
onChanged: onChanged,
style: TextStyle(color: Colors.black),
hint: Padding(
padding: const EdgeInsets.only(left: 10),
child: Text(
hintMessage,
style: Theme.of(context).textTheme.bodySmall!.copyWith(
color: ColorsManager.textGray,
),
),
),
customButton: Container(
height: 45,
decoration: BoxDecoration(
border:
Border.all(color: ColorsManager.textGray, width: 1.0),
borderRadius: BorderRadius.circular(10),
),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Expanded(
flex: 8,
child: Padding(
padding: const EdgeInsets.only(left: 10),
child: Text(
selectedValue != null
? spaces
.firstWhere((e) => e.uuid == selectedValue)
.name
: hintMessage,
style:
Theme.of(context).textTheme.bodySmall!.copyWith(
color: selectedValue != null
? Colors.black
: ColorsManager.textGray,
),
overflow: TextOverflow.ellipsis,
),
),
),
Expanded(
child: Container(
decoration: BoxDecoration(
color: Colors.grey[200],
borderRadius: const BorderRadius.only(
topRight: Radius.circular(10),
bottomRight: Radius.circular(10),
),
),
height: 45,
child: const Icon(
Icons.keyboard_arrow_down,
color: ColorsManager.textGray,
),
),
),
],
),
),
dropdownStyleData: DropdownStyleData(
maxHeight: MediaQuery.of(context).size.height * 0.4,
decoration: BoxDecoration(
color: ColorsManager.whiteColors,
borderRadius: BorderRadius.circular(10),
),
),
menuItemStyleData: const MenuItemStyleData(
height: 60,
),
),
), ),
), ),
], ],

View File

@ -121,7 +121,8 @@ class _RoutineViewCardState extends State<RoutineViewCard> {
child: SizedBox( child: SizedBox(
width: 16, width: 16,
height: 16, height: 16,
child: CircularProgressIndicator(strokeWidth: 2), child:
CircularProgressIndicator(strokeWidth: 2),
), ),
), ),
) )
@ -159,8 +160,9 @@ class _RoutineViewCardState extends State<RoutineViewCard> {
height: iconSize, height: iconSize,
width: iconSize, width: iconSize,
fit: BoxFit.contain, fit: BoxFit.contain,
errorBuilder: (context, error, stackTrace) => errorBuilder:
Image.asset( (context, error, stackTrace) =>
Image.asset(
Assets.logo, Assets.logo,
height: iconSize, height: iconSize,
width: iconSize, width: iconSize,
@ -203,7 +205,8 @@ class _RoutineViewCardState extends State<RoutineViewCard> {
maxLines: 1, maxLines: 1,
style: context.textTheme.bodySmall?.copyWith( style: context.textTheme.bodySmall?.copyWith(
color: ColorsManager.blackColor, color: ColorsManager.blackColor,
fontSize: widget.isSmallScreenSize(context) ? 10 : 12, fontSize:
widget.isSmallScreenSize(context) ? 10 : 12,
), ),
), ),
if (widget.spaceName != '') if (widget.spaceName != '')
@ -222,8 +225,9 @@ class _RoutineViewCardState extends State<RoutineViewCard> {
maxLines: 1, maxLines: 1,
style: context.textTheme.bodySmall?.copyWith( style: context.textTheme.bodySmall?.copyWith(
color: ColorsManager.blackColor, color: ColorsManager.blackColor,
fontSize: fontSize: widget.isSmallScreenSize(context)
widget.isSmallScreenSize(context) ? 10 : 12, ? 10
: 12,
), ),
), ),
], ],

View File

@ -21,7 +21,6 @@ import 'package:syncrow_web/utils/snack_bar.dart';
class VisitorPasswordBloc class VisitorPasswordBloc
extends Bloc<VisitorPasswordEvent, VisitorPasswordState> { extends Bloc<VisitorPasswordEvent, VisitorPasswordState> {
VisitorPasswordBloc() : super(VisitorPasswordInitial()) { VisitorPasswordBloc() : super(VisitorPasswordInitial()) {
on<SelectUsageFrequency>(selectUsageFrequency); on<SelectUsageFrequency>(selectUsageFrequency);
on<FetchDevice>(_onFetchDevice); on<FetchDevice>(_onFetchDevice);
@ -87,6 +86,9 @@ class VisitorPasswordBloc
SelectTimeVisitorPassword event, SelectTimeVisitorPassword event,
Emitter<VisitorPasswordState> emit, Emitter<VisitorPasswordState> emit,
) async { ) async {
// Ensure expirationTimeTimeStamp has a value
effectiveTimeTimeStamp ??= DateTime.now().millisecondsSinceEpoch ~/ 1000;
final DateTime? picked = await showDatePicker( final DateTime? picked = await showDatePicker(
context: event.context, context: event.context,
initialDate: DateTime.now(), initialDate: DateTime.now(),
@ -94,86 +96,124 @@ class VisitorPasswordBloc
lastDate: DateTime.now().add(const Duration(days: 5095)), lastDate: DateTime.now().add(const Duration(days: 5095)),
); );
if (picked != null) { if (picked == null) return;
final TimeOfDay? timePicked = await showTimePicker(
context: event.context, final TimeOfDay? timePicked = await showTimePicker(
initialTime: TimeOfDay.now(), context: event.context,
builder: (context, child) { initialTime: TimeOfDay.now(),
return Theme( builder: (context, child) {
data: ThemeData.light().copyWith( return Theme(
colorScheme: const ColorScheme.light( data: ThemeData.light().copyWith(
primary: ColorsManager.primaryColor, colorScheme: const ColorScheme.light(
onSurface: Colors.black, primary: ColorsManager.primaryColor,
), onSurface: Colors.black,
buttonTheme: const ButtonThemeData(
colorScheme: ColorScheme.light(
primary: Colors.green,
),
),
), ),
child: child!, ),
); child: child!,
},
);
if (timePicked != null) {
final selectedDateTime = DateTime(
picked.year,
picked.month,
picked.day,
timePicked.hour,
timePicked.minute,
); );
},
);
final selectedTimestamp = if (timePicked == null) return;
selectedDateTime.millisecondsSinceEpoch ~/ 1000;
if (event.isStart) { final selectedDateTime = DateTime(
if (expirationTimeTimeStamp != null && picked.year,
selectedTimestamp > expirationTimeTimeStamp!) { picked.month,
CustomSnackBar.displaySnackBar( picked.day,
timePicked.hour,
timePicked.minute,
);
final selectedTimestamp = selectedDateTime.millisecondsSinceEpoch ~/ 1000;
final currentTimestamp = DateTime.now().millisecondsSinceEpoch ~/ 1000;
if (event.isStart) {
// START TIME VALIDATION
if (expirationTimeTimeStamp != null &&
selectedTimestamp > expirationTimeTimeStamp!) {
await showDialog<void>(
context: event.context,
builder: (context) => AlertDialog(
title: const Text(
'Effective Time cannot be later than Expiration Time.', 'Effective Time cannot be later than Expiration Time.',
); ),
return; actionsAlignment: MainAxisAlignment.center,
} content: FilledButton(
if(selectedTimestamp < DateTime.now().millisecondsSinceEpoch ~/ 1000) { onPressed: () {
if(selectedTimestamp < DateTime.now().millisecondsSinceEpoch ~/ 1000) { Navigator.of(event.context).pop();
await showDialog<void>( add(SelectTimeVisitorPassword(
context: event.context, context: event.context,
builder: (context) => AlertDialog( isStart: true,
title: const Text('Effective Time cannot be earlier than current time.'), isRepeat: false,
actionsAlignment: MainAxisAlignment.center, ));
content: },
FilledButton( child: const Text('OK'),
onPressed: () { ),
Navigator.of(event.context).pop(); ),
add(SelectTimeVisitorPassword(context: event.context, isStart: true, isRepeat: false)); );
}, return;
child: const Text('OK'),
),
),
);
}
return;
}
effectiveTimeTimeStamp = selectedTimestamp;
startTimeAccess = selectedDateTime.toString().split('.').first;
} else {
if (effectiveTimeTimeStamp != null &&
selectedTimestamp < effectiveTimeTimeStamp!) {
CustomSnackBar.displaySnackBar(
'Expiration Time cannot be earlier than Effective Time.',
);
return;
}
expirationTimeTimeStamp = selectedTimestamp;
endTimeAccess = selectedDateTime.toString().split('.').first;
}
emit(ChangeTimeState());
emit(VisitorPasswordInitial());
} }
if (selectedTimestamp < currentTimestamp) {
await showDialog<void>(
context: event.context,
builder: (context) => AlertDialog(
title: const Text(
'Effective Time cannot be earlier than current time.',
),
actionsAlignment: MainAxisAlignment.center,
content: FilledButton(
onPressed: () {
Navigator.of(event.context).pop();
add(SelectTimeVisitorPassword(
context: event.context,
isStart: true,
isRepeat: false,
));
},
child: const Text('OK'),
),
),
);
return;
}
// Save effective time
effectiveTimeTimeStamp = selectedTimestamp;
startTimeAccess = selectedDateTime.toString().split('.').first;
} else {
// END TIME VALIDATION
if (effectiveTimeTimeStamp != null &&
selectedTimestamp < effectiveTimeTimeStamp!) {
await showDialog<void>(
context: event.context,
builder: (context) => AlertDialog(
title: const Text(
'Expiration Time cannot be earlier than Effective Time.',
),
actionsAlignment: MainAxisAlignment.center,
content: FilledButton(
onPressed: () {
Navigator.of(event.context).pop();
add(SelectTimeVisitorPassword(
context: event.context,
isStart: false,
isRepeat: false,
));
},
child: const Text('OK'),
),
),
);
return;
}
// Save expiration time
expirationTimeTimeStamp = selectedTimestamp;
endTimeAccess = selectedDateTime.toString().split('.').first;
} }
emit(ChangeTimeState());
emit(VisitorPasswordInitial());
} }
bool toggleRepeat( bool toggleRepeat(
@ -213,7 +253,7 @@ class VisitorPasswordBloc
FetchDevice event, Emitter<VisitorPasswordState> emit) async { FetchDevice event, Emitter<VisitorPasswordState> emit) async {
try { try {
emit(DeviceLoaded()); emit(DeviceLoaded());
final projectUuid = await ProjectManager.getProjectUUID() ?? ''; final projectUuid = await ProjectManager.getProjectUUID() ?? '';
data = await AccessMangApi().fetchDoorLockDeviceList(projectUuid); data = await AccessMangApi().fetchDoorLockDeviceList(projectUuid);
emit(TableLoaded(data)); emit(TableLoaded(data));

View File

@ -14,7 +14,8 @@ class Assets {
static const String rightLine = 'assets/images/right_line.png'; static const String rightLine = 'assets/images/right_line.png';
static const String google = 'assets/images/google.svg'; static const String google = 'assets/images/google.svg';
static const String facebook = 'assets/images/facebook.svg'; static const String facebook = 'assets/images/facebook.svg';
static const String invisiblePassword = 'assets/images/Password_invisible.svg'; static const String invisiblePassword =
'assets/images/Password_invisible.svg';
static const String visiblePassword = 'assets/images/password_visible.svg'; static const String visiblePassword = 'assets/images/password_visible.svg';
static const String accessIcon = 'assets/images/access_icon.svg'; static const String accessIcon = 'assets/images/access_icon.svg';
static const String spaseManagementIcon = static const String spaseManagementIcon =
@ -33,7 +34,8 @@ class Assets {
static const String emptyTable = 'assets/images/empty_table.svg'; static const String emptyTable = 'assets/images/empty_table.svg';
// General assets // General assets
static const String motionlessDetection = 'assets/icons/motionless_detection.svg'; static const String motionlessDetection =
'assets/icons/motionless_detection.svg';
static const String acHeating = 'assets/icons/ac_heating.svg'; static const String acHeating = 'assets/icons/ac_heating.svg';
static const String acPowerOff = 'assets/icons/ac_power_off.svg'; static const String acPowerOff = 'assets/icons/ac_power_off.svg';
static const String acFanMiddle = 'assets/icons/ac_fan_middle.svg'; static const String acFanMiddle = 'assets/icons/ac_fan_middle.svg';
@ -70,19 +72,22 @@ class Assets {
'assets/icons/automation_functions/temp_password_unlock.svg'; 'assets/icons/automation_functions/temp_password_unlock.svg';
static const String doorlockNormalOpen = static const String doorlockNormalOpen =
'assets/icons/automation_functions/doorlock_normal_open.svg'; 'assets/icons/automation_functions/doorlock_normal_open.svg';
static const String doorbell = 'assets/icons/automation_functions/doorbell.svg'; static const String doorbell =
'assets/icons/automation_functions/doorbell.svg';
static const String remoteUnlockViaApp = static const String remoteUnlockViaApp =
'assets/icons/automation_functions/remote_unlock_via_app.svg'; 'assets/icons/automation_functions/remote_unlock_via_app.svg';
static const String doubleLock = static const String doubleLock =
'assets/icons/automation_functions/double_lock.svg'; 'assets/icons/automation_functions/double_lock.svg';
static const String selfTestResult = static const String selfTestResult =
'assets/icons/automation_functions/self_test_result.svg'; 'assets/icons/automation_functions/self_test_result.svg';
static const String lockAlarm = 'assets/icons/automation_functions/lock_alarm.svg'; static const String lockAlarm =
'assets/icons/automation_functions/lock_alarm.svg';
static const String presenceState = static const String presenceState =
'assets/icons/automation_functions/presence_state.svg'; 'assets/icons/automation_functions/presence_state.svg';
static const String currentTemp = static const String currentTemp =
'assets/icons/automation_functions/current_temp.svg'; 'assets/icons/automation_functions/current_temp.svg';
static const String presence = 'assets/icons/automation_functions/presence.svg'; static const String presence =
'assets/icons/automation_functions/presence.svg';
static const String residualElectricity = static const String residualElectricity =
'assets/icons/automation_functions/residual_electricity.svg'; 'assets/icons/automation_functions/residual_electricity.svg';
static const String hijackAlarm = static const String hijackAlarm =
@ -99,12 +104,15 @@ class Assets {
// Presence Sensor Assets // Presence Sensor Assets
static const String sensorMotionIcon = 'assets/icons/sensor_motion_ic.svg'; static const String sensorMotionIcon = 'assets/icons/sensor_motion_ic.svg';
static const String sensorPresenceIcon = 'assets/icons/sensor_presence_ic.svg'; static const String sensorPresenceIcon =
'assets/icons/sensor_presence_ic.svg';
static const String sensorVacantIcon = 'assets/icons/sensor_vacant_ic.svg'; static const String sensorVacantIcon = 'assets/icons/sensor_vacant_ic.svg';
static const String illuminanceRecordIcon = static const String illuminanceRecordIcon =
'assets/icons/illuminance_record_ic.svg'; 'assets/icons/illuminance_record_ic.svg';
static const String presenceRecordIcon = 'assets/icons/presence_record_ic.svg'; static const String presenceRecordIcon =
static const String helpDescriptionIcon = 'assets/icons/help_description_ic.svg'; 'assets/icons/presence_record_ic.svg';
static const String helpDescriptionIcon =
'assets/icons/help_description_ic.svg';
static const String lightPulp = 'assets/icons/light_pulb.svg'; static const String lightPulp = 'assets/icons/light_pulb.svg';
static const String acDevice = 'assets/icons/ac_device.svg'; static const String acDevice = 'assets/icons/ac_device.svg';
@ -158,10 +166,12 @@ class Assets {
static const String unit = 'assets/icons/unit_icon.svg'; static const String unit = 'assets/icons/unit_icon.svg';
static const String villa = 'assets/icons/villa_icon.svg'; static const String villa = 'assets/icons/villa_icon.svg';
static const String iconEdit = 'assets/icons/icon_edit_icon.svg'; static const String iconEdit = 'assets/icons/icon_edit_icon.svg';
static const String textFieldSearch = 'assets/icons/textfield_search_icon.svg'; static const String textFieldSearch =
'assets/icons/textfield_search_icon.svg';
static const String roundedAddIcon = 'assets/icons/rounded_add_icon.svg'; static const String roundedAddIcon = 'assets/icons/rounded_add_icon.svg';
static const String addIcon = 'assets/icons/add_icon.svg'; static const String addIcon = 'assets/icons/add_icon.svg';
static const String smartThermostatIcon = 'assets/icons/smart_thermostat_icon.svg'; static const String smartThermostatIcon =
'assets/icons/smart_thermostat_icon.svg';
static const String smartLightIcon = 'assets/icons/smart_light_icon.svg'; static const String smartLightIcon = 'assets/icons/smart_light_icon.svg';
static const String presenceSensor = 'assets/icons/presence_sensor.svg'; static const String presenceSensor = 'assets/icons/presence_sensor.svg';
static const String Gang3SwitchIcon = 'assets/icons/3_Gang_switch_icon.svg'; static const String Gang3SwitchIcon = 'assets/icons/3_Gang_switch_icon.svg';
@ -209,7 +219,8 @@ class Assets {
//assets/icons/water_leak_normal.svg //assets/icons/water_leak_normal.svg
static const String waterLeakNormal = 'assets/icons/water_leak_normal.svg'; static const String waterLeakNormal = 'assets/icons/water_leak_normal.svg';
//assets/icons/water_leak_detected.svg //assets/icons/water_leak_detected.svg
static const String waterLeakDetected = 'assets/icons/water_leak_detected.svg'; static const String waterLeakDetected =
'assets/icons/water_leak_detected.svg';
//assets/icons/automation_records.svg //assets/icons/automation_records.svg
static const String automationRecords = 'assets/icons/automation_records.svg'; static const String automationRecords = 'assets/icons/automation_records.svg';
@ -280,13 +291,16 @@ class Assets {
'assets/icons/functions_icons/sensitivity.svg'; 'assets/icons/functions_icons/sensitivity.svg';
static const String assetsSensitivityOperationIcon = static const String assetsSensitivityOperationIcon =
'assets/icons/functions_icons/sesitivity_operation_icon.svg'; 'assets/icons/functions_icons/sesitivity_operation_icon.svg';
static const String assetsAcPower = 'assets/icons/functions_icons/ac_power.svg'; static const String assetsAcPower =
'assets/icons/functions_icons/ac_power.svg';
static const String assetsAcPowerOFF = static const String assetsAcPowerOFF =
'assets/icons/functions_icons/ac_power_off.svg'; 'assets/icons/functions_icons/ac_power_off.svg';
static const String assetsChildLock = static const String assetsChildLock =
'assets/icons/functions_icons/child_lock.svg'; 'assets/icons/functions_icons/child_lock.svg';
static const String assetsFreezing = 'assets/icons/functions_icons/freezing.svg'; static const String assetsFreezing =
static const String assetsFanSpeed = 'assets/icons/functions_icons/fan_speed.svg'; 'assets/icons/functions_icons/freezing.svg';
static const String assetsFanSpeed =
'assets/icons/functions_icons/fan_speed.svg';
static const String assetsAcCooling = static const String assetsAcCooling =
'assets/icons/functions_icons/ac_cooling.svg'; 'assets/icons/functions_icons/ac_cooling.svg';
static const String assetsAcHeating = static const String assetsAcHeating =
@ -295,7 +309,8 @@ class Assets {
'assets/icons/functions_icons/celsius_degrees.svg'; 'assets/icons/functions_icons/celsius_degrees.svg';
static const String assetsTempreture = static const String assetsTempreture =
'assets/icons/functions_icons/tempreture.svg'; 'assets/icons/functions_icons/tempreture.svg';
static const String assetsAcFanLow = 'assets/icons/functions_icons/ac_fan_low.svg'; static const String assetsAcFanLow =
'assets/icons/functions_icons/ac_fan_low.svg';
static const String assetsAcFanMiddle = static const String assetsAcFanMiddle =
'assets/icons/functions_icons/ac_fan_middle.svg'; 'assets/icons/functions_icons/ac_fan_middle.svg';
static const String assetsAcFanHigh = static const String assetsAcFanHigh =
@ -314,7 +329,8 @@ class Assets {
'assets/icons/functions_icons/far_detection.svg'; 'assets/icons/functions_icons/far_detection.svg';
static const String assetsFarDetectionFunction = static const String assetsFarDetectionFunction =
'assets/icons/functions_icons/far_detection_function.svg'; 'assets/icons/functions_icons/far_detection_function.svg';
static const String assetsIndicator = 'assets/icons/functions_icons/indicator.svg'; static const String assetsIndicator =
'assets/icons/functions_icons/indicator.svg';
static const String assetsMotionDetection = static const String assetsMotionDetection =
'assets/icons/functions_icons/motion_detection.svg'; 'assets/icons/functions_icons/motion_detection.svg';
static const String assetsMotionlessDetection = static const String assetsMotionlessDetection =
@ -327,7 +343,8 @@ class Assets {
'assets/icons/functions_icons/master_state.svg'; 'assets/icons/functions_icons/master_state.svg';
static const String assetsSwitchAlarmSound = static const String assetsSwitchAlarmSound =
'assets/icons/functions_icons/switch_alarm_sound.svg'; 'assets/icons/functions_icons/switch_alarm_sound.svg';
static const String assetsResetOff = 'assets/icons/functions_icons/reset_off.svg'; static const String assetsResetOff =
'assets/icons/functions_icons/reset_off.svg';
// Assets for automation_functions // Assets for automation_functions
static const String assetsCardUnlock = static const String assetsCardUnlock =
@ -371,13 +388,15 @@ class Assets {
static const String activeUser = 'assets/icons/active_user.svg'; static const String activeUser = 'assets/icons/active_user.svg';
static const String deActiveUser = 'assets/icons/deactive_user.svg'; static const String deActiveUser = 'assets/icons/deactive_user.svg';
static const String invitedIcon = 'assets/icons/invited_icon.svg'; static const String invitedIcon = 'assets/icons/invited_icon.svg';
static const String rectangleCheckBox = 'assets/icons/rectangle_check_box.png'; static const String rectangleCheckBox =
'assets/icons/rectangle_check_box.png';
static const String CheckBoxChecked = 'assets/icons/box_checked.png'; static const String CheckBoxChecked = 'assets/icons/box_checked.png';
static const String emptyBox = 'assets/icons/empty_box.png'; static const String emptyBox = 'assets/icons/empty_box.png';
static const String completeProcessIcon = static const String completeProcessIcon =
'assets/icons/compleate_process_icon.svg'; 'assets/icons/compleate_process_icon.svg';
static const String completedDoneIcon = 'assets/images/completed_done.svg'; static const String completedDoneIcon = 'assets/images/completed_done.svg';
static const String currentProcessIcon = 'assets/icons/current_process_icon.svg'; static const String currentProcessIcon =
'assets/icons/current_process_icon.svg';
static const String uncomplete_ProcessIcon = static const String uncomplete_ProcessIcon =
'assets/icons/uncompleate_process_icon.svg'; 'assets/icons/uncompleate_process_icon.svg';
static const String wrongProcessIcon = 'assets/icons/wrong_process_icon.svg'; static const String wrongProcessIcon = 'assets/icons/wrong_process_icon.svg';
@ -398,9 +417,11 @@ class Assets {
static const String successIcon = 'assets/icons/success_icon.svg'; static const String successIcon = 'assets/icons/success_icon.svg';
static const String spaceLocationIcon = 'assets/icons/spaseLocationIcon.svg'; static const String spaceLocationIcon = 'assets/icons/spaseLocationIcon.svg';
static const String scenesPlayIcon = 'assets/icons/scenesPlayIcon.png'; static const String scenesPlayIcon = 'assets/icons/scenesPlayIcon.png';
static const String scenesPlayIconCheck = 'assets/icons/scenesPlayIconCheck.png'; static const String scenesPlayIconCheck =
'assets/icons/scenesPlayIconCheck.png';
static const String presenceStateIcon = 'assets/icons/presence_state.svg'; static const String presenceStateIcon = 'assets/icons/presence_state.svg';
static const String currentDistanceIcon = 'assets/icons/current_distance_icon.svg'; static const String currentDistanceIcon =
'assets/icons/current_distance_icon.svg';
static const String farDetectionIcon = 'assets/icons/far_detection_icon.svg'; static const String farDetectionIcon = 'assets/icons/far_detection_icon.svg';
static const String motionDetectionSensitivityIcon = static const String motionDetectionSensitivityIcon =
@ -423,29 +444,44 @@ class Assets {
static const String cpsMode4 = 'assets/icons/cps_mode4.svg'; static const String cpsMode4 = 'assets/icons/cps_mode4.svg';
static const String closeToMotion = 'assets/icons/close_to_motion.svg'; static const String closeToMotion = 'assets/icons/close_to_motion.svg';
static const String farAwayMotion = 'assets/icons/far_away_motion.svg'; static const String farAwayMotion = 'assets/icons/far_away_motion.svg';
static const String communicationFault = 'assets/icons/communication_fault.svg'; static const String communicationFault =
'assets/icons/communication_fault.svg';
static const String radarFault = 'assets/icons/radar_fault.svg'; static const String radarFault = 'assets/icons/radar_fault.svg';
static const String selfTestingSuccess = 'assets/icons/self_testing_success.svg'; static const String selfTestingSuccess =
static const String selfTestingFailure = 'assets/icons/self_testing_failure.svg'; 'assets/icons/self_testing_success.svg';
static const String selfTestingTimeout = 'assets/icons/self_testing_timeout.svg'; static const String selfTestingFailure =
'assets/icons/self_testing_failure.svg';
static const String selfTestingTimeout =
'assets/icons/self_testing_timeout.svg';
static const String movingSpeed = 'assets/icons/moving_speed.svg'; static const String movingSpeed = 'assets/icons/moving_speed.svg';
static const String boundary = 'assets/icons/boundary.svg'; static const String boundary = 'assets/icons/boundary.svg';
static const String motionMeter = 'assets/icons/motion_meter.svg'; static const String motionMeter = 'assets/icons/motion_meter.svg';
static const String spatialStaticValue = 'assets/icons/spatial_static_value.svg'; static const String spatialStaticValue =
static const String spatialMotionValue = 'assets/icons/spatial_motion_value.svg'; 'assets/icons/spatial_static_value.svg';
static const String spatialMotionValue =
'assets/icons/spatial_motion_value.svg';
static const String presenceJudgementThrshold = static const String presenceJudgementThrshold =
'assets/icons/presence_judgement_threshold.svg'; 'assets/icons/presence_judgement_threshold.svg';
static const String spaceType = 'assets/icons/space_type.svg'; static const String spaceType = 'assets/icons/space_type.svg';
static const String sportsPara = 'assets/icons/sports_para.svg'; static const String sportsPara = 'assets/icons/sports_para.svg';
static const String sensitivityFeature1 = 'assets/icons/sensitivity_feature_1.svg'; static const String sensitivityFeature1 =
static const String sensitivityFeature2 = 'assets/icons/sensitivity_feature_2.svg'; 'assets/icons/sensitivity_feature_1.svg';
static const String sensitivityFeature3 = 'assets/icons/sensitivity_feature_3.svg'; static const String sensitivityFeature2 =
static const String sensitivityFeature4 = 'assets/icons/sensitivity_feature_4.svg'; 'assets/icons/sensitivity_feature_2.svg';
static const String sensitivityFeature5 = 'assets/icons/sensitivity_feature_5.svg'; static const String sensitivityFeature3 =
static const String sensitivityFeature6 = 'assets/icons/sensitivity_feature_6.svg'; 'assets/icons/sensitivity_feature_3.svg';
static const String sensitivityFeature7 = 'assets/icons/sensitivity_feature_7.svg'; static const String sensitivityFeature4 =
static const String sensitivityFeature8 = 'assets/icons/sensitivity_feature_8.svg'; 'assets/icons/sensitivity_feature_4.svg';
static const String sensitivityFeature9 = 'assets/icons/sensitivity_feature_9.svg'; static const String sensitivityFeature5 =
'assets/icons/sensitivity_feature_5.svg';
static const String sensitivityFeature6 =
'assets/icons/sensitivity_feature_6.svg';
static const String sensitivityFeature7 =
'assets/icons/sensitivity_feature_7.svg';
static const String sensitivityFeature8 =
'assets/icons/sensitivity_feature_8.svg';
static const String sensitivityFeature9 =
'assets/icons/sensitivity_feature_9.svg';
static const String deviceTagIcon = 'assets/icons/device_tag_ic.svg'; static const String deviceTagIcon = 'assets/icons/device_tag_ic.svg';
static const String targetConfirmTimeIcon = static const String targetConfirmTimeIcon =
'assets/icons/target_confirm_time_icon.svg'; 'assets/icons/target_confirm_time_icon.svg';
@ -453,10 +489,13 @@ class Assets {
static const String indentLevelIcon = 'assets/icons/indent_level_icon.svg'; static const String indentLevelIcon = 'assets/icons/indent_level_icon.svg';
static const String triggerLevelIcon = 'assets/icons/trigger_level_icon.svg'; static const String triggerLevelIcon = 'assets/icons/trigger_level_icon.svg';
static const String blankCalendar = 'assets/icons/blank_calendar.svg'; static const String blankCalendar = 'assets/icons/blank_calendar.svg';
static const String refreshStatusIcon = 'assets/icons/refresh_status_icon.svg'; static const String refreshStatusIcon =
static const String energyConsumedIcon = 'assets/icons/energy_consumed_icon.svg'; 'assets/icons/refresh_status_icon.svg';
static const String energyConsumedIcon =
'assets/icons/energy_consumed_icon.svg';
static const String closeSettingsIcon = 'assets/icons/close_settings_icon.svg'; static const String closeSettingsIcon =
'assets/icons/close_settings_icon.svg';
static const String editNameIconSettings = static const String editNameIconSettings =
'assets/icons/edit_name_icon_settings.svg'; 'assets/icons/edit_name_icon_settings.svg';
@ -476,4 +515,6 @@ class Assets {
'assets/icons/empty_energy_management_per_device.svg'; 'assets/icons/empty_energy_management_per_device.svg';
static const String emptyHeatmap = 'assets/icons/empty_heatmap.svg'; static const String emptyHeatmap = 'assets/icons/empty_heatmap.svg';
static const String emptyRangeOfAqi = 'assets/icons/empty_range_of_aqi.svg'; static const String emptyRangeOfAqi = 'assets/icons/empty_range_of_aqi.svg';
static const String homeIcon = 'assets/icons/home_icon.svg';
static const String groupIcon = 'assets/icons/group_icon.svg';
} }

View File

@ -1,8 +0,0 @@
abstract final class SafeDivisionHelper {
const SafeDivisionHelper._();
static double divide(num value, num divider) {
if (divider == 0) return 0;
return value / divider;
}
}