mirror of
https://github.com/SyncrowIOT/web.git
synced 2025-07-15 17:47:53 +00:00
Compare commits
24 Commits
Build-Sche
...
SP-1721-FE
Author | SHA1 | Date | |
---|---|---|---|
d65f9ceea9 | |||
f539b0ac8d | |||
5a3cf93748 | |||
e740652507 | |||
c60078c96a | |||
903c5dd29b | |||
df39fca050 | |||
f832c5d884 | |||
fa930571dc | |||
acefe7b355 | |||
b223194950 | |||
466f5b89c7 | |||
de5d8df01c | |||
5218641705 | |||
ab6a6851f2 | |||
beb33e37fa | |||
3bee17c574 | |||
f4b5c6fb52 | |||
086f3cedf8 | |||
035c03c6b2 | |||
cf1b34ee0a | |||
5663e2084e | |||
3cd0125310 | |||
e0980b324c |
5
assets/icons/x_delete.svg
Normal file
5
assets/icons/x_delete.svg
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
<svg width="35" height="35" viewBox="0 0 35 35" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M17.5 34.9999C27.1649 34.9999 34.9999 27.1649 34.9999 17.4999C34.9999 7.83499 27.1649 0 17.5 0C7.83499 0 0 7.83499 0 17.5C0 27.1651 7.83499 34.9999 17.5 34.9999Z" fill="#FF6465"/>
|
||||||
|
<path opacity="0.1" d="M4.70804 17.5C4.70804 8.63343 11.3024 1.30805 19.854 0.158115C19.0839 0.0545507 18.2984 0 17.5 0C7.835 0 0 7.835 0 17.5C0 27.1651 7.83499 35 17.4999 35C18.2983 35 19.0839 34.9455 19.8539 34.8419C11.3024 33.6919 4.70804 26.3665 4.70804 17.5Z" fill="black"/>
|
||||||
|
<path d="M21.4229 17.5003L26.0301 12.8931C26.365 12.5582 26.365 12.0152 26.0301 11.6804L23.3197 8.96992C22.9848 8.63503 22.4418 8.63503 22.107 8.96992L17.4997 13.5772L12.8924 8.96992C12.5576 8.63503 12.0146 8.63503 11.6798 8.96992L8.96931 11.6804C8.63442 12.0153 8.63442 12.5582 8.96931 12.8931L13.5766 17.5003L8.96931 22.1076C8.63442 22.4425 8.63442 22.9855 8.96931 23.3204L11.6798 26.0308C12.0146 26.3657 12.5576 26.3657 12.8924 26.0308L17.4997 21.4235L22.1071 26.0308C22.442 26.3657 22.9849 26.3657 23.3198 26.0308L26.0302 23.3204C26.3651 22.9855 26.3651 22.4425 26.0302 22.1076L21.4229 17.5003Z" fill="white"/>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 1.2 KiB |
@ -29,9 +29,7 @@ class CountdownModeButtons extends StatelessWidget {
|
|||||||
children: [
|
children: [
|
||||||
Expanded(
|
Expanded(
|
||||||
child: DefaultButton(
|
child: DefaultButton(
|
||||||
elevation: 2.5,
|
|
||||||
height: 40,
|
height: 40,
|
||||||
borderRadius: 8,
|
|
||||||
onPressed: () => Navigator.pop(context),
|
onPressed: () => Navigator.pop(context),
|
||||||
backgroundColor: ColorsManager.boxColor,
|
backgroundColor: ColorsManager.boxColor,
|
||||||
child: Text('Cancel', style: context.textTheme.bodyMedium),
|
child: Text('Cancel', style: context.textTheme.bodyMedium),
|
||||||
@ -41,8 +39,6 @@ class CountdownModeButtons extends StatelessWidget {
|
|||||||
Expanded(
|
Expanded(
|
||||||
child: isActive
|
child: isActive
|
||||||
? DefaultButton(
|
? DefaultButton(
|
||||||
elevation: 2.5,
|
|
||||||
borderRadius: 8,
|
|
||||||
height: 40,
|
height: 40,
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
context.read<ScheduleBloc>().add(
|
context.read<ScheduleBloc>().add(
|
||||||
@ -53,12 +49,10 @@ class CountdownModeButtons extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
backgroundColor: ColorsManager.red100,
|
backgroundColor: Colors.red,
|
||||||
child: const Text('Stop'),
|
child: const Text('Stop'),
|
||||||
)
|
)
|
||||||
: DefaultButton(
|
: DefaultButton(
|
||||||
elevation: 2.5,
|
|
||||||
borderRadius: 8,
|
|
||||||
height: 40,
|
height: 40,
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
context.read<ScheduleBloc>().add(
|
context.read<ScheduleBloc>().add(
|
||||||
@ -69,7 +63,7 @@ class CountdownModeButtons extends StatelessWidget {
|
|||||||
countDownCode: countDownCode),
|
countDownCode: countDownCode),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
backgroundColor: ColorsManager.primaryColorWithOpacity,
|
backgroundColor: ColorsManager.primaryColor,
|
||||||
child: const Text('Save'),
|
child: const Text('Save'),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
@ -226,7 +226,6 @@ class _CountdownInchingViewState extends State<CountdownInchingView> {
|
|||||||
index.toString().padLeft(2, '0'),
|
index.toString().padLeft(2, '0'),
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: 24,
|
fontSize: 24,
|
||||||
fontWeight: FontWeight.w400,
|
|
||||||
color: isActive ? ColorsManager.grayColor : Colors.black,
|
color: isActive ? ColorsManager.grayColor : Colors.black,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@ -241,8 +240,7 @@ class _CountdownInchingViewState extends State<CountdownInchingView> {
|
|||||||
label,
|
label,
|
||||||
style: const TextStyle(
|
style: const TextStyle(
|
||||||
color: ColorsManager.grayColor,
|
color: ColorsManager.grayColor,
|
||||||
fontSize: 24,
|
fontSize: 18,
|
||||||
fontWeight: FontWeight.w400,
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
@ -31,11 +31,12 @@ class BuildScheduleView extends StatelessWidget {
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return BlocProvider(
|
return BlocProvider(
|
||||||
create: (_) => ScheduleBloc(deviceId: deviceUuid,)
|
create: (_) => ScheduleBloc(
|
||||||
|
deviceId: deviceUuid,
|
||||||
|
)
|
||||||
..add(ScheduleGetEvent(category: category))
|
..add(ScheduleGetEvent(category: category))
|
||||||
..add(ScheduleFetchStatusEvent(
|
..add(ScheduleFetchStatusEvent(
|
||||||
deviceId: deviceUuid,
|
deviceId: deviceUuid, countdownCode: countdownCode ?? '')),
|
||||||
countdownCode: countdownCode ?? '')),
|
|
||||||
child: Dialog(
|
child: Dialog(
|
||||||
backgroundColor: Colors.white,
|
backgroundColor: Colors.white,
|
||||||
insetPadding: const EdgeInsets.all(20),
|
insetPadding: const EdgeInsets.all(20),
|
||||||
@ -76,8 +77,7 @@ class BuildScheduleView extends StatelessWidget {
|
|||||||
category: category,
|
category: category,
|
||||||
time: '',
|
time: '',
|
||||||
function: Status(
|
function: Status(
|
||||||
code: code.toString(),
|
code: code.toString(), value: null),
|
||||||
value: true),
|
|
||||||
days: [],
|
days: [],
|
||||||
),
|
),
|
||||||
isEdit: false,
|
isEdit: false,
|
||||||
|
@ -13,9 +13,9 @@ class ScheduleHeader extends StatelessWidget {
|
|||||||
Text(
|
Text(
|
||||||
'Scheduling',
|
'Scheduling',
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
color: ColorsManager.primaryColorWithOpacity,
|
fontWeight: FontWeight.bold,
|
||||||
fontWeight: FontWeight.w700,
|
fontSize: 22,
|
||||||
fontSize: 30,
|
color: ColorsManager.dialogBlueTitle,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
Container(
|
Container(
|
||||||
|
@ -27,7 +27,7 @@ class ScheduleManagementUI extends StatelessWidget {
|
|||||||
width: 170,
|
width: 170,
|
||||||
height: 40,
|
height: 40,
|
||||||
child: DefaultButton(
|
child: DefaultButton(
|
||||||
borderColor: ColorsManager.grayColor.withOpacity(0.5),
|
borderColor: ColorsManager.boxColor,
|
||||||
padding: 2,
|
padding: 2,
|
||||||
backgroundColor: ColorsManager.graysColor,
|
backgroundColor: ColorsManager.graysColor,
|
||||||
borderRadius: 15,
|
borderRadius: 15,
|
||||||
|
@ -19,8 +19,6 @@ class ScheduleModeButtons extends StatelessWidget {
|
|||||||
children: [
|
children: [
|
||||||
Expanded(
|
Expanded(
|
||||||
child: DefaultButton(
|
child: DefaultButton(
|
||||||
elevation: 2.5,
|
|
||||||
borderRadius: 8,
|
|
||||||
height: 40,
|
height: 40,
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
Navigator.pop(context);
|
Navigator.pop(context);
|
||||||
@ -35,11 +33,9 @@ class ScheduleModeButtons extends StatelessWidget {
|
|||||||
const SizedBox(width: 20),
|
const SizedBox(width: 20),
|
||||||
Expanded(
|
Expanded(
|
||||||
child: DefaultButton(
|
child: DefaultButton(
|
||||||
elevation: 2.5,
|
|
||||||
borderRadius: 8,
|
|
||||||
height: 40,
|
height: 40,
|
||||||
onPressed: onSave,
|
onPressed: onSave,
|
||||||
backgroundColor: ColorsManager.primaryColorWithOpacity,
|
backgroundColor: ColorsManager.primaryColor,
|
||||||
child: const Text('Save'),
|
child: const Text('Save'),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
@ -35,12 +35,12 @@ class ScheduleModeSelector extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
const SizedBox(height: 4),
|
const SizedBox(height: 4),
|
||||||
Row(
|
Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
||||||
children: [
|
children: [
|
||||||
_buildRadioTile(
|
_buildRadioTile(
|
||||||
context, 'Countdown', ScheduleModes.countdown, currentMode),
|
context, 'Countdown', ScheduleModes.countdown, currentMode),
|
||||||
_buildRadioTile(
|
_buildRadioTile(
|
||||||
context, 'Schedule', ScheduleModes.schedule, currentMode),
|
context, 'Schedule', ScheduleModes.schedule, currentMode),
|
||||||
const Spacer(flex: 1),
|
|
||||||
// _buildRadioTile(
|
// _buildRadioTile(
|
||||||
// context, 'Circulate', ScheduleModes.circulate, currentMode),
|
// context, 'Circulate', ScheduleModes.circulate, currentMode),
|
||||||
// _buildRadioTile(
|
// _buildRadioTile(
|
||||||
@ -65,7 +65,6 @@ class ScheduleModeSelector extends StatelessWidget {
|
|||||||
style: context.textTheme.bodySmall!.copyWith(
|
style: context.textTheme.bodySmall!.copyWith(
|
||||||
fontSize: 13,
|
fontSize: 13,
|
||||||
color: ColorsManager.blackColor,
|
color: ColorsManager.blackColor,
|
||||||
fontWeight: FontWeight.w400,
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
leading: Radio<ScheduleModes>(
|
leading: Radio<ScheduleModes>(
|
||||||
|
@ -1,8 +1,6 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:syncrow_web/pages/device_managment/schedule_device/schedule_widgets/schedule_mode_buttons.dart';
|
|
||||||
import 'package:syncrow_web/pages/device_managment/water_heater/models/schedule_entry.dart';
|
import 'package:syncrow_web/pages/device_managment/water_heater/models/schedule_entry.dart';
|
||||||
import 'package:syncrow_web/pages/device_managment/all_devices/models/device_status.dart';
|
import 'package:syncrow_web/pages/device_managment/all_devices/models/device_status.dart';
|
||||||
import 'package:syncrow_web/utils/color_manager.dart';
|
|
||||||
|
|
||||||
class ScheduleDialogHelper {
|
class ScheduleDialogHelper {
|
||||||
static const List<String> allDays = [
|
static const List<String> allDays = [
|
||||||
@ -58,9 +56,8 @@ class ScheduleDialogHelper {
|
|||||||
Text(
|
Text(
|
||||||
isEdit ? 'Edit Schedule' : 'Add Schedule',
|
isEdit ? 'Edit Schedule' : 'Add Schedule',
|
||||||
style: Theme.of(context).textTheme.titleLarge!.copyWith(
|
style: Theme.of(context).textTheme.titleLarge!.copyWith(
|
||||||
color: ColorsManager.primaryColorWithOpacity,
|
color: Colors.blue,
|
||||||
fontWeight: FontWeight.w700,
|
fontWeight: FontWeight.bold,
|
||||||
fontSize: 30,
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(),
|
const SizedBox(),
|
||||||
@ -72,9 +69,9 @@ class ScheduleDialogHelper {
|
|||||||
height: 40,
|
height: 40,
|
||||||
child: ElevatedButton(
|
child: ElevatedButton(
|
||||||
style: ElevatedButton.styleFrom(
|
style: ElevatedButton.styleFrom(
|
||||||
backgroundColor: ColorsManager.boxColor,
|
backgroundColor: Colors.grey[200],
|
||||||
shape: RoundedRectangleBorder(
|
shape: RoundedRectangleBorder(
|
||||||
borderRadius: BorderRadius.circular(8),
|
borderRadius: BorderRadius.circular(15),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
onPressed: () async {
|
onPressed: () async {
|
||||||
@ -113,27 +110,39 @@ class ScheduleDialogHelper {
|
|||||||
],
|
],
|
||||||
),
|
),
|
||||||
actions: [
|
actions: [
|
||||||
ScheduleModeButtons(
|
SizedBox(
|
||||||
onSave: () {
|
width: 100,
|
||||||
dynamic temp;
|
child: OutlinedButton(
|
||||||
if (deviceType == 'CUR_2') {
|
onPressed: () {
|
||||||
temp = functionOn! ? 'open' : 'close';
|
Navigator.pop(ctx, null);
|
||||||
} else {
|
},
|
||||||
temp = functionOn;
|
child: const Text('Cancel'),
|
||||||
}
|
),
|
||||||
final entry = ScheduleEntry(
|
|
||||||
category: schedule?.category ?? 'switch_1',
|
|
||||||
time: _formatTimeOfDayToISO(selectedTime),
|
|
||||||
function: Status(
|
|
||||||
code: code ?? 'switch_1',
|
|
||||||
value: temp,
|
|
||||||
),
|
|
||||||
days: _convertSelectedDaysToStrings(selectedDays),
|
|
||||||
scheduleId: schedule.scheduleId,
|
|
||||||
);
|
|
||||||
Navigator.pop(ctx, entry);
|
|
||||||
},
|
|
||||||
),
|
),
|
||||||
|
SizedBox(
|
||||||
|
width: 100,
|
||||||
|
child: ElevatedButton(
|
||||||
|
onPressed: () {
|
||||||
|
dynamic temp;
|
||||||
|
if (deviceType == 'CUR_2') {
|
||||||
|
temp = functionOn! ? 'open' : 'close';
|
||||||
|
} else {
|
||||||
|
temp = functionOn;
|
||||||
|
}
|
||||||
|
final entry = ScheduleEntry(
|
||||||
|
category: schedule?.category ?? 'switch_1',
|
||||||
|
time: _formatTimeOfDayToISO(selectedTime),
|
||||||
|
function: Status(
|
||||||
|
code: code ?? 'switch_1',
|
||||||
|
value: temp,
|
||||||
|
),
|
||||||
|
days: _convertSelectedDaysToStrings(selectedDays),
|
||||||
|
scheduleId: schedule.scheduleId,
|
||||||
|
);
|
||||||
|
Navigator.pop(ctx, entry);
|
||||||
|
},
|
||||||
|
child: const Text('Save'),
|
||||||
|
)),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
@ -0,0 +1,43 @@
|
|||||||
|
import 'package:syncrow_web/pages/space_management_v2/modules/communities/domain/models/space_model.dart';
|
||||||
|
import 'package:syncrow_web/pages/space_management_v2/modules/space_details/domain/models/space_details_model.dart';
|
||||||
|
|
||||||
|
abstract final class SpacesRecursiveHelper {
|
||||||
|
static List<SpaceModel> recusrivelyUpdate(
|
||||||
|
List<SpaceModel> spaces,
|
||||||
|
SpaceDetailsModel updatedSpace,
|
||||||
|
) {
|
||||||
|
return spaces.map((space) {
|
||||||
|
final isUpdatedSpace = space.uuid == updatedSpace.uuid;
|
||||||
|
if (isUpdatedSpace) {
|
||||||
|
return space.copyWith(
|
||||||
|
spaceName: updatedSpace.spaceName,
|
||||||
|
icon: updatedSpace.icon,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
final hasChildren = space.children.isNotEmpty;
|
||||||
|
if (hasChildren) {
|
||||||
|
return space.copyWith(
|
||||||
|
children: recusrivelyUpdate(space.children, updatedSpace),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return space;
|
||||||
|
}).toList();
|
||||||
|
}
|
||||||
|
|
||||||
|
static List<SpaceModel> recusrivelyDelete(
|
||||||
|
List<SpaceModel> spaces,
|
||||||
|
String spaceUuid,
|
||||||
|
) {
|
||||||
|
final updatedSpaces = spaces.map((space) {
|
||||||
|
if (space.uuid == spaceUuid) return null;
|
||||||
|
if (space.children.isNotEmpty) {
|
||||||
|
return space.copyWith(
|
||||||
|
children: recusrivelyDelete(space.children, spaceUuid),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return space;
|
||||||
|
}).toList();
|
||||||
|
final nonNullSpaces = updatedSpaces.whereType<SpaceModel>().toList();
|
||||||
|
return nonNullSpaces;
|
||||||
|
}
|
||||||
|
}
|
@ -5,13 +5,14 @@ import 'package:syncrow_web/utils/color_manager.dart';
|
|||||||
class SpacesConnectionsArrowPainter extends CustomPainter {
|
class SpacesConnectionsArrowPainter extends CustomPainter {
|
||||||
final List<SpaceConnectionModel> connections;
|
final List<SpaceConnectionModel> connections;
|
||||||
final Map<String, Offset> positions;
|
final Map<String, Offset> positions;
|
||||||
final double cardWidth = 150.0;
|
final Map<String, double> cardWidths;
|
||||||
final double cardHeight = 90.0;
|
final double cardHeight = 90.0;
|
||||||
final Set<String> highlightedUuids;
|
final Set<String> highlightedUuids;
|
||||||
|
|
||||||
SpacesConnectionsArrowPainter({
|
SpacesConnectionsArrowPainter({
|
||||||
required this.connections,
|
required this.connections,
|
||||||
required this.positions,
|
required this.positions,
|
||||||
|
required this.cardWidths,
|
||||||
required this.highlightedUuids,
|
required this.highlightedUuids,
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -29,19 +30,30 @@ class SpacesConnectionsArrowPainter extends CustomPainter {
|
|||||||
|
|
||||||
final from = positions[connection.from];
|
final from = positions[connection.from];
|
||||||
final to = positions[connection.to];
|
final to = positions[connection.to];
|
||||||
|
final fromWidth = cardWidths[connection.from] ?? 150.0;
|
||||||
|
final toWidth = cardWidths[connection.to] ?? 150.0;
|
||||||
|
|
||||||
if (from != null && to != null) {
|
if (from != null && to != null) {
|
||||||
final startPoint =
|
final startPoint =
|
||||||
Offset(from.dx + cardWidth / 2, from.dy + cardHeight - 10);
|
Offset(from.dx + fromWidth / 2, from.dy + cardHeight - 10);
|
||||||
final endPoint = Offset(to.dx + cardWidth / 2, to.dy);
|
final endPoint = Offset(to.dx + toWidth / 2, to.dy);
|
||||||
|
|
||||||
final path = Path()..moveTo(startPoint.dx, startPoint.dy);
|
final path = Path()..moveTo(startPoint.dx, startPoint.dy);
|
||||||
|
|
||||||
final controlPoint1 = Offset(startPoint.dx, startPoint.dy + 20);
|
if ((startPoint.dx - endPoint.dx).abs() < 1.0) {
|
||||||
final controlPoint2 = Offset(endPoint.dx, endPoint.dy - 60);
|
path.lineTo(endPoint.dx, endPoint.dy);
|
||||||
|
} else {
|
||||||
path.cubicTo(controlPoint1.dx, controlPoint1.dy, controlPoint2.dx,
|
final controlPoint1 = Offset(startPoint.dx, startPoint.dy + 100);
|
||||||
controlPoint2.dy, endPoint.dx, endPoint.dy);
|
final controlPoint2 = Offset(endPoint.dx, endPoint.dy - 100);
|
||||||
|
path.cubicTo(
|
||||||
|
controlPoint1.dx,
|
||||||
|
controlPoint1.dy,
|
||||||
|
controlPoint2.dx,
|
||||||
|
controlPoint2.dy,
|
||||||
|
endPoint.dx,
|
||||||
|
endPoint.dy,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
canvas.drawPath(path, paint);
|
canvas.drawPath(path, paint);
|
||||||
|
|
||||||
@ -51,7 +63,7 @@ class SpacesConnectionsArrowPainter extends CustomPainter {
|
|||||||
: ColorsManager.blackColor.withValues(alpha: 0.5)
|
: ColorsManager.blackColor.withValues(alpha: 0.5)
|
||||||
..style = PaintingStyle.fill
|
..style = PaintingStyle.fill
|
||||||
..blendMode = BlendMode.srcIn;
|
..blendMode = BlendMode.srcIn;
|
||||||
canvas.drawCircle(endPoint, 4, circlePaint);
|
canvas.drawCircle(endPoint, 6, circlePaint);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -10,7 +10,7 @@ import 'package:syncrow_web/pages/space_management_v2/modules/communities/presen
|
|||||||
import 'package:syncrow_web/pages/space_management_v2/modules/products/data/services/remote_products_service.dart';
|
import 'package:syncrow_web/pages/space_management_v2/modules/products/data/services/remote_products_service.dart';
|
||||||
import 'package:syncrow_web/pages/space_management_v2/modules/products/presentation/bloc/products_bloc.dart';
|
import 'package:syncrow_web/pages/space_management_v2/modules/products/presentation/bloc/products_bloc.dart';
|
||||||
import 'package:syncrow_web/pages/space_management_v2/modules/space_details/data/services/remote_space_details_service.dart';
|
import 'package:syncrow_web/pages/space_management_v2/modules/space_details/data/services/remote_space_details_service.dart';
|
||||||
import 'package:syncrow_web/pages/space_management_v2/modules/space_details/data/services/unique_subspaces_decorator.dart';
|
import 'package:syncrow_web/pages/space_management_v2/modules/space_details/data/services/unique_space_details_spaces_decorator_service.dart';
|
||||||
import 'package:syncrow_web/pages/space_management_v2/modules/space_details/presentation/bloc/space_details_bloc.dart';
|
import 'package:syncrow_web/pages/space_management_v2/modules/space_details/presentation/bloc/space_details_bloc.dart';
|
||||||
import 'package:syncrow_web/services/api/http_service.dart';
|
import 'package:syncrow_web/services/api/http_service.dart';
|
||||||
import 'package:syncrow_web/utils/theme/responsive_text_theme.dart';
|
import 'package:syncrow_web/utils/theme/responsive_text_theme.dart';
|
||||||
@ -49,7 +49,7 @@ class _SpaceManagementPageState extends State<SpaceManagementPage> {
|
|||||||
),
|
),
|
||||||
BlocProvider(
|
BlocProvider(
|
||||||
create: (context) => SpaceDetailsBloc(
|
create: (context) => SpaceDetailsBloc(
|
||||||
UniqueSubspacesDecorator(
|
UniqueSpaceDetailsSpacesDecoratorService(
|
||||||
RemoteSpaceDetailsService(httpService: HTTPService()),
|
RemoteSpaceDetailsService(httpService: HTTPService()),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
@ -30,10 +30,11 @@ class CommunityStructureCanvas extends StatefulWidget {
|
|||||||
class _CommunityStructureCanvasState extends State<CommunityStructureCanvas>
|
class _CommunityStructureCanvasState extends State<CommunityStructureCanvas>
|
||||||
with SingleTickerProviderStateMixin {
|
with SingleTickerProviderStateMixin {
|
||||||
final Map<String, Offset> _positions = {};
|
final Map<String, Offset> _positions = {};
|
||||||
final double _cardWidth = 150.0;
|
final Map<String, double> _cardWidths = {};
|
||||||
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;
|
||||||
|
static const double _minCardWidth = 150.0;
|
||||||
|
|
||||||
late final TransformationController _transformationController;
|
late final TransformationController _transformationController;
|
||||||
late final AnimationController _animationController;
|
late final AnimationController _animationController;
|
||||||
@ -52,6 +53,7 @@ class _CommunityStructureCanvasState extends State<CommunityStructureCanvas>
|
|||||||
@override
|
@override
|
||||||
void didUpdateWidget(covariant CommunityStructureCanvas oldWidget) {
|
void didUpdateWidget(covariant CommunityStructureCanvas oldWidget) {
|
||||||
super.didUpdateWidget(oldWidget);
|
super.didUpdateWidget(oldWidget);
|
||||||
|
if (widget.selectedSpace == null) return;
|
||||||
if (widget.selectedSpace?.uuid != oldWidget.selectedSpace?.uuid) {
|
if (widget.selectedSpace?.uuid != oldWidget.selectedSpace?.uuid) {
|
||||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||||
if (mounted) {
|
if (mounted) {
|
||||||
@ -68,6 +70,34 @@ class _CommunityStructureCanvasState extends State<CommunityStructureCanvas>
|
|||||||
super.dispose();
|
super.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
double _calculateCardWidth(String text) {
|
||||||
|
final style = context.textTheme.bodyLarge?.copyWith(
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
);
|
||||||
|
final textPainter = TextPainter(
|
||||||
|
text: TextSpan(text: text, style: style),
|
||||||
|
maxLines: 1,
|
||||||
|
textDirection: TextDirection.ltr,
|
||||||
|
)..layout();
|
||||||
|
|
||||||
|
const iconWidth = 40.0;
|
||||||
|
const horizontalPadding = 10.0;
|
||||||
|
const contentPadding = 10.0;
|
||||||
|
final calculatedWidth =
|
||||||
|
iconWidth + horizontalPadding + textPainter.width + contentPadding;
|
||||||
|
|
||||||
|
return calculatedWidth.clamp(_minCardWidth, double.infinity);
|
||||||
|
}
|
||||||
|
|
||||||
|
void _calculateAllCardWidths(List<SpaceModel> spaces) {
|
||||||
|
for (final space in spaces) {
|
||||||
|
_cardWidths[space.uuid] = _calculateCardWidth(space.spaceName);
|
||||||
|
if (space.children.isNotEmpty) {
|
||||||
|
_calculateAllCardWidths(space.children);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Set<String> _getAllDescendantUuids(SpaceModel space) {
|
Set<String> _getAllDescendantUuids(SpaceModel space) {
|
||||||
final uuids = <String>{};
|
final uuids = <String>{};
|
||||||
for (final child in space.children) {
|
for (final child in space.children) {
|
||||||
@ -102,11 +132,12 @@ class _CommunityStructureCanvasState extends State<CommunityStructureCanvas>
|
|||||||
final position = _positions[space.uuid];
|
final position = _positions[space.uuid];
|
||||||
if (position == null) return;
|
if (position == null) return;
|
||||||
|
|
||||||
|
final cardWidth = _cardWidths[space.uuid] ?? _minCardWidth;
|
||||||
const scale = 1;
|
const scale = 1;
|
||||||
final viewSize = context.size;
|
final viewSize = context.size;
|
||||||
if (viewSize == null) return;
|
if (viewSize == null) return;
|
||||||
|
|
||||||
final x = -position.dx * scale + (viewSize.width / 2) - (_cardWidth * scale / 2);
|
final x = -position.dx * scale + (viewSize.width / 2) - (cardWidth * scale / 2);
|
||||||
final y =
|
final y =
|
||||||
-position.dy * scale + (viewSize.height / 2) - (_cardHeight * scale / 2);
|
-position.dy * scale + (viewSize.height / 2) - (_cardHeight * scale / 2);
|
||||||
|
|
||||||
@ -155,13 +186,16 @@ class _CommunityStructureCanvasState extends State<CommunityStructureCanvas>
|
|||||||
Map<int, double> levelXOffset,
|
Map<int, double> levelXOffset,
|
||||||
) {
|
) {
|
||||||
for (final space in spaces) {
|
for (final space in spaces) {
|
||||||
|
final cardWidth = _cardWidths[space.uuid] ?? _minCardWidth;
|
||||||
double childSubtreeWidth = 0;
|
double childSubtreeWidth = 0;
|
||||||
if (space.children.isNotEmpty) {
|
if (space.children.isNotEmpty) {
|
||||||
_calculateLayout(space.children, depth + 1, levelXOffset);
|
_calculateLayout(space.children, depth + 1, levelXOffset);
|
||||||
final firstChildPos = _positions[space.children.first.uuid];
|
final firstChildPos = _positions[space.children.first.uuid];
|
||||||
final lastChildPos = _positions[space.children.last.uuid];
|
final lastChildPos = _positions[space.children.last.uuid];
|
||||||
if (firstChildPos != null && lastChildPos != null) {
|
if (firstChildPos != null && lastChildPos != null) {
|
||||||
childSubtreeWidth = (lastChildPos.dx + _cardWidth) - firstChildPos.dx;
|
final lastChildWidth =
|
||||||
|
_cardWidths[space.children.last.uuid] ?? _minCardWidth;
|
||||||
|
childSubtreeWidth = (lastChildPos.dx + lastChildWidth) - firstChildPos.dx;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -170,7 +204,7 @@ class _CommunityStructureCanvasState extends State<CommunityStructureCanvas>
|
|||||||
|
|
||||||
if (space.children.isNotEmpty) {
|
if (space.children.isNotEmpty) {
|
||||||
final firstChildPos = _positions[space.children.first.uuid]!;
|
final firstChildPos = _positions[space.children.first.uuid]!;
|
||||||
x = firstChildPos.dx + (childSubtreeWidth - _cardWidth) / 2;
|
x = firstChildPos.dx + (childSubtreeWidth - cardWidth) / 2;
|
||||||
} else {
|
} else {
|
||||||
x = currentX;
|
x = currentX;
|
||||||
}
|
}
|
||||||
@ -187,7 +221,7 @@ class _CommunityStructureCanvasState extends State<CommunityStructureCanvas>
|
|||||||
|
|
||||||
final y = depth * (_verticalSpacing + _cardHeight);
|
final y = depth * (_verticalSpacing + _cardHeight);
|
||||||
_positions[space.uuid] = Offset(x, y);
|
_positions[space.uuid] = Offset(x, y);
|
||||||
levelXOffset[depth] = x + _cardWidth + _horizontalSpacing;
|
levelXOffset[depth] = x + cardWidth + _horizontalSpacing;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -202,8 +236,11 @@ class _CommunityStructureCanvasState extends State<CommunityStructureCanvas>
|
|||||||
|
|
||||||
List<Widget> _buildTreeWidgets() {
|
List<Widget> _buildTreeWidgets() {
|
||||||
_positions.clear();
|
_positions.clear();
|
||||||
|
_cardWidths.clear();
|
||||||
final community = widget.community;
|
final community = widget.community;
|
||||||
|
|
||||||
|
_calculateAllCardWidths(community.spaces);
|
||||||
|
|
||||||
final levelXOffset = <int, double>{};
|
final levelXOffset = <int, double>{};
|
||||||
_calculateLayout(community.spaces, 0, levelXOffset);
|
_calculateLayout(community.spaces, 0, levelXOffset);
|
||||||
|
|
||||||
@ -240,6 +277,7 @@ class _CommunityStructureCanvasState extends State<CommunityStructureCanvas>
|
|||||||
painter: SpacesConnectionsArrowPainter(
|
painter: SpacesConnectionsArrowPainter(
|
||||||
connections: connections,
|
connections: connections,
|
||||||
positions: _positions,
|
positions: _positions,
|
||||||
|
cardWidths: _cardWidths,
|
||||||
highlightedUuids: highlightedUuids,
|
highlightedUuids: highlightedUuids,
|
||||||
),
|
),
|
||||||
child: Stack(alignment: AlignmentDirectional.center, children: widgets),
|
child: Stack(alignment: AlignmentDirectional.center, children: widgets),
|
||||||
@ -271,6 +309,7 @@ class _CommunityStructureCanvasState extends State<CommunityStructureCanvas>
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
final cardWidth = _cardWidths[space.uuid] ?? _minCardWidth;
|
||||||
final isHighlighted = highlightedUuids.contains(space.uuid);
|
final isHighlighted = highlightedUuids.contains(space.uuid);
|
||||||
final hasNoSelectedSpace = widget.selectedSpace == null;
|
final hasNoSelectedSpace = widget.selectedSpace == null;
|
||||||
|
|
||||||
@ -278,14 +317,10 @@ class _CommunityStructureCanvasState extends State<CommunityStructureCanvas>
|
|||||||
buildSpaceContainer: () {
|
buildSpaceContainer: () {
|
||||||
return Opacity(
|
return Opacity(
|
||||||
opacity: hasNoSelectedSpace || isHighlighted ? 1.0 : 0.5,
|
opacity: hasNoSelectedSpace || isHighlighted ? 1.0 : 0.5,
|
||||||
child: Tooltip(
|
child: SpaceCell(
|
||||||
message: space.spaceName,
|
onTap: () => _onSpaceTapped(space),
|
||||||
preferBelow: false,
|
icon: space.icon,
|
||||||
child: SpaceCell(
|
name: space.spaceName,
|
||||||
onTap: () => _onSpaceTapped(space),
|
|
||||||
icon: space.icon,
|
|
||||||
name: space.spaceName,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
@ -305,7 +340,7 @@ class _CommunityStructureCanvasState extends State<CommunityStructureCanvas>
|
|||||||
Positioned(
|
Positioned(
|
||||||
left: position.dx,
|
left: position.dx,
|
||||||
top: position.dy,
|
top: position.dy,
|
||||||
width: _cardWidth,
|
width: cardWidth,
|
||||||
height: _cardHeight,
|
height: _cardHeight,
|
||||||
child: Draggable<SpaceReorderDataModel>(
|
child: Draggable<SpaceReorderDataModel>(
|
||||||
data: reorderData,
|
data: reorderData,
|
||||||
@ -314,7 +349,7 @@ class _CommunityStructureCanvasState extends State<CommunityStructureCanvas>
|
|||||||
child: Opacity(
|
child: Opacity(
|
||||||
opacity: 0.2,
|
opacity: 0.2,
|
||||||
child: SizedBox(
|
child: SizedBox(
|
||||||
width: _cardWidth,
|
width: cardWidth,
|
||||||
height: _cardHeight,
|
height: _cardHeight,
|
||||||
child: spaceCard,
|
child: spaceCard,
|
||||||
),
|
),
|
||||||
@ -330,7 +365,7 @@ class _CommunityStructureCanvasState extends State<CommunityStructureCanvas>
|
|||||||
);
|
);
|
||||||
|
|
||||||
final targetPos = Offset(
|
final targetPos = Offset(
|
||||||
position.dx + _cardWidth + (_horizontalSpacing / 4) - 20,
|
position.dx + cardWidth + (_horizontalSpacing / 4) - 20,
|
||||||
position.dy,
|
position.dy,
|
||||||
);
|
);
|
||||||
widgets.add(_buildDropTarget(parent, community, i + 1, targetPos));
|
widgets.add(_buildDropTarget(parent, community, i + 1, targetPos));
|
||||||
@ -418,17 +453,17 @@ class _CommunityStructureCanvasState extends State<CommunityStructureCanvas>
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final treeWidgets = _buildTreeWidgets();
|
final treeWidgets = _buildTreeWidgets();
|
||||||
return InteractiveViewer(
|
return GestureDetector(
|
||||||
transformationController: _transformationController,
|
onTap: _resetSelectionAndZoom,
|
||||||
boundaryMargin: EdgeInsets.symmetric(
|
child: InteractiveViewer(
|
||||||
horizontal: context.screenWidth * 0.3,
|
transformationController: _transformationController,
|
||||||
vertical: context.screenHeight * 0.3,
|
boundaryMargin: EdgeInsets.symmetric(
|
||||||
),
|
horizontal: context.screenWidth * 0.3,
|
||||||
minScale: 0.5,
|
vertical: context.screenHeight * 0.3,
|
||||||
maxScale: 3.0,
|
),
|
||||||
constrained: false,
|
minScale: 0.5,
|
||||||
child: GestureDetector(
|
maxScale: 3.0,
|
||||||
onTap: _resetSelectionAndZoom,
|
constrained: false,
|
||||||
child: SizedBox(
|
child: SizedBox(
|
||||||
width: context.screenWidth * 5,
|
width: context.screenWidth * 5,
|
||||||
height: context.screenHeight * 5,
|
height: context.screenHeight * 5,
|
||||||
|
@ -2,41 +2,17 @@ import 'package:flutter/material.dart';
|
|||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
import 'package:flutter_svg/svg.dart';
|
import 'package:flutter_svg/svg.dart';
|
||||||
import 'package:syncrow_web/pages/space_management_v2/main_module/shared/helpers/space_management_community_dialog_helper.dart';
|
import 'package:syncrow_web/pages/space_management_v2/main_module/shared/helpers/space_management_community_dialog_helper.dart';
|
||||||
import 'package:syncrow_web/pages/space_management_v2/main_module/widgets/community_structure_header_action_buttons.dart';
|
import 'package:syncrow_web/pages/space_management_v2/main_module/widgets/community_structure_header_action_buttons_composer.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/bloc/communities_bloc.dart';
|
|
||||||
import 'package:syncrow_web/pages/space_management_v2/modules/communities/presentation/communities_tree_selection_bloc/communities_tree_selection_bloc.dart';
|
import 'package:syncrow_web/pages/space_management_v2/modules/communities/presentation/communities_tree_selection_bloc/communities_tree_selection_bloc.dart';
|
||||||
import 'package:syncrow_web/pages/space_management_v2/modules/space_details/domain/models/space_details_model.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';
|
||||||
import 'package:syncrow_web/utils/constants/assets.dart';
|
import 'package:syncrow_web/utils/constants/assets.dart';
|
||||||
|
import 'package:syncrow_web/utils/extension/build_context_x.dart';
|
||||||
|
|
||||||
class CommunityStructureHeader extends StatelessWidget {
|
class CommunityStructureHeader extends StatelessWidget {
|
||||||
const CommunityStructureHeader({super.key});
|
const CommunityStructureHeader({super.key});
|
||||||
|
|
||||||
List<SpaceModel> _updateRecursive(
|
|
||||||
List<SpaceModel> spaces,
|
|
||||||
SpaceDetailsModel updatedSpace,
|
|
||||||
) {
|
|
||||||
return spaces.map((space) {
|
|
||||||
if (space.uuid == updatedSpace.uuid) {
|
|
||||||
return space.copyWith(
|
|
||||||
spaceName: updatedSpace.spaceName,
|
|
||||||
icon: updatedSpace.icon,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
if (space.children.isNotEmpty) {
|
|
||||||
return space.copyWith(
|
|
||||||
children: _updateRecursive(space.children, updatedSpace),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return space;
|
|
||||||
}).toList();
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final theme = Theme.of(context);
|
|
||||||
final screenWidth = MediaQuery.of(context).size.width;
|
final screenWidth = MediaQuery.of(context).size.width;
|
||||||
return Container(
|
return Container(
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 16.0, vertical: 16.0),
|
padding: const EdgeInsets.symmetric(horizontal: 16.0, vertical: 16.0),
|
||||||
@ -57,7 +33,7 @@ class CommunityStructureHeader extends StatelessWidget {
|
|||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
Expanded(
|
Expanded(
|
||||||
child: _buildCommunityInfo(context, theme, screenWidth),
|
child: _buildCommunityInfo(context, screenWidth),
|
||||||
),
|
),
|
||||||
const SizedBox(width: 16),
|
const SizedBox(width: 16),
|
||||||
],
|
],
|
||||||
@ -67,8 +43,7 @@ class CommunityStructureHeader extends StatelessWidget {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildCommunityInfo(
|
Widget _buildCommunityInfo(BuildContext context, double screenWidth) {
|
||||||
BuildContext context, ThemeData theme, double screenWidth) {
|
|
||||||
final selectedCommunity =
|
final selectedCommunity =
|
||||||
context.watch<CommunitiesTreeSelectionBloc>().state.selectedCommunity;
|
context.watch<CommunitiesTreeSelectionBloc>().state.selectedCommunity;
|
||||||
final selectedSpace =
|
final selectedSpace =
|
||||||
@ -78,7 +53,7 @@ class CommunityStructureHeader extends StatelessWidget {
|
|||||||
children: [
|
children: [
|
||||||
Text(
|
Text(
|
||||||
'Community Structure',
|
'Community Structure',
|
||||||
style: theme.textTheme.headlineLarge?.copyWith(
|
style: context.textTheme.headlineLarge?.copyWith(
|
||||||
color: ColorsManager.blackColor,
|
color: ColorsManager.blackColor,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@ -91,7 +66,7 @@ class CommunityStructureHeader extends StatelessWidget {
|
|||||||
Flexible(
|
Flexible(
|
||||||
child: SelectableText(
|
child: SelectableText(
|
||||||
selectedCommunity.name,
|
selectedCommunity.name,
|
||||||
style: theme.textTheme.bodyLarge?.copyWith(
|
style: context.textTheme.bodyLarge?.copyWith(
|
||||||
color: ColorsManager.blackColor,
|
color: ColorsManager.blackColor,
|
||||||
),
|
),
|
||||||
maxLines: 1,
|
maxLines: 1,
|
||||||
@ -115,27 +90,8 @@ class CommunityStructureHeader extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(width: 8),
|
const SizedBox(width: 8),
|
||||||
CommunityStructureHeaderActionButtons(
|
CommunityStructureHeaderActionButtonsComposer(
|
||||||
onDelete: (space) {},
|
selectedCommunity: selectedCommunity,
|
||||||
onDuplicate: (space) {},
|
|
||||||
onEdit: (space) => SpaceDetailsDialogHelper.showEdit(
|
|
||||||
context,
|
|
||||||
spaceModel: selectedSpace!,
|
|
||||||
communityUuid: selectedCommunity.uuid,
|
|
||||||
onSuccess: (updatedSpaceDetails) {
|
|
||||||
final communitiesBloc = context.read<CommunitiesBloc>();
|
|
||||||
final updatedSpaces = _updateRecursive(
|
|
||||||
selectedCommunity.spaces,
|
|
||||||
updatedSpaceDetails,
|
|
||||||
);
|
|
||||||
|
|
||||||
final community = selectedCommunity.copyWith(
|
|
||||||
spaces: updatedSpaces,
|
|
||||||
);
|
|
||||||
|
|
||||||
communitiesBloc.add(CommunitiesUpdateCommunity(community));
|
|
||||||
},
|
|
||||||
),
|
|
||||||
selectedSpace: selectedSpace,
|
selectedSpace: selectedSpace,
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
@ -19,27 +19,27 @@ class CommunityStructureHeaderActionButtons extends StatelessWidget {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
if (selectedSpace == null) return const SizedBox.shrink();
|
||||||
|
|
||||||
return Wrap(
|
return Wrap(
|
||||||
alignment: WrapAlignment.end,
|
alignment: WrapAlignment.end,
|
||||||
spacing: 10,
|
spacing: 10,
|
||||||
children: [
|
children: [
|
||||||
if (selectedSpace != null) ...[
|
CommunityStructureHeaderButton(
|
||||||
CommunityStructureHeaderButton(
|
label: 'Edit',
|
||||||
label: 'Edit',
|
svgAsset: Assets.editSpace,
|
||||||
svgAsset: Assets.editSpace,
|
onPressed: () => onEdit(selectedSpace!),
|
||||||
onPressed: () => onEdit(selectedSpace!),
|
),
|
||||||
),
|
CommunityStructureHeaderButton(
|
||||||
CommunityStructureHeaderButton(
|
label: 'Duplicate',
|
||||||
label: 'Duplicate',
|
svgAsset: Assets.duplicate,
|
||||||
svgAsset: Assets.duplicate,
|
onPressed: () => onDuplicate(selectedSpace!),
|
||||||
onPressed: () => onDuplicate(selectedSpace!),
|
),
|
||||||
),
|
CommunityStructureHeaderButton(
|
||||||
CommunityStructureHeaderButton(
|
label: 'Delete',
|
||||||
label: 'Delete',
|
svgAsset: Assets.spaceDelete,
|
||||||
svgAsset: Assets.spaceDelete,
|
onPressed: () => onDelete(selectedSpace!),
|
||||||
onPressed: () => onDelete(selectedSpace!),
|
),
|
||||||
),
|
|
||||||
],
|
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,69 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
|
import 'package:syncrow_web/pages/space_management_v2/main_module/helpers/spaces_recursive_helper.dart';
|
||||||
|
import 'package:syncrow_web/pages/space_management_v2/main_module/widgets/community_structure_header_action_buttons.dart';
|
||||||
|
import 'package:syncrow_web/pages/space_management_v2/modules/communities/domain/models/community_model.dart';
|
||||||
|
import 'package:syncrow_web/pages/space_management_v2/modules/communities/domain/models/space_model.dart';
|
||||||
|
import 'package:syncrow_web/pages/space_management_v2/modules/communities/presentation/bloc/communities_bloc.dart';
|
||||||
|
import 'package:syncrow_web/pages/space_management_v2/modules/communities/presentation/communities_tree_selection_bloc/communities_tree_selection_bloc.dart';
|
||||||
|
import 'package:syncrow_web/pages/space_management_v2/modules/delete_space/presentation/widgets/delete_space_dialog.dart';
|
||||||
|
import 'package:syncrow_web/pages/space_management_v2/modules/space_details/presentation/helpers/space_details_dialog_helper.dart';
|
||||||
|
|
||||||
|
class CommunityStructureHeaderActionButtonsComposer extends StatelessWidget {
|
||||||
|
const CommunityStructureHeaderActionButtonsComposer({
|
||||||
|
required this.selectedCommunity,
|
||||||
|
required this.selectedSpace,
|
||||||
|
super.key,
|
||||||
|
});
|
||||||
|
final CommunityModel selectedCommunity;
|
||||||
|
final SpaceModel? selectedSpace;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return CommunityStructureHeaderActionButtons(
|
||||||
|
onDelete: (space) => showDialog<void>(
|
||||||
|
context: context,
|
||||||
|
barrierDismissible: false,
|
||||||
|
builder: (_) => DeleteSpaceDialog(
|
||||||
|
space: space,
|
||||||
|
community: selectedCommunity,
|
||||||
|
onSuccess: () {
|
||||||
|
final updatedSpaces = SpacesRecursiveHelper.recusrivelyDelete(
|
||||||
|
selectedCommunity.spaces,
|
||||||
|
space.uuid,
|
||||||
|
);
|
||||||
|
final community = selectedCommunity.copyWith(
|
||||||
|
spaces: updatedSpaces,
|
||||||
|
);
|
||||||
|
context.read<CommunitiesBloc>().add(
|
||||||
|
CommunitiesUpdateCommunity(community),
|
||||||
|
);
|
||||||
|
context.read<CommunitiesTreeSelectionBloc>().add(
|
||||||
|
SelectCommunityEvent(community: selectedCommunity),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
onDuplicate: (space) {},
|
||||||
|
onEdit: (space) => SpaceDetailsDialogHelper.showEdit(
|
||||||
|
context,
|
||||||
|
spaceModel: selectedSpace!,
|
||||||
|
communityUuid: selectedCommunity.uuid,
|
||||||
|
onSuccess: (updatedSpaceDetails) {
|
||||||
|
final communitiesBloc = context.read<CommunitiesBloc>();
|
||||||
|
final updatedSpaces = SpacesRecursiveHelper.recusrivelyUpdate(
|
||||||
|
selectedCommunity.spaces,
|
||||||
|
updatedSpaceDetails,
|
||||||
|
);
|
||||||
|
|
||||||
|
final community = selectedCommunity.copyWith(
|
||||||
|
spaces: updatedSpaces,
|
||||||
|
);
|
||||||
|
|
||||||
|
communitiesBloc.add(CommunitiesUpdateCommunity(community));
|
||||||
|
},
|
||||||
|
),
|
||||||
|
selectedSpace: selectedSpace,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -2,31 +2,22 @@ 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 Offset offset;
|
final void Function() onTap;
|
||||||
final void Function() onButtonTap;
|
|
||||||
|
|
||||||
const PlusButtonWidget({
|
const PlusButtonWidget({
|
||||||
|
required this.onTap,
|
||||||
super.key,
|
super.key,
|
||||||
required this.offset,
|
|
||||||
required this.onButtonTap,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return GestureDetector(
|
return IconButton.filled(
|
||||||
onTap: onButtonTap,
|
onPressed: onTap,
|
||||||
child: Container(
|
style: IconButton.styleFrom(backgroundColor: ColorsManager.spaceColor),
|
||||||
width: 30,
|
icon: const Icon(
|
||||||
height: 30,
|
Icons.add,
|
||||||
decoration: const BoxDecoration(
|
color: ColorsManager.whiteColors,
|
||||||
color: ColorsManager.spaceColor,
|
size: 20,
|
||||||
shape: BoxShape.circle,
|
|
||||||
),
|
|
||||||
child: const Icon(
|
|
||||||
Icons.add,
|
|
||||||
color: ColorsManager.whiteColors,
|
|
||||||
size: 20,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -29,10 +29,9 @@ class _SpaceCardWidgetState extends State<SpaceCardWidget> {
|
|||||||
widget.buildSpaceContainer(),
|
widget.buildSpaceContainer(),
|
||||||
if (isHovered)
|
if (isHovered)
|
||||||
Positioned(
|
Positioned(
|
||||||
bottom: 0,
|
bottom: -5,
|
||||||
child: PlusButtonWidget(
|
child: PlusButtonWidget(
|
||||||
offset: Offset.zero,
|
onTap: widget.onTap,
|
||||||
onButtonTap: widget.onTap,
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
@ -20,21 +20,19 @@ class SpaceCell extends StatelessWidget {
|
|||||||
return InkWell(
|
return InkWell(
|
||||||
onTap: onTap,
|
onTap: onTap,
|
||||||
child: Container(
|
child: Container(
|
||||||
width: 150,
|
padding: const EdgeInsetsDirectional.only(end: 10),
|
||||||
height: 70,
|
height: 70,
|
||||||
decoration: _containerDecoration(),
|
decoration: _containerDecoration(),
|
||||||
child: Row(
|
child: Row(
|
||||||
|
spacing: 10,
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
children: [
|
children: [
|
||||||
_buildIconContainer(),
|
_buildIconContainer(),
|
||||||
const SizedBox(width: 10),
|
Text(
|
||||||
Expanded(
|
name,
|
||||||
child: Text(
|
style: context.textTheme.bodyLarge?.copyWith(
|
||||||
name,
|
fontWeight: FontWeight.bold,
|
||||||
style: context.textTheme.bodyLarge?.copyWith(
|
color: ColorsManager.blackColor,
|
||||||
fontWeight: FontWeight.bold,
|
|
||||||
color: ColorsManager.blackColor,
|
|
||||||
),
|
|
||||||
overflow: TextOverflow.ellipsis,
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
@ -3,6 +3,8 @@ import 'package:flutter_bloc/flutter_bloc.dart';
|
|||||||
import 'package:syncrow_web/pages/space_management_v2/main_module/widgets/community_structure_canvas.dart';
|
import 'package:syncrow_web/pages/space_management_v2/main_module/widgets/community_structure_canvas.dart';
|
||||||
import 'package:syncrow_web/pages/space_management_v2/main_module/widgets/community_structure_header.dart';
|
import 'package:syncrow_web/pages/space_management_v2/main_module/widgets/community_structure_header.dart';
|
||||||
import 'package:syncrow_web/pages/space_management_v2/main_module/widgets/create_space_button.dart';
|
import 'package:syncrow_web/pages/space_management_v2/main_module/widgets/create_space_button.dart';
|
||||||
|
import 'package:syncrow_web/pages/space_management_v2/modules/communities/domain/models/community_model.dart';
|
||||||
|
import 'package:syncrow_web/pages/space_management_v2/modules/communities/domain/models/space_model.dart';
|
||||||
import 'package:syncrow_web/pages/space_management_v2/modules/communities/presentation/communities_tree_selection_bloc/communities_tree_selection_bloc.dart';
|
import 'package:syncrow_web/pages/space_management_v2/modules/communities/presentation/communities_tree_selection_bloc/communities_tree_selection_bloc.dart';
|
||||||
|
|
||||||
class SpaceManagementCommunityStructure extends StatelessWidget {
|
class SpaceManagementCommunityStructure extends StatelessWidget {
|
||||||
@ -13,28 +15,44 @@ class SpaceManagementCommunityStructure extends StatelessWidget {
|
|||||||
final selectionBloc = context.watch<CommunitiesTreeSelectionBloc>().state;
|
final selectionBloc = context.watch<CommunitiesTreeSelectionBloc>().state;
|
||||||
final selectedCommunity = selectionBloc.selectedCommunity;
|
final selectedCommunity = selectionBloc.selectedCommunity;
|
||||||
final selectedSpace = selectionBloc.selectedSpace;
|
final selectedSpace = selectionBloc.selectedSpace;
|
||||||
|
return Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
const CommunityStructureHeader(),
|
||||||
|
Visibility(
|
||||||
|
visible: selectedCommunity!.spaces.isNotEmpty,
|
||||||
|
replacement: _buildEmptyWidget(selectedCommunity),
|
||||||
|
child: _buildCanvas(selectedCommunity, selectedSpace),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildCanvas(
|
||||||
|
CommunityModel selectedCommunity,
|
||||||
|
SpaceModel? selectedSpace,
|
||||||
|
) {
|
||||||
|
return Expanded(
|
||||||
|
child: CommunityStructureCanvas(
|
||||||
|
community: selectedCommunity,
|
||||||
|
selectedSpace: selectedSpace,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildEmptyWidget(CommunityModel selectedCommunity) {
|
||||||
const spacer = Spacer(flex: 6);
|
const spacer = Spacer(flex: 6);
|
||||||
return Visibility(
|
|
||||||
visible: selectedCommunity!.spaces.isNotEmpty,
|
return Expanded(
|
||||||
replacement: Row(
|
child: Row(
|
||||||
children: [
|
children: [
|
||||||
spacer,
|
spacer,
|
||||||
Expanded(
|
Expanded(
|
||||||
child: CreateSpaceButton(communityUuid: selectedCommunity.uuid),
|
child: CreateSpaceButton(
|
||||||
),
|
communityUuid: selectedCommunity.uuid,
|
||||||
spacer
|
|
||||||
],
|
|
||||||
),
|
|
||||||
child: Column(
|
|
||||||
mainAxisSize: MainAxisSize.min,
|
|
||||||
children: [
|
|
||||||
const CommunityStructureHeader(),
|
|
||||||
Expanded(
|
|
||||||
child: CommunityStructureCanvas(
|
|
||||||
community: selectedCommunity,
|
|
||||||
selectedSpace: selectedSpace,
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
spacer,
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
@ -0,0 +1,64 @@
|
|||||||
|
import 'package:dio/dio.dart';
|
||||||
|
import 'package:syncrow_web/pages/common/bloc/project_manager.dart';
|
||||||
|
import 'package:syncrow_web/pages/space_management_v2/modules/delete_space/domain/params/delete_space_param.dart';
|
||||||
|
import 'package:syncrow_web/pages/space_management_v2/modules/delete_space/domain/services/delete_space_service.dart';
|
||||||
|
import 'package:syncrow_web/services/api/api_exception.dart';
|
||||||
|
import 'package:syncrow_web/services/api/http_service.dart';
|
||||||
|
import 'package:syncrow_web/utils/constants/api_const.dart';
|
||||||
|
|
||||||
|
final class RemoteDeleteSpaceService implements DeleteSpaceService {
|
||||||
|
const RemoteDeleteSpaceService(this._httpService);
|
||||||
|
|
||||||
|
final HTTPService _httpService;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> delete(DeleteSpaceParam param) async {
|
||||||
|
try {
|
||||||
|
await _httpService.delete(
|
||||||
|
path: await _makeUrl(param),
|
||||||
|
expectedResponseModel: (json) {
|
||||||
|
final response = json as Map<String, dynamic>;
|
||||||
|
final hasSuccessfullyDeletedSpace = response['success'] as bool? ?? false;
|
||||||
|
|
||||||
|
if (!hasSuccessfullyDeletedSpace) {
|
||||||
|
throw APIException('Failed to delete space');
|
||||||
|
}
|
||||||
|
|
||||||
|
return hasSuccessfullyDeletedSpace;
|
||||||
|
},
|
||||||
|
);
|
||||||
|
} on DioException catch (e) {
|
||||||
|
final message = e.response?.data as Map<String, dynamic>?;
|
||||||
|
throw APIException(_getErrorMessageFromBody(message));
|
||||||
|
} catch (e) {
|
||||||
|
throw APIException(e.toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
String _getErrorMessageFromBody(Map<String, dynamic>? body) {
|
||||||
|
if (body == null) return 'Failed to delete space';
|
||||||
|
final error = body['error'] as Map<String, dynamic>?;
|
||||||
|
final errorMessage = error?['message'] as String? ?? '';
|
||||||
|
return errorMessage;
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<String> _makeUrl(DeleteSpaceParam param) async {
|
||||||
|
final projectUuid = await ProjectManager.getProjectUUID();
|
||||||
|
if (projectUuid == null) {
|
||||||
|
throw APIException('Project UUID is not set');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (param.communityUuid.isEmpty) {
|
||||||
|
throw APIException('Community UUID is not set');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (param.spaceUuid.isEmpty) {
|
||||||
|
throw APIException('Space UUID is not set');
|
||||||
|
}
|
||||||
|
|
||||||
|
return ApiEndpoints.deleteSpace
|
||||||
|
.replaceAll('{projectId}', projectUuid)
|
||||||
|
.replaceAll('{communityId}', param.communityUuid)
|
||||||
|
.replaceAll('{spaceId}', param.spaceUuid);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,9 @@
|
|||||||
|
class DeleteSpaceParam {
|
||||||
|
const DeleteSpaceParam({
|
||||||
|
required this.spaceUuid,
|
||||||
|
required this.communityUuid,
|
||||||
|
});
|
||||||
|
|
||||||
|
final String spaceUuid;
|
||||||
|
final String communityUuid;
|
||||||
|
}
|
@ -0,0 +1,5 @@
|
|||||||
|
import 'package:syncrow_web/pages/space_management_v2/modules/delete_space/domain/params/delete_space_param.dart';
|
||||||
|
|
||||||
|
abstract interface class DeleteSpaceService {
|
||||||
|
Future<void> delete(DeleteSpaceParam param);
|
||||||
|
}
|
@ -0,0 +1,31 @@
|
|||||||
|
import 'package:bloc/bloc.dart';
|
||||||
|
import 'package:equatable/equatable.dart';
|
||||||
|
import 'package:syncrow_web/pages/space_management_v2/modules/delete_space/domain/params/delete_space_param.dart';
|
||||||
|
import 'package:syncrow_web/pages/space_management_v2/modules/delete_space/domain/services/delete_space_service.dart';
|
||||||
|
import 'package:syncrow_web/services/api/api_exception.dart';
|
||||||
|
|
||||||
|
part 'delete_space_event.dart';
|
||||||
|
part 'delete_space_state.dart';
|
||||||
|
|
||||||
|
class DeleteSpaceBloc extends Bloc<DeleteSpaceEvent, DeleteSpaceState> {
|
||||||
|
DeleteSpaceBloc(this._deleteSpaceService) : super(DeleteSpaceInitial()) {
|
||||||
|
on<DeleteSpace>(_onDeleteSpace);
|
||||||
|
}
|
||||||
|
|
||||||
|
final DeleteSpaceService _deleteSpaceService;
|
||||||
|
|
||||||
|
Future<void> _onDeleteSpace(
|
||||||
|
DeleteSpace event,
|
||||||
|
Emitter<DeleteSpaceState> emit,
|
||||||
|
) async {
|
||||||
|
emit(DeleteSpaceLoading());
|
||||||
|
try {
|
||||||
|
await _deleteSpaceService.delete(event.param);
|
||||||
|
emit(const DeleteSpaceSuccess('Space deleted successfully'));
|
||||||
|
} on APIException catch (e) {
|
||||||
|
emit(DeleteSpaceFailure(e.message));
|
||||||
|
} catch (e) {
|
||||||
|
emit(DeleteSpaceFailure(e.toString()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,17 @@
|
|||||||
|
part of 'delete_space_bloc.dart';
|
||||||
|
|
||||||
|
sealed class DeleteSpaceEvent extends Equatable {
|
||||||
|
const DeleteSpaceEvent();
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<Object> get props => [];
|
||||||
|
}
|
||||||
|
|
||||||
|
final class DeleteSpace extends DeleteSpaceEvent {
|
||||||
|
const DeleteSpace(this.param);
|
||||||
|
|
||||||
|
final DeleteSpaceParam param;
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<Object> get props => [param];
|
||||||
|
}
|
@ -0,0 +1,30 @@
|
|||||||
|
part of 'delete_space_bloc.dart';
|
||||||
|
|
||||||
|
sealed class DeleteSpaceState extends Equatable {
|
||||||
|
const DeleteSpaceState();
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<Object> get props => [];
|
||||||
|
}
|
||||||
|
|
||||||
|
final class DeleteSpaceInitial extends DeleteSpaceState {}
|
||||||
|
|
||||||
|
final class DeleteSpaceLoading extends DeleteSpaceState {}
|
||||||
|
|
||||||
|
final class DeleteSpaceSuccess extends DeleteSpaceState {
|
||||||
|
const DeleteSpaceSuccess(this.successMessage);
|
||||||
|
|
||||||
|
final String successMessage;
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<Object> get props => [successMessage];
|
||||||
|
}
|
||||||
|
|
||||||
|
final class DeleteSpaceFailure extends DeleteSpaceState {
|
||||||
|
const DeleteSpaceFailure(this.errorMessage);
|
||||||
|
|
||||||
|
final String errorMessage;
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<Object> get props => [errorMessage];
|
||||||
|
}
|
@ -0,0 +1,73 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_bloc/flutter_bloc.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/delete_space/data/remote_delete_space_service.dart';
|
||||||
|
import 'package:syncrow_web/pages/space_management_v2/modules/delete_space/presentation/bloc/delete_space_bloc.dart';
|
||||||
|
import 'package:syncrow_web/pages/space_management_v2/modules/delete_space/presentation/widgets/delete_space_dialog_form.dart';
|
||||||
|
import 'package:syncrow_web/pages/space_management_v2/modules/delete_space/presentation/widgets/delete_space_loading_widget.dart';
|
||||||
|
import 'package:syncrow_web/pages/space_management_v2/modules/delete_space/presentation/widgets/delete_space_status_widget.dart';
|
||||||
|
import 'package:syncrow_web/services/api/http_service.dart';
|
||||||
|
import 'package:syncrow_web/utils/color_manager.dart';
|
||||||
|
import 'package:syncrow_web/utils/extension/build_context_x.dart';
|
||||||
|
|
||||||
|
class DeleteSpaceDialog extends StatelessWidget {
|
||||||
|
const DeleteSpaceDialog({
|
||||||
|
required this.space,
|
||||||
|
required this.community,
|
||||||
|
required this.onSuccess,
|
||||||
|
super.key,
|
||||||
|
});
|
||||||
|
|
||||||
|
final SpaceModel space;
|
||||||
|
final CommunityModel community;
|
||||||
|
final void Function() onSuccess;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return BlocProvider(
|
||||||
|
create: (context) => DeleteSpaceBloc(
|
||||||
|
RemoteDeleteSpaceService(HTTPService()),
|
||||||
|
),
|
||||||
|
child: Builder(
|
||||||
|
builder: (context) => Dialog(
|
||||||
|
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
|
||||||
|
child: Container(
|
||||||
|
padding: const EdgeInsetsDirectional.all(32),
|
||||||
|
constraints: BoxConstraints(
|
||||||
|
maxWidth: context.screenWidth * 0.2,
|
||||||
|
),
|
||||||
|
child: BlocConsumer<DeleteSpaceBloc, DeleteSpaceState>(
|
||||||
|
listener: (context, state) {
|
||||||
|
if (state case DeleteSpaceSuccess()) onSuccess();
|
||||||
|
},
|
||||||
|
builder: (context, state) => switch (state) {
|
||||||
|
DeleteSpaceInitial() => DeleteSpaceDialogForm(
|
||||||
|
space: space,
|
||||||
|
communityUuid: community.uuid,
|
||||||
|
),
|
||||||
|
DeleteSpaceLoading() => const DeleteSpaceLoadingWidget(),
|
||||||
|
DeleteSpaceSuccess() => DeleteSpaceStatusWidget(
|
||||||
|
message: state.successMessage,
|
||||||
|
icon: const Icon(
|
||||||
|
Icons.check_circle,
|
||||||
|
size: 92,
|
||||||
|
color: ColorsManager.goodGreen,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
DeleteSpaceFailure() => DeleteSpaceStatusWidget(
|
||||||
|
message: state.errorMessage,
|
||||||
|
icon: const Icon(
|
||||||
|
Icons.error,
|
||||||
|
size: 92,
|
||||||
|
color: ColorsManager.red,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,106 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
|
import 'package:flutter_svg/svg.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/delete_space/domain/params/delete_space_param.dart';
|
||||||
|
import 'package:syncrow_web/pages/space_management_v2/modules/delete_space/presentation/bloc/delete_space_bloc.dart';
|
||||||
|
import 'package:syncrow_web/utils/color_manager.dart';
|
||||||
|
import 'package:syncrow_web/utils/constants/assets.dart';
|
||||||
|
import 'package:syncrow_web/utils/extension/build_context_x.dart';
|
||||||
|
|
||||||
|
class DeleteSpaceDialogForm extends StatelessWidget {
|
||||||
|
const DeleteSpaceDialogForm({
|
||||||
|
required this.space,
|
||||||
|
required this.communityUuid,
|
||||||
|
super.key,
|
||||||
|
});
|
||||||
|
|
||||||
|
final SpaceModel space;
|
||||||
|
final String communityUuid;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
SvgPicture.asset(Assets.xDelete, width: 36, height: 36),
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
SelectableText(
|
||||||
|
'Delete Space',
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
style: context.textTheme.titleLarge?.copyWith(
|
||||||
|
color: ColorsManager.blackColor,
|
||||||
|
fontWeight: FontWeight.w400,
|
||||||
|
fontSize: 24,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 8),
|
||||||
|
SelectableText(
|
||||||
|
'Are you sure you want to delete this space? This action is irreversible',
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
style: context.textTheme.bodyLarge?.copyWith(
|
||||||
|
color: ColorsManager.lightGreyColor,
|
||||||
|
fontWeight: FontWeight.w400,
|
||||||
|
fontSize: 14,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 24),
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
Expanded(
|
||||||
|
child: FilledButton(
|
||||||
|
style: _buildButtonStyle(
|
||||||
|
context,
|
||||||
|
color: ColorsManager.grey25,
|
||||||
|
textColor: ColorsManager.blackColor,
|
||||||
|
),
|
||||||
|
onPressed: Navigator.of(context).pop,
|
||||||
|
child: const Text('Cancel'),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(width: 16),
|
||||||
|
Expanded(
|
||||||
|
child: FilledButton(
|
||||||
|
style: _buildButtonStyle(
|
||||||
|
context,
|
||||||
|
color: ColorsManager.semiTransparentRed,
|
||||||
|
textColor: ColorsManager.whiteColors,
|
||||||
|
),
|
||||||
|
onPressed: () {
|
||||||
|
context.read<DeleteSpaceBloc>().add(
|
||||||
|
DeleteSpace(
|
||||||
|
DeleteSpaceParam(
|
||||||
|
spaceUuid: space.uuid,
|
||||||
|
communityUuid: communityUuid,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
child: const Text('Delete'),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
ButtonStyle _buildButtonStyle(
|
||||||
|
BuildContext context, {
|
||||||
|
required Color color,
|
||||||
|
required Color textColor,
|
||||||
|
}) {
|
||||||
|
return FilledButton.styleFrom(
|
||||||
|
padding: const EdgeInsets.symmetric(vertical: 12),
|
||||||
|
backgroundColor: color,
|
||||||
|
shape: RoundedRectangleBorder(
|
||||||
|
borderRadius: BorderRadius.circular(8),
|
||||||
|
),
|
||||||
|
foregroundColor: textColor,
|
||||||
|
textStyle: context.textTheme.bodyLarge?.copyWith(
|
||||||
|
fontWeight: FontWeight.w400,
|
||||||
|
fontSize: 14,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,13 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
class DeleteSpaceLoadingWidget extends StatelessWidget {
|
||||||
|
const DeleteSpaceLoadingWidget({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return const SizedBox.square(
|
||||||
|
dimension: 32,
|
||||||
|
child: Center(child: CircularProgressIndicator()),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,38 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:syncrow_web/utils/color_manager.dart';
|
||||||
|
import 'package:syncrow_web/utils/extension/build_context_x.dart';
|
||||||
|
|
||||||
|
class DeleteSpaceStatusWidget extends StatelessWidget {
|
||||||
|
const DeleteSpaceStatusWidget({
|
||||||
|
required this.message,
|
||||||
|
required this.icon,
|
||||||
|
super.key,
|
||||||
|
});
|
||||||
|
|
||||||
|
final String message;
|
||||||
|
final Widget icon;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.center,
|
||||||
|
spacing: 16,
|
||||||
|
children: [
|
||||||
|
icon,
|
||||||
|
SelectableText(
|
||||||
|
message,
|
||||||
|
style: context.textTheme.bodyMedium?.copyWith(
|
||||||
|
color: ColorsManager.blackColor,
|
||||||
|
fontSize: 22,
|
||||||
|
),
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
),
|
||||||
|
FilledButton(
|
||||||
|
onPressed: Navigator.of(context).pop,
|
||||||
|
child: const Text('Close'),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -2,23 +2,27 @@ import 'package:syncrow_web/pages/space_management_v2/modules/space_details/doma
|
|||||||
import 'package:syncrow_web/pages/space_management_v2/modules/space_details/domain/params/load_space_details_param.dart';
|
import 'package:syncrow_web/pages/space_management_v2/modules/space_details/domain/params/load_space_details_param.dart';
|
||||||
import 'package:syncrow_web/pages/space_management_v2/modules/space_details/domain/services/space_details_service.dart';
|
import 'package:syncrow_web/pages/space_management_v2/modules/space_details/domain/services/space_details_service.dart';
|
||||||
|
|
||||||
class UniqueSubspacesDecorator implements SpaceDetailsService {
|
class UniqueSpaceDetailsSpacesDecoratorService implements SpaceDetailsService {
|
||||||
final SpaceDetailsService _decoratee;
|
final SpaceDetailsService _decoratee;
|
||||||
|
|
||||||
const UniqueSubspacesDecorator(this._decoratee);
|
const UniqueSpaceDetailsSpacesDecoratorService(this._decoratee);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<SpaceDetailsModel> getSpaceDetails(LoadSpaceDetailsParam param) async {
|
Future<SpaceDetailsModel> getSpaceDetails(LoadSpaceDetailsParam param) async {
|
||||||
final response = await _decoratee.getSpaceDetails(param);
|
final response = await _decoratee.getSpaceDetails(param);
|
||||||
|
|
||||||
final uniqueSubspaces = <String, Subspace>{};
|
final uniqueSubspaces = <String, Subspace>{};
|
||||||
|
final duplicateNames = <String>{};
|
||||||
|
|
||||||
for (final subspace in response.subspaces) {
|
for (final subspace in response.subspaces) {
|
||||||
final normalizedName = subspace.name.trim().toLowerCase();
|
final normalizedName = subspace.name.trim().toLowerCase();
|
||||||
if (!uniqueSubspaces.containsKey(normalizedName)) {
|
if (uniqueSubspaces.containsKey(normalizedName)) {
|
||||||
|
duplicateNames.add(normalizedName);
|
||||||
|
} else {
|
||||||
uniqueSubspaces[normalizedName] = subspace;
|
uniqueSubspaces[normalizedName] = subspace;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
duplicateNames.forEach(uniqueSubspaces.remove);
|
||||||
|
|
||||||
return response.copyWith(
|
return response.copyWith(
|
||||||
subspaces: uniqueSubspaces.values.toList(),
|
subspaces: uniqueSubspaces.values.toList(),
|
@ -10,7 +10,12 @@ 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 AddDeviceTypeWidget extends StatefulWidget {
|
class AddDeviceTypeWidget extends StatefulWidget {
|
||||||
const AddDeviceTypeWidget({super.key});
|
const AddDeviceTypeWidget({
|
||||||
|
super.key,
|
||||||
|
this.initialProducts = const [],
|
||||||
|
});
|
||||||
|
|
||||||
|
final List<Product> initialProducts;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<AddDeviceTypeWidget> createState() => _AddDeviceTypeWidgetState();
|
State<AddDeviceTypeWidget> createState() => _AddDeviceTypeWidgetState();
|
||||||
@ -18,6 +23,16 @@ class AddDeviceTypeWidget extends StatefulWidget {
|
|||||||
|
|
||||||
class _AddDeviceTypeWidgetState extends State<AddDeviceTypeWidget> {
|
class _AddDeviceTypeWidgetState extends State<AddDeviceTypeWidget> {
|
||||||
final Map<Product, int> _selectedProducts = {};
|
final Map<Product, int> _selectedProducts = {};
|
||||||
|
final Map<Product, int> _initialProductCounts = {};
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
for (final product in widget.initialProducts) {
|
||||||
|
_initialProductCounts[product] = (_initialProductCounts[product] ?? 0) + 1;
|
||||||
|
}
|
||||||
|
_selectedProducts.addAll(_initialProductCounts);
|
||||||
|
}
|
||||||
|
|
||||||
void _onIncrement(Product product) {
|
void _onIncrement(Product product) {
|
||||||
setState(() {
|
setState(() {
|
||||||
@ -27,8 +42,12 @@ class _AddDeviceTypeWidgetState extends State<AddDeviceTypeWidget> {
|
|||||||
|
|
||||||
void _onDecrement(Product product) {
|
void _onDecrement(Product product) {
|
||||||
setState(() {
|
setState(() {
|
||||||
if ((_selectedProducts[product] ?? 0) > 0) {
|
final initialCount = _initialProductCounts[product] ?? 0;
|
||||||
_selectedProducts[product] = _selectedProducts[product]! - 1;
|
final currentCount = _selectedProducts[product] ?? 0;
|
||||||
|
if (currentCount > initialCount) {
|
||||||
|
_selectedProducts[product] = currentCount - 1;
|
||||||
|
} else if (currentCount > 0 && initialCount == 0) {
|
||||||
|
_selectedProducts[product] = currentCount - 1;
|
||||||
if (_selectedProducts[product] == 0) {
|
if (_selectedProducts[product] == 0) {
|
||||||
_selectedProducts.remove(product);
|
_selectedProducts.remove(product);
|
||||||
}
|
}
|
||||||
@ -63,7 +82,22 @@ class _AddDeviceTypeWidgetState extends State<AddDeviceTypeWidget> {
|
|||||||
actions: [
|
actions: [
|
||||||
SpaceDetailsActionButtons(
|
SpaceDetailsActionButtons(
|
||||||
onSave: () {
|
onSave: () {
|
||||||
final result = _selectedProducts.entries
|
final resultMap = <Product, int>{};
|
||||||
|
resultMap.addAll(_selectedProducts);
|
||||||
|
|
||||||
|
for (final entry in _initialProductCounts.entries) {
|
||||||
|
final product = entry.key;
|
||||||
|
final initialCount = entry.value;
|
||||||
|
final currentCount = resultMap[product] ?? 0;
|
||||||
|
|
||||||
|
if (currentCount > initialCount) {
|
||||||
|
resultMap[product] = currentCount - initialCount;
|
||||||
|
} else {
|
||||||
|
resultMap.remove(product);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
final result = resultMap.entries
|
||||||
.expand((entry) => List.generate(entry.value, (_) => entry.key))
|
.expand((entry) => List.generate(entry.value, (_) => entry.key))
|
||||||
.toList();
|
.toList();
|
||||||
Navigator.of(context).pop(result);
|
Navigator.of(context).pop(result);
|
||||||
|
@ -205,7 +205,14 @@ class _AssignTagsDialogState extends State<AssignTagsDialog> {
|
|||||||
onCancel: () async {
|
onCancel: () async {
|
||||||
final newProducts = await showDialog<List<Product>>(
|
final newProducts = await showDialog<List<Product>>(
|
||||||
context: context,
|
context: context,
|
||||||
builder: (context) => const AddDeviceTypeWidget(),
|
builder: (context) => AddDeviceTypeWidget(
|
||||||
|
initialProducts: [
|
||||||
|
..._space.productAllocations.map((e) => e.product),
|
||||||
|
..._space.subspaces
|
||||||
|
.expand((s) => s.productAllocations)
|
||||||
|
.map((e) => e.product),
|
||||||
|
],
|
||||||
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
if (newProducts == null || newProducts.isEmpty) return;
|
if (newProducts == null || newProducts.isEmpty) return;
|
||||||
|
@ -84,6 +84,5 @@ abstract class ColorsManager {
|
|||||||
static const Color minBlueDot = Color(0xFF023DFE);
|
static const Color minBlueDot = Color(0xFF023DFE);
|
||||||
static const Color grey25 = Color(0xFFF9F9F9);
|
static const Color grey25 = Color(0xFFF9F9F9);
|
||||||
static const Color grey50 = Color(0xFF718096);
|
static const Color grey50 = Color(0xFF718096);
|
||||||
static const Color red100 = Color(0xFFFE0202);
|
|
||||||
static const Color grey800 = Color(0xffF8F8F8);
|
static const Color grey800 = Color(0xffF8F8F8);
|
||||||
}
|
}
|
||||||
|
@ -517,4 +517,5 @@ class Assets {
|
|||||||
static const String emptyRangeOfAqi = 'assets/icons/empty_range_of_aqi.svg';
|
static const String emptyRangeOfAqi = 'assets/icons/empty_range_of_aqi.svg';
|
||||||
static const String homeIcon = 'assets/icons/home_icon.svg';
|
static const String homeIcon = 'assets/icons/home_icon.svg';
|
||||||
static const String groupIcon = 'assets/icons/group_icon.svg';
|
static const String groupIcon = 'assets/icons/group_icon.svg';
|
||||||
|
static const String xDelete = 'assets/icons/x_delete.svg';
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user