mirror of
https://github.com/SyncrowIOT/web.git
synced 2025-07-16 01:56:24 +00:00
Compare commits
24 Commits
SP-1708-FE
...
bb846f797f
Author | SHA1 | Date | |
---|---|---|---|
bb846f797f | |||
e234c9f3b2 | |||
bcd0ae4a2a | |||
cebce2ce7f | |||
97e3fb68bf | |||
a4024067c7 | |||
50f8158830 | |||
72af55ef98 | |||
b888f516e2 | |||
c1e61ee61d | |||
7750290be4 | |||
7f26c773a7 | |||
1adbae6735 | |||
ede2da6632 | |||
b06e4bd2ba | |||
0847cb8a41 | |||
818bdee745 | |||
0a022d8a8d | |||
f33b3e8bd2 | |||
8f0eb88567 | |||
19739c6e4d | |||
9f86b8d638 | |||
037895844a | |||
e6fe9f35b0 |
15
assets/icons/group_icon.svg
Normal file
15
assets/icons/group_icon.svg
Normal 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 |
4
assets/icons/home_icon.svg
Normal file
4
assets/icons/home_icon.svg
Normal 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 |
@ -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: () {})
|
||||
],
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
))
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
@ -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,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
@ -2,302 +2,86 @@ import 'package:flutter/material.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/pages/access_management/bloc/access_state.dart';
|
||||
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:syncrow_web/pages/access_management/booking_system/view/booking_page.dart';
|
||||
import 'package:syncrow_web/pages/access_management/view/access_overview_content.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/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/web_layout/web_scaffold.dart';
|
||||
|
||||
class AccessManagementPage extends StatelessWidget with HelperResponsiveLayout {
|
||||
class AccessManagementPage extends StatefulWidget {
|
||||
const AccessManagementPage({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);
|
||||
State<AccessManagementPage> createState() => _AccessManagementPageState();
|
||||
}
|
||||
|
||||
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,
|
||||
appBarTitle: Text(
|
||||
'Access Management',
|
||||
style: ResponsiveTextTheme.of(context).deviceManagementTitle,
|
||||
),
|
||||
rightBody: const NavigateHomeGridView(),
|
||||
scaffoldBody: 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,
|
||||
centerBody: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
TextButton(
|
||||
onPressed: () => _switchPage(0),
|
||||
child: Text(
|
||||
'Create Visitor Password ',
|
||||
style: context.textTheme.titleSmall!
|
||||
.copyWith(color: Colors.white, fontSize: 12),
|
||||
)),
|
||||
'Access Overview',
|
||||
style: context.textTheme.titleMedium?.copyWith(
|
||||
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(
|
||||
// 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),
|
||||
// )),
|
||||
// ),
|
||||
],
|
||||
rightBody: const NavigateHomeGridView(),
|
||||
scaffoldBody: PageView(
|
||||
controller: _pageController,
|
||||
physics: const NeverScrollableScrollPhysics(),
|
||||
children: const [
|
||||
AccessOverviewContent(),
|
||||
BookingPage(),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
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());
|
||||
},
|
||||
),
|
||||
],
|
||||
);
|
||||
void _switchPage(int index) {
|
||||
setState(() => _currentPageIndex = index);
|
||||
_pageController.jumpToPage(index);
|
||||
}
|
||||
}
|
||||
|
289
lib/pages/access_management/view/access_overview_content.dart
Normal file
289
lib/pages/access_management/view/access_overview_content.dart
Normal 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());
|
||||
},
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
@ -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,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -15,7 +15,8 @@ import 'package:syncrow_web/utils/constants/assets.dart';
|
||||
import 'package:syncrow_web/utils/extension/build_context_x.dart';
|
||||
import 'package:syncrow_web/utils/helpers/responsice_layout_helper/responsive_layout_helper.dart';
|
||||
|
||||
class AcDeviceBatchControlView extends StatelessWidget with HelperResponsiveLayout {
|
||||
class AcDeviceBatchControlView extends StatelessWidget
|
||||
with HelperResponsiveLayout {
|
||||
const AcDeviceBatchControlView({super.key, required this.devicesIds});
|
||||
|
||||
final List<String> devicesIds;
|
||||
@ -51,7 +52,7 @@ class AcDeviceBatchControlView extends StatelessWidget with HelperResponsiveLayo
|
||||
deviceId: devicesIds.first,
|
||||
code: 'switch',
|
||||
value: state.status.acSwitch,
|
||||
label: 'ThermoState',
|
||||
label: 'Thermostat',
|
||||
icon: Assets.ac,
|
||||
onChange: (value) {
|
||||
context.read<AcBloc>().add(AcBatchControlEvent(
|
||||
@ -100,8 +101,8 @@ class AcDeviceBatchControlView extends StatelessWidget with HelperResponsiveLayo
|
||||
),
|
||||
Text(
|
||||
'h',
|
||||
style:
|
||||
context.textTheme.bodySmall!.copyWith(color: ColorsManager.blackColor),
|
||||
style: context.textTheme.bodySmall!
|
||||
.copyWith(color: ColorsManager.blackColor),
|
||||
),
|
||||
Text(
|
||||
'30',
|
||||
@ -148,7 +149,8 @@ class AcDeviceBatchControlView extends StatelessWidget with HelperResponsiveLayo
|
||||
callFactoryReset: () {
|
||||
context.read<AcBloc>().add(AcFactoryResetEvent(
|
||||
deviceId: state.status.uuid,
|
||||
factoryResetModel: FactoryResetModel(devicesUuid: devicesIds),
|
||||
factoryResetModel:
|
||||
FactoryResetModel(devicesUuid: devicesIds),
|
||||
));
|
||||
},
|
||||
),
|
||||
|
@ -68,6 +68,7 @@ class DeviceManagementBody extends StatelessWidget with HelperResponsiveLayout {
|
||||
children: [
|
||||
Expanded(child: SpaceTreeView(
|
||||
onSelect: () {
|
||||
context.read<DeviceManagementBloc>().add(ResetFilters());
|
||||
context.read<DeviceManagementBloc>().add(FetchDevices(context));
|
||||
},
|
||||
)),
|
||||
|
@ -17,6 +17,7 @@ class CalibrateCompletedDialog extends StatelessWidget {
|
||||
@override
|
||||
Widget build(_) {
|
||||
return AlertDialog(
|
||||
backgroundColor: ColorsManager.whiteColors,
|
||||
contentPadding: EdgeInsets.zero,
|
||||
content: SizedBox(
|
||||
height: 250,
|
||||
|
@ -277,6 +277,32 @@ class SmartPowerDeviceControl extends StatelessWidget
|
||||
totalConsumption: 10000,
|
||||
date: blocProvider.formattedDate,
|
||||
),
|
||||
EnergyConsumptionPage(
|
||||
formattedDate:
|
||||
'${blocProvider.dateTime!.day}/${blocProvider.dateTime!.month}/${blocProvider.dateTime!.year} ${blocProvider.endChartDate}',
|
||||
onTap: () {
|
||||
blocProvider.add(SelectDateEvent(context: context));
|
||||
},
|
||||
widget: blocProvider.dateSwitcher(),
|
||||
chartData: blocProvider.energyDataList.isNotEmpty
|
||||
? blocProvider.energyDataList
|
||||
: [
|
||||
EnergyData('12:00 AM', 4.0),
|
||||
EnergyData('01:00 AM', 6.5),
|
||||
EnergyData('02:00 AM', 3.8),
|
||||
EnergyData('03:00 AM', 3.2),
|
||||
EnergyData('04:00 AM', 6.0),
|
||||
EnergyData('05:00 AM', 3.4),
|
||||
EnergyData('06:00 AM', 5.2),
|
||||
EnergyData('07:00 AM', 3.5),
|
||||
EnergyData('08:00 AM', 6.8),
|
||||
EnergyData('09:00 AM', 5.6),
|
||||
EnergyData('10:00 AM', 3.9),
|
||||
EnergyData('11:00 AM', 4.0),
|
||||
],
|
||||
totalConsumption: 10000,
|
||||
date: blocProvider.formattedDate,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
@ -232,7 +232,6 @@ class ScheduleBloc extends Bloc<ScheduleEvent, ScheduleState> {
|
||||
selectedDays: List.filled(7, false),
|
||||
functionOn: false,
|
||||
isEditing: false,
|
||||
countdownRemaining: Duration.zero,
|
||||
));
|
||||
} else {
|
||||
emit(ScheduleLoaded(
|
||||
|
@ -32,113 +32,114 @@ class SpaceDropdown extends StatelessWidget {
|
||||
color: ColorsManager.blackColor,
|
||||
),
|
||||
),
|
||||
SizedBox(
|
||||
child: Container(
|
||||
DropdownButton2<String>(
|
||||
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,
|
||||
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(
|
||||
color: ColorsManager.whiteColors,
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
),
|
||||
child: DropdownButton2<String>(
|
||||
underline: const SizedBox(),
|
||||
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: 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,
|
||||
),
|
||||
),
|
||||
),
|
||||
menuItemStyleData: const MenuItemStyleData(
|
||||
height: 60,
|
||||
),
|
||||
),
|
||||
],
|
||||
|
@ -121,7 +121,8 @@ class _RoutineViewCardState extends State<RoutineViewCard> {
|
||||
child: SizedBox(
|
||||
width: 16,
|
||||
height: 16,
|
||||
child: CircularProgressIndicator(strokeWidth: 2),
|
||||
child:
|
||||
CircularProgressIndicator(strokeWidth: 2),
|
||||
),
|
||||
),
|
||||
)
|
||||
@ -159,8 +160,9 @@ class _RoutineViewCardState extends State<RoutineViewCard> {
|
||||
height: iconSize,
|
||||
width: iconSize,
|
||||
fit: BoxFit.contain,
|
||||
errorBuilder: (context, error, stackTrace) =>
|
||||
Image.asset(
|
||||
errorBuilder:
|
||||
(context, error, stackTrace) =>
|
||||
Image.asset(
|
||||
Assets.logo,
|
||||
height: iconSize,
|
||||
width: iconSize,
|
||||
@ -203,7 +205,8 @@ class _RoutineViewCardState extends State<RoutineViewCard> {
|
||||
maxLines: 1,
|
||||
style: context.textTheme.bodySmall?.copyWith(
|
||||
color: ColorsManager.blackColor,
|
||||
fontSize: widget.isSmallScreenSize(context) ? 10 : 12,
|
||||
fontSize:
|
||||
widget.isSmallScreenSize(context) ? 10 : 12,
|
||||
),
|
||||
),
|
||||
if (widget.spaceName != '')
|
||||
@ -222,8 +225,9 @@ class _RoutineViewCardState extends State<RoutineViewCard> {
|
||||
maxLines: 1,
|
||||
style: context.textTheme.bodySmall?.copyWith(
|
||||
color: ColorsManager.blackColor,
|
||||
fontSize:
|
||||
widget.isSmallScreenSize(context) ? 10 : 12,
|
||||
fontSize: widget.isSmallScreenSize(context)
|
||||
? 10
|
||||
: 12,
|
||||
),
|
||||
),
|
||||
],
|
||||
|
@ -7,6 +7,8 @@ import 'package:syncrow_web/pages/space_management_v2/modules/communities/data/s
|
||||
import 'package:syncrow_web/pages/space_management_v2/modules/communities/domain/params/load_communities_param.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/products/data/services/remote_products_service.dart';
|
||||
import 'package:syncrow_web/pages/space_management_v2/modules/products/presentation/bloc/products_bloc.dart';
|
||||
import 'package:syncrow_web/pages/space_management_v2/modules/space_details/data/services/remote_space_details_service.dart';
|
||||
import 'package:syncrow_web/pages/space_management_v2/modules/space_details/presentation/bloc/space_details_bloc.dart';
|
||||
import 'package:syncrow_web/services/api/http_service.dart';
|
||||
@ -33,6 +35,11 @@ class SpaceManagementPage extends StatelessWidget {
|
||||
RemoteSpaceDetailsService(httpService: HTTPService()),
|
||||
),
|
||||
),
|
||||
BlocProvider(
|
||||
create: (context) => ProductsBloc(
|
||||
RemoteProductsService(HTTPService()),
|
||||
),
|
||||
),
|
||||
],
|
||||
child: WebScaffold(
|
||||
appBarTitle: Text(
|
||||
|
@ -1,9 +1,9 @@
|
||||
import 'package:dio/dio.dart';
|
||||
import 'package:syncrow_web/pages/space_management_v2/modules/products/domain/models/product.dart';
|
||||
import 'package:syncrow_web/pages/space_management_v2/modules/products/domain/params/load_products_param.dart';
|
||||
import 'package:syncrow_web/pages/space_management_v2/modules/products/domain/services/products_service.dart';
|
||||
import 'package:syncrow_web/services/api/api_exception.dart';
|
||||
import 'package:syncrow_web/services/api/http_service.dart';
|
||||
import 'package:syncrow_web/utils/constants/api_const.dart';
|
||||
|
||||
class RemoteProductsService implements ProductsService {
|
||||
const RemoteProductsService(this._httpService);
|
||||
@ -13,17 +13,14 @@ class RemoteProductsService implements ProductsService {
|
||||
static const _defaultErrorMessage = 'Failed to load devices';
|
||||
|
||||
@override
|
||||
Future<List<Product>> getProducts(LoadProductsParam param) async {
|
||||
Future<List<Product>> getProducts() async {
|
||||
try {
|
||||
final response = await _httpService.get(
|
||||
path: 'devices',
|
||||
queryParameters: {
|
||||
'spaceUuid': param.spaceUuid,
|
||||
if (param.type != null) 'type': param.type,
|
||||
if (param.status != null) 'status': param.status,
|
||||
},
|
||||
path: ApiEndpoints.listProducts,
|
||||
expectedResponseModel: (data) {
|
||||
return (data as List)
|
||||
final json = data as Map<String, dynamic>;
|
||||
final products = json['data'] as List<dynamic>;
|
||||
return products
|
||||
.map((e) => Product.fromJson(e as Map<String, dynamic>))
|
||||
.toList();
|
||||
},
|
||||
|
@ -3,16 +3,18 @@ import 'package:equatable/equatable.dart';
|
||||
class Product extends Equatable {
|
||||
final String uuid;
|
||||
final String name;
|
||||
|
||||
final String productType;
|
||||
const Product({
|
||||
required this.uuid,
|
||||
required this.name,
|
||||
required this.productType,
|
||||
});
|
||||
|
||||
factory Product.fromJson(Map<String, dynamic> json) {
|
||||
return Product(
|
||||
uuid: json['uuid'] as String,
|
||||
name: json['name'] as String,
|
||||
uuid: json['uuid'] as String? ?? '',
|
||||
name: json['name'] as String? ?? '',
|
||||
productType: json['prodType'] as String? ?? '',
|
||||
);
|
||||
}
|
||||
|
||||
@ -20,9 +22,10 @@ class Product extends Equatable {
|
||||
return {
|
||||
'uuid': uuid,
|
||||
'name': name,
|
||||
'productType': productType,
|
||||
};
|
||||
}
|
||||
|
||||
@override
|
||||
List<Object?> get props => [uuid, name];
|
||||
List<Object?> get props => [uuid, name, productType];
|
||||
}
|
||||
|
@ -1,11 +0,0 @@
|
||||
class LoadProductsParam {
|
||||
final String spaceUuid;
|
||||
final String? type;
|
||||
final String? status;
|
||||
|
||||
const LoadProductsParam({
|
||||
required this.spaceUuid,
|
||||
this.type,
|
||||
this.status,
|
||||
});
|
||||
}
|
@ -1,6 +1,5 @@
|
||||
import 'package:syncrow_web/pages/space_management_v2/modules/products/domain/models/product.dart';
|
||||
import 'package:syncrow_web/pages/space_management_v2/modules/products/domain/params/load_products_param.dart';
|
||||
|
||||
abstract class ProductsService {
|
||||
Future<List<Product>> getProducts(LoadProductsParam param);
|
||||
Future<List<Product>> getProducts();
|
||||
}
|
||||
|
@ -1,7 +1,6 @@
|
||||
import 'package:bloc/bloc.dart';
|
||||
import 'package:equatable/equatable.dart';
|
||||
import 'package:syncrow_web/pages/space_management_v2/modules/products/domain/models/product.dart';
|
||||
import 'package:syncrow_web/pages/space_management_v2/modules/products/domain/params/load_products_param.dart';
|
||||
import 'package:syncrow_web/pages/space_management_v2/modules/products/domain/services/products_service.dart';
|
||||
import 'package:syncrow_web/services/api/api_exception.dart';
|
||||
|
||||
@ -9,20 +8,20 @@ part 'products_event.dart';
|
||||
part 'products_state.dart';
|
||||
|
||||
class ProductsBloc extends Bloc<ProductsEvent, ProductsState> {
|
||||
final ProductsService _deviceService;
|
||||
|
||||
ProductsBloc(this._deviceService) : super(ProductsInitial()) {
|
||||
ProductsBloc(this._productsService) : super(ProductsInitial()) {
|
||||
on<LoadProducts>(_onLoadProducts);
|
||||
}
|
||||
|
||||
final ProductsService _productsService;
|
||||
|
||||
Future<void> _onLoadProducts(
|
||||
LoadProducts event,
|
||||
Emitter<ProductsState> emit,
|
||||
) async {
|
||||
emit(ProductsLoading());
|
||||
try {
|
||||
final devices = await _deviceService.getProducts(event.param);
|
||||
emit(ProductsLoaded(devices));
|
||||
final products = await _productsService.getProducts();
|
||||
emit(ProductsLoaded(products));
|
||||
} on APIException catch (e) {
|
||||
emit(ProductsFailure(e.message));
|
||||
} catch (e) {
|
||||
|
@ -8,10 +8,5 @@ sealed class ProductsEvent extends Equatable {
|
||||
}
|
||||
|
||||
final class LoadProducts extends ProductsEvent {
|
||||
const LoadProducts(this.param);
|
||||
|
||||
final LoadProductsParam param;
|
||||
|
||||
@override
|
||||
List<Object> get props => [param];
|
||||
const LoadProducts();
|
||||
}
|
||||
|
@ -21,10 +21,10 @@ final class ProductsLoaded extends ProductsState {
|
||||
}
|
||||
|
||||
final class ProductsFailure extends ProductsState {
|
||||
final String message;
|
||||
final String errorMessage;
|
||||
|
||||
const ProductsFailure(this.message);
|
||||
const ProductsFailure(this.errorMessage);
|
||||
|
||||
@override
|
||||
List<Object> get props => [message];
|
||||
List<Object> get props => [errorMessage];
|
||||
}
|
||||
|
@ -21,7 +21,7 @@ class SpaceDetailsModel extends Equatable {
|
||||
factory SpaceDetailsModel.empty() => const SpaceDetailsModel(
|
||||
uuid: '',
|
||||
spaceName: '',
|
||||
icon: Assets.villa,
|
||||
icon: Assets.location,
|
||||
productAllocations: [],
|
||||
subspaces: [],
|
||||
);
|
||||
|
@ -8,10 +8,14 @@ class SpaceDetailsActionButtons extends StatelessWidget {
|
||||
super.key,
|
||||
required this.onSave,
|
||||
required this.onCancel,
|
||||
this.saveButtonLabel = 'OK',
|
||||
this.cancelButtonLabel = 'Cancel',
|
||||
});
|
||||
|
||||
final VoidCallback onCancel;
|
||||
final VoidCallback? onSave;
|
||||
final String saveButtonLabel;
|
||||
final String cancelButtonLabel;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
@ -27,10 +31,7 @@ class SpaceDetailsActionButtons extends StatelessWidget {
|
||||
}
|
||||
|
||||
Widget _buildCancelButton(BuildContext context) {
|
||||
return CancelButton(
|
||||
onPressed: onCancel,
|
||||
label: 'Cancel',
|
||||
);
|
||||
return CancelButton(onPressed: onCancel, label: cancelButtonLabel);
|
||||
}
|
||||
|
||||
Widget _buildSaveButton() {
|
||||
@ -39,7 +40,7 @@ class SpaceDetailsActionButtons extends StatelessWidget {
|
||||
borderRadius: 10,
|
||||
backgroundColor: ColorsManager.secondaryColor,
|
||||
foregroundColor: ColorsManager.whiteColors,
|
||||
child: const Text('OK'),
|
||||
child: Text(saveButtonLabel),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -1,9 +1,13 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_svg/svg.dart';
|
||||
import 'package:syncrow_web/common/edit_chip.dart';
|
||||
import 'package:syncrow_web/pages/space_management_v2/modules/space_details/domain/models/space_details_model.dart';
|
||||
import 'package:syncrow_web/pages/space_management_v2/modules/space_details/presentation/widgets/button_content_widget.dart';
|
||||
import 'package:syncrow_web/pages/space_management_v2/modules/tags/presentation/widgets/assign_tags_dialog.dart';
|
||||
import 'package:syncrow_web/utils/color_manager.dart';
|
||||
import 'package:syncrow_web/utils/constants/assets.dart';
|
||||
import 'package:syncrow_web/utils/enum/device_types.dart';
|
||||
import 'package:syncrow_web/utils/extension/build_context_x.dart';
|
||||
|
||||
class SpaceDetailsDevicesBox extends StatelessWidget {
|
||||
const SpaceDetailsDevicesBox({
|
||||
@ -35,46 +39,39 @@ class SpaceDetailsDevicesBox extends StatelessWidget {
|
||||
spacing: 8.0,
|
||||
runSpacing: 8.0,
|
||||
children: [
|
||||
// Combine tags from spaceModel and subspaces
|
||||
// ...TagHelper.groupTags([
|
||||
// ...?tags,
|
||||
// ...?subspaces?.expand((subspace) => subspace.tags ?? [])
|
||||
// ]).entries.map(
|
||||
// (entry) => Chip(
|
||||
// avatar: SizedBox(
|
||||
// width: 24,
|
||||
// height: 24,
|
||||
// child: SvgPicture.asset(
|
||||
// entry.key.icon ?? 'assets/icons/gateway.svg',
|
||||
// fit: BoxFit.contain,
|
||||
// ),
|
||||
// ),
|
||||
// label: Text(
|
||||
// 'x${entry.value}',
|
||||
// style: Theme.of(context)
|
||||
// .textTheme
|
||||
// .bodySmall
|
||||
// ?.copyWith(color: ColorsManager.spaceColor),
|
||||
// ),
|
||||
// backgroundColor: ColorsManager.whiteColors,
|
||||
// shape: RoundedRectangleBorder(
|
||||
// borderRadius: BorderRadius.circular(16),
|
||||
// side: const BorderSide(
|
||||
// color: ColorsManager.spaceColor,
|
||||
// ),
|
||||
// ),
|
||||
// ),
|
||||
// ),
|
||||
|
||||
...productAllocations.map(
|
||||
(entry) => Chip(
|
||||
avatar: SizedBox(
|
||||
width: 24,
|
||||
height: 24,
|
||||
child: SvgPicture.asset(
|
||||
_getDeviceIcon(entry.product.productType),
|
||||
fit: BoxFit.contain,
|
||||
),
|
||||
),
|
||||
label: Text(
|
||||
entry.product.productType,
|
||||
style: context.textTheme.bodySmall
|
||||
?.copyWith(color: ColorsManager.spaceColor),
|
||||
),
|
||||
backgroundColor: ColorsManager.whiteColors,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
side: const BorderSide(
|
||||
color: ColorsManager.spaceColor,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
EditChip(
|
||||
onTap: () {},
|
||||
onTap: () => _showAssignTagsDialog(context),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
} else {
|
||||
return TextButton(
|
||||
onPressed: () {},
|
||||
onPressed: () => _showAssignTagsDialog(context),
|
||||
style: TextButton.styleFrom(
|
||||
padding: EdgeInsets.zero,
|
||||
),
|
||||
@ -83,10 +80,44 @@ class SpaceDetailsDevicesBox extends StatelessWidget {
|
||||
child: ButtonContentWidget(
|
||||
svgAssets: Assets.addIcon,
|
||||
label: 'Add Devices',
|
||||
// disabled: isTagsAndSubspaceModelDisabled,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
void _showAssignTagsDialog(BuildContext context) {
|
||||
showDialog<void>(
|
||||
context: context,
|
||||
builder: (context) => AssignTagsDialog(space: space),
|
||||
);
|
||||
}
|
||||
|
||||
String _getDeviceIcon(String productType) =>
|
||||
switch (devicesTypesMap[productType]) {
|
||||
DeviceType.LightBulb => Assets.lightBulb,
|
||||
DeviceType.CeilingSensor => Assets.sensors,
|
||||
DeviceType.AC => Assets.ac,
|
||||
DeviceType.DoorLock => Assets.doorLock,
|
||||
DeviceType.Curtain => Assets.curtain,
|
||||
DeviceType.ThreeGang => Assets.gangSwitch,
|
||||
DeviceType.Gateway => Assets.gateway,
|
||||
DeviceType.OneGang => Assets.oneGang,
|
||||
DeviceType.TwoGang => Assets.twoGang,
|
||||
DeviceType.WH => Assets.waterHeater,
|
||||
DeviceType.DoorSensor => Assets.openCloseDoor,
|
||||
DeviceType.GarageDoor => Assets.openedDoor,
|
||||
DeviceType.WaterLeak => Assets.waterLeakNormal,
|
||||
DeviceType.Curtain2 => Assets.curtainIcon,
|
||||
DeviceType.Blind => Assets.curtainIcon,
|
||||
DeviceType.WallSensor => Assets.sensors,
|
||||
DeviceType.DS => Assets.openCloseDoor,
|
||||
DeviceType.OneTouch => Assets.gangSwitch,
|
||||
DeviceType.TowTouch => Assets.gangSwitch,
|
||||
DeviceType.ThreeTouch => Assets.gangSwitch,
|
||||
DeviceType.NCPS => Assets.sensors,
|
||||
DeviceType.PC => Assets.powerClamp,
|
||||
DeviceType.Other => Assets.blackLogo,
|
||||
null => Assets.blackLogo,
|
||||
};
|
||||
}
|
||||
|
@ -0,0 +1,87 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:syncrow_web/pages/space_management_v2/modules/products/data/services/remote_products_service.dart';
|
||||
import 'package:syncrow_web/pages/space_management_v2/modules/products/presentation/bloc/products_bloc.dart';
|
||||
import 'package:syncrow_web/pages/space_management_v2/modules/tags/presentation/widgets/product_type_card.dart';
|
||||
import 'package:syncrow_web/services/api/http_service.dart';
|
||||
import 'package:syncrow_web/utils/color_manager.dart';
|
||||
import 'package:syncrow_web/utils/extension/build_context_x.dart';
|
||||
|
||||
class AddDeviceTypeWidget extends StatelessWidget {
|
||||
const AddDeviceTypeWidget({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final size = MediaQuery.of(context).size;
|
||||
final crossAxisCount = switch (context.screenWidth) {
|
||||
> 1200 => 8,
|
||||
> 800 => 5,
|
||||
_ => 3,
|
||||
};
|
||||
|
||||
return BlocProvider(
|
||||
create: (_) => ProductsBloc(RemoteProductsService(HTTPService()))
|
||||
..add(const LoadProducts()),
|
||||
child: Builder(
|
||||
builder: (context) => AlertDialog(
|
||||
title: const Text('Add Devices'),
|
||||
backgroundColor: ColorsManager.whiteColors,
|
||||
content: BlocBuilder<ProductsBloc, ProductsState>(
|
||||
builder: (context, state) {
|
||||
return switch (state) {
|
||||
ProductsInitial() => const Center(
|
||||
child: CircularProgressIndicator(),
|
||||
),
|
||||
ProductsLoading() => const Center(
|
||||
child: CircularProgressIndicator(),
|
||||
),
|
||||
ProductsLoaded(:final products) => SingleChildScrollView(
|
||||
child: Container(
|
||||
width: size.width * 0.9,
|
||||
height: size.height * 0.65,
|
||||
decoration: BoxDecoration(
|
||||
color: ColorsManager.textFieldGreyColor,
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
child: Column(
|
||||
children: [
|
||||
const SizedBox(height: 16),
|
||||
Expanded(
|
||||
child: GridView.builder(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 20,
|
||||
),
|
||||
shrinkWrap: true,
|
||||
gridDelegate:
|
||||
SliverGridDelegateWithFixedCrossAxisCount(
|
||||
crossAxisCount: crossAxisCount,
|
||||
mainAxisSpacing: 6,
|
||||
crossAxisSpacing: 4,
|
||||
childAspectRatio: 0.8,
|
||||
),
|
||||
itemCount: products.length,
|
||||
itemBuilder: (context, index) => ProductTypeCard(
|
||||
product: products[index],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
ProductsFailure(:final errorMessage) => Center(
|
||||
child: Text(
|
||||
errorMessage,
|
||||
style: context.textTheme.bodyMedium?.copyWith(
|
||||
color: context.theme.colorScheme.error,
|
||||
),
|
||||
),
|
||||
),
|
||||
};
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
@ -0,0 +1,39 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:syncrow_web/pages/space_management_v2/modules/space_details/domain/models/space_details_model.dart';
|
||||
import 'package:syncrow_web/pages/space_management_v2/modules/space_details/presentation/widgets/space_details_action_buttons.dart';
|
||||
import 'package:syncrow_web/pages/space_management_v2/modules/tags/presentation/widgets/add_device_type_widget.dart';
|
||||
import 'package:syncrow_web/pages/space_management_v2/modules/tags/presentation/widgets/assign_tags_table.dart';
|
||||
import 'package:syncrow_web/utils/extension/build_context_x.dart';
|
||||
|
||||
class AssignTagsDialog extends StatelessWidget {
|
||||
const AssignTagsDialog({required this.space, super.key});
|
||||
|
||||
final SpaceDetailsModel space;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return AlertDialog(
|
||||
title: const Text('Assign Tags'),
|
||||
content: ConstrainedBox(
|
||||
constraints: BoxConstraints(
|
||||
maxWidth: context.screenWidth * 0.6,
|
||||
minWidth: context.screenWidth * 0.6,
|
||||
maxHeight: context.screenHeight * 0.8,
|
||||
),
|
||||
child: AssignTagsTable(productAllocations: space.productAllocations),
|
||||
),
|
||||
actions: [
|
||||
SpaceDetailsActionButtons(
|
||||
onSave: () {},
|
||||
onCancel: () {
|
||||
showDialog<void>(
|
||||
context: context,
|
||||
builder: (context) => const AddDeviceTypeWidget(),
|
||||
);
|
||||
},
|
||||
cancelButtonLabel: 'Add New Device',
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
@ -0,0 +1,170 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:syncrow_web/common/dialog_dropdown.dart';
|
||||
import 'package:syncrow_web/pages/space_management_v2/modules/space_details/domain/models/space_details_model.dart';
|
||||
import 'package:syncrow_web/pages/space_management_v2/modules/tags/domain/models/tag.dart';
|
||||
import 'package:syncrow_web/pages/space_management_v2/modules/tags/presentation/widgets/product_tag_field.dart';
|
||||
import 'package:syncrow_web/utils/color_manager.dart';
|
||||
import 'package:syncrow_web/utils/extension/build_context_x.dart';
|
||||
import 'package:uuid/uuid.dart';
|
||||
|
||||
class AssignTagsTable extends StatefulWidget {
|
||||
const AssignTagsTable({
|
||||
required this.productAllocations,
|
||||
super.key,
|
||||
});
|
||||
|
||||
final List<ProductAllocation> productAllocations;
|
||||
|
||||
@override
|
||||
State<AssignTagsTable> createState() => _AssignTagsTableState();
|
||||
}
|
||||
|
||||
class _AssignTagsTableState extends State<AssignTagsTable> {
|
||||
List<TextEditingController> _controllers = [];
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_controllers = List.generate(
|
||||
widget.productAllocations.length,
|
||||
(index) => TextEditingController(
|
||||
text: widget.productAllocations[index].product.name,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
for (final controller in _controllers) {
|
||||
controller.dispose();
|
||||
}
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
DataColumn _buildDataColumn(String label) {
|
||||
return DataColumn(label: Text(label, style: context.textTheme.bodyMedium));
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return ClipRRect(
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
child: DataTable(
|
||||
headingRowColor: WidgetStateProperty.all(ColorsManager.dataHeaderGrey),
|
||||
key: ValueKey(widget.productAllocations.length),
|
||||
border: TableBorder.all(
|
||||
color: ColorsManager.dataHeaderGrey,
|
||||
width: 1,
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
),
|
||||
columns: [
|
||||
_buildDataColumn('#'),
|
||||
_buildDataColumn('Device'),
|
||||
_buildDataColumn('Tag'),
|
||||
_buildDataColumn('Location'),
|
||||
],
|
||||
rows: widget.productAllocations.isEmpty
|
||||
? [
|
||||
DataRow(
|
||||
cells: [
|
||||
DataCell(
|
||||
Center(
|
||||
child: Text(
|
||||
'No Devices Available',
|
||||
style: context.textTheme.bodyMedium?.copyWith(
|
||||
color: ColorsManager.lightGrayColor,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
DataCell.empty,
|
||||
DataCell.empty,
|
||||
DataCell.empty,
|
||||
],
|
||||
),
|
||||
]
|
||||
: List.generate(widget.productAllocations.length, (index) {
|
||||
final productAllocation = widget.productAllocations[index];
|
||||
final controller = _controllers[index];
|
||||
|
||||
return DataRow(
|
||||
cells: [
|
||||
DataCell(Text((index + 1).toString())),
|
||||
DataCell(
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Expanded(
|
||||
child: Text(
|
||||
productAllocation.product.name,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
)),
|
||||
const SizedBox(width: 10),
|
||||
Container(
|
||||
width: 20,
|
||||
height: 20,
|
||||
decoration: BoxDecoration(
|
||||
shape: BoxShape.circle,
|
||||
border: Border.all(
|
||||
color: ColorsManager.lightGrayColor,
|
||||
width: 1,
|
||||
),
|
||||
),
|
||||
child: IconButton(
|
||||
icon: const Icon(
|
||||
Icons.close,
|
||||
color: ColorsManager.lightGreyColor,
|
||||
size: 16,
|
||||
),
|
||||
onPressed: () {
|
||||
// TODO: Delete the product allocation
|
||||
},
|
||||
tooltip: 'Delete Tag',
|
||||
padding: EdgeInsets.zero,
|
||||
constraints: const BoxConstraints(),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
DataCell(
|
||||
Container(
|
||||
alignment: Alignment.centerLeft,
|
||||
width: double.infinity,
|
||||
child: ProductTagField(
|
||||
key: ValueKey('dropdown_${const Uuid().v4()}_$index'),
|
||||
productName: productAllocation.product.uuid,
|
||||
initialValue: null,
|
||||
onSelected: (value) {
|
||||
controller.text = value.name;
|
||||
},
|
||||
items: const [
|
||||
Tag(
|
||||
uuid: '',
|
||||
name: 'Tag',
|
||||
createdAt: '',
|
||||
updatedAt: '',
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
DataCell(
|
||||
SizedBox(
|
||||
width: double.infinity,
|
||||
child: DialogDropdown(
|
||||
items: const [],
|
||||
// items: widget.locations,
|
||||
selectedValue: productAllocation.tag.name.isEmpty
|
||||
? 'Main Space'
|
||||
: productAllocation.tag.name,
|
||||
onSelected: (value) {},
|
||||
)),
|
||||
),
|
||||
],
|
||||
);
|
||||
}),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
@ -0,0 +1,209 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:syncrow_web/pages/space_management_v2/modules/tags/domain/models/tag.dart';
|
||||
import 'package:syncrow_web/utils/color_manager.dart';
|
||||
import 'package:syncrow_web/utils/extension/build_context_x.dart';
|
||||
|
||||
class ProductTagField extends StatefulWidget {
|
||||
final List<Tag> items;
|
||||
final ValueChanged<Tag> onSelected;
|
||||
final Tag? initialValue;
|
||||
final String productName;
|
||||
|
||||
const ProductTagField({
|
||||
super.key,
|
||||
required this.items,
|
||||
required this.onSelected,
|
||||
this.initialValue,
|
||||
required this.productName,
|
||||
});
|
||||
|
||||
@override
|
||||
State<ProductTagField> createState() => _ProductTagFieldState();
|
||||
}
|
||||
|
||||
class _ProductTagFieldState extends State<ProductTagField> {
|
||||
bool _isOpen = false;
|
||||
OverlayEntry? _overlayEntry;
|
||||
final TextEditingController _controller = TextEditingController();
|
||||
final FocusNode _focusNode = FocusNode();
|
||||
List<Tag> _filteredItems = [];
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_controller.text = widget.initialValue?.name ?? '';
|
||||
|
||||
_filterItems();
|
||||
|
||||
_focusNode.addListener(() {
|
||||
if (!_focusNode.hasFocus) {
|
||||
final selectedTag = _filteredItems.firstWhere(
|
||||
(tag) => tag.name == _controller.text,
|
||||
orElse: () => Tag(
|
||||
name: _controller.text,
|
||||
uuid: '',
|
||||
createdAt: '',
|
||||
updatedAt: '',
|
||||
),
|
||||
);
|
||||
widget.onSelected(selectedTag);
|
||||
_closeDropdown();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_controller.dispose();
|
||||
_focusNode.dispose();
|
||||
_overlayEntry = null;
|
||||
_isOpen = false;
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
void _filterItems() => setState(() => _filteredItems = widget.items);
|
||||
|
||||
void _toggleDropdown() {
|
||||
if (_isOpen) {
|
||||
_closeDropdown();
|
||||
} else {
|
||||
_openDropdown();
|
||||
}
|
||||
}
|
||||
|
||||
void _openDropdown() {
|
||||
_overlayEntry = _createOverlayEntry();
|
||||
Overlay.of(context).insert(_overlayEntry!);
|
||||
_isOpen = true;
|
||||
}
|
||||
|
||||
void _closeDropdown() {
|
||||
if (_isOpen && _overlayEntry != null) {
|
||||
_overlayEntry!.remove();
|
||||
_overlayEntry = null;
|
||||
_isOpen = false;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return GestureDetector(
|
||||
onTap: () => FocusScope.of(context).unfocus(),
|
||||
behavior: HitTestBehavior.opaque,
|
||||
child: Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 12.0, vertical: 8.0),
|
||||
decoration: BoxDecoration(
|
||||
border: Border.all(color: ColorsManager.transparentColor),
|
||||
borderRadius: BorderRadius.circular(8.0),
|
||||
),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Expanded(
|
||||
child: TextFormField(
|
||||
controller: _controller,
|
||||
focusNode: _focusNode,
|
||||
onFieldSubmitted: (value) {
|
||||
final selectedTag = _filteredItems.firstWhere(
|
||||
(tag) => tag.name == value,
|
||||
orElse: () =>
|
||||
Tag(name: value, uuid: '', createdAt: '', updatedAt: ''));
|
||||
widget.onSelected(selectedTag);
|
||||
_closeDropdown();
|
||||
},
|
||||
onTapOutside: (event) {
|
||||
widget.onSelected(_filteredItems.firstWhere(
|
||||
(tag) => tag.name == _controller.text,
|
||||
orElse: () => Tag(
|
||||
name: _controller.text,
|
||||
uuid: '',
|
||||
createdAt: '',
|
||||
updatedAt: '')));
|
||||
_closeDropdown();
|
||||
},
|
||||
style: context.textTheme.bodyMedium,
|
||||
decoration: const InputDecoration(
|
||||
hintText: 'Enter or Select a tag',
|
||||
border: InputBorder.none,
|
||||
),
|
||||
),
|
||||
),
|
||||
GestureDetector(
|
||||
onTap: _toggleDropdown,
|
||||
child: const Icon(Icons.arrow_drop_down),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
OverlayEntry _createOverlayEntry() {
|
||||
final renderBox = context.findRenderObject()! as RenderBox;
|
||||
final size = renderBox.size;
|
||||
final offset = renderBox.localToGlobal(Offset.zero);
|
||||
|
||||
return OverlayEntry(
|
||||
builder: (context) {
|
||||
return GestureDetector(
|
||||
onTap: _closeDropdown,
|
||||
behavior: HitTestBehavior.translucent,
|
||||
child: Stack(
|
||||
children: [
|
||||
Positioned(
|
||||
left: offset.dx,
|
||||
top: offset.dy + size.height,
|
||||
width: size.width,
|
||||
child: Material(
|
||||
elevation: 4.0,
|
||||
child: Container(
|
||||
color: ColorsManager.whiteColors,
|
||||
constraints: const BoxConstraints(maxHeight: 200.0),
|
||||
child: StatefulBuilder(
|
||||
builder: (context, setStateDropdown) {
|
||||
return ListView.builder(
|
||||
shrinkWrap: true,
|
||||
itemCount: _filteredItems.length,
|
||||
itemBuilder: (context, index) {
|
||||
final tag = _filteredItems[index];
|
||||
|
||||
return Container(
|
||||
decoration: const BoxDecoration(
|
||||
border: Border(
|
||||
bottom: BorderSide(
|
||||
color: ColorsManager.lightGrayBorderColor,
|
||||
width: 1.0,
|
||||
),
|
||||
),
|
||||
),
|
||||
child: ListTile(
|
||||
title: Text(
|
||||
tag.name,
|
||||
style: context.textTheme.bodyMedium?.copyWith(
|
||||
color: ColorsManager.textPrimaryColor,
|
||||
),
|
||||
),
|
||||
onTap: () {
|
||||
_controller.text = tag.name;
|
||||
widget.onSelected(tag);
|
||||
setState(() {
|
||||
_filteredItems.remove(tag);
|
||||
});
|
||||
_closeDropdown();
|
||||
},
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
@ -0,0 +1,61 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:syncrow_web/pages/space_management_v2/modules/products/domain/models/product.dart';
|
||||
import 'package:syncrow_web/pages/spaces_management/all_spaces/widgets/counter_widget.dart';
|
||||
import 'package:syncrow_web/pages/spaces_management/tag_model/widgets/device_icon_widget.dart';
|
||||
import 'package:syncrow_web/utils/color_manager.dart';
|
||||
import 'package:syncrow_web/utils/extension/build_context_x.dart';
|
||||
|
||||
class ProductTypeCard extends StatelessWidget {
|
||||
const ProductTypeCard({super.key, required this.product});
|
||||
final Product product;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Card(
|
||||
elevation: 2,
|
||||
color: ColorsManager.whiteColors,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(4.0),
|
||||
child: Column(
|
||||
spacing: 8,
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
Expanded(
|
||||
child: DeviceIconWidget(
|
||||
icon: product.name,
|
||||
),
|
||||
),
|
||||
_buildName(context, product.name),
|
||||
CounterWidget(
|
||||
isCreate: false,
|
||||
initialCount: 0,
|
||||
onCountChanged: (newCount) {},
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildName(BuildContext context, String name) {
|
||||
return Expanded(
|
||||
child: SizedBox(
|
||||
height: 35,
|
||||
child: Text(
|
||||
name,
|
||||
style: context.textTheme.bodySmall?.copyWith(
|
||||
color: ColorsManager.blackColor,
|
||||
),
|
||||
textAlign: TextAlign.center,
|
||||
maxLines: 2,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
@ -0,0 +1,65 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:syncrow_web/utils/color_manager.dart';
|
||||
|
||||
class ProductTypeCardCounter extends StatefulWidget {
|
||||
const ProductTypeCardCounter({
|
||||
super.key,
|
||||
required this.onIncrement,
|
||||
required this.onDecrement,
|
||||
required this.count,
|
||||
});
|
||||
|
||||
final int count;
|
||||
|
||||
final void Function() onIncrement;
|
||||
final void Function() onDecrement;
|
||||
|
||||
@override
|
||||
State<ProductTypeCardCounter> createState() => _ProductTypeCardCounterState();
|
||||
}
|
||||
|
||||
class _ProductTypeCardCounterState extends State<ProductTypeCardCounter> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final theme = Theme.of(context);
|
||||
|
||||
return Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 5),
|
||||
decoration: BoxDecoration(
|
||||
color: ColorsManager.counterBackgroundColor,
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
),
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
spacing: 8,
|
||||
children: [
|
||||
_buildCounterButton(
|
||||
Icons.remove,
|
||||
widget.onDecrement,
|
||||
),
|
||||
Text(
|
||||
widget.count.toString(),
|
||||
style: theme.textTheme.bodyLarge?.copyWith(
|
||||
color: ColorsManager.spaceColor,
|
||||
),
|
||||
),
|
||||
_buildCounterButton(Icons.add, widget.onIncrement),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildCounterButton(
|
||||
IconData icon,
|
||||
VoidCallback onPressed,
|
||||
) {
|
||||
return GestureDetector(
|
||||
onTap: onPressed,
|
||||
child: Icon(
|
||||
icon,
|
||||
color: ColorsManager.spaceColor.withValues(alpha: 0.3),
|
||||
size: 18,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
@ -21,7 +21,6 @@ import 'package:syncrow_web/utils/snack_bar.dart';
|
||||
|
||||
class VisitorPasswordBloc
|
||||
extends Bloc<VisitorPasswordEvent, VisitorPasswordState> {
|
||||
|
||||
VisitorPasswordBloc() : super(VisitorPasswordInitial()) {
|
||||
on<SelectUsageFrequency>(selectUsageFrequency);
|
||||
on<FetchDevice>(_onFetchDevice);
|
||||
@ -87,6 +86,9 @@ class VisitorPasswordBloc
|
||||
SelectTimeVisitorPassword event,
|
||||
Emitter<VisitorPasswordState> emit,
|
||||
) async {
|
||||
// Ensure expirationTimeTimeStamp has a value
|
||||
effectiveTimeTimeStamp ??= DateTime.now().millisecondsSinceEpoch ~/ 1000;
|
||||
|
||||
final DateTime? picked = await showDatePicker(
|
||||
context: event.context,
|
||||
initialDate: DateTime.now(),
|
||||
@ -94,86 +96,124 @@ class VisitorPasswordBloc
|
||||
lastDate: DateTime.now().add(const Duration(days: 5095)),
|
||||
);
|
||||
|
||||
if (picked != null) {
|
||||
final TimeOfDay? timePicked = await showTimePicker(
|
||||
context: event.context,
|
||||
initialTime: TimeOfDay.now(),
|
||||
builder: (context, child) {
|
||||
return Theme(
|
||||
data: ThemeData.light().copyWith(
|
||||
colorScheme: const ColorScheme.light(
|
||||
primary: ColorsManager.primaryColor,
|
||||
onSurface: Colors.black,
|
||||
),
|
||||
buttonTheme: const ButtonThemeData(
|
||||
colorScheme: ColorScheme.light(
|
||||
primary: Colors.green,
|
||||
),
|
||||
),
|
||||
if (picked == null) return;
|
||||
|
||||
final TimeOfDay? timePicked = await showTimePicker(
|
||||
context: event.context,
|
||||
initialTime: TimeOfDay.now(),
|
||||
builder: (context, child) {
|
||||
return Theme(
|
||||
data: ThemeData.light().copyWith(
|
||||
colorScheme: const ColorScheme.light(
|
||||
primary: ColorsManager.primaryColor,
|
||||
onSurface: Colors.black,
|
||||
),
|
||||
child: child!,
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
if (timePicked != null) {
|
||||
final selectedDateTime = DateTime(
|
||||
picked.year,
|
||||
picked.month,
|
||||
picked.day,
|
||||
timePicked.hour,
|
||||
timePicked.minute,
|
||||
),
|
||||
child: child!,
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
final selectedTimestamp =
|
||||
selectedDateTime.millisecondsSinceEpoch ~/ 1000;
|
||||
if (timePicked == null) return;
|
||||
|
||||
if (event.isStart) {
|
||||
if (expirationTimeTimeStamp != null &&
|
||||
selectedTimestamp > expirationTimeTimeStamp!) {
|
||||
CustomSnackBar.displaySnackBar(
|
||||
final selectedDateTime = DateTime(
|
||||
picked.year,
|
||||
picked.month,
|
||||
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.',
|
||||
);
|
||||
return;
|
||||
}
|
||||
if(selectedTimestamp < DateTime.now().millisecondsSinceEpoch ~/ 1000) {
|
||||
if(selectedTimestamp < DateTime.now().millisecondsSinceEpoch ~/ 1000) {
|
||||
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;
|
||||
}
|
||||
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());
|
||||
),
|
||||
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;
|
||||
}
|
||||
|
||||
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(
|
||||
@ -213,7 +253,7 @@ class VisitorPasswordBloc
|
||||
FetchDevice event, Emitter<VisitorPasswordState> emit) async {
|
||||
try {
|
||||
emit(DeviceLoaded());
|
||||
final projectUuid = await ProjectManager.getProjectUUID() ?? '';
|
||||
final projectUuid = await ProjectManager.getProjectUUID() ?? '';
|
||||
|
||||
data = await AccessMangApi().fetchDoorLockDeviceList(projectUuid);
|
||||
emit(TableLoaded(data));
|
||||
|
@ -14,7 +14,8 @@ class Assets {
|
||||
static const String rightLine = 'assets/images/right_line.png';
|
||||
static const String google = 'assets/images/google.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 accessIcon = 'assets/images/access_icon.svg';
|
||||
static const String spaseManagementIcon =
|
||||
@ -33,7 +34,8 @@ class Assets {
|
||||
static const String emptyTable = 'assets/images/empty_table.svg';
|
||||
|
||||
// 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 acPowerOff = 'assets/icons/ac_power_off.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';
|
||||
static const String doorlockNormalOpen =
|
||||
'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 =
|
||||
'assets/icons/automation_functions/remote_unlock_via_app.svg';
|
||||
static const String doubleLock =
|
||||
'assets/icons/automation_functions/double_lock.svg';
|
||||
static const String selfTestResult =
|
||||
'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 =
|
||||
'assets/icons/automation_functions/presence_state.svg';
|
||||
static const String currentTemp =
|
||||
'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 =
|
||||
'assets/icons/automation_functions/residual_electricity.svg';
|
||||
static const String hijackAlarm =
|
||||
@ -99,12 +104,15 @@ class Assets {
|
||||
|
||||
// Presence Sensor Assets
|
||||
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 illuminanceRecordIcon =
|
||||
'assets/icons/illuminance_record_ic.svg';
|
||||
static const String presenceRecordIcon = 'assets/icons/presence_record_ic.svg';
|
||||
static const String helpDescriptionIcon = 'assets/icons/help_description_ic.svg';
|
||||
static const String presenceRecordIcon =
|
||||
'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 acDevice = 'assets/icons/ac_device.svg';
|
||||
@ -158,10 +166,12 @@ class Assets {
|
||||
static const String unit = 'assets/icons/unit_icon.svg';
|
||||
static const String villa = 'assets/icons/villa_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 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 presenceSensor = 'assets/icons/presence_sensor.svg';
|
||||
static const String Gang3SwitchIcon = 'assets/icons/3_Gang_switch_icon.svg';
|
||||
@ -209,7 +219,8 @@ class Assets {
|
||||
//assets/icons/water_leak_normal.svg
|
||||
static const String waterLeakNormal = 'assets/icons/water_leak_normal.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
|
||||
static const String automationRecords = 'assets/icons/automation_records.svg';
|
||||
@ -280,13 +291,16 @@ class Assets {
|
||||
'assets/icons/functions_icons/sensitivity.svg';
|
||||
static const String assetsSensitivityOperationIcon =
|
||||
'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 =
|
||||
'assets/icons/functions_icons/ac_power_off.svg';
|
||||
static const String assetsChildLock =
|
||||
'assets/icons/functions_icons/child_lock.svg';
|
||||
static const String assetsFreezing = 'assets/icons/functions_icons/freezing.svg';
|
||||
static const String assetsFanSpeed = 'assets/icons/functions_icons/fan_speed.svg';
|
||||
static const String assetsFreezing =
|
||||
'assets/icons/functions_icons/freezing.svg';
|
||||
static const String assetsFanSpeed =
|
||||
'assets/icons/functions_icons/fan_speed.svg';
|
||||
static const String assetsAcCooling =
|
||||
'assets/icons/functions_icons/ac_cooling.svg';
|
||||
static const String assetsAcHeating =
|
||||
@ -295,7 +309,8 @@ class Assets {
|
||||
'assets/icons/functions_icons/celsius_degrees.svg';
|
||||
static const String assetsTempreture =
|
||||
'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 =
|
||||
'assets/icons/functions_icons/ac_fan_middle.svg';
|
||||
static const String assetsAcFanHigh =
|
||||
@ -314,7 +329,8 @@ class Assets {
|
||||
'assets/icons/functions_icons/far_detection.svg';
|
||||
static const String assetsFarDetectionFunction =
|
||||
'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 =
|
||||
'assets/icons/functions_icons/motion_detection.svg';
|
||||
static const String assetsMotionlessDetection =
|
||||
@ -327,7 +343,8 @@ class Assets {
|
||||
'assets/icons/functions_icons/master_state.svg';
|
||||
static const String assetsSwitchAlarmSound =
|
||||
'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
|
||||
static const String assetsCardUnlock =
|
||||
@ -371,13 +388,15 @@ class Assets {
|
||||
static const String activeUser = 'assets/icons/active_user.svg';
|
||||
static const String deActiveUser = 'assets/icons/deactive_user.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 emptyBox = 'assets/icons/empty_box.png';
|
||||
static const String completeProcessIcon =
|
||||
'assets/icons/compleate_process_icon.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 =
|
||||
'assets/icons/uncompleate_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 spaceLocationIcon = 'assets/icons/spaseLocationIcon.svg';
|
||||
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 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 motionDetectionSensitivityIcon =
|
||||
@ -423,29 +444,44 @@ class Assets {
|
||||
static const String cpsMode4 = 'assets/icons/cps_mode4.svg';
|
||||
static const String closeToMotion = 'assets/icons/close_to_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 selfTestingSuccess = 'assets/icons/self_testing_success.svg';
|
||||
static const String selfTestingFailure = 'assets/icons/self_testing_failure.svg';
|
||||
static const String selfTestingTimeout = 'assets/icons/self_testing_timeout.svg';
|
||||
static const String selfTestingSuccess =
|
||||
'assets/icons/self_testing_success.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 boundary = 'assets/icons/boundary.svg';
|
||||
static const String motionMeter = 'assets/icons/motion_meter.svg';
|
||||
static const String spatialStaticValue = 'assets/icons/spatial_static_value.svg';
|
||||
static const String spatialMotionValue = 'assets/icons/spatial_motion_value.svg';
|
||||
static const String spatialStaticValue =
|
||||
'assets/icons/spatial_static_value.svg';
|
||||
static const String spatialMotionValue =
|
||||
'assets/icons/spatial_motion_value.svg';
|
||||
static const String presenceJudgementThrshold =
|
||||
'assets/icons/presence_judgement_threshold.svg';
|
||||
static const String spaceType = 'assets/icons/space_type.svg';
|
||||
static const String sportsPara = 'assets/icons/sports_para.svg';
|
||||
static const String sensitivityFeature1 = 'assets/icons/sensitivity_feature_1.svg';
|
||||
static const String sensitivityFeature2 = 'assets/icons/sensitivity_feature_2.svg';
|
||||
static const String sensitivityFeature3 = 'assets/icons/sensitivity_feature_3.svg';
|
||||
static const String sensitivityFeature4 = 'assets/icons/sensitivity_feature_4.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 sensitivityFeature1 =
|
||||
'assets/icons/sensitivity_feature_1.svg';
|
||||
static const String sensitivityFeature2 =
|
||||
'assets/icons/sensitivity_feature_2.svg';
|
||||
static const String sensitivityFeature3 =
|
||||
'assets/icons/sensitivity_feature_3.svg';
|
||||
static const String sensitivityFeature4 =
|
||||
'assets/icons/sensitivity_feature_4.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 targetConfirmTimeIcon =
|
||||
'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 triggerLevelIcon = 'assets/icons/trigger_level_icon.svg';
|
||||
static const String blankCalendar = 'assets/icons/blank_calendar.svg';
|
||||
static const String refreshStatusIcon = 'assets/icons/refresh_status_icon.svg';
|
||||
static const String energyConsumedIcon = 'assets/icons/energy_consumed_icon.svg';
|
||||
static const String refreshStatusIcon =
|
||||
'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 =
|
||||
'assets/icons/edit_name_icon_settings.svg';
|
||||
@ -476,4 +515,6 @@ class Assets {
|
||||
'assets/icons/empty_energy_management_per_device.svg';
|
||||
static const String emptyHeatmap = 'assets/icons/empty_heatmap.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';
|
||||
}
|
||||
|
Reference in New Issue
Block a user