mirror of
https://github.com/SyncrowIOT/web.git
synced 2026-10-26 17:37:55 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ae3eb6fca8 | ||
|
|
f341dcd482 | ||
|
|
e98b091253 | ||
|
|
77d6d822cb | ||
|
|
f1aab13263 | ||
|
|
a56e422bf5 | ||
|
|
97530dd351 | ||
|
|
a57b6e0853 | ||
|
|
8f71fcb96a | ||
|
|
845397e819 | ||
|
|
2077ef053f | ||
|
|
d21850edc8 | ||
|
|
85f53ed1f2 | ||
|
|
5fde74fc7d | ||
|
|
994efc302b | ||
|
|
c403048da7 | ||
|
|
04b7a506be | ||
|
|
19ddf443a9 | ||
|
|
3779176978 | ||
|
|
7c5bca35fc | ||
|
|
aed3004a31 | ||
|
|
8ae4e561c2 | ||
|
|
4241d11cb6 | ||
|
|
ef8c9efff0 | ||
|
|
c59d2b7fd6 | ||
|
|
71f0da9299 | ||
|
|
e6d9000ee2 | ||
|
|
7f3dfebf15 | ||
|
|
59058cf2d2 | ||
|
|
b738596b50 | ||
|
|
995ce480cb | ||
|
|
b41733ee40 | ||
|
|
fe090175e3 | ||
|
|
85544c69f8 | ||
|
|
9f71bbff63 | ||
|
|
deb227034a |
@@ -1,4 +1,3 @@
|
|||||||
// booking_page.dart
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
import 'package:calendar_view/calendar_view.dart';
|
import 'package:calendar_view/calendar_view.dart';
|
||||||
@@ -20,7 +19,8 @@ import 'package:syncrow_web/utils/color_manager.dart';
|
|||||||
import 'package:syncrow_web/utils/constants/assets.dart';
|
import 'package:syncrow_web/utils/constants/assets.dart';
|
||||||
|
|
||||||
class BookingPage extends StatefulWidget {
|
class BookingPage extends StatefulWidget {
|
||||||
const BookingPage({super.key});
|
final PageController? pageController;
|
||||||
|
const BookingPage({super.key, this.pageController});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<BookingPage> createState() => _BookingPageState();
|
State<BookingPage> createState() => _BookingPageState();
|
||||||
@@ -42,12 +42,15 @@ class _BookingPageState extends State<BookingPage> {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
child: _BookingPageContent(),
|
child: _BookingPageContent(widget.pageController),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class _BookingPageContent extends StatefulWidget {
|
class _BookingPageContent extends StatefulWidget {
|
||||||
|
final PageController? pageController;
|
||||||
|
const _BookingPageContent(this.pageController);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<_BookingPageContent> createState() => _BookingPageContentState();
|
State<_BookingPageContent> createState() => _BookingPageContentState();
|
||||||
}
|
}
|
||||||
@@ -90,8 +93,7 @@ class _BookingPageContentState extends State<_BookingPageContent> {
|
|||||||
return BlocListener<SelectedBookableSpaceBloc, SelectedBookableSpaceState>(
|
return BlocListener<SelectedBookableSpaceBloc, SelectedBookableSpaceState>(
|
||||||
listener: (context, state) {
|
listener: (context, state) {
|
||||||
if (state.selectedBookableSpace != null) {
|
if (state.selectedBookableSpace != null) {
|
||||||
// Reset events and clear cache when room changes
|
context.read<CalendarEventsBloc>().add(const ResetEvents());
|
||||||
context.read<CalendarEventsBloc>().add(ResetEvents());
|
|
||||||
_loadEvents(context);
|
_loadEvents(context);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -176,7 +178,9 @@ class _BookingPageContentState extends State<_BookingPageContent> {
|
|||||||
SvgTextButton(
|
SvgTextButton(
|
||||||
svgAsset: Assets.homeIcon,
|
svgAsset: Assets.homeIcon,
|
||||||
label: 'Manage Bookable Spaces',
|
label: 'Manage Bookable Spaces',
|
||||||
onPressed: () {},
|
onPressed: () {
|
||||||
|
widget.pageController!.jumpToPage(2);
|
||||||
|
},
|
||||||
),
|
),
|
||||||
const SizedBox(width: 20),
|
const SizedBox(width: 20),
|
||||||
SvgTextButton(
|
SvgTextButton(
|
||||||
@@ -237,7 +241,6 @@ class _BookingPageContentState extends State<_BookingPageContent> {
|
|||||||
.watch<DateSelectionBloc>()
|
.watch<DateSelectionBloc>()
|
||||||
.state
|
.state
|
||||||
.selectedDateFromSideBarCalender,
|
.selectedDateFromSideBarCalender,
|
||||||
// isLoading: eventState is EventsLoading,
|
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|||||||
+8
-1
@@ -19,6 +19,7 @@ class WeekNavigation extends StatelessWidget {
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Container(
|
return Container(
|
||||||
|
width: 250,
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 5),
|
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 5),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: ColorsManager.circleRolesBackground,
|
color: ColorsManager.circleRolesBackground,
|
||||||
@@ -32,6 +33,8 @@ class WeekNavigation extends StatelessWidget {
|
|||||||
],
|
],
|
||||||
),
|
),
|
||||||
child: Row(
|
child: Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.center,
|
||||||
children: [
|
children: [
|
||||||
IconButton(
|
IconButton(
|
||||||
iconSize: 15,
|
iconSize: 15,
|
||||||
@@ -40,13 +43,17 @@ class WeekNavigation extends StatelessWidget {
|
|||||||
onPressed: onPreviousWeek,
|
onPressed: onPreviousWeek,
|
||||||
),
|
),
|
||||||
const SizedBox(width: 10),
|
const SizedBox(width: 10),
|
||||||
Text(
|
SizedBox(
|
||||||
|
width: 120,
|
||||||
|
child: Text(
|
||||||
_getMonthYearText(weekStart, weekEnd),
|
_getMonthYearText(weekStart, weekEnd),
|
||||||
style: const TextStyle(
|
style: const TextStyle(
|
||||||
color: ColorsManager.lightGrayColor,
|
color: ColorsManager.lightGrayColor,
|
||||||
fontSize: 14,
|
fontSize: 14,
|
||||||
fontWeight: FontWeight.w400,
|
fontWeight: FontWeight.w400,
|
||||||
),
|
),
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(width: 10),
|
const SizedBox(width: 10),
|
||||||
IconButton(
|
IconButton(
|
||||||
|
|||||||
@@ -1,57 +0,0 @@
|
|||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:syncrow_web/pages/access_management/booking_system/presentation/view/widgets/icon_text_button.dart';
|
|
||||||
|
|
||||||
import 'package:syncrow_web/utils/constants/assets.dart';
|
|
||||||
|
|
||||||
class BookingPage extends StatelessWidget {
|
|
||||||
final PageController pageController;
|
|
||||||
const BookingPage({
|
|
||||||
super.key,
|
|
||||||
required this.pageController,
|
|
||||||
});
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return Row(
|
|
||||||
children: [
|
|
||||||
Expanded(
|
|
||||||
child: Container(
|
|
||||||
color: Colors.blueGrey[100],
|
|
||||||
child: const Center(
|
|
||||||
child: Text(
|
|
||||||
'Side bar',
|
|
||||||
style: TextStyle(fontSize: 24, fontWeight: FontWeight.bold),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
)),
|
|
||||||
Expanded(
|
|
||||||
flex: 4,
|
|
||||||
child: Padding(
|
|
||||||
padding: const EdgeInsets.all(20.0),
|
|
||||||
child: SizedBox(
|
|
||||||
child: Column(
|
|
||||||
children: [
|
|
||||||
Row(
|
|
||||||
mainAxisAlignment: MainAxisAlignment.start,
|
|
||||||
children: [
|
|
||||||
SvgTextButton(
|
|
||||||
svgAsset: Assets.homeIcon,
|
|
||||||
label: 'Manage Bookable Spaces',
|
|
||||||
onPressed: () {
|
|
||||||
pageController.jumpToPage(2);
|
|
||||||
}),
|
|
||||||
const SizedBox(width: 20),
|
|
||||||
SvgTextButton(
|
|
||||||
svgAsset: Assets.groupIcon,
|
|
||||||
label: 'Manage Users',
|
|
||||||
onPressed: () {})
|
|
||||||
],
|
|
||||||
)
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
))
|
|
||||||
],
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -2,9 +2,8 @@ import 'package:flutter/material.dart';
|
|||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
import 'package:syncrow_web/pages/access_management/bloc/access_bloc.dart';
|
import 'package:syncrow_web/pages/access_management/bloc/access_bloc.dart';
|
||||||
import 'package:syncrow_web/pages/access_management/bloc/access_event.dart';
|
import 'package:syncrow_web/pages/access_management/bloc/access_event.dart';
|
||||||
import 'package:syncrow_web/pages/access_management/booking_system/view/booking_page.dart';
|
|
||||||
import 'package:syncrow_web/pages/access_management/manage_bookable_spaces/presentation/screens/manage_bookable_spaces_screen.dart';
|
import 'package:syncrow_web/pages/access_management/manage_bookable_spaces/presentation/screens/manage_bookable_spaces_screen.dart';
|
||||||
import 'package:syncrow_web/pages/access_management/booking_system/presentation/view/booking_page.dart' hide BookingPage;
|
import 'package:syncrow_web/pages/access_management/booking_system/presentation/view/booking_page.dart';
|
||||||
import 'package:syncrow_web/pages/access_management/view/access_overview_content.dart';
|
import 'package:syncrow_web/pages/access_management/view/access_overview_content.dart';
|
||||||
import 'package:syncrow_web/pages/device_managment/shared/navigate_home_grid_view.dart';
|
import 'package:syncrow_web/pages/device_managment/shared/navigate_home_grid_view.dart';
|
||||||
import 'package:syncrow_web/utils/extension/build_context_x.dart';
|
import 'package:syncrow_web/utils/extension/build_context_x.dart';
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
|
import 'package:syncrow_web/utils/color_manager.dart';
|
||||||
import 'space_tree_dropdown_bloc.dart';
|
import 'space_tree_dropdown_bloc.dart';
|
||||||
|
|
||||||
class DropdownMenuContent extends StatefulWidget {
|
class DropdownMenuContent extends StatefulWidget {
|
||||||
@@ -75,7 +76,8 @@ class _DropdownMenuContentState extends State<DropdownMenuContent> {
|
|||||||
child: TextFormField(
|
child: TextFormField(
|
||||||
controller: _searchController,
|
controller: _searchController,
|
||||||
onChanged: _handleSearch,
|
onChanged: _handleSearch,
|
||||||
style: const TextStyle(fontSize: 14, color: Colors.black),
|
style: const TextStyle(
|
||||||
|
fontSize: 14, color: ColorsManager.blackColor),
|
||||||
decoration: InputDecoration(
|
decoration: InputDecoration(
|
||||||
hintText: 'Search for space...',
|
hintText: 'Search for space...',
|
||||||
prefixIcon: const Icon(Icons.search, size: 20),
|
prefixIcon: const Icon(Icons.search, size: 20),
|
||||||
@@ -84,6 +86,12 @@ class _DropdownMenuContentState extends State<DropdownMenuContent> {
|
|||||||
border: OutlineInputBorder(
|
border: OutlineInputBorder(
|
||||||
borderRadius: BorderRadius.circular(8),
|
borderRadius: BorderRadius.circular(8),
|
||||||
),
|
),
|
||||||
|
focusedBorder: OutlineInputBorder(
|
||||||
|
borderRadius: BorderRadius.circular(8),
|
||||||
|
borderSide: const BorderSide(
|
||||||
|
color: ColorsManager.dropDownSelectBlue,
|
||||||
|
),
|
||||||
|
),
|
||||||
isDense: true,
|
isDense: true,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -117,7 +125,9 @@ class _DropdownMenuContentState extends State<DropdownMenuContent> {
|
|||||||
title: Text(
|
title: Text(
|
||||||
community.name,
|
community.name,
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
color: isSelected ? Colors.blue : Colors.black,
|
color: isSelected
|
||||||
|
? ColorsManager.dropDownSelectBlue
|
||||||
|
: ColorsManager.blackColor,
|
||||||
fontWeight:
|
fontWeight:
|
||||||
isSelected ? FontWeight.bold : FontWeight.normal,
|
isSelected ? FontWeight.bold : FontWeight.normal,
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -52,7 +52,7 @@ class SpaceDropdown extends StatelessWidget {
|
|||||||
fontSize: 16,
|
fontSize: 16,
|
||||||
fontWeight: FontWeight.bold,
|
fontWeight: FontWeight.bold,
|
||||||
color: selectedValue == space.uuid
|
color: selectedValue == space.uuid
|
||||||
? ColorsManager.dialogBlueTitle
|
? ColorsManager.dropDownSelectBlue
|
||||||
: ColorsManager.blackColor,
|
: ColorsManager.blackColor,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -61,7 +61,7 @@ class SpaceDropdown extends StatelessWidget {
|
|||||||
style: Theme.of(context).textTheme.bodyMedium!.copyWith(
|
style: Theme.of(context).textTheme.bodyMedium!.copyWith(
|
||||||
fontSize: 12,
|
fontSize: 12,
|
||||||
color: selectedValue == space.uuid
|
color: selectedValue == space.uuid
|
||||||
? ColorsManager.dialogBlueTitle
|
? ColorsManager.dropDownSelectBlue
|
||||||
: ColorsManager.blackColor,
|
: ColorsManager.blackColor,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
import 'dart:math';
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
import 'package:syncrow_web/pages/space_management_v2/main_module/helpers/spaces_recursive_helper.dart';
|
import 'package:syncrow_web/pages/space_management_v2/main_module/helpers/spaces_recursive_helper.dart';
|
||||||
@@ -51,13 +53,23 @@ class _CommunityStructureCanvasState extends State<CommunityStructureCanvas>
|
|||||||
duration: const Duration(milliseconds: 150),
|
duration: const Duration(milliseconds: 150),
|
||||||
);
|
);
|
||||||
super.initState();
|
super.initState();
|
||||||
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||||
|
if (mounted) {
|
||||||
|
_centerOnTree();
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void didUpdateWidget(covariant CommunityStructureCanvas oldWidget) {
|
void didUpdateWidget(covariant CommunityStructureCanvas oldWidget) {
|
||||||
super.didUpdateWidget(oldWidget);
|
super.didUpdateWidget(oldWidget);
|
||||||
if (widget.selectedSpace == null) return;
|
if (oldWidget.community.uuid != widget.community.uuid) {
|
||||||
if (widget.selectedSpace?.uuid != oldWidget.selectedSpace?.uuid) {
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||||
|
if (mounted) {
|
||||||
|
_centerOnTree(animate: true);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else if (widget.selectedSpace?.uuid != oldWidget.selectedSpace?.uuid) {
|
||||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||||
if (mounted) {
|
if (mounted) {
|
||||||
_animateToSpace(widget.selectedSpace);
|
_animateToSpace(widget.selectedSpace);
|
||||||
@@ -151,6 +163,60 @@ class _CommunityStructureCanvasState extends State<CommunityStructureCanvas>
|
|||||||
_runAnimation(matrix);
|
_runAnimation(matrix);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void _centerOnTree({bool animate = false}) {
|
||||||
|
if (_positions.isEmpty) {
|
||||||
|
if (animate) {
|
||||||
|
_runAnimation(Matrix4.identity());
|
||||||
|
} else {
|
||||||
|
_transformationController.value = Matrix4.identity();
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var minX = double.infinity;
|
||||||
|
var maxX = double.negativeInfinity;
|
||||||
|
var minY = double.infinity;
|
||||||
|
var maxY = double.negativeInfinity;
|
||||||
|
|
||||||
|
_positions.forEach((uuid, offset) {
|
||||||
|
final cardWidth = _cardWidths[uuid] ?? _minCardWidth;
|
||||||
|
minX = min(minX, offset.dx);
|
||||||
|
maxX = max(maxX, offset.dx + cardWidth);
|
||||||
|
minY = min(minY, offset.dy);
|
||||||
|
maxY = max(maxY, offset.dy + _cardHeight);
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!minX.isFinite || !maxX.isFinite || !minY.isFinite || !maxY.isFinite) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
final treeWidth = maxX - minX;
|
||||||
|
final treeHeight = maxY - minY;
|
||||||
|
|
||||||
|
final viewSize = context.size;
|
||||||
|
if (viewSize == null) return;
|
||||||
|
|
||||||
|
final scaleX = viewSize.width / treeWidth;
|
||||||
|
final scaleY = viewSize.height / treeHeight;
|
||||||
|
final scale = min(scaleX, scaleY).clamp(0.5, 1.0) * 0.9;
|
||||||
|
|
||||||
|
final treeCenterX = minX + treeWidth / 2;
|
||||||
|
final treeCenterY = minY + treeHeight / 2;
|
||||||
|
|
||||||
|
final x = -treeCenterX * scale + viewSize.width / 2;
|
||||||
|
final y = -treeCenterY * scale + viewSize.height / 2;
|
||||||
|
|
||||||
|
final matrix = Matrix4.identity()
|
||||||
|
..translate(x, y)
|
||||||
|
..scale(scale);
|
||||||
|
|
||||||
|
if (animate) {
|
||||||
|
_runAnimation(matrix);
|
||||||
|
} else {
|
||||||
|
_transformationController.value = matrix;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void _onReorder(SpaceReorderDataModel data, int newIndex) {
|
void _onReorder(SpaceReorderDataModel data, int newIndex) {
|
||||||
final newCommunity = widget.community.copyWith();
|
final newCommunity = widget.community.copyWith();
|
||||||
final children = data.parent?.children ?? newCommunity.spaces;
|
final children = data.parent?.children ?? newCommunity.spaces;
|
||||||
|
|||||||
+17
-1
@@ -7,6 +7,7 @@ import 'package:syncrow_web/pages/space_management_v2/modules/communities/domain
|
|||||||
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/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/delete_space/presentation/widgets/delete_space_dialog.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/duplicate_space/presentation/views/duplicate_space_dialog.dart';
|
||||||
import 'package:syncrow_web/pages/space_management_v2/modules/space_details/presentation/helpers/space_details_dialog_helper.dart';
|
import 'package:syncrow_web/pages/space_management_v2/modules/space_details/presentation/helpers/space_details_dialog_helper.dart';
|
||||||
|
|
||||||
class CommunityStructureHeaderActionButtonsComposer extends StatelessWidget {
|
class CommunityStructureHeaderActionButtonsComposer extends StatelessWidget {
|
||||||
@@ -44,7 +45,22 @@ class CommunityStructureHeaderActionButtonsComposer extends StatelessWidget {
|
|||||||
},
|
},
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
onDuplicate: (space) {},
|
onDuplicate: (space) => showDialog<void>(
|
||||||
|
context: context,
|
||||||
|
builder: (_) => DuplicateSpaceDialog(
|
||||||
|
initialName: space.spaceName,
|
||||||
|
selectedSpaceUuid: space.uuid,
|
||||||
|
selectedCommunityUuid: selectedCommunity.uuid,
|
||||||
|
onSuccess: (spaces) {
|
||||||
|
final updatedCommunity = selectedCommunity.copyWith(
|
||||||
|
spaces: spaces,
|
||||||
|
);
|
||||||
|
context.read<CommunitiesBloc>().add(
|
||||||
|
CommunitiesUpdateCommunity(updatedCommunity),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
onEdit: (space) => SpaceDetailsDialogHelper.showEdit(
|
onEdit: (space) => SpaceDetailsDialogHelper.showEdit(
|
||||||
context,
|
context,
|
||||||
spaceModel: selectedSpace!,
|
spaceModel: selectedSpace!,
|
||||||
|
|||||||
@@ -10,9 +10,11 @@ class SpaceManagementBody extends StatelessWidget {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Row(
|
return Stack(
|
||||||
children: [
|
children: [
|
||||||
const SpaceManagementCommunitiesTree(),
|
Row(
|
||||||
|
children: [
|
||||||
|
const SizedBox(width: 320),
|
||||||
Expanded(
|
Expanded(
|
||||||
child: BlocBuilder<CommunitiesTreeSelectionBloc,
|
child: BlocBuilder<CommunitiesTreeSelectionBloc,
|
||||||
CommunitiesTreeSelectionState>(
|
CommunitiesTreeSelectionState>(
|
||||||
@@ -26,6 +28,9 @@ class SpaceManagementBody extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
),
|
||||||
|
const SpaceManagementCommunitiesTree(),
|
||||||
|
],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+10
-1
@@ -9,6 +9,7 @@ import 'package:syncrow_web/pages/space_management_v2/modules/communities/presen
|
|||||||
import 'package:syncrow_web/pages/space_management_v2/modules/communities/presentation/widgets/space_management_communities_tree_community_tile.dart';
|
import 'package:syncrow_web/pages/space_management_v2/modules/communities/presentation/widgets/space_management_communities_tree_community_tile.dart';
|
||||||
import 'package:syncrow_web/pages/space_management_v2/modules/communities/presentation/widgets/space_management_sidebar_communities_list.dart';
|
import 'package:syncrow_web/pages/space_management_v2/modules/communities/presentation/widgets/space_management_sidebar_communities_list.dart';
|
||||||
import 'package:syncrow_web/pages/space_management_v2/modules/communities/presentation/widgets/space_management_sidebar_header.dart';
|
import 'package:syncrow_web/pages/space_management_v2/modules/communities/presentation/widgets/space_management_sidebar_header.dart';
|
||||||
|
import 'package:syncrow_web/utils/color_manager.dart';
|
||||||
import 'package:syncrow_web/utils/style.dart';
|
import 'package:syncrow_web/utils/style.dart';
|
||||||
|
|
||||||
class SpaceManagementCommunitiesTree extends StatefulWidget {
|
class SpaceManagementCommunitiesTree extends StatefulWidget {
|
||||||
@@ -44,7 +45,15 @@ class _SpaceManagementCommunitiesTreeState
|
|||||||
return BlocBuilder<CommunitiesBloc, CommunitiesState>(
|
return BlocBuilder<CommunitiesBloc, CommunitiesState>(
|
||||||
builder: (context, state) => Container(
|
builder: (context, state) => Container(
|
||||||
width: 320,
|
width: 320,
|
||||||
decoration: subSectionContainerDecoration,
|
decoration: subSectionContainerDecoration.copyWith(
|
||||||
|
boxShadow: [
|
||||||
|
BoxShadow(
|
||||||
|
color: ColorsManager.shadowBlackColor.withValues(alpha: 0.1),
|
||||||
|
blurRadius: 10,
|
||||||
|
offset: const Offset(10, 0),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
child: Column(
|
child: Column(
|
||||||
children: [
|
children: [
|
||||||
const SpaceManagementSidebarHeader(),
|
const SpaceManagementSidebarHeader(),
|
||||||
|
|||||||
+60
@@ -0,0 +1,60 @@
|
|||||||
|
import 'package:dio/dio.dart';
|
||||||
|
import 'package:syncrow_web/pages/common/bloc/project_manager.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/duplicate_space/domain/params/duplicate_space_param.dart';
|
||||||
|
import 'package:syncrow_web/pages/space_management_v2/modules/duplicate_space/domain/services/duplicate_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 RemoteDuplicateSpaceService implements DuplicateSpaceService {
|
||||||
|
RemoteDuplicateSpaceService(this._httpService);
|
||||||
|
|
||||||
|
final HTTPService _httpService;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<List<SpaceModel>> duplicateSpace(DuplicateSpaceParam param) async {
|
||||||
|
try {
|
||||||
|
final response = await _httpService.post(
|
||||||
|
path: await _makeUrl(param),
|
||||||
|
body: param.toJson(),
|
||||||
|
expectedResponseModel: (json) {
|
||||||
|
final response = json as Map<String, dynamic>;
|
||||||
|
final data = response['data'] as List<dynamic>;
|
||||||
|
return data
|
||||||
|
.map((e) => SpaceModel.fromJson(e as Map<String, dynamic>))
|
||||||
|
.toList();
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
return response;
|
||||||
|
} on DioException catch (e) {
|
||||||
|
final message = e.response?.data as Map<String, dynamic>?;
|
||||||
|
final error = message?['error'] as Map<String, dynamic>?;
|
||||||
|
final errorMessage = error?['error'] as String? ?? '';
|
||||||
|
throw APIException(errorMessage);
|
||||||
|
} catch (e) {
|
||||||
|
throw APIException(e.toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<String> _makeUrl(DuplicateSpaceParam 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.duplicateSpace
|
||||||
|
.replaceAll('{projectUuid}', projectUuid)
|
||||||
|
.replaceAll('{communityUuid}', param.communityUuid)
|
||||||
|
.replaceAll('{spaceUuid}', param.spaceUuid);
|
||||||
|
}
|
||||||
|
}
|
||||||
+15
@@ -0,0 +1,15 @@
|
|||||||
|
class DuplicateSpaceParam {
|
||||||
|
final String communityUuid;
|
||||||
|
final String spaceUuid;
|
||||||
|
final String newSpaceName;
|
||||||
|
|
||||||
|
DuplicateSpaceParam({
|
||||||
|
required this.communityUuid,
|
||||||
|
required this.spaceUuid,
|
||||||
|
required this.newSpaceName,
|
||||||
|
});
|
||||||
|
|
||||||
|
Map<String, dynamic> toJson() => {
|
||||||
|
'spaceName': newSpaceName,
|
||||||
|
};
|
||||||
|
}
|
||||||
+6
@@ -0,0 +1,6 @@
|
|||||||
|
import 'package:syncrow_web/pages/space_management_v2/modules/communities/domain/models/space_model.dart';
|
||||||
|
import 'package:syncrow_web/pages/space_management_v2/modules/duplicate_space/domain/params/duplicate_space_param.dart';
|
||||||
|
|
||||||
|
abstract interface class DuplicateSpaceService {
|
||||||
|
Future<List<SpaceModel>> duplicateSpace(DuplicateSpaceParam param);
|
||||||
|
}
|
||||||
+36
@@ -0,0 +1,36 @@
|
|||||||
|
import 'package:bloc/bloc.dart';
|
||||||
|
import 'package:equatable/equatable.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/duplicate_space/domain/params/duplicate_space_param.dart';
|
||||||
|
import 'package:syncrow_web/pages/space_management_v2/modules/duplicate_space/domain/services/duplicate_space_service.dart';
|
||||||
|
import 'package:syncrow_web/services/api/api_exception.dart';
|
||||||
|
|
||||||
|
part 'duplicate_space_event.dart';
|
||||||
|
part 'duplicate_space_state.dart';
|
||||||
|
|
||||||
|
class DuplicateSpaceBloc extends Bloc<DuplicateSpaceEvent, DuplicateSpaceState> {
|
||||||
|
DuplicateSpaceBloc(
|
||||||
|
this._duplicateSpaceService,
|
||||||
|
) : super(const DuplicateSpaceInitial()) {
|
||||||
|
on<DuplicateSpaceEvent>(_onDuplicateSpaceEvent);
|
||||||
|
}
|
||||||
|
|
||||||
|
final DuplicateSpaceService _duplicateSpaceService;
|
||||||
|
|
||||||
|
Future<void> _onDuplicateSpaceEvent(
|
||||||
|
DuplicateSpaceEvent event,
|
||||||
|
Emitter<DuplicateSpaceState> emit,
|
||||||
|
) async {
|
||||||
|
try {
|
||||||
|
emit(const DuplicateSpaceLoading());
|
||||||
|
final result = await _duplicateSpaceService.duplicateSpace(event.param);
|
||||||
|
emit(DuplicateSpaceSuccess(result));
|
||||||
|
} on APIException catch (e) {
|
||||||
|
emit(DuplicateSpaceFailure(e.message));
|
||||||
|
} catch (e) {
|
||||||
|
emit(DuplicateSpaceFailure(e.toString()));
|
||||||
|
} finally {
|
||||||
|
emit(const DuplicateSpaceInitial());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+10
@@ -0,0 +1,10 @@
|
|||||||
|
part of 'duplicate_space_bloc.dart';
|
||||||
|
|
||||||
|
final class DuplicateSpaceEvent extends Equatable {
|
||||||
|
const DuplicateSpaceEvent({required this.param});
|
||||||
|
|
||||||
|
final DuplicateSpaceParam param;
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<Object> get props => [param];
|
||||||
|
}
|
||||||
+34
@@ -0,0 +1,34 @@
|
|||||||
|
part of 'duplicate_space_bloc.dart';
|
||||||
|
|
||||||
|
sealed class DuplicateSpaceState extends Equatable {
|
||||||
|
const DuplicateSpaceState();
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<Object> get props => [];
|
||||||
|
}
|
||||||
|
|
||||||
|
final class DuplicateSpaceInitial extends DuplicateSpaceState {
|
||||||
|
const DuplicateSpaceInitial();
|
||||||
|
}
|
||||||
|
|
||||||
|
final class DuplicateSpaceLoading extends DuplicateSpaceState {
|
||||||
|
const DuplicateSpaceLoading();
|
||||||
|
}
|
||||||
|
|
||||||
|
final class DuplicateSpaceSuccess extends DuplicateSpaceState {
|
||||||
|
const DuplicateSpaceSuccess(this.spaces);
|
||||||
|
|
||||||
|
final List<SpaceModel> spaces;
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<Object> get props => [spaces];
|
||||||
|
}
|
||||||
|
|
||||||
|
final class DuplicateSpaceFailure extends DuplicateSpaceState {
|
||||||
|
const DuplicateSpaceFailure(this.errorMessage);
|
||||||
|
|
||||||
|
final String errorMessage;
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<Object> get props => [errorMessage];
|
||||||
|
}
|
||||||
+69
@@ -0,0 +1,69 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
|
import 'package:syncrow_web/common/widgets/app_loading_indicator.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/duplicate_space/data/services/remote_duplicate_space_service.dart';
|
||||||
|
import 'package:syncrow_web/pages/space_management_v2/modules/duplicate_space/presentation/bloc/duplicate_space_bloc.dart';
|
||||||
|
import 'package:syncrow_web/pages/space_management_v2/modules/duplicate_space/presentation/widgets/duplicate_space_dialog_form.dart';
|
||||||
|
import 'package:syncrow_web/services/api/http_service.dart';
|
||||||
|
import 'package:syncrow_web/utils/extension/app_snack_bar.dart';
|
||||||
|
|
||||||
|
class DuplicateSpaceDialog extends StatelessWidget {
|
||||||
|
const DuplicateSpaceDialog({
|
||||||
|
required this.initialName,
|
||||||
|
required this.onSuccess,
|
||||||
|
required this.selectedSpaceUuid,
|
||||||
|
required this.selectedCommunityUuid,
|
||||||
|
super.key,
|
||||||
|
});
|
||||||
|
|
||||||
|
final String initialName;
|
||||||
|
final void Function(List<SpaceModel> spaces) onSuccess;
|
||||||
|
final String selectedSpaceUuid;
|
||||||
|
final String selectedCommunityUuid;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return BlocProvider(
|
||||||
|
create: (context) => DuplicateSpaceBloc(
|
||||||
|
RemoteDuplicateSpaceService(HTTPService()),
|
||||||
|
),
|
||||||
|
child: BlocListener<DuplicateSpaceBloc, DuplicateSpaceState>(
|
||||||
|
listener: _listener,
|
||||||
|
child: DuplicateSpaceDialogForm(
|
||||||
|
initialName: initialName,
|
||||||
|
selectedSpaceUuid: selectedSpaceUuid,
|
||||||
|
selectedCommunityUuid: selectedCommunityUuid,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
void _listener(BuildContext context, DuplicateSpaceState state) {
|
||||||
|
switch (state) {
|
||||||
|
case DuplicateSpaceLoading():
|
||||||
|
showDialog<void>(
|
||||||
|
context: context,
|
||||||
|
barrierDismissible: false,
|
||||||
|
builder: (context) => const AppLoadingIndicator(),
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case DuplicateSpaceFailure(:final errorMessage):
|
||||||
|
Navigator.pop(context);
|
||||||
|
Navigator.pop(context);
|
||||||
|
context.showFailureSnackbar(errorMessage);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case DuplicateSpaceSuccess(:final spaces):
|
||||||
|
onSuccess.call(spaces);
|
||||||
|
Navigator.of(context).pop();
|
||||||
|
Navigator.of(context).pop();
|
||||||
|
context.showSuccessSnackbar('Space duplicated successfully');
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+84
@@ -0,0 +1,84 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
|
import 'package:syncrow_web/pages/space_management_v2/modules/duplicate_space/domain/params/duplicate_space_param.dart';
|
||||||
|
import 'package:syncrow_web/pages/space_management_v2/modules/duplicate_space/presentation/bloc/duplicate_space_bloc.dart';
|
||||||
|
import 'package:syncrow_web/pages/space_management_v2/modules/duplicate_space/presentation/widgets/duplicate_space_text_field.dart';
|
||||||
|
|
||||||
|
class DuplicateSpaceDialogForm extends StatefulWidget {
|
||||||
|
const DuplicateSpaceDialogForm({
|
||||||
|
required this.initialName,
|
||||||
|
required this.selectedSpaceUuid,
|
||||||
|
required this.selectedCommunityUuid,
|
||||||
|
super.key,
|
||||||
|
});
|
||||||
|
|
||||||
|
final String initialName;
|
||||||
|
final String selectedSpaceUuid;
|
||||||
|
final String selectedCommunityUuid;
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<DuplicateSpaceDialogForm> createState() => _DuplicateSpaceDialogFormState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _DuplicateSpaceDialogFormState extends State<DuplicateSpaceDialogForm> {
|
||||||
|
late final TextEditingController _nameController;
|
||||||
|
bool _isNameValid = true;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
_nameController = TextEditingController(text: '${widget.initialName}(1)');
|
||||||
|
_nameController.addListener(_validateName);
|
||||||
|
}
|
||||||
|
|
||||||
|
void _validateName() => setState(
|
||||||
|
() => _isNameValid = _nameController.text.trim() != widget.initialName,
|
||||||
|
);
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
_nameController.dispose();
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return AlertDialog(
|
||||||
|
title: const SelectableText('Duplicate Space'),
|
||||||
|
content: Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
spacing: 16,
|
||||||
|
children: [
|
||||||
|
const SelectableText('Enter a new name for the duplicated space:'),
|
||||||
|
DuplicateSpaceTextField(
|
||||||
|
nameController: _nameController,
|
||||||
|
isNameValid: _isNameValid,
|
||||||
|
initialName: widget.initialName,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
actions: [
|
||||||
|
TextButton(
|
||||||
|
onPressed: Navigator.of(context).pop,
|
||||||
|
child: const Text('Cancel'),
|
||||||
|
),
|
||||||
|
TextButton(
|
||||||
|
onPressed: _isNameValid ? () => _submit(context) : null,
|
||||||
|
child: const Text('Duplicate'),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
void _submit(BuildContext context) {
|
||||||
|
context.read<DuplicateSpaceBloc>().add(
|
||||||
|
DuplicateSpaceEvent(
|
||||||
|
param: DuplicateSpaceParam(
|
||||||
|
newSpaceName: _nameController.text,
|
||||||
|
spaceUuid: widget.selectedSpaceUuid,
|
||||||
|
communityUuid: widget.selectedCommunityUuid,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
+21
@@ -0,0 +1,21 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
class DuplicateSpaceFailureDialog extends StatelessWidget {
|
||||||
|
const DuplicateSpaceFailureDialog(this.errorMessage, {super.key});
|
||||||
|
|
||||||
|
final String errorMessage;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return AlertDialog(
|
||||||
|
title: const Text('Failed to duplicate space'),
|
||||||
|
content: Text(errorMessage),
|
||||||
|
actions: [
|
||||||
|
TextButton(
|
||||||
|
onPressed: Navigator.of(context).pop,
|
||||||
|
child: const Text('Close'),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
+51
@@ -0,0 +1,51 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:syncrow_web/utils/color_manager.dart';
|
||||||
|
import 'package:syncrow_web/utils/extension/build_context_x.dart';
|
||||||
|
|
||||||
|
class DuplicateSpaceTextField extends StatelessWidget {
|
||||||
|
const DuplicateSpaceTextField({
|
||||||
|
required this.nameController,
|
||||||
|
required this.isNameValid,
|
||||||
|
required this.initialName,
|
||||||
|
super.key,
|
||||||
|
});
|
||||||
|
|
||||||
|
final TextEditingController nameController;
|
||||||
|
final bool isNameValid;
|
||||||
|
final String initialName;
|
||||||
|
|
||||||
|
String get _errorText => 'Name must be different from "$initialName"';
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return TextField(
|
||||||
|
controller: nameController,
|
||||||
|
style: context.textTheme.bodyMedium!.copyWith(
|
||||||
|
color: ColorsManager.blackColor,
|
||||||
|
),
|
||||||
|
decoration: InputDecoration(
|
||||||
|
label: const Text('Space Name'),
|
||||||
|
border: _border(),
|
||||||
|
enabledBorder: _border(),
|
||||||
|
focusedBorder: _border(ColorsManager.primaryColor),
|
||||||
|
errorBorder: _border(context.theme.colorScheme.error),
|
||||||
|
focusedErrorBorder: _border(context.theme.colorScheme.error),
|
||||||
|
errorStyle: context.textTheme.bodyMedium!.copyWith(
|
||||||
|
color: context.theme.colorScheme.error,
|
||||||
|
fontSize: 8,
|
||||||
|
),
|
||||||
|
errorText: isNameValid ? null : _errorText,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
OutlineInputBorder _border([Color? color]) {
|
||||||
|
return OutlineInputBorder(
|
||||||
|
borderRadius: BorderRadius.circular(16),
|
||||||
|
borderSide: BorderSide(
|
||||||
|
color: color ?? ColorsManager.blackColor,
|
||||||
|
width: 0.5,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
+13
@@ -19,6 +19,7 @@ class SpaceSubSpacesDialog extends StatefulWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class _SpaceSubSpacesDialogState extends State<SpaceSubSpacesDialog> {
|
class _SpaceSubSpacesDialogState extends State<SpaceSubSpacesDialog> {
|
||||||
|
late final TextEditingController _subspaceNameController;
|
||||||
late List<Subspace> _subspaces;
|
late List<Subspace> _subspaces;
|
||||||
|
|
||||||
bool get _hasDuplicateNames =>
|
bool get _hasDuplicateNames =>
|
||||||
@@ -29,6 +30,13 @@ class _SpaceSubSpacesDialogState extends State<SpaceSubSpacesDialog> {
|
|||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
_subspaces = List.from(widget.subspaces);
|
_subspaces = List.from(widget.subspaces);
|
||||||
|
_subspaceNameController = TextEditingController();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
_subspaceNameController.dispose();
|
||||||
|
super.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
void _handleSubspaceAdded(String name) {
|
void _handleSubspaceAdded(String name) {
|
||||||
@@ -49,6 +57,10 @@ class _SpaceSubSpacesDialogState extends State<SpaceSubSpacesDialog> {
|
|||||||
);
|
);
|
||||||
|
|
||||||
void _handleSave() {
|
void _handleSave() {
|
||||||
|
final name = _subspaceNameController.text.trim();
|
||||||
|
if (name.isNotEmpty) {
|
||||||
|
_handleSubspaceAdded(name);
|
||||||
|
}
|
||||||
widget.onSave(_subspaces);
|
widget.onSave(_subspaces);
|
||||||
Navigator.of(context).pop();
|
Navigator.of(context).pop();
|
||||||
}
|
}
|
||||||
@@ -65,6 +77,7 @@ class _SpaceSubSpacesDialogState extends State<SpaceSubSpacesDialog> {
|
|||||||
subSpaces: _subspaces,
|
subSpaces: _subspaces,
|
||||||
onSubspaceAdded: _handleSubspaceAdded,
|
onSubspaceAdded: _handleSubspaceAdded,
|
||||||
onSubspaceDeleted: _handleSubspaceDeleted,
|
onSubspaceDeleted: _handleSubspaceDeleted,
|
||||||
|
controller: _subspaceNameController,
|
||||||
),
|
),
|
||||||
AnimatedSwitcher(
|
AnimatedSwitcher(
|
||||||
duration: const Duration(milliseconds: 100),
|
duration: const Duration(milliseconds: 100),
|
||||||
|
|||||||
+4
-5
@@ -10,29 +10,28 @@ class SubSpacesInput extends StatefulWidget {
|
|||||||
required this.subSpaces,
|
required this.subSpaces,
|
||||||
required this.onSubspaceAdded,
|
required this.onSubspaceAdded,
|
||||||
required this.onSubspaceDeleted,
|
required this.onSubspaceDeleted,
|
||||||
|
required this.controller,
|
||||||
});
|
});
|
||||||
|
|
||||||
final List<Subspace> subSpaces;
|
final List<Subspace> subSpaces;
|
||||||
final void Function(String name) onSubspaceAdded;
|
final void Function(String name) onSubspaceAdded;
|
||||||
final void Function(String uuid) onSubspaceDeleted;
|
final void Function(String uuid) onSubspaceDeleted;
|
||||||
|
final TextEditingController controller;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<SubSpacesInput> createState() => _SubSpacesInputState();
|
State<SubSpacesInput> createState() => _SubSpacesInputState();
|
||||||
}
|
}
|
||||||
|
|
||||||
class _SubSpacesInputState extends State<SubSpacesInput> {
|
class _SubSpacesInputState extends State<SubSpacesInput> {
|
||||||
late final TextEditingController _subspaceNameController;
|
|
||||||
late final FocusNode _focusNode;
|
late final FocusNode _focusNode;
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
_subspaceNameController = TextEditingController();
|
|
||||||
_focusNode = FocusNode();
|
_focusNode = FocusNode();
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void dispose() {
|
void dispose() {
|
||||||
_subspaceNameController.dispose();
|
|
||||||
_focusNode.dispose();
|
_focusNode.dispose();
|
||||||
super.dispose();
|
super.dispose();
|
||||||
}
|
}
|
||||||
@@ -81,7 +80,7 @@ class _SubSpacesInputState extends State<SubSpacesInput> {
|
|||||||
width: 200,
|
width: 200,
|
||||||
child: TextField(
|
child: TextField(
|
||||||
focusNode: _focusNode,
|
focusNode: _focusNode,
|
||||||
controller: _subspaceNameController,
|
controller: widget.controller,
|
||||||
decoration: InputDecoration(
|
decoration: InputDecoration(
|
||||||
border: InputBorder.none,
|
border: InputBorder.none,
|
||||||
hintText: widget.subSpaces.isEmpty ? 'Please enter the name' : null,
|
hintText: widget.subSpaces.isEmpty ? 'Please enter the name' : null,
|
||||||
@@ -93,7 +92,7 @@ class _SubSpacesInputState extends State<SubSpacesInput> {
|
|||||||
final trimmedValue = value.trim();
|
final trimmedValue = value.trim();
|
||||||
if (trimmedValue.isNotEmpty) {
|
if (trimmedValue.isNotEmpty) {
|
||||||
widget.onSubspaceAdded(trimmedValue);
|
widget.onSubspaceAdded(trimmedValue);
|
||||||
_subspaceNameController.clear();
|
widget.controller.clear();
|
||||||
_focusNode.requestFocus();
|
_focusNode.requestFocus();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -85,6 +85,8 @@ abstract class ColorsManager {
|
|||||||
static const Color minBlue = Color(0xFF93AAFD);
|
static const Color minBlue = Color(0xFF93AAFD);
|
||||||
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 dropDownSelectBlue = Color(0xFF2196F3);
|
||||||
|
static const Color drpoDownSelectBlue = Color(0xFF2196F3);
|
||||||
static const Color grey50 = Color(0xFF718096);
|
static const Color grey50 = Color(0xFF718096);
|
||||||
static const Color red100 = Color(0xFFFE0202);
|
static const Color red100 = Color(0xFFFE0202);
|
||||||
static const Color grey800 = Color(0xffF8F8F8);
|
static const Color grey800 = Color(0xffF8F8F8);
|
||||||
@@ -93,4 +95,5 @@ abstract class ColorsManager {
|
|||||||
static const Color shadowOfDetailsContainer = Color(0x40000000);
|
static const Color shadowOfDetailsContainer = Color(0x40000000);
|
||||||
static const Color checkBoxBorderGray = Color(0xffD0D0D0);
|
static const Color checkBoxBorderGray = Color(0xffD0D0D0);
|
||||||
static const Color timePickerColor = Color(0xff000000);
|
static const Color timePickerColor = Color(0xff000000);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -141,7 +141,7 @@ abstract class ApiEndpoints {
|
|||||||
'/projects/{projectUuid}/communities/{communityUuid}/spaces/{spaceUuid}/subspaces/{subSpaceUuid}/devices/{deviceUuid}';
|
'/projects/{projectUuid}/communities/{communityUuid}/spaces/{spaceUuid}/subspaces/{subSpaceUuid}/devices/{deviceUuid}';
|
||||||
static const String saveSchedule = '/schedule/{deviceUuid}';
|
static const String saveSchedule = '/schedule/{deviceUuid}';
|
||||||
|
|
||||||
|
static const String duplicateSpace = '/projects/{projectUuid}/communities/{communityUuid}/spaces/{spaceUuid}/duplicate';
|
||||||
|
|
||||||
////booking System
|
////booking System
|
||||||
static const String bookableSpaces = '/bookable-spaces';
|
static const String bookableSpaces = '/bookable-spaces';
|
||||||
|
|||||||
@@ -0,0 +1,56 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:syncrow_web/utils/color_manager.dart';
|
||||||
|
import 'package:syncrow_web/utils/extension/build_context_x.dart';
|
||||||
|
|
||||||
|
extension AppSnackBarsBuildContextExtension on BuildContext {
|
||||||
|
void showSuccessSnackbar(String message) {
|
||||||
|
ScaffoldMessenger.of(this).showSnackBar(
|
||||||
|
_makeSnackbar(
|
||||||
|
message: message,
|
||||||
|
icon: Icons.check_circle,
|
||||||
|
backgroundColor: Colors.green,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
void showFailureSnackbar(String message) {
|
||||||
|
ScaffoldMessenger.of(this).showSnackBar(
|
||||||
|
_makeSnackbar(
|
||||||
|
message: message,
|
||||||
|
icon: Icons.error,
|
||||||
|
backgroundColor: Colors.red,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
SnackBar _makeSnackbar({
|
||||||
|
required String message,
|
||||||
|
required Color backgroundColor,
|
||||||
|
required IconData icon,
|
||||||
|
}) {
|
||||||
|
return SnackBar(
|
||||||
|
content: Row(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
spacing: 8,
|
||||||
|
children: [
|
||||||
|
Icon(icon, color: Colors.white),
|
||||||
|
Text(
|
||||||
|
message,
|
||||||
|
style: textTheme.bodyMedium?.copyWith(
|
||||||
|
color: ColorsManager.whiteColors,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
backgroundColor: backgroundColor,
|
||||||
|
behavior: SnackBarBehavior.floating,
|
||||||
|
shape: RoundedRectangleBorder(
|
||||||
|
borderRadius: BorderRadius.circular(12.0),
|
||||||
|
),
|
||||||
|
margin: const EdgeInsetsDirectional.symmetric(
|
||||||
|
horizontal: 92,
|
||||||
|
vertical: 32,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user