mirror of
https://github.com/SyncrowIOT/web.git
synced 2025-07-10 15:17:31 +00:00
Compare commits
45 Commits
analytics-
...
42c410d982
Author | SHA1 | Date | |
---|---|---|---|
42c410d982 | |||
368b1be3c0 | |||
c13119a4e8 | |||
35e9b606b2 | |||
387586f6f7 | |||
7cf4d0b5a9 | |||
e4a27b5651 | |||
f89660a9ff | |||
1a3dc60bd2 | |||
201348a9bf | |||
e2d4e48875 | |||
50f8158830 | |||
72af55ef98 | |||
b888f516e2 | |||
c1e61ee61d | |||
7750290be4 | |||
7f26c773a7 | |||
1adbae6735 | |||
ede2da6632 | |||
b06e4bd2ba | |||
0847cb8a41 | |||
818bdee745 | |||
0a022d8a8d | |||
f33b3e8bd2 | |||
8f0eb88567 | |||
19739c6e4d | |||
9f86b8d638 | |||
95907661d2 | |||
9c9b7d99dc | |||
037895844a | |||
c07bae5cbc | |||
e6fe9f35b0 | |||
8cb6c13cd5 | |||
949c27938a | |||
4c582b865d | |||
d7467adeda | |||
5486f0832d | |||
fd239a3907 | |||
e2d6f5eea8 | |||
289922071a | |||
8594168548 | |||
bd9a74b380 | |||
15ee79688d | |||
e5e88385e9 | |||
62d5bbce7e |
10
assets/icons/add_button_Icon.svg
Normal file
10
assets/icons/add_button_Icon.svg
Normal file
@ -0,0 +1,10 @@
|
||||
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g clip-path="url(#clip0_9795_9381)">
|
||||
<path d="M9.21875 13.5149V10.7805H6.48438C6.05286 10.7805 5.70312 10.4308 5.70312 9.99924C5.70312 9.56787 6.05286 9.21799 6.48438 9.21799H9.21875V6.48361C9.21875 6.05225 9.56848 5.70236 10 5.70236C10.4315 5.70236 10.7812 6.05225 10.7812 6.48361V9.21799H13.5156C13.9471 9.21799 14.2969 9.56787 14.2969 9.99924C14.2969 10.4308 13.9471 10.7805 13.5156 10.7805H10.7812V13.5149C10.7812 13.9464 10.4315 14.2961 10 14.2961C9.56848 14.2961 9.21875 13.9464 9.21875 13.5149ZM17.0711 2.92892C15.1823 1.04019 12.6711 0 10 0C7.32895 0 4.81766 1.04019 2.92892 2.92892C1.04019 4.81766 0 7.32895 0 10C0 12.6711 1.04019 15.1823 2.92892 17.0711C4.81766 18.9598 7.32895 20 10 20C11.8286 20 13.6179 19.5016 15.1743 18.5588C15.5434 18.3353 15.6613 17.8549 15.4378 17.486C15.2142 17.1169 14.7337 16.9989 14.3648 17.2224C13.0525 18.0173 11.5431 18.4375 10 18.4375C5.3476 18.4375 1.5625 14.6524 1.5625 10C1.5625 5.3476 5.3476 1.5625 10 1.5625C14.6524 1.5625 18.4375 5.3476 18.4375 10C18.4375 11.6637 17.9428 13.2829 17.0068 14.6831C16.767 15.0417 16.8634 15.5269 17.2221 15.7668C17.5807 16.0065 18.0659 15.91 18.3058 15.5515C19.4141 13.8936 20 11.9739 20 10C20 7.32895 18.9598 4.81766 17.0711 2.92892Z" fill="#023DFE" fill-opacity="0.7"/>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0_9795_9381">
|
||||
<rect width="20" height="20" fill="white"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
After Width: | Height: | Size: 1.4 KiB |
4
assets/icons/back_button_icon.svg
Normal file
4
assets/icons/back_button_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 20C15.514 20 19.9998 15.514 19.9998 9.99995C19.9998 4.48604 15.514 0 10 0C4.48613 0 0.000183105 4.48604 0.000183105 9.99995C0.000183105 15.514 4.48613 20 10 20ZM10 1.36892C14.7591 1.36892 18.6309 5.24077 18.631 9.99995C18.631 14.7591 14.7592 18.631 10 18.6311C5.24095 18.631 1.36919 14.7591 1.36919 9.99986C1.36919 5.24086 5.24095 1.36892 10 1.36892Z" fill="#023DFE" fill-opacity="0.7"/>
|
||||
<path d="M8.65713 14.2828C8.92444 14.55 9.35784 14.5499 9.62505 14.2828C9.89245 14.0154 9.89245 13.5821 9.62496 13.3147L6.99481 10.6846L14.6112 10.6839C14.9892 10.6838 15.2956 10.3775 15.2956 9.99926C15.2955 9.62126 14.9891 9.31499 14.6111 9.31499L6.99444 9.31572L9.62523 6.68511C9.89254 6.41781 9.89254 5.98432 9.62523 5.7171C9.49154 5.5835 9.3164 5.5166 9.14118 5.5166C8.96605 5.5166 8.79092 5.5835 8.65722 5.71701L4.85811 9.51604C4.7297 9.64435 4.65761 9.81838 4.65761 9.99999C4.6577 10.1816 4.7298 10.3555 4.8582 10.4841L8.65713 14.2828Z" fill="#023DFE" fill-opacity="0.7"/>
|
||||
</svg>
|
After Width: | Height: | Size: 1.1 KiB |
4
assets/icons/clock_icon.svg
Normal file
4
assets/icons/clock_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="M9.9999 0C4.48595 0 0 4.48586 0 9.99971C0 15.514 4.48595 20 9.9999 20C15.5138 20 19.9996 15.5139 19.9996 9.99971C19.9996 4.48586 15.5138 0 9.9999 0ZM9.9999 18.5665C5.27638 18.5665 1.43349 14.7234 1.43349 9.99971C1.43349 5.27628 5.27638 1.43349 9.9999 1.43349C14.7233 1.43349 18.5661 5.27628 18.5661 9.99971C18.5661 14.7234 14.7233 18.5665 9.9999 18.5665Z" fill="#D5D5D5"/>
|
||||
<path d="M15.1416 9.83211H10.4423V4.69526C10.4423 4.29943 10.1215 3.97852 9.72553 3.97852C9.3297 3.97852 9.00879 4.29943 9.00879 4.69526V10.5489C9.00879 10.9447 9.3297 11.2656 9.72553 11.2656H15.1416C15.5376 11.2656 15.8584 10.9447 15.8584 10.5489C15.8584 10.153 15.5375 9.83211 15.1416 9.83211Z" fill="#D5D5D5"/>
|
||||
</svg>
|
After Width: | Height: | Size: 799 B |
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 |
24
assets/icons/no_data_table.svg
Normal file
24
assets/icons/no_data_table.svg
Normal file
File diff suppressed because one or more lines are too long
After Width: | Height: | Size: 18 KiB |
@ -0,0 +1,57 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:syncrow_web/pages/access_management/booking_system/view/widgets/icon_text_button.dart';
|
||||
import 'package:syncrow_web/utils/constants/assets.dart';
|
||||
import 'package:syncrow_web/utils/constants/routes_const.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: () {
|
||||
context
|
||||
.go(RoutesConst.manageBookableSapcesPage);
|
||||
}),
|
||||
const 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,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
@ -0,0 +1,61 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:syncrow_web/pages/access_management/manage_bookable_spaces/domain/models/bookable_space_config.dart';
|
||||
import 'package:syncrow_web/pages/access_management/manage_bookable_spaces/domain/models/bookable_space_model.dart';
|
||||
import 'package:syncrow_web/pages/access_management/manage_bookable_spaces/domain/params/bookable_spaces_params.dart';
|
||||
import 'package:syncrow_web/pages/access_management/manage_bookable_spaces/domain/service/bookable_spaces_service.dart';
|
||||
import 'package:syncrow_web/pages/space_management_v2/main_module/shared/models/paginated_data_model.dart';
|
||||
|
||||
class DummyBookableSpacesService implements BookableSpacesService {
|
||||
@override
|
||||
Future<PaginatedDataModel<BookableSpacemodel>> load(
|
||||
BookableSpacesParams param) async {
|
||||
return PaginatedDataModel(
|
||||
data: [
|
||||
BookableSpacemodel(
|
||||
spaceName: 'space1',
|
||||
spaceConfig: BookableSpaceConfig(
|
||||
configUuid: 'uuid',
|
||||
bookableDays: ['wed', 'saturday'],
|
||||
availability: true,
|
||||
bookingEndTime: const TimeOfDay(hour: 13, minute: 20),
|
||||
bookingStartTime: const TimeOfDay(hour: 13, minute: 20),
|
||||
cost: 6,
|
||||
),
|
||||
spaceUuid: 'uuiiddd',
|
||||
spaceVirtualAddress: 'idvirtual1',
|
||||
),
|
||||
BookableSpacemodel(
|
||||
spaceName: 'space2',
|
||||
spaceConfig: BookableSpaceConfig(
|
||||
configUuid: 'uuid',
|
||||
bookableDays: ['wed', 'thur'],
|
||||
availability: true,
|
||||
bookingEndTime: const TimeOfDay(hour: 13, minute: 20),
|
||||
bookingStartTime: const TimeOfDay(hour: 13, minute: 20),
|
||||
cost: 6,
|
||||
),
|
||||
spaceUuid: 'uuiiddd',
|
||||
spaceVirtualAddress: 'idvirtual1',
|
||||
),
|
||||
BookableSpacemodel(
|
||||
spaceName: 'space3',
|
||||
spaceConfig: BookableSpaceConfig(
|
||||
configUuid: 'uuid',
|
||||
bookableDays: ['wed', 'fri', 'tues'],
|
||||
availability: true,
|
||||
bookingEndTime: const TimeOfDay(hour: 13, minute: 20),
|
||||
bookingStartTime: const TimeOfDay(hour: 13, minute: 20),
|
||||
cost: 6,
|
||||
),
|
||||
spaceUuid: 'uuiiddd',
|
||||
spaceVirtualAddress: 'idvirtual1',
|
||||
)
|
||||
],
|
||||
page: 1,
|
||||
size: 1,
|
||||
hasNext: false,
|
||||
totalItems: 3,
|
||||
totalPages: 1,
|
||||
);
|
||||
}
|
||||
}
|
@ -0,0 +1,73 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:syncrow_web/pages/access_management/manage_bookable_spaces/domain/models/bookable_space_model.dart';
|
||||
import 'package:syncrow_web/pages/access_management/manage_bookable_spaces/domain/models/bookable_space_config.dart';
|
||||
import 'package:syncrow_web/pages/access_management/manage_bookable_spaces/domain/params/non_bookable_spaces_params.dart';
|
||||
import 'package:syncrow_web/pages/access_management/manage_bookable_spaces/domain/service/non_bookable_spaces_service.dart';
|
||||
import 'package:syncrow_web/pages/access_management/manage_bookable_spaces/domain/service/send_bookable_spaces_to_api_params.dart';
|
||||
import 'package:syncrow_web/pages/space_management_v2/main_module/shared/models/paginated_data_model.dart';
|
||||
|
||||
class DummyNonNookableSpaces implements NonBookableSpacesService {
|
||||
@override
|
||||
Future<PaginatedDataModel<BookableSpacemodel>> load(
|
||||
NonBookableSpacesParams params) {
|
||||
return Future.value(PaginatedDataModel<BookableSpacemodel>(
|
||||
data: [
|
||||
BookableSpacemodel(
|
||||
spaceName: 'space3',
|
||||
spaceConfig: BookableSpaceConfig(
|
||||
configUuid: 'uuid',
|
||||
bookableDays: ['wed', 'saturday'],
|
||||
availability: true,
|
||||
bookingEndTime: const TimeOfDay(hour: 13, minute: 20),
|
||||
bookingStartTime: const TimeOfDay(hour: 6, minute: 20),
|
||||
cost: 6,
|
||||
),
|
||||
spaceUuid: 'uuiiddd',
|
||||
spaceVirtualAddress: 'idvirtual1',
|
||||
),
|
||||
BookableSpacemodel(
|
||||
spaceName: 'space3',
|
||||
spaceConfig: BookableSpaceConfig(
|
||||
configUuid: 'uuid',
|
||||
bookableDays: ['wed', 'saturday', 'thuresday'],
|
||||
availability: true,
|
||||
bookingEndTime: const TimeOfDay(hour: 13, minute: 20),
|
||||
bookingStartTime: const TimeOfDay(hour: 5, minute: 20),
|
||||
cost: 5,
|
||||
),
|
||||
spaceUuid: 'uuiiddd',
|
||||
spaceVirtualAddress: 'idvirtual2',
|
||||
),
|
||||
BookableSpacemodel(
|
||||
spaceName: 'space3',
|
||||
spaceConfig: BookableSpaceConfig(
|
||||
configUuid: 'uuid',
|
||||
bookableDays: [
|
||||
'saturday',
|
||||
'sunday',
|
||||
'Monday',
|
||||
'tuesday',
|
||||
'wed',
|
||||
'thuresday'
|
||||
],
|
||||
availability: true,
|
||||
bookingEndTime: const TimeOfDay(hour: 13, minute: 20),
|
||||
bookingStartTime: const TimeOfDay(hour: 15, minute: 20),
|
||||
cost: 2,
|
||||
),
|
||||
spaceUuid: 'uuiiddd',
|
||||
spaceVirtualAddress: 'idvirtual3',
|
||||
)
|
||||
],
|
||||
page: 1,
|
||||
size: 1,
|
||||
hasNext: false,
|
||||
totalPages: 0,
|
||||
totalItems: 0,
|
||||
));
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> sendBookableSpacesToApi(
|
||||
SendBookableSpacesToApiParams params) async {}
|
||||
}
|
@ -0,0 +1,45 @@
|
||||
import 'package:dio/dio.dart';
|
||||
import 'package:syncrow_web/pages/access_management/manage_bookable_spaces/domain/models/bookable_space_model.dart';
|
||||
import 'package:syncrow_web/pages/access_management/manage_bookable_spaces/domain/params/bookable_spaces_params.dart';
|
||||
import 'package:syncrow_web/pages/access_management/manage_bookable_spaces/domain/service/bookable_spaces_service.dart';
|
||||
import 'package:syncrow_web/pages/space_management_v2/main_module/shared/models/paginated_data_model.dart';
|
||||
import 'package:syncrow_web/services/api/api_exception.dart';
|
||||
import 'package:syncrow_web/services/api/http_service.dart';
|
||||
import 'package:syncrow_web/utils/constants/api_const.dart';
|
||||
|
||||
class RemoteBookableSpacesService implements BookableSpacesService {
|
||||
final HTTPService _httpService;
|
||||
RemoteBookableSpacesService(this._httpService);
|
||||
static const _defaultErrorMessage = 'Failed to load tags';
|
||||
@override
|
||||
Future<PaginatedDataModel<BookableSpacemodel>> load(
|
||||
BookableSpacesParams param) async {
|
||||
try {
|
||||
final response = await _httpService.get(
|
||||
//TODO: you have to Chage this API call Path
|
||||
path: ApiEndpoints.listTags,
|
||||
//*************|********** */
|
||||
expectedResponseModel: (json) {
|
||||
final result = json as Map<String, dynamic>;
|
||||
return PaginatedDataModel.fromJson(
|
||||
result,
|
||||
BookableSpacemodel.fromJsonList,
|
||||
);
|
||||
},
|
||||
);
|
||||
return response;
|
||||
} on DioException catch (e) {
|
||||
final message = e.response?.data as Map<String, dynamic>?;
|
||||
final error = message?['error'] as Map<String, dynamic>?;
|
||||
final errorMessage = error?['error'] as String? ?? '';
|
||||
final formattedErrorMessage = [
|
||||
_defaultErrorMessage,
|
||||
errorMessage,
|
||||
].join(': ');
|
||||
throw APIException(formattedErrorMessage);
|
||||
} catch (e) {
|
||||
final formattedErrorMessage = [_defaultErrorMessage, '$e'].join(': ');
|
||||
throw APIException(formattedErrorMessage);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,71 @@
|
||||
import 'package:dio/dio.dart';
|
||||
import 'package:syncrow_web/pages/access_management/manage_bookable_spaces/domain/models/bookable_space_model.dart';
|
||||
import 'package:syncrow_web/pages/access_management/manage_bookable_spaces/domain/params/non_bookable_spaces_params.dart';
|
||||
import 'package:syncrow_web/pages/access_management/manage_bookable_spaces/domain/service/non_bookable_spaces_service.dart';
|
||||
import 'package:syncrow_web/pages/access_management/manage_bookable_spaces/domain/service/send_bookable_spaces_to_api_params.dart';
|
||||
import 'package:syncrow_web/pages/space_management_v2/main_module/shared/models/paginated_data_model.dart';
|
||||
import 'package:syncrow_web/services/api/api_exception.dart';
|
||||
import 'package:syncrow_web/services/api/http_service.dart';
|
||||
import 'package:syncrow_web/utils/constants/api_const.dart';
|
||||
|
||||
class RemoteNonBookableSpaces implements NonBookableSpacesService {
|
||||
final HTTPService _httpService;
|
||||
RemoteNonBookableSpaces(this._httpService);
|
||||
static const _defaultErrorMessage = 'Failed to load Spaces';
|
||||
|
||||
@override
|
||||
Future<PaginatedDataModel<BookableSpacemodel>> load(
|
||||
NonBookableSpacesParams params) async {
|
||||
try {
|
||||
final response = await _httpService.get(
|
||||
//TODO: you have to Chage this API call Path
|
||||
path: ApiEndpoints.listTags,
|
||||
//*************|********** */
|
||||
expectedResponseModel: (json) {
|
||||
final result = json as Map<String, dynamic>;
|
||||
return PaginatedDataModel.fromJson(
|
||||
result,
|
||||
BookableSpacemodel.fromJsonList,
|
||||
);
|
||||
},
|
||||
);
|
||||
return response;
|
||||
} on DioException catch (e) {
|
||||
final message = e.response?.data as Map<String, dynamic>?;
|
||||
final error = message?['error'] as Map<String, dynamic>?;
|
||||
final errorMessage = error?['error'] as String? ?? '';
|
||||
final formattedErrorMessage = [
|
||||
_defaultErrorMessage,
|
||||
errorMessage,
|
||||
].join(': ');
|
||||
throw APIException(formattedErrorMessage);
|
||||
} catch (e) {
|
||||
final formattedErrorMessage = [_defaultErrorMessage, '$e'].join(': ');
|
||||
throw APIException(formattedErrorMessage);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> sendBookableSpacesToApi(
|
||||
SendBookableSpacesToApiParams params) async {
|
||||
try {
|
||||
await _httpService.post(
|
||||
path: ApiEndpoints.addBookableSpaces,
|
||||
body: params.toJson(),
|
||||
expectedResponseModel: (p0) {},
|
||||
);
|
||||
} on DioException catch (e) {
|
||||
final message = e.response?.data as Map<String, dynamic>?;
|
||||
final error = message?['error'] as Map<String, dynamic>?;
|
||||
final errorMessage = error?['error'] as String? ?? '';
|
||||
final formattedErrorMessage = [
|
||||
_defaultErrorMessage,
|
||||
errorMessage,
|
||||
].join(': ');
|
||||
throw APIException(formattedErrorMessage);
|
||||
} catch (e) {
|
||||
final formattedErrorMessage = [_defaultErrorMessage, '$e'].join(': ');
|
||||
throw APIException(formattedErrorMessage);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,45 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class BookableSpaceConfig {
|
||||
String configUuid;
|
||||
List<String> bookableDays;
|
||||
TimeOfDay bookingStartTime;
|
||||
TimeOfDay bookingEndTime;
|
||||
int cost;
|
||||
bool availability;
|
||||
BookableSpaceConfig({
|
||||
required this.configUuid,
|
||||
required this.availability,
|
||||
required this.bookableDays,
|
||||
required this.bookingEndTime,
|
||||
required this.bookingStartTime,
|
||||
required this.cost,
|
||||
});
|
||||
factory BookableSpaceConfig.zero() => BookableSpaceConfig(
|
||||
configUuid: '',
|
||||
bookableDays: [],
|
||||
availability: false,
|
||||
bookingEndTime: TimeOfDay.now(),
|
||||
bookingStartTime: TimeOfDay.now(),
|
||||
cost: -1,
|
||||
);
|
||||
factory BookableSpaceConfig.fromJson(Map<String, dynamic> json) =>
|
||||
BookableSpaceConfig(
|
||||
configUuid: json['uuid'] as String,
|
||||
bookableDays: json['daysAvailable'] as List<String>,
|
||||
availability: (json['active'] as bool?) ?? false,
|
||||
bookingEndTime: parseTimeOfDay(json['startTime'] as String),
|
||||
bookingStartTime: parseTimeOfDay(json['endTime'] as String),
|
||||
cost: json['points'] as int,
|
||||
);
|
||||
|
||||
static TimeOfDay parseTimeOfDay(String timeString) {
|
||||
final parts = timeString.split(':');
|
||||
final hour = int.parse(parts[0]);
|
||||
final minute = int.parse(parts[1]);
|
||||
return TimeOfDay(hour: hour, minute: minute);
|
||||
}
|
||||
|
||||
bool get isValid =>
|
||||
configUuid.isNotEmpty && bookableDays.isNotEmpty && cost > 0;
|
||||
}
|
@ -0,0 +1,42 @@
|
||||
import 'package:syncrow_web/pages/access_management/manage_bookable_spaces/domain/models/bookable_space_config.dart';
|
||||
|
||||
class BookableSpacemodel {
|
||||
String spaceUuid;
|
||||
String spaceName;
|
||||
BookableSpaceConfig spaceConfig;
|
||||
String spaceVirtualAddress;
|
||||
|
||||
BookableSpacemodel({
|
||||
required this.spaceUuid,
|
||||
required this.spaceName,
|
||||
required this.spaceConfig,
|
||||
required this.spaceVirtualAddress,
|
||||
});
|
||||
factory BookableSpacemodel.zero() => BookableSpacemodel(
|
||||
spaceUuid: '',
|
||||
spaceName: '',
|
||||
spaceConfig: BookableSpaceConfig.zero(),
|
||||
spaceVirtualAddress: '',
|
||||
);
|
||||
factory BookableSpacemodel.fromJson(Map<String, dynamic> json) =>
|
||||
BookableSpacemodel(
|
||||
spaceUuid: json['uuid'] as String,
|
||||
spaceName: json['spaceName'] as String,
|
||||
spaceConfig: BookableSpaceConfig.fromJson(
|
||||
json['bookableConfig'] as Map<String, dynamic>),
|
||||
spaceVirtualAddress: json['spaceVirtualAddress'] as String,
|
||||
);
|
||||
|
||||
static List<BookableSpacemodel> fromJsonList(List<dynamic> jsonList) =>
|
||||
jsonList
|
||||
.map(
|
||||
(e) => BookableSpacemodel.fromJson(e as Map<String, dynamic>),
|
||||
)
|
||||
.toList();
|
||||
|
||||
bool get isValid =>
|
||||
spaceUuid.isNotEmpty &&
|
||||
spaceName.isNotEmpty &&
|
||||
spaceVirtualAddress.isNotEmpty &&
|
||||
spaceConfig.isValid;
|
||||
}
|
@ -0,0 +1,10 @@
|
||||
class BookableSpacesParams {
|
||||
int currentPage;
|
||||
BookableSpacesParams({
|
||||
required this.currentPage,
|
||||
});
|
||||
|
||||
Map<String, dynamic> toJson() => {
|
||||
'page': currentPage,
|
||||
};
|
||||
}
|
@ -0,0 +1,12 @@
|
||||
class NonBookableSpacesParams {
|
||||
int currentPage;
|
||||
String? searchedWords;
|
||||
NonBookableSpacesParams({
|
||||
required this.currentPage,
|
||||
this.searchedWords,
|
||||
});
|
||||
|
||||
Map<String, dynamic> toJson() => {
|
||||
'page': currentPage,
|
||||
};
|
||||
}
|
@ -0,0 +1,8 @@
|
||||
import 'package:syncrow_web/pages/access_management/manage_bookable_spaces/domain/models/bookable_space_model.dart';
|
||||
import 'package:syncrow_web/pages/access_management/manage_bookable_spaces/domain/params/bookable_spaces_params.dart';
|
||||
import 'package:syncrow_web/pages/space_management_v2/main_module/shared/models/paginated_data_model.dart';
|
||||
|
||||
abstract class BookableSpacesService {
|
||||
Future<PaginatedDataModel<BookableSpacemodel>> load(
|
||||
BookableSpacesParams param);
|
||||
}
|
@ -0,0 +1,10 @@
|
||||
import 'package:syncrow_web/pages/access_management/manage_bookable_spaces/domain/models/bookable_space_model.dart';
|
||||
import 'package:syncrow_web/pages/access_management/manage_bookable_spaces/domain/params/non_bookable_spaces_params.dart';
|
||||
import 'package:syncrow_web/pages/access_management/manage_bookable_spaces/domain/service/send_bookable_spaces_to_api_params.dart';
|
||||
import 'package:syncrow_web/pages/space_management_v2/main_module/shared/models/paginated_data_model.dart';
|
||||
|
||||
abstract class NonBookableSpacesService {
|
||||
Future<PaginatedDataModel<BookableSpacemodel>> load(
|
||||
NonBookableSpacesParams params);
|
||||
Future<void> sendBookableSpacesToApi(SendBookableSpacesToApiParams params);
|
||||
}
|
@ -0,0 +1,41 @@
|
||||
import 'package:syncrow_web/pages/access_management/manage_bookable_spaces/domain/models/bookable_space_model.dart';
|
||||
import 'package:syncrow_web/utils/string_utils.dart';
|
||||
|
||||
class SendBookableSpacesToApiParams {
|
||||
List<String> spaceUuids;
|
||||
List<String> daysAvailable;
|
||||
String startTime;
|
||||
String endTime;
|
||||
int points;
|
||||
SendBookableSpacesToApiParams({
|
||||
required this.spaceUuids,
|
||||
required this.daysAvailable,
|
||||
required this.startTime,
|
||||
required this.endTime,
|
||||
required this.points,
|
||||
});
|
||||
|
||||
static SendBookableSpacesToApiParams fromBookableSpacesModel(
|
||||
List<BookableSpacemodel> bookableSpaces) {
|
||||
return SendBookableSpacesToApiParams(
|
||||
spaceUuids: bookableSpaces.map((space) => space.spaceUuid).toList(),
|
||||
daysAvailable: bookableSpaces
|
||||
.expand((space) => space.spaceConfig.bookableDays)
|
||||
.toSet()
|
||||
.toList(),
|
||||
startTime: formatTimeOfDayTo24HourString(
|
||||
bookableSpaces.first.spaceConfig.bookingStartTime),
|
||||
endTime: formatTimeOfDayTo24HourString(
|
||||
bookableSpaces.first.spaceConfig.bookingEndTime),
|
||||
points: bookableSpaces.first.spaceConfig.cost,
|
||||
);
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() => {
|
||||
'spaceUuids': spaceUuids,
|
||||
'daysAvailable': daysAvailable,
|
||||
'startTime': startTime,
|
||||
'endTime': endTime,
|
||||
'points': points
|
||||
};
|
||||
}
|
@ -0,0 +1,34 @@
|
||||
import 'package:bloc/bloc.dart';
|
||||
import 'package:equatable/equatable.dart';
|
||||
import 'package:syncrow_web/pages/access_management/manage_bookable_spaces/domain/models/bookable_space_model.dart';
|
||||
import 'package:syncrow_web/pages/access_management/manage_bookable_spaces/domain/params/bookable_spaces_params.dart';
|
||||
import 'package:syncrow_web/pages/access_management/manage_bookable_spaces/domain/service/bookable_spaces_service.dart';
|
||||
import 'package:syncrow_web/pages/space_management_v2/main_module/shared/models/paginated_data_model.dart';
|
||||
import 'package:syncrow_web/services/api/api_exception.dart';
|
||||
|
||||
part 'bookable_spaces_event.dart';
|
||||
part 'bookable_spaces_state.dart';
|
||||
|
||||
class BookableSpacesBloc
|
||||
extends Bloc<BookableSpacesEvent, BookableSpacesState> {
|
||||
final BookableSpacesService bookableSpacesService;
|
||||
BookableSpacesBloc(this.bookableSpacesService)
|
||||
: super(BookableSpacesInitial()) {
|
||||
on<LoadBookableSpacesEvent>(_onLoadBookableSpaces);
|
||||
}
|
||||
|
||||
Future<void> _onLoadBookableSpaces(
|
||||
LoadBookableSpacesEvent event, Emitter<BookableSpacesState> emit) async {
|
||||
emit(BookableSpacesLoading());
|
||||
try {
|
||||
final bookableSpaces = await bookableSpacesService.load(event.params);
|
||||
emit(BookableSpacesLoaded(bookableSpacesList: bookableSpaces));
|
||||
} on APIException catch (e) {
|
||||
emit(BookableSpacesError(error: e.message));
|
||||
} catch (e) {
|
||||
emit(
|
||||
BookableSpacesError(error: e.toString()),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,13 @@
|
||||
part of 'bookable_spaces_bloc.dart';
|
||||
|
||||
sealed class BookableSpacesEvent extends Equatable {
|
||||
const BookableSpacesEvent();
|
||||
|
||||
@override
|
||||
List<Object> get props => [];
|
||||
}
|
||||
|
||||
class LoadBookableSpacesEvent extends BookableSpacesEvent {
|
||||
final BookableSpacesParams params;
|
||||
const LoadBookableSpacesEvent(this.params);
|
||||
}
|
@ -0,0 +1,26 @@
|
||||
part of 'bookable_spaces_bloc.dart';
|
||||
|
||||
sealed class BookableSpacesState extends Equatable {
|
||||
const BookableSpacesState();
|
||||
|
||||
@override
|
||||
List<Object> get props => [];
|
||||
}
|
||||
|
||||
final class BookableSpacesInitial extends BookableSpacesState {}
|
||||
|
||||
final class BookableSpacesLoading extends BookableSpacesState {}
|
||||
|
||||
final class BookableSpacesLoaded extends BookableSpacesState {
|
||||
final PaginatedDataModel<BookableSpacemodel> bookableSpacesList;
|
||||
const BookableSpacesLoaded({
|
||||
required this.bookableSpacesList,
|
||||
});
|
||||
}
|
||||
|
||||
final class BookableSpacesError extends BookableSpacesState {
|
||||
final String error;
|
||||
const BookableSpacesError({
|
||||
required this.error,
|
||||
});
|
||||
}
|
@ -0,0 +1,99 @@
|
||||
import 'package:bloc/bloc.dart';
|
||||
import 'package:equatable/equatable.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:syncrow_web/pages/access_management/manage_bookable_spaces/domain/models/bookable_space_model.dart';
|
||||
import 'package:syncrow_web/pages/access_management/manage_bookable_spaces/domain/params/non_bookable_spaces_params.dart';
|
||||
import 'package:syncrow_web/pages/access_management/manage_bookable_spaces/domain/service/non_bookable_spaces_service.dart';
|
||||
import 'package:syncrow_web/pages/access_management/manage_bookable_spaces/domain/service/send_bookable_spaces_to_api_params.dart';
|
||||
import 'package:syncrow_web/pages/space_management_v2/main_module/shared/models/paginated_data_model.dart';
|
||||
|
||||
part 'non_bookaable_spaces_event.dart';
|
||||
part 'non_bookaable_spaces_state.dart';
|
||||
|
||||
class NonBookableSpacesBloc
|
||||
extends Bloc<NonBookableSpacesEvent, NonBookableSpacesState> {
|
||||
NonBookableSpacesService nonBookableSpacesService;
|
||||
List<BookableSpacemodel> selectedBookableSpaces = [];
|
||||
NonBookableSpacesBloc(this.nonBookableSpacesService)
|
||||
: super(NonBookableSpacesInitial()) {
|
||||
on<LoadUnBookableSpacesEvent>(_onLoadUnBookableSpacesEvent);
|
||||
on<AddToBookableSpaceEvent>(_onAddToBookableSpaceEvent);
|
||||
on<RemoveFromBookableSpaceEvent>(_onRemoveFromBookableSpaceEvent);
|
||||
on<SendBookableSpacesToApi>(_onSendBookableSpacesToApi);
|
||||
}
|
||||
|
||||
TimeOfDay get endTime =>
|
||||
selectedBookableSpaces.first.spaceConfig.bookingEndTime;
|
||||
|
||||
TimeOfDay get startTime =>
|
||||
selectedBookableSpaces.first.spaceConfig.bookingStartTime;
|
||||
Future<void> _onLoadUnBookableSpacesEvent(LoadUnBookableSpacesEvent event,
|
||||
Emitter<NonBookableSpacesState> emit) async {
|
||||
emit(NonBookableSpacesLoading());
|
||||
try {
|
||||
final nonBookableSpacesList = await nonBookableSpacesService.load(
|
||||
event.nonBookableSpacesParams,
|
||||
);
|
||||
emit(
|
||||
NonBookableSpacesLoaded(nonBookableSpaces: nonBookableSpacesList),
|
||||
);
|
||||
} catch (e) {
|
||||
emit(
|
||||
NonBookableSpacesError(e.toString()),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
void _onAddToBookableSpaceEvent(
|
||||
AddToBookableSpaceEvent event,
|
||||
Emitter<NonBookableSpacesState> emit,
|
||||
) {
|
||||
if (state is NonBookableSpacesLoaded) {
|
||||
final currentState = state as NonBookableSpacesLoaded;
|
||||
|
||||
final updatedSelectedSpaces =
|
||||
List<BookableSpacemodel>.from(currentState.selectedBookableSpaces)
|
||||
..add(event.nonBookableSpace);
|
||||
|
||||
selectedBookableSpaces.add(event.nonBookableSpace);
|
||||
|
||||
emit(
|
||||
NonBookableSpacesLoaded(
|
||||
nonBookableSpaces: currentState.nonBookableSpaces,
|
||||
selectedBookableSpaces: updatedSelectedSpaces,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
void _onRemoveFromBookableSpaceEvent(RemoveFromBookableSpaceEvent event,
|
||||
Emitter<NonBookableSpacesState> emit) {
|
||||
if (state is NonBookableSpacesLoaded) {
|
||||
final currentState = state as NonBookableSpacesLoaded;
|
||||
currentState.selectedBookableSpaces.remove(event.bookableSpace);
|
||||
selectedBookableSpaces.remove(event.bookableSpace);
|
||||
emit(
|
||||
NonBookableSpacesLoaded(
|
||||
nonBookableSpaces: currentState.nonBookableSpaces,
|
||||
selectedBookableSpaces: currentState.selectedBookableSpaces,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _onSendBookableSpacesToApi(SendBookableSpacesToApi event,
|
||||
Emitter<NonBookableSpacesState> emit) async {
|
||||
emit(NonBookableSpacesLoading());
|
||||
try {
|
||||
await nonBookableSpacesService.sendBookableSpacesToApi(
|
||||
SendBookableSpacesToApiParams.fromBookableSpacesModel(
|
||||
selectedBookableSpaces,
|
||||
),
|
||||
);
|
||||
} catch (e) {
|
||||
emit(
|
||||
NonBookableSpacesError(e.toString()),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,31 @@
|
||||
part of 'non_bookaable_spaces_bloc.dart';
|
||||
|
||||
sealed class NonBookableSpacesEvent extends Equatable {
|
||||
const NonBookableSpacesEvent();
|
||||
|
||||
@override
|
||||
List<Object> get props => [];
|
||||
}
|
||||
|
||||
class LoadUnBookableSpacesEvent extends NonBookableSpacesEvent {
|
||||
final NonBookableSpacesParams nonBookableSpacesParams;
|
||||
const LoadUnBookableSpacesEvent({
|
||||
required this.nonBookableSpacesParams,
|
||||
});
|
||||
}
|
||||
|
||||
class AddToBookableSpaceEvent extends NonBookableSpacesEvent {
|
||||
final BookableSpacemodel nonBookableSpace;
|
||||
const AddToBookableSpaceEvent({
|
||||
required this.nonBookableSpace,
|
||||
});
|
||||
}
|
||||
|
||||
class RemoveFromBookableSpaceEvent extends NonBookableSpacesEvent {
|
||||
final BookableSpacemodel bookableSpace;
|
||||
const RemoveFromBookableSpaceEvent({
|
||||
required this.bookableSpace,
|
||||
});
|
||||
}
|
||||
|
||||
class SendBookableSpacesToApi extends NonBookableSpacesEvent {}
|
@ -0,0 +1,26 @@
|
||||
part of 'non_bookaable_spaces_bloc.dart';
|
||||
|
||||
sealed class NonBookableSpacesState extends Equatable {
|
||||
const NonBookableSpacesState();
|
||||
|
||||
@override
|
||||
List<Object> get props => [];
|
||||
}
|
||||
|
||||
final class NonBookableSpacesInitial extends NonBookableSpacesState {}
|
||||
|
||||
class NonBookableSpacesLoading extends NonBookableSpacesState {}
|
||||
|
||||
class NonBookableSpacesLoaded extends NonBookableSpacesState {
|
||||
final PaginatedDataModel<BookableSpacemodel> nonBookableSpaces;
|
||||
final List<BookableSpacemodel> selectedBookableSpaces;
|
||||
const NonBookableSpacesLoaded({
|
||||
required this.nonBookableSpaces,
|
||||
this.selectedBookableSpaces = const [],
|
||||
});
|
||||
}
|
||||
|
||||
class NonBookableSpacesError extends NonBookableSpacesState {
|
||||
final String error;
|
||||
const NonBookableSpacesError(this.error);
|
||||
}
|
@ -0,0 +1,18 @@
|
||||
import 'package:bloc/bloc.dart';
|
||||
import 'package:equatable/equatable.dart';
|
||||
|
||||
part 'steps_state.dart';
|
||||
|
||||
class StepsCubit extends Cubit<StepsState> {
|
||||
StepsCubit() : super(StepsInitial());
|
||||
|
||||
void initDialogValue() {
|
||||
emit(StepOneState());
|
||||
}
|
||||
|
||||
void goToNextStep() {
|
||||
if (state is StepOneState) {
|
||||
emit(StepTwoState());
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,16 @@
|
||||
part of 'steps_cubit.dart';
|
||||
|
||||
sealed class StepsState extends Equatable {
|
||||
const StepsState();
|
||||
|
||||
@override
|
||||
List<Object> get props => [];
|
||||
}
|
||||
|
||||
final class StepsInitial extends StepsState {}
|
||||
|
||||
final class StepOneState extends StepsState {}
|
||||
|
||||
final class StepTwoState extends StepsState {}
|
||||
|
||||
final class StepEditMode extends StepsState {}
|
@ -0,0 +1,201 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:flutter_svg/svg.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:syncrow_web/pages/access_management/booking_system/view/widgets/icon_text_button.dart';
|
||||
import 'package:syncrow_web/pages/access_management/manage_bookable_spaces/data/dummy_bookable_spaces_service.dart';
|
||||
import 'package:syncrow_web/pages/access_management/manage_bookable_spaces/domain/models/bookable_space_model.dart';
|
||||
import 'package:syncrow_web/pages/access_management/manage_bookable_spaces/domain/params/bookable_spaces_params.dart';
|
||||
import 'package:syncrow_web/pages/access_management/manage_bookable_spaces/presentation/blocs/bookable_spaces_bloc/bookable_spaces_bloc.dart';
|
||||
import 'package:syncrow_web/pages/access_management/manage_bookable_spaces/presentation/screens/setup_bookable_spaces_dialog.dart';
|
||||
import 'package:syncrow_web/pages/access_management/manage_bookable_spaces/presentation/widgets/custom_data_table.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/shared/navigate_home_grid_view.dart';
|
||||
import 'package:syncrow_web/utils/constants/assets.dart';
|
||||
import 'package:syncrow_web/utils/extension/build_context_x.dart';
|
||||
import 'package:syncrow_web/utils/theme/responsive_text_theme.dart';
|
||||
import 'package:syncrow_web/web_layout/web_scaffold.dart';
|
||||
|
||||
class ManageBookableSpacesPage extends StatefulWidget {
|
||||
const ManageBookableSpacesPage({super.key});
|
||||
|
||||
@override
|
||||
State<ManageBookableSpacesPage> createState() =>
|
||||
_ManageBookableSpacesPageState();
|
||||
}
|
||||
|
||||
class _ManageBookableSpacesPageState extends State<ManageBookableSpacesPage> {
|
||||
final PageController _pageController = PageController(initialPage: 1);
|
||||
int _currentPageIndex = 1;
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return WebScaffold(
|
||||
enableMenuSidebar: false,
|
||||
appBarTitle: Text(
|
||||
'Access Management',
|
||||
style: ResponsiveTextTheme.of(context).deviceManagementTitle,
|
||||
),
|
||||
centerBody: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
TextButton(
|
||||
onPressed: () => _switchPage(0),
|
||||
child: Text(
|
||||
'Access Overview',
|
||||
style: context.textTheme.titleMedium?.copyWith(
|
||||
color: _currentPageIndex == 0 ? Colors.white : Colors.grey,
|
||||
fontWeight:
|
||||
_currentPageIndex == 0 ? FontWeight.w700 : FontWeight.w400,
|
||||
),
|
||||
),
|
||||
),
|
||||
TextButton(
|
||||
onPressed: () => _switchPage(1),
|
||||
child: Text(
|
||||
'Booking System',
|
||||
style: context.textTheme.titleMedium?.copyWith(
|
||||
color: _currentPageIndex == 1 ? Colors.white : Colors.grey,
|
||||
fontWeight:
|
||||
_currentPageIndex == 1 ? FontWeight.w700 : FontWeight.w400,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
rightBody: const NavigateHomeGridView(),
|
||||
scaffoldBody: BlocProvider(
|
||||
create: (context) => BookableSpacesBloc(
|
||||
DummyBookableSpacesService(),
|
||||
// RemoteBookableSpacesService(HTTPService()),
|
||||
)..add(LoadBookableSpacesEvent(
|
||||
BookableSpacesParams(currentPage: 1),
|
||||
)),
|
||||
child: const ManageBookableSpacesWidget(),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
void _switchPage(int index) {
|
||||
setState(() => _currentPageIndex = index);
|
||||
_pageController.jumpToPage(index);
|
||||
}
|
||||
}
|
||||
|
||||
class ManageBookableSpacesWidget extends StatelessWidget {
|
||||
const ManageBookableSpacesWidget({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
padding: const EdgeInsets.all(20),
|
||||
child: Column(
|
||||
children: [
|
||||
Expanded(
|
||||
flex: 1,
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
SvgTextButton(
|
||||
svgAsset: Assets.backButtonIcon,
|
||||
label: 'Booking Home',
|
||||
onPressed: () {
|
||||
context.pop();
|
||||
}),
|
||||
SvgTextButton(
|
||||
svgAsset: Assets.backButtonIcon,
|
||||
label: 'Set Up a Bookable Spaces',
|
||||
onPressed: () async => showDialog(
|
||||
context: context,
|
||||
builder: (context) => SetupBookableSpacesDialog(),
|
||||
),
|
||||
)
|
||||
],
|
||||
)),
|
||||
Expanded(
|
||||
flex: 9,
|
||||
child: BlocBuilder<BookableSpacesBloc, BookableSpacesState>(
|
||||
builder: (context, state) {
|
||||
if (state is BookableSpacesLoading) {
|
||||
return const CircularProgressIndicator();
|
||||
} else if (state is BookableSpacesError) {
|
||||
return Text(state.error);
|
||||
} else if (state is BookableSpacesLoaded) {
|
||||
return CustomDataTable<BookableSpacemodel>(
|
||||
items: state.bookableSpacesList.data,
|
||||
cellsWidgets: (space) => [
|
||||
DataCell(
|
||||
Padding(
|
||||
padding: const EdgeInsetsGeometry.only(left: 10),
|
||||
child: Text(space.spaceName)),
|
||||
),
|
||||
DataCell(Padding(
|
||||
padding: const EdgeInsetsGeometry.only(left: 10),
|
||||
child: Text(space.spaceVirtualAddress))),
|
||||
DataCell(SizedBox(
|
||||
width: 200,
|
||||
child: Wrap(
|
||||
spacing: 4,
|
||||
children: space.spaceConfig.bookableDays
|
||||
.map((day) => Text(
|
||||
day,
|
||||
style: const TextStyle(fontSize: 12),
|
||||
))
|
||||
.toList(),
|
||||
),
|
||||
)),
|
||||
DataCell(
|
||||
Padding(
|
||||
padding: const EdgeInsetsGeometry.only(left: 10),
|
||||
child: Text(
|
||||
space.spaceConfig.bookingStartTime.format(context),
|
||||
),
|
||||
),
|
||||
),
|
||||
DataCell(
|
||||
Padding(
|
||||
padding: const EdgeInsetsGeometry.only(left: 10),
|
||||
child: Text(
|
||||
space.spaceConfig.bookingEndTime.format(context),
|
||||
),
|
||||
),
|
||||
),
|
||||
DataCell(Padding(
|
||||
padding: const EdgeInsetsGeometry.only(left: 10),
|
||||
child: Text('${space.spaceConfig.cost} Points'))),
|
||||
DataCell(Center(
|
||||
child: Transform.scale(
|
||||
scale: 0.7,
|
||||
child: Switch(
|
||||
value: space.spaceConfig.availability,
|
||||
onChanged: (value) {},
|
||||
),
|
||||
),
|
||||
)),
|
||||
DataCell(Center(
|
||||
child: ElevatedButton(
|
||||
onPressed: () {},
|
||||
child: SvgPicture.asset(Assets.settings),
|
||||
),
|
||||
)),
|
||||
],
|
||||
columnsTitles: const [
|
||||
'space',
|
||||
'space Virtual Address',
|
||||
'Bookable Days',
|
||||
'Booking Start Time',
|
||||
'Booking End Time',
|
||||
'Cost',
|
||||
'Availability',
|
||||
'Settings',
|
||||
],
|
||||
);
|
||||
} else {
|
||||
return const SizedBox();
|
||||
}
|
||||
},
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
@ -0,0 +1,146 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:syncrow_web/pages/access_management/manage_bookable_spaces/data/dummy_non_nookable_spaces.dart';
|
||||
import 'package:syncrow_web/pages/access_management/manage_bookable_spaces/domain/params/non_bookable_spaces_params.dart';
|
||||
import 'package:syncrow_web/pages/access_management/manage_bookable_spaces/presentation/blocs/non_bookable_spaces_bloc/non_bookaable_spaces_bloc.dart';
|
||||
import 'package:syncrow_web/pages/access_management/manage_bookable_spaces/presentation/blocs/steps_cubit/cubit/steps_cubit.dart';
|
||||
import 'package:syncrow_web/pages/access_management/manage_bookable_spaces/presentation/widgets/buttons_divider_bottom_dialog_widget.dart';
|
||||
import 'package:syncrow_web/pages/access_management/manage_bookable_spaces/presentation/widgets/space_step_part_widget.dart';
|
||||
import 'package:syncrow_web/pages/access_management/manage_bookable_spaces/presentation/widgets/step_two_details_widget.dart';
|
||||
import 'package:syncrow_web/pages/access_management/manage_bookable_spaces/presentation/widgets/stepper_part_widget.dart';
|
||||
import 'package:syncrow_web/utils/color_manager.dart';
|
||||
|
||||
class SetupBookableSpacesDialog extends StatelessWidget {
|
||||
final TextEditingController pointsController = TextEditingController();
|
||||
SetupBookableSpacesDialog({super.key});
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return MultiBlocProvider(
|
||||
providers: [
|
||||
BlocProvider<StepsCubit>(
|
||||
create: (context) => StepsCubit()..initDialogValue(),
|
||||
),
|
||||
BlocProvider<NonBookableSpacesBloc>(
|
||||
create: (context) => NonBookableSpacesBloc(
|
||||
DummyNonNookableSpaces(),
|
||||
)..add(
|
||||
LoadUnBookableSpacesEvent(
|
||||
nonBookableSpacesParams:
|
||||
NonBookableSpacesParams(currentPage: 1),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
child: AlertDialog(
|
||||
contentPadding: EdgeInsets.zero,
|
||||
title: Text(
|
||||
'Set Up a Bookable Spaces',
|
||||
style: TextStyle(
|
||||
fontWeight: FontWeight.w700,
|
||||
color: ColorsManager.dialogBlueTitle,
|
||||
fontSize: 15,
|
||||
),
|
||||
),
|
||||
content: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
const Divider(),
|
||||
Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
const Expanded(
|
||||
flex: 3,
|
||||
child: StepperPartWidget(),
|
||||
),
|
||||
const SizedBox(
|
||||
height: 588,
|
||||
child: VerticalDivider(
|
||||
thickness: 0.5,
|
||||
width: 1,
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
flex: 7,
|
||||
child: DetailsStepsWidget(
|
||||
pointsController: pointsController,
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
Builder(builder: (context) {
|
||||
return ButtonsDividerBottomDialogWidget(
|
||||
onNextPressed: () {
|
||||
final stepsState = context.read<StepsCubit>().state;
|
||||
final selectedSpaces = context
|
||||
.read<NonBookableSpacesBloc>()
|
||||
.selectedBookableSpaces;
|
||||
if (stepsState is StepOneState) {
|
||||
if (selectedSpaces.isNotEmpty) {
|
||||
context.read<StepsCubit>().goToNextStep();
|
||||
} else {
|
||||
ScaffoldMessenger.of(context).clearSnackBars();
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
const SnackBar(
|
||||
content: Text('Please select at least one space.'),
|
||||
),
|
||||
);
|
||||
}
|
||||
} else if (stepsState is StepTwoState) {
|
||||
selectedSpaces.forEach(
|
||||
(e) => e.spaceConfig.cost = int.parse(
|
||||
pointsController.text.isEmpty
|
||||
? '0'
|
||||
: pointsController.text),
|
||||
);
|
||||
if (selectedSpaces.any(
|
||||
(element) => !element.isValid,
|
||||
)) {
|
||||
ScaffoldMessenger.of(context).clearSnackBars();
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
const SnackBar(
|
||||
content: Text('Please fill the required fields.'),
|
||||
),
|
||||
);
|
||||
} else {
|
||||
print(selectedSpaces.first.spaceUuid);
|
||||
}
|
||||
}
|
||||
},
|
||||
onCancelPressed: () => context.pop(),
|
||||
);
|
||||
}),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class DetailsStepsWidget extends StatelessWidget {
|
||||
final TextEditingController pointsController;
|
||||
const DetailsStepsWidget({
|
||||
super.key,
|
||||
required this.pointsController,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 30, vertical: 20),
|
||||
child: BlocBuilder<StepsCubit, StepsState>(
|
||||
builder: (context, state) {
|
||||
if (state is StepOneState) {
|
||||
return SpacesStepDetailsWidget();
|
||||
} else if (state is StepTwoState) {
|
||||
return StepTwoDetailsWidget(
|
||||
pointsController: pointsController,
|
||||
);
|
||||
} else {
|
||||
return const SizedBox();
|
||||
}
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
@ -0,0 +1,88 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:syncrow_web/pages/access_management/manage_bookable_spaces/presentation/blocs/steps_cubit/cubit/steps_cubit.dart';
|
||||
import 'package:syncrow_web/utils/color_manager.dart';
|
||||
|
||||
class ButtonsDividerBottomDialogWidget extends StatelessWidget {
|
||||
final void Function() onNextPressed;
|
||||
final void Function() onCancelPressed;
|
||||
const ButtonsDividerBottomDialogWidget({
|
||||
super.key,
|
||||
required this.onNextPressed,
|
||||
required this.onCancelPressed,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Column(
|
||||
children: [
|
||||
const Divider(
|
||||
thickness: 0.5,
|
||||
height: 1,
|
||||
),
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: InkWell(
|
||||
borderRadius: const BorderRadius.only(
|
||||
bottomLeft: Radius.circular(26),
|
||||
),
|
||||
onTap: onCancelPressed,
|
||||
child: Container(
|
||||
height: 40,
|
||||
alignment: Alignment.center,
|
||||
decoration: const BoxDecoration(
|
||||
border: Border(
|
||||
right: BorderSide(
|
||||
color: ColorsManager.grayBorder,
|
||||
),
|
||||
),
|
||||
borderRadius: BorderRadius.only(
|
||||
bottomLeft: Radius.circular(26),
|
||||
),
|
||||
),
|
||||
child: const Text(
|
||||
'Cancel',
|
||||
style: TextStyle(color: ColorsManager.grayBorder),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child: BlocBuilder<StepsCubit, StepsState>(
|
||||
builder: (context, state) {
|
||||
return InkWell(
|
||||
borderRadius: const BorderRadius.only(
|
||||
bottomRight: Radius.circular(26),
|
||||
),
|
||||
onTap: onNextPressed,
|
||||
child: Container(
|
||||
height: 40,
|
||||
alignment: Alignment.center,
|
||||
decoration: const BoxDecoration(
|
||||
border: Border(
|
||||
right: BorderSide(
|
||||
color: ColorsManager.grayBorder,
|
||||
),
|
||||
),
|
||||
borderRadius: BorderRadius.only(
|
||||
bottomRight: Radius.circular(26),
|
||||
),
|
||||
),
|
||||
child: Text(
|
||||
state is StepOneState ? 'Next' : 'Save',
|
||||
style: const TextStyle(
|
||||
color: ColorsManager.blueColor,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
)
|
||||
],
|
||||
)
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
@ -0,0 +1,37 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:syncrow_web/utils/color_manager.dart';
|
||||
|
||||
class ColumnTitleWidget extends StatelessWidget {
|
||||
final bool isFirst;
|
||||
final bool isLast;
|
||||
final String title;
|
||||
const ColumnTitleWidget({
|
||||
super.key,
|
||||
required this.title,
|
||||
required this.isFirst,
|
||||
required this.isLast,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
alignment: Alignment.centerLeft,
|
||||
padding: const EdgeInsets.only(left: 10),
|
||||
decoration: BoxDecoration(
|
||||
color: ColorsManager.graysColor,
|
||||
borderRadius: isFirst
|
||||
? const BorderRadius.only(
|
||||
topLeft: Radius.circular(12),
|
||||
)
|
||||
: isLast
|
||||
? const BorderRadius.only(
|
||||
topRight: Radius.circular(12),
|
||||
)
|
||||
: null,
|
||||
),
|
||||
child: Text(
|
||||
title,
|
||||
style: const TextStyle(color: ColorsManager.grayColor),
|
||||
));
|
||||
}
|
||||
}
|
@ -0,0 +1,55 @@
|
||||
import 'package:data_table_2/data_table_2.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_svg/flutter_svg.dart';
|
||||
import 'package:syncrow_web/pages/access_management/manage_bookable_spaces/presentation/widgets/column_title_widget.dart';
|
||||
import 'package:syncrow_web/utils/color_manager.dart';
|
||||
import 'package:syncrow_web/utils/constants/assets.dart';
|
||||
|
||||
class CustomDataTable<T> extends StatelessWidget {
|
||||
final List<String> columnsTitles;
|
||||
final List<DataCell> Function(T item) cellsWidgets;
|
||||
final List<T> items;
|
||||
|
||||
const CustomDataTable({
|
||||
super.key,
|
||||
required this.items,
|
||||
required this.cellsWidgets,
|
||||
required this.columnsTitles,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return DataTable2(
|
||||
dividerThickness: 0.5,
|
||||
columnSpacing: 2,
|
||||
horizontalMargin: 0,
|
||||
empty: SvgPicture.asset(Assets.emptyDataTable),
|
||||
decoration: BoxDecoration(
|
||||
color: ColorsManager.circleRolesBackground,
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
boxShadow: const [
|
||||
BoxShadow(
|
||||
color: ColorsManager.textGray,
|
||||
blurRadius: 12,
|
||||
offset: Offset(0, 4),
|
||||
),
|
||||
],
|
||||
),
|
||||
columns: columnsTitles.asMap().entries.map((entry) {
|
||||
final index = entry.key;
|
||||
final title = entry.value;
|
||||
|
||||
return DataColumn(
|
||||
label: ColumnTitleWidget(
|
||||
title: title,
|
||||
isFirst: index == 0,
|
||||
isLast: index == columnsTitles.length - 1,
|
||||
),
|
||||
);
|
||||
}).toList(),
|
||||
rows: items.map((item) {
|
||||
return DataRow(cells: cellsWidgets(item));
|
||||
}).toList(),
|
||||
);
|
||||
}
|
||||
}
|
@ -0,0 +1,59 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
|
||||
class SearchUnbookableSpacesWidget extends StatelessWidget {
|
||||
final String title;
|
||||
final Widget? suffix;
|
||||
final double? height;
|
||||
final double? width;
|
||||
final TextEditingController? controller;
|
||||
final List<TextInputFormatter>? inputFormatters;
|
||||
final void Function(String)? onChanged;
|
||||
const SearchUnbookableSpacesWidget({
|
||||
required this.title,
|
||||
this.controller,
|
||||
this.onChanged,
|
||||
this.suffix,
|
||||
this.height,
|
||||
this.width,
|
||||
this.inputFormatters,
|
||||
super.key,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
width: width ?? 480,
|
||||
height: height ?? 30,
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white,
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
boxShadow: const [
|
||||
BoxShadow(
|
||||
color: Color(0x26000000),
|
||||
offset: Offset(0, 4),
|
||||
blurRadius: 5,
|
||||
),
|
||||
],
|
||||
),
|
||||
child: TextField(
|
||||
controller: controller,
|
||||
inputFormatters: inputFormatters,
|
||||
onChanged: onChanged,
|
||||
decoration: InputDecoration(
|
||||
contentPadding:
|
||||
const EdgeInsets.symmetric(vertical: 5, horizontal: 15),
|
||||
hintText: title,
|
||||
hintStyle: const TextStyle(color: Colors.grey),
|
||||
border: InputBorder.none,
|
||||
suffixIcon:
|
||||
suffix ?? const Icon(Icons.search, size: 20, color: Colors.grey),
|
||||
),
|
||||
style: const TextStyle(
|
||||
fontSize: 14,
|
||||
color: Colors.grey,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
@ -0,0 +1,166 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:syncrow_web/pages/access_management/manage_bookable_spaces/domain/models/bookable_space_model.dart';
|
||||
import 'package:syncrow_web/pages/access_management/manage_bookable_spaces/domain/params/non_bookable_spaces_params.dart';
|
||||
import 'package:syncrow_web/pages/access_management/manage_bookable_spaces/presentation/blocs/non_bookable_spaces_bloc/non_bookaable_spaces_bloc.dart';
|
||||
import 'package:syncrow_web/pages/access_management/manage_bookable_spaces/presentation/widgets/search_unbookable_spaces_widget.dart';
|
||||
import 'package:syncrow_web/utils/color_manager.dart';
|
||||
|
||||
class SpacesStepDetailsWidget extends StatelessWidget {
|
||||
SpacesStepDetailsWidget({
|
||||
super.key,
|
||||
});
|
||||
Timer? _debounce;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BlocBuilder<NonBookableSpacesBloc, NonBookableSpacesState>(
|
||||
builder: (context, state) {
|
||||
if (state is NonBookableSpacesLoading) {
|
||||
return const Center(child: CircularProgressIndicator());
|
||||
} else if (state is NonBookableSpacesError) {
|
||||
return Text(state.error);
|
||||
} else if (state is NonBookableSpacesLoaded) {
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
const Text(
|
||||
'Select Space',
|
||||
style: TextStyle(
|
||||
fontWeight: FontWeight.w700,
|
||||
color: ColorsManager.blackColor,
|
||||
),
|
||||
),
|
||||
const SizedBox(
|
||||
height: 20,
|
||||
),
|
||||
Container(
|
||||
width: 450,
|
||||
height: 480,
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white,
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
boxShadow: const [
|
||||
BoxShadow(
|
||||
color: Color(0x40000000),
|
||||
offset: Offset(0, 4),
|
||||
blurRadius: 5,
|
||||
),
|
||||
],
|
||||
),
|
||||
child: Column(
|
||||
children: [
|
||||
Container(
|
||||
width: 520,
|
||||
height: 70,
|
||||
padding: const EdgeInsets.symmetric(
|
||||
vertical: 15, horizontal: 20),
|
||||
decoration: const BoxDecoration(
|
||||
color: Color(0xFFF8F8F8),
|
||||
borderRadius: BorderRadius.vertical(
|
||||
top: Radius.circular(20),
|
||||
),
|
||||
),
|
||||
child: SearchUnbookableSpacesWidget(
|
||||
title: 'Search',
|
||||
onChanged: (p0) {
|
||||
if (_debounce?.isActive ?? false) _debounce!.cancel();
|
||||
_debounce =
|
||||
Timer(const Duration(milliseconds: 500), () {
|
||||
context.read<NonBookableSpacesBloc>().add(
|
||||
LoadUnBookableSpacesEvent(
|
||||
nonBookableSpacesParams:
|
||||
NonBookableSpacesParams(
|
||||
currentPage: 1,
|
||||
searchedWords: p0,
|
||||
),
|
||||
),
|
||||
);
|
||||
});
|
||||
},
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child: Container(
|
||||
width: 490,
|
||||
decoration: const BoxDecoration(
|
||||
color: Colors.white,
|
||||
borderRadius: BorderRadius.vertical(
|
||||
bottom: Radius.circular(20),
|
||||
),
|
||||
),
|
||||
padding:
|
||||
const EdgeInsets.only(top: 10, left: 20, bottom: 5),
|
||||
child: ListView.separated(
|
||||
separatorBuilder: (context, index) => const SizedBox(
|
||||
height: 5,
|
||||
),
|
||||
itemCount: state.nonBookableSpaces.data.length,
|
||||
itemBuilder: (context, index) => CheckBoxSpaceWidget(
|
||||
nonBookableSpace:
|
||||
state.nonBookableSpaces.data[index],
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
)
|
||||
],
|
||||
);
|
||||
} else {
|
||||
return const SizedBox();
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class CheckBoxSpaceWidget extends StatefulWidget {
|
||||
final BookableSpacemodel nonBookableSpace;
|
||||
|
||||
const CheckBoxSpaceWidget({
|
||||
super.key,
|
||||
required this.nonBookableSpace,
|
||||
});
|
||||
|
||||
@override
|
||||
State<CheckBoxSpaceWidget> createState() => _CheckBoxSpaceWidgetState();
|
||||
}
|
||||
|
||||
class _CheckBoxSpaceWidgetState extends State<CheckBoxSpaceWidget> {
|
||||
bool isChecked = false;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Row(
|
||||
children: [
|
||||
Checkbox(
|
||||
value: isChecked,
|
||||
onChanged: (value) => setState(() {
|
||||
isChecked = value ?? false;
|
||||
if (isChecked) {
|
||||
context.read<NonBookableSpacesBloc>().add(
|
||||
AddToBookableSpaceEvent(
|
||||
nonBookableSpace: widget.nonBookableSpace,
|
||||
),
|
||||
);
|
||||
} else {
|
||||
context.read<NonBookableSpacesBloc>().add(
|
||||
RemoveFromBookableSpaceEvent(
|
||||
bookableSpace: widget.nonBookableSpace,
|
||||
),
|
||||
);
|
||||
}
|
||||
}),
|
||||
),
|
||||
const SizedBox(
|
||||
width: 5,
|
||||
),
|
||||
Text(widget.nonBookableSpace.spaceName),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
@ -0,0 +1,151 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:syncrow_web/pages/access_management/manage_bookable_spaces/presentation/blocs/non_bookable_spaces_bloc/non_bookaable_spaces_bloc.dart';
|
||||
import 'package:syncrow_web/pages/access_management/manage_bookable_spaces/presentation/widgets/search_unbookable_spaces_widget.dart';
|
||||
import 'package:syncrow_web/pages/access_management/manage_bookable_spaces/presentation/widgets/time_picker_widget.dart';
|
||||
import 'package:syncrow_web/pages/access_management/manage_bookable_spaces/presentation/widgets/week_checkbox_title_widget.dart';
|
||||
import 'package:syncrow_web/utils/color_manager.dart';
|
||||
import 'package:syncrow_web/utils/string_utils.dart';
|
||||
|
||||
class StepTwoDetailsWidget extends StatelessWidget {
|
||||
final TextEditingController pointsController;
|
||||
const StepTwoDetailsWidget({
|
||||
super.key,
|
||||
required this.pointsController,
|
||||
});
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return SizedBox(
|
||||
width: 450,
|
||||
height: 480,
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
const WeekDaysCheckboxRow(),
|
||||
const SizedBox(
|
||||
height: 20,
|
||||
),
|
||||
Row(
|
||||
children: [
|
||||
TitleAndTimePickerWidget(
|
||||
title: 'Booking Start Time',
|
||||
onTimePicked: (timePicked) {
|
||||
if (timePicked == null) {
|
||||
return;
|
||||
}
|
||||
final nonBookableBloc = context.read<NonBookableSpacesBloc>();
|
||||
if (isEndTimeAfterStartTime(
|
||||
timePicked, nonBookableBloc.endTime)) {
|
||||
ScaffoldMessenger.of(context).clearSnackBars();
|
||||
ScaffoldMessenger.of(context).showSnackBar(const SnackBar(
|
||||
content:
|
||||
Text("You can't choose start Time Before End time"),
|
||||
duration: Duration(seconds: 2),
|
||||
backgroundColor: ColorsManager.red,
|
||||
));
|
||||
throw Exception();
|
||||
} else {
|
||||
nonBookableBloc.selectedBookableSpaces.forEach(
|
||||
(e) => e.spaceConfig.bookingStartTime = timePicked,
|
||||
);
|
||||
}
|
||||
},
|
||||
),
|
||||
const SizedBox(
|
||||
width: 20,
|
||||
),
|
||||
TitleAndTimePickerWidget(
|
||||
title: 'Booking End Time',
|
||||
onTimePicked: (timePicked) {
|
||||
if (timePicked == null) {
|
||||
return;
|
||||
}
|
||||
final nonBookableBloc = context.read<NonBookableSpacesBloc>();
|
||||
if (isEndTimeAfterStartTime(
|
||||
nonBookableBloc.startTime, timePicked)) {
|
||||
ScaffoldMessenger.of(context).clearSnackBars();
|
||||
ScaffoldMessenger.of(context).showSnackBar(const SnackBar(
|
||||
content:
|
||||
Text("You can't choose End Time After Start time"),
|
||||
duration: Duration(seconds: 2),
|
||||
backgroundColor: ColorsManager.red,
|
||||
));
|
||||
throw Exception();
|
||||
} else {
|
||||
nonBookableBloc.selectedBookableSpaces.forEach(
|
||||
(e) => e.spaceConfig.bookingEndTime = timePicked,
|
||||
);
|
||||
}
|
||||
},
|
||||
)
|
||||
],
|
||||
),
|
||||
const SizedBox(
|
||||
height: 20,
|
||||
),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
'* ',
|
||||
style: Theme.of(context)
|
||||
.textTheme
|
||||
.bodyMedium!
|
||||
.copyWith(color: Colors.red),
|
||||
),
|
||||
const Text('Points/hrs'),
|
||||
],
|
||||
),
|
||||
const SizedBox(
|
||||
height: 5,
|
||||
),
|
||||
SearchUnbookableSpacesWidget(
|
||||
title: 'Ex: 0',
|
||||
controller: pointsController,
|
||||
inputFormatters: [FilteringTextInputFormatter.digitsOnly],
|
||||
suffix: const SizedBox(),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class TitleAndTimePickerWidget extends StatelessWidget {
|
||||
final void Function(TimeOfDay? timePicked) onTimePicked;
|
||||
final String title;
|
||||
const TitleAndTimePickerWidget({
|
||||
super.key,
|
||||
required this.onTimePicked,
|
||||
required this.title,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
'* ',
|
||||
style: Theme.of(context)
|
||||
.textTheme
|
||||
.bodyMedium!
|
||||
.copyWith(color: Colors.red),
|
||||
),
|
||||
Text(title),
|
||||
],
|
||||
),
|
||||
const SizedBox(
|
||||
height: 5,
|
||||
),
|
||||
TimePickerWidget(
|
||||
onTimePicked: onTimePicked,
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
@ -0,0 +1,117 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:syncrow_web/pages/access_management/manage_bookable_spaces/presentation/blocs/steps_cubit/cubit/steps_cubit.dart';
|
||||
import 'package:syncrow_web/utils/color_manager.dart';
|
||||
|
||||
class StepperPartWidget extends StatelessWidget {
|
||||
const StepperPartWidget({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
margin: const EdgeInsets.all(20),
|
||||
padding: const EdgeInsetsGeometry.only(left: 20),
|
||||
child: BlocBuilder<StepsCubit, StepsState>(
|
||||
builder: (context, state) {
|
||||
if (state is StepOneState) {
|
||||
return Column(
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
children: [
|
||||
const SizedBox(
|
||||
height: 10,
|
||||
),
|
||||
const CircleTitleStepperWidget(
|
||||
title: 'Space',
|
||||
),
|
||||
Container(
|
||||
alignment: Alignment.centerLeft,
|
||||
height: 50,
|
||||
child: const VerticalDivider(
|
||||
width: 8,
|
||||
)),
|
||||
const CircleTitleStepperWidget(
|
||||
title: 'Settings',
|
||||
titleColor: ColorsManager.softGray,
|
||||
circleColor: ColorsManager.softGray,
|
||||
)
|
||||
],
|
||||
);
|
||||
} else if (state is StepTwoState) {
|
||||
return Column(
|
||||
children: [
|
||||
const SizedBox(
|
||||
height: 10,
|
||||
),
|
||||
const CircleTitleStepperWidget(
|
||||
title: 'Space',
|
||||
titleColor: ColorsManager.softGray,
|
||||
cicleIcon: Icon(
|
||||
Icons.check,
|
||||
color: ColorsManager.whiteColors,
|
||||
size: 12,
|
||||
),
|
||||
circleColor: ColorsManager.trueIconGreen,
|
||||
radius: 3,
|
||||
),
|
||||
Container(
|
||||
alignment: Alignment.centerLeft,
|
||||
height: 50,
|
||||
child: const VerticalDivider(
|
||||
width: 8,
|
||||
)),
|
||||
const CircleTitleStepperWidget(
|
||||
title: 'Settings',
|
||||
)
|
||||
],
|
||||
);
|
||||
} else if (state is StepEditMode) {
|
||||
return const CircleTitleStepperWidget(
|
||||
title: 'Settings',
|
||||
);
|
||||
} else {
|
||||
return const SizedBox();
|
||||
}
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class CircleTitleStepperWidget extends StatelessWidget {
|
||||
final double? radius;
|
||||
final Widget? cicleIcon;
|
||||
final Color? circleColor;
|
||||
final Color? titleColor;
|
||||
final String title;
|
||||
const CircleTitleStepperWidget({
|
||||
super.key,
|
||||
required this.title,
|
||||
this.circleColor,
|
||||
this.cicleIcon,
|
||||
this.titleColor,
|
||||
this.radius,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Row(
|
||||
children: [
|
||||
CircleAvatar(
|
||||
minRadius: radius ?? 5,
|
||||
backgroundColor: circleColor ?? ColorsManager.blue1,
|
||||
child: cicleIcon,
|
||||
),
|
||||
const SizedBox(
|
||||
width: 10,
|
||||
),
|
||||
Text(
|
||||
title,
|
||||
style: TextStyle(
|
||||
fontWeight: FontWeight.w700,
|
||||
color: titleColor ?? ColorsManager.blackColor,
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
@ -0,0 +1,83 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:flutter_svg/flutter_svg.dart';
|
||||
|
||||
import 'package:syncrow_web/utils/color_manager.dart';
|
||||
import 'package:syncrow_web/utils/constants/assets.dart';
|
||||
|
||||
class TimePickerWidget extends StatefulWidget {
|
||||
const TimePickerWidget({
|
||||
super.key,
|
||||
required this.onTimePicked,
|
||||
});
|
||||
|
||||
final void Function(TimeOfDay? timePicked) onTimePicked;
|
||||
@override
|
||||
State<TimePickerWidget> createState() => _TimePickerWidgetState();
|
||||
}
|
||||
|
||||
class _TimePickerWidgetState extends State<TimePickerWidget> {
|
||||
TimeOfDay? timePicked;
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return InkWell(
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
onTap: () async {
|
||||
final tempTime = await showTimePicker(
|
||||
context: context,
|
||||
initialTime: TimeOfDay.now(),
|
||||
builder: (context, child) {
|
||||
return Theme(
|
||||
data: ThemeData.light().copyWith(
|
||||
colorScheme: const ColorScheme.light(
|
||||
primary: ColorsManager.primaryColor,
|
||||
onSurface: Colors.black,
|
||||
),
|
||||
),
|
||||
child: child!,
|
||||
);
|
||||
},
|
||||
);
|
||||
widget.onTimePicked(tempTime);
|
||||
timePicked = tempTime;
|
||||
setState(() {});
|
||||
},
|
||||
child: Row(
|
||||
children: [
|
||||
Container(
|
||||
width: 56,
|
||||
height: 32,
|
||||
decoration: const BoxDecoration(
|
||||
color: Colors.white,
|
||||
borderRadius: BorderRadius.only(
|
||||
topLeft: Radius.circular(10),
|
||||
bottomLeft: Radius.circular(10),
|
||||
),
|
||||
),
|
||||
alignment: Alignment.center,
|
||||
child: SvgPicture.asset(Assets.clockIcon),
|
||||
),
|
||||
Container(
|
||||
width: 120,
|
||||
height: 32,
|
||||
decoration: const BoxDecoration(
|
||||
color: Colors.white,
|
||||
borderRadius: BorderRadius.only(
|
||||
topRight: Radius.circular(10),
|
||||
bottomRight: Radius.circular(10),
|
||||
),
|
||||
),
|
||||
alignment: Alignment.centerLeft,
|
||||
padding: const EdgeInsets.symmetric(horizontal: 8),
|
||||
child: Text(
|
||||
timePicked == null
|
||||
? TimeOfDay.now().format(context)
|
||||
: timePicked!.format(context),
|
||||
style: const TextStyle(color: Color(0xB2D5D5D5)),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
@ -0,0 +1,62 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:syncrow_web/pages/access_management/manage_bookable_spaces/presentation/blocs/non_bookable_spaces_bloc/non_bookaable_spaces_bloc.dart';
|
||||
|
||||
class WeekDaysCheckboxRow extends StatefulWidget {
|
||||
const WeekDaysCheckboxRow({super.key});
|
||||
|
||||
@override
|
||||
State<WeekDaysCheckboxRow> createState() => _WeekDaysCheckboxRowState();
|
||||
}
|
||||
|
||||
class _WeekDaysCheckboxRowState extends State<WeekDaysCheckboxRow> {
|
||||
final Map<String, bool> _daysChecked = {
|
||||
'Mon': false,
|
||||
'Tue': false,
|
||||
'Wed': false,
|
||||
'Thu': false,
|
||||
'Fri': false,
|
||||
'Sat': false,
|
||||
'Sun': false,
|
||||
};
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: _daysChecked.entries.map((entry) {
|
||||
return Expanded(
|
||||
child: Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: Checkbox(
|
||||
value: entry.value,
|
||||
onChanged: (newValue) {
|
||||
setState(() {
|
||||
_daysChecked[entry.key] = newValue ?? false;
|
||||
final selectedDays = _daysChecked.entries
|
||||
.where((e) => e.value)
|
||||
.map((e) => e.key)
|
||||
.toList();
|
||||
|
||||
for (var space in context
|
||||
.read<NonBookableSpacesBloc>()
|
||||
.selectedBookableSpaces) {
|
||||
space.spaceConfig.bookableDays = selectedDays;
|
||||
}
|
||||
});
|
||||
},
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child: Text(
|
||||
entry.key,
|
||||
style: const TextStyle(fontSize: 10),
|
||||
)),
|
||||
],
|
||||
),
|
||||
);
|
||||
}).toList(),
|
||||
);
|
||||
}
|
||||
}
|
@ -2,302 +2,86 @@ import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:syncrow_web/pages/access_management/bloc/access_bloc.dart';
|
||||
import 'package:syncrow_web/pages/access_management/bloc/access_event.dart';
|
||||
import 'package:syncrow_web/pages/access_management/bloc/access_state.dart';
|
||||
import 'package:syncrow_web/pages/common/buttons/default_button.dart';
|
||||
import 'package:syncrow_web/pages/common/buttons/search_reset_buttons.dart';
|
||||
import 'package:syncrow_web/pages/common/custom_table.dart';
|
||||
import 'package:syncrow_web/pages/common/date_time_widget.dart';
|
||||
import 'package:syncrow_web/pages/common/filter/filter_widget.dart';
|
||||
import 'package:syncrow_web/pages/common/text_field/custom_web_textfield.dart';
|
||||
import 'package:syncrow_web/pages/access_management/booking_system/view/booking_page.dart';
|
||||
import 'package:syncrow_web/pages/access_management/view/access_overview_content.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/shared/navigate_home_grid_view.dart';
|
||||
import 'package:syncrow_web/pages/visitor_password/view/visitor_password_dialog.dart';
|
||||
// import 'package:syncrow_web/utils/color_manager.dart';
|
||||
import 'package:syncrow_web/utils/constants/app_enum.dart';
|
||||
import 'package:syncrow_web/utils/constants/assets.dart';
|
||||
import 'package:syncrow_web/utils/extension/build_context_x.dart';
|
||||
import 'package:syncrow_web/utils/helpers/responsice_layout_helper/responsive_layout_helper.dart';
|
||||
import 'package:syncrow_web/utils/style.dart';
|
||||
import 'package:syncrow_web/utils/theme/responsive_text_theme.dart';
|
||||
import 'package:syncrow_web/web_layout/web_scaffold.dart';
|
||||
|
||||
class AccessManagementPage extends StatelessWidget with HelperResponsiveLayout {
|
||||
class AccessManagementPage extends StatefulWidget {
|
||||
const AccessManagementPage({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final isLargeScreen = isLargeScreenSize(context);
|
||||
final isSmallScreen = isSmallScreenSize(context);
|
||||
final isHalfMediumScreen = isHafMediumScreenSize(context);
|
||||
final padding =
|
||||
isLargeScreen ? const EdgeInsets.all(30) : const EdgeInsets.all(15);
|
||||
State<AccessManagementPage> createState() => _AccessManagementPageState();
|
||||
}
|
||||
|
||||
return WebScaffold(
|
||||
class _AccessManagementPageState extends State<AccessManagementPage>
|
||||
with HelperResponsiveLayout {
|
||||
final PageController _pageController = PageController(initialPage: 0);
|
||||
int _currentPageIndex = 0;
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_pageController.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BlocProvider(
|
||||
create: (BuildContext context) => AccessBloc()..add(FetchTableData()),
|
||||
child: WebScaffold(
|
||||
enableMenuSidebar: false,
|
||||
appBarTitle: Text(
|
||||
'Access Management',
|
||||
style: ResponsiveTextTheme.of(context).deviceManagementTitle,
|
||||
),
|
||||
rightBody: const NavigateHomeGridView(),
|
||||
scaffoldBody: BlocProvider(
|
||||
create: (BuildContext context) =>
|
||||
AccessBloc()..add(FetchTableData()),
|
||||
child: BlocConsumer<AccessBloc, AccessState>(
|
||||
listener: (context, state) {},
|
||||
builder: (context, state) {
|
||||
final accessBloc = BlocProvider.of<AccessBloc>(context);
|
||||
final filteredData = accessBloc.filteredData;
|
||||
return state is AccessLoaded
|
||||
? const Center(child: CircularProgressIndicator())
|
||||
: Container(
|
||||
padding: padding,
|
||||
height: MediaQuery.of(context).size.height,
|
||||
width: MediaQuery.of(context).size.width,
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
FilterWidget(
|
||||
size: MediaQuery.of(context).size,
|
||||
tabs: accessBloc.tabs,
|
||||
selectedIndex: accessBloc.selectedIndex,
|
||||
onTabChanged: (index) {
|
||||
accessBloc.add(TabChangedEvent(index));
|
||||
},
|
||||
),
|
||||
const SizedBox(height: 20),
|
||||
if (isSmallScreen || isHalfMediumScreen)
|
||||
_buildSmallSearchFilters(context, accessBloc)
|
||||
else
|
||||
_buildNormalSearchWidgets(context, accessBloc),
|
||||
const SizedBox(height: 20),
|
||||
_buildVisitorAdminPasswords(context, accessBloc),
|
||||
const SizedBox(height: 20),
|
||||
Expanded(
|
||||
child: DynamicTable(
|
||||
tableName: 'AccessManagement',
|
||||
uuidIndex: 1,
|
||||
withSelectAll: true,
|
||||
isEmpty: filteredData.isEmpty,
|
||||
withCheckBox: false,
|
||||
size: MediaQuery.of(context).size,
|
||||
cellDecoration: containerDecoration,
|
||||
headers: const [
|
||||
'Name',
|
||||
'Access Type',
|
||||
'Access Start',
|
||||
'Access End',
|
||||
'Accessible Device',
|
||||
'Authorizer',
|
||||
'Authorization Date & Time',
|
||||
'Access Status'
|
||||
],
|
||||
data: filteredData.map((item) {
|
||||
return [
|
||||
item.passwordName,
|
||||
item.passwordType.value,
|
||||
accessBloc
|
||||
.timestampToDate(item.effectiveTime),
|
||||
accessBloc
|
||||
.timestampToDate(item.invalidTime),
|
||||
item.deviceName.toString(),
|
||||
item.authorizerEmail.toString(),
|
||||
accessBloc
|
||||
.timestampToDate(item.invalidTime),
|
||||
item.passwordStatus.value,
|
||||
];
|
||||
}).toList(),
|
||||
)),
|
||||
],
|
||||
),
|
||||
);
|
||||
})));
|
||||
}
|
||||
|
||||
Wrap _buildVisitorAdminPasswords(
|
||||
BuildContext context, AccessBloc accessBloc) {
|
||||
return Wrap(
|
||||
spacing: 10,
|
||||
runSpacing: 10,
|
||||
children: [
|
||||
Container(
|
||||
width: 205,
|
||||
height: 42,
|
||||
decoration: containerDecoration,
|
||||
child: DefaultButton(
|
||||
onPressed: () {
|
||||
showDialog(
|
||||
context: context,
|
||||
barrierDismissible: false,
|
||||
builder: (BuildContext context) {
|
||||
return const VisitorPasswordDialog();
|
||||
},
|
||||
).then((v) {
|
||||
if (v != null) {
|
||||
accessBloc.add(FetchTableData());
|
||||
}
|
||||
});
|
||||
},
|
||||
borderRadius: 8,
|
||||
centerBody: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
TextButton(
|
||||
onPressed: () => _switchPage(0),
|
||||
child: Text(
|
||||
'Create Visitor Password ',
|
||||
style: context.textTheme.titleSmall!
|
||||
.copyWith(color: Colors.white, fontSize: 12),
|
||||
)),
|
||||
'Access Overview',
|
||||
style: context.textTheme.titleMedium?.copyWith(
|
||||
color: _currentPageIndex == 0 ? Colors.white : Colors.grey,
|
||||
fontWeight: _currentPageIndex == 0
|
||||
? FontWeight.w700
|
||||
: FontWeight.w400,
|
||||
),
|
||||
),
|
||||
),
|
||||
TextButton(
|
||||
onPressed: () => _switchPage(1),
|
||||
child: Text(
|
||||
'Booking System',
|
||||
style: context.textTheme.titleMedium?.copyWith(
|
||||
color: _currentPageIndex == 1 ? Colors.white : Colors.grey,
|
||||
fontWeight: _currentPageIndex == 1
|
||||
? FontWeight.w700
|
||||
: FontWeight.w400,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
// Container(
|
||||
// width: 133,
|
||||
// height: 42,
|
||||
// decoration: containerDecoration,
|
||||
// child: DefaultButton(
|
||||
// borderRadius: 8,
|
||||
// backgroundColor: ColorsManager.whiteColors,
|
||||
// child: Text(
|
||||
// 'Admin Password',
|
||||
// style: context.textTheme.titleSmall!
|
||||
// .copyWith(color: Colors.black, fontSize: 12),
|
||||
// )),
|
||||
// ),
|
||||
],
|
||||
rightBody: const NavigateHomeGridView(),
|
||||
scaffoldBody: PageView(
|
||||
controller: _pageController,
|
||||
physics: const NeverScrollableScrollPhysics(),
|
||||
children: const [
|
||||
AccessOverviewContent(),
|
||||
BookingPage(),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Row _buildNormalSearchWidgets(BuildContext context, AccessBloc accessBloc) {
|
||||
// TimeOfDay _selectedTime = TimeOfDay.now();
|
||||
|
||||
return Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.end,
|
||||
textBaseline: TextBaseline.ideographic,
|
||||
children: [
|
||||
SizedBox(
|
||||
width: 250,
|
||||
child: CustomWebTextField(
|
||||
controller: accessBloc.passwordName,
|
||||
height: 43,
|
||||
isRequired: false,
|
||||
textFieldName: 'Name',
|
||||
description: '',
|
||||
onSubmitted: (value) {
|
||||
accessBloc.add(FilterDataEvent(
|
||||
emailAuthorizer:
|
||||
accessBloc.emailAuthorizer.text.toLowerCase(),
|
||||
selectedTabIndex:
|
||||
BlocProvider.of<AccessBloc>(context).selectedIndex,
|
||||
passwordName: accessBloc.passwordName.text.toLowerCase(),
|
||||
startTime: accessBloc.effectiveTimeTimeStamp,
|
||||
endTime: accessBloc.expirationTimeTimeStamp));
|
||||
},
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 15),
|
||||
SizedBox(
|
||||
width: 250,
|
||||
child: CustomWebTextField(
|
||||
controller: accessBloc.emailAuthorizer,
|
||||
height: 43,
|
||||
isRequired: false,
|
||||
textFieldName: 'Authorizer',
|
||||
description: '',
|
||||
onSubmitted: (value) {
|
||||
accessBloc.add(FilterDataEvent(
|
||||
emailAuthorizer:
|
||||
accessBloc.emailAuthorizer.text.toLowerCase(),
|
||||
selectedTabIndex:
|
||||
BlocProvider.of<AccessBloc>(context).selectedIndex,
|
||||
passwordName: accessBloc.passwordName.text.toLowerCase(),
|
||||
startTime: accessBloc.effectiveTimeTimeStamp,
|
||||
endTime: accessBloc.expirationTimeTimeStamp));
|
||||
},
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 15),
|
||||
SizedBox(
|
||||
child: DateTimeWebWidget(
|
||||
icon: Assets.calendarIcon,
|
||||
isRequired: false,
|
||||
title: 'Access Time',
|
||||
size: MediaQuery.of(context).size,
|
||||
endTime: () {
|
||||
accessBloc.add(SelectTime(context: context, isStart: false));
|
||||
},
|
||||
startTime: () {
|
||||
accessBloc.add(SelectTime(context: context, isStart: true));
|
||||
},
|
||||
firstString: BlocProvider.of<AccessBloc>(context).startTime,
|
||||
secondString: BlocProvider.of<AccessBloc>(context).endTime,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 15),
|
||||
SearchResetButtons(
|
||||
onSearch: () {
|
||||
accessBloc.add(FilterDataEvent(
|
||||
emailAuthorizer: accessBloc.emailAuthorizer.text.toLowerCase(),
|
||||
selectedTabIndex:
|
||||
BlocProvider.of<AccessBloc>(context).selectedIndex,
|
||||
passwordName: accessBloc.passwordName.text.toLowerCase(),
|
||||
startTime: accessBloc.effectiveTimeTimeStamp,
|
||||
endTime: accessBloc.expirationTimeTimeStamp));
|
||||
},
|
||||
onReset: () {
|
||||
accessBloc.add(ResetSearch());
|
||||
},
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildSmallSearchFilters(BuildContext context, AccessBloc accessBloc) {
|
||||
return Wrap(
|
||||
spacing: 20,
|
||||
runSpacing: 10,
|
||||
children: [
|
||||
SizedBox(
|
||||
width: 300,
|
||||
child: CustomWebTextField(
|
||||
controller: accessBloc.passwordName,
|
||||
isRequired: true,
|
||||
height: 40,
|
||||
textFieldName: 'Name',
|
||||
description: '',
|
||||
onSubmitted: (value) {
|
||||
accessBloc.add(FilterDataEvent(
|
||||
emailAuthorizer:
|
||||
accessBloc.emailAuthorizer.text.toLowerCase(),
|
||||
selectedTabIndex:
|
||||
BlocProvider.of<AccessBloc>(context).selectedIndex,
|
||||
passwordName: accessBloc.passwordName.text.toLowerCase(),
|
||||
startTime: accessBloc.effectiveTimeTimeStamp,
|
||||
endTime: accessBloc.expirationTimeTimeStamp));
|
||||
}),
|
||||
),
|
||||
DateTimeWebWidget(
|
||||
icon: Assets.calendarIcon,
|
||||
isRequired: false,
|
||||
title: 'Access Time',
|
||||
size: MediaQuery.of(context).size,
|
||||
endTime: () {
|
||||
accessBloc.add(SelectTime(context: context, isStart: false));
|
||||
},
|
||||
startTime: () {
|
||||
accessBloc.add(SelectTime(context: context, isStart: true));
|
||||
},
|
||||
firstString: BlocProvider.of<AccessBloc>(context).startTime,
|
||||
secondString: BlocProvider.of<AccessBloc>(context).endTime,
|
||||
),
|
||||
SearchResetButtons(
|
||||
onSearch: () {
|
||||
accessBloc.add(FilterDataEvent(
|
||||
emailAuthorizer: accessBloc.emailAuthorizer.text.toLowerCase(),
|
||||
selectedTabIndex:
|
||||
BlocProvider.of<AccessBloc>(context).selectedIndex,
|
||||
passwordName: accessBloc.passwordName.text.toLowerCase(),
|
||||
startTime: accessBloc.effectiveTimeTimeStamp,
|
||||
endTime: accessBloc.expirationTimeTimeStamp));
|
||||
},
|
||||
onReset: () {
|
||||
accessBloc.add(ResetSearch());
|
||||
},
|
||||
),
|
||||
],
|
||||
);
|
||||
void _switchPage(int index) {
|
||||
setState(() => _currentPageIndex = index);
|
||||
_pageController.jumpToPage(index);
|
||||
}
|
||||
}
|
||||
|
289
lib/pages/access_management/view/access_overview_content.dart
Normal file
289
lib/pages/access_management/view/access_overview_content.dart
Normal file
@ -0,0 +1,289 @@
|
||||
import 'package:syncrow_web/pages/common/buttons/default_button.dart';
|
||||
import 'package:syncrow_web/pages/common/buttons/search_reset_buttons.dart';
|
||||
import 'package:syncrow_web/pages/common/custom_table.dart';
|
||||
import 'package:syncrow_web/pages/common/date_time_widget.dart';
|
||||
import 'package:syncrow_web/pages/common/filter/filter_widget.dart';
|
||||
import 'package:syncrow_web/pages/common/text_field/custom_web_textfield.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:syncrow_web/pages/access_management/bloc/access_state.dart';
|
||||
import 'package:syncrow_web/pages/visitor_password/view/visitor_password_dialog.dart';
|
||||
import 'package:syncrow_web/utils/constants/app_enum.dart';
|
||||
import 'package:syncrow_web/utils/constants/assets.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:syncrow_web/pages/access_management/bloc/access_bloc.dart';
|
||||
import 'package:syncrow_web/pages/access_management/bloc/access_event.dart';
|
||||
import 'package:syncrow_web/utils/extension/build_context_x.dart';
|
||||
import 'package:syncrow_web/utils/helpers/responsice_layout_helper/responsive_layout_helper.dart';
|
||||
import 'package:syncrow_web/utils/style.dart';
|
||||
|
||||
class AccessOverviewContent extends StatelessWidget
|
||||
with HelperResponsiveLayout {
|
||||
const AccessOverviewContent({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final isLargeScreen = isLargeScreenSize(context);
|
||||
final isSmallScreen = isSmallScreenSize(context);
|
||||
final isHalfMediumScreen = isHafMediumScreenSize(context);
|
||||
final padding =
|
||||
isLargeScreen ? const EdgeInsets.all(30) : const EdgeInsets.all(15);
|
||||
|
||||
return BlocProvider(
|
||||
create: (BuildContext context) => AccessBloc()..add(FetchTableData()),
|
||||
child: BlocConsumer<AccessBloc, AccessState>(
|
||||
listener: (context, state) {},
|
||||
builder: (context, state) {
|
||||
final accessBloc = BlocProvider.of<AccessBloc>(context);
|
||||
final filteredData = accessBloc.filteredData;
|
||||
return state is AccessLoaded
|
||||
? const Center(child: CircularProgressIndicator())
|
||||
: Container(
|
||||
padding: padding,
|
||||
height: MediaQuery.of(context).size.height,
|
||||
width: MediaQuery.of(context).size.width,
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
FilterWidget(
|
||||
size: MediaQuery.of(context).size,
|
||||
tabs: accessBloc.tabs,
|
||||
selectedIndex: accessBloc.selectedIndex,
|
||||
onTabChanged: (index) {
|
||||
accessBloc.add(TabChangedEvent(index));
|
||||
},
|
||||
),
|
||||
const SizedBox(height: 20),
|
||||
if (isSmallScreen || isHalfMediumScreen)
|
||||
_buildSmallSearchFilters(context, accessBloc)
|
||||
else
|
||||
_buildNormalSearchWidgets(context, accessBloc),
|
||||
const SizedBox(height: 20),
|
||||
_buildVisitorAdminPasswords(context, accessBloc),
|
||||
const SizedBox(height: 20),
|
||||
Expanded(
|
||||
child: DynamicTable(
|
||||
tableName: 'AccessManagement',
|
||||
uuidIndex: 1,
|
||||
withSelectAll: true,
|
||||
isEmpty: filteredData.isEmpty,
|
||||
withCheckBox: false,
|
||||
size: MediaQuery.of(context).size,
|
||||
cellDecoration: containerDecoration,
|
||||
headers: const [
|
||||
'Name',
|
||||
'Access Type',
|
||||
'Access Start',
|
||||
'Access End',
|
||||
'Accessible Device',
|
||||
'Authorizer',
|
||||
'Authorization Date & Time',
|
||||
'Access Status'
|
||||
],
|
||||
data: filteredData.map((item) {
|
||||
return [
|
||||
item.passwordName,
|
||||
item.passwordType.value,
|
||||
accessBloc.timestampToDate(item.effectiveTime),
|
||||
accessBloc.timestampToDate(item.invalidTime),
|
||||
item.deviceName.toString(),
|
||||
item.authorizerEmail.toString(),
|
||||
accessBloc.timestampToDate(item.invalidTime),
|
||||
item.passwordStatus.value,
|
||||
];
|
||||
}).toList(),
|
||||
)),
|
||||
],
|
||||
),
|
||||
);
|
||||
}));
|
||||
}
|
||||
|
||||
Wrap _buildVisitorAdminPasswords(
|
||||
BuildContext context, AccessBloc accessBloc) {
|
||||
return Wrap(
|
||||
spacing: 10,
|
||||
runSpacing: 10,
|
||||
children: [
|
||||
Container(
|
||||
width: 205,
|
||||
height: 42,
|
||||
decoration: containerDecoration,
|
||||
child: DefaultButton(
|
||||
onPressed: () {
|
||||
showDialog(
|
||||
context: context,
|
||||
barrierDismissible: false,
|
||||
builder: (BuildContext context) {
|
||||
return const VisitorPasswordDialog();
|
||||
},
|
||||
).then((v) {
|
||||
if (v != null) {
|
||||
accessBloc.add(FetchTableData());
|
||||
}
|
||||
});
|
||||
},
|
||||
borderRadius: 8,
|
||||
child: Text(
|
||||
'Create Visitor Password ',
|
||||
style: context.textTheme.titleSmall!
|
||||
.copyWith(color: Colors.white, fontSize: 12),
|
||||
)),
|
||||
),
|
||||
// Container(
|
||||
// width: 133,
|
||||
// height: 42,
|
||||
// decoration: containerDecoration,
|
||||
// child: DefaultButton(
|
||||
// borderRadius: 8,
|
||||
// backgroundColor: ColorsManager.whiteColors,
|
||||
// child: Text(
|
||||
// 'Admin Password',
|
||||
// style: context.textTheme.titleSmall!
|
||||
// .copyWith(color: Colors.black, fontSize: 12),
|
||||
// )),
|
||||
// ),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Row _buildNormalSearchWidgets(BuildContext context, AccessBloc accessBloc) {
|
||||
// TimeOfDay _selectedTime = TimeOfDay.now();
|
||||
|
||||
return Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.end,
|
||||
textBaseline: TextBaseline.ideographic,
|
||||
children: [
|
||||
SizedBox(
|
||||
width: 250,
|
||||
child: CustomWebTextField(
|
||||
controller: accessBloc.passwordName,
|
||||
height: 43,
|
||||
isRequired: false,
|
||||
textFieldName: 'Name',
|
||||
description: '',
|
||||
onSubmitted: (value) {
|
||||
accessBloc.add(FilterDataEvent(
|
||||
emailAuthorizer:
|
||||
accessBloc.emailAuthorizer.text.toLowerCase(),
|
||||
selectedTabIndex:
|
||||
BlocProvider.of<AccessBloc>(context).selectedIndex,
|
||||
passwordName: accessBloc.passwordName.text.toLowerCase(),
|
||||
startTime: accessBloc.effectiveTimeTimeStamp,
|
||||
endTime: accessBloc.expirationTimeTimeStamp));
|
||||
},
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 15),
|
||||
SizedBox(
|
||||
width: 250,
|
||||
child: CustomWebTextField(
|
||||
controller: accessBloc.emailAuthorizer,
|
||||
height: 43,
|
||||
isRequired: false,
|
||||
textFieldName: 'Authorizer',
|
||||
description: '',
|
||||
onSubmitted: (value) {
|
||||
accessBloc.add(FilterDataEvent(
|
||||
emailAuthorizer:
|
||||
accessBloc.emailAuthorizer.text.toLowerCase(),
|
||||
selectedTabIndex:
|
||||
BlocProvider.of<AccessBloc>(context).selectedIndex,
|
||||
passwordName: accessBloc.passwordName.text.toLowerCase(),
|
||||
startTime: accessBloc.effectiveTimeTimeStamp,
|
||||
endTime: accessBloc.expirationTimeTimeStamp));
|
||||
},
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 15),
|
||||
SizedBox(
|
||||
child: DateTimeWebWidget(
|
||||
icon: Assets.calendarIcon,
|
||||
isRequired: false,
|
||||
title: 'Access Time',
|
||||
size: MediaQuery.of(context).size,
|
||||
endTime: () {
|
||||
accessBloc.add(SelectTime(context: context, isStart: false));
|
||||
},
|
||||
startTime: () {
|
||||
accessBloc.add(SelectTime(context: context, isStart: true));
|
||||
},
|
||||
firstString: BlocProvider.of<AccessBloc>(context).startTime,
|
||||
secondString: BlocProvider.of<AccessBloc>(context).endTime,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 15),
|
||||
SearchResetButtons(
|
||||
onSearch: () {
|
||||
accessBloc.add(FilterDataEvent(
|
||||
emailAuthorizer: accessBloc.emailAuthorizer.text.toLowerCase(),
|
||||
selectedTabIndex:
|
||||
BlocProvider.of<AccessBloc>(context).selectedIndex,
|
||||
passwordName: accessBloc.passwordName.text.toLowerCase(),
|
||||
startTime: accessBloc.effectiveTimeTimeStamp,
|
||||
endTime: accessBloc.expirationTimeTimeStamp));
|
||||
},
|
||||
onReset: () {
|
||||
accessBloc.add(ResetSearch());
|
||||
},
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildSmallSearchFilters(BuildContext context, AccessBloc accessBloc) {
|
||||
return Wrap(
|
||||
spacing: 20,
|
||||
runSpacing: 10,
|
||||
children: [
|
||||
SizedBox(
|
||||
width: 300,
|
||||
child: CustomWebTextField(
|
||||
controller: accessBloc.passwordName,
|
||||
isRequired: true,
|
||||
height: 40,
|
||||
textFieldName: 'Name',
|
||||
description: '',
|
||||
onSubmitted: (value) {
|
||||
accessBloc.add(FilterDataEvent(
|
||||
emailAuthorizer:
|
||||
accessBloc.emailAuthorizer.text.toLowerCase(),
|
||||
selectedTabIndex:
|
||||
BlocProvider.of<AccessBloc>(context).selectedIndex,
|
||||
passwordName: accessBloc.passwordName.text.toLowerCase(),
|
||||
startTime: accessBloc.effectiveTimeTimeStamp,
|
||||
endTime: accessBloc.expirationTimeTimeStamp));
|
||||
}),
|
||||
),
|
||||
DateTimeWebWidget(
|
||||
icon: Assets.calendarIcon,
|
||||
isRequired: false,
|
||||
title: 'Access Time',
|
||||
size: MediaQuery.of(context).size,
|
||||
endTime: () {
|
||||
accessBloc.add(SelectTime(context: context, isStart: false));
|
||||
},
|
||||
startTime: () {
|
||||
accessBloc.add(SelectTime(context: context, isStart: true));
|
||||
},
|
||||
firstString: BlocProvider.of<AccessBloc>(context).startTime,
|
||||
secondString: BlocProvider.of<AccessBloc>(context).endTime,
|
||||
),
|
||||
SearchResetButtons(
|
||||
onSearch: () {
|
||||
accessBloc.add(FilterDataEvent(
|
||||
emailAuthorizer: accessBloc.emailAuthorizer.text.toLowerCase(),
|
||||
selectedTabIndex:
|
||||
BlocProvider.of<AccessBloc>(context).selectedIndex,
|
||||
passwordName: accessBloc.passwordName.text.toLowerCase(),
|
||||
startTime: accessBloc.effectiveTimeTimeStamp,
|
||||
endTime: accessBloc.expirationTimeTimeStamp));
|
||||
},
|
||||
onReset: () {
|
||||
accessBloc.add(ResetSearch());
|
||||
},
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
@ -20,6 +20,7 @@ class AqiDistributionChart extends StatelessWidget {
|
||||
return BarChart(
|
||||
BarChartData(
|
||||
maxY: 100.1,
|
||||
alignment: BarChartAlignment.start,
|
||||
gridData: EnergyManagementChartsHelper.gridData(
|
||||
horizontalInterval: 20,
|
||||
),
|
||||
|
@ -38,7 +38,7 @@ class AnalyticsEnergyManagementView extends StatelessWidget {
|
||||
return SingleChildScrollView(
|
||||
child: Container(
|
||||
padding: _padding,
|
||||
height: MediaQuery.sizeOf(context).height * 1,
|
||||
height: MediaQuery.sizeOf(context).height * 1.05,
|
||||
child: const Column(
|
||||
children: [
|
||||
Expanded(
|
||||
|
@ -18,6 +18,7 @@ class OccupancyChart extends StatelessWidget {
|
||||
return BarChart(
|
||||
BarChartData(
|
||||
maxY: 100.001,
|
||||
alignment: BarChartAlignment.start,
|
||||
gridData: EnergyManagementChartsHelper.gridData().copyWith(
|
||||
checkToShowHorizontalLine: (value) => true,
|
||||
horizontalInterval: 20,
|
||||
|
@ -17,8 +17,8 @@ class DeviceLocationDetailsServiceDecorator implements DeviceLocationService {
|
||||
'reverse',
|
||||
queryParameters: {
|
||||
'format': 'json',
|
||||
'lat': param.latitude,
|
||||
'lon': param.longitude,
|
||||
'lat': 25.1880567,
|
||||
'lon': 55.266608,
|
||||
},
|
||||
);
|
||||
|
||||
|
@ -50,6 +50,9 @@ class _DynamicTableState extends State<DynamicTable> {
|
||||
bool _selectAll = false;
|
||||
final ScrollController _verticalScrollController = ScrollController();
|
||||
final ScrollController _horizontalScrollController = ScrollController();
|
||||
static const double _fixedRowHeight = 60;
|
||||
static const double _checkboxColumnWidth = 50;
|
||||
static const double _settingsColumnWidth = 100;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
@ -67,7 +70,6 @@ class _DynamicTableState extends State<DynamicTable> {
|
||||
|
||||
bool _compareListOfLists(
|
||||
List<List<dynamic>> oldList, List<List<dynamic>> newList) {
|
||||
// Check if the old and new lists are the same
|
||||
if (oldList.length != newList.length) return false;
|
||||
|
||||
for (int i = 0; i < oldList.length; i++) {
|
||||
@ -104,73 +106,130 @@ class _DynamicTableState extends State<DynamicTable> {
|
||||
context.read<DeviceManagementBloc>().add(UpdateSelection(_selectedRows));
|
||||
}
|
||||
|
||||
double get _totalTableWidth {
|
||||
final hasSettings = widget.headers.contains('Settings');
|
||||
final base = (widget.withCheckBox ? _checkboxColumnWidth : 0) +
|
||||
(hasSettings ? _settingsColumnWidth : 0);
|
||||
final regularCount = widget.headers.length - (hasSettings ? 1 : 0);
|
||||
final regularWidth = (widget.size.width - base) / regularCount;
|
||||
return base + regularCount * regularWidth;
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
width: widget.size.width,
|
||||
height: widget.size.height,
|
||||
decoration: widget.cellDecoration,
|
||||
child: Scrollbar(
|
||||
controller: _verticalScrollController,
|
||||
thumbVisibility: true,
|
||||
trackVisibility: true,
|
||||
child: ScrollConfiguration(
|
||||
behavior: const ScrollBehavior().copyWith(scrollbars: false),
|
||||
child: Scrollbar(
|
||||
//fixed the horizontal scrollbar issue
|
||||
controller: _horizontalScrollController,
|
||||
thumbVisibility: true,
|
||||
trackVisibility: true,
|
||||
notificationPredicate: (notif) => notif.depth == 1,
|
||||
notificationPredicate: (notif) =>
|
||||
notif.metrics.axis == Axis.horizontal,
|
||||
child: SingleChildScrollView(
|
||||
controller: _verticalScrollController,
|
||||
child: SingleChildScrollView(
|
||||
controller: _horizontalScrollController,
|
||||
scrollDirection: Axis.horizontal,
|
||||
child: SizedBox(
|
||||
width: widget.size.width,
|
||||
child: Column(
|
||||
children: [
|
||||
Container(
|
||||
decoration: widget.headerDecoration ??
|
||||
const BoxDecoration(
|
||||
color: ColorsManager.boxColor,
|
||||
controller: _horizontalScrollController,
|
||||
scrollDirection: Axis.horizontal,
|
||||
child: SizedBox(
|
||||
width: _totalTableWidth,
|
||||
child: Column(
|
||||
children: [
|
||||
Container(
|
||||
height: _fixedRowHeight,
|
||||
decoration: widget.headerDecoration ??
|
||||
const BoxDecoration(color: ColorsManager.boxColor),
|
||||
child: Row(
|
||||
children: [
|
||||
if (widget.withCheckBox)
|
||||
_buildSelectAllCheckbox(_checkboxColumnWidth),
|
||||
for (var i = 0; i < widget.headers.length; i++)
|
||||
_buildTableHeaderCell(
|
||||
widget.headers[i],
|
||||
widget.headers[i] == 'Settings'
|
||||
? _settingsColumnWidth
|
||||
: (_totalTableWidth -
|
||||
(widget.withCheckBox
|
||||
? _checkboxColumnWidth
|
||||
: 0) -
|
||||
(widget.headers.contains('Settings')
|
||||
? _settingsColumnWidth
|
||||
: 0)) /
|
||||
(widget.headers.length -
|
||||
(widget.headers.contains('Settings')
|
||||
? 1
|
||||
: 0)),
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
if (widget.withCheckBox) _buildSelectAllCheckbox(),
|
||||
...List.generate(widget.headers.length, (index) {
|
||||
return _buildTableHeaderCell(
|
||||
widget.headers[index], index);
|
||||
})
|
||||
//...widget.headers.map((header) => _buildTableHeaderCell(header)),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
SizedBox(
|
||||
width: widget.size.width,
|
||||
child: widget.isEmpty
|
||||
? _buildEmptyState()
|
||||
: Column(
|
||||
children:
|
||||
List.generate(widget.data.length, (rowIndex) {
|
||||
),
|
||||
|
||||
Expanded(
|
||||
child: widget.isEmpty
|
||||
? _buildEmptyState()
|
||||
: Scrollbar(
|
||||
controller: _verticalScrollController,
|
||||
thumbVisibility: true,
|
||||
trackVisibility: true,
|
||||
notificationPredicate: (notif) =>
|
||||
notif.metrics.axis == Axis.vertical,
|
||||
child: ListView.builder(
|
||||
controller: _verticalScrollController,
|
||||
itemCount: widget.data.length,
|
||||
itemBuilder: (_, rowIndex) {
|
||||
final row = widget.data[rowIndex];
|
||||
return Row(
|
||||
children: [
|
||||
if (widget.withCheckBox)
|
||||
_buildRowCheckbox(
|
||||
rowIndex, widget.size.height * 0.08),
|
||||
...row.asMap().entries.map((entry) {
|
||||
return _buildTableCell(
|
||||
entry.value.toString(),
|
||||
widget.size.height * 0.08,
|
||||
rowIndex: rowIndex,
|
||||
columnIndex: entry.key,
|
||||
);
|
||||
}).toList(),
|
||||
],
|
||||
return SizedBox(
|
||||
height: _fixedRowHeight,
|
||||
child: Row(
|
||||
children: [
|
||||
if (widget.withCheckBox)
|
||||
_buildRowCheckbox(
|
||||
rowIndex,
|
||||
_checkboxColumnWidth,
|
||||
),
|
||||
for (var colIndex = 0;
|
||||
colIndex < row.length;
|
||||
colIndex++)
|
||||
widget.headers[colIndex] == 'Settings'
|
||||
? buildSettingsIcon(
|
||||
width: _settingsColumnWidth,
|
||||
onTap: () => widget
|
||||
.onSettingsPressed
|
||||
?.call(rowIndex),
|
||||
)
|
||||
: _buildTableCell(
|
||||
row[colIndex].toString(),
|
||||
width: widget.headers[
|
||||
colIndex] ==
|
||||
'Settings'
|
||||
? _settingsColumnWidth
|
||||
: (_totalTableWidth -
|
||||
(widget.withCheckBox
|
||||
? _checkboxColumnWidth
|
||||
: 0) -
|
||||
(widget.headers
|
||||
.contains(
|
||||
'Settings')
|
||||
? _settingsColumnWidth
|
||||
: 0)) /
|
||||
(widget.headers.length -
|
||||
(widget.headers
|
||||
.contains(
|
||||
'Settings')
|
||||
? 1
|
||||
: 0)),
|
||||
rowIndex: rowIndex,
|
||||
columnIndex: colIndex,
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}),
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
@ -210,9 +269,10 @@ class _DynamicTableState extends State<DynamicTable> {
|
||||
],
|
||||
),
|
||||
);
|
||||
Widget _buildSelectAllCheckbox() {
|
||||
|
||||
Widget _buildSelectAllCheckbox(double width) {
|
||||
return Container(
|
||||
width: 50,
|
||||
width: width,
|
||||
decoration: const BoxDecoration(
|
||||
border: Border.symmetric(
|
||||
vertical: BorderSide(color: ColorsManager.boxDivider),
|
||||
@ -227,11 +287,11 @@ class _DynamicTableState extends State<DynamicTable> {
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildRowCheckbox(int index, double size) {
|
||||
Widget _buildRowCheckbox(int index, double width) {
|
||||
return Container(
|
||||
width: 50,
|
||||
width: width,
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
height: size,
|
||||
height: _fixedRowHeight,
|
||||
decoration: const BoxDecoration(
|
||||
border: Border(
|
||||
bottom: BorderSide(
|
||||
@ -253,50 +313,47 @@ class _DynamicTableState extends State<DynamicTable> {
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildTableHeaderCell(String title, int index) {
|
||||
return Expanded(
|
||||
child: Container(
|
||||
decoration: const BoxDecoration(
|
||||
border: Border.symmetric(
|
||||
vertical: BorderSide(color: ColorsManager.boxDivider),
|
||||
),
|
||||
Widget _buildTableHeaderCell(String title, double width) {
|
||||
return Container(
|
||||
width: width,
|
||||
decoration: const BoxDecoration(
|
||||
border: Border.symmetric(
|
||||
vertical: BorderSide(color: ColorsManager.boxDivider),
|
||||
),
|
||||
constraints: const BoxConstraints.expand(height: 40),
|
||||
alignment: Alignment.centerLeft,
|
||||
child: Padding(
|
||||
padding: EdgeInsets.symmetric(
|
||||
horizontal: index == widget.headers.length - 1 ? 12 : 8.0,
|
||||
vertical: 4),
|
||||
child: Text(
|
||||
title,
|
||||
style: context.textTheme.titleSmall!.copyWith(
|
||||
color: ColorsManager.grayColor,
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.w400,
|
||||
),
|
||||
maxLines: 2,
|
||||
),
|
||||
constraints: BoxConstraints(minHeight: 40, maxHeight: _fixedRowHeight),
|
||||
alignment: Alignment.centerLeft,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 8.0, vertical: 4),
|
||||
child: Text(
|
||||
title,
|
||||
style: context.textTheme.titleSmall!.copyWith(
|
||||
color: ColorsManager.grayColor,
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.w400,
|
||||
),
|
||||
maxLines: 2,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildTableCell(String content, double size,
|
||||
{required int rowIndex, required int columnIndex}) {
|
||||
Widget _buildTableCell(String content,
|
||||
{required double width,
|
||||
required int rowIndex,
|
||||
required int columnIndex}) {
|
||||
bool isBatteryLevel = content.endsWith('%');
|
||||
double? batteryLevel;
|
||||
|
||||
if (isBatteryLevel) {
|
||||
batteryLevel = double.tryParse(content.replaceAll('%', '').trim());
|
||||
}
|
||||
|
||||
bool isSettingsColumn = widget.headers[columnIndex] == 'Settings';
|
||||
if (isSettingsColumn) {
|
||||
return buildSettingsIcon(
|
||||
width: 120,
|
||||
height: 60,
|
||||
iconSize: 40,
|
||||
onTap: () => widget.onSettingsPressed?.call(rowIndex),
|
||||
);
|
||||
width: width, onTap: () => widget.onSettingsPressed?.call(rowIndex));
|
||||
}
|
||||
|
||||
Color? statusColor;
|
||||
@ -320,92 +377,82 @@ class _DynamicTableState extends State<DynamicTable> {
|
||||
statusColor = Colors.black;
|
||||
}
|
||||
|
||||
return Expanded(
|
||||
child: Container(
|
||||
height: size,
|
||||
padding: const EdgeInsets.all(5.0),
|
||||
decoration: const BoxDecoration(
|
||||
border: Border(
|
||||
bottom: BorderSide(
|
||||
color: ColorsManager.boxDivider,
|
||||
width: 1.0,
|
||||
),
|
||||
return Container(
|
||||
width: width,
|
||||
height: _fixedRowHeight,
|
||||
padding: const EdgeInsets.symmetric(horizontal: 8.0, vertical: 4),
|
||||
decoration: const BoxDecoration(
|
||||
border: Border(
|
||||
bottom: BorderSide(
|
||||
color: ColorsManager.boxDivider,
|
||||
width: 1.0,
|
||||
),
|
||||
color: Colors.white,
|
||||
),
|
||||
alignment: Alignment.centerLeft,
|
||||
child: Text(
|
||||
content,
|
||||
style: TextStyle(
|
||||
color: (batteryLevel != null && batteryLevel < 20)
|
||||
? ColorsManager.red
|
||||
: (batteryLevel != null && batteryLevel > 20)
|
||||
? ColorsManager.green
|
||||
: statusColor,
|
||||
fontSize: 13,
|
||||
fontWeight: FontWeight.w400),
|
||||
maxLines: 2,
|
||||
color: Colors.white,
|
||||
),
|
||||
alignment: Alignment.centerLeft,
|
||||
child: Text(
|
||||
content,
|
||||
style: TextStyle(
|
||||
color: (batteryLevel != null && batteryLevel < 20)
|
||||
? ColorsManager.red
|
||||
: (batteryLevel != null && batteryLevel > 20)
|
||||
? ColorsManager.green
|
||||
: statusColor,
|
||||
fontSize: 13,
|
||||
fontWeight: FontWeight.w400,
|
||||
),
|
||||
maxLines: 2,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget buildSettingsIcon(
|
||||
{double width = 120,
|
||||
double height = 60,
|
||||
double iconSize = 40,
|
||||
VoidCallback? onTap}) {
|
||||
return Column(
|
||||
children: [
|
||||
Container(
|
||||
padding: const EdgeInsets.only(top: 10, bottom: 15, left: 10),
|
||||
margin: const EdgeInsets.only(right: 15),
|
||||
decoration: const BoxDecoration(
|
||||
color: ColorsManager.whiteColors,
|
||||
border: Border(
|
||||
bottom: BorderSide(
|
||||
color: ColorsManager.boxDivider,
|
||||
width: 1.0,
|
||||
),
|
||||
),
|
||||
Widget buildSettingsIcon({required double width, VoidCallback? onTap}) {
|
||||
return Container(
|
||||
width: width,
|
||||
height: _fixedRowHeight,
|
||||
padding: const EdgeInsets.only(left: 15, top: 10, bottom: 10),
|
||||
decoration: const BoxDecoration(
|
||||
color: ColorsManager.whiteColors,
|
||||
border: Border(
|
||||
bottom: BorderSide(
|
||||
color: ColorsManager.boxDivider,
|
||||
width: 1.0,
|
||||
),
|
||||
width: width,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.only(
|
||||
right: 16.0,
|
||||
left: 17.0,
|
||||
),
|
||||
child: Container(
|
||||
width: 50,
|
||||
decoration: BoxDecoration(
|
||||
color: const Color(0xFFF7F8FA),
|
||||
borderRadius: BorderRadius.circular(height / 2),
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: Colors.black.withOpacity(0.17),
|
||||
blurRadius: 14,
|
||||
offset: const Offset(0, 4),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
child: Align(
|
||||
alignment: Alignment.centerLeft,
|
||||
child: Container(
|
||||
width: 50,
|
||||
decoration: BoxDecoration(
|
||||
color: const Color(0xFFF7F8FA),
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: Colors.black.withOpacity(0.17),
|
||||
blurRadius: 14,
|
||||
offset: const Offset(0, 4),
|
||||
),
|
||||
child: InkWell(
|
||||
onTap: onTap,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
child: Center(
|
||||
child: SvgPicture.asset(
|
||||
Assets.settings,
|
||||
width: 40,
|
||||
height: 22,
|
||||
color: ColorsManager.primaryColor,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
child: InkWell(
|
||||
onTap: onTap,
|
||||
child: Padding(
|
||||
padding: EdgeInsets.all(8.0),
|
||||
child: Center(
|
||||
child: SvgPicture.asset(
|
||||
Assets.settings,
|
||||
width: 40,
|
||||
height: 20,
|
||||
color: ColorsManager.primaryColor,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -15,7 +15,8 @@ import 'package:syncrow_web/utils/constants/assets.dart';
|
||||
import 'package:syncrow_web/utils/extension/build_context_x.dart';
|
||||
import 'package:syncrow_web/utils/helpers/responsice_layout_helper/responsive_layout_helper.dart';
|
||||
|
||||
class AcDeviceBatchControlView extends StatelessWidget with HelperResponsiveLayout {
|
||||
class AcDeviceBatchControlView extends StatelessWidget
|
||||
with HelperResponsiveLayout {
|
||||
const AcDeviceBatchControlView({super.key, required this.devicesIds});
|
||||
|
||||
final List<String> devicesIds;
|
||||
@ -51,7 +52,7 @@ class AcDeviceBatchControlView extends StatelessWidget with HelperResponsiveLayo
|
||||
deviceId: devicesIds.first,
|
||||
code: 'switch',
|
||||
value: state.status.acSwitch,
|
||||
label: 'ThermoState',
|
||||
label: 'Thermostat',
|
||||
icon: Assets.ac,
|
||||
onChange: (value) {
|
||||
context.read<AcBloc>().add(AcBatchControlEvent(
|
||||
@ -100,8 +101,8 @@ class AcDeviceBatchControlView extends StatelessWidget with HelperResponsiveLayo
|
||||
),
|
||||
Text(
|
||||
'h',
|
||||
style:
|
||||
context.textTheme.bodySmall!.copyWith(color: ColorsManager.blackColor),
|
||||
style: context.textTheme.bodySmall!
|
||||
.copyWith(color: ColorsManager.blackColor),
|
||||
),
|
||||
Text(
|
||||
'30',
|
||||
@ -148,7 +149,8 @@ class AcDeviceBatchControlView extends StatelessWidget with HelperResponsiveLayo
|
||||
callFactoryReset: () {
|
||||
context.read<AcBloc>().add(AcFactoryResetEvent(
|
||||
deviceId: state.status.uuid,
|
||||
factoryResetModel: FactoryResetModel(devicesUuid: devicesIds),
|
||||
factoryResetModel:
|
||||
FactoryResetModel(devicesUuid: devicesIds),
|
||||
));
|
||||
},
|
||||
),
|
||||
|
@ -57,6 +57,9 @@ class Status {
|
||||
};
|
||||
}
|
||||
|
||||
factory Status.fromJson(String source) => Status.fromMap(json.decode(source));
|
||||
|
||||
String toJson() => json.encode(toMap());
|
||||
Status copyWith({
|
||||
String? code,
|
||||
dynamic value,
|
||||
@ -66,8 +69,4 @@ class Status {
|
||||
value: value ?? this.value,
|
||||
);
|
||||
}
|
||||
|
||||
factory Status.fromJson(String source) => Status.fromMap(json.decode(source));
|
||||
|
||||
String toJson() => json.encode(toMap());
|
||||
}
|
||||
|
@ -68,6 +68,7 @@ class DeviceManagementBody extends StatelessWidget with HelperResponsiveLayout {
|
||||
children: [
|
||||
Expanded(child: SpaceTreeView(
|
||||
onSelect: () {
|
||||
context.read<DeviceManagementBloc>().add(ResetFilters());
|
||||
context.read<DeviceManagementBloc>().add(FetchDevices(context));
|
||||
},
|
||||
)),
|
||||
|
@ -62,9 +62,10 @@ class CurtainModuleItems extends StatelessWidget with HelperResponsiveLayout {
|
||||
BlocProvider.of<CurtainModuleBloc>(context),
|
||||
child: BuildScheduleView(
|
||||
deviceUuid: deviceId,
|
||||
category: 'CUR_2',
|
||||
category: 'Timer',
|
||||
code: 'control',
|
||||
|
||||
countdownCode: 'Timer',
|
||||
deviceType: 'CUR_2',
|
||||
),
|
||||
));
|
||||
},
|
||||
|
@ -17,6 +17,7 @@ class CalibrateCompletedDialog extends StatelessWidget {
|
||||
@override
|
||||
Widget build(_) {
|
||||
return AlertDialog(
|
||||
backgroundColor: ColorsManager.whiteColors,
|
||||
contentPadding: EdgeInsets.zero,
|
||||
content: SizedBox(
|
||||
height: 250,
|
||||
|
@ -40,7 +40,7 @@ class OneGangGlassSwitchBloc
|
||||
emit(OneGangGlassSwitchLoading());
|
||||
try {
|
||||
final status = await DevicesManagementApi().getDeviceStatus(event.deviceId);
|
||||
_listenToChanges(event.deviceId, emit);
|
||||
_listenToChanges(event.deviceId);
|
||||
deviceStatus = OneGangGlassStatusModel.fromJson(event.deviceId, status.status);
|
||||
emit(OneGangGlassSwitchStatusLoaded(deviceStatus));
|
||||
} catch (e) {
|
||||
@ -48,42 +48,28 @@ class OneGangGlassSwitchBloc
|
||||
}
|
||||
}
|
||||
|
||||
void _listenToChanges(
|
||||
String deviceId,
|
||||
Emitter<OneGangGlassSwitchState> emit,
|
||||
) {
|
||||
StreamSubscription<DatabaseEvent>? _deviceStatusSubscription;
|
||||
|
||||
void _listenToChanges(String deviceId) {
|
||||
try {
|
||||
final ref = FirebaseDatabase.instance.ref('device-status/$deviceId');
|
||||
final stream = ref.onValue;
|
||||
_deviceStatusSubscription = ref.onValue.listen((DatabaseEvent event) async {
|
||||
if (event.snapshot.value == null) return;
|
||||
|
||||
stream.listen((DatabaseEvent event) {
|
||||
final data = event.snapshot.value as Map<dynamic, dynamic>?;
|
||||
if (data == null) return;
|
||||
final usersMap = event.snapshot.value! as Map<dynamic, dynamic>;
|
||||
|
||||
final statusList = <Status>[];
|
||||
if (data['status'] != null) {
|
||||
for (var element in data['status']) {
|
||||
statusList.add(
|
||||
Status(
|
||||
code: element['code'].toString(),
|
||||
value: element['value'].toString(),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
if (statusList.isNotEmpty) {
|
||||
final newStatus = OneGangGlassStatusModel.fromJson(deviceId, statusList);
|
||||
if (newStatus != deviceStatus) {
|
||||
deviceStatus = newStatus;
|
||||
if (!isClosed) {
|
||||
add(StatusUpdated(deviceStatus));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
usersMap['status'].forEach((element) {
|
||||
statusList.add(Status(code: element['code'], value: element['value']));
|
||||
});
|
||||
|
||||
deviceStatus =
|
||||
OneGangGlassStatusModel.fromJson(usersMap['productUuid'], statusList);
|
||||
|
||||
add(StatusUpdated(deviceStatus));
|
||||
});
|
||||
} catch (e) {
|
||||
emit(OneGangGlassSwitchError('Failed to listen to changes: $e'));
|
||||
}
|
||||
} catch (_) {}
|
||||
}
|
||||
|
||||
void _onStatusUpdated(
|
||||
@ -174,4 +160,10 @@ class OneGangGlassSwitchBloc
|
||||
deviceStatus = deviceStatus.copyWith(switch1: value);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> close() {
|
||||
_deviceStatusSubscription?.cancel();
|
||||
return super.close();
|
||||
}
|
||||
}
|
||||
|
@ -90,6 +90,8 @@ class OneGangGlassSwitchControlView extends StatelessWidget
|
||||
child: BuildScheduleView(
|
||||
category: 'switch_1',
|
||||
deviceUuid: deviceId,
|
||||
countdownCode: 'countdown_1',
|
||||
deviceType: '1GT',
|
||||
),
|
||||
));
|
||||
},
|
||||
|
@ -80,6 +80,8 @@ class WallLightDeviceControl extends StatelessWidget
|
||||
child: BuildScheduleView(
|
||||
category: 'switch_1',
|
||||
deviceUuid: deviceId,
|
||||
countdownCode: 'countdown_1',
|
||||
deviceType: '1G',
|
||||
),
|
||||
));
|
||||
},
|
||||
|
@ -277,6 +277,32 @@ class SmartPowerDeviceControl extends StatelessWidget
|
||||
totalConsumption: 10000,
|
||||
date: blocProvider.formattedDate,
|
||||
),
|
||||
EnergyConsumptionPage(
|
||||
formattedDate:
|
||||
'${blocProvider.dateTime!.day}/${blocProvider.dateTime!.month}/${blocProvider.dateTime!.year} ${blocProvider.endChartDate}',
|
||||
onTap: () {
|
||||
blocProvider.add(SelectDateEvent(context: context));
|
||||
},
|
||||
widget: blocProvider.dateSwitcher(),
|
||||
chartData: blocProvider.energyDataList.isNotEmpty
|
||||
? blocProvider.energyDataList
|
||||
: [
|
||||
EnergyData('12:00 AM', 4.0),
|
||||
EnergyData('01:00 AM', 6.5),
|
||||
EnergyData('02:00 AM', 3.8),
|
||||
EnergyData('03:00 AM', 3.2),
|
||||
EnergyData('04:00 AM', 6.0),
|
||||
EnergyData('05:00 AM', 3.4),
|
||||
EnergyData('06:00 AM', 5.2),
|
||||
EnergyData('07:00 AM', 3.5),
|
||||
EnergyData('08:00 AM', 6.8),
|
||||
EnergyData('09:00 AM', 5.6),
|
||||
EnergyData('10:00 AM', 3.9),
|
||||
EnergyData('11:00 AM', 4.0),
|
||||
],
|
||||
totalConsumption: 10000,
|
||||
date: blocProvider.formattedDate,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
@ -47,7 +47,7 @@ class ScheduleBloc extends Bloc<ScheduleEvent, ScheduleState> {
|
||||
final success = await RemoteControlDeviceService().controlDevice(
|
||||
deviceUuid: deviceId,
|
||||
status: Status(
|
||||
code: 'countdown_1',
|
||||
code: event.countdownCode,
|
||||
value: 0,
|
||||
),
|
||||
);
|
||||
@ -80,15 +80,18 @@ class ScheduleBloc extends Bloc<ScheduleEvent, ScheduleState> {
|
||||
) {
|
||||
if (state is ScheduleLoaded) {
|
||||
final currentState = state as ScheduleLoaded;
|
||||
|
||||
emit(currentState.copyWith(
|
||||
countdownSeconds: currentState.countdownSeconds,
|
||||
selectedTime: currentState.selectedTime,
|
||||
deviceId: deviceId,
|
||||
scheduleMode: event.scheduleMode,
|
||||
countdownRemaining: Duration.zero,
|
||||
countdownHours: 0,
|
||||
countdownMinutes: 0,
|
||||
inchingHours: 0,
|
||||
inchingMinutes: 0,
|
||||
isCountdownActive: false,
|
||||
countdownHours: currentState.countdownHours,
|
||||
countdownMinutes: currentState.countdownMinutes,
|
||||
inchingHours: currentState.inchingHours,
|
||||
inchingMinutes: currentState.inchingMinutes,
|
||||
isInchingActive: false,
|
||||
isCountdownActive: currentState.countdownRemaining > Duration.zero,
|
||||
));
|
||||
}
|
||||
}
|
||||
@ -221,7 +224,6 @@ class ScheduleBloc extends Bloc<ScheduleEvent, ScheduleState> {
|
||||
deviceId,
|
||||
event.category,
|
||||
);
|
||||
|
||||
if (state is ScheduleLoaded) {
|
||||
final currentState = state as ScheduleLoaded;
|
||||
emit(currentState.copyWith(
|
||||
@ -230,7 +232,6 @@ class ScheduleBloc extends Bloc<ScheduleEvent, ScheduleState> {
|
||||
selectedDays: List.filled(7, false),
|
||||
functionOn: false,
|
||||
isEditing: false,
|
||||
countdownRemaining: Duration.zero,
|
||||
));
|
||||
} else {
|
||||
emit(ScheduleLoaded(
|
||||
@ -285,9 +286,8 @@ class ScheduleBloc extends Bloc<ScheduleEvent, ScheduleState> {
|
||||
) async {
|
||||
try {
|
||||
if (state is ScheduleLoaded) {
|
||||
final dateTime = DateTime.parse(event.time);
|
||||
Status status = Status(code: '', value: '');
|
||||
if (event.category == 'CUR_2') {
|
||||
if (event.deviceType == 'CUR_2') {
|
||||
status = status.copyWith(
|
||||
code: 'control',
|
||||
value: event.functionOn == true ? 'open' : 'close');
|
||||
@ -295,6 +295,8 @@ class ScheduleBloc extends Bloc<ScheduleEvent, ScheduleState> {
|
||||
status =
|
||||
status.copyWith(code: event.category, value: event.functionOn);
|
||||
}
|
||||
|
||||
final dateTime = DateTime.parse(event.time);
|
||||
final updatedSchedule = ScheduleEntry(
|
||||
scheduleId: event.scheduleId,
|
||||
category: event.category,
|
||||
@ -405,7 +407,7 @@ class ScheduleBloc extends Bloc<ScheduleEvent, ScheduleState> {
|
||||
final totalSeconds =
|
||||
Duration(hours: event.hours, minutes: event.minutes).inSeconds;
|
||||
final code = event.mode == ScheduleModes.countdown
|
||||
? 'countdown_1'
|
||||
? event.countDownCode
|
||||
: 'switch_inching';
|
||||
final currentState = state as ScheduleLoaded;
|
||||
final duration = Duration(seconds: totalSeconds);
|
||||
@ -432,7 +434,7 @@ class ScheduleBloc extends Bloc<ScheduleEvent, ScheduleState> {
|
||||
);
|
||||
|
||||
if (success) {
|
||||
if (code == 'countdown_1') {
|
||||
if (code == event.countDownCode) {
|
||||
final countdownDuration = Duration(seconds: totalSeconds);
|
||||
|
||||
emit(
|
||||
@ -446,7 +448,7 @@ class ScheduleBloc extends Bloc<ScheduleEvent, ScheduleState> {
|
||||
);
|
||||
|
||||
if (countdownDuration.inSeconds > 0) {
|
||||
_startCountdownTimer(emit, countdownDuration);
|
||||
_startCountdownTimer(emit, countdownDuration, event.countDownCode);
|
||||
} else {
|
||||
_countdownTimer?.cancel();
|
||||
emit(
|
||||
@ -476,9 +478,7 @@ class ScheduleBloc extends Bloc<ScheduleEvent, ScheduleState> {
|
||||
}
|
||||
|
||||
void _startCountdownTimer(
|
||||
Emitter<ScheduleState> emit,
|
||||
Duration duration,
|
||||
) {
|
||||
Emitter<ScheduleState> emit, Duration duration, String countdownCode) {
|
||||
_countdownTimer?.cancel();
|
||||
_countdownTimer = Timer.periodic(const Duration(seconds: 1), (timer) {
|
||||
if (_currentCountdown != null && _currentCountdown! > Duration.zero) {
|
||||
@ -488,6 +488,7 @@ class ScheduleBloc extends Bloc<ScheduleEvent, ScheduleState> {
|
||||
} else {
|
||||
timer.cancel();
|
||||
add(StopScheduleEvent(
|
||||
countdownCode: countdownCode,
|
||||
mode: _currentCountdown == null
|
||||
? ScheduleModes.countdown
|
||||
: ScheduleModes.inching,
|
||||
@ -524,70 +525,75 @@ class ScheduleBloc extends Bloc<ScheduleEvent, ScheduleState> {
|
||||
try {
|
||||
final status =
|
||||
await DevicesManagementApi().getDeviceStatus(event.deviceId);
|
||||
print(status.status);
|
||||
int totalSeconds = 0;
|
||||
final countdownItem = status.status.firstWhere(
|
||||
(item) => item.code == event.countdownCode,
|
||||
orElse: () => Status(code: '', value: 0),
|
||||
);
|
||||
totalSeconds = (countdownItem.value as int?) ?? 0;
|
||||
final countdownHours = totalSeconds ~/ 3600;
|
||||
final countdownMinutes = (totalSeconds % 3600) ~/ 60;
|
||||
final countdownSeconds = totalSeconds % 60;
|
||||
|
||||
final deviceStatus =
|
||||
WaterHeaterStatusModel.fromJson(event.deviceId, status.status);
|
||||
final isCountdownActive = totalSeconds > 0;
|
||||
final isInchingActive = !isCountdownActive &&
|
||||
(deviceStatus.inchingHours > 0 || deviceStatus.inchingMinutes > 0);
|
||||
|
||||
final scheduleMode =
|
||||
deviceStatus.countdownHours > 0 || deviceStatus.countdownMinutes > 0
|
||||
? ScheduleModes.countdown
|
||||
: deviceStatus.inchingHours > 0 || deviceStatus.inchingMinutes > 0
|
||||
? ScheduleModes.inching
|
||||
: ScheduleModes.schedule;
|
||||
final isCountdown = scheduleMode == ScheduleModes.countdown;
|
||||
final isInching = scheduleMode == ScheduleModes.inching;
|
||||
final newState = state is ScheduleLoaded
|
||||
? (state as ScheduleLoaded).copyWith(
|
||||
scheduleMode: ScheduleModes.schedule,
|
||||
countdownHours: countdownHours,
|
||||
countdownMinutes: countdownMinutes,
|
||||
countdownSeconds: countdownSeconds,
|
||||
inchingHours: deviceStatus.inchingHours,
|
||||
inchingMinutes: deviceStatus.inchingMinutes,
|
||||
isCountdownActive: isCountdownActive,
|
||||
isInchingActive: isInchingActive,
|
||||
countdownRemaining: isCountdownActive
|
||||
? Duration(seconds: totalSeconds)
|
||||
: Duration.zero,
|
||||
)
|
||||
: ScheduleLoaded(
|
||||
scheduleMode: ScheduleModes.schedule,
|
||||
schedules: const [],
|
||||
selectedTime: null,
|
||||
selectedDays: List.filled(7, false),
|
||||
functionOn: false,
|
||||
isEditing: false,
|
||||
deviceId: event.deviceId,
|
||||
countdownHours: countdownHours,
|
||||
countdownMinutes: countdownMinutes,
|
||||
countdownSeconds: countdownSeconds,
|
||||
inchingHours: deviceStatus.inchingHours,
|
||||
inchingMinutes: deviceStatus.inchingMinutes,
|
||||
isCountdownActive: isCountdownActive,
|
||||
isInchingActive: isInchingActive,
|
||||
countdownRemaining: isCountdownActive
|
||||
? Duration(seconds: totalSeconds)
|
||||
: Duration.zero,
|
||||
);
|
||||
emit(newState);
|
||||
|
||||
Duration? countdownRemaining;
|
||||
var isCountdownActive = false;
|
||||
var isInchingActive = false;
|
||||
if (isCountdownActive) {
|
||||
_countdownTimer?.cancel();
|
||||
_currentCountdown = Duration(seconds: totalSeconds);
|
||||
countdownRemaining = _currentCountdown!;
|
||||
|
||||
if (isCountdown) {
|
||||
countdownRemaining = Duration(
|
||||
hours: deviceStatus.countdownHours,
|
||||
minutes: deviceStatus.countdownMinutes,
|
||||
);
|
||||
isCountdownActive = countdownRemaining > Duration.zero;
|
||||
} else if (isInching) {
|
||||
isInchingActive = Duration(
|
||||
hours: deviceStatus.inchingHours,
|
||||
minutes: deviceStatus.inchingMinutes,
|
||||
) >
|
||||
Duration.zero;
|
||||
}
|
||||
if (state is ScheduleLoaded) {
|
||||
final currentState = state as ScheduleLoaded;
|
||||
emit(currentState.copyWith(
|
||||
scheduleMode: scheduleMode,
|
||||
countdownHours: deviceStatus.countdownHours,
|
||||
countdownMinutes: deviceStatus.countdownMinutes,
|
||||
inchingHours: deviceStatus.inchingHours,
|
||||
inchingMinutes: deviceStatus.inchingMinutes,
|
||||
isCountdownActive: isCountdownActive,
|
||||
isInchingActive: isInchingActive,
|
||||
countdownRemaining: countdownRemaining ?? Duration.zero,
|
||||
));
|
||||
if (totalSeconds > 0) {
|
||||
_startCountdownTimer(
|
||||
emit, Duration(seconds: totalSeconds), event.countdownCode);
|
||||
} else {
|
||||
add(StopScheduleEvent(
|
||||
countdownCode: event.countdownCode,
|
||||
mode: ScheduleModes.countdown,
|
||||
deviceId: event.deviceId,
|
||||
));
|
||||
}
|
||||
} else {
|
||||
emit(ScheduleLoaded(
|
||||
schedules: const [],
|
||||
selectedTime: null,
|
||||
selectedDays: List.filled(7, false),
|
||||
functionOn: false,
|
||||
isEditing: false,
|
||||
deviceId: deviceId,
|
||||
scheduleMode: scheduleMode,
|
||||
countdownHours: deviceStatus.countdownHours,
|
||||
countdownMinutes: deviceStatus.countdownMinutes,
|
||||
inchingHours: deviceStatus.inchingHours,
|
||||
inchingMinutes: deviceStatus.inchingMinutes,
|
||||
isCountdownActive: isCountdownActive,
|
||||
isInchingActive: isInchingActive,
|
||||
countdownRemaining: countdownRemaining ?? Duration.zero,
|
||||
));
|
||||
_countdownTimer?.cancel();
|
||||
}
|
||||
|
||||
// if (isCountdownActive && countdownRemaining != null) {
|
||||
// _startCountdownTimer(emit, countdownRemaining);
|
||||
// }
|
||||
} catch (e) {
|
||||
emit(ScheduleError('Failed to fetch device status: $e'));
|
||||
}
|
||||
|
@ -91,6 +91,7 @@ class ScheduleEditEvent extends ScheduleEvent {
|
||||
final String time;
|
||||
final List<String> selectedDays;
|
||||
final bool functionOn;
|
||||
final String deviceType;
|
||||
|
||||
const ScheduleEditEvent({
|
||||
required this.scheduleId,
|
||||
@ -98,6 +99,7 @@ class ScheduleEditEvent extends ScheduleEvent {
|
||||
required this.time,
|
||||
required this.selectedDays,
|
||||
required this.functionOn,
|
||||
required this.deviceType,
|
||||
});
|
||||
|
||||
@override
|
||||
@ -107,6 +109,7 @@ class ScheduleEditEvent extends ScheduleEvent {
|
||||
time,
|
||||
selectedDays,
|
||||
functionOn,
|
||||
deviceType,
|
||||
];
|
||||
}
|
||||
|
||||
@ -138,11 +141,13 @@ class ScheduleUpdateEntryEvent extends ScheduleEvent {
|
||||
|
||||
class UpdateScheduleModeEvent extends ScheduleEvent {
|
||||
final ScheduleModes scheduleMode;
|
||||
final String countdownCode;
|
||||
|
||||
const UpdateScheduleModeEvent({required this.scheduleMode});
|
||||
const UpdateScheduleModeEvent(
|
||||
{required this.scheduleMode, required this.countdownCode});
|
||||
|
||||
@override
|
||||
List<Object> get props => [scheduleMode];
|
||||
List<Object> get props => [scheduleMode, countdownCode!];
|
||||
}
|
||||
|
||||
class UpdateCountdownTimeEvent extends ScheduleEvent {
|
||||
@ -177,28 +182,32 @@ class StartScheduleEvent extends ScheduleEvent {
|
||||
final ScheduleModes mode;
|
||||
final int hours;
|
||||
final int minutes;
|
||||
final String countDownCode;
|
||||
|
||||
const StartScheduleEvent({
|
||||
required this.mode,
|
||||
required this.hours,
|
||||
required this.minutes,
|
||||
required this.countDownCode,
|
||||
});
|
||||
|
||||
@override
|
||||
List<Object?> get props => [mode, hours, minutes];
|
||||
List<Object?> get props => [mode, hours, minutes, countDownCode];
|
||||
}
|
||||
|
||||
class StopScheduleEvent extends ScheduleEvent {
|
||||
final ScheduleModes mode;
|
||||
final String deviceId;
|
||||
final String countdownCode;
|
||||
|
||||
const StopScheduleEvent({
|
||||
required this.mode,
|
||||
required this.deviceId,
|
||||
required this.countdownCode,
|
||||
});
|
||||
|
||||
@override
|
||||
List<Object?> get props => [mode, deviceId];
|
||||
List<Object?> get props => [mode, deviceId, countdownCode];
|
||||
}
|
||||
|
||||
class ScheduleDecrementCountdownEvent extends ScheduleEvent {
|
||||
@ -210,11 +219,13 @@ class ScheduleDecrementCountdownEvent extends ScheduleEvent {
|
||||
|
||||
class ScheduleFetchStatusEvent extends ScheduleEvent {
|
||||
final String deviceId;
|
||||
final String countdownCode;
|
||||
|
||||
const ScheduleFetchStatusEvent(this.deviceId);
|
||||
const ScheduleFetchStatusEvent(
|
||||
{required this.deviceId, required this.countdownCode});
|
||||
|
||||
@override
|
||||
List<Object> get props => [deviceId];
|
||||
List<Object> get props => [deviceId, countdownCode];
|
||||
}
|
||||
|
||||
class DeleteScheduleEvent extends ScheduleEvent {
|
||||
|
@ -29,7 +29,7 @@ class ScheduleLoaded extends ScheduleState {
|
||||
final int inchingSeconds;
|
||||
final bool isInchingActive;
|
||||
final ScheduleModes scheduleMode;
|
||||
final Duration? countdownRemaining;
|
||||
final Duration countdownRemaining;
|
||||
final int? countdownSeconds;
|
||||
|
||||
const ScheduleLoaded({
|
||||
@ -48,7 +48,7 @@ class ScheduleLoaded extends ScheduleState {
|
||||
this.inchingMinutes = 0,
|
||||
this.isInchingActive = false,
|
||||
this.scheduleMode = ScheduleModes.countdown,
|
||||
this.countdownRemaining,
|
||||
this.countdownRemaining = Duration.zero,
|
||||
});
|
||||
|
||||
ScheduleLoaded copyWith({
|
||||
|
@ -11,6 +11,7 @@ class CountdownModeButtons extends StatelessWidget {
|
||||
final String deviceId;
|
||||
final int hours;
|
||||
final int minutes;
|
||||
final String countDownCode;
|
||||
|
||||
const CountdownModeButtons({
|
||||
super.key,
|
||||
@ -18,6 +19,7 @@ class CountdownModeButtons extends StatelessWidget {
|
||||
required this.deviceId,
|
||||
required this.hours,
|
||||
required this.minutes,
|
||||
required this.countDownCode,
|
||||
});
|
||||
|
||||
@override
|
||||
@ -43,6 +45,7 @@ class CountdownModeButtons extends StatelessWidget {
|
||||
StopScheduleEvent(
|
||||
mode: ScheduleModes.countdown,
|
||||
deviceId: deviceId,
|
||||
countdownCode: countDownCode,
|
||||
),
|
||||
);
|
||||
},
|
||||
@ -54,10 +57,10 @@ class CountdownModeButtons extends StatelessWidget {
|
||||
onPressed: () {
|
||||
context.read<ScheduleBloc>().add(
|
||||
StartScheduleEvent(
|
||||
mode: ScheduleModes.countdown,
|
||||
hours: hours,
|
||||
minutes: minutes,
|
||||
),
|
||||
mode: ScheduleModes.countdown,
|
||||
hours: hours,
|
||||
minutes: minutes,
|
||||
countDownCode: countDownCode),
|
||||
);
|
||||
},
|
||||
backgroundColor: ColorsManager.primaryColor,
|
||||
|
@ -75,23 +75,33 @@ class _CountdownInchingViewState extends State<CountdownInchingView> {
|
||||
final isCountDown = state.scheduleMode == ScheduleModes.countdown;
|
||||
final isActive =
|
||||
isCountDown ? state.isCountdownActive : state.isInchingActive;
|
||||
final displayHours = isActive && state.countdownRemaining != null
|
||||
? state.countdownRemaining!.inHours
|
||||
: (isCountDown ? state.countdownHours : state.inchingHours);
|
||||
final displayMinutes = isActive && state.countdownRemaining != null
|
||||
? state.countdownRemaining!.inMinutes.remainder(60)
|
||||
: (isCountDown ? state.countdownMinutes : state.inchingMinutes);
|
||||
final displaySeconds = isActive && state.countdownRemaining != null
|
||||
? state.countdownRemaining!.inSeconds.remainder(60)
|
||||
: (isCountDown ? state.countdownSeconds : state.inchingSeconds);
|
||||
|
||||
_updateControllers(displayHours, displayMinutes, displaySeconds!);
|
||||
final displayHours =
|
||||
isActive && state.countdownRemaining != Duration.zero
|
||||
? state.countdownRemaining.inHours
|
||||
: (isCountDown ? state.countdownHours : state.inchingHours);
|
||||
|
||||
if (displayHours == 0 && displayMinutes == 0 && displaySeconds == 0) {
|
||||
final displayMinutes =
|
||||
isActive && state.countdownRemaining != Duration.zero
|
||||
? state.countdownRemaining.inMinutes.remainder(60)
|
||||
: (isCountDown ? state.countdownMinutes : state.inchingMinutes);
|
||||
|
||||
final displaySeconds =
|
||||
isActive && state.countdownRemaining != Duration.zero
|
||||
? state.countdownRemaining.inSeconds.remainder(60)
|
||||
: (isCountDown ? (state.countdownSeconds ?? 0) : 0);
|
||||
|
||||
_updateControllers(displayHours, displayMinutes, displaySeconds);
|
||||
|
||||
if (isActive &&
|
||||
displayHours == 0 &&
|
||||
displayMinutes == 0 &&
|
||||
displaySeconds == 0) {
|
||||
context.read<ScheduleBloc>().add(
|
||||
StopScheduleEvent(
|
||||
mode: ScheduleModes.countdown,
|
||||
deviceId: widget.deviceId,
|
||||
countdownCode: '',
|
||||
),
|
||||
);
|
||||
}
|
||||
|
@ -43,7 +43,9 @@ class InchingModeButtons extends StatelessWidget {
|
||||
onPressed: () {
|
||||
context.read<ScheduleBloc>().add(
|
||||
StopScheduleEvent(
|
||||
deviceId: deviceId, mode: ScheduleModes.inching),
|
||||
deviceId: deviceId,
|
||||
mode: ScheduleModes.inching,
|
||||
countdownCode: ''),
|
||||
);
|
||||
},
|
||||
backgroundColor: Colors.red,
|
||||
|
@ -18,11 +18,15 @@ class BuildScheduleView extends StatelessWidget {
|
||||
super.key,
|
||||
required this.deviceUuid,
|
||||
required this.category,
|
||||
required this.countdownCode,
|
||||
this.code,
|
||||
required this.deviceType,
|
||||
});
|
||||
final String deviceUuid;
|
||||
final String category;
|
||||
final String? code;
|
||||
final String? countdownCode;
|
||||
final String deviceType;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
@ -31,7 +35,8 @@ class BuildScheduleView extends StatelessWidget {
|
||||
deviceId: deviceUuid,
|
||||
)
|
||||
..add(ScheduleGetEvent(category: category))
|
||||
..add(ScheduleFetchStatusEvent(deviceUuid)),
|
||||
..add(ScheduleFetchStatusEvent(
|
||||
deviceId: deviceUuid, countdownCode: countdownCode ?? '')),
|
||||
child: Dialog(
|
||||
backgroundColor: Colors.white,
|
||||
insetPadding: const EdgeInsets.all(20),
|
||||
@ -52,31 +57,32 @@ class BuildScheduleView extends StatelessWidget {
|
||||
children: [
|
||||
const ScheduleHeader(),
|
||||
const SizedBox(height: 20),
|
||||
if (category == 'CUR_2')
|
||||
if (deviceType == 'CUR_2')
|
||||
const SizedBox()
|
||||
else
|
||||
ScheduleModeSelector(
|
||||
countdownCode: countdownCode ?? '',
|
||||
currentMode: state.scheduleMode,
|
||||
),
|
||||
const SizedBox(height: 20),
|
||||
if (state.scheduleMode == ScheduleModes.schedule)
|
||||
ScheduleManagementUI(
|
||||
deviceType: deviceType,
|
||||
category: category,
|
||||
deviceUuid: deviceUuid,
|
||||
onAddSchedule: () async {
|
||||
final entry = await ScheduleDialogHelper
|
||||
.showAddScheduleDialog(
|
||||
context,
|
||||
schedule: ScheduleEntry(
|
||||
category: category,
|
||||
time: '',
|
||||
function: Status(
|
||||
code: code.toString(), value: null),
|
||||
days: [],
|
||||
),
|
||||
isEdit: false,
|
||||
code: code,
|
||||
);
|
||||
.showAddScheduleDialog(context,
|
||||
schedule: ScheduleEntry(
|
||||
category: category,
|
||||
time: '',
|
||||
function: Status(
|
||||
code: code.toString(), value: null),
|
||||
days: [],
|
||||
),
|
||||
isEdit: false,
|
||||
code: code,
|
||||
deviceType: deviceType);
|
||||
if (entry != null) {
|
||||
context.read<ScheduleBloc>().add(
|
||||
ScheduleAddEvent(
|
||||
@ -90,14 +96,16 @@ class BuildScheduleView extends StatelessWidget {
|
||||
}
|
||||
},
|
||||
),
|
||||
if (state.scheduleMode == ScheduleModes.countdown ||
|
||||
state.scheduleMode == ScheduleModes.inching)
|
||||
CountdownInchingView(
|
||||
deviceId: deviceUuid,
|
||||
),
|
||||
if (deviceType != 'CUR_2')
|
||||
if (state.scheduleMode == ScheduleModes.countdown ||
|
||||
state.scheduleMode == ScheduleModes.inching)
|
||||
CountdownInchingView(
|
||||
deviceId: deviceUuid,
|
||||
),
|
||||
const SizedBox(height: 20),
|
||||
if (state.scheduleMode == ScheduleModes.countdown)
|
||||
CountdownModeButtons(
|
||||
countDownCode: countdownCode ?? '',
|
||||
isActive: state.isCountdownActive,
|
||||
deviceId: deviceUuid,
|
||||
hours: state.countdownHours,
|
||||
|
@ -5,14 +5,16 @@ import 'package:syncrow_web/utils/color_manager.dart';
|
||||
import 'package:syncrow_web/utils/extension/build_context_x.dart';
|
||||
|
||||
class ScheduleManagementUI extends StatelessWidget {
|
||||
final String deviceUuid;
|
||||
final String deviceUuid;
|
||||
final VoidCallback onAddSchedule;
|
||||
final String category;
|
||||
final String deviceType;
|
||||
|
||||
const ScheduleManagementUI({
|
||||
super.key,
|
||||
required this.deviceUuid,
|
||||
required this.onAddSchedule,
|
||||
required this.deviceType,
|
||||
this.category = 'switch_1',
|
||||
});
|
||||
|
||||
@ -44,7 +46,11 @@ class ScheduleManagementUI extends StatelessWidget {
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 20),
|
||||
ScheduleTableWidget(deviceUuid: deviceUuid, category: category),
|
||||
ScheduleTableWidget(
|
||||
deviceUuid: deviceUuid,
|
||||
category: category,
|
||||
deviceType: deviceType,
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
@ -7,10 +7,12 @@ import 'package:syncrow_web/utils/extension/build_context_x.dart';
|
||||
|
||||
class ScheduleModeSelector extends StatelessWidget {
|
||||
final ScheduleModes currentMode;
|
||||
final String countdownCode;
|
||||
|
||||
const ScheduleModeSelector({
|
||||
super.key,
|
||||
required this.currentMode,
|
||||
required this.countdownCode,
|
||||
});
|
||||
|
||||
@override
|
||||
@ -71,7 +73,8 @@ class ScheduleModeSelector extends StatelessWidget {
|
||||
onChanged: (ScheduleModes? value) {
|
||||
if (value != null) {
|
||||
context.read<ScheduleBloc>().add(
|
||||
UpdateScheduleModeEvent(scheduleMode: value),
|
||||
UpdateScheduleModeEvent(
|
||||
scheduleMode: value, countdownCode: countdownCode),
|
||||
);
|
||||
if (value == ScheduleModes.schedule) {
|
||||
context.read<ScheduleBloc>().add(
|
||||
|
@ -12,11 +12,13 @@ import 'package:syncrow_web/utils/format_date_time.dart';
|
||||
class ScheduleTableWidget extends StatelessWidget {
|
||||
final String deviceUuid;
|
||||
final String category;
|
||||
final String deviceType;
|
||||
|
||||
const ScheduleTableWidget({
|
||||
super.key,
|
||||
required this.deviceUuid,
|
||||
this.category = 'switch_1',
|
||||
required this.deviceType,
|
||||
});
|
||||
|
||||
@override
|
||||
@ -25,13 +27,14 @@ class ScheduleTableWidget extends StatelessWidget {
|
||||
create: (_) => ScheduleBloc(
|
||||
deviceId: deviceUuid,
|
||||
)..add(ScheduleGetEvent(category: category)),
|
||||
child: _ScheduleTableView(),
|
||||
child: _ScheduleTableView(deviceType),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _ScheduleTableView extends StatelessWidget {
|
||||
const _ScheduleTableView();
|
||||
final String deviceType;
|
||||
const _ScheduleTableView(this.deviceType);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
@ -81,7 +84,7 @@ class _ScheduleTableView extends StatelessWidget {
|
||||
bottomLeft: Radius.circular(20),
|
||||
bottomRight: Radius.circular(20)),
|
||||
),
|
||||
child: _buildTableBody(state.schedules, context));
|
||||
child: _buildTableBody(state.schedules, context, deviceType));
|
||||
}
|
||||
if (state is ScheduleError) {
|
||||
return Center(child: Text(state.error));
|
||||
@ -123,7 +126,8 @@ class _ScheduleTableView extends StatelessWidget {
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildTableBody(List<ScheduleModel> schedules, BuildContext context) {
|
||||
Widget _buildTableBody(
|
||||
List<ScheduleModel> schedules, BuildContext context, String deviceType) {
|
||||
return SizedBox(
|
||||
height: 200,
|
||||
child: SingleChildScrollView(
|
||||
@ -132,7 +136,8 @@ class _ScheduleTableView extends StatelessWidget {
|
||||
defaultVerticalAlignment: TableCellVerticalAlignment.middle,
|
||||
children: [
|
||||
for (int i = 0; i < schedules.length; i++)
|
||||
_buildScheduleRow(schedules[i], i, context),
|
||||
_buildScheduleRow(schedules[i], i, context,
|
||||
deviceType: deviceType),
|
||||
],
|
||||
),
|
||||
),
|
||||
@ -155,25 +160,19 @@ class _ScheduleTableView extends StatelessWidget {
|
||||
}
|
||||
|
||||
TableRow _buildScheduleRow(
|
||||
ScheduleModel schedule, int index, BuildContext context) {
|
||||
ScheduleModel schedule, int index, BuildContext context,
|
||||
{required String deviceType}) {
|
||||
return TableRow(
|
||||
children: [
|
||||
Center(
|
||||
child: GestureDetector(
|
||||
behavior: HitTestBehavior.opaque,
|
||||
onTap: () {
|
||||
bool temp;
|
||||
if (schedule.category == 'CUR_2') {
|
||||
temp = schedule.function.value == 'open' ? true : false;
|
||||
} else {
|
||||
temp = schedule.function.value as bool;
|
||||
}
|
||||
context.read<ScheduleBloc>().add(
|
||||
ScheduleUpdateEntryEvent(
|
||||
category: schedule.category,
|
||||
scheduleId: schedule.scheduleId,
|
||||
functionOn: temp,
|
||||
// schedule.function.value,
|
||||
functionOn: schedule.function.value,
|
||||
enable: !schedule.enable,
|
||||
),
|
||||
);
|
||||
@ -195,8 +194,9 @@ class _ScheduleTableView extends StatelessWidget {
|
||||
child: Text(_getSelectedDays(
|
||||
ScheduleModel.parseSelectedDays(schedule.days)))),
|
||||
Center(child: Text(formatIsoStringToTime(schedule.time, context))),
|
||||
if (schedule.category == 'CUR_2')
|
||||
Center(child: Text(schedule.function.value))
|
||||
if (deviceType == 'CUR_2')
|
||||
Center(
|
||||
child: Text(schedule.function.value == true ? 'open' : 'close'))
|
||||
else
|
||||
Center(child: Text(schedule.function.value ? 'On' : 'Off')),
|
||||
Center(
|
||||
@ -206,14 +206,14 @@ class _ScheduleTableView extends StatelessWidget {
|
||||
TextButton(
|
||||
style: TextButton.styleFrom(padding: EdgeInsets.zero),
|
||||
onPressed: () {
|
||||
ScheduleDialogHelper.showAddScheduleDialog(
|
||||
context,
|
||||
schedule: ScheduleEntry.fromScheduleModel(schedule),
|
||||
isEdit: true,
|
||||
).then((updatedSchedule) {
|
||||
ScheduleDialogHelper.showAddScheduleDialog(context,
|
||||
schedule: ScheduleEntry.fromScheduleModel(schedule),
|
||||
isEdit: true,
|
||||
deviceType: deviceType)
|
||||
.then((updatedSchedule) {
|
||||
if (updatedSchedule != null) {
|
||||
bool temp;
|
||||
if (schedule.category == 'CUR_2') {
|
||||
if (deviceType == 'CUR_2') {
|
||||
updatedSchedule.function.value == 'open'
|
||||
? temp = true
|
||||
: temp = false;
|
||||
@ -222,6 +222,7 @@ class _ScheduleTableView extends StatelessWidget {
|
||||
}
|
||||
context.read<ScheduleBloc>().add(
|
||||
ScheduleEditEvent(
|
||||
deviceType: deviceType,
|
||||
scheduleId: schedule.scheduleId,
|
||||
category: schedule.category,
|
||||
time: updatedSchedule.time,
|
||||
|
@ -41,7 +41,7 @@ class ThreeGangGlassSwitchBloc
|
||||
emit(ThreeGangGlassSwitchLoading());
|
||||
try {
|
||||
final status = await DevicesManagementApi().getDeviceStatus(event.deviceId);
|
||||
_listenToChanges(event.deviceId, emit);
|
||||
_listenToChanges(event.deviceId);
|
||||
deviceStatus =
|
||||
ThreeGangGlassStatusModel.fromJson(event.deviceId, status.status);
|
||||
emit(ThreeGangGlassSwitchStatusLoaded(deviceStatus));
|
||||
@ -50,42 +50,28 @@ class ThreeGangGlassSwitchBloc
|
||||
}
|
||||
}
|
||||
|
||||
void _listenToChanges(
|
||||
String deviceId,
|
||||
Emitter<ThreeGangGlassSwitchState> emit,
|
||||
) {
|
||||
StreamSubscription<DatabaseEvent>? _deviceStatusSubscription;
|
||||
|
||||
void _listenToChanges(String deviceId) {
|
||||
try {
|
||||
final ref = FirebaseDatabase.instance.ref('device-status/$deviceId');
|
||||
final stream = ref.onValue;
|
||||
_deviceStatusSubscription = ref.onValue.listen((DatabaseEvent event) async {
|
||||
if (event.snapshot.value == null) return;
|
||||
|
||||
stream.listen((DatabaseEvent event) {
|
||||
final data = event.snapshot.value as Map<dynamic, dynamic>?;
|
||||
if (data == null) return;
|
||||
final usersMap = event.snapshot.value! as Map<dynamic, dynamic>;
|
||||
|
||||
final statusList = <Status>[];
|
||||
if (data['status'] != null) {
|
||||
for (var element in data['status']) {
|
||||
statusList.add(
|
||||
Status(
|
||||
code: element['code'].toString(),
|
||||
value: element['value'].toString(),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
if (statusList.isNotEmpty) {
|
||||
final newStatus = ThreeGangGlassStatusModel.fromJson(deviceId, statusList);
|
||||
if (newStatus != deviceStatus) {
|
||||
deviceStatus = newStatus;
|
||||
if (!isClosed) {
|
||||
add(StatusUpdated(deviceStatus));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
usersMap['status'].forEach((element) {
|
||||
statusList.add(Status(code: element['code'], value: element['value']));
|
||||
});
|
||||
|
||||
deviceStatus =
|
||||
ThreeGangGlassStatusModel.fromJson(usersMap['productUuid'], statusList);
|
||||
|
||||
add(StatusUpdated(deviceStatus));
|
||||
});
|
||||
} catch (e) {
|
||||
emit(ThreeGangGlassSwitchError('Failed to listen to changes: $e'));
|
||||
}
|
||||
} catch (_) {}
|
||||
}
|
||||
|
||||
void _onStatusUpdated(
|
||||
@ -184,4 +170,10 @@ class ThreeGangGlassSwitchBloc
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> close() {
|
||||
_deviceStatusSubscription?.cancel();
|
||||
return super.close();
|
||||
}
|
||||
}
|
||||
|
@ -111,6 +111,8 @@ class ThreeGangGlassSwitchControlView extends StatelessWidget
|
||||
child: BuildScheduleView(
|
||||
category: 'switch_1',
|
||||
deviceUuid: deviceId,
|
||||
countdownCode: 'countdown_1',
|
||||
deviceType: '3GT',
|
||||
),
|
||||
));
|
||||
},
|
||||
@ -127,6 +129,8 @@ class ThreeGangGlassSwitchControlView extends StatelessWidget
|
||||
child: BuildScheduleView(
|
||||
category: 'switch_2',
|
||||
deviceUuid: deviceId,
|
||||
countdownCode: 'countdown_2',
|
||||
deviceType: '3GT',
|
||||
),
|
||||
));
|
||||
},
|
||||
@ -143,6 +147,8 @@ class ThreeGangGlassSwitchControlView extends StatelessWidget
|
||||
child: BuildScheduleView(
|
||||
category: 'switch_3',
|
||||
deviceUuid: deviceId,
|
||||
countdownCode: 'countdown_3',
|
||||
deviceType: '3GT',
|
||||
),
|
||||
));
|
||||
},
|
||||
|
@ -102,6 +102,8 @@ class LivingRoomDeviceControlsView extends StatelessWidget
|
||||
child: BuildScheduleView(
|
||||
deviceUuid: deviceId,
|
||||
category: 'switch_1',
|
||||
countdownCode: 'countdown_1',
|
||||
deviceType: '3G',
|
||||
),
|
||||
));
|
||||
},
|
||||
@ -118,6 +120,8 @@ class LivingRoomDeviceControlsView extends StatelessWidget
|
||||
child: BuildScheduleView(
|
||||
deviceUuid: deviceId,
|
||||
category: 'switch_2',
|
||||
countdownCode: 'countdown_2',
|
||||
deviceType: '3G',
|
||||
),
|
||||
));
|
||||
},
|
||||
@ -134,6 +138,8 @@ class LivingRoomDeviceControlsView extends StatelessWidget
|
||||
child: BuildScheduleView(
|
||||
deviceUuid: deviceId,
|
||||
category: 'switch_3',
|
||||
countdownCode: 'countdown_3',
|
||||
deviceType: '3G',
|
||||
),
|
||||
));
|
||||
},
|
||||
|
@ -1,173 +1,177 @@
|
||||
import 'dart:async';
|
||||
import 'dart:developer';
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:bloc/bloc.dart';
|
||||
import 'package:equatable/equatable.dart';
|
||||
import 'package:firebase_database/firebase_database.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/all_devices/models/device_status.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/all_devices/models/factory_reset_model.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/two_g_glass_switch/models/two_gang_glass_status_model.dart';
|
||||
import 'package:syncrow_web/services/batch_control_devices_service.dart';
|
||||
import 'package:syncrow_web/services/control_device_service.dart';
|
||||
import 'package:syncrow_web/services/devices_mang_api.dart';
|
||||
import 'package:bloc/bloc.dart';
|
||||
import 'package:equatable/equatable.dart';
|
||||
import 'package:firebase_database/firebase_database.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/all_devices/models/device_status.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/all_devices/models/factory_reset_model.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/two_g_glass_switch/models/two_gang_glass_status_model.dart';
|
||||
import 'package:syncrow_web/services/batch_control_devices_service.dart';
|
||||
import 'package:syncrow_web/services/control_device_service.dart';
|
||||
import 'package:syncrow_web/services/devices_mang_api.dart';
|
||||
|
||||
part 'two_gang_glass_switch_event.dart';
|
||||
part 'two_gang_glass_switch_state.dart';
|
||||
part 'two_gang_glass_switch_event.dart';
|
||||
part 'two_gang_glass_switch_state.dart';
|
||||
|
||||
class TwoGangGlassSwitchBloc
|
||||
extends Bloc<TwoGangGlassSwitchEvent, TwoGangGlassSwitchState> {
|
||||
final String deviceId;
|
||||
final ControlDeviceService controlDeviceService;
|
||||
final BatchControlDevicesService batchControlDevicesService;
|
||||
class TwoGangGlassSwitchBloc
|
||||
extends Bloc<TwoGangGlassSwitchEvent, TwoGangGlassSwitchState> {
|
||||
final String deviceId;
|
||||
final ControlDeviceService controlDeviceService;
|
||||
final BatchControlDevicesService batchControlDevicesService;
|
||||
|
||||
late TwoGangGlassStatusModel deviceStatus;
|
||||
late TwoGangGlassStatusModel deviceStatus;
|
||||
|
||||
TwoGangGlassSwitchBloc({
|
||||
required this.deviceId,
|
||||
required this.controlDeviceService,
|
||||
required this.batchControlDevicesService,
|
||||
}) : super(TwoGangGlassSwitchInitial()) {
|
||||
on<TwoGangGlassSwitchFetchDeviceEvent>(_onFetchDeviceStatus);
|
||||
on<TwoGangGlassSwitchControl>(_onControl);
|
||||
on<TwoGangGlassSwitchBatchControl>(_onBatchControl);
|
||||
on<TwoGangGlassSwitchFetchBatchStatusEvent>(_onFetchBatchStatus);
|
||||
on<TwoGangGlassFactoryReset>(_onFactoryReset);
|
||||
on<StatusUpdated>(_onStatusUpdated);
|
||||
}
|
||||
|
||||
Future<void> _onFetchDeviceStatus(
|
||||
TwoGangGlassSwitchFetchDeviceEvent event,
|
||||
Emitter<TwoGangGlassSwitchState> emit,
|
||||
) async {
|
||||
emit(TwoGangGlassSwitchLoading());
|
||||
try {
|
||||
final status = await DevicesManagementApi().getDeviceStatus(event.deviceId);
|
||||
deviceStatus = TwoGangGlassStatusModel.fromJson(event.deviceId, status.status);
|
||||
_listenToChanges(event.deviceId);
|
||||
emit(TwoGangGlassSwitchStatusLoaded(deviceStatus));
|
||||
} catch (e) {
|
||||
emit(TwoGangGlassSwitchError(e.toString()));
|
||||
TwoGangGlassSwitchBloc({
|
||||
required this.deviceId,
|
||||
required this.controlDeviceService,
|
||||
required this.batchControlDevicesService,
|
||||
}) : super(TwoGangGlassSwitchInitial()) {
|
||||
on<TwoGangGlassSwitchFetchDeviceEvent>(_onFetchDeviceStatus);
|
||||
on<TwoGangGlassSwitchControl>(_onControl);
|
||||
on<TwoGangGlassSwitchBatchControl>(_onBatchControl);
|
||||
on<TwoGangGlassSwitchFetchBatchStatusEvent>(_onFetchBatchStatus);
|
||||
on<TwoGangGlassFactoryReset>(_onFactoryReset);
|
||||
on<StatusUpdated>(_onStatusUpdated);
|
||||
}
|
||||
}
|
||||
|
||||
void _listenToChanges(String deviceId) {
|
||||
try {
|
||||
final ref = FirebaseDatabase.instance.ref(
|
||||
'device-status/$deviceId',
|
||||
);
|
||||
|
||||
ref.onValue.listen((event) {
|
||||
final eventsMap = event.snapshot.value as Map<dynamic, dynamic>;
|
||||
|
||||
List<Status> statusList = [];
|
||||
eventsMap['status'].forEach((element) {
|
||||
statusList.add(Status(code: element['code'], value: element['value']));
|
||||
});
|
||||
|
||||
deviceStatus = TwoGangGlassStatusModel.fromJson(deviceId, statusList);
|
||||
add(StatusUpdated(deviceStatus));
|
||||
});
|
||||
} catch (_) {
|
||||
log(
|
||||
'Error listening to changes',
|
||||
name: 'TwoGangGlassSwitchBloc._listenToChanges',
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _onControl(
|
||||
TwoGangGlassSwitchControl event,
|
||||
Emitter<TwoGangGlassSwitchState> emit,
|
||||
) async {
|
||||
emit(TwoGangGlassSwitchLoading());
|
||||
_updateLocalValue(event.code, event.value);
|
||||
emit(TwoGangGlassSwitchStatusLoaded(deviceStatus));
|
||||
|
||||
try {
|
||||
await controlDeviceService.controlDevice(
|
||||
deviceUuid: event.deviceId,
|
||||
status: Status(code: event.code, value: event.value),
|
||||
);
|
||||
} catch (e) {
|
||||
_updateLocalValue(event.code, !event.value);
|
||||
emit(TwoGangGlassSwitchError(e.toString()));
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _onBatchControl(
|
||||
TwoGangGlassSwitchBatchControl event,
|
||||
Emitter<TwoGangGlassSwitchState> emit,
|
||||
) async {
|
||||
emit(TwoGangGlassSwitchLoading());
|
||||
_updateLocalValue(event.code, event.value);
|
||||
emit(TwoGangGlassSwitchBatchStatusLoaded(deviceStatus));
|
||||
|
||||
try {
|
||||
await batchControlDevicesService.batchControlDevices(
|
||||
uuids: event.deviceIds,
|
||||
code: event.code,
|
||||
value: event.value,
|
||||
);
|
||||
} catch (e) {
|
||||
_updateLocalValue(event.code, !event.value);
|
||||
emit(TwoGangGlassSwitchError(e.toString()));
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _onFetchBatchStatus(
|
||||
TwoGangGlassSwitchFetchBatchStatusEvent event,
|
||||
Emitter<TwoGangGlassSwitchState> emit,
|
||||
) async {
|
||||
emit(TwoGangGlassSwitchLoading());
|
||||
try {
|
||||
final status = await DevicesManagementApi().getBatchStatus(event.deviceIds);
|
||||
deviceStatus = TwoGangGlassStatusModel.fromJson(
|
||||
event.deviceIds.first,
|
||||
status.status,
|
||||
);
|
||||
emit(TwoGangGlassSwitchBatchStatusLoaded(deviceStatus));
|
||||
} catch (e) {
|
||||
emit(TwoGangGlassSwitchError(e.toString()));
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _onFactoryReset(
|
||||
TwoGangGlassFactoryReset event,
|
||||
Emitter<TwoGangGlassSwitchState> emit,
|
||||
) async {
|
||||
emit(TwoGangGlassSwitchLoading());
|
||||
try {
|
||||
final response = await DevicesManagementApi().factoryReset(
|
||||
event.factoryReset,
|
||||
event.deviceId,
|
||||
);
|
||||
if (!response) {
|
||||
emit(TwoGangGlassSwitchError('Failed to reset device'));
|
||||
} else {
|
||||
add(TwoGangGlassSwitchFetchDeviceEvent(event.deviceId));
|
||||
Future<void> _onFetchDeviceStatus(
|
||||
TwoGangGlassSwitchFetchDeviceEvent event,
|
||||
Emitter<TwoGangGlassSwitchState> emit,
|
||||
) async {
|
||||
emit(TwoGangGlassSwitchLoading());
|
||||
try {
|
||||
final status = await DevicesManagementApi().getDeviceStatus(event.deviceId);
|
||||
deviceStatus = TwoGangGlassStatusModel.fromJson(event.deviceId, status.status);
|
||||
_listenToChanges(event.deviceId);
|
||||
emit(TwoGangGlassSwitchStatusLoaded(deviceStatus));
|
||||
} catch (e) {
|
||||
emit(TwoGangGlassSwitchError(e.toString()));
|
||||
}
|
||||
} catch (e) {
|
||||
emit(TwoGangGlassSwitchError(e.toString()));
|
||||
}
|
||||
}
|
||||
|
||||
void _onStatusUpdated(
|
||||
StatusUpdated event,
|
||||
Emitter<TwoGangGlassSwitchState> emit,
|
||||
) {
|
||||
deviceStatus = event.deviceStatus;
|
||||
emit(TwoGangGlassSwitchStatusLoaded(deviceStatus));
|
||||
}
|
||||
StreamSubscription<DatabaseEvent>? _deviceStatusSubscription;
|
||||
|
||||
void _updateLocalValue(String code, bool value) {
|
||||
switch (code) {
|
||||
case 'switch_1':
|
||||
deviceStatus = deviceStatus.copyWith(switch1: value);
|
||||
break;
|
||||
case 'switch_2':
|
||||
deviceStatus = deviceStatus.copyWith(switch2: value);
|
||||
break;
|
||||
void _listenToChanges(String deviceId) {
|
||||
try {
|
||||
final ref = FirebaseDatabase.instance.ref('device-status/$deviceId');
|
||||
_deviceStatusSubscription = ref.onValue.listen((DatabaseEvent event) async {
|
||||
if (event.snapshot.value == null) return;
|
||||
|
||||
final usersMap = event.snapshot.value! as Map<dynamic, dynamic>;
|
||||
|
||||
final statusList = <Status>[];
|
||||
|
||||
usersMap['status'].forEach((element) {
|
||||
statusList.add(Status(code: element['code'], value: element['value']));
|
||||
});
|
||||
|
||||
deviceStatus =
|
||||
TwoGangGlassStatusModel.fromJson(usersMap['productUuid'], statusList);
|
||||
|
||||
add(StatusUpdated(deviceStatus));
|
||||
});
|
||||
} catch (_) {}
|
||||
}
|
||||
|
||||
Future<void> _onControl(
|
||||
TwoGangGlassSwitchControl event,
|
||||
Emitter<TwoGangGlassSwitchState> emit,
|
||||
) async {
|
||||
emit(TwoGangGlassSwitchLoading());
|
||||
_updateLocalValue(event.code, event.value);
|
||||
emit(TwoGangGlassSwitchStatusLoaded(deviceStatus));
|
||||
|
||||
try {
|
||||
await controlDeviceService.controlDevice(
|
||||
deviceUuid: event.deviceId,
|
||||
status: Status(code: event.code, value: event.value),
|
||||
);
|
||||
} catch (e) {
|
||||
_updateLocalValue(event.code, !event.value);
|
||||
emit(TwoGangGlassSwitchError(e.toString()));
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _onBatchControl(
|
||||
TwoGangGlassSwitchBatchControl event,
|
||||
Emitter<TwoGangGlassSwitchState> emit,
|
||||
) async {
|
||||
emit(TwoGangGlassSwitchLoading());
|
||||
_updateLocalValue(event.code, event.value);
|
||||
emit(TwoGangGlassSwitchBatchStatusLoaded(deviceStatus));
|
||||
|
||||
try {
|
||||
await batchControlDevicesService.batchControlDevices(
|
||||
uuids: event.deviceIds,
|
||||
code: event.code,
|
||||
value: event.value,
|
||||
);
|
||||
} catch (e) {
|
||||
_updateLocalValue(event.code, !event.value);
|
||||
emit(TwoGangGlassSwitchError(e.toString()));
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _onFetchBatchStatus(
|
||||
TwoGangGlassSwitchFetchBatchStatusEvent event,
|
||||
Emitter<TwoGangGlassSwitchState> emit,
|
||||
) async {
|
||||
emit(TwoGangGlassSwitchLoading());
|
||||
try {
|
||||
final status = await DevicesManagementApi().getBatchStatus(event.deviceIds);
|
||||
deviceStatus = TwoGangGlassStatusModel.fromJson(
|
||||
event.deviceIds.first,
|
||||
status.status,
|
||||
);
|
||||
emit(TwoGangGlassSwitchBatchStatusLoaded(deviceStatus));
|
||||
} catch (e) {
|
||||
emit(TwoGangGlassSwitchError(e.toString()));
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _onFactoryReset(
|
||||
TwoGangGlassFactoryReset event,
|
||||
Emitter<TwoGangGlassSwitchState> emit,
|
||||
) async {
|
||||
emit(TwoGangGlassSwitchLoading());
|
||||
try {
|
||||
final response = await DevicesManagementApi().factoryReset(
|
||||
event.factoryReset,
|
||||
event.deviceId,
|
||||
);
|
||||
if (!response) {
|
||||
emit(TwoGangGlassSwitchError('Failed to reset device'));
|
||||
} else {
|
||||
add(TwoGangGlassSwitchFetchDeviceEvent(event.deviceId));
|
||||
}
|
||||
} catch (e) {
|
||||
emit(TwoGangGlassSwitchError(e.toString()));
|
||||
}
|
||||
}
|
||||
|
||||
void _onStatusUpdated(
|
||||
StatusUpdated event,
|
||||
Emitter<TwoGangGlassSwitchState> emit,
|
||||
) {
|
||||
deviceStatus = event.deviceStatus;
|
||||
emit(TwoGangGlassSwitchStatusLoaded(deviceStatus));
|
||||
}
|
||||
|
||||
void _updateLocalValue(String code, bool value) {
|
||||
switch (code) {
|
||||
case 'switch_1':
|
||||
deviceStatus = deviceStatus.copyWith(switch1: value);
|
||||
break;
|
||||
case 'switch_2':
|
||||
deviceStatus = deviceStatus.copyWith(switch2: value);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> close() {
|
||||
_deviceStatusSubscription?.cancel();
|
||||
return super.close();
|
||||
}
|
||||
}
|
||||
|
@ -102,6 +102,8 @@ class TwoGangGlassSwitchControlView extends StatelessWidget
|
||||
builder: (ctx) => BlocProvider.value(
|
||||
value: BlocProvider.of<TwoGangGlassSwitchBloc>(context),
|
||||
child: BuildScheduleView(
|
||||
deviceType: '2GT',
|
||||
countdownCode: 'countdown_1',
|
||||
deviceUuid: deviceId,
|
||||
category: 'switch_1',
|
||||
),
|
||||
@ -118,6 +120,8 @@ class TwoGangGlassSwitchControlView extends StatelessWidget
|
||||
builder: (ctx) => BlocProvider.value(
|
||||
value: BlocProvider.of<TwoGangGlassSwitchBloc>(context),
|
||||
child: BuildScheduleView(
|
||||
deviceType: '2GT',
|
||||
countdownCode: 'countdown_2',
|
||||
deviceUuid: deviceId,
|
||||
category: 'switch_2',
|
||||
),
|
||||
|
@ -97,6 +97,8 @@ class TwoGangBatchControlView extends StatelessWidget
|
||||
child: BuildScheduleView(
|
||||
category: 'switch_1',
|
||||
deviceUuid: deviceIds.first,
|
||||
countdownCode: 'countdown_1',
|
||||
deviceType: '2G',
|
||||
),
|
||||
));
|
||||
},
|
||||
@ -114,6 +116,8 @@ class TwoGangBatchControlView extends StatelessWidget
|
||||
child: BuildScheduleView(
|
||||
category: 'switch_2',
|
||||
deviceUuid: deviceIds.first,
|
||||
countdownCode: 'countdown_2',
|
||||
deviceType: '2G',
|
||||
),
|
||||
));
|
||||
},
|
||||
@ -121,10 +125,7 @@ class TwoGangBatchControlView extends StatelessWidget
|
||||
subtitle: 'Scheduling',
|
||||
iconPath: Assets.scheduling,
|
||||
),
|
||||
// FirmwareUpdateWidget(
|
||||
// deviceId: deviceIds.first,
|
||||
// version: 12,
|
||||
// ),
|
||||
|
||||
FactoryResetWidget(callFactoryReset: () {
|
||||
context.read<TwoGangSwitchBloc>().add(
|
||||
TwoGangFactoryReset(
|
||||
|
@ -103,6 +103,8 @@ class TwoGangDeviceControlView extends StatelessWidget
|
||||
child: BuildScheduleView(
|
||||
deviceUuid: deviceId,
|
||||
category: 'switch_1',
|
||||
countdownCode: 'countdown_1',
|
||||
deviceType: '2G',
|
||||
),
|
||||
));
|
||||
},
|
||||
@ -125,6 +127,8 @@ class TwoGangDeviceControlView extends StatelessWidget
|
||||
child: BuildScheduleView(
|
||||
deviceUuid: deviceId,
|
||||
category: 'switch_2',
|
||||
countdownCode: 'countdown_2',
|
||||
deviceType: '2G',
|
||||
),
|
||||
));
|
||||
},
|
||||
|
@ -18,9 +18,10 @@ class ScheduleDialogHelper {
|
||||
ScheduleEntry? schedule,
|
||||
bool isEdit = false,
|
||||
String? code,
|
||||
required String deviceType,
|
||||
}) {
|
||||
bool temp;
|
||||
if (schedule?.category == 'CUR_2') {
|
||||
if (deviceType == 'CUR_2') {
|
||||
temp = schedule!.function.value == 'open' ? true : false;
|
||||
} else {
|
||||
temp = schedule!.function.value;
|
||||
@ -103,8 +104,7 @@ class ScheduleDialogHelper {
|
||||
setState(() => selectedDays[i] = v);
|
||||
}),
|
||||
const SizedBox(height: 16),
|
||||
_buildFunctionSwitch(schedule!.category, ctx, functionOn!,
|
||||
(v) {
|
||||
_buildFunctionSwitch(deviceType, ctx, functionOn!, (v) {
|
||||
setState(() => functionOn = v);
|
||||
}),
|
||||
],
|
||||
@ -120,32 +120,29 @@ class ScheduleDialogHelper {
|
||||
),
|
||||
),
|
||||
SizedBox(
|
||||
width: 100,
|
||||
child: ElevatedButton(
|
||||
onPressed: () {
|
||||
dynamic temp;
|
||||
if (schedule?.category == 'CUR_2') {
|
||||
temp = functionOn! ? 'open' : 'close';
|
||||
} else {
|
||||
temp = functionOn;
|
||||
}
|
||||
print(temp);
|
||||
final entry = ScheduleEntry(
|
||||
category: schedule?.category ?? 'switch_1',
|
||||
time: _formatTimeOfDayToISO(selectedTime),
|
||||
function: Status(
|
||||
code: code ?? 'switch_1',
|
||||
value: temp,
|
||||
// functionOn,
|
||||
),
|
||||
days: _convertSelectedDaysToStrings(selectedDays),
|
||||
scheduleId: schedule?.scheduleId,
|
||||
);
|
||||
Navigator.pop(ctx, entry);
|
||||
},
|
||||
child: const Text('Save'),
|
||||
),
|
||||
),
|
||||
width: 100,
|
||||
child: ElevatedButton(
|
||||
onPressed: () {
|
||||
dynamic temp;
|
||||
if (deviceType == 'CUR_2') {
|
||||
temp = functionOn! ? 'open' : 'close';
|
||||
} else {
|
||||
temp = functionOn;
|
||||
}
|
||||
final entry = ScheduleEntry(
|
||||
category: schedule?.category ?? 'switch_1',
|
||||
time: _formatTimeOfDayToISO(selectedTime),
|
||||
function: Status(
|
||||
code: code ?? 'switch_1',
|
||||
value: temp,
|
||||
),
|
||||
days: _convertSelectedDaysToStrings(selectedDays),
|
||||
scheduleId: schedule.scheduleId,
|
||||
);
|
||||
Navigator.pop(ctx, entry);
|
||||
},
|
||||
child: const Text('Save'),
|
||||
)),
|
||||
],
|
||||
);
|
||||
},
|
||||
|
@ -84,6 +84,8 @@ class WaterHeaterDeviceControlView extends StatelessWidget
|
||||
child: BuildScheduleView(
|
||||
deviceUuid: device.uuid ?? '',
|
||||
category: 'switch_1',
|
||||
countdownCode: 'countdown_1',
|
||||
deviceType: 'WH',
|
||||
),
|
||||
));
|
||||
},
|
||||
|
@ -455,7 +455,7 @@ class UsersBloc extends Bloc<UsersEvent, UsersState> {
|
||||
Future<void> checkEmail(
|
||||
CheckEmailEvent event, Emitter<UsersState> emit) async {
|
||||
emit(UsersLoadingState());
|
||||
String? res = await UserPermissionApi().checkEmail(
|
||||
String? res = await UserPermissionApi().checkEmail(
|
||||
emailController.text,
|
||||
);
|
||||
checkEmailValid = res!;
|
||||
|
@ -34,7 +34,8 @@ class _AddNewUserDialogState extends State<AddNewUserDialog> {
|
||||
return Dialog(
|
||||
child: Container(
|
||||
decoration: const BoxDecoration(
|
||||
color: Colors.white, borderRadius: BorderRadius.all(Radius.circular(20))),
|
||||
color: Colors.white,
|
||||
borderRadius: BorderRadius.all(Radius.circular(20))),
|
||||
width: 900,
|
||||
child: Column(
|
||||
children: [
|
||||
@ -63,7 +64,8 @@ class _AddNewUserDialogState extends State<AddNewUserDialog> {
|
||||
children: [
|
||||
_buildStep1Indicator(1, "Basics", _blocRole),
|
||||
_buildStep2Indicator(2, "Spaces", _blocRole),
|
||||
_buildStep3Indicator(3, "Role & Permissions", _blocRole),
|
||||
_buildStep3Indicator(
|
||||
3, "Role & Permissions", _blocRole),
|
||||
],
|
||||
),
|
||||
),
|
||||
@ -105,18 +107,32 @@ class _AddNewUserDialogState extends State<AddNewUserDialog> {
|
||||
),
|
||||
InkWell(
|
||||
onTap: () {
|
||||
final isBasicsStep = currentStep == 1;
|
||||
|
||||
if (isBasicsStep) {
|
||||
// Validate the form first
|
||||
final isValid = _blocRole.formKey.currentState
|
||||
?.validate() ??
|
||||
false;
|
||||
|
||||
if (!isValid)
|
||||
return; // Stop if form is not valid
|
||||
}
|
||||
_blocRole.add(const CheckEmailEvent());
|
||||
|
||||
setState(() {
|
||||
if (currentStep < 3) {
|
||||
currentStep++;
|
||||
if (currentStep == 2) {
|
||||
_blocRole.add(const CheckStepStatus(isEditUser: false));
|
||||
_blocRole.add(const CheckStepStatus(
|
||||
isEditUser: false));
|
||||
} else if (currentStep == 3) {
|
||||
_blocRole.add(const CheckSpacesStepStatus());
|
||||
_blocRole
|
||||
.add(const CheckSpacesStepStatus());
|
||||
}
|
||||
} else {
|
||||
_blocRole.add(SendInviteUsers(context: context));
|
||||
_blocRole
|
||||
.add(SendInviteUsers(context: context));
|
||||
}
|
||||
});
|
||||
},
|
||||
@ -124,8 +140,11 @@ class _AddNewUserDialogState extends State<AddNewUserDialog> {
|
||||
currentStep < 3 ? "Next" : "Save",
|
||||
style: TextStyle(
|
||||
color: (_blocRole.isCompleteSpaces == false ||
|
||||
_blocRole.isCompleteBasics == false ||
|
||||
_blocRole.isCompleteRolePermissions == false) &&
|
||||
_blocRole.isCompleteBasics ==
|
||||
false ||
|
||||
_blocRole
|
||||
.isCompleteRolePermissions ==
|
||||
false) &&
|
||||
currentStep == 3
|
||||
? ColorsManager.grayColor
|
||||
: ColorsManager.secondaryColor),
|
||||
@ -143,7 +162,7 @@ class _AddNewUserDialogState extends State<AddNewUserDialog> {
|
||||
Widget _getFormContent() {
|
||||
switch (currentStep) {
|
||||
case 1:
|
||||
return const BasicsView(
|
||||
return BasicsView(
|
||||
userId: '',
|
||||
);
|
||||
case 2:
|
||||
@ -196,8 +215,12 @@ class _AddNewUserDialogState extends State<AddNewUserDialog> {
|
||||
label,
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
color: currentStep == step ? ColorsManager.blackColor : ColorsManager.greyColor,
|
||||
fontWeight: currentStep == step ? FontWeight.bold : FontWeight.normal,
|
||||
color: currentStep == step
|
||||
? ColorsManager.blackColor
|
||||
: ColorsManager.greyColor,
|
||||
fontWeight: currentStep == step
|
||||
? FontWeight.bold
|
||||
: FontWeight.normal,
|
||||
),
|
||||
),
|
||||
],
|
||||
@ -260,8 +283,12 @@ class _AddNewUserDialogState extends State<AddNewUserDialog> {
|
||||
label,
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
color: currentStep == step ? ColorsManager.blackColor : ColorsManager.greyColor,
|
||||
fontWeight: currentStep == step ? FontWeight.bold : FontWeight.normal,
|
||||
color: currentStep == step
|
||||
? ColorsManager.blackColor
|
||||
: ColorsManager.greyColor,
|
||||
fontWeight: currentStep == step
|
||||
? FontWeight.bold
|
||||
: FontWeight.normal,
|
||||
),
|
||||
),
|
||||
],
|
||||
@ -318,8 +345,12 @@ class _AddNewUserDialogState extends State<AddNewUserDialog> {
|
||||
label,
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
color: currentStep == step ? ColorsManager.blackColor : ColorsManager.greyColor,
|
||||
fontWeight: currentStep == step ? FontWeight.bold : FontWeight.normal,
|
||||
color: currentStep == step
|
||||
? ColorsManager.blackColor
|
||||
: ColorsManager.greyColor,
|
||||
fontWeight: currentStep == step
|
||||
? FontWeight.bold
|
||||
: FontWeight.normal,
|
||||
),
|
||||
),
|
||||
],
|
||||
|
@ -1,9 +1,12 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:intl_phone_field/countries.dart';
|
||||
import 'package:intl_phone_field/country_picker_dialog.dart';
|
||||
import 'package:intl_phone_field/intl_phone_field.dart';
|
||||
import 'package:syncrow_web/pages/roles_and_permission/users_page/add_user_dialog/bloc/users_bloc.dart';
|
||||
import 'package:syncrow_web/pages/roles_and_permission/users_page/add_user_dialog/bloc/users_event.dart';
|
||||
import 'package:syncrow_web/pages/roles_and_permission/users_page/add_user_dialog/bloc/users_status.dart';
|
||||
import 'package:syncrow_web/utils/color_manager.dart';
|
||||
import 'package:syncrow_web/utils/extension/build_context_x.dart';
|
||||
@ -11,7 +14,9 @@ import 'package:syncrow_web/utils/style.dart';
|
||||
|
||||
class BasicsView extends StatelessWidget {
|
||||
final String? userId;
|
||||
const BasicsView({super.key, this.userId = ''});
|
||||
Timer? _debounce;
|
||||
|
||||
BasicsView({super.key, this.userId = ''});
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BlocBuilder<UsersBloc, UsersState>(builder: (context, state) {
|
||||
@ -21,6 +26,7 @@ class BasicsView extends StatelessWidget {
|
||||
}
|
||||
return Form(
|
||||
key: _blocRole.formKey,
|
||||
autovalidateMode: AutovalidateMode.onUserInteraction,
|
||||
child: ListView(
|
||||
shrinkWrap: true,
|
||||
children: [
|
||||
@ -208,6 +214,14 @@ class BasicsView extends StatelessWidget {
|
||||
fontSize: 12,
|
||||
color: ColorsManager.textGray),
|
||||
),
|
||||
|
||||
onChanged: (value) {
|
||||
if (_debounce?.isActive ?? false) _debounce!.cancel();
|
||||
_debounce = Timer(const Duration(milliseconds: 800), () {
|
||||
_blocRole.add(const CheckEmailEvent());
|
||||
});
|
||||
},
|
||||
|
||||
validator: (value) {
|
||||
if (value == null || value.isEmpty) {
|
||||
return 'Enter Email Address';
|
||||
|
@ -32,113 +32,114 @@ class SpaceDropdown extends StatelessWidget {
|
||||
color: ColorsManager.blackColor,
|
||||
),
|
||||
),
|
||||
SizedBox(
|
||||
child: Container(
|
||||
DropdownButton2<String>(
|
||||
underline: const SizedBox(),
|
||||
buttonStyleData: ButtonStyleData(
|
||||
decoration:
|
||||
BoxDecoration(borderRadius: BorderRadius.circular(12)),
|
||||
),
|
||||
value: selectedValue,
|
||||
items: spaces.map((space) {
|
||||
return DropdownMenuItem<String>(
|
||||
value: space.uuid,
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Text(
|
||||
' ${space.name}',
|
||||
style: Theme.of(context).textTheme.bodyMedium!.copyWith(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: selectedValue == space.uuid
|
||||
? ColorsManager.dialogBlueTitle
|
||||
: ColorsManager.blackColor,
|
||||
),
|
||||
),
|
||||
Text(
|
||||
' ${space.lastThreeParents}',
|
||||
style: Theme.of(context).textTheme.bodyMedium!.copyWith(
|
||||
fontSize: 12,
|
||||
color: selectedValue == space.uuid
|
||||
? ColorsManager.dialogBlueTitle
|
||||
: ColorsManager.blackColor,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}).toList(),
|
||||
onChanged: onChanged,
|
||||
style: TextStyle(
|
||||
color: Colors.black,
|
||||
fontSize: 13,
|
||||
),
|
||||
hint: Padding(
|
||||
padding: const EdgeInsets.only(left: 10),
|
||||
child: Text(
|
||||
hintMessage,
|
||||
style: Theme.of(context).textTheme.bodySmall!.copyWith(
|
||||
color: ColorsManager.textGray,
|
||||
),
|
||||
),
|
||||
),
|
||||
customButton: Container(
|
||||
height: 40,
|
||||
decoration: BoxDecoration(
|
||||
border: Border.all(color: ColorsManager.textGray, width: 1.0),
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Expanded(
|
||||
flex: 8,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.only(left: 10),
|
||||
child: Text(
|
||||
selectedValue != null
|
||||
? spaces
|
||||
.firstWhere((e) => e.uuid == selectedValue)
|
||||
.name
|
||||
: hintMessage,
|
||||
style: Theme.of(context).textTheme.bodySmall!.copyWith(
|
||||
fontSize: 13,
|
||||
color: selectedValue != null
|
||||
? Colors.black
|
||||
: ColorsManager.textGray,
|
||||
),
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.grey[200],
|
||||
borderRadius: const BorderRadius.only(
|
||||
topRight: Radius.circular(10),
|
||||
bottomRight: Radius.circular(10),
|
||||
),
|
||||
),
|
||||
height: 45,
|
||||
child: const Icon(
|
||||
Icons.keyboard_arrow_down,
|
||||
color: ColorsManager.textGray,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
dropdownStyleData: DropdownStyleData(
|
||||
maxHeight: MediaQuery.of(context).size.height * 0.4,
|
||||
decoration: BoxDecoration(
|
||||
color: ColorsManager.whiteColors,
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
),
|
||||
child: DropdownButton2<String>(
|
||||
underline: const SizedBox(),
|
||||
value: selectedValue,
|
||||
items: spaces.map((space) {
|
||||
return DropdownMenuItem<String>(
|
||||
value: space.uuid,
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Text(
|
||||
' ${space.name}',
|
||||
style:
|
||||
Theme.of(context).textTheme.bodyMedium!.copyWith(
|
||||
fontSize: 12,
|
||||
color: ColorsManager.blackColor,
|
||||
),
|
||||
),
|
||||
Text(
|
||||
' ${space.lastThreeParents}',
|
||||
style:
|
||||
Theme.of(context).textTheme.bodyMedium!.copyWith(
|
||||
fontSize: 12,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}).toList(),
|
||||
onChanged: onChanged,
|
||||
style: TextStyle(color: Colors.black),
|
||||
hint: Padding(
|
||||
padding: const EdgeInsets.only(left: 10),
|
||||
child: Text(
|
||||
hintMessage,
|
||||
style: Theme.of(context).textTheme.bodySmall!.copyWith(
|
||||
color: ColorsManager.textGray,
|
||||
),
|
||||
),
|
||||
),
|
||||
customButton: Container(
|
||||
height: 45,
|
||||
decoration: BoxDecoration(
|
||||
border:
|
||||
Border.all(color: ColorsManager.textGray, width: 1.0),
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Expanded(
|
||||
flex: 8,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.only(left: 10),
|
||||
child: Text(
|
||||
selectedValue != null
|
||||
? spaces
|
||||
.firstWhere((e) => e.uuid == selectedValue)
|
||||
.name
|
||||
: hintMessage,
|
||||
style:
|
||||
Theme.of(context).textTheme.bodySmall!.copyWith(
|
||||
color: selectedValue != null
|
||||
? Colors.black
|
||||
: ColorsManager.textGray,
|
||||
),
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.grey[200],
|
||||
borderRadius: const BorderRadius.only(
|
||||
topRight: Radius.circular(10),
|
||||
bottomRight: Radius.circular(10),
|
||||
),
|
||||
),
|
||||
height: 45,
|
||||
child: const Icon(
|
||||
Icons.keyboard_arrow_down,
|
||||
color: ColorsManager.textGray,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
dropdownStyleData: DropdownStyleData(
|
||||
maxHeight: MediaQuery.of(context).size.height * 0.4,
|
||||
decoration: BoxDecoration(
|
||||
color: ColorsManager.whiteColors,
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
),
|
||||
),
|
||||
menuItemStyleData: const MenuItemStyleData(
|
||||
height: 60,
|
||||
),
|
||||
),
|
||||
),
|
||||
menuItemStyleData: const MenuItemStyleData(
|
||||
height: 60,
|
||||
),
|
||||
),
|
||||
],
|
||||
|
@ -121,7 +121,8 @@ class _RoutineViewCardState extends State<RoutineViewCard> {
|
||||
child: SizedBox(
|
||||
width: 16,
|
||||
height: 16,
|
||||
child: CircularProgressIndicator(strokeWidth: 2),
|
||||
child:
|
||||
CircularProgressIndicator(strokeWidth: 2),
|
||||
),
|
||||
),
|
||||
)
|
||||
@ -159,8 +160,9 @@ class _RoutineViewCardState extends State<RoutineViewCard> {
|
||||
height: iconSize,
|
||||
width: iconSize,
|
||||
fit: BoxFit.contain,
|
||||
errorBuilder: (context, error, stackTrace) =>
|
||||
Image.asset(
|
||||
errorBuilder:
|
||||
(context, error, stackTrace) =>
|
||||
Image.asset(
|
||||
Assets.logo,
|
||||
height: iconSize,
|
||||
width: iconSize,
|
||||
@ -203,7 +205,8 @@ class _RoutineViewCardState extends State<RoutineViewCard> {
|
||||
maxLines: 1,
|
||||
style: context.textTheme.bodySmall?.copyWith(
|
||||
color: ColorsManager.blackColor,
|
||||
fontSize: widget.isSmallScreenSize(context) ? 10 : 12,
|
||||
fontSize:
|
||||
widget.isSmallScreenSize(context) ? 10 : 12,
|
||||
),
|
||||
),
|
||||
if (widget.spaceName != '')
|
||||
@ -222,8 +225,9 @@ class _RoutineViewCardState extends State<RoutineViewCard> {
|
||||
maxLines: 1,
|
||||
style: context.textTheme.bodySmall?.copyWith(
|
||||
color: ColorsManager.blackColor,
|
||||
fontSize:
|
||||
widget.isSmallScreenSize(context) ? 10 : 12,
|
||||
fontSize: widget.isSmallScreenSize(context)
|
||||
? 10
|
||||
: 12,
|
||||
),
|
||||
),
|
||||
],
|
||||
|
@ -21,7 +21,6 @@ import 'package:syncrow_web/utils/snack_bar.dart';
|
||||
|
||||
class VisitorPasswordBloc
|
||||
extends Bloc<VisitorPasswordEvent, VisitorPasswordState> {
|
||||
|
||||
VisitorPasswordBloc() : super(VisitorPasswordInitial()) {
|
||||
on<SelectUsageFrequency>(selectUsageFrequency);
|
||||
on<FetchDevice>(_onFetchDevice);
|
||||
@ -87,6 +86,9 @@ class VisitorPasswordBloc
|
||||
SelectTimeVisitorPassword event,
|
||||
Emitter<VisitorPasswordState> emit,
|
||||
) async {
|
||||
// Ensure expirationTimeTimeStamp has a value
|
||||
effectiveTimeTimeStamp ??= DateTime.now().millisecondsSinceEpoch ~/ 1000;
|
||||
|
||||
final DateTime? picked = await showDatePicker(
|
||||
context: event.context,
|
||||
initialDate: DateTime.now(),
|
||||
@ -94,86 +96,124 @@ class VisitorPasswordBloc
|
||||
lastDate: DateTime.now().add(const Duration(days: 5095)),
|
||||
);
|
||||
|
||||
if (picked != null) {
|
||||
final TimeOfDay? timePicked = await showTimePicker(
|
||||
context: event.context,
|
||||
initialTime: TimeOfDay.now(),
|
||||
builder: (context, child) {
|
||||
return Theme(
|
||||
data: ThemeData.light().copyWith(
|
||||
colorScheme: const ColorScheme.light(
|
||||
primary: ColorsManager.primaryColor,
|
||||
onSurface: Colors.black,
|
||||
),
|
||||
buttonTheme: const ButtonThemeData(
|
||||
colorScheme: ColorScheme.light(
|
||||
primary: Colors.green,
|
||||
),
|
||||
),
|
||||
if (picked == null) return;
|
||||
|
||||
final TimeOfDay? timePicked = await showTimePicker(
|
||||
context: event.context,
|
||||
initialTime: TimeOfDay.now(),
|
||||
builder: (context, child) {
|
||||
return Theme(
|
||||
data: ThemeData.light().copyWith(
|
||||
colorScheme: const ColorScheme.light(
|
||||
primary: ColorsManager.primaryColor,
|
||||
onSurface: Colors.black,
|
||||
),
|
||||
child: child!,
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
if (timePicked != null) {
|
||||
final selectedDateTime = DateTime(
|
||||
picked.year,
|
||||
picked.month,
|
||||
picked.day,
|
||||
timePicked.hour,
|
||||
timePicked.minute,
|
||||
),
|
||||
child: child!,
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
final selectedTimestamp =
|
||||
selectedDateTime.millisecondsSinceEpoch ~/ 1000;
|
||||
if (timePicked == null) return;
|
||||
|
||||
if (event.isStart) {
|
||||
if (expirationTimeTimeStamp != null &&
|
||||
selectedTimestamp > expirationTimeTimeStamp!) {
|
||||
CustomSnackBar.displaySnackBar(
|
||||
final selectedDateTime = DateTime(
|
||||
picked.year,
|
||||
picked.month,
|
||||
picked.day,
|
||||
timePicked.hour,
|
||||
timePicked.minute,
|
||||
);
|
||||
final selectedTimestamp = selectedDateTime.millisecondsSinceEpoch ~/ 1000;
|
||||
final currentTimestamp = DateTime.now().millisecondsSinceEpoch ~/ 1000;
|
||||
|
||||
if (event.isStart) {
|
||||
// START TIME VALIDATION
|
||||
if (expirationTimeTimeStamp != null &&
|
||||
selectedTimestamp > expirationTimeTimeStamp!) {
|
||||
await showDialog<void>(
|
||||
context: event.context,
|
||||
builder: (context) => AlertDialog(
|
||||
title: const Text(
|
||||
'Effective Time cannot be later than Expiration Time.',
|
||||
);
|
||||
return;
|
||||
}
|
||||
if(selectedTimestamp < DateTime.now().millisecondsSinceEpoch ~/ 1000) {
|
||||
if(selectedTimestamp < DateTime.now().millisecondsSinceEpoch ~/ 1000) {
|
||||
await showDialog<void>(
|
||||
context: event.context,
|
||||
builder: (context) => AlertDialog(
|
||||
title: const Text('Effective Time cannot be earlier than current time.'),
|
||||
actionsAlignment: MainAxisAlignment.center,
|
||||
content:
|
||||
FilledButton(
|
||||
onPressed: () {
|
||||
Navigator.of(event.context).pop();
|
||||
add(SelectTimeVisitorPassword(context: event.context, isStart: true, isRepeat: false));
|
||||
},
|
||||
child: const Text('OK'),
|
||||
),
|
||||
|
||||
),
|
||||
);
|
||||
}
|
||||
return;
|
||||
}
|
||||
effectiveTimeTimeStamp = selectedTimestamp;
|
||||
startTimeAccess = selectedDateTime.toString().split('.').first;
|
||||
} else {
|
||||
if (effectiveTimeTimeStamp != null &&
|
||||
selectedTimestamp < effectiveTimeTimeStamp!) {
|
||||
CustomSnackBar.displaySnackBar(
|
||||
'Expiration Time cannot be earlier than Effective Time.',
|
||||
);
|
||||
return;
|
||||
}
|
||||
expirationTimeTimeStamp = selectedTimestamp;
|
||||
endTimeAccess = selectedDateTime.toString().split('.').first;
|
||||
}
|
||||
emit(ChangeTimeState());
|
||||
emit(VisitorPasswordInitial());
|
||||
),
|
||||
actionsAlignment: MainAxisAlignment.center,
|
||||
content: FilledButton(
|
||||
onPressed: () {
|
||||
Navigator.of(event.context).pop();
|
||||
add(SelectTimeVisitorPassword(
|
||||
context: event.context,
|
||||
isStart: true,
|
||||
isRepeat: false,
|
||||
));
|
||||
},
|
||||
child: const Text('OK'),
|
||||
),
|
||||
),
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
if (selectedTimestamp < currentTimestamp) {
|
||||
await showDialog<void>(
|
||||
context: event.context,
|
||||
builder: (context) => AlertDialog(
|
||||
title: const Text(
|
||||
'Effective Time cannot be earlier than current time.',
|
||||
),
|
||||
actionsAlignment: MainAxisAlignment.center,
|
||||
content: FilledButton(
|
||||
onPressed: () {
|
||||
Navigator.of(event.context).pop();
|
||||
add(SelectTimeVisitorPassword(
|
||||
context: event.context,
|
||||
isStart: true,
|
||||
isRepeat: false,
|
||||
));
|
||||
},
|
||||
child: const Text('OK'),
|
||||
),
|
||||
),
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
// Save effective time
|
||||
effectiveTimeTimeStamp = selectedTimestamp;
|
||||
startTimeAccess = selectedDateTime.toString().split('.').first;
|
||||
} else {
|
||||
|
||||
if (effectiveTimeTimeStamp != null &&
|
||||
selectedTimestamp < effectiveTimeTimeStamp!) {
|
||||
await showDialog<void>(
|
||||
context: event.context,
|
||||
builder: (context) => AlertDialog(
|
||||
title: const Text(
|
||||
'Expiration Time cannot be earlier than Effective Time.',
|
||||
),
|
||||
actionsAlignment: MainAxisAlignment.center,
|
||||
content: FilledButton(
|
||||
onPressed: () {
|
||||
Navigator.of(event.context).pop();
|
||||
add(SelectTimeVisitorPassword(
|
||||
context: event.context,
|
||||
isStart: false,
|
||||
isRepeat: false,
|
||||
));
|
||||
},
|
||||
child: const Text('OK'),
|
||||
),
|
||||
),
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
// Save expiration time
|
||||
expirationTimeTimeStamp = selectedTimestamp;
|
||||
endTimeAccess = selectedDateTime.toString().split('.').first;
|
||||
}
|
||||
|
||||
emit(ChangeTimeState());
|
||||
emit(VisitorPasswordInitial());
|
||||
}
|
||||
|
||||
bool toggleRepeat(
|
||||
@ -213,7 +253,7 @@ class VisitorPasswordBloc
|
||||
FetchDevice event, Emitter<VisitorPasswordState> emit) async {
|
||||
try {
|
||||
emit(DeviceLoaded());
|
||||
final projectUuid = await ProjectManager.getProjectUUID() ?? '';
|
||||
final projectUuid = await ProjectManager.getProjectUUID() ?? '';
|
||||
|
||||
data = await AccessMangApi().fetchDoorLockDeviceList(projectUuid);
|
||||
emit(TableLoaded(data));
|
||||
|
@ -1,4 +1,5 @@
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:syncrow_web/pages/access_management/manage_bookable_spaces/presentation/screens/manage_bookable_spaces_screen.dart';
|
||||
import 'package:syncrow_web/pages/access_management/view/access_management.dart';
|
||||
import 'package:syncrow_web/pages/analytics/modules/analytics/views/analytics_page.dart';
|
||||
import 'package:syncrow_web/pages/auth/view/login_page.dart';
|
||||
@ -28,6 +29,10 @@ class AppRoutes {
|
||||
path: RoutesConst.accessManagementPage,
|
||||
builder: (context, state) => const AccessManagementPage(),
|
||||
),
|
||||
GoRoute(
|
||||
path: RoutesConst.manageBookableSapcesPage,
|
||||
builder: (context, state) => const ManageBookableSpacesPage(),
|
||||
),
|
||||
GoRoute(
|
||||
path: RoutesConst.deviceManagementPage,
|
||||
builder: (context, state) => const DeviceManagementPage(),
|
||||
|
@ -64,6 +64,7 @@ abstract class ColorsManager {
|
||||
static const Color circleRolesBackground = Color(0xFFF8F8F8);
|
||||
static const Color activeGreen = Color(0xFF99FF93);
|
||||
static const Color activeGreenText = Color(0xFF008905);
|
||||
static const Color trueIconGreen = Color(0xFFBBEC6C);
|
||||
static const Color disabledPink = Color(0xFFFF9395);
|
||||
static const Color disabledRedText = Color(0xFF890002);
|
||||
static const Color invitedOrange = Color(0xFFFFE193);
|
||||
|
@ -46,7 +46,8 @@ abstract class ApiEndpoints {
|
||||
// Community Module
|
||||
static const String createCommunity = '/projects/{projectId}/communities';
|
||||
static const String getCommunityList = '/projects/{projectId}/communities';
|
||||
static const String getCommunityListv2 = '/projects/{projectId}/communities/v2';
|
||||
static const String getCommunityListv2 =
|
||||
'/projects/{projectId}/communities/v2';
|
||||
static const String getCommunityById =
|
||||
'/projects/{projectId}/communities/{communityId}';
|
||||
static const String updateCommunity =
|
||||
@ -138,4 +139,7 @@ abstract class ApiEndpoints {
|
||||
static const String assignDeviceToRoom =
|
||||
'/projects/{projectUuid}/communities/{communityUuid}/spaces/{spaceUuid}/subspaces/{subSpaceUuid}/devices/{deviceUuid}';
|
||||
static const String saveSchedule = '/schedule/{deviceUuid}';
|
||||
|
||||
////booking System
|
||||
static const String addBookableSpaces = '/bookable-spaces';
|
||||
}
|
||||
|
@ -14,9 +14,13 @@ class Assets {
|
||||
static const String rightLine = 'assets/images/right_line.png';
|
||||
static const String google = 'assets/images/google.svg';
|
||||
static const String facebook = 'assets/images/facebook.svg';
|
||||
static const String invisiblePassword = 'assets/images/Password_invisible.svg';
|
||||
static const String invisiblePassword =
|
||||
'assets/images/Password_invisible.svg';
|
||||
static const String visiblePassword = 'assets/images/password_visible.svg';
|
||||
static const String accessIcon = 'assets/images/access_icon.svg';
|
||||
static const String addButtonIcon = 'assets/icons/add_button_Icon.svg';
|
||||
static const String backButtonIcon = 'assets/icons/back_button_icon.svg';
|
||||
static const String emptyDataTable = 'assets/icons/no_data_table.svg';
|
||||
static const String spaseManagementIcon =
|
||||
'assets/images/spase_management_icon.svg';
|
||||
static const String devicesIcon = 'assets/images/devices_icon.svg';
|
||||
@ -33,7 +37,8 @@ class Assets {
|
||||
static const String emptyTable = 'assets/images/empty_table.svg';
|
||||
|
||||
// General assets
|
||||
static const String motionlessDetection = 'assets/icons/motionless_detection.svg';
|
||||
static const String motionlessDetection =
|
||||
'assets/icons/motionless_detection.svg';
|
||||
static const String acHeating = 'assets/icons/ac_heating.svg';
|
||||
static const String acPowerOff = 'assets/icons/ac_power_off.svg';
|
||||
static const String acFanMiddle = 'assets/icons/ac_fan_middle.svg';
|
||||
@ -70,19 +75,22 @@ class Assets {
|
||||
'assets/icons/automation_functions/temp_password_unlock.svg';
|
||||
static const String doorlockNormalOpen =
|
||||
'assets/icons/automation_functions/doorlock_normal_open.svg';
|
||||
static const String doorbell = 'assets/icons/automation_functions/doorbell.svg';
|
||||
static const String doorbell =
|
||||
'assets/icons/automation_functions/doorbell.svg';
|
||||
static const String remoteUnlockViaApp =
|
||||
'assets/icons/automation_functions/remote_unlock_via_app.svg';
|
||||
static const String doubleLock =
|
||||
'assets/icons/automation_functions/double_lock.svg';
|
||||
static const String selfTestResult =
|
||||
'assets/icons/automation_functions/self_test_result.svg';
|
||||
static const String lockAlarm = 'assets/icons/automation_functions/lock_alarm.svg';
|
||||
static const String lockAlarm =
|
||||
'assets/icons/automation_functions/lock_alarm.svg';
|
||||
static const String presenceState =
|
||||
'assets/icons/automation_functions/presence_state.svg';
|
||||
static const String currentTemp =
|
||||
'assets/icons/automation_functions/current_temp.svg';
|
||||
static const String presence = 'assets/icons/automation_functions/presence.svg';
|
||||
static const String presence =
|
||||
'assets/icons/automation_functions/presence.svg';
|
||||
static const String residualElectricity =
|
||||
'assets/icons/automation_functions/residual_electricity.svg';
|
||||
static const String hijackAlarm =
|
||||
@ -99,12 +107,15 @@ class Assets {
|
||||
|
||||
// Presence Sensor Assets
|
||||
static const String sensorMotionIcon = 'assets/icons/sensor_motion_ic.svg';
|
||||
static const String sensorPresenceIcon = 'assets/icons/sensor_presence_ic.svg';
|
||||
static const String sensorPresenceIcon =
|
||||
'assets/icons/sensor_presence_ic.svg';
|
||||
static const String sensorVacantIcon = 'assets/icons/sensor_vacant_ic.svg';
|
||||
static const String illuminanceRecordIcon =
|
||||
'assets/icons/illuminance_record_ic.svg';
|
||||
static const String presenceRecordIcon = 'assets/icons/presence_record_ic.svg';
|
||||
static const String helpDescriptionIcon = 'assets/icons/help_description_ic.svg';
|
||||
static const String presenceRecordIcon =
|
||||
'assets/icons/presence_record_ic.svg';
|
||||
static const String helpDescriptionIcon =
|
||||
'assets/icons/help_description_ic.svg';
|
||||
|
||||
static const String lightPulp = 'assets/icons/light_pulb.svg';
|
||||
static const String acDevice = 'assets/icons/ac_device.svg';
|
||||
@ -158,10 +169,12 @@ class Assets {
|
||||
static const String unit = 'assets/icons/unit_icon.svg';
|
||||
static const String villa = 'assets/icons/villa_icon.svg';
|
||||
static const String iconEdit = 'assets/icons/icon_edit_icon.svg';
|
||||
static const String textFieldSearch = 'assets/icons/textfield_search_icon.svg';
|
||||
static const String textFieldSearch =
|
||||
'assets/icons/textfield_search_icon.svg';
|
||||
static const String roundedAddIcon = 'assets/icons/rounded_add_icon.svg';
|
||||
static const String addIcon = 'assets/icons/add_icon.svg';
|
||||
static const String smartThermostatIcon = 'assets/icons/smart_thermostat_icon.svg';
|
||||
static const String smartThermostatIcon =
|
||||
'assets/icons/smart_thermostat_icon.svg';
|
||||
static const String smartLightIcon = 'assets/icons/smart_light_icon.svg';
|
||||
static const String presenceSensor = 'assets/icons/presence_sensor.svg';
|
||||
static const String Gang3SwitchIcon = 'assets/icons/3_Gang_switch_icon.svg';
|
||||
@ -195,6 +208,7 @@ class Assets {
|
||||
|
||||
//assets/icons/ac_lock.svg
|
||||
static const String acLock = 'assets/icons/ac_lock.svg';
|
||||
static const String clockIcon = 'assets/icons/clock_icon.svg';
|
||||
|
||||
//assets/icons/ac_schedule.svg
|
||||
static const String acSchedule = 'assets/icons/ac_schedule.svg';
|
||||
@ -209,7 +223,8 @@ class Assets {
|
||||
//assets/icons/water_leak_normal.svg
|
||||
static const String waterLeakNormal = 'assets/icons/water_leak_normal.svg';
|
||||
//assets/icons/water_leak_detected.svg
|
||||
static const String waterLeakDetected = 'assets/icons/water_leak_detected.svg';
|
||||
static const String waterLeakDetected =
|
||||
'assets/icons/water_leak_detected.svg';
|
||||
|
||||
//assets/icons/automation_records.svg
|
||||
static const String automationRecords = 'assets/icons/automation_records.svg';
|
||||
@ -280,13 +295,16 @@ class Assets {
|
||||
'assets/icons/functions_icons/sensitivity.svg';
|
||||
static const String assetsSensitivityOperationIcon =
|
||||
'assets/icons/functions_icons/sesitivity_operation_icon.svg';
|
||||
static const String assetsAcPower = 'assets/icons/functions_icons/ac_power.svg';
|
||||
static const String assetsAcPower =
|
||||
'assets/icons/functions_icons/ac_power.svg';
|
||||
static const String assetsAcPowerOFF =
|
||||
'assets/icons/functions_icons/ac_power_off.svg';
|
||||
static const String assetsChildLock =
|
||||
'assets/icons/functions_icons/child_lock.svg';
|
||||
static const String assetsFreezing = 'assets/icons/functions_icons/freezing.svg';
|
||||
static const String assetsFanSpeed = 'assets/icons/functions_icons/fan_speed.svg';
|
||||
static const String assetsFreezing =
|
||||
'assets/icons/functions_icons/freezing.svg';
|
||||
static const String assetsFanSpeed =
|
||||
'assets/icons/functions_icons/fan_speed.svg';
|
||||
static const String assetsAcCooling =
|
||||
'assets/icons/functions_icons/ac_cooling.svg';
|
||||
static const String assetsAcHeating =
|
||||
@ -295,7 +313,8 @@ class Assets {
|
||||
'assets/icons/functions_icons/celsius_degrees.svg';
|
||||
static const String assetsTempreture =
|
||||
'assets/icons/functions_icons/tempreture.svg';
|
||||
static const String assetsAcFanLow = 'assets/icons/functions_icons/ac_fan_low.svg';
|
||||
static const String assetsAcFanLow =
|
||||
'assets/icons/functions_icons/ac_fan_low.svg';
|
||||
static const String assetsAcFanMiddle =
|
||||
'assets/icons/functions_icons/ac_fan_middle.svg';
|
||||
static const String assetsAcFanHigh =
|
||||
@ -314,7 +333,8 @@ class Assets {
|
||||
'assets/icons/functions_icons/far_detection.svg';
|
||||
static const String assetsFarDetectionFunction =
|
||||
'assets/icons/functions_icons/far_detection_function.svg';
|
||||
static const String assetsIndicator = 'assets/icons/functions_icons/indicator.svg';
|
||||
static const String assetsIndicator =
|
||||
'assets/icons/functions_icons/indicator.svg';
|
||||
static const String assetsMotionDetection =
|
||||
'assets/icons/functions_icons/motion_detection.svg';
|
||||
static const String assetsMotionlessDetection =
|
||||
@ -327,7 +347,8 @@ class Assets {
|
||||
'assets/icons/functions_icons/master_state.svg';
|
||||
static const String assetsSwitchAlarmSound =
|
||||
'assets/icons/functions_icons/switch_alarm_sound.svg';
|
||||
static const String assetsResetOff = 'assets/icons/functions_icons/reset_off.svg';
|
||||
static const String assetsResetOff =
|
||||
'assets/icons/functions_icons/reset_off.svg';
|
||||
|
||||
// Assets for automation_functions
|
||||
static const String assetsCardUnlock =
|
||||
@ -371,13 +392,15 @@ class Assets {
|
||||
static const String activeUser = 'assets/icons/active_user.svg';
|
||||
static const String deActiveUser = 'assets/icons/deactive_user.svg';
|
||||
static const String invitedIcon = 'assets/icons/invited_icon.svg';
|
||||
static const String rectangleCheckBox = 'assets/icons/rectangle_check_box.png';
|
||||
static const String rectangleCheckBox =
|
||||
'assets/icons/rectangle_check_box.png';
|
||||
static const String CheckBoxChecked = 'assets/icons/box_checked.png';
|
||||
static const String emptyBox = 'assets/icons/empty_box.png';
|
||||
static const String completeProcessIcon =
|
||||
'assets/icons/compleate_process_icon.svg';
|
||||
static const String completedDoneIcon = 'assets/images/completed_done.svg';
|
||||
static const String currentProcessIcon = 'assets/icons/current_process_icon.svg';
|
||||
static const String currentProcessIcon =
|
||||
'assets/icons/current_process_icon.svg';
|
||||
static const String uncomplete_ProcessIcon =
|
||||
'assets/icons/uncompleate_process_icon.svg';
|
||||
static const String wrongProcessIcon = 'assets/icons/wrong_process_icon.svg';
|
||||
@ -398,9 +421,11 @@ class Assets {
|
||||
static const String successIcon = 'assets/icons/success_icon.svg';
|
||||
static const String spaceLocationIcon = 'assets/icons/spaseLocationIcon.svg';
|
||||
static const String scenesPlayIcon = 'assets/icons/scenesPlayIcon.png';
|
||||
static const String scenesPlayIconCheck = 'assets/icons/scenesPlayIconCheck.png';
|
||||
static const String scenesPlayIconCheck =
|
||||
'assets/icons/scenesPlayIconCheck.png';
|
||||
static const String presenceStateIcon = 'assets/icons/presence_state.svg';
|
||||
static const String currentDistanceIcon = 'assets/icons/current_distance_icon.svg';
|
||||
static const String currentDistanceIcon =
|
||||
'assets/icons/current_distance_icon.svg';
|
||||
|
||||
static const String farDetectionIcon = 'assets/icons/far_detection_icon.svg';
|
||||
static const String motionDetectionSensitivityIcon =
|
||||
@ -423,29 +448,44 @@ class Assets {
|
||||
static const String cpsMode4 = 'assets/icons/cps_mode4.svg';
|
||||
static const String closeToMotion = 'assets/icons/close_to_motion.svg';
|
||||
static const String farAwayMotion = 'assets/icons/far_away_motion.svg';
|
||||
static const String communicationFault = 'assets/icons/communication_fault.svg';
|
||||
static const String communicationFault =
|
||||
'assets/icons/communication_fault.svg';
|
||||
static const String radarFault = 'assets/icons/radar_fault.svg';
|
||||
static const String selfTestingSuccess = 'assets/icons/self_testing_success.svg';
|
||||
static const String selfTestingFailure = 'assets/icons/self_testing_failure.svg';
|
||||
static const String selfTestingTimeout = 'assets/icons/self_testing_timeout.svg';
|
||||
static const String selfTestingSuccess =
|
||||
'assets/icons/self_testing_success.svg';
|
||||
static const String selfTestingFailure =
|
||||
'assets/icons/self_testing_failure.svg';
|
||||
static const String selfTestingTimeout =
|
||||
'assets/icons/self_testing_timeout.svg';
|
||||
static const String movingSpeed = 'assets/icons/moving_speed.svg';
|
||||
static const String boundary = 'assets/icons/boundary.svg';
|
||||
static const String motionMeter = 'assets/icons/motion_meter.svg';
|
||||
static const String spatialStaticValue = 'assets/icons/spatial_static_value.svg';
|
||||
static const String spatialMotionValue = 'assets/icons/spatial_motion_value.svg';
|
||||
static const String spatialStaticValue =
|
||||
'assets/icons/spatial_static_value.svg';
|
||||
static const String spatialMotionValue =
|
||||
'assets/icons/spatial_motion_value.svg';
|
||||
static const String presenceJudgementThrshold =
|
||||
'assets/icons/presence_judgement_threshold.svg';
|
||||
static const String spaceType = 'assets/icons/space_type.svg';
|
||||
static const String sportsPara = 'assets/icons/sports_para.svg';
|
||||
static const String sensitivityFeature1 = 'assets/icons/sensitivity_feature_1.svg';
|
||||
static const String sensitivityFeature2 = 'assets/icons/sensitivity_feature_2.svg';
|
||||
static const String sensitivityFeature3 = 'assets/icons/sensitivity_feature_3.svg';
|
||||
static const String sensitivityFeature4 = 'assets/icons/sensitivity_feature_4.svg';
|
||||
static const String sensitivityFeature5 = 'assets/icons/sensitivity_feature_5.svg';
|
||||
static const String sensitivityFeature6 = 'assets/icons/sensitivity_feature_6.svg';
|
||||
static const String sensitivityFeature7 = 'assets/icons/sensitivity_feature_7.svg';
|
||||
static const String sensitivityFeature8 = 'assets/icons/sensitivity_feature_8.svg';
|
||||
static const String sensitivityFeature9 = 'assets/icons/sensitivity_feature_9.svg';
|
||||
static const String sensitivityFeature1 =
|
||||
'assets/icons/sensitivity_feature_1.svg';
|
||||
static const String sensitivityFeature2 =
|
||||
'assets/icons/sensitivity_feature_2.svg';
|
||||
static const String sensitivityFeature3 =
|
||||
'assets/icons/sensitivity_feature_3.svg';
|
||||
static const String sensitivityFeature4 =
|
||||
'assets/icons/sensitivity_feature_4.svg';
|
||||
static const String sensitivityFeature5 =
|
||||
'assets/icons/sensitivity_feature_5.svg';
|
||||
static const String sensitivityFeature6 =
|
||||
'assets/icons/sensitivity_feature_6.svg';
|
||||
static const String sensitivityFeature7 =
|
||||
'assets/icons/sensitivity_feature_7.svg';
|
||||
static const String sensitivityFeature8 =
|
||||
'assets/icons/sensitivity_feature_8.svg';
|
||||
static const String sensitivityFeature9 =
|
||||
'assets/icons/sensitivity_feature_9.svg';
|
||||
static const String deviceTagIcon = 'assets/icons/device_tag_ic.svg';
|
||||
static const String targetConfirmTimeIcon =
|
||||
'assets/icons/target_confirm_time_icon.svg';
|
||||
@ -453,10 +493,13 @@ class Assets {
|
||||
static const String indentLevelIcon = 'assets/icons/indent_level_icon.svg';
|
||||
static const String triggerLevelIcon = 'assets/icons/trigger_level_icon.svg';
|
||||
static const String blankCalendar = 'assets/icons/blank_calendar.svg';
|
||||
static const String refreshStatusIcon = 'assets/icons/refresh_status_icon.svg';
|
||||
static const String energyConsumedIcon = 'assets/icons/energy_consumed_icon.svg';
|
||||
static const String refreshStatusIcon =
|
||||
'assets/icons/refresh_status_icon.svg';
|
||||
static const String energyConsumedIcon =
|
||||
'assets/icons/energy_consumed_icon.svg';
|
||||
|
||||
static const String closeSettingsIcon = 'assets/icons/close_settings_icon.svg';
|
||||
static const String closeSettingsIcon =
|
||||
'assets/icons/close_settings_icon.svg';
|
||||
|
||||
static const String editNameIconSettings =
|
||||
'assets/icons/edit_name_icon_settings.svg';
|
||||
@ -476,4 +519,6 @@ class Assets {
|
||||
'assets/icons/empty_energy_management_per_device.svg';
|
||||
static const String emptyHeatmap = 'assets/icons/empty_heatmap.svg';
|
||||
static const String emptyRangeOfAqi = 'assets/icons/empty_range_of_aqi.svg';
|
||||
static const String homeIcon = 'assets/icons/home_icon.svg';
|
||||
static const String groupIcon = 'assets/icons/group_icon.svg';
|
||||
}
|
||||
|
@ -7,4 +7,5 @@ class RoutesConst {
|
||||
static const String spacesManagementPage = '/spaces_management-page';
|
||||
static const String rolesAndPermissions = '/roles_and_Permissions-page';
|
||||
static const String analytics = '/syncrow_analytics';
|
||||
static const String manageBookableSapcesPage = '/manage_bookable_spaces';
|
||||
}
|
||||
|
@ -1,6 +1,25 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class StringUtils {
|
||||
static String capitalizeFirstLetter(String text) {
|
||||
if (text.isEmpty) return text;
|
||||
return text[0].toUpperCase() + text.substring(1);
|
||||
}
|
||||
}
|
||||
|
||||
bool isEndTimeAfterStartTime(TimeOfDay start, TimeOfDay end) {
|
||||
final startMinutes = start.hour * 60 + start.minute;
|
||||
final endMinutes = end.hour * 60 + end.minute;
|
||||
|
||||
if (endMinutes <= startMinutes) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
String formatTimeOfDayTo24HourString(TimeOfDay time) {
|
||||
final hour = time.hour.toString().padLeft(2, '0');
|
||||
final minute = time.minute.toString().padLeft(2, '0');
|
||||
return '$hour:$minute';
|
||||
}
|
||||
|
@ -44,7 +44,7 @@ dependencies:
|
||||
flutter_secure_storage: ^9.2.2
|
||||
shared_preferences: ^2.3.0
|
||||
dropdown_button2: ^2.3.9
|
||||
data_table_2: ^2.5.15
|
||||
data_table_2: ^2.6.0
|
||||
go_router:
|
||||
intl: ^0.20.2
|
||||
dropdown_search: ^6.0.2
|
||||
|
Reference in New Issue
Block a user