mirror of
https://github.com/SyncrowIOT/web.git
synced 2025-07-11 15:47:44 +00:00
Compare commits
19 Commits
Add-Flush-
...
bugfix/sav
Author | SHA1 | Date | |
---|---|---|---|
6ef0b2f9d1 | |||
52608b1f35 | |||
0f56273d99 | |||
f30d7c0117 | |||
976d6e385a | |||
ff07e7509d | |||
17a582ab99 | |||
09fb604acc | |||
2068df173d | |||
bfc2a381d2 | |||
778257644d | |||
c8e540e938 | |||
ba20998067 | |||
75b0b24543 | |||
c03b8f290c | |||
2c684a9495 | |||
fbc45b465f | |||
c9c939c59f | |||
d1bb7b129f |
@ -60,7 +60,15 @@ class _CurrentTempState extends State<CurrentTemp> {
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
void didUpdateWidget(CurrentTemp oldWidget) {
|
||||
super.didUpdateWidget(oldWidget);
|
||||
if (oldWidget.tempSet != widget.tempSet) {
|
||||
setState(() {
|
||||
_adjustedValue = _initialAdjustedValue(widget.tempSet);
|
||||
});
|
||||
}
|
||||
}
|
||||
@override
|
||||
void dispose() {
|
||||
_debounce?.cancel();
|
||||
|
@ -9,6 +9,7 @@ import 'package:syncrow_web/pages/device_managment/curtain/view/curtain_batch_st
|
||||
import 'package:syncrow_web/pages/device_managment/curtain/view/curtain_status_view.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/door_lock/view/door_lock_batch_control_view.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/door_lock/view/door_lock_control_view.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/flush_mounted_presence_sensor/views/flush_mounted_presence_sensor_batch_control_view.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/flush_mounted_presence_sensor/views/flush_mounted_presence_sensor_control_view.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/garage_door/view/garage_door_batch_control_view.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/garage_door/view/garage_door_control_view.dart';
|
||||
@ -198,6 +199,10 @@ mixin RouteControlsBasedCode {
|
||||
return SOSBatchControlView(
|
||||
deviceIds: devices.where((e) => (e.productType == 'SOS')).map((e) => e.uuid!).toList(),
|
||||
);
|
||||
case 'NCPS':
|
||||
return FlushMountedPresenceSensorBatchControlView(
|
||||
devicesIds: devices.where((e) => (e.productType == 'NCPS')).map((e) => e.uuid!).toList(),
|
||||
);
|
||||
default:
|
||||
return const SizedBox();
|
||||
}
|
||||
|
@ -108,7 +108,7 @@ class DeviceManagementPage extends StatelessWidget with HelperResponsiveLayout {
|
||||
return DeviceManagementBody(
|
||||
devices: deviceState.filteredDevices);
|
||||
} else {
|
||||
return const Center(child: Text('Error fetching Devices'));
|
||||
return const DeviceManagementBody(devices: []);
|
||||
}
|
||||
},
|
||||
);
|
||||
|
@ -16,7 +16,8 @@ import 'package:syncrow_web/pages/device_managment/shared/table/report_table.dar
|
||||
import 'package:syncrow_web/utils/constants/assets.dart';
|
||||
import 'package:syncrow_web/utils/helpers/responsice_layout_helper/responsive_layout_helper.dart';
|
||||
|
||||
class CeilingSensorControlsView extends StatelessWidget with HelperResponsiveLayout {
|
||||
class CeilingSensorControlsView extends StatelessWidget
|
||||
with HelperResponsiveLayout {
|
||||
const CeilingSensorControlsView({super.key, required this.device});
|
||||
|
||||
final AllDevicesModel device;
|
||||
@ -31,29 +32,35 @@ class CeilingSensorControlsView extends StatelessWidget with HelperResponsiveLay
|
||||
..add(CeilingInitialEvent(device.uuid ?? '')),
|
||||
child: BlocBuilder<CeilingSensorBloc, CeilingSensorState>(
|
||||
builder: (context, state) {
|
||||
if (state is CeilingLoadingInitialState || state is CeilingReportsLoadingState) {
|
||||
if (state is CeilingLoadingInitialState ||
|
||||
state is CeilingReportsLoadingState) {
|
||||
return const Center(child: CircularProgressIndicator());
|
||||
} else if (state is CeilingUpdateState) {
|
||||
return _buildGridView(
|
||||
context, state.ceilingSensorModel, isExtraLarge, isLarge, isMedium);
|
||||
return _buildGridView(context, state.ceilingSensorModel,
|
||||
isExtraLarge, isLarge, isMedium);
|
||||
} else if (state is CeilingReportsState) {
|
||||
return ReportsTable(
|
||||
report: state.deviceReport,
|
||||
onRowTap: (index) {},
|
||||
onClose: () {
|
||||
context.read<CeilingSensorBloc>().add(BackToCeilingGridViewEvent());
|
||||
context
|
||||
.read<CeilingSensorBloc>()
|
||||
.add(BackToCeilingGridViewEvent());
|
||||
},
|
||||
);
|
||||
} else if (state is ShowCeilingDescriptionState) {
|
||||
return DescriptionView(
|
||||
description: state.description,
|
||||
onClose: () {
|
||||
context.read<CeilingSensorBloc>().add(BackToCeilingGridViewEvent());
|
||||
context
|
||||
.read<CeilingSensorBloc>()
|
||||
.add(BackToCeilingGridViewEvent());
|
||||
},
|
||||
);
|
||||
} else if (state is CeilingReportsFailedState) {
|
||||
final model = context.read<CeilingSensorBloc>().deviceStatus;
|
||||
return _buildGridView(context, model, isExtraLarge, isLarge, isMedium);
|
||||
return _buildGridView(
|
||||
context, model, isExtraLarge, isLarge, isMedium);
|
||||
}
|
||||
return const Center(child: Text('Error fetching status'));
|
||||
},
|
||||
@ -61,8 +68,8 @@ class CeilingSensorControlsView extends StatelessWidget with HelperResponsiveLay
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildGridView(BuildContext context, CeilingSensorModel model, bool isExtraLarge,
|
||||
bool isLarge, bool isMedium) {
|
||||
Widget _buildGridView(BuildContext context, CeilingSensorModel model,
|
||||
bool isExtraLarge, bool isLarge, bool isMedium) {
|
||||
return GridView(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 50),
|
||||
shrinkWrap: true,
|
||||
@ -143,8 +150,8 @@ class CeilingSensorControlsView extends StatelessWidget with HelperResponsiveLay
|
||||
),
|
||||
GestureDetector(
|
||||
onTap: () {
|
||||
context.read<CeilingSensorBloc>().add(
|
||||
GetCeilingDeviceReportsEvent(code: 'presence_state', deviceUuid: device.uuid!));
|
||||
context.read<CeilingSensorBloc>().add(GetCeilingDeviceReportsEvent(
|
||||
code: 'presence_state', deviceUuid: device.uuid!));
|
||||
},
|
||||
child: const PresenceStaticWidget(
|
||||
icon: Assets.illuminanceRecordIcon,
|
||||
@ -153,9 +160,8 @@ class CeilingSensorControlsView extends StatelessWidget with HelperResponsiveLay
|
||||
),
|
||||
GestureDetector(
|
||||
onTap: () {
|
||||
context
|
||||
.read<CeilingSensorBloc>()
|
||||
.add(GetCeilingDeviceReportsEvent(code: '', deviceUuid: device.uuid!));
|
||||
context.read<CeilingSensorBloc>().add(GetCeilingDeviceReportsEvent(
|
||||
code: '', deviceUuid: device.uuid!));
|
||||
},
|
||||
child: const PresenceStaticWidget(
|
||||
icon: Assets.helpDescriptionIcon,
|
||||
|
@ -49,6 +49,9 @@ class FlushMountedPresenceSensorBloc
|
||||
on<FlushMountedPresenceSensorFactoryResetEvent>(
|
||||
_onFlushMountedPresenceSensorFactoryResetEvent,
|
||||
);
|
||||
on<FlushMountedPresenceSensorStatusUpdatedEvent>(
|
||||
_onFlushMountedPresenceSensorStatusUpdatedEvent,
|
||||
);
|
||||
}
|
||||
|
||||
void _onFlushMountedPresenceSensorFetchStatusEvent(
|
||||
@ -60,7 +63,7 @@ class FlushMountedPresenceSensorBloc
|
||||
final response = await DevicesManagementApi().getDeviceStatus(deviceId);
|
||||
deviceStatus = FlushMountedPresenceSensorModel.fromJson(response.status);
|
||||
emit(FlushMountedPresenceSensorUpdateState(model: deviceStatus));
|
||||
_listenToChanges(emit, deviceId);
|
||||
_listenToChanges(deviceId);
|
||||
} catch (e) {
|
||||
emit(FlushMountedPresenceSensorFailedState(error: e.toString()));
|
||||
return;
|
||||
@ -81,31 +84,33 @@ class FlushMountedPresenceSensorBloc
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _listenToChanges(
|
||||
Emitter<FlushMountedPresenceSensorState> emit,
|
||||
String deviceId,
|
||||
) async {
|
||||
final ref = FirebaseDatabase.instance.ref(
|
||||
'device-status/$deviceId',
|
||||
);
|
||||
void _listenToChanges(String deviceId) {
|
||||
try {
|
||||
final ref = FirebaseDatabase.instance.ref(
|
||||
'device-status/$deviceId',
|
||||
);
|
||||
|
||||
ref.onValue.listen((event) {
|
||||
final eventsMap = event.snapshot.value as Map<dynamic, dynamic>;
|
||||
|
||||
await ref.onValue.listen(
|
||||
(DatabaseEvent event) async {
|
||||
Map<dynamic, dynamic> usersMap =
|
||||
event.snapshot.value as Map<dynamic, dynamic>;
|
||||
List<Status> statusList = [];
|
||||
|
||||
(usersMap['status'] as List<dynamic>?)?.forEach((element) {
|
||||
statusList.add(Status(code: element['code'], value: element['value']));
|
||||
eventsMap['status'].forEach((element) {
|
||||
statusList.add(
|
||||
Status(code: element['code'], value: element['value']),
|
||||
);
|
||||
});
|
||||
|
||||
deviceStatus = FlushMountedPresenceSensorModel.fromJson(statusList);
|
||||
if (!emit.isDone) {
|
||||
emit(FlushMountedPresenceSensorLoadingNewSate(model: deviceStatus));
|
||||
if (!isClosed) {
|
||||
add(FlushMountedPresenceSensorStatusUpdatedEvent(deviceStatus));
|
||||
}
|
||||
},
|
||||
onError: (error) => log(error.toString(), name: 'FirebaseDatabaseError'),
|
||||
).asFuture();
|
||||
});
|
||||
} catch (_) {
|
||||
log(
|
||||
'Error listening to changes',
|
||||
name: 'FlushMountedPresenceSensorBloc._listenToChanges',
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
void _onFlushMountedPresenceSensorChangeValueEvent(
|
||||
@ -234,4 +239,12 @@ class FlushMountedPresenceSensorBloc
|
||||
emit(FlushMountedPresenceSensorFailedState(error: e.toString()));
|
||||
}
|
||||
}
|
||||
|
||||
void _onFlushMountedPresenceSensorStatusUpdatedEvent(
|
||||
FlushMountedPresenceSensorStatusUpdatedEvent event,
|
||||
Emitter<FlushMountedPresenceSensorState> emit,
|
||||
) {
|
||||
deviceStatus = event.model;
|
||||
emit(FlushMountedPresenceSensorUpdateState(model: deviceStatus));
|
||||
}
|
||||
}
|
||||
|
@ -10,6 +10,16 @@ sealed class FlushMountedPresenceSensorEvent extends Equatable {
|
||||
class FlushMountedPresenceSensorFetchStatusEvent
|
||||
extends FlushMountedPresenceSensorEvent {}
|
||||
|
||||
class FlushMountedPresenceSensorStatusUpdatedEvent
|
||||
extends FlushMountedPresenceSensorEvent {
|
||||
const FlushMountedPresenceSensorStatusUpdatedEvent(this.model);
|
||||
|
||||
final FlushMountedPresenceSensorModel model;
|
||||
|
||||
@override
|
||||
List<Object> get props => [model];
|
||||
}
|
||||
|
||||
class FlushMountedPresenceSensorChangeValueEvent
|
||||
extends FlushMountedPresenceSensorEvent {
|
||||
final int value;
|
||||
|
@ -16,6 +16,6 @@ abstract final class FlushMountedPresenceSensorBlocFactory {
|
||||
batchControlDevicesService: DebouncedBatchControlDevicesService(
|
||||
decoratee: RemoteBatchControlDevicesService(),
|
||||
),
|
||||
)..add(FlushMountedPresenceSensorFetchStatusEvent());
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -22,7 +22,7 @@ class FlushMountedPresenceSensorBatchControlView extends StatelessWidget
|
||||
return BlocProvider(
|
||||
create: (context) => FlushMountedPresenceSensorBlocFactory.create(
|
||||
deviceId: devicesIds.first,
|
||||
),
|
||||
)..add(FlushMountedPresenceSensorFetchBatchStatusEvent(devicesIds)),
|
||||
child: BlocBuilder<FlushMountedPresenceSensorBloc,
|
||||
FlushMountedPresenceSensorState>(
|
||||
builder: (context, state) {
|
||||
@ -67,7 +67,8 @@ class FlushMountedPresenceSensorBatchControlView extends StatelessWidget
|
||||
maxValue: 9,
|
||||
steps: 1,
|
||||
action: (int value) => context.read<FlushMountedPresenceSensorBloc>().add(
|
||||
FlushMountedPresenceSensorChangeValueEvent(
|
||||
FlushMountedPresenceSensorBatchControlEvent(
|
||||
deviceIds: devicesIds,
|
||||
code: FlushMountedPresenceSensorModel.codeSensitivity,
|
||||
value: value,
|
||||
),
|
||||
@ -83,7 +84,8 @@ class FlushMountedPresenceSensorBatchControlView extends StatelessWidget
|
||||
valuesPercision: 1,
|
||||
action: (double value) =>
|
||||
context.read<FlushMountedPresenceSensorBloc>().add(
|
||||
FlushMountedPresenceSensorChangeValueEvent(
|
||||
FlushMountedPresenceSensorBatchControlEvent(
|
||||
deviceIds: devicesIds,
|
||||
code: FlushMountedPresenceSensorModel.codeNearDetection,
|
||||
value: (value * 100).toInt(),
|
||||
),
|
||||
@ -99,7 +101,8 @@ class FlushMountedPresenceSensorBatchControlView extends StatelessWidget
|
||||
valuesPercision: 1,
|
||||
action: (double value) =>
|
||||
context.read<FlushMountedPresenceSensorBloc>().add(
|
||||
FlushMountedPresenceSensorChangeValueEvent(
|
||||
FlushMountedPresenceSensorBatchControlEvent(
|
||||
deviceIds: devicesIds,
|
||||
code: FlushMountedPresenceSensorModel.codeFarDetection,
|
||||
value: (value * 100).toInt(),
|
||||
),
|
||||
@ -112,20 +115,22 @@ class FlushMountedPresenceSensorBatchControlView extends StatelessWidget
|
||||
maxValue: 3,
|
||||
steps: 1,
|
||||
action: (int value) => context.read<FlushMountedPresenceSensorBloc>().add(
|
||||
FlushMountedPresenceSensorChangeValueEvent(
|
||||
FlushMountedPresenceSensorBatchControlEvent(
|
||||
deviceIds: devicesIds,
|
||||
code: FlushMountedPresenceSensorModel.codePresenceDelay,
|
||||
value: value,
|
||||
),
|
||||
),
|
||||
),
|
||||
PresenceUpdateData(
|
||||
value: (model.occurDistReduce.toDouble()),
|
||||
value: model.occurDistReduce.toDouble(),
|
||||
title: 'Indent Level:',
|
||||
minValue: 0,
|
||||
maxValue: 3,
|
||||
steps: 1,
|
||||
action: (int value) => context.read<FlushMountedPresenceSensorBloc>().add(
|
||||
FlushMountedPresenceSensorChangeValueEvent(
|
||||
FlushMountedPresenceSensorBatchControlEvent(
|
||||
deviceIds: devicesIds,
|
||||
code: FlushMountedPresenceSensorModel.codeOccurDistReduce,
|
||||
value: value,
|
||||
),
|
||||
@ -139,7 +144,8 @@ class FlushMountedPresenceSensorBatchControlView extends StatelessWidget
|
||||
maxValue: 3,
|
||||
steps: 1,
|
||||
action: (int value) => context.read<FlushMountedPresenceSensorBloc>().add(
|
||||
FlushMountedPresenceSensorChangeValueEvent(
|
||||
FlushMountedPresenceSensorBatchControlEvent(
|
||||
deviceIds: devicesIds,
|
||||
code: FlushMountedPresenceSensorModel.codeSensiReduce,
|
||||
value: value,
|
||||
),
|
||||
@ -154,7 +160,8 @@ class FlushMountedPresenceSensorBatchControlView extends StatelessWidget
|
||||
steps: 1,
|
||||
action: (double value) =>
|
||||
context.read<FlushMountedPresenceSensorBloc>().add(
|
||||
FlushMountedPresenceSensorChangeValueEvent(
|
||||
FlushMountedPresenceSensorBatchControlEvent(
|
||||
deviceIds: devicesIds,
|
||||
code: FlushMountedPresenceSensorModel.codeNoneDelay,
|
||||
value: (value * 10).round(),
|
||||
),
|
||||
|
@ -24,7 +24,7 @@ class FlushMountedPresenceSensorControlView extends StatelessWidget
|
||||
return BlocProvider(
|
||||
create: (context) => FlushMountedPresenceSensorBlocFactory.create(
|
||||
deviceId: device.uuid ?? '-1',
|
||||
),
|
||||
)..add(FlushMountedPresenceSensorFetchStatusEvent()),
|
||||
child: BlocBuilder<FlushMountedPresenceSensorBloc,
|
||||
FlushMountedPresenceSensorState>(
|
||||
builder: (context, state) {
|
||||
|
@ -84,6 +84,16 @@ class _PresenceUpdateDataState extends State<PresenceNoBodyTime> {
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void didUpdateWidget(PresenceNoBodyTime oldWidget) {
|
||||
super.didUpdateWidget(oldWidget);
|
||||
if (oldWidget.value != widget.value) {
|
||||
setState(() {
|
||||
_currentValue = widget.value;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return DeviceControlsContainer(
|
||||
|
@ -21,6 +21,7 @@ class WallSensorBloc extends Bloc<WallSensorEvent, WallSensorState> {
|
||||
on<ShowDescriptionEvent>(_showDescription);
|
||||
on<BackToGridViewEvent>(_backToGridView);
|
||||
on<WallSensorFactoryResetEvent>(_onFactoryReset);
|
||||
on<WallSensorRealtimeUpdateEvent>(_onRealtimeUpdate);
|
||||
}
|
||||
|
||||
void _fetchWallSensorStatus(
|
||||
@ -30,7 +31,7 @@ class WallSensorBloc extends Bloc<WallSensorEvent, WallSensorState> {
|
||||
var response = await DevicesManagementApi().getDeviceStatus(deviceId);
|
||||
deviceStatus = WallSensorModel.fromJson(response.status);
|
||||
emit(WallSensorUpdateState(wallSensorModel: deviceStatus));
|
||||
_listenToChanges(emit, deviceId);
|
||||
_listenToChanges(deviceId);
|
||||
} catch (e) {
|
||||
emit(WallSensorFailedState(error: e.toString()));
|
||||
return;
|
||||
@ -52,28 +53,27 @@ class WallSensorBloc extends Bloc<WallSensorEvent, WallSensorState> {
|
||||
}
|
||||
}
|
||||
|
||||
_listenToChanges(Emitter<WallSensorState> emit, deviceId) {
|
||||
try {
|
||||
DatabaseReference ref =
|
||||
FirebaseDatabase.instance.ref('device-status/$deviceId');
|
||||
Stream<DatabaseEvent> stream = ref.onValue;
|
||||
void _listenToChanges(String deviceId) {
|
||||
DatabaseReference ref =
|
||||
FirebaseDatabase.instance.ref('device-status/$deviceId');
|
||||
ref.onValue.listen((DatabaseEvent event) {
|
||||
final data = event.snapshot.value as Map<dynamic, dynamic>?;
|
||||
if (data == null) return;
|
||||
|
||||
stream.listen((DatabaseEvent event) {
|
||||
Map<dynamic, dynamic> usersMap =
|
||||
event.snapshot.value as Map<dynamic, dynamic>;
|
||||
List<Status> statusList = [];
|
||||
final statusList = (data['status'] as List?)
|
||||
?.map((e) => Status(code: e['code'], value: e['value']))
|
||||
.toList();
|
||||
|
||||
usersMap['status'].forEach((element) {
|
||||
statusList
|
||||
.add(Status(code: element['code'], value: element['value']));
|
||||
});
|
||||
|
||||
deviceStatus = WallSensorModel.fromJson(statusList);
|
||||
emit(WallSensorLoadingNewSate(wallSensorModel: deviceStatus));
|
||||
});
|
||||
} catch (_) {}
|
||||
if (statusList != null) {
|
||||
final updatedDeviceStatus = WallSensorModel.fromJson(statusList);
|
||||
if (!isClosed) {
|
||||
add(WallSensorRealtimeUpdateEvent(updatedDeviceStatus));
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
void _changeValue(
|
||||
WallSensorChangeValueEvent event, Emitter<WallSensorState> emit) async {
|
||||
emit(WallSensorLoadingNewSate(wallSensorModel: deviceStatus));
|
||||
@ -195,4 +195,12 @@ class WallSensorBloc extends Bloc<WallSensorEvent, WallSensorState> {
|
||||
emit(WallSensorFailedState(error: e.toString()));
|
||||
}
|
||||
}
|
||||
|
||||
void _onRealtimeUpdate(
|
||||
WallSensorRealtimeUpdateEvent event,
|
||||
Emitter<WallSensorState> emit,
|
||||
) {
|
||||
deviceStatus = event.deviceStatus;
|
||||
emit(WallSensorUpdateState(wallSensorModel: deviceStatus));
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,6 @@
|
||||
import 'package:equatable/equatable.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/all_devices/models/factory_reset_model.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/wall_sensor/model/wall_sensor_model.dart';
|
||||
|
||||
abstract class WallSensorEvent extends Equatable {
|
||||
const WallSensorEvent();
|
||||
@ -70,3 +71,8 @@ class WallSensorFactoryResetEvent extends WallSensorEvent {
|
||||
required this.factoryReset,
|
||||
});
|
||||
}
|
||||
|
||||
class WallSensorRealtimeUpdateEvent extends WallSensorEvent {
|
||||
final WallSensorModel deviceStatus;
|
||||
const WallSensorRealtimeUpdateEvent(this.deviceStatus);
|
||||
}
|
||||
|
@ -9,6 +9,7 @@ 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/widgets/dialog_footer.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/utils/color_manager.dart';
|
||||
import 'package:syncrow_web/utils/extension/build_context_x.dart';
|
||||
|
||||
@ -74,11 +75,26 @@ class ACHelper {
|
||||
child: _buildFunctionsList(
|
||||
context: context,
|
||||
acFunctions: acFunctions,
|
||||
onFunctionSelected: (functionCode, operationName) =>
|
||||
context.read<FunctionBloc>().add(SelectFunction(
|
||||
functionCode: functionCode,
|
||||
operationName: operationName,
|
||||
)),
|
||||
device: device,
|
||||
onFunctionSelected: (functionCode, operationName) {
|
||||
RoutineTapFunctionHelper.onTapFunction(
|
||||
context,
|
||||
functionCode: functionCode,
|
||||
functionOperationName: operationName,
|
||||
functionValueDescription:
|
||||
selectedFunctionData.valueDescription,
|
||||
deviceUuid: device?.uuid,
|
||||
codesToAddIntoFunctionsWithDefaultValue: [
|
||||
'temp_set',
|
||||
'temp_current',
|
||||
],
|
||||
defaultValue: functionCode == 'temp_set'
|
||||
? 200
|
||||
: functionCode == 'temp_current'
|
||||
? -100
|
||||
: 0,
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
// Value selector
|
||||
@ -137,6 +153,7 @@ class ACHelper {
|
||||
required BuildContext context,
|
||||
required List<ACFunction> acFunctions,
|
||||
required Function(String, String) onFunctionSelected,
|
||||
required AllDevicesModel? device,
|
||||
}) {
|
||||
return ListView.separated(
|
||||
shrinkWrap: false,
|
||||
|
@ -131,7 +131,12 @@ class _CeilingSensorDialogState extends State<CeilingSensorDialog> {
|
||||
return Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
CpsFunctionsList(cpsFunctions: _cpsFunctions),
|
||||
CpsFunctionsList(
|
||||
cpsFunctions: _cpsFunctions,
|
||||
device: widget.device,
|
||||
selectedFunctionData: selectedFunctionData,
|
||||
dialogType: widget.dialogType,
|
||||
),
|
||||
if (state.selectedFunction != null)
|
||||
Expanded(
|
||||
child: isToggleFunction
|
||||
|
@ -1,14 +1,24 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:syncrow_web/pages/routines/bloc/functions_bloc/functions_bloc_bloc.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/all_devices/models/devices_model.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/widgets/routine_dialog_function_list_tile.dart';
|
||||
import 'package:syncrow_web/pages/routines/widgets/routine_dialogs/helpers/routine_tap_function_helper.dart';
|
||||
import 'package:syncrow_web/utils/color_manager.dart';
|
||||
|
||||
class CpsFunctionsList extends StatelessWidget {
|
||||
const CpsFunctionsList({required this.cpsFunctions, super.key});
|
||||
const CpsFunctionsList({
|
||||
required this.cpsFunctions,
|
||||
required this.device,
|
||||
required this.selectedFunctionData,
|
||||
required this.dialogType,
|
||||
super.key,
|
||||
});
|
||||
|
||||
final List<CpsFunctions> cpsFunctions;
|
||||
final AllDevicesModel? device;
|
||||
final DeviceFunctionData? selectedFunctionData;
|
||||
final String dialogType;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
@ -26,12 +36,27 @@ class CpsFunctionsList extends StatelessWidget {
|
||||
return RoutineDialogFunctionListTile(
|
||||
iconPath: function.icon,
|
||||
operationName: function.operationName,
|
||||
onTap: () => context.read<FunctionBloc>().add(
|
||||
SelectFunction(
|
||||
functionCode: function.code,
|
||||
operationName: function.operationName,
|
||||
),
|
||||
),
|
||||
onTap: () => RoutineTapFunctionHelper.onTapFunction(
|
||||
context,
|
||||
functionCode: function.code,
|
||||
functionOperationName: function.operationName,
|
||||
functionValueDescription: selectedFunctionData?.valueDescription,
|
||||
deviceUuid: device?.uuid,
|
||||
codesToAddIntoFunctionsWithDefaultValue: [
|
||||
'static_max_dis',
|
||||
'presence_reference',
|
||||
'moving_reference',
|
||||
'perceptual_boundary',
|
||||
'moving_boundary',
|
||||
'moving_rigger_time',
|
||||
'moving_static_time',
|
||||
'none_body_time',
|
||||
'moving_max_dis',
|
||||
'moving_range',
|
||||
'presence_range',
|
||||
if (dialogType == "IF") 'sensitivity',
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
|
@ -0,0 +1,46 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_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';
|
||||
|
||||
abstract final class RoutineTapFunctionHelper {
|
||||
const RoutineTapFunctionHelper._();
|
||||
|
||||
static void onTapFunction(
|
||||
BuildContext context, {
|
||||
required String functionCode,
|
||||
required String functionOperationName,
|
||||
required String? functionValueDescription,
|
||||
required String? deviceUuid,
|
||||
required List<String> codesToAddIntoFunctionsWithDefaultValue,
|
||||
int defaultValue = 0,
|
||||
}) {
|
||||
final functionsBloc = context.read<FunctionBloc>();
|
||||
functionsBloc.add(
|
||||
SelectFunction(
|
||||
functionCode: functionCode,
|
||||
operationName: functionOperationName,
|
||||
),
|
||||
);
|
||||
final addedFunctions = functionsBloc.state.addedFunctions;
|
||||
final isFunctionAlreadyAdded = addedFunctions.any(
|
||||
(e) => e.functionCode == functionCode,
|
||||
);
|
||||
final shouldAddFunction =
|
||||
codesToAddIntoFunctionsWithDefaultValue.contains(functionCode);
|
||||
if (!isFunctionAlreadyAdded && shouldAddFunction) {
|
||||
context.read<FunctionBloc>().add(
|
||||
AddFunction(
|
||||
functionData: DeviceFunctionData(
|
||||
entityId: deviceUuid ?? '',
|
||||
functionCode: functionCode,
|
||||
operationName: functionOperationName,
|
||||
value: defaultValue,
|
||||
condition: '==',
|
||||
valueDescription: functionValueDescription,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
@ -11,6 +11,7 @@ import 'package:syncrow_web/pages/routines/models/gang_switches/one_gang_switch/
|
||||
import 'package:syncrow_web/pages/routines/models/gang_switches/switch_operational_value.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/helpers/routine_tap_function_helper.dart';
|
||||
import 'package:syncrow_web/utils/color_manager.dart';
|
||||
import 'package:syncrow_web/utils/extension/build_context_x.dart';
|
||||
|
||||
@ -24,21 +25,23 @@ class OneGangSwitchHelper {
|
||||
required String uniqueCustomId,
|
||||
required bool removeComparetors,
|
||||
}) async {
|
||||
List<BaseSwitchFunction> oneGangFunctions = functions.whereType<BaseSwitchFunction>().toList();
|
||||
List<BaseSwitchFunction> oneGangFunctions =
|
||||
functions.whereType<BaseSwitchFunction>().toList();
|
||||
|
||||
return showDialog<Map<String, dynamic>?>(
|
||||
context: context,
|
||||
builder: (BuildContext context) {
|
||||
return BlocProvider(
|
||||
create: (_) => FunctionBloc()..add(InitializeFunctions(deviceSelectedFunctions ?? [])),
|
||||
create: (_) => FunctionBloc()
|
||||
..add(InitializeFunctions(deviceSelectedFunctions ?? [])),
|
||||
child: AlertDialog(
|
||||
contentPadding: EdgeInsets.zero,
|
||||
content: BlocBuilder<FunctionBloc, FunctionBlocState>(
|
||||
builder: (context, state) {
|
||||
final selectedFunction = state.selectedFunction;
|
||||
final selectedOperationName = state.selectedOperationName;
|
||||
final selectedFunctionData =
|
||||
state.addedFunctions.firstWhere((f) => f.functionCode == selectedFunction,
|
||||
final selectedFunctionData = state.addedFunctions
|
||||
.firstWhere((f) => f.functionCode == selectedFunction,
|
||||
orElse: () => DeviceFunctionData(
|
||||
entityId: '',
|
||||
functionCode: selectedFunction ?? '',
|
||||
@ -84,12 +87,19 @@ class OneGangSwitchHelper {
|
||||
size: 16,
|
||||
color: ColorsManager.textGray,
|
||||
),
|
||||
onTap: () {
|
||||
context.read<FunctionBloc>().add(SelectFunction(
|
||||
functionCode: function.code,
|
||||
operationName: function.operationName,
|
||||
));
|
||||
},
|
||||
onTap: () =>
|
||||
RoutineTapFunctionHelper.onTapFunction(
|
||||
context,
|
||||
functionCode: function.code,
|
||||
functionOperationName:
|
||||
function.operationName,
|
||||
functionValueDescription:
|
||||
selectedFunctionData.valueDescription,
|
||||
deviceUuid: device?.uuid,
|
||||
codesToAddIntoFunctionsWithDefaultValue: [
|
||||
'countdown_1',
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
@ -220,11 +230,11 @@ class OneGangSwitchHelper {
|
||||
selectedFunctionData,
|
||||
),
|
||||
const SizedBox(height: 20),
|
||||
_buildCountDownDisplay(
|
||||
context, initialValue, device, operationName, selectedFunctionData, selectCode),
|
||||
_buildCountDownDisplay(context, initialValue, device, operationName,
|
||||
selectedFunctionData, selectCode),
|
||||
const SizedBox(height: 20),
|
||||
_buildCountDownSlider(
|
||||
context, initialValue, device, operationName, selectedFunctionData, selectCode),
|
||||
_buildCountDownSlider(context, initialValue, device, operationName,
|
||||
selectedFunctionData, selectCode),
|
||||
],
|
||||
);
|
||||
}
|
||||
@ -314,9 +324,10 @@ class OneGangSwitchHelper {
|
||||
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(),
|
||||
divisions:
|
||||
(((operationalValues.maxValue ?? 0) - (operationalValues.minValue ?? 0)) /
|
||||
(operationalValues.stepValue ?? 1))
|
||||
.round(),
|
||||
onChanged: (value) {
|
||||
context.read<FunctionBloc>().add(
|
||||
AddFunction(
|
||||
@ -368,7 +379,9 @@ class OneGangSwitchHelper {
|
||||
trailing: Icon(
|
||||
isSelected ? Icons.radio_button_checked : Icons.radio_button_unchecked,
|
||||
size: 24,
|
||||
color: isSelected ? ColorsManager.primaryColorWithOpacity : ColorsManager.textGray,
|
||||
color: isSelected
|
||||
? ColorsManager.primaryColorWithOpacity
|
||||
: ColorsManager.textGray,
|
||||
),
|
||||
onTap: () {
|
||||
if (!isSelected) {
|
||||
|
@ -10,6 +10,7 @@ import 'package:syncrow_web/pages/routines/models/gang_switches/base_switch_func
|
||||
import 'package:syncrow_web/pages/routines/models/gang_switches/switch_operational_value.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/helpers/routine_tap_function_helper.dart';
|
||||
import 'package:syncrow_web/utils/color_manager.dart';
|
||||
import 'package:syncrow_web/utils/extension/build_context_x.dart';
|
||||
|
||||
@ -85,15 +86,21 @@ class ThreeGangSwitchHelper {
|
||||
size: 16,
|
||||
color: ColorsManager.textGray,
|
||||
),
|
||||
onTap: () {
|
||||
context
|
||||
.read<FunctionBloc>()
|
||||
.add(SelectFunction(
|
||||
functionCode: function.code,
|
||||
operationName:
|
||||
function.operationName,
|
||||
));
|
||||
},
|
||||
onTap: () =>
|
||||
RoutineTapFunctionHelper.onTapFunction(
|
||||
context,
|
||||
functionCode: function.code,
|
||||
functionOperationName:
|
||||
function.operationName,
|
||||
functionValueDescription:
|
||||
selectedFunctionData.valueDescription,
|
||||
deviceUuid: device?.uuid,
|
||||
codesToAddIntoFunctionsWithDefaultValue: [
|
||||
'countdown_1',
|
||||
'countdown_2',
|
||||
'countdown_3',
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
@ -183,8 +190,7 @@ class ThreeGangSwitchHelper {
|
||||
);
|
||||
}
|
||||
|
||||
final selectedFn =
|
||||
switchFunctions.firstWhere((f) => f.code == selectedFunction);
|
||||
final selectedFn = switchFunctions.firstWhere((f) => f.code == selectedFunction);
|
||||
final values = selectedFn.getOperationalValues();
|
||||
|
||||
return _buildOperationalValuesList(
|
||||
@ -266,8 +272,7 @@ class ThreeGangSwitchHelper {
|
||||
minHeight: 40.0,
|
||||
minWidth: 40.0,
|
||||
),
|
||||
isSelected:
|
||||
conditions.map((c) => c == (currentCondition ?? "==")).toList(),
|
||||
isSelected: conditions.map((c) => c == (currentCondition ?? "==")).toList(),
|
||||
children: conditions.map((c) => Text(c)).toList(),
|
||||
);
|
||||
}
|
||||
@ -316,10 +321,10 @@ class ThreeGangSwitchHelper {
|
||||
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(),
|
||||
divisions:
|
||||
(((operationalValues.maxValue ?? 0) - (operationalValues.minValue ?? 0)) /
|
||||
(operationalValues.stepValue ?? 1))
|
||||
.round(),
|
||||
onChanged: (value) {
|
||||
context.read<FunctionBloc>().add(
|
||||
AddFunction(
|
||||
@ -369,9 +374,7 @@ class ThreeGangSwitchHelper {
|
||||
style: context.textTheme.bodyMedium,
|
||||
),
|
||||
trailing: Icon(
|
||||
isSelected
|
||||
? Icons.radio_button_checked
|
||||
: Icons.radio_button_unchecked,
|
||||
isSelected ? Icons.radio_button_checked : Icons.radio_button_unchecked,
|
||||
size: 24,
|
||||
color: isSelected
|
||||
? ColorsManager.primaryColorWithOpacity
|
||||
@ -387,8 +390,7 @@ class ThreeGangSwitchHelper {
|
||||
operationName: operationName,
|
||||
value: value.value,
|
||||
condition: selectedFunctionData?.condition,
|
||||
valueDescription:
|
||||
selectedFunctionData?.valueDescription,
|
||||
valueDescription: selectedFunctionData?.valueDescription,
|
||||
),
|
||||
),
|
||||
);
|
||||
|
@ -10,6 +10,7 @@ import 'package:syncrow_web/pages/routines/models/gang_switches/base_switch_func
|
||||
import 'package:syncrow_web/pages/routines/models/gang_switches/switch_operational_value.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/helpers/routine_tap_function_helper.dart';
|
||||
import 'package:syncrow_web/utils/color_manager.dart';
|
||||
import 'package:syncrow_web/utils/extension/build_context_x.dart';
|
||||
|
||||
@ -85,15 +86,20 @@ class TwoGangSwitchHelper {
|
||||
size: 16,
|
||||
color: ColorsManager.textGray,
|
||||
),
|
||||
onTap: () {
|
||||
context
|
||||
.read<FunctionBloc>()
|
||||
.add(SelectFunction(
|
||||
functionCode: function.code,
|
||||
operationName:
|
||||
function.operationName,
|
||||
));
|
||||
},
|
||||
onTap: () =>
|
||||
RoutineTapFunctionHelper.onTapFunction(
|
||||
context,
|
||||
functionCode: function.code,
|
||||
functionOperationName:
|
||||
function.operationName,
|
||||
functionValueDescription:
|
||||
selectedFunctionData.valueDescription,
|
||||
deviceUuid: device?.uuid,
|
||||
codesToAddIntoFunctionsWithDefaultValue: [
|
||||
'countdown_1',
|
||||
'countdown_2',
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
@ -167,8 +173,7 @@ class TwoGangSwitchHelper {
|
||||
required String operationName,
|
||||
required bool removeComparetors,
|
||||
}) {
|
||||
if (selectedFunction == 'countdown_1' ||
|
||||
selectedFunction == 'countdown_2') {
|
||||
if (selectedFunction == 'countdown_1' || selectedFunction == 'countdown_2') {
|
||||
final initialValue = selectedFunctionData?.value ?? 0;
|
||||
return _buildTemperatureSelector(
|
||||
context: context,
|
||||
@ -182,8 +187,7 @@ class TwoGangSwitchHelper {
|
||||
);
|
||||
}
|
||||
|
||||
final selectedFn =
|
||||
switchFunctions.firstWhere((f) => f.code == selectedFunction);
|
||||
final selectedFn = switchFunctions.firstWhere((f) => f.code == selectedFunction);
|
||||
final values = selectedFn.getOperationalValues();
|
||||
|
||||
return _buildOperationalValuesList(
|
||||
@ -265,8 +269,7 @@ class TwoGangSwitchHelper {
|
||||
minHeight: 40.0,
|
||||
minWidth: 40.0,
|
||||
),
|
||||
isSelected:
|
||||
conditions.map((c) => c == (currentCondition ?? "==")).toList(),
|
||||
isSelected: conditions.map((c) => c == (currentCondition ?? "==")).toList(),
|
||||
children: conditions.map((c) => Text(c)).toList(),
|
||||
);
|
||||
}
|
||||
@ -315,10 +318,10 @@ class TwoGangSwitchHelper {
|
||||
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(),
|
||||
divisions:
|
||||
(((operationalValues.maxValue ?? 0) - (operationalValues.minValue ?? 0)) /
|
||||
(operationalValues.stepValue ?? 1))
|
||||
.round(),
|
||||
onChanged: (value) {
|
||||
context.read<FunctionBloc>().add(
|
||||
AddFunction(
|
||||
@ -368,9 +371,7 @@ class TwoGangSwitchHelper {
|
||||
style: context.textTheme.bodyMedium,
|
||||
),
|
||||
trailing: Icon(
|
||||
isSelected
|
||||
? Icons.radio_button_checked
|
||||
: Icons.radio_button_unchecked,
|
||||
isSelected ? Icons.radio_button_checked : Icons.radio_button_unchecked,
|
||||
size: 24,
|
||||
color: isSelected
|
||||
? ColorsManager.primaryColorWithOpacity
|
||||
@ -386,8 +387,7 @@ class TwoGangSwitchHelper {
|
||||
operationName: operationName,
|
||||
value: value.value,
|
||||
condition: selectedFunctionData?.condition,
|
||||
valueDescription:
|
||||
selectedFunctionData?.valueDescription,
|
||||
valueDescription: selectedFunctionData?.valueDescription,
|
||||
),
|
||||
),
|
||||
);
|
||||
|
@ -8,6 +8,7 @@ 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/widgets/dialog_footer.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/wall_sensor/wps_value_selector_widget.dart';
|
||||
import 'package:syncrow_web/utils/color_manager.dart';
|
||||
import 'package:syncrow_web/utils/extension/build_context_x.dart';
|
||||
@ -111,13 +112,23 @@ class _WallPresenceSensorState extends State<WallPresenceSensor> {
|
||||
return Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
_buildFunctionList(context),
|
||||
_buildFunctionList(context, state),
|
||||
if (state.selectedFunction != null) _buildValueSelector(context, state),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildFunctionList(BuildContext context) {
|
||||
Widget _buildFunctionList(BuildContext context, FunctionBlocState state) {
|
||||
final selectedFunction = state.selectedFunction;
|
||||
final selectedFunctionData = state.addedFunctions.firstWhere(
|
||||
(f) => f.functionCode == selectedFunction,
|
||||
orElse: () => DeviceFunctionData(
|
||||
entityId: '',
|
||||
functionCode: selectedFunction ?? '',
|
||||
operationName: '',
|
||||
value: null,
|
||||
),
|
||||
);
|
||||
return SizedBox(
|
||||
width: 360,
|
||||
child: ListView.separated(
|
||||
@ -149,12 +160,18 @@ class _WallPresenceSensorState extends State<WallPresenceSensor> {
|
||||
size: 16,
|
||||
color: ColorsManager.textGray,
|
||||
),
|
||||
onTap: () => context.read<FunctionBloc>().add(
|
||||
SelectFunction(
|
||||
functionCode: function.code,
|
||||
operationName: function.operationName,
|
||||
),
|
||||
),
|
||||
onTap: () => RoutineTapFunctionHelper.onTapFunction(
|
||||
context,
|
||||
functionCode: function.code,
|
||||
functionOperationName: function.operationName,
|
||||
functionValueDescription: selectedFunctionData.valueDescription,
|
||||
deviceUuid: widget.device?.uuid,
|
||||
codesToAddIntoFunctionsWithDefaultValue: [
|
||||
'dis_current',
|
||||
'presence_time',
|
||||
'illuminance_value',
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
|
@ -4,6 +4,7 @@ import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:syncrow_web/pages/common/bloc/project_manager.dart';
|
||||
import 'package:syncrow_web/pages/space_tree/bloc/space_tree_bloc.dart';
|
||||
import 'package:syncrow_web/pages/space_tree/bloc/space_tree_event.dart';
|
||||
import 'package:syncrow_web/pages/space_tree/bloc/space_tree_state.dart';
|
||||
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/community_model.dart';
|
||||
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/create_subspace_model.dart';
|
||||
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/product_model.dart';
|
||||
@ -246,7 +247,9 @@ class SpaceManagementBloc extends Bloc<SpaceManagementEvent, SpaceManagementStat
|
||||
final previousState = state;
|
||||
final projectUuid = await ProjectManager.getProjectUUID() ?? '';
|
||||
var spaceBloc = event.context.read<SpaceTreeBloc>();
|
||||
List<CommunityModel> communities = await _waitForCommunityList(spaceBloc);
|
||||
var spaceTreeState = event.context.read<SpaceTreeBloc>().state;
|
||||
|
||||
List<CommunityModel> communities = await _waitForCommunityList(spaceBloc, spaceTreeState);
|
||||
await fetchSpaceModels();
|
||||
await fetchTags();
|
||||
|
||||
@ -277,11 +280,13 @@ class SpaceManagementBloc extends Bloc<SpaceManagementEvent, SpaceManagementStat
|
||||
LoadCommunityAndSpacesEvent event,
|
||||
Emitter<SpaceManagementState> emit,
|
||||
) async {
|
||||
var spaceTreeState = event.context.read<SpaceTreeBloc>().state;
|
||||
var spaceBloc = event.context.read<SpaceTreeBloc>();
|
||||
|
||||
_onloadProducts();
|
||||
await fetchTags();
|
||||
// Wait until `communityList` is loaded
|
||||
List<CommunityModel> communities = await _waitForCommunityList(spaceBloc);
|
||||
List<CommunityModel> communities = await _waitForCommunityList(spaceBloc, spaceTreeState);
|
||||
|
||||
// Fetch space models after communities are available
|
||||
final prevSpaceModels = await fetchSpaceModels();
|
||||
@ -292,23 +297,38 @@ class SpaceManagementBloc extends Bloc<SpaceManagementEvent, SpaceManagementStat
|
||||
allTags: _cachedTags ?? []));
|
||||
}
|
||||
|
||||
Future<List<CommunityModel>> _waitForCommunityList(SpaceTreeBloc spaceBloc) async {
|
||||
Future<List<CommunityModel>> _waitForCommunityList(
|
||||
SpaceTreeBloc spaceBloc, SpaceTreeState spaceTreeState) async {
|
||||
// Check if communityList is already populated
|
||||
if (spaceBloc.state.communityList.isNotEmpty) {
|
||||
return spaceBloc.state.communityList;
|
||||
final filteredCommunities = spaceTreeState.searchQuery.isNotEmpty
|
||||
? spaceTreeState.filteredCommunity
|
||||
: spaceTreeState.communityList;
|
||||
if (filteredCommunities.isNotEmpty) {
|
||||
return filteredCommunities;
|
||||
}
|
||||
|
||||
final completer = Completer<List<CommunityModel>>();
|
||||
final subscription = spaceBloc.stream.listen((state) {
|
||||
if (state.communityList.isNotEmpty) {
|
||||
completer.complete(state.communityList);
|
||||
if (!completer.isCompleted && state.communityList.isNotEmpty) {
|
||||
completer
|
||||
.complete(state.searchQuery.isNotEmpty ? state.filteredCommunity : state.communityList);
|
||||
}
|
||||
});
|
||||
try {
|
||||
final communities = await completer.future.timeout(
|
||||
const Duration(seconds: 10),
|
||||
onTimeout: () {
|
||||
if (!completer.isCompleted) {
|
||||
completer.complete([]);
|
||||
}
|
||||
return [];
|
||||
},
|
||||
);
|
||||
|
||||
// Return the list once available, then cancel the listener
|
||||
final communities = await completer.future;
|
||||
await subscription.cancel();
|
||||
return communities;
|
||||
return communities;
|
||||
} finally {
|
||||
await subscription.cancel();
|
||||
}
|
||||
}
|
||||
|
||||
void _onCommunityDelete(
|
||||
@ -491,12 +511,21 @@ class SpaceManagementBloc extends Bloc<SpaceManagementEvent, SpaceManagementStat
|
||||
Future<List<SpaceModel>> saveSpacesHierarchically(
|
||||
BuildContext context, List<SpaceModel> spaces, String communityUuid) async {
|
||||
final orderedSpaces = flattenHierarchy(spaces);
|
||||
|
||||
final projectUuid = await ProjectManager.getProjectUUID() ?? '';
|
||||
var spaceBloc = context.read<SpaceTreeBloc>();
|
||||
List<CommunityModel> communities = spaceBloc.state.communityList;
|
||||
CommunityModel? selectedCommunity = communities.firstWhere(
|
||||
(community) => community.uuid == communityUuid,
|
||||
);
|
||||
CommunityModel? selectedCommunity;
|
||||
try {
|
||||
final spaceTreeState = context.read<SpaceTreeBloc>().state;
|
||||
final filteredCommunities = spaceTreeState.searchQuery.isNotEmpty
|
||||
? spaceTreeState.filteredCommunity
|
||||
: spaceTreeState.communityList;
|
||||
|
||||
selectedCommunity = filteredCommunities.firstWhere(
|
||||
(community) => community.uuid == communityUuid,
|
||||
);
|
||||
} catch (e) {
|
||||
return [];
|
||||
}
|
||||
|
||||
final parentsToDelete = orderedSpaces.where((space) =>
|
||||
space.status == SpaceStatus.deleted &&
|
||||
@ -669,9 +698,12 @@ class SpaceManagementBloc extends Bloc<SpaceManagementEvent, SpaceManagementStat
|
||||
|
||||
try {
|
||||
await fetchTags();
|
||||
final projectUuid = await ProjectManager.getProjectUUID() ?? '';
|
||||
var spaceBloc = event.context.read<SpaceTreeBloc>();
|
||||
List<CommunityModel> communities = spaceBloc.state.communityList;
|
||||
final spaceTreeState = event.context.read<SpaceTreeBloc>().state;
|
||||
final filteredCommunities = spaceTreeState.searchQuery.isNotEmpty
|
||||
? spaceTreeState.filteredCommunity
|
||||
: spaceTreeState.communityList;
|
||||
|
||||
List<CommunityModel> communities = filteredCommunities;
|
||||
|
||||
var prevSpaceModels = await fetchSpaceModels();
|
||||
|
||||
|
@ -1,4 +1,6 @@
|
||||
// Flutter imports
|
||||
import 'dart:math';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
|
||||
@ -336,6 +338,7 @@ class _CommunityStructureAreaState extends State<CommunityStructureArea> {
|
||||
}
|
||||
spaces.add(newSpace);
|
||||
_updateNodePosition(newSpace, newSpace.position);
|
||||
realignTree();
|
||||
});
|
||||
},
|
||||
);
|
||||
@ -450,7 +453,6 @@ class _CommunityStructureAreaState extends State<CommunityStructureArea> {
|
||||
|
||||
void _saveSpaces() {
|
||||
if (widget.selectedCommunity == null) {
|
||||
debugPrint("No community selected for saving spaces.");
|
||||
return;
|
||||
}
|
||||
|
||||
@ -465,7 +467,6 @@ class _CommunityStructureAreaState extends State<CommunityStructureArea> {
|
||||
}
|
||||
|
||||
String communityUuid = widget.selectedCommunity!.uuid;
|
||||
|
||||
context.read<SpaceManagementBloc>().add(SaveSpacesEvent(
|
||||
context,
|
||||
spaces: spacesToSave,
|
||||
@ -530,35 +531,83 @@ class _CommunityStructureAreaState extends State<CommunityStructureArea> {
|
||||
}
|
||||
|
||||
Offset getBalancedChildPosition(SpaceModel parent) {
|
||||
int totalSiblings = parent.children.length + 1;
|
||||
double totalWidth = (totalSiblings - 1) * 250; // Horizontal spacing
|
||||
double startX = parent.position.dx - (totalWidth / 2);
|
||||
const double nodeWidth = 200;
|
||||
const double verticalGap = 180;
|
||||
|
||||
Offset position = Offset(startX + (parent.children.length * 250), parent.position.dy + 180);
|
||||
if (parent.children.isEmpty) {
|
||||
// First child → exactly center
|
||||
return Offset(parent.position.dx, parent.position.dy + verticalGap);
|
||||
} else {
|
||||
// More children → arrange them spaced horizontally
|
||||
double totalWidth = (parent.children.length) * (nodeWidth + 60);
|
||||
double startX = parent.position.dx - (totalWidth / 2);
|
||||
|
||||
// Check for overlaps & adjust
|
||||
while (spaces.any((s) => (s.position - position).distance < 250)) {
|
||||
position = Offset(position.dx + 250, position.dy);
|
||||
double childX = startX + (parent.children.length * (nodeWidth + 60));
|
||||
return Offset(childX, parent.position.dy + verticalGap);
|
||||
}
|
||||
|
||||
return position;
|
||||
}
|
||||
|
||||
void realignTree() {
|
||||
void updatePositions(SpaceModel node, double x, double y) {
|
||||
node.position = Offset(x, y);
|
||||
const double nodeWidth = 200;
|
||||
const double nodeHeight = 100;
|
||||
const double horizontalGap = 60;
|
||||
const double verticalGap = 180;
|
||||
const double rootGap = 400; // extra space between different roots
|
||||
|
||||
int numChildren = node.children.length;
|
||||
double childStartX = x - ((numChildren - 1) * 250) / 2;
|
||||
double canvasRightEdge = 1000;
|
||||
double canvasBottomEdge = 1000;
|
||||
|
||||
for (int i = 0; i < numChildren; i++) {
|
||||
updatePositions(node.children[i], childStartX + (i * 250), y + 180);
|
||||
double calculateSubtreeWidth(SpaceModel node) {
|
||||
if (node.children.isEmpty) return nodeWidth;
|
||||
double totalWidth = 0;
|
||||
for (var child in node.children) {
|
||||
totalWidth += calculateSubtreeWidth(child) + horizontalGap;
|
||||
}
|
||||
return totalWidth - horizontalGap;
|
||||
}
|
||||
|
||||
void layoutSubtree(SpaceModel node, double startX, double y) {
|
||||
double subtreeWidth = calculateSubtreeWidth(node);
|
||||
double centerX = startX + subtreeWidth / 2 - nodeWidth / 2;
|
||||
node.position = Offset(centerX, y);
|
||||
|
||||
canvasRightEdge = max(canvasRightEdge, centerX + nodeWidth);
|
||||
canvasBottomEdge = max(canvasBottomEdge, y + nodeHeight);
|
||||
|
||||
if (node.children.length == 1) {
|
||||
final child = node.children.first;
|
||||
layoutSubtree(child, centerX, y + verticalGap);
|
||||
} else {
|
||||
double childX = startX;
|
||||
for (var child in node.children) {
|
||||
double childWidth = calculateSubtreeWidth(child);
|
||||
layoutSubtree(child, childX, y + verticalGap);
|
||||
childX += childWidth + horizontalGap;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (spaces.isNotEmpty) {
|
||||
updatePositions(spaces.first, spaces.first.position.dx, spaces.first.position.dy);
|
||||
// ⚡ New: layout each root separately
|
||||
final List<SpaceModel> roots = spaces
|
||||
.where((s) =>
|
||||
s.parent == null &&
|
||||
s.status != SpaceStatus.deleted &&
|
||||
s.status != SpaceStatus.parentDeleted)
|
||||
.toList();
|
||||
|
||||
double currentX = 100; // start some margin from left
|
||||
double currentY = 100; // top margin
|
||||
|
||||
for (var root in roots) {
|
||||
layoutSubtree(root, currentX, currentY);
|
||||
double rootWidth = calculateSubtreeWidth(root);
|
||||
currentX += rootWidth + rootGap;
|
||||
}
|
||||
|
||||
setState(() {
|
||||
canvasWidth = canvasRightEdge + 400;
|
||||
canvasHeight = canvasBottomEdge + 400;
|
||||
});
|
||||
}
|
||||
|
||||
void _onDuplicate(BuildContext parentContext) {
|
||||
@ -642,63 +691,19 @@ class _CommunityStructureAreaState extends State<CommunityStructureArea> {
|
||||
}
|
||||
|
||||
void _duplicateSpace(SpaceModel space) {
|
||||
final Map<SpaceModel, SpaceModel> originalToDuplicate = {};
|
||||
double horizontalGap = 250.0; // Increased spacing
|
||||
double verticalGap = 180.0; // Adjusted for better visualization
|
||||
final double horizontalGap = 250.0;
|
||||
final double verticalGap = 180.0;
|
||||
final double nodeWidth = 200;
|
||||
final double nodeHeight = 100;
|
||||
final double breathingSpace = 300.0; // extra gap after original tree
|
||||
|
||||
print("🟢 Duplicating: ${space.name}");
|
||||
|
||||
/// **Find a new position ensuring no overlap**
|
||||
Offset getBalancedChildPosition(SpaceModel parent) {
|
||||
int totalSiblings = parent.children.length + 1;
|
||||
double totalWidth = (totalSiblings - 1) * horizontalGap;
|
||||
double startX = parent.position.dx - (totalWidth / 2);
|
||||
Offset position = Offset(
|
||||
startX + (parent.children.length * horizontalGap), parent.position.dy + verticalGap);
|
||||
|
||||
// **Check for overlaps & adjust**
|
||||
while (spaces.any((s) => (s.position - position).distance < horizontalGap)) {
|
||||
position = Offset(position.dx + horizontalGap, position.dy);
|
||||
}
|
||||
|
||||
print("🔹 New position for ${parent.name}: (${position.dx}, ${position.dy})");
|
||||
return position;
|
||||
}
|
||||
|
||||
/// **Realign the entire tree after duplication**
|
||||
void realignTree() {
|
||||
void updatePositions(SpaceModel node, double x, double y) {
|
||||
node.position = Offset(x, y);
|
||||
print("✅ Adjusted ${node.name} to (${x}, ${y})");
|
||||
|
||||
int numChildren = node.children.length;
|
||||
double childStartX = x - ((numChildren - 1) * horizontalGap) / 2;
|
||||
|
||||
for (int i = 0; i < numChildren; i++) {
|
||||
updatePositions(node.children[i], childStartX + (i * horizontalGap), y + verticalGap);
|
||||
}
|
||||
}
|
||||
|
||||
if (spaces.isNotEmpty) {
|
||||
print("🔄 Realigning tree...");
|
||||
updatePositions(spaces.first, spaces.first.position.dx, spaces.first.position.dy);
|
||||
}
|
||||
}
|
||||
|
||||
/// **Recursive duplication logic**
|
||||
/// Helper to recursively duplicate a node and its children
|
||||
SpaceModel duplicateRecursive(SpaceModel original, SpaceModel? duplicatedParent) {
|
||||
Offset newPosition = duplicatedParent == null
|
||||
? Offset(original.position.dx + horizontalGap, original.position.dy)
|
||||
: getBalancedChildPosition(duplicatedParent);
|
||||
|
||||
final duplicatedName = SpaceHelper.generateUniqueSpaceName(original.name, spaces);
|
||||
print(
|
||||
"🟡 Duplicating ${original.name} → ${duplicatedName} at (${newPosition.dx}, ${newPosition.dy})");
|
||||
|
||||
final duplicated = SpaceModel(
|
||||
name: duplicatedName,
|
||||
icon: original.icon,
|
||||
position: newPosition,
|
||||
position: Offset.zero,
|
||||
isPrivate: original.isPrivate,
|
||||
children: [],
|
||||
status: SpaceStatus.newSpace,
|
||||
@ -708,28 +713,20 @@ class _CommunityStructureAreaState extends State<CommunityStructureArea> {
|
||||
tags: original.tags,
|
||||
);
|
||||
|
||||
setState(() {
|
||||
spaces.add(duplicated);
|
||||
_updateNodePosition(duplicated, duplicated.position);
|
||||
spaces.add(duplicated);
|
||||
|
||||
if (duplicatedParent != null) {
|
||||
final newConnection = Connection(
|
||||
startSpace: duplicatedParent,
|
||||
endSpace: duplicated,
|
||||
direction: "down",
|
||||
);
|
||||
connections.add(newConnection);
|
||||
duplicated.incomingConnection = newConnection;
|
||||
duplicatedParent.addOutgoingConnection(newConnection);
|
||||
duplicatedParent.children.add(duplicated);
|
||||
print("🔗 Created connection: ${duplicatedParent.name} → ${duplicated.name}");
|
||||
}
|
||||
if (duplicatedParent != null) {
|
||||
final newConnection = Connection(
|
||||
startSpace: duplicatedParent,
|
||||
endSpace: duplicated,
|
||||
direction: "down",
|
||||
);
|
||||
connections.add(newConnection);
|
||||
duplicated.incomingConnection = newConnection;
|
||||
duplicatedParent.addOutgoingConnection(newConnection);
|
||||
duplicatedParent.children.add(duplicated);
|
||||
}
|
||||
|
||||
// **Recalculate the whole tree to avoid overlaps**
|
||||
realignTree();
|
||||
});
|
||||
|
||||
// Recursively duplicate children
|
||||
for (var child in original.children) {
|
||||
duplicateRecursive(child, duplicated);
|
||||
}
|
||||
@ -737,21 +734,49 @@ class _CommunityStructureAreaState extends State<CommunityStructureArea> {
|
||||
return duplicated;
|
||||
}
|
||||
|
||||
/// **Handle root duplication**
|
||||
if (space.parent == null) {
|
||||
print("🟠 Duplicating root node: ${space.name}");
|
||||
SpaceModel duplicatedRoot = duplicateRecursive(space, null);
|
||||
/// Layout a subtree rooted at node
|
||||
void layoutSubtree(SpaceModel node, double startX, double startY) {
|
||||
double calculateSubtreeWidth(SpaceModel n) {
|
||||
if (n.children.isEmpty) return nodeWidth;
|
||||
double width = 0;
|
||||
for (var child in n.children) {
|
||||
width += calculateSubtreeWidth(child) + horizontalGap;
|
||||
}
|
||||
return width - horizontalGap;
|
||||
}
|
||||
|
||||
setState(() {
|
||||
spaces.add(duplicatedRoot);
|
||||
realignTree();
|
||||
});
|
||||
void assignPositions(SpaceModel n, double x, double y) {
|
||||
double subtreeWidth = calculateSubtreeWidth(n);
|
||||
double centerX = x + subtreeWidth / 2 - nodeWidth / 2;
|
||||
n.position = Offset(centerX, y);
|
||||
|
||||
print("✅ Root duplication successful: ${duplicatedRoot.name}");
|
||||
} else {
|
||||
duplicateRecursive(space, space.parent);
|
||||
if (n.children.length == 1) {
|
||||
assignPositions(n.children.first, centerX, y + verticalGap);
|
||||
} else {
|
||||
double childX = x;
|
||||
for (var child in n.children) {
|
||||
double childWidth = calculateSubtreeWidth(child);
|
||||
assignPositions(child, childX, y + verticalGap);
|
||||
childX += childWidth + horizontalGap;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
double totalSubtreeWidth = calculateSubtreeWidth(node);
|
||||
assignPositions(node, startX, startY);
|
||||
}
|
||||
|
||||
print("🟢 Finished duplication process for: ${space.name}");
|
||||
/// Actual duplication process
|
||||
setState(() {
|
||||
if (space.parent == null) {
|
||||
// Duplicating a ROOT node
|
||||
SpaceModel duplicatedRoot = duplicateRecursive(space, null);
|
||||
realignTree();
|
||||
} else {
|
||||
// Duplicating a CHILD node inside its parent
|
||||
SpaceModel duplicated = duplicateRecursive(space, space.parent);
|
||||
realignTree();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -1,8 +1,11 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:syncrow_web/common/widgets/empty_search_result_widget.dart';
|
||||
import 'package:syncrow_web/common/widgets/search_bar.dart';
|
||||
import 'package:syncrow_web/common/widgets/sidebar_communities_list.dart';
|
||||
import 'package:syncrow_web/pages/space_tree/bloc/space_tree_bloc.dart';
|
||||
import 'package:syncrow_web/pages/spaces_management/all_spaces/bloc/space_management_bloc.dart';
|
||||
import 'package:syncrow_web/pages/spaces_management/all_spaces/bloc/space_management_event.dart';
|
||||
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/community_model.dart';
|
||||
@ -15,6 +18,8 @@ import 'package:syncrow_web/pages/spaces_management/structure_selector/bloc/cent
|
||||
import 'package:syncrow_web/pages/spaces_management/structure_selector/bloc/center_body_event.dart';
|
||||
import 'package:syncrow_web/utils/style.dart';
|
||||
|
||||
import '../../../space_tree/bloc/space_tree_event.dart';
|
||||
|
||||
class SidebarWidget extends StatefulWidget {
|
||||
final List<CommunityModel> communities;
|
||||
final String? selectedSpaceUuid;
|
||||
@ -33,6 +38,7 @@ class SidebarWidget extends StatefulWidget {
|
||||
|
||||
class _SidebarWidgetState extends State<SidebarWidget> {
|
||||
late final ScrollController _scrollController;
|
||||
Timer? _debounce;
|
||||
|
||||
String _searchQuery = '';
|
||||
String? _selectedSpaceUuid;
|
||||
@ -40,17 +46,46 @@ class _SidebarWidgetState extends State<SidebarWidget> {
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
_selectedId = widget.selectedSpaceUuid;
|
||||
_scrollController = ScrollController();
|
||||
super.initState();
|
||||
_scrollController = ScrollController();
|
||||
_scrollController.addListener(_onScroll);
|
||||
_selectedId = widget.selectedSpaceUuid;
|
||||
}
|
||||
|
||||
void _onScroll() {
|
||||
if (_scrollController.position.pixels >= _scrollController.position.maxScrollExtent - 100) {
|
||||
// Trigger pagination event
|
||||
final bloc = context.read<SpaceTreeBloc>();
|
||||
if (!bloc.state.paginationIsLoading && bloc.state.paginationModel?.hasNext == true) {
|
||||
bloc.add(
|
||||
PaginationEvent(
|
||||
bloc.state.paginationModel!,
|
||||
bloc.state.communityList,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_scrollController.removeListener(_onScroll);
|
||||
_scrollController.dispose();
|
||||
_debounce?.cancel();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
void _onSearchChanged(String query) {
|
||||
if (_debounce?.isActive ?? false) _debounce?.cancel();
|
||||
|
||||
_debounce = Timer(const Duration(milliseconds: 500), () {
|
||||
setState(() {
|
||||
_searchQuery = query;
|
||||
});
|
||||
context.read<SpaceTreeBloc>().add(SearchQueryEvent(query));
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
void didUpdateWidget(covariant SidebarWidget oldWidget) {
|
||||
if (widget.selectedSpaceUuid != oldWidget.selectedSpaceUuid) {
|
||||
@ -59,38 +94,6 @@ class _SidebarWidgetState extends State<SidebarWidget> {
|
||||
super.didUpdateWidget(oldWidget);
|
||||
}
|
||||
|
||||
List<CommunityModel> _filteredCommunities() {
|
||||
if (_searchQuery.isEmpty) {
|
||||
_selectedSpaceUuid = null;
|
||||
return widget.communities;
|
||||
}
|
||||
|
||||
return widget.communities.where((community) {
|
||||
final containsQueryInCommunity =
|
||||
community.name.toLowerCase().contains(_searchQuery.toLowerCase());
|
||||
final containsQueryInSpaces = community.spaces.any((space) =>
|
||||
_containsQuery(space: space, query: _searchQuery.toLowerCase()));
|
||||
|
||||
return containsQueryInCommunity || containsQueryInSpaces;
|
||||
}).toList();
|
||||
}
|
||||
|
||||
bool _containsQuery({
|
||||
required SpaceModel space,
|
||||
required String query,
|
||||
}) {
|
||||
final matchesSpace = space.name.toLowerCase().contains(query);
|
||||
final matchesChildren = space.children.any(
|
||||
(child) => _containsQuery(space: child, query: query),
|
||||
);
|
||||
|
||||
if (matchesSpace || matchesChildren) {
|
||||
_selectedSpaceUuid = space.uuid;
|
||||
}
|
||||
|
||||
return matchesSpace || matchesChildren;
|
||||
}
|
||||
|
||||
bool _isSpaceOrChildSelected(SpaceModel space) {
|
||||
final isSpaceSelected = _selectedSpaceUuid == space.uuid;
|
||||
final anySubSpaceIsSelected = space.children.any(_isSpaceOrChildSelected);
|
||||
@ -101,7 +104,10 @@ class _SidebarWidgetState extends State<SidebarWidget> {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final filteredCommunities = _filteredCommunities();
|
||||
final spaceTreeState = context.watch<SpaceTreeBloc>().state;
|
||||
final filteredCommunities = spaceTreeState.searchQuery.isNotEmpty
|
||||
? spaceTreeState.filteredCommunity
|
||||
: spaceTreeState.communityList;
|
||||
|
||||
return Container(
|
||||
width: _width,
|
||||
@ -112,7 +118,7 @@ class _SidebarWidgetState extends State<SidebarWidget> {
|
||||
children: [
|
||||
SidebarHeader(onAddCommunity: _onAddCommunity),
|
||||
CustomSearchBar(
|
||||
onSearchChanged: (query) => setState(() => _searchQuery = query),
|
||||
onSearchChanged: _onSearchChanged,
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
Expanded(
|
||||
@ -120,14 +126,23 @@ class _SidebarWidgetState extends State<SidebarWidget> {
|
||||
visible: filteredCommunities.isNotEmpty,
|
||||
replacement: const EmptySearchResultWidget(),
|
||||
child: SidebarCommunitiesList(
|
||||
scrollController: _scrollController,
|
||||
onScrollToEnd: () {},
|
||||
communities: filteredCommunities,
|
||||
itemBuilder: (context, index) => _buildCommunityTile(
|
||||
context,
|
||||
filteredCommunities[index],
|
||||
),
|
||||
),
|
||||
scrollController: _scrollController,
|
||||
onScrollToEnd: () {},
|
||||
communities: filteredCommunities,
|
||||
itemBuilder: (context, index) {
|
||||
if (index == filteredCommunities.length) {
|
||||
final spaceTreeState = context.read<SpaceTreeBloc>().state;
|
||||
if (spaceTreeState.paginationIsLoading) {
|
||||
return const Padding(
|
||||
padding: EdgeInsets.all(8.0),
|
||||
child: Center(child: CircularProgressIndicator()),
|
||||
);
|
||||
} else {
|
||||
return const SizedBox.shrink();
|
||||
}
|
||||
}
|
||||
return _buildCommunityTile(context, filteredCommunities[index]);
|
||||
}),
|
||||
),
|
||||
),
|
||||
],
|
||||
@ -205,9 +220,8 @@ class _SidebarWidgetState extends State<SidebarWidget> {
|
||||
);
|
||||
}
|
||||
|
||||
void _onAddCommunity() => _selectedId?.isNotEmpty ?? true
|
||||
? _clearSelection()
|
||||
: _showCreateCommunityDialog();
|
||||
void _onAddCommunity() =>
|
||||
_selectedId?.isNotEmpty ?? true ? _clearSelection() : _showCreateCommunityDialog();
|
||||
|
||||
void _clearSelection() {
|
||||
setState(() => _selectedId = '');
|
||||
|
@ -51,7 +51,7 @@ final class DebouncedBatchControlDevicesService
|
||||
|
||||
DebouncedBatchControlDevicesService({
|
||||
required this.decoratee,
|
||||
this.debounceDuration = const Duration(milliseconds: 1500),
|
||||
this.debounceDuration = const Duration(milliseconds: 800),
|
||||
});
|
||||
|
||||
@override
|
||||
|
@ -40,7 +40,7 @@ final class DebouncedControlDeviceService implements ControlDeviceService {
|
||||
|
||||
DebouncedControlDeviceService({
|
||||
required this.decoratee,
|
||||
this.debounceDuration = const Duration(milliseconds: 1500),
|
||||
this.debounceDuration = const Duration(milliseconds: 800),
|
||||
});
|
||||
|
||||
final _pendingRequests = <(String deviceUuid, Status status)>[];
|
||||
|
@ -281,6 +281,7 @@ class CommunitySpaceManagementApi {
|
||||
return json['success'] ?? false;
|
||||
},
|
||||
);
|
||||
|
||||
return response;
|
||||
} catch (e) {
|
||||
debugPrint('Error updating space: $e');
|
||||
|
Reference in New Issue
Block a user