Compare commits

..

5 Commits

140 changed files with 3500 additions and 5083 deletions

1
.gitignore vendored
View File

@ -30,7 +30,6 @@ migrate_working_dir/
.pub-cache/ .pub-cache/
.pub/ .pub/
/build/ /build/
pubspec.lock
# Symbolication related # Symbolication related
app.*.symbols app.*.symbols

View File

@ -10,7 +10,6 @@
analyzer: analyzer:
errors: errors:
constant_identifier_names: ignore constant_identifier_names: ignore
overridden_fields: ignore
include: package:flutter_lints/flutter.yaml include: package:flutter_lints/flutter.yaml
linter: linter:

View File

@ -1,12 +0,0 @@
<svg width="22" height="22" viewBox="0 0 22 22" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_7305_15779)">
<path d="M17.0872 11.5142C17.0872 13.2025 16.427 14.8021 15.2211 15.9954C14.0278 17.2014 12.4283 17.8615 10.7399 17.8615C9.05141 17.8615 7.45185 17.2014 6.25856 15.9954C5.05262 14.8021 4.39249 13.2025 4.39249 11.5142C4.39249 9.82574 5.05266 8.22618 6.25856 7.03289C7.45185 5.8269 9.05141 5.16681 10.7399 5.16681C11.8063 5.16681 12.8471 5.43337 13.7866 5.95388L11.2984 8.97523H21.0861L18.6486 0L16.2113 2.97053C14.5737 1.91691 12.6948 1.35835 10.7398 1.35835C8.02314 1.35835 5.47142 2.41197 3.55459 4.32888C1.63765 6.24578 0.583984 8.79747 0.583984 11.5142C0.583984 14.2309 1.63765 16.7825 3.55459 18.6994C5.47146 20.6163 8.0231 21.67 10.7398 21.67C13.4565 21.67 16.0082 20.6163 17.925 18.6994C19.8419 16.7825 20.8956 14.2309 20.8956 11.5142V10.8794H17.0872V11.5142Z" fill="#77DD00"/>
<path d="M17.0876 10.8799H20.8961V11.5146C20.8961 14.2313 19.8424 16.7829 17.9254 18.6998C16.0086 20.6168 13.4569 21.6704 10.7402 21.6704V17.862C12.4287 17.862 14.0282 17.2019 15.2215 15.9959C16.4275 14.8026 17.0876 13.203 17.0876 11.5147V10.8799H17.0876Z" fill="#66BB00"/>
<path d="M13.787 5.95388C12.8475 5.43333 11.8066 5.16681 10.7402 5.16681V1.35835C12.6952 1.35835 14.5741 1.91691 16.2117 2.97057L18.6491 0L21.0866 8.97523H11.2989L13.787 5.95388Z" fill="#66BB00"/>
</g>
<defs>
<clipPath id="clip0_7305_15779">
<rect width="21.67" height="21.67" fill="white"/>
</clipPath>
</defs>
</svg>

Before

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 40 KiB

View File

@ -17,8 +17,7 @@ class TagDialogTextfieldDropdown extends StatefulWidget {
}) : super(key: key); }) : super(key: key);
@override @override
_DialogTextfieldDropdownState createState() => _DialogTextfieldDropdownState createState() => _DialogTextfieldDropdownState();
_DialogTextfieldDropdownState();
} }
class _DialogTextfieldDropdownState extends State<TagDialogTextfieldDropdown> { class _DialogTextfieldDropdownState extends State<TagDialogTextfieldDropdown> {
@ -37,12 +36,6 @@ class _DialogTextfieldDropdownState extends State<TagDialogTextfieldDropdown> {
_focusNode.addListener(() { _focusNode.addListener(() {
if (!_focusNode.hasFocus) { if (!_focusNode.hasFocus) {
// Call onSelected when focus is lost
final selectedTag = _filteredItems.firstWhere(
(tag) => tag.tag == _controller.text,
orElse: () => Tag(tag: _controller.text),
);
widget.onSelected(selectedTag);
_closeDropdown(); _closeDropdown();
} }
}); });
@ -50,9 +43,7 @@ class _DialogTextfieldDropdownState extends State<TagDialogTextfieldDropdown> {
void _filterItems() { void _filterItems() {
setState(() { setState(() {
_filteredItems = widget.items; _filteredItems = widget.items.where((tag) => tag.product?.uuid == widget.product).toList();
// .where((tag) => tag.product?.uuid == widget.product)
// .toList();
}); });
} }
@ -121,9 +112,7 @@ class _DialogTextfieldDropdownState extends State<TagDialogTextfieldDropdown> {
style: Theme.of(context) style: Theme.of(context)
.textTheme .textTheme
.bodyMedium .bodyMedium
?.copyWith( ?.copyWith(color: ColorsManager.textPrimaryColor)),
color: ColorsManager
.textPrimaryColor)),
onTap: () { onTap: () {
_controller.text = tag.tag ?? ''; _controller.text = tag.tag ?? '';
widget.onSelected(tag); widget.onSelected(tag);
@ -167,15 +156,13 @@ class _DialogTextfieldDropdownState extends State<TagDialogTextfieldDropdown> {
controller: _controller, controller: _controller,
focusNode: _focusNode, focusNode: _focusNode,
onFieldSubmitted: (value) { onFieldSubmitted: (value) {
final selectedTag = _filteredItems.firstWhere( final selectedTag = _filteredItems.firstWhere((tag) => tag.tag == value,
(tag) => tag.tag == value,
orElse: () => Tag(tag: value)); orElse: () => Tag(tag: value));
widget.onSelected(selectedTag); widget.onSelected(selectedTag);
_closeDropdown(); _closeDropdown();
}, },
onTapOutside: (event) { onTapOutside: (event) {
widget.onSelected(_filteredItems.firstWhere( widget.onSelected(_filteredItems.firstWhere((tag) => tag.tag == _controller.text,
(tag) => tag.tag == _controller.text,
orElse: () => Tag(tag: _controller.text))); orElse: () => Tag(tag: _controller.text)));
_closeDropdown(); _closeDropdown();
}, },

View File

@ -1,16 +0,0 @@
import 'enums.dart';
class GenericException implements Exception {
final ExceptionType type;
final String errorMessage;
const GenericException(
{required this.type, this.errorMessage = "Unknown Error"});
@override
String toString() {
return errorMessage;
}
List<Object?> get props => [type, errorMessage];
}

View File

@ -1,36 +0,0 @@
import 'package:dio/dio.dart';
import '../../utils/constants/api_const.dart';
class DioInstance {
Dio? _dio;
Dio get dio => _dio ?? _instantiate();
String? baseUrl;
DioInstance({this.baseUrl});
Dio _instantiate() {
Dio dio = Dio(
BaseOptions(
baseUrl: baseUrl ?? '${ApiEndpoints.baseUrl}/',
receiveDataWhenStatusError: true,
headers: {
'content_Type': 'application/json',
},
),
);
dio.interceptors.add(
LogInterceptor(
responseHeader: false,
requestHeader: false,
requestBody: true,
responseBody: true,
),
);
return dio;
}
}

View File

@ -1,3 +0,0 @@
class EndPoints {
static const String fetchCommunities = 'projects/{projectUuid}/communities';
}

View File

@ -1,50 +0,0 @@
enum RequestType {
get,
post,
delete,
put,
}
enum ExceptionType {
notAuthenticated,
connectionError,
// related to http status code exceptions
notAuthorized,
notFound,
internalServerException,
serviceUnavailableException,
pageGone,
// related to bad request status code
// related to auth requests
invalidCredentials,
solutionAlreadySunmitted,
invalidValidation,
// other
other,
}
enum ExceptionMessage {
NOT_AUTHENTICATED,
INVALID_CREDENTIALS,
The_password_field_must_be_at_least_8_characters,
SOLUTION_ALREADY_SUBMITTED,
you_are_not_authorized,
page_not_found,
page_gone,
INTERNAL_SERVER_ERROR,
service_unavailable,
}
enum NotificationsType {
payment,
transporation,
product,
zero,
}
enum ImagesType {
assets,
svg,
network,
}

View File

@ -1,121 +0,0 @@
import 'dart:convert';
import 'package:dio/dio.dart';
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
import '../../pages/auth/model/token.dart';
import 'custom_exceptions.dart';
import 'dio.dart';
import 'enums.dart';
class Request {
String endPoint;
bool? autherized;
bool? isFormData;
RequestType? method;
Map<String, dynamic>? headers;
final Map<String, dynamic>? queryParams;
Map<String, dynamic>? body;
Duration? receiveTimeout;
Request(
this.endPoint, {
this.autherized,
this.isFormData,
this.method,
this.headers,
this.queryParams,
this.body,
this.receiveTimeout,
}) {
headers = {
'content_Type': 'application/json',
'Accept': 'application/json',
};
}
Future<Map<String, dynamic>> sendRequest() async {
Response? response;
if (autherized != null && autherized!) {
final storage = const FlutterSecureStorage();
final token = await storage.read(key: Token.loginAccessTokenKey);
if (token != null) {
headers!["authorization"] = "Bearer $token";
}
}
try {
response = await DioInstance().dio.request(
endPoint,
queryParameters: queryParams,
data: isFormData != null && isFormData == true
? FormData.fromMap(body!)
: body,
options: Options(
method: method!.name.toUpperCase(),
headers: headers,
contentType: 'application/json',
receiveTimeout: receiveTimeout,
),
);
if (response.statusCode! >= 200 && response.statusCode! < 300) {
return response.data;
}
} on DioException catch (error) {
if (error.type == DioExceptionType.badResponse) {
throw badRequestException[error.response!.data["error"]] ??
const GenericException(
type: ExceptionType.other,
);
}
if (error.type == DioExceptionType.connectionError ||
error.type == DioExceptionType.connectionTimeout ||
error.type == DioExceptionType.receiveTimeout ||
error.type == DioExceptionType.sendTimeout ||
error.type == DioExceptionType.unknown) {
throw const GenericException(
type: ExceptionType.connectionError,
errorMessage: 'no_internet_connection',
);
}
}
return {};
}
Map<String, dynamic> toJson() => {
'endPoint': endPoint,
'method': method!.name.toUpperCase(),
'body': json.encode(body),
'headers': headers,
'queryParams': queryParams,
'autherized': autherized,
'isFormData': isFormData,
};
@override
String toString() {
return jsonEncode(toJson());
}
List<Object?> get props => [
endPoint,
autherized,
isFormData,
method,
headers,
queryParams,
body,
receiveTimeout,
];
}
Map<String, GenericException> badRequestException = {
ExceptionMessage.INVALID_CREDENTIALS.name: const GenericException(
type: ExceptionType.invalidCredentials,
errorMessage: "Invalid credentials ...",
),
"The password field must be at least 8 characters.": const GenericException(
type: ExceptionType.invalidValidation,
errorMessage: 'password must be 8 or more characters',
),
ExceptionMessage.NOT_AUTHENTICATED.name: const GenericException(
type: ExceptionType.notAuthenticated,
errorMessage: "not authenticated",
),
};

View File

@ -1,71 +0,0 @@
class AnalyticsDevice {
const AnalyticsDevice({
required this.uuid,
required this.name,
this.createdAt,
this.updatedAt,
this.deviceTuyaUuid,
this.isActive,
this.productDevice,
this.spaceUuid,
});
final String uuid;
final String name;
final DateTime? createdAt;
final DateTime? updatedAt;
final String? deviceTuyaUuid;
final bool? isActive;
final ProductDevice? productDevice;
final String? spaceUuid;
factory AnalyticsDevice.fromJson(Map<String, dynamic> json) {
return AnalyticsDevice(
uuid: json['uuid'] as String,
name: json['name'] as String,
createdAt: json['createdAt'] != null ? DateTime.parse(json['createdAt'] as String) : null,
updatedAt: json['updatedAt'] != null ? DateTime.parse(json['updatedAt'] as String) : null,
deviceTuyaUuid: json['deviceTuyaUuid'] as String?,
isActive: json['isActive'] as bool?,
productDevice: json['productDevice'] != null
? ProductDevice.fromJson(json['productDevice'] as Map<String, dynamic>)
: null,
spaceUuid: (json['spaces'] as List<dynamic>?)
?.map((e) => e['uuid'])
.firstOrNull
?.toString(),
);
}
}
class ProductDevice {
const ProductDevice({
this.uuid,
this.createdAt,
this.updatedAt,
this.catName,
this.prodId,
this.name,
this.prodType,
});
final String? uuid;
final DateTime? createdAt;
final DateTime? updatedAt;
final String? catName;
final String? prodId;
final String? name;
final String? prodType;
factory ProductDevice.fromJson(Map<String, dynamic> json) {
return ProductDevice(
uuid: json['uuid'] as String?,
createdAt: json['createdAt'] != null ? DateTime.parse(json['createdAt'] as String) : null,
updatedAt: json['updatedAt'] != null ? DateTime.parse(json['updatedAt'] as String) : null,
catName: json['catName'] as String?,
prodId: json['prodId'] as String?,
name: json['name'] as String?,
prodType: json['prodType'] as String?,
);
}
}

View File

@ -1,32 +1,18 @@
import 'package:equatable/equatable.dart'; import 'package:equatable/equatable.dart';
class Occupacy extends Equatable { class Occupacy extends Equatable {
final DateTime date; final String date;
final String occupancy; final String occupancy;
final String spaceUuid;
final int occupiedSeconds;
const Occupacy({ const Occupacy({required this.date, required this.occupancy});
required this.date,
required this.occupancy,
required this.spaceUuid,
required this.occupiedSeconds,
});
factory Occupacy.fromJson(Map<String, dynamic> json) { factory Occupacy.fromJson(Map<String, dynamic> json) {
return Occupacy( return Occupacy(
date: DateTime.parse(json['event_date'] as String? ?? '${DateTime.now()}'), date: json['date'] as String,
occupancy: (json['occupancy_percentage'] ?? 0).toString(), occupancy: json['occupancy'] as String,
spaceUuid: json['space_uuid'] as String? ?? '',
occupiedSeconds: json['occupied_seconds'] as int? ?? 0,
); );
} }
@override @override
List<Object?> get props => [ List<Object?> get props => [date, occupancy];
date,
occupancy,
spaceUuid,
occupiedSeconds,
];
} }

View File

@ -1,28 +1,22 @@
import 'package:equatable/equatable.dart'; import 'package:equatable/equatable.dart';
class OccupancyHeatMapModel extends Equatable { class OccupancyHeatMapModel extends Equatable {
final String uuid; final DateTime date;
final DateTime eventDate; final int occupancy;
final int countTotalPresenceDetected;
const OccupancyHeatMapModel({ const OccupancyHeatMapModel({
required this.uuid, required this.date,
required this.eventDate, required this.occupancy,
required this.countTotalPresenceDetected,
}); });
factory OccupancyHeatMapModel.fromJson(Map<String, dynamic> json) { factory OccupancyHeatMapModel.fromJson(Map<String, dynamic> json) {
return OccupancyHeatMapModel( return OccupancyHeatMapModel(
uuid: json['uuid'] as String? ?? '', date: DateTime.parse(json['date'] as String),
eventDate: DateTime.parse( occupancy: json['occupancy'] as int,
json['event_date'] as String? ?? '${DateTime.now()}',
),
countTotalPresenceDetected: json['count_total_presence_detected'] as int? ?? 0,
); );
} }
@override @override
List<Object?> get props => [uuid, eventDate, countTotalPresenceDetected]; List<Object?> get props => [date, occupancy];
} }

View File

@ -1,66 +1,27 @@
import 'package:equatable/equatable.dart'; import 'package:equatable/equatable.dart';
class PhasesEnergyConsumption extends Equatable { class PhasesEnergyConsumption extends Equatable {
final String uuid; final int month;
final DateTime createdAt; final double phaseA;
final DateTime updatedAt; final double phaseB;
final String deviceUuid; final double phaseC;
final DateTime date;
final double energyConsumedKw;
final double energyConsumedA;
final double energyConsumedB;
final double energyConsumedC;
const PhasesEnergyConsumption({ const PhasesEnergyConsumption({
required this.uuid, required this.month,
required this.createdAt, required this.phaseA,
required this.updatedAt, required this.phaseB,
required this.deviceUuid, required this.phaseC,
required this.date,
required this.energyConsumedKw,
required this.energyConsumedA,
required this.energyConsumedB,
required this.energyConsumedC,
}); });
@override @override
List<Object?> get props => [ List<Object?> get props => [month, phaseA, phaseB, phaseC];
uuid,
createdAt,
updatedAt,
deviceUuid,
date,
energyConsumedKw,
energyConsumedA,
energyConsumedB,
energyConsumedC,
];
factory PhasesEnergyConsumption.fromJson(Map<String, dynamic> json) { factory PhasesEnergyConsumption.fromJson(Map<String, dynamic> json) {
return PhasesEnergyConsumption( return PhasesEnergyConsumption(
uuid: json['uuid'] as String, month: json['month'] as int,
createdAt: DateTime.parse(json['createdAt'] as String), phaseA: (json['phaseA'] as num).toDouble(),
updatedAt: DateTime.parse(json['updatedAt'] as String), phaseB: (json['phaseB'] as num).toDouble(),
deviceUuid: json['deviceUuid'] as String, phaseC: (json['phaseC'] as num).toDouble(),
date: DateTime.parse(json['date'] as String),
energyConsumedKw: double.parse(json['energyConsumedKw']),
energyConsumedA: double.parse(json['energyConsumedA']),
energyConsumedB: double.parse(json['energyConsumedB']),
energyConsumedC: double.parse(json['energyConsumedC']),
); );
} }
Map<String, dynamic> toJson() {
return {
'uuid': uuid,
'createdAt': createdAt.toIso8601String(),
'updatedAt': updatedAt.toIso8601String(),
'deviceUuid': deviceUuid,
'date': date.toIso8601String().split('T')[0],
'energyConsumedKw': energyConsumedKw.toString(),
'energyConsumedA': energyConsumedA.toString(),
'energyConsumedB': energyConsumedB.toString(),
'energyConsumedC': energyConsumedC.toString(),
};
}
} }

View File

@ -1,69 +0,0 @@
import 'package:bloc/bloc.dart';
import 'package:equatable/equatable.dart';
import 'package:syncrow_web/pages/analytics/models/analytics_device.dart';
import 'package:syncrow_web/pages/analytics/params/get_analytics_devices_param.dart';
import 'package:syncrow_web/pages/analytics/services/analytics_devices/analytics_devices_service.dart';
part 'analytics_devices_event.dart';
part 'analytics_devices_state.dart';
class AnalyticsDevicesBloc
extends Bloc<AnalyticsDevicesEvent, AnalyticsDevicesState> {
AnalyticsDevicesBloc(
this._analyticsDevicesService,
) : super(const AnalyticsDevicesState()) {
on<LoadAnalyticsDevicesEvent>(_onLoadAnalyticsDevices);
on<SelectAnalyticsDeviceEvent>(_onSelectAnalyticsDevice);
on<ClearAnalyticsDeviceEvent>(_onClearAnalyticsDevice);
}
final AnalyticsDevicesService _analyticsDevicesService;
Future<void> _onLoadAnalyticsDevices(
LoadAnalyticsDevicesEvent event,
Emitter<AnalyticsDevicesState> emit,
) async {
emit(const AnalyticsDevicesState(status: AnalyticsDevicesStatus.loading));
try {
final devices = await _analyticsDevicesService.getDevices(event.param);
emit(
AnalyticsDevicesState(
status: AnalyticsDevicesStatus.loaded,
devices: devices,
selectedDevice: devices.firstOrNull,
),
);
if (devices.isNotEmpty) {
event.onSuccess(devices.first);
}
} catch (e) {
emit(
AnalyticsDevicesState(
status: AnalyticsDevicesStatus.failure,
errorMessage: e.toString(),
),
);
}
}
void _onSelectAnalyticsDevice(
SelectAnalyticsDeviceEvent event,
Emitter<AnalyticsDevicesState> emit,
) {
emit(
AnalyticsDevicesState(
selectedDevice: event.device,
devices: state.devices,
errorMessage: state.errorMessage,
status: state.status,
),
);
}
void _onClearAnalyticsDevice(
ClearAnalyticsDeviceEvent event,
Emitter<AnalyticsDevicesState> emit,
) {
emit(const AnalyticsDevicesState());
}
}

View File

@ -1,31 +0,0 @@
part of 'analytics_devices_bloc.dart';
sealed class AnalyticsDevicesEvent extends Equatable {
const AnalyticsDevicesEvent();
@override
List<Object> get props => [];
}
final class LoadAnalyticsDevicesEvent extends AnalyticsDevicesEvent {
const LoadAnalyticsDevicesEvent({required this.param, required this.onSuccess});
final GetAnalyticsDevicesParam param;
final void Function(AnalyticsDevice device) onSuccess;
@override
List<Object> get props => [param];
}
final class SelectAnalyticsDeviceEvent extends AnalyticsDevicesEvent {
const SelectAnalyticsDeviceEvent(this.device);
final AnalyticsDevice device;
@override
List<Object> get props => [device];
}
final class ClearAnalyticsDeviceEvent extends AnalyticsDevicesEvent {
const ClearAnalyticsDeviceEvent();
}

View File

@ -1,20 +0,0 @@
part of 'analytics_devices_bloc.dart';
enum AnalyticsDevicesStatus { initial, loading, loaded, failure }
final class AnalyticsDevicesState extends Equatable {
const AnalyticsDevicesState({
this.status = AnalyticsDevicesStatus.initial,
this.devices = const [],
this.errorMessage,
this.selectedDevice,
});
final AnalyticsDevicesStatus status;
final List<AnalyticsDevice> devices;
final AnalyticsDevice? selectedDevice;
final String? errorMessage;
@override
List<Object?> get props => [status, devices, errorMessage, selectedDevice];
}

View File

@ -20,18 +20,7 @@ class EnergyManagementDataLoadingStrategy implements AnalyticsDataLoadingStrateg
spaces, spaces,
), ),
); );
FetchEnergyManagementDataHelper.loadEnergyManagementData(context);
final spaceTreeState = context.read<SpaceTreeBloc>().state;
if (spaceTreeState.selectedCommunities.contains(community.uuid)) {
clearData(context);
return;
}
FetchEnergyManagementDataHelper.loadEnergyManagementData(
context,
communityId: community.uuid,
spaceId: spaces.isNotEmpty ? spaces.first.uuid ?? '' : '',
);
} }
@override @override
@ -47,19 +36,7 @@ class EnergyManagementDataLoadingStrategy implements AnalyticsDataLoadingStrateg
space.children, space.children,
), ),
); );
FetchEnergyManagementDataHelper.loadEnergyManagementData(context);
final spaceTreeState = context.read<SpaceTreeBloc>().state;
if (spaceTreeState.selectedCommunities.contains(community.uuid) ||
spaceTreeState.selectedSpaces.contains(space.uuid)) {
clearData(context);
return;
}
FetchEnergyManagementDataHelper.loadEnergyManagementData(
context,
communityId: community.uuid,
spaceId: space.uuid ?? '',
);
} }
@override @override
@ -68,9 +45,14 @@ class EnergyManagementDataLoadingStrategy implements AnalyticsDataLoadingStrateg
CommunityModel community, CommunityModel community,
SpaceModel child, SpaceModel child,
) { ) {
if (child.children.isNotEmpty) { context.read<SpaceTreeBloc>().add(
return onSpaceSelected(context, community, child); OnSpaceSelected(
} community,
child.uuid ?? '',
child.children,
),
);
FetchEnergyManagementDataHelper.loadEnergyManagementData(context);
} }
@override @override

View File

@ -14,7 +14,13 @@ class OccupancyDataLoadingStrategy implements AnalyticsDataLoadingStrategy {
CommunityModel community, CommunityModel community,
List<SpaceModel> spaces, List<SpaceModel> spaces,
) { ) {
// Do Nothing context.read<SpaceTreeBloc>().add(
OnCommunitySelected(
community.uuid,
spaces,
),
);
FetchOccupancyDataHelper.loadOccupancyData(context);
} }
@override @override
@ -23,23 +29,14 @@ class OccupancyDataLoadingStrategy implements AnalyticsDataLoadingStrategy {
CommunityModel community, CommunityModel community,
SpaceModel space, SpaceModel space,
) { ) {
final spaceTreeBloc = context.read<SpaceTreeBloc>(); context.read<SpaceTreeBloc>().add(
final isSpaceSelected = spaceTreeBloc.state.selectedSpaces.contains(space.uuid); OnSpaceSelected(
community,
if (isSpaceSelected) { space.uuid ?? '',
clearData(context); space.children,
return; ),
} );
FetchOccupancyDataHelper.loadOccupancyData(context);
spaceTreeBloc
..add(const SpaceTreeClearSelectionEvent())
..add(OnSpaceSelected(community, space.uuid ?? '', []));
FetchOccupancyDataHelper.loadOccupancyData(
context,
communityId: community.uuid,
spaceId: space.uuid ?? '',
);
} }
@override @override
@ -48,12 +45,18 @@ class OccupancyDataLoadingStrategy implements AnalyticsDataLoadingStrategy {
CommunityModel community, CommunityModel community,
SpaceModel child, SpaceModel child,
) { ) {
onSpaceSelected(context, community, child); context.read<SpaceTreeBloc>().add(
OnSpaceSelected(
community,
child.uuid ?? '',
child.children,
),
);
FetchOccupancyDataHelper.loadOccupancyData(context);
} }
@override @override
void clearData(BuildContext context) { void clearData(BuildContext context) {
context.read<SpaceTreeBloc>().add(const SpaceTreeClearSelectionEvent()); context.read<SpaceTreeBloc>().add(const SpaceTreeClearSelectionEvent());
FetchOccupancyDataHelper.clearAllData(context);
} }
} }

View File

@ -1,7 +1,6 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:syncrow_web/pages/analytics/modules/analytics/blocs/analytics_date_picker_bloc/analytics_date_picker_bloc.dart'; import 'package:syncrow_web/pages/analytics/modules/analytics/blocs/analytics_date_picker_bloc/analytics_date_picker_bloc.dart';
import 'package:syncrow_web/pages/analytics/modules/analytics/blocs/analytics_devices/analytics_devices_bloc.dart';
import 'package:syncrow_web/pages/analytics/modules/analytics/blocs/analytics_tab/analytics_tab_bloc.dart'; import 'package:syncrow_web/pages/analytics/modules/analytics/blocs/analytics_tab/analytics_tab_bloc.dart';
import 'package:syncrow_web/pages/analytics/modules/analytics/widgets/analytics_communities_sidebar.dart'; import 'package:syncrow_web/pages/analytics/modules/analytics/widgets/analytics_communities_sidebar.dart';
import 'package:syncrow_web/pages/analytics/modules/analytics/widgets/analytics_page_tabs_and_children.dart'; import 'package:syncrow_web/pages/analytics/modules/analytics/widgets/analytics_page_tabs_and_children.dart';
@ -12,13 +11,10 @@ import 'package:syncrow_web/pages/analytics/modules/energy_management/blocs/real
import 'package:syncrow_web/pages/analytics/modules/energy_management/blocs/total_energy_consumption/total_energy_consumption_bloc.dart'; import 'package:syncrow_web/pages/analytics/modules/energy_management/blocs/total_energy_consumption/total_energy_consumption_bloc.dart';
import 'package:syncrow_web/pages/analytics/modules/occupancy/blocs/occupancy/occupancy_bloc.dart'; import 'package:syncrow_web/pages/analytics/modules/occupancy/blocs/occupancy/occupancy_bloc.dart';
import 'package:syncrow_web/pages/analytics/modules/occupancy/blocs/occupancy_heat_map/occupancy_heat_map_bloc.dart'; import 'package:syncrow_web/pages/analytics/modules/occupancy/blocs/occupancy_heat_map/occupancy_heat_map_bloc.dart';
import 'package:syncrow_web/pages/analytics/services/analytics_devices/analytics_devices_service_delagate.dart'; import 'package:syncrow_web/pages/analytics/services/energy_consumption_by_phases/fake_energy_consumption_by_phases_service.dart';
import 'package:syncrow_web/pages/analytics/services/analytics_devices/remote_energy_management_analytics_devices_service.dart'; import 'package:syncrow_web/pages/analytics/services/energy_consumption_per_device/fake_energy_consumption_per_device_service.dart';
import 'package:syncrow_web/pages/analytics/services/analytics_devices/remote_occupancy_analytics_devices_service.dart'; import 'package:syncrow_web/pages/analytics/services/occupacy/fake_occupacy_service.dart';
import 'package:syncrow_web/pages/analytics/services/energy_consumption_by_phases/remote_energy_consumption_by_phases_service.dart'; import 'package:syncrow_web/pages/analytics/services/occupancy_heat_map/fake_occupancy_heat_map_service.dart';
import 'package:syncrow_web/pages/analytics/services/energy_consumption_per_device/remote_energy_consumption_per_device_service.dart';
import 'package:syncrow_web/pages/analytics/services/occupacy/remote_occupancy_service.dart';
import 'package:syncrow_web/pages/analytics/services/occupancy_heat_map/remote_occupancy_heat_map_service.dart';
import 'package:syncrow_web/pages/analytics/services/power_clamp_info/remote_power_clamp_info_service.dart'; import 'package:syncrow_web/pages/analytics/services/power_clamp_info/remote_power_clamp_info_service.dart';
import 'package:syncrow_web/pages/analytics/services/realtime_device_service/firebase_realtime_device_service.dart'; import 'package:syncrow_web/pages/analytics/services/realtime_device_service/firebase_realtime_device_service.dart';
import 'package:syncrow_web/pages/analytics/services/total_energy_consumption/remote_total_energy_consumption_service.dart'; import 'package:syncrow_web/pages/analytics/services/total_energy_consumption/remote_total_energy_consumption_service.dart';
@ -27,22 +23,9 @@ import 'package:syncrow_web/services/api/http_service.dart';
import 'package:syncrow_web/utils/theme/responsive_text_theme.dart'; import 'package:syncrow_web/utils/theme/responsive_text_theme.dart';
import 'package:syncrow_web/web_layout/web_scaffold.dart'; import 'package:syncrow_web/web_layout/web_scaffold.dart';
class AnalyticsPage extends StatefulWidget { class AnalyticsPage extends StatelessWidget {
const AnalyticsPage({super.key}); const AnalyticsPage({super.key});
@override
State<AnalyticsPage> createState() => _AnalyticsPageState();
}
class _AnalyticsPageState extends State<AnalyticsPage> {
late final HTTPService _httpService;
@override
void initState() {
super.initState();
_httpService = HTTPService();
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return MultiBlocProvider( return MultiBlocProvider(
@ -52,22 +35,22 @@ class _AnalyticsPageState extends State<AnalyticsPage> {
), ),
BlocProvider( BlocProvider(
create: (context) => TotalEnergyConsumptionBloc( create: (context) => TotalEnergyConsumptionBloc(
RemoteTotalEnergyConsumptionService(_httpService), RemoteTotalEnergyConsumptionService(HTTPService()),
), ),
), ),
BlocProvider( BlocProvider(
create: (context) => EnergyConsumptionByPhasesBloc( create: (context) => EnergyConsumptionByPhasesBloc(
RemoteEnergyConsumptionByPhasesService(_httpService), FakeEnergyConsumptionByPhasesService(),
), ),
), ),
BlocProvider( BlocProvider(
create: (context) => EnergyConsumptionPerDeviceBloc( create: (context) => EnergyConsumptionPerDeviceBloc(
RemoteEnergyConsumptionPerDeviceService(_httpService), FakeEnergyConsumptionPerDeviceService(),
), ),
), ),
BlocProvider( BlocProvider(
create: (context) => PowerClampInfoBloc( create: (context) => PowerClampInfoBloc(
RemotePowerClampInfoService(_httpService), RemotePowerClampInfoService(HTTPService()),
), ),
), ),
BlocProvider<RealtimeDeviceChangesBloc>( BlocProvider<RealtimeDeviceChangesBloc>(
@ -75,25 +58,11 @@ class _AnalyticsPageState extends State<AnalyticsPage> {
FirebaseRealtimeDeviceService(), FirebaseRealtimeDeviceService(),
), ),
), ),
BlocProvider(create: (context) => OccupancyBloc(FakeOccupacyService())),
BlocProvider( BlocProvider(
create: (context) => OccupancyBloc( create: (context) => OccupancyHeatMapBloc(FakeOccupancyHeatMapService()),
RemoteOccupancyService(_httpService),
),
),
BlocProvider(
create: (context) => OccupancyHeatMapBloc(
RemoteOccupancyHeatMapService(_httpService),
),
), ),
BlocProvider(create: (context) => AnalyticsDatePickerBloc()), BlocProvider(create: (context) => AnalyticsDatePickerBloc()),
BlocProvider(
create: (context) => AnalyticsDevicesBloc(
AnalyticsDevicesServiceDelegate(
RemoteOccupancyAnalyticsDevicesService(_httpService),
RemoteEnergyManagementAnalyticsDevicesService(_httpService),
),
),
),
], ],
child: const AnalyticsPageForm(), child: const AnalyticsPageForm(),
); );

View File

@ -9,21 +9,29 @@ class AnalyticsCommunitiesSidebar extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final selectedTab = context.watch<AnalyticsTabBloc>().state; return Builder(
final strategy = AnalyticsDataLoadingStrategyFactory.getStrategy(selectedTab); builder: (context) {
final selectedTab = context.read<AnalyticsTabBloc>().state;
final strategy =
AnalyticsDataLoadingStrategyFactory.getStrategy(selectedTab);
return Expanded( // Clear data when tab changes
child: AnalyticsSpaceTreeView( strategy.clearData(context);
onSelectCommunity: (community, spaces) {
strategy.onCommunitySelected(context, community, spaces); return Expanded(
}, child: AnalyticsSpaceTreeView(
onSelectSpace: (community, space) { onSelectCommunity: (community, spaces) {
strategy.onSpaceSelected(context, community, space); strategy.onCommunitySelected(context, community, spaces);
}, },
onSelectChildSpace: (community, child) { onSelectSpace: (community, space) {
strategy.onChildSpaceSelected(context, community, child); strategy.onSpaceSelected(context, community, space);
}, },
), onSelectChildSpace: (community, child) {
strategy.onChildSpaceSelected(context, community, child);
},
),
);
},
); );
} }
} }

View File

@ -6,7 +6,6 @@ import 'package:syncrow_web/pages/analytics/modules/analytics/enums/analytics_pa
import 'package:syncrow_web/pages/analytics/modules/analytics/widgets/analytics_date_filter_button.dart'; import 'package:syncrow_web/pages/analytics/modules/analytics/widgets/analytics_date_filter_button.dart';
import 'package:syncrow_web/pages/analytics/modules/analytics/widgets/analytics_page_tab_button.dart'; import 'package:syncrow_web/pages/analytics/modules/analytics/widgets/analytics_page_tab_button.dart';
import 'package:syncrow_web/pages/analytics/modules/energy_management/helpers/fetch_energy_management_data_helper.dart'; import 'package:syncrow_web/pages/analytics/modules/energy_management/helpers/fetch_energy_management_data_helper.dart';
import 'package:syncrow_web/pages/space_tree/bloc/space_tree_bloc.dart';
import 'package:syncrow_web/utils/style.dart'; import 'package:syncrow_web/utils/style.dart';
class AnalyticsPageTabsAndChildren extends StatelessWidget { class AnalyticsPageTabsAndChildren extends StatelessWidget {
@ -54,40 +53,32 @@ class AnalyticsPageTabsAndChildren extends StatelessWidget {
), ),
), ),
const Spacer(), const Spacer(),
Visibility( _buildAnimation(
key: ValueKey(selectedTab), child: Visibility(
visible: selectedTab == AnalyticsPageTab.energyManagement, key: ValueKey(selectedTab),
child: Expanded( visible: selectedTab == AnalyticsPageTab.energyManagement,
flex: 2, child: Expanded(
child: FittedBox( flex: 2,
fit: BoxFit.scaleDown, child: FittedBox(
alignment: AlignmentDirectional.centerEnd, fit: BoxFit.scaleDown,
child: AnalyticsDateFilterButton( alignment: AlignmentDirectional.centerEnd,
onDateSelected: (DateTime value) { child: AnalyticsDateFilterButton(
context.read<AnalyticsDatePickerBloc>().add( onDateSelected: (DateTime value) {
UpdateAnalyticsDatePickerEvent(montlyDate: value), context.read<AnalyticsDatePickerBloc>().add(
); UpdateAnalyticsDatePickerEvent(
montlyDate: value),
final spaceTreeState = );
context.read<SpaceTreeBloc>().state;
if (spaceTreeState.selectedSpaces.isNotEmpty) {
FetchEnergyManagementDataHelper FetchEnergyManagementDataHelper
.loadEnergyManagementData( .fetchEnergyManagementData(
context, context,
shouldFetchAnalyticsDevices: false,
selectedDate: value, selectedDate: value,
communityId:
spaceTreeState.selectedCommunities.firstOrNull ??
'',
spaceId:
spaceTreeState.selectedSpaces.firstOrNull ?? '',
); );
} },
}, selectedDate: context
selectedDate: context .watch<AnalyticsDatePickerBloc>()
.watch<AnalyticsDatePickerBloc>() .state
.state .monthlyDate,
.monthlyDate, ),
), ),
), ),
), ),

View File

@ -45,7 +45,7 @@ class _MonthPickerWidgetState extends State<MonthPickerWidget> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Dialog( return Dialog(
backgroundColor: ColorsManager.whiteColors, backgroundColor: Theme.of(context).colorScheme.surface,
child: Container( child: Container(
padding: const EdgeInsetsDirectional.all(20), padding: const EdgeInsetsDirectional.all(20),
width: 320, width: 320,
@ -121,7 +121,6 @@ class _MonthPickerWidgetState extends State<MonthPickerWidget> {
} }
Row _buildYearSelector() { Row _buildYearSelector() {
final currentYear = DateTime.now().year;
return Row( return Row(
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
children: [ children: [
@ -135,35 +134,17 @@ class _MonthPickerWidgetState extends State<MonthPickerWidget> {
), ),
const Spacer(), const Spacer(),
IconButton( IconButton(
onPressed: () { onPressed: () => setState(() => _currentYear = _currentYear - 1),
setState(() {
_currentYear = _currentYear - 1;
});
},
icon: const Icon( icon: const Icon(
Icons.chevron_left, Icons.chevron_left,
color: ColorsManager.grey700, color: ColorsManager.grey700,
), ),
), ),
IconButton( IconButton(
onPressed: _currentYear < currentYear onPressed: () => setState(() => _currentYear = _currentYear + 1),
? () { icon: const Icon(
setState(() {
_currentYear = _currentYear + 1;
// Clear selected month if it becomes invalid in the new year
if (_currentYear == currentYear &&
_selectedMonth != null &&
_selectedMonth! > DateTime.now().month - 1) {
_selectedMonth = null;
}
});
}
: null,
icon: Icon(
Icons.chevron_right, Icons.chevron_right,
color: _currentYear < currentYear color: ColorsManager.grey700,
? ColorsManager.grey700
: ColorsManager.grey700.withValues(alpha: 0.3),
), ),
), ),
], ],
@ -171,13 +152,11 @@ class _MonthPickerWidgetState extends State<MonthPickerWidget> {
} }
Widget _buildMonthsGrid() { Widget _buildMonthsGrid() {
final currentDate = DateTime.now();
final isCurrentYear = _currentYear == currentDate.year;
return GridView.builder( return GridView.builder(
shrinkWrap: true, shrinkWrap: true,
itemCount: 12, itemCount: 12,
physics: const NeverScrollableScrollPhysics(), physics: const NeverScrollableScrollPhysics(),
padding: const EdgeInsets.all(8),
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount( gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 3, crossAxisCount: 3,
childAspectRatio: 2.5, childAspectRatio: 2.5,
@ -186,43 +165,25 @@ class _MonthPickerWidgetState extends State<MonthPickerWidget> {
), ),
itemBuilder: (context, index) { itemBuilder: (context, index) {
final isSelected = _selectedMonth == index; final isSelected = _selectedMonth == index;
final isFutureMonth = isCurrentYear && index > currentDate.month - 1;
return InkWell( return InkWell(
onTap: isFutureMonth ? null : () => setState(() => _selectedMonth = index), onTap: () => setState(() => _selectedMonth = index),
child: DecoratedBox( child: Container(
alignment: Alignment.center,
decoration: BoxDecoration( decoration: BoxDecoration(
color: const Color(0xFFEDF2F7), color: isSelected
borderRadius: BorderRadius.only( ? ColorsManager.vividBlue.withValues(alpha: 0.7)
topLeft: index % 3 == 0 ? const Radius.circular(16) : Radius.zero, : const Color(0xFFEDF2F7),
bottomLeft: index % 3 == 0 ? const Radius.circular(16) : Radius.zero, borderRadius:
topRight: index % 3 == 2 ? const Radius.circular(16) : Radius.zero, isSelected ? BorderRadius.circular(15) : BorderRadius.zero,
bottomRight:
index % 3 == 2 ? const Radius.circular(16) : Radius.zero,
),
), ),
child: Container( child: Text(
alignment: Alignment.center, _monthNames[index],
decoration: BoxDecoration( style: context.textTheme.titleSmall?.copyWith(
fontSize: 12,
color: isSelected color: isSelected
? ColorsManager.vividBlue.withValues(alpha: 0.7) ? ColorsManager.whiteColors
: isFutureMonth : ColorsManager.blackColor.withValues(alpha: 0.8),
? ColorsManager.grey700.withValues(alpha: 0.1) fontWeight: FontWeight.w500,
: const Color(0xFFEDF2F7),
borderRadius:
isSelected ? BorderRadius.circular(15) : BorderRadius.zero,
),
child: Text(
_monthNames[index],
style: context.textTheme.titleSmall?.copyWith(
fontSize: 12,
color: isSelected
? ColorsManager.whiteColors
: isFutureMonth
? ColorsManager.blackColor.withValues(alpha: 0.3)
: ColorsManager.blackColor.withValues(alpha: 0.8),
fontWeight: FontWeight.w500,
),
), ),
), ),
), ),

View File

@ -20,9 +20,9 @@ class _YearPickerWidgetState extends State<YearPickerWidget> {
late int _currentYear; late int _currentYear;
static final years = List.generate( static final years = List.generate(
DateTime.now().year - (DateTime.now().year - 5) + 1, DateTime.now().year - 2020 + 1,
(index) => (2020 + index), (index) => (2020 + index),
).where((year) => year <= DateTime.now().year).toList(); );
@override @override
void initState() { void initState() {
@ -33,7 +33,7 @@ class _YearPickerWidgetState extends State<YearPickerWidget> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Dialog( return Dialog(
backgroundColor: ColorsManager.whiteColors, backgroundColor: Theme.of(context).colorScheme.surface,
child: Container( child: Container(
padding: const EdgeInsetsDirectional.all(20), padding: const EdgeInsetsDirectional.all(20),
width: 320, width: 320,
@ -109,6 +109,7 @@ class _YearPickerWidgetState extends State<YearPickerWidget> {
shrinkWrap: true, shrinkWrap: true,
itemCount: years.length, itemCount: years.length,
physics: const NeverScrollableScrollPhysics(), physics: const NeverScrollableScrollPhysics(),
padding: const EdgeInsets.all(8),
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount( gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 3, crossAxisCount: 3,
childAspectRatio: 2.5, childAspectRatio: 2.5,
@ -119,35 +120,23 @@ class _YearPickerWidgetState extends State<YearPickerWidget> {
final isSelected = _currentYear == years[index]; final isSelected = _currentYear == years[index];
return InkWell( return InkWell(
onTap: () => setState(() => _currentYear = years[index]), onTap: () => setState(() => _currentYear = years[index]),
child: DecoratedBox( child: Container(
alignment: Alignment.center,
decoration: BoxDecoration( decoration: BoxDecoration(
color: const Color(0xFFEDF2F7), color: isSelected
borderRadius: BorderRadius.only( ? ColorsManager.vividBlue.withValues(alpha: 0.7)
topLeft: index % 3 == 0 ? const Radius.circular(16) : Radius.zero, : const Color(0xFFEDF2F7),
bottomLeft: index % 3 == 0 ? const Radius.circular(16) : Radius.zero, borderRadius:
topRight: index % 3 == 2 ? const Radius.circular(16) : Radius.zero, isSelected ? BorderRadius.circular(15) : BorderRadius.zero,
bottomRight:
index % 3 == 2 ? const Radius.circular(16) : Radius.zero,
),
), ),
child: Container( child: Text(
alignment: Alignment.center, years[index].toString(),
decoration: BoxDecoration( style: context.textTheme.titleSmall?.copyWith(
fontSize: 12,
color: isSelected color: isSelected
? ColorsManager.vividBlue.withValues(alpha: 0.7) ? ColorsManager.whiteColors
: const Color(0xFFEDF2F7), : ColorsManager.blackColor.withValues(alpha: 0.8),
borderRadius: fontWeight: FontWeight.w500,
isSelected ? BorderRadius.circular(15) : BorderRadius.zero,
),
child: Text(
years[index].toString(),
style: context.textTheme.titleSmall?.copyWith(
fontSize: 12,
color: isSelected
? ColorsManager.whiteColors
: ColorsManager.blackColor.withValues(alpha: 0.8),
fontWeight: FontWeight.w500,
),
), ),
), ),
), ),

View File

@ -0,0 +1,20 @@
import 'package:syncrow_web/pages/analytics/models/phases_energy_consumption.dart';
abstract final class EnergyConsumptionByPhasesChartHelper {
const EnergyConsumptionByPhasesChartHelper._();
static const fakeData = <PhasesEnergyConsumption>[
PhasesEnergyConsumption(month: 1, phaseA: 200, phaseB: 300, phaseC: 400),
PhasesEnergyConsumption(month: 2, phaseA: 300, phaseB: 400, phaseC: 500),
PhasesEnergyConsumption(month: 3, phaseA: 400, phaseB: 500, phaseC: 600),
PhasesEnergyConsumption(month: 4, phaseA: 100, phaseB: 100, phaseC: 100),
PhasesEnergyConsumption(month: 5, phaseA: 300, phaseB: 400, phaseC: 500),
PhasesEnergyConsumption(month: 6, phaseA: 300, phaseB: 100, phaseC: 400),
PhasesEnergyConsumption(month: 7, phaseA: 300, phaseB: 100, phaseC: 400),
PhasesEnergyConsumption(month: 8, phaseA: 500, phaseB: 100, phaseC: 100),
PhasesEnergyConsumption(month: 9, phaseA: 500, phaseB: 100, phaseC: 200),
PhasesEnergyConsumption(month: 10, phaseA: 100, phaseB: 50, phaseC: 50),
PhasesEnergyConsumption(month: 11, phaseA: 600, phaseB: 750, phaseC: 130),
PhasesEnergyConsumption(month: 12, phaseA: 100, phaseB: 80, phaseC: 100),
];
}

View File

@ -21,13 +21,12 @@ abstract final class EnergyManagementChartsHelper {
reservedSize: 32, reservedSize: 32,
showTitles: true, showTitles: true,
maxIncluded: true, maxIncluded: true,
minIncluded: true,
getTitlesWidget: (value, meta) => Padding( getTitlesWidget: (value, meta) => Padding(
padding: const EdgeInsetsDirectional.only(top: 20.0), padding: const EdgeInsetsDirectional.only(top: 20.0),
child: Text( child: Text(
value.toString(), (value + 1).toString(),
style: context.textTheme.bodySmall?.copyWith( style: context.textTheme.bodySmall?.copyWith(
color: ColorsManager.lightGreyColor, color: ColorsManager.greyColor,
fontSize: 12, fontSize: 12,
), ),
), ),
@ -37,8 +36,7 @@ abstract final class EnergyManagementChartsHelper {
leftTitles: AxisTitles( leftTitles: AxisTitles(
sideTitles: SideTitles( sideTitles: SideTitles(
showTitles: true, showTitles: true,
maxIncluded: false, maxIncluded: true,
minIncluded: true,
interval: leftTitlesInterval, interval: leftTitlesInterval,
reservedSize: 110, reservedSize: 110,
getTitlesWidget: (value, meta) => Padding( getTitlesWidget: (value, meta) => Padding(
@ -72,7 +70,7 @@ abstract final class EnergyManagementChartsHelper {
static List<LineTooltipItem?> getTooltipItems(List<LineBarSpot> touchedSpots) { static List<LineTooltipItem?> getTooltipItems(List<LineBarSpot> touchedSpots) {
return touchedSpots.map((spot) { return touchedSpots.map((spot) {
return LineTooltipItem( return LineTooltipItem(
getToolTipLabel(spot.x, spot.y), getToolTipLabel(spot.x + 1, spot.y),
const TextStyle( const TextStyle(
color: ColorsManager.textPrimaryColor, color: ColorsManager.textPrimaryColor,
fontWeight: FontWeight.w600, fontWeight: FontWeight.w600,
@ -93,38 +91,31 @@ abstract final class EnergyManagementChartsHelper {
); );
} }
static FlGridData gridData() { static FlBorderData borderData() {
return FlGridData( return FlBorderData(
show: true, show: true,
drawVerticalLine: false, border: const Border.symmetric(
drawHorizontalLine: true, horizontal: BorderSide(
horizontalInterval: 250,
getDrawingHorizontalLine: (value) {
return FlLine(
color: ColorsManager.greyColor, color: ColorsManager.greyColor,
strokeWidth: 1, style: BorderStyle.solid,
dashArray: value == 0 ? null : [5, 5], width: 1,
); ),
}, ),
); );
} }
static FlBorderData borderData() { static FlGridData gridData() {
return FlBorderData( return const FlGridData(
border: const Border(
bottom: BorderSide(
color: ColorsManager.greyColor,
style: BorderStyle.solid,
),
),
show: true, show: true,
drawVerticalLine: false,
drawHorizontalLine: true,
); );
} }
static LineTouchData lineTouchData() { static LineTouchData lineTouchData() {
return LineTouchData( return LineTouchData(
handleBuiltInTouches: true, handleBuiltInTouches: true,
touchSpotThreshold: 16, touchSpotThreshold: 2,
touchTooltipData: EnergyManagementChartsHelper.lineTouchTooltipData(), touchTooltipData: EnergyManagementChartsHelper.lineTouchTooltipData(),
); );
} }

View File

@ -1,80 +1,75 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:syncrow_web/pages/analytics/models/analytics_device.dart';
import 'package:syncrow_web/pages/analytics/modules/analytics/blocs/analytics_date_picker_bloc/analytics_date_picker_bloc.dart'; import 'package:syncrow_web/pages/analytics/modules/analytics/blocs/analytics_date_picker_bloc/analytics_date_picker_bloc.dart';
import 'package:syncrow_web/pages/analytics/modules/analytics/blocs/analytics_devices/analytics_devices_bloc.dart';
import 'package:syncrow_web/pages/analytics/modules/energy_management/blocs/energy_consumption_by_phases/energy_consumption_by_phases_bloc.dart'; import 'package:syncrow_web/pages/analytics/modules/energy_management/blocs/energy_consumption_by_phases/energy_consumption_by_phases_bloc.dart';
import 'package:syncrow_web/pages/analytics/modules/energy_management/blocs/energy_consumption_per_device/energy_consumption_per_device_bloc.dart'; import 'package:syncrow_web/pages/analytics/modules/energy_management/blocs/energy_consumption_per_device/energy_consumption_per_device_bloc.dart';
import 'package:syncrow_web/pages/analytics/modules/energy_management/blocs/power_clamp_info/power_clamp_info_bloc.dart'; import 'package:syncrow_web/pages/analytics/modules/energy_management/blocs/power_clamp_info/power_clamp_info_bloc.dart';
import 'package:syncrow_web/pages/analytics/modules/energy_management/blocs/realtime_device_changes/realtime_device_changes_bloc.dart'; import 'package:syncrow_web/pages/analytics/modules/energy_management/blocs/realtime_device_changes/realtime_device_changes_bloc.dart';
import 'package:syncrow_web/pages/analytics/modules/energy_management/blocs/total_energy_consumption/total_energy_consumption_bloc.dart'; import 'package:syncrow_web/pages/analytics/modules/energy_management/blocs/total_energy_consumption/total_energy_consumption_bloc.dart';
import 'package:syncrow_web/pages/analytics/params/get_analytics_devices_param.dart';
import 'package:syncrow_web/pages/analytics/params/get_energy_consumption_by_phases_param.dart'; import 'package:syncrow_web/pages/analytics/params/get_energy_consumption_by_phases_param.dart';
import 'package:syncrow_web/pages/analytics/params/get_energy_consumption_per_device_param.dart'; import 'package:syncrow_web/pages/analytics/params/get_energy_consumption_per_device_param.dart';
import 'package:syncrow_web/pages/analytics/params/get_total_energy_consumption_param.dart'; import 'package:syncrow_web/pages/analytics/params/get_total_energy_consumption_param.dart';
import 'package:syncrow_web/pages/space_tree/bloc/space_tree_bloc.dart';
abstract final class FetchEnergyManagementDataHelper { abstract final class FetchEnergyManagementDataHelper {
const FetchEnergyManagementDataHelper._(); const FetchEnergyManagementDataHelper._();
// static const String _powerClampId = 'cb71d6ad-6e29-4eaa-ae3e-1a0d1c5f60fa'; static void fetchEnergyManagementData(
static AnalyticsDevice? getSelectedDevice(BuildContext context) {
return context.read<AnalyticsDevicesBloc>().state.selectedDevice;
}
static void loadEnergyManagementData(
BuildContext context, { BuildContext context, {
required String communityId,
required String spaceId,
DateTime? selectedDate, DateTime? selectedDate,
bool shouldFetchAnalyticsDevices = true,
}) { }) {
if (communityId.isEmpty && spaceId.isEmpty) { final (selectedCommunities, selectedSpaces) =
getSelectedCommunitiesAndSpaces(context);
if (selectedCommunities.isEmpty && selectedSpaces.isEmpty) {
clearAllData(context); clearAllData(context);
return; return;
} }
final datePickerState = context.read<AnalyticsDatePickerBloc>().state; final datePickerState = context.read<AnalyticsDatePickerBloc>().state;
final selectedDate0 = selectedDate ?? datePickerState.monthlyDate;
if (shouldFetchAnalyticsDevices) { loadTotalEnergyConsumption(context, selectedDate: datePickerState.monthlyDate);
loadAnalyticsDevices( loadEnergyConsumptionByPhases(
context,
communityUuid: communityId,
spaceUuid: spaceId,
selectedDate: selectedDate0,
);
loadRealtimeDeviceChanges(context);
loadPowerClampInfo(context);
}
loadTotalEnergyConsumption(
context, context,
selectedDate: selectedDate0, selectedDate: datePickerState.monthlyDate,
communityId: communityId,
spaceId: spaceId,
); );
final selectedDevice = getSelectedDevice(context); loadEnergyConsumptionPerDevice(context);
if (selectedDevice case final AnalyticsDevice device) { return;
loadEnergyConsumptionByPhases( }
context,
powerClampUuid: device.uuid, static void loadEnergyManagementData(BuildContext context) {
selectedDate: selectedDate0, final (selectedCommunities, selectedSpaces) =
); FetchEnergyManagementDataHelper.getSelectedCommunitiesAndSpaces(context);
if (selectedCommunities.isEmpty && selectedSpaces.isEmpty) return;
FetchEnergyManagementDataHelper.fetchEnergyManagementData(context,
selectedDate: DateTime.now());
FetchEnergyManagementDataHelper.loadRealtimeDeviceChanges(context);
context.read<PowerClampInfoBloc>().add(const ClearPowerClampInfoEvent());
if (selectedCommunities.isEmpty && selectedSpaces.isEmpty) {
context.read<PowerClampInfoBloc>().add(
const ClearPowerClampInfoEvent(),
);
} else {
FetchEnergyManagementDataHelper.loadPowerClampInfo(context);
} }
loadEnergyConsumptionPerDevice( }
context,
communityId: communityId, static (List<String> selectedCommunities, List<String> selectedSpaces)
spaceId: spaceId, getSelectedCommunitiesAndSpaces(BuildContext context) {
selectedDate: selectedDate0, final spaceTreeState = context.read<SpaceTreeBloc>().state;
); final selectedCommunities = spaceTreeState.selectedCommunities;
final selectedSpaces = spaceTreeState.selectedSpaces;
return (selectedCommunities, selectedSpaces);
} }
static void loadEnergyConsumptionByPhases( static void loadEnergyConsumptionByPhases(
BuildContext context, { BuildContext context, {
required String powerClampUuid,
DateTime? selectedDate, DateTime? selectedDate,
}) { }) {
final param = GetEnergyConsumptionByPhasesParam( final param = GetEnergyConsumptionByPhasesParam(
date: selectedDate, startDate: selectedDate,
powerClampUuid: powerClampUuid, spaceId: '',
); );
context.read<EnergyConsumptionByPhasesBloc>().add( context.read<EnergyConsumptionByPhasesBloc>().add(
LoadEnergyConsumptionByPhasesEvent(param: param), LoadEnergyConsumptionByPhasesEvent(param: param),
@ -84,12 +79,13 @@ abstract final class FetchEnergyManagementDataHelper {
static void loadTotalEnergyConsumption( static void loadTotalEnergyConsumption(
BuildContext context, { BuildContext context, {
DateTime? selectedDate, DateTime? selectedDate,
required String communityId,
required String spaceId,
}) { }) {
final (selectedCommunities, selectedSpaces) =
getSelectedCommunitiesAndSpaces(context);
final param = GetTotalEnergyConsumptionParam( final param = GetTotalEnergyConsumptionParam(
spaceId: spaceId, spaceId: selectedCommunities.firstOrNull,
communityId: communityId, communityId: selectedCommunities.firstOrNull,
monthDate: selectedDate, monthDate: selectedDate,
); );
context.read<TotalEnergyConsumptionBloc>().add( context.read<TotalEnergyConsumptionBloc>().add(
@ -97,81 +93,33 @@ abstract final class FetchEnergyManagementDataHelper {
); );
} }
static void loadEnergyConsumptionPerDevice( static void loadEnergyConsumptionPerDevice(BuildContext context) {
BuildContext context, { const param = GetEnergyConsumptionPerDeviceParam();
DateTime? selectedDate,
required String communityId,
required String spaceId,
}) {
final param = GetEnergyConsumptionPerDeviceParam(
spaceId: spaceId,
communityId: communityId,
monthDate: selectedDate,
);
context.read<EnergyConsumptionPerDeviceBloc>().add( context.read<EnergyConsumptionPerDeviceBloc>().add(
LoadEnergyConsumptionPerDeviceEvent(param), const LoadEnergyConsumptionPerDeviceEvent(param),
); );
} }
static void loadPowerClampInfo(BuildContext context) { static void loadPowerClampInfo(BuildContext context) {
final selectedDevice = getSelectedDevice(context); context.read<PowerClampInfoBloc>().add(
if (selectedDevice case final AnalyticsDevice device) { const LoadPowerClampInfoEvent('cb71d6ad-6e29-4eaa-ae3e-1a0d1c5f60fa'),
context.read<PowerClampInfoBloc>().add(
LoadPowerClampInfoEvent(device.uuid),
);
}
}
static void loadRealtimeDeviceChanges(
BuildContext context, {
String? deviceUuid,
}) {
final selectedDevice = getSelectedDevice(context);
context.read<RealtimeDeviceChangesBloc>().add(
RealtimeDeviceChangesStarted(deviceUuid ?? selectedDevice?.uuid ?? ''),
); );
} }
static void loadAnalyticsDevices( static void loadRealtimeDeviceChanges(BuildContext context) {
BuildContext context, { context.read<RealtimeDeviceChangesBloc>().add(
required String communityUuid, const RealtimeDeviceChangesStarted('cb71d6ad-6e29-4eaa-ae3e-1a0d1c5f60fa'),
required String spaceUuid,
required DateTime selectedDate,
}) {
context.read<AnalyticsDevicesBloc>().add(
LoadAnalyticsDevicesEvent(
onSuccess: (device) {
context.read<PowerClampInfoBloc>().add(
LoadPowerClampInfoEvent(device.uuid),
);
loadEnergyConsumptionByPhases(
context,
powerClampUuid: device.uuid,
selectedDate: selectedDate,
);
context.read<RealtimeDeviceChangesBloc>().add(
RealtimeDeviceChangesStarted(device.uuid),
);
},
param: GetAnalyticsDevicesParam(
communityUuid: communityUuid,
spaceUuid: spaceUuid,
deviceTypes: ['PC'],
requestType: AnalyticsDeviceRequestType.energyManagement,
),
),
); );
} }
static void clearAllData(BuildContext context) { static void clearAllData(BuildContext context) {
context.read<PowerClampInfoBloc>().add(
const ClearPowerClampInfoEvent(),
);
context.read<RealtimeDeviceChangesBloc>().add( context.read<RealtimeDeviceChangesBloc>().add(
const RealtimeDeviceChangesClosed(), const RealtimeDeviceChangesClosed(),
); );
context.read<PowerClampInfoBloc>().add(
const ClearPowerClampInfoEvent(),
);
context.read<EnergyConsumptionPerDeviceBloc>().add( context.read<EnergyConsumptionPerDeviceBloc>().add(
const ClearEnergyConsumptionPerDeviceEvent(), const ClearEnergyConsumptionPerDeviceEvent(),
); );
@ -183,6 +131,5 @@ abstract final class FetchEnergyManagementDataHelper {
context.read<EnergyConsumptionByPhasesBloc>().add( context.read<EnergyConsumptionByPhasesBloc>().add(
const ClearEnergyConsumptionByPhasesEvent(), const ClearEnergyConsumptionByPhasesEvent(),
); );
context.read<AnalyticsDevicesBloc>().add(const ClearAnalyticsDeviceEvent());
} }
} }

View File

@ -1,10 +1,8 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:syncrow_web/pages/analytics/modules/energy_management/helpers/fetch_energy_management_data_helper.dart'; import 'package:syncrow_web/pages/analytics/modules/energy_management/helpers/fetch_energy_management_data_helper.dart';
import 'package:syncrow_web/pages/analytics/modules/energy_management/widgets/energy_consumption_per_device_chart_box.dart'; import 'package:syncrow_web/pages/analytics/modules/energy_management/widgets/energy_consumption_per_device_chart_box.dart';
import 'package:syncrow_web/pages/analytics/modules/energy_management/widgets/power_clamp_energy_data_widget.dart'; import 'package:syncrow_web/pages/analytics/modules/energy_management/widgets/power_clamp_energy_data_widget.dart';
import 'package:syncrow_web/pages/analytics/modules/energy_management/widgets/total_energy_consumption_chart_box.dart'; import 'package:syncrow_web/pages/analytics/modules/energy_management/widgets/total_energy_consumption_chart_box.dart';
import 'package:syncrow_web/pages/space_tree/bloc/space_tree_bloc.dart';
class AnalyticsEnergyManagementView extends StatefulWidget { class AnalyticsEnergyManagementView extends StatefulWidget {
const AnalyticsEnergyManagementView({super.key}); const AnalyticsEnergyManagementView({super.key});
@ -18,14 +16,7 @@ class _AnalyticsEnergyManagementViewState
extends State<AnalyticsEnergyManagementView> { extends State<AnalyticsEnergyManagementView> {
@override @override
void initState() { void initState() {
final spaceTreeBloc = context.read<SpaceTreeBloc>(); FetchEnergyManagementDataHelper.loadEnergyManagementData(context);
final communityId = spaceTreeBloc.state.selectedCommunities.firstOrNull;
final spaceId = spaceTreeBloc.state.selectedSpaces.firstOrNull;
FetchEnergyManagementDataHelper.loadEnergyManagementData(
context,
communityId: communityId ?? '',
spaceId: spaceId ?? '',
);
super.initState(); super.initState();
} }

View File

@ -1,108 +0,0 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:syncrow_web/pages/analytics/models/analytics_device.dart';
import 'package:syncrow_web/pages/analytics/modules/analytics/blocs/analytics_devices/analytics_devices_bloc.dart';
import 'package:syncrow_web/utils/color_manager.dart';
import 'package:syncrow_web/utils/extension/build_context_x.dart';
class AnalyticsDeviceDropdown extends StatelessWidget {
const AnalyticsDeviceDropdown({required this.onChanged, super.key});
final ValueChanged<AnalyticsDevice> onChanged;
@override
Widget build(BuildContext context) {
return BlocBuilder<AnalyticsDevicesBloc, AnalyticsDevicesState>(
builder: (context, state) {
return Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(16),
border: Border.all(
color: ColorsManager.greyColor,
width: 1,
),
),
child: Visibility(
visible: state.devices.isNotEmpty,
replacement: _buildNoDevicesFound(context),
child: _buildDevicesDropdown(context, state),
),
);
},
);
}
static const _defaultPadding = EdgeInsetsDirectional.symmetric(
horizontal: 20,
vertical: 2,
);
Widget _buildNoDevicesFound(BuildContext context) {
return Padding(
padding: _defaultPadding,
child: Text(
'no devices found',
style: _getTextStyle(context),
),
);
}
Widget _buildDevicesDropdown(BuildContext context, AnalyticsDevicesState state) {
final spaceUuid = state.selectedDevice?.spaceUuid;
return DropdownButton<AnalyticsDevice?>(
value: state.selectedDevice,
isDense: true,
borderRadius: BorderRadius.circular(16),
dropdownColor: ColorsManager.whiteColors,
underline: const SizedBox.shrink(),
icon: const RotatedBox(
quarterTurns: 1,
child: Icon(Icons.chevron_right, size: 16),
),
style: _getTextStyle(context),
padding: _defaultPadding,
selectedItemBuilder: (context) {
return state.devices.map((e) => Text(e.name)).toList();
},
items: state.devices.map((e) {
return DropdownMenuItem(
value: e,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: [
Text(e.name),
if (spaceUuid != null)
FittedBox(
fit: BoxFit.scaleDown,
alignment: AlignmentDirectional.centerStart,
child: Text(
spaceUuid,
style: _getTextStyle(context)?.copyWith(
fontSize: 10,
),
),
),
],
),
);
}).toList(),
onChanged: (value) {
if (value case final AnalyticsDevice device) {
context.read<AnalyticsDevicesBloc>().add(
SelectAnalyticsDeviceEvent(device),
);
onChanged.call(device);
}
},
);
}
TextStyle? _getTextStyle(BuildContext context) {
return context.textTheme.labelSmall?.copyWith(
color: ColorsManager.textPrimaryColor,
fontWeight: FontWeight.w700,
fontSize: 14,
);
}
}

View File

@ -1,6 +1,6 @@
import 'package:fl_chart/fl_chart.dart'; import 'package:fl_chart/fl_chart.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:intl/intl.dart'; import 'package:syncrow_web/pages/analytics/helpers/get_month_name_from_int.dart';
import 'package:syncrow_web/pages/analytics/models/phases_energy_consumption.dart'; import 'package:syncrow_web/pages/analytics/models/phases_energy_consumption.dart';
import 'package:syncrow_web/pages/analytics/modules/energy_management/helpers/energy_management_charts_helper.dart'; import 'package:syncrow_web/pages/analytics/modules/energy_management/helpers/energy_management_charts_helper.dart';
import 'package:syncrow_web/utils/color_manager.dart'; import 'package:syncrow_web/utils/color_manager.dart';
@ -18,10 +18,7 @@ class EnergyConsumptionByPhasesChart extends StatelessWidget {
Widget build(BuildContext context) { Widget build(BuildContext context) {
return BarChart( return BarChart(
BarChartData( BarChartData(
gridData: EnergyManagementChartsHelper.gridData().copyWith( gridData: EnergyManagementChartsHelper.gridData(),
checkToShowHorizontalLine: (value) => true,
horizontalInterval: 250,
),
borderData: EnergyManagementChartsHelper.borderData(), borderData: EnergyManagementChartsHelper.borderData(),
barTouchData: _barTouchData(context), barTouchData: _barTouchData(context),
titlesData: _titlesData(context), titlesData: _titlesData(context),
@ -34,29 +31,25 @@ class EnergyConsumptionByPhasesChart extends StatelessWidget {
barRods: [ barRods: [
BarChartRodData( BarChartRodData(
color: ColorsManager.vividBlue.withValues(alpha: 0.1), color: ColorsManager.vividBlue.withValues(alpha: 0.1),
toY: data.energyConsumedA + toY: data.phaseA + data.phaseB + data.phaseC,
data.energyConsumedB +
data.energyConsumedC,
rodStackItems: [ rodStackItems: [
BarChartRodStackItem( BarChartRodStackItem(
0, 0,
data.energyConsumedA, data.phaseA,
ColorsManager.vividBlue.withValues(alpha: 0.8), ColorsManager.vividBlue.withValues(alpha: 0.8),
), ),
BarChartRodStackItem( BarChartRodStackItem(
data.energyConsumedA, data.phaseA,
data.energyConsumedA + data.energyConsumedB, data.phaseA + data.phaseB,
ColorsManager.vividBlue.withValues(alpha: 0.4), ColorsManager.vividBlue.withValues(alpha: 0.4),
), ),
BarChartRodStackItem( BarChartRodStackItem(
data.energyConsumedA + data.energyConsumedB, data.phaseA + data.phaseB,
data.energyConsumedA + data.phaseA + data.phaseB + data.phaseC,
data.energyConsumedB +
data.energyConsumedC,
ColorsManager.vividBlue.withValues(alpha: 0.15), ColorsManager.vividBlue.withValues(alpha: 0.15),
), ),
], ],
width: 8, width: 16,
borderRadius: const BorderRadius.only( borderRadius: const BorderRadius.only(
topLeft: Radius.circular(8), topLeft: Radius.circular(8),
topRight: Radius.circular(8), topRight: Radius.circular(8),
@ -66,7 +59,6 @@ class EnergyConsumptionByPhasesChart extends StatelessWidget {
); );
}).toList(), }).toList(),
), ),
duration: Duration.zero,
); );
} }
@ -99,27 +91,18 @@ class EnergyConsumptionByPhasesChart extends StatelessWidget {
}) { }) {
final data = energyData; final data = energyData;
final date = DateFormat('dd/MM/yyyy').format(data[group.x.toInt()].date); final month = data[group.x.toInt()].month.getMonthName;
final phaseA = data[group.x.toInt()].energyConsumedA; final phaseA = data[group.x.toInt()].phaseA;
final phaseB = data[group.x.toInt()].energyConsumedB; final phaseB = data[group.x.toInt()].phaseB;
final phaseC = data[group.x.toInt()].energyConsumedC; final phaseC = data[group.x.toInt()].phaseC;
final total = data[group.x.toInt()].energyConsumedKw;
return BarTooltipItem( return BarTooltipItem(
'$date\n', '$month\n',
context.textTheme.bodyMedium!.copyWith( context.textTheme.bodyMedium!.copyWith(
color: ColorsManager.blackColor, color: ColorsManager.blackColor,
fontSize: 14, fontSize: 14,
), ),
textAlign: TextAlign.start,
children: [ children: [
TextSpan(
text: 'Total: $total\n',
style: context.textTheme.bodySmall?.copyWith(
color: ColorsManager.blackColor,
fontSize: 12,
),
),
TextSpan( TextSpan(
text: 'Phase A: $phaseA\n', text: 'Phase A: $phaseA\n',
style: context.textTheme.bodySmall?.copyWith( style: context.textTheme.bodySmall?.copyWith(
@ -161,9 +144,9 @@ class EnergyConsumptionByPhasesChart extends StatelessWidget {
sideTitles: SideTitles( sideTitles: SideTitles(
showTitles: true, showTitles: true,
getTitlesWidget: (value, _) { getTitlesWidget: (value, _) {
final month = DateFormat('d').format(energyData[value.toInt()].date); final month = energyData[value.toInt()].month.getMonthName;
return FittedBox( return FittedBox(
alignment: AlignmentDirectional.center, alignment: AlignmentDirectional.bottomCenter,
fit: BoxFit.scaleDown, fit: BoxFit.scaleDown,
child: RotatedBox( child: RotatedBox(
quarterTurns: 3, quarterTurns: 3,
@ -177,7 +160,7 @@ class EnergyConsumptionByPhasesChart extends StatelessWidget {
), ),
); );
}, },
reservedSize: 18, reservedSize: 36,
), ),
); );

View File

@ -16,11 +16,7 @@ class EnergyConsumptionPerDeviceChart extends StatelessWidget {
context, context,
leftTitlesInterval: 250, leftTitlesInterval: 250,
), ),
gridData: EnergyManagementChartsHelper.gridData(),
gridData: EnergyManagementChartsHelper.gridData().copyWith(
checkToShowHorizontalLine: (value) => true,
horizontalInterval: 250,
),
borderData: EnergyManagementChartsHelper.borderData(), borderData: EnergyManagementChartsHelper.borderData(),
lineTouchData: EnergyManagementChartsHelper.lineTouchData(), lineTouchData: EnergyManagementChartsHelper.lineTouchData(),
lineBarsData: chartData.map((e) { lineBarsData: chartData.map((e) {
@ -37,7 +33,7 @@ class EnergyConsumptionPerDeviceChart extends StatelessWidget {
); );
}).toList(), }).toList(),
), ),
duration: Duration.zero, duration: Durations.extralong1,
curve: Curves.easeIn, curve: Curves.easeIn,
); );
} }

View File

@ -1,6 +1,5 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:syncrow_web/pages/analytics/modules/analytics/blocs/analytics_devices/analytics_devices_bloc.dart';
import 'package:syncrow_web/pages/analytics/modules/energy_management/blocs/energy_consumption_per_device/energy_consumption_per_device_bloc.dart'; import 'package:syncrow_web/pages/analytics/modules/energy_management/blocs/energy_consumption_per_device/energy_consumption_per_device_bloc.dart';
import 'package:syncrow_web/pages/analytics/modules/energy_management/widgets/chart_title.dart'; import 'package:syncrow_web/pages/analytics/modules/energy_management/widgets/chart_title.dart';
import 'package:syncrow_web/pages/analytics/modules/energy_management/widgets/energy_consumption_per_device_chart.dart'; import 'package:syncrow_web/pages/analytics/modules/energy_management/widgets/energy_consumption_per_device_chart.dart';
@ -47,7 +46,6 @@ class EnergyConsumptionPerDeviceChartBox extends StatelessWidget {
flex: 2, flex: 2,
child: EnergyConsumptionPerDeviceDevicesList( child: EnergyConsumptionPerDeviceDevicesList(
chartData: state.chartData, chartData: state.chartData,
devices: context.watch<AnalyticsDevicesBloc>().state.devices,
), ),
), ),
], ],

View File

@ -1,16 +1,10 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:syncrow_web/pages/analytics/models/analytics_device.dart';
import 'package:syncrow_web/pages/analytics/models/device_energy_data_model.dart'; import 'package:syncrow_web/pages/analytics/models/device_energy_data_model.dart';
import 'package:syncrow_web/utils/color_manager.dart'; import 'package:syncrow_web/utils/color_manager.dart';
class EnergyConsumptionPerDeviceDevicesList extends StatelessWidget { class EnergyConsumptionPerDeviceDevicesList extends StatelessWidget {
const EnergyConsumptionPerDeviceDevicesList({ const EnergyConsumptionPerDeviceDevicesList({required this.chartData, super.key});
required this.chartData,
required this.devices,
super.key,
});
final List<AnalyticsDevice> devices;
final List<DeviceEnergyDataModel> chartData; final List<DeviceEnergyDataModel> chartData;
@override @override
@ -22,60 +16,45 @@ class EnergyConsumptionPerDeviceDevicesList extends StatelessWidget {
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.end, crossAxisAlignment: CrossAxisAlignment.end,
mainAxisAlignment: MainAxisAlignment.end, mainAxisAlignment: MainAxisAlignment.end,
children: devices.map((e) => _buildDeviceCell(context, e)).toList(), children: chartData.map((e) => _buildDeviceCell(context, e)).toList(),
), ),
); );
} }
Widget _buildDeviceCell(BuildContext context, AnalyticsDevice device) { Widget _buildDeviceCell(BuildContext context, DeviceEnergyDataModel device) {
final deviceColor = chartData return Container(
.firstWhere( height: MediaQuery.sizeOf(context).height * 0.0365,
(element) => element.deviceId == device.uuid, padding: const EdgeInsetsDirectional.symmetric(
orElse: () => const DeviceEnergyDataModel( vertical: 8,
energy: [], horizontal: 12,
deviceName: '', ),
deviceId: '', decoration: BoxDecoration(
color: Colors.red, borderRadius: BorderRadiusDirectional.circular(8),
), border: Border.all(
) color: ColorsManager.greyColor,
.color; width: 1,
return Tooltip(
message: '${device.name}\n${device.productDevice?.uuid ?? ''}',
child: Container(
height: MediaQuery.sizeOf(context).height * 0.0365,
padding: const EdgeInsetsDirectional.symmetric(
vertical: 8,
horizontal: 12,
), ),
decoration: BoxDecoration( ),
borderRadius: BorderRadiusDirectional.circular(8), child: FittedBox(
border: Border.all( fit: BoxFit.scaleDown,
color: ColorsManager.greyColor, alignment: Alignment.center,
width: 1, child: Row(
), spacing: 6,
), children: [
child: FittedBox( CircleAvatar(
fit: BoxFit.scaleDown, radius: 4,
alignment: Alignment.center, backgroundColor: device.color,
child: Row( ),
spacing: 6, Text(
children: [ device.deviceName,
CircleAvatar( textAlign: TextAlign.center,
radius: 4, style: const TextStyle(
backgroundColor: deviceColor, color: ColorsManager.blackColor,
fontWeight: FontWeight.w400,
fontSize: 14,
), ),
Text( ),
device.name, ],
textAlign: TextAlign.center,
style: const TextStyle(
color: ColorsManager.blackColor,
fontWeight: FontWeight.w400,
fontSize: 14,
),
),
],
),
), ),
), ),
); );

View File

@ -0,0 +1,55 @@
import 'package:flutter/material.dart';
import 'package:syncrow_web/utils/color_manager.dart';
import 'package:syncrow_web/utils/extension/build_context_x.dart';
class PowerClampEnergyDataDeviceDropdown extends StatelessWidget {
const PowerClampEnergyDataDeviceDropdown({super.key});
@override
Widget build(BuildContext context) {
return Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(16),
border: Border.all(
color: ColorsManager.greyColor,
width: 1,
),
),
child: DropdownButton<String>(
value: 'Device 1',
isDense: true,
borderRadius: BorderRadius.circular(16),
dropdownColor: ColorsManager.whiteColors,
underline: const SizedBox.shrink(),
icon: const RotatedBox(
quarterTurns: 1,
child: Icon(Icons.chevron_right, size: 16),
),
style: context.textTheme.labelSmall?.copyWith(
color: ColorsManager.textPrimaryColor,
fontWeight: FontWeight.w700,
fontSize: 14,
),
padding: const EdgeInsetsDirectional.symmetric(
horizontal: 20,
vertical: 2,
),
items: [
for (var i = 1; i < 10; i++)
DropdownMenuItem(
value: 'Device $i',
child: Text(
'Device $i',
style: context.textTheme.labelSmall?.copyWith(
color: ColorsManager.textPrimaryColor,
fontWeight: FontWeight.w700,
fontSize: 14,
),
),
),
],
onChanged: (value) {},
),
);
}
}

View File

@ -1,13 +1,10 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:syncrow_web/pages/analytics/models/power_clamp_energy_status.dart'; import 'package:syncrow_web/pages/analytics/models/power_clamp_energy_status.dart';
import 'package:syncrow_web/pages/analytics/modules/analytics/blocs/analytics_date_picker_bloc/analytics_date_picker_bloc.dart';
import 'package:syncrow_web/pages/analytics/modules/analytics/blocs/analytics_devices/analytics_devices_bloc.dart';
import 'package:syncrow_web/pages/analytics/modules/energy_management/blocs/power_clamp_info/power_clamp_info_bloc.dart'; import 'package:syncrow_web/pages/analytics/modules/energy_management/blocs/power_clamp_info/power_clamp_info_bloc.dart';
import 'package:syncrow_web/pages/analytics/modules/energy_management/blocs/realtime_device_changes/realtime_device_changes_bloc.dart'; import 'package:syncrow_web/pages/analytics/modules/energy_management/blocs/realtime_device_changes/realtime_device_changes_bloc.dart';
import 'package:syncrow_web/pages/analytics/modules/energy_management/helpers/fetch_energy_management_data_helper.dart';
import 'package:syncrow_web/pages/analytics/modules/energy_management/widgets/analytics_device_dropdown.dart';
import 'package:syncrow_web/pages/analytics/modules/energy_management/widgets/energy_consumption_by_phases_chart_box.dart'; import 'package:syncrow_web/pages/analytics/modules/energy_management/widgets/energy_consumption_by_phases_chart_box.dart';
import 'package:syncrow_web/pages/analytics/modules/energy_management/widgets/power_clamp_energy_data_device_dropdown.dart';
import 'package:syncrow_web/pages/analytics/modules/energy_management/widgets/power_clamp_energy_status_widget.dart'; import 'package:syncrow_web/pages/analytics/modules/energy_management/widgets/power_clamp_energy_status_widget.dart';
import 'package:syncrow_web/pages/analytics/modules/energy_management/widgets/power_clamp_phases_data_widget.dart'; import 'package:syncrow_web/pages/analytics/modules/energy_management/widgets/power_clamp_phases_data_widget.dart';
import 'package:syncrow_web/pages/analytics/widgets/analytics_error_widget.dart'; import 'package:syncrow_web/pages/analytics/widgets/analytics_error_widget.dart';
@ -53,8 +50,7 @@ class PowerClampEnergyDataWidget extends StatelessWidget {
), ),
const SizedBox(height: 6), const SizedBox(height: 6),
SelectableText( SelectableText(
context.watch<AnalyticsDevicesBloc>().state.selectedDevice?.uuid ?? state.powerClampModel?.productUuid ?? 'N/A',
'N/A',
style: context.textTheme.bodySmall?.copyWith( style: context.textTheme.bodySmall?.copyWith(
color: ColorsManager.blackColor, color: ColorsManager.blackColor,
fontWeight: FontWeight.w400, fontWeight: FontWeight.w400,
@ -111,7 +107,7 @@ class PowerClampEnergyDataWidget extends StatelessWidget {
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: [ children: [
Expanded( Expanded(
flex: 3, flex: 2,
child: FittedBox( child: FittedBox(
fit: BoxFit.scaleDown, fit: BoxFit.scaleDown,
alignment: AlignmentDirectional.centerStart, alignment: AlignmentDirectional.centerStart,
@ -126,25 +122,11 @@ class PowerClampEnergyDataWidget extends StatelessWidget {
), ),
), ),
const Spacer(), const Spacer(),
Expanded( const Expanded(
flex: 2,
child: FittedBox( child: FittedBox(
fit: BoxFit.scaleDown, fit: BoxFit.scaleDown,
alignment: AlignmentDirectional.centerEnd, alignment: AlignmentDirectional.centerEnd,
child: AnalyticsDeviceDropdown( child: PowerClampEnergyDataDeviceDropdown(),
onChanged: (value) {
FetchEnergyManagementDataHelper.loadEnergyConsumptionByPhases(
context,
powerClampUuid: value.uuid,
selectedDate:
context.read<AnalyticsDatePickerBloc>().state.monthlyDate,
);
FetchEnergyManagementDataHelper.loadRealtimeDeviceChanges(
context,
deviceUuid: value.uuid,
);
},
),
), ),
), ),
], ],

View File

@ -48,9 +48,6 @@ class PowerClampEnergyStatusWidget extends StatelessWidget {
fontWeight: FontWeight.w400, fontWeight: FontWeight.w400,
fontSize: 16, fontSize: 16,
), ),
softWrap: true,
maxLines: 2,
overflow: TextOverflow.ellipsis,
), ),
trailing: Text.rich( trailing: Text.rich(
TextSpan( TextSpan(

View File

@ -4,6 +4,15 @@ import 'package:syncrow_web/pages/analytics/models/energy_data_model.dart';
import 'package:syncrow_web/pages/analytics/modules/energy_management/helpers/energy_management_charts_helper.dart'; import 'package:syncrow_web/pages/analytics/modules/energy_management/helpers/energy_management_charts_helper.dart';
import 'package:syncrow_web/utils/color_manager.dart'; import 'package:syncrow_web/utils/color_manager.dart';
// energy_consumption_chart will return id, name and consumption
const phasesJson = {
"1": {
"phaseOne": 1000,
"phaseTwo": 2000,
"phaseThree": 3000,
}
};
class TotalEnergyConsumptionChart extends StatelessWidget { class TotalEnergyConsumptionChart extends StatelessWidget {
const TotalEnergyConsumptionChart({required this.chartData, super.key}); const TotalEnergyConsumptionChart({required this.chartData, super.key});
@ -14,19 +23,13 @@ class TotalEnergyConsumptionChart extends StatelessWidget {
return Expanded( return Expanded(
child: LineChart( child: LineChart(
LineChartData( LineChartData(
titlesData: EnergyManagementChartsHelper.titlesData( titlesData: EnergyManagementChartsHelper.titlesData(context),
context, gridData: EnergyManagementChartsHelper.gridData(),
leftTitlesInterval: 250,
),
gridData: EnergyManagementChartsHelper.gridData().copyWith(
checkToShowHorizontalLine: (value) => true,
horizontalInterval: 250,
),
borderData: EnergyManagementChartsHelper.borderData(), borderData: EnergyManagementChartsHelper.borderData(),
lineTouchData: EnergyManagementChartsHelper.lineTouchData(), lineTouchData: EnergyManagementChartsHelper.lineTouchData(),
lineBarsData: _lineBarsData, lineBarsData: _lineBarsData,
), ),
duration: Duration.zero, duration: Durations.extralong1,
curve: Curves.easeIn, curve: Curves.easeIn,
), ),
); );
@ -43,7 +46,7 @@ class TotalEnergyConsumptionChart extends StatelessWidget {
.entries .entries
.map( .map(
(entry) => FlSpot( (entry) => FlSpot(
entry.value.date.day.toDouble(), entry.key.toDouble(),
entry.value.value, entry.value.value,
), ),
) )

View File

@ -1,111 +1,57 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:syncrow_web/pages/analytics/models/analytics_device.dart';
import 'package:syncrow_web/pages/analytics/modules/analytics/blocs/analytics_date_picker_bloc/analytics_date_picker_bloc.dart'; import 'package:syncrow_web/pages/analytics/modules/analytics/blocs/analytics_date_picker_bloc/analytics_date_picker_bloc.dart';
import 'package:syncrow_web/pages/analytics/modules/analytics/blocs/analytics_devices/analytics_devices_bloc.dart';
import 'package:syncrow_web/pages/analytics/modules/energy_management/blocs/realtime_device_changes/realtime_device_changes_bloc.dart'; import 'package:syncrow_web/pages/analytics/modules/energy_management/blocs/realtime_device_changes/realtime_device_changes_bloc.dart';
import 'package:syncrow_web/pages/analytics/modules/energy_management/helpers/fetch_energy_management_data_helper.dart';
import 'package:syncrow_web/pages/analytics/modules/occupancy/blocs/occupancy/occupancy_bloc.dart'; import 'package:syncrow_web/pages/analytics/modules/occupancy/blocs/occupancy/occupancy_bloc.dart';
import 'package:syncrow_web/pages/analytics/modules/occupancy/blocs/occupancy_heat_map/occupancy_heat_map_bloc.dart'; import 'package:syncrow_web/pages/analytics/modules/occupancy/blocs/occupancy_heat_map/occupancy_heat_map_bloc.dart';
import 'package:syncrow_web/pages/analytics/params/get_analytics_devices_param.dart';
import 'package:syncrow_web/pages/analytics/params/get_occupancy_heat_map_param.dart'; import 'package:syncrow_web/pages/analytics/params/get_occupancy_heat_map_param.dart';
import 'package:syncrow_web/pages/analytics/params/get_occupancy_param.dart'; import 'package:syncrow_web/pages/analytics/params/get_occupancy_param.dart';
abstract final class FetchOccupancyDataHelper { abstract final class FetchOccupancyDataHelper {
const FetchOccupancyDataHelper._(); const FetchOccupancyDataHelper._();
static void loadOccupancyData( static void loadOccupancyData(BuildContext context) {
BuildContext context, { final (selectedCommunities, selectedSpaces) =
required String communityId, FetchEnergyManagementDataHelper.getSelectedCommunitiesAndSpaces(context);
required String spaceId, if (selectedCommunities.isEmpty && selectedSpaces.isEmpty) {
}) { context.read<OccupancyBloc>().add(
if (communityId.isEmpty && spaceId.isEmpty) { const ClearOccupancyEvent(),
clearAllData(context); );
context.read<OccupancyHeatMapBloc>().add(
const ClearOccupancyHeatMapEvent(),
);
return; return;
} }
final datePickerState = context.read<AnalyticsDatePickerBloc>().state; final datePickerState = context.read<AnalyticsDatePickerBloc>().state;
loadAnalyticsDevices(context, communityUuid: communityId, spaceUuid: spaceId);
final selectedDevice = context.read<AnalyticsDevicesBloc>().state.selectedDevice;
loadOccupancyChartData(
context,
spaceUuid: spaceId,
date: datePickerState.monthlyDate,
);
loadHeatMapData(context, spaceUuid: spaceId, year: datePickerState.yearlyDate);
if (selectedDevice case final AnalyticsDevice device) {
context.read<RealtimeDeviceChangesBloc>()
..add(const RealtimeDeviceChangesClosed())
..add(
RealtimeDeviceChangesStarted(device.uuid),
);
}
}
static void loadHeatMapData(
BuildContext context, {
required String spaceUuid,
required DateTime year,
}) {
context.read<OccupancyHeatMapBloc>().add(
LoadOccupancyHeatMapEvent(
GetOccupancyHeatMapParam(spaceUuid: spaceUuid, year: year),
),
);
}
static void loadOccupancyChartData(
BuildContext context, {
required String spaceUuid,
required DateTime date,
}) {
context.read<OccupancyBloc>().add( context.read<OccupancyBloc>().add(
LoadOccupancyEvent( LoadOccupancyEvent(
GetOccupancyParam( GetOccupancyParam(
monthDate: '${date.year}-${date.month.toString().padLeft(2, '0')}', monthDate:
spaceUuid: spaceUuid, '${datePickerState.monthlyDate.year}-${datePickerState.monthlyDate.month}',
spaceUuid: selectedSpaces.firstOrNull,
communityUuid: selectedCommunities.first,
), ),
), ),
); );
}
static void loadAnalyticsDevices(
BuildContext context, {
required String communityUuid,
required String spaceUuid,
}) {
context.read<AnalyticsDevicesBloc>().add(
LoadAnalyticsDevicesEvent(
param: GetAnalyticsDevicesParam(
communityUuid: communityUuid,
spaceUuid: spaceUuid,
deviceTypes: ['WPS', 'CPS'],
requestType: AnalyticsDeviceRequestType.occupancy,
),
onSuccess: (device) {
context.read<RealtimeDeviceChangesBloc>()
..add(const RealtimeDeviceChangesClosed())
..add(RealtimeDeviceChangesStarted(device.uuid));
},
),
);
}
static void clearAllData(BuildContext context) {
context.read<OccupancyBloc>().add(
const ClearOccupancyEvent(),
);
context.read<OccupancyHeatMapBloc>().add( context.read<OccupancyHeatMapBloc>().add(
const ClearOccupancyHeatMapEvent(), LoadOccupancyHeatMapEvent(
); GetOccupancyHeatMapParam(
context.read<RealtimeDeviceChangesBloc>().add( spaceId: selectedSpaces.isNotEmpty ? selectedSpaces.first : '',
const RealtimeDeviceChangesClosed(), communityId:
selectedCommunities.isNotEmpty ? selectedCommunities.first : '',
year: datePickerState.yearlyDate,
),
),
); );
context.read<AnalyticsDevicesBloc>().add( context.read<RealtimeDeviceChangesBloc>()
const ClearAnalyticsDeviceEvent(), ..add(const RealtimeDeviceChangesClosed())
); ..add(
const RealtimeDeviceChangesStarted('14fe6e7e-47af-4a07-ae0a-7c4a26ef8135'),
);
} }
} }

View File

@ -1,13 +1,25 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:syncrow_web/pages/analytics/modules/occupancy/helpers/fetch_occupancy_data_helper.dart';
import 'package:syncrow_web/pages/analytics/modules/occupancy/widgets/occupancy_chart_box.dart'; import 'package:syncrow_web/pages/analytics/modules/occupancy/widgets/occupancy_chart_box.dart';
import 'package:syncrow_web/pages/analytics/modules/occupancy/widgets/occupancy_end_side_bar.dart'; import 'package:syncrow_web/pages/analytics/modules/occupancy/widgets/occupancy_end_side_bar.dart';
import 'package:syncrow_web/pages/analytics/modules/occupancy/widgets/occupancy_heat_map_box.dart'; import 'package:syncrow_web/pages/analytics/modules/occupancy/widgets/occupancy_heat_map_box.dart';
class AnalyticsOccupancyView extends StatelessWidget { class AnalyticsOccupancyView extends StatefulWidget {
const AnalyticsOccupancyView({super.key}); const AnalyticsOccupancyView({super.key});
static const _padding = EdgeInsetsDirectional.all(32); static const _padding = EdgeInsetsDirectional.all(32);
@override
State<AnalyticsOccupancyView> createState() => _AnalyticsOccupancyViewState();
}
class _AnalyticsOccupancyViewState extends State<AnalyticsOccupancyView> {
@override
void initState() {
FetchOccupancyDataHelper.loadOccupancyData(context);
super.initState();
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final height = MediaQuery.sizeOf(context).height; final height = MediaQuery.sizeOf(context).height;
@ -16,13 +28,13 @@ class AnalyticsOccupancyView extends StatelessWidget {
final isMediumOrLess = constraints.maxWidth <= 900; final isMediumOrLess = constraints.maxWidth <= 900;
if (isMediumOrLess) { if (isMediumOrLess) {
return SingleChildScrollView( return SingleChildScrollView(
padding: _padding, padding: AnalyticsOccupancyView._padding,
child: Column( child: Column(
spacing: 32, spacing: 32,
children: [ children: [
SizedBox(height: height * 0.46, child: const OccupancyEndSideBar()), SizedBox(height: height * 0.45, child: const OccupancyEndSideBar()),
SizedBox(height: height * 0.5, child: const OccupancyChartBox()), SizedBox(height: height * 0.5, child: const OccupancyChartBox()),
SizedBox(height: height * 0.5, child: const OccupancyHeatMapBox()), SizedBox(height: height * 0.5, child: const Placeholder()),
], ],
), ),
); );
@ -30,7 +42,7 @@ class AnalyticsOccupancyView extends StatelessWidget {
return SingleChildScrollView( return SingleChildScrollView(
child: Container( child: Container(
padding: _padding, padding: AnalyticsOccupancyView._padding,
height: height * 0.9, height: height * 0.9,
child: const Row( child: const Row(
spacing: 32, spacing: 32,

View File

@ -1,54 +0,0 @@
import 'package:flutter/material.dart';
import 'package:intl/intl.dart';
import 'package:syncrow_web/utils/color_manager.dart';
import 'package:syncrow_web/utils/extension/build_context_x.dart';
class HeatMapTooltip extends StatelessWidget {
const HeatMapTooltip({
required this.date,
required this.value,
super.key,
});
final DateTime date;
final int value;
@override
Widget build(BuildContext context) {
return FittedBox(
fit: BoxFit.scaleDown,
alignment: AlignmentDirectional.topStart,
child: Container(
padding: const EdgeInsets.all(8),
decoration: BoxDecoration(
color: ColorsManager.grey700,
borderRadius: BorderRadius.circular(3),
),
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.start,
children: [
Text(
DateFormat('MMM d, yyyy').format(date),
style: context.textTheme.bodySmall?.copyWith(
fontSize: 12,
fontWeight: FontWeight.w700,
color: ColorsManager.whiteColors,
),
),
const Divider(height: 2, thickness: 1),
Text(
'$value Occupants',
style: context.textTheme.bodySmall?.copyWith(
fontSize: 10,
fontWeight: FontWeight.w500,
color: ColorsManager.whiteColors,
),
),
],
),
),
);
}
}

View File

@ -1,108 +0,0 @@
import 'package:flutter/material.dart';
import 'package:syncrow_web/pages/analytics/modules/occupancy/widgets/heat_map_tooltip.dart';
import 'package:syncrow_web/pages/analytics/modules/occupancy/widgets/occupancy_painter.dart';
class InteractiveHeatMap extends StatefulWidget {
const InteractiveHeatMap({
required this.items,
required this.maxValue,
required this.cellSize,
super.key,
});
final List<OccupancyPaintItem> items;
final int maxValue;
final double cellSize;
@override
State<InteractiveHeatMap> createState() => _InteractiveHeatMapState();
}
class _InteractiveHeatMapState extends State<InteractiveHeatMap> {
OccupancyPaintItem? _hoveredItem;
OverlayEntry? _overlayEntry;
final LayerLink _layerLink = LayerLink();
@override
void dispose() {
_removeOverlay();
_overlayEntry?.dispose();
super.dispose();
}
void _removeOverlay() {
_overlayEntry?.remove();
_overlayEntry = null;
}
void _showTooltip(OccupancyPaintItem item, Offset localPosition) {
_removeOverlay();
final column = item.index ~/ 7;
final row = item.index % 7;
final x = column * widget.cellSize;
final y = row * widget.cellSize;
_overlayEntry = OverlayEntry(
builder: (context) => Positioned(
child: CompositedTransformFollower(
link: _layerLink,
offset: Offset(x + widget.cellSize, y),
child: Material(
color: Colors.transparent,
child: Transform.translate(
offset: Offset(-(widget.cellSize * 2.5), -50),
child: HeatMapTooltip(date: item.date, value: item.value),
),
),
),
),
);
Overlay.of(context).insert(_overlayEntry!);
}
@override
Widget build(BuildContext context) {
return CompositedTransformTarget(
link: _layerLink,
child: MouseRegion(
onHover: (event) {
final column = event.localPosition.dx ~/ widget.cellSize;
final row = event.localPosition.dy ~/ widget.cellSize;
final index = column * 7 + row;
if (index >= 0 && index < widget.items.length) {
final item = widget.items[index];
if (_hoveredItem != item) {
setState(() => _hoveredItem = item);
_showTooltip(item, event.localPosition);
}
} else {
_removeOverlay();
setState(() => _hoveredItem = null);
}
},
onExit: (_) {
_removeOverlay();
setState(() => _hoveredItem = null);
},
child: CustomPaint(
isComplex: true,
size: _painterSize,
painter: OccupancyPainter(
items: widget.items,
maxValue: widget.maxValue,
hoveredItem: _hoveredItem,
),
),
),
);
}
Size get _painterSize {
final height = 7 * widget.cellSize;
final width = widget.items.length ~/ 7 * widget.cellSize;
return Size(width, height);
}
}

View File

@ -16,38 +16,30 @@ class OccupancyChart extends StatelessWidget {
Widget build(BuildContext context) { Widget build(BuildContext context) {
return BarChart( return BarChart(
BarChartData( BarChartData(
maxY: 100.0, maxY: 1.0,
gridData: EnergyManagementChartsHelper.gridData().copyWith( gridData: EnergyManagementChartsHelper.gridData().copyWith(
checkToShowHorizontalLine: (value) => true, checkToShowHorizontalLine: (value) => true,
horizontalInterval: 20, horizontalInterval: 0.25,
), ),
borderData: EnergyManagementChartsHelper.borderData(), borderData: EnergyManagementChartsHelper.borderData(),
barTouchData: _barTouchData(context), barTouchData: _barTouchData(context),
titlesData: _titlesData(context).copyWith( titlesData: _titlesData(context),
leftTitles: _titlesData(context).leftTitles.copyWith(
sideTitles: _titlesData(context).leftTitles.sideTitles.copyWith(
maxIncluded: true,
minIncluded: true,
),
),
),
barGroups: List.generate(chartData.length, (index) { barGroups: List.generate(chartData.length, (index) {
final actual = chartData[index]; final actual = chartData[index];
final occupancyValue = double.parse(actual.occupancy);
return BarChartGroupData( return BarChartGroupData(
x: index, x: index,
barsSpace: 0, barsSpace: 0,
groupVertically: true, groupVertically: true,
barRods: [ barRods: [
BarChartRodData( BarChartRodData(
toY: 100.0, toY: 1.0,
fromY: occupancyValue == 0 ? occupancyValue : occupancyValue + 2.5, fromY: double.parse(actual.occupancy) + 0.025,
color: ColorsManager.graysColor, color: ColorsManager.graysColor,
width: _chartWidth, width: _chartWidth,
borderRadius: BorderRadius.circular(10), borderRadius: BorderRadius.circular(10),
), ),
BarChartRodData( BarChartRodData(
toY: occupancyValue, toY: double.parse(actual.occupancy),
color: ColorsManager.vividBlue.withValues(alpha: 0.8), color: ColorsManager.vividBlue.withValues(alpha: 0.8),
width: _chartWidth, width: _chartWidth,
borderRadius: BorderRadius.circular(10), borderRadius: BorderRadius.circular(10),
@ -89,7 +81,7 @@ class OccupancyChart extends StatelessWidget {
final data = chartData; final data = chartData;
final occupancyValue = double.parse(data[group.x.toInt()].occupancy); final occupancyValue = double.parse(data[group.x.toInt()].occupancy);
final percentage = '${(occupancyValue).toStringAsFixed(0)}%'; final percentage = '${(occupancyValue * 100).toStringAsFixed(0)}%';
return BarTooltipItem( return BarTooltipItem(
percentage, percentage,
@ -109,14 +101,14 @@ class OccupancyChart extends StatelessWidget {
final leftTitles = titlesData.leftTitles.copyWith( final leftTitles = titlesData.leftTitles.copyWith(
sideTitles: titlesData.leftTitles.sideTitles.copyWith( sideTitles: titlesData.leftTitles.sideTitles.copyWith(
reservedSize: 70, reservedSize: 70,
interval: 20, interval: 0.25,
getTitlesWidget: (value, meta) => Padding( getTitlesWidget: (value, meta) => Padding(
padding: const EdgeInsetsDirectional.only(end: 12), padding: const EdgeInsetsDirectional.only(end: 12),
child: FittedBox( child: FittedBox(
alignment: AlignmentDirectional.centerStart, alignment: AlignmentDirectional.centerStart,
fit: BoxFit.scaleDown, fit: BoxFit.scaleDown,
child: Text( child: Text(
'${(value).toStringAsFixed(0)}%', '${(value * 100).toStringAsFixed(0)}%',
style: context.textTheme.bodySmall?.copyWith( style: context.textTheme.bodySmall?.copyWith(
fontSize: 12, fontSize: 12,
color: ColorsManager.greyColor, color: ColorsManager.greyColor,

View File

@ -7,7 +7,6 @@ import 'package:syncrow_web/pages/analytics/modules/occupancy/blocs/occupancy/oc
import 'package:syncrow_web/pages/analytics/modules/occupancy/helpers/fetch_occupancy_data_helper.dart'; import 'package:syncrow_web/pages/analytics/modules/occupancy/helpers/fetch_occupancy_data_helper.dart';
import 'package:syncrow_web/pages/analytics/modules/occupancy/widgets/occupancy_chart.dart'; import 'package:syncrow_web/pages/analytics/modules/occupancy/widgets/occupancy_chart.dart';
import 'package:syncrow_web/pages/analytics/widgets/analytics_error_widget.dart'; import 'package:syncrow_web/pages/analytics/widgets/analytics_error_widget.dart';
import 'package:syncrow_web/pages/space_tree/bloc/space_tree_bloc.dart';
import 'package:syncrow_web/utils/style.dart'; import 'package:syncrow_web/utils/style.dart';
class OccupancyChartBox extends StatelessWidget { class OccupancyChartBox extends StatelessWidget {
@ -15,7 +14,6 @@ class OccupancyChartBox extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final spaceTreeState = context.watch<SpaceTreeBloc>().state;
return BlocBuilder<OccupancyBloc, OccupancyState>( return BlocBuilder<OccupancyBloc, OccupancyState>(
builder: (context, state) { builder: (context, state) {
return Container( return Container(
@ -47,14 +45,7 @@ class OccupancyChartBox extends StatelessWidget {
context.read<AnalyticsDatePickerBloc>().add( context.read<AnalyticsDatePickerBloc>().add(
UpdateAnalyticsDatePickerEvent(montlyDate: value), UpdateAnalyticsDatePickerEvent(montlyDate: value),
); );
if (spaceTreeState.selectedSpaces.isNotEmpty) { FetchOccupancyDataHelper.loadOccupancyData(context);
FetchOccupancyDataHelper.loadOccupancyChartData(
context,
spaceUuid:
spaceTreeState.selectedSpaces.firstOrNull ?? '',
date: value,
);
}
}, },
selectedDate: context selectedDate: context
.watch<AnalyticsDatePickerBloc>() .watch<AnalyticsDatePickerBloc>()

View File

@ -1,16 +1,15 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:syncrow_web/pages/analytics/models/power_clamp_energy_status.dart'; import 'package:syncrow_web/pages/analytics/models/power_clamp_energy_status.dart';
import 'package:syncrow_web/pages/analytics/modules/analytics/blocs/analytics_devices/analytics_devices_bloc.dart';
import 'package:syncrow_web/pages/analytics/modules/energy_management/blocs/realtime_device_changes/realtime_device_changes_bloc.dart'; import 'package:syncrow_web/pages/analytics/modules/energy_management/blocs/realtime_device_changes/realtime_device_changes_bloc.dart';
import 'package:syncrow_web/pages/analytics/modules/energy_management/helpers/fetch_energy_management_data_helper.dart'; import 'package:syncrow_web/pages/analytics/modules/energy_management/widgets/power_clamp_energy_data_device_dropdown.dart';
import 'package:syncrow_web/pages/analytics/modules/energy_management/widgets/analytics_device_dropdown.dart';
import 'package:syncrow_web/pages/analytics/modules/energy_management/widgets/power_clamp_energy_status_widget.dart'; import 'package:syncrow_web/pages/analytics/modules/energy_management/widgets/power_clamp_energy_status_widget.dart';
import 'package:syncrow_web/pages/device_managment/all_devices/models/device_status.dart'; import 'package:syncrow_web/pages/device_managment/all_devices/models/device_status.dart';
import 'package:syncrow_web/utils/color_manager.dart'; import 'package:syncrow_web/utils/color_manager.dart';
import 'package:syncrow_web/utils/constants/assets.dart'; import 'package:syncrow_web/utils/constants/assets.dart';
import 'package:syncrow_web/utils/extension/build_context_x.dart'; import 'package:syncrow_web/utils/extension/build_context_x.dart';
import 'package:syncrow_web/utils/style.dart'; import 'package:syncrow_web/utils/style.dart';
import 'package:uuid/uuid.dart';
class OccupancyEndSideBar extends StatelessWidget { class OccupancyEndSideBar extends StatelessWidget {
const OccupancyEndSideBar({super.key}); const OccupancyEndSideBar({super.key});
@ -38,8 +37,7 @@ class OccupancyEndSideBar extends StatelessWidget {
), ),
const SizedBox(height: 6), const SizedBox(height: 6),
SelectableText( SelectableText(
context.watch<AnalyticsDevicesBloc>().state.selectedDevice?.uuid ?? (const Uuid().v4()),
'N/A',
style: context.textTheme.bodySmall?.copyWith( style: context.textTheme.bodySmall?.copyWith(
color: ColorsManager.blackColor, color: ColorsManager.blackColor,
fontWeight: FontWeight.w400, fontWeight: FontWeight.w400,
@ -107,7 +105,7 @@ class OccupancyEndSideBar extends StatelessWidget {
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: [ children: [
Expanded( Expanded(
flex: 3, flex: 2,
child: FittedBox( child: FittedBox(
fit: BoxFit.scaleDown, fit: BoxFit.scaleDown,
alignment: AlignmentDirectional.centerStart, alignment: AlignmentDirectional.centerStart,
@ -122,18 +120,11 @@ class OccupancyEndSideBar extends StatelessWidget {
), ),
), ),
const Spacer(), const Spacer(),
Expanded( const Expanded(
flex: 2,
child: FittedBox( child: FittedBox(
fit: BoxFit.scaleDown, fit: BoxFit.scaleDown,
alignment: AlignmentDirectional.centerEnd, alignment: AlignmentDirectional.centerEnd,
child: AnalyticsDeviceDropdown( child: PowerClampEnergyDataDeviceDropdown(),
onChanged: (value) =>
FetchEnergyManagementDataHelper.loadRealtimeDeviceChanges(
context,
deviceUuid: value.uuid,
),
),
), ),
), ),
], ],

View File

@ -1,7 +1,6 @@
import 'dart:math' as math show max; import 'dart:math' as math show max;
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:syncrow_web/pages/analytics/modules/occupancy/widgets/interactive_heat_map.dart';
import 'package:syncrow_web/pages/analytics/modules/occupancy/widgets/occupancy_heat_map_days.dart'; import 'package:syncrow_web/pages/analytics/modules/occupancy/widgets/occupancy_heat_map_days.dart';
import 'package:syncrow_web/pages/analytics/modules/occupancy/widgets/occupancy_heat_map_gradient.dart'; import 'package:syncrow_web/pages/analytics/modules/occupancy/widgets/occupancy_heat_map_gradient.dart';
import 'package:syncrow_web/pages/analytics/modules/occupancy/widgets/occupancy_heat_map_months.dart'; import 'package:syncrow_web/pages/analytics/modules/occupancy/widgets/occupancy_heat_map_months.dart';
@ -16,7 +15,7 @@ class OccupancyHeatMap extends StatelessWidget {
static const _totalWeeks = 53; static const _totalWeeks = 53;
int get _maxValue => heatMapData.isNotEmpty int get _maxValue => heatMapData.isNotEmpty
? heatMapData.keys.map((key) => heatMapData[key] ?? 0).reduce(math.max) ? heatMapData.keys.map((key) => heatMapData[key]!).reduce(math.max)
: 0; : 0;
DateTime _getStartingDate() { DateTime _getStartingDate() {
@ -29,7 +28,7 @@ class OccupancyHeatMap extends StatelessWidget {
return List.generate(_totalWeeks * 7, (index) { return List.generate(_totalWeeks * 7, (index) {
final date = startDate.add(Duration(days: index)); final date = startDate.add(Duration(days: index));
final value = heatMapData[date] ?? 0; final value = heatMapData[date] ?? 0;
return OccupancyPaintItem(index: index, value: value, date: date); return OccupancyPaintItem(index: index, value: value);
}); });
} }
@ -59,13 +58,15 @@ class OccupancyHeatMap extends StatelessWidget {
child: Row( child: Row(
children: [ children: [
const OccupancyHeatMapDays(cellSize: _cellSize), const OccupancyHeatMapDays(cellSize: _cellSize),
SizedBox( CustomPaint(
width: _totalWeeks * _cellSize, size: const Size(_totalWeeks * _cellSize, 7 * _cellSize),
height: 7 * _cellSize, child: CustomPaint(
child: InteractiveHeatMap( isComplex: true,
items: paintItems, size: const Size(_totalWeeks * _cellSize, 7 * _cellSize),
maxValue: _maxValue, painter: OccupancyPainter(
cellSize: _cellSize, items: paintItems,
maxValue: _maxValue,
),
), ),
), ),
], ],

View File

@ -7,7 +7,6 @@ import 'package:syncrow_web/pages/analytics/modules/occupancy/blocs/occupancy_he
import 'package:syncrow_web/pages/analytics/modules/occupancy/helpers/fetch_occupancy_data_helper.dart'; import 'package:syncrow_web/pages/analytics/modules/occupancy/helpers/fetch_occupancy_data_helper.dart';
import 'package:syncrow_web/pages/analytics/modules/occupancy/widgets/occupancy_heat_map.dart'; import 'package:syncrow_web/pages/analytics/modules/occupancy/widgets/occupancy_heat_map.dart';
import 'package:syncrow_web/pages/analytics/widgets/analytics_error_widget.dart'; import 'package:syncrow_web/pages/analytics/widgets/analytics_error_widget.dart';
import 'package:syncrow_web/pages/space_tree/bloc/space_tree_bloc.dart';
import 'package:syncrow_web/utils/style.dart'; import 'package:syncrow_web/utils/style.dart';
class OccupancyHeatMapBox extends StatelessWidget { class OccupancyHeatMapBox extends StatelessWidget {
@ -15,7 +14,6 @@ class OccupancyHeatMapBox extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final spaceTreeState = context.watch<SpaceTreeBloc>().state;
return BlocBuilder<OccupancyHeatMapBloc, OccupancyHeatMapState>( return BlocBuilder<OccupancyHeatMapBloc, OccupancyHeatMapState>(
builder: (context, state) { builder: (context, state) {
return Container( return Container(
@ -47,14 +45,7 @@ class OccupancyHeatMapBox extends StatelessWidget {
context.read<AnalyticsDatePickerBloc>().add( context.read<AnalyticsDatePickerBloc>().add(
UpdateAnalyticsDatePickerEvent(yearlyDate: value), UpdateAnalyticsDatePickerEvent(yearlyDate: value),
); );
if (spaceTreeState.selectedSpaces.isNotEmpty) { FetchOccupancyDataHelper.loadOccupancyData(context);
FetchOccupancyDataHelper.loadHeatMapData(
context,
spaceUuid:
spaceTreeState.selectedSpaces.firstOrNull ?? '',
year: value,
);
}
}, },
datePickerType: DatePickerType.year, datePickerType: DatePickerType.year,
selectedDate: context selectedDate: context
@ -70,10 +61,7 @@ class OccupancyHeatMapBox extends StatelessWidget {
Expanded( Expanded(
child: OccupancyHeatMap( child: OccupancyHeatMap(
heatMapData: state.heatMapData.asMap().map( heatMapData: state.heatMapData.asMap().map(
(_, value) => MapEntry( (_, value) => MapEntry(value.date, value.occupancy),
value.eventDate,
value.countTotalPresenceDetected,
),
), ),
), ),
), ),

View File

@ -4,25 +4,18 @@ import 'package:syncrow_web/utils/color_manager.dart';
class OccupancyPaintItem { class OccupancyPaintItem {
final int index; final int index;
final int value; final int value;
final DateTime date;
const OccupancyPaintItem({ const OccupancyPaintItem({required this.index, required this.value});
required this.index,
required this.value,
required this.date,
});
} }
class OccupancyPainter extends CustomPainter { class OccupancyPainter extends CustomPainter {
OccupancyPainter({ OccupancyPainter({
required this.items, required this.items,
required this.maxValue, required this.maxValue,
this.hoveredItem,
}); });
final List<OccupancyPaintItem> items; final List<OccupancyPaintItem> items;
final int maxValue; final int maxValue;
final OccupancyPaintItem? hoveredItem;
static const double cellSize = 16.0; static const double cellSize = 16.0;
@ -32,10 +25,6 @@ class OccupancyPainter extends CustomPainter {
final Paint borderPaint = Paint() final Paint borderPaint = Paint()
..color = ColorsManager.grayBorder.withValues(alpha: 0.4) ..color = ColorsManager.grayBorder.withValues(alpha: 0.4)
..style = PaintingStyle.stroke; ..style = PaintingStyle.stroke;
final Paint hoveredBorderPaint = Paint()
..color = Colors.black
..style = PaintingStyle.stroke
..strokeWidth = 1.5;
for (final item in items) { for (final item in items) {
final column = item.index ~/ 7; final column = item.index ~/ 7;
@ -48,27 +37,22 @@ class OccupancyPainter extends CustomPainter {
final rect = Rect.fromLTWH(x, y, cellSize, cellSize); final rect = Rect.fromLTWH(x, y, cellSize, cellSize);
canvas.drawRect(rect, fillPaint); canvas.drawRect(rect, fillPaint);
// Highlight the hovered item _drawDashedLine(
if (hoveredItem != null && hoveredItem!.index == item.index) { canvas,
canvas.drawRect(rect, hoveredBorderPaint); Offset(x, y),
} else { Offset(x + cellSize, y),
_drawDashedLine( borderPaint,
canvas, );
Offset(x, y), _drawDashedLine(
Offset(x + cellSize, y), canvas,
borderPaint, Offset(x, y + cellSize),
); Offset(x + cellSize, y + cellSize),
_drawDashedLine( borderPaint,
canvas, );
Offset(x, y + cellSize),
Offset(x + cellSize, y + cellSize),
borderPaint,
);
canvas.drawLine(Offset(x, y), Offset(x, y + cellSize), borderPaint); canvas.drawLine(Offset(x, y), Offset(x, y + cellSize), borderPaint);
canvas.drawLine(Offset(x + cellSize, y), Offset(x + cellSize, y + cellSize), canvas.drawLine(
borderPaint); Offset(x + cellSize, y), Offset(x + cellSize, y + cellSize), borderPaint);
}
} }
} }
@ -96,6 +80,5 @@ class OccupancyPainter extends CustomPainter {
} }
@override @override
bool shouldRepaint(covariant OccupancyPainter oldDelegate) => bool shouldRepaint(covariant CustomPainter oldDelegate) => false;
oldDelegate.hoveredItem != hoveredItem;
} }

View File

@ -1,22 +0,0 @@
enum AnalyticsDeviceRequestType { energyManagement, occupancy }
class GetAnalyticsDevicesParam {
final String? spaceUuid;
final List<String> deviceTypes;
final String? communityUuid;
final AnalyticsDeviceRequestType requestType;
const GetAnalyticsDevicesParam({
required this.requestType,
required this.spaceUuid,
required this.deviceTypes,
required this.communityUuid,
});
Map<String, dynamic> toJson() {
return <String, dynamic>{
if (spaceUuid != null) 'spaceUuid': spaceUuid,
if (communityUuid != null) 'communityUuid': communityUuid,
};
}
}

View File

@ -1,20 +1,24 @@
import 'package:equatable/equatable.dart'; import 'package:equatable/equatable.dart';
class GetEnergyConsumptionByPhasesParam extends Equatable { class GetEnergyConsumptionByPhasesParam extends Equatable {
final String powerClampUuid; final DateTime? startDate;
final DateTime? date; final DateTime? endDate;
final String? spaceId;
const GetEnergyConsumptionByPhasesParam({ const GetEnergyConsumptionByPhasesParam({
required this.powerClampUuid, this.startDate,
this.date, this.endDate,
this.spaceId,
}); });
Map<String, dynamic> toJson() { Map<String, dynamic> toJson() {
return { return {
'monthDate': '${date?.year}-${date?.month.toString().padLeft(2, '0')}', 'startDate': startDate?.toIso8601String(),
'endDate': endDate?.toIso8601String(),
'spaceId': spaceId,
}; };
} }
@override @override
List<Object?> get props => [powerClampUuid, date]; List<Object?> get props => [startDate, endDate, spaceId];
} }

View File

@ -1,19 +1,3 @@
class GetEnergyConsumptionPerDeviceParam { class GetEnergyConsumptionPerDeviceParam {
const GetEnergyConsumptionPerDeviceParam({ const GetEnergyConsumptionPerDeviceParam();
this.monthDate,
this.spaceId,
this.communityId,
});
final DateTime? monthDate;
final String? spaceId;
final String? communityId;
Map<String, dynamic> toJson() => {
'monthDate':
'${monthDate?.year}-${monthDate?.month.toString().padLeft(2, '0')}',
if (spaceId == null || spaceId == null) 'spaceUuid': spaceId,
'communityUuid': communityId,
'groupByDevice': true,
};
} }

View File

@ -1,13 +1,19 @@
class GetOccupancyHeatMapParam { class GetOccupancyHeatMapParam {
final DateTime year; final DateTime year;
final String spaceUuid; final String communityId;
final String spaceId;
const GetOccupancyHeatMapParam({ const GetOccupancyHeatMapParam({
required this.year, required this.year,
required this.spaceUuid, required this.communityId,
required this.spaceId,
}); });
Map<String, dynamic> toJson() { Map<String, dynamic> toJson() {
return {'year': year.year}; return {
'year': year.toIso8601String(),
'communityId': communityId,
'spaceId': spaceId,
};
} }
} }

View File

@ -1,11 +1,19 @@
class GetOccupancyParam { class GetOccupancyParam {
final String monthDate; final String monthDate;
final String? spaceUuid; final String? spaceUuid;
final String communityUuid;
GetOccupancyParam({ GetOccupancyParam({
required this.monthDate, required this.monthDate,
required this.spaceUuid, required this.spaceUuid,
required this.communityUuid,
}); });
Map<String, dynamic> toJson() => {'monthDate': monthDate}; Map<String, dynamic> toJson() {
return {
'monthDate': monthDate,
'spaceUuid': spaceUuid,
'communityUuid': communityUuid,
};
}
} }

View File

@ -13,7 +13,7 @@ class GetTotalEnergyConsumptionParam {
return { return {
'monthDate': 'monthDate':
'${monthDate?.year}-${monthDate?.month.toString().padLeft(2, '0')}', '${monthDate?.year}-${monthDate?.month.toString().padLeft(2, '0')}',
if (spaceId == null || spaceId == null) 'spaceUuid': spaceId, if (communityId == null || communityId!.isEmpty) 'spaceUuid': spaceId,
'communityUuid': communityId, 'communityUuid': communityId,
'groupByDevice': false, 'groupByDevice': false,
}; };

View File

@ -1,6 +0,0 @@
import 'package:syncrow_web/pages/analytics/models/analytics_device.dart';
import 'package:syncrow_web/pages/analytics/params/get_analytics_devices_param.dart';
abstract interface class AnalyticsDevicesService {
Future<List<AnalyticsDevice>> getDevices(GetAnalyticsDevicesParam param);
}

View File

@ -1,24 +0,0 @@
import 'package:syncrow_web/pages/analytics/models/analytics_device.dart';
import 'package:syncrow_web/pages/analytics/params/get_analytics_devices_param.dart';
import 'package:syncrow_web/pages/analytics/services/analytics_devices/analytics_devices_service.dart';
class AnalyticsDevicesServiceDelegate implements AnalyticsDevicesService {
const AnalyticsDevicesServiceDelegate(
this._occupancyService,
this._energyManagementService,
);
final AnalyticsDevicesService _occupancyService;
final AnalyticsDevicesService _energyManagementService;
@override
Future<List<AnalyticsDevice>> getDevices(
GetAnalyticsDevicesParam param,
) {
return switch (param.requestType) {
AnalyticsDeviceRequestType.occupancy => _occupancyService.getDevices(param),
AnalyticsDeviceRequestType.energyManagement =>
_energyManagementService.getDevices(param),
};
}
}

View File

@ -1,36 +0,0 @@
import 'package:syncrow_web/pages/analytics/models/analytics_device.dart';
import 'package:syncrow_web/pages/analytics/params/get_analytics_devices_param.dart';
import 'package:syncrow_web/pages/analytics/services/analytics_devices/analytics_devices_service.dart';
import 'package:syncrow_web/services/api/http_service.dart';
final class RemoteEnergyManagementAnalyticsDevicesService
implements AnalyticsDevicesService {
const RemoteEnergyManagementAnalyticsDevicesService(this._httpService);
final HTTPService _httpService;
@override
Future<List<AnalyticsDevice>> getDevices(GetAnalyticsDevicesParam param) async {
try {
final response = await _httpService.get(
path: '/devices-space-community/recursive-child',
queryParameters: param.toJson()
..addAll({'productType': param.deviceTypes.first}),
expectedResponseModel: (response) {
final json = response as Map<String, dynamic>;
final dailyData = json['data'] as List<dynamic>? ?? <dynamic>[];
final result = dailyData.map(
(json) => AnalyticsDevice.fromJson(json as Map<String, dynamic>),
);
return result.toList();
},
);
return response;
} catch (e) {
throw Exception('Failed to load total energy consumption: $e');
}
}
}

View File

@ -1,61 +0,0 @@
import 'package:syncrow_web/pages/analytics/models/analytics_device.dart';
import 'package:syncrow_web/pages/analytics/params/get_analytics_devices_param.dart';
import 'package:syncrow_web/pages/analytics/services/analytics_devices/analytics_devices_service.dart';
import 'package:syncrow_web/pages/common/bloc/project_manager.dart';
import 'package:syncrow_web/services/api/http_service.dart';
class RemoteOccupancyAnalyticsDevicesService implements AnalyticsDevicesService {
const RemoteOccupancyAnalyticsDevicesService(this._httpService);
final HTTPService _httpService;
@override
Future<List<AnalyticsDevice>> getDevices(GetAnalyticsDevicesParam param) async {
try {
final requests = await Future.wait<List<AnalyticsDevice>>(
param.deviceTypes.map((e) {
final mappedParam = GetAnalyticsDevicesParam(
requestType: AnalyticsDeviceRequestType.occupancy,
spaceUuid: param.spaceUuid,
deviceTypes: [e],
communityUuid: param.communityUuid,
);
return _makeRequest(mappedParam);
}).toList(),
);
final result = requests.map((e) => e.first).toList();
return result;
} catch (e) {
throw Exception('Failed to load total energy consumption: $e');
}
}
Future<List<AnalyticsDevice>> _makeRequest(GetAnalyticsDevicesParam param) async {
try {
final projectUuid = await ProjectManager.getProjectUUID();
final response = await _httpService.get(
path:
'/projects/$projectUuid/communities/${param.communityUuid}/spaces/${param.spaceUuid}/devices',
queryParameters: {
'communityUuid': param.communityUuid,
'spaceUuid': param.spaceUuid,
'productType': param.deviceTypes.first,
},
expectedResponseModel: (response) {
final json = response as Map<String, dynamic>;
final dailyData = json['data'] as List<dynamic>? ?? <dynamic>[];
final result = dailyData.map(
(json) => AnalyticsDevice.fromJson(json as Map<String, dynamic>),
);
return result.toList();
},
);
return response;
} catch (e) {
rethrow;
}
}
}

View File

@ -0,0 +1,29 @@
import 'package:syncrow_web/pages/analytics/models/phases_energy_consumption.dart';
import 'package:syncrow_web/pages/analytics/params/get_energy_consumption_by_phases_param.dart';
import 'package:syncrow_web/pages/analytics/services/energy_consumption_by_phases/energy_consumption_by_phases_service.dart';
class FakeEnergyConsumptionByPhasesService
implements EnergyConsumptionByPhasesService {
@override
Future<List<PhasesEnergyConsumption>> load(
GetEnergyConsumptionByPhasesParam param,
) {
return Future.delayed(
const Duration(milliseconds: 500),
() => const [
PhasesEnergyConsumption(month: 1, phaseA: 200, phaseB: 300, phaseC: 400),
PhasesEnergyConsumption(month: 2, phaseA: 300, phaseB: 400, phaseC: 500),
PhasesEnergyConsumption(month: 3, phaseA: 400, phaseB: 500, phaseC: 600),
PhasesEnergyConsumption(month: 4, phaseA: 100, phaseB: 100, phaseC: 100),
PhasesEnergyConsumption(month: 5, phaseA: 300, phaseB: 400, phaseC: 500),
PhasesEnergyConsumption(month: 6, phaseA: 300, phaseB: 100, phaseC: 400),
PhasesEnergyConsumption(month: 7, phaseA: 300, phaseB: 100, phaseC: 400),
PhasesEnergyConsumption(month: 8, phaseA: 500, phaseB: 100, phaseC: 100),
PhasesEnergyConsumption(month: 9, phaseA: 500, phaseB: 100, phaseC: 200),
PhasesEnergyConsumption(month: 10, phaseA: 100, phaseB: 50, phaseC: 50),
PhasesEnergyConsumption(month: 11, phaseA: 600, phaseB: 750, phaseC: 130),
PhasesEnergyConsumption(month: 12, phaseA: 100, phaseB: 100, phaseC: 100),
],
);
}
}

View File

@ -15,9 +15,8 @@ final class RemoteEnergyConsumptionByPhasesService
) async { ) async {
try { try {
final response = await _httpService.get( final response = await _httpService.get(
path: '/power-clamp/${param.powerClampUuid}/historical', path: 'endpoint',
showServerMessage: true, showServerMessage: true,
queryParameters: param.toJson(),
expectedResponseModel: (data) { expectedResponseModel: (data) {
final json = data as Map<String, dynamic>? ?? {}; final json = data as Map<String, dynamic>? ?? {};
final mappedData = json['data'] as List<dynamic>? ?? []; final mappedData = json['data'] as List<dynamic>? ?? [];
@ -29,7 +28,7 @@ final class RemoteEnergyConsumptionByPhasesService
); );
return response; return response;
} catch (e) { } catch (e) {
throw Exception('Failed to load energy consumption per phase: $e'); throw Exception('Failed to load energy consumption per device: $e');
} }
} }
} }

View File

@ -0,0 +1,39 @@
import 'dart:math' as math show Random;
import 'package:flutter/material.dart';
import 'package:syncrow_web/pages/analytics/models/device_energy_data_model.dart';
import 'package:syncrow_web/pages/analytics/models/energy_data_model.dart';
import 'package:syncrow_web/pages/analytics/params/get_energy_consumption_per_device_param.dart';
import 'package:syncrow_web/pages/analytics/services/energy_consumption_per_device/energy_consumption_per_device_service.dart';
class FakeEnergyConsumptionPerDeviceService
implements EnergyConsumptionPerDeviceService {
@override
Future<List<DeviceEnergyDataModel>> load(
GetEnergyConsumptionPerDeviceParam param,
) {
final random = math.Random();
return Future.delayed(const Duration(milliseconds: 500), () {
return [
(Colors.redAccent, 1),
(Colors.lightBlueAccent, 2),
(Colors.purpleAccent, 3),
].map((e) {
final (color, index) = e;
return DeviceEnergyDataModel(
color: color,
energy: List.generate(30, (i) => i)
.map(
(index) => EnergyDataModel(
date: DateTime(2025, 1, index + 1),
value: random.nextInt(100) + (index * 100),
),
)
.toList(),
deviceName: 'Device $index',
deviceId: 'device_$index',
);
}).toList();
});
}
}

View File

@ -1,6 +1,4 @@
import 'package:flutter/material.dart';
import 'package:syncrow_web/pages/analytics/models/device_energy_data_model.dart'; import 'package:syncrow_web/pages/analytics/models/device_energy_data_model.dart';
import 'package:syncrow_web/pages/analytics/models/energy_data_model.dart';
import 'package:syncrow_web/pages/analytics/params/get_energy_consumption_per_device_param.dart'; import 'package:syncrow_web/pages/analytics/params/get_energy_consumption_per_device_param.dart';
import 'package:syncrow_web/pages/analytics/services/energy_consumption_per_device/energy_consumption_per_device_service.dart'; import 'package:syncrow_web/pages/analytics/services/energy_consumption_per_device/energy_consumption_per_device_service.dart';
import 'package:syncrow_web/services/api/http_service.dart'; import 'package:syncrow_web/services/api/http_service.dart';
@ -17,10 +15,16 @@ class RemoteEnergyConsumptionPerDeviceService
) async { ) async {
try { try {
final response = await _httpService.get( final response = await _httpService.get(
path: '/power-clamp/historical', path: 'endpoint',
showServerMessage: true, showServerMessage: true,
queryParameters: param.toJson(), expectedResponseModel: (data) {
expectedResponseModel: _EnergyConsumptionPerDeviceMapper.map, final json = data as Map<String, dynamic>? ?? {};
final mappedData = json['data'] as List<dynamic>? ?? [];
return mappedData.map((e) {
final jsonData = e as Map<String, dynamic>;
return DeviceEnergyDataModel.fromJson(jsonData);
}).toList();
},
); );
return response; return response;
} catch (e) { } catch (e) {
@ -28,30 +32,3 @@ class RemoteEnergyConsumptionPerDeviceService
} }
} }
} }
abstract final class _EnergyConsumptionPerDeviceMapper {
const _EnergyConsumptionPerDeviceMapper._();
static List<DeviceEnergyDataModel> map(dynamic data) {
final json = data as Map<String, dynamic>? ?? {};
final mappedData = json['data'] as List<dynamic>? ?? [];
return mappedData.map((e) {
final deviceData = e as Map<String, dynamic>;
final energyData = deviceData['data'] as List<dynamic>;
return DeviceEnergyDataModel(
deviceId: deviceData['deviceUuid'] as String,
deviceName: deviceData['deviceName'] as String,
color: Color((DateTime.now().microsecondsSinceEpoch +
deviceData['deviceUuid'].hashCode) |
0xFF000000),
energy: energyData.map((data) {
final energyJson = data as Map<String, dynamic>;
return EnergyDataModel(
date: DateTime.parse(energyJson['date'] as String),
value: double.parse(energyJson['total_energy_consumed_kw'] as String),
);
}).toList(),
);
}).toList();
}
}

View File

@ -0,0 +1,19 @@
import 'package:syncrow_web/pages/analytics/models/occupacy.dart';
import 'package:syncrow_web/pages/analytics/params/get_occupancy_param.dart';
import 'package:syncrow_web/pages/analytics/services/occupacy/occupacy_service.dart';
class FakeOccupacyService implements OccupacyService {
@override
Future<List<Occupacy>> load(GetOccupancyParam param) async {
return await Future.delayed(
const Duration(seconds: 1),
() => List.generate(
30,
(index) => Occupacy(
date: DateTime.now().subtract(Duration(days: index)).toString(),
occupancy: ((index / 100)).toString(),
),
),
);
}
}

View File

@ -1,32 +0,0 @@
import 'package:syncrow_web/pages/analytics/models/occupacy.dart';
import 'package:syncrow_web/pages/analytics/params/get_occupancy_param.dart';
import 'package:syncrow_web/pages/analytics/services/occupacy/occupacy_service.dart';
import 'package:syncrow_web/services/api/http_service.dart';
final class RemoteOccupancyService implements OccupacyService {
const RemoteOccupancyService(this._httpService);
final HTTPService _httpService;
@override
Future<List<Occupacy>> load(GetOccupancyParam param) async {
try {
final response = await _httpService.get(
path: '/occupancy/duration/space/${param.spaceUuid}',
showServerMessage: true,
queryParameters: param.toJson(),
expectedResponseModel: (data) {
final json = data as Map<String, dynamic>? ?? {};
final mappedData = json['data'] as List<dynamic>? ?? [];
return mappedData.map((e) {
final jsonData = e as Map<String, dynamic>;
return Occupacy.fromJson(jsonData);
}).toList();
},
);
return response;
} catch (e) {
throw Exception('Failed to load energy consumption per phase: $e');
}
}
}

View File

@ -0,0 +1,25 @@
import 'package:syncrow_web/pages/analytics/models/occupancy_heat_map_model.dart';
import 'package:syncrow_web/pages/analytics/params/get_occupancy_heat_map_param.dart';
import 'package:syncrow_web/pages/analytics/services/occupancy_heat_map/occupancy_heat_map_service.dart';
class FakeOccupancyHeatMapService implements OccupancyHeatMapService {
@override
Future<List<OccupancyHeatMapModel>> load(GetOccupancyHeatMapParam param) {
return Future.delayed(const Duration(milliseconds: 200), () {
final now = DateTime.now();
final startOfYear = DateTime(now.year, 1, 1);
final endOfYear = DateTime(now.year, 12, 31);
final daysInYear = endOfYear.difference(startOfYear).inDays + 1;
final List<OccupancyHeatMapModel> data = List.generate(
daysInYear,
(index) => OccupancyHeatMapModel(
date: startOfYear.add(Duration(days: index)),
occupancy: ((index + 1) * 10) % 100,
),
);
return data;
});
}
}

View File

@ -1,35 +0,0 @@
import 'package:syncrow_web/pages/analytics/models/occupancy_heat_map_model.dart';
import 'package:syncrow_web/pages/analytics/params/get_occupancy_heat_map_param.dart';
import 'package:syncrow_web/pages/analytics/services/occupancy_heat_map/occupancy_heat_map_service.dart';
import 'package:syncrow_web/services/api/http_service.dart';
final class RemoteOccupancyHeatMapService implements OccupancyHeatMapService {
const RemoteOccupancyHeatMapService(this._httpService);
final HTTPService _httpService;
@override
Future<List<OccupancyHeatMapModel>> load(GetOccupancyHeatMapParam param) async {
try {
final response = await _httpService.get(
path: '/occupancy/heat-map/space/${param.spaceUuid}',
showServerMessage: true,
queryParameters: param.toJson(),
expectedResponseModel: (response) {
final json = response as Map<String, dynamic>;
final dailyData = json['data'] as List<dynamic>? ?? <dynamic>[];
final result = dailyData.map(
(json) => OccupancyHeatMapModel.fromJson(json as Map<String, dynamic>),
);
return result.toList();
},
);
return response;
} catch (e) {
throw Exception('Failed to load total energy consumption:');
}
}
}

View File

@ -12,7 +12,7 @@ class AnalyticsErrorWidget extends StatelessWidget {
return Visibility( return Visibility(
visible: errorMessage != null || (errorMessage?.isNotEmpty ?? false), visible: errorMessage != null || (errorMessage?.isNotEmpty ?? false),
child: Text( child: Text(
errorMessage ?? 'Something went wrong', '$errorMessage ?? "Something went wrong"',
maxLines: 1, maxLines: 1,
overflow: TextOverflow.ellipsis, overflow: TextOverflow.ellipsis,
style: context.textTheme.bodySmall?.copyWith( style: context.textTheme.bodySmall?.copyWith(

View File

@ -63,8 +63,7 @@ class _DynamicTableState extends State<DynamicTable> {
} }
} }
bool _compareListOfLists( bool _compareListOfLists(List<List<dynamic>> oldList, List<List<dynamic>> newList) {
List<List<dynamic>> oldList, List<List<dynamic>> newList) {
// Check if the old and new lists are the same // Check if the old and new lists are the same
if (oldList.length != newList.length) return false; if (oldList.length != newList.length) return false;
@ -112,8 +111,8 @@ class _DynamicTableState extends State<DynamicTable> {
trackVisibility: true, trackVisibility: true,
child: Scrollbar( child: Scrollbar(
controller: _horizontalScrollController, controller: _horizontalScrollController,
thumbVisibility: true, thumbVisibility: false,
trackVisibility: true, trackVisibility: false,
notificationPredicate: (notif) => notif.depth == 1, notificationPredicate: (notif) => notif.depth == 1,
child: SingleChildScrollView( child: SingleChildScrollView(
controller: _verticalScrollController, controller: _verticalScrollController,
@ -133,60 +132,46 @@ class _DynamicTableState extends State<DynamicTable> {
children: [ children: [
if (widget.withCheckBox) _buildSelectAllCheckbox(), if (widget.withCheckBox) _buildSelectAllCheckbox(),
...List.generate(widget.headers.length, (index) { ...List.generate(widget.headers.length, (index) {
return _buildTableHeaderCell( return _buildTableHeaderCell(widget.headers[index], index);
widget.headers[index], index);
}) })
//...widget.headers.map((header) => _buildTableHeaderCell(header)), //...widget.headers.map((header) => _buildTableHeaderCell(header)),
], ],
), ),
), ),
widget.isEmpty widget.isEmpty
? SizedBox( ? Column(
height: widget.size.height * 0.5, mainAxisAlignment: MainAxisAlignment.center,
width: widget.size.width, children: [
child: Column( Row(
mainAxisAlignment: MainAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.center,
children: [ mainAxisAlignment: MainAxisAlignment.center,
Row( children: [
crossAxisAlignment: CrossAxisAlignment.center, Column(
mainAxisAlignment: MainAxisAlignment.center, children: [
children: [ SvgPicture.asset(Assets.emptyTable),
Column( const SizedBox(
children: [ height: 15,
SvgPicture.asset(Assets.emptyTable), ),
const SizedBox( Text(
height: 15, widget.tableName == 'AccessManagement' ? 'No Password ' : 'No Devices',
), style: Theme.of(context)
Text( .textTheme
widget.tableName == 'AccessManagement' .bodySmall!
? 'No Password ' .copyWith(color: ColorsManager.grayColor),
: 'No Devices', )
style: Theme.of(context) ],
.textTheme ),
.bodySmall! ],
.copyWith( ),
color: ],
ColorsManager.grayColor),
)
],
),
],
),
],
),
) )
: Column( : Column(
children: children: List.generate(widget.data.length, (index) {
List.generate(widget.data.length, (index) {
final row = widget.data[index]; final row = widget.data[index];
return Row( return Row(
children: [ children: [
if (widget.withCheckBox) if (widget.withCheckBox) _buildRowCheckbox(index, widget.size.height * 0.08),
_buildRowCheckbox( ...row.map((cell) => _buildTableCell(cell.toString(), widget.size.height * 0.08)),
index, widget.size.height * 0.08),
...row.map((cell) => _buildTableCell(
cell.toString(),
widget.size.height * 0.08)),
], ],
); );
}), }),
@ -211,9 +196,7 @@ class _DynamicTableState extends State<DynamicTable> {
), ),
child: Checkbox( child: Checkbox(
value: _selectAll, value: _selectAll,
onChanged: widget.withSelectAll && widget.data.isNotEmpty onChanged: widget.withSelectAll && widget.data.isNotEmpty ? _toggleSelectAll : null,
? _toggleSelectAll
: null,
), ),
); );
} }
@ -255,9 +238,7 @@ class _DynamicTableState extends State<DynamicTable> {
constraints: const BoxConstraints.expand(height: 40), constraints: const BoxConstraints.expand(height: 40),
alignment: Alignment.centerLeft, alignment: Alignment.centerLeft,
child: Padding( child: Padding(
padding: EdgeInsets.symmetric( padding: EdgeInsets.symmetric(horizontal: index == widget.headers.length - 1 ? 12 : 8.0, vertical: 4),
horizontal: index == widget.headers.length - 1 ? 12 : 8.0,
vertical: 4),
child: Text( child: Text(
title, title,
style: context.textTheme.titleSmall!.copyWith( style: context.textTheme.titleSmall!.copyWith(

View File

@ -12,7 +12,6 @@ import 'package:syncrow_web/pages/routines/models/gang_switches/one_gang_switch/
import 'package:syncrow_web/pages/routines/models/gang_switches/three_gang_switch/three_gang_switch.dart'; import 'package:syncrow_web/pages/routines/models/gang_switches/three_gang_switch/three_gang_switch.dart';
import 'package:syncrow_web/pages/routines/models/gang_switches/two_gang_switch/two_gang_switch.dart'; import 'package:syncrow_web/pages/routines/models/gang_switches/two_gang_switch/two_gang_switch.dart';
import 'package:syncrow_web/pages/routines/models/gateway.dart'; import 'package:syncrow_web/pages/routines/models/gateway.dart';
import 'package:syncrow_web/pages/routines/models/water_heater/water_heater_functions.dart';
import 'package:syncrow_web/pages/routines/models/wps/wps_functions.dart'; import 'package:syncrow_web/pages/routines/models/wps/wps_functions.dart';
import 'package:syncrow_web/pages/routines/widgets/routine_dialogs/ceiling_sensor/ceiling_sensor_helper.dart'; import 'package:syncrow_web/pages/routines/widgets/routine_dialogs/ceiling_sensor/ceiling_sensor_helper.dart';
import 'package:syncrow_web/utils/constants/assets.dart'; import 'package:syncrow_web/utils/constants/assets.dart';
@ -359,10 +358,7 @@ SOS
case 'NCPS': case 'NCPS':
return [ return [
FlushPresenceDelayFunction( FlushPresenceDelayFunction(
deviceId: uuid ?? '', deviceId: uuid ?? '', deviceName: name ?? '', type: 'IF',),
deviceName: name ?? '',
type: 'IF',
),
FlushIlluminanceFunction( FlushIlluminanceFunction(
deviceId: uuid ?? '', deviceName: name ?? '', type: 'IF'), deviceId: uuid ?? '', deviceName: name ?? '', type: 'IF'),
@ -382,17 +378,6 @@ SOS
FlushTriggerLevelFunction( FlushTriggerLevelFunction(
deviceId: uuid ?? '', deviceName: name ?? '', type: 'THEN'), deviceId: uuid ?? '', deviceName: name ?? '', type: 'THEN'),
]; ];
case 'WH':
return [
WHRestartStatusFunction(
deviceId: uuid ?? '', deviceName: name ?? '', type: 'IF'),
WHSwitchFunction(
deviceId: uuid ?? '', deviceName: name ?? '', type: 'BOTH'),
TimerConfirmTimeFunction(
deviceId: uuid ?? '', deviceName: name ?? '', type: 'BOTH'),
BacklightFunction(
deviceId: uuid ?? '', deviceName: name ?? '', type: 'IF'),
];
default: default:
return []; return [];

View File

@ -97,8 +97,7 @@ class DeviceControlDialog extends StatelessWidget with RouteControlsBasedCode {
children: [ children: [
_buildInfoRow('Space Name:', _buildInfoRow('Space Name:',
device.spaces?.firstOrNull?.spaceName ?? 'N/A'), device.spaces?.firstOrNull?.spaceName ?? 'N/A'),
_buildInfoRow( _buildInfoRow('Room:', device.subspace?.subspaceName ?? 'N/A'),
'Sub space:', device.subspace?.subspaceName ?? 'N/A'),
], ],
), ),
TableRow( TableRow(

View File

@ -51,13 +51,12 @@ class HomeBloc extends Bloc<HomeEvent, HomeState> {
Future _fetchUserInfo(FetchUserInfo event, Emitter<HomeState> emit) async { Future _fetchUserInfo(FetchUserInfo event, Emitter<HomeState> emit) async {
try { try {
var uuid = var uuid = await const FlutterSecureStorage().read(key: UserModel.userUuidKey);
await const FlutterSecureStorage().read(key: UserModel.userUuidKey);
user = await HomeApi().fetchUserInfo(uuid); user = await HomeApi().fetchUserInfo(uuid);
if (user != null && user!.project != null) { if (user != null && user!.project != null) {
await ProjectManager.setProjectUUID(user!.project!.uuid); await ProjectManager.setProjectUUID(user!.project!.uuid);
// NavigationService.navigatorKey.currentContext!.read<SpaceTreeBloc>().add(InitialEvent()); NavigationService.navigatorKey.currentContext!.read<SpaceTreeBloc>().add(InitialEvent());
} }
add(FetchTermEvent()); add(FetchTermEvent());
add(FetchPolicyEvent()); add(FetchPolicyEvent());
@ -89,12 +88,10 @@ class HomeBloc extends Bloc<HomeEvent, HomeState> {
} }
} }
Future _confirmUserAgreement( Future _confirmUserAgreement(ConfirmUserAgreementEvent event, Emitter<HomeState> emit) async {
ConfirmUserAgreementEvent event, Emitter<HomeState> emit) async {
try { try {
emit(LoadingHome()); emit(LoadingHome());
var uuid = var uuid = await const FlutterSecureStorage().read(key: UserModel.userUuidKey);
await const FlutterSecureStorage().read(key: UserModel.userUuidKey);
policy = await HomeApi().confirmUserAgreements(uuid); policy = await HomeApi().confirmUserAgreements(uuid);
emit(PolicyAgreement()); emit(PolicyAgreement());
} catch (e) { } catch (e) {
@ -158,7 +155,7 @@ class HomeBloc extends Bloc<HomeEvent, HomeState> {
}, },
color: ColorsManager.primaryColor, color: ColorsManager.primaryColor,
), ),
// HomeItemModel( // HomeItemModel(
// title: 'Move in', // title: 'Move in',
// icon: Assets.moveinIcon, // icon: Assets.moveinIcon,

View File

@ -24,7 +24,7 @@ class _HomeWebPageState extends State<HomeWebPage> {
void initState() { void initState() {
super.initState(); super.initState();
final homeBloc = BlocProvider.of<HomeBloc>(context); final homeBloc = BlocProvider.of<HomeBloc>(context);
// homeBloc.add(const FetchUserInfo()); homeBloc.add(const FetchUserInfo());
} }
@override @override
@ -38,10 +38,8 @@ class _HomeWebPageState extends State<HomeWebPage> {
child: BlocConsumer<HomeBloc, HomeState>( child: BlocConsumer<HomeBloc, HomeState>(
listener: (BuildContext context, state) { listener: (BuildContext context, state) {
if (state is HomeInitial) { if (state is HomeInitial) {
if (homeBloc.user!.hasAcceptedWebAgreement == false && if (homeBloc.user!.hasAcceptedWebAgreement == false && !_dialogShown) {
!_dialogShown) { _dialogShown = true; // Set the flag to true to indicate the dialog is showing.
_dialogShown =
true; // Set the flag to true to indicate the dialog is showing.
Future.delayed(const Duration(seconds: 1), () { Future.delayed(const Duration(seconds: 1), () {
showDialog( showDialog(
context: context, context: context,
@ -56,7 +54,7 @@ class _HomeWebPageState extends State<HomeWebPage> {
_dialogShown = false; _dialogShown = false;
if (v != null) { if (v != null) {
homeBloc.add(ConfirmUserAgreementEvent()); homeBloc.add(ConfirmUserAgreementEvent());
// homeBloc.add(const FetchUserInfo()); homeBloc.add(const FetchUserInfo());
} }
}); });
}); });
@ -100,8 +98,7 @@ class _HomeWebPageState extends State<HomeWebPage> {
width: size.width * 0.68, width: size.width * 0.68,
child: GridView.builder( child: GridView.builder(
itemCount: homeBloc.homeItems.length, itemCount: homeBloc.homeItems.length,
gridDelegate: gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
const SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 3, // Adjust as needed. crossAxisCount: 3, // Adjust as needed.
crossAxisSpacing: 20.0, crossAxisSpacing: 20.0,
mainAxisSpacing: 20.0, mainAxisSpacing: 20.0,
@ -113,8 +110,7 @@ class _HomeWebPageState extends State<HomeWebPage> {
active: homeBloc.homeItems[index].active!, active: homeBloc.homeItems[index].active!,
name: homeBloc.homeItems[index].title!, name: homeBloc.homeItems[index].title!,
img: homeBloc.homeItems[index].icon!, img: homeBloc.homeItems[index].icon!,
onTap: () => onTap: () => homeBloc.homeItems[index].onPress(context),
homeBloc.homeItems[index].onPress(context),
); );
}, },
), ),

View File

@ -26,10 +26,8 @@ class FunctionBloc extends Bloc<FunctionBlocEvent, FunctionBlocState> {
functionCode: event.functionData.functionCode, functionCode: event.functionData.functionCode,
operationName: event.functionData.operationName, operationName: event.functionData.operationName,
value: event.functionData.value ?? existingData.value, value: event.functionData.value ?? existingData.value,
valueDescription: event.functionData.valueDescription ?? valueDescription: event.functionData.valueDescription ?? existingData.valueDescription,
existingData.valueDescription,
condition: event.functionData.condition ?? existingData.condition, condition: event.functionData.condition ?? existingData.condition,
step: event.functionData.step ?? existingData.step,
); );
} else { } else {
functions.clear(); functions.clear();
@ -61,10 +59,8 @@ class FunctionBloc extends Bloc<FunctionBlocEvent, FunctionBlocState> {
); );
} }
FutureOr<void> _onSelectFunction( FutureOr<void> _onSelectFunction(SelectFunction event, Emitter<FunctionBlocState> emit) {
SelectFunction event, Emitter<FunctionBlocState> emit) {
emit(state.copyWith( emit(state.copyWith(
selectedFunction: event.functionCode, selectedFunction: event.functionCode, selectedOperationName: event.operationName));
selectedOperationName: event.operationName));
} }
} }

View File

@ -536,7 +536,7 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
// 'entityId': 'tab_to_run', // 'entityId': 'tab_to_run',
// 'uniqueCustomId': const Uuid().v4(), // 'uniqueCustomId': const Uuid().v4(),
// 'deviceId': 'tab_to_run', // 'deviceId': 'tab_to_run',
// 'title': 'Tap to run', // 'title': 'Tab to run',
// 'productType': 'tab_to_run', // 'productType': 'tab_to_run',
// 'imagePath': Assets.tabToRun, // 'imagePath': Assets.tabToRun,
// } // }
@ -771,7 +771,7 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
'entityId': 'tab_to_run', 'entityId': 'tab_to_run',
'uniqueCustomId': const Uuid().v4(), 'uniqueCustomId': const Uuid().v4(),
'deviceId': 'tab_to_run', 'deviceId': 'tab_to_run',
'title': 'Tap to run', 'title': 'Tab to run',
'productType': 'tab_to_run', 'productType': 'tab_to_run',
'imagePath': Assets.tabToRun, 'imagePath': Assets.tabToRun,
} }

View File

@ -10,7 +10,6 @@ import 'package:syncrow_web/pages/routines/widgets/routine_dialogs/one_gang_swit
import 'package:syncrow_web/pages/routines/widgets/routine_dialogs/three_gang_switch_dialog.dart'; import 'package:syncrow_web/pages/routines/widgets/routine_dialogs/three_gang_switch_dialog.dart';
import 'package:syncrow_web/pages/routines/widgets/routine_dialogs/two_gang_switch_dialog.dart'; import 'package:syncrow_web/pages/routines/widgets/routine_dialogs/two_gang_switch_dialog.dart';
import 'package:syncrow_web/pages/routines/widgets/routine_dialogs/wall_sensor/wall_presence_sensor.dart'; import 'package:syncrow_web/pages/routines/widgets/routine_dialogs/wall_sensor/wall_presence_sensor.dart';
import 'package:syncrow_web/pages/routines/widgets/routine_dialogs/water_heater/water_heater_presence_sensor.dart';
class DeviceDialogHelper { class DeviceDialogHelper {
static Future<Map<String, dynamic>?> showDeviceDialog({ static Future<Map<String, dynamic>?> showDeviceDialog({
@ -127,15 +126,6 @@ class DeviceDialogHelper {
dialogType: dialogType, dialogType: dialogType,
device: data['device'], device: data['device'],
); );
case 'WH':
return WaterHeaterDialogRoutines.showWHFunctionsDialog(
context: context,
functions: functions,
uniqueCustomId: data['uniqueCustomId'],
deviceSelectedFunctions: deviceSelectedFunctions,
dialogType: dialogType,
device: data['device'],
);
default: default:
return null; return null;

View File

@ -162,7 +162,7 @@ class SaveRoutineHelper {
width: 24, width: 24,
height: 24, height: 24,
), ),
title: const Text('Tap to run'), title: const Text('Tab to run'),
), ),
if (state.isAutomation) if (state.isAutomation)
...state.ifItems.map((item) { ...state.ifItems.map((item) {

View File

@ -14,10 +14,6 @@ abstract class ACFunction extends DeviceFunction<AcStatusModel> {
required super.operationName, required super.operationName,
required super.icon, required super.icon,
required this.type, required this.type,
super.step,
super.unit,
super.max,
super.min,
}); });
List<ACOperationalValue> getOperationalValues(); List<ACOperationalValue> getOperationalValues();
@ -79,24 +75,26 @@ class ModeFunction extends ACFunction {
} }
class TempSetFunction extends ACFunction { class TempSetFunction extends ACFunction {
TempSetFunction({ final int min;
required super.deviceId, final int max;
required super.deviceName, final int step;
required super.type,
}) : super( TempSetFunction(
{required super.deviceId, required super.deviceName, required type})
: min = 160,
max = 300,
step = 1,
super(
code: 'temp_set', code: 'temp_set',
operationName: 'Set Temperature', operationName: 'Set Temperature',
icon: Assets.assetsTempreture, icon: Assets.assetsTempreture,
min: 200, type: type,
max: 300,
step: 1,
unit: "°C",
); );
@override @override
List<ACOperationalValue> getOperationalValues() { List<ACOperationalValue> getOperationalValues() {
List<ACOperationalValue> values = []; List<ACOperationalValue> values = [];
for (int temp = min!.toInt(); temp <= max!; temp += step!.toInt()) { for (int temp = min; temp <= max; temp += step) {
values.add(ACOperationalValue( values.add(ACOperationalValue(
icon: Assets.assetsTempreture, icon: Assets.assetsTempreture,
description: "${temp / 10}°C", description: "${temp / 10}°C",
@ -106,6 +104,7 @@ class TempSetFunction extends ACFunction {
return values; return values;
} }
} }
class LevelFunction extends ACFunction { class LevelFunction extends ACFunction {
LevelFunction( LevelFunction(
{required super.deviceId, required super.deviceName, required type}) {required super.deviceId, required super.deviceName, required type})
@ -167,10 +166,9 @@ class ChildLockFunction extends ACFunction {
} }
class CurrentTempFunction extends ACFunction { class CurrentTempFunction extends ACFunction {
final double min; final int min;
final double max; final int max;
final double step; final int step;
final String unit = "°C";
CurrentTempFunction( CurrentTempFunction(
{required super.deviceId, required super.deviceName, required type}) {required super.deviceId, required super.deviceName, required type})
@ -187,7 +185,7 @@ class CurrentTempFunction extends ACFunction {
@override @override
List<ACOperationalValue> getOperationalValues() { List<ACOperationalValue> getOperationalValues() {
List<ACOperationalValue> values = []; List<ACOperationalValue> values = [];
for (int temp = min.toInt(); temp <= max; temp += step.toInt()) { for (int temp = min; temp <= max; temp += step) {
values.add(ACOperationalValue( values.add(ACOperationalValue(
icon: Assets.currentTemp, icon: Assets.currentTemp,
description: "${temp / 10}°C", description: "${temp / 10}°C",

View File

@ -6,12 +6,10 @@ class CpsOperationalValue {
final String description; final String description;
final dynamic value; final dynamic value;
CpsOperationalValue({ CpsOperationalValue({
required this.icon, required this.icon,
required this.description, required this.description,
required this.value, required this.value,
}); });
} }
@ -96,9 +94,9 @@ final class CpsSensitivityFunction extends CpsFunctions {
icon: Assets.sensitivity, icon: Assets.sensitivity,
); );
final double min; final int min;
final double max; final int max;
final double step; final int step;
static const _images = <String>[ static const _images = <String>[
Assets.sensitivityFeature1, Assets.sensitivityFeature1,
@ -117,10 +115,10 @@ final class CpsSensitivityFunction extends CpsFunctions {
@override @override
List<CpsOperationalValue> getOperationalValues() { List<CpsOperationalValue> getOperationalValues() {
final values = <CpsOperationalValue>[]; final values = <CpsOperationalValue>[];
for (var value = min; value <= max; value += step.toInt()) { for (var value = min; value <= max; value += step) {
values.add( values.add(
CpsOperationalValue( CpsOperationalValue(
icon: _images[value.toInt()], icon: _images[value],
description: '$value', description: '$value',
value: value, value: value,
), ),
@ -144,9 +142,9 @@ final class CpsMovingSpeedFunction extends CpsFunctions {
icon: Assets.speedoMeter, icon: Assets.speedoMeter,
); );
final double min; final int min;
final double max; final int max;
final double step; final int step;
@override @override
List<CpsOperationalValue> getOperationalValues() { List<CpsOperationalValue> getOperationalValues() {
@ -175,9 +173,9 @@ final class CpsSpatialStaticValueFunction extends CpsFunctions {
icon: Assets.spatialStaticValue, icon: Assets.spatialStaticValue,
); );
final double min; final int min;
final double max; final int max;
final double step; final int step;
@override @override
List<CpsOperationalValue> getOperationalValues() { List<CpsOperationalValue> getOperationalValues() {
@ -206,9 +204,9 @@ final class CpsSpatialMotionValueFunction extends CpsFunctions {
icon: Assets.spatialMotionValue, icon: Assets.spatialMotionValue,
); );
final double min; final int min;
final double max; final int max;
final double step; final int step;
@override @override
List<CpsOperationalValue> getOperationalValues() { List<CpsOperationalValue> getOperationalValues() {
@ -377,9 +375,9 @@ final class CpsPresenceJudgementThrsholdFunction extends CpsFunctions {
icon: Assets.presenceJudgementThrshold, icon: Assets.presenceJudgementThrshold,
); );
final double min; final int min;
final double max; final int max;
final double step; final int step;
@override @override
List<CpsOperationalValue> getOperationalValues() { List<CpsOperationalValue> getOperationalValues() {
@ -408,9 +406,9 @@ final class CpsMotionAmplitudeTriggerThresholdFunction extends CpsFunctions {
icon: Assets.presenceJudgementThrshold, icon: Assets.presenceJudgementThrshold,
); );
final double min; final int min;
final double max; final int max;
final double step; final int step;
@override @override
List<CpsOperationalValue> getOperationalValues() { List<CpsOperationalValue> getOperationalValues() {

View File

@ -4,11 +4,6 @@ abstract class DeviceFunction<T> {
final String code; final String code;
final String operationName; final String operationName;
final String icon; final String icon;
final double? step;
final String? unit;
final double? max;
final double? min;
DeviceFunction({ DeviceFunction({
required this.deviceId, required this.deviceId,
@ -16,10 +11,6 @@ abstract class DeviceFunction<T> {
required this.code, required this.code,
required this.operationName, required this.operationName,
required this.icon, required this.icon,
this.step,
this.unit,
this.max,
this.min,
}); });
} }
@ -31,10 +22,6 @@ class DeviceFunctionData {
final dynamic value; final dynamic value;
final String? condition; final String? condition;
final String? valueDescription; final String? valueDescription;
final double? step;
final String? unit;
final double? max;
final double? min;
DeviceFunctionData({ DeviceFunctionData({
required this.entityId, required this.entityId,
@ -44,10 +31,6 @@ class DeviceFunctionData {
required this.value, required this.value,
this.condition, this.condition,
this.valueDescription, this.valueDescription,
this.step,
this.unit,
this.max,
this.min,
}); });
Map<String, dynamic> toJson() { Map<String, dynamic> toJson() {
@ -59,10 +42,6 @@ class DeviceFunctionData {
'value': value, 'value': value,
if (condition != null) 'condition': condition, if (condition != null) 'condition': condition,
if (valueDescription != null) 'valueDescription': valueDescription, if (valueDescription != null) 'valueDescription': valueDescription,
if (step != null) 'step': step,
if (unit != null) 'unit': unit,
if (max != null) 'max': max,
if (min != null) 'min': min,
}; };
} }
@ -75,10 +54,6 @@ class DeviceFunctionData {
value: json['value'], value: json['value'],
condition: json['condition'], condition: json['condition'],
valueDescription: json['valueDescription'], valueDescription: json['valueDescription'],
step: json['step']?.toDouble(),
unit: json['unit'],
max: json['max']?.toDouble(),
min: json['min']?.toDouble(),
); );
} }
@ -93,11 +68,7 @@ class DeviceFunctionData {
other.operationName == operationName && other.operationName == operationName &&
other.value == value && other.value == value &&
other.condition == condition && other.condition == condition &&
other.valueDescription == valueDescription && other.valueDescription == valueDescription;
other.step == step &&
other.unit == unit &&
other.max == max &&
other.min == min;
} }
@override @override
@ -108,10 +79,6 @@ class DeviceFunctionData {
operationName.hashCode ^ operationName.hashCode ^
value.hashCode ^ value.hashCode ^
condition.hashCode ^ condition.hashCode ^
valueDescription.hashCode ^ valueDescription.hashCode;
step.hashCode ^
unit.hashCode ^
max.hashCode ^
min.hashCode;
} }
} }

View File

@ -20,11 +20,12 @@ abstract class FlushFunctions
} }
class FlushPresenceDelayFunction extends FlushFunctions { class FlushPresenceDelayFunction extends FlushFunctions {
final int min;
FlushPresenceDelayFunction({ FlushPresenceDelayFunction({
required super.deviceId, required super.deviceId,
required super.deviceName, required super.deviceName,
required super.type, required super.type,
}) : }) : min = 0,
super( super(
code: FlushMountedPresenceSensorModel.codePresenceState, code: FlushMountedPresenceSensorModel.codePresenceState,
operationName: 'Presence State', operationName: 'Presence State',
@ -49,9 +50,9 @@ class FlushPresenceDelayFunction extends FlushFunctions {
} }
class FlushSensiReduceFunction extends FlushFunctions { class FlushSensiReduceFunction extends FlushFunctions {
final double min; final int min;
final double max; final int max;
final double step; final int step;
FlushSensiReduceFunction({ FlushSensiReduceFunction({
required super.deviceId, required super.deviceId,
@ -79,8 +80,8 @@ class FlushSensiReduceFunction extends FlushFunctions {
} }
class FlushNoneDelayFunction extends FlushFunctions { class FlushNoneDelayFunction extends FlushFunctions {
final double min; final int min;
final double max; final int max;
final String unit; final String unit;
FlushNoneDelayFunction({ FlushNoneDelayFunction({
@ -109,9 +110,9 @@ class FlushNoneDelayFunction extends FlushFunctions {
} }
class FlushIlluminanceFunction extends FlushFunctions { class FlushIlluminanceFunction extends FlushFunctions {
final double min; final int min;
final double max; final int max;
final double step; final int step;
FlushIlluminanceFunction({ FlushIlluminanceFunction({
required super.deviceId, required super.deviceId,
@ -129,7 +130,7 @@ class FlushIlluminanceFunction extends FlushFunctions {
@override @override
List<FlushOperationalValue> getOperationalValues() { List<FlushOperationalValue> getOperationalValues() {
List<FlushOperationalValue> values = []; List<FlushOperationalValue> values = [];
for (int lux = min.toInt(); lux <= max; lux += step.toInt()) { for (int lux = min; lux <= max; lux += step) {
values.add(FlushOperationalValue( values.add(FlushOperationalValue(
icon: Assets.IlluminanceIcon, icon: Assets.IlluminanceIcon,
description: "$lux Lux", description: "$lux Lux",
@ -141,9 +142,9 @@ class FlushIlluminanceFunction extends FlushFunctions {
} }
class FlushOccurDistReduceFunction extends FlushFunctions { class FlushOccurDistReduceFunction extends FlushFunctions {
final double min; final int min;
final double max; final int max;
final double step; final int step;
FlushOccurDistReduceFunction({ FlushOccurDistReduceFunction({
required super.deviceId, required super.deviceId,
@ -172,9 +173,9 @@ class FlushOccurDistReduceFunction extends FlushFunctions {
// ==== then functions ==== // ==== then functions ====
class FlushSensitivityFunction extends FlushFunctions { class FlushSensitivityFunction extends FlushFunctions {
final double min; final int min;
final double max; final int max;
final double step; final int step;
FlushSensitivityFunction({ FlushSensitivityFunction({
required super.deviceId, required super.deviceId,
@ -202,9 +203,9 @@ class FlushSensitivityFunction extends FlushFunctions {
} }
class FlushNearDetectionFunction extends FlushFunctions { class FlushNearDetectionFunction extends FlushFunctions {
final double min; final int min;
final double max; final double max;
final double step; final int step;
final String unit; final String unit;
FlushNearDetectionFunction({ FlushNearDetectionFunction({
@ -224,7 +225,7 @@ class FlushNearDetectionFunction extends FlushFunctions {
@override @override
List<FlushOperationalValue> getOperationalValues() { List<FlushOperationalValue> getOperationalValues() {
final values = <FlushOperationalValue>[]; final values = <FlushOperationalValue>[];
for (var value = min.toDouble(); value <= max; value += step) { for (var value = min; value <= max; value += step) {
values.add(FlushOperationalValue( values.add(FlushOperationalValue(
icon: Assets.nobodyTime, icon: Assets.nobodyTime,
description: '$value $unit', description: '$value $unit',
@ -236,9 +237,9 @@ class FlushNearDetectionFunction extends FlushFunctions {
} }
class FlushMaxDetectDistFunction extends FlushFunctions { class FlushMaxDetectDistFunction extends FlushFunctions {
final double min; final int min;
final double max; final int max;
final double step; final int step;
final String unit; final String unit;
FlushMaxDetectDistFunction({ FlushMaxDetectDistFunction({
@ -258,7 +259,7 @@ class FlushMaxDetectDistFunction extends FlushFunctions {
@override @override
List<FlushOperationalValue> getOperationalValues() { List<FlushOperationalValue> getOperationalValues() {
final values = <FlushOperationalValue>[]; final values = <FlushOperationalValue>[];
for (var value = min; value <= max; value += step.toInt()) { for (var value = min; value <= max; value += step) {
values.add(FlushOperationalValue( values.add(FlushOperationalValue(
icon: Assets.nobodyTime, icon: Assets.nobodyTime,
description: '$value $unit', description: '$value $unit',
@ -270,9 +271,9 @@ class FlushMaxDetectDistFunction extends FlushFunctions {
} }
class FlushTargetConfirmTimeFunction extends FlushFunctions { class FlushTargetConfirmTimeFunction extends FlushFunctions {
final double min; final int min;
final double max; final int max;
final double step; final int step;
final String unit; final String unit;
FlushTargetConfirmTimeFunction({ FlushTargetConfirmTimeFunction({
@ -292,7 +293,7 @@ class FlushTargetConfirmTimeFunction extends FlushFunctions {
@override @override
List<FlushOperationalValue> getOperationalValues() { List<FlushOperationalValue> getOperationalValues() {
final values = <FlushOperationalValue>[]; final values = <FlushOperationalValue>[];
for (var value = min.toDouble(); value <= max; value += step) { for (var value = min; value <= max; value += step) {
values.add(FlushOperationalValue( values.add(FlushOperationalValue(
icon: Assets.nobodyTime, icon: Assets.nobodyTime,
description: '$value $unit', description: '$value $unit',
@ -304,9 +305,9 @@ class FlushTargetConfirmTimeFunction extends FlushFunctions {
} }
class FlushDisappeDelayFunction extends FlushFunctions { class FlushDisappeDelayFunction extends FlushFunctions {
final double min; final int min;
final double max; final int max;
final double step; final int step;
final String unit; final String unit;
FlushDisappeDelayFunction({ FlushDisappeDelayFunction({
@ -326,7 +327,7 @@ class FlushDisappeDelayFunction extends FlushFunctions {
@override @override
List<FlushOperationalValue> getOperationalValues() { List<FlushOperationalValue> getOperationalValues() {
final values = <FlushOperationalValue>[]; final values = <FlushOperationalValue>[];
for (var value = min.toDouble(); value <= max; value += step) { for (var value = min; value <= max; value += step) {
values.add(FlushOperationalValue( values.add(FlushOperationalValue(
icon: Assets.nobodyTime, icon: Assets.nobodyTime,
description: '$value $unit', description: '$value $unit',
@ -338,9 +339,9 @@ class FlushDisappeDelayFunction extends FlushFunctions {
} }
class FlushIndentLevelFunction extends FlushFunctions { class FlushIndentLevelFunction extends FlushFunctions {
final double min; final int min;
final double max; final int max;
final double step; final int step;
final String unit; final String unit;
FlushIndentLevelFunction({ FlushIndentLevelFunction({
@ -360,7 +361,7 @@ class FlushIndentLevelFunction extends FlushFunctions {
@override @override
List<FlushOperationalValue> getOperationalValues() { List<FlushOperationalValue> getOperationalValues() {
final values = <FlushOperationalValue>[]; final values = <FlushOperationalValue>[];
for (var value = min.toDouble(); value <= max; value += step) { for (var value = min; value <= max; value += step) {
values.add(FlushOperationalValue( values.add(FlushOperationalValue(
icon: Assets.nobodyTime, icon: Assets.nobodyTime,
description: '$value $unit', description: '$value $unit',
@ -372,9 +373,9 @@ class FlushIndentLevelFunction extends FlushFunctions {
} }
class FlushTriggerLevelFunction extends FlushFunctions { class FlushTriggerLevelFunction extends FlushFunctions {
final double min; final int min;
final double max; final int max;
final double step; final int step;
final String unit; final String unit;
FlushTriggerLevelFunction({ FlushTriggerLevelFunction({
@ -394,7 +395,7 @@ class FlushTriggerLevelFunction extends FlushFunctions {
@override @override
List<FlushOperationalValue> getOperationalValues() { List<FlushOperationalValue> getOperationalValues() {
final values = <FlushOperationalValue>[]; final values = <FlushOperationalValue>[];
for (var value = min.toDouble(); value <= max; value += step) { for (var value = min; value <= max; value += step) {
values.add(FlushOperationalValue( values.add(FlushOperationalValue(
icon: Assets.nobodyTime, icon: Assets.nobodyTime,
description: '$value $unit', description: '$value $unit',

View File

@ -1,130 +0,0 @@
import 'package:syncrow_web/pages/device_managment/water_heater/models/water_heater_status_model.dart';
import 'package:syncrow_web/pages/routines/models/device_functions.dart';
import 'package:syncrow_web/pages/routines/models/water_heater/water_heater_operational_value.dart';
import 'package:syncrow_web/utils/constants/assets.dart';
abstract class WaterHeaterFunctions
extends DeviceFunction<WaterHeaterStatusModel> {
final String type;
WaterHeaterFunctions({
required super.deviceId,
required super.deviceName,
required super.code,
required super.operationName,
required super.icon,
required this.type,
});
List<WaterHeaterOperationalValue> getOperationalValues();
}
class WHRestartStatusFunction extends WaterHeaterFunctions {
WHRestartStatusFunction({
required super.deviceId,
required super.deviceName,
required super.type,
}) : super(
code: 'relay_status',
operationName: 'Restart Status',
icon: Assets.refreshStatusIcon,
);
@override
List<WaterHeaterOperationalValue> getOperationalValues() {
return [
WaterHeaterOperationalValue(
icon: Assets.assetsAcPowerOFF,
description: 'Power OFF',
value: "off",
),
WaterHeaterOperationalValue(
icon: Assets.assetsAcPower,
description: 'Power ON',
value: 'on',
),
WaterHeaterOperationalValue(
icon: Assets.refreshStatusIcon,
description: "Restart Memory",
value: 'memory',
),
];
}
}
class WHSwitchFunction extends WaterHeaterFunctions {
WHSwitchFunction({
required super.deviceId,
required super.deviceName,
required super.type,
}) : super(
code: 'switch_1',
operationName: 'Switch',
icon: Assets.assetsAcPower,
);
@override
List<WaterHeaterOperationalValue> getOperationalValues() {
return [
WaterHeaterOperationalValue(
icon: Assets.assetsAcPower,
description: 'ON',
value: true,
),
WaterHeaterOperationalValue(
icon: Assets.assetsAcPowerOFF,
description: 'OFF',
value: false,
),
];
}
}
class TimerConfirmTimeFunction extends WaterHeaterFunctions {
TimerConfirmTimeFunction({
required super.deviceId,
required super.deviceName,
required super.type,
}) : super(
code: 'countdown_1',
operationName: 'Timer',
icon: Assets.targetConfirmTimeIcon,
);
@override
List<WaterHeaterOperationalValue> getOperationalValues() {
final values = <WaterHeaterOperationalValue>[];
return values;
}
}
class BacklightFunction extends WaterHeaterFunctions {
BacklightFunction({
required super.deviceId,
required super.deviceName,
required super.type,
}) :
super(
code: 'switch_backlight',
operationName: 'Backlight',
icon: Assets.indicator,
);
@override
List<WaterHeaterOperationalValue> getOperationalValues() {
return [
WaterHeaterOperationalValue(
icon: Assets.assetsAcPower,
description: 'ON',
value: true,
),
WaterHeaterOperationalValue(
icon: Assets.assetsAcPowerOFF,
description: 'OFF',
value: false,
),
];
}
}

View File

@ -1,11 +0,0 @@
class WaterHeaterOperationalValue {
final String icon;
final String description;
final dynamic value;
WaterHeaterOperationalValue({
required this.icon,
required this.description,
required this.value,
});
}

View File

@ -4,7 +4,7 @@ import 'package:syncrow_web/pages/routines/models/wps/wps_operational_value.dart
import 'package:syncrow_web/utils/constants/assets.dart'; import 'package:syncrow_web/utils/constants/assets.dart';
abstract class WpsFunctions extends DeviceFunction<WallSensorModel> { abstract class WpsFunctions extends DeviceFunction<WallSensorModel> {
final String type; final String type;
WpsFunctions({ WpsFunctions({
required super.deviceId, required super.deviceId,
@ -13,10 +13,6 @@ abstract class WpsFunctions extends DeviceFunction<WallSensorModel> {
required super.operationName, required super.operationName,
required super.icon, required super.icon,
required this.type, required this.type,
super.step,
super.unit,
super.max,
super.min,
}); });
List<WpsOperationalValue> getOperationalValues(); List<WpsOperationalValue> getOperationalValues();
@ -24,13 +20,9 @@ abstract class WpsFunctions extends DeviceFunction<WallSensorModel> {
// For far_detection (75-600cm in 75cm steps) // For far_detection (75-600cm in 75cm steps)
class FarDetectionFunction extends WpsFunctions { class FarDetectionFunction extends WpsFunctions {
final int min;
final double min; final int max;
@override final int step;
final double max;
@override
final double step;
@override
final String unit; final String unit;
FarDetectionFunction( FarDetectionFunction(
@ -49,7 +41,7 @@ class FarDetectionFunction extends WpsFunctions {
@override @override
List<WpsOperationalValue> getOperationalValues() { List<WpsOperationalValue> getOperationalValues() {
final values = <WpsOperationalValue>[]; final values = <WpsOperationalValue>[];
for (var value = min; value <= max; value += step.toInt()) { for (var value = min; value <= max; value += step) {
values.add(WpsOperationalValue( values.add(WpsOperationalValue(
icon: Assets.currentDistanceIcon, icon: Assets.currentDistanceIcon,
description: '$value $unit', description: '$value $unit',
@ -62,9 +54,9 @@ class FarDetectionFunction extends WpsFunctions {
// For presence_time (0-65535 minutes) // For presence_time (0-65535 minutes)
class PresenceTimeFunction extends WpsFunctions { class PresenceTimeFunction extends WpsFunctions {
final double min; final int min;
final double max; final int max;
final double step; final int step;
final String unit; final String unit;
PresenceTimeFunction( PresenceTimeFunction(
@ -94,9 +86,9 @@ class PresenceTimeFunction extends WpsFunctions {
// For motion_sensitivity_value (1-5 levels) // For motion_sensitivity_value (1-5 levels)
class MotionSensitivityFunction extends WpsFunctions { class MotionSensitivityFunction extends WpsFunctions {
final double min; final int min;
final double max; final int max;
final double step; final int step;
MotionSensitivityFunction( MotionSensitivityFunction(
{required super.deviceId, required super.deviceName, required type}) {required super.deviceId, required super.deviceName, required type})
@ -124,9 +116,9 @@ class MotionSensitivityFunction extends WpsFunctions {
} }
class MotionLessSensitivityFunction extends WpsFunctions { class MotionLessSensitivityFunction extends WpsFunctions {
final double min; final int min;
final double max; final int max;
final double step; final int step;
MotionLessSensitivityFunction( MotionLessSensitivityFunction(
{required super.deviceId, required super.deviceName, required type}) {required super.deviceId, required super.deviceName, required type})
@ -179,8 +171,8 @@ class IndicatorFunction extends WpsFunctions {
} }
class NoOneTimeFunction extends WpsFunctions { class NoOneTimeFunction extends WpsFunctions {
final double min; final int min;
final double max; final int max;
final String unit; final String unit;
NoOneTimeFunction( NoOneTimeFunction(
@ -233,9 +225,9 @@ class PresenceStateFunction extends WpsFunctions {
} }
class CurrentDistanceFunction extends WpsFunctions { class CurrentDistanceFunction extends WpsFunctions {
final double min; final int min;
final double max; final int max;
final double step; final int step;
CurrentDistanceFunction( CurrentDistanceFunction(
{required super.deviceId, required super.deviceName, required type}) {required super.deviceId, required super.deviceName, required type})
@ -252,10 +244,11 @@ class CurrentDistanceFunction extends WpsFunctions {
@override @override
List<WpsOperationalValue> getOperationalValues() { List<WpsOperationalValue> getOperationalValues() {
List<WpsOperationalValue> values = []; List<WpsOperationalValue> values = [];
for (int cm = min.toInt(); cm <= max; cm += step.toInt()) { for (int cm = min; cm <= max; cm += step) {
values.add(WpsOperationalValue( values.add(WpsOperationalValue(
icon: Assets.assetsTempreture, icon: Assets.assetsTempreture,
description: "${cm}CM", description: "${cm}CM",
value: cm, value: cm,
)); ));
} }
@ -264,9 +257,9 @@ class CurrentDistanceFunction extends WpsFunctions {
} }
class IlluminanceValueFunction extends WpsFunctions { class IlluminanceValueFunction extends WpsFunctions {
final double min; final int min;
final double max; final int max;
final double step; final int step;
IlluminanceValueFunction({ IlluminanceValueFunction({
required super.deviceId, required super.deviceId,
@ -284,7 +277,7 @@ class IlluminanceValueFunction extends WpsFunctions {
@override @override
List<WpsOperationalValue> getOperationalValues() { List<WpsOperationalValue> getOperationalValues() {
List<WpsOperationalValue> values = []; List<WpsOperationalValue> values = [];
for (int lux = min.toInt(); lux <= max; lux += step.toInt()) { for (int lux = min; lux <= max; lux += step) {
values.add(WpsOperationalValue( values.add(WpsOperationalValue(
icon: Assets.IlluminanceIcon, icon: Assets.IlluminanceIcon,
description: "$lux Lux", description: "$lux Lux",

View File

@ -29,11 +29,11 @@ class ConditionsRoutinesDevicesView extends StatelessWidget {
children: [ children: [
DraggableCard( DraggableCard(
imagePath: Assets.tabToRun, imagePath: Assets.tabToRun,
title: 'Tap to run', title: 'Tab to run',
deviceData: { deviceData: {
'deviceId': 'tab_to_run', 'deviceId': 'tab_to_run',
'type': 'trigger', 'type': 'trigger',
'name': 'Tap to run', 'name': 'Tab to run',
}, },
), ),
DraggableCard( DraggableCard(

View File

@ -1,297 +0,0 @@
import 'dart:math';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:syncrow_web/pages/routines/widgets/condition_toggle.dart';
import 'package:syncrow_web/pages/routines/widgets/slider_value_selector.dart';
import 'package:syncrow_web/utils/color_manager.dart';
import 'package:syncrow_web/utils/extension/build_context_x.dart';
class CustomRoutinesTextbox extends StatefulWidget {
final String? currentCondition;
final String dialogType;
final (double, double) sliderRange;
final dynamic displayedValue;
final dynamic initialValue;
final void Function(String condition) onConditionChanged;
final void Function(double value) onTextChanged;
final String unit;
final double dividendOfRange;
final double stepIncreaseAmount;
final bool withSpecialChar;
const CustomRoutinesTextbox({
required this.dialogType,
required this.sliderRange,
required this.displayedValue,
required this.initialValue,
required this.onConditionChanged,
required this.onTextChanged,
required this.currentCondition,
required this.unit,
required this.dividendOfRange,
required this.stepIncreaseAmount,
required this.withSpecialChar,
super.key,
});
@override
State<CustomRoutinesTextbox> createState() => _CustomRoutinesTextboxState();
}
class _CustomRoutinesTextboxState extends State<CustomRoutinesTextbox> {
late final TextEditingController _controller;
bool hasError = false;
String? errorMessage;
int getDecimalPlaces(double step) {
String stepStr = step.toString();
if (stepStr.contains('.')) {
List<String> parts = stepStr.split('.');
String decimalPart = parts[1];
decimalPart = decimalPart.replaceAll(RegExp(r'0+$'), '');
return decimalPart.isEmpty ? 0 : decimalPart.length;
} else {
return 0;
}
}
@override
void initState() {
super.initState();
int decimalPlaces = getDecimalPlaces(widget.stepIncreaseAmount);
double initialValue;
if (widget.initialValue != null &&
widget.initialValue is num &&
(widget.initialValue as num) == 0) {
initialValue = 0.0;
} else {
initialValue = double.tryParse(widget.displayedValue) ?? 0.0;
}
_controller = TextEditingController(
text: initialValue.toStringAsFixed(decimalPlaces),
);
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
void _validateInput(String value) {
final doubleValue = double.tryParse(value);
if (doubleValue == null) {
setState(() {
errorMessage = "Invalid number";
hasError = true;
});
return;
}
final min = widget.sliderRange.$1;
final max = widget.sliderRange.$2;
if (doubleValue < min) {
setState(() {
errorMessage = "Value must be at least $min";
hasError = true;
});
} else if (doubleValue > max) {
setState(() {
errorMessage = "Value must be at most $max";
hasError = true;
});
} else {
int decimalPlaces = getDecimalPlaces(widget.stepIncreaseAmount);
int factor = pow(10, decimalPlaces).toInt();
int scaledStep = (widget.stepIncreaseAmount * factor).round();
int scaledValue = (doubleValue * factor).round();
if (scaledValue % scaledStep != 0) {
setState(() {
errorMessage = "must be a multiple of ${widget.stepIncreaseAmount}";
hasError = true;
});
} else {
setState(() {
errorMessage = null;
hasError = false;
});
}
}
}
@override
void didUpdateWidget(CustomRoutinesTextbox oldWidget) {
super.didUpdateWidget(oldWidget);
if (widget.initialValue != oldWidget.initialValue) {
if (widget.initialValue != null &&
widget.initialValue is num &&
(widget.initialValue as num) == 0) {
int decimalPlaces = getDecimalPlaces(widget.stepIncreaseAmount);
_controller.text = 0.0.toStringAsFixed(decimalPlaces);
}
}
}
void _correctAndUpdateValue(String value) {
final doubleValue = double.tryParse(value) ?? 0.0;
int decimalPlaces = getDecimalPlaces(widget.stepIncreaseAmount);
double rounded = (doubleValue / widget.stepIncreaseAmount).round() *
widget.stepIncreaseAmount;
rounded = rounded.clamp(widget.sliderRange.$1, widget.sliderRange.$2);
rounded = double.parse(rounded.toStringAsFixed(decimalPlaces));
setState(() {
hasError = false;
errorMessage = null;
});
_controller.text = rounded.toStringAsFixed(decimalPlaces);
_controller.selection = TextSelection.fromPosition(
TextPosition(offset: _controller.text.length),
);
widget.onTextChanged(rounded);
}
@override
Widget build(BuildContext context) {
int decimalPlaces = getDecimalPlaces(widget.stepIncreaseAmount);
List<TextInputFormatter> formatters = [];
if (decimalPlaces == 0) {
formatters.add(FilteringTextInputFormatter.digitsOnly);
} else {
formatters.add(FilteringTextInputFormatter.allow(
RegExp(r'^\d*\.?\d{0,' + decimalPlaces.toString() + r'}$'),
));
}
formatters.add(RangeInputFormatter(
min: widget.sliderRange.$1,
max: widget.sliderRange.$2,
));
return Column(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.center,
children: [
if (widget.dialogType == 'IF')
ConditionToggle(
currentCondition: widget.currentCondition,
onChanged: widget.onConditionChanged,
),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 35, vertical: 2),
child: Row(
crossAxisAlignment: CrossAxisAlignment.end,
mainAxisAlignment: MainAxisAlignment.end,
children: [
Text(
'Step: ${widget.stepIncreaseAmount}',
style: context.textTheme.bodySmall?.copyWith(
color: ColorsManager.grayColor,
fontSize: 10,
fontWeight: FontWeight.w400,
),
),
],
),
),
Center(
child: Container(
width: 170,
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 8),
decoration: BoxDecoration(
color: const Color(0xFFF8F8F8),
borderRadius: BorderRadius.circular(20),
border: hasError
? Border.all(color: Colors.red, width: 1)
: Border.all(
color: ColorsManager.lightGrayBorderColor, width: 1),
boxShadow: [
BoxShadow(
color: ColorsManager.blackColor.withOpacity(0.05),
blurRadius: 8,
offset: const Offset(0, 4),
),
],
),
child: Row(
children: [
Expanded(
child: TextFormField(
controller: _controller,
style: context.textTheme.bodyLarge?.copyWith(
fontSize: 20,
fontWeight: FontWeight.bold,
color: ColorsManager.blackColor,
),
keyboardType: TextInputType.number,
inputFormatters: widget.withSpecialChar == true
? [FilteringTextInputFormatter.digitsOnly]
: null,
decoration: const InputDecoration(
border: InputBorder.none,
isDense: true,
contentPadding: EdgeInsets.zero,
),
onChanged: _validateInput,
onFieldSubmitted: _correctAndUpdateValue,
onTapOutside: (_) =>
_correctAndUpdateValue(_controller.text),
),
),
const SizedBox(width: 12),
Text(
widget.unit,
style: context.textTheme.bodyMedium?.copyWith(
fontSize: 20,
fontWeight: FontWeight.bold,
color: ColorsManager.vividBlue,
),
),
],
),
),
),
if (errorMessage != null)
Padding(
padding: const EdgeInsets.only(top: 2.0),
child: Text(
errorMessage!,
style: context.textTheme.bodySmall?.copyWith(
color: Colors.red,
fontSize: 10,
),
),
),
const SizedBox(height: 16),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 32),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
'Min. ${widget.sliderRange.$1.toInt()}${widget.unit}',
style: context.textTheme.bodySmall?.copyWith(
color: ColorsManager.grayColor,
fontSize: 10,
fontWeight: FontWeight.w400,
),
),
Text(
'Max. ${widget.sliderRange.$2.toInt()}${widget.unit}',
style: context.textTheme.bodySmall?.copyWith(
color: ColorsManager.grayColor,
fontSize: 10,
fontWeight: FontWeight.w400,
),
),
],
),
),
const SizedBox(height: 16),
],
);
}
}

View File

@ -43,7 +43,7 @@ class IfContainer extends StatelessWidget {
children: [ children: [
DraggableCard( DraggableCard(
imagePath: Assets.tabToRun, imagePath: Assets.tabToRun,
title: 'Tap to run', title: 'Tab to run',
deviceData: {}, deviceData: {},
), ),
], ],
@ -76,8 +76,7 @@ class IfContainer extends StatelessWidget {
'WPS', 'WPS',
'GW', 'GW',
'CPS', 'CPS',
'NCPS', 'NCPS'
'WH',
].contains(state.ifItems[index] ].contains(state.ifItems[index]
['productType'])) { ['productType'])) {
@ -137,7 +136,7 @@ class IfContainer extends StatelessWidget {
context context
.read<RoutineBloc>() .read<RoutineBloc>()
.add(AddToIfContainer(mutableData, false)); .add(AddToIfContainer(mutableData, false));
} else if (!['AC', '1G', '2G', '3G', 'WPS', 'GW', 'CPS', 'NCPS','WH'] } else if (!['AC', '1G', '2G', '3G', 'WPS', 'GW', 'CPS', 'NCPS']
.contains(mutableData['productType'])) { .contains(mutableData['productType'])) {
context context
.read<RoutineBloc>() .read<RoutineBloc>()

View File

@ -26,7 +26,7 @@ class FetchRoutineScenesAutomation extends StatelessWidget
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: [ children: [
_buildListTitle(context, "Scenes (Tap to Run)"), _buildListTitle(context, "Scenes (Tab to Run)"),
const SizedBox(height: 10), const SizedBox(height: 10),
Visibility( Visibility(
visible: state.scenes.isNotEmpty, visible: state.scenes.isNotEmpty,

View File

@ -25,8 +25,7 @@ class _RoutineDevicesState extends State<RoutineDevices> {
'WPS', 'WPS',
'GW', 'GW',
'CPS', 'CPS',
'NCPS', 'NCPS'
'WH',
}; };
@override @override

View File

@ -7,7 +7,6 @@ import 'package:syncrow_web/pages/routines/bloc/routine_bloc/routine_bloc.dart';
import 'package:syncrow_web/pages/routines/models/ac/ac_function.dart'; import 'package:syncrow_web/pages/routines/models/ac/ac_function.dart';
import 'package:syncrow_web/pages/routines/models/ac/ac_operational_value.dart'; import 'package:syncrow_web/pages/routines/models/ac/ac_operational_value.dart';
import 'package:syncrow_web/pages/routines/models/device_functions.dart'; import 'package:syncrow_web/pages/routines/models/device_functions.dart';
import 'package:syncrow_web/pages/routines/widgets/custom_routines_textbox.dart';
import 'package:syncrow_web/pages/routines/widgets/dialog_footer.dart'; import 'package:syncrow_web/pages/routines/widgets/dialog_footer.dart';
import 'package:syncrow_web/pages/routines/widgets/dialog_header.dart'; import 'package:syncrow_web/pages/routines/widgets/dialog_header.dart';
import 'package:syncrow_web/pages/routines/widgets/routine_dialogs/helpers/routine_tap_function_helper.dart'; import 'package:syncrow_web/pages/routines/widgets/routine_dialogs/helpers/routine_tap_function_helper.dart';
@ -77,20 +76,24 @@ class ACHelper {
context: context, context: context,
acFunctions: acFunctions, acFunctions: acFunctions,
device: device, device: device,
onFunctionSelected: onFunctionSelected: (functionCode, operationName) {
(functionCode, operationName) {
RoutineTapFunctionHelper.onTapFunction( RoutineTapFunctionHelper.onTapFunction(
context, context,
functionCode: functionCode, functionCode: functionCode,
functionOperationName: operationName, functionOperationName: operationName,
functionValueDescription: functionValueDescription:
selectedFunctionData.valueDescription, selectedFunctionData.valueDescription,
deviceUuid: device?.uuid, deviceUuid: device?.uuid,
codesToAddIntoFunctionsWithDefaultValue: [ codesToAddIntoFunctionsWithDefaultValue: [
'temp_set', 'temp_set',
'temp_current', 'temp_current',
], ],
defaultValue: 0); defaultValue: functionCode == 'temp_set'
? 200
: functionCode == 'temp_current'
? -100
: 0,
);
}, },
), ),
), ),
@ -203,61 +206,27 @@ class ACHelper {
required String operationName, required String operationName,
bool? removeComparators, bool? removeComparators,
}) { }) {
final selectedFn = final initialVal = selectedFunction == 'temp_set' ? 200 : -100;
acFunctions.firstWhere((f) => f.code == selectedFunction);
if (selectedFunction == 'temp_set' || selectedFunction == 'temp_current') { if (selectedFunction == 'temp_set' || selectedFunction == 'temp_current') {
// Convert stored integer value to display value final initialValue = selectedFunctionData?.value ?? initialVal;
final displayValue = return _buildTemperatureSelector(
(selectedFunctionData?.value ?? selectedFn.min ?? 0) / 10; context: context,
final minValue = selectedFn.min! / 10; initialValue: initialValue,
final maxValue = selectedFn.max! / 10; selectCode: selectedFunction,
return CustomRoutinesTextbox(
withSpecialChar: true,
dividendOfRange: maxValue,
currentCondition: selectedFunctionData?.condition, currentCondition: selectedFunctionData?.condition,
dialogType: selectedFn.type, device: device,
sliderRange: (minValue, maxValue), operationName: operationName,
displayedValue: displayValue.toStringAsFixed(1), selectedFunctionData: selectedFunctionData,
initialValue: displayValue.toDouble(), removeComparators: removeComparators,
unit: selectedFn.unit!,
onConditionChanged: (condition) => context.read<FunctionBloc>().add(
AddFunction(
functionData: DeviceFunctionData(
entityId: device?.uuid ?? '',
functionCode: selectedFunction,
operationName: selectedFn.operationName,
condition: condition,
value: 0,
step: selectedFn.step,
unit: selectedFn.unit,
max: selectedFn.max,
min: selectedFn.min,
),
),
),
onTextChanged: (value) => context.read<FunctionBloc>().add(
AddFunction(
functionData: DeviceFunctionData(
entityId: device?.uuid ?? '',
functionCode: selectedFunction,
operationName: selectedFn.operationName,
value: (value * 10).round(), // Store as integer
condition: selectedFunctionData?.condition,
step: selectedFn.step,
unit: selectedFn.unit,
max: selectedFn.max,
min: selectedFn.min,
),
),
),
stepIncreaseAmount: selectedFn.step! / 10, // Convert step for display
); );
} }
final selectedFn = acFunctions.firstWhere((f) => f.code == selectedFunction);
final values = selectedFn.getOperationalValues();
return _buildOperationalValuesList( return _buildOperationalValuesList(
context: context, context: context,
values: selectedFn.getOperationalValues(), values: values,
selectedValue: selectedFunctionData?.value, selectedValue: selectedFunctionData?.value,
device: device, device: device,
operationName: operationName, operationName: operationName,
@ -266,151 +235,150 @@ class ACHelper {
); );
} }
// /// Build temperature selector for AC functions dialog /// Build temperature selector for AC functions dialog
// static Widget _buildTemperatureSelector({ static Widget _buildTemperatureSelector({
// required BuildContext context, required BuildContext context,
// required dynamic initialValue, required dynamic initialValue,
// required String? currentCondition, required String? currentCondition,
// required String selectCode, required String selectCode,
// AllDevicesModel? device, AllDevicesModel? device,
// required String operationName, required String operationName,
// DeviceFunctionData? selectedFunctionData, DeviceFunctionData? selectedFunctionData,
// bool? removeComparators, bool? removeComparators,
// }) { }) {
// return Column( return Column(
// mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
// children: [ children: [
// if (removeComparators != true) if (removeComparators != true)
// _buildConditionToggle( _buildConditionToggle(
// context, context,
// currentCondition, currentCondition,
// selectCode, selectCode,
// device, device,
// operationName, operationName,
// selectedFunctionData, selectedFunctionData,
// ), ),
// const SizedBox(height: 20), const SizedBox(height: 20),
// _buildTemperatureDisplay( _buildTemperatureDisplay(
// context, context,
// initialValue, initialValue,
// device, device,
// operationName, operationName,
// selectedFunctionData, selectedFunctionData,
// selectCode, selectCode,
// ), ),
// const SizedBox(height: 20), const SizedBox(height: 20),
// _buildTemperatureSlider( _buildTemperatureSlider(
// context, context,
// initialValue, initialValue,
// device, device,
// operationName, operationName,
// selectedFunctionData, selectedFunctionData,
// selectCode, selectCode,
// ), ),
// ], ],
// ); );
// } }
// /// Build condition toggle for AC functions dialog /// Build condition toggle for AC functions dialog
// static Widget _buildConditionToggle( static Widget _buildConditionToggle(
// BuildContext context, BuildContext context,
// String? currentCondition, String? currentCondition,
// String selectCode, String selectCode,
// AllDevicesModel? device, AllDevicesModel? device,
// String operationName, String operationName,
// DeviceFunctionData? selectedFunctionData, DeviceFunctionData? selectedFunctionData,
// // Function(String) onConditionChanged, // Function(String) onConditionChanged,
// ) { ) {
// final conditions = ["<", "==", ">"]; final conditions = ["<", "==", ">"];
// return ToggleButtons( return ToggleButtons(
// onPressed: (int index) { onPressed: (int index) {
// context.read<FunctionBloc>().add( context.read<FunctionBloc>().add(
// AddFunction( AddFunction(
// functionData: DeviceFunctionData( functionData: DeviceFunctionData(
// entityId: device?.uuid ?? '', entityId: device?.uuid ?? '',
// functionCode: selectCode, functionCode: selectCode,
// operationName: operationName, operationName: operationName,
// condition: conditions[index], condition: conditions[index],
// value: selectedFunctionData?.value ?? selectCode == 'temp_set' value: selectedFunctionData?.value ?? selectCode == 'temp_set'
// ? 200 ? 200
// : -100, : -100,
// valueDescription: selectedFunctionData?.valueDescription, valueDescription: selectedFunctionData?.valueDescription,
// ), ),
// ), ),
// ); );
// }, },
// borderRadius: const BorderRadius.all(Radius.circular(8)), borderRadius: const BorderRadius.all(Radius.circular(8)),
// selectedBorderColor: ColorsManager.primaryColorWithOpacity, selectedBorderColor: ColorsManager.primaryColorWithOpacity,
// selectedColor: Colors.white, selectedColor: Colors.white,
// fillColor: ColorsManager.primaryColorWithOpacity, fillColor: ColorsManager.primaryColorWithOpacity,
// color: ColorsManager.primaryColorWithOpacity, color: ColorsManager.primaryColorWithOpacity,
// constraints: const BoxConstraints( constraints: const BoxConstraints(
// minHeight: 40.0, minHeight: 40.0,
// minWidth: 40.0, minWidth: 40.0,
// ), ),
// isSelected: isSelected: conditions.map((c) => c == (currentCondition ?? "==")).toList(),
// conditions.map((c) => c == (currentCondition ?? "==")).toList(), children: conditions.map((c) => Text(c)).toList(),
// children: conditions.map((c) => Text(c)).toList(), );
// ); }
// }
// /// Build temperature display for AC functions dialog /// Build temperature display for AC functions dialog
// static Widget _buildTemperatureDisplay( static Widget _buildTemperatureDisplay(
// BuildContext context, BuildContext context,
// dynamic initialValue, dynamic initialValue,
// AllDevicesModel? device, AllDevicesModel? device,
// String operationName, String operationName,
// DeviceFunctionData? selectedFunctionData, DeviceFunctionData? selectedFunctionData,
// String selectCode, String selectCode,
// ) { ) {
// final initialVal = selectCode == 'temp_set' ? 200 : -100; final initialVal = selectCode == 'temp_set' ? 200 : -100;
// return Container( return Container(
// padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 10), padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 10),
// decoration: BoxDecoration( decoration: BoxDecoration(
// color: ColorsManager.primaryColorWithOpacity.withOpacity(0.1), color: ColorsManager.primaryColorWithOpacity.withOpacity(0.1),
// borderRadius: BorderRadius.circular(10), borderRadius: BorderRadius.circular(10),
// ), ),
// child: Text( child: Text(
// '${(initialValue ?? initialVal) / 10}°C', '${(initialValue ?? initialVal) / 10}°C',
// style: context.textTheme.headlineMedium!.copyWith( style: context.textTheme.headlineMedium!.copyWith(
// color: ColorsManager.primaryColorWithOpacity, color: ColorsManager.primaryColorWithOpacity,
// ), ),
// ), ),
// ); );
// } }
// static Widget _buildTemperatureSlider( static Widget _buildTemperatureSlider(
// BuildContext context, BuildContext context,
// dynamic initialValue, dynamic initialValue,
// AllDevicesModel? device, AllDevicesModel? device,
// String operationName, String operationName,
// DeviceFunctionData? selectedFunctionData, DeviceFunctionData? selectedFunctionData,
// String selectCode, String selectCode,
// ) { ) {
// return Slider( return Slider(
// value: initialValue is int ? initialValue.toDouble() : 200.0, value: initialValue is int ? initialValue.toDouble() : 200.0,
// min: selectCode == 'temp_current' ? -100 : 200, min: selectCode == 'temp_current' ? -100 : 200,
// max: selectCode == 'temp_current' ? 900 : 300, max: selectCode == 'temp_current' ? 900 : 300,
// divisions: 10, divisions: 10,
// label: '${((initialValue ?? 160) / 10).toInt()}°C', label: '${((initialValue ?? 160) / 10).toInt()}°C',
// onChanged: (value) { onChanged: (value) {
// context.read<FunctionBloc>().add( context.read<FunctionBloc>().add(
// AddFunction( AddFunction(
// functionData: DeviceFunctionData( functionData: DeviceFunctionData(
// entityId: device?.uuid ?? '', entityId: device?.uuid ?? '',
// functionCode: selectCode, functionCode: selectCode,
// operationName: operationName, operationName: operationName,
// value: value, value: value,
// condition: selectedFunctionData?.condition, condition: selectedFunctionData?.condition,
// valueDescription: selectedFunctionData?.valueDescription, valueDescription: selectedFunctionData?.valueDescription,
// ), ),
// ), ),
// ); );
// }, },
// ); );
// } }
static Widget _buildOperationalValuesList({ static Widget _buildOperationalValuesList({
required BuildContext context, required BuildContext context,
@ -446,9 +414,7 @@ class ACHelper {
style: context.textTheme.bodyMedium, style: context.textTheme.bodyMedium,
), ),
trailing: Icon( trailing: Icon(
isSelected isSelected ? Icons.radio_button_checked : Icons.radio_button_unchecked,
? Icons.radio_button_checked
: Icons.radio_button_unchecked,
size: 24, size: 24,
color: isSelected color: isSelected
? ColorsManager.primaryColorWithOpacity ? ColorsManager.primaryColorWithOpacity
@ -464,8 +430,7 @@ class ACHelper {
operationName: operationName, operationName: operationName,
value: value.value, value: value.value,
condition: selectedFunctionData?.condition, condition: selectedFunctionData?.condition,
valueDescription: valueDescription: selectedFunctionData?.valueDescription,
selectedFunctionData?.valueDescription,
), ),
), ),
); );

View File

@ -41,8 +41,7 @@ class _CeilingSensorDialogState extends State<CeilingSensorDialog> {
void initState() { void initState() {
super.initState(); super.initState();
_cpsFunctions = _cpsFunctions = widget.functions.whereType<CpsFunctions>().where((function) {
widget.functions.whereType<CpsFunctions>().where((function) {
if (widget.dialogType == 'THEN') { if (widget.dialogType == 'THEN') {
return function.type == 'THEN' || function.type == 'BOTH'; return function.type == 'THEN' || function.type == 'BOTH';
} }
@ -150,7 +149,6 @@ class _CeilingSensorDialogState extends State<CeilingSensorDialog> {
device: widget.device, device: widget.device,
) )
: CpsDialogSliderSelector( : CpsDialogSliderSelector(
step: selectedCpsFunctions.step!,
operations: operations, operations: operations,
selectedFunction: selectedFunction ?? '', selectedFunction: selectedFunction ?? '',
selectedFunctionData: selectedFunctionData, selectedFunctionData: selectedFunctionData,

View File

@ -4,7 +4,6 @@ import 'package:syncrow_web/pages/device_managment/all_devices/models/devices_mo
import 'package:syncrow_web/pages/routines/bloc/functions_bloc/functions_bloc_bloc.dart'; import 'package:syncrow_web/pages/routines/bloc/functions_bloc/functions_bloc_bloc.dart';
import 'package:syncrow_web/pages/routines/models/ceiling_presence_sensor_functions.dart'; import 'package:syncrow_web/pages/routines/models/ceiling_presence_sensor_functions.dart';
import 'package:syncrow_web/pages/routines/models/device_functions.dart'; import 'package:syncrow_web/pages/routines/models/device_functions.dart';
import 'package:syncrow_web/pages/routines/widgets/custom_routines_textbox.dart';
import 'package:syncrow_web/pages/routines/widgets/routine_dialogs/ceiling_sensor/cps_slider_helpers.dart'; import 'package:syncrow_web/pages/routines/widgets/routine_dialogs/ceiling_sensor/cps_slider_helpers.dart';
import 'package:syncrow_web/pages/routines/widgets/slider_value_selector.dart'; import 'package:syncrow_web/pages/routines/widgets/slider_value_selector.dart';
@ -17,7 +16,6 @@ class CpsDialogSliderSelector extends StatelessWidget {
required this.device, required this.device,
required this.operationName, required this.operationName,
required this.dialogType, required this.dialogType,
required this.step,
super.key, super.key,
}); });
@ -28,16 +26,13 @@ class CpsDialogSliderSelector extends StatelessWidget {
final AllDevicesModel? device; final AllDevicesModel? device;
final String operationName; final String operationName;
final String dialogType; final String dialogType;
final double step;
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return CustomRoutinesTextbox( return SliderValueSelector(
withSpecialChar: false,
currentCondition: selectedFunctionData.condition, currentCondition: selectedFunctionData.condition,
dialogType: dialogType, dialogType: dialogType,
sliderRange: sliderRange: CpsSliderHelpers.sliderRange(selectedFunctionData.functionCode),
CpsSliderHelpers.sliderRange(selectedFunctionData.functionCode),
displayedValue: CpsSliderHelpers.displayText( displayedValue: CpsSliderHelpers.displayText(
value: selectedFunctionData.value, value: selectedFunctionData.value,
functionCode: selectedFunctionData.functionCode, functionCode: selectedFunctionData.functionCode,
@ -55,7 +50,7 @@ class CpsDialogSliderSelector extends StatelessWidget {
), ),
), ),
), ),
onTextChanged: (value) => context.read<FunctionBloc>().add( onSliderChanged: (value) => context.read<FunctionBloc>().add(
AddFunction( AddFunction(
functionData: DeviceFunctionData( functionData: DeviceFunctionData(
entityId: device?.uuid ?? '', entityId: device?.uuid ?? '',
@ -69,7 +64,6 @@ class CpsDialogSliderSelector extends StatelessWidget {
dividendOfRange: CpsSliderHelpers.dividendOfRange( dividendOfRange: CpsSliderHelpers.dividendOfRange(
selectedFunctionData.functionCode, selectedFunctionData.functionCode,
), ),
stepIncreaseAmount: step,
); );
} }
} }

View File

@ -34,33 +34,30 @@ class CpsFunctionsList extends StatelessWidget {
itemBuilder: (context, index) { itemBuilder: (context, index) {
final function = cpsFunctions[index]; final function = cpsFunctions[index];
return RoutineDialogFunctionListTile( return RoutineDialogFunctionListTile(
iconPath: function.icon, iconPath: function.icon,
operationName: function.operationName, operationName: function.operationName,
onTap: () { onTap: () => RoutineTapFunctionHelper.onTapFunction(
RoutineTapFunctionHelper.onTapFunction( context,
context, functionCode: function.code,
step: function.step, functionOperationName: function.operationName,
functionCode: function.code, functionValueDescription: selectedFunctionData?.valueDescription,
functionOperationName: function.operationName, deviceUuid: device?.uuid,
functionValueDescription: codesToAddIntoFunctionsWithDefaultValue: [
selectedFunctionData?.valueDescription, 'static_max_dis',
deviceUuid: device?.uuid, 'presence_reference',
codesToAddIntoFunctionsWithDefaultValue: [ 'moving_reference',
'static_max_dis', 'perceptual_boundary',
'presence_reference', 'moving_boundary',
'moving_reference', 'moving_rigger_time',
'perceptual_boundary', 'moving_static_time',
'moving_boundary', 'none_body_time',
'moving_rigger_time', 'moving_max_dis',
'moving_static_time', 'moving_range',
'none_body_time', 'presence_range',
'moving_max_dis', if (dialogType == "IF") 'sensitivity',
'moving_range', ],
'presence_range', ),
if (dialogType == "IF") 'sensitivity', );
],
);
});
}, },
), ),
); );

View File

@ -1,7 +1,11 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_svg/svg.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_svg/flutter_svg.dart';
import 'package:syncrow_web/pages/device_managment/all_devices/models/devices_model.dart'; import 'package:syncrow_web/pages/device_managment/all_devices/models/devices_model.dart';
import 'package:syncrow_web/pages/routines/bloc/functions_bloc/functions_bloc_bloc.dart';
import 'package:syncrow_web/pages/routines/models/device_functions.dart';
import 'package:syncrow_web/pages/routines/models/flush/flush_operational_value.dart'; import 'package:syncrow_web/pages/routines/models/flush/flush_operational_value.dart';
import 'package:syncrow_web/pages/routines/widgets/routine_dialogs/wall_sensor/time_wheel.dart';
class FlushOperationalValuesList extends StatelessWidget { class FlushOperationalValuesList extends StatelessWidget {
final List<FlushOperationalValue> values; final List<FlushOperationalValue> values;
@ -22,20 +26,22 @@ class FlushOperationalValuesList extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return ListView.builder( return ListView.builder(
padding: const EdgeInsets.all(20), padding: const EdgeInsets.all(20),
itemCount: values.length, itemCount: values.length,
itemBuilder: (context, index) => _buildValueItem(context, values[index]), itemBuilder: (context, index) =>
); _buildValueItem(context, values[index]),
);
} }
Widget _buildValueItem(BuildContext context, FlushOperationalValue value) { Widget _buildValueItem(BuildContext context, FlushOperationalValue value) {
return Padding( return Padding(
padding: const EdgeInsets.all(8.0), padding: const EdgeInsets.all(8.0),
child: Row( child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [ children: [
SvgPicture.asset(value.icon, width: 25, height: 25),
Expanded(child: _buildValueDescription(value)), Expanded(child: _buildValueDescription(value)),
_buildValueRadio(context, value), _buildValueRadio(context, value),
], ],
@ -43,6 +49,9 @@ class FlushOperationalValuesList extends StatelessWidget {
); );
} }
Widget _buildValueDescription(FlushOperationalValue value) { Widget _buildValueDescription(FlushOperationalValue value) {
return Padding( return Padding(
padding: const EdgeInsets.symmetric(horizontal: 8.0), padding: const EdgeInsets.symmetric(horizontal: 8.0),
@ -56,4 +65,6 @@ class FlushOperationalValuesList extends StatelessWidget {
groupValue: selectedValue, groupValue: selectedValue,
onChanged: (_) => onSelect(value)); onChanged: (_) => onSelect(value));
} }
} }

View File

@ -5,7 +5,6 @@ import 'package:syncrow_web/pages/device_managment/flush_mounted_presence_sensor
import 'package:syncrow_web/pages/routines/bloc/functions_bloc/functions_bloc_bloc.dart'; import 'package:syncrow_web/pages/routines/bloc/functions_bloc/functions_bloc_bloc.dart';
import 'package:syncrow_web/pages/routines/models/device_functions.dart'; import 'package:syncrow_web/pages/routines/models/device_functions.dart';
import 'package:syncrow_web/pages/routines/models/flush/flush_functions.dart'; import 'package:syncrow_web/pages/routines/models/flush/flush_functions.dart';
import 'package:syncrow_web/pages/routines/widgets/custom_routines_textbox.dart';
import 'package:syncrow_web/pages/routines/widgets/routine_dialogs/flush_presence_sensor/flush_operational_values_list.dart'; import 'package:syncrow_web/pages/routines/widgets/routine_dialogs/flush_presence_sensor/flush_operational_values_list.dart';
import 'package:syncrow_web/pages/routines/widgets/slider_value_selector.dart'; import 'package:syncrow_web/pages/routines/widgets/slider_value_selector.dart';
@ -67,8 +66,7 @@ class FlushValueSelectorWidget extends StatelessWidget {
if (isDistanceDetection) { if (isDistanceDetection) {
initialValue = initialValue / 100; initialValue = initialValue / 100;
} }
return CustomRoutinesTextbox( return SliderValueSelector(
withSpecialChar: true,
currentCondition: functionData.condition, currentCondition: functionData.condition,
dialogType: dialogType, dialogType: dialogType,
sliderRange: sliderRange, sliderRange: sliderRange,
@ -85,7 +83,7 @@ class FlushValueSelectorWidget extends StatelessWidget {
), ),
), ),
), ),
onTextChanged: (value) { onSliderChanged: (value) {
final roundedValue = _roundToStep(value, stepSize); final roundedValue = _roundToStep(value, stepSize);
final finalValue = final finalValue =
isDistanceDetection ? (roundedValue * 100).toInt() : roundedValue; isDistanceDetection ? (roundedValue * 100).toInt() : roundedValue;
@ -104,7 +102,6 @@ class FlushValueSelectorWidget extends StatelessWidget {
}, },
unit: _unit, unit: _unit,
dividendOfRange: stepSize, dividendOfRange: stepSize,
stepIncreaseAmount: stepSize,
); );
} }

View File

@ -8,7 +8,6 @@ abstract final class RoutineTapFunctionHelper {
static void onTapFunction( static void onTapFunction(
BuildContext context, { BuildContext context, {
double? step,
required String functionCode, required String functionCode,
required String functionOperationName, required String functionOperationName,
required String? functionValueDescription, required String? functionValueDescription,

View File

@ -4,11 +4,11 @@ import 'package:flutter_svg/flutter_svg.dart';
import 'package:syncrow_web/pages/device_managment/all_devices/models/devices_model.dart'; import 'package:syncrow_web/pages/device_managment/all_devices/models/devices_model.dart';
import 'package:syncrow_web/pages/routines/bloc/functions_bloc/functions_bloc_bloc.dart'; import 'package:syncrow_web/pages/routines/bloc/functions_bloc/functions_bloc_bloc.dart';
import 'package:syncrow_web/pages/routines/bloc/routine_bloc/routine_bloc.dart'; import 'package:syncrow_web/pages/routines/bloc/routine_bloc/routine_bloc.dart';
import 'package:syncrow_web/pages/routines/helper/duration_format_helper.dart';
import 'package:syncrow_web/pages/routines/models/device_functions.dart'; import 'package:syncrow_web/pages/routines/models/device_functions.dart';
import 'package:syncrow_web/pages/routines/models/gang_switches/base_switch_function.dart'; import 'package:syncrow_web/pages/routines/models/gang_switches/base_switch_function.dart';
import 'package:syncrow_web/pages/routines/models/gang_switches/one_gang_switch/one_gang_switch.dart'; import 'package:syncrow_web/pages/routines/models/gang_switches/one_gang_switch/one_gang_switch.dart';
import 'package:syncrow_web/pages/routines/models/gang_switches/switch_operational_value.dart'; import 'package:syncrow_web/pages/routines/models/gang_switches/switch_operational_value.dart';
import 'package:syncrow_web/pages/routines/widgets/custom_routines_textbox.dart';
import 'package:syncrow_web/pages/routines/widgets/dialog_footer.dart'; import 'package:syncrow_web/pages/routines/widgets/dialog_footer.dart';
import 'package:syncrow_web/pages/routines/widgets/dialog_header.dart'; import 'package:syncrow_web/pages/routines/widgets/dialog_header.dart';
import 'package:syncrow_web/pages/routines/widgets/routine_dialogs/helpers/routine_tap_function_helper.dart'; import 'package:syncrow_web/pages/routines/widgets/routine_dialogs/helpers/routine_tap_function_helper.dart';
@ -87,15 +87,14 @@ class OneGangSwitchHelper {
size: 16, size: 16,
color: ColorsManager.textGray, color: ColorsManager.textGray,
), ),
onTap: () => RoutineTapFunctionHelper onTap: () =>
.onTapFunction( RoutineTapFunctionHelper.onTapFunction(
context, context,
functionCode: function.code, functionCode: function.code,
functionOperationName: functionOperationName:
function.operationName, function.operationName,
functionValueDescription: functionValueDescription:
selectedFunctionData selectedFunctionData.valueDescription,
.valueDescription,
deviceUuid: device?.uuid, deviceUuid: device?.uuid,
codesToAddIntoFunctionsWithDefaultValue: [ codesToAddIntoFunctionsWithDefaultValue: [
'countdown_1', 'countdown_1',
@ -109,16 +108,14 @@ class OneGangSwitchHelper {
if (selectedFunction != null) if (selectedFunction != null)
Expanded( Expanded(
child: _buildValueSelector( child: _buildValueSelector(
context: context, context: context,
selectedFunction: selectedFunction, selectedFunction: selectedFunction,
selectedFunctionData: selectedFunctionData: selectedFunctionData,
selectedFunctionData, acFunctions: oneGangFunctions,
acFunctions: oneGangFunctions, device: device,
device: device, operationName: selectedOperationName ?? '',
operationName: removeComparetors: removeComparetors,
selectedOperationName ?? '', ),
removeComparetors: removeComparetors,
dialogType: dialogType),
), ),
], ],
), ),
@ -175,7 +172,6 @@ class OneGangSwitchHelper {
AllDevicesModel? device, AllDevicesModel? device,
required String operationName, required String operationName,
required bool removeComparetors, required bool removeComparetors,
required String dialogType,
}) { }) {
if (selectedFunction == 'countdown_1') { if (selectedFunction == 'countdown_1') {
final initialValue = selectedFunctionData?.value ?? 0; final initialValue = selectedFunctionData?.value ?? 0;
@ -188,7 +184,6 @@ class OneGangSwitchHelper {
operationName: operationName, operationName: operationName,
selectedFunctionData: selectedFunctionData, selectedFunctionData: selectedFunctionData,
removeComparetors: removeComparetors, removeComparetors: removeComparetors,
dialogType: dialogType,
); );
} }
final selectedFn = acFunctions.firstWhere( final selectedFn = acFunctions.firstWhere(
@ -221,18 +216,93 @@ class OneGangSwitchHelper {
required String operationName, required String operationName,
DeviceFunctionData? selectedFunctionData, DeviceFunctionData? selectedFunctionData,
required bool removeComparetors, required bool removeComparetors,
String? dialogType,
}) { }) {
return Column( return Column(
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
children: [ children: [
if (removeComparetors != true)
_buildConditionToggle(
context,
currentCondition,
selectCode,
device,
operationName,
selectedFunctionData,
),
const SizedBox(height: 20),
_buildCountDownDisplay(context, initialValue, device, operationName,
selectedFunctionData, selectCode),
const SizedBox(height: 20), const SizedBox(height: 20),
_buildCountDownSlider(context, initialValue, device, operationName, _buildCountDownSlider(context, initialValue, device, operationName,
selectedFunctionData, selectCode, dialogType!), selectedFunctionData, selectCode),
], ],
); );
} }
/// Build condition toggle for AC functions dialog
static Widget _buildConditionToggle(
BuildContext context,
String? currentCondition,
String selectCode,
AllDevicesModel? device,
String operationName,
DeviceFunctionData? selectedFunctionData,
// Function(String) onConditionChanged,
) {
final conditions = ["<", "==", ">"];
return ToggleButtons(
onPressed: (int index) {
context.read<FunctionBloc>().add(
AddFunction(
functionData: DeviceFunctionData(
entityId: device?.uuid ?? '',
functionCode: selectCode,
operationName: operationName,
condition: conditions[index],
value: selectedFunctionData?.value ?? 0,
valueDescription: selectedFunctionData?.valueDescription,
),
),
);
},
borderRadius: const BorderRadius.all(Radius.circular(8)),
selectedBorderColor: ColorsManager.primaryColorWithOpacity,
selectedColor: Colors.white,
fillColor: ColorsManager.primaryColorWithOpacity,
color: ColorsManager.primaryColorWithOpacity,
constraints: const BoxConstraints(
minHeight: 40.0,
minWidth: 40.0,
),
isSelected: conditions.map((c) => c == (currentCondition ?? "==")).toList(),
children: conditions.map((c) => Text(c)).toList(),
);
}
/// Build temperature display for AC functions dialog
static Widget _buildCountDownDisplay(
BuildContext context,
dynamic initialValue,
AllDevicesModel? device,
String operationName,
DeviceFunctionData? selectedFunctionData,
String selectCode) {
return Container(
padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 10),
decoration: BoxDecoration(
color: ColorsManager.primaryColorWithOpacity.withOpacity(0.1),
borderRadius: BorderRadius.circular(10),
),
child: Text(
DurationFormatMixin.formatDuration(initialValue?.toInt() ?? 0),
style: context.textTheme.headlineMedium!.copyWith(
color: ColorsManager.primaryColorWithOpacity,
),
),
);
}
static Widget _buildCountDownSlider( static Widget _buildCountDownSlider(
BuildContext context, BuildContext context,
dynamic initialValue, dynamic initialValue,
@ -240,47 +310,38 @@ class OneGangSwitchHelper {
String operationName, String operationName,
DeviceFunctionData? selectedFunctionData, DeviceFunctionData? selectedFunctionData,
String selectCode, String selectCode,
String dialogType,
) { ) {
return CustomRoutinesTextbox( const twelveHoursInSeconds = 43200.0;
withSpecialChar: false, final operationalValues = SwitchOperationalValue(
currentCondition: selectedFunctionData?.condition, icon: '',
dialogType: dialogType, description: "sec",
sliderRange: (0, 43200), value: 0.0,
displayedValue: (initialValue ?? 0).toString(), minValue: 0,
initialValue: (initialValue ?? 0).toString(), maxValue: twelveHoursInSeconds,
onConditionChanged: (condition) { stepValue: 1,
);
return Slider(
value: (initialValue ?? 0).toDouble(),
min: operationalValues.minValue?.toDouble() ?? 0.0,
max: operationalValues.maxValue?.toDouble() ?? 0.0,
divisions:
(((operationalValues.maxValue ?? 0) - (operationalValues.minValue ?? 0)) /
(operationalValues.stepValue ?? 1))
.round(),
onChanged: (value) {
context.read<FunctionBloc>().add( context.read<FunctionBloc>().add(
AddFunction( AddFunction(
functionData: DeviceFunctionData( functionData: DeviceFunctionData(
entityId: device?.uuid ?? '', entityId: device?.uuid ?? '',
functionCode: selectCode, functionCode: selectCode,
operationName: operationName, operationName: operationName,
condition: condition, value: value,
value: selectedFunctionData?.value ?? 0,
valueDescription: selectedFunctionData?.valueDescription,
),
),
);
},
onTextChanged: (value) {
final roundedValue = value.round();
context.read<FunctionBloc>().add(
AddFunction(
functionData: DeviceFunctionData(
entityId: device?.uuid ?? '',
functionCode: selectCode,
operationName: operationName,
value: roundedValue,
condition: selectedFunctionData?.condition, condition: selectedFunctionData?.condition,
valueDescription: selectedFunctionData?.valueDescription, valueDescription: selectedFunctionData?.valueDescription,
), ),
), ),
); );
}, },
unit: 'sec',
dividendOfRange: 1,
stepIncreaseAmount: 1,
); );
} }
@ -316,9 +377,7 @@ class OneGangSwitchHelper {
style: context.textTheme.bodyMedium, style: context.textTheme.bodyMedium,
), ),
trailing: Icon( trailing: Icon(
isSelected isSelected ? Icons.radio_button_checked : Icons.radio_button_unchecked,
? Icons.radio_button_checked
: Icons.radio_button_unchecked,
size: 24, size: 24,
color: isSelected color: isSelected
? ColorsManager.primaryColorWithOpacity ? ColorsManager.primaryColorWithOpacity
@ -334,8 +393,7 @@ class OneGangSwitchHelper {
operationName: operationName, operationName: operationName,
value: value.value, value: value.value,
condition: selectedFunctionData?.condition, condition: selectedFunctionData?.condition,
valueDescription: valueDescription: selectedFunctionData?.valueDescription,
selectedFunctionData?.valueDescription,
), ),
), ),
); );

View File

@ -4,10 +4,10 @@ import 'package:flutter_svg/flutter_svg.dart';
import 'package:syncrow_web/pages/device_managment/all_devices/models/devices_model.dart'; import 'package:syncrow_web/pages/device_managment/all_devices/models/devices_model.dart';
import 'package:syncrow_web/pages/routines/bloc/functions_bloc/functions_bloc_bloc.dart'; import 'package:syncrow_web/pages/routines/bloc/functions_bloc/functions_bloc_bloc.dart';
import 'package:syncrow_web/pages/routines/bloc/routine_bloc/routine_bloc.dart'; import 'package:syncrow_web/pages/routines/bloc/routine_bloc/routine_bloc.dart';
import 'package:syncrow_web/pages/routines/helper/duration_format_helper.dart';
import 'package:syncrow_web/pages/routines/models/device_functions.dart'; import 'package:syncrow_web/pages/routines/models/device_functions.dart';
import 'package:syncrow_web/pages/routines/models/gang_switches/base_switch_function.dart'; import 'package:syncrow_web/pages/routines/models/gang_switches/base_switch_function.dart';
import 'package:syncrow_web/pages/routines/models/gang_switches/switch_operational_value.dart'; import 'package:syncrow_web/pages/routines/models/gang_switches/switch_operational_value.dart';
import 'package:syncrow_web/pages/routines/widgets/custom_routines_textbox.dart';
import 'package:syncrow_web/pages/routines/widgets/dialog_footer.dart'; import 'package:syncrow_web/pages/routines/widgets/dialog_footer.dart';
import 'package:syncrow_web/pages/routines/widgets/dialog_header.dart'; import 'package:syncrow_web/pages/routines/widgets/dialog_header.dart';
import 'package:syncrow_web/pages/routines/widgets/routine_dialogs/helpers/routine_tap_function_helper.dart'; import 'package:syncrow_web/pages/routines/widgets/routine_dialogs/helpers/routine_tap_function_helper.dart';
@ -86,21 +86,20 @@ class ThreeGangSwitchHelper {
size: 16, size: 16,
color: ColorsManager.textGray, color: ColorsManager.textGray,
), ),
onTap: () => RoutineTapFunctionHelper onTap: () =>
.onTapFunction( RoutineTapFunctionHelper.onTapFunction(
context, context,
functionCode: function.code, functionCode: function.code,
functionOperationName: functionOperationName:
function.operationName, function.operationName,
functionValueDescription: functionValueDescription:
selectedFunctionData selectedFunctionData.valueDescription,
.valueDescription,
deviceUuid: device?.uuid, deviceUuid: device?.uuid,
codesToAddIntoFunctionsWithDefaultValue: codesToAddIntoFunctionsWithDefaultValue: [
function.code 'countdown_1',
.startsWith('countdown') 'countdown_2',
? [function.code] 'countdown_3',
: [], ],
), ),
); );
}, },
@ -110,16 +109,14 @@ class ThreeGangSwitchHelper {
if (selectedFunction != null) if (selectedFunction != null)
Expanded( Expanded(
child: _buildValueSelector( child: _buildValueSelector(
context: context, context: context,
selectedFunction: selectedFunction, selectedFunction: selectedFunction,
selectedFunctionData: selectedFunctionData: selectedFunctionData,
selectedFunctionData, switchFunctions: switchFunctions,
switchFunctions: switchFunctions, device: device,
device: device, operationName: selectedOperationName ?? '',
operationName: removeComparetors: removeComparetors,
selectedOperationName ?? '', ),
removeComparetors: removeComparetors,
dialogType: dialogType),
), ),
], ],
), ),
@ -136,6 +133,14 @@ class ThreeGangSwitchHelper {
onConfirm: state.addedFunctions.isNotEmpty onConfirm: state.addedFunctions.isNotEmpty
? () { ? () {
/// add the functions to the routine bloc /// add the functions to the routine bloc
// for (var function in state.addedFunctions) {
// context.read<RoutineBloc>().add(
// AddFunctionToRoutine(
// function,
// uniqueCustomId,
// ),
// );
// }
context.read<RoutineBloc>().add( context.read<RoutineBloc>().add(
AddFunctionToRoutine( AddFunctionToRoutine(
state.addedFunctions, state.addedFunctions,
@ -168,26 +173,24 @@ class ThreeGangSwitchHelper {
AllDevicesModel? device, AllDevicesModel? device,
required String operationName, required String operationName,
required bool removeComparetors, required bool removeComparetors,
required String dialogType,
}) { }) {
if (selectedFunction == 'countdown_1' || if (selectedFunction == 'countdown_1' ||
selectedFunction == 'countdown_2' || selectedFunction == 'countdown_2' ||
selectedFunction == 'countdown_3') { selectedFunction == 'countdown_3') {
final initialValue = selectedFunctionData?.value ?? 0; final initialValue = selectedFunctionData?.value ?? 0;
return _buildTemperatureSelector( return _buildTemperatureSelector(
context: context, context: context,
initialValue: initialValue, initialValue: initialValue,
selectCode: selectedFunction, selectCode: selectedFunction,
currentCondition: selectedFunctionData?.condition, currentCondition: selectedFunctionData?.condition,
device: device, device: device,
operationName: operationName, operationName: operationName,
selectedFunctionData: selectedFunctionData, selectedFunctionData: selectedFunctionData,
removeComparetors: removeComparetors, removeComparetors: removeComparetors,
dialogType: dialogType); );
} }
final selectedFn = final selectedFn = switchFunctions.firstWhere((f) => f.code == selectedFunction);
switchFunctions.firstWhere((f) => f.code == selectedFunction);
final values = selectedFn.getOperationalValues(); final values = selectedFn.getOperationalValues();
return _buildOperationalValuesList( return _buildOperationalValuesList(
@ -210,18 +213,93 @@ class ThreeGangSwitchHelper {
required String operationName, required String operationName,
DeviceFunctionData? selectedFunctionData, DeviceFunctionData? selectedFunctionData,
bool? removeComparetors, bool? removeComparetors,
required String dialogType,
}) { }) {
return Column( return Column(
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
children: [ children: [
if (removeComparetors != true)
_buildConditionToggle(
context,
currentCondition,
selectCode,
device,
operationName,
selectedFunctionData,
),
const SizedBox(height: 20),
_buildCountDownDisplay(context, initialValue, device, operationName,
selectedFunctionData, selectCode),
const SizedBox(height: 20), const SizedBox(height: 20),
_buildCountDownSlider(context, initialValue, device, operationName, _buildCountDownSlider(context, initialValue, device, operationName,
selectedFunctionData, selectCode, dialogType), selectedFunctionData, selectCode),
], ],
); );
} }
/// Build condition toggle for AC functions dialog
static Widget _buildConditionToggle(
BuildContext context,
String? currentCondition,
String selectCode,
AllDevicesModel? device,
String operationName,
DeviceFunctionData? selectedFunctionData,
// Function(String) onConditionChanged,
) {
final conditions = ["<", "==", ">"];
return ToggleButtons(
onPressed: (int index) {
context.read<FunctionBloc>().add(
AddFunction(
functionData: DeviceFunctionData(
entityId: device?.uuid ?? '',
functionCode: selectCode,
operationName: operationName,
condition: conditions[index],
value: selectedFunctionData?.value ?? 0,
valueDescription: selectedFunctionData?.valueDescription,
),
),
);
},
borderRadius: const BorderRadius.all(Radius.circular(8)),
selectedBorderColor: ColorsManager.primaryColorWithOpacity,
selectedColor: Colors.white,
fillColor: ColorsManager.primaryColorWithOpacity,
color: ColorsManager.primaryColorWithOpacity,
constraints: const BoxConstraints(
minHeight: 40.0,
minWidth: 40.0,
),
isSelected: conditions.map((c) => c == (currentCondition ?? "==")).toList(),
children: conditions.map((c) => Text(c)).toList(),
);
}
/// Build temperature display for AC functions dialog
static Widget _buildCountDownDisplay(
BuildContext context,
dynamic initialValue,
AllDevicesModel? device,
String operationName,
DeviceFunctionData? selectedFunctionData,
String selectCode) {
return Container(
padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 10),
decoration: BoxDecoration(
color: ColorsManager.primaryColorWithOpacity.withOpacity(0.1),
borderRadius: BorderRadius.circular(10),
),
child: Text(
DurationFormatMixin.formatDuration(initialValue?.toInt() ?? 0),
style: context.textTheme.headlineMedium!.copyWith(
color: ColorsManager.primaryColorWithOpacity,
),
),
);
}
static Widget _buildCountDownSlider( static Widget _buildCountDownSlider(
BuildContext context, BuildContext context,
dynamic initialValue, dynamic initialValue,
@ -229,47 +307,38 @@ class ThreeGangSwitchHelper {
String operationName, String operationName,
DeviceFunctionData? selectedFunctionData, DeviceFunctionData? selectedFunctionData,
String selectCode, String selectCode,
String dialogType,
) { ) {
return CustomRoutinesTextbox( const twelveHoursInSeconds = 43200.0;
withSpecialChar: true, final operationalValues = SwitchOperationalValue(
currentCondition: selectedFunctionData?.condition, icon: '',
dialogType: dialogType, description: "sec",
sliderRange: (0, 43200), value: 0.0,
displayedValue: (initialValue ?? 0).toString(), minValue: 0,
initialValue: (initialValue ?? 0).toString(), maxValue: twelveHoursInSeconds,
onConditionChanged: (condition) { stepValue: 1,
);
return Slider(
value: (initialValue ?? 0).toDouble(),
min: operationalValues.minValue?.toDouble() ?? 0.0,
max: operationalValues.maxValue?.toDouble() ?? 0.0,
divisions:
(((operationalValues.maxValue ?? 0) - (operationalValues.minValue ?? 0)) /
(operationalValues.stepValue ?? 1))
.round(),
onChanged: (value) {
context.read<FunctionBloc>().add( context.read<FunctionBloc>().add(
AddFunction( AddFunction(
functionData: DeviceFunctionData( functionData: DeviceFunctionData(
entityId: device?.uuid ?? '', entityId: device?.uuid ?? '',
functionCode: selectCode, functionCode: selectCode,
operationName: operationName, operationName: operationName,
condition: condition, value: value,
value: selectedFunctionData?.value ?? 0,
valueDescription: selectedFunctionData?.valueDescription,
),
),
);
},
onTextChanged: (value) {
final roundedValue = value.round();
context.read<FunctionBloc>().add(
AddFunction(
functionData: DeviceFunctionData(
entityId: device?.uuid ?? '',
functionCode: selectCode,
operationName: operationName,
value: roundedValue,
condition: selectedFunctionData?.condition, condition: selectedFunctionData?.condition,
valueDescription: selectedFunctionData?.valueDescription, valueDescription: selectedFunctionData?.valueDescription,
), ),
), ),
); );
}, },
unit: 'sec',
dividendOfRange: 1,
stepIncreaseAmount: 1,
); );
} }
@ -305,9 +374,7 @@ class ThreeGangSwitchHelper {
style: context.textTheme.bodyMedium, style: context.textTheme.bodyMedium,
), ),
trailing: Icon( trailing: Icon(
isSelected isSelected ? Icons.radio_button_checked : Icons.radio_button_unchecked,
? Icons.radio_button_checked
: Icons.radio_button_unchecked,
size: 24, size: 24,
color: isSelected color: isSelected
? ColorsManager.primaryColorWithOpacity ? ColorsManager.primaryColorWithOpacity
@ -323,8 +390,7 @@ class ThreeGangSwitchHelper {
operationName: operationName, operationName: operationName,
value: value.value, value: value.value,
condition: selectedFunctionData?.condition, condition: selectedFunctionData?.condition,
valueDescription: valueDescription: selectedFunctionData?.valueDescription,
selectedFunctionData?.valueDescription,
), ),
), ),
); );

View File

@ -8,7 +8,6 @@ import 'package:syncrow_web/pages/routines/helper/duration_format_helper.dart';
import 'package:syncrow_web/pages/routines/models/device_functions.dart'; import 'package:syncrow_web/pages/routines/models/device_functions.dart';
import 'package:syncrow_web/pages/routines/models/gang_switches/base_switch_function.dart'; import 'package:syncrow_web/pages/routines/models/gang_switches/base_switch_function.dart';
import 'package:syncrow_web/pages/routines/models/gang_switches/switch_operational_value.dart'; import 'package:syncrow_web/pages/routines/models/gang_switches/switch_operational_value.dart';
import 'package:syncrow_web/pages/routines/widgets/custom_routines_textbox.dart';
import 'package:syncrow_web/pages/routines/widgets/dialog_footer.dart'; import 'package:syncrow_web/pages/routines/widgets/dialog_footer.dart';
import 'package:syncrow_web/pages/routines/widgets/dialog_header.dart'; import 'package:syncrow_web/pages/routines/widgets/dialog_header.dart';
import 'package:syncrow_web/pages/routines/widgets/routine_dialogs/helpers/routine_tap_function_helper.dart'; import 'package:syncrow_web/pages/routines/widgets/routine_dialogs/helpers/routine_tap_function_helper.dart';
@ -87,15 +86,14 @@ class TwoGangSwitchHelper {
size: 16, size: 16,
color: ColorsManager.textGray, color: ColorsManager.textGray,
), ),
onTap: () => RoutineTapFunctionHelper onTap: () =>
.onTapFunction( RoutineTapFunctionHelper.onTapFunction(
context, context,
functionCode: function.code, functionCode: function.code,
functionOperationName: functionOperationName:
function.operationName, function.operationName,
functionValueDescription: functionValueDescription:
selectedFunctionData selectedFunctionData.valueDescription,
.valueDescription,
deviceUuid: device?.uuid, deviceUuid: device?.uuid,
codesToAddIntoFunctionsWithDefaultValue: [ codesToAddIntoFunctionsWithDefaultValue: [
'countdown_1', 'countdown_1',
@ -117,7 +115,6 @@ class TwoGangSwitchHelper {
device: device, device: device,
operationName: selectedOperationName ?? '', operationName: selectedOperationName ?? '',
removeComparetors: removeComparetors, removeComparetors: removeComparetors,
dialogType: dialogType,
), ),
), ),
], ],
@ -175,25 +172,22 @@ class TwoGangSwitchHelper {
AllDevicesModel? device, AllDevicesModel? device,
required String operationName, required String operationName,
required bool removeComparetors, required bool removeComparetors,
required String dialogType,
}) { }) {
if (selectedFunction == 'countdown_1' || if (selectedFunction == 'countdown_1' || selectedFunction == 'countdown_2') {
selectedFunction == 'countdown_2') {
final initialValue = selectedFunctionData?.value ?? 0; final initialValue = selectedFunctionData?.value ?? 0;
return _buildTemperatureSelector( return _buildTemperatureSelector(
context: context, context: context,
initialValue: initialValue, initialValue: initialValue,
selectCode: selectedFunction, selectCode: selectedFunction,
currentCondition: selectedFunctionData?.condition, currentCondition: selectedFunctionData?.condition,
device: device, device: device,
operationName: operationName, operationName: operationName,
selectedFunctionData: selectedFunctionData, selectedFunctionData: selectedFunctionData,
removeComparetors: removeComparetors, removeComparetors: removeComparetors,
dialogType: dialogType); );
} }
final selectedFn = final selectedFn = switchFunctions.firstWhere((f) => f.code == selectedFunction);
switchFunctions.firstWhere((f) => f.code == selectedFunction);
final values = selectedFn.getOperationalValues(); final values = selectedFn.getOperationalValues();
return _buildOperationalValuesList( return _buildOperationalValuesList(
@ -216,13 +210,25 @@ class TwoGangSwitchHelper {
required String operationName, required String operationName,
DeviceFunctionData? selectedFunctionData, DeviceFunctionData? selectedFunctionData,
bool? removeComparetors, bool? removeComparetors,
String? dialogType,
}) { }) {
return Column( return Column(
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
children: [ children: [
if (removeComparetors != true)
_buildConditionToggle(
context,
currentCondition,
selectCode,
device,
operationName,
selectedFunctionData,
),
const SizedBox(height: 20),
_buildCountDownDisplay(context, initialValue, device, operationName,
selectedFunctionData, selectCode),
const SizedBox(height: 20),
_buildCountDownSlider(context, initialValue, device, operationName, _buildCountDownSlider(context, initialValue, device, operationName,
selectedFunctionData, selectCode, dialogType!), selectedFunctionData, selectCode),
], ],
); );
} }
@ -263,8 +269,7 @@ class TwoGangSwitchHelper {
minHeight: 40.0, minHeight: 40.0,
minWidth: 40.0, minWidth: 40.0,
), ),
isSelected: isSelected: conditions.map((c) => c == (currentCondition ?? "==")).toList(),
conditions.map((c) => c == (currentCondition ?? "==")).toList(),
children: conditions.map((c) => Text(c)).toList(), children: conditions.map((c) => Text(c)).toList(),
); );
} }
@ -299,48 +304,38 @@ class TwoGangSwitchHelper {
String operationName, String operationName,
DeviceFunctionData? selectedFunctionData, DeviceFunctionData? selectedFunctionData,
String selectCode, String selectCode,
String dialogType,
) { ) {
return CustomRoutinesTextbox( const twelveHoursInSeconds = 43200.0;
withSpecialChar: true, final operationalValues = SwitchOperationalValue(
currentCondition: selectedFunctionData?.condition, icon: '',
dialogType: dialogType, description: "sec",
sliderRange: (0, 43200), value: 0.0,
displayedValue: (initialValue ?? 0).toString(), minValue: 0,
initialValue: (initialValue ?? 0).toString(), maxValue: twelveHoursInSeconds,
onConditionChanged: (condition) { stepValue: 1,
);
return Slider(
value: (initialValue ?? 0).toDouble(),
min: operationalValues.minValue?.toDouble() ?? 0.0,
max: operationalValues.maxValue?.toDouble() ?? 0.0,
divisions:
(((operationalValues.maxValue ?? 0) - (operationalValues.minValue ?? 0)) /
(operationalValues.stepValue ?? 1))
.round(),
onChanged: (value) {
context.read<FunctionBloc>().add( context.read<FunctionBloc>().add(
AddFunction( AddFunction(
functionData: DeviceFunctionData( functionData: DeviceFunctionData(
entityId: device?.uuid ?? '', entityId: device?.uuid ?? '',
functionCode: selectCode, functionCode: selectCode,
operationName: operationName, operationName: operationName,
condition: condition, value: value,
value: selectedFunctionData?.value ?? 0,
valueDescription: selectedFunctionData?.valueDescription,
),
),
);
},
onTextChanged: (value) {
final roundedValue =
value.round(); // Round to nearest integer (stepSize 1)
context.read<FunctionBloc>().add(
AddFunction(
functionData: DeviceFunctionData(
entityId: device?.uuid ?? '',
functionCode: selectCode,
operationName: operationName,
value: roundedValue,
condition: selectedFunctionData?.condition, condition: selectedFunctionData?.condition,
valueDescription: selectedFunctionData?.valueDescription, valueDescription: selectedFunctionData?.valueDescription,
), ),
), ),
); );
}, },
unit: 'sec',
dividendOfRange: 1,
stepIncreaseAmount: 1,
); );
} }
@ -376,9 +371,7 @@ class TwoGangSwitchHelper {
style: context.textTheme.bodyMedium, style: context.textTheme.bodyMedium,
), ),
trailing: Icon( trailing: Icon(
isSelected isSelected ? Icons.radio_button_checked : Icons.radio_button_unchecked,
? Icons.radio_button_checked
: Icons.radio_button_unchecked,
size: 24, size: 24,
color: isSelected color: isSelected
? ColorsManager.primaryColorWithOpacity ? ColorsManager.primaryColorWithOpacity
@ -394,8 +387,7 @@ class TwoGangSwitchHelper {
operationName: operationName, operationName: operationName,
value: value.value, value: value.value,
condition: selectedFunctionData?.condition, condition: selectedFunctionData?.condition,
valueDescription: valueDescription: selectedFunctionData?.valueDescription,
selectedFunctionData?.valueDescription,
), ),
), ),
); );

View File

@ -4,8 +4,8 @@ import 'package:syncrow_web/pages/device_managment/all_devices/models/devices_mo
import 'package:syncrow_web/pages/routines/bloc/functions_bloc/functions_bloc_bloc.dart'; import 'package:syncrow_web/pages/routines/bloc/functions_bloc/functions_bloc_bloc.dart';
import 'package:syncrow_web/pages/routines/models/device_functions.dart'; import 'package:syncrow_web/pages/routines/models/device_functions.dart';
import 'package:syncrow_web/pages/routines/models/wps/wps_functions.dart'; import 'package:syncrow_web/pages/routines/models/wps/wps_functions.dart';
import 'package:syncrow_web/pages/routines/widgets/custom_routines_textbox.dart';
import 'package:syncrow_web/pages/routines/widgets/routine_dialogs/wall_sensor/wps_operational_values_list.dart'; import 'package:syncrow_web/pages/routines/widgets/routine_dialogs/wall_sensor/wps_operational_values_list.dart';
import 'package:syncrow_web/pages/routines/widgets/slider_value_selector.dart';
class WpsValueSelectorWidget extends StatelessWidget { class WpsValueSelectorWidget extends StatelessWidget {
final String selectedFunction; final String selectedFunction;
@ -27,13 +27,11 @@ class WpsValueSelectorWidget extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final selectedFn = final selectedFn = wpsFunctions.firstWhere((f) => f.code == selectedFunction);
wpsFunctions.firstWhere((f) => f.code == selectedFunction);
final values = selectedFn.getOperationalValues(); final values = selectedFn.getOperationalValues();
if (_isSliderFunction(selectedFunction)) { if (_isSliderFunction(selectedFunction)) {
return CustomRoutinesTextbox( return SliderValueSelector(
withSpecialChar: false,
currentCondition: functionData.condition, currentCondition: functionData.condition,
dialogType: dialogType, dialogType: dialogType,
sliderRange: sliderRange, sliderRange: sliderRange,
@ -50,7 +48,7 @@ class WpsValueSelectorWidget extends StatelessWidget {
), ),
), ),
), ),
onTextChanged: (value) => context.read<FunctionBloc>().add( onSliderChanged: (value) => context.read<FunctionBloc>().add(
AddFunction( AddFunction(
functionData: DeviceFunctionData( functionData: DeviceFunctionData(
entityId: device?.uuid ?? '', entityId: device?.uuid ?? '',
@ -63,7 +61,6 @@ class WpsValueSelectorWidget extends StatelessWidget {
), ),
unit: _unit, unit: _unit,
dividendOfRange: 1, dividendOfRange: 1,
stepIncreaseAmount: _steps,
); );
} }
@ -102,10 +99,4 @@ class WpsValueSelectorWidget extends StatelessWidget {
'illuminance_value' => 'Lux', 'illuminance_value' => 'Lux',
_ => '', _ => '',
}; };
double get _steps => switch (functionData.functionCode) {
'presence_time' => 1,
'dis_current' => 1,
'illuminance_value' => 1,
_ => 1,
};
} }

View File

@ -1,65 +0,0 @@
import 'package:flutter/material.dart';
import 'package:flutter_svg/svg.dart';
import 'package:syncrow_web/pages/device_managment/all_devices/models/devices_model.dart';
import 'package:syncrow_web/pages/routines/models/water_heater/water_heater_operational_value.dart';
class WaterHeaterOperationalValuesList extends StatelessWidget {
final List<WaterHeaterOperationalValue> values;
final dynamic selectedValue;
final AllDevicesModel? device;
final String operationName;
final String selectCode;
final ValueChanged<WaterHeaterOperationalValue> onSelect;
const WaterHeaterOperationalValuesList({
required this.values,
required this.selectedValue,
required this.device,
required this.operationName,
required this.selectCode,
required this.onSelect,
super.key,
});
@override
Widget build(BuildContext context) {
return ListView.builder(
padding: const EdgeInsets.all(20),
itemCount: values.length,
itemBuilder: (context, index) => _buildValueItem(context, values[index]),
);
}
Widget _buildValueItem(
BuildContext context, WaterHeaterOperationalValue value) {
return Padding(
padding: const EdgeInsets.all(8.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
SvgPicture.asset(
value.icon,
width: 24,
height: 24,
),
Expanded(child: _buildValueDescription(value)),
_buildValueRadio(context, value),
],
),
);
}
Widget _buildValueDescription(WaterHeaterOperationalValue value) {
return Padding(
padding: const EdgeInsets.symmetric(horizontal: 8.0),
child: Text(value.description),
);
}
Widget _buildValueRadio(context, WaterHeaterOperationalValue value) {
return Radio<dynamic>(
value: value.value,
groupValue: selectedValue,
onChanged: (_) => onSelect(value));
}
}

View File

@ -1,204 +0,0 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_svg/flutter_svg.dart';
import 'package:syncrow_web/pages/device_managment/all_devices/models/devices_model.dart';
import 'package:syncrow_web/pages/routines/bloc/functions_bloc/functions_bloc_bloc.dart';
import 'package:syncrow_web/pages/routines/bloc/routine_bloc/routine_bloc.dart';
import 'package:syncrow_web/pages/routines/models/device_functions.dart';
import 'package:syncrow_web/pages/routines/models/water_heater/water_heater_functions.dart';
import 'package:syncrow_web/pages/routines/widgets/dialog_footer.dart';
import 'package:syncrow_web/pages/routines/widgets/dialog_header.dart';
import 'package:syncrow_web/pages/routines/widgets/routine_dialogs/water_heater/water_heater_value_selector_widget.dart';
import 'package:syncrow_web/utils/color_manager.dart';
import 'package:syncrow_web/utils/extension/build_context_x.dart';
class WaterHeaterDialogRoutines extends StatefulWidget {
final List<DeviceFunction> functions;
final AllDevicesModel? device;
final List<DeviceFunctionData>? deviceSelectedFunctions;
final String? uniqueCustomId;
final String dialogType;
const WaterHeaterDialogRoutines({
super.key,
required this.functions,
this.device,
this.deviceSelectedFunctions,
this.uniqueCustomId,
required this.dialogType,
});
static Future<Map<String, dynamic>?> showWHFunctionsDialog({
required BuildContext context,
required List<DeviceFunction> functions,
AllDevicesModel? device,
List<DeviceFunctionData>? deviceSelectedFunctions,
String? uniqueCustomId,
required String dialogType,
}) async {
return showDialog<Map<String, dynamic>?>(
context: context,
builder: (context) => WaterHeaterDialogRoutines(
functions: functions,
device: device,
deviceSelectedFunctions: deviceSelectedFunctions,
uniqueCustomId: uniqueCustomId,
dialogType: dialogType,
),
);
}
@override
State<WaterHeaterDialogRoutines> createState() =>
_WaterHeaterDialogRoutinesState();
}
class _WaterHeaterDialogRoutinesState extends State<WaterHeaterDialogRoutines> {
late final List<WaterHeaterFunctions> _waterHeaterFunctions;
@override
void initState() {
super.initState();
_waterHeaterFunctions =
widget.functions.whereType<WaterHeaterFunctions>().where((function) {
if (widget.dialogType == 'THEN') {
return function.type == 'THEN' || function.type == 'BOTH';
}
return function.type == 'IF' || function.type == 'BOTH';
}).toList();
}
@override
Widget build(BuildContext context) {
return BlocProvider(
create: (_) => FunctionBloc()
..add(InitializeFunctions(widget.deviceSelectedFunctions ?? [])),
child: _buildDialogContent(),
);
}
Widget _buildDialogContent() {
return AlertDialog(
contentPadding: EdgeInsets.zero,
content: BlocBuilder<FunctionBloc, FunctionBlocState>(
builder: (context, state) {
final selectedFunction = state.selectedFunction;
return Container(
width: selectedFunction != null ? 600 : 360,
height: 450,
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(20),
),
padding: const EdgeInsets.only(top: 20),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
const DialogHeader('Water Heater Condition'),
Expanded(child: _buildMainContent(context, state)),
_buildDialogFooter(context, state),
],
),
);
},
),
);
}
Widget _buildMainContent(BuildContext context, FunctionBlocState state) {
return Row(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
_buildFunctionList(context),
if (state.selectedFunction != null) _buildValueSelector(context, state),
],
);
}
Widget _buildFunctionList(BuildContext context) {
return SizedBox(
width: 360,
child: ListView.separated(
shrinkWrap: false,
physics: const AlwaysScrollableScrollPhysics(),
itemCount: _waterHeaterFunctions.length,
separatorBuilder: (context, index) => const Padding(
padding: EdgeInsets.symmetric(horizontal: 40.0),
child: Divider(color: ColorsManager.dividerColor),
),
itemBuilder: (context, index) {
final function = _waterHeaterFunctions[index];
return ListTile(
leading: SvgPicture.asset(
function.icon,
width: 24,
height: 24,
placeholderBuilder: (context) => const SizedBox(
width: 24,
height: 24,
),
),
title: Text(
function.operationName,
style: context.textTheme.bodyMedium,
),
trailing: const Icon(
Icons.arrow_forward_ios,
size: 16,
color: ColorsManager.textGray,
),
onTap: () => context.read<FunctionBloc>().add(
SelectFunction(
functionCode: function.code,
operationName: function.operationName,
),
),
);
},
),
);
}
Widget _buildValueSelector(BuildContext context, FunctionBlocState state) {
final selectedFunction = state.selectedFunction ?? '';
final functionData = state.addedFunctions.firstWhere(
(f) => f.functionCode == selectedFunction,
orElse: () => DeviceFunctionData(
entityId: '',
functionCode: selectedFunction,
operationName: state.selectedOperationName ?? '',
value: null,
),
);
return Expanded(
child: WaterHeaterValueSelectorWidget(
selectedFunction: selectedFunction,
functionData: functionData,
whFunctions: _waterHeaterFunctions,
device: widget.device,
dialogType: widget.dialogType,
),
);
}
Widget _buildDialogFooter(BuildContext context, FunctionBlocState state) {
return DialogFooter(
onCancel: () => Navigator.pop(context),
onConfirm: state.addedFunctions.isNotEmpty
? () {
context.read<RoutineBloc>().add(
AddFunctionToRoutine(
state.addedFunctions,
widget.uniqueCustomId!,
),
);
Navigator.pop(
context,
{'deviceId': widget.functions.first.deviceId},
);
}
: null,
isConfirmEnabled: state.selectedFunction != null,
);
}
}

Some files were not shown because too many files have changed in this diff Show More