mirror of
https://github.com/SyncrowIOT/web.git
synced 2025-07-16 18:16:34 +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:flutter_bloc/flutter_bloc.dart';
|
||||||
import 'package:syncrow_web/pages/access_management/bloc/access_bloc.dart';
|
import 'package:syncrow_web/pages/access_management/bloc/access_bloc.dart';
|
||||||
import 'package:syncrow_web/pages/access_management/bloc/access_event.dart';
|
import 'package:syncrow_web/pages/access_management/bloc/access_event.dart';
|
||||||
import 'package:syncrow_web/pages/access_management/bloc/access_state.dart';
|
import 'package:syncrow_web/pages/access_management/booking_system/view/booking_page.dart';
|
||||||
import 'package:syncrow_web/pages/common/buttons/default_button.dart';
|
import 'package:syncrow_web/pages/access_management/view/access_overview_content.dart';
|
||||||
import 'package:syncrow_web/pages/common/buttons/search_reset_buttons.dart';
|
|
||||||
import 'package:syncrow_web/pages/common/custom_table.dart';
|
|
||||||
import 'package:syncrow_web/pages/common/date_time_widget.dart';
|
|
||||||
import 'package:syncrow_web/pages/common/filter/filter_widget.dart';
|
|
||||||
import 'package:syncrow_web/pages/common/text_field/custom_web_textfield.dart';
|
|
||||||
import 'package:syncrow_web/pages/device_managment/shared/navigate_home_grid_view.dart';
|
import 'package:syncrow_web/pages/device_managment/shared/navigate_home_grid_view.dart';
|
||||||
import 'package:syncrow_web/pages/visitor_password/view/visitor_password_dialog.dart';
|
|
||||||
// import 'package:syncrow_web/utils/color_manager.dart';
|
|
||||||
import 'package:syncrow_web/utils/constants/app_enum.dart';
|
|
||||||
import 'package:syncrow_web/utils/constants/assets.dart';
|
|
||||||
import 'package:syncrow_web/utils/extension/build_context_x.dart';
|
import 'package:syncrow_web/utils/extension/build_context_x.dart';
|
||||||
import 'package:syncrow_web/utils/helpers/responsice_layout_helper/responsive_layout_helper.dart';
|
import 'package:syncrow_web/utils/helpers/responsice_layout_helper/responsive_layout_helper.dart';
|
||||||
import 'package:syncrow_web/utils/style.dart';
|
|
||||||
import 'package:syncrow_web/utils/theme/responsive_text_theme.dart';
|
import 'package:syncrow_web/utils/theme/responsive_text_theme.dart';
|
||||||
import 'package:syncrow_web/web_layout/web_scaffold.dart';
|
import 'package:syncrow_web/web_layout/web_scaffold.dart';
|
||||||
|
|
||||||
class AccessManagementPage extends StatelessWidget with HelperResponsiveLayout {
|
class AccessManagementPage extends StatefulWidget {
|
||||||
const AccessManagementPage({super.key});
|
const AccessManagementPage({super.key});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
State<AccessManagementPage> createState() => _AccessManagementPageState();
|
||||||
final isLargeScreen = isLargeScreenSize(context);
|
}
|
||||||
final isSmallScreen = isSmallScreenSize(context);
|
|
||||||
final isHalfMediumScreen = isHafMediumScreenSize(context);
|
|
||||||
final padding =
|
|
||||||
isLargeScreen ? const EdgeInsets.all(30) : const EdgeInsets.all(15);
|
|
||||||
|
|
||||||
return WebScaffold(
|
class _AccessManagementPageState extends State<AccessManagementPage>
|
||||||
|
with HelperResponsiveLayout {
|
||||||
|
final PageController _pageController = PageController(initialPage: 0);
|
||||||
|
int _currentPageIndex = 0;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
_pageController.dispose();
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return BlocProvider(
|
||||||
|
create: (BuildContext context) => AccessBloc()..add(FetchTableData()),
|
||||||
|
child: WebScaffold(
|
||||||
enableMenuSidebar: false,
|
enableMenuSidebar: false,
|
||||||
appBarTitle: Text(
|
appBarTitle: Text(
|
||||||
'Access Management',
|
'Access Management',
|
||||||
style: ResponsiveTextTheme.of(context).deviceManagementTitle,
|
style: ResponsiveTextTheme.of(context).deviceManagementTitle,
|
||||||
),
|
),
|
||||||
rightBody: const NavigateHomeGridView(),
|
centerBody: Row(
|
||||||
scaffoldBody: BlocProvider(
|
mainAxisSize: MainAxisSize.min,
|
||||||
create: (BuildContext context) =>
|
children: [
|
||||||
AccessBloc()..add(FetchTableData()),
|
TextButton(
|
||||||
child: BlocConsumer<AccessBloc, AccessState>(
|
onPressed: () => _switchPage(0),
|
||||||
listener: (context, state) {},
|
|
||||||
builder: (context, state) {
|
|
||||||
final accessBloc = BlocProvider.of<AccessBloc>(context);
|
|
||||||
final filteredData = accessBloc.filteredData;
|
|
||||||
return state is AccessLoaded
|
|
||||||
? const Center(child: CircularProgressIndicator())
|
|
||||||
: Container(
|
|
||||||
padding: padding,
|
|
||||||
height: MediaQuery.of(context).size.height,
|
|
||||||
width: MediaQuery.of(context).size.width,
|
|
||||||
child: Column(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: [
|
|
||||||
FilterWidget(
|
|
||||||
size: MediaQuery.of(context).size,
|
|
||||||
tabs: accessBloc.tabs,
|
|
||||||
selectedIndex: accessBloc.selectedIndex,
|
|
||||||
onTabChanged: (index) {
|
|
||||||
accessBloc.add(TabChangedEvent(index));
|
|
||||||
},
|
|
||||||
),
|
|
||||||
const SizedBox(height: 20),
|
|
||||||
if (isSmallScreen || isHalfMediumScreen)
|
|
||||||
_buildSmallSearchFilters(context, accessBloc)
|
|
||||||
else
|
|
||||||
_buildNormalSearchWidgets(context, accessBloc),
|
|
||||||
const SizedBox(height: 20),
|
|
||||||
_buildVisitorAdminPasswords(context, accessBloc),
|
|
||||||
const SizedBox(height: 20),
|
|
||||||
Expanded(
|
|
||||||
child: DynamicTable(
|
|
||||||
tableName: 'AccessManagement',
|
|
||||||
uuidIndex: 1,
|
|
||||||
withSelectAll: true,
|
|
||||||
isEmpty: filteredData.isEmpty,
|
|
||||||
withCheckBox: false,
|
|
||||||
size: MediaQuery.of(context).size,
|
|
||||||
cellDecoration: containerDecoration,
|
|
||||||
headers: const [
|
|
||||||
'Name',
|
|
||||||
'Access Type',
|
|
||||||
'Access Start',
|
|
||||||
'Access End',
|
|
||||||
'Accessible Device',
|
|
||||||
'Authorizer',
|
|
||||||
'Authorization Date & Time',
|
|
||||||
'Access Status'
|
|
||||||
],
|
|
||||||
data: filteredData.map((item) {
|
|
||||||
return [
|
|
||||||
item.passwordName,
|
|
||||||
item.passwordType.value,
|
|
||||||
accessBloc
|
|
||||||
.timestampToDate(item.effectiveTime),
|
|
||||||
accessBloc
|
|
||||||
.timestampToDate(item.invalidTime),
|
|
||||||
item.deviceName.toString(),
|
|
||||||
item.authorizerEmail.toString(),
|
|
||||||
accessBloc
|
|
||||||
.timestampToDate(item.invalidTime),
|
|
||||||
item.passwordStatus.value,
|
|
||||||
];
|
|
||||||
}).toList(),
|
|
||||||
)),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
})));
|
|
||||||
}
|
|
||||||
|
|
||||||
Wrap _buildVisitorAdminPasswords(
|
|
||||||
BuildContext context, AccessBloc accessBloc) {
|
|
||||||
return Wrap(
|
|
||||||
spacing: 10,
|
|
||||||
runSpacing: 10,
|
|
||||||
children: [
|
|
||||||
Container(
|
|
||||||
width: 205,
|
|
||||||
height: 42,
|
|
||||||
decoration: containerDecoration,
|
|
||||||
child: DefaultButton(
|
|
||||||
onPressed: () {
|
|
||||||
showDialog(
|
|
||||||
context: context,
|
|
||||||
barrierDismissible: false,
|
|
||||||
builder: (BuildContext context) {
|
|
||||||
return const VisitorPasswordDialog();
|
|
||||||
},
|
|
||||||
).then((v) {
|
|
||||||
if (v != null) {
|
|
||||||
accessBloc.add(FetchTableData());
|
|
||||||
}
|
|
||||||
});
|
|
||||||
},
|
|
||||||
borderRadius: 8,
|
|
||||||
child: Text(
|
child: Text(
|
||||||
'Create Visitor Password ',
|
'Access Overview',
|
||||||
style: context.textTheme.titleSmall!
|
style: context.textTheme.titleMedium?.copyWith(
|
||||||
.copyWith(color: Colors.white, fontSize: 12),
|
color: _currentPageIndex == 0 ? Colors.white : Colors.grey,
|
||||||
)),
|
fontWeight: _currentPageIndex == 0
|
||||||
|
? FontWeight.w700
|
||||||
|
: FontWeight.w400,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
TextButton(
|
||||||
|
onPressed: () => _switchPage(1),
|
||||||
|
child: Text(
|
||||||
|
'Booking System',
|
||||||
|
style: context.textTheme.titleMedium?.copyWith(
|
||||||
|
color: _currentPageIndex == 1 ? Colors.white : Colors.grey,
|
||||||
|
fontWeight: _currentPageIndex == 1
|
||||||
|
? FontWeight.w700
|
||||||
|
: FontWeight.w400,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
// Container(
|
rightBody: const NavigateHomeGridView(),
|
||||||
// width: 133,
|
scaffoldBody: PageView(
|
||||||
// height: 42,
|
controller: _pageController,
|
||||||
// decoration: containerDecoration,
|
physics: const NeverScrollableScrollPhysics(),
|
||||||
// child: DefaultButton(
|
children: const [
|
||||||
// borderRadius: 8,
|
AccessOverviewContent(),
|
||||||
// backgroundColor: ColorsManager.whiteColors,
|
BookingPage(),
|
||||||
// child: Text(
|
],
|
||||||
// 'Admin Password',
|
),
|
||||||
// style: context.textTheme.titleSmall!
|
),
|
||||||
// .copyWith(color: Colors.black, fontSize: 12),
|
|
||||||
// )),
|
|
||||||
// ),
|
|
||||||
],
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Row _buildNormalSearchWidgets(BuildContext context, AccessBloc accessBloc) {
|
void _switchPage(int index) {
|
||||||
// TimeOfDay _selectedTime = TimeOfDay.now();
|
setState(() => _currentPageIndex = index);
|
||||||
|
_pageController.jumpToPage(index);
|
||||||
return Row(
|
|
||||||
mainAxisSize: MainAxisSize.min,
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.end,
|
|
||||||
textBaseline: TextBaseline.ideographic,
|
|
||||||
children: [
|
|
||||||
SizedBox(
|
|
||||||
width: 250,
|
|
||||||
child: CustomWebTextField(
|
|
||||||
controller: accessBloc.passwordName,
|
|
||||||
height: 43,
|
|
||||||
isRequired: false,
|
|
||||||
textFieldName: 'Name',
|
|
||||||
description: '',
|
|
||||||
onSubmitted: (value) {
|
|
||||||
accessBloc.add(FilterDataEvent(
|
|
||||||
emailAuthorizer:
|
|
||||||
accessBloc.emailAuthorizer.text.toLowerCase(),
|
|
||||||
selectedTabIndex:
|
|
||||||
BlocProvider.of<AccessBloc>(context).selectedIndex,
|
|
||||||
passwordName: accessBloc.passwordName.text.toLowerCase(),
|
|
||||||
startTime: accessBloc.effectiveTimeTimeStamp,
|
|
||||||
endTime: accessBloc.expirationTimeTimeStamp));
|
|
||||||
},
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(width: 15),
|
|
||||||
SizedBox(
|
|
||||||
width: 250,
|
|
||||||
child: CustomWebTextField(
|
|
||||||
controller: accessBloc.emailAuthorizer,
|
|
||||||
height: 43,
|
|
||||||
isRequired: false,
|
|
||||||
textFieldName: 'Authorizer',
|
|
||||||
description: '',
|
|
||||||
onSubmitted: (value) {
|
|
||||||
accessBloc.add(FilterDataEvent(
|
|
||||||
emailAuthorizer:
|
|
||||||
accessBloc.emailAuthorizer.text.toLowerCase(),
|
|
||||||
selectedTabIndex:
|
|
||||||
BlocProvider.of<AccessBloc>(context).selectedIndex,
|
|
||||||
passwordName: accessBloc.passwordName.text.toLowerCase(),
|
|
||||||
startTime: accessBloc.effectiveTimeTimeStamp,
|
|
||||||
endTime: accessBloc.expirationTimeTimeStamp));
|
|
||||||
},
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(width: 15),
|
|
||||||
SizedBox(
|
|
||||||
child: DateTimeWebWidget(
|
|
||||||
icon: Assets.calendarIcon,
|
|
||||||
isRequired: false,
|
|
||||||
title: 'Access Time',
|
|
||||||
size: MediaQuery.of(context).size,
|
|
||||||
endTime: () {
|
|
||||||
accessBloc.add(SelectTime(context: context, isStart: false));
|
|
||||||
},
|
|
||||||
startTime: () {
|
|
||||||
accessBloc.add(SelectTime(context: context, isStart: true));
|
|
||||||
},
|
|
||||||
firstString: BlocProvider.of<AccessBloc>(context).startTime,
|
|
||||||
secondString: BlocProvider.of<AccessBloc>(context).endTime,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(width: 15),
|
|
||||||
SearchResetButtons(
|
|
||||||
onSearch: () {
|
|
||||||
accessBloc.add(FilterDataEvent(
|
|
||||||
emailAuthorizer: accessBloc.emailAuthorizer.text.toLowerCase(),
|
|
||||||
selectedTabIndex:
|
|
||||||
BlocProvider.of<AccessBloc>(context).selectedIndex,
|
|
||||||
passwordName: accessBloc.passwordName.text.toLowerCase(),
|
|
||||||
startTime: accessBloc.effectiveTimeTimeStamp,
|
|
||||||
endTime: accessBloc.expirationTimeTimeStamp));
|
|
||||||
},
|
|
||||||
onReset: () {
|
|
||||||
accessBloc.add(ResetSearch());
|
|
||||||
},
|
|
||||||
),
|
|
||||||
],
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Widget _buildSmallSearchFilters(BuildContext context, AccessBloc accessBloc) {
|
|
||||||
return Wrap(
|
|
||||||
spacing: 20,
|
|
||||||
runSpacing: 10,
|
|
||||||
children: [
|
|
||||||
SizedBox(
|
|
||||||
width: 300,
|
|
||||||
child: CustomWebTextField(
|
|
||||||
controller: accessBloc.passwordName,
|
|
||||||
isRequired: true,
|
|
||||||
height: 40,
|
|
||||||
textFieldName: 'Name',
|
|
||||||
description: '',
|
|
||||||
onSubmitted: (value) {
|
|
||||||
accessBloc.add(FilterDataEvent(
|
|
||||||
emailAuthorizer:
|
|
||||||
accessBloc.emailAuthorizer.text.toLowerCase(),
|
|
||||||
selectedTabIndex:
|
|
||||||
BlocProvider.of<AccessBloc>(context).selectedIndex,
|
|
||||||
passwordName: accessBloc.passwordName.text.toLowerCase(),
|
|
||||||
startTime: accessBloc.effectiveTimeTimeStamp,
|
|
||||||
endTime: accessBloc.expirationTimeTimeStamp));
|
|
||||||
}),
|
|
||||||
),
|
|
||||||
DateTimeWebWidget(
|
|
||||||
icon: Assets.calendarIcon,
|
|
||||||
isRequired: false,
|
|
||||||
title: 'Access Time',
|
|
||||||
size: MediaQuery.of(context).size,
|
|
||||||
endTime: () {
|
|
||||||
accessBloc.add(SelectTime(context: context, isStart: false));
|
|
||||||
},
|
|
||||||
startTime: () {
|
|
||||||
accessBloc.add(SelectTime(context: context, isStart: true));
|
|
||||||
},
|
|
||||||
firstString: BlocProvider.of<AccessBloc>(context).startTime,
|
|
||||||
secondString: BlocProvider.of<AccessBloc>(context).endTime,
|
|
||||||
),
|
|
||||||
SearchResetButtons(
|
|
||||||
onSearch: () {
|
|
||||||
accessBloc.add(FilterDataEvent(
|
|
||||||
emailAuthorizer: accessBloc.emailAuthorizer.text.toLowerCase(),
|
|
||||||
selectedTabIndex:
|
|
||||||
BlocProvider.of<AccessBloc>(context).selectedIndex,
|
|
||||||
passwordName: accessBloc.passwordName.text.toLowerCase(),
|
|
||||||
startTime: accessBloc.effectiveTimeTimeStamp,
|
|
||||||
endTime: accessBloc.expirationTimeTimeStamp));
|
|
||||||
},
|
|
||||||
onReset: () {
|
|
||||||
accessBloc.add(ResetSearch());
|
|
||||||
},
|
|
||||||
),
|
|
||||||
],
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
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;
|
bool _selectAll = false;
|
||||||
final ScrollController _verticalScrollController = ScrollController();
|
final ScrollController _verticalScrollController = ScrollController();
|
||||||
final ScrollController _horizontalScrollController = ScrollController();
|
final ScrollController _horizontalScrollController = ScrollController();
|
||||||
|
static const double _fixedRowHeight = 60;
|
||||||
|
static const double _checkboxColumnWidth = 50;
|
||||||
|
static const double _settingsColumnWidth = 100;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
@ -67,7 +70,6 @@ class _DynamicTableState extends State<DynamicTable> {
|
|||||||
|
|
||||||
bool _compareListOfLists(
|
bool _compareListOfLists(
|
||||||
List<List<dynamic>> oldList, List<List<dynamic>> newList) {
|
List<List<dynamic>> oldList, List<List<dynamic>> newList) {
|
||||||
// Check if the old and new lists are the same
|
|
||||||
if (oldList.length != newList.length) return false;
|
if (oldList.length != newList.length) return false;
|
||||||
|
|
||||||
for (int i = 0; i < oldList.length; i++) {
|
for (int i = 0; i < oldList.length; i++) {
|
||||||
@ -104,73 +106,130 @@ class _DynamicTableState extends State<DynamicTable> {
|
|||||||
context.read<DeviceManagementBloc>().add(UpdateSelection(_selectedRows));
|
context.read<DeviceManagementBloc>().add(UpdateSelection(_selectedRows));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
double get _totalTableWidth {
|
||||||
|
final hasSettings = widget.headers.contains('Settings');
|
||||||
|
final base = (widget.withCheckBox ? _checkboxColumnWidth : 0) +
|
||||||
|
(hasSettings ? _settingsColumnWidth : 0);
|
||||||
|
final regularCount = widget.headers.length - (hasSettings ? 1 : 0);
|
||||||
|
final regularWidth = (widget.size.width - base) / regularCount;
|
||||||
|
return base + regularCount * regularWidth;
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Container(
|
return Container(
|
||||||
|
width: widget.size.width,
|
||||||
|
height: widget.size.height,
|
||||||
decoration: widget.cellDecoration,
|
decoration: widget.cellDecoration,
|
||||||
child: Scrollbar(
|
child: ScrollConfiguration(
|
||||||
controller: _verticalScrollController,
|
behavior: const ScrollBehavior().copyWith(scrollbars: false),
|
||||||
thumbVisibility: true,
|
|
||||||
trackVisibility: true,
|
|
||||||
child: Scrollbar(
|
child: Scrollbar(
|
||||||
//fixed the horizontal scrollbar issue
|
|
||||||
controller: _horizontalScrollController,
|
controller: _horizontalScrollController,
|
||||||
thumbVisibility: true,
|
thumbVisibility: true,
|
||||||
trackVisibility: true,
|
trackVisibility: true,
|
||||||
notificationPredicate: (notif) => notif.depth == 1,
|
notificationPredicate: (notif) =>
|
||||||
|
notif.metrics.axis == Axis.horizontal,
|
||||||
child: SingleChildScrollView(
|
child: SingleChildScrollView(
|
||||||
controller: _verticalScrollController,
|
controller: _horizontalScrollController,
|
||||||
child: SingleChildScrollView(
|
scrollDirection: Axis.horizontal,
|
||||||
controller: _horizontalScrollController,
|
child: SizedBox(
|
||||||
scrollDirection: Axis.horizontal,
|
width: _totalTableWidth,
|
||||||
child: SizedBox(
|
child: Column(
|
||||||
width: widget.size.width,
|
children: [
|
||||||
child: Column(
|
Container(
|
||||||
children: [
|
height: _fixedRowHeight,
|
||||||
Container(
|
decoration: widget.headerDecoration ??
|
||||||
decoration: widget.headerDecoration ??
|
const BoxDecoration(color: ColorsManager.boxColor),
|
||||||
const BoxDecoration(
|
child: Row(
|
||||||
color: ColorsManager.boxColor,
|
children: [
|
||||||
|
if (widget.withCheckBox)
|
||||||
|
_buildSelectAllCheckbox(_checkboxColumnWidth),
|
||||||
|
for (var i = 0; i < widget.headers.length; i++)
|
||||||
|
_buildTableHeaderCell(
|
||||||
|
widget.headers[i],
|
||||||
|
widget.headers[i] == 'Settings'
|
||||||
|
? _settingsColumnWidth
|
||||||
|
: (_totalTableWidth -
|
||||||
|
(widget.withCheckBox
|
||||||
|
? _checkboxColumnWidth
|
||||||
|
: 0) -
|
||||||
|
(widget.headers.contains('Settings')
|
||||||
|
? _settingsColumnWidth
|
||||||
|
: 0)) /
|
||||||
|
(widget.headers.length -
|
||||||
|
(widget.headers.contains('Settings')
|
||||||
|
? 1
|
||||||
|
: 0)),
|
||||||
),
|
),
|
||||||
child: Row(
|
],
|
||||||
children: [
|
|
||||||
if (widget.withCheckBox) _buildSelectAllCheckbox(),
|
|
||||||
...List.generate(widget.headers.length, (index) {
|
|
||||||
return _buildTableHeaderCell(
|
|
||||||
widget.headers[index], index);
|
|
||||||
})
|
|
||||||
//...widget.headers.map((header) => _buildTableHeaderCell(header)),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
SizedBox(
|
),
|
||||||
width: widget.size.width,
|
|
||||||
child: widget.isEmpty
|
Expanded(
|
||||||
? _buildEmptyState()
|
child: widget.isEmpty
|
||||||
: Column(
|
? _buildEmptyState()
|
||||||
children:
|
: Scrollbar(
|
||||||
List.generate(widget.data.length, (rowIndex) {
|
controller: _verticalScrollController,
|
||||||
|
thumbVisibility: true,
|
||||||
|
trackVisibility: true,
|
||||||
|
notificationPredicate: (notif) =>
|
||||||
|
notif.metrics.axis == Axis.vertical,
|
||||||
|
child: ListView.builder(
|
||||||
|
controller: _verticalScrollController,
|
||||||
|
itemCount: widget.data.length,
|
||||||
|
itemBuilder: (_, rowIndex) {
|
||||||
final row = widget.data[rowIndex];
|
final row = widget.data[rowIndex];
|
||||||
return Row(
|
return SizedBox(
|
||||||
children: [
|
height: _fixedRowHeight,
|
||||||
if (widget.withCheckBox)
|
child: Row(
|
||||||
_buildRowCheckbox(
|
children: [
|
||||||
rowIndex, widget.size.height * 0.08),
|
if (widget.withCheckBox)
|
||||||
...row.asMap().entries.map((entry) {
|
_buildRowCheckbox(
|
||||||
return _buildTableCell(
|
rowIndex,
|
||||||
entry.value.toString(),
|
_checkboxColumnWidth,
|
||||||
widget.size.height * 0.08,
|
),
|
||||||
rowIndex: rowIndex,
|
for (var colIndex = 0;
|
||||||
columnIndex: entry.key,
|
colIndex < row.length;
|
||||||
);
|
colIndex++)
|
||||||
}).toList(),
|
widget.headers[colIndex] == 'Settings'
|
||||||
],
|
? buildSettingsIcon(
|
||||||
|
width: _settingsColumnWidth,
|
||||||
|
onTap: () => widget
|
||||||
|
.onSettingsPressed
|
||||||
|
?.call(rowIndex),
|
||||||
|
)
|
||||||
|
: _buildTableCell(
|
||||||
|
row[colIndex].toString(),
|
||||||
|
width: widget.headers[
|
||||||
|
colIndex] ==
|
||||||
|
'Settings'
|
||||||
|
? _settingsColumnWidth
|
||||||
|
: (_totalTableWidth -
|
||||||
|
(widget.withCheckBox
|
||||||
|
? _checkboxColumnWidth
|
||||||
|
: 0) -
|
||||||
|
(widget.headers
|
||||||
|
.contains(
|
||||||
|
'Settings')
|
||||||
|
? _settingsColumnWidth
|
||||||
|
: 0)) /
|
||||||
|
(widget.headers.length -
|
||||||
|
(widget.headers
|
||||||
|
.contains(
|
||||||
|
'Settings')
|
||||||
|
? 1
|
||||||
|
: 0)),
|
||||||
|
rowIndex: rowIndex,
|
||||||
|
columnIndex: colIndex,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}),
|
},
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
),
|
||||||
),
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@ -210,9 +269,10 @@ class _DynamicTableState extends State<DynamicTable> {
|
|||||||
],
|
],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
Widget _buildSelectAllCheckbox() {
|
|
||||||
|
Widget _buildSelectAllCheckbox(double width) {
|
||||||
return Container(
|
return Container(
|
||||||
width: 50,
|
width: width,
|
||||||
decoration: const BoxDecoration(
|
decoration: const BoxDecoration(
|
||||||
border: Border.symmetric(
|
border: Border.symmetric(
|
||||||
vertical: BorderSide(color: ColorsManager.boxDivider),
|
vertical: BorderSide(color: ColorsManager.boxDivider),
|
||||||
@ -227,11 +287,11 @@ class _DynamicTableState extends State<DynamicTable> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildRowCheckbox(int index, double size) {
|
Widget _buildRowCheckbox(int index, double width) {
|
||||||
return Container(
|
return Container(
|
||||||
width: 50,
|
width: width,
|
||||||
padding: const EdgeInsets.all(8.0),
|
padding: const EdgeInsets.all(8.0),
|
||||||
height: size,
|
height: _fixedRowHeight,
|
||||||
decoration: const BoxDecoration(
|
decoration: const BoxDecoration(
|
||||||
border: Border(
|
border: Border(
|
||||||
bottom: BorderSide(
|
bottom: BorderSide(
|
||||||
@ -253,50 +313,47 @@ class _DynamicTableState extends State<DynamicTable> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildTableHeaderCell(String title, int index) {
|
Widget _buildTableHeaderCell(String title, double width) {
|
||||||
return Expanded(
|
return Container(
|
||||||
child: Container(
|
width: width,
|
||||||
decoration: const BoxDecoration(
|
decoration: const BoxDecoration(
|
||||||
border: Border.symmetric(
|
border: Border.symmetric(
|
||||||
vertical: BorderSide(color: ColorsManager.boxDivider),
|
vertical: BorderSide(color: ColorsManager.boxDivider),
|
||||||
),
|
|
||||||
),
|
),
|
||||||
constraints: const BoxConstraints.expand(height: 40),
|
),
|
||||||
alignment: Alignment.centerLeft,
|
constraints: BoxConstraints(minHeight: 40, maxHeight: _fixedRowHeight),
|
||||||
child: Padding(
|
alignment: Alignment.centerLeft,
|
||||||
padding: EdgeInsets.symmetric(
|
child: Padding(
|
||||||
horizontal: index == widget.headers.length - 1 ? 12 : 8.0,
|
padding: const EdgeInsets.symmetric(horizontal: 8.0, vertical: 4),
|
||||||
vertical: 4),
|
child: Text(
|
||||||
child: Text(
|
title,
|
||||||
title,
|
style: context.textTheme.titleSmall!.copyWith(
|
||||||
style: context.textTheme.titleSmall!.copyWith(
|
color: ColorsManager.grayColor,
|
||||||
color: ColorsManager.grayColor,
|
fontSize: 12,
|
||||||
fontSize: 12,
|
fontWeight: FontWeight.w400,
|
||||||
fontWeight: FontWeight.w400,
|
|
||||||
),
|
|
||||||
maxLines: 2,
|
|
||||||
),
|
),
|
||||||
|
maxLines: 2,
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildTableCell(String content, double size,
|
Widget _buildTableCell(String content,
|
||||||
{required int rowIndex, required int columnIndex}) {
|
{required double width,
|
||||||
|
required int rowIndex,
|
||||||
|
required int columnIndex}) {
|
||||||
bool isBatteryLevel = content.endsWith('%');
|
bool isBatteryLevel = content.endsWith('%');
|
||||||
double? batteryLevel;
|
double? batteryLevel;
|
||||||
|
|
||||||
if (isBatteryLevel) {
|
if (isBatteryLevel) {
|
||||||
batteryLevel = double.tryParse(content.replaceAll('%', '').trim());
|
batteryLevel = double.tryParse(content.replaceAll('%', '').trim());
|
||||||
}
|
}
|
||||||
|
|
||||||
bool isSettingsColumn = widget.headers[columnIndex] == 'Settings';
|
bool isSettingsColumn = widget.headers[columnIndex] == 'Settings';
|
||||||
if (isSettingsColumn) {
|
if (isSettingsColumn) {
|
||||||
return buildSettingsIcon(
|
return buildSettingsIcon(
|
||||||
width: 120,
|
width: width, onTap: () => widget.onSettingsPressed?.call(rowIndex));
|
||||||
height: 60,
|
|
||||||
iconSize: 40,
|
|
||||||
onTap: () => widget.onSettingsPressed?.call(rowIndex),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Color? statusColor;
|
Color? statusColor;
|
||||||
@ -320,92 +377,82 @@ class _DynamicTableState extends State<DynamicTable> {
|
|||||||
statusColor = Colors.black;
|
statusColor = Colors.black;
|
||||||
}
|
}
|
||||||
|
|
||||||
return Expanded(
|
return Container(
|
||||||
child: Container(
|
width: width,
|
||||||
height: size,
|
height: _fixedRowHeight,
|
||||||
padding: const EdgeInsets.all(5.0),
|
padding: const EdgeInsets.symmetric(horizontal: 8.0, vertical: 4),
|
||||||
decoration: const BoxDecoration(
|
decoration: const BoxDecoration(
|
||||||
border: Border(
|
border: Border(
|
||||||
bottom: BorderSide(
|
bottom: BorderSide(
|
||||||
color: ColorsManager.boxDivider,
|
color: ColorsManager.boxDivider,
|
||||||
width: 1.0,
|
width: 1.0,
|
||||||
),
|
|
||||||
),
|
),
|
||||||
color: Colors.white,
|
|
||||||
),
|
),
|
||||||
alignment: Alignment.centerLeft,
|
color: Colors.white,
|
||||||
child: Text(
|
),
|
||||||
content,
|
alignment: Alignment.centerLeft,
|
||||||
style: TextStyle(
|
child: Text(
|
||||||
color: (batteryLevel != null && batteryLevel < 20)
|
content,
|
||||||
? ColorsManager.red
|
style: TextStyle(
|
||||||
: (batteryLevel != null && batteryLevel > 20)
|
color: (batteryLevel != null && batteryLevel < 20)
|
||||||
? ColorsManager.green
|
? ColorsManager.red
|
||||||
: statusColor,
|
: (batteryLevel != null && batteryLevel > 20)
|
||||||
fontSize: 13,
|
? ColorsManager.green
|
||||||
fontWeight: FontWeight.w400),
|
: statusColor,
|
||||||
maxLines: 2,
|
fontSize: 13,
|
||||||
|
fontWeight: FontWeight.w400,
|
||||||
),
|
),
|
||||||
|
maxLines: 2,
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget buildSettingsIcon(
|
Widget buildSettingsIcon({required double width, VoidCallback? onTap}) {
|
||||||
{double width = 120,
|
return Container(
|
||||||
double height = 60,
|
width: width,
|
||||||
double iconSize = 40,
|
height: _fixedRowHeight,
|
||||||
VoidCallback? onTap}) {
|
padding: const EdgeInsets.only(left: 15, top: 10, bottom: 10),
|
||||||
return Column(
|
decoration: const BoxDecoration(
|
||||||
children: [
|
color: ColorsManager.whiteColors,
|
||||||
Container(
|
border: Border(
|
||||||
padding: const EdgeInsets.only(top: 10, bottom: 15, left: 10),
|
bottom: BorderSide(
|
||||||
margin: const EdgeInsets.only(right: 15),
|
color: ColorsManager.boxDivider,
|
||||||
decoration: const BoxDecoration(
|
width: 1.0,
|
||||||
color: ColorsManager.whiteColors,
|
|
||||||
border: Border(
|
|
||||||
bottom: BorderSide(
|
|
||||||
color: ColorsManager.boxDivider,
|
|
||||||
width: 1.0,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
width: width,
|
),
|
||||||
child: Padding(
|
),
|
||||||
padding: const EdgeInsets.only(
|
child: Align(
|
||||||
right: 16.0,
|
alignment: Alignment.centerLeft,
|
||||||
left: 17.0,
|
child: Container(
|
||||||
),
|
width: 50,
|
||||||
child: Container(
|
decoration: BoxDecoration(
|
||||||
width: 50,
|
color: const Color(0xFFF7F8FA),
|
||||||
decoration: BoxDecoration(
|
borderRadius: BorderRadius.circular(20),
|
||||||
color: const Color(0xFFF7F8FA),
|
boxShadow: [
|
||||||
borderRadius: BorderRadius.circular(height / 2),
|
BoxShadow(
|
||||||
boxShadow: [
|
color: Colors.black.withOpacity(0.17),
|
||||||
BoxShadow(
|
blurRadius: 14,
|
||||||
color: Colors.black.withOpacity(0.17),
|
offset: const Offset(0, 4),
|
||||||
blurRadius: 14,
|
|
||||||
offset: const Offset(0, 4),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
),
|
||||||
child: InkWell(
|
],
|
||||||
onTap: onTap,
|
),
|
||||||
child: Padding(
|
child: InkWell(
|
||||||
padding: const EdgeInsets.all(8.0),
|
onTap: onTap,
|
||||||
child: Center(
|
child: Padding(
|
||||||
child: SvgPicture.asset(
|
padding: EdgeInsets.all(8.0),
|
||||||
Assets.settings,
|
child: Center(
|
||||||
width: 40,
|
child: SvgPicture.asset(
|
||||||
height: 22,
|
Assets.settings,
|
||||||
color: ColorsManager.primaryColor,
|
width: 40,
|
||||||
),
|
height: 20,
|
||||||
),
|
color: ColorsManager.primaryColor,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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/extension/build_context_x.dart';
|
||||||
import 'package:syncrow_web/utils/helpers/responsice_layout_helper/responsive_layout_helper.dart';
|
import 'package:syncrow_web/utils/helpers/responsice_layout_helper/responsive_layout_helper.dart';
|
||||||
|
|
||||||
class AcDeviceBatchControlView extends StatelessWidget with HelperResponsiveLayout {
|
class AcDeviceBatchControlView extends StatelessWidget
|
||||||
|
with HelperResponsiveLayout {
|
||||||
const AcDeviceBatchControlView({super.key, required this.devicesIds});
|
const AcDeviceBatchControlView({super.key, required this.devicesIds});
|
||||||
|
|
||||||
final List<String> devicesIds;
|
final List<String> devicesIds;
|
||||||
@ -51,7 +52,7 @@ class AcDeviceBatchControlView extends StatelessWidget with HelperResponsiveLayo
|
|||||||
deviceId: devicesIds.first,
|
deviceId: devicesIds.first,
|
||||||
code: 'switch',
|
code: 'switch',
|
||||||
value: state.status.acSwitch,
|
value: state.status.acSwitch,
|
||||||
label: 'ThermoState',
|
label: 'Thermostat',
|
||||||
icon: Assets.ac,
|
icon: Assets.ac,
|
||||||
onChange: (value) {
|
onChange: (value) {
|
||||||
context.read<AcBloc>().add(AcBatchControlEvent(
|
context.read<AcBloc>().add(AcBatchControlEvent(
|
||||||
@ -100,8 +101,8 @@ class AcDeviceBatchControlView extends StatelessWidget with HelperResponsiveLayo
|
|||||||
),
|
),
|
||||||
Text(
|
Text(
|
||||||
'h',
|
'h',
|
||||||
style:
|
style: context.textTheme.bodySmall!
|
||||||
context.textTheme.bodySmall!.copyWith(color: ColorsManager.blackColor),
|
.copyWith(color: ColorsManager.blackColor),
|
||||||
),
|
),
|
||||||
Text(
|
Text(
|
||||||
'30',
|
'30',
|
||||||
@ -148,7 +149,8 @@ class AcDeviceBatchControlView extends StatelessWidget with HelperResponsiveLayo
|
|||||||
callFactoryReset: () {
|
callFactoryReset: () {
|
||||||
context.read<AcBloc>().add(AcFactoryResetEvent(
|
context.read<AcBloc>().add(AcFactoryResetEvent(
|
||||||
deviceId: state.status.uuid,
|
deviceId: state.status.uuid,
|
||||||
factoryResetModel: FactoryResetModel(devicesUuid: devicesIds),
|
factoryResetModel:
|
||||||
|
FactoryResetModel(devicesUuid: devicesIds),
|
||||||
));
|
));
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
@ -68,6 +68,7 @@ class DeviceManagementBody extends StatelessWidget with HelperResponsiveLayout {
|
|||||||
children: [
|
children: [
|
||||||
Expanded(child: SpaceTreeView(
|
Expanded(child: SpaceTreeView(
|
||||||
onSelect: () {
|
onSelect: () {
|
||||||
|
context.read<DeviceManagementBloc>().add(ResetFilters());
|
||||||
context.read<DeviceManagementBloc>().add(FetchDevices(context));
|
context.read<DeviceManagementBloc>().add(FetchDevices(context));
|
||||||
},
|
},
|
||||||
)),
|
)),
|
||||||
|
@ -17,6 +17,7 @@ class CalibrateCompletedDialog extends StatelessWidget {
|
|||||||
@override
|
@override
|
||||||
Widget build(_) {
|
Widget build(_) {
|
||||||
return AlertDialog(
|
return AlertDialog(
|
||||||
|
backgroundColor: ColorsManager.whiteColors,
|
||||||
contentPadding: EdgeInsets.zero,
|
contentPadding: EdgeInsets.zero,
|
||||||
content: SizedBox(
|
content: SizedBox(
|
||||||
height: 250,
|
height: 250,
|
||||||
|
@ -277,6 +277,32 @@ class SmartPowerDeviceControl extends StatelessWidget
|
|||||||
totalConsumption: 10000,
|
totalConsumption: 10000,
|
||||||
date: blocProvider.formattedDate,
|
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),
|
selectedDays: List.filled(7, false),
|
||||||
functionOn: false,
|
functionOn: false,
|
||||||
isEditing: false,
|
isEditing: false,
|
||||||
countdownRemaining: Duration.zero,
|
|
||||||
));
|
));
|
||||||
} else {
|
} else {
|
||||||
emit(ScheduleLoaded(
|
emit(ScheduleLoaded(
|
||||||
|
@ -32,113 +32,114 @@ class SpaceDropdown extends StatelessWidget {
|
|||||||
color: ColorsManager.blackColor,
|
color: ColorsManager.blackColor,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
SizedBox(
|
DropdownButton2<String>(
|
||||||
child: Container(
|
underline: const SizedBox(),
|
||||||
|
buttonStyleData: ButtonStyleData(
|
||||||
|
decoration:
|
||||||
|
BoxDecoration(borderRadius: BorderRadius.circular(12)),
|
||||||
|
),
|
||||||
|
value: selectedValue,
|
||||||
|
items: spaces.map((space) {
|
||||||
|
return DropdownMenuItem<String>(
|
||||||
|
value: space.uuid,
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
' ${space.name}',
|
||||||
|
style: Theme.of(context).textTheme.bodyMedium!.copyWith(
|
||||||
|
fontSize: 16,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
color: selectedValue == space.uuid
|
||||||
|
? ColorsManager.dialogBlueTitle
|
||||||
|
: ColorsManager.blackColor,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Text(
|
||||||
|
' ${space.lastThreeParents}',
|
||||||
|
style: Theme.of(context).textTheme.bodyMedium!.copyWith(
|
||||||
|
fontSize: 12,
|
||||||
|
color: selectedValue == space.uuid
|
||||||
|
? ColorsManager.dialogBlueTitle
|
||||||
|
: ColorsManager.blackColor,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}).toList(),
|
||||||
|
onChanged: onChanged,
|
||||||
|
style: TextStyle(
|
||||||
|
color: Colors.black,
|
||||||
|
fontSize: 13,
|
||||||
|
),
|
||||||
|
hint: Padding(
|
||||||
|
padding: const EdgeInsets.only(left: 10),
|
||||||
|
child: Text(
|
||||||
|
hintMessage,
|
||||||
|
style: Theme.of(context).textTheme.bodySmall!.copyWith(
|
||||||
|
color: ColorsManager.textGray,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
customButton: Container(
|
||||||
height: 40,
|
height: 40,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
border: Border.all(color: ColorsManager.textGray, width: 1.0),
|
||||||
|
borderRadius: BorderRadius.circular(10),
|
||||||
|
),
|
||||||
|
child: Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
children: [
|
||||||
|
Expanded(
|
||||||
|
flex: 8,
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.only(left: 10),
|
||||||
|
child: Text(
|
||||||
|
selectedValue != null
|
||||||
|
? spaces
|
||||||
|
.firstWhere((e) => e.uuid == selectedValue)
|
||||||
|
.name
|
||||||
|
: hintMessage,
|
||||||
|
style: Theme.of(context).textTheme.bodySmall!.copyWith(
|
||||||
|
fontSize: 13,
|
||||||
|
color: selectedValue != null
|
||||||
|
? Colors.black
|
||||||
|
: ColorsManager.textGray,
|
||||||
|
),
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Expanded(
|
||||||
|
child: Container(
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: Colors.grey[200],
|
||||||
|
borderRadius: const BorderRadius.only(
|
||||||
|
topRight: Radius.circular(10),
|
||||||
|
bottomRight: Radius.circular(10),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
height: 45,
|
||||||
|
child: const Icon(
|
||||||
|
Icons.keyboard_arrow_down,
|
||||||
|
color: ColorsManager.textGray,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
dropdownStyleData: DropdownStyleData(
|
||||||
|
maxHeight: MediaQuery.of(context).size.height * 0.4,
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: ColorsManager.whiteColors,
|
color: ColorsManager.whiteColors,
|
||||||
borderRadius: BorderRadius.circular(10),
|
borderRadius: BorderRadius.circular(10),
|
||||||
),
|
),
|
||||||
child: DropdownButton2<String>(
|
),
|
||||||
underline: const SizedBox(),
|
menuItemStyleData: const MenuItemStyleData(
|
||||||
value: selectedValue,
|
height: 60,
|
||||||
items: spaces.map((space) {
|
|
||||||
return DropdownMenuItem<String>(
|
|
||||||
value: space.uuid,
|
|
||||||
child: Column(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
|
||||||
children: [
|
|
||||||
Text(
|
|
||||||
' ${space.name}',
|
|
||||||
style:
|
|
||||||
Theme.of(context).textTheme.bodyMedium!.copyWith(
|
|
||||||
fontSize: 12,
|
|
||||||
color: ColorsManager.blackColor,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Text(
|
|
||||||
' ${space.lastThreeParents}',
|
|
||||||
style:
|
|
||||||
Theme.of(context).textTheme.bodyMedium!.copyWith(
|
|
||||||
fontSize: 12,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}).toList(),
|
|
||||||
onChanged: onChanged,
|
|
||||||
style: TextStyle(color: Colors.black),
|
|
||||||
hint: Padding(
|
|
||||||
padding: const EdgeInsets.only(left: 10),
|
|
||||||
child: Text(
|
|
||||||
hintMessage,
|
|
||||||
style: Theme.of(context).textTheme.bodySmall!.copyWith(
|
|
||||||
color: ColorsManager.textGray,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
customButton: Container(
|
|
||||||
height: 45,
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
border:
|
|
||||||
Border.all(color: ColorsManager.textGray, width: 1.0),
|
|
||||||
borderRadius: BorderRadius.circular(10),
|
|
||||||
),
|
|
||||||
child: Row(
|
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
|
||||||
children: [
|
|
||||||
Expanded(
|
|
||||||
flex: 8,
|
|
||||||
child: Padding(
|
|
||||||
padding: const EdgeInsets.only(left: 10),
|
|
||||||
child: Text(
|
|
||||||
selectedValue != null
|
|
||||||
? spaces
|
|
||||||
.firstWhere((e) => e.uuid == selectedValue)
|
|
||||||
.name
|
|
||||||
: hintMessage,
|
|
||||||
style:
|
|
||||||
Theme.of(context).textTheme.bodySmall!.copyWith(
|
|
||||||
color: selectedValue != null
|
|
||||||
? Colors.black
|
|
||||||
: ColorsManager.textGray,
|
|
||||||
),
|
|
||||||
overflow: TextOverflow.ellipsis,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Expanded(
|
|
||||||
child: Container(
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: Colors.grey[200],
|
|
||||||
borderRadius: const BorderRadius.only(
|
|
||||||
topRight: Radius.circular(10),
|
|
||||||
bottomRight: Radius.circular(10),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
height: 45,
|
|
||||||
child: const Icon(
|
|
||||||
Icons.keyboard_arrow_down,
|
|
||||||
color: ColorsManager.textGray,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
dropdownStyleData: DropdownStyleData(
|
|
||||||
maxHeight: MediaQuery.of(context).size.height * 0.4,
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: ColorsManager.whiteColors,
|
|
||||||
borderRadius: BorderRadius.circular(10),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
menuItemStyleData: const MenuItemStyleData(
|
|
||||||
height: 60,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
@ -121,7 +121,8 @@ class _RoutineViewCardState extends State<RoutineViewCard> {
|
|||||||
child: SizedBox(
|
child: SizedBox(
|
||||||
width: 16,
|
width: 16,
|
||||||
height: 16,
|
height: 16,
|
||||||
child: CircularProgressIndicator(strokeWidth: 2),
|
child:
|
||||||
|
CircularProgressIndicator(strokeWidth: 2),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
@ -159,8 +160,9 @@ class _RoutineViewCardState extends State<RoutineViewCard> {
|
|||||||
height: iconSize,
|
height: iconSize,
|
||||||
width: iconSize,
|
width: iconSize,
|
||||||
fit: BoxFit.contain,
|
fit: BoxFit.contain,
|
||||||
errorBuilder: (context, error, stackTrace) =>
|
errorBuilder:
|
||||||
Image.asset(
|
(context, error, stackTrace) =>
|
||||||
|
Image.asset(
|
||||||
Assets.logo,
|
Assets.logo,
|
||||||
height: iconSize,
|
height: iconSize,
|
||||||
width: iconSize,
|
width: iconSize,
|
||||||
@ -203,7 +205,8 @@ class _RoutineViewCardState extends State<RoutineViewCard> {
|
|||||||
maxLines: 1,
|
maxLines: 1,
|
||||||
style: context.textTheme.bodySmall?.copyWith(
|
style: context.textTheme.bodySmall?.copyWith(
|
||||||
color: ColorsManager.blackColor,
|
color: ColorsManager.blackColor,
|
||||||
fontSize: widget.isSmallScreenSize(context) ? 10 : 12,
|
fontSize:
|
||||||
|
widget.isSmallScreenSize(context) ? 10 : 12,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
if (widget.spaceName != '')
|
if (widget.spaceName != '')
|
||||||
@ -222,8 +225,9 @@ class _RoutineViewCardState extends State<RoutineViewCard> {
|
|||||||
maxLines: 1,
|
maxLines: 1,
|
||||||
style: context.textTheme.bodySmall?.copyWith(
|
style: context.textTheme.bodySmall?.copyWith(
|
||||||
color: ColorsManager.blackColor,
|
color: ColorsManager.blackColor,
|
||||||
fontSize:
|
fontSize: widget.isSmallScreenSize(context)
|
||||||
widget.isSmallScreenSize(context) ? 10 : 12,
|
? 10
|
||||||
|
: 12,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
@ -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/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/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/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/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/pages/space_management_v2/modules/space_details/presentation/bloc/space_details_bloc.dart';
|
||||||
import 'package:syncrow_web/services/api/http_service.dart';
|
import 'package:syncrow_web/services/api/http_service.dart';
|
||||||
@ -33,6 +35,11 @@ class SpaceManagementPage extends StatelessWidget {
|
|||||||
RemoteSpaceDetailsService(httpService: HTTPService()),
|
RemoteSpaceDetailsService(httpService: HTTPService()),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
BlocProvider(
|
||||||
|
create: (context) => ProductsBloc(
|
||||||
|
RemoteProductsService(HTTPService()),
|
||||||
|
),
|
||||||
|
),
|
||||||
],
|
],
|
||||||
child: WebScaffold(
|
child: WebScaffold(
|
||||||
appBarTitle: Text(
|
appBarTitle: Text(
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
import 'package:dio/dio.dart';
|
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/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/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/api_exception.dart';
|
||||||
import 'package:syncrow_web/services/api/http_service.dart';
|
import 'package:syncrow_web/services/api/http_service.dart';
|
||||||
|
import 'package:syncrow_web/utils/constants/api_const.dart';
|
||||||
|
|
||||||
class RemoteProductsService implements ProductsService {
|
class RemoteProductsService implements ProductsService {
|
||||||
const RemoteProductsService(this._httpService);
|
const RemoteProductsService(this._httpService);
|
||||||
@ -13,17 +13,14 @@ class RemoteProductsService implements ProductsService {
|
|||||||
static const _defaultErrorMessage = 'Failed to load devices';
|
static const _defaultErrorMessage = 'Failed to load devices';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<List<Product>> getProducts(LoadProductsParam param) async {
|
Future<List<Product>> getProducts() async {
|
||||||
try {
|
try {
|
||||||
final response = await _httpService.get(
|
final response = await _httpService.get(
|
||||||
path: 'devices',
|
path: ApiEndpoints.listProducts,
|
||||||
queryParameters: {
|
|
||||||
'spaceUuid': param.spaceUuid,
|
|
||||||
if (param.type != null) 'type': param.type,
|
|
||||||
if (param.status != null) 'status': param.status,
|
|
||||||
},
|
|
||||||
expectedResponseModel: (data) {
|
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>))
|
.map((e) => Product.fromJson(e as Map<String, dynamic>))
|
||||||
.toList();
|
.toList();
|
||||||
},
|
},
|
||||||
|
@ -3,16 +3,18 @@ import 'package:equatable/equatable.dart';
|
|||||||
class Product extends Equatable {
|
class Product extends Equatable {
|
||||||
final String uuid;
|
final String uuid;
|
||||||
final String name;
|
final String name;
|
||||||
|
final String productType;
|
||||||
const Product({
|
const Product({
|
||||||
required this.uuid,
|
required this.uuid,
|
||||||
required this.name,
|
required this.name,
|
||||||
|
required this.productType,
|
||||||
});
|
});
|
||||||
|
|
||||||
factory Product.fromJson(Map<String, dynamic> json) {
|
factory Product.fromJson(Map<String, dynamic> json) {
|
||||||
return Product(
|
return Product(
|
||||||
uuid: json['uuid'] as String,
|
uuid: json['uuid'] as String? ?? '',
|
||||||
name: json['name'] as String,
|
name: json['name'] as String? ?? '',
|
||||||
|
productType: json['prodType'] as String? ?? '',
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -20,9 +22,10 @@ class Product extends Equatable {
|
|||||||
return {
|
return {
|
||||||
'uuid': uuid,
|
'uuid': uuid,
|
||||||
'name': name,
|
'name': name,
|
||||||
|
'productType': productType,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@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/models/product.dart';
|
||||||
import 'package:syncrow_web/pages/space_management_v2/modules/products/domain/params/load_products_param.dart';
|
|
||||||
|
|
||||||
abstract class ProductsService {
|
abstract class ProductsService {
|
||||||
Future<List<Product>> getProducts(LoadProductsParam param);
|
Future<List<Product>> getProducts();
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
import 'package:bloc/bloc.dart';
|
import 'package:bloc/bloc.dart';
|
||||||
import 'package:equatable/equatable.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/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/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/api_exception.dart';
|
||||||
|
|
||||||
@ -9,20 +8,20 @@ part 'products_event.dart';
|
|||||||
part 'products_state.dart';
|
part 'products_state.dart';
|
||||||
|
|
||||||
class ProductsBloc extends Bloc<ProductsEvent, ProductsState> {
|
class ProductsBloc extends Bloc<ProductsEvent, ProductsState> {
|
||||||
final ProductsService _deviceService;
|
ProductsBloc(this._productsService) : super(ProductsInitial()) {
|
||||||
|
|
||||||
ProductsBloc(this._deviceService) : super(ProductsInitial()) {
|
|
||||||
on<LoadProducts>(_onLoadProducts);
|
on<LoadProducts>(_onLoadProducts);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
final ProductsService _productsService;
|
||||||
|
|
||||||
Future<void> _onLoadProducts(
|
Future<void> _onLoadProducts(
|
||||||
LoadProducts event,
|
LoadProducts event,
|
||||||
Emitter<ProductsState> emit,
|
Emitter<ProductsState> emit,
|
||||||
) async {
|
) async {
|
||||||
emit(ProductsLoading());
|
emit(ProductsLoading());
|
||||||
try {
|
try {
|
||||||
final devices = await _deviceService.getProducts(event.param);
|
final products = await _productsService.getProducts();
|
||||||
emit(ProductsLoaded(devices));
|
emit(ProductsLoaded(products));
|
||||||
} on APIException catch (e) {
|
} on APIException catch (e) {
|
||||||
emit(ProductsFailure(e.message));
|
emit(ProductsFailure(e.message));
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
@ -8,10 +8,5 @@ sealed class ProductsEvent extends Equatable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
final class LoadProducts extends ProductsEvent {
|
final class LoadProducts extends ProductsEvent {
|
||||||
const LoadProducts(this.param);
|
const LoadProducts();
|
||||||
|
|
||||||
final LoadProductsParam param;
|
|
||||||
|
|
||||||
@override
|
|
||||||
List<Object> get props => [param];
|
|
||||||
}
|
}
|
||||||
|
@ -21,10 +21,10 @@ final class ProductsLoaded extends ProductsState {
|
|||||||
}
|
}
|
||||||
|
|
||||||
final class ProductsFailure extends ProductsState {
|
final class ProductsFailure extends ProductsState {
|
||||||
final String message;
|
final String errorMessage;
|
||||||
|
|
||||||
const ProductsFailure(this.message);
|
const ProductsFailure(this.errorMessage);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
List<Object> get props => [message];
|
List<Object> get props => [errorMessage];
|
||||||
}
|
}
|
||||||
|
@ -21,7 +21,7 @@ class SpaceDetailsModel extends Equatable {
|
|||||||
factory SpaceDetailsModel.empty() => const SpaceDetailsModel(
|
factory SpaceDetailsModel.empty() => const SpaceDetailsModel(
|
||||||
uuid: '',
|
uuid: '',
|
||||||
spaceName: '',
|
spaceName: '',
|
||||||
icon: Assets.villa,
|
icon: Assets.location,
|
||||||
productAllocations: [],
|
productAllocations: [],
|
||||||
subspaces: [],
|
subspaces: [],
|
||||||
);
|
);
|
||||||
|
@ -8,10 +8,14 @@ class SpaceDetailsActionButtons extends StatelessWidget {
|
|||||||
super.key,
|
super.key,
|
||||||
required this.onSave,
|
required this.onSave,
|
||||||
required this.onCancel,
|
required this.onCancel,
|
||||||
|
this.saveButtonLabel = 'OK',
|
||||||
|
this.cancelButtonLabel = 'Cancel',
|
||||||
});
|
});
|
||||||
|
|
||||||
final VoidCallback onCancel;
|
final VoidCallback onCancel;
|
||||||
final VoidCallback? onSave;
|
final VoidCallback? onSave;
|
||||||
|
final String saveButtonLabel;
|
||||||
|
final String cancelButtonLabel;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
@ -27,10 +31,7 @@ class SpaceDetailsActionButtons extends StatelessWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildCancelButton(BuildContext context) {
|
Widget _buildCancelButton(BuildContext context) {
|
||||||
return CancelButton(
|
return CancelButton(onPressed: onCancel, label: cancelButtonLabel);
|
||||||
onPressed: onCancel,
|
|
||||||
label: 'Cancel',
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildSaveButton() {
|
Widget _buildSaveButton() {
|
||||||
@ -39,7 +40,7 @@ class SpaceDetailsActionButtons extends StatelessWidget {
|
|||||||
borderRadius: 10,
|
borderRadius: 10,
|
||||||
backgroundColor: ColorsManager.secondaryColor,
|
backgroundColor: ColorsManager.secondaryColor,
|
||||||
foregroundColor: ColorsManager.whiteColors,
|
foregroundColor: ColorsManager.whiteColors,
|
||||||
child: const Text('OK'),
|
child: Text(saveButtonLabel),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,9 +1,13 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_svg/svg.dart';
|
||||||
import 'package:syncrow_web/common/edit_chip.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/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/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/color_manager.dart';
|
||||||
import 'package:syncrow_web/utils/constants/assets.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 {
|
class SpaceDetailsDevicesBox extends StatelessWidget {
|
||||||
const SpaceDetailsDevicesBox({
|
const SpaceDetailsDevicesBox({
|
||||||
@ -35,46 +39,39 @@ class SpaceDetailsDevicesBox extends StatelessWidget {
|
|||||||
spacing: 8.0,
|
spacing: 8.0,
|
||||||
runSpacing: 8.0,
|
runSpacing: 8.0,
|
||||||
children: [
|
children: [
|
||||||
// Combine tags from spaceModel and subspaces
|
...productAllocations.map(
|
||||||
// ...TagHelper.groupTags([
|
(entry) => Chip(
|
||||||
// ...?tags,
|
avatar: SizedBox(
|
||||||
// ...?subspaces?.expand((subspace) => subspace.tags ?? [])
|
width: 24,
|
||||||
// ]).entries.map(
|
height: 24,
|
||||||
// (entry) => Chip(
|
child: SvgPicture.asset(
|
||||||
// avatar: SizedBox(
|
_getDeviceIcon(entry.product.productType),
|
||||||
// width: 24,
|
fit: BoxFit.contain,
|
||||||
// height: 24,
|
),
|
||||||
// child: SvgPicture.asset(
|
),
|
||||||
// entry.key.icon ?? 'assets/icons/gateway.svg',
|
label: Text(
|
||||||
// fit: BoxFit.contain,
|
entry.product.productType,
|
||||||
// ),
|
style: context.textTheme.bodySmall
|
||||||
// ),
|
?.copyWith(color: ColorsManager.spaceColor),
|
||||||
// label: Text(
|
),
|
||||||
// 'x${entry.value}',
|
backgroundColor: ColorsManager.whiteColors,
|
||||||
// style: Theme.of(context)
|
shape: RoundedRectangleBorder(
|
||||||
// .textTheme
|
borderRadius: BorderRadius.circular(16),
|
||||||
// .bodySmall
|
side: const BorderSide(
|
||||||
// ?.copyWith(color: ColorsManager.spaceColor),
|
color: ColorsManager.spaceColor,
|
||||||
// ),
|
),
|
||||||
// backgroundColor: ColorsManager.whiteColors,
|
),
|
||||||
// shape: RoundedRectangleBorder(
|
),
|
||||||
// borderRadius: BorderRadius.circular(16),
|
),
|
||||||
// side: const BorderSide(
|
|
||||||
// color: ColorsManager.spaceColor,
|
|
||||||
// ),
|
|
||||||
// ),
|
|
||||||
// ),
|
|
||||||
// ),
|
|
||||||
|
|
||||||
EditChip(
|
EditChip(
|
||||||
onTap: () {},
|
onTap: () => _showAssignTagsDialog(context),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
return TextButton(
|
return TextButton(
|
||||||
onPressed: () {},
|
onPressed: () => _showAssignTagsDialog(context),
|
||||||
style: TextButton.styleFrom(
|
style: TextButton.styleFrom(
|
||||||
padding: EdgeInsets.zero,
|
padding: EdgeInsets.zero,
|
||||||
),
|
),
|
||||||
@ -83,10 +80,44 @@ class SpaceDetailsDevicesBox extends StatelessWidget {
|
|||||||
child: ButtonContentWidget(
|
child: ButtonContentWidget(
|
||||||
svgAssets: Assets.addIcon,
|
svgAssets: Assets.addIcon,
|
||||||
label: 'Add Devices',
|
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
|
class VisitorPasswordBloc
|
||||||
extends Bloc<VisitorPasswordEvent, VisitorPasswordState> {
|
extends Bloc<VisitorPasswordEvent, VisitorPasswordState> {
|
||||||
|
|
||||||
VisitorPasswordBloc() : super(VisitorPasswordInitial()) {
|
VisitorPasswordBloc() : super(VisitorPasswordInitial()) {
|
||||||
on<SelectUsageFrequency>(selectUsageFrequency);
|
on<SelectUsageFrequency>(selectUsageFrequency);
|
||||||
on<FetchDevice>(_onFetchDevice);
|
on<FetchDevice>(_onFetchDevice);
|
||||||
@ -87,6 +86,9 @@ class VisitorPasswordBloc
|
|||||||
SelectTimeVisitorPassword event,
|
SelectTimeVisitorPassword event,
|
||||||
Emitter<VisitorPasswordState> emit,
|
Emitter<VisitorPasswordState> emit,
|
||||||
) async {
|
) async {
|
||||||
|
// Ensure expirationTimeTimeStamp has a value
|
||||||
|
effectiveTimeTimeStamp ??= DateTime.now().millisecondsSinceEpoch ~/ 1000;
|
||||||
|
|
||||||
final DateTime? picked = await showDatePicker(
|
final DateTime? picked = await showDatePicker(
|
||||||
context: event.context,
|
context: event.context,
|
||||||
initialDate: DateTime.now(),
|
initialDate: DateTime.now(),
|
||||||
@ -94,86 +96,124 @@ class VisitorPasswordBloc
|
|||||||
lastDate: DateTime.now().add(const Duration(days: 5095)),
|
lastDate: DateTime.now().add(const Duration(days: 5095)),
|
||||||
);
|
);
|
||||||
|
|
||||||
if (picked != null) {
|
if (picked == null) return;
|
||||||
final TimeOfDay? timePicked = await showTimePicker(
|
|
||||||
context: event.context,
|
final TimeOfDay? timePicked = await showTimePicker(
|
||||||
initialTime: TimeOfDay.now(),
|
context: event.context,
|
||||||
builder: (context, child) {
|
initialTime: TimeOfDay.now(),
|
||||||
return Theme(
|
builder: (context, child) {
|
||||||
data: ThemeData.light().copyWith(
|
return Theme(
|
||||||
colorScheme: const ColorScheme.light(
|
data: ThemeData.light().copyWith(
|
||||||
primary: ColorsManager.primaryColor,
|
colorScheme: const ColorScheme.light(
|
||||||
onSurface: Colors.black,
|
primary: ColorsManager.primaryColor,
|
||||||
),
|
onSurface: Colors.black,
|
||||||
buttonTheme: const ButtonThemeData(
|
|
||||||
colorScheme: ColorScheme.light(
|
|
||||||
primary: Colors.green,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
child: child!,
|
),
|
||||||
);
|
child: child!,
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
if (timePicked != null) {
|
|
||||||
final selectedDateTime = DateTime(
|
|
||||||
picked.year,
|
|
||||||
picked.month,
|
|
||||||
picked.day,
|
|
||||||
timePicked.hour,
|
|
||||||
timePicked.minute,
|
|
||||||
);
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
final selectedTimestamp =
|
if (timePicked == null) return;
|
||||||
selectedDateTime.millisecondsSinceEpoch ~/ 1000;
|
|
||||||
|
|
||||||
if (event.isStart) {
|
final selectedDateTime = DateTime(
|
||||||
if (expirationTimeTimeStamp != null &&
|
picked.year,
|
||||||
selectedTimestamp > expirationTimeTimeStamp!) {
|
picked.month,
|
||||||
CustomSnackBar.displaySnackBar(
|
picked.day,
|
||||||
|
timePicked.hour,
|
||||||
|
timePicked.minute,
|
||||||
|
);
|
||||||
|
final selectedTimestamp = selectedDateTime.millisecondsSinceEpoch ~/ 1000;
|
||||||
|
final currentTimestamp = DateTime.now().millisecondsSinceEpoch ~/ 1000;
|
||||||
|
|
||||||
|
if (event.isStart) {
|
||||||
|
// START TIME VALIDATION
|
||||||
|
if (expirationTimeTimeStamp != null &&
|
||||||
|
selectedTimestamp > expirationTimeTimeStamp!) {
|
||||||
|
await showDialog<void>(
|
||||||
|
context: event.context,
|
||||||
|
builder: (context) => AlertDialog(
|
||||||
|
title: const Text(
|
||||||
'Effective Time cannot be later than Expiration Time.',
|
'Effective Time cannot be later than Expiration Time.',
|
||||||
);
|
),
|
||||||
return;
|
actionsAlignment: MainAxisAlignment.center,
|
||||||
}
|
content: FilledButton(
|
||||||
if(selectedTimestamp < DateTime.now().millisecondsSinceEpoch ~/ 1000) {
|
onPressed: () {
|
||||||
if(selectedTimestamp < DateTime.now().millisecondsSinceEpoch ~/ 1000) {
|
Navigator.of(event.context).pop();
|
||||||
await showDialog<void>(
|
add(SelectTimeVisitorPassword(
|
||||||
context: event.context,
|
context: event.context,
|
||||||
builder: (context) => AlertDialog(
|
isStart: true,
|
||||||
title: const Text('Effective Time cannot be earlier than current time.'),
|
isRepeat: false,
|
||||||
actionsAlignment: MainAxisAlignment.center,
|
));
|
||||||
content:
|
},
|
||||||
FilledButton(
|
child: const Text('OK'),
|
||||||
onPressed: () {
|
),
|
||||||
Navigator.of(event.context).pop();
|
),
|
||||||
add(SelectTimeVisitorPassword(context: event.context, isStart: true, isRepeat: false));
|
);
|
||||||
},
|
return;
|
||||||
child: const Text('OK'),
|
|
||||||
),
|
|
||||||
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
effectiveTimeTimeStamp = selectedTimestamp;
|
|
||||||
startTimeAccess = selectedDateTime.toString().split('.').first;
|
|
||||||
} else {
|
|
||||||
if (effectiveTimeTimeStamp != null &&
|
|
||||||
selectedTimestamp < effectiveTimeTimeStamp!) {
|
|
||||||
CustomSnackBar.displaySnackBar(
|
|
||||||
'Expiration Time cannot be earlier than Effective Time.',
|
|
||||||
);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
expirationTimeTimeStamp = selectedTimestamp;
|
|
||||||
endTimeAccess = selectedDateTime.toString().split('.').first;
|
|
||||||
}
|
|
||||||
emit(ChangeTimeState());
|
|
||||||
emit(VisitorPasswordInitial());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (selectedTimestamp < currentTimestamp) {
|
||||||
|
await showDialog<void>(
|
||||||
|
context: event.context,
|
||||||
|
builder: (context) => AlertDialog(
|
||||||
|
title: const Text(
|
||||||
|
'Effective Time cannot be earlier than current time.',
|
||||||
|
),
|
||||||
|
actionsAlignment: MainAxisAlignment.center,
|
||||||
|
content: FilledButton(
|
||||||
|
onPressed: () {
|
||||||
|
Navigator.of(event.context).pop();
|
||||||
|
add(SelectTimeVisitorPassword(
|
||||||
|
context: event.context,
|
||||||
|
isStart: true,
|
||||||
|
isRepeat: false,
|
||||||
|
));
|
||||||
|
},
|
||||||
|
child: const Text('OK'),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Save effective time
|
||||||
|
effectiveTimeTimeStamp = selectedTimestamp;
|
||||||
|
startTimeAccess = selectedDateTime.toString().split('.').first;
|
||||||
|
} else {
|
||||||
|
// END TIME VALIDATION
|
||||||
|
if (effectiveTimeTimeStamp != null &&
|
||||||
|
selectedTimestamp < effectiveTimeTimeStamp!) {
|
||||||
|
await showDialog<void>(
|
||||||
|
context: event.context,
|
||||||
|
builder: (context) => AlertDialog(
|
||||||
|
title: const Text(
|
||||||
|
'Expiration Time cannot be earlier than Effective Time.',
|
||||||
|
),
|
||||||
|
actionsAlignment: MainAxisAlignment.center,
|
||||||
|
content: FilledButton(
|
||||||
|
onPressed: () {
|
||||||
|
Navigator.of(event.context).pop();
|
||||||
|
add(SelectTimeVisitorPassword(
|
||||||
|
context: event.context,
|
||||||
|
isStart: false,
|
||||||
|
isRepeat: false,
|
||||||
|
));
|
||||||
|
},
|
||||||
|
child: const Text('OK'),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Save expiration time
|
||||||
|
expirationTimeTimeStamp = selectedTimestamp;
|
||||||
|
endTimeAccess = selectedDateTime.toString().split('.').first;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
emit(ChangeTimeState());
|
||||||
|
emit(VisitorPasswordInitial());
|
||||||
}
|
}
|
||||||
|
|
||||||
bool toggleRepeat(
|
bool toggleRepeat(
|
||||||
@ -213,7 +253,7 @@ class VisitorPasswordBloc
|
|||||||
FetchDevice event, Emitter<VisitorPasswordState> emit) async {
|
FetchDevice event, Emitter<VisitorPasswordState> emit) async {
|
||||||
try {
|
try {
|
||||||
emit(DeviceLoaded());
|
emit(DeviceLoaded());
|
||||||
final projectUuid = await ProjectManager.getProjectUUID() ?? '';
|
final projectUuid = await ProjectManager.getProjectUUID() ?? '';
|
||||||
|
|
||||||
data = await AccessMangApi().fetchDoorLockDeviceList(projectUuid);
|
data = await AccessMangApi().fetchDoorLockDeviceList(projectUuid);
|
||||||
emit(TableLoaded(data));
|
emit(TableLoaded(data));
|
||||||
|
@ -14,7 +14,8 @@ class Assets {
|
|||||||
static const String rightLine = 'assets/images/right_line.png';
|
static const String rightLine = 'assets/images/right_line.png';
|
||||||
static const String google = 'assets/images/google.svg';
|
static const String google = 'assets/images/google.svg';
|
||||||
static const String facebook = 'assets/images/facebook.svg';
|
static const String facebook = 'assets/images/facebook.svg';
|
||||||
static const String invisiblePassword = 'assets/images/Password_invisible.svg';
|
static const String invisiblePassword =
|
||||||
|
'assets/images/Password_invisible.svg';
|
||||||
static const String visiblePassword = 'assets/images/password_visible.svg';
|
static const String visiblePassword = 'assets/images/password_visible.svg';
|
||||||
static const String accessIcon = 'assets/images/access_icon.svg';
|
static const String accessIcon = 'assets/images/access_icon.svg';
|
||||||
static const String spaseManagementIcon =
|
static const String spaseManagementIcon =
|
||||||
@ -33,7 +34,8 @@ class Assets {
|
|||||||
static const String emptyTable = 'assets/images/empty_table.svg';
|
static const String emptyTable = 'assets/images/empty_table.svg';
|
||||||
|
|
||||||
// General assets
|
// General assets
|
||||||
static const String motionlessDetection = 'assets/icons/motionless_detection.svg';
|
static const String motionlessDetection =
|
||||||
|
'assets/icons/motionless_detection.svg';
|
||||||
static const String acHeating = 'assets/icons/ac_heating.svg';
|
static const String acHeating = 'assets/icons/ac_heating.svg';
|
||||||
static const String acPowerOff = 'assets/icons/ac_power_off.svg';
|
static const String acPowerOff = 'assets/icons/ac_power_off.svg';
|
||||||
static const String acFanMiddle = 'assets/icons/ac_fan_middle.svg';
|
static const String acFanMiddle = 'assets/icons/ac_fan_middle.svg';
|
||||||
@ -70,19 +72,22 @@ class Assets {
|
|||||||
'assets/icons/automation_functions/temp_password_unlock.svg';
|
'assets/icons/automation_functions/temp_password_unlock.svg';
|
||||||
static const String doorlockNormalOpen =
|
static const String doorlockNormalOpen =
|
||||||
'assets/icons/automation_functions/doorlock_normal_open.svg';
|
'assets/icons/automation_functions/doorlock_normal_open.svg';
|
||||||
static const String doorbell = 'assets/icons/automation_functions/doorbell.svg';
|
static const String doorbell =
|
||||||
|
'assets/icons/automation_functions/doorbell.svg';
|
||||||
static const String remoteUnlockViaApp =
|
static const String remoteUnlockViaApp =
|
||||||
'assets/icons/automation_functions/remote_unlock_via_app.svg';
|
'assets/icons/automation_functions/remote_unlock_via_app.svg';
|
||||||
static const String doubleLock =
|
static const String doubleLock =
|
||||||
'assets/icons/automation_functions/double_lock.svg';
|
'assets/icons/automation_functions/double_lock.svg';
|
||||||
static const String selfTestResult =
|
static const String selfTestResult =
|
||||||
'assets/icons/automation_functions/self_test_result.svg';
|
'assets/icons/automation_functions/self_test_result.svg';
|
||||||
static const String lockAlarm = 'assets/icons/automation_functions/lock_alarm.svg';
|
static const String lockAlarm =
|
||||||
|
'assets/icons/automation_functions/lock_alarm.svg';
|
||||||
static const String presenceState =
|
static const String presenceState =
|
||||||
'assets/icons/automation_functions/presence_state.svg';
|
'assets/icons/automation_functions/presence_state.svg';
|
||||||
static const String currentTemp =
|
static const String currentTemp =
|
||||||
'assets/icons/automation_functions/current_temp.svg';
|
'assets/icons/automation_functions/current_temp.svg';
|
||||||
static const String presence = 'assets/icons/automation_functions/presence.svg';
|
static const String presence =
|
||||||
|
'assets/icons/automation_functions/presence.svg';
|
||||||
static const String residualElectricity =
|
static const String residualElectricity =
|
||||||
'assets/icons/automation_functions/residual_electricity.svg';
|
'assets/icons/automation_functions/residual_electricity.svg';
|
||||||
static const String hijackAlarm =
|
static const String hijackAlarm =
|
||||||
@ -99,12 +104,15 @@ class Assets {
|
|||||||
|
|
||||||
// Presence Sensor Assets
|
// Presence Sensor Assets
|
||||||
static const String sensorMotionIcon = 'assets/icons/sensor_motion_ic.svg';
|
static const String sensorMotionIcon = 'assets/icons/sensor_motion_ic.svg';
|
||||||
static const String sensorPresenceIcon = 'assets/icons/sensor_presence_ic.svg';
|
static const String sensorPresenceIcon =
|
||||||
|
'assets/icons/sensor_presence_ic.svg';
|
||||||
static const String sensorVacantIcon = 'assets/icons/sensor_vacant_ic.svg';
|
static const String sensorVacantIcon = 'assets/icons/sensor_vacant_ic.svg';
|
||||||
static const String illuminanceRecordIcon =
|
static const String illuminanceRecordIcon =
|
||||||
'assets/icons/illuminance_record_ic.svg';
|
'assets/icons/illuminance_record_ic.svg';
|
||||||
static const String presenceRecordIcon = 'assets/icons/presence_record_ic.svg';
|
static const String presenceRecordIcon =
|
||||||
static const String helpDescriptionIcon = 'assets/icons/help_description_ic.svg';
|
'assets/icons/presence_record_ic.svg';
|
||||||
|
static const String helpDescriptionIcon =
|
||||||
|
'assets/icons/help_description_ic.svg';
|
||||||
|
|
||||||
static const String lightPulp = 'assets/icons/light_pulb.svg';
|
static const String lightPulp = 'assets/icons/light_pulb.svg';
|
||||||
static const String acDevice = 'assets/icons/ac_device.svg';
|
static const String acDevice = 'assets/icons/ac_device.svg';
|
||||||
@ -158,10 +166,12 @@ class Assets {
|
|||||||
static const String unit = 'assets/icons/unit_icon.svg';
|
static const String unit = 'assets/icons/unit_icon.svg';
|
||||||
static const String villa = 'assets/icons/villa_icon.svg';
|
static const String villa = 'assets/icons/villa_icon.svg';
|
||||||
static const String iconEdit = 'assets/icons/icon_edit_icon.svg';
|
static const String iconEdit = 'assets/icons/icon_edit_icon.svg';
|
||||||
static const String textFieldSearch = 'assets/icons/textfield_search_icon.svg';
|
static const String textFieldSearch =
|
||||||
|
'assets/icons/textfield_search_icon.svg';
|
||||||
static const String roundedAddIcon = 'assets/icons/rounded_add_icon.svg';
|
static const String roundedAddIcon = 'assets/icons/rounded_add_icon.svg';
|
||||||
static const String addIcon = 'assets/icons/add_icon.svg';
|
static const String addIcon = 'assets/icons/add_icon.svg';
|
||||||
static const String smartThermostatIcon = 'assets/icons/smart_thermostat_icon.svg';
|
static const String smartThermostatIcon =
|
||||||
|
'assets/icons/smart_thermostat_icon.svg';
|
||||||
static const String smartLightIcon = 'assets/icons/smart_light_icon.svg';
|
static const String smartLightIcon = 'assets/icons/smart_light_icon.svg';
|
||||||
static const String presenceSensor = 'assets/icons/presence_sensor.svg';
|
static const String presenceSensor = 'assets/icons/presence_sensor.svg';
|
||||||
static const String Gang3SwitchIcon = 'assets/icons/3_Gang_switch_icon.svg';
|
static const String Gang3SwitchIcon = 'assets/icons/3_Gang_switch_icon.svg';
|
||||||
@ -209,7 +219,8 @@ class Assets {
|
|||||||
//assets/icons/water_leak_normal.svg
|
//assets/icons/water_leak_normal.svg
|
||||||
static const String waterLeakNormal = 'assets/icons/water_leak_normal.svg';
|
static const String waterLeakNormal = 'assets/icons/water_leak_normal.svg';
|
||||||
//assets/icons/water_leak_detected.svg
|
//assets/icons/water_leak_detected.svg
|
||||||
static const String waterLeakDetected = 'assets/icons/water_leak_detected.svg';
|
static const String waterLeakDetected =
|
||||||
|
'assets/icons/water_leak_detected.svg';
|
||||||
|
|
||||||
//assets/icons/automation_records.svg
|
//assets/icons/automation_records.svg
|
||||||
static const String automationRecords = 'assets/icons/automation_records.svg';
|
static const String automationRecords = 'assets/icons/automation_records.svg';
|
||||||
@ -280,13 +291,16 @@ class Assets {
|
|||||||
'assets/icons/functions_icons/sensitivity.svg';
|
'assets/icons/functions_icons/sensitivity.svg';
|
||||||
static const String assetsSensitivityOperationIcon =
|
static const String assetsSensitivityOperationIcon =
|
||||||
'assets/icons/functions_icons/sesitivity_operation_icon.svg';
|
'assets/icons/functions_icons/sesitivity_operation_icon.svg';
|
||||||
static const String assetsAcPower = 'assets/icons/functions_icons/ac_power.svg';
|
static const String assetsAcPower =
|
||||||
|
'assets/icons/functions_icons/ac_power.svg';
|
||||||
static const String assetsAcPowerOFF =
|
static const String assetsAcPowerOFF =
|
||||||
'assets/icons/functions_icons/ac_power_off.svg';
|
'assets/icons/functions_icons/ac_power_off.svg';
|
||||||
static const String assetsChildLock =
|
static const String assetsChildLock =
|
||||||
'assets/icons/functions_icons/child_lock.svg';
|
'assets/icons/functions_icons/child_lock.svg';
|
||||||
static const String assetsFreezing = 'assets/icons/functions_icons/freezing.svg';
|
static const String assetsFreezing =
|
||||||
static const String assetsFanSpeed = 'assets/icons/functions_icons/fan_speed.svg';
|
'assets/icons/functions_icons/freezing.svg';
|
||||||
|
static const String assetsFanSpeed =
|
||||||
|
'assets/icons/functions_icons/fan_speed.svg';
|
||||||
static const String assetsAcCooling =
|
static const String assetsAcCooling =
|
||||||
'assets/icons/functions_icons/ac_cooling.svg';
|
'assets/icons/functions_icons/ac_cooling.svg';
|
||||||
static const String assetsAcHeating =
|
static const String assetsAcHeating =
|
||||||
@ -295,7 +309,8 @@ class Assets {
|
|||||||
'assets/icons/functions_icons/celsius_degrees.svg';
|
'assets/icons/functions_icons/celsius_degrees.svg';
|
||||||
static const String assetsTempreture =
|
static const String assetsTempreture =
|
||||||
'assets/icons/functions_icons/tempreture.svg';
|
'assets/icons/functions_icons/tempreture.svg';
|
||||||
static const String assetsAcFanLow = 'assets/icons/functions_icons/ac_fan_low.svg';
|
static const String assetsAcFanLow =
|
||||||
|
'assets/icons/functions_icons/ac_fan_low.svg';
|
||||||
static const String assetsAcFanMiddle =
|
static const String assetsAcFanMiddle =
|
||||||
'assets/icons/functions_icons/ac_fan_middle.svg';
|
'assets/icons/functions_icons/ac_fan_middle.svg';
|
||||||
static const String assetsAcFanHigh =
|
static const String assetsAcFanHigh =
|
||||||
@ -314,7 +329,8 @@ class Assets {
|
|||||||
'assets/icons/functions_icons/far_detection.svg';
|
'assets/icons/functions_icons/far_detection.svg';
|
||||||
static const String assetsFarDetectionFunction =
|
static const String assetsFarDetectionFunction =
|
||||||
'assets/icons/functions_icons/far_detection_function.svg';
|
'assets/icons/functions_icons/far_detection_function.svg';
|
||||||
static const String assetsIndicator = 'assets/icons/functions_icons/indicator.svg';
|
static const String assetsIndicator =
|
||||||
|
'assets/icons/functions_icons/indicator.svg';
|
||||||
static const String assetsMotionDetection =
|
static const String assetsMotionDetection =
|
||||||
'assets/icons/functions_icons/motion_detection.svg';
|
'assets/icons/functions_icons/motion_detection.svg';
|
||||||
static const String assetsMotionlessDetection =
|
static const String assetsMotionlessDetection =
|
||||||
@ -327,7 +343,8 @@ class Assets {
|
|||||||
'assets/icons/functions_icons/master_state.svg';
|
'assets/icons/functions_icons/master_state.svg';
|
||||||
static const String assetsSwitchAlarmSound =
|
static const String assetsSwitchAlarmSound =
|
||||||
'assets/icons/functions_icons/switch_alarm_sound.svg';
|
'assets/icons/functions_icons/switch_alarm_sound.svg';
|
||||||
static const String assetsResetOff = 'assets/icons/functions_icons/reset_off.svg';
|
static const String assetsResetOff =
|
||||||
|
'assets/icons/functions_icons/reset_off.svg';
|
||||||
|
|
||||||
// Assets for automation_functions
|
// Assets for automation_functions
|
||||||
static const String assetsCardUnlock =
|
static const String assetsCardUnlock =
|
||||||
@ -371,13 +388,15 @@ class Assets {
|
|||||||
static const String activeUser = 'assets/icons/active_user.svg';
|
static const String activeUser = 'assets/icons/active_user.svg';
|
||||||
static const String deActiveUser = 'assets/icons/deactive_user.svg';
|
static const String deActiveUser = 'assets/icons/deactive_user.svg';
|
||||||
static const String invitedIcon = 'assets/icons/invited_icon.svg';
|
static const String invitedIcon = 'assets/icons/invited_icon.svg';
|
||||||
static const String rectangleCheckBox = 'assets/icons/rectangle_check_box.png';
|
static const String rectangleCheckBox =
|
||||||
|
'assets/icons/rectangle_check_box.png';
|
||||||
static const String CheckBoxChecked = 'assets/icons/box_checked.png';
|
static const String CheckBoxChecked = 'assets/icons/box_checked.png';
|
||||||
static const String emptyBox = 'assets/icons/empty_box.png';
|
static const String emptyBox = 'assets/icons/empty_box.png';
|
||||||
static const String completeProcessIcon =
|
static const String completeProcessIcon =
|
||||||
'assets/icons/compleate_process_icon.svg';
|
'assets/icons/compleate_process_icon.svg';
|
||||||
static const String completedDoneIcon = 'assets/images/completed_done.svg';
|
static const String completedDoneIcon = 'assets/images/completed_done.svg';
|
||||||
static const String currentProcessIcon = 'assets/icons/current_process_icon.svg';
|
static const String currentProcessIcon =
|
||||||
|
'assets/icons/current_process_icon.svg';
|
||||||
static const String uncomplete_ProcessIcon =
|
static const String uncomplete_ProcessIcon =
|
||||||
'assets/icons/uncompleate_process_icon.svg';
|
'assets/icons/uncompleate_process_icon.svg';
|
||||||
static const String wrongProcessIcon = 'assets/icons/wrong_process_icon.svg';
|
static const String wrongProcessIcon = 'assets/icons/wrong_process_icon.svg';
|
||||||
@ -398,9 +417,11 @@ class Assets {
|
|||||||
static const String successIcon = 'assets/icons/success_icon.svg';
|
static const String successIcon = 'assets/icons/success_icon.svg';
|
||||||
static const String spaceLocationIcon = 'assets/icons/spaseLocationIcon.svg';
|
static const String spaceLocationIcon = 'assets/icons/spaseLocationIcon.svg';
|
||||||
static const String scenesPlayIcon = 'assets/icons/scenesPlayIcon.png';
|
static const String scenesPlayIcon = 'assets/icons/scenesPlayIcon.png';
|
||||||
static const String scenesPlayIconCheck = 'assets/icons/scenesPlayIconCheck.png';
|
static const String scenesPlayIconCheck =
|
||||||
|
'assets/icons/scenesPlayIconCheck.png';
|
||||||
static const String presenceStateIcon = 'assets/icons/presence_state.svg';
|
static const String presenceStateIcon = 'assets/icons/presence_state.svg';
|
||||||
static const String currentDistanceIcon = 'assets/icons/current_distance_icon.svg';
|
static const String currentDistanceIcon =
|
||||||
|
'assets/icons/current_distance_icon.svg';
|
||||||
|
|
||||||
static const String farDetectionIcon = 'assets/icons/far_detection_icon.svg';
|
static const String farDetectionIcon = 'assets/icons/far_detection_icon.svg';
|
||||||
static const String motionDetectionSensitivityIcon =
|
static const String motionDetectionSensitivityIcon =
|
||||||
@ -423,29 +444,44 @@ class Assets {
|
|||||||
static const String cpsMode4 = 'assets/icons/cps_mode4.svg';
|
static const String cpsMode4 = 'assets/icons/cps_mode4.svg';
|
||||||
static const String closeToMotion = 'assets/icons/close_to_motion.svg';
|
static const String closeToMotion = 'assets/icons/close_to_motion.svg';
|
||||||
static const String farAwayMotion = 'assets/icons/far_away_motion.svg';
|
static const String farAwayMotion = 'assets/icons/far_away_motion.svg';
|
||||||
static const String communicationFault = 'assets/icons/communication_fault.svg';
|
static const String communicationFault =
|
||||||
|
'assets/icons/communication_fault.svg';
|
||||||
static const String radarFault = 'assets/icons/radar_fault.svg';
|
static const String radarFault = 'assets/icons/radar_fault.svg';
|
||||||
static const String selfTestingSuccess = 'assets/icons/self_testing_success.svg';
|
static const String selfTestingSuccess =
|
||||||
static const String selfTestingFailure = 'assets/icons/self_testing_failure.svg';
|
'assets/icons/self_testing_success.svg';
|
||||||
static const String selfTestingTimeout = 'assets/icons/self_testing_timeout.svg';
|
static const String selfTestingFailure =
|
||||||
|
'assets/icons/self_testing_failure.svg';
|
||||||
|
static const String selfTestingTimeout =
|
||||||
|
'assets/icons/self_testing_timeout.svg';
|
||||||
static const String movingSpeed = 'assets/icons/moving_speed.svg';
|
static const String movingSpeed = 'assets/icons/moving_speed.svg';
|
||||||
static const String boundary = 'assets/icons/boundary.svg';
|
static const String boundary = 'assets/icons/boundary.svg';
|
||||||
static const String motionMeter = 'assets/icons/motion_meter.svg';
|
static const String motionMeter = 'assets/icons/motion_meter.svg';
|
||||||
static const String spatialStaticValue = 'assets/icons/spatial_static_value.svg';
|
static const String spatialStaticValue =
|
||||||
static const String spatialMotionValue = 'assets/icons/spatial_motion_value.svg';
|
'assets/icons/spatial_static_value.svg';
|
||||||
|
static const String spatialMotionValue =
|
||||||
|
'assets/icons/spatial_motion_value.svg';
|
||||||
static const String presenceJudgementThrshold =
|
static const String presenceJudgementThrshold =
|
||||||
'assets/icons/presence_judgement_threshold.svg';
|
'assets/icons/presence_judgement_threshold.svg';
|
||||||
static const String spaceType = 'assets/icons/space_type.svg';
|
static const String spaceType = 'assets/icons/space_type.svg';
|
||||||
static const String sportsPara = 'assets/icons/sports_para.svg';
|
static const String sportsPara = 'assets/icons/sports_para.svg';
|
||||||
static const String sensitivityFeature1 = 'assets/icons/sensitivity_feature_1.svg';
|
static const String sensitivityFeature1 =
|
||||||
static const String sensitivityFeature2 = 'assets/icons/sensitivity_feature_2.svg';
|
'assets/icons/sensitivity_feature_1.svg';
|
||||||
static const String sensitivityFeature3 = 'assets/icons/sensitivity_feature_3.svg';
|
static const String sensitivityFeature2 =
|
||||||
static const String sensitivityFeature4 = 'assets/icons/sensitivity_feature_4.svg';
|
'assets/icons/sensitivity_feature_2.svg';
|
||||||
static const String sensitivityFeature5 = 'assets/icons/sensitivity_feature_5.svg';
|
static const String sensitivityFeature3 =
|
||||||
static const String sensitivityFeature6 = 'assets/icons/sensitivity_feature_6.svg';
|
'assets/icons/sensitivity_feature_3.svg';
|
||||||
static const String sensitivityFeature7 = 'assets/icons/sensitivity_feature_7.svg';
|
static const String sensitivityFeature4 =
|
||||||
static const String sensitivityFeature8 = 'assets/icons/sensitivity_feature_8.svg';
|
'assets/icons/sensitivity_feature_4.svg';
|
||||||
static const String sensitivityFeature9 = 'assets/icons/sensitivity_feature_9.svg';
|
static const String sensitivityFeature5 =
|
||||||
|
'assets/icons/sensitivity_feature_5.svg';
|
||||||
|
static const String sensitivityFeature6 =
|
||||||
|
'assets/icons/sensitivity_feature_6.svg';
|
||||||
|
static const String sensitivityFeature7 =
|
||||||
|
'assets/icons/sensitivity_feature_7.svg';
|
||||||
|
static const String sensitivityFeature8 =
|
||||||
|
'assets/icons/sensitivity_feature_8.svg';
|
||||||
|
static const String sensitivityFeature9 =
|
||||||
|
'assets/icons/sensitivity_feature_9.svg';
|
||||||
static const String deviceTagIcon = 'assets/icons/device_tag_ic.svg';
|
static const String deviceTagIcon = 'assets/icons/device_tag_ic.svg';
|
||||||
static const String targetConfirmTimeIcon =
|
static const String targetConfirmTimeIcon =
|
||||||
'assets/icons/target_confirm_time_icon.svg';
|
'assets/icons/target_confirm_time_icon.svg';
|
||||||
@ -453,10 +489,13 @@ class Assets {
|
|||||||
static const String indentLevelIcon = 'assets/icons/indent_level_icon.svg';
|
static const String indentLevelIcon = 'assets/icons/indent_level_icon.svg';
|
||||||
static const String triggerLevelIcon = 'assets/icons/trigger_level_icon.svg';
|
static const String triggerLevelIcon = 'assets/icons/trigger_level_icon.svg';
|
||||||
static const String blankCalendar = 'assets/icons/blank_calendar.svg';
|
static const String blankCalendar = 'assets/icons/blank_calendar.svg';
|
||||||
static const String refreshStatusIcon = 'assets/icons/refresh_status_icon.svg';
|
static const String refreshStatusIcon =
|
||||||
static const String energyConsumedIcon = 'assets/icons/energy_consumed_icon.svg';
|
'assets/icons/refresh_status_icon.svg';
|
||||||
|
static const String energyConsumedIcon =
|
||||||
|
'assets/icons/energy_consumed_icon.svg';
|
||||||
|
|
||||||
static const String closeSettingsIcon = 'assets/icons/close_settings_icon.svg';
|
static const String closeSettingsIcon =
|
||||||
|
'assets/icons/close_settings_icon.svg';
|
||||||
|
|
||||||
static const String editNameIconSettings =
|
static const String editNameIconSettings =
|
||||||
'assets/icons/edit_name_icon_settings.svg';
|
'assets/icons/edit_name_icon_settings.svg';
|
||||||
@ -476,4 +515,6 @@ class Assets {
|
|||||||
'assets/icons/empty_energy_management_per_device.svg';
|
'assets/icons/empty_energy_management_per_device.svg';
|
||||||
static const String emptyHeatmap = 'assets/icons/empty_heatmap.svg';
|
static const String emptyHeatmap = 'assets/icons/empty_heatmap.svg';
|
||||||
static const String emptyRangeOfAqi = 'assets/icons/empty_range_of_aqi.svg';
|
static const String emptyRangeOfAqi = 'assets/icons/empty_range_of_aqi.svg';
|
||||||
|
static const String homeIcon = 'assets/icons/home_icon.svg';
|
||||||
|
static const String groupIcon = 'assets/icons/group_icon.svg';
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user