Compare commits

..

2 Commits

Author SHA1 Message Date
1828ffb87a remove print statment 2025-06-29 09:17:57 +03:00
bd53388438 make one API with new QP to filter on spacesId 2025-06-29 09:17:22 +03:00
39 changed files with 546 additions and 1031 deletions

View File

@ -18,11 +18,7 @@ abstract final class RangeOfAqiChartsHelper {
(ColorsManager.hazardousPurple, 'Hazardous'),
];
static FlTitlesData titlesData(
BuildContext context,
List<RangeOfAqi> data, {
double leftSideInterval = 50,
}) {
static FlTitlesData titlesData(BuildContext context, List<RangeOfAqi> data) {
final titlesData = EnergyManagementChartsHelper.titlesData(context);
return titlesData.copyWith(
bottomTitles: titlesData.bottomTitles.copyWith(
@ -43,11 +39,11 @@ abstract final class RangeOfAqiChartsHelper {
leftTitles: titlesData.leftTitles.copyWith(
sideTitles: titlesData.leftTitles.sideTitles.copyWith(
reservedSize: 70,
interval: leftSideInterval,
interval: 50,
maxIncluded: false,
minIncluded: true,
getTitlesWidget: (value, meta) {
final text = value.toInt().toString();
final text = value >= 300 ? '301+' : value.toInt().toString();
return Padding(
padding: const EdgeInsetsDirectional.only(end: 12),
child: FittedBox(

View File

@ -3,7 +3,6 @@ import 'package:flutter/material.dart';
import 'package:intl/intl.dart';
import 'package:syncrow_web/pages/analytics/models/air_quality_data_model.dart';
import 'package:syncrow_web/pages/analytics/modules/energy_management/helpers/energy_management_charts_helper.dart';
import 'package:syncrow_web/pages/analytics/widgets/charts_x_axis_title.dart';
import 'package:syncrow_web/utils/color_manager.dart';
import 'package:syncrow_web/utils/extension/build_context_x.dart';
@ -150,7 +149,6 @@ class AqiDistributionChart extends StatelessWidget {
);
final bottomTitles = AxisTitles(
axisNameWidget: const ChartsXAxisTitle(),
sideTitles: SideTitles(
showTitles: chartData.isNotEmpty,
getTitlesWidget: (value, _) => FittedBox(

View File

@ -2,18 +2,15 @@ import 'package:fl_chart/fl_chart.dart';
import 'package:flutter/material.dart';
import 'package:syncrow_web/pages/analytics/models/range_of_aqi.dart';
import 'package:syncrow_web/pages/analytics/modules/air_quality/helpers/range_of_aqi_charts_helper.dart';
import 'package:syncrow_web/pages/analytics/modules/air_quality/widgets/aqi_type_dropdown.dart';
import 'package:syncrow_web/pages/analytics/modules/energy_management/helpers/energy_management_charts_helper.dart';
import 'package:syncrow_web/utils/color_manager.dart';
class RangeOfAqiChart extends StatelessWidget {
final List<RangeOfAqi> chartData;
final AqiType selectedAqiType;
const RangeOfAqiChart({
super.key,
required this.chartData,
required this.selectedAqiType,
});
List<(List<double> values, Color color, Color? dotColor)> get _lines {
@ -48,34 +45,15 @@ class RangeOfAqiChart extends StatelessWidget {
];
}
(double maxY, double interval) get _maxYForAqiType {
const aqiMaxValues = <AqiType, (double maxY, double interval)>{
AqiType.aqi: (401, 100),
AqiType.pm25: (351, 50),
AqiType.pm10: (501, 100),
AqiType.hcho: (301, 50),
AqiType.tvoc: (501, 50),
AqiType.co2: (1251, 250),
};
return aqiMaxValues[selectedAqiType]!;
}
@override
Widget build(BuildContext context) {
return LineChart(
LineChartData(
minY: 0,
maxY: _maxYForAqiType.$1,
maxY: 301,
clipData: const FlClipData.vertical(),
gridData: EnergyManagementChartsHelper.gridData(
horizontalInterval: _maxYForAqiType.$2,
),
titlesData: RangeOfAqiChartsHelper.titlesData(
context,
chartData,
leftSideInterval: _maxYForAqiType.$2,
),
gridData: EnergyManagementChartsHelper.gridData(horizontalInterval: 50),
titlesData: RangeOfAqiChartsHelper.titlesData(context, chartData),
borderData: EnergyManagementChartsHelper.borderData(),
lineTouchData: RangeOfAqiChartsHelper.lineTouchData(chartData),
betweenBarsData: [

View File

@ -32,12 +32,7 @@ class RangeOfAqiChartBox extends StatelessWidget {
const SizedBox(height: 10),
const Divider(),
const SizedBox(height: 20),
Expanded(
child: RangeOfAqiChart(
chartData: state.filteredRangeOfAqi,
selectedAqiType: state.selectedAqiType,
),
),
Expanded(child: RangeOfAqiChart(chartData: state.filteredRangeOfAqi)),
],
),
);

View File

@ -1,7 +1,6 @@
import 'package:fl_chart/fl_chart.dart';
import 'package:flutter/material.dart';
import 'package:syncrow_web/pages/analytics/helpers/format_number_to_kwh.dart';
import 'package:syncrow_web/pages/analytics/widgets/charts_x_axis_title.dart';
import 'package:syncrow_web/utils/color_manager.dart';
import 'package:syncrow_web/utils/extension/build_context_x.dart';
@ -16,7 +15,6 @@ abstract final class EnergyManagementChartsHelper {
return FlTitlesData(
show: true,
bottomTitles: AxisTitles(
axisNameWidget: const ChartsXAxisTitle(),
drawBelowEverything: true,
sideTitles: SideTitles(
interval: 1,
@ -64,12 +62,17 @@ abstract final class EnergyManagementChartsHelper {
);
}
static String getToolTipLabel(double value) => value.formatNumberToKwh;
static String getToolTipLabel(num month, double value) {
final monthLabel = month.toString();
final valueLabel = value.formatNumberToKwh;
final labels = [monthLabel, valueLabel];
return labels.where((element) => element.isNotEmpty).join(', ');
}
static List<LineTooltipItem?> getTooltipItems(List<LineBarSpot> touchedSpots) {
return touchedSpots.map((spot) {
return LineTooltipItem(
getToolTipLabel(spot.y),
getToolTipLabel(spot.x, spot.y),
const TextStyle(
color: ColorsManager.textPrimaryColor,
fontWeight: FontWeight.w600,

View File

@ -37,7 +37,7 @@ class EnergyConsumptionPerDeviceChartBox extends StatelessWidget {
fit: BoxFit.scaleDown,
alignment: AlignmentDirectional.centerStart,
child: ChartTitle(
title: Text('Device energy consumed'),
title: Text('Energy Consumption per Device'),
),
),
),

View File

@ -32,7 +32,7 @@ class TotalEnergyConsumptionChartBox extends StatelessWidget {
child: FittedBox(
alignment: AlignmentDirectional.centerStart,
fit: BoxFit.scaleDown,
child: ChartTitle(title: Text('Space energy consumed')),
child: ChartTitle(title: Text('Total Energy Consumption')),
),
),
const Spacer(flex: 4),

View File

@ -39,7 +39,7 @@ class HeatMapTooltip extends StatelessWidget {
),
const Divider(height: 2, thickness: 1),
Text(
'Occupancy detected: $value',
'$value Occupants',
style: context.textTheme.bodySmall?.copyWith(
fontSize: 10,
fontWeight: FontWeight.w500,

View File

@ -2,7 +2,6 @@ import 'package:fl_chart/fl_chart.dart';
import 'package:flutter/material.dart';
import 'package:syncrow_web/pages/analytics/models/occupacy.dart';
import 'package:syncrow_web/pages/analytics/modules/energy_management/helpers/energy_management_charts_helper.dart';
import 'package:syncrow_web/pages/analytics/widgets/charts_x_axis_title.dart';
import 'package:syncrow_web/utils/color_manager.dart';
import 'package:syncrow_web/utils/extension/build_context_x.dart';
@ -89,8 +88,8 @@ class OccupancyChart extends StatelessWidget {
}) {
final data = chartData;
final occupancyValue = double.parse(data[group.x].occupancy);
final percentage = '${occupancyValue.toStringAsFixed(0)}%';
final occupancyValue = double.parse(data[group.x.toInt()].occupancy);
final percentage = '${(occupancyValue).toStringAsFixed(0)}%';
return BarTooltipItem(
percentage,
@ -117,7 +116,7 @@ class OccupancyChart extends StatelessWidget {
alignment: AlignmentDirectional.centerStart,
fit: BoxFit.scaleDown,
child: Text(
'${value.toStringAsFixed(0)}%',
'${(value).toStringAsFixed(0)}%',
style: context.textTheme.bodySmall?.copyWith(
fontSize: 12,
color: ColorsManager.greyColor,
@ -129,7 +128,6 @@ class OccupancyChart extends StatelessWidget {
);
final bottomTitles = AxisTitles(
axisNameWidget: const ChartsXAxisTitle(),
sideTitles: SideTitles(
showTitles: true,
getTitlesWidget: (value, _) => FittedBox(

View File

@ -23,7 +23,7 @@ class OccupancyEndSideBar extends StatelessWidget {
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const AnalyticsSidebarHeader(title: 'Presence Sensor'),
const AnalyticsSidebarHeader(title: 'Presnce Sensor'),
Expanded(
child: SizedBox(
// height: MediaQuery.sizeOf(context).height * 0.2,

View File

@ -1,23 +0,0 @@
import 'package:flutter/material.dart';
import 'package:syncrow_web/utils/color_manager.dart';
import 'package:syncrow_web/utils/extension/build_context_x.dart';
class ChartsXAxisTitle extends StatelessWidget {
const ChartsXAxisTitle({
this.label = 'Day of month',
super.key,
});
final String label;
@override
Widget build(BuildContext context) {
return Text(
label,
style: context.textTheme.bodySmall?.copyWith(
color: ColorsManager.lightGreyColor,
fontSize: 8,
),
);
}
}

View File

@ -45,8 +45,7 @@ class AcBloc extends Bloc<AcsEvent, AcsState> {
) async {
emit(AcsLoadingState());
try {
final status =
await DevicesManagementApi().getDeviceStatus(event.deviceId);
final status = await DevicesManagementApi().getDeviceStatus(event.deviceId);
deviceStatus = AcStatusModel.fromJson(event.deviceId, status.status);
if (deviceStatus.countdown1 != 0) {
final totalMinutes = deviceStatus.countdown1 * 6;
@ -69,13 +68,12 @@ class AcBloc extends Bloc<AcsEvent, AcsState> {
}
}
StreamSubscription<DatabaseEvent>? _deviceStatusSubscription;
void _listenToChanges(String deviceId) {
void _listenToChanges(deviceId) {
try {
final ref = FirebaseDatabase.instance.ref('device-status/$deviceId');
_deviceStatusSubscription =
ref.onValue.listen((DatabaseEvent event) async {
final stream = ref.onValue;
stream.listen((DatabaseEvent event) async {
if (event.snapshot.value == null) return;
Map<dynamic, dynamic> usersMap =
@ -84,14 +82,10 @@ class AcBloc extends Bloc<AcsEvent, AcsState> {
List<Status> statusList = [];
usersMap['status'].forEach((element) {
statusList
.add(Status(code: element['code'], value: element['value']));
statusList.add(Status(code: element['code'], value: element['value']));
});
deviceStatus =
AcStatusModel.fromJson(usersMap['productUuid'], statusList);
print('Device status updated: ${deviceStatus.acSwitch}');
deviceStatus = AcStatusModel.fromJson(usersMap['productUuid'], statusList);
if (!isClosed) {
add(AcStatusUpdated(deviceStatus));
}
@ -112,14 +106,15 @@ class AcBloc extends Bloc<AcsEvent, AcsState> {
Emitter<AcsState> emit,
) async {
emit(AcsLoadingState());
_updateDeviceFunctionFromCode(event.code, event.value);
emit(ACStatusLoaded(status: deviceStatus));
try {
final success = await controlDeviceService.controlDevice(
deviceUuid: event.deviceId,
status: Status(code: event.code, value: event.value),
);
_updateDeviceFunctionFromCode(event.code, event.value);
emit(ACStatusLoaded(status: deviceStatus));
if (!success) {
emit(const AcsFailedState(error: 'Failed to control device'));
}
@ -134,10 +129,8 @@ class AcBloc extends Bloc<AcsEvent, AcsState> {
) async {
emit(AcsLoadingState());
try {
final status =
await DevicesManagementApi().getBatchStatus(event.devicesIds);
deviceStatus =
AcStatusModel.fromJson(event.devicesIds.first, status.status);
final status = await DevicesManagementApi().getBatchStatus(event.devicesIds);
deviceStatus = AcStatusModel.fromJson(event.devicesIds.first, status.status);
emit(ACStatusLoaded(status: deviceStatus));
} catch (e) {
emit(AcsFailedState(error: e.toString()));
@ -300,17 +293,13 @@ class AcBloc extends Bloc<AcsEvent, AcsState> {
totalSeconds--;
scheduledHours = totalSeconds ~/ 3600;
scheduledMinutes = (totalSeconds % 3600) ~/ 60;
if (!isClosed) {
add(UpdateTimerEvent());
}
add(UpdateTimerEvent());
} else {
_countdownTimer?.cancel();
timerActive = false;
scheduledHours = 0;
scheduledMinutes = 0;
if (!isClosed) {
add(TimerCompletedEvent());
}
add(TimerCompletedEvent());
}
});
}
@ -337,9 +326,7 @@ class AcBloc extends Bloc<AcsEvent, AcsState> {
_startCountdownTimer(
emit,
);
if (!isClosed) {
add(UpdateTimerEvent());
}
add(UpdateTimerEvent());
}
}
@ -383,8 +370,6 @@ class AcBloc extends Bloc<AcsEvent, AcsState> {
@override
Future<void> close() {
add(OnClose());
_countdownTimer?.cancel();
_deviceStatusSubscription?.cancel();
return super.close();
}
}

View File

@ -41,18 +41,14 @@ class DeviceManagementBloc
_devices.clear();
var spaceBloc = event.context.read<SpaceTreeBloc>();
final projectUuid = await ProjectManager.getProjectUUID() ?? '';
if (spaceBloc.state.selectedCommunities.isEmpty) {
devices =
await DevicesManagementApi().fetchDevices('', '', projectUuid);
devices = await DevicesManagementApi().fetchDevices(projectUuid);
} else {
for (var community in spaceBloc.state.selectedCommunities) {
List<String> spacesList =
spaceBloc.state.selectedCommunityAndSpaces[community] ?? [];
for (var space in spacesList) {
devices.addAll(await DevicesManagementApi()
.fetchDevices(community, space, projectUuid));
}
devices.addAll(await DevicesManagementApi()
.fetchDevices(projectUuid, spacesId: spacesList));
}
}
@ -270,6 +266,7 @@ class DeviceManagementBloc
return 'All';
}
}
void _onSearchDevices(
SearchDevices event, Emitter<DeviceManagementState> emit) {
if ((event.community == null || event.community!.isEmpty) &&

View File

@ -12,8 +12,7 @@ import 'package:syncrow_web/utils/constants/assets.dart';
import 'package:syncrow_web/utils/helpers/responsice_layout_helper/responsive_layout_helper.dart';
//Smart Power Clamp
class SmartPowerDeviceControl extends StatelessWidget
with HelperResponsiveLayout {
class SmartPowerDeviceControl extends StatelessWidget with HelperResponsiveLayout {
final String deviceId;
const SmartPowerDeviceControl({super.key, required this.deviceId});
@ -146,16 +145,13 @@ class SmartPowerDeviceControl extends StatelessWidget
children: [
IconButton(
icon: const Icon(Icons.arrow_left),
onPressed: blocProvider.currentPage <= 0
? null
: () {
blocProvider
.add(SmartPowerArrowPressedEvent(-1));
pageController.previousPage(
duration: const Duration(milliseconds: 300),
curve: Curves.easeInOut,
);
},
onPressed: () {
blocProvider.add(SmartPowerArrowPressedEvent(-1));
pageController.previousPage(
duration: const Duration(milliseconds: 300),
curve: Curves.easeInOut,
);
},
),
Text(
currentPage == 0
@ -169,16 +165,13 @@ class SmartPowerDeviceControl extends StatelessWidget
),
IconButton(
icon: const Icon(Icons.arrow_right),
onPressed: blocProvider.currentPage >= 3
? null
: () {
blocProvider
.add(SmartPowerArrowPressedEvent(1));
pageController.nextPage(
duration: const Duration(milliseconds: 300),
curve: Curves.easeInOut,
);
},
onPressed: () {
blocProvider.add(SmartPowerArrowPressedEvent(1));
pageController.nextPage(
duration: const Duration(milliseconds: 300),
curve: Curves.easeInOut,
);
},
),
],
),
@ -202,8 +195,8 @@ class SmartPowerDeviceControl extends StatelessWidget
blocProvider.add(SelectDateEvent(context: context));
blocProvider.add(FilterRecordsByDateEvent(
selectedDate: blocProvider.dateTime!,
viewType: blocProvider
.views[blocProvider.currentIndex]));
viewType:
blocProvider.views[blocProvider.currentIndex]));
},
widget: blocProvider.dateSwitcher(),
chartData: blocProvider.energyDataList.isNotEmpty

View File

@ -83,12 +83,6 @@ class ScheduleBloc extends Bloc<ScheduleEvent, ScheduleState> {
emit(currentState.copyWith(
scheduleMode: event.scheduleMode,
countdownRemaining: Duration.zero,
countdownHours: 0,
countdownMinutes: 0,
inchingHours: 0,
inchingMinutes: 0,
isCountdownActive: false,
isInchingActive: false,
));
}
}
@ -100,7 +94,6 @@ class ScheduleBloc extends Bloc<ScheduleEvent, ScheduleState> {
if (state is ScheduleLoaded) {
final currentState = state as ScheduleLoaded;
emit(currentState.copyWith(
countdownSeconds: event.seconds,
countdownHours: event.hours,
countdownMinutes: event.minutes,
inchingHours: 0,
@ -120,7 +113,6 @@ class ScheduleBloc extends Bloc<ScheduleEvent, ScheduleState> {
inchingHours: event.hours,
inchingMinutes: event.minutes,
countdownRemaining: Duration.zero,
inchingSeconds: 0, // Add this
));
}
}
@ -432,7 +424,6 @@ class ScheduleBloc extends Bloc<ScheduleEvent, ScheduleState> {
countdownMinutes: countdownDuration.inMinutes % 60,
countdownRemaining: countdownDuration,
isCountdownActive: true,
countdownSeconds: countdownDuration.inSeconds,
),
);
@ -446,7 +437,6 @@ class ScheduleBloc extends Bloc<ScheduleEvent, ScheduleState> {
countdownMinutes: 0,
countdownRemaining: Duration.zero,
isCountdownActive: false,
countdownSeconds: 0,
),
);
}
@ -458,7 +448,6 @@ class ScheduleBloc extends Bloc<ScheduleEvent, ScheduleState> {
inchingMinutes: inchingDuration.inMinutes % 60,
isInchingActive: true,
countdownRemaining: inchingDuration,
countdownSeconds: inchingDuration.inSeconds,
),
);
}
@ -585,7 +574,8 @@ class ScheduleBloc extends Bloc<ScheduleEvent, ScheduleState> {
}
String extractTime(String isoDateTime) {
return isoDateTime.split('T')[1].split('.')[0];
// Example input: "2025-06-19T15:45:00.000"
return isoDateTime.split('T')[1].split('.')[0]; // gives "15:45:00"
}
int? getTimeStampWithoutSeconds(DateTime? dateTime) {

View File

@ -146,16 +146,14 @@ class UpdateScheduleModeEvent extends ScheduleEvent {
class UpdateCountdownTimeEvent extends ScheduleEvent {
final int hours;
final int minutes;
final int seconds;
const UpdateCountdownTimeEvent({
required this.hours,
required this.minutes,
required this.seconds,
});
@override
List<Object> get props => [hours, minutes, seconds];
List<Object> get props => [hours, minutes];
}
class UpdateInchingTimeEvent extends ScheduleEvent {

View File

@ -26,15 +26,11 @@ class ScheduleLoaded extends ScheduleState {
final bool isCountdownActive;
final int inchingHours;
final int inchingMinutes;
final int inchingSeconds;
final bool isInchingActive;
final ScheduleModes scheduleMode;
final Duration? countdownRemaining;
final int? countdownSeconds;
const ScheduleLoaded({
this.countdownSeconds = 0,
this.inchingSeconds = 0,
required this.schedules,
this.selectedTime,
required this.selectedDays,
@ -65,9 +61,6 @@ class ScheduleLoaded extends ScheduleState {
bool? isInchingActive,
ScheduleModes? scheduleMode,
Duration? countdownRemaining,
String? deviceId,
int? countdownSeconds,
int? inchingSeconds,
}) {
return ScheduleLoaded(
schedules: schedules ?? this.schedules,
@ -75,7 +68,7 @@ class ScheduleLoaded extends ScheduleState {
selectedDays: selectedDays ?? this.selectedDays,
functionOn: functionOn ?? this.functionOn,
isEditing: isEditing ?? this.isEditing,
deviceId: deviceId ?? this.deviceId,
deviceId: deviceId,
countdownHours: countdownHours ?? this.countdownHours,
countdownMinutes: countdownMinutes ?? this.countdownMinutes,
isCountdownActive: isCountdownActive ?? this.isCountdownActive,
@ -84,8 +77,6 @@ class ScheduleLoaded extends ScheduleState {
isInchingActive: isInchingActive ?? this.isInchingActive,
scheduleMode: scheduleMode ?? this.scheduleMode,
countdownRemaining: countdownRemaining ?? this.countdownRemaining,
countdownSeconds: countdownSeconds ?? this.countdownSeconds,
inchingSeconds: inchingSeconds ?? this.inchingSeconds,
);
}
@ -105,8 +96,6 @@ class ScheduleLoaded extends ScheduleState {
isInchingActive,
scheduleMode,
countdownRemaining,
countdownSeconds,
inchingSeconds,
];
}

View File

@ -6,8 +6,7 @@ import 'package:syncrow_web/utils/color_manager.dart';
import 'package:syncrow_web/utils/extension/build_context_x.dart';
class CountdownInchingView extends StatefulWidget {
final String deviceId;
const CountdownInchingView({super.key, required this.deviceId});
const CountdownInchingView({super.key});
@override
State<CountdownInchingView> createState() => _CountdownInchingViewState();
@ -16,30 +15,25 @@ class CountdownInchingView extends StatefulWidget {
class _CountdownInchingViewState extends State<CountdownInchingView> {
late FixedExtentScrollController _hoursController;
late FixedExtentScrollController _minutesController;
late FixedExtentScrollController _secondsController;
int _lastHours = -1;
int _lastMinutes = -1;
int _lastSeconds = -1;
@override
void initState() {
super.initState();
_hoursController = FixedExtentScrollController();
_minutesController = FixedExtentScrollController();
_secondsController = FixedExtentScrollController();
}
@override
void dispose() {
_hoursController.dispose();
_minutesController.dispose();
_secondsController.dispose();
super.dispose();
}
void _updateControllers(
int displayHours, int displayMinutes, int displaySeconds) {
void _updateControllers(int displayHours, int displayMinutes) {
if (_lastHours != displayHours) {
WidgetsBinding.instance.addPostFrameCallback((_) {
if (_hoursController.hasClients) {
@ -56,15 +50,6 @@ class _CountdownInchingViewState extends State<CountdownInchingView> {
});
_lastMinutes = displayMinutes;
}
// Update seconds controller
if (_lastSeconds != displaySeconds) {
WidgetsBinding.instance.addPostFrameCallback((_) {
if (_secondsController.hasClients) {
_secondsController.jumpToItem(displaySeconds);
}
});
_lastSeconds = displaySeconds;
}
}
@override
@ -72,6 +57,7 @@ class _CountdownInchingViewState extends State<CountdownInchingView> {
return BlocBuilder<ScheduleBloc, ScheduleState>(
builder: (context, state) {
if (state is! ScheduleLoaded) return const SizedBox.shrink();
final isCountDown = state.scheduleMode == ScheduleModes.countdown;
final isActive =
isCountDown ? state.isCountdownActive : state.isInchingActive;
@ -81,21 +67,8 @@ class _CountdownInchingViewState extends State<CountdownInchingView> {
final displayMinutes = isActive && state.countdownRemaining != null
? state.countdownRemaining!.inMinutes.remainder(60)
: (isCountDown ? state.countdownMinutes : state.inchingMinutes);
final displaySeconds = isActive && state.countdownRemaining != null
? state.countdownRemaining!.inSeconds.remainder(60)
: (isCountDown ? state.countdownSeconds : state.inchingSeconds);
_updateControllers(displayHours, displayMinutes, displaySeconds!);
if (displayHours == 0 && displayMinutes == 0 && displaySeconds == 0) {
context.read<ScheduleBloc>().add(
StopScheduleEvent(
mode: ScheduleModes.countdown,
deviceId: widget.deviceId,
),
);
}
_updateControllers(displayHours, displayMinutes);
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
@ -127,10 +100,7 @@ class _CountdownInchingViewState extends State<CountdownInchingView> {
(value) {
if (!isActive) {
context.read<ScheduleBloc>().add(UpdateCountdownTimeEvent(
hours: value,
minutes: displayMinutes,
seconds: displaySeconds,
));
hours: value, minutes: displayMinutes));
}
},
isActive: isActive,
@ -145,35 +115,11 @@ class _CountdownInchingViewState extends State<CountdownInchingView> {
(value) {
if (!isActive) {
context.read<ScheduleBloc>().add(UpdateCountdownTimeEvent(
hours: displayHours,
minutes: value,
seconds: displaySeconds,
));
hours: displayHours, minutes: value));
}
},
isActive: isActive,
),
const SizedBox(width: 10),
if (isActive)
_buildPickerColumn(
context,
's',
displaySeconds,
60,
_secondsController,
(value) {
if (!isActive) {
context
.read<ScheduleBloc>()
.add(UpdateCountdownTimeEvent(
hours: displayHours,
minutes: displayMinutes,
seconds: value,
));
}
},
isActive: isActive,
),
],
),
],

View File

@ -74,9 +74,7 @@ class BuildScheduleView extends StatelessWidget {
),
if (state.scheduleMode == ScheduleModes.countdown ||
state.scheduleMode == ScheduleModes.inching)
CountdownInchingView(
deviceId: deviceUuid,
),
const CountdownInchingView(),
const SizedBox(height: 20),
if (state.scheduleMode == ScheduleModes.countdown)
CountdownModeButtons(

View File

@ -79,7 +79,6 @@ class DeviceControlDialog extends StatelessWidget with RouteControlsBasedCode {
}
Widget _buildDeviceInfoSection() {
final isOnlineDevice = device.online != null && device.online!;
return Padding(
padding: const EdgeInsets.symmetric(vertical: 25, horizontal: 50),
child: Table(
@ -108,7 +107,7 @@ class DeviceControlDialog extends StatelessWidget with RouteControlsBasedCode {
'Installation Date and Time:',
formatDateTime(
DateTime.fromMillisecondsSinceEpoch(
(device.createTime ?? 0) * 1000,
((device.createTime ?? 0) * 1000),
),
),
),
@ -127,16 +126,12 @@ class DeviceControlDialog extends StatelessWidget with RouteControlsBasedCode {
),
TableRow(
children: [
_buildInfoRow(
'Status:',
isOnlineDevice ? 'Online' : 'offline',
statusColor: isOnlineDevice ? Colors.green : Colors.red,
),
_buildInfoRow('Status:', 'Online', statusColor: Colors.green),
_buildInfoRow(
'Last Offline Date and Time:',
formatDateTime(
DateTime.fromMillisecondsSinceEpoch(
(device.updateTime ?? 0) * 1000,
((device.updateTime ?? 0) * 1000),
),
),
),

View File

@ -170,45 +170,45 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
}
}
Future<void> _onLoadScenes(
LoadScenes event, Emitter<RoutineState> emit) async {
emit(state.copyWith(isLoading: true, errorMessage: null));
List<ScenesModel> scenes = [];
try {
BuildContext context = NavigationService.navigatorKey.currentContext!;
var createRoutineBloc = context.read<CreateRoutineBloc>();
final projectUuid = await ProjectManager.getProjectUUID() ?? '';
if (createRoutineBloc.selectedSpaceId == '' &&
createRoutineBloc.selectedCommunityId == '') {
var spaceBloc = context.read<SpaceTreeBloc>();
for (var communityId in spaceBloc.state.selectedCommunities) {
List<String> spacesList =
spaceBloc.state.selectedCommunityAndSpaces[communityId] ?? [];
for (var spaceId in spacesList) {
scenes.addAll(
await SceneApi.getScenes(spaceId, communityId, projectUuid));
Future<void> _onLoadScenes(
LoadScenes event, Emitter<RoutineState> emit) async {
emit(state.copyWith(isLoading: true, errorMessage: null));
List<ScenesModel> scenes = [];
try {
BuildContext context = NavigationService.navigatorKey.currentContext!;
var createRoutineBloc = context.read<CreateRoutineBloc>();
final projectUuid = await ProjectManager.getProjectUUID() ?? '';
if (createRoutineBloc.selectedSpaceId == '' &&
createRoutineBloc.selectedCommunityId == '') {
var spaceBloc = context.read<SpaceTreeBloc>();
for (var communityId in spaceBloc.state.selectedCommunities) {
List<String> spacesList =
spaceBloc.state.selectedCommunityAndSpaces[communityId] ?? [];
for (var spaceId in spacesList) {
scenes.addAll(
await SceneApi.getScenes(spaceId, communityId, projectUuid));
}
}
} else {
scenes.addAll(await SceneApi.getScenes(
createRoutineBloc.selectedSpaceId,
createRoutineBloc.selectedCommunityId,
projectUuid));
}
} else {
scenes.addAll(await SceneApi.getScenes(
createRoutineBloc.selectedSpaceId,
createRoutineBloc.selectedCommunityId,
projectUuid));
}
emit(state.copyWith(
scenes: scenes,
isLoading: false,
));
} catch (e) {
emit(state.copyWith(
emit(state.copyWith(
scenes: scenes,
isLoading: false,
loadScenesErrorMessage: 'Failed to load scenes',
errorMessage: '',
loadAutomationErrorMessage: '',
scenes: scenes));
));
} catch (e) {
emit(state.copyWith(
isLoading: false,
loadScenesErrorMessage: 'Failed to load scenes',
errorMessage: '',
loadAutomationErrorMessage: '',
scenes: scenes));
}
}
}
Future<void> _onLoadAutomation(
LoadAutomation event, Emitter<RoutineState> emit) async {
@ -936,16 +936,12 @@ Future<void> _onLoadScenes(
for (var communityId in spaceBloc.state.selectedCommunities) {
List<String> spacesList =
spaceBloc.state.selectedCommunityAndSpaces[communityId] ?? [];
for (var spaceId in spacesList) {
devices.addAll(await DevicesManagementApi()
.fetchDevices(communityId, spaceId, projectUuid));
}
devices.addAll(await DevicesManagementApi()
.fetchDevices(projectUuid, spacesId: spacesList));
}
} else {
devices.addAll(await DevicesManagementApi().fetchDevices(
createRoutineBloc.selectedCommunityId,
createRoutineBloc.selectedSpaceId,
projectUuid));
devices.addAll(await DevicesManagementApi().fetchDevices(projectUuid,
spacesId: [createRoutineBloc.selectedSpaceId]));
}
emit(state.copyWith(isLoading: false, devices: devices));

View File

@ -7,22 +7,21 @@ class SpacesConnectionsArrowPainter extends CustomPainter {
final Map<String, Offset> positions;
final double cardWidth = 150.0;
final double cardHeight = 90.0;
final Set<String> highlightedUuids;
final String? selectedSpaceUuid;
SpacesConnectionsArrowPainter({
required this.connections,
required this.positions,
required this.highlightedUuids,
this.selectedSpaceUuid,
});
@override
void paint(Canvas canvas, Size size) {
for (final connection in connections) {
final isSelected = highlightedUuids.contains(connection.from) ||
highlightedUuids.contains(connection.to);
final isSelected = connection.to == selectedSpaceUuid;
final paint = Paint()
..color = isSelected
? ColorsManager.blackColor
? ColorsManager.primaryColor
: ColorsManager.blackColor.withValues(alpha: 0.5)
..strokeWidth = 2.0
..style = PaintingStyle.stroke;
@ -37,7 +36,7 @@ class SpacesConnectionsArrowPainter extends CustomPainter {
final path = Path()..moveTo(startPoint.dx, startPoint.dy);
final controlPoint1 = Offset(startPoint.dx, startPoint.dy + 20);
final controlPoint1 = Offset(startPoint.dx, startPoint.dy + 60);
final controlPoint2 = Offset(endPoint.dx, endPoint.dy - 60);
path.cubicTo(controlPoint1.dx, controlPoint1.dy, controlPoint2.dx,
@ -47,7 +46,7 @@ class SpacesConnectionsArrowPainter extends CustomPainter {
final circlePaint = Paint()
..color = isSelected
? ColorsManager.blackColor
? ColorsManager.primaryColor
: ColorsManager.blackColor.withValues(alpha: 0.5)
..style = PaintingStyle.fill
..blendMode = BlendMode.srcIn;

View File

@ -1,26 +1,21 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:syncrow_web/pages/space_management_v2/main_module/models/space_connection_model.dart';
import 'package:syncrow_web/pages/space_management_v2/main_module/painters/spaces_connections_arrow_painter.dart';
import 'package:syncrow_web/pages/space_management_v2/main_module/widgets/space_card_widget.dart';
import 'package:syncrow_web/pages/space_management_v2/main_module/widgets/space_cell.dart';
import 'package:syncrow_web/pages/space_management_v2/modules/communities/domain/models/community_model.dart';
import 'package:syncrow_web/pages/space_management_v2/modules/communities/domain/models/space_model.dart';
import 'package:syncrow_web/pages/space_management_v2/modules/communities/presentation/communities_tree_selection_bloc/communities_tree_selection_bloc.dart';
import 'package:syncrow_web/pages/space_management_v2/modules/space_details/presentation/helpers/space_details_dialog_helper.dart';
class CommunityStructureCanvas extends StatefulWidget {
const CommunityStructureCanvas({
required this.community,
required this.selectedSpace,
super.key,
});
final CommunityModel community;
final SpaceModel? selectedSpace;
@override
State<CommunityStructureCanvas> createState() => _CommunityStructureCanvasState();
State<CommunityStructureCanvas> createState() =>_CommunityStructureCanvasState();
}
class _CommunityStructureCanvasState extends State<CommunityStructureCanvas>
@ -30,30 +25,19 @@ class _CommunityStructureCanvasState extends State<CommunityStructureCanvas>
final double _cardHeight = 90.0;
final double _horizontalSpacing = 150.0;
final double _verticalSpacing = 120.0;
String? _selectedSpaceUuid;
late TransformationController _transformationController;
late AnimationController _animationController;
@override
void initState() {
super.initState();
_transformationController = TransformationController();
_animationController = AnimationController(
vsync: this,
duration: const Duration(milliseconds: 150),
duration: const Duration(milliseconds: 100),
);
super.initState();
}
@override
void didUpdateWidget(covariant CommunityStructureCanvas oldWidget) {
super.didUpdateWidget(oldWidget);
if (widget.selectedSpace?.uuid != oldWidget.selectedSpace?.uuid) {
WidgetsBinding.instance.addPostFrameCallback((_) {
if (mounted) {
_animateToSpace(widget.selectedSpace);
}
});
}
}
@override
@ -63,15 +47,6 @@ class _CommunityStructureCanvasState extends State<CommunityStructureCanvas>
super.dispose();
}
Set<String> _getAllDescendantUuids(SpaceModel space) {
final uuids = <String>{};
for (final child in space.children) {
uuids.add(child.uuid);
uuids.addAll(_getAllDescendantUuids(child));
}
return uuids;
}
void _runAnimation(Matrix4 target) {
final animation = Matrix4Tween(
begin: _transformationController.value,
@ -88,16 +63,15 @@ class _CommunityStructureCanvasState extends State<CommunityStructureCanvas>
});
}
void _animateToSpace(SpaceModel? space) {
if (space == null) {
_runAnimation(Matrix4.identity());
return;
}
void _onSpaceTapped(String spaceUuid) {
setState(() {
_selectedSpaceUuid = spaceUuid;
});
final position = _positions[space.uuid];
final position = _positions[spaceUuid];
if (position == null) return;
const scale = 1.5;
const scale = 2.0;
final viewSize = context.size;
if (viewSize == null) return;
@ -112,19 +86,11 @@ class _CommunityStructureCanvasState extends State<CommunityStructureCanvas>
_runAnimation(matrix);
}
void _onSpaceTapped(SpaceModel? space) {
context.read<CommunitiesTreeSelectionBloc>().add(
SelectSpaceEvent(community: widget.community, space: space),
);
}
void _resetSelectionAndZoom() {
context.read<CommunitiesTreeSelectionBloc>().add(
SelectSpaceEvent(
community: widget.community,
space: null,
),
);
setState(() {
_selectedSpaceUuid = null;
});
_runAnimation(Matrix4.identity());
}
void _calculateLayout(
@ -184,23 +150,16 @@ class _CommunityStructureCanvasState extends State<CommunityStructureCanvas>
_calculateLayout(community.spaces, 0, {});
final selectedSpace = widget.selectedSpace;
final highlightedUuids = <String>{};
if (selectedSpace != null) {
highlightedUuids.add(selectedSpace.uuid);
highlightedUuids.addAll(_getAllDescendantUuids(selectedSpace));
}
final widgets = <Widget>[];
final connections = <SpaceConnectionModel>[];
_generateWidgets(community.spaces, widgets, connections, highlightedUuids);
_generateWidgets(community.spaces, widgets, connections);
return [
CustomPaint(
painter: SpacesConnectionsArrowPainter(
connections: connections,
positions: _positions,
highlightedUuids: highlightedUuids,
selectedSpaceUuid: _selectedSpaceUuid,
),
child: Stack(alignment: AlignmentDirectional.center, children: widgets),
),
@ -211,15 +170,11 @@ class _CommunityStructureCanvasState extends State<CommunityStructureCanvas>
List<SpaceModel> spaces,
List<Widget> widgets,
List<SpaceConnectionModel> connections,
Set<String> highlightedUuids,
) {
for (final space in spaces) {
final position = _positions[space.uuid];
if (position == null) continue;
final isHighlighted = highlightedUuids.contains(space.uuid);
final hasNoSelectedSpace = widget.selectedSpace == null;
widgets.add(
Positioned(
left: position.dx,
@ -227,31 +182,32 @@ class _CommunityStructureCanvasState extends State<CommunityStructureCanvas>
width: _cardWidth,
height: _cardHeight,
child: SpaceCardWidget(
buildSpaceContainer: () {
index: spaces.indexOf(space),
onPositionChanged: (newPosition) {},
buildSpaceContainer: (index) {
return Opacity(
opacity: hasNoSelectedSpace || isHighlighted ? 1.0 : 0.5,
child: Tooltip(
message: space.spaceName,
preferBelow: false,
child: SpaceCell(
onTap: () => _onSpaceTapped(space),
icon: space.icon,
name: space.spaceName,
),
opacity: 1.0,
child: SpaceCell(
index: index,
onTap: () => _onSpaceTapped(space.uuid),
icon: space.icon,
name: space.spaceName,
),
);
},
onTap: () => SpaceDetailsDialogHelper.showCreate(context),
screenSize: MediaQuery.sizeOf(context),
position: position,
isHovered: false,
onHoverChanged: (int index, bool isHovered) {},
onButtonTap: (int index, Offset newPosition) {},
),
),
);
for (final child in space.children) {
connections.add(
SpaceConnectionModel(from: space.uuid, to: child.uuid),
);
connections.add(SpaceConnectionModel(from: space.uuid, to: child.uuid));
}
_generateWidgets(space.children, widgets, connections, highlightedUuids);
_generateWidgets(space.children, widgets, connections);
}
}
@ -262,7 +218,7 @@ class _CommunityStructureCanvasState extends State<CommunityStructureCanvas>
transformationController: _transformationController,
boundaryMargin: EdgeInsets.symmetric(
horizontal: MediaQuery.sizeOf(context).width * 0.3,
vertical: MediaQuery.sizeOf(context).height * 0.3,
vertical: MediaQuery.sizeOf(context).height * 0.2,
),
minScale: 0.5,
maxScale: 3.0,
@ -270,8 +226,8 @@ class _CommunityStructureCanvasState extends State<CommunityStructureCanvas>
child: GestureDetector(
onTap: _resetSelectionAndZoom,
child: SizedBox(
width: MediaQuery.sizeOf(context).width * 5,
height: MediaQuery.sizeOf(context).height * 5,
width: MediaQuery.sizeOf(context).width * 2,
height: MediaQuery.sizeOf(context).height * 2,
child: Stack(children: treeWidgets),
),
),

View File

@ -1,5 +1,4 @@
import 'package:flutter/material.dart';
import 'package:syncrow_web/pages/space_management_v2/modules/space_details/presentation/helpers/space_details_dialog_helper.dart';
import 'package:syncrow_web/utils/color_manager.dart';
class CreateSpaceButton extends StatelessWidget {
@ -8,7 +7,7 @@ class CreateSpaceButton extends StatelessWidget {
@override
Widget build(BuildContext context) {
return GestureDetector(
onTap: () => SpaceDetailsDialogHelper.showCreate(context),
onTap: () {},
child: Container(
height: 60,
decoration: BoxDecoration(

View File

@ -2,11 +2,15 @@ import 'package:flutter/material.dart';
import 'package:syncrow_web/utils/color_manager.dart';
class PlusButtonWidget extends StatelessWidget {
final int index;
final String direction;
final Offset offset;
final void Function() onButtonTap;
final void Function(int index, Offset newPosition) onButtonTap;
const PlusButtonWidget({
super.key,
required this.index,
required this.direction,
required this.offset,
required this.onButtonTap,
});
@ -14,7 +18,13 @@ class PlusButtonWidget extends StatelessWidget {
@override
Widget build(BuildContext context) {
return GestureDetector(
onTap: onButtonTap,
onTap: () {
if (direction == 'down') {
onButtonTap(index, const Offset(0, 150));
} else {
onButtonTap(index, const Offset(150, 0));
}
},
child: Container(
width: 30,
height: 30,

View File

@ -1,39 +1,60 @@
import 'package:flutter/material.dart';
import 'package:syncrow_web/pages/space_management_v2/main_module/widgets/plus_button_widget.dart';
class SpaceCardWidget extends StatefulWidget {
final void Function() onTap;
final Widget Function() buildSpaceContainer;
class SpaceCardWidget extends StatelessWidget {
final int index;
final Size screenSize;
final Offset position;
final bool isHovered;
final void Function(int index, bool isHovered) onHoverChanged;
final void Function(int index, Offset newPosition) onButtonTap;
final Widget Function(int index) buildSpaceContainer;
final ValueChanged<Offset> onPositionChanged;
const SpaceCardWidget({
required this.onTap,
required this.buildSpaceContainer,
super.key,
required this.index,
required this.onPositionChanged,
required this.screenSize,
required this.position,
required this.isHovered,
required this.onHoverChanged,
required this.onButtonTap,
required this.buildSpaceContainer,
});
@override
State<SpaceCardWidget> createState() => _SpaceCardWidgetState();
}
class _SpaceCardWidgetState extends State<SpaceCardWidget> {
bool isHovered = false;
@override
Widget build(BuildContext context) {
return MouseRegion(
onEnter: (_) => setState(() => isHovered = true),
onExit: (_) => setState(() => isHovered = false),
onEnter: (_) => onHoverChanged(index, true),
onExit: (_) => onHoverChanged(index, false),
child: SizedBox(
width: 150,
height: 90,
child: Stack(
clipBehavior: Clip.none,
alignment: Alignment.center,
children: [
widget.buildSpaceContainer(),
buildSpaceContainer(index),
if (isHovered)
Positioned(
bottom: 0,
child: PlusButtonWidget(
index: index,
direction: 'down',
offset: Offset.zero,
onButtonTap: widget.onTap,
onButtonTap: onButtonTap,
),
),
if (isHovered)
Positioned(
right: -15,
child: PlusButtonWidget(
index: index,
direction: 'right',
offset: Offset.zero,
onButtonTap: onButtonTap,
),
),
],

View File

@ -1,23 +1,29 @@
import 'package:flutter/material.dart';
import 'package:flutter_svg/svg.dart';
import 'package:syncrow_web/utils/color_manager.dart';
import 'package:syncrow_web/utils/extension/build_context_x.dart';
class SpaceCell extends StatelessWidget {
final int index;
final String icon;
final String name;
final VoidCallback? onDoubleTap;
final VoidCallback? onTap;
const SpaceCell({
super.key,
required this.index,
required this.icon,
required this.name,
required this.onTap,
this.onTap,
this.onDoubleTap,
});
@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
return GestureDetector(
onDoubleTap: onDoubleTap,
onTap: onTap,
child: Container(
width: 150,
@ -30,7 +36,7 @@ class SpaceCell extends StatelessWidget {
Expanded(
child: Text(
name,
style: context.textTheme.bodyLarge?.copyWith(
style: theme.textTheme.bodyLarge?.copyWith(
fontWeight: FontWeight.bold,
color: ColorsManager.blackColor,
),
@ -57,10 +63,7 @@ class SpaceCell extends StatelessWidget {
child: Center(
child: SvgPicture.asset(
icon,
colorFilter: const ColorFilter.mode(
ColorsManager.whiteColors,
BlendMode.srcIn,
),
color: ColorsManager.whiteColors,
width: 24,
height: 24,
),

View File

@ -9,19 +9,14 @@ class SpaceManagementCommunityStructure extends StatelessWidget {
@override
Widget build(BuildContext context) {
final selectionBloc = context.watch<CommunitiesTreeSelectionBloc>().state;
final selectedCommunity = selectionBloc.selectedCommunity;
final selectedSpace = selectionBloc.selectedSpace;
final selectedCommunity =
context.watch<CommunitiesTreeSelectionBloc>().state.selectedCommunity!;
const spacer = Spacer(flex: 10);
return Visibility(
visible: selectedCommunity!.spaces.isNotEmpty,
visible: selectedCommunity.spaces.isNotEmpty,
replacement: const Row(
children: [spacer, Expanded(child: CreateSpaceButton()), spacer],
),
child: CommunityStructureCanvas(
community: selectedCommunity,
selectedSpace: selectedSpace,
),
children: [spacer, Expanded(child: CreateSpaceButton()), spacer]),
child: CommunityStructureCanvas(community: selectedCommunity),
);
}
}

View File

@ -7,24 +7,26 @@ class SpaceManagementTemplatesView extends StatelessWidget {
const SpaceManagementTemplatesView({super.key});
@override
Widget build(BuildContext context) {
return ColoredBox(
color: ColorsManager.whiteColors,
child: GridView.builder(
padding: const EdgeInsets.symmetric(horizontal: 40, vertical: 20),
gridDelegate: const SliverGridDelegateWithMaxCrossAxisExtent(
maxCrossAxisExtent: 400,
mainAxisSpacing: 10,
crossAxisSpacing: 10,
childAspectRatio: 2.0,
return Expanded(
child: ColoredBox(
color: ColorsManager.whiteColors,
child: GridView.builder(
padding: const EdgeInsets.symmetric(horizontal: 40, vertical: 20),
gridDelegate: const SliverGridDelegateWithMaxCrossAxisExtent(
maxCrossAxisExtent: 400,
mainAxisSpacing: 10,
crossAxisSpacing: 10,
childAspectRatio: 2.0,
),
itemCount: _gridItems(context).length,
itemBuilder: (context, index) {
final model = _gridItems(context)[index];
return CommunityTemplateCell(
onTap: model.onTap,
title: model.title,
);
},
),
itemCount: _gridItems(context).length,
itemBuilder: (context, index) {
final model = _gridItems(context)[index];
return CommunityTemplateCell(
onTap: model.onTap,
title: model.title,
);
},
),
);
}

View File

@ -16,7 +16,7 @@ final class SelectCommunityEvent extends CommunitiesTreeSelectionEvent {
}
final class SelectSpaceEvent extends CommunitiesTreeSelectionEvent {
final SpaceModel? space;
final SpaceModel space;
final CommunityModel community;
const SelectSpaceEvent({required this.space, required this.community});

View File

@ -1,11 +0,0 @@
import 'package:flutter/material.dart';
import 'package:syncrow_web/pages/space_management_v2/modules/space_details/presentation/widgets/space_details_dialog.dart';
abstract final class SpaceDetailsDialogHelper {
static void showCreate(BuildContext context) {
showDialog<void>(
context: context,
builder: (context) => const SpaceDetailsDialog(),
);
}
}

View File

@ -1,12 +0,0 @@
import 'package:flutter/material.dart';
class SpaceDetailsDialog extends StatelessWidget {
const SpaceDetailsDialog({super.key});
@override
Widget build(BuildContext context) {
return const Dialog(
child: Text('Create Space'),
);
}
}

View File

@ -289,6 +289,7 @@ class SpaceTreeBloc extends Bloc<SpaceTreeEvent, SpaceTreeState> {
selectedSpaces: updatedSelectedSpaces,
soldCheck: updatedSoldChecks,
selectedCommunityAndSpaces: communityAndSpaces));
emit(state.copyWith(selectedSpaces: updatedSelectedSpaces));
} catch (e) {
emit(const SpaceTreeErrorState('Something went wrong'));
}
@ -444,12 +445,10 @@ class SpaceTreeBloc extends Bloc<SpaceTreeEvent, SpaceTreeState> {
List<String> _getThePathToChild(String communityId, String selectedSpaceId) {
List<String> ids = [];
final communityDataSource =
state.searchQuery.isNotEmpty ? state.filteredCommunity : state.communityList;
for (final community in communityDataSource) {
for (var community in state.communityList) {
if (community.uuid == communityId) {
for (final space in community.spaces) {
final list = <String>[];
for (var space in community.spaces) {
List<String> list = [];
list.add(space.uuid!);
ids = _getAllParentsIds(space, selectedSpaceId, List.from(list));
if (ids.isNotEmpty) {

View File

@ -1,120 +0,0 @@
import 'package:flutter/material.dart';
import 'package:syncrow_web/pages/visitor_password/bloc/visitor_password_bloc.dart';
class AccessTypeRadioGroup extends StatelessWidget {
final String? selectedType;
final String? accessTypeSelected;
final Function(String) onTypeSelected;
final VisitorPasswordBloc visitorBloc;
const AccessTypeRadioGroup({
super.key,
required this.selectedType,
required this.accessTypeSelected,
required this.onTypeSelected,
required this.visitorBloc,
});
@override
Widget build(BuildContext context) {
Size size = MediaQuery.of(context).size;
final text = Theme.of(context)
.textTheme
.bodySmall!
.copyWith(color: Colors.black, fontSize: 13);
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Text(
'* ',
style: Theme.of(context)
.textTheme
.bodyMedium!
.copyWith(color: Colors.red),
),
Text('Access Type', style: text),
],
),
const SizedBox(height: 8),
if (size.width < 800)
Column(
children: [
_buildRadioTile(
context,
'Online Password',
selectedType ?? accessTypeSelected,
onTypeSelected,
),
const SizedBox(height: 8),
_buildRadioTile(
context,
'Offline Password',
selectedType ?? accessTypeSelected,
onTypeSelected,
),
],
)
else
Row(
children: [
Expanded(
flex: 2,
child: Row(
children: [
_buildRadioTile(
context,
'Online Password',
selectedType ?? accessTypeSelected,
onTypeSelected,
width: size.width * 0.12,
),
_buildRadioTile(
context,
'Offline Password',
selectedType ?? accessTypeSelected,
onTypeSelected,
width: size.width * 0.12,
),
],
),
),
const Spacer(flex: 2),
],
),
],
);
}
Widget _buildRadioTile(
BuildContext context,
String value,
String? groupValue,
Function(String) onChanged, {
double? width,
}) {
return SizedBox(
width: width,
child: RadioListTile<String>(
contentPadding: EdgeInsets.zero,
title: Text(value,
style: Theme.of(context).textTheme.bodySmall!.copyWith(
color: Colors.black,
fontSize: 13,
)),
value: value,
groupValue: groupValue,
onChanged: (value) {
if (value != null) {
onChanged(value);
if (value == 'Dynamic Password') {
visitorBloc.usageFrequencySelected = '';
}
}
},
),
);
}
}

View File

@ -1,73 +0,0 @@
import 'package:flutter/material.dart';
import 'package:syncrow_web/pages/common/text_field/custom_web_textfield.dart';
class NameAndEmailFields extends StatelessWidget {
final TextEditingController nameController;
final TextEditingController emailController;
final String? Function(String?)? nameValidator;
final String? Function(String?)? emailValidator;
const NameAndEmailFields({
super.key,
required this.nameController,
required this.emailController,
required this.nameValidator,
required this.emailValidator,
});
@override
Widget build(BuildContext context) {
Size size = MediaQuery.of(context).size;
return Container(
width: size.width,
child: size.width < 800
? Column(
children: [
CustomWebTextField(
validator: nameValidator,
controller: nameController,
isRequired: true,
textFieldName: 'Name',
description: '',
),
const SizedBox(height: 15),
CustomWebTextField(
validator: emailValidator,
controller: emailController,
isRequired: true,
textFieldName: 'Email Address',
description:
'The password will be sent to the visitors email address.',
),
],
)
: Row(
children: [
Expanded(
flex: 2,
child: CustomWebTextField(
validator: nameValidator,
controller: nameController,
isRequired: true,
textFieldName: 'Name',
description: '',
),
),
const Spacer(),
Expanded(
flex: 2,
child: CustomWebTextField(
validator: emailValidator,
controller: emailController,
isRequired: true,
textFieldName: 'Email Address',
description:
'The password will be sent to the visitors email address.',
),
),
const Spacer(),
],
),
);
}
}

View File

@ -1,91 +0,0 @@
import 'package:flutter/material.dart';
class UsageFrequencyRadioGroup extends StatelessWidget {
final String? selectedFrequency;
final String? usageFrequencySelected;
final Function(String) onFrequencySelected;
const UsageFrequencyRadioGroup({
super.key,
required this.selectedFrequency,
required this.usageFrequencySelected,
required this.onFrequencySelected,
});
@override
Widget build(BuildContext context) {
Size size = MediaQuery.of(context).size;
final text = Theme.of(context)
.textTheme
.bodySmall!
.copyWith(color: Colors.black, fontSize: 13);
return size.width < 600
? Column(
children: [
_buildRadioTile(
context,
'One-Time',
selectedFrequency ?? usageFrequencySelected,
onFrequencySelected,
text: text,
fullWidth: true,
),
const SizedBox(height: 8),
_buildRadioTile(
context,
'Periodic',
selectedFrequency ?? usageFrequencySelected,
onFrequencySelected,
text: text,
fullWidth: true,
),
],
)
: Row(
children: [
_buildRadioTile(
context,
'One-Time',
selectedFrequency ?? usageFrequencySelected,
onFrequencySelected,
width: size.width * 0.12,
text: text,
),
_buildRadioTile(
context,
'Periodic',
selectedFrequency ?? usageFrequencySelected,
onFrequencySelected,
width: size.width * 0.12,
text: text,
),
],
);
}
Widget _buildRadioTile(
BuildContext context,
String value,
String? groupValue,
Function(String) onChanged, {
double? width,
required TextStyle text,
bool fullWidth = false,
}) {
return SizedBox(
width: fullWidth ? double.infinity : width,
child: RadioListTile<String>(
contentPadding: EdgeInsets.zero,
title: Text(value, style: text),
value: value,
groupValue: groupValue,
onChanged: (String? value) {
if (value != null) {
onChanged(value);
}
},
),
);
}
}

View File

@ -9,11 +9,8 @@ import 'package:syncrow_web/pages/common/text_field/custom_web_textfield.dart';
import 'package:syncrow_web/pages/visitor_password/bloc/visitor_password_bloc.dart';
import 'package:syncrow_web/pages/visitor_password/bloc/visitor_password_event.dart';
import 'package:syncrow_web/pages/visitor_password/bloc/visitor_password_state.dart';
import 'package:syncrow_web/pages/visitor_password/view/access_type_radio_group.dart';
import 'package:syncrow_web/pages/visitor_password/view/add_device_dialog.dart';
import 'package:syncrow_web/pages/visitor_password/view/repeat_widget.dart';
import 'package:syncrow_web/pages/visitor_password/view/responsive_fields_row.dart';
import 'package:syncrow_web/pages/visitor_password/view/usage_frequency_radio_group.dart';
import 'package:syncrow_web/utils/color_manager.dart';
import 'package:syncrow_web/utils/constants/assets.dart';
import 'package:syncrow_web/utils/style.dart';
@ -24,10 +21,7 @@ class VisitorPasswordDialog extends StatelessWidget {
@override
Widget build(BuildContext context) {
Size size = MediaQuery.of(context).size;
var text = Theme.of(context)
.textTheme
.bodySmall!
.copyWith(color: Colors.black, fontSize: 13);
var text = Theme.of(context).textTheme.bodySmall!.copyWith(color: Colors.black, fontSize: 13);
return BlocProvider(
create: (context) => VisitorPasswordBloc(),
child: BlocListener<VisitorPasswordBloc, VisitorPasswordState>(
@ -41,8 +35,7 @@ class VisitorPasswordDialog extends StatelessWidget {
title: 'Sent Successfully',
widgeta: Column(
children: [
if (visitorBloc
.passwordStatus!.failedOperations.isNotEmpty)
if (visitorBloc.passwordStatus!.failedOperations.isNotEmpty)
Column(
children: [
const Text('Failed Devices'),
@ -52,8 +45,7 @@ class VisitorPasswordDialog extends StatelessWidget {
child: ListView.builder(
scrollDirection: Axis.horizontal,
shrinkWrap: true,
itemCount: visitorBloc
.passwordStatus!.failedOperations.length,
itemCount: visitorBloc.passwordStatus!.failedOperations.length,
itemBuilder: (context, index) {
return Container(
margin: EdgeInsets.all(5),
@ -61,17 +53,14 @@ class VisitorPasswordDialog extends StatelessWidget {
height: 45,
child: Center(
child: Text(visitorBloc
.passwordStatus!
.failedOperations[index]
.deviceUuid)),
.passwordStatus!.failedOperations[index].deviceUuid)),
);
},
),
),
],
),
if (visitorBloc
.passwordStatus!.successOperations.isNotEmpty)
if (visitorBloc.passwordStatus!.successOperations.isNotEmpty)
Column(
children: [
const Text('Success Devices'),
@ -81,18 +70,15 @@ class VisitorPasswordDialog extends StatelessWidget {
child: ListView.builder(
scrollDirection: Axis.horizontal,
shrinkWrap: true,
itemCount: visitorBloc
.passwordStatus!.successOperations.length,
itemCount: visitorBloc.passwordStatus!.successOperations.length,
itemBuilder: (context, index) {
return Container(
margin: EdgeInsets.all(5),
decoration: containerDecoration,
height: 45,
child: Center(
child: Text(visitorBloc
.passwordStatus!
.successOperations[index]
.deviceUuid)),
child: Text(visitorBloc.passwordStatus!
.successOperations[index].deviceUuid)),
);
},
),
@ -102,7 +88,8 @@ class VisitorPasswordDialog extends StatelessWidget {
],
))
.then((v) {
Navigator.of(context).pop(v);
Navigator.of(context).pop(true);
});
} else if (state is FailedState) {
visitorBloc.stateDialog(
@ -115,16 +102,15 @@ class VisitorPasswordDialog extends StatelessWidget {
child: BlocBuilder<VisitorPasswordBloc, VisitorPasswordState>(
builder: (BuildContext context, VisitorPasswordState state) {
final visitorBloc = BlocProvider.of<VisitorPasswordBloc>(context);
bool isRepeat =
state is IsRepeatState ? state.repeat : visitorBloc.repeat;
bool isRepeat = state is IsRepeatState ? state.repeat : visitorBloc.repeat;
return AlertDialog(
backgroundColor: Colors.white,
title: Text(
'Create visitor password',
style: Theme.of(context).textTheme.headlineLarge!.copyWith(
fontWeight: FontWeight.w400,
fontSize: 24,
color: Colors.black),
style: Theme.of(context)
.textTheme
.headlineLarge!
.copyWith(fontWeight: FontWeight.w400, fontSize: 24, color: Colors.black),
),
content: state is LoadingInitialState
? const Center(child: CircularProgressIndicator())
@ -135,11 +121,34 @@ class VisitorPasswordDialog extends StatelessWidget {
padding: const EdgeInsets.all(5.0),
child: ListBody(
children: <Widget>[
NameAndEmailFields(
nameController: visitorBloc.userNameController,
emailController: visitorBloc.emailController,
nameValidator: visitorBloc.validate,
emailValidator: visitorBloc.validateEmail,
Container(
child: Row(
children: [
Expanded(
flex: 2,
child: CustomWebTextField(
validator: visitorBloc.validate,
controller: visitorBloc.userNameController,
isRequired: true,
textFieldName: 'Name',
description: '',
),
),
const Spacer(),
Expanded(
flex: 2,
child: CustomWebTextField(
validator: visitorBloc.validateEmail,
controller: visitorBloc.emailController,
isRequired: true,
textFieldName: 'Email Address',
description:
'The password will be sent to the visitors email address.',
),
),
const Spacer(),
],
),
),
const SizedBox(
height: 15,
@ -147,43 +156,107 @@ class VisitorPasswordDialog extends StatelessWidget {
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
AccessTypeRadioGroup(
selectedType: state is PasswordTypeSelected
? state.selectedType
: null,
accessTypeSelected:
visitorBloc.accessTypeSelected,
onTypeSelected: (value) {
context
.read<VisitorPasswordBloc>()
.add(SelectPasswordType(value));
},
visitorBloc: visitorBloc,
Row(
children: [
Text(
'* ',
style: Theme.of(context)
.textTheme
.bodyMedium!
.copyWith(color: Colors.red),
),
Text('Access Type', style: text),
],
),
if (visitorBloc.accessTypeSelected ==
'Online Password')
Row(
children: <Widget>[
Expanded(
flex: 2,
child: Row(
children: [
SizedBox(
width: size.width * 0.12,
child: RadioListTile<String>(
contentPadding: EdgeInsets.zero,
title: Text(
'Online Password',
style: text,
),
value: 'Online Password',
groupValue: (state is PasswordTypeSelected)
? state.selectedType
: visitorBloc.accessTypeSelected,
onChanged: (String? value) {
if (value != null) {
context
.read<VisitorPasswordBloc>()
.add(SelectPasswordType(value));
}
},
),
),
SizedBox(
width: size.width * 0.12,
child: RadioListTile<String>(
contentPadding: EdgeInsets.zero,
title: Text('Offline Password', style: text),
value: 'Offline Password',
groupValue: (state is PasswordTypeSelected)
? state.selectedType
: visitorBloc.accessTypeSelected,
onChanged: (String? value) {
if (value != null) {
context
.read<VisitorPasswordBloc>()
.add(SelectPasswordType(value));
}
},
),
),
// SizedBox(
// width: size.width * 0.12,
// child: RadioListTile<String>(
// contentPadding: EdgeInsets.zero,
// title: Text(
// 'Dynamic Password',
// style: text,
// ),
// value: 'Dynamic Password',
// groupValue: (state is PasswordTypeSelected)
// ? state.selectedType
// : visitorBloc.accessTypeSelected,
// onChanged: (String? value) {
// if (value != null) {
// context
// .read<VisitorPasswordBloc>()
// .add(SelectPasswordType(value));
// visitorBloc.usageFrequencySelected = '';
// }
// },
// ),
// ),
],
)),
const Spacer(
flex: 2,
),
],
),
if (visitorBloc.accessTypeSelected == 'Online Password')
Text(
'Only currently online devices can be selected. It is recommended to use when the device network is stable, and the system randomly generates a digital password',
style: Theme.of(context)
.textTheme
.bodySmall!
.copyWith(
fontWeight: FontWeight.w400,
color: ColorsManager.grayColor,
fontSize: 9),
style: Theme.of(context).textTheme.bodySmall!.copyWith(
fontWeight: FontWeight.w400,
color: ColorsManager.grayColor,
fontSize: 9),
),
if (visitorBloc.accessTypeSelected ==
'Offline Password')
if (visitorBloc.accessTypeSelected == 'Offline Password')
Text(
'Unaffected by the online status of the device, you can select online or offline device, and the system randomly generates a digital password',
style: Theme.of(context)
.textTheme
.bodySmall!
.copyWith(
fontWeight: FontWeight.w400,
color: ColorsManager.grayColor,
fontSize: 9),
style: Theme.of(context).textTheme.bodySmall!.copyWith(
fontWeight: FontWeight.w400,
color: ColorsManager.grayColor,
fontSize: 9),
),
// if (visitorBloc.accessTypeSelected == 'Dynamic Password')
// Text(
@ -198,170 +271,143 @@ class VisitorPasswordDialog extends StatelessWidget {
)
],
),
if (visitorBloc.accessTypeSelected ==
'Dynamic Password')
const SizedBox()
else
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
visitorBloc.accessTypeSelected == 'Dynamic Password'
? const SizedBox()
: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'* ',
style: Theme.of(context)
.textTheme
.bodyMedium!
.copyWith(color: Colors.red),
Row(
children: [
Text(
'* ',
style: Theme.of(context)
.textTheme
.bodyMedium!
.copyWith(color: Colors.red),
),
Text(
'Usage Frequency',
style: text,
),
],
),
Text(
'Usage Frequency',
style: text,
Row(
children: <Widget>[
SizedBox(
width: size.width * 0.12,
child: RadioListTile<String>(
contentPadding: EdgeInsets.zero,
title: Text(
'One-Time',
style: text,
),
value: 'One-Time',
groupValue: (state is UsageFrequencySelected)
? state.selectedFrequency
: visitorBloc.usageFrequencySelected,
onChanged: (String? value) {
if (value != null) {
context
.read<VisitorPasswordBloc>()
.add(SelectUsageFrequency(value));
}
},
),
),
SizedBox(
width: size.width * 0.12,
child: RadioListTile<String>(
contentPadding: EdgeInsets.zero,
title: Text('Periodic', style: text),
value: 'Periodic',
groupValue: (state is UsageFrequencySelected)
? state.selectedFrequency
: visitorBloc.usageFrequencySelected,
onChanged: (String? value) {
if (value != null) {
context.read<VisitorPasswordBloc>()
.add(SelectUsageFrequency(value));
}
},
),
),
],
),
//One-Time
if (visitorBloc.usageFrequencySelected == 'One-Time' &&
visitorBloc.accessTypeSelected == 'Online Password')
Text(
'Within the validity period, each device can be unlocked only once.',
style: Theme.of(context).textTheme.bodySmall!.copyWith(
color: ColorsManager.grayColor, fontSize: 9),
),
if (visitorBloc.usageFrequencySelected == 'One-Time' &&
visitorBloc.accessTypeSelected == 'Offline Password')
Text(
'Within the validity period, each device can be unlocked only once, and the maximum validity period is 6 hours',
style: Theme.of(context).textTheme.bodySmall!.copyWith(
color: ColorsManager.grayColor, fontSize: 9),
),
// Periodic
if (visitorBloc.usageFrequencySelected == 'Periodic' &&
visitorBloc.accessTypeSelected == 'Offline Password')
Text(
'Within the validity period, there is no limit to the number of times each device can be unlocked, and it should be used at least once within 24 hours after the entry into force.',
style: Theme.of(context).textTheme.bodySmall!.copyWith(
color: ColorsManager.grayColor, fontSize: 9),
),
if (visitorBloc.usageFrequencySelected == 'Periodic' &&
visitorBloc.accessTypeSelected == 'Online Password')
Text(
'Within the validity period, there is no limit to the number of times each device can be unlocked.',
style: Theme.of(context).textTheme.bodySmall!.copyWith(
color: ColorsManager.grayColor, fontSize: 9),
),
],
),
UsageFrequencyRadioGroup(
selectedFrequency:
state is UsageFrequencySelected
? state.selectedFrequency
: null,
usageFrequencySelected:
visitorBloc.usageFrequencySelected,
onFrequencySelected: (value) {
context
.read<VisitorPasswordBloc>()
.add(SelectUsageFrequency(value));
},
),
//One-Time
if (visitorBloc.usageFrequencySelected ==
'One-Time' &&
visitorBloc.accessTypeSelected ==
'Online Password')
Text(
'Within the validity period, each device can be unlocked only once.',
style: Theme.of(context)
.textTheme
.bodySmall!
.copyWith(
color: ColorsManager.grayColor,
fontSize: 9),
),
if (visitorBloc.usageFrequencySelected ==
'One-Time' &&
visitorBloc.accessTypeSelected ==
'Offline Password')
Text(
'Within the validity period, each device can be unlocked only once, and the maximum validity period is 6 hours',
style: Theme.of(context)
.textTheme
.bodySmall!
.copyWith(
color: ColorsManager.grayColor,
fontSize: 9),
),
// Periodic
if (visitorBloc.usageFrequencySelected ==
'Periodic' &&
visitorBloc.accessTypeSelected ==
'Offline Password')
Text(
'Within the validity period, there is no limit to the number of times each device can be unlocked, and it should be used at least once within 24 hours after the entry into force.',
style: Theme.of(context)
.textTheme
.bodySmall!
.copyWith(
color: ColorsManager.grayColor,
fontSize: 9),
),
if (visitorBloc.usageFrequencySelected ==
'Periodic' &&
visitorBloc.accessTypeSelected ==
'Online Password')
Text(
'Within the validity period, there is no limit to the number of times each device can be unlocked.',
style: Theme.of(context)
.textTheme
.bodySmall!
.copyWith(
color: ColorsManager.grayColor,
fontSize: 9),
),
],
),
const SizedBox(
height: 20,
),
if ((visitorBloc.usageFrequencySelected !=
'One-Time' ||
visitorBloc.accessTypeSelected !=
'Offline Password') &&
if ((visitorBloc.usageFrequencySelected != 'One-Time' ||
visitorBloc.accessTypeSelected != 'Offline Password') &&
(visitorBloc.usageFrequencySelected != ''))
DateTimeWebWidget(
isRequired: true,
title: 'Access Period',
size: size,
endTime: () {
if (visitorBloc.usageFrequencySelected ==
'Periodic' &&
visitorBloc.accessTypeSelected ==
'Offline Password') {
visitorBloc.add(SelectTimeEvent(
context: context,
isEffective: false));
if (visitorBloc.usageFrequencySelected == 'Periodic' &&
visitorBloc.accessTypeSelected == 'Offline Password') {
visitorBloc.add(SelectTimeEvent(context: context, isEffective: false));
} else {
visitorBloc.add(
SelectTimeVisitorPassword(
context: context,
isStart: false,
isRepeat: false));
visitorBloc.add(SelectTimeVisitorPassword(context: context, isStart: false, isRepeat: false));
}
},
startTime: () {
if (visitorBloc.usageFrequencySelected ==
'Periodic' &&
visitorBloc.accessTypeSelected ==
'Offline Password') {
visitorBloc.add(SelectTimeEvent(
context: context,
isEffective: true));
} else {
if (visitorBloc.usageFrequencySelected == 'Periodic' &&
visitorBloc.accessTypeSelected == 'Offline Password') {
visitorBloc.add(
SelectTimeVisitorPassword(
context: context,
isStart: true,
isRepeat: false));
SelectTimeEvent(context: context, isEffective: true));
} else {
visitorBloc.add(SelectTimeVisitorPassword(
context: context, isStart: true, isRepeat: false));
}
},
firstString: (visitorBloc
.usageFrequencySelected ==
'Periodic' &&
visitorBloc.accessTypeSelected ==
'Offline Password')
firstString: (visitorBloc.usageFrequencySelected ==
'Periodic' && visitorBloc.accessTypeSelected == 'Offline Password')
? visitorBloc.effectiveTime
: visitorBloc.startTimeAccess
.toString(),
secondString: (visitorBloc
.usageFrequencySelected ==
'Periodic' &&
visitorBloc.accessTypeSelected ==
'Offline Password')
: visitorBloc.startTimeAccess.toString(),
secondString: (visitorBloc.usageFrequencySelected ==
'Periodic' && visitorBloc.accessTypeSelected == 'Offline Password')
? visitorBloc.expirationTime
: visitorBloc.endTimeAccess.toString(),
icon: Assets.calendarIcon),
const SizedBox(
height: 10,
),
Text(
visitorBloc.accessPeriodValidate,
style: Theme.of(context)
.textTheme
.bodyMedium!
.copyWith(color: ColorsManager.red),
),
const SizedBox(height: 10,),
Text(visitorBloc.accessPeriodValidate,
style: Theme.of(context).textTheme.bodyMedium!.copyWith(color: ColorsManager.red),),
const SizedBox(
height: 20,
),
@ -385,21 +431,16 @@ class VisitorPasswordDialog extends StatelessWidget {
),
Text(
'Within the validity period, each device can be unlocked only once.',
style: Theme.of(context)
.textTheme
.bodySmall!
.copyWith(
fontWeight: FontWeight.w400,
color: ColorsManager.grayColor,
fontSize: 9),
style: Theme.of(context).textTheme.bodySmall!.copyWith(
fontWeight: FontWeight.w400,
color: ColorsManager.grayColor,
fontSize: 9),
),
const SizedBox(
height: 20,
),
if (visitorBloc.usageFrequencySelected ==
'Periodic' &&
visitorBloc.accessTypeSelected ==
'Online Password')
if (visitorBloc.usageFrequencySelected == 'Periodic' &&
visitorBloc.accessTypeSelected == 'Online Password')
SizedBox(
width: 100,
child: Column(
@ -410,8 +451,7 @@ class VisitorPasswordDialog extends StatelessWidget {
child: CupertinoSwitch(
value: visitorBloc.repeat,
onChanged: (value) {
visitorBloc
.add(ToggleRepeatEvent());
visitorBloc.add(ToggleRepeatEvent());
},
applyTheme: true,
),
@ -419,16 +459,12 @@ class VisitorPasswordDialog extends StatelessWidget {
],
),
),
if (visitorBloc.usageFrequencySelected ==
'Periodic' &&
visitorBloc.accessTypeSelected ==
'Online Password')
isRepeat
? const RepeatWidget()
: const SizedBox(),
if (visitorBloc.usageFrequencySelected == 'Periodic' &&
visitorBloc.accessTypeSelected == 'Online Password')
isRepeat ? const RepeatWidget() : const SizedBox(),
Container(
decoration: containerDecoration,
width: size.width / 6,
width: size.width / 9,
child: DefaultButton(
onPressed: () {
showDialog(
@ -436,28 +472,22 @@ class VisitorPasswordDialog extends StatelessWidget {
barrierDismissible: false,
builder: (BuildContext context) {
return AddDeviceDialog(
selectedDeviceIds:
visitorBloc.selectedDevices,
selectedDeviceIds: visitorBloc.selectedDevices,
);
},
).then((listDevice) {
if (listDevice != null) {
visitorBloc.selectedDevices =
listDevice;
visitorBloc.selectedDevices = listDevice;
}
});
},
borderRadius: 8,
child: Text(
'+ Add Device',
style: Theme.of(context)
.textTheme
.bodySmall!
.copyWith(
fontWeight: FontWeight.w400,
color:
ColorsManager.whiteColors,
fontSize: 12),
style: Theme.of(context).textTheme.bodySmall!.copyWith(
fontWeight: FontWeight.w400,
color: ColorsManager.whiteColors,
fontSize: 12),
),
),
),
@ -476,7 +506,7 @@ class VisitorPasswordDialog extends StatelessWidget {
child: DefaultButton(
borderRadius: 8,
onPressed: () {
Navigator.of(context).pop(null);
Navigator.of(context).pop(true);
},
backgroundColor: Colors.white,
child: Text(
@ -495,37 +525,30 @@ class VisitorPasswordDialog extends StatelessWidget {
onPressed: () {
if (visitorBloc.forgetFormKey.currentState!.validate()) {
if (visitorBloc.selectedDevices.isNotEmpty) {
if (visitorBloc.usageFrequencySelected ==
'One-Time' &&
visitorBloc.accessTypeSelected ==
'Offline Password') {
if (visitorBloc.usageFrequencySelected == 'One-Time' &&
visitorBloc.accessTypeSelected == 'Offline Password') {
setPasswordFunction(context, size, visitorBloc);
} else if (visitorBloc.usageFrequencySelected ==
'Periodic' &&
visitorBloc.accessTypeSelected ==
'Offline Password') {
} else if (visitorBloc.usageFrequencySelected == 'Periodic' &&
visitorBloc.accessTypeSelected == 'Offline Password') {
if (visitorBloc.expirationTime != 'End Time' &&
visitorBloc.effectiveTime != 'Start Time') {
visitorBloc.effectiveTime != 'Start Time' ) {
setPasswordFunction(context, size, visitorBloc);
} else {
}else{
visitorBloc.stateDialog(
context: context,
message:
'Please select Access Period to continue',
message: 'Please select Access Period to continue',
title: 'Access Period');
}
} else if (visitorBloc.endTimeAccess.toString() !=
'End Time' &&
visitorBloc.startTimeAccess.toString() !=
'Start Time') {
} else if(
visitorBloc.endTimeAccess.toString()!='End Time'
&&visitorBloc.startTimeAccess.toString()!='Start Time') {
if (visitorBloc.effectiveTimeTimeStamp != null &&
visitorBloc.expirationTimeTimeStamp != null) {
if (isRepeat == true) {
if (visitorBloc.expirationTime != 'End Time' &&
visitorBloc.effectiveTime != 'Start Time' &&
visitorBloc.selectedDays.isNotEmpty) {
setPasswordFunction(
context, size, visitorBloc);
setPasswordFunction(context, size, visitorBloc);
} else {
visitorBloc.stateDialog(
context: context,
@ -539,16 +562,14 @@ class VisitorPasswordDialog extends StatelessWidget {
} else {
visitorBloc.stateDialog(
context: context,
message:
'Please select Access Period to continue',
message: 'Please select Access Period to continue',
title: 'Access Period');
}
} else {
visitorBloc.stateDialog(
context: context,
message:
'Please select Access Period to continue',
title: 'Access Period');
}else{
visitorBloc.stateDialog(
context: context,
message: 'Please select Access Period to continue',
title: 'Access Period');
}
} else {
visitorBloc.stateDialog(
@ -594,8 +615,7 @@ class VisitorPasswordDialog extends StatelessWidget {
content: SizedBox(
height: size.height * 0.25,
child: Center(
child:
CircularProgressIndicator(), // Display a loading spinner
child: CircularProgressIndicator(), // Display a loading spinner
),
),
);
@ -619,10 +639,7 @@ class VisitorPasswordDialog extends StatelessWidget {
),
Text(
'Set Password',
style: Theme.of(context)
.textTheme
.headlineLarge!
.copyWith(
style: Theme.of(context).textTheme.headlineLarge!.copyWith(
fontSize: 30,
fontWeight: FontWeight.w400,
color: Colors.black,
@ -651,7 +668,7 @@ class VisitorPasswordDialog extends StatelessWidget {
child: DefaultButton(
borderRadius: 8,
onPressed: () {
Navigator.of(context).pop(null);
Navigator.of(context).pop();
},
backgroundColor: Colors.white,
child: Text(
@ -672,45 +689,37 @@ class VisitorPasswordDialog extends StatelessWidget {
onPressed: () {
Navigator.pop(context);
if (visitorBloc.usageFrequencySelected == 'One-Time' &&
visitorBloc.accessTypeSelected ==
'Online Password') {
visitorBloc.accessTypeSelected == 'Online Password') {
visitorBloc.add(OnlineOneTimePasswordEvent(
context: context,
passwordName: visitorBloc.userNameController.text,
email: visitorBloc.emailController.text,
));
} else if (visitorBloc.usageFrequencySelected ==
'Periodic' &&
visitorBloc.accessTypeSelected ==
'Online Password') {
}
else if (visitorBloc.usageFrequencySelected == 'Periodic' &&
visitorBloc.accessTypeSelected == 'Online Password') {
visitorBloc.add(OnlineMultipleTimePasswordEvent(
passwordName: visitorBloc.userNameController.text,
email: visitorBloc.emailController.text,
effectiveTime:
visitorBloc.effectiveTimeTimeStamp.toString(),
invalidTime:
visitorBloc.expirationTimeTimeStamp.toString(),
effectiveTime: visitorBloc.effectiveTimeTimeStamp.toString(),
invalidTime: visitorBloc.expirationTimeTimeStamp.toString(),
));
} else if (visitorBloc.usageFrequencySelected ==
'One-Time' &&
visitorBloc.accessTypeSelected ==
'Offline Password') {
}
else if (visitorBloc.usageFrequencySelected == 'One-Time' &&
visitorBloc.accessTypeSelected == 'Offline Password') {
visitorBloc.add(OfflineOneTimePasswordEvent(
context: context,
passwordName: visitorBloc.userNameController.text,
email: visitorBloc.emailController.text,
));
} else if (visitorBloc.usageFrequencySelected ==
'Periodic' &&
visitorBloc.accessTypeSelected ==
'Offline Password') {
}
else if (visitorBloc.usageFrequencySelected == 'Periodic' &&
visitorBloc.accessTypeSelected == 'Offline Password') {
visitorBloc.add(OfflineMultipleTimePasswordEvent(
passwordName: visitorBloc.userNameController.text,
email: visitorBloc.emailController.text,
effectiveTime:
visitorBloc.effectiveTimeTimeStamp.toString(),
invalidTime:
visitorBloc.expirationTimeTimeStamp.toString(),
effectiveTime: visitorBloc.effectiveTimeTimeStamp.toString(),
invalidTime: visitorBloc.expirationTimeTimeStamp.toString(),
));
}
},

View File

@ -13,15 +13,13 @@ import 'package:syncrow_web/utils/constants/api_const.dart';
class DevicesManagementApi {
Future<List<AllDevicesModel>> fetchDevices(
String communityId, String spaceId, String projectId) async {
String projectId, {
List<String>? spacesId,
}) async {
try {
final response = await HTTPService().get(
path: communityId.isNotEmpty && spaceId.isNotEmpty
? ApiEndpoints.getSpaceDevices
.replaceAll('{spaceUuid}', spaceId)
.replaceAll('{communityUuid}', communityId)
.replaceAll('{projectId}', projectId)
: ApiEndpoints.getAllDevices.replaceAll('{projectId}', projectId),
queryParameters: {if (spacesId != null) 'spaces': spacesId},
path: ApiEndpoints.getAllDevices.replaceAll('{projectId}', projectId),
showServerMessage: true,
expectedResponseModel: (json) {
List<dynamic> jsonData = json['data'];
@ -416,5 +414,4 @@ class DevicesManagementApi {
);
return response;
}
}

View File

@ -18,7 +18,7 @@ abstract class ApiEndpoints {
static const String getAllDevices = '/projects/{projectId}/devices';
static const String getSpaceDevices =
'/projects/{projectId}/communities/{communityUuid}/spaces/{spaceUuid}/devices';
'/projects/{projectId}/devices';
static const String getDeviceStatus = '/devices/{uuid}/functions/status';
static const String getBatchStatus = '/devices/batch';