Compare commits

..

7 Commits

Author SHA1 Message Date
2c5ca67b10 Refactor operation dialog types and add counter steps 2025-05-13 14:46:27 +03:00
79c1205932 Merge pull request #100 from SyncrowIOT/fix-flush-bugs
fix flush bugs
2025-05-13 10:51:06 +03:00
3589e349b3 fix comments 2025-05-13 10:50:09 +03:00
86bd3fdd5b fix flush bugs 2025-05-13 10:23:35 +03:00
2e2db90c19 Merge pull request #99 from SyncrowIOT/SP-1459-FE-Display-Build-Number-and-Environment-Label-DEV-in-App-UI
Refactor menu view and add build number and environment label
2025-05-12 11:11:43 +03:00
21cad0d9e8 Refactor menu view and add build number and environment label 2025-05-12 10:58:16 +03:00
9c6ab888a7 Merge pull request #98 from SyncrowIOT/SP-1523-FE-Implement-Task-Dialogs-for-Editable-Sensor-Parameters-Sensitivity-Distances-Timings-Levels
Implement Flush Mounted Presence Sensor Routine Control and change th…
2025-05-12 10:51:11 +03:00
12 changed files with 323 additions and 77 deletions

View File

@ -22,7 +22,7 @@ class FlushSensorBloc extends Bloc<FlushSensorEvent, FlushSensorState> {
on<FlushSensorChangeValueEvent>(_changeValue);
on<FlushSensorUpdatedEvent>(_flushSensorUpdated);
on<FlushSensorGetDeviceReportsEvent>(_getDeviceReports);
on<FlushSensorInitialDeviseInfo>(fetchDeviceInfo);
on<FlushSensorInitialDeviceInfo>(fetchDeviceInfo);
}
void _fetchFlushSensorStatus(
@ -163,7 +163,7 @@ class FlushSensorBloc extends Bloc<FlushSensorEvent, FlushSensorState> {
);
static String deviceName = '';
void fetchDeviceInfo(FlushSensorInitialDeviseInfo event,
void fetchDeviceInfo(FlushSensorInitialDeviceInfo event,
Emitter<FlushSensorState> emit) async {
try {
emit(FlushSensorLoadingInitialState());

View File

@ -11,7 +11,7 @@ class FlushSensorLoadingEvent extends FlushSensorEvent {}
class FlushSensorInitialEvent extends FlushSensorEvent {}
class FlushSensorInitialDeviseInfo extends FlushSensorEvent {}
class FlushSensorInitialDeviceInfo extends FlushSensorEvent {}
class FlushSensorUpdatedEvent extends FlushSensorEvent {}

View File

@ -9,7 +9,6 @@ import 'package:syncrow_app/features/devices/bloc/flush_sensor_bloc/flush_sensor
import 'package:syncrow_app/features/devices/model/device_model.dart';
import 'package:syncrow_app/features/devices/model/flush_sensor_model.dart';
import 'package:syncrow_app/features/devices/view/device_settings/settings_page.dart';
import 'package:syncrow_app/features/devices/view/widgets/device_appbar.dart';
import 'package:syncrow_app/features/devices/view/widgets/flush_sensor/flush_persence_records.dart';
import 'package:syncrow_app/features/devices/view/widgets/flush_sensor/flush_sensor_option.dart';
import 'package:syncrow_app/features/shared_widgets/default_scaffold.dart';
@ -20,7 +19,6 @@ import 'package:syncrow_app/utils/context_extension.dart';
import 'package:syncrow_app/utils/helpers/misc_string_helpers.dart';
import 'package:syncrow_app/utils/helpers/snack_bar.dart';
import 'package:syncrow_app/utils/resource_manager/color_manager.dart';
import 'package:syncrow_app/utils/resource_manager/constants.dart';
import 'package:syncrow_app/utils/resource_manager/font_manager.dart';
part "presence_indicator.dart";
part "flush_sensor_options_list.dart";
@ -33,7 +31,8 @@ class FlushMountedInterface extends StatelessWidget {
Widget build(BuildContext context) {
return BlocProvider(
create: (context) => FlushSensorBloc(deviceId: deviceModel.uuid ?? '')
..add(FlushSensorInitialEvent()),
..add(FlushSensorInitialEvent())
..add(FlushSensorInitialDeviceInfo()),
child: BlocBuilder<FlushSensorBloc, FlushSensorState>(
builder: (context, state) {
final bloc = BlocProvider.of<FlushSensorBloc>(context);
@ -72,7 +71,7 @@ class FlushMountedInterface extends StatelessWidget {
),
);
if (val == null) {
bloc.add(FlushSensorInitialDeviseInfo());
bloc.add(FlushSensorInitialDeviceInfo());
bloc.add(FlushSensorInitialEvent());
}
},

View File

@ -4,6 +4,7 @@ import 'package:syncrow_app/features/auth/bloc/auth_cubit.dart';
import 'package:syncrow_app/features/menu/bloc/menu_cubit.dart';
import 'package:syncrow_app/features/menu/view/widgets/menu_list.dart';
import 'package:syncrow_app/features/menu/view/widgets/profile/profile_tab.dart';
import 'package:syncrow_app/features/shared_widgets/build_number_environment.dart';
import 'package:syncrow_app/features/shared_widgets/default_container.dart';
import 'package:syncrow_app/features/shared_widgets/text_widgets/body_large.dart';
import 'package:syncrow_app/utils/context_extension.dart';
@ -51,6 +52,7 @@ class MenuView extends StatelessWidget {
],
),
),
buildNumberAndEnvironmentLabel()
],
),
);

View File

@ -4,6 +4,7 @@ enum OperationDialogType {
temperature,
onOff,
integerSteps,
counterSteps,
listOfOptions,
none,
}

View File

@ -116,7 +116,7 @@ class FlushFunctionsHelper {
operationName: 'Min Detection Distance',
code: 'near_detection',
functionValue: functionValue,
operationDialogType: OperationDialogType.integerSteps,
operationDialogType: OperationDialogType.counterSteps,
operationalValues: [
SceneOperationalValue(
icon: '',
@ -136,10 +136,10 @@ class FlushFunctionsHelper {
operationName: 'Max Detection Distance',
code: 'far_detection',
functionValue: functionValue,
operationDialogType: OperationDialogType.integerSteps,
operationDialogType: OperationDialogType.counterSteps,
operationalValues: [
SceneOperationalValue(
icon: '',
icon: Assets.assetsCelsiusDegrees,
value: 0.0,
description: "m",
minValue: 0.0,
@ -156,23 +156,17 @@ class FlushFunctionsHelper {
operationName: 'Trigger Level',
code: 'sensi_reduce',
functionValue: functionValue,
operationDialogType: OperationDialogType.listOfOptions,
operationDialogType: OperationDialogType.counterSteps,
operationalValues: [
SceneOperationalValue(
icon: Assets.assetsSensitivityOperationIcon,
icon: Assets.assetsCelsiusDegrees,
value: 1,
description: 1.toString(),
),
SceneOperationalValue(
icon: Assets.assetsSensitivityOperationIcon,
value: 2,
description: 2.toString(),
),
SceneOperationalValue(
icon: Assets.assetsSensitivityOperationIcon,
value: 3,
description: 3.toString(),
description: "",
minValue: 1,
maxValue: 3,
stepValue: 1,
),
],
),
SceneStaticFunction(
@ -183,23 +177,17 @@ class FlushFunctionsHelper {
operationName: 'Indent Level',
code: 'occur_dist_reduce',
functionValue: functionValue,
operationDialogType: OperationDialogType.listOfOptions,
operationDialogType: OperationDialogType.counterSteps,
operationalValues: [
SceneOperationalValue(
icon: Assets.assetsSensitivityOperationIcon,
icon: Assets.assetsCelsiusDegrees,
value: 1,
description: 1.toString(),
),
SceneOperationalValue(
icon: Assets.assetsSensitivityOperationIcon,
value: 2,
description: 2.toString(),
),
SceneOperationalValue(
icon: Assets.assetsSensitivityOperationIcon,
value: 3,
description: 3.toString(),
description: "",
minValue: 1,
maxValue: 3,
stepValue: 1,
),
],
),
SceneStaticFunction(
@ -210,7 +198,7 @@ class FlushFunctionsHelper {
operationName: 'Target Confirm Time',
code: 'presence_delay',
functionValue: functionValue,
operationDialogType: OperationDialogType.integerSteps,
operationDialogType: OperationDialogType.counterSteps,
operationalValues: [
SceneOperationalValue(
icon: '',
@ -235,7 +223,7 @@ class FlushFunctionsHelper {
SceneOperationalValue(
icon: '',
value: 20.0,
description: "",
description: "sec",
minValue: 20.0,
maxValue: 300.0,
stepValue: 1.0,

View File

@ -8,6 +8,7 @@ import 'package:syncrow_app/features/scene/model/create_automation_model.dart';
import 'package:syncrow_app/features/scene/model/create_scene_model.dart';
import 'package:syncrow_app/features/scene/model/scene_static_function.dart';
import 'package:syncrow_app/features/scene/widgets/alert_dialogs/alert_dialog_countdown.dart';
import 'package:syncrow_app/features/scene/widgets/alert_dialogs/alert_dialog_counter.dart';
import 'package:syncrow_app/features/scene/widgets/alert_dialogs/alert_dialog_functions_body.dart';
import 'package:syncrow_app/features/scene/widgets/alert_dialogs/alert_dialog_slider_steps.dart';
import 'package:syncrow_app/features/scene/widgets/alert_dialogs/alert_dialog_temperature_body.dart';
@ -132,7 +133,7 @@ mixin SceneLogicHelper {
'presence_delay' ||
action.executorProperty!.functionCode == 'none_delay') {
action.executorProperty!.functionValue =
action.executorProperty!.functionValue * 10;
(action.executorProperty!.functionValue * 10).round();
}
}
}
@ -193,12 +194,12 @@ mixin SceneLogicHelper {
if (action.executorProperty!.functionCode == 'near_detection' ||
action.executorProperty!.functionCode == 'far_detection') {
action.executorProperty!.functionValue =
action.executorProperty!.functionValue * 100;
(action.executorProperty!.functionValue * 100).round();
} else if (action.executorProperty!.functionCode ==
'presence_delay' ||
action.executorProperty!.functionCode == 'none_delay') {
action.executorProperty!.functionValue =
action.executorProperty!.functionValue * 10;
(action.executorProperty!.functionValue * 10).round();
}
}
}
@ -234,6 +235,13 @@ mixin SceneLogicHelper {
functionValue: functionValue ?? taskItem.functionValue,
isAutomation: isAutomation,
);
} else if (taskItem.operationDialogType ==
OperationDialogType.counterSteps) {
return AlertDialogCounterSteps(
taskItem: taskItem,
functionValue: functionValue ?? taskItem.functionValue,
isAutomation: isAutomation,
);
}
return AlertDialogFunctionsOperationsBody(

View File

@ -1748,7 +1748,7 @@ mixin SceneOperationsDataHelper {
action.deviceName,
Assets.flushIcon,
'Min Detection distance',
OperationDialogType.integerSteps,
OperationDialogType.counterSteps,
_createMinDetection(),
isAutomation,
comparator,
@ -1763,7 +1763,7 @@ mixin SceneOperationsDataHelper {
action.deviceName,
Assets.flushIcon,
'Max Detection distance',
OperationDialogType.integerSteps,
OperationDialogType.counterSteps,
_createFarDetection(),
isAutomation,
comparator,
@ -1778,7 +1778,7 @@ mixin SceneOperationsDataHelper {
action.deviceName,
Assets.flushIcon,
"Trigger Level",
OperationDialogType.listOfOptions,
OperationDialogType.counterSteps,
_createTriggerLevelFunction(),
isAutomation,
comparator,
@ -1789,19 +1789,12 @@ mixin SceneOperationsDataHelper {
List<SceneOperationalValue> _createTriggerLevelFunction() {
return [
SceneOperationalValue(
icon: Assets.assetsSensitivityOperationIcon,
description: "1",
value: 1,
),
SceneOperationalValue(
icon: Assets.assetsSensitivityOperationIcon,
description: "2",
value: 2,
),
SceneOperationalValue(
icon: Assets.assetsSensitivityOperationIcon,
description: "3",
value: 3,
icon: Assets.assetsCelsiusDegrees,
value: 0,
description: '',
minValue: 0,
maxValue: 3,
stepValue: 1,
),
];
}
@ -1813,7 +1806,7 @@ mixin SceneOperationsDataHelper {
action.deviceName,
Assets.flushIcon,
'Indent Level',
OperationDialogType.listOfOptions,
OperationDialogType.counterSteps,
_createIndentLevelFunction(),
isAutomation,
comparator,
@ -1824,19 +1817,12 @@ mixin SceneOperationsDataHelper {
List<SceneOperationalValue> _createIndentLevelFunction() {
return [
SceneOperationalValue(
icon: Assets.assetsSensitivityOperationIcon,
description: "1",
value: 1,
),
SceneOperationalValue(
icon: Assets.assetsSensitivityOperationIcon,
description: "2",
value: 2,
),
SceneOperationalValue(
icon: Assets.assetsSensitivityOperationIcon,
description: "3",
value: 3,
icon: Assets.assetsCelsiusDegrees,
value: 0,
description: '',
minValue: 0,
maxValue: 3,
stepValue: 1,
),
];
}
@ -1848,7 +1834,7 @@ mixin SceneOperationsDataHelper {
action.deviceName,
Assets.flushIcon,
'Target Confirm Time',
OperationDialogType.integerSteps,
OperationDialogType.counterSteps,
_targetConfirmTimeFun(),
isAutomation,
comparator,
@ -1876,7 +1862,7 @@ mixin SceneOperationsDataHelper {
SceneOperationalValue(
icon: Assets.flushIcon,
value: 0.0,
description: '',
description: 'sec',
minValue: 20,
maxValue: 300,
stepValue: 1,
@ -1892,7 +1878,7 @@ mixin SceneOperationsDataHelper {
value: 0.0,
minValue: 0.0,
maxValue: 9.5,
stepValue: 1,
stepValue: 0.10,
),
];
}
@ -1905,7 +1891,7 @@ mixin SceneOperationsDataHelper {
value: 0.0,
minValue: 0.0,
maxValue: 9.5,
stepValue: 1,
stepValue: 0.10,
),
];
}
@ -1915,7 +1901,7 @@ mixin SceneOperationsDataHelper {
SceneOperationalValue(
icon: Assets.assetsCelsiusDegrees,
value: 0.0,
description: '',
description: 'sec',
minValue: 0.0,
maxValue: 0.5,
stepValue: 0.1,

View File

@ -0,0 +1,243 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:syncrow_app/features/scene/bloc/create_scene/create_scene_bloc.dart';
import 'package:syncrow_app/features/scene/model/scene_static_function.dart';
import 'package:syncrow_app/features/shared_widgets/text_widgets/title_medium.dart';
import 'package:syncrow_app/utils/context_extension.dart';
import 'package:syncrow_app/utils/resource_manager/color_manager.dart';
class AlertDialogCounterSteps extends StatefulWidget {
const AlertDialogCounterSteps({
super.key,
this.functionValue,
required this.taskItem,
required this.isAutomation,
});
final dynamic functionValue;
final SceneStaticFunction taskItem;
final bool isAutomation;
@override
State<AlertDialogCounterSteps> createState() =>
_AlertDialogCounterStepsState();
}
class _AlertDialogCounterStepsState extends State<AlertDialogCounterSteps> {
double? groupValue;
int selectedToggleIndex = 1;
@override
void didChangeDependencies() {
super.didChangeDependencies();
final createSceneBloc = context.read<CreateSceneBloc>();
if (widget.taskItem.comparator != null) {
selectedToggleIndex = _comparatorToIndex(widget.taskItem.comparator);
}
if (widget.isAutomation) {
final automationTempTaskList = createSceneBloc.automationTempTasksList;
final automationComparatorValues =
createSceneBloc.automationComparatorValues;
for (var element in automationTempTaskList) {
if (element.code == widget.taskItem.code) {
groupValue = element.functionValue;
selectedToggleIndex =
_comparatorToIndex(automationComparatorValues[element.code]);
}
}
}
if (widget.taskItem.code == 'temp_current') {
groupValue = widget.functionValue != null
? _normalizeValue(
double.tryParse(widget.functionValue.toString()) ??
widget.taskItem.operationalValues[0].minValue,
)
: widget.taskItem.operationalValues[0].minValue;
} else {
groupValue = widget.functionValue != null
? _normalizeValue(widget.functionValue)
: _normalizeValue(widget.taskItem.operationalValues[0].minValue);
}
setState(() {});
context.read<CreateSceneBloc>().add(
SelectedValueEvent(
value: _deNormalizeValue(groupValue),
code: widget.taskItem.code,
isAutomation: widget.isAutomation,
comparator: _indexToComparator(selectedToggleIndex),
),
);
}
int _comparatorToIndex(String? comparator) {
switch (comparator) {
case "<":
return 0;
case "==":
return 1;
case ">":
return 2;
default:
return 1;
}
}
String _indexToComparator(int index) {
switch (index) {
case 0:
return "<";
case 1:
return "==";
case 2:
return ">";
default:
return "==";
}
}
@override
Widget build(BuildContext context) {
return Column(
mainAxisSize: MainAxisSize.min,
children: [
if (widget.isAutomation)
ToggleButtons(
isSelected: [
selectedToggleIndex == 0,
selectedToggleIndex == 1,
selectedToggleIndex == 2,
],
onPressed: (index) {
setState(() {
selectedToggleIndex = index;
});
context.read<CreateSceneBloc>().add(
SelectedValueEvent(
value: _deNormalizeValue(groupValue),
code: widget.taskItem.code,
isAutomation: widget.isAutomation,
comparator: _indexToComparator(selectedToggleIndex),
),
);
},
borderRadius: BorderRadius.circular(30),
selectedColor: Colors.white,
color: ColorsManager.blackColor,
fillColor: ColorsManager.primaryColorWithOpacity,
borderColor: ColorsManager.greyColor,
constraints: BoxConstraints.tight(Size(70, 30)),
children: const [
SizedBox(width: 70, height: 30, child: Center(child: Text("<"))),
SizedBox(width: 70, height: 30, child: Center(child: Text("="))),
SizedBox(width: 70, height: 30, child: Center(child: Text(">"))),
],
),
const SizedBox(height: 12),
...widget.taskItem.operationalValues.map(
(operation) => BlocBuilder<CreateSceneBloc, CreateSceneState>(
builder: (context, state) {
final step = operation.stepValue?.toDouble() ?? 1.0;
final min = operation.minValue?.toDouble() ?? 0.0;
final max = operation.maxValue?.toDouble() ?? 100.0;
return Column(
children: [
const SizedBox(height: 12),
const SizedBox(height: 8),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
IconButton(
icon: const Icon(Icons.remove),
onPressed: () {
setState(() {
groupValue =
((groupValue ?? min) - step).clamp(min, max);
});
context.read<CreateSceneBloc>().add(
SelectedValueEvent(
value: _deNormalizeValue(groupValue),
code: widget.taskItem.code,
isAutomation: widget.isAutomation,
comparator:
_indexToComparator(selectedToggleIndex),
),
);
},
),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
TitleMedium(
text: groupValue != null
? (groupValue! % 1 == 0
? groupValue!.toStringAsFixed(0)
: groupValue!.toStringAsFixed(1))
: "0",
style: context.titleMedium.copyWith(
color: ColorsManager.primaryColorWithOpacity,
fontSize: 30,
),
),
const SizedBox(width: 8),
TitleMedium(
text: operation.description.toString(),
style: context.titleMedium.copyWith(
color: ColorsManager.primaryColorWithOpacity,
fontSize: 30,
),
),
],
),
IconButton(
icon: const Icon(Icons.add),
onPressed: () {
setState(() {
groupValue =
((groupValue ?? 0) + step).clamp(min, max);
});
context.read<CreateSceneBloc>().add(
SelectedValueEvent(
value: _deNormalizeValue(groupValue),
code: widget.taskItem.code,
isAutomation: widget.isAutomation,
comparator:
_indexToComparator(selectedToggleIndex),
),
);
},
),
],
),
const SizedBox(height: 12),
],
);
},
),
),
],
);
}
double _normalizeValue(dynamic value) {
if ((widget.taskItem.code == "temp_set" && value > 199) ||
(widget.taskItem.code == "temp_current" && value >= -99.0)) {
return (value) / 10;
}
return value.toDouble();
}
double _deNormalizeValue(double? value) {
if (widget.taskItem.code == "temp_set" ||
widget.taskItem.code == "temp_current") {
return (value ?? 0) * 10;
}
return value ?? 0;
}
}

View File

@ -0,0 +1,16 @@
import 'package:flutter/material.dart';
import 'package:flutter_dotenv/flutter_dotenv.dart';
import 'package:syncrow_app/main.dart';
Widget buildNumberAndEnvironmentLabel() {
String envNAME = dotenv.env['ENV_NAME'] ?? '';
return SizedBox(
child: Center(
child: Text(
'\n$envNAME \nBuild Number $buildNumber',
textAlign: TextAlign.center,
),
),
);
}

View File

@ -9,6 +9,9 @@ import 'package:syncrow_app/utils/bloc_observer.dart';
import 'package:syncrow_app/utils/helpers/localization_helpers.dart';
import 'my_app.dart';
const String buildNumber = '1.0.30+17';
void main() {
//to observe the state of the blocs in the output console
Bloc.observer = MyBlocObserver();

View File

@ -579,7 +579,7 @@ class DevicesAPI {
.replaceAll('{code}', code),
showServerMessage: false,
expectedResponseModel: (json) {
return DeviceReport.fromJson(json);
return DeviceReport.fromJson(json['data'] as Map<String, dynamic>);
},
);
return response;