mirror of
https://github.com/SyncrowIOT/web.git
synced 2025-07-15 01:35:25 +00:00
Compare commits
31 Commits
SP-1713-Im
...
On-Access-
Author | SHA1 | Date | |
---|---|---|---|
562c67a958 | |||
423ad6e687 | |||
932e50f518 | |||
c649044a1f | |||
c46cfb04a7 | |||
8754960713 | |||
c6e98fa245 | |||
277a9ce4f0 | |||
db9e856bca | |||
07435ec89e | |||
2a2fb7ffca | |||
5a2299ea2f | |||
90f8305aa1 | |||
329b2ba472 | |||
0fb9149613 | |||
87b45fff1d | |||
95ae50d12d | |||
95d6e1ecda | |||
479aa4a091 | |||
03a6c5474b | |||
5f30a5a61b | |||
0712e6d64b | |||
1f82e84115 | |||
23c3bf11f9 | |||
5201a65a97 | |||
e4cc5fce50 | |||
8dea89db0e | |||
ad5ada9d55 | |||
7172a0e3fb | |||
78898968e8 | |||
666c64231f |
@ -38,9 +38,9 @@ class RangeOfAqiValue extends Equatable {
|
|||||||
factory RangeOfAqiValue.fromJson(Map<String, dynamic> json) {
|
factory RangeOfAqiValue.fromJson(Map<String, dynamic> json) {
|
||||||
return RangeOfAqiValue(
|
return RangeOfAqiValue(
|
||||||
type: json['type'] as String,
|
type: json['type'] as String,
|
||||||
min: (json['min'] as num).toDouble(),
|
min: (json['min'] as num? ?? 0).toDouble(),
|
||||||
average: (json['average'] as num).toDouble(),
|
average: (json['average'] as num? ?? 0).toDouble(),
|
||||||
max: (json['max'] as num).toDouble(),
|
max: (json['max'] as num? ?? 0).toDouble(),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -24,11 +24,13 @@ abstract final class FetchAirQualityDataHelper {
|
|||||||
}) {
|
}) {
|
||||||
final date = context.read<AnalyticsDatePickerBloc>().state.monthlyDate;
|
final date = context.read<AnalyticsDatePickerBloc>().state.monthlyDate;
|
||||||
final aqiType = context.read<AirQualityDistributionBloc>().state.selectedAqiType;
|
final aqiType = context.read<AirQualityDistributionBloc>().state.selectedAqiType;
|
||||||
loadAnalyticsDevices(
|
if (shouldFetchAnalyticsDevices) {
|
||||||
context,
|
loadAnalyticsDevices(
|
||||||
communityUuid: communityUuid,
|
context,
|
||||||
spaceUuid: spaceUuid,
|
communityUuid: communityUuid,
|
||||||
);
|
spaceUuid: spaceUuid,
|
||||||
|
);
|
||||||
|
}
|
||||||
loadRangeOfAqi(
|
loadRangeOfAqi(
|
||||||
context,
|
context,
|
||||||
spaceUuid: spaceUuid,
|
spaceUuid: spaceUuid,
|
||||||
|
@ -23,6 +23,7 @@ abstract final class RangeOfAqiChartsHelper {
|
|||||||
return titlesData.copyWith(
|
return titlesData.copyWith(
|
||||||
bottomTitles: titlesData.bottomTitles.copyWith(
|
bottomTitles: titlesData.bottomTitles.copyWith(
|
||||||
sideTitles: titlesData.bottomTitles.sideTitles.copyWith(
|
sideTitles: titlesData.bottomTitles.sideTitles.copyWith(
|
||||||
|
reservedSize: 36,
|
||||||
getTitlesWidget: (value, meta) => Padding(
|
getTitlesWidget: (value, meta) => Padding(
|
||||||
padding: const EdgeInsetsDirectional.only(top: 20.0),
|
padding: const EdgeInsetsDirectional.only(top: 20.0),
|
||||||
child: Text(
|
child: Text(
|
||||||
@ -40,6 +41,7 @@ abstract final class RangeOfAqiChartsHelper {
|
|||||||
reservedSize: 70,
|
reservedSize: 70,
|
||||||
interval: 50,
|
interval: 50,
|
||||||
maxIncluded: false,
|
maxIncluded: false,
|
||||||
|
minIncluded: true,
|
||||||
getTitlesWidget: (value, meta) {
|
getTitlesWidget: (value, meta) {
|
||||||
final text = value >= 300 ? '301+' : value.toInt().toString();
|
final text = value >= 300 ? '301+' : value.toInt().toString();
|
||||||
return Padding(
|
return Padding(
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:syncrow_web/pages/analytics/modules/air_quality/widgets/air_quality_end_side_widget.dart';
|
import 'package:syncrow_web/pages/analytics/modules/air_quality/widgets/air_quality_end_side_widget.dart';
|
||||||
import 'package:syncrow_web/pages/analytics/modules/air_quality/widgets/aqi_distribution_chart_box.dart';
|
import 'package:syncrow_web/pages/analytics/modules/air_quality/widgets/aqi_distribution_chart_box.dart';
|
||||||
|
import 'package:syncrow_web/pages/analytics/modules/air_quality/widgets/aqi_legend.dart';
|
||||||
import 'package:syncrow_web/pages/analytics/modules/air_quality/widgets/range_of_aqi_chart_box.dart';
|
import 'package:syncrow_web/pages/analytics/modules/air_quality/widgets/range_of_aqi_chart_box.dart';
|
||||||
|
|
||||||
class AirQualityView extends StatelessWidget {
|
class AirQualityView extends StatelessWidget {
|
||||||
@ -20,6 +21,10 @@ class AirQualityView extends StatelessWidget {
|
|||||||
child: Column(
|
child: Column(
|
||||||
spacing: 32,
|
spacing: 32,
|
||||||
children: [
|
children: [
|
||||||
|
SizedBox(
|
||||||
|
height: height * 0.1,
|
||||||
|
child: const AqiLegend(),
|
||||||
|
),
|
||||||
SizedBox(
|
SizedBox(
|
||||||
height: height * 1.2,
|
height: height * 1.2,
|
||||||
child: const AirQualityEndSideWidget(),
|
child: const AirQualityEndSideWidget(),
|
||||||
@ -40,7 +45,7 @@ class AirQualityView extends StatelessWidget {
|
|||||||
return SingleChildScrollView(
|
return SingleChildScrollView(
|
||||||
child: Container(
|
child: Container(
|
||||||
padding: _padding,
|
padding: _padding,
|
||||||
height: height * 1.1,
|
height: height * 1.2,
|
||||||
child: const Column(
|
child: const Column(
|
||||||
children: [
|
children: [
|
||||||
Expanded(
|
Expanded(
|
||||||
@ -52,8 +57,9 @@ class AirQualityView extends StatelessWidget {
|
|||||||
child: Column(
|
child: Column(
|
||||||
spacing: 20,
|
spacing: 20,
|
||||||
children: [
|
children: [
|
||||||
Expanded(child: RangeOfAqiChartBox()),
|
Expanded(flex: 2, child: AqiLegend()),
|
||||||
Expanded(child: AqiDistributionChartBox()),
|
Expanded(flex: 12, child: RangeOfAqiChartBox()),
|
||||||
|
Expanded(flex: 12, child: AqiDistributionChartBox()),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
@ -32,8 +32,13 @@ class AqiDistributionChart extends StatelessWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
List<BarChartGroupData> _buildBarGroups() {
|
List<BarChartGroupData> _buildBarGroups() {
|
||||||
return List.generate(chartData.length, (index) {
|
final groups = <BarChartGroupData>[];
|
||||||
final data = chartData[index];
|
for (var i = 0; i < chartData.length; i++) {
|
||||||
|
final data = chartData[i];
|
||||||
|
final isAllZero = data.data.every((d) => d.percentage == 0);
|
||||||
|
if (isAllZero) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
final stackItems = <BarChartRodData>[];
|
final stackItems = <BarChartRodData>[];
|
||||||
double currentY = 0;
|
double currentY = 0;
|
||||||
var isFirstElement = true;
|
var isFirstElement = true;
|
||||||
@ -56,13 +61,15 @@ class AqiDistributionChart extends StatelessWidget {
|
|||||||
currentY += percentageData.percentage + _rodStackItemsSpacing;
|
currentY += percentageData.percentage + _rodStackItemsSpacing;
|
||||||
isFirstElement = false;
|
isFirstElement = false;
|
||||||
}
|
}
|
||||||
|
groups.add(
|
||||||
return BarChartGroupData(
|
BarChartGroupData(
|
||||||
x: index,
|
x: i,
|
||||||
barRods: stackItems,
|
barRods: stackItems,
|
||||||
groupVertically: true,
|
groupVertically: true,
|
||||||
|
),
|
||||||
);
|
);
|
||||||
});
|
}
|
||||||
|
return groups;
|
||||||
}
|
}
|
||||||
|
|
||||||
BarTouchData _barTouchData(BuildContext context) {
|
BarTouchData _barTouchData(BuildContext context) {
|
||||||
@ -73,6 +80,7 @@ class AqiDistributionChart extends StatelessWidget {
|
|||||||
color: ColorsManager.semiTransparentBlack,
|
color: ColorsManager.semiTransparentBlack,
|
||||||
),
|
),
|
||||||
tooltipRoundedRadius: 16,
|
tooltipRoundedRadius: 16,
|
||||||
|
maxContentWidth: 500,
|
||||||
tooltipPadding: const EdgeInsets.all(8),
|
tooltipPadding: const EdgeInsets.all(8),
|
||||||
getTooltipItem: (group, groupIndex, rod, rodIndex) {
|
getTooltipItem: (group, groupIndex, rod, rodIndex) {
|
||||||
final data = chartData[group.x];
|
final data = chartData[group.x];
|
||||||
@ -81,10 +89,13 @@ class AqiDistributionChart extends StatelessWidget {
|
|||||||
|
|
||||||
final textStyle = context.textTheme.bodySmall?.copyWith(
|
final textStyle = context.textTheme.bodySmall?.copyWith(
|
||||||
color: ColorsManager.blackColor,
|
color: ColorsManager.blackColor,
|
||||||
fontSize: 8,
|
fontSize: 11,
|
||||||
);
|
);
|
||||||
|
|
||||||
for (final percentageData in data.data) {
|
for (final percentageData in data.data) {
|
||||||
|
if (percentageData.percentage == 0) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
final percentage = percentageData.percentage.toStringAsFixed(1);
|
final percentage = percentageData.percentage.toStringAsFixed(1);
|
||||||
final type = percentageData.type[0].toUpperCase() +
|
final type = percentageData.type[0].toUpperCase() +
|
||||||
percentageData.type.substring(1).replaceAll('_', ' ');
|
percentageData.type.substring(1).replaceAll('_', ' ');
|
||||||
@ -98,7 +109,7 @@ class AqiDistributionChart extends StatelessWidget {
|
|||||||
DateFormat('dd/MM/yyyy').format(data.date),
|
DateFormat('dd/MM/yyyy').format(data.date),
|
||||||
context.textTheme.bodyMedium!.copyWith(
|
context.textTheme.bodyMedium!.copyWith(
|
||||||
color: ColorsManager.blackColor,
|
color: ColorsManager.blackColor,
|
||||||
fontSize: 9,
|
fontSize: 12,
|
||||||
fontWeight: FontWeight.w600,
|
fontWeight: FontWeight.w600,
|
||||||
),
|
),
|
||||||
textAlign: TextAlign.start,
|
textAlign: TextAlign.start,
|
||||||
@ -118,7 +129,6 @@ class AqiDistributionChart extends StatelessWidget {
|
|||||||
final leftTitles = titlesData.leftTitles.copyWith(
|
final leftTitles = titlesData.leftTitles.copyWith(
|
||||||
sideTitles: titlesData.leftTitles.sideTitles.copyWith(
|
sideTitles: titlesData.leftTitles.sideTitles.copyWith(
|
||||||
reservedSize: 70,
|
reservedSize: 70,
|
||||||
interval: 20,
|
|
||||||
maxIncluded: false,
|
maxIncluded: false,
|
||||||
minIncluded: true,
|
minIncluded: true,
|
||||||
getTitlesWidget: (value, meta) => Padding(
|
getTitlesWidget: (value, meta) => Padding(
|
||||||
@ -140,7 +150,7 @@ class AqiDistributionChart extends StatelessWidget {
|
|||||||
|
|
||||||
final bottomTitles = AxisTitles(
|
final bottomTitles = AxisTitles(
|
||||||
sideTitles: SideTitles(
|
sideTitles: SideTitles(
|
||||||
showTitles: true,
|
showTitles: chartData.isNotEmpty,
|
||||||
getTitlesWidget: (value, _) => FittedBox(
|
getTitlesWidget: (value, _) => FittedBox(
|
||||||
alignment: AlignmentDirectional.bottomCenter,
|
alignment: AlignmentDirectional.bottomCenter,
|
||||||
fit: BoxFit.scaleDown,
|
fit: BoxFit.scaleDown,
|
||||||
@ -148,7 +158,7 @@ class AqiDistributionChart extends StatelessWidget {
|
|||||||
chartData[value.toInt()].date.day.toString(),
|
chartData[value.toInt()].date.day.toString(),
|
||||||
style: context.textTheme.bodySmall?.copyWith(
|
style: context.textTheme.bodySmall?.copyWith(
|
||||||
color: ColorsManager.lightGreyColor,
|
color: ColorsManager.lightGreyColor,
|
||||||
fontSize: 8,
|
fontSize: 12,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
@ -19,7 +19,7 @@ class AqiDistributionChartTitle extends StatelessWidget {
|
|||||||
children: [
|
children: [
|
||||||
ChartsLoadingWidget(isLoading: isLoading),
|
ChartsLoadingWidget(isLoading: isLoading),
|
||||||
const Expanded(
|
const Expanded(
|
||||||
flex: 3,
|
flex: 4,
|
||||||
child: FittedBox(
|
child: FittedBox(
|
||||||
fit: BoxFit.scaleDown,
|
fit: BoxFit.scaleDown,
|
||||||
alignment: AlignmentDirectional.centerStart,
|
alignment: AlignmentDirectional.centerStart,
|
||||||
@ -28,23 +28,26 @@ class AqiDistributionChartTitle extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
FittedBox(
|
Expanded(
|
||||||
alignment: AlignmentDirectional.centerEnd,
|
flex: 2,
|
||||||
fit: BoxFit.scaleDown,
|
child: FittedBox(
|
||||||
child: AqiTypeDropdown(
|
alignment: AlignmentDirectional.centerEnd,
|
||||||
onChanged: (value) {
|
fit: BoxFit.scaleDown,
|
||||||
if (value != null) {
|
child: AqiTypeDropdown(
|
||||||
final bloc = context.read<AirQualityDistributionBloc>();
|
onChanged: (value) {
|
||||||
try {
|
if (value != null) {
|
||||||
final param = _makeLoadAqiDistributionParam(context, value);
|
final bloc = context.read<AirQualityDistributionBloc>();
|
||||||
bloc.add(LoadAirQualityDistribution(param));
|
try {
|
||||||
} catch (_) {
|
final param = _makeLoadAqiDistributionParam(context, value);
|
||||||
return;
|
bloc.add(LoadAirQualityDistribution(param));
|
||||||
} finally {
|
} catch (_) {
|
||||||
bloc.add(UpdateAqiTypeEvent(value));
|
return;
|
||||||
|
} finally {
|
||||||
|
bloc.add(UpdateAqiTypeEvent(value));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
},
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
@ -0,0 +1,38 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:syncrow_web/pages/analytics/modules/air_quality/helpers/range_of_aqi_charts_helper.dart';
|
||||||
|
import 'package:syncrow_web/pages/analytics/modules/analytics/widgets/chart_informative_cell.dart';
|
||||||
|
import 'package:syncrow_web/utils/style.dart';
|
||||||
|
|
||||||
|
class AqiLegend extends StatelessWidget {
|
||||||
|
const AqiLegend({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Container(
|
||||||
|
padding: const EdgeInsetsDirectional.all(20),
|
||||||
|
decoration: subSectionContainerDecoration.copyWith(
|
||||||
|
borderRadius: BorderRadius.circular(20),
|
||||||
|
),
|
||||||
|
child: Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
spacing: 16,
|
||||||
|
children: RangeOfAqiChartsHelper.gradientData.map((e) {
|
||||||
|
return Flexible(
|
||||||
|
flex: 4,
|
||||||
|
child: FittedBox(
|
||||||
|
fit: BoxFit.fill,
|
||||||
|
child: ChartInformativeCell(
|
||||||
|
color: e.$1,
|
||||||
|
title: FittedBox(
|
||||||
|
fit: BoxFit.fill,
|
||||||
|
child: Text(e.$2),
|
||||||
|
),
|
||||||
|
height: null,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}).toList(),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -47,36 +47,37 @@ class AqiLocationInfoCell extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
Align(
|
Align(
|
||||||
alignment: AlignmentDirectional.bottomEnd,
|
alignment: AlignmentDirectional.bottomCenter,
|
||||||
child: Padding(
|
child: Row(
|
||||||
padding: const EdgeInsetsDirectional.all(10),
|
crossAxisAlignment: CrossAxisAlignment.end,
|
||||||
child: SizedBox(
|
children: [
|
||||||
height: 40,
|
Expanded(
|
||||||
width: 120,
|
child: SvgPicture.asset(
|
||||||
child: FittedBox(
|
svgPath,
|
||||||
fit: BoxFit.scaleDown,
|
fit: BoxFit.scaleDown,
|
||||||
alignment: AlignmentDirectional.bottomEnd,
|
alignment: AlignmentDirectional.bottomStart,
|
||||||
child: Text(
|
),
|
||||||
value,
|
),
|
||||||
style: context.textTheme.bodySmall?.copyWith(
|
Expanded(
|
||||||
color: ColorsManager.vividBlue.withValues(alpha: 0.7),
|
child: FittedBox(
|
||||||
fontWeight: FontWeight.w700,
|
fit: BoxFit.scaleDown,
|
||||||
fontSize: 24,
|
alignment: AlignmentDirectional.bottomEnd,
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsetsDirectional.all(10),
|
||||||
|
child: Text(
|
||||||
|
value,
|
||||||
|
style: context.textTheme.bodySmall?.copyWith(
|
||||||
|
color: ColorsManager.vividBlue.withValues(
|
||||||
|
alpha: 0.7,
|
||||||
|
),
|
||||||
|
fontWeight: FontWeight.w700,
|
||||||
|
fontSize: 24,
|
||||||
|
),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
],
|
||||||
),
|
|
||||||
),
|
|
||||||
Align(
|
|
||||||
alignment: AlignmentDirectional.bottomStart,
|
|
||||||
child: SizedBox.square(
|
|
||||||
dimension: MediaQuery.sizeOf(context).width * 0.45,
|
|
||||||
child: FittedBox(
|
|
||||||
fit: BoxFit.scaleDown,
|
|
||||||
alignment: AlignmentDirectional.bottomStart,
|
|
||||||
child: SvgPicture.asset(svgPath),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
@ -7,16 +7,18 @@ class ChartInformativeCell extends StatelessWidget {
|
|||||||
required this.title,
|
required this.title,
|
||||||
required this.color,
|
required this.color,
|
||||||
this.hasBorder = false,
|
this.hasBorder = false,
|
||||||
|
this.height,
|
||||||
});
|
});
|
||||||
|
|
||||||
final Widget title;
|
final Widget title;
|
||||||
final Color color;
|
final Color color;
|
||||||
final bool hasBorder;
|
final bool hasBorder;
|
||||||
|
final double? height;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Container(
|
return Container(
|
||||||
height: MediaQuery.sizeOf(context).height * 0.0385,
|
height: height ?? MediaQuery.sizeOf(context).height * 0.0385,
|
||||||
padding: const EdgeInsetsDirectional.symmetric(
|
padding: const EdgeInsetsDirectional.symmetric(
|
||||||
vertical: 8,
|
vertical: 8,
|
||||||
horizontal: 12,
|
horizontal: 12,
|
||||||
|
@ -179,31 +179,36 @@ class _DynamicTableState extends State<DynamicTable> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildEmptyState() => Column(
|
Widget _buildEmptyState() => Container(
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
height: widget.size.height,
|
||||||
children: [
|
color: ColorsManager.whiteColors,
|
||||||
Row(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.center,
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
children: [
|
||||||
children: [
|
Row(
|
||||||
Column(
|
crossAxisAlignment: CrossAxisAlignment.center,
|
||||||
children: [
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
SvgPicture.asset(Assets.emptyTable),
|
children: [
|
||||||
const SizedBox(height: 15),
|
Column(
|
||||||
Text(
|
children: [
|
||||||
widget.tableName == 'AccessManagement'
|
SvgPicture.asset(Assets.emptyTable),
|
||||||
? 'No Password '
|
const SizedBox(height: 15),
|
||||||
: 'No Devices',
|
Text(
|
||||||
style: Theme.of(context)
|
widget.tableName == 'AccessManagement'
|
||||||
.textTheme
|
? 'No Password '
|
||||||
.bodySmall!
|
: 'No Devices',
|
||||||
.copyWith(color: ColorsManager.grayColor),
|
style: Theme.of(context)
|
||||||
)
|
.textTheme
|
||||||
],
|
.bodySmall!
|
||||||
),
|
.copyWith(color: ColorsManager.grayColor),
|
||||||
],
|
)
|
||||||
),
|
],
|
||||||
],
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
SizedBox(height: widget.size.height * 0.5),
|
||||||
|
],
|
||||||
|
),
|
||||||
);
|
);
|
||||||
Widget _buildSelectAllCheckbox() {
|
Widget _buildSelectAllCheckbox() {
|
||||||
return Container(
|
return Container(
|
||||||
|
@ -12,7 +12,8 @@ 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 with HelperResponsiveLayout {
|
class SmartPowerDeviceControl extends StatelessWidget
|
||||||
|
with HelperResponsiveLayout {
|
||||||
final String deviceId;
|
final String deviceId;
|
||||||
|
|
||||||
const SmartPowerDeviceControl({super.key, required this.deviceId});
|
const SmartPowerDeviceControl({super.key, required this.deviceId});
|
||||||
@ -145,13 +146,16 @@ class SmartPowerDeviceControl extends StatelessWidget with HelperResponsiveLayou
|
|||||||
children: [
|
children: [
|
||||||
IconButton(
|
IconButton(
|
||||||
icon: const Icon(Icons.arrow_left),
|
icon: const Icon(Icons.arrow_left),
|
||||||
onPressed: () {
|
onPressed: blocProvider.currentPage <= 0
|
||||||
blocProvider.add(SmartPowerArrowPressedEvent(-1));
|
? null
|
||||||
pageController.previousPage(
|
: () {
|
||||||
duration: const Duration(milliseconds: 300),
|
blocProvider
|
||||||
curve: Curves.easeInOut,
|
.add(SmartPowerArrowPressedEvent(-1));
|
||||||
);
|
pageController.previousPage(
|
||||||
},
|
duration: const Duration(milliseconds: 300),
|
||||||
|
curve: Curves.easeInOut,
|
||||||
|
);
|
||||||
|
},
|
||||||
),
|
),
|
||||||
Text(
|
Text(
|
||||||
currentPage == 0
|
currentPage == 0
|
||||||
@ -165,13 +169,16 @@ class SmartPowerDeviceControl extends StatelessWidget with HelperResponsiveLayou
|
|||||||
),
|
),
|
||||||
IconButton(
|
IconButton(
|
||||||
icon: const Icon(Icons.arrow_right),
|
icon: const Icon(Icons.arrow_right),
|
||||||
onPressed: () {
|
onPressed: blocProvider.currentPage >= 3
|
||||||
blocProvider.add(SmartPowerArrowPressedEvent(1));
|
? null
|
||||||
pageController.nextPage(
|
: () {
|
||||||
duration: const Duration(milliseconds: 300),
|
blocProvider
|
||||||
curve: Curves.easeInOut,
|
.add(SmartPowerArrowPressedEvent(1));
|
||||||
);
|
pageController.nextPage(
|
||||||
},
|
duration: const Duration(milliseconds: 300),
|
||||||
|
curve: Curves.easeInOut,
|
||||||
|
);
|
||||||
|
},
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
@ -195,8 +202,8 @@ class SmartPowerDeviceControl extends StatelessWidget with HelperResponsiveLayou
|
|||||||
blocProvider.add(SelectDateEvent(context: context));
|
blocProvider.add(SelectDateEvent(context: context));
|
||||||
blocProvider.add(FilterRecordsByDateEvent(
|
blocProvider.add(FilterRecordsByDateEvent(
|
||||||
selectedDate: blocProvider.dateTime!,
|
selectedDate: blocProvider.dateTime!,
|
||||||
viewType:
|
viewType: blocProvider
|
||||||
blocProvider.views[blocProvider.currentIndex]));
|
.views[blocProvider.currentIndex]));
|
||||||
},
|
},
|
||||||
widget: blocProvider.dateSwitcher(),
|
widget: blocProvider.dateSwitcher(),
|
||||||
chartData: blocProvider.energyDataList.isNotEmpty
|
chartData: blocProvider.energyDataList.isNotEmpty
|
||||||
|
@ -83,6 +83,12 @@ 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,
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -94,6 +100,7 @@ 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,
|
||||||
@ -113,6 +120,7 @@ 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
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -424,6 +432,7 @@ 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,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -437,6 +446,7 @@ class ScheduleBloc extends Bloc<ScheduleEvent, ScheduleState> {
|
|||||||
countdownMinutes: 0,
|
countdownMinutes: 0,
|
||||||
countdownRemaining: Duration.zero,
|
countdownRemaining: Duration.zero,
|
||||||
isCountdownActive: false,
|
isCountdownActive: false,
|
||||||
|
countdownSeconds: 0,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -448,6 +458,7 @@ 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,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -574,8 +585,7 @@ class ScheduleBloc extends Bloc<ScheduleEvent, ScheduleState> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
String extractTime(String isoDateTime) {
|
String extractTime(String isoDateTime) {
|
||||||
// Example input: "2025-06-19T15:45:00.000"
|
return isoDateTime.split('T')[1].split('.')[0];
|
||||||
return isoDateTime.split('T')[1].split('.')[0]; // gives "15:45:00"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
int? getTimeStampWithoutSeconds(DateTime? dateTime) {
|
int? getTimeStampWithoutSeconds(DateTime? dateTime) {
|
||||||
|
@ -146,14 +146,16 @@ 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];
|
List<Object> get props => [hours, minutes, seconds];
|
||||||
}
|
}
|
||||||
|
|
||||||
class UpdateInchingTimeEvent extends ScheduleEvent {
|
class UpdateInchingTimeEvent extends ScheduleEvent {
|
||||||
|
@ -26,11 +26,15 @@ 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,
|
||||||
@ -61,6 +65,9 @@ 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,
|
||||||
@ -68,7 +75,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,
|
deviceId: deviceId ?? this.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,
|
||||||
@ -77,6 +84,8 @@ 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,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -96,6 +105,8 @@ class ScheduleLoaded extends ScheduleState {
|
|||||||
isInchingActive,
|
isInchingActive,
|
||||||
scheduleMode,
|
scheduleMode,
|
||||||
countdownRemaining,
|
countdownRemaining,
|
||||||
|
countdownSeconds,
|
||||||
|
inchingSeconds,
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -6,7 +6,8 @@ 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 {
|
||||||
const CountdownInchingView({super.key});
|
final String deviceId;
|
||||||
|
const CountdownInchingView({super.key, required this.deviceId});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<CountdownInchingView> createState() => _CountdownInchingViewState();
|
State<CountdownInchingView> createState() => _CountdownInchingViewState();
|
||||||
@ -15,25 +16,30 @@ 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(int displayHours, int displayMinutes) {
|
void _updateControllers(
|
||||||
|
int displayHours, int displayMinutes, int displaySeconds) {
|
||||||
if (_lastHours != displayHours) {
|
if (_lastHours != displayHours) {
|
||||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||||
if (_hoursController.hasClients) {
|
if (_hoursController.hasClients) {
|
||||||
@ -50,6 +56,15 @@ 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
|
||||||
@ -57,7 +72,6 @@ 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;
|
||||||
@ -67,8 +81,21 @@ 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: [
|
||||||
@ -100,7 +127,10 @@ class _CountdownInchingViewState extends State<CountdownInchingView> {
|
|||||||
(value) {
|
(value) {
|
||||||
if (!isActive) {
|
if (!isActive) {
|
||||||
context.read<ScheduleBloc>().add(UpdateCountdownTimeEvent(
|
context.read<ScheduleBloc>().add(UpdateCountdownTimeEvent(
|
||||||
hours: value, minutes: displayMinutes));
|
hours: value,
|
||||||
|
minutes: displayMinutes,
|
||||||
|
seconds: displaySeconds,
|
||||||
|
));
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
isActive: isActive,
|
isActive: isActive,
|
||||||
@ -115,11 +145,35 @@ class _CountdownInchingViewState extends State<CountdownInchingView> {
|
|||||||
(value) {
|
(value) {
|
||||||
if (!isActive) {
|
if (!isActive) {
|
||||||
context.read<ScheduleBloc>().add(UpdateCountdownTimeEvent(
|
context.read<ScheduleBloc>().add(UpdateCountdownTimeEvent(
|
||||||
hours: displayHours, minutes: value));
|
hours: displayHours,
|
||||||
|
minutes: value,
|
||||||
|
seconds: displaySeconds,
|
||||||
|
));
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
isActive: isActive,
|
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,
|
||||||
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
@ -74,7 +74,9 @@ class BuildScheduleView extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
if (state.scheduleMode == ScheduleModes.countdown ||
|
if (state.scheduleMode == ScheduleModes.countdown ||
|
||||||
state.scheduleMode == ScheduleModes.inching)
|
state.scheduleMode == ScheduleModes.inching)
|
||||||
const CountdownInchingView(),
|
CountdownInchingView(
|
||||||
|
deviceId: deviceUuid,
|
||||||
|
),
|
||||||
const SizedBox(height: 20),
|
const SizedBox(height: 20),
|
||||||
if (state.scheduleMode == ScheduleModes.countdown)
|
if (state.scheduleMode == ScheduleModes.countdown)
|
||||||
CountdownModeButtons(
|
CountdownModeButtons(
|
||||||
|
@ -79,6 +79,7 @@ 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(
|
||||||
@ -107,7 +108,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,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@ -126,12 +127,16 @@ class DeviceControlDialog extends StatelessWidget with RouteControlsBasedCode {
|
|||||||
),
|
),
|
||||||
TableRow(
|
TableRow(
|
||||||
children: [
|
children: [
|
||||||
_buildInfoRow('Status:', 'Online', statusColor: Colors.green),
|
_buildInfoRow(
|
||||||
|
'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,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
@ -7,21 +7,22 @@ class SpacesConnectionsArrowPainter extends CustomPainter {
|
|||||||
final Map<String, Offset> positions;
|
final Map<String, Offset> positions;
|
||||||
final double cardWidth = 150.0;
|
final double cardWidth = 150.0;
|
||||||
final double cardHeight = 90.0;
|
final double cardHeight = 90.0;
|
||||||
final String? selectedSpaceUuid;
|
final Set<String> highlightedUuids;
|
||||||
|
|
||||||
SpacesConnectionsArrowPainter({
|
SpacesConnectionsArrowPainter({
|
||||||
required this.connections,
|
required this.connections,
|
||||||
required this.positions,
|
required this.positions,
|
||||||
this.selectedSpaceUuid,
|
required this.highlightedUuids,
|
||||||
});
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void paint(Canvas canvas, Size size) {
|
void paint(Canvas canvas, Size size) {
|
||||||
for (final connection in connections) {
|
for (final connection in connections) {
|
||||||
final isSelected = connection.to == selectedSpaceUuid;
|
final isSelected = highlightedUuids.contains(connection.from) ||
|
||||||
|
highlightedUuids.contains(connection.to);
|
||||||
final paint = Paint()
|
final paint = Paint()
|
||||||
..color = isSelected
|
..color = isSelected
|
||||||
? ColorsManager.primaryColor
|
? ColorsManager.blackColor
|
||||||
: ColorsManager.blackColor.withValues(alpha: 0.5)
|
: ColorsManager.blackColor.withValues(alpha: 0.5)
|
||||||
..strokeWidth = 2.0
|
..strokeWidth = 2.0
|
||||||
..style = PaintingStyle.stroke;
|
..style = PaintingStyle.stroke;
|
||||||
@ -36,7 +37,7 @@ class SpacesConnectionsArrowPainter extends CustomPainter {
|
|||||||
|
|
||||||
final path = Path()..moveTo(startPoint.dx, startPoint.dy);
|
final path = Path()..moveTo(startPoint.dx, startPoint.dy);
|
||||||
|
|
||||||
final controlPoint1 = Offset(startPoint.dx, startPoint.dy + 60);
|
final controlPoint1 = Offset(startPoint.dx, startPoint.dy + 20);
|
||||||
final controlPoint2 = Offset(endPoint.dx, endPoint.dy - 60);
|
final controlPoint2 = Offset(endPoint.dx, endPoint.dy - 60);
|
||||||
|
|
||||||
path.cubicTo(controlPoint1.dx, controlPoint1.dy, controlPoint2.dx,
|
path.cubicTo(controlPoint1.dx, controlPoint1.dy, controlPoint2.dx,
|
||||||
@ -46,7 +47,7 @@ class SpacesConnectionsArrowPainter extends CustomPainter {
|
|||||||
|
|
||||||
final circlePaint = Paint()
|
final circlePaint = Paint()
|
||||||
..color = isSelected
|
..color = isSelected
|
||||||
? ColorsManager.primaryColor
|
? ColorsManager.blackColor
|
||||||
: ColorsManager.blackColor.withValues(alpha: 0.5)
|
: ColorsManager.blackColor.withValues(alpha: 0.5)
|
||||||
..style = PaintingStyle.fill
|
..style = PaintingStyle.fill
|
||||||
..blendMode = BlendMode.srcIn;
|
..blendMode = BlendMode.srcIn;
|
||||||
|
@ -1,21 +1,26 @@
|
|||||||
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/models/space_connection_model.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/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_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/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/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/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 {
|
class CommunityStructureCanvas extends StatefulWidget {
|
||||||
const CommunityStructureCanvas({
|
const CommunityStructureCanvas({
|
||||||
required this.community,
|
required this.community,
|
||||||
|
required this.selectedSpace,
|
||||||
super.key,
|
super.key,
|
||||||
});
|
});
|
||||||
|
|
||||||
final CommunityModel community;
|
final CommunityModel community;
|
||||||
|
final SpaceModel? selectedSpace;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<CommunityStructureCanvas> createState() =>_CommunityStructureCanvasState();
|
State<CommunityStructureCanvas> createState() => _CommunityStructureCanvasState();
|
||||||
}
|
}
|
||||||
|
|
||||||
class _CommunityStructureCanvasState extends State<CommunityStructureCanvas>
|
class _CommunityStructureCanvasState extends State<CommunityStructureCanvas>
|
||||||
@ -25,19 +30,30 @@ class _CommunityStructureCanvasState extends State<CommunityStructureCanvas>
|
|||||||
final double _cardHeight = 90.0;
|
final double _cardHeight = 90.0;
|
||||||
final double _horizontalSpacing = 150.0;
|
final double _horizontalSpacing = 150.0;
|
||||||
final double _verticalSpacing = 120.0;
|
final double _verticalSpacing = 120.0;
|
||||||
String? _selectedSpaceUuid;
|
|
||||||
|
|
||||||
late TransformationController _transformationController;
|
late TransformationController _transformationController;
|
||||||
late AnimationController _animationController;
|
late AnimationController _animationController;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
|
||||||
_transformationController = TransformationController();
|
_transformationController = TransformationController();
|
||||||
_animationController = AnimationController(
|
_animationController = AnimationController(
|
||||||
vsync: this,
|
vsync: this,
|
||||||
duration: const Duration(milliseconds: 100),
|
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
|
@override
|
||||||
@ -47,6 +63,15 @@ class _CommunityStructureCanvasState extends State<CommunityStructureCanvas>
|
|||||||
super.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) {
|
void _runAnimation(Matrix4 target) {
|
||||||
final animation = Matrix4Tween(
|
final animation = Matrix4Tween(
|
||||||
begin: _transformationController.value,
|
begin: _transformationController.value,
|
||||||
@ -63,15 +88,16 @@ class _CommunityStructureCanvasState extends State<CommunityStructureCanvas>
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
void _onSpaceTapped(String spaceUuid) {
|
void _animateToSpace(SpaceModel? space) {
|
||||||
setState(() {
|
if (space == null) {
|
||||||
_selectedSpaceUuid = spaceUuid;
|
_runAnimation(Matrix4.identity());
|
||||||
});
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
final position = _positions[spaceUuid];
|
final position = _positions[space.uuid];
|
||||||
if (position == null) return;
|
if (position == null) return;
|
||||||
|
|
||||||
const scale = 2.0;
|
const scale = 1.5;
|
||||||
final viewSize = context.size;
|
final viewSize = context.size;
|
||||||
if (viewSize == null) return;
|
if (viewSize == null) return;
|
||||||
|
|
||||||
@ -86,11 +112,19 @@ class _CommunityStructureCanvasState extends State<CommunityStructureCanvas>
|
|||||||
_runAnimation(matrix);
|
_runAnimation(matrix);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void _onSpaceTapped(SpaceModel? space) {
|
||||||
|
context.read<CommunitiesTreeSelectionBloc>().add(
|
||||||
|
SelectSpaceEvent(community: widget.community, space: space),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
void _resetSelectionAndZoom() {
|
void _resetSelectionAndZoom() {
|
||||||
setState(() {
|
context.read<CommunitiesTreeSelectionBloc>().add(
|
||||||
_selectedSpaceUuid = null;
|
SelectSpaceEvent(
|
||||||
});
|
community: widget.community,
|
||||||
_runAnimation(Matrix4.identity());
|
space: null,
|
||||||
|
),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
void _calculateLayout(
|
void _calculateLayout(
|
||||||
@ -150,16 +184,23 @@ class _CommunityStructureCanvasState extends State<CommunityStructureCanvas>
|
|||||||
|
|
||||||
_calculateLayout(community.spaces, 0, {});
|
_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 widgets = <Widget>[];
|
||||||
final connections = <SpaceConnectionModel>[];
|
final connections = <SpaceConnectionModel>[];
|
||||||
_generateWidgets(community.spaces, widgets, connections);
|
_generateWidgets(community.spaces, widgets, connections, highlightedUuids);
|
||||||
|
|
||||||
return [
|
return [
|
||||||
CustomPaint(
|
CustomPaint(
|
||||||
painter: SpacesConnectionsArrowPainter(
|
painter: SpacesConnectionsArrowPainter(
|
||||||
connections: connections,
|
connections: connections,
|
||||||
positions: _positions,
|
positions: _positions,
|
||||||
selectedSpaceUuid: _selectedSpaceUuid,
|
highlightedUuids: highlightedUuids,
|
||||||
),
|
),
|
||||||
child: Stack(alignment: AlignmentDirectional.center, children: widgets),
|
child: Stack(alignment: AlignmentDirectional.center, children: widgets),
|
||||||
),
|
),
|
||||||
@ -170,11 +211,15 @@ class _CommunityStructureCanvasState extends State<CommunityStructureCanvas>
|
|||||||
List<SpaceModel> spaces,
|
List<SpaceModel> spaces,
|
||||||
List<Widget> widgets,
|
List<Widget> widgets,
|
||||||
List<SpaceConnectionModel> connections,
|
List<SpaceConnectionModel> connections,
|
||||||
|
Set<String> highlightedUuids,
|
||||||
) {
|
) {
|
||||||
for (final space in spaces) {
|
for (final space in spaces) {
|
||||||
final position = _positions[space.uuid];
|
final position = _positions[space.uuid];
|
||||||
if (position == null) continue;
|
if (position == null) continue;
|
||||||
|
|
||||||
|
final isHighlighted = highlightedUuids.contains(space.uuid);
|
||||||
|
final hasNoSelectedSpace = widget.selectedSpace == null;
|
||||||
|
|
||||||
widgets.add(
|
widgets.add(
|
||||||
Positioned(
|
Positioned(
|
||||||
left: position.dx,
|
left: position.dx,
|
||||||
@ -182,32 +227,31 @@ class _CommunityStructureCanvasState extends State<CommunityStructureCanvas>
|
|||||||
width: _cardWidth,
|
width: _cardWidth,
|
||||||
height: _cardHeight,
|
height: _cardHeight,
|
||||||
child: SpaceCardWidget(
|
child: SpaceCardWidget(
|
||||||
index: spaces.indexOf(space),
|
buildSpaceContainer: () {
|
||||||
onPositionChanged: (newPosition) {},
|
|
||||||
buildSpaceContainer: (index) {
|
|
||||||
return Opacity(
|
return Opacity(
|
||||||
opacity: 1.0,
|
opacity: hasNoSelectedSpace || isHighlighted ? 1.0 : 0.5,
|
||||||
child: SpaceCell(
|
child: Tooltip(
|
||||||
index: index,
|
message: space.spaceName,
|
||||||
onTap: () => _onSpaceTapped(space.uuid),
|
preferBelow: false,
|
||||||
icon: space.icon,
|
child: SpaceCell(
|
||||||
name: space.spaceName,
|
onTap: () => _onSpaceTapped(space),
|
||||||
|
icon: space.icon,
|
||||||
|
name: space.spaceName,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
screenSize: MediaQuery.sizeOf(context),
|
onTap: () => SpaceDetailsDialogHelper.showCreate(context),
|
||||||
position: position,
|
|
||||||
isHovered: false,
|
|
||||||
onHoverChanged: (int index, bool isHovered) {},
|
|
||||||
onButtonTap: (int index, Offset newPosition) {},
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
for (final child in space.children) {
|
for (final child in space.children) {
|
||||||
connections.add(SpaceConnectionModel(from: space.uuid, to: child.uuid));
|
connections.add(
|
||||||
|
SpaceConnectionModel(from: space.uuid, to: child.uuid),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
_generateWidgets(space.children, widgets, connections);
|
_generateWidgets(space.children, widgets, connections, highlightedUuids);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -218,7 +262,7 @@ class _CommunityStructureCanvasState extends State<CommunityStructureCanvas>
|
|||||||
transformationController: _transformationController,
|
transformationController: _transformationController,
|
||||||
boundaryMargin: EdgeInsets.symmetric(
|
boundaryMargin: EdgeInsets.symmetric(
|
||||||
horizontal: MediaQuery.sizeOf(context).width * 0.3,
|
horizontal: MediaQuery.sizeOf(context).width * 0.3,
|
||||||
vertical: MediaQuery.sizeOf(context).height * 0.2,
|
vertical: MediaQuery.sizeOf(context).height * 0.3,
|
||||||
),
|
),
|
||||||
minScale: 0.5,
|
minScale: 0.5,
|
||||||
maxScale: 3.0,
|
maxScale: 3.0,
|
||||||
@ -226,8 +270,8 @@ class _CommunityStructureCanvasState extends State<CommunityStructureCanvas>
|
|||||||
child: GestureDetector(
|
child: GestureDetector(
|
||||||
onTap: _resetSelectionAndZoom,
|
onTap: _resetSelectionAndZoom,
|
||||||
child: SizedBox(
|
child: SizedBox(
|
||||||
width: MediaQuery.sizeOf(context).width * 2,
|
width: MediaQuery.sizeOf(context).width * 5,
|
||||||
height: MediaQuery.sizeOf(context).height * 2,
|
height: MediaQuery.sizeOf(context).height * 5,
|
||||||
child: Stack(children: treeWidgets),
|
child: Stack(children: treeWidgets),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import 'package:flutter/material.dart';
|
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';
|
import 'package:syncrow_web/utils/color_manager.dart';
|
||||||
|
|
||||||
class CreateSpaceButton extends StatelessWidget {
|
class CreateSpaceButton extends StatelessWidget {
|
||||||
@ -7,7 +8,7 @@ class CreateSpaceButton extends StatelessWidget {
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return GestureDetector(
|
return GestureDetector(
|
||||||
onTap: () {},
|
onTap: () => SpaceDetailsDialogHelper.showCreate(context),
|
||||||
child: Container(
|
child: Container(
|
||||||
height: 60,
|
height: 60,
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
|
@ -2,15 +2,11 @@ import 'package:flutter/material.dart';
|
|||||||
import 'package:syncrow_web/utils/color_manager.dart';
|
import 'package:syncrow_web/utils/color_manager.dart';
|
||||||
|
|
||||||
class PlusButtonWidget extends StatelessWidget {
|
class PlusButtonWidget extends StatelessWidget {
|
||||||
final int index;
|
|
||||||
final String direction;
|
|
||||||
final Offset offset;
|
final Offset offset;
|
||||||
final void Function(int index, Offset newPosition) onButtonTap;
|
final void Function() onButtonTap;
|
||||||
|
|
||||||
const PlusButtonWidget({
|
const PlusButtonWidget({
|
||||||
super.key,
|
super.key,
|
||||||
required this.index,
|
|
||||||
required this.direction,
|
|
||||||
required this.offset,
|
required this.offset,
|
||||||
required this.onButtonTap,
|
required this.onButtonTap,
|
||||||
});
|
});
|
||||||
@ -18,13 +14,7 @@ class PlusButtonWidget extends StatelessWidget {
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return GestureDetector(
|
return GestureDetector(
|
||||||
onTap: () {
|
onTap: onButtonTap,
|
||||||
if (direction == 'down') {
|
|
||||||
onButtonTap(index, const Offset(0, 150));
|
|
||||||
} else {
|
|
||||||
onButtonTap(index, const Offset(150, 0));
|
|
||||||
}
|
|
||||||
},
|
|
||||||
child: Container(
|
child: Container(
|
||||||
width: 30,
|
width: 30,
|
||||||
height: 30,
|
height: 30,
|
||||||
|
@ -1,60 +1,39 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:syncrow_web/pages/space_management_v2/main_module/widgets/plus_button_widget.dart';
|
import 'package:syncrow_web/pages/space_management_v2/main_module/widgets/plus_button_widget.dart';
|
||||||
|
|
||||||
class SpaceCardWidget extends StatelessWidget {
|
class SpaceCardWidget extends StatefulWidget {
|
||||||
final int index;
|
final void Function() onTap;
|
||||||
final Size screenSize;
|
final Widget Function() buildSpaceContainer;
|
||||||
final Offset position;
|
|
||||||
final bool isHovered;
|
|
||||||
final void Function(int index, bool isHovered) onHoverChanged;
|
|
||||||
final void Function(int index, Offset newPosition) onButtonTap;
|
|
||||||
final Widget Function(int index) buildSpaceContainer;
|
|
||||||
final ValueChanged<Offset> onPositionChanged;
|
|
||||||
|
|
||||||
const SpaceCardWidget({
|
const SpaceCardWidget({
|
||||||
super.key,
|
required this.onTap,
|
||||||
required this.index,
|
|
||||||
required this.onPositionChanged,
|
|
||||||
required this.screenSize,
|
|
||||||
required this.position,
|
|
||||||
required this.isHovered,
|
|
||||||
required this.onHoverChanged,
|
|
||||||
required this.onButtonTap,
|
|
||||||
required this.buildSpaceContainer,
|
required this.buildSpaceContainer,
|
||||||
|
super.key,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<SpaceCardWidget> createState() => _SpaceCardWidgetState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _SpaceCardWidgetState extends State<SpaceCardWidget> {
|
||||||
|
bool isHovered = false;
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return MouseRegion(
|
return MouseRegion(
|
||||||
onEnter: (_) => onHoverChanged(index, true),
|
onEnter: (_) => setState(() => isHovered = true),
|
||||||
onExit: (_) => onHoverChanged(index, false),
|
onExit: (_) => setState(() => isHovered = false),
|
||||||
child: SizedBox(
|
child: SizedBox(
|
||||||
width: 150,
|
|
||||||
height: 90,
|
|
||||||
child: Stack(
|
child: Stack(
|
||||||
clipBehavior: Clip.none,
|
clipBehavior: Clip.none,
|
||||||
alignment: Alignment.center,
|
alignment: Alignment.center,
|
||||||
children: [
|
children: [
|
||||||
buildSpaceContainer(index),
|
widget.buildSpaceContainer(),
|
||||||
|
|
||||||
if (isHovered)
|
if (isHovered)
|
||||||
Positioned(
|
Positioned(
|
||||||
bottom: 0,
|
bottom: 0,
|
||||||
child: PlusButtonWidget(
|
child: PlusButtonWidget(
|
||||||
index: index,
|
|
||||||
direction: 'down',
|
|
||||||
offset: Offset.zero,
|
offset: Offset.zero,
|
||||||
onButtonTap: onButtonTap,
|
onButtonTap: widget.onTap,
|
||||||
),
|
|
||||||
),
|
|
||||||
if (isHovered)
|
|
||||||
Positioned(
|
|
||||||
right: -15,
|
|
||||||
child: PlusButtonWidget(
|
|
||||||
index: index,
|
|
||||||
direction: 'right',
|
|
||||||
offset: Offset.zero,
|
|
||||||
onButtonTap: onButtonTap,
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
@ -1,29 +1,23 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_svg/svg.dart';
|
import 'package:flutter_svg/svg.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';
|
||||||
|
|
||||||
class SpaceCell extends StatelessWidget {
|
class SpaceCell extends StatelessWidget {
|
||||||
final int index;
|
|
||||||
final String icon;
|
final String icon;
|
||||||
final String name;
|
final String name;
|
||||||
final VoidCallback? onDoubleTap;
|
|
||||||
final VoidCallback? onTap;
|
final VoidCallback? onTap;
|
||||||
|
|
||||||
const SpaceCell({
|
const SpaceCell({
|
||||||
super.key,
|
super.key,
|
||||||
required this.index,
|
|
||||||
required this.icon,
|
required this.icon,
|
||||||
required this.name,
|
required this.name,
|
||||||
this.onTap,
|
required this.onTap,
|
||||||
this.onDoubleTap,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final theme = Theme.of(context);
|
|
||||||
|
|
||||||
return GestureDetector(
|
return GestureDetector(
|
||||||
onDoubleTap: onDoubleTap,
|
|
||||||
onTap: onTap,
|
onTap: onTap,
|
||||||
child: Container(
|
child: Container(
|
||||||
width: 150,
|
width: 150,
|
||||||
@ -36,7 +30,7 @@ class SpaceCell extends StatelessWidget {
|
|||||||
Expanded(
|
Expanded(
|
||||||
child: Text(
|
child: Text(
|
||||||
name,
|
name,
|
||||||
style: theme.textTheme.bodyLarge?.copyWith(
|
style: context.textTheme.bodyLarge?.copyWith(
|
||||||
fontWeight: FontWeight.bold,
|
fontWeight: FontWeight.bold,
|
||||||
color: ColorsManager.blackColor,
|
color: ColorsManager.blackColor,
|
||||||
),
|
),
|
||||||
@ -63,7 +57,10 @@ class SpaceCell extends StatelessWidget {
|
|||||||
child: Center(
|
child: Center(
|
||||||
child: SvgPicture.asset(
|
child: SvgPicture.asset(
|
||||||
icon,
|
icon,
|
||||||
color: ColorsManager.whiteColors,
|
colorFilter: const ColorFilter.mode(
|
||||||
|
ColorsManager.whiteColors,
|
||||||
|
BlendMode.srcIn,
|
||||||
|
),
|
||||||
width: 24,
|
width: 24,
|
||||||
height: 24,
|
height: 24,
|
||||||
),
|
),
|
||||||
|
@ -9,14 +9,19 @@ class SpaceManagementCommunityStructure extends StatelessWidget {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final selectedCommunity =
|
final selectionBloc = context.watch<CommunitiesTreeSelectionBloc>().state;
|
||||||
context.watch<CommunitiesTreeSelectionBloc>().state.selectedCommunity!;
|
final selectedCommunity = selectionBloc.selectedCommunity;
|
||||||
|
final selectedSpace = selectionBloc.selectedSpace;
|
||||||
const spacer = Spacer(flex: 10);
|
const spacer = Spacer(flex: 10);
|
||||||
return Visibility(
|
return Visibility(
|
||||||
visible: selectedCommunity.spaces.isNotEmpty,
|
visible: selectedCommunity!.spaces.isNotEmpty,
|
||||||
replacement: const Row(
|
replacement: const Row(
|
||||||
children: [spacer, Expanded(child: CreateSpaceButton()), spacer]),
|
children: [spacer, Expanded(child: CreateSpaceButton()), spacer],
|
||||||
child: CommunityStructureCanvas(community: selectedCommunity),
|
),
|
||||||
|
child: CommunityStructureCanvas(
|
||||||
|
community: selectedCommunity,
|
||||||
|
selectedSpace: selectedSpace,
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -7,26 +7,24 @@ class SpaceManagementTemplatesView extends StatelessWidget {
|
|||||||
const SpaceManagementTemplatesView({super.key});
|
const SpaceManagementTemplatesView({super.key});
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Expanded(
|
return ColoredBox(
|
||||||
child: ColoredBox(
|
color: ColorsManager.whiteColors,
|
||||||
color: ColorsManager.whiteColors,
|
child: GridView.builder(
|
||||||
child: GridView.builder(
|
padding: const EdgeInsets.symmetric(horizontal: 40, vertical: 20),
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 40, vertical: 20),
|
gridDelegate: const SliverGridDelegateWithMaxCrossAxisExtent(
|
||||||
gridDelegate: const SliverGridDelegateWithMaxCrossAxisExtent(
|
maxCrossAxisExtent: 400,
|
||||||
maxCrossAxisExtent: 400,
|
mainAxisSpacing: 10,
|
||||||
mainAxisSpacing: 10,
|
crossAxisSpacing: 10,
|
||||||
crossAxisSpacing: 10,
|
childAspectRatio: 2.0,
|
||||||
childAspectRatio: 2.0,
|
|
||||||
),
|
|
||||||
itemCount: _gridItems(context).length,
|
|
||||||
itemBuilder: (context, index) {
|
|
||||||
final model = _gridItems(context)[index];
|
|
||||||
return CommunityTemplateCell(
|
|
||||||
onTap: model.onTap,
|
|
||||||
title: model.title,
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
),
|
||||||
|
itemCount: _gridItems(context).length,
|
||||||
|
itemBuilder: (context, index) {
|
||||||
|
final model = _gridItems(context)[index];
|
||||||
|
return CommunityTemplateCell(
|
||||||
|
onTap: model.onTap,
|
||||||
|
title: model.title,
|
||||||
|
);
|
||||||
|
},
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -16,7 +16,7 @@ final class SelectCommunityEvent extends CommunitiesTreeSelectionEvent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
final class SelectSpaceEvent extends CommunitiesTreeSelectionEvent {
|
final class SelectSpaceEvent extends CommunitiesTreeSelectionEvent {
|
||||||
final SpaceModel space;
|
final SpaceModel? space;
|
||||||
final CommunityModel community;
|
final CommunityModel community;
|
||||||
|
|
||||||
const SelectSpaceEvent({required this.space, required this.community});
|
const SelectSpaceEvent({required this.space, required this.community});
|
||||||
|
@ -0,0 +1,11 @@
|
|||||||
|
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(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,12 @@
|
|||||||
|
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'),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -2,11 +2,13 @@ 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) {
|
||||||
@ -14,6 +16,7 @@ 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? ?? '',
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -22,21 +25,22 @@ 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) {
|
||||||
@ -44,6 +48,7 @@ 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? ?? '',
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -52,6 +57,7 @@ class SuccessOperation {
|
|||||||
'success': success,
|
'success': success,
|
||||||
// 'result': result.toJson(),
|
// 'result': result.toJson(),
|
||||||
'deviceUuid': deviceUuid,
|
'deviceUuid': deviceUuid,
|
||||||
|
'deviceName': deviceName,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -92,8 +98,6 @@ class SuccessOperation {
|
|||||||
// }
|
// }
|
||||||
// }
|
// }
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class PasswordStatus {
|
class PasswordStatus {
|
||||||
final List<SuccessOperation> successOperations;
|
final List<SuccessOperation> successOperations;
|
||||||
final List<FailedOperation> failedOperations;
|
final List<FailedOperation> failedOperations;
|
||||||
@ -121,4 +125,3 @@ class PasswordStatus {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
120
lib/pages/visitor_password/view/access_type_radio_group.dart
Normal file
120
lib/pages/visitor_password/view/access_type_radio_group.dart
Normal file
@ -0,0 +1,120 @@
|
|||||||
|
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 = '';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
73
lib/pages/visitor_password/view/responsive_fields_row.dart
Normal file
73
lib/pages/visitor_password/view/responsive_fields_row.dart
Normal file
@ -0,0 +1,73 @@
|
|||||||
|
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(),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,91 @@
|
|||||||
|
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,8 +9,11 @@ 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';
|
||||||
@ -21,7 +24,10 @@ 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).textTheme.bodySmall!.copyWith(color: Colors.black, fontSize: 13);
|
var text = Theme.of(context)
|
||||||
|
.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>(
|
||||||
@ -35,7 +41,8 @@ class VisitorPasswordDialog extends StatelessWidget {
|
|||||||
title: 'Sent Successfully',
|
title: 'Sent Successfully',
|
||||||
widgeta: Column(
|
widgeta: Column(
|
||||||
children: [
|
children: [
|
||||||
if (visitorBloc.passwordStatus!.failedOperations.isNotEmpty)
|
if (visitorBloc
|
||||||
|
.passwordStatus!.failedOperations.isNotEmpty)
|
||||||
Column(
|
Column(
|
||||||
children: [
|
children: [
|
||||||
const Text('Failed Devices'),
|
const Text('Failed Devices'),
|
||||||
@ -45,7 +52,8 @@ class VisitorPasswordDialog extends StatelessWidget {
|
|||||||
child: ListView.builder(
|
child: ListView.builder(
|
||||||
scrollDirection: Axis.horizontal,
|
scrollDirection: Axis.horizontal,
|
||||||
shrinkWrap: true,
|
shrinkWrap: true,
|
||||||
itemCount: visitorBloc.passwordStatus!.failedOperations.length,
|
itemCount: visitorBloc
|
||||||
|
.passwordStatus!.failedOperations.length,
|
||||||
itemBuilder: (context, index) {
|
itemBuilder: (context, index) {
|
||||||
return Container(
|
return Container(
|
||||||
margin: EdgeInsets.all(5),
|
margin: EdgeInsets.all(5),
|
||||||
@ -53,14 +61,17 @@ class VisitorPasswordDialog extends StatelessWidget {
|
|||||||
height: 45,
|
height: 45,
|
||||||
child: Center(
|
child: Center(
|
||||||
child: Text(visitorBloc
|
child: Text(visitorBloc
|
||||||
.passwordStatus!.failedOperations[index].deviceUuid)),
|
.passwordStatus!
|
||||||
|
.failedOperations[index]
|
||||||
|
.deviceName)),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
if (visitorBloc.passwordStatus!.successOperations.isNotEmpty)
|
if (visitorBloc
|
||||||
|
.passwordStatus!.successOperations.isNotEmpty)
|
||||||
Column(
|
Column(
|
||||||
children: [
|
children: [
|
||||||
const Text('Success Devices'),
|
const Text('Success Devices'),
|
||||||
@ -70,15 +81,18 @@ class VisitorPasswordDialog extends StatelessWidget {
|
|||||||
child: ListView.builder(
|
child: ListView.builder(
|
||||||
scrollDirection: Axis.horizontal,
|
scrollDirection: Axis.horizontal,
|
||||||
shrinkWrap: true,
|
shrinkWrap: true,
|
||||||
itemCount: visitorBloc.passwordStatus!.successOperations.length,
|
itemCount: visitorBloc
|
||||||
|
.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.passwordStatus!
|
child: Text(visitorBloc
|
||||||
.successOperations[index].deviceUuid)),
|
.passwordStatus!
|
||||||
|
.successOperations[index]
|
||||||
|
.deviceName)),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
@ -88,8 +102,7 @@ class VisitorPasswordDialog extends StatelessWidget {
|
|||||||
],
|
],
|
||||||
))
|
))
|
||||||
.then((v) {
|
.then((v) {
|
||||||
Navigator.of(context).pop(true);
|
Navigator.of(context).pop(v);
|
||||||
|
|
||||||
});
|
});
|
||||||
} else if (state is FailedState) {
|
} else if (state is FailedState) {
|
||||||
visitorBloc.stateDialog(
|
visitorBloc.stateDialog(
|
||||||
@ -102,15 +115,16 @@ 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 = state is IsRepeatState ? state.repeat : visitorBloc.repeat;
|
bool isRepeat =
|
||||||
|
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)
|
style: Theme.of(context).textTheme.headlineLarge!.copyWith(
|
||||||
.textTheme
|
fontWeight: FontWeight.w400,
|
||||||
.headlineLarge!
|
fontSize: 24,
|
||||||
.copyWith(fontWeight: FontWeight.w400, fontSize: 24, color: Colors.black),
|
color: Colors.black),
|
||||||
),
|
),
|
||||||
content: state is LoadingInitialState
|
content: state is LoadingInitialState
|
||||||
? const Center(child: CircularProgressIndicator())
|
? const Center(child: CircularProgressIndicator())
|
||||||
@ -121,34 +135,11 @@ class VisitorPasswordDialog extends StatelessWidget {
|
|||||||
padding: const EdgeInsets.all(5.0),
|
padding: const EdgeInsets.all(5.0),
|
||||||
child: ListBody(
|
child: ListBody(
|
||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
Container(
|
NameAndEmailFields(
|
||||||
child: Row(
|
nameController: visitorBloc.userNameController,
|
||||||
children: [
|
emailController: visitorBloc.emailController,
|
||||||
Expanded(
|
nameValidator: visitorBloc.validate,
|
||||||
flex: 2,
|
emailValidator: visitorBloc.validateEmail,
|
||||||
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,
|
||||||
@ -156,107 +147,43 @@ class VisitorPasswordDialog extends StatelessWidget {
|
|||||||
Column(
|
Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
Row(
|
AccessTypeRadioGroup(
|
||||||
children: [
|
selectedType: state is PasswordTypeSelected
|
||||||
Text(
|
? state.selectedType
|
||||||
'* ',
|
: null,
|
||||||
style: Theme.of(context)
|
accessTypeSelected:
|
||||||
.textTheme
|
visitorBloc.accessTypeSelected,
|
||||||
.bodyMedium!
|
onTypeSelected: (value) {
|
||||||
.copyWith(color: Colors.red),
|
context
|
||||||
),
|
.read<VisitorPasswordBloc>()
|
||||||
Text('Access Type', style: text),
|
.add(SelectPasswordType(value));
|
||||||
],
|
},
|
||||||
|
visitorBloc: visitorBloc,
|
||||||
),
|
),
|
||||||
Row(
|
|
||||||
children: <Widget>[
|
if (visitorBloc.accessTypeSelected ==
|
||||||
Expanded(
|
'Online Password')
|
||||||
flex: 2,
|
|
||||||
child: Row(
|
|
||||||
children: [
|
|
||||||
SizedBox(
|
|
||||||
width: size.width * 0.12,
|
|
||||||
child: RadioListTile<String>(
|
|
||||||
contentPadding: EdgeInsets.zero,
|
|
||||||
title: Text(
|
|
||||||
'Online Password',
|
|
||||||
style: text,
|
|
||||||
),
|
|
||||||
value: 'Online Password',
|
|
||||||
groupValue: (state is PasswordTypeSelected)
|
|
||||||
? state.selectedType
|
|
||||||
: visitorBloc.accessTypeSelected,
|
|
||||||
onChanged: (String? value) {
|
|
||||||
if (value != null) {
|
|
||||||
context
|
|
||||||
.read<VisitorPasswordBloc>()
|
|
||||||
.add(SelectPasswordType(value));
|
|
||||||
}
|
|
||||||
},
|
|
||||||
),
|
|
||||||
),
|
|
||||||
SizedBox(
|
|
||||||
width: size.width * 0.12,
|
|
||||||
child: RadioListTile<String>(
|
|
||||||
contentPadding: EdgeInsets.zero,
|
|
||||||
title: Text('Offline Password', style: text),
|
|
||||||
value: 'Offline Password',
|
|
||||||
groupValue: (state is PasswordTypeSelected)
|
|
||||||
? state.selectedType
|
|
||||||
: visitorBloc.accessTypeSelected,
|
|
||||||
onChanged: (String? value) {
|
|
||||||
if (value != null) {
|
|
||||||
context
|
|
||||||
.read<VisitorPasswordBloc>()
|
|
||||||
.add(SelectPasswordType(value));
|
|
||||||
}
|
|
||||||
},
|
|
||||||
),
|
|
||||||
),
|
|
||||||
// SizedBox(
|
|
||||||
// width: size.width * 0.12,
|
|
||||||
// child: RadioListTile<String>(
|
|
||||||
// contentPadding: EdgeInsets.zero,
|
|
||||||
// title: Text(
|
|
||||||
// 'Dynamic Password',
|
|
||||||
// style: text,
|
|
||||||
// ),
|
|
||||||
// value: 'Dynamic Password',
|
|
||||||
// groupValue: (state is PasswordTypeSelected)
|
|
||||||
// ? state.selectedType
|
|
||||||
// : visitorBloc.accessTypeSelected,
|
|
||||||
// onChanged: (String? value) {
|
|
||||||
// if (value != null) {
|
|
||||||
// context
|
|
||||||
// .read<VisitorPasswordBloc>()
|
|
||||||
// .add(SelectPasswordType(value));
|
|
||||||
// visitorBloc.usageFrequencySelected = '';
|
|
||||||
// }
|
|
||||||
// },
|
|
||||||
// ),
|
|
||||||
// ),
|
|
||||||
],
|
|
||||||
)),
|
|
||||||
const Spacer(
|
|
||||||
flex: 2,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
if (visitorBloc.accessTypeSelected == 'Online Password')
|
|
||||||
Text(
|
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).textTheme.bodySmall!.copyWith(
|
style: Theme.of(context)
|
||||||
fontWeight: FontWeight.w400,
|
.textTheme
|
||||||
color: ColorsManager.grayColor,
|
.bodySmall!
|
||||||
fontSize: 9),
|
.copyWith(
|
||||||
|
fontWeight: FontWeight.w400,
|
||||||
|
color: ColorsManager.grayColor,
|
||||||
|
fontSize: 9),
|
||||||
),
|
),
|
||||||
if (visitorBloc.accessTypeSelected == 'Offline Password')
|
if (visitorBloc.accessTypeSelected ==
|
||||||
|
'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).textTheme.bodySmall!.copyWith(
|
style: Theme.of(context)
|
||||||
fontWeight: FontWeight.w400,
|
.textTheme
|
||||||
color: ColorsManager.grayColor,
|
.bodySmall!
|
||||||
fontSize: 9),
|
.copyWith(
|
||||||
|
fontWeight: FontWeight.w400,
|
||||||
|
color: ColorsManager.grayColor,
|
||||||
|
fontSize: 9),
|
||||||
),
|
),
|
||||||
// if (visitorBloc.accessTypeSelected == 'Dynamic Password')
|
// if (visitorBloc.accessTypeSelected == 'Dynamic Password')
|
||||||
// Text(
|
// Text(
|
||||||
@ -271,143 +198,170 @@ class VisitorPasswordDialog extends StatelessWidget {
|
|||||||
)
|
)
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
visitorBloc.accessTypeSelected == 'Dynamic Password'
|
if (visitorBloc.accessTypeSelected ==
|
||||||
? const SizedBox()
|
'Dynamic Password')
|
||||||
: Column(
|
const SizedBox()
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
else
|
||||||
|
Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Row(
|
||||||
children: [
|
children: [
|
||||||
Row(
|
Text(
|
||||||
children: [
|
'* ',
|
||||||
Text(
|
style: Theme.of(context)
|
||||||
'* ',
|
.textTheme
|
||||||
style: Theme.of(context)
|
.bodyMedium!
|
||||||
.textTheme
|
.copyWith(color: Colors.red),
|
||||||
.bodyMedium!
|
|
||||||
.copyWith(color: Colors.red),
|
|
||||||
),
|
|
||||||
Text(
|
|
||||||
'Usage Frequency',
|
|
||||||
style: text,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
),
|
||||||
Row(
|
Text(
|
||||||
children: <Widget>[
|
'Usage Frequency',
|
||||||
SizedBox(
|
style: text,
|
||||||
width: size.width * 0.12,
|
|
||||||
child: RadioListTile<String>(
|
|
||||||
contentPadding: EdgeInsets.zero,
|
|
||||||
title: Text(
|
|
||||||
'One-Time',
|
|
||||||
style: text,
|
|
||||||
),
|
|
||||||
value: 'One-Time',
|
|
||||||
groupValue: (state is UsageFrequencySelected)
|
|
||||||
? state.selectedFrequency
|
|
||||||
: visitorBloc.usageFrequencySelected,
|
|
||||||
onChanged: (String? value) {
|
|
||||||
if (value != null) {
|
|
||||||
context
|
|
||||||
.read<VisitorPasswordBloc>()
|
|
||||||
.add(SelectUsageFrequency(value));
|
|
||||||
}
|
|
||||||
},
|
|
||||||
),
|
|
||||||
),
|
|
||||||
SizedBox(
|
|
||||||
width: size.width * 0.12,
|
|
||||||
child: RadioListTile<String>(
|
|
||||||
contentPadding: EdgeInsets.zero,
|
|
||||||
title: Text('Periodic', style: text),
|
|
||||||
value: 'Periodic',
|
|
||||||
groupValue: (state is UsageFrequencySelected)
|
|
||||||
? state.selectedFrequency
|
|
||||||
: visitorBloc.usageFrequencySelected,
|
|
||||||
onChanged: (String? value) {
|
|
||||||
if (value != null) {
|
|
||||||
context.read<VisitorPasswordBloc>()
|
|
||||||
.add(SelectUsageFrequency(value));
|
|
||||||
}
|
|
||||||
},
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
),
|
||||||
|
|
||||||
//One-Time
|
|
||||||
if (visitorBloc.usageFrequencySelected == 'One-Time' &&
|
|
||||||
visitorBloc.accessTypeSelected == 'Online Password')
|
|
||||||
Text(
|
|
||||||
'Within the validity period, each device can be unlocked only once.',
|
|
||||||
style: Theme.of(context).textTheme.bodySmall!.copyWith(
|
|
||||||
color: ColorsManager.grayColor, fontSize: 9),
|
|
||||||
),
|
|
||||||
if (visitorBloc.usageFrequencySelected == 'One-Time' &&
|
|
||||||
visitorBloc.accessTypeSelected == 'Offline Password')
|
|
||||||
Text(
|
|
||||||
'Within the validity period, each device can be unlocked only once, and the maximum validity period is 6 hours',
|
|
||||||
style: Theme.of(context).textTheme.bodySmall!.copyWith(
|
|
||||||
color: ColorsManager.grayColor, fontSize: 9),
|
|
||||||
),
|
|
||||||
|
|
||||||
// Periodic
|
|
||||||
if (visitorBloc.usageFrequencySelected == 'Periodic' &&
|
|
||||||
visitorBloc.accessTypeSelected == 'Offline Password')
|
|
||||||
Text(
|
|
||||||
'Within the validity period, there is no limit to the number of times each device can be unlocked, and it should be used at least once within 24 hours after the entry into force.',
|
|
||||||
style: Theme.of(context).textTheme.bodySmall!.copyWith(
|
|
||||||
color: ColorsManager.grayColor, fontSize: 9),
|
|
||||||
),
|
|
||||||
|
|
||||||
if (visitorBloc.usageFrequencySelected == 'Periodic' &&
|
|
||||||
visitorBloc.accessTypeSelected == 'Online Password')
|
|
||||||
Text(
|
|
||||||
'Within the validity period, there is no limit to the number of times each device can be unlocked.',
|
|
||||||
style: Theme.of(context).textTheme.bodySmall!.copyWith(
|
|
||||||
color: ColorsManager.grayColor, fontSize: 9),
|
|
||||||
),
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
UsageFrequencyRadioGroup(
|
||||||
|
selectedFrequency:
|
||||||
|
state is UsageFrequencySelected
|
||||||
|
? state.selectedFrequency
|
||||||
|
: null,
|
||||||
|
usageFrequencySelected:
|
||||||
|
visitorBloc.usageFrequencySelected,
|
||||||
|
onFrequencySelected: (value) {
|
||||||
|
context
|
||||||
|
.read<VisitorPasswordBloc>()
|
||||||
|
.add(SelectUsageFrequency(value));
|
||||||
|
},
|
||||||
|
),
|
||||||
|
|
||||||
|
//One-Time
|
||||||
|
if (visitorBloc.usageFrequencySelected ==
|
||||||
|
'One-Time' &&
|
||||||
|
visitorBloc.accessTypeSelected ==
|
||||||
|
'Online Password')
|
||||||
|
Text(
|
||||||
|
'Within the validity period, each device can be unlocked only once.',
|
||||||
|
style: Theme.of(context)
|
||||||
|
.textTheme
|
||||||
|
.bodySmall!
|
||||||
|
.copyWith(
|
||||||
|
color: ColorsManager.grayColor,
|
||||||
|
fontSize: 9),
|
||||||
|
),
|
||||||
|
if (visitorBloc.usageFrequencySelected ==
|
||||||
|
'One-Time' &&
|
||||||
|
visitorBloc.accessTypeSelected ==
|
||||||
|
'Offline Password')
|
||||||
|
Text(
|
||||||
|
'Within the validity period, each device can be unlocked only once, and the maximum validity period is 6 hours',
|
||||||
|
style: Theme.of(context)
|
||||||
|
.textTheme
|
||||||
|
.bodySmall!
|
||||||
|
.copyWith(
|
||||||
|
color: ColorsManager.grayColor,
|
||||||
|
fontSize: 9),
|
||||||
|
),
|
||||||
|
|
||||||
|
// Periodic
|
||||||
|
if (visitorBloc.usageFrequencySelected ==
|
||||||
|
'Periodic' &&
|
||||||
|
visitorBloc.accessTypeSelected ==
|
||||||
|
'Offline Password')
|
||||||
|
Text(
|
||||||
|
'Within the validity period, there is no limit to the number of times each device can be unlocked, and it should be used at least once within 24 hours after the entry into force.',
|
||||||
|
style: Theme.of(context)
|
||||||
|
.textTheme
|
||||||
|
.bodySmall!
|
||||||
|
.copyWith(
|
||||||
|
color: ColorsManager.grayColor,
|
||||||
|
fontSize: 9),
|
||||||
|
),
|
||||||
|
|
||||||
|
if (visitorBloc.usageFrequencySelected ==
|
||||||
|
'Periodic' &&
|
||||||
|
visitorBloc.accessTypeSelected ==
|
||||||
|
'Online Password')
|
||||||
|
Text(
|
||||||
|
'Within the validity period, there is no limit to the number of times each device can be unlocked.',
|
||||||
|
style: Theme.of(context)
|
||||||
|
.textTheme
|
||||||
|
.bodySmall!
|
||||||
|
.copyWith(
|
||||||
|
color: ColorsManager.grayColor,
|
||||||
|
fontSize: 9),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
const SizedBox(
|
const SizedBox(
|
||||||
height: 20,
|
height: 20,
|
||||||
),
|
),
|
||||||
if ((visitorBloc.usageFrequencySelected != 'One-Time' ||
|
if ((visitorBloc.usageFrequencySelected !=
|
||||||
visitorBloc.accessTypeSelected != 'Offline Password') &&
|
'One-Time' ||
|
||||||
|
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 == 'Periodic' &&
|
if (visitorBloc.usageFrequencySelected ==
|
||||||
visitorBloc.accessTypeSelected == 'Offline Password') {
|
'Periodic' &&
|
||||||
visitorBloc.add(SelectTimeEvent(context: context, isEffective: false));
|
visitorBloc.accessTypeSelected ==
|
||||||
|
'Offline Password') {
|
||||||
|
visitorBloc.add(SelectTimeEvent(
|
||||||
|
context: context,
|
||||||
|
isEffective: false));
|
||||||
} else {
|
} else {
|
||||||
visitorBloc.add(SelectTimeVisitorPassword(context: context, isStart: false, isRepeat: false));
|
visitorBloc.add(
|
||||||
|
SelectTimeVisitorPassword(
|
||||||
|
context: context,
|
||||||
|
isStart: false,
|
||||||
|
isRepeat: false));
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
startTime: () {
|
startTime: () {
|
||||||
if (visitorBloc.usageFrequencySelected == 'Periodic' &&
|
if (visitorBloc.usageFrequencySelected ==
|
||||||
visitorBloc.accessTypeSelected == 'Offline Password') {
|
'Periodic' &&
|
||||||
visitorBloc.add(
|
visitorBloc.accessTypeSelected ==
|
||||||
SelectTimeEvent(context: context, isEffective: true));
|
'Offline Password') {
|
||||||
|
visitorBloc.add(SelectTimeEvent(
|
||||||
|
context: context,
|
||||||
|
isEffective: true));
|
||||||
} else {
|
} else {
|
||||||
visitorBloc.add(SelectTimeVisitorPassword(
|
visitorBloc.add(
|
||||||
context: context, isStart: true, isRepeat: false));
|
SelectTimeVisitorPassword(
|
||||||
|
context: context,
|
||||||
|
isStart: true,
|
||||||
|
isRepeat: false));
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
firstString: (visitorBloc.usageFrequencySelected ==
|
firstString: (visitorBloc
|
||||||
'Periodic' && visitorBloc.accessTypeSelected == 'Offline Password')
|
.usageFrequencySelected ==
|
||||||
|
'Periodic' &&
|
||||||
|
visitorBloc.accessTypeSelected ==
|
||||||
|
'Offline Password')
|
||||||
? visitorBloc.effectiveTime
|
? visitorBloc.effectiveTime
|
||||||
: visitorBloc.startTimeAccess.toString(),
|
: visitorBloc.startTimeAccess
|
||||||
secondString: (visitorBloc.usageFrequencySelected ==
|
.toString(),
|
||||||
'Periodic' && visitorBloc.accessTypeSelected == 'Offline Password')
|
secondString: (visitorBloc
|
||||||
|
.usageFrequencySelected ==
|
||||||
|
'Periodic' &&
|
||||||
|
visitorBloc.accessTypeSelected ==
|
||||||
|
'Offline Password')
|
||||||
? visitorBloc.expirationTime
|
? visitorBloc.expirationTime
|
||||||
: visitorBloc.endTimeAccess.toString(),
|
: visitorBloc.endTimeAccess.toString(),
|
||||||
icon: Assets.calendarIcon),
|
icon: Assets.calendarIcon),
|
||||||
const SizedBox(height: 10,),
|
const SizedBox(
|
||||||
Text(visitorBloc.accessPeriodValidate,
|
height: 10,
|
||||||
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,
|
||||||
),
|
),
|
||||||
@ -431,16 +385,21 @@ 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).textTheme.bodySmall!.copyWith(
|
style: Theme.of(context)
|
||||||
fontWeight: FontWeight.w400,
|
.textTheme
|
||||||
color: ColorsManager.grayColor,
|
.bodySmall!
|
||||||
fontSize: 9),
|
.copyWith(
|
||||||
|
fontWeight: FontWeight.w400,
|
||||||
|
color: ColorsManager.grayColor,
|
||||||
|
fontSize: 9),
|
||||||
),
|
),
|
||||||
const SizedBox(
|
const SizedBox(
|
||||||
height: 20,
|
height: 20,
|
||||||
),
|
),
|
||||||
if (visitorBloc.usageFrequencySelected == 'Periodic' &&
|
if (visitorBloc.usageFrequencySelected ==
|
||||||
visitorBloc.accessTypeSelected == 'Online Password')
|
'Periodic' &&
|
||||||
|
visitorBloc.accessTypeSelected ==
|
||||||
|
'Online Password')
|
||||||
SizedBox(
|
SizedBox(
|
||||||
width: 100,
|
width: 100,
|
||||||
child: Column(
|
child: Column(
|
||||||
@ -451,7 +410,8 @@ class VisitorPasswordDialog extends StatelessWidget {
|
|||||||
child: CupertinoSwitch(
|
child: CupertinoSwitch(
|
||||||
value: visitorBloc.repeat,
|
value: visitorBloc.repeat,
|
||||||
onChanged: (value) {
|
onChanged: (value) {
|
||||||
visitorBloc.add(ToggleRepeatEvent());
|
visitorBloc
|
||||||
|
.add(ToggleRepeatEvent());
|
||||||
},
|
},
|
||||||
applyTheme: true,
|
applyTheme: true,
|
||||||
),
|
),
|
||||||
@ -459,12 +419,16 @@ class VisitorPasswordDialog extends StatelessWidget {
|
|||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
if (visitorBloc.usageFrequencySelected == 'Periodic' &&
|
if (visitorBloc.usageFrequencySelected ==
|
||||||
visitorBloc.accessTypeSelected == 'Online Password')
|
'Periodic' &&
|
||||||
isRepeat ? const RepeatWidget() : const SizedBox(),
|
visitorBloc.accessTypeSelected ==
|
||||||
|
'Online Password')
|
||||||
|
isRepeat
|
||||||
|
? const RepeatWidget()
|
||||||
|
: const SizedBox(),
|
||||||
Container(
|
Container(
|
||||||
decoration: containerDecoration,
|
decoration: containerDecoration,
|
||||||
width: size.width / 9,
|
width: size.width / 6,
|
||||||
child: DefaultButton(
|
child: DefaultButton(
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
showDialog(
|
showDialog(
|
||||||
@ -472,22 +436,28 @@ class VisitorPasswordDialog extends StatelessWidget {
|
|||||||
barrierDismissible: false,
|
barrierDismissible: false,
|
||||||
builder: (BuildContext context) {
|
builder: (BuildContext context) {
|
||||||
return AddDeviceDialog(
|
return AddDeviceDialog(
|
||||||
selectedDeviceIds: visitorBloc.selectedDevices,
|
selectedDeviceIds:
|
||||||
|
visitorBloc.selectedDevices,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
).then((listDevice) {
|
).then((listDevice) {
|
||||||
if (listDevice != null) {
|
if (listDevice != null) {
|
||||||
visitorBloc.selectedDevices = listDevice;
|
visitorBloc.selectedDevices =
|
||||||
|
listDevice;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
borderRadius: 8,
|
borderRadius: 8,
|
||||||
child: Text(
|
child: Text(
|
||||||
'+ Add Device',
|
'+ Add Device',
|
||||||
style: Theme.of(context).textTheme.bodySmall!.copyWith(
|
style: Theme.of(context)
|
||||||
fontWeight: FontWeight.w400,
|
.textTheme
|
||||||
color: ColorsManager.whiteColors,
|
.bodySmall!
|
||||||
fontSize: 12),
|
.copyWith(
|
||||||
|
fontWeight: FontWeight.w400,
|
||||||
|
color:
|
||||||
|
ColorsManager.whiteColors,
|
||||||
|
fontSize: 12),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@ -506,7 +476,7 @@ class VisitorPasswordDialog extends StatelessWidget {
|
|||||||
child: DefaultButton(
|
child: DefaultButton(
|
||||||
borderRadius: 8,
|
borderRadius: 8,
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
Navigator.of(context).pop(true);
|
Navigator.of(context).pop(null);
|
||||||
},
|
},
|
||||||
backgroundColor: Colors.white,
|
backgroundColor: Colors.white,
|
||||||
child: Text(
|
child: Text(
|
||||||
@ -525,30 +495,37 @@ 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 == 'One-Time' &&
|
if (visitorBloc.usageFrequencySelected ==
|
||||||
visitorBloc.accessTypeSelected == 'Offline Password') {
|
'One-Time' &&
|
||||||
|
visitorBloc.accessTypeSelected ==
|
||||||
|
'Offline Password') {
|
||||||
setPasswordFunction(context, size, visitorBloc);
|
setPasswordFunction(context, size, visitorBloc);
|
||||||
} else if (visitorBloc.usageFrequencySelected == 'Periodic' &&
|
} else if (visitorBloc.usageFrequencySelected ==
|
||||||
visitorBloc.accessTypeSelected == 'Offline Password') {
|
'Periodic' &&
|
||||||
|
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: 'Please select Access Period to continue',
|
message:
|
||||||
|
'Please select Access Period to continue',
|
||||||
title: 'Access Period');
|
title: 'Access Period');
|
||||||
}
|
}
|
||||||
} else if(
|
} else if (visitorBloc.endTimeAccess.toString() !=
|
||||||
visitorBloc.endTimeAccess.toString()!='End Time'
|
'End Time' &&
|
||||||
&&visitorBloc.startTimeAccess.toString()!='Start Time') {
|
visitorBloc.startTimeAccess.toString() !=
|
||||||
|
'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(context, size, visitorBloc);
|
setPasswordFunction(
|
||||||
|
context, size, visitorBloc);
|
||||||
} else {
|
} else {
|
||||||
visitorBloc.stateDialog(
|
visitorBloc.stateDialog(
|
||||||
context: context,
|
context: context,
|
||||||
@ -562,14 +539,16 @@ class VisitorPasswordDialog extends StatelessWidget {
|
|||||||
} else {
|
} else {
|
||||||
visitorBloc.stateDialog(
|
visitorBloc.stateDialog(
|
||||||
context: context,
|
context: context,
|
||||||
message: 'Please select Access Period to continue',
|
message:
|
||||||
|
'Please select Access Period to continue',
|
||||||
title: 'Access Period');
|
title: 'Access Period');
|
||||||
}
|
}
|
||||||
}else{
|
} else {
|
||||||
visitorBloc.stateDialog(
|
visitorBloc.stateDialog(
|
||||||
context: context,
|
context: context,
|
||||||
message: 'Please select Access Period to continue',
|
message:
|
||||||
title: 'Access Period');
|
'Please select Access Period to continue',
|
||||||
|
title: 'Access Period');
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
visitorBloc.stateDialog(
|
visitorBloc.stateDialog(
|
||||||
@ -615,7 +594,8 @@ class VisitorPasswordDialog extends StatelessWidget {
|
|||||||
content: SizedBox(
|
content: SizedBox(
|
||||||
height: size.height * 0.25,
|
height: size.height * 0.25,
|
||||||
child: Center(
|
child: Center(
|
||||||
child: CircularProgressIndicator(), // Display a loading spinner
|
child:
|
||||||
|
CircularProgressIndicator(), // Display a loading spinner
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
@ -639,7 +619,10 @@ class VisitorPasswordDialog extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
Text(
|
Text(
|
||||||
'Set Password',
|
'Set Password',
|
||||||
style: Theme.of(context).textTheme.headlineLarge!.copyWith(
|
style: Theme.of(context)
|
||||||
|
.textTheme
|
||||||
|
.headlineLarge!
|
||||||
|
.copyWith(
|
||||||
fontSize: 30,
|
fontSize: 30,
|
||||||
fontWeight: FontWeight.w400,
|
fontWeight: FontWeight.w400,
|
||||||
color: Colors.black,
|
color: Colors.black,
|
||||||
@ -668,7 +651,7 @@ class VisitorPasswordDialog extends StatelessWidget {
|
|||||||
child: DefaultButton(
|
child: DefaultButton(
|
||||||
borderRadius: 8,
|
borderRadius: 8,
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
Navigator.of(context).pop();
|
Navigator.of(context).pop(null);
|
||||||
},
|
},
|
||||||
backgroundColor: Colors.white,
|
backgroundColor: Colors.white,
|
||||||
child: Text(
|
child: Text(
|
||||||
@ -689,37 +672,45 @@ class VisitorPasswordDialog extends StatelessWidget {
|
|||||||
onPressed: () {
|
onPressed: () {
|
||||||
Navigator.pop(context);
|
Navigator.pop(context);
|
||||||
if (visitorBloc.usageFrequencySelected == 'One-Time' &&
|
if (visitorBloc.usageFrequencySelected == 'One-Time' &&
|
||||||
visitorBloc.accessTypeSelected == 'Online Password') {
|
visitorBloc.accessTypeSelected ==
|
||||||
|
'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 ==
|
||||||
else if (visitorBloc.usageFrequencySelected == 'Periodic' &&
|
'Periodic' &&
|
||||||
visitorBloc.accessTypeSelected == 'Online Password') {
|
visitorBloc.accessTypeSelected ==
|
||||||
|
'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: visitorBloc.effectiveTimeTimeStamp.toString(),
|
effectiveTime:
|
||||||
invalidTime: visitorBloc.expirationTimeTimeStamp.toString(),
|
visitorBloc.effectiveTimeTimeStamp.toString(),
|
||||||
|
invalidTime:
|
||||||
|
visitorBloc.expirationTimeTimeStamp.toString(),
|
||||||
));
|
));
|
||||||
}
|
} else if (visitorBloc.usageFrequencySelected ==
|
||||||
else if (visitorBloc.usageFrequencySelected == 'One-Time' &&
|
'One-Time' &&
|
||||||
visitorBloc.accessTypeSelected == 'Offline Password') {
|
visitorBloc.accessTypeSelected ==
|
||||||
|
'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 ==
|
||||||
else if (visitorBloc.usageFrequencySelected == 'Periodic' &&
|
'Periodic' &&
|
||||||
visitorBloc.accessTypeSelected == 'Offline Password') {
|
visitorBloc.accessTypeSelected ==
|
||||||
|
'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: visitorBloc.effectiveTimeTimeStamp.toString(),
|
effectiveTime:
|
||||||
invalidTime: visitorBloc.expirationTimeTimeStamp.toString(),
|
visitorBloc.effectiveTimeTimeStamp.toString(),
|
||||||
|
invalidTime:
|
||||||
|
visitorBloc.expirationTimeTimeStamp.toString(),
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
Reference in New Issue
Block a user