mirror of
https://github.com/SyncrowIOT/web.git
synced 2025-07-14 17:25:50 +00:00
Compare commits
1 Commits
uses-UTC-d
...
SP-1387-fe
Author | SHA1 | Date | |
---|---|---|---|
388391eec4 |
@ -25,8 +25,8 @@ class AnalyticsDevice {
|
|||||||
|
|
||||||
factory AnalyticsDevice.fromJson(Map<String, dynamic> json) {
|
factory AnalyticsDevice.fromJson(Map<String, dynamic> json) {
|
||||||
return AnalyticsDevice(
|
return AnalyticsDevice(
|
||||||
uuid: json['uuid'] as String? ?? '',
|
uuid: json['uuid'] as String,
|
||||||
name: json['name'] as String? ?? '',
|
name: json['name'] as String,
|
||||||
createdAt: json['createdAt'] != null
|
createdAt: json['createdAt'] != null
|
||||||
? DateTime.parse(json['createdAt'] as String)
|
? DateTime.parse(json['createdAt'] as String)
|
||||||
: null,
|
: null,
|
||||||
@ -39,8 +39,8 @@ class AnalyticsDevice {
|
|||||||
? ProductDevice.fromJson(json['productDevice'] as Map<String, dynamic>)
|
? ProductDevice.fromJson(json['productDevice'] as Map<String, dynamic>)
|
||||||
: null,
|
: null,
|
||||||
spaceUuid: json['spaceUuid'] as String?,
|
spaceUuid: json['spaceUuid'] as String?,
|
||||||
latitude: json['lat'] != null ? double.parse(json['lat'] as String? ?? '0.0') : null,
|
latitude: json['lat'] != null ? double.parse(json['lat'] as String) : null,
|
||||||
longitude: json['lon'] != null ? double.parse(json['lon'] as String? ?? '0.0') : null,
|
longitude: json['lon'] != null ? double.parse(json['lon'] as String) : null,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -14,21 +14,12 @@ class OccupancyHeatMapModel extends Equatable {
|
|||||||
});
|
});
|
||||||
|
|
||||||
factory OccupancyHeatMapModel.fromJson(Map<String, dynamic> json) {
|
factory OccupancyHeatMapModel.fromJson(Map<String, dynamic> json) {
|
||||||
final eventDate = json['event_date'] as String?;
|
|
||||||
final year = eventDate?.split('-')[0];
|
|
||||||
final month = eventDate?.split('-')[1];
|
|
||||||
final day = eventDate?.split('-')[2];
|
|
||||||
|
|
||||||
return OccupancyHeatMapModel(
|
return OccupancyHeatMapModel(
|
||||||
uuid: json['uuid'] as String? ?? '',
|
uuid: json['uuid'] as String? ?? '',
|
||||||
eventDate: DateTime.utc(
|
eventDate: DateTime.parse(
|
||||||
int.parse(year ?? '2025'),
|
json['event_date'] as String? ?? '${DateTime.now()}',
|
||||||
int.parse(month ?? '1'),
|
|
||||||
int.parse(day ?? '1'),
|
|
||||||
),
|
),
|
||||||
countTotalPresenceDetected: num.parse(
|
countTotalPresenceDetected: json['count_total_presence_detected'] as int? ?? 0,
|
||||||
json['count_total_presence_detected']?.toString() ?? '0',
|
|
||||||
).toInt(),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -4,6 +4,7 @@ import 'package:syncrow_web/pages/analytics/modules/air_quality/blocs/air_qualit
|
|||||||
import 'package:syncrow_web/pages/analytics/modules/air_quality/blocs/device_location/device_location_bloc.dart';
|
import 'package:syncrow_web/pages/analytics/modules/air_quality/blocs/device_location/device_location_bloc.dart';
|
||||||
import 'package:syncrow_web/pages/analytics/modules/air_quality/blocs/range_of_aqi/range_of_aqi_bloc.dart';
|
import 'package:syncrow_web/pages/analytics/modules/air_quality/blocs/range_of_aqi/range_of_aqi_bloc.dart';
|
||||||
import 'package:syncrow_web/pages/analytics/modules/air_quality/widgets/aqi_type_dropdown.dart';
|
import 'package:syncrow_web/pages/analytics/modules/air_quality/widgets/aqi_type_dropdown.dart';
|
||||||
|
import 'package:syncrow_web/pages/analytics/modules/analytics/blocs/analytics_date_picker_bloc/analytics_date_picker_bloc.dart';
|
||||||
import 'package:syncrow_web/pages/analytics/modules/analytics/blocs/analytics_devices/analytics_devices_bloc.dart';
|
import 'package:syncrow_web/pages/analytics/modules/analytics/blocs/analytics_devices/analytics_devices_bloc.dart';
|
||||||
import 'package:syncrow_web/pages/analytics/modules/energy_management/blocs/realtime_device_changes/realtime_device_changes_bloc.dart';
|
import 'package:syncrow_web/pages/analytics/modules/energy_management/blocs/realtime_device_changes/realtime_device_changes_bloc.dart';
|
||||||
import 'package:syncrow_web/pages/analytics/params/get_air_quality_distribution_param.dart';
|
import 'package:syncrow_web/pages/analytics/params/get_air_quality_distribution_param.dart';
|
||||||
@ -21,6 +22,7 @@ abstract final class FetchAirQualityDataHelper {
|
|||||||
required String spaceUuid,
|
required String spaceUuid,
|
||||||
bool shouldFetchAnalyticsDevices = true,
|
bool shouldFetchAnalyticsDevices = true,
|
||||||
}) {
|
}) {
|
||||||
|
final date = context.read<AnalyticsDatePickerBloc>().state.monthlyDate;
|
||||||
final aqiType = context.read<AirQualityDistributionBloc>().state.selectedAqiType;
|
final aqiType = context.read<AirQualityDistributionBloc>().state.selectedAqiType;
|
||||||
if (shouldFetchAnalyticsDevices) {
|
if (shouldFetchAnalyticsDevices) {
|
||||||
loadAnalyticsDevices(
|
loadAnalyticsDevices(
|
||||||
|
@ -18,11 +18,7 @@ abstract final class RangeOfAqiChartsHelper {
|
|||||||
(ColorsManager.hazardousPurple, 'Hazardous'),
|
(ColorsManager.hazardousPurple, 'Hazardous'),
|
||||||
];
|
];
|
||||||
|
|
||||||
static FlTitlesData titlesData(
|
static FlTitlesData titlesData(BuildContext context, List<RangeOfAqi> data) {
|
||||||
BuildContext context,
|
|
||||||
List<RangeOfAqi> data, {
|
|
||||||
double leftSideInterval = 50,
|
|
||||||
}) {
|
|
||||||
final titlesData = EnergyManagementChartsHelper.titlesData(context);
|
final titlesData = EnergyManagementChartsHelper.titlesData(context);
|
||||||
return titlesData.copyWith(
|
return titlesData.copyWith(
|
||||||
bottomTitles: titlesData.bottomTitles.copyWith(
|
bottomTitles: titlesData.bottomTitles.copyWith(
|
||||||
@ -43,11 +39,11 @@ abstract final class RangeOfAqiChartsHelper {
|
|||||||
leftTitles: titlesData.leftTitles.copyWith(
|
leftTitles: titlesData.leftTitles.copyWith(
|
||||||
sideTitles: titlesData.leftTitles.sideTitles.copyWith(
|
sideTitles: titlesData.leftTitles.sideTitles.copyWith(
|
||||||
reservedSize: 70,
|
reservedSize: 70,
|
||||||
interval: leftSideInterval,
|
interval: 50,
|
||||||
maxIncluded: false,
|
maxIncluded: false,
|
||||||
minIncluded: true,
|
minIncluded: true,
|
||||||
getTitlesWidget: (value, meta) {
|
getTitlesWidget: (value, meta) {
|
||||||
final text = value.toInt().toString();
|
final text = value >= 300 ? '301+' : value.toInt().toString();
|
||||||
return Padding(
|
return Padding(
|
||||||
padding: const EdgeInsetsDirectional.only(end: 12),
|
padding: const EdgeInsetsDirectional.only(end: 12),
|
||||||
child: FittedBox(
|
child: FittedBox(
|
||||||
|
@ -65,7 +65,7 @@ class AqiDeviceInfo extends StatelessWidget {
|
|||||||
);
|
);
|
||||||
final tvocValue = _getValueForStatus(
|
final tvocValue = _getValueForStatus(
|
||||||
status,
|
status,
|
||||||
'voc_value',
|
'tvoc_value',
|
||||||
formatter: (value) => (value / 100).toStringAsFixed(2),
|
formatter: (value) => (value / 100).toStringAsFixed(2),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -3,7 +3,6 @@ import 'package:flutter/material.dart';
|
|||||||
import 'package:intl/intl.dart';
|
import 'package:intl/intl.dart';
|
||||||
import 'package:syncrow_web/pages/analytics/models/air_quality_data_model.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/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/color_manager.dart';
|
||||||
import 'package:syncrow_web/utils/extension/build_context_x.dart';
|
import 'package:syncrow_web/utils/extension/build_context_x.dart';
|
||||||
|
|
||||||
@ -150,7 +149,6 @@ class AqiDistributionChart extends StatelessWidget {
|
|||||||
);
|
);
|
||||||
|
|
||||||
final bottomTitles = AxisTitles(
|
final bottomTitles = AxisTitles(
|
||||||
axisNameWidget: const ChartsXAxisTitle(),
|
|
||||||
sideTitles: SideTitles(
|
sideTitles: SideTitles(
|
||||||
showTitles: chartData.isNotEmpty,
|
showTitles: chartData.isNotEmpty,
|
||||||
getTitlesWidget: (value, _) => FittedBox(
|
getTitlesWidget: (value, _) => FittedBox(
|
||||||
|
@ -6,8 +6,8 @@ enum AqiType {
|
|||||||
aqi('AQI', '', 'aqi'),
|
aqi('AQI', '', 'aqi'),
|
||||||
pm25('PM2.5', 'µg/m³', 'pm25'),
|
pm25('PM2.5', 'µg/m³', 'pm25'),
|
||||||
pm10('PM10', 'µg/m³', 'pm10'),
|
pm10('PM10', 'µg/m³', 'pm10'),
|
||||||
hcho('HCHO', 'mg/m³', 'ch2o'),
|
hcho('HCHO', 'mg/m³', 'cho2'),
|
||||||
tvoc('TVOC', 'mg/m³', 'voc'),
|
tvoc('TVOC', 'µg/m³', 'voc'),
|
||||||
co2('CO2', 'ppm', 'co2');
|
co2('CO2', 'ppm', 'co2');
|
||||||
|
|
||||||
const AqiType(this.value, this.unit, this.code);
|
const AqiType(this.value, this.unit, this.code);
|
||||||
|
@ -2,18 +2,15 @@ import 'package:fl_chart/fl_chart.dart';
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:syncrow_web/pages/analytics/models/range_of_aqi.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/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/pages/analytics/modules/energy_management/helpers/energy_management_charts_helper.dart';
|
||||||
import 'package:syncrow_web/utils/color_manager.dart';
|
import 'package:syncrow_web/utils/color_manager.dart';
|
||||||
|
|
||||||
class RangeOfAqiChart extends StatelessWidget {
|
class RangeOfAqiChart extends StatelessWidget {
|
||||||
final List<RangeOfAqi> chartData;
|
final List<RangeOfAqi> chartData;
|
||||||
final AqiType selectedAqiType;
|
|
||||||
|
|
||||||
const RangeOfAqiChart({
|
const RangeOfAqiChart({
|
||||||
super.key,
|
super.key,
|
||||||
required this.chartData,
|
required this.chartData,
|
||||||
required this.selectedAqiType,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
List<(List<double> values, Color color, Color? dotColor)> get _lines {
|
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
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return LineChart(
|
return LineChart(
|
||||||
LineChartData(
|
LineChartData(
|
||||||
minY: 0,
|
minY: 0,
|
||||||
maxY: _maxYForAqiType.$1,
|
maxY: 301,
|
||||||
clipData: const FlClipData.vertical(),
|
clipData: const FlClipData.vertical(),
|
||||||
gridData: EnergyManagementChartsHelper.gridData(
|
gridData: EnergyManagementChartsHelper.gridData(horizontalInterval: 50),
|
||||||
horizontalInterval: _maxYForAqiType.$2,
|
titlesData: RangeOfAqiChartsHelper.titlesData(context, chartData),
|
||||||
),
|
|
||||||
titlesData: RangeOfAqiChartsHelper.titlesData(
|
|
||||||
context,
|
|
||||||
chartData,
|
|
||||||
leftSideInterval: _maxYForAqiType.$2,
|
|
||||||
),
|
|
||||||
borderData: EnergyManagementChartsHelper.borderData(),
|
borderData: EnergyManagementChartsHelper.borderData(),
|
||||||
lineTouchData: RangeOfAqiChartsHelper.lineTouchData(chartData),
|
lineTouchData: RangeOfAqiChartsHelper.lineTouchData(chartData),
|
||||||
betweenBarsData: [
|
betweenBarsData: [
|
||||||
|
@ -32,12 +32,7 @@ class RangeOfAqiChartBox extends StatelessWidget {
|
|||||||
const SizedBox(height: 10),
|
const SizedBox(height: 10),
|
||||||
const Divider(),
|
const Divider(),
|
||||||
const SizedBox(height: 20),
|
const SizedBox(height: 20),
|
||||||
Expanded(
|
Expanded(child: RangeOfAqiChart(chartData: state.filteredRangeOfAqi)),
|
||||||
child: RangeOfAqiChart(
|
|
||||||
chartData: state.filteredRangeOfAqi,
|
|
||||||
selectedAqiType: state.selectedAqiType,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
import 'package:fl_chart/fl_chart.dart';
|
import 'package:fl_chart/fl_chart.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:syncrow_web/pages/analytics/helpers/format_number_to_kwh.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/color_manager.dart';
|
||||||
import 'package:syncrow_web/utils/extension/build_context_x.dart';
|
import 'package:syncrow_web/utils/extension/build_context_x.dart';
|
||||||
|
|
||||||
@ -16,7 +15,6 @@ abstract final class EnergyManagementChartsHelper {
|
|||||||
return FlTitlesData(
|
return FlTitlesData(
|
||||||
show: true,
|
show: true,
|
||||||
bottomTitles: AxisTitles(
|
bottomTitles: AxisTitles(
|
||||||
axisNameWidget: const ChartsXAxisTitle(),
|
|
||||||
drawBelowEverything: true,
|
drawBelowEverything: true,
|
||||||
sideTitles: SideTitles(
|
sideTitles: SideTitles(
|
||||||
interval: 1,
|
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) {
|
static List<LineTooltipItem?> getTooltipItems(List<LineBarSpot> touchedSpots) {
|
||||||
return touchedSpots.map((spot) {
|
return touchedSpots.map((spot) {
|
||||||
return LineTooltipItem(
|
return LineTooltipItem(
|
||||||
getToolTipLabel(spot.y),
|
getToolTipLabel(spot.x, spot.y),
|
||||||
const TextStyle(
|
const TextStyle(
|
||||||
color: ColorsManager.textPrimaryColor,
|
color: ColorsManager.textPrimaryColor,
|
||||||
fontWeight: FontWeight.w600,
|
fontWeight: FontWeight.w600,
|
||||||
|
@ -27,30 +27,16 @@ class AnalyticsDeviceDropdown extends StatelessWidget {
|
|||||||
width: 1,
|
width: 1,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
child: Visibility(
|
|
||||||
visible: state.status != AnalyticsDevicesStatus.loading,
|
|
||||||
replacement: _buildLoadingIndicator(),
|
|
||||||
child: Visibility(
|
child: Visibility(
|
||||||
visible: state.devices.isNotEmpty,
|
visible: state.devices.isNotEmpty,
|
||||||
replacement: _buildNoDevicesFound(context),
|
replacement: _buildNoDevicesFound(context),
|
||||||
child: _buildDevicesDropdown(context, state),
|
child: _buildDevicesDropdown(context, state),
|
||||||
),
|
),
|
||||||
),
|
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildLoadingIndicator() {
|
|
||||||
return const Center(
|
|
||||||
child: SizedBox(
|
|
||||||
width: 24,
|
|
||||||
height: 24,
|
|
||||||
child: CircularProgressIndicator(strokeWidth: 3),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
static const _defaultPadding = EdgeInsetsDirectional.symmetric(
|
static const _defaultPadding = EdgeInsetsDirectional.symmetric(
|
||||||
horizontal: 20,
|
horizontal: 20,
|
||||||
vertical: 2,
|
vertical: 2,
|
||||||
|
@ -37,7 +37,7 @@ class EnergyConsumptionPerDeviceChartBox extends StatelessWidget {
|
|||||||
fit: BoxFit.scaleDown,
|
fit: BoxFit.scaleDown,
|
||||||
alignment: AlignmentDirectional.centerStart,
|
alignment: AlignmentDirectional.centerStart,
|
||||||
child: ChartTitle(
|
child: ChartTitle(
|
||||||
title: Text('Device energy consumed'),
|
title: Text('Energy Consumption per Device'),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
@ -14,17 +14,14 @@ class TotalEnergyConsumptionChart extends StatelessWidget {
|
|||||||
return Expanded(
|
return Expanded(
|
||||||
child: LineChart(
|
child: LineChart(
|
||||||
LineChartData(
|
LineChartData(
|
||||||
maxY: chartData.isEmpty
|
|
||||||
? null
|
|
||||||
: chartData.map((e) => e.value).reduce((a, b) => a > b ? a : b) + 250,
|
|
||||||
clipData: const FlClipData.vertical(),
|
clipData: const FlClipData.vertical(),
|
||||||
titlesData: EnergyManagementChartsHelper.titlesData(
|
titlesData: EnergyManagementChartsHelper.titlesData(
|
||||||
context,
|
context,
|
||||||
leftTitlesInterval: 500,
|
leftTitlesInterval: 250,
|
||||||
),
|
),
|
||||||
gridData: EnergyManagementChartsHelper.gridData().copyWith(
|
gridData: EnergyManagementChartsHelper.gridData().copyWith(
|
||||||
checkToShowHorizontalLine: (value) => true,
|
checkToShowHorizontalLine: (value) => true,
|
||||||
horizontalInterval: 500,
|
horizontalInterval: 250,
|
||||||
),
|
),
|
||||||
borderData: EnergyManagementChartsHelper.borderData(),
|
borderData: EnergyManagementChartsHelper.borderData(),
|
||||||
lineTouchData: EnergyManagementChartsHelper.lineTouchData(),
|
lineTouchData: EnergyManagementChartsHelper.lineTouchData(),
|
||||||
@ -32,6 +29,7 @@ class TotalEnergyConsumptionChart extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
duration: Duration.zero,
|
duration: Duration.zero,
|
||||||
curve: Curves.easeIn,
|
curve: Curves.easeIn,
|
||||||
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -32,7 +32,7 @@ class TotalEnergyConsumptionChartBox extends StatelessWidget {
|
|||||||
child: FittedBox(
|
child: FittedBox(
|
||||||
alignment: AlignmentDirectional.centerStart,
|
alignment: AlignmentDirectional.centerStart,
|
||||||
fit: BoxFit.scaleDown,
|
fit: BoxFit.scaleDown,
|
||||||
child: ChartTitle(title: Text('Space energy consumed')),
|
child: ChartTitle(title: Text('Total Energy Consumption')),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const Spacer(flex: 4),
|
const Spacer(flex: 4),
|
||||||
|
@ -81,7 +81,7 @@ abstract final class FetchOccupancyDataHelper {
|
|||||||
param: GetAnalyticsDevicesParam(
|
param: GetAnalyticsDevicesParam(
|
||||||
communityUuid: communityUuid,
|
communityUuid: communityUuid,
|
||||||
spaceUuid: spaceUuid,
|
spaceUuid: spaceUuid,
|
||||||
deviceTypes: ['WPS', 'CPS', 'NCPS'],
|
deviceTypes: ['WPS', 'CPS'],
|
||||||
requestType: AnalyticsDeviceRequestType.occupancy,
|
requestType: AnalyticsDeviceRequestType.occupancy,
|
||||||
),
|
),
|
||||||
onSuccess: (device) {
|
onSuccess: (device) {
|
||||||
|
@ -20,7 +20,7 @@ class AnalyticsOccupancyView extends StatelessWidget {
|
|||||||
child: Column(
|
child: Column(
|
||||||
spacing: 32,
|
spacing: 32,
|
||||||
children: [
|
children: [
|
||||||
SizedBox(height: height * 0.8, child: const OccupancyEndSideBar()),
|
SizedBox(height: height * 0.46, child: const OccupancyEndSideBar()),
|
||||||
SizedBox(height: height * 0.5, child: const OccupancyChartBox()),
|
SizedBox(height: height * 0.5, child: const OccupancyChartBox()),
|
||||||
SizedBox(height: height * 0.5, child: const OccupancyHeatMapBox()),
|
SizedBox(height: height * 0.5, child: const OccupancyHeatMapBox()),
|
||||||
],
|
],
|
||||||
|
@ -39,7 +39,7 @@ class HeatMapTooltip extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
const Divider(height: 2, thickness: 1),
|
const Divider(height: 2, thickness: 1),
|
||||||
Text(
|
Text(
|
||||||
'Occupancy detected: $value',
|
'$value Occupants',
|
||||||
style: context.textTheme.bodySmall?.copyWith(
|
style: context.textTheme.bodySmall?.copyWith(
|
||||||
fontSize: 10,
|
fontSize: 10,
|
||||||
fontWeight: FontWeight.w500,
|
fontWeight: FontWeight.w500,
|
||||||
|
@ -52,7 +52,7 @@ class _InteractiveHeatMapState extends State<InteractiveHeatMap> {
|
|||||||
color: Colors.transparent,
|
color: Colors.transparent,
|
||||||
child: Transform.translate(
|
child: Transform.translate(
|
||||||
offset: Offset(-(widget.cellSize * 2.5), -50),
|
offset: Offset(-(widget.cellSize * 2.5), -50),
|
||||||
child: HeatMapTooltip(date: item.date.toUtc(), value: item.value),
|
child: HeatMapTooltip(date: item.date, value: item.value),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
@ -2,7 +2,6 @@ import 'package:fl_chart/fl_chart.dart';
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:syncrow_web/pages/analytics/models/occupacy.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/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/color_manager.dart';
|
||||||
import 'package:syncrow_web/utils/extension/build_context_x.dart';
|
import 'package:syncrow_web/utils/extension/build_context_x.dart';
|
||||||
|
|
||||||
@ -89,8 +88,8 @@ class OccupancyChart extends StatelessWidget {
|
|||||||
}) {
|
}) {
|
||||||
final data = chartData;
|
final data = chartData;
|
||||||
|
|
||||||
final occupancyValue = double.parse(data[group.x].occupancy);
|
final occupancyValue = double.parse(data[group.x.toInt()].occupancy);
|
||||||
final percentage = '${occupancyValue.toStringAsFixed(0)}%';
|
final percentage = '${(occupancyValue).toStringAsFixed(0)}%';
|
||||||
|
|
||||||
return BarTooltipItem(
|
return BarTooltipItem(
|
||||||
percentage,
|
percentage,
|
||||||
@ -117,7 +116,7 @@ class OccupancyChart extends StatelessWidget {
|
|||||||
alignment: AlignmentDirectional.centerStart,
|
alignment: AlignmentDirectional.centerStart,
|
||||||
fit: BoxFit.scaleDown,
|
fit: BoxFit.scaleDown,
|
||||||
child: Text(
|
child: Text(
|
||||||
'${value.toStringAsFixed(0)}%',
|
'${(value).toStringAsFixed(0)}%',
|
||||||
style: context.textTheme.bodySmall?.copyWith(
|
style: context.textTheme.bodySmall?.copyWith(
|
||||||
fontSize: 12,
|
fontSize: 12,
|
||||||
color: ColorsManager.greyColor,
|
color: ColorsManager.greyColor,
|
||||||
@ -129,7 +128,6 @@ class OccupancyChart extends StatelessWidget {
|
|||||||
);
|
);
|
||||||
|
|
||||||
final bottomTitles = AxisTitles(
|
final bottomTitles = AxisTitles(
|
||||||
axisNameWidget: const ChartsXAxisTitle(),
|
|
||||||
sideTitles: SideTitles(
|
sideTitles: SideTitles(
|
||||||
showTitles: true,
|
showTitles: true,
|
||||||
getTitlesWidget: (value, _) => FittedBox(
|
getTitlesWidget: (value, _) => FittedBox(
|
||||||
|
@ -23,9 +23,10 @@ class OccupancyEndSideBar extends StatelessWidget {
|
|||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
const AnalyticsSidebarHeader(title: 'Presence Sensor'),
|
const AnalyticsSidebarHeader(title: 'Presnce Sensor'),
|
||||||
Expanded(
|
Expanded(
|
||||||
child: SizedBox(
|
child: SizedBox(
|
||||||
|
// height: MediaQuery.sizeOf(context).height * 0.2,
|
||||||
child: PowerClampEnergyStatusWidget(
|
child: PowerClampEnergyStatusWidget(
|
||||||
status: [
|
status: [
|
||||||
PowerClampEnergyStatus(
|
PowerClampEnergyStatus(
|
||||||
|
@ -20,7 +20,7 @@ class OccupancyHeatMap extends StatelessWidget {
|
|||||||
: 0;
|
: 0;
|
||||||
|
|
||||||
DateTime _getStartingDate() {
|
DateTime _getStartingDate() {
|
||||||
final jan1 = DateTime.utc(DateTime.now().year, 1, 1);
|
final jan1 = DateTime(DateTime.now().year, 1, 1);
|
||||||
final startOfWeek = jan1.subtract(Duration(days: jan1.weekday - 1));
|
final startOfWeek = jan1.subtract(Duration(days: jan1.weekday - 1));
|
||||||
return startOfWeek;
|
return startOfWeek;
|
||||||
}
|
}
|
||||||
|
@ -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,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
@ -36,8 +36,7 @@ class AuthBloc extends Bloc<AuthEvent, AuthState> {
|
|||||||
|
|
||||||
////////////////////////////// forget password //////////////////////////////////
|
////////////////////////////// forget password //////////////////////////////////
|
||||||
final TextEditingController forgetEmailController = TextEditingController();
|
final TextEditingController forgetEmailController = TextEditingController();
|
||||||
final TextEditingController forgetPasswordController =
|
final TextEditingController forgetPasswordController = TextEditingController();
|
||||||
TextEditingController();
|
|
||||||
final TextEditingController forgetOtp = TextEditingController();
|
final TextEditingController forgetOtp = TextEditingController();
|
||||||
final forgetFormKey = GlobalKey<FormState>();
|
final forgetFormKey = GlobalKey<FormState>();
|
||||||
final forgetEmailKey = GlobalKey<FormState>();
|
final forgetEmailKey = GlobalKey<FormState>();
|
||||||
@ -54,8 +53,7 @@ class AuthBloc extends Bloc<AuthEvent, AuthState> {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
_remainingTime = 1;
|
_remainingTime = 1;
|
||||||
add(UpdateTimerEvent(
|
add(UpdateTimerEvent(remainingTime: _remainingTime, isButtonEnabled: false));
|
||||||
remainingTime: _remainingTime, isButtonEnabled: false));
|
|
||||||
try {
|
try {
|
||||||
forgetEmailValidate = '';
|
forgetEmailValidate = '';
|
||||||
_remainingTime = (await AuthenticationAPI.sendOtp(
|
_remainingTime = (await AuthenticationAPI.sendOtp(
|
||||||
@ -92,8 +90,7 @@ class AuthBloc extends Bloc<AuthEvent, AuthState> {
|
|||||||
_timer?.cancel();
|
_timer?.cancel();
|
||||||
add(const UpdateTimerEvent(remainingTime: 0, isButtonEnabled: true));
|
add(const UpdateTimerEvent(remainingTime: 0, isButtonEnabled: true));
|
||||||
} else {
|
} else {
|
||||||
add(UpdateTimerEvent(
|
add(UpdateTimerEvent(remainingTime: _remainingTime, isButtonEnabled: false));
|
||||||
remainingTime: _remainingTime, isButtonEnabled: false));
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -103,7 +100,7 @@ class AuthBloc extends Bloc<AuthEvent, AuthState> {
|
|||||||
emit(const TimerState(isButtonEnabled: true, remainingTime: 0));
|
emit(const TimerState(isButtonEnabled: true, remainingTime: 0));
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> changePassword(
|
Future<void> changePassword(
|
||||||
ChangePasswordEvent event, Emitter<AuthState> emit) async {
|
ChangePasswordEvent event, Emitter<AuthState> emit) async {
|
||||||
emit(LoadingForgetState());
|
emit(LoadingForgetState());
|
||||||
try {
|
try {
|
||||||
@ -125,6 +122,7 @@ class AuthBloc extends Bloc<AuthEvent, AuthState> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
String? validateCode(String? value) {
|
String? validateCode(String? value) {
|
||||||
if (value == null || value.isEmpty) {
|
if (value == null || value.isEmpty) {
|
||||||
return 'Code is required';
|
return 'Code is required';
|
||||||
@ -133,9 +131,7 @@ class AuthBloc extends Bloc<AuthEvent, AuthState> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void _onUpdateTimer(UpdateTimerEvent event, Emitter<AuthState> emit) {
|
void _onUpdateTimer(UpdateTimerEvent event, Emitter<AuthState> emit) {
|
||||||
emit(TimerState(
|
emit(TimerState(isButtonEnabled: event.isButtonEnabled, remainingTime: event.remainingTime));
|
||||||
isButtonEnabled: event.isButtonEnabled,
|
|
||||||
remainingTime: event.remainingTime));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
///////////////////////////////////// login /////////////////////////////////////
|
///////////////////////////////////// login /////////////////////////////////////
|
||||||
@ -155,6 +151,7 @@ class AuthBloc extends Bloc<AuthEvent, AuthState> {
|
|||||||
static UserModel? user;
|
static UserModel? user;
|
||||||
bool showValidationMessage = false;
|
bool showValidationMessage = false;
|
||||||
|
|
||||||
|
|
||||||
void _login(LoginButtonPressed event, Emitter<AuthState> emit) async {
|
void _login(LoginButtonPressed event, Emitter<AuthState> emit) async {
|
||||||
emit(AuthLoading());
|
emit(AuthLoading());
|
||||||
if (isChecked) {
|
if (isChecked) {
|
||||||
@ -173,11 +170,11 @@ class AuthBloc extends Bloc<AuthEvent, AuthState> {
|
|||||||
);
|
);
|
||||||
} on APIException catch (e) {
|
} on APIException catch (e) {
|
||||||
validate = e.message;
|
validate = e.message;
|
||||||
emit(LoginFailure(error: validate));
|
emit(LoginInitial());
|
||||||
return;
|
return;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
validate = 'Something went wrong';
|
validate = 'Something went wrong';
|
||||||
emit(LoginFailure(error: validate));
|
emit(LoginInitial());
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -200,6 +197,7 @@ class AuthBloc extends Bloc<AuthEvent, AuthState> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
checkBoxToggle(
|
checkBoxToggle(
|
||||||
CheckBoxEvent event,
|
CheckBoxEvent event,
|
||||||
Emitter<AuthState> emit,
|
Emitter<AuthState> emit,
|
||||||
@ -341,14 +339,12 @@ class AuthBloc extends Bloc<AuthEvent, AuthState> {
|
|||||||
static Future<String> getTokenAndValidate() async {
|
static Future<String> getTokenAndValidate() async {
|
||||||
try {
|
try {
|
||||||
const storage = FlutterSecureStorage();
|
const storage = FlutterSecureStorage();
|
||||||
final firstLaunch = await SharedPreferencesHelper.readBoolFromSP(
|
final firstLaunch =
|
||||||
StringsManager.firstLaunch) ??
|
await SharedPreferencesHelper.readBoolFromSP(StringsManager.firstLaunch) ?? true;
|
||||||
true;
|
|
||||||
if (firstLaunch) {
|
if (firstLaunch) {
|
||||||
storage.deleteAll();
|
storage.deleteAll();
|
||||||
}
|
}
|
||||||
await SharedPreferencesHelper.saveBoolToSP(
|
await SharedPreferencesHelper.saveBoolToSP(StringsManager.firstLaunch, false);
|
||||||
StringsManager.firstLaunch, false);
|
|
||||||
final value = await storage.read(key: Token.loginAccessTokenKey) ?? '';
|
final value = await storage.read(key: Token.loginAccessTokenKey) ?? '';
|
||||||
if (value.isEmpty) {
|
if (value.isEmpty) {
|
||||||
return 'Token not found';
|
return 'Token not found';
|
||||||
@ -401,9 +397,7 @@ class AuthBloc extends Bloc<AuthEvent, AuthState> {
|
|||||||
final String formattedTime = [
|
final String formattedTime = [
|
||||||
if (days > 0) '${days}d', // Append 'd' for days
|
if (days > 0) '${days}d', // Append 'd' for days
|
||||||
if (days > 0 || hours > 0)
|
if (days > 0 || hours > 0)
|
||||||
hours
|
hours.toString().padLeft(2, '0'), // Show hours if there are days or hours
|
||||||
.toString()
|
|
||||||
.padLeft(2, '0'), // Show hours if there are days or hours
|
|
||||||
minutes.toString().padLeft(2, '0'),
|
minutes.toString().padLeft(2, '0'),
|
||||||
seconds.toString().padLeft(2, '0'),
|
seconds.toString().padLeft(2, '0'),
|
||||||
].join(':');
|
].join(':');
|
||||||
|
@ -45,8 +45,7 @@ class AcBloc extends Bloc<AcsEvent, AcsState> {
|
|||||||
) async {
|
) async {
|
||||||
emit(AcsLoadingState());
|
emit(AcsLoadingState());
|
||||||
try {
|
try {
|
||||||
final status =
|
final status = await DevicesManagementApi().getDeviceStatus(event.deviceId);
|
||||||
await DevicesManagementApi().getDeviceStatus(event.deviceId);
|
|
||||||
deviceStatus = AcStatusModel.fromJson(event.deviceId, status.status);
|
deviceStatus = AcStatusModel.fromJson(event.deviceId, status.status);
|
||||||
if (deviceStatus.countdown1 != 0) {
|
if (deviceStatus.countdown1 != 0) {
|
||||||
final totalMinutes = deviceStatus.countdown1 * 6;
|
final totalMinutes = deviceStatus.countdown1 * 6;
|
||||||
@ -69,13 +68,12 @@ class AcBloc extends Bloc<AcsEvent, AcsState> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
StreamSubscription<DatabaseEvent>? _deviceStatusSubscription;
|
void _listenToChanges(deviceId) {
|
||||||
|
|
||||||
void _listenToChanges(String deviceId) {
|
|
||||||
try {
|
try {
|
||||||
final ref = FirebaseDatabase.instance.ref('device-status/$deviceId');
|
final ref = FirebaseDatabase.instance.ref('device-status/$deviceId');
|
||||||
_deviceStatusSubscription =
|
final stream = ref.onValue;
|
||||||
ref.onValue.listen((DatabaseEvent event) async {
|
|
||||||
|
stream.listen((DatabaseEvent event) async {
|
||||||
if (event.snapshot.value == null) return;
|
if (event.snapshot.value == null) return;
|
||||||
|
|
||||||
Map<dynamic, dynamic> usersMap =
|
Map<dynamic, dynamic> usersMap =
|
||||||
@ -84,14 +82,10 @@ class AcBloc extends Bloc<AcsEvent, AcsState> {
|
|||||||
List<Status> statusList = [];
|
List<Status> statusList = [];
|
||||||
|
|
||||||
usersMap['status'].forEach((element) {
|
usersMap['status'].forEach((element) {
|
||||||
statusList
|
statusList.add(Status(code: element['code'], value: element['value']));
|
||||||
.add(Status(code: element['code'], value: element['value']));
|
|
||||||
});
|
});
|
||||||
|
|
||||||
deviceStatus =
|
deviceStatus = AcStatusModel.fromJson(usersMap['productUuid'], statusList);
|
||||||
AcStatusModel.fromJson(usersMap['productUuid'], statusList);
|
|
||||||
print('Device status updated: ${deviceStatus.acSwitch}');
|
|
||||||
|
|
||||||
if (!isClosed) {
|
if (!isClosed) {
|
||||||
add(AcStatusUpdated(deviceStatus));
|
add(AcStatusUpdated(deviceStatus));
|
||||||
}
|
}
|
||||||
@ -112,14 +106,15 @@ class AcBloc extends Bloc<AcsEvent, AcsState> {
|
|||||||
Emitter<AcsState> emit,
|
Emitter<AcsState> emit,
|
||||||
) async {
|
) async {
|
||||||
emit(AcsLoadingState());
|
emit(AcsLoadingState());
|
||||||
|
_updateDeviceFunctionFromCode(event.code, event.value);
|
||||||
|
emit(ACStatusLoaded(status: deviceStatus));
|
||||||
|
|
||||||
try {
|
try {
|
||||||
final success = await controlDeviceService.controlDevice(
|
final success = await controlDeviceService.controlDevice(
|
||||||
deviceUuid: event.deviceId,
|
deviceUuid: event.deviceId,
|
||||||
status: Status(code: event.code, value: event.value),
|
status: Status(code: event.code, value: event.value),
|
||||||
);
|
);
|
||||||
_updateDeviceFunctionFromCode(event.code, event.value);
|
|
||||||
emit(ACStatusLoaded(status: deviceStatus));
|
|
||||||
if (!success) {
|
if (!success) {
|
||||||
emit(const AcsFailedState(error: 'Failed to control device'));
|
emit(const AcsFailedState(error: 'Failed to control device'));
|
||||||
}
|
}
|
||||||
@ -134,10 +129,8 @@ class AcBloc extends Bloc<AcsEvent, AcsState> {
|
|||||||
) async {
|
) async {
|
||||||
emit(AcsLoadingState());
|
emit(AcsLoadingState());
|
||||||
try {
|
try {
|
||||||
final status =
|
final status = await DevicesManagementApi().getBatchStatus(event.devicesIds);
|
||||||
await DevicesManagementApi().getBatchStatus(event.devicesIds);
|
deviceStatus = AcStatusModel.fromJson(event.devicesIds.first, status.status);
|
||||||
deviceStatus =
|
|
||||||
AcStatusModel.fromJson(event.devicesIds.first, status.status);
|
|
||||||
emit(ACStatusLoaded(status: deviceStatus));
|
emit(ACStatusLoaded(status: deviceStatus));
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
emit(AcsFailedState(error: e.toString()));
|
emit(AcsFailedState(error: e.toString()));
|
||||||
@ -300,18 +293,14 @@ class AcBloc extends Bloc<AcsEvent, AcsState> {
|
|||||||
totalSeconds--;
|
totalSeconds--;
|
||||||
scheduledHours = totalSeconds ~/ 3600;
|
scheduledHours = totalSeconds ~/ 3600;
|
||||||
scheduledMinutes = (totalSeconds % 3600) ~/ 60;
|
scheduledMinutes = (totalSeconds % 3600) ~/ 60;
|
||||||
if (!isClosed) {
|
|
||||||
add(UpdateTimerEvent());
|
add(UpdateTimerEvent());
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
_countdownTimer?.cancel();
|
_countdownTimer?.cancel();
|
||||||
timerActive = false;
|
timerActive = false;
|
||||||
scheduledHours = 0;
|
scheduledHours = 0;
|
||||||
scheduledMinutes = 0;
|
scheduledMinutes = 0;
|
||||||
if (!isClosed) {
|
|
||||||
add(TimerCompletedEvent());
|
add(TimerCompletedEvent());
|
||||||
}
|
}
|
||||||
}
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -337,11 +326,9 @@ class AcBloc extends Bloc<AcsEvent, AcsState> {
|
|||||||
_startCountdownTimer(
|
_startCountdownTimer(
|
||||||
emit,
|
emit,
|
||||||
);
|
);
|
||||||
if (!isClosed) {
|
|
||||||
add(UpdateTimerEvent());
|
add(UpdateTimerEvent());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
void _updateDeviceFunctionFromCode(String code, dynamic value) {
|
void _updateDeviceFunctionFromCode(String code, dynamic value) {
|
||||||
switch (code) {
|
switch (code) {
|
||||||
@ -383,8 +370,6 @@ class AcBloc extends Bloc<AcsEvent, AcsState> {
|
|||||||
@override
|
@override
|
||||||
Future<void> close() {
|
Future<void> close() {
|
||||||
add(OnClose());
|
add(OnClose());
|
||||||
_countdownTimer?.cancel();
|
|
||||||
_deviceStatusSubscription?.cancel();
|
|
||||||
return super.close();
|
return super.close();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -111,6 +111,8 @@ class DeviceManagementBody extends StatelessWidget with HelperResponsiveLayout {
|
|||||||
onPressed: isControlButtonEnabled
|
onPressed: isControlButtonEnabled
|
||||||
? () {
|
? () {
|
||||||
if (isAnyDeviceOffline) {
|
if (isAnyDeviceOffline) {
|
||||||
|
ScaffoldMessenger.of(context)
|
||||||
|
.clearSnackBars();
|
||||||
ScaffoldMessenger.of(context)
|
ScaffoldMessenger.of(context)
|
||||||
.showSnackBar(
|
.showSnackBar(
|
||||||
const SnackBar(
|
const SnackBar(
|
||||||
|
@ -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';
|
import 'package:syncrow_web/utils/helpers/responsice_layout_helper/responsive_layout_helper.dart';
|
||||||
|
|
||||||
//Smart Power Clamp
|
//Smart Power Clamp
|
||||||
class SmartPowerDeviceControl extends StatelessWidget
|
class SmartPowerDeviceControl extends StatelessWidget with HelperResponsiveLayout {
|
||||||
with HelperResponsiveLayout {
|
|
||||||
final String deviceId;
|
final String deviceId;
|
||||||
|
|
||||||
const SmartPowerDeviceControl({super.key, required this.deviceId});
|
const SmartPowerDeviceControl({super.key, required this.deviceId});
|
||||||
@ -146,11 +145,8 @@ class SmartPowerDeviceControl extends StatelessWidget
|
|||||||
children: [
|
children: [
|
||||||
IconButton(
|
IconButton(
|
||||||
icon: const Icon(Icons.arrow_left),
|
icon: const Icon(Icons.arrow_left),
|
||||||
onPressed: blocProvider.currentPage <= 0
|
onPressed: () {
|
||||||
? null
|
blocProvider.add(SmartPowerArrowPressedEvent(-1));
|
||||||
: () {
|
|
||||||
blocProvider
|
|
||||||
.add(SmartPowerArrowPressedEvent(-1));
|
|
||||||
pageController.previousPage(
|
pageController.previousPage(
|
||||||
duration: const Duration(milliseconds: 300),
|
duration: const Duration(milliseconds: 300),
|
||||||
curve: Curves.easeInOut,
|
curve: Curves.easeInOut,
|
||||||
@ -169,11 +165,8 @@ class SmartPowerDeviceControl extends StatelessWidget
|
|||||||
),
|
),
|
||||||
IconButton(
|
IconButton(
|
||||||
icon: const Icon(Icons.arrow_right),
|
icon: const Icon(Icons.arrow_right),
|
||||||
onPressed: blocProvider.currentPage >= 3
|
onPressed: () {
|
||||||
? null
|
blocProvider.add(SmartPowerArrowPressedEvent(1));
|
||||||
: () {
|
|
||||||
blocProvider
|
|
||||||
.add(SmartPowerArrowPressedEvent(1));
|
|
||||||
pageController.nextPage(
|
pageController.nextPage(
|
||||||
duration: const Duration(milliseconds: 300),
|
duration: const Duration(milliseconds: 300),
|
||||||
curve: Curves.easeInOut,
|
curve: Curves.easeInOut,
|
||||||
@ -202,8 +195,8 @@ class SmartPowerDeviceControl extends StatelessWidget
|
|||||||
blocProvider.add(SelectDateEvent(context: context));
|
blocProvider.add(SelectDateEvent(context: context));
|
||||||
blocProvider.add(FilterRecordsByDateEvent(
|
blocProvider.add(FilterRecordsByDateEvent(
|
||||||
selectedDate: blocProvider.dateTime!,
|
selectedDate: blocProvider.dateTime!,
|
||||||
viewType: blocProvider
|
viewType:
|
||||||
.views[blocProvider.currentIndex]));
|
blocProvider.views[blocProvider.currentIndex]));
|
||||||
},
|
},
|
||||||
widget: blocProvider.dateSwitcher(),
|
widget: blocProvider.dateSwitcher(),
|
||||||
chartData: blocProvider.energyDataList.isNotEmpty
|
chartData: blocProvider.energyDataList.isNotEmpty
|
||||||
|
@ -83,12 +83,6 @@ class ScheduleBloc extends Bloc<ScheduleEvent, ScheduleState> {
|
|||||||
emit(currentState.copyWith(
|
emit(currentState.copyWith(
|
||||||
scheduleMode: event.scheduleMode,
|
scheduleMode: event.scheduleMode,
|
||||||
countdownRemaining: Duration.zero,
|
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) {
|
if (state is ScheduleLoaded) {
|
||||||
final currentState = state as ScheduleLoaded;
|
final currentState = state as ScheduleLoaded;
|
||||||
emit(currentState.copyWith(
|
emit(currentState.copyWith(
|
||||||
countdownSeconds: event.seconds,
|
|
||||||
countdownHours: event.hours,
|
countdownHours: event.hours,
|
||||||
countdownMinutes: event.minutes,
|
countdownMinutes: event.minutes,
|
||||||
inchingHours: 0,
|
inchingHours: 0,
|
||||||
@ -120,7 +113,6 @@ class ScheduleBloc extends Bloc<ScheduleEvent, ScheduleState> {
|
|||||||
inchingHours: event.hours,
|
inchingHours: event.hours,
|
||||||
inchingMinutes: event.minutes,
|
inchingMinutes: event.minutes,
|
||||||
countdownRemaining: Duration.zero,
|
countdownRemaining: Duration.zero,
|
||||||
inchingSeconds: 0, // Add this
|
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -432,7 +424,6 @@ class ScheduleBloc extends Bloc<ScheduleEvent, ScheduleState> {
|
|||||||
countdownMinutes: countdownDuration.inMinutes % 60,
|
countdownMinutes: countdownDuration.inMinutes % 60,
|
||||||
countdownRemaining: countdownDuration,
|
countdownRemaining: countdownDuration,
|
||||||
isCountdownActive: true,
|
isCountdownActive: true,
|
||||||
countdownSeconds: countdownDuration.inSeconds,
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -446,7 +437,6 @@ class ScheduleBloc extends Bloc<ScheduleEvent, ScheduleState> {
|
|||||||
countdownMinutes: 0,
|
countdownMinutes: 0,
|
||||||
countdownRemaining: Duration.zero,
|
countdownRemaining: Duration.zero,
|
||||||
isCountdownActive: false,
|
isCountdownActive: false,
|
||||||
countdownSeconds: 0,
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -458,7 +448,6 @@ class ScheduleBloc extends Bloc<ScheduleEvent, ScheduleState> {
|
|||||||
inchingMinutes: inchingDuration.inMinutes % 60,
|
inchingMinutes: inchingDuration.inMinutes % 60,
|
||||||
isInchingActive: true,
|
isInchingActive: true,
|
||||||
countdownRemaining: inchingDuration,
|
countdownRemaining: inchingDuration,
|
||||||
countdownSeconds: inchingDuration.inSeconds,
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -585,7 +574,8 @@ class ScheduleBloc extends Bloc<ScheduleEvent, ScheduleState> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
String extractTime(String isoDateTime) {
|
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) {
|
int? getTimeStampWithoutSeconds(DateTime? dateTime) {
|
||||||
|
@ -146,16 +146,14 @@ class UpdateScheduleModeEvent extends ScheduleEvent {
|
|||||||
class UpdateCountdownTimeEvent extends ScheduleEvent {
|
class UpdateCountdownTimeEvent extends ScheduleEvent {
|
||||||
final int hours;
|
final int hours;
|
||||||
final int minutes;
|
final int minutes;
|
||||||
final int seconds;
|
|
||||||
|
|
||||||
const UpdateCountdownTimeEvent({
|
const UpdateCountdownTimeEvent({
|
||||||
required this.hours,
|
required this.hours,
|
||||||
required this.minutes,
|
required this.minutes,
|
||||||
required this.seconds,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
List<Object> get props => [hours, minutes, seconds];
|
List<Object> get props => [hours, minutes];
|
||||||
}
|
}
|
||||||
|
|
||||||
class UpdateInchingTimeEvent extends ScheduleEvent {
|
class UpdateInchingTimeEvent extends ScheduleEvent {
|
||||||
|
@ -26,15 +26,11 @@ class ScheduleLoaded extends ScheduleState {
|
|||||||
final bool isCountdownActive;
|
final bool isCountdownActive;
|
||||||
final int inchingHours;
|
final int inchingHours;
|
||||||
final int inchingMinutes;
|
final int inchingMinutes;
|
||||||
final int inchingSeconds;
|
|
||||||
final bool isInchingActive;
|
final bool isInchingActive;
|
||||||
final ScheduleModes scheduleMode;
|
final ScheduleModes scheduleMode;
|
||||||
final Duration? countdownRemaining;
|
final Duration? countdownRemaining;
|
||||||
final int? countdownSeconds;
|
|
||||||
|
|
||||||
const ScheduleLoaded({
|
const ScheduleLoaded({
|
||||||
this.countdownSeconds = 0,
|
|
||||||
this.inchingSeconds = 0,
|
|
||||||
required this.schedules,
|
required this.schedules,
|
||||||
this.selectedTime,
|
this.selectedTime,
|
||||||
required this.selectedDays,
|
required this.selectedDays,
|
||||||
@ -65,9 +61,6 @@ class ScheduleLoaded extends ScheduleState {
|
|||||||
bool? isInchingActive,
|
bool? isInchingActive,
|
||||||
ScheduleModes? scheduleMode,
|
ScheduleModes? scheduleMode,
|
||||||
Duration? countdownRemaining,
|
Duration? countdownRemaining,
|
||||||
String? deviceId,
|
|
||||||
int? countdownSeconds,
|
|
||||||
int? inchingSeconds,
|
|
||||||
}) {
|
}) {
|
||||||
return ScheduleLoaded(
|
return ScheduleLoaded(
|
||||||
schedules: schedules ?? this.schedules,
|
schedules: schedules ?? this.schedules,
|
||||||
@ -75,7 +68,7 @@ class ScheduleLoaded extends ScheduleState {
|
|||||||
selectedDays: selectedDays ?? this.selectedDays,
|
selectedDays: selectedDays ?? this.selectedDays,
|
||||||
functionOn: functionOn ?? this.functionOn,
|
functionOn: functionOn ?? this.functionOn,
|
||||||
isEditing: isEditing ?? this.isEditing,
|
isEditing: isEditing ?? this.isEditing,
|
||||||
deviceId: deviceId ?? this.deviceId,
|
deviceId: deviceId,
|
||||||
countdownHours: countdownHours ?? this.countdownHours,
|
countdownHours: countdownHours ?? this.countdownHours,
|
||||||
countdownMinutes: countdownMinutes ?? this.countdownMinutes,
|
countdownMinutes: countdownMinutes ?? this.countdownMinutes,
|
||||||
isCountdownActive: isCountdownActive ?? this.isCountdownActive,
|
isCountdownActive: isCountdownActive ?? this.isCountdownActive,
|
||||||
@ -84,8 +77,6 @@ class ScheduleLoaded extends ScheduleState {
|
|||||||
isInchingActive: isInchingActive ?? this.isInchingActive,
|
isInchingActive: isInchingActive ?? this.isInchingActive,
|
||||||
scheduleMode: scheduleMode ?? this.scheduleMode,
|
scheduleMode: scheduleMode ?? this.scheduleMode,
|
||||||
countdownRemaining: countdownRemaining ?? this.countdownRemaining,
|
countdownRemaining: countdownRemaining ?? this.countdownRemaining,
|
||||||
countdownSeconds: countdownSeconds ?? this.countdownSeconds,
|
|
||||||
inchingSeconds: inchingSeconds ?? this.inchingSeconds,
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -105,8 +96,6 @@ class ScheduleLoaded extends ScheduleState {
|
|||||||
isInchingActive,
|
isInchingActive,
|
||||||
scheduleMode,
|
scheduleMode,
|
||||||
countdownRemaining,
|
countdownRemaining,
|
||||||
countdownSeconds,
|
|
||||||
inchingSeconds,
|
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -6,8 +6,7 @@ import 'package:syncrow_web/utils/color_manager.dart';
|
|||||||
import 'package:syncrow_web/utils/extension/build_context_x.dart';
|
import 'package:syncrow_web/utils/extension/build_context_x.dart';
|
||||||
|
|
||||||
class CountdownInchingView extends StatefulWidget {
|
class CountdownInchingView extends StatefulWidget {
|
||||||
final String deviceId;
|
const CountdownInchingView({super.key});
|
||||||
const CountdownInchingView({super.key, required this.deviceId});
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<CountdownInchingView> createState() => _CountdownInchingViewState();
|
State<CountdownInchingView> createState() => _CountdownInchingViewState();
|
||||||
@ -16,30 +15,25 @@ class CountdownInchingView extends StatefulWidget {
|
|||||||
class _CountdownInchingViewState extends State<CountdownInchingView> {
|
class _CountdownInchingViewState extends State<CountdownInchingView> {
|
||||||
late FixedExtentScrollController _hoursController;
|
late FixedExtentScrollController _hoursController;
|
||||||
late FixedExtentScrollController _minutesController;
|
late FixedExtentScrollController _minutesController;
|
||||||
late FixedExtentScrollController _secondsController;
|
|
||||||
|
|
||||||
int _lastHours = -1;
|
int _lastHours = -1;
|
||||||
int _lastMinutes = -1;
|
int _lastMinutes = -1;
|
||||||
int _lastSeconds = -1;
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
_hoursController = FixedExtentScrollController();
|
_hoursController = FixedExtentScrollController();
|
||||||
_minutesController = FixedExtentScrollController();
|
_minutesController = FixedExtentScrollController();
|
||||||
_secondsController = FixedExtentScrollController();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void dispose() {
|
void dispose() {
|
||||||
_hoursController.dispose();
|
_hoursController.dispose();
|
||||||
_minutesController.dispose();
|
_minutesController.dispose();
|
||||||
_secondsController.dispose();
|
|
||||||
super.dispose();
|
super.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
void _updateControllers(
|
void _updateControllers(int displayHours, int displayMinutes) {
|
||||||
int displayHours, int displayMinutes, int displaySeconds) {
|
|
||||||
if (_lastHours != displayHours) {
|
if (_lastHours != displayHours) {
|
||||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||||
if (_hoursController.hasClients) {
|
if (_hoursController.hasClients) {
|
||||||
@ -56,15 +50,6 @@ class _CountdownInchingViewState extends State<CountdownInchingView> {
|
|||||||
});
|
});
|
||||||
_lastMinutes = displayMinutes;
|
_lastMinutes = displayMinutes;
|
||||||
}
|
}
|
||||||
// Update seconds controller
|
|
||||||
if (_lastSeconds != displaySeconds) {
|
|
||||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
|
||||||
if (_secondsController.hasClients) {
|
|
||||||
_secondsController.jumpToItem(displaySeconds);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
_lastSeconds = displaySeconds;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -72,6 +57,7 @@ class _CountdownInchingViewState extends State<CountdownInchingView> {
|
|||||||
return BlocBuilder<ScheduleBloc, ScheduleState>(
|
return BlocBuilder<ScheduleBloc, ScheduleState>(
|
||||||
builder: (context, state) {
|
builder: (context, state) {
|
||||||
if (state is! ScheduleLoaded) return const SizedBox.shrink();
|
if (state is! ScheduleLoaded) return const SizedBox.shrink();
|
||||||
|
|
||||||
final isCountDown = state.scheduleMode == ScheduleModes.countdown;
|
final isCountDown = state.scheduleMode == ScheduleModes.countdown;
|
||||||
final isActive =
|
final isActive =
|
||||||
isCountDown ? state.isCountdownActive : state.isInchingActive;
|
isCountDown ? state.isCountdownActive : state.isInchingActive;
|
||||||
@ -81,21 +67,8 @@ class _CountdownInchingViewState extends State<CountdownInchingView> {
|
|||||||
final displayMinutes = isActive && state.countdownRemaining != null
|
final displayMinutes = isActive && state.countdownRemaining != null
|
||||||
? state.countdownRemaining!.inMinutes.remainder(60)
|
? state.countdownRemaining!.inMinutes.remainder(60)
|
||||||
: (isCountDown ? state.countdownMinutes : state.inchingMinutes);
|
: (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(
|
return Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
@ -127,10 +100,7 @@ class _CountdownInchingViewState extends State<CountdownInchingView> {
|
|||||||
(value) {
|
(value) {
|
||||||
if (!isActive) {
|
if (!isActive) {
|
||||||
context.read<ScheduleBloc>().add(UpdateCountdownTimeEvent(
|
context.read<ScheduleBloc>().add(UpdateCountdownTimeEvent(
|
||||||
hours: value,
|
hours: value, minutes: displayMinutes));
|
||||||
minutes: displayMinutes,
|
|
||||||
seconds: displaySeconds,
|
|
||||||
));
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
isActive: isActive,
|
isActive: isActive,
|
||||||
@ -145,31 +115,7 @@ class _CountdownInchingViewState extends State<CountdownInchingView> {
|
|||||||
(value) {
|
(value) {
|
||||||
if (!isActive) {
|
if (!isActive) {
|
||||||
context.read<ScheduleBloc>().add(UpdateCountdownTimeEvent(
|
context.read<ScheduleBloc>().add(UpdateCountdownTimeEvent(
|
||||||
hours: displayHours,
|
hours: displayHours, minutes: value));
|
||||||
minutes: value,
|
|
||||||
seconds: displaySeconds,
|
|
||||||
));
|
|
||||||
}
|
|
||||||
},
|
|
||||||
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,
|
isActive: isActive,
|
||||||
|
@ -74,9 +74,7 @@ class BuildScheduleView extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
if (state.scheduleMode == ScheduleModes.countdown ||
|
if (state.scheduleMode == ScheduleModes.countdown ||
|
||||||
state.scheduleMode == ScheduleModes.inching)
|
state.scheduleMode == ScheduleModes.inching)
|
||||||
CountdownInchingView(
|
const CountdownInchingView(),
|
||||||
deviceId: deviceUuid,
|
|
||||||
),
|
|
||||||
const SizedBox(height: 20),
|
const SizedBox(height: 20),
|
||||||
if (state.scheduleMode == ScheduleModes.countdown)
|
if (state.scheduleMode == ScheduleModes.countdown)
|
||||||
CountdownModeButtons(
|
CountdownModeButtons(
|
||||||
|
@ -79,7 +79,6 @@ class DeviceControlDialog extends StatelessWidget with RouteControlsBasedCode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildDeviceInfoSection() {
|
Widget _buildDeviceInfoSection() {
|
||||||
final isOnlineDevice = device.online != null && device.online!;
|
|
||||||
return Padding(
|
return Padding(
|
||||||
padding: const EdgeInsets.symmetric(vertical: 25, horizontal: 50),
|
padding: const EdgeInsets.symmetric(vertical: 25, horizontal: 50),
|
||||||
child: Table(
|
child: Table(
|
||||||
@ -108,7 +107,7 @@ class DeviceControlDialog extends StatelessWidget with RouteControlsBasedCode {
|
|||||||
'Installation Date and Time:',
|
'Installation Date and Time:',
|
||||||
formatDateTime(
|
formatDateTime(
|
||||||
DateTime.fromMillisecondsSinceEpoch(
|
DateTime.fromMillisecondsSinceEpoch(
|
||||||
(device.createTime ?? 0) * 1000,
|
((device.createTime ?? 0) * 1000),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@ -127,16 +126,12 @@ class DeviceControlDialog extends StatelessWidget with RouteControlsBasedCode {
|
|||||||
),
|
),
|
||||||
TableRow(
|
TableRow(
|
||||||
children: [
|
children: [
|
||||||
_buildInfoRow(
|
_buildInfoRow('Status:', 'Online', statusColor: Colors.green),
|
||||||
'Status:',
|
|
||||||
isOnlineDevice ? 'Online' : 'offline',
|
|
||||||
statusColor: isOnlineDevice ? Colors.green : Colors.red,
|
|
||||||
),
|
|
||||||
_buildInfoRow(
|
_buildInfoRow(
|
||||||
'Last Offline Date and Time:',
|
'Last Offline Date and Time:',
|
||||||
formatDateTime(
|
formatDateTime(
|
||||||
DateTime.fromMillisecondsSinceEpoch(
|
DateTime.fromMillisecondsSinceEpoch(
|
||||||
(device.updateTime ?? 0) * 1000,
|
((device.updateTime ?? 0) * 1000),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
@ -1,9 +1,7 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
import 'package:flutter_svg/flutter_svg.dart';
|
import 'package:flutter_svg/flutter_svg.dart';
|
||||||
import 'package:syncrow_web/pages/auth/model/user_model.dart';
|
|
||||||
import 'package:syncrow_web/pages/common/bloc/project_manager.dart';
|
import 'package:syncrow_web/pages/common/bloc/project_manager.dart';
|
||||||
import 'package:syncrow_web/pages/roles_and_permission/model/roles_user_model.dart';
|
|
||||||
import 'package:syncrow_web/pages/roles_and_permission/users_page/add_user_dialog/bloc/users_bloc.dart';
|
import 'package:syncrow_web/pages/roles_and_permission/users_page/add_user_dialog/bloc/users_bloc.dart';
|
||||||
import 'package:syncrow_web/pages/roles_and_permission/users_page/add_user_dialog/bloc/users_event.dart';
|
import 'package:syncrow_web/pages/roles_and_permission/users_page/add_user_dialog/bloc/users_event.dart';
|
||||||
import 'package:syncrow_web/pages/roles_and_permission/users_page/add_user_dialog/bloc/users_status.dart';
|
import 'package:syncrow_web/pages/roles_and_permission/users_page/add_user_dialog/bloc/users_status.dart';
|
||||||
@ -14,11 +12,8 @@ import 'package:syncrow_web/utils/color_manager.dart';
|
|||||||
import 'package:syncrow_web/utils/constants/assets.dart';
|
import 'package:syncrow_web/utils/constants/assets.dart';
|
||||||
|
|
||||||
class EditUserDialog extends StatefulWidget {
|
class EditUserDialog extends StatefulWidget {
|
||||||
final RolesUserModel? user;
|
final String? userId;
|
||||||
const EditUserDialog({
|
const EditUserDialog({super.key, this.userId});
|
||||||
super.key,
|
|
||||||
this.user,
|
|
||||||
});
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
_EditUserDialogState createState() => _EditUserDialogState();
|
_EditUserDialogState createState() => _EditUserDialogState();
|
||||||
@ -33,11 +28,10 @@ class _EditUserDialogState extends State<EditUserDialog> {
|
|||||||
create: (BuildContext context) => UsersBloc()
|
create: (BuildContext context) => UsersBloc()
|
||||||
// ..add(const LoadCommunityAndSpacesEvent())
|
// ..add(const LoadCommunityAndSpacesEvent())
|
||||||
..add(const RoleEvent())
|
..add(const RoleEvent())
|
||||||
..add(GetUserByIdEvent(uuid: widget.user!.uuid)),
|
..add(GetUserByIdEvent(uuid: widget.userId)),
|
||||||
child: BlocConsumer<UsersBloc, UsersState>(listener: (context, state) {
|
child: BlocConsumer<UsersBloc, UsersState>(listener: (context, state) {
|
||||||
if (state is SpacesLoadedState) {
|
if (state is SpacesLoadedState) {
|
||||||
BlocProvider.of<UsersBloc>(context)
|
BlocProvider.of<UsersBloc>(context).add(GetUserByIdEvent(uuid: widget.userId));
|
||||||
.add(GetUserByIdEvent(uuid: widget.user!.uuid));
|
|
||||||
}
|
}
|
||||||
}, builder: (context, state) {
|
}, builder: (context, state) {
|
||||||
final _blocRole = BlocProvider.of<UsersBloc>(context);
|
final _blocRole = BlocProvider.of<UsersBloc>(context);
|
||||||
@ -45,8 +39,7 @@ class _EditUserDialogState extends State<EditUserDialog> {
|
|||||||
return Dialog(
|
return Dialog(
|
||||||
child: Container(
|
child: Container(
|
||||||
decoration: const BoxDecoration(
|
decoration: const BoxDecoration(
|
||||||
color: Colors.white,
|
color: Colors.white, borderRadius: BorderRadius.all(Radius.circular(20))),
|
||||||
borderRadius: BorderRadius.all(Radius.circular(20))),
|
|
||||||
width: 900,
|
width: 900,
|
||||||
child: Column(
|
child: Column(
|
||||||
children: [
|
children: [
|
||||||
@ -75,8 +68,7 @@ class _EditUserDialogState extends State<EditUserDialog> {
|
|||||||
children: [
|
children: [
|
||||||
_buildStep1Indicator(1, "Basics", _blocRole),
|
_buildStep1Indicator(1, "Basics", _blocRole),
|
||||||
_buildStep2Indicator(2, "Spaces", _blocRole),
|
_buildStep2Indicator(2, "Spaces", _blocRole),
|
||||||
_buildStep3Indicator(
|
_buildStep3Indicator(3, "Role & Permissions", _blocRole),
|
||||||
3, "Role & Permissions", _blocRole),
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@ -94,7 +86,7 @@ class _EditUserDialogState extends State<EditUserDialog> {
|
|||||||
children: [
|
children: [
|
||||||
const SizedBox(height: 10),
|
const SizedBox(height: 10),
|
||||||
Expanded(
|
Expanded(
|
||||||
child: _getFormContent(widget.user!),
|
child: _getFormContent(widget.userId),
|
||||||
),
|
),
|
||||||
const SizedBox(height: 20),
|
const SizedBox(height: 20),
|
||||||
],
|
],
|
||||||
@ -124,14 +116,13 @@ class _EditUserDialogState extends State<EditUserDialog> {
|
|||||||
if (currentStep < 3) {
|
if (currentStep < 3) {
|
||||||
currentStep++;
|
currentStep++;
|
||||||
if (currentStep == 2) {
|
if (currentStep == 2) {
|
||||||
_blocRole
|
_blocRole.add(CheckStepStatus(isEditUser: true));
|
||||||
.add(CheckStepStatus(isEditUser: true));
|
|
||||||
} else if (currentStep == 3) {
|
} else if (currentStep == 3) {
|
||||||
_blocRole.add(const CheckSpacesStepStatus());
|
_blocRole.add(const CheckSpacesStepStatus());
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
_blocRole.add(EditInviteUsers(
|
_blocRole
|
||||||
context: context, userId: widget.user!.uuid));
|
.add(EditInviteUsers(context: context, userId: widget.userId!));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
@ -140,8 +131,7 @@ class _EditUserDialogState extends State<EditUserDialog> {
|
|||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
color: (_blocRole.isCompleteSpaces == false ||
|
color: (_blocRole.isCompleteSpaces == false ||
|
||||||
_blocRole.isCompleteBasics == false ||
|
_blocRole.isCompleteBasics == false ||
|
||||||
_blocRole.isCompleteRolePermissions ==
|
_blocRole.isCompleteRolePermissions == false) &&
|
||||||
false) &&
|
|
||||||
currentStep == 3
|
currentStep == 3
|
||||||
? ColorsManager.grayColor
|
? ColorsManager.grayColor
|
||||||
: ColorsManager.secondaryColor),
|
: ColorsManager.secondaryColor),
|
||||||
@ -156,15 +146,15 @@ class _EditUserDialogState extends State<EditUserDialog> {
|
|||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _getFormContent(RolesUserModel user) {
|
Widget _getFormContent(userid) {
|
||||||
switch (currentStep) {
|
switch (currentStep) {
|
||||||
case 1:
|
case 1:
|
||||||
return BasicsView(
|
return BasicsView(
|
||||||
userId: user.uuid,
|
userId: userid,
|
||||||
);
|
);
|
||||||
case 2:
|
case 2:
|
||||||
return SpacesAccessView(
|
return SpacesAccessView(
|
||||||
userId: user.uuid,
|
userId: userid,
|
||||||
);
|
);
|
||||||
case 3:
|
case 3:
|
||||||
return const RolesAndPermission();
|
return const RolesAndPermission();
|
||||||
@ -176,7 +166,6 @@ class _EditUserDialogState extends State<EditUserDialog> {
|
|||||||
int step3 = 0;
|
int step3 = 0;
|
||||||
|
|
||||||
Widget _buildStep1Indicator(int step, String label, UsersBloc bloc) {
|
Widget _buildStep1Indicator(int step, String label, UsersBloc bloc) {
|
||||||
final isCurrentStep = currentStep == step;
|
|
||||||
return GestureDetector(
|
return GestureDetector(
|
||||||
onTap: () {
|
onTap: () {
|
||||||
setState(() {
|
setState(() {
|
||||||
@ -200,7 +189,7 @@ class _EditUserDialogState extends State<EditUserDialog> {
|
|||||||
child: Row(
|
child: Row(
|
||||||
children: [
|
children: [
|
||||||
SvgPicture.asset(
|
SvgPicture.asset(
|
||||||
isCurrentStep
|
currentStep == step
|
||||||
? Assets.currentProcessIcon
|
? Assets.currentProcessIcon
|
||||||
: bloc.isCompleteBasics == false
|
: bloc.isCompleteBasics == false
|
||||||
? Assets.wrongProcessIcon
|
? Assets.wrongProcessIcon
|
||||||
@ -215,11 +204,8 @@ class _EditUserDialogState extends State<EditUserDialog> {
|
|||||||
label,
|
label,
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: 16,
|
fontSize: 16,
|
||||||
color: isCurrentStep
|
color: currentStep == step ? ColorsManager.blackColor : ColorsManager.greyColor,
|
||||||
? ColorsManager.blackColor
|
fontWeight: currentStep == step ? FontWeight.bold : FontWeight.normal,
|
||||||
: ColorsManager.greyColor,
|
|
||||||
fontWeight:
|
|
||||||
isCurrentStep ? FontWeight.bold : FontWeight.normal,
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
@ -243,7 +229,6 @@ class _EditUserDialogState extends State<EditUserDialog> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildStep2Indicator(int step, String label, UsersBloc bloc) {
|
Widget _buildStep2Indicator(int step, String label, UsersBloc bloc) {
|
||||||
final isCurrentStep = currentStep == step;
|
|
||||||
return GestureDetector(
|
return GestureDetector(
|
||||||
onTap: () {
|
onTap: () {
|
||||||
setState(() {
|
setState(() {
|
||||||
@ -263,7 +248,7 @@ class _EditUserDialogState extends State<EditUserDialog> {
|
|||||||
child: Row(
|
child: Row(
|
||||||
children: [
|
children: [
|
||||||
SvgPicture.asset(
|
SvgPicture.asset(
|
||||||
isCurrentStep
|
currentStep == step
|
||||||
? Assets.currentProcessIcon
|
? Assets.currentProcessIcon
|
||||||
: bloc.isCompleteSpaces == false
|
: bloc.isCompleteSpaces == false
|
||||||
? Assets.wrongProcessIcon
|
? Assets.wrongProcessIcon
|
||||||
@ -278,11 +263,8 @@ class _EditUserDialogState extends State<EditUserDialog> {
|
|||||||
label,
|
label,
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: 16,
|
fontSize: 16,
|
||||||
color: isCurrentStep
|
color: currentStep == step ? ColorsManager.blackColor : ColorsManager.greyColor,
|
||||||
? ColorsManager.blackColor
|
fontWeight: currentStep == step ? FontWeight.bold : FontWeight.normal,
|
||||||
: ColorsManager.greyColor,
|
|
||||||
fontWeight:
|
|
||||||
isCurrentStep ? FontWeight.bold : FontWeight.normal,
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
@ -306,7 +288,6 @@ class _EditUserDialogState extends State<EditUserDialog> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildStep3Indicator(int step, String label, UsersBloc bloc) {
|
Widget _buildStep3Indicator(int step, String label, UsersBloc bloc) {
|
||||||
final isCurrentStep = currentStep == step;
|
|
||||||
return GestureDetector(
|
return GestureDetector(
|
||||||
onTap: () {
|
onTap: () {
|
||||||
setState(() {
|
setState(() {
|
||||||
@ -325,7 +306,7 @@ class _EditUserDialogState extends State<EditUserDialog> {
|
|||||||
child: Row(
|
child: Row(
|
||||||
children: [
|
children: [
|
||||||
SvgPicture.asset(
|
SvgPicture.asset(
|
||||||
isCurrentStep
|
currentStep == step
|
||||||
? Assets.currentProcessIcon
|
? Assets.currentProcessIcon
|
||||||
: bloc.isCompleteRolePermissions == false
|
: bloc.isCompleteRolePermissions == false
|
||||||
? Assets.wrongProcessIcon
|
? Assets.wrongProcessIcon
|
||||||
@ -340,11 +321,8 @@ class _EditUserDialogState extends State<EditUserDialog> {
|
|||||||
label,
|
label,
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: 16,
|
fontSize: 16,
|
||||||
color: isCurrentStep
|
color: currentStep == step ? ColorsManager.blackColor : ColorsManager.greyColor,
|
||||||
? ColorsManager.blackColor
|
fontWeight: currentStep == step ? FontWeight.bold : FontWeight.normal,
|
||||||
: ColorsManager.greyColor,
|
|
||||||
fontWeight:
|
|
||||||
isCurrentStep ? FontWeight.bold : FontWeight.normal,
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
@ -19,7 +19,6 @@ import 'package:syncrow_web/utils/color_manager.dart';
|
|||||||
import 'package:syncrow_web/utils/constants/assets.dart';
|
import 'package:syncrow_web/utils/constants/assets.dart';
|
||||||
import 'package:syncrow_web/utils/extension/build_context_x.dart';
|
import 'package:syncrow_web/utils/extension/build_context_x.dart';
|
||||||
import 'package:syncrow_web/utils/style.dart';
|
import 'package:syncrow_web/utils/style.dart';
|
||||||
|
|
||||||
class UsersPage extends StatelessWidget {
|
class UsersPage extends StatelessWidget {
|
||||||
UsersPage({super.key});
|
UsersPage({super.key});
|
||||||
|
|
||||||
@ -452,8 +451,8 @@ class UsersPage extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
Row(
|
Row(
|
||||||
children: [
|
children: [
|
||||||
if (user.isEnabled != false)
|
user.isEnabled != false
|
||||||
actionButton(
|
? actionButton(
|
||||||
isActive: true,
|
isActive: true,
|
||||||
title: "Edit",
|
title: "Edit",
|
||||||
onTap: () {
|
onTap: () {
|
||||||
@ -464,17 +463,19 @@ class UsersPage extends StatelessWidget {
|
|||||||
context: context,
|
context: context,
|
||||||
barrierDismissible: false,
|
barrierDismissible: false,
|
||||||
builder: (BuildContext context) {
|
builder: (BuildContext context) {
|
||||||
return EditUserDialog(user: user);
|
return EditUserDialog(
|
||||||
|
userId: user.uuid);
|
||||||
},
|
},
|
||||||
).then((v) {
|
).then((v) {
|
||||||
|
if (v != null) {
|
||||||
if (v != null) {
|
if (v != null) {
|
||||||
_blocRole.add(const GetUsers());
|
_blocRole.add(const GetUsers());
|
||||||
}
|
}
|
||||||
|
}
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
else
|
: actionButton(
|
||||||
actionButton(
|
|
||||||
title: "Edit",
|
title: "Edit",
|
||||||
),
|
),
|
||||||
actionButton(
|
actionButton(
|
||||||
|
@ -1,6 +0,0 @@
|
|||||||
class SpaceConnectionModel {
|
|
||||||
final String from;
|
|
||||||
final String to;
|
|
||||||
|
|
||||||
const SpaceConnectionModel({required this.from, required this.to});
|
|
||||||
}
|
|
@ -1,61 +0,0 @@
|
|||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:syncrow_web/pages/space_management_v2/main_module/models/space_connection_model.dart';
|
|
||||||
import 'package:syncrow_web/utils/color_manager.dart';
|
|
||||||
|
|
||||||
class SpacesConnectionsArrowPainter extends CustomPainter {
|
|
||||||
final List<SpaceConnectionModel> connections;
|
|
||||||
final Map<String, Offset> positions;
|
|
||||||
final double cardWidth = 150.0;
|
|
||||||
final double cardHeight = 90.0;
|
|
||||||
final Set<String> highlightedUuids;
|
|
||||||
|
|
||||||
SpacesConnectionsArrowPainter({
|
|
||||||
required this.connections,
|
|
||||||
required this.positions,
|
|
||||||
required this.highlightedUuids,
|
|
||||||
});
|
|
||||||
|
|
||||||
@override
|
|
||||||
void paint(Canvas canvas, Size size) {
|
|
||||||
for (final connection in connections) {
|
|
||||||
final isSelected = highlightedUuids.contains(connection.from) ||
|
|
||||||
highlightedUuids.contains(connection.to);
|
|
||||||
final paint = Paint()
|
|
||||||
..color = isSelected
|
|
||||||
? ColorsManager.blackColor
|
|
||||||
: ColorsManager.blackColor.withValues(alpha: 0.5)
|
|
||||||
..strokeWidth = 2.0
|
|
||||||
..style = PaintingStyle.stroke;
|
|
||||||
|
|
||||||
final from = positions[connection.from];
|
|
||||||
final to = positions[connection.to];
|
|
||||||
|
|
||||||
if (from != null && to != null) {
|
|
||||||
final startPoint =
|
|
||||||
Offset(from.dx + cardWidth / 2, from.dy + cardHeight - 10);
|
|
||||||
final endPoint = Offset(to.dx + cardWidth / 2, to.dy);
|
|
||||||
|
|
||||||
final path = Path()..moveTo(startPoint.dx, startPoint.dy);
|
|
||||||
|
|
||||||
final controlPoint1 = Offset(startPoint.dx, startPoint.dy + 20);
|
|
||||||
final controlPoint2 = Offset(endPoint.dx, endPoint.dy - 60);
|
|
||||||
|
|
||||||
path.cubicTo(controlPoint1.dx, controlPoint1.dy, controlPoint2.dx,
|
|
||||||
controlPoint2.dy, endPoint.dx, endPoint.dy);
|
|
||||||
|
|
||||||
canvas.drawPath(path, paint);
|
|
||||||
|
|
||||||
final circlePaint = Paint()
|
|
||||||
..color = isSelected
|
|
||||||
? ColorsManager.blackColor
|
|
||||||
: ColorsManager.blackColor.withValues(alpha: 0.5)
|
|
||||||
..style = PaintingStyle.fill
|
|
||||||
..blendMode = BlendMode.srcIn;
|
|
||||||
canvas.drawCircle(endPoint, 4, circlePaint);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
bool shouldRepaint(covariant CustomPainter oldDelegate) => true;
|
|
||||||
}
|
|
@ -1,24 +0,0 @@
|
|||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
|
||||||
import 'package:syncrow_web/pages/space_management_v2/modules/communities/presentation/bloc/communities_bloc.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/create_community/presentation/create_community_dialog.dart';
|
|
||||||
|
|
||||||
abstract final class SpaceManagementCommunityDialogHelper {
|
|
||||||
static void showCreateDialog(BuildContext context) {
|
|
||||||
showDialog<void>(
|
|
||||||
context: context,
|
|
||||||
builder: (_) => CreateCommunityDialog(
|
|
||||||
title: const SelectableText('Community Name'),
|
|
||||||
onCreateCommunity: (community) {
|
|
||||||
context.read<CommunitiesBloc>().add(
|
|
||||||
InsertCommunity(community),
|
|
||||||
);
|
|
||||||
context.read<CommunitiesTreeSelectionBloc>().add(
|
|
||||||
SelectCommunityEvent(community: community),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,280 +0,0 @@
|
|||||||
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();
|
|
||||||
}
|
|
||||||
|
|
||||||
class _CommunityStructureCanvasState extends State<CommunityStructureCanvas>
|
|
||||||
with SingleTickerProviderStateMixin {
|
|
||||||
final Map<String, Offset> _positions = {};
|
|
||||||
final double _cardWidth = 150.0;
|
|
||||||
final double _cardHeight = 90.0;
|
|
||||||
final double _horizontalSpacing = 150.0;
|
|
||||||
final double _verticalSpacing = 120.0;
|
|
||||||
|
|
||||||
late TransformationController _transformationController;
|
|
||||||
late AnimationController _animationController;
|
|
||||||
|
|
||||||
@override
|
|
||||||
void initState() {
|
|
||||||
_transformationController = TransformationController();
|
|
||||||
_animationController = AnimationController(
|
|
||||||
vsync: this,
|
|
||||||
duration: const Duration(milliseconds: 150),
|
|
||||||
);
|
|
||||||
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
|
|
||||||
void dispose() {
|
|
||||||
_transformationController.dispose();
|
|
||||||
_animationController.dispose();
|
|
||||||
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,
|
|
||||||
end: target,
|
|
||||||
).animate(_animationController);
|
|
||||||
|
|
||||||
void listener() {
|
|
||||||
_transformationController.value = animation.value;
|
|
||||||
}
|
|
||||||
|
|
||||||
animation.addListener(listener);
|
|
||||||
_animationController.forward(from: 0).whenCompleteOrCancel(() {
|
|
||||||
animation.removeListener(listener);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
void _animateToSpace(SpaceModel? space) {
|
|
||||||
if (space == null) {
|
|
||||||
_runAnimation(Matrix4.identity());
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
final position = _positions[space.uuid];
|
|
||||||
if (position == null) return;
|
|
||||||
|
|
||||||
const scale = 1.5;
|
|
||||||
final viewSize = context.size;
|
|
||||||
if (viewSize == null) return;
|
|
||||||
|
|
||||||
final x = -position.dx * scale + (viewSize.width / 2) - (_cardWidth * scale / 2);
|
|
||||||
final y =
|
|
||||||
-position.dy * scale + (viewSize.height / 2) - (_cardHeight * scale / 2);
|
|
||||||
|
|
||||||
final matrix = Matrix4.identity()
|
|
||||||
..translate(x, y)
|
|
||||||
..scale(scale);
|
|
||||||
|
|
||||||
_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,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
void _calculateLayout(
|
|
||||||
List<SpaceModel> spaces,
|
|
||||||
int depth,
|
|
||||||
Map<int, double> levelXOffset,
|
|
||||||
) {
|
|
||||||
for (final space in spaces) {
|
|
||||||
double childSubtreeWidth = 0;
|
|
||||||
if (space.children.isNotEmpty) {
|
|
||||||
_calculateLayout(space.children, depth + 1, levelXOffset);
|
|
||||||
final firstChildPos = _positions[space.children.first.uuid];
|
|
||||||
final lastChildPos = _positions[space.children.last.uuid];
|
|
||||||
if (firstChildPos != null && lastChildPos != null) {
|
|
||||||
childSubtreeWidth = (lastChildPos.dx + _cardWidth) - firstChildPos.dx;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
final currentX = levelXOffset.putIfAbsent(depth, () => 0.0);
|
|
||||||
double? x;
|
|
||||||
|
|
||||||
if (space.children.isNotEmpty) {
|
|
||||||
final firstChildPos = _positions[space.children.first.uuid]!;
|
|
||||||
x = firstChildPos.dx + (childSubtreeWidth - _cardWidth) / 2;
|
|
||||||
} else {
|
|
||||||
x = currentX;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (x < currentX) {
|
|
||||||
final shiftX = currentX - x;
|
|
||||||
_shiftSubtree(space, shiftX);
|
|
||||||
final keysToShift = levelXOffset.keys.where((d) => d > depth).toList();
|
|
||||||
for (final key in keysToShift) {
|
|
||||||
levelXOffset[key] = levelXOffset[key]! + shiftX;
|
|
||||||
}
|
|
||||||
x += shiftX;
|
|
||||||
}
|
|
||||||
|
|
||||||
final y = depth * (_verticalSpacing + _cardHeight);
|
|
||||||
_positions[space.uuid] = Offset(x, y);
|
|
||||||
levelXOffset[depth] = x + _cardWidth + _horizontalSpacing;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void _shiftSubtree(SpaceModel space, double shiftX) {
|
|
||||||
if (_positions.containsKey(space.uuid)) {
|
|
||||||
_positions[space.uuid] = _positions[space.uuid]!.translate(shiftX, 0);
|
|
||||||
}
|
|
||||||
for (final child in space.children) {
|
|
||||||
_shiftSubtree(child, shiftX);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
List<Widget> _buildTreeWidgets() {
|
|
||||||
_positions.clear();
|
|
||||||
final community = widget.community;
|
|
||||||
|
|
||||||
_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);
|
|
||||||
|
|
||||||
return [
|
|
||||||
CustomPaint(
|
|
||||||
painter: SpacesConnectionsArrowPainter(
|
|
||||||
connections: connections,
|
|
||||||
positions: _positions,
|
|
||||||
highlightedUuids: highlightedUuids,
|
|
||||||
),
|
|
||||||
child: Stack(alignment: AlignmentDirectional.center, children: widgets),
|
|
||||||
),
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
void _generateWidgets(
|
|
||||||
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,
|
|
||||||
top: position.dy,
|
|
||||||
width: _cardWidth,
|
|
||||||
height: _cardHeight,
|
|
||||||
child: SpaceCardWidget(
|
|
||||||
buildSpaceContainer: () {
|
|
||||||
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,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
onTap: () => SpaceDetailsDialogHelper.showCreate(context),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
for (final child in space.children) {
|
|
||||||
connections.add(
|
|
||||||
SpaceConnectionModel(from: space.uuid, to: child.uuid),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
_generateWidgets(space.children, widgets, connections, highlightedUuids);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
final treeWidgets = _buildTreeWidgets();
|
|
||||||
return InteractiveViewer(
|
|
||||||
transformationController: _transformationController,
|
|
||||||
boundaryMargin: EdgeInsets.symmetric(
|
|
||||||
horizontal: MediaQuery.sizeOf(context).width * 0.3,
|
|
||||||
vertical: MediaQuery.sizeOf(context).height * 0.3,
|
|
||||||
),
|
|
||||||
minScale: 0.5,
|
|
||||||
maxScale: 3.0,
|
|
||||||
constrained: false,
|
|
||||||
child: GestureDetector(
|
|
||||||
onTap: _resetSelectionAndZoom,
|
|
||||||
child: SizedBox(
|
|
||||||
width: MediaQuery.sizeOf(context).width * 5,
|
|
||||||
height: MediaQuery.sizeOf(context).height * 5,
|
|
||||||
child: Stack(children: treeWidgets),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,50 +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 CommunityTemplateCell extends StatelessWidget {
|
|
||||||
const CommunityTemplateCell({
|
|
||||||
super.key,
|
|
||||||
required this.onTap,
|
|
||||||
required this.title,
|
|
||||||
});
|
|
||||||
|
|
||||||
final void Function() onTap;
|
|
||||||
final Widget title;
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return GestureDetector(
|
|
||||||
onTap: onTap,
|
|
||||||
child: Column(
|
|
||||||
spacing: 8,
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.center,
|
|
||||||
children: [
|
|
||||||
Expanded(
|
|
||||||
child: AspectRatio(
|
|
||||||
aspectRatio: 2.0,
|
|
||||||
child: Container(
|
|
||||||
decoration: ShapeDecoration(
|
|
||||||
shape: RoundedRectangleBorder(
|
|
||||||
side: const BorderSide(
|
|
||||||
width: 4,
|
|
||||||
strokeAlign: BorderSide.strokeAlignOutside,
|
|
||||||
color: ColorsManager.borderColor,
|
|
||||||
),
|
|
||||||
borderRadius: BorderRadius.circular(5),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
DefaultTextStyle(
|
|
||||||
style: context.textTheme.bodyLarge!.copyWith(
|
|
||||||
color: ColorsManager.blackColor,
|
|
||||||
),
|
|
||||||
child: title,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,43 +0,0 @@
|
|||||||
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 {
|
|
||||||
const CreateSpaceButton({super.key});
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return GestureDetector(
|
|
||||||
onTap: () => SpaceDetailsDialogHelper.showCreate(context),
|
|
||||||
child: Container(
|
|
||||||
height: 60,
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: Colors.white,
|
|
||||||
borderRadius: BorderRadius.circular(20),
|
|
||||||
boxShadow: [
|
|
||||||
BoxShadow(
|
|
||||||
color: Colors.grey.withValues(alpha: 0.5),
|
|
||||||
spreadRadius: 5,
|
|
||||||
blurRadius: 7,
|
|
||||||
offset: const Offset(0, 3),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
child: Center(
|
|
||||||
child: Container(
|
|
||||||
width: 40,
|
|
||||||
height: 40,
|
|
||||||
decoration: const BoxDecoration(
|
|
||||||
color: ColorsManager.boxColor,
|
|
||||||
shape: BoxShape.circle,
|
|
||||||
),
|
|
||||||
child: const Icon(
|
|
||||||
Icons.add,
|
|
||||||
color: Colors.blue,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,33 +0,0 @@
|
|||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:syncrow_web/utils/color_manager.dart';
|
|
||||||
|
|
||||||
class PlusButtonWidget extends StatelessWidget {
|
|
||||||
final Offset offset;
|
|
||||||
final void Function() onButtonTap;
|
|
||||||
|
|
||||||
const PlusButtonWidget({
|
|
||||||
super.key,
|
|
||||||
required this.offset,
|
|
||||||
required this.onButtonTap,
|
|
||||||
});
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return GestureDetector(
|
|
||||||
onTap: onButtonTap,
|
|
||||||
child: Container(
|
|
||||||
width: 30,
|
|
||||||
height: 30,
|
|
||||||
decoration: const BoxDecoration(
|
|
||||||
color: ColorsManager.spaceColor,
|
|
||||||
shape: BoxShape.circle,
|
|
||||||
),
|
|
||||||
child: const Icon(
|
|
||||||
Icons.add,
|
|
||||||
color: ColorsManager.whiteColors,
|
|
||||||
size: 20,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,44 +0,0 @@
|
|||||||
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;
|
|
||||||
|
|
||||||
const SpaceCardWidget({
|
|
||||||
required this.onTap,
|
|
||||||
required this.buildSpaceContainer,
|
|
||||||
super.key,
|
|
||||||
});
|
|
||||||
|
|
||||||
@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),
|
|
||||||
child: SizedBox(
|
|
||||||
child: Stack(
|
|
||||||
clipBehavior: Clip.none,
|
|
||||||
alignment: Alignment.center,
|
|
||||||
children: [
|
|
||||||
widget.buildSpaceContainer(),
|
|
||||||
if (isHovered)
|
|
||||||
Positioned(
|
|
||||||
bottom: 0,
|
|
||||||
child: PlusButtonWidget(
|
|
||||||
offset: Offset.zero,
|
|
||||||
onButtonTap: widget.onTap,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,85 +0,0 @@
|
|||||||
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 String icon;
|
|
||||||
final String name;
|
|
||||||
final VoidCallback? onTap;
|
|
||||||
|
|
||||||
const SpaceCell({
|
|
||||||
super.key,
|
|
||||||
required this.icon,
|
|
||||||
required this.name,
|
|
||||||
required this.onTap,
|
|
||||||
});
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return GestureDetector(
|
|
||||||
onTap: onTap,
|
|
||||||
child: Container(
|
|
||||||
width: 150,
|
|
||||||
height: 70,
|
|
||||||
decoration: _containerDecoration(),
|
|
||||||
child: Row(
|
|
||||||
children: [
|
|
||||||
_buildIconContainer(),
|
|
||||||
const SizedBox(width: 10),
|
|
||||||
Expanded(
|
|
||||||
child: Text(
|
|
||||||
name,
|
|
||||||
style: context.textTheme.bodyLarge?.copyWith(
|
|
||||||
fontWeight: FontWeight.bold,
|
|
||||||
color: ColorsManager.blackColor,
|
|
||||||
),
|
|
||||||
overflow: TextOverflow.ellipsis,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Widget _buildIconContainer() {
|
|
||||||
return Container(
|
|
||||||
width: 40,
|
|
||||||
height: double.infinity,
|
|
||||||
decoration: const BoxDecoration(
|
|
||||||
color: ColorsManager.spaceColor,
|
|
||||||
borderRadius: BorderRadius.only(
|
|
||||||
topLeft: Radius.circular(15),
|
|
||||||
bottomLeft: Radius.circular(15),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
child: Center(
|
|
||||||
child: SvgPicture.asset(
|
|
||||||
icon,
|
|
||||||
colorFilter: const ColorFilter.mode(
|
|
||||||
ColorsManager.whiteColors,
|
|
||||||
BlendMode.srcIn,
|
|
||||||
),
|
|
||||||
width: 24,
|
|
||||||
height: 24,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
BoxDecoration _containerDecoration() {
|
|
||||||
return BoxDecoration(
|
|
||||||
color: ColorsManager.whiteColors,
|
|
||||||
borderRadius: BorderRadius.circular(15),
|
|
||||||
boxShadow: [
|
|
||||||
BoxShadow(
|
|
||||||
color: ColorsManager.lightGrayColor.withValues(alpha: 0.5),
|
|
||||||
spreadRadius: 2,
|
|
||||||
blurRadius: 5,
|
|
||||||
offset: const Offset(0, 3),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,8 +1,4 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
|
||||||
import 'package:syncrow_web/pages/space_management_v2/main_module/widgets/space_management_community_structure.dart';
|
|
||||||
import 'package:syncrow_web/pages/space_management_v2/main_module/widgets/space_management_templates_view.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/communities/presentation/widgets/space_management_communities_tree.dart';
|
import 'package:syncrow_web/pages/space_management_v2/modules/communities/presentation/widgets/space_management_communities_tree.dart';
|
||||||
|
|
||||||
class SpaceManagementBody extends StatelessWidget {
|
class SpaceManagementBody extends StatelessWidget {
|
||||||
@ -10,21 +6,9 @@ class SpaceManagementBody extends StatelessWidget {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Row(
|
return const Row(
|
||||||
children: [
|
children: [
|
||||||
const SpaceManagementCommunitiesTree(),
|
SpaceManagementCommunitiesTree(),
|
||||||
Expanded(
|
|
||||||
child: BlocBuilder<CommunitiesTreeSelectionBloc,
|
|
||||||
CommunitiesTreeSelectionState>(
|
|
||||||
buildWhen: (previous, current) =>
|
|
||||||
previous.selectedCommunity != current.selectedCommunity,
|
|
||||||
builder: (context, state) => Visibility(
|
|
||||||
visible: state.selectedCommunity == null,
|
|
||||||
replacement: const SpaceManagementCommunityStructure(),
|
|
||||||
child: const SpaceManagementTemplatesView(),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -1,27 +0,0 @@
|
|||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
|
||||||
import 'package:syncrow_web/pages/space_management_v2/main_module/widgets/community_structure_canvas.dart';
|
|
||||||
import 'package:syncrow_web/pages/space_management_v2/main_module/widgets/create_space_button.dart';
|
|
||||||
import 'package:syncrow_web/pages/space_management_v2/modules/communities/presentation/communities_tree_selection_bloc/communities_tree_selection_bloc.dart';
|
|
||||||
|
|
||||||
class SpaceManagementCommunityStructure extends StatelessWidget {
|
|
||||||
const SpaceManagementCommunityStructure({super.key});
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
final selectionBloc = context.watch<CommunitiesTreeSelectionBloc>().state;
|
|
||||||
final selectedCommunity = selectionBloc.selectedCommunity;
|
|
||||||
final selectedSpace = selectionBloc.selectedSpace;
|
|
||||||
const spacer = Spacer(flex: 10);
|
|
||||||
return Visibility(
|
|
||||||
visible: selectedCommunity!.spaces.isNotEmpty,
|
|
||||||
replacement: const Row(
|
|
||||||
children: [spacer, Expanded(child: CreateSpaceButton()), spacer],
|
|
||||||
),
|
|
||||||
child: CommunityStructureCanvas(
|
|
||||||
community: selectedCommunity,
|
|
||||||
selectedSpace: selectedSpace,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,50 +0,0 @@
|
|||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:syncrow_web/pages/space_management_v2/main_module/shared/helpers/space_management_community_dialog_helper.dart';
|
|
||||||
import 'package:syncrow_web/pages/space_management_v2/main_module/widgets/community_template_cell.dart';
|
|
||||||
import 'package:syncrow_web/utils/color_manager.dart';
|
|
||||||
|
|
||||||
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,
|
|
||||||
),
|
|
||||||
itemCount: _gridItems(context).length,
|
|
||||||
itemBuilder: (context, index) {
|
|
||||||
final model = _gridItems(context)[index];
|
|
||||||
return CommunityTemplateCell(
|
|
||||||
onTap: model.onTap,
|
|
||||||
title: model.title,
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
List<_CommunityTemplateModel> _gridItems(BuildContext context) {
|
|
||||||
return [
|
|
||||||
_CommunityTemplateModel(
|
|
||||||
title: const Text('Blank'),
|
|
||||||
onTap: () => SpaceManagementCommunityDialogHelper.showCreateDialog(context),
|
|
||||||
),
|
|
||||||
];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class _CommunityTemplateModel {
|
|
||||||
final Widget title;
|
|
||||||
final void Function() onTap;
|
|
||||||
|
|
||||||
_CommunityTemplateModel({
|
|
||||||
required this.title,
|
|
||||||
required this.onTap,
|
|
||||||
});
|
|
||||||
}
|
|
@ -32,7 +32,7 @@ class CommunitiesTreeSelectionBloc
|
|||||||
) {
|
) {
|
||||||
emit(
|
emit(
|
||||||
CommunitiesTreeSelectionState(
|
CommunitiesTreeSelectionState(
|
||||||
selectedCommunity: event.community,
|
selectedCommunity: null,
|
||||||
selectedSpace: event.space,
|
selectedSpace: event.space,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
@ -8,7 +8,7 @@ sealed class CommunitiesTreeSelectionEvent extends Equatable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
final class SelectCommunityEvent extends CommunitiesTreeSelectionEvent {
|
final class SelectCommunityEvent extends CommunitiesTreeSelectionEvent {
|
||||||
final CommunityModel community;
|
final CommunityModel? community;
|
||||||
|
|
||||||
const SelectCommunityEvent({required this.community});
|
const SelectCommunityEvent({required this.community});
|
||||||
@override
|
@override
|
||||||
@ -17,9 +17,8 @@ final class SelectCommunityEvent extends CommunitiesTreeSelectionEvent {
|
|||||||
|
|
||||||
final class SelectSpaceEvent extends CommunitiesTreeSelectionEvent {
|
final class SelectSpaceEvent extends CommunitiesTreeSelectionEvent {
|
||||||
final SpaceModel? space;
|
final SpaceModel? space;
|
||||||
final CommunityModel community;
|
|
||||||
|
|
||||||
const SelectSpaceEvent({required this.space, required this.community});
|
const SelectSpaceEvent({required this.space});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
List<Object?> get props => [space];
|
List<Object?> get props => [space];
|
||||||
|
@ -30,7 +30,7 @@ class SpaceManagementCommunitiesTreeSpaceTile extends StatelessWidget {
|
|||||||
initiallyExpanded: spaceIsExpanded,
|
initiallyExpanded: spaceIsExpanded,
|
||||||
onExpansionChanged: (expanded) {},
|
onExpansionChanged: (expanded) {},
|
||||||
onItemSelected: () => context.read<CommunitiesTreeSelectionBloc>().add(
|
onItemSelected: () => context.read<CommunitiesTreeSelectionBloc>().add(
|
||||||
SelectSpaceEvent(community: community, space: space),
|
SelectSpaceEvent(space: space),
|
||||||
),
|
),
|
||||||
children: space.children
|
children: space.children
|
||||||
.map(
|
.map(
|
||||||
|
@ -1,8 +1,9 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
import 'package:syncrow_web/pages/space_management_v2/main_module/shared/helpers/space_management_community_dialog_helper.dart';
|
import 'package:syncrow_web/pages/space_management_v2/modules/communities/presentation/bloc/communities_bloc.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/communities/presentation/communities_tree_selection_bloc/communities_tree_selection_bloc.dart';
|
||||||
import 'package:syncrow_web/pages/space_management_v2/modules/communities/presentation/widgets/space_management_sidebar_add_community_button.dart';
|
import 'package:syncrow_web/pages/space_management_v2/modules/communities/presentation/widgets/space_management_sidebar_add_community_button.dart';
|
||||||
|
import 'package:syncrow_web/pages/space_management_v2/modules/create_community/presentation/create_community_dialog.dart';
|
||||||
import 'package:syncrow_web/utils/color_manager.dart';
|
import 'package:syncrow_web/utils/color_manager.dart';
|
||||||
import 'package:syncrow_web/utils/extension/build_context_x.dart';
|
import 'package:syncrow_web/utils/extension/build_context_x.dart';
|
||||||
import 'package:syncrow_web/utils/style.dart';
|
import 'package:syncrow_web/utils/style.dart';
|
||||||
@ -40,7 +41,7 @@ class SpaceManagementSidebarHeader extends StatelessWidget {
|
|||||||
if (isSelected) {
|
if (isSelected) {
|
||||||
_clearSelection(context);
|
_clearSelection(context);
|
||||||
} else {
|
} else {
|
||||||
SpaceManagementCommunityDialogHelper.showCreateDialog(context);
|
_showCreateCommunityDialog(context);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -49,4 +50,19 @@ class SpaceManagementSidebarHeader extends StatelessWidget {
|
|||||||
const ClearCommunitiesTreeSelectionEvent(),
|
const ClearCommunitiesTreeSelectionEvent(),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void _showCreateCommunityDialog(BuildContext context) => showDialog<void>(
|
||||||
|
context: context,
|
||||||
|
builder: (_) => CreateCommunityDialog(
|
||||||
|
title: const Text('Community Name'),
|
||||||
|
onCreateCommunity: (community) {
|
||||||
|
context.read<CommunitiesBloc>().add(
|
||||||
|
InsertCommunity(community),
|
||||||
|
);
|
||||||
|
context.read<CommunitiesTreeSelectionBloc>().add(
|
||||||
|
SelectCommunityEvent(community: community),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
@ -53,7 +53,7 @@ class RemoteCreateCommunityService implements CreateCommunityService {
|
|||||||
return _defaultErrorMessage;
|
return _defaultErrorMessage;
|
||||||
}
|
}
|
||||||
final error = body['error'] as Map<String, dynamic>?;
|
final error = body['error'] as Map<String, dynamic>?;
|
||||||
final errorMessage = error?['message'] as String? ?? '';
|
final errorMessage = error?['error'] as String? ?? '';
|
||||||
return errorMessage;
|
return errorMessage;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -41,8 +41,11 @@ class CreateCommunityDialog extends StatelessWidget {
|
|||||||
);
|
);
|
||||||
onCreateCommunity.call(community);
|
onCreateCommunity.call(community);
|
||||||
break;
|
break;
|
||||||
case CreateCommunityFailure():
|
case CreateCommunityFailure(:final message):
|
||||||
Navigator.of(context).pop();
|
Navigator.of(context).pop();
|
||||||
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
|
SnackBar(content: Text(message)),
|
||||||
|
);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
|
@ -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(),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
@ -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'),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
@ -289,6 +289,7 @@ class SpaceTreeBloc extends Bloc<SpaceTreeEvent, SpaceTreeState> {
|
|||||||
selectedSpaces: updatedSelectedSpaces,
|
selectedSpaces: updatedSelectedSpaces,
|
||||||
soldCheck: updatedSoldChecks,
|
soldCheck: updatedSoldChecks,
|
||||||
selectedCommunityAndSpaces: communityAndSpaces));
|
selectedCommunityAndSpaces: communityAndSpaces));
|
||||||
|
emit(state.copyWith(selectedSpaces: updatedSelectedSpaces));
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
emit(const SpaceTreeErrorState('Something went wrong'));
|
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> _getThePathToChild(String communityId, String selectedSpaceId) {
|
||||||
List<String> ids = [];
|
List<String> ids = [];
|
||||||
final communityDataSource =
|
for (var community in state.communityList) {
|
||||||
state.searchQuery.isNotEmpty ? state.filteredCommunity : state.communityList;
|
|
||||||
for (final community in communityDataSource) {
|
|
||||||
if (community.uuid == communityId) {
|
if (community.uuid == communityId) {
|
||||||
for (final space in community.spaces) {
|
for (var space in community.spaces) {
|
||||||
final list = <String>[];
|
List<String> list = [];
|
||||||
list.add(space.uuid!);
|
list.add(space.uuid!);
|
||||||
ids = _getAllParentsIds(space, selectedSpaceId, List.from(list));
|
ids = _getAllParentsIds(space, selectedSpaceId, List.from(list));
|
||||||
if (ids.isNotEmpty) {
|
if (ids.isNotEmpty) {
|
||||||
|
@ -68,7 +68,7 @@ class VisitorPasswordBloc
|
|||||||
DateTime? startTime = DateTime.now();
|
DateTime? startTime = DateTime.now();
|
||||||
DateTime? endTime;
|
DateTime? endTime;
|
||||||
|
|
||||||
String startTimeAccess = DateTime.now().toString().split('.').first;
|
String startTimeAccess = 'Start Time';
|
||||||
String endTimeAccess = 'End Time';
|
String endTimeAccess = 'End Time';
|
||||||
PasswordStatus? passwordStatus;
|
PasswordStatus? passwordStatus;
|
||||||
selectAccessType(
|
selectAccessType(
|
||||||
@ -136,27 +136,6 @@ class VisitorPasswordBloc
|
|||||||
);
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if(selectedTimestamp < DateTime.now().millisecondsSinceEpoch ~/ 1000) {
|
|
||||||
if(selectedTimestamp < DateTime.now().millisecondsSinceEpoch ~/ 1000) {
|
|
||||||
await showDialog<void>(
|
|
||||||
context: event.context,
|
|
||||||
builder: (context) => AlertDialog(
|
|
||||||
title: const Text('Effective Time cannot be earlier than current time.'),
|
|
||||||
actionsAlignment: MainAxisAlignment.center,
|
|
||||||
content:
|
|
||||||
FilledButton(
|
|
||||||
onPressed: () {
|
|
||||||
Navigator.of(event.context).pop();
|
|
||||||
add(SelectTimeVisitorPassword(context: event.context, isStart: true, isRepeat: false));
|
|
||||||
},
|
|
||||||
child: const Text('OK'),
|
|
||||||
),
|
|
||||||
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
effectiveTimeTimeStamp = selectedTimestamp;
|
effectiveTimeTimeStamp = selectedTimestamp;
|
||||||
startTimeAccess = selectedDateTime.toString().split('.').first;
|
startTimeAccess = selectedDateTime.toString().split('.').first;
|
||||||
} else {
|
} else {
|
||||||
|
@ -2,13 +2,11 @@ class FailedOperation {
|
|||||||
final bool success;
|
final bool success;
|
||||||
final dynamic deviceUuid;
|
final dynamic deviceUuid;
|
||||||
final dynamic error;
|
final dynamic error;
|
||||||
final String deviceName;
|
|
||||||
|
|
||||||
FailedOperation({
|
FailedOperation({
|
||||||
required this.success,
|
required this.success,
|
||||||
required this.deviceUuid,
|
required this.deviceUuid,
|
||||||
required this.error,
|
required this.error,
|
||||||
required this.deviceName,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
factory FailedOperation.fromJson(Map<String, dynamic> json) {
|
factory FailedOperation.fromJson(Map<String, dynamic> json) {
|
||||||
@ -16,7 +14,6 @@ class FailedOperation {
|
|||||||
success: json['success'],
|
success: json['success'],
|
||||||
deviceUuid: json['deviceUuid'],
|
deviceUuid: json['deviceUuid'],
|
||||||
error: json['error'],
|
error: json['error'],
|
||||||
deviceName: json['deviceName'] as String? ?? '',
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -25,22 +22,21 @@ class FailedOperation {
|
|||||||
'success': success,
|
'success': success,
|
||||||
'deviceUuid': deviceUuid,
|
'deviceUuid': deviceUuid,
|
||||||
'error': error,
|
'error': error,
|
||||||
'deviceName': deviceName,
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class SuccessOperation {
|
class SuccessOperation {
|
||||||
final bool success;
|
final bool success;
|
||||||
// final Result result;
|
// final Result result;
|
||||||
final String deviceUuid;
|
final String deviceUuid;
|
||||||
final String deviceName;
|
|
||||||
|
|
||||||
SuccessOperation({
|
SuccessOperation({
|
||||||
required this.success,
|
required this.success,
|
||||||
// required this.result,
|
// required this.result,
|
||||||
required this.deviceUuid,
|
required this.deviceUuid,
|
||||||
required this.deviceName,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
factory SuccessOperation.fromJson(Map<String, dynamic> json) {
|
factory SuccessOperation.fromJson(Map<String, dynamic> json) {
|
||||||
@ -48,7 +44,6 @@ class SuccessOperation {
|
|||||||
success: json['success'],
|
success: json['success'],
|
||||||
// result: Result.fromJson(json['result']),
|
// result: Result.fromJson(json['result']),
|
||||||
deviceUuid: json['deviceUuid'],
|
deviceUuid: json['deviceUuid'],
|
||||||
deviceName: json['deviceName'] as String? ?? '',
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -57,7 +52,6 @@ class SuccessOperation {
|
|||||||
'success': success,
|
'success': success,
|
||||||
// 'result': result.toJson(),
|
// 'result': result.toJson(),
|
||||||
'deviceUuid': deviceUuid,
|
'deviceUuid': deviceUuid,
|
||||||
'deviceName': deviceName,
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -98,6 +92,8 @@ class SuccessOperation {
|
|||||||
// }
|
// }
|
||||||
// }
|
// }
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class PasswordStatus {
|
class PasswordStatus {
|
||||||
final List<SuccessOperation> successOperations;
|
final List<SuccessOperation> successOperations;
|
||||||
final List<FailedOperation> failedOperations;
|
final List<FailedOperation> failedOperations;
|
||||||
@ -125,3 +121,4 @@ class PasswordStatus {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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 = '';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
@ -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 visitor’s 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 visitor’s email address.',
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const Spacer(),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
@ -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);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
@ -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_bloc.dart';
|
||||||
import 'package:syncrow_web/pages/visitor_password/bloc/visitor_password_event.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/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/add_device_dialog.dart';
|
||||||
import 'package:syncrow_web/pages/visitor_password/view/repeat_widget.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/color_manager.dart';
|
||||||
import 'package:syncrow_web/utils/constants/assets.dart';
|
import 'package:syncrow_web/utils/constants/assets.dart';
|
||||||
import 'package:syncrow_web/utils/style.dart';
|
import 'package:syncrow_web/utils/style.dart';
|
||||||
@ -24,10 +21,7 @@ class VisitorPasswordDialog extends StatelessWidget {
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
Size size = MediaQuery.of(context).size;
|
Size size = MediaQuery.of(context).size;
|
||||||
var text = Theme.of(context)
|
var text = Theme.of(context).textTheme.bodySmall!.copyWith(color: Colors.black, fontSize: 13);
|
||||||
.textTheme
|
|
||||||
.bodySmall!
|
|
||||||
.copyWith(color: Colors.black, fontSize: 13);
|
|
||||||
return BlocProvider(
|
return BlocProvider(
|
||||||
create: (context) => VisitorPasswordBloc(),
|
create: (context) => VisitorPasswordBloc(),
|
||||||
child: BlocListener<VisitorPasswordBloc, VisitorPasswordState>(
|
child: BlocListener<VisitorPasswordBloc, VisitorPasswordState>(
|
||||||
@ -41,8 +35,7 @@ class VisitorPasswordDialog extends StatelessWidget {
|
|||||||
title: 'Sent Successfully',
|
title: 'Sent Successfully',
|
||||||
widgeta: Column(
|
widgeta: Column(
|
||||||
children: [
|
children: [
|
||||||
if (visitorBloc
|
if (visitorBloc.passwordStatus!.failedOperations.isNotEmpty)
|
||||||
.passwordStatus!.failedOperations.isNotEmpty)
|
|
||||||
Column(
|
Column(
|
||||||
children: [
|
children: [
|
||||||
const Text('Failed Devices'),
|
const Text('Failed Devices'),
|
||||||
@ -52,8 +45,7 @@ class VisitorPasswordDialog extends StatelessWidget {
|
|||||||
child: ListView.builder(
|
child: ListView.builder(
|
||||||
scrollDirection: Axis.horizontal,
|
scrollDirection: Axis.horizontal,
|
||||||
shrinkWrap: true,
|
shrinkWrap: true,
|
||||||
itemCount: visitorBloc
|
itemCount: visitorBloc.passwordStatus!.failedOperations.length,
|
||||||
.passwordStatus!.failedOperations.length,
|
|
||||||
itemBuilder: (context, index) {
|
itemBuilder: (context, index) {
|
||||||
return Container(
|
return Container(
|
||||||
margin: EdgeInsets.all(5),
|
margin: EdgeInsets.all(5),
|
||||||
@ -61,17 +53,14 @@ class VisitorPasswordDialog extends StatelessWidget {
|
|||||||
height: 45,
|
height: 45,
|
||||||
child: Center(
|
child: Center(
|
||||||
child: Text(visitorBloc
|
child: Text(visitorBloc
|
||||||
.passwordStatus!
|
.passwordStatus!.failedOperations[index].deviceUuid)),
|
||||||
.failedOperations[index]
|
|
||||||
.deviceName)),
|
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
if (visitorBloc
|
if (visitorBloc.passwordStatus!.successOperations.isNotEmpty)
|
||||||
.passwordStatus!.successOperations.isNotEmpty)
|
|
||||||
Column(
|
Column(
|
||||||
children: [
|
children: [
|
||||||
const Text('Success Devices'),
|
const Text('Success Devices'),
|
||||||
@ -81,18 +70,15 @@ class VisitorPasswordDialog extends StatelessWidget {
|
|||||||
child: ListView.builder(
|
child: ListView.builder(
|
||||||
scrollDirection: Axis.horizontal,
|
scrollDirection: Axis.horizontal,
|
||||||
shrinkWrap: true,
|
shrinkWrap: true,
|
||||||
itemCount: visitorBloc
|
itemCount: visitorBloc.passwordStatus!.successOperations.length,
|
||||||
.passwordStatus!.successOperations.length,
|
|
||||||
itemBuilder: (context, index) {
|
itemBuilder: (context, index) {
|
||||||
return Container(
|
return Container(
|
||||||
margin: EdgeInsets.all(5),
|
margin: EdgeInsets.all(5),
|
||||||
decoration: containerDecoration,
|
decoration: containerDecoration,
|
||||||
height: 45,
|
height: 45,
|
||||||
child: Center(
|
child: Center(
|
||||||
child: Text(visitorBloc
|
child: Text(visitorBloc.passwordStatus!
|
||||||
.passwordStatus!
|
.successOperations[index].deviceUuid)),
|
||||||
.successOperations[index]
|
|
||||||
.deviceName)),
|
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
@ -102,7 +88,8 @@ class VisitorPasswordDialog extends StatelessWidget {
|
|||||||
],
|
],
|
||||||
))
|
))
|
||||||
.then((v) {
|
.then((v) {
|
||||||
Navigator.of(context).pop(v);
|
Navigator.of(context).pop(true);
|
||||||
|
|
||||||
});
|
});
|
||||||
} else if (state is FailedState) {
|
} else if (state is FailedState) {
|
||||||
visitorBloc.stateDialog(
|
visitorBloc.stateDialog(
|
||||||
@ -115,16 +102,15 @@ class VisitorPasswordDialog extends StatelessWidget {
|
|||||||
child: BlocBuilder<VisitorPasswordBloc, VisitorPasswordState>(
|
child: BlocBuilder<VisitorPasswordBloc, VisitorPasswordState>(
|
||||||
builder: (BuildContext context, VisitorPasswordState state) {
|
builder: (BuildContext context, VisitorPasswordState state) {
|
||||||
final visitorBloc = BlocProvider.of<VisitorPasswordBloc>(context);
|
final visitorBloc = BlocProvider.of<VisitorPasswordBloc>(context);
|
||||||
bool isRepeat =
|
bool isRepeat = state is IsRepeatState ? state.repeat : visitorBloc.repeat;
|
||||||
state is IsRepeatState ? state.repeat : visitorBloc.repeat;
|
|
||||||
return AlertDialog(
|
return AlertDialog(
|
||||||
backgroundColor: Colors.white,
|
backgroundColor: Colors.white,
|
||||||
title: Text(
|
title: Text(
|
||||||
'Create visitor password',
|
'Create visitor password',
|
||||||
style: Theme.of(context).textTheme.headlineLarge!.copyWith(
|
style: Theme.of(context)
|
||||||
fontWeight: FontWeight.w400,
|
.textTheme
|
||||||
fontSize: 24,
|
.headlineLarge!
|
||||||
color: Colors.black),
|
.copyWith(fontWeight: FontWeight.w400, fontSize: 24, color: Colors.black),
|
||||||
),
|
),
|
||||||
content: state is LoadingInitialState
|
content: state is LoadingInitialState
|
||||||
? const Center(child: CircularProgressIndicator())
|
? const Center(child: CircularProgressIndicator())
|
||||||
@ -135,11 +121,34 @@ class VisitorPasswordDialog extends StatelessWidget {
|
|||||||
padding: const EdgeInsets.all(5.0),
|
padding: const EdgeInsets.all(5.0),
|
||||||
child: ListBody(
|
child: ListBody(
|
||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
NameAndEmailFields(
|
Container(
|
||||||
nameController: visitorBloc.userNameController,
|
child: Row(
|
||||||
emailController: visitorBloc.emailController,
|
children: [
|
||||||
nameValidator: visitorBloc.validate,
|
Expanded(
|
||||||
emailValidator: visitorBloc.validateEmail,
|
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 visitor’s email address.',
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const Spacer(),
|
||||||
|
],
|
||||||
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(
|
const SizedBox(
|
||||||
height: 15,
|
height: 15,
|
||||||
@ -147,40 +156,104 @@ class VisitorPasswordDialog extends StatelessWidget {
|
|||||||
Column(
|
Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
AccessTypeRadioGroup(
|
Row(
|
||||||
selectedType: state is PasswordTypeSelected
|
children: [
|
||||||
|
Text(
|
||||||
|
'* ',
|
||||||
|
style: Theme.of(context)
|
||||||
|
.textTheme
|
||||||
|
.bodyMedium!
|
||||||
|
.copyWith(color: Colors.red),
|
||||||
|
),
|
||||||
|
Text('Access Type', style: text),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
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
|
? state.selectedType
|
||||||
: null,
|
: visitorBloc.accessTypeSelected,
|
||||||
accessTypeSelected:
|
onChanged: (String? value) {
|
||||||
visitorBloc.accessTypeSelected,
|
if (value != null) {
|
||||||
onTypeSelected: (value) {
|
|
||||||
context
|
context
|
||||||
.read<VisitorPasswordBloc>()
|
.read<VisitorPasswordBloc>()
|
||||||
.add(SelectPasswordType(value));
|
.add(SelectPasswordType(value));
|
||||||
|
}
|
||||||
},
|
},
|
||||||
visitorBloc: visitorBloc,
|
|
||||||
),
|
),
|
||||||
|
),
|
||||||
if (visitorBloc.accessTypeSelected ==
|
SizedBox(
|
||||||
'Online Password')
|
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(
|
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',
|
'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)
|
style: Theme.of(context).textTheme.bodySmall!.copyWith(
|
||||||
.textTheme
|
|
||||||
.bodySmall!
|
|
||||||
.copyWith(
|
|
||||||
fontWeight: FontWeight.w400,
|
fontWeight: FontWeight.w400,
|
||||||
color: ColorsManager.grayColor,
|
color: ColorsManager.grayColor,
|
||||||
fontSize: 9),
|
fontSize: 9),
|
||||||
),
|
),
|
||||||
if (visitorBloc.accessTypeSelected ==
|
if (visitorBloc.accessTypeSelected == 'Offline Password')
|
||||||
'Offline Password')
|
|
||||||
Text(
|
Text(
|
||||||
'Unaffected by the online status of the device, you can select online or offline device, and the system randomly generates a digital password',
|
'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)
|
style: Theme.of(context).textTheme.bodySmall!.copyWith(
|
||||||
.textTheme
|
|
||||||
.bodySmall!
|
|
||||||
.copyWith(
|
|
||||||
fontWeight: FontWeight.w400,
|
fontWeight: FontWeight.w400,
|
||||||
color: ColorsManager.grayColor,
|
color: ColorsManager.grayColor,
|
||||||
fontSize: 9),
|
fontSize: 9),
|
||||||
@ -198,11 +271,9 @@ class VisitorPasswordDialog extends StatelessWidget {
|
|||||||
)
|
)
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
if (visitorBloc.accessTypeSelected ==
|
visitorBloc.accessTypeSelected == 'Dynamic Password'
|
||||||
'Dynamic Password')
|
? const SizedBox()
|
||||||
const SizedBox()
|
: Column(
|
||||||
else
|
|
||||||
Column(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
Row(
|
Row(
|
||||||
@ -220,148 +291,123 @@ class VisitorPasswordDialog extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
UsageFrequencyRadioGroup(
|
Row(
|
||||||
selectedFrequency:
|
children: <Widget>[
|
||||||
state is UsageFrequencySelected
|
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
|
? state.selectedFrequency
|
||||||
: null,
|
: visitorBloc.usageFrequencySelected,
|
||||||
usageFrequencySelected:
|
onChanged: (String? value) {
|
||||||
visitorBloc.usageFrequencySelected,
|
if (value != null) {
|
||||||
onFrequencySelected: (value) {
|
|
||||||
context
|
context
|
||||||
.read<VisitorPasswordBloc>()
|
.read<VisitorPasswordBloc>()
|
||||||
.add(SelectUsageFrequency(value));
|
.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
|
//One-Time
|
||||||
if (visitorBloc.usageFrequencySelected ==
|
if (visitorBloc.usageFrequencySelected == 'One-Time' &&
|
||||||
'One-Time' &&
|
visitorBloc.accessTypeSelected == 'Online Password')
|
||||||
visitorBloc.accessTypeSelected ==
|
|
||||||
'Online Password')
|
|
||||||
Text(
|
Text(
|
||||||
'Within the validity period, each device can be unlocked only once.',
|
'Within the validity period, each device can be unlocked only once.',
|
||||||
style: Theme.of(context)
|
style: Theme.of(context).textTheme.bodySmall!.copyWith(
|
||||||
.textTheme
|
color: ColorsManager.grayColor, fontSize: 9),
|
||||||
.bodySmall!
|
|
||||||
.copyWith(
|
|
||||||
color: ColorsManager.grayColor,
|
|
||||||
fontSize: 9),
|
|
||||||
),
|
),
|
||||||
if (visitorBloc.usageFrequencySelected ==
|
if (visitorBloc.usageFrequencySelected == 'One-Time' &&
|
||||||
'One-Time' &&
|
visitorBloc.accessTypeSelected == 'Offline Password')
|
||||||
visitorBloc.accessTypeSelected ==
|
|
||||||
'Offline Password')
|
|
||||||
Text(
|
Text(
|
||||||
'Within the validity period, each device can be unlocked only once, and the maximum validity period is 6 hours',
|
'Within the validity period, each device can be unlocked only once, and the maximum validity period is 6 hours',
|
||||||
style: Theme.of(context)
|
style: Theme.of(context).textTheme.bodySmall!.copyWith(
|
||||||
.textTheme
|
color: ColorsManager.grayColor, fontSize: 9),
|
||||||
.bodySmall!
|
|
||||||
.copyWith(
|
|
||||||
color: ColorsManager.grayColor,
|
|
||||||
fontSize: 9),
|
|
||||||
),
|
),
|
||||||
|
|
||||||
// Periodic
|
// Periodic
|
||||||
if (visitorBloc.usageFrequencySelected ==
|
if (visitorBloc.usageFrequencySelected == 'Periodic' &&
|
||||||
'Periodic' &&
|
visitorBloc.accessTypeSelected == 'Offline Password')
|
||||||
visitorBloc.accessTypeSelected ==
|
|
||||||
'Offline Password')
|
|
||||||
Text(
|
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.',
|
'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)
|
style: Theme.of(context).textTheme.bodySmall!.copyWith(
|
||||||
.textTheme
|
color: ColorsManager.grayColor, fontSize: 9),
|
||||||
.bodySmall!
|
|
||||||
.copyWith(
|
|
||||||
color: ColorsManager.grayColor,
|
|
||||||
fontSize: 9),
|
|
||||||
),
|
),
|
||||||
|
|
||||||
if (visitorBloc.usageFrequencySelected ==
|
if (visitorBloc.usageFrequencySelected == 'Periodic' &&
|
||||||
'Periodic' &&
|
visitorBloc.accessTypeSelected == 'Online Password')
|
||||||
visitorBloc.accessTypeSelected ==
|
|
||||||
'Online Password')
|
|
||||||
Text(
|
Text(
|
||||||
'Within the validity period, there is no limit to the number of times each device can be unlocked.',
|
'Within the validity period, there is no limit to the number of times each device can be unlocked.',
|
||||||
style: Theme.of(context)
|
style: Theme.of(context).textTheme.bodySmall!.copyWith(
|
||||||
.textTheme
|
color: ColorsManager.grayColor, fontSize: 9),
|
||||||
.bodySmall!
|
|
||||||
.copyWith(
|
|
||||||
color: ColorsManager.grayColor,
|
|
||||||
fontSize: 9),
|
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
const SizedBox(
|
const SizedBox(
|
||||||
height: 20,
|
height: 20,
|
||||||
),
|
),
|
||||||
if ((visitorBloc.usageFrequencySelected !=
|
if ((visitorBloc.usageFrequencySelected != 'One-Time' ||
|
||||||
'One-Time' ||
|
visitorBloc.accessTypeSelected != 'Offline Password') &&
|
||||||
visitorBloc.accessTypeSelected !=
|
|
||||||
'Offline Password') &&
|
|
||||||
(visitorBloc.usageFrequencySelected != ''))
|
(visitorBloc.usageFrequencySelected != ''))
|
||||||
DateTimeWebWidget(
|
DateTimeWebWidget(
|
||||||
isRequired: true,
|
isRequired: true,
|
||||||
title: 'Access Period',
|
title: 'Access Period',
|
||||||
size: size,
|
size: size,
|
||||||
endTime: () {
|
endTime: () {
|
||||||
if (visitorBloc.usageFrequencySelected ==
|
if (visitorBloc.usageFrequencySelected == 'Periodic' &&
|
||||||
'Periodic' &&
|
visitorBloc.accessTypeSelected == 'Offline Password') {
|
||||||
visitorBloc.accessTypeSelected ==
|
visitorBloc.add(SelectTimeEvent(context: context, isEffective: false));
|
||||||
'Offline Password') {
|
|
||||||
visitorBloc.add(SelectTimeEvent(
|
|
||||||
context: context,
|
|
||||||
isEffective: false));
|
|
||||||
} else {
|
} else {
|
||||||
visitorBloc.add(
|
visitorBloc.add(SelectTimeVisitorPassword(context: context, isStart: false, isRepeat: false));
|
||||||
SelectTimeVisitorPassword(
|
|
||||||
context: context,
|
|
||||||
isStart: false,
|
|
||||||
isRepeat: false));
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
startTime: () {
|
startTime: () {
|
||||||
if (visitorBloc.usageFrequencySelected ==
|
if (visitorBloc.usageFrequencySelected == 'Periodic' &&
|
||||||
'Periodic' &&
|
visitorBloc.accessTypeSelected == 'Offline Password') {
|
||||||
visitorBloc.accessTypeSelected ==
|
|
||||||
'Offline Password') {
|
|
||||||
visitorBloc.add(SelectTimeEvent(
|
|
||||||
context: context,
|
|
||||||
isEffective: true));
|
|
||||||
} else {
|
|
||||||
visitorBloc.add(
|
visitorBloc.add(
|
||||||
SelectTimeVisitorPassword(
|
SelectTimeEvent(context: context, isEffective: true));
|
||||||
context: context,
|
} else {
|
||||||
isStart: true,
|
visitorBloc.add(SelectTimeVisitorPassword(
|
||||||
isRepeat: false));
|
context: context, isStart: true, isRepeat: false));
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
firstString: (visitorBloc
|
firstString: (visitorBloc.usageFrequencySelected ==
|
||||||
.usageFrequencySelected ==
|
'Periodic' && visitorBloc.accessTypeSelected == 'Offline Password')
|
||||||
'Periodic' &&
|
|
||||||
visitorBloc.accessTypeSelected ==
|
|
||||||
'Offline Password')
|
|
||||||
? visitorBloc.effectiveTime
|
? visitorBloc.effectiveTime
|
||||||
: visitorBloc.startTimeAccess
|
: visitorBloc.startTimeAccess.toString(),
|
||||||
.toString(),
|
secondString: (visitorBloc.usageFrequencySelected ==
|
||||||
secondString: (visitorBloc
|
'Periodic' && visitorBloc.accessTypeSelected == 'Offline Password')
|
||||||
.usageFrequencySelected ==
|
|
||||||
'Periodic' &&
|
|
||||||
visitorBloc.accessTypeSelected ==
|
|
||||||
'Offline Password')
|
|
||||||
? visitorBloc.expirationTime
|
? visitorBloc.expirationTime
|
||||||
: visitorBloc.endTimeAccess.toString(),
|
: visitorBloc.endTimeAccess.toString(),
|
||||||
icon: Assets.calendarIcon),
|
icon: Assets.calendarIcon),
|
||||||
const SizedBox(
|
const SizedBox(height: 10,),
|
||||||
height: 10,
|
Text(visitorBloc.accessPeriodValidate,
|
||||||
),
|
style: Theme.of(context).textTheme.bodyMedium!.copyWith(color: ColorsManager.red),),
|
||||||
Text(
|
|
||||||
visitorBloc.accessPeriodValidate,
|
|
||||||
style: Theme.of(context)
|
|
||||||
.textTheme
|
|
||||||
.bodyMedium!
|
|
||||||
.copyWith(color: ColorsManager.red),
|
|
||||||
),
|
|
||||||
const SizedBox(
|
const SizedBox(
|
||||||
height: 20,
|
height: 20,
|
||||||
),
|
),
|
||||||
@ -385,10 +431,7 @@ class VisitorPasswordDialog extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
Text(
|
Text(
|
||||||
'Within the validity period, each device can be unlocked only once.',
|
'Within the validity period, each device can be unlocked only once.',
|
||||||
style: Theme.of(context)
|
style: Theme.of(context).textTheme.bodySmall!.copyWith(
|
||||||
.textTheme
|
|
||||||
.bodySmall!
|
|
||||||
.copyWith(
|
|
||||||
fontWeight: FontWeight.w400,
|
fontWeight: FontWeight.w400,
|
||||||
color: ColorsManager.grayColor,
|
color: ColorsManager.grayColor,
|
||||||
fontSize: 9),
|
fontSize: 9),
|
||||||
@ -396,10 +439,8 @@ class VisitorPasswordDialog extends StatelessWidget {
|
|||||||
const SizedBox(
|
const SizedBox(
|
||||||
height: 20,
|
height: 20,
|
||||||
),
|
),
|
||||||
if (visitorBloc.usageFrequencySelected ==
|
if (visitorBloc.usageFrequencySelected == 'Periodic' &&
|
||||||
'Periodic' &&
|
visitorBloc.accessTypeSelected == 'Online Password')
|
||||||
visitorBloc.accessTypeSelected ==
|
|
||||||
'Online Password')
|
|
||||||
SizedBox(
|
SizedBox(
|
||||||
width: 100,
|
width: 100,
|
||||||
child: Column(
|
child: Column(
|
||||||
@ -410,8 +451,7 @@ class VisitorPasswordDialog extends StatelessWidget {
|
|||||||
child: CupertinoSwitch(
|
child: CupertinoSwitch(
|
||||||
value: visitorBloc.repeat,
|
value: visitorBloc.repeat,
|
||||||
onChanged: (value) {
|
onChanged: (value) {
|
||||||
visitorBloc
|
visitorBloc.add(ToggleRepeatEvent());
|
||||||
.add(ToggleRepeatEvent());
|
|
||||||
},
|
},
|
||||||
applyTheme: true,
|
applyTheme: true,
|
||||||
),
|
),
|
||||||
@ -419,16 +459,12 @@ class VisitorPasswordDialog extends StatelessWidget {
|
|||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
if (visitorBloc.usageFrequencySelected ==
|
if (visitorBloc.usageFrequencySelected == 'Periodic' &&
|
||||||
'Periodic' &&
|
visitorBloc.accessTypeSelected == 'Online Password')
|
||||||
visitorBloc.accessTypeSelected ==
|
isRepeat ? const RepeatWidget() : const SizedBox(),
|
||||||
'Online Password')
|
|
||||||
isRepeat
|
|
||||||
? const RepeatWidget()
|
|
||||||
: const SizedBox(),
|
|
||||||
Container(
|
Container(
|
||||||
decoration: containerDecoration,
|
decoration: containerDecoration,
|
||||||
width: size.width / 6,
|
width: size.width / 9,
|
||||||
child: DefaultButton(
|
child: DefaultButton(
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
showDialog(
|
showDialog(
|
||||||
@ -436,27 +472,21 @@ class VisitorPasswordDialog extends StatelessWidget {
|
|||||||
barrierDismissible: false,
|
barrierDismissible: false,
|
||||||
builder: (BuildContext context) {
|
builder: (BuildContext context) {
|
||||||
return AddDeviceDialog(
|
return AddDeviceDialog(
|
||||||
selectedDeviceIds:
|
selectedDeviceIds: visitorBloc.selectedDevices,
|
||||||
visitorBloc.selectedDevices,
|
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
).then((listDevice) {
|
).then((listDevice) {
|
||||||
if (listDevice != null) {
|
if (listDevice != null) {
|
||||||
visitorBloc.selectedDevices =
|
visitorBloc.selectedDevices = listDevice;
|
||||||
listDevice;
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
borderRadius: 8,
|
borderRadius: 8,
|
||||||
child: Text(
|
child: Text(
|
||||||
'+ Add Device',
|
'+ Add Device',
|
||||||
style: Theme.of(context)
|
style: Theme.of(context).textTheme.bodySmall!.copyWith(
|
||||||
.textTheme
|
|
||||||
.bodySmall!
|
|
||||||
.copyWith(
|
|
||||||
fontWeight: FontWeight.w400,
|
fontWeight: FontWeight.w400,
|
||||||
color:
|
color: ColorsManager.whiteColors,
|
||||||
ColorsManager.whiteColors,
|
|
||||||
fontSize: 12),
|
fontSize: 12),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@ -476,7 +506,7 @@ class VisitorPasswordDialog extends StatelessWidget {
|
|||||||
child: DefaultButton(
|
child: DefaultButton(
|
||||||
borderRadius: 8,
|
borderRadius: 8,
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
Navigator.of(context).pop(null);
|
Navigator.of(context).pop(true);
|
||||||
},
|
},
|
||||||
backgroundColor: Colors.white,
|
backgroundColor: Colors.white,
|
||||||
child: Text(
|
child: Text(
|
||||||
@ -495,37 +525,30 @@ class VisitorPasswordDialog extends StatelessWidget {
|
|||||||
onPressed: () {
|
onPressed: () {
|
||||||
if (visitorBloc.forgetFormKey.currentState!.validate()) {
|
if (visitorBloc.forgetFormKey.currentState!.validate()) {
|
||||||
if (visitorBloc.selectedDevices.isNotEmpty) {
|
if (visitorBloc.selectedDevices.isNotEmpty) {
|
||||||
if (visitorBloc.usageFrequencySelected ==
|
if (visitorBloc.usageFrequencySelected == 'One-Time' &&
|
||||||
'One-Time' &&
|
visitorBloc.accessTypeSelected == 'Offline Password') {
|
||||||
visitorBloc.accessTypeSelected ==
|
|
||||||
'Offline Password') {
|
|
||||||
setPasswordFunction(context, size, visitorBloc);
|
setPasswordFunction(context, size, visitorBloc);
|
||||||
} else if (visitorBloc.usageFrequencySelected ==
|
} else if (visitorBloc.usageFrequencySelected == 'Periodic' &&
|
||||||
'Periodic' &&
|
visitorBloc.accessTypeSelected == 'Offline Password') {
|
||||||
visitorBloc.accessTypeSelected ==
|
|
||||||
'Offline Password') {
|
|
||||||
if (visitorBloc.expirationTime != 'End Time' &&
|
if (visitorBloc.expirationTime != 'End Time' &&
|
||||||
visitorBloc.effectiveTime != 'Start Time') {
|
visitorBloc.effectiveTime != 'Start Time' ) {
|
||||||
setPasswordFunction(context, size, visitorBloc);
|
setPasswordFunction(context, size, visitorBloc);
|
||||||
} else {
|
}else{
|
||||||
visitorBloc.stateDialog(
|
visitorBloc.stateDialog(
|
||||||
context: context,
|
context: context,
|
||||||
message:
|
message: 'Please select Access Period to continue',
|
||||||
'Please select Access Period to continue',
|
|
||||||
title: 'Access Period');
|
title: 'Access Period');
|
||||||
}
|
}
|
||||||
} else if (visitorBloc.endTimeAccess.toString() !=
|
} else if(
|
||||||
'End Time' &&
|
visitorBloc.endTimeAccess.toString()!='End Time'
|
||||||
visitorBloc.startTimeAccess.toString() !=
|
&&visitorBloc.startTimeAccess.toString()!='Start Time') {
|
||||||
'Start Time') {
|
|
||||||
if (visitorBloc.effectiveTimeTimeStamp != null &&
|
if (visitorBloc.effectiveTimeTimeStamp != null &&
|
||||||
visitorBloc.expirationTimeTimeStamp != null) {
|
visitorBloc.expirationTimeTimeStamp != null) {
|
||||||
if (isRepeat == true) {
|
if (isRepeat == true) {
|
||||||
if (visitorBloc.expirationTime != 'End Time' &&
|
if (visitorBloc.expirationTime != 'End Time' &&
|
||||||
visitorBloc.effectiveTime != 'Start Time' &&
|
visitorBloc.effectiveTime != 'Start Time' &&
|
||||||
visitorBloc.selectedDays.isNotEmpty) {
|
visitorBloc.selectedDays.isNotEmpty) {
|
||||||
setPasswordFunction(
|
setPasswordFunction(context, size, visitorBloc);
|
||||||
context, size, visitorBloc);
|
|
||||||
} else {
|
} else {
|
||||||
visitorBloc.stateDialog(
|
visitorBloc.stateDialog(
|
||||||
context: context,
|
context: context,
|
||||||
@ -539,15 +562,13 @@ class VisitorPasswordDialog extends StatelessWidget {
|
|||||||
} else {
|
} else {
|
||||||
visitorBloc.stateDialog(
|
visitorBloc.stateDialog(
|
||||||
context: context,
|
context: context,
|
||||||
message:
|
message: 'Please select Access Period to continue',
|
||||||
'Please select Access Period to continue',
|
|
||||||
title: 'Access Period');
|
title: 'Access Period');
|
||||||
}
|
}
|
||||||
} else {
|
}else{
|
||||||
visitorBloc.stateDialog(
|
visitorBloc.stateDialog(
|
||||||
context: context,
|
context: context,
|
||||||
message:
|
message: 'Please select Access Period to continue',
|
||||||
'Please select Access Period to continue',
|
|
||||||
title: 'Access Period');
|
title: 'Access Period');
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@ -594,8 +615,7 @@ class VisitorPasswordDialog extends StatelessWidget {
|
|||||||
content: SizedBox(
|
content: SizedBox(
|
||||||
height: size.height * 0.25,
|
height: size.height * 0.25,
|
||||||
child: Center(
|
child: Center(
|
||||||
child:
|
child: CircularProgressIndicator(), // Display a loading spinner
|
||||||
CircularProgressIndicator(), // Display a loading spinner
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
@ -619,10 +639,7 @@ class VisitorPasswordDialog extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
Text(
|
Text(
|
||||||
'Set Password',
|
'Set Password',
|
||||||
style: Theme.of(context)
|
style: Theme.of(context).textTheme.headlineLarge!.copyWith(
|
||||||
.textTheme
|
|
||||||
.headlineLarge!
|
|
||||||
.copyWith(
|
|
||||||
fontSize: 30,
|
fontSize: 30,
|
||||||
fontWeight: FontWeight.w400,
|
fontWeight: FontWeight.w400,
|
||||||
color: Colors.black,
|
color: Colors.black,
|
||||||
@ -651,7 +668,7 @@ class VisitorPasswordDialog extends StatelessWidget {
|
|||||||
child: DefaultButton(
|
child: DefaultButton(
|
||||||
borderRadius: 8,
|
borderRadius: 8,
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
Navigator.of(context).pop(null);
|
Navigator.of(context).pop();
|
||||||
},
|
},
|
||||||
backgroundColor: Colors.white,
|
backgroundColor: Colors.white,
|
||||||
child: Text(
|
child: Text(
|
||||||
@ -672,45 +689,37 @@ class VisitorPasswordDialog extends StatelessWidget {
|
|||||||
onPressed: () {
|
onPressed: () {
|
||||||
Navigator.pop(context);
|
Navigator.pop(context);
|
||||||
if (visitorBloc.usageFrequencySelected == 'One-Time' &&
|
if (visitorBloc.usageFrequencySelected == 'One-Time' &&
|
||||||
visitorBloc.accessTypeSelected ==
|
visitorBloc.accessTypeSelected == 'Online Password') {
|
||||||
'Online Password') {
|
|
||||||
visitorBloc.add(OnlineOneTimePasswordEvent(
|
visitorBloc.add(OnlineOneTimePasswordEvent(
|
||||||
context: context,
|
context: context,
|
||||||
passwordName: visitorBloc.userNameController.text,
|
passwordName: visitorBloc.userNameController.text,
|
||||||
email: visitorBloc.emailController.text,
|
email: visitorBloc.emailController.text,
|
||||||
));
|
));
|
||||||
} else if (visitorBloc.usageFrequencySelected ==
|
}
|
||||||
'Periodic' &&
|
else if (visitorBloc.usageFrequencySelected == 'Periodic' &&
|
||||||
visitorBloc.accessTypeSelected ==
|
visitorBloc.accessTypeSelected == 'Online Password') {
|
||||||
'Online Password') {
|
|
||||||
visitorBloc.add(OnlineMultipleTimePasswordEvent(
|
visitorBloc.add(OnlineMultipleTimePasswordEvent(
|
||||||
passwordName: visitorBloc.userNameController.text,
|
passwordName: visitorBloc.userNameController.text,
|
||||||
email: visitorBloc.emailController.text,
|
email: visitorBloc.emailController.text,
|
||||||
effectiveTime:
|
effectiveTime: visitorBloc.effectiveTimeTimeStamp.toString(),
|
||||||
visitorBloc.effectiveTimeTimeStamp.toString(),
|
invalidTime: visitorBloc.expirationTimeTimeStamp.toString(),
|
||||||
invalidTime:
|
|
||||||
visitorBloc.expirationTimeTimeStamp.toString(),
|
|
||||||
));
|
));
|
||||||
} else if (visitorBloc.usageFrequencySelected ==
|
}
|
||||||
'One-Time' &&
|
else if (visitorBloc.usageFrequencySelected == 'One-Time' &&
|
||||||
visitorBloc.accessTypeSelected ==
|
visitorBloc.accessTypeSelected == 'Offline Password') {
|
||||||
'Offline Password') {
|
|
||||||
visitorBloc.add(OfflineOneTimePasswordEvent(
|
visitorBloc.add(OfflineOneTimePasswordEvent(
|
||||||
context: context,
|
context: context,
|
||||||
passwordName: visitorBloc.userNameController.text,
|
passwordName: visitorBloc.userNameController.text,
|
||||||
email: visitorBloc.emailController.text,
|
email: visitorBloc.emailController.text,
|
||||||
));
|
));
|
||||||
} else if (visitorBloc.usageFrequencySelected ==
|
}
|
||||||
'Periodic' &&
|
else if (visitorBloc.usageFrequencySelected == 'Periodic' &&
|
||||||
visitorBloc.accessTypeSelected ==
|
visitorBloc.accessTypeSelected == 'Offline Password') {
|
||||||
'Offline Password') {
|
|
||||||
visitorBloc.add(OfflineMultipleTimePasswordEvent(
|
visitorBloc.add(OfflineMultipleTimePasswordEvent(
|
||||||
passwordName: visitorBloc.userNameController.text,
|
passwordName: visitorBloc.userNameController.text,
|
||||||
email: visitorBloc.emailController.text,
|
email: visitorBloc.emailController.text,
|
||||||
effectiveTime:
|
effectiveTime: visitorBloc.effectiveTimeTimeStamp.toString(),
|
||||||
visitorBloc.effectiveTimeTimeStamp.toString(),
|
invalidTime: visitorBloc.expirationTimeTimeStamp.toString(),
|
||||||
invalidTime:
|
|
||||||
visitorBloc.expirationTimeTimeStamp.toString(),
|
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
Reference in New Issue
Block a user