Compare commits

...
Author SHA1 Message Date
hannathkadher 42c7577b35 validity for space name 2024-11-29 19:03:27 +04:00
hannathkadher ae2b4b9b06 border 2024-11-29 17:31:37 +04:00
hannathkadher 98faef2bde typo 2024-11-29 17:30:03 +04:00
hannathkadher edf297c569 border 2024-11-29 17:29:18 +04:00
hannathkadher 2cd850b66f border color change to red 2024-11-29 17:26:57 +04:00
hannathkadher 3c543b2e0f added validation for community name 2024-11-29 17:22:00 +04:00
hannathkadher a3c6421b0d updated flow for create community 2024-11-29 16:50:49 +04:00
hannathkadher 51402720dd space load on space save 2024-11-28 22:50:05 +04:00
hannathkadher 123291fd89 parse incoming connection 2024-11-28 20:31:19 +04:00
hannathkadher 69e3f50269 reduced width of text controller 2024-11-28 20:11:16 +04:00
hannathkadher bd78c8c1cc format 2024-11-28 20:09:28 +04:00
hannathkadher 4047664c59 create community should move back to blank page 2024-11-28 20:07:18 +04:00
hannathkadher 084f4fbda8 updated highlighted space 2024-11-28 20:02:41 +04:00
hannathkadher 817671dbeb updated deselect on structure 2024-11-28 20:01:14 +04:00
hannathkadher 660b5aaf6c remove unused code 2024-11-28 19:56:40 +04:00
hannathkadher 0f500a4efa remove unused state functions 2024-11-28 19:55:45 +04:00
hannathkadher 0b2d44fc2b updated LoadedSpaceView 2024-11-28 19:54:04 +04:00
hannathkadher b75f8a3440 side bar widget uses context 2024-11-28 19:52:20 +04:00
hannathkadher d02d680520 add events for select space and community 2024-11-28 19:45:24 +04:00
hannathkadher 68ae992963 added select space and select community event 2024-11-28 19:40:44 +04:00
hannathkadher ea20afd34e fixed issue of not recursively calling communtiy list 2024-11-28 15:16:07 +04:00
Abdullah b0f0a91cda Merge pull request #49 from SyncrowIOT/feature/space-management
Feature/space management
2024-11-28 12:15:06 +03:00
13 changed files with 519 additions and 286 deletions
@@ -7,20 +7,25 @@ import 'package:syncrow_web/pages/spaces_management/bloc/space_management_state.
import 'package:syncrow_web/services/product_api.dart'; import 'package:syncrow_web/services/product_api.dart';
import 'package:syncrow_web/services/space_mana_api.dart'; import 'package:syncrow_web/services/space_mana_api.dart';
class SpaceManagementBloc extends Bloc<SpaceManagementEvent, SpaceManagementState> { class SpaceManagementBloc
extends Bloc<SpaceManagementEvent, SpaceManagementState> {
final CommunitySpaceManagementApi _api; final CommunitySpaceManagementApi _api;
final ProductApi _productApi; final ProductApi _productApi;
List<ProductModel>? _cachedProducts; List<ProductModel>? _cachedProducts;
SpaceManagementBloc(this._api, this._productApi) : super(SpaceManagementInitial()) { SpaceManagementBloc(this._api, this._productApi)
: super(SpaceManagementInitial()) {
on<LoadCommunityAndSpacesEvent>(_onLoadCommunityAndSpaces); on<LoadCommunityAndSpacesEvent>(_onLoadCommunityAndSpaces);
on<UpdateSpacePositionEvent>(_onUpdateSpacePosition); on<UpdateSpacePositionEvent>(_onUpdateSpacePosition);
on<CreateCommunityEvent>(_onCreateCommunity); on<CreateCommunityEvent>(_onCreateCommunity);
on<SaveSpacesEvent>(_onSaveSpaces); on<SelectCommunityEvent>(_onSelectCommunity);
on<FetchProductsEvent>(_onFetchProducts);
on<DeleteCommunityEvent>(_onCommunityDelete); on<DeleteCommunityEvent>(_onCommunityDelete);
on<UpdateCommunityEvent>(_onUpdateCommunity); on<UpdateCommunityEvent>(_onUpdateCommunity);
on<SaveSpacesEvent>(_onSaveSpaces);
on<FetchProductsEvent>(_onFetchProducts);
on<SelectSpaceEvent>(_onSelectSpace);
on<NewCommunityEvent>(_onNewCommunity);
} }
void _onUpdateCommunity( void _onUpdateCommunity(
@@ -30,10 +35,12 @@ class SpaceManagementBloc extends Bloc<SpaceManagementEvent, SpaceManagementStat
final previousState = state; final previousState = state;
try { try {
emit(SpaceManagementLoading()); emit(SpaceManagementLoading());
final success = await _api.updateCommunity(event.communityUuid, event.name); final success =
await _api.updateCommunity(event.communityUuid, event.name);
if (success) { if (success) {
if (previousState is SpaceManagementLoaded) { if (previousState is SpaceManagementLoaded) {
final updatedCommunities = List<CommunityModel>.from(previousState.communities); final updatedCommunities =
List<CommunityModel>.from(previousState.communities);
for (var community in updatedCommunities) { for (var community in updatedCommunities) {
if (community.uuid == event.communityUuid) { if (community.uuid == event.communityUuid) {
community.name = event.name; community.name = event.name;
@@ -45,7 +52,6 @@ class SpaceManagementBloc extends Bloc<SpaceManagementEvent, SpaceManagementStat
products: previousState.products, products: previousState.products,
selectedCommunity: previousState.selectedCommunity, selectedCommunity: previousState.selectedCommunity,
)); ));
} }
} else { } else {
emit(const SpaceManagementError('Failed to update the community.')); emit(const SpaceManagementError('Failed to update the community.'));
@@ -55,20 +61,45 @@ class SpaceManagementBloc extends Bloc<SpaceManagementEvent, SpaceManagementStat
} }
} }
void _onloadProducts() async {
if (_cachedProducts == null) {
final products = await _productApi.fetchProducts();
_cachedProducts = products;
}
}
void _onFetchProducts( void _onFetchProducts(
FetchProductsEvent event, FetchProductsEvent event,
Emitter<SpaceManagementState> emit, Emitter<SpaceManagementState> emit,
) async { ) async {
if (_cachedProducts != null) { try {
// Products are already cached, no need to fetch again _onloadProducts();
} catch (e) {
emit(SpaceManagementError('Error fetching products: $e'));
}
}
Future<List<SpaceModel>> _fetchSpacesForCommunity(
String communityUuid) async {
return await _api.getSpaceHierarchy(communityUuid);
}
void _onNewCommunity(
NewCommunityEvent event,
Emitter<SpaceManagementState> emit,
) {
try {
if (event.communities.isEmpty) {
emit(const SpaceManagementError('No communities provided.'));
return; return;
} }
try { emit(BlankState(
final products = await _productApi.fetchProducts(); communities: event.communities,
_cachedProducts = products; // Cache the products locally products: _cachedProducts ?? [],
} catch (e) { ));
emit(SpaceManagementError('Error fetching products: $e')); } catch (error) {
emit(SpaceManagementError('Error loading communities: $error'));
} }
} }
@@ -78,17 +109,13 @@ class SpaceManagementBloc extends Bloc<SpaceManagementEvent, SpaceManagementStat
) async { ) async {
emit(SpaceManagementLoading()); emit(SpaceManagementLoading());
try { try {
if (_cachedProducts == null) { _onloadProducts();
final products = await _productApi.fetchProducts();
_cachedProducts = products;
}
// Fetch all communities
List<CommunityModel> communities = await _api.fetchCommunities(); List<CommunityModel> communities = await _api.fetchCommunities();
List<CommunityModel> updatedCommunities = await Future.wait( List<CommunityModel> updatedCommunities = await Future.wait(
communities.map((community) async { communities.map((community) async {
List<SpaceModel> spaces = await _api.getSpaceHierarchy(community.uuid); List<SpaceModel> spaces =
await _fetchSpacesForCommunity(community.uuid);
return CommunityModel( return CommunityModel(
uuid: community.uuid, uuid: community.uuid,
createdAt: community.createdAt, createdAt: community.createdAt,
@@ -101,7 +128,8 @@ class SpaceManagementBloc extends Bloc<SpaceManagementEvent, SpaceManagementStat
}).toList(), }).toList(),
); );
emit(SpaceManagementLoaded(communities: updatedCommunities, products: _cachedProducts ?? [])); emit(SpaceManagementLoaded(
communities: updatedCommunities, products: _cachedProducts ?? []));
} catch (e) { } catch (e) {
emit(SpaceManagementError('Error loading communities and spaces: $e')); emit(SpaceManagementError('Error loading communities and spaces: $e'));
} }
@@ -139,16 +167,21 @@ class SpaceManagementBloc extends Bloc<SpaceManagementEvent, SpaceManagementStat
emit(SpaceManagementLoading()); emit(SpaceManagementLoading());
try { try {
CommunityModel? newCommunity = await _api.createCommunity(event.name, event.description); CommunityModel? newCommunity =
await _api.createCommunity(event.name, event.description);
if (newCommunity != null) { if (newCommunity != null) {
if (previousState is SpaceManagementLoaded) { if (previousState is SpaceManagementLoaded ||
final updatedCommunities = List<CommunityModel>.from(previousState.communities) previousState is BlankState) {
..add(newCommunity); final prevCommunities = List<CommunityModel>.from(
(previousState as dynamic).communities,
);
final updatedCommunities = prevCommunities..add(newCommunity);
emit(SpaceManagementLoaded( emit(SpaceManagementLoaded(
communities: updatedCommunities, communities: updatedCommunities,
products: _cachedProducts ?? [], products: _cachedProducts ?? [],
selectedCommunity: newCommunity)); selectedCommunity: newCommunity,
selectedSpace: null));
} }
} else { } else {
emit(const SpaceManagementError('Error creating community')); emit(const SpaceManagementError('Error creating community'));
@@ -158,6 +191,54 @@ class SpaceManagementBloc extends Bloc<SpaceManagementEvent, SpaceManagementStat
} }
} }
void _onSelectCommunity(
SelectCommunityEvent event,
Emitter<SpaceManagementState> emit,
) async {
_handleCommunitySpaceStateUpdate(
emit: emit,
selectedCommunity: event.selectedCommunity,
selectedSpace: null,
);
}
void _onSelectSpace(
SelectSpaceEvent event,
Emitter<SpaceManagementState> emit,
) {
_handleCommunitySpaceStateUpdate(
emit: emit,
selectedCommunity: event.selectedCommunity,
selectedSpace: event.selectedSpace,
);
}
void _handleCommunitySpaceStateUpdate({
required Emitter<SpaceManagementState> emit,
CommunityModel? selectedCommunity,
SpaceModel? selectedSpace,
}) {
final previousState = state;
emit(SpaceManagementLoading());
try {
if (previousState is SpaceManagementLoaded ||
previousState is BlankState) {
final communities = List<CommunityModel>.from(
(previousState as dynamic).communities,
);
emit(SpaceManagementLoaded(
communities: communities,
products: _cachedProducts ?? [],
selectedCommunity: selectedCommunity,
selectedSpace: selectedSpace,
));
}
} catch (e) {
emit(SpaceManagementError('Error updating state: $e'));
}
}
void _onSaveSpaces( void _onSaveSpaces(
SaveSpacesEvent event, SaveSpacesEvent event,
Emitter<SpaceManagementState> emit, Emitter<SpaceManagementState> emit,
@@ -166,17 +247,54 @@ class SpaceManagementBloc extends Bloc<SpaceManagementEvent, SpaceManagementStat
emit(SpaceManagementLoading()); emit(SpaceManagementLoading());
try { try {
final updatedSpaces = await saveSpacesHierarchically(event.spaces, event.communityUuid); final updatedSpaces =
await saveSpacesHierarchically(event.spaces, event.communityUuid);
final allSpaces = await _fetchSpacesForCommunity(event.communityUuid);
emit(SpaceCreationSuccess(spaces: updatedSpaces)); emit(SpaceCreationSuccess(spaces: updatedSpaces));
if (previousState is SpaceManagementLoaded) {
_updateLoadedState(
previousState,
allSpaces,
event.communityUuid,
emit,
);
} else {
add(LoadCommunityAndSpacesEvent()); add(LoadCommunityAndSpacesEvent());
}
} catch (e) { } catch (e) {
emit(SpaceManagementError('Error saving spaces: $e')); emit(SpaceManagementError('Error saving spaces: $e'));
if (previousState is SpaceManagementLoaded) { if (previousState is SpaceManagementLoaded) {
emit(previousState); emit(previousState);
} }
} }
} }
void _updateLoadedState(
SpaceManagementLoaded previousState,
List<SpaceModel> allSpaces,
String communityUuid,
Emitter<SpaceManagementState> emit,
) {
final communities = List<CommunityModel>.from(previousState.communities);
for (var community in communities) {
if (community.uuid == communityUuid) {
community.spaces = allSpaces;
emit(SpaceManagementLoaded(
communities: communities,
products: _cachedProducts ?? [],
selectedCommunity: community,
selectedSpace: null,
));
return;
}
}
}
Future<List<SpaceModel>> saveSpacesHierarchically( Future<List<SpaceModel>> saveSpacesHierarchically(
List<SpaceModel> spaces, String communityUuid) async { List<SpaceModel> spaces, String communityUuid) async {
final orderedSpaces = flattenHierarchy(spaces); final orderedSpaces = flattenHierarchy(spaces);
@@ -187,7 +305,6 @@ class SpaceManagementBloc extends Bloc<SpaceManagementEvent, SpaceManagementStat
for (var parent in parentsToDelete) { for (var parent in parentsToDelete) {
try { try {
// Ensure parent.uuid is not null before calling the API
if (parent.uuid != null) { if (parent.uuid != null) {
await _api.deleteSpace(communityUuid, parent.uuid!); await _api.deleteSpace(communityUuid, parent.uuid!);
} }
@@ -1,5 +1,6 @@
import 'package:equatable/equatable.dart'; import 'package:equatable/equatable.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:syncrow_web/pages/spaces_management/model/community_model.dart';
import 'package:syncrow_web/pages/spaces_management/model/space_model.dart'; // Import for Offset import 'package:syncrow_web/pages/spaces_management/model/space_model.dart'; // Import for Offset
abstract class SpaceManagementEvent extends Equatable { abstract class SpaceManagementEvent extends Equatable {
@@ -83,17 +84,6 @@ class CreateCommunityEvent extends SpaceManagementEvent {
List<Object> get props => [name, description]; List<Object> get props => [name, description];
} }
class FetchProductsEvent extends SpaceManagementEvent {}
class LoadSpaceHierarchyEvent extends SpaceManagementEvent {
final String communityId;
const LoadSpaceHierarchyEvent({required this.communityId});
@override
List<Object> get props => [communityId];
}
class UpdateCommunityEvent extends SpaceManagementEvent { class UpdateCommunityEvent extends SpaceManagementEvent {
final String communityUuid; final String communityUuid;
final String name; final String name;
@@ -106,3 +96,47 @@ class UpdateCommunityEvent extends SpaceManagementEvent {
@override @override
List<Object> get props => [communityUuid, name]; List<Object> get props => [communityUuid, name];
} }
class SelectCommunityEvent extends SpaceManagementEvent {
final CommunityModel? selectedCommunity;
const SelectCommunityEvent({
required this.selectedCommunity,
});
@override
List<Object> get props => [];
}
class NewCommunityEvent extends SpaceManagementEvent {
final List<CommunityModel> communities;
const NewCommunityEvent({required this.communities});
@override
List<Object> get props => [communities];
}
class SelectSpaceEvent extends SpaceManagementEvent {
final CommunityModel? selectedCommunity;
final SpaceModel? selectedSpace;
const SelectSpaceEvent({
required this.selectedCommunity,
required this.selectedSpace,
});
@override
List<Object> get props => [];
}
class FetchProductsEvent extends SpaceManagementEvent {}
class LoadSpaceHierarchyEvent extends SpaceManagementEvent {
final String communityId;
const LoadSpaceHierarchyEvent({required this.communityId});
@override
List<Object> get props => [communityId];
}
@@ -17,10 +17,24 @@ class SpaceManagementLoading extends SpaceManagementState {}
class SpaceManagementLoaded extends SpaceManagementState { class SpaceManagementLoaded extends SpaceManagementState {
final List<CommunityModel> communities; final List<CommunityModel> communities;
final List<ProductModel> products; final List<ProductModel> products;
CommunityModel? selectedCommunity; // Include products in the state CommunityModel? selectedCommunity;
SpaceModel? selectedSpace;
SpaceManagementLoaded( SpaceManagementLoaded(
{required this.communities, required this.products, this.selectedCommunity}); {required this.communities,
required this.products,
this.selectedCommunity,
this.selectedSpace});
}
class BlankState extends SpaceManagementState {
final List<CommunityModel> communities;
final List<ProductModel> products;
BlankState({
required this.communities,
required this.products,
});
} }
class SpaceCreationSuccess extends SpaceManagementState { class SpaceCreationSuccess extends SpaceManagementState {
@@ -44,7 +44,8 @@ class SpaceModel {
this.selectedProducts = const [], this.selectedProducts = const [],
}) : internalId = internalId ?? const Uuid().v4(); }) : internalId = internalId ?? const Uuid().v4();
factory SpaceModel.fromJson(Map<String, dynamic> json, {String? parentInternalId}) { factory SpaceModel.fromJson(Map<String, dynamic> json,
{String? parentInternalId}) {
final String internalId = json['internalId'] ?? const Uuid().v4(); final String internalId = json['internalId'] ?? const Uuid().v4();
final List<SpaceModel> children = json['children'] != null final List<SpaceModel> children = json['children'] != null
@@ -56,7 +57,7 @@ class SpaceModel {
}).toList() }).toList()
: []; : [];
return SpaceModel( final instance = SpaceModel(
internalId: internalId, internalId: internalId,
uuid: json['uuid'] ?? '', uuid: json['uuid'] ?? '',
spaceTuyaUuid: json['spaceTuyaUuid'], spaceTuyaUuid: json['spaceTuyaUuid'],
@@ -72,11 +73,14 @@ class SpaceModel {
isPrivate: json['parent']?['isPrivate'] ?? false, isPrivate: json['parent']?['isPrivate'] ?? false,
invitationCode: json['parent']?['invitationCode'], invitationCode: json['parent']?['invitationCode'],
children: [], children: [],
position: Offset(json['parent']?['x'] ?? 0, json['parent']?['y'] ?? 0), position:
Offset(json['parent']?['x'] ?? 0, json['parent']?['y'] ?? 0),
icon: json['parent']?['icon'] ?? Assets.location, icon: json['parent']?['icon'] ?? Assets.location,
) )
: null, : null,
community: json['community'] != null ? CommunityModel.fromJson(json['community']) : null, community: json['community'] != null
? CommunityModel.fromJson(json['community'])
: null,
children: children, children: children,
icon: json['icon'] ?? Assets.location, icon: json['icon'] ?? Assets.location,
position: Offset(json['x'] ?? 0, json['y'] ?? 0), position: Offset(json['x'] ?? 0, json['y'] ?? 0),
@@ -90,6 +94,20 @@ class SpaceModel {
}).toList() }).toList()
: [], : [],
); );
if (json['incomingConnections'] != null &&
json['incomingConnections'] is List &&
(json['incomingConnections'] as List).isNotEmpty &&
instance.parent != null) {
final conn = json['incomingConnections'][0];
instance.incomingConnection = Connection(
startSpace: instance.parent ?? instance, // Parent space
endSpace: instance, // This space instance
direction: conn['direction'],
);
}
return instance;
} }
Map<String, dynamic> toMap() { Map<String, dynamic> toMap() {
@@ -20,8 +20,6 @@ class SpaceManagementPage extends StatefulWidget {
} }
class SpaceManagementPageState extends State<SpaceManagementPage> { class SpaceManagementPageState extends State<SpaceManagementPage> {
CommunityModel? selectedCommunity;
SpaceModel? selectedSpace;
final CommunitySpaceManagementApi _api = CommunitySpaceManagementApi(); final CommunitySpaceManagementApi _api = CommunitySpaceManagementApi();
final ProductApi _productApi = ProductApi(); final ProductApi _productApi = ProductApi();
Map<String, List<SpaceModel>> communitySpaces = {}; Map<String, List<SpaceModel>> communitySpaces = {};
@@ -36,43 +34,30 @@ class SpaceManagementPageState extends State<SpaceManagementPage> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return BlocProvider( return BlocProvider(
create: (context) => create: (context) => SpaceManagementBloc(_api, _productApi)
SpaceManagementBloc(_api, _productApi)..add(LoadCommunityAndSpacesEvent()), ..add(LoadCommunityAndSpacesEvent()),
child: WebScaffold( child: WebScaffold(
appBarTitle: Text('Space Management', style: Theme.of(context).textTheme.headlineLarge), appBarTitle: Text('Space Management',
style: Theme.of(context).textTheme.headlineLarge),
enableMenuSidebar: false, enableMenuSidebar: false,
rightBody: const NavigateHomeGridView(), rightBody: const NavigateHomeGridView(),
scaffoldBody: scaffoldBody: BlocBuilder<SpaceManagementBloc, SpaceManagementState>(
BlocBuilder<SpaceManagementBloc, SpaceManagementState>(builder: (context, state) { builder: (context, state) {
if (state is SpaceManagementLoading) { if (state is SpaceManagementLoading) {
return const Center(child: CircularProgressIndicator()); return const Center(child: CircularProgressIndicator());
} else if (state is SpaceManagementLoaded) { } else if (state is BlankState) {
int selectedIndex = state.communities.indexWhere(
(community) => community.uuid == selectedCommunity?.uuid,
);
if (selectedIndex != -1) {
selectedCommunity = state.communities[selectedIndex];
} else if (state.selectedCommunity != null) {
selectedCommunity = state.selectedCommunity;
} else {
selectedCommunity = null;
selectedSpace = null;
}
return LoadedSpaceView( return LoadedSpaceView(
communities: state.communities, communities: state.communities,
selectedCommunity: selectedCommunity, selectedCommunity: null,
selectedSpace: selectedSpace, selectedSpace: null,
products: state.products,
);
} else if (state is SpaceManagementLoaded) {
return LoadedSpaceView(
communities: state.communities,
selectedCommunity: state.selectedCommunity,
selectedSpace: state.selectedSpace,
products: state.products, products: state.products,
onCommunitySelected: (community) {
setState(() {
selectedCommunity = community;
});
},
onSpaceSelected: (space) {
setState(() {
selectedSpace = space;
});
},
); );
} else if (state is SpaceManagementError) { } else if (state is SpaceManagementError) {
return Center(child: Text('Error: ${state.errorMessage}')); return Center(child: Text('Error: ${state.errorMessage}'));
@@ -2,31 +2,41 @@ import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:syncrow_web/pages/spaces_management/bloc/space_management_bloc.dart'; import 'package:syncrow_web/pages/spaces_management/bloc/space_management_bloc.dart';
import 'package:syncrow_web/pages/spaces_management/bloc/space_management_event.dart'; import 'package:syncrow_web/pages/spaces_management/bloc/space_management_event.dart';
import 'package:syncrow_web/pages/spaces_management/model/community_model.dart';
import 'package:syncrow_web/pages/spaces_management/widgets/dialogs/create_community_dialog.dart'; import 'package:syncrow_web/pages/spaces_management/widgets/dialogs/create_community_dialog.dart';
import 'package:syncrow_web/utils/color_manager.dart'; import 'package:syncrow_web/utils/color_manager.dart';
class BlankCommunityWidget extends StatelessWidget { class BlankCommunityWidget extends StatefulWidget {
const BlankCommunityWidget({Key? key}) : super(key: key); final List<CommunityModel> communities;
BlankCommunityWidget({required this.communities});
@override
_BlankCommunityWidgetState createState() => _BlankCommunityWidgetState();
}
class _BlankCommunityWidgetState extends State<BlankCommunityWidget> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Expanded( return Expanded(
child: Container( child: Container(
color: ColorsManager.whiteColors, // Parent container with white background color:
ColorsManager.whiteColors, // Parent container with white background
child: GridView.builder( child: GridView.builder(
padding: const EdgeInsets.only(left: 40.0, top: 20.0), padding: const EdgeInsets.only(left: 40.0, top: 20.0),
gridDelegate: SliverGridDelegateWithMaxCrossAxisExtent( gridDelegate: const SliverGridDelegateWithMaxCrossAxisExtent(
maxCrossAxisExtent: 400, // Each item's width will be 400 or less maxCrossAxisExtent: 400,
mainAxisSpacing: 10, // Spacing between items mainAxisSpacing: 10,
crossAxisSpacing: 10, // Spacing between items crossAxisSpacing: 10,
childAspectRatio: 2.0, // Aspect ratio for width:height (e.g., 300:150 = 2.0) childAspectRatio: 2.0,
), ),
itemCount: 1, // Only one item itemCount: 1, // Only one item
itemBuilder: (context, index) { itemBuilder: (context, index) {
return GestureDetector( return GestureDetector(
onTap: () => _showCreateCommunityDialog(context), onTap: () => _showCreateCommunityDialog(context),
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.center, // Center align the content crossAxisAlignment:
CrossAxisAlignment.center, // Center align the content
children: [ children: [
Expanded( Expanded(
child: AspectRatio( child: AspectRatio(
@@ -34,7 +44,7 @@ class BlankCommunityWidget extends StatelessWidget {
child: Container( child: Container(
decoration: ShapeDecoration( decoration: ShapeDecoration(
shape: RoundedRectangleBorder( shape: RoundedRectangleBorder(
side: BorderSide( side: const BorderSide(
width: 4, width: 4,
strokeAlign: BorderSide.strokeAlignOutside, strokeAlign: BorderSide.strokeAlignOutside,
color: ColorsManager.borderColor, color: ColorsManager.borderColor,
@@ -45,9 +55,8 @@ class BlankCommunityWidget extends StatelessWidget {
), ),
), ),
), ),
const SizedBox(height: 9),
const SizedBox(height: 9), // Space between item and text Text('Blank',
Text('Blank', // Text below the item
style: Theme.of(context).textTheme.bodyLarge?.copyWith( style: Theme.of(context).textTheme.bodyLarge?.copyWith(
color: ColorsManager.blackColor, color: ColorsManager.blackColor,
)), )),
@@ -63,6 +72,7 @@ class BlankCommunityWidget extends StatelessWidget {
showDialog( showDialog(
context: parentContext, context: parentContext,
builder: (context) => CreateCommunityDialog( builder: (context) => CreateCommunityDialog(
communities: widget.communities,
onCreateCommunity: (String communityName, String description) { onCreateCommunity: (String communityName, String description) {
parentContext.read<SpaceManagementBloc>().add( parentContext.read<SpaceManagementBloc>().add(
CreateCommunityEvent( CreateCommunityEvent(
@@ -86,7 +86,7 @@ class CommunityStructureHeader extends StatelessWidget {
), ),
if (isEditingName) if (isEditingName)
SizedBox( SizedBox(
width: screenWidth * 0.3, width: screenWidth * 0.1,
child: TextField( child: TextField(
controller: nameController, controller: nameController,
decoration: const InputDecoration( decoration: const InputDecoration(
@@ -24,12 +24,13 @@ class CommunityStructureArea extends StatefulWidget {
SpaceModel? selectedSpace; SpaceModel? selectedSpace;
final List<ProductModel>? products; final List<ProductModel>? products;
final ValueChanged<SpaceModel?>? onSpaceSelected; final ValueChanged<SpaceModel?>? onSpaceSelected;
final List<CommunityModel> communities;
final List<SpaceModel> spaces; final List<SpaceModel> spaces;
CommunityStructureArea({ CommunityStructureArea({
this.selectedCommunity, this.selectedCommunity,
this.selectedSpace, this.selectedSpace,
required this.communities,
this.products, this.products,
required this.spaces, required this.spaces,
this.onSpaceSelected, this.onSpaceSelected,
@@ -52,7 +53,8 @@ class _CommunityStructureAreaState extends State<CommunityStructureArea> {
void initState() { void initState() {
super.initState(); super.initState();
spaces = widget.spaces.isNotEmpty ? flattenSpaces(widget.spaces) : []; spaces = widget.spaces.isNotEmpty ? flattenSpaces(widget.spaces) : [];
connections = widget.spaces.isNotEmpty ? createConnections(widget.spaces) : []; connections =
widget.spaces.isNotEmpty ? createConnections(widget.spaces) : [];
_adjustCanvasSizeForSpaces(); _adjustCanvasSizeForSpaces();
_nameController = TextEditingController( _nameController = TextEditingController(
text: widget.selectedCommunity?.name ?? '', text: widget.selectedCommunity?.name ?? '',
@@ -79,12 +81,14 @@ class _CommunityStructureAreaState extends State<CommunityStructureArea> {
if (oldWidget.spaces != widget.spaces) { if (oldWidget.spaces != widget.spaces) {
setState(() { setState(() {
spaces = widget.spaces.isNotEmpty ? flattenSpaces(widget.spaces) : []; spaces = widget.spaces.isNotEmpty ? flattenSpaces(widget.spaces) : [];
connections = widget.spaces.isNotEmpty ? createConnections(widget.spaces) : []; connections =
widget.spaces.isNotEmpty ? createConnections(widget.spaces) : [];
_adjustCanvasSizeForSpaces(); _adjustCanvasSizeForSpaces();
}); });
} }
if (widget.selectedSpace != oldWidget.selectedSpace && widget.selectedSpace != null) { if (widget.selectedSpace != oldWidget.selectedSpace &&
widget.selectedSpace != null) {
WidgetsBinding.instance.addPostFrameCallback((_) { WidgetsBinding.instance.addPostFrameCallback((_) {
_moveToSpace(widget.selectedSpace!); _moveToSpace(widget.selectedSpace!);
}); });
@@ -94,14 +98,16 @@ class _CommunityStructureAreaState extends State<CommunityStructureArea> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
if (widget.selectedCommunity == null) { if (widget.selectedCommunity == null) {
return BlankCommunityWidget(); return BlankCommunityWidget(
communities: widget.communities,
);
} }
Size screenSize = MediaQuery.of(context).size; Size screenSize = MediaQuery.of(context).size;
return Expanded( return Expanded(
child: GestureDetector( child: GestureDetector(
onTap: () { onTap: () {
_deselectSpace(); _deselectSpace(context);
}, },
child: Container( child: Container(
decoration: const BoxDecoration( decoration: const BoxDecoration(
@@ -156,9 +162,11 @@ class _CommunityStructureAreaState extends State<CommunityStructureArea> {
children: [ children: [
for (var connection in connections) for (var connection in connections)
Opacity( Opacity(
opacity: opacity: _isHighlightedConnection(connection)
_isHighlightedConnection(connection) ? 1.0 : 0.3, // Adjust opacity ? 1.0
child: CustomPaint(painter: CurvedLinePainter([connection])), : 0.3, // Adjust opacity
child: CustomPaint(
painter: CurvedLinePainter([connection])),
), ),
for (var entry in spaces.asMap().entries) for (var entry in spaces.asMap().entries)
if (entry.value.status != SpaceStatus.deleted) if (entry.value.status != SpaceStatus.deleted)
@@ -167,10 +175,12 @@ class _CommunityStructureAreaState extends State<CommunityStructureArea> {
top: entry.value.position.dy, top: entry.value.position.dy,
child: SpaceCardWidget( child: SpaceCardWidget(
index: entry.key, index: entry.key,
onButtonTap: (int index, Offset newPosition, String direction) { onButtonTap: (int index, Offset newPosition,
String direction) {
_showCreateSpaceDialog( _showCreateSpaceDialog(
screenSize, screenSize,
position: spaces[index].position + newPosition, position:
spaces[index].position + newPosition,
parentIndex: index, parentIndex: index,
direction: direction, direction: direction,
); );
@@ -183,7 +193,8 @@ class _CommunityStructureAreaState extends State<CommunityStructureArea> {
_updateNodePosition(entry.value, newPosition); _updateNodePosition(entry.value, newPosition);
}, },
buildSpaceContainer: (int index) { buildSpaceContainer: (int index) {
final bool isHighlighted = _isHighlightedSpace(spaces[index]); final bool isHighlighted =
_isHighlightedSpace(spaces[index]);
return Opacity( return Opacity(
opacity: isHighlighted ? 1.0 : 0.3, opacity: isHighlighted ? 1.0 : 0.3,
@@ -193,7 +204,7 @@ class _CommunityStructureAreaState extends State<CommunityStructureArea> {
_showEditSpaceDialog(spaces[index]); _showEditSpaceDialog(spaces[index]);
}, },
onTap: () { onTap: () {
_selectSpace(spaces[index]); _selectSpace(context, spaces[index]);
}, },
icon: spaces[index].icon ?? '', icon: spaces[index].icon ?? '',
name: spaces[index].name, name: spaces[index].name,
@@ -210,7 +221,8 @@ class _CommunityStructureAreaState extends State<CommunityStructureArea> {
child: AddSpaceButton( child: AddSpaceButton(
onTap: () { onTap: () {
_showCreateSpaceDialog(screenSize, _showCreateSpaceDialog(screenSize,
canvasHeight: canvasHeight, canvasWidth: canvasWidth); canvasHeight: canvasHeight,
canvasWidth: canvasWidth);
}, },
), ),
), ),
@@ -271,11 +283,13 @@ class _CommunityStructureAreaState extends State<CommunityStructureArea> {
return CreateSpaceDialog( return CreateSpaceDialog(
products: widget.products, products: widget.products,
parentSpace: parentIndex != null ? spaces[parentIndex] : null, parentSpace: parentIndex != null ? spaces[parentIndex] : null,
onCreateSpace: (String name, String icon, List<SelectedProduct> selectedProducts) { onCreateSpace: (String name, String icon,
List<SelectedProduct> selectedProducts) {
setState(() { setState(() {
// Set the first space in the center or use passed position // Set the first space in the center or use passed position
Offset centerPosition = position ?? _getCenterPosition(screenSize); Offset centerPosition =
position ?? _getCenterPosition(screenSize);
SpaceModel newSpace = SpaceModel( SpaceModel newSpace = SpaceModel(
name: name, name: name,
icon: icon, icon: icon,
@@ -317,9 +331,11 @@ class _CommunityStructureAreaState extends State<CommunityStructureArea> {
products: widget.products, products: widget.products,
name: space.name, name: space.name,
icon: space.icon, icon: space.icon,
editSpace: space,
isEdit: true, isEdit: true,
selectedProducts: space.selectedProducts, selectedProducts: space.selectedProducts,
onCreateSpace: (String name, String icon, List<SelectedProduct> selectedProducts) { onCreateSpace: (String name, String icon,
List<SelectedProduct> selectedProducts) {
setState(() { setState(() {
// Update the space's properties // Update the space's properties
space.name = name; space.name = name;
@@ -331,8 +347,7 @@ class _CommunityStructureAreaState extends State<CommunityStructureArea> {
} }
}); });
}, },
// Pre-fill the dialog with current space data key: Key(space.name),
key: Key(space.name), // Add a unique key to ensure dialog refresh
); );
}, },
); );
@@ -465,40 +480,30 @@ class _CommunityStructureAreaState extends State<CommunityStructureArea> {
..scale(1.2); ..scale(1.2);
} }
void _selectSpace(SpaceModel space) { void _selectSpace(BuildContext context, SpaceModel space) {
setState(() { context.read<SpaceManagementBloc>().add(
widget.selectedSpace = space; SelectSpaceEvent(
}); selectedCommunity: widget.selectedCommunity,
selectedSpace: space),
if (widget.onSpaceSelected != null) { );
widget.onSpaceSelected!(space);
}
} }
bool _isHighlightedSpace(SpaceModel space) { bool _isHighlightedSpace(SpaceModel space) {
if (widget.selectedSpace == null) return true; final selectedSpace = widget.selectedSpace;
if (space == widget.selectedSpace) return true; if (selectedSpace == null) return true;
if (widget.selectedSpace?.parent?.internalId == space.internalId) return true;
if (widget.selectedSpace?.children != null) { return space == selectedSpace ||
for (var child in widget.selectedSpace!.children) { selectedSpace.parent?.internalId == space.internalId ||
if (child.internalId == space.internalId) { selectedSpace.children
return true; ?.any((child) => child.internalId == space.internalId) ==
} true;
}
}
return false;
} }
void _deselectSpace() { void _deselectSpace(BuildContext context) {
if (widget.selectedSpace != null) { context.read<SpaceManagementBloc>().add(
setState(() { SelectSpaceEvent(
widget.selectedSpace = null; selectedCommunity: widget.selectedCommunity, selectedSpace: null),
}); );
if (widget.onSpaceSelected != null) {
widget.onSpaceSelected!(null); // Notify parent that no space is selected
}
}
} }
bool _isHighlightedConnection(Connection connection) { bool _isHighlightedConnection(Connection connection) {
@@ -1,37 +1,47 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:syncrow_web/pages/common/buttons/cancel_button.dart';
import 'package:syncrow_web/pages/common/buttons/default_button.dart';
import 'package:syncrow_web/pages/spaces_management/model/community_model.dart';
import 'package:syncrow_web/utils/color_manager.dart'; import 'package:syncrow_web/utils/color_manager.dart';
class CreateCommunityDialog extends StatefulWidget { class CreateCommunityDialog extends StatefulWidget {
final Function(String name, String description) onCreateCommunity; final Function(String name, String description) onCreateCommunity;
final List<CommunityModel> communities;
const CreateCommunityDialog({super.key, required this.onCreateCommunity}); const CreateCommunityDialog(
{super.key, required this.onCreateCommunity, required this.communities});
@override @override
CreateCommunityDialogState createState() => CreateCommunityDialogState(); CreateCommunityDialogState createState() => CreateCommunityDialogState();
} }
class CreateCommunityDialogState extends State<CreateCommunityDialog> { class CreateCommunityDialogState extends State<CreateCommunityDialog> {
String enteredName = ''; // Store the entered community name String enteredName = '';
bool isNameFieldExist = false;
bool isNameEmpty = false;
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final screenWidth = MediaQuery.of(context).size.width;
return Dialog( return Dialog(
shape: RoundedRectangleBorder( shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(20), borderRadius: BorderRadius.circular(20),
), ),
backgroundColor: Colors.transparent, // Transparent for shadow effect backgroundColor:
ColorsManager.transparentColor, // Transparent for shadow effect
child: Stack( child: Stack(
children: [ children: [
// Background container with shadow and rounded corners // Background container with shadow and rounded corners
Container( Container(
width: 490, width: screenWidth * 0.3,
padding: const EdgeInsets.all(20), padding: const EdgeInsets.all(20),
decoration: BoxDecoration( decoration: BoxDecoration(
color: Colors.white, color: ColorsManager.whiteColors,
borderRadius: BorderRadius.circular(20), borderRadius: BorderRadius.circular(20),
boxShadow: [ boxShadow: [
BoxShadow( BoxShadow(
color: Colors.black.withOpacity(0.25), color: ColorsManager.blackColor.withOpacity(0.25),
blurRadius: 20, blurRadius: 20,
spreadRadius: 5, spreadRadius: 5,
offset: const Offset(0, 5), offset: const Offset(0, 5),
@@ -53,10 +63,20 @@ class CreateCommunityDialogState extends State<CreateCommunityDialog> {
// Input field for the community name // Input field for the community name
TextField( TextField(
onChanged: (value) { onChanged: (value) {
enteredName = value; // Capture entered name setState(() {
enteredName = value.trim();
isNameFieldExist = widget.communities.any(
(community) => community.name == enteredName,
);
if (value.isEmpty) {
isNameEmpty = true;
} else {
isNameEmpty = false;
}
});
}, },
style: const TextStyle( style: const TextStyle(
color: Colors.black, color: ColorsManager.blackColor,
), ),
decoration: InputDecoration( decoration: InputDecoration(
hintText: 'Please enter the community name', hintText: 'Please enter the community name',
@@ -68,45 +88,68 @@ class CreateCommunityDialogState extends State<CreateCommunityDialog> {
fontWeight: FontWeight.w400, fontWeight: FontWeight.w400,
), ),
border: OutlineInputBorder( border: OutlineInputBorder(
borderSide: const BorderSide(color: ColorsManager.boxColor, width: 1), borderSide: BorderSide(
color: isNameFieldExist || isNameEmpty
? ColorsManager.red
: ColorsManager.boxColor,
width: 1),
borderRadius: BorderRadius.circular(10), borderRadius: BorderRadius.circular(10),
), ),
enabledBorder: OutlineInputBorder( enabledBorder: OutlineInputBorder(
borderSide: const BorderSide(color: ColorsManager.boxColor, width: 1), borderSide: BorderSide(
color: isNameFieldExist || isNameEmpty
? ColorsManager.red
: ColorsManager.boxColor, width: 1),
borderRadius: BorderRadius.circular(10), borderRadius: BorderRadius.circular(10),
), ),
focusedBorder: OutlineInputBorder( focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(10), borderRadius: BorderRadius.circular(10),
borderSide: const BorderSide(color: ColorsManager.boxColor, width: 1), borderSide: BorderSide(
color: isNameFieldExist || isNameEmpty
? ColorsManager.red
: ColorsManager.boxColor,
width: 1),
), ),
), ),
), ),
if (isNameFieldExist)
Padding(
padding: const EdgeInsets.only(top: 8.0),
child: Text(
'*Name already exists.',
style: Theme.of(context)
.textTheme
.bodySmall
?.copyWith(color: ColorsManager.red),
),
),
if (isNameEmpty)
Padding(
padding: const EdgeInsets.only(top: 8.0),
child: Text(
'*Name should not be empty.',
style: Theme.of(context)
.textTheme
.bodySmall
?.copyWith(color: ColorsManager.red),
),
),
const SizedBox(height: 24), const SizedBox(height: 24),
// Action buttons // Action buttons
Row( Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [ children: [
Expanded( Expanded(
child: TextButton( child: CancelButton(
label: 'Cancel',
onPressed: () => Navigator.of(context).pop(), onPressed: () => Navigator.of(context).pop(),
style: TextButton.styleFrom(
padding: const EdgeInsets.symmetric(vertical: 16),
backgroundColor: ColorsManager.boxColor,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(10),
),
),
child: const Text(
'Cancel',
style: TextStyle(color: Colors.black),
),
), ),
), ),
const SizedBox(width: 16), const SizedBox(width: 16),
Expanded( Expanded(
child: ElevatedButton( child: DefaultButton(
onPressed: () { onPressed: () {
if (enteredName.isNotEmpty) { if (enteredName.isNotEmpty && !isNameFieldExist) {
widget.onCreateCommunity( widget.onCreateCommunity(
enteredName, enteredName,
"", "",
@@ -114,14 +157,11 @@ class CreateCommunityDialogState extends State<CreateCommunityDialog> {
Navigator.of(context).pop(); Navigator.of(context).pop();
} }
}, },
style: ElevatedButton.styleFrom( backgroundColor: isNameFieldExist || isNameEmpty
padding: const EdgeInsets.symmetric(vertical: 16), ? ColorsManager.lightGrayColor
backgroundColor: ColorsManager.secondaryColor, : ColorsManager.secondaryColor,
foregroundColor: Colors.white, borderRadius: 10,
shape: RoundedRectangleBorder( foregroundColor: ColorsManager.whiteColors,
borderRadius: BorderRadius.circular(10),
),
),
child: const Text('OK'), child: const Text('OK'),
), ),
), ),
@@ -13,13 +13,15 @@ import 'package:syncrow_web/utils/constants/assets.dart';
import 'package:syncrow_web/utils/constants/space_icon_const.dart'; import 'package:syncrow_web/utils/constants/space_icon_const.dart';
class CreateSpaceDialog extends StatefulWidget { class CreateSpaceDialog extends StatefulWidget {
final Function(String, String, List<SelectedProduct> selectedProducts) onCreateSpace; final Function(String, String, List<SelectedProduct> selectedProducts)
onCreateSpace;
final List<ProductModel>? products; final List<ProductModel>? products;
final String? name; final String? name;
final String? icon; final String? icon;
final bool isEdit; final bool isEdit;
final List<SelectedProduct> selectedProducts; final List<SelectedProduct> selectedProducts;
final SpaceModel? parentSpace; final SpaceModel? parentSpace;
final SpaceModel? editSpace;
const CreateSpaceDialog( const CreateSpaceDialog(
{super.key, {super.key,
@@ -29,6 +31,7 @@ class CreateSpaceDialog extends StatefulWidget {
this.name, this.name,
this.icon, this.icon,
this.isEdit = false, this.isEdit = false,
this.editSpace,
this.selectedProducts = const []}); this.selectedProducts = const []});
@override @override
@@ -49,8 +52,10 @@ class CreateSpaceDialogState extends State<CreateSpaceDialog> {
super.initState(); super.initState();
selectedIcon = widget.icon ?? Assets.location; selectedIcon = widget.icon ?? Assets.location;
nameController = TextEditingController(text: widget.name ?? ''); nameController = TextEditingController(text: widget.name ?? '');
selectedProducts = widget.selectedProducts.isNotEmpty ? widget.selectedProducts : []; selectedProducts =
isOkButtonEnabled = enteredName.isNotEmpty || nameController.text.isNotEmpty; widget.selectedProducts.isNotEmpty ? widget.selectedProducts : [];
isOkButtonEnabled =
enteredName.isNotEmpty || nameController.text.isNotEmpty;
} }
@override @override
@@ -59,7 +64,9 @@ class CreateSpaceDialogState extends State<CreateSpaceDialog> {
final screenWidth = MediaQuery.of(context).size.width; final screenWidth = MediaQuery.of(context).size.width;
return AlertDialog( return AlertDialog(
title: widget.isEdit ? const Text('Edit Space') : const Text('Create New Space'), title: widget.isEdit
? const Text('Edit Space')
: const Text('Create New Space'),
backgroundColor: ColorsManager.whiteColors, backgroundColor: ColorsManager.whiteColors,
content: SizedBox( content: SizedBox(
width: screenWidth * 0.5, // Limit dialog width width: screenWidth * 0.5, // Limit dialog width
@@ -126,11 +133,17 @@ class CreateSpaceDialogState extends State<CreateSpaceDialog> {
isNameFieldInvalid = value.isEmpty; isNameFieldInvalid = value.isEmpty;
if (!isNameFieldInvalid) { if (!isNameFieldInvalid) {
if (widget.parentSpace?.children if ((widget.parentSpace?.children.any(
.any((child) => child.name == value) ?? (child) => child.name == value) ??
false) { false) ||
(widget.parentSpace?.name == value) ||
(widget.editSpace?.children.any(
(child) => child.name == value) ??
false)) {
isNameFieldExist = true; isNameFieldExist = true;
isOkButtonEnabled = false;
} else { } else {
isNameFieldExist = false;
isOkButtonEnabled = true; isOkButtonEnabled = true;
} }
} }
@@ -218,7 +231,8 @@ class CreateSpaceDialogState extends State<CreateSpaceDialog> {
padding: const EdgeInsets.only(left: 6.0), padding: const EdgeInsets.only(left: 6.0),
child: SvgPicture.asset( child: SvgPicture.asset(
Assets.addIcon, Assets.addIcon,
width: screenWidth * 0.015, // Adjust icon size width: screenWidth *
0.015, // Adjust icon size
height: screenWidth * 0.015, height: screenWidth * 0.015,
), ),
), ),
@@ -226,8 +240,11 @@ class CreateSpaceDialogState extends State<CreateSpaceDialog> {
Flexible( Flexible(
child: Text( child: Text(
'Add devices / Assign a space model', 'Add devices / Assign a space model',
overflow: TextOverflow.ellipsis, // Prevent overflow overflow: TextOverflow
style: Theme.of(context).textTheme.bodyMedium, .ellipsis, // Prevent overflow
style: Theme.of(context)
.textTheme
.bodyMedium,
), ),
), ),
], ],
@@ -262,16 +279,20 @@ class CreateSpaceDialogState extends State<CreateSpaceDialog> {
}); });
return; return;
} else { } else {
String newName = enteredName.isNotEmpty ? enteredName : (widget.name ?? ''); String newName = enteredName.isNotEmpty
? enteredName
: (widget.name ?? '');
if (newName.isNotEmpty) { if (newName.isNotEmpty) {
widget.onCreateSpace(newName, selectedIcon, selectedProducts); widget.onCreateSpace(
newName, selectedIcon, selectedProducts);
Navigator.of(context).pop(); Navigator.of(context).pop();
} }
} }
}, },
borderRadius: 10, borderRadius: 10,
backgroundColor: backgroundColor: isOkButtonEnabled
isOkButtonEnabled ? ColorsManager.secondaryColor : ColorsManager.grayColor, ? ColorsManager.secondaryColor
: ColorsManager.grayColor,
foregroundColor: ColorsManager.whiteColors, foregroundColor: ColorsManager.whiteColors,
child: const Text('OK'), child: const Text('OK'),
), ),
@@ -318,7 +339,8 @@ class CreateSpaceDialogState extends State<CreateSpaceDialog> {
children: [ children: [
for (var i = 0; i < selectedProducts.length; i++) ...[ for (var i = 0; i < selectedProducts.length; i++) ...[
HoverableButton( HoverableButton(
iconPath: _mapIconToProduct(selectedProducts[i].productId, products), iconPath:
_mapIconToProduct(selectedProducts[i].productId, products),
text: 'x${selectedProducts[i].count}', text: 'x${selectedProducts[i].count}',
onTap: () { onTap: () {
setState(() { setState(() {
@@ -328,7 +350,8 @@ class CreateSpaceDialogState extends State<CreateSpaceDialog> {
}, },
), ),
if (i < selectedProducts.length - 1) if (i < selectedProducts.length - 1)
const SizedBox(width: 2), // Add space except after the last button const SizedBox(
width: 2), // Add space except after the last button
], ],
const SizedBox(width: 2), const SizedBox(width: 2),
GestureDetector( GestureDetector(
@@ -364,6 +387,8 @@ class CreateSpaceDialogState extends State<CreateSpaceDialog> {
); );
} }
String _mapIconToProduct(String uuid, List<ProductModel> products) { String _mapIconToProduct(String uuid, List<ProductModel> products) {
// Find the product with the matching UUID // Find the product with the matching UUID
final product = products.firstWhere( final product = products.firstWhere(
@@ -10,8 +10,6 @@ class LoadedSpaceView extends StatefulWidget {
final List<CommunityModel> communities; final List<CommunityModel> communities;
final CommunityModel? selectedCommunity; final CommunityModel? selectedCommunity;
final SpaceModel? selectedSpace; final SpaceModel? selectedSpace;
final ValueChanged<CommunityModel>? onCommunitySelected;
final ValueChanged<SpaceModel?>? onSpaceSelected;
final List<ProductModel>? products; final List<ProductModel>? products;
const LoadedSpaceView({ const LoadedSpaceView({
@@ -19,8 +17,6 @@ class LoadedSpaceView extends StatefulWidget {
required this.communities, required this.communities,
this.selectedCommunity, this.selectedCommunity,
this.selectedSpace, this.selectedSpace,
required this.onCommunitySelected,
required this.onSpaceSelected,
this.products, this.products,
}); });
@@ -38,27 +34,16 @@ class _LoadedStateViewState extends State<LoadedSpaceView> {
children: [ children: [
SidebarWidget( SidebarWidget(
communities: widget.communities, communities: widget.communities,
onCommunitySelected: widget.onCommunitySelected, selectedSpaceUuid: widget.selectedSpace?.uuid ??
onSpaceSelected: widget.onSpaceSelected, widget.selectedCommunity?.uuid ??
selectedSpaceUuid: widget.selectedSpace?.uuid, '',
onSelectedSpaceChanged: (String? spaceUuid) { ),
setState(() {
final selectedSpace = findSpaceByUuid(spaceUuid, widget.communities);
if (selectedSpace != null) {
widget.onSpaceSelected!(selectedSpace);
}
});
}),
CommunityStructureArea( CommunityStructureArea(
selectedCommunity: widget.selectedCommunity, selectedCommunity: widget.selectedCommunity,
selectedSpace: widget.selectedSpace, selectedSpace: widget.selectedSpace,
spaces: widget.selectedCommunity?.spaces ?? [], spaces: widget.selectedCommunity?.spaces ?? [],
products: widget.products, products: widget.products,
onSpaceSelected: (SpaceModel? space) { communities: widget.communities,
setState(() {
widget.onSpaceSelected!(space);
});
},
), ),
], ],
), ),
@@ -7,25 +7,17 @@ import 'package:syncrow_web/pages/spaces_management/bloc/space_management_event.
import 'package:syncrow_web/pages/spaces_management/model/community_model.dart'; import 'package:syncrow_web/pages/spaces_management/model/community_model.dart';
import 'package:syncrow_web/pages/spaces_management/model/space_model.dart'; import 'package:syncrow_web/pages/spaces_management/model/space_model.dart';
import 'package:syncrow_web/pages/spaces_management/widgets/community_tile.dart'; import 'package:syncrow_web/pages/spaces_management/widgets/community_tile.dart';
import 'package:syncrow_web/pages/spaces_management/widgets/dialogs/create_community_dialog.dart';
import 'package:syncrow_web/pages/spaces_management/widgets/space_tile_widget.dart'; import 'package:syncrow_web/pages/spaces_management/widgets/space_tile_widget.dart';
import 'package:syncrow_web/utils/color_manager.dart'; import 'package:syncrow_web/utils/color_manager.dart';
import 'package:syncrow_web/utils/constants/assets.dart'; import 'package:syncrow_web/utils/constants/assets.dart';
import 'package:syncrow_web/utils/style.dart'; import 'package:syncrow_web/utils/style.dart';
class SidebarWidget extends StatefulWidget { class SidebarWidget extends StatefulWidget {
final Function(CommunityModel)? onCommunitySelected;
final Function(SpaceModel?)? onSpaceSelected;
final List<CommunityModel> communities; final List<CommunityModel> communities;
final Function(String?)? onSelectedSpaceChanged; // New callback
final String? selectedSpaceUuid; final String? selectedSpaceUuid;
const SidebarWidget({ const SidebarWidget({
super.key, super.key,
this.onCommunitySelected,
this.onSpaceSelected,
this.onSelectedSpaceChanged,
required this.communities, required this.communities,
this.selectedSpaceUuid, this.selectedSpaceUuid,
}); });
@@ -42,7 +34,8 @@ class _SidebarWidgetState extends State<SidebarWidget> {
@override @override
void initState() { void initState() {
super.initState(); super.initState();
_selectedId = widget.selectedSpaceUuid; // Initialize with the passed selected space UUID _selectedId = widget
.selectedSpaceUuid; // Initialize with the passed selected space UUID
} }
@override @override
@@ -55,22 +48,6 @@ class _SidebarWidgetState extends State<SidebarWidget> {
} }
} }
void _showCreateCommunityDialog(BuildContext parentContext) {
showDialog(
context: parentContext,
builder: (context) => CreateCommunityDialog(
onCreateCommunity: (String communityName, String description) {
parentContext.read<SpaceManagementBloc>().add(
CreateCommunityEvent(
name: communityName,
description: description,
),
);
},
),
);
}
// Function to filter communities based on the search query // Function to filter communities based on the search query
List<CommunityModel> _filterCommunities() { List<CommunityModel> _filterCommunities() {
if (_searchQuery.isEmpty) { if (_searchQuery.isEmpty) {
@@ -83,8 +60,8 @@ class _SidebarWidgetState extends State<SidebarWidget> {
return widget.communities.where((community) { return widget.communities.where((community) {
final containsQueryInCommunity = final containsQueryInCommunity =
community.name.toLowerCase().contains(_searchQuery.toLowerCase()); community.name.toLowerCase().contains(_searchQuery.toLowerCase());
final containsQueryInSpaces = final containsQueryInSpaces = community.spaces
community.spaces.any((space) => _containsQuery(space, _searchQuery.toLowerCase())); .any((space) => _containsQuery(space, _searchQuery.toLowerCase()));
return containsQueryInCommunity || containsQueryInSpaces; return containsQueryInCommunity || containsQueryInSpaces;
}).toList(); }).toList();
@@ -93,8 +70,8 @@ class _SidebarWidgetState extends State<SidebarWidget> {
// Helper function to determine if any space or its children match the search query // Helper function to determine if any space or its children match the search query
bool _containsQuery(SpaceModel space, String query) { bool _containsQuery(SpaceModel space, String query) {
final matchesSpace = space.name.toLowerCase().contains(query); final matchesSpace = space.name.toLowerCase().contains(query);
final matchesChildren = final matchesChildren = space.children.any((child) =>
space.children.any((child) => _containsQuery(child, query)); // Recursive check for children _containsQuery(child, query)); // Recursive check for children
// If the space or any of its children match the query, expand this space // If the space or any of its children match the query, expand this space
if (matchesSpace || matchesChildren) { if (matchesSpace || matchesChildren) {
@@ -128,7 +105,8 @@ class _SidebarWidgetState extends State<SidebarWidget> {
width: 300, width: 300,
decoration: subSectionContainerDecoration, decoration: subSectionContainerDecoration,
child: Column( child: Column(
mainAxisSize: MainAxisSize.min, // Ensures the Column only takes necessary height mainAxisSize:
MainAxisSize.min, // Ensures the Column only takes necessary height
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
// Communities title with the add button // Communities title with the add button
@@ -143,7 +121,7 @@ class _SidebarWidgetState extends State<SidebarWidget> {
color: Colors.black, color: Colors.black,
)), )),
GestureDetector( GestureDetector(
onTap: () => _showCreateCommunityDialog(context), onTap: () => _navigateToBlank(context),
child: Container( child: Container(
width: 30, width: 30,
height: 30, height: 30,
@@ -176,7 +154,7 @@ class _SidebarWidgetState extends State<SidebarWidget> {
Expanded( Expanded(
child: ListView( child: ListView(
children: filteredCommunities.map((community) { children: filteredCommunities.map((community) {
return _buildCommunityTile(community); return _buildCommunityTile(context, community);
}).toList(), }).toList(),
), ),
), ),
@@ -185,7 +163,16 @@ class _SidebarWidgetState extends State<SidebarWidget> {
); );
} }
Widget _buildCommunityTile(CommunityModel community) { void _navigateToBlank(BuildContext context) {
setState(() {
_selectedId = '';
});
context.read<SpaceManagementBloc>().add(
NewCommunityEvent(communities: widget.communities),
);
}
Widget _buildCommunityTile(BuildContext context, CommunityModel community) {
bool hasChildren = community.spaces.isNotEmpty; bool hasChildren = community.spaces.isNotEmpty;
return CommunityTile( return CommunityTile(
@@ -199,23 +186,23 @@ class _SidebarWidgetState extends State<SidebarWidget> {
_selectedSpaceUuid = null; // Update the selected community _selectedSpaceUuid = null; // Update the selected community
}); });
if (widget.onCommunitySelected != null) { context.read<SpaceManagementBloc>().add(
widget.onCommunitySelected!(community); SelectCommunityEvent(selectedCommunity: community),
widget.onSpaceSelected!(null); // Pass the entire community );
}
}, },
onExpansionChanged: (String title, bool expanded) { onExpansionChanged: (String title, bool expanded) {
_handleExpansionChange(community.uuid, expanded); _handleExpansionChange(community.uuid, expanded);
}, },
children: hasChildren children: hasChildren
? community.spaces.map((space) => _buildSpaceTile(space, community)).toList() ? community.spaces
.map((space) => _buildSpaceTile(space, community))
.toList()
: null, // Render spaces within the community : null, // Render spaces within the community
); );
} }
Widget _buildSpaceTile(SpaceModel space, CommunityModel community) { Widget _buildSpaceTile(SpaceModel space, CommunityModel community) {
bool isExpandedSpace = _isSpaceOrChildSelected(space); bool isExpandedSpace = _isSpaceOrChildSelected(space);
// Check if space should be expanded
return SpaceTile( return SpaceTile(
title: space.name, title: space.name,
key: ValueKey(space.uuid), key: ValueKey(space.uuid),
@@ -230,17 +217,15 @@ class _SidebarWidgetState extends State<SidebarWidget> {
_selectedSpaceUuid = space.uuid; _selectedSpaceUuid = space.uuid;
}); });
if (widget.onSpaceSelected != null) { context.read<SpaceManagementBloc>().add(
widget.onCommunitySelected!(community); SelectSpaceEvent(
widget.onSpaceSelected!(space); selectedCommunity: community, selectedSpace: space),
} );
if (widget.onSelectedSpaceChanged != null) {
widget.onSelectedSpaceChanged!(space.uuid);
}
}, },
children: space.children.isNotEmpty children: space.children.isNotEmpty
? space.children.map((childSpace) => _buildSpaceTile(childSpace, community)).toList() ? space.children
.map((childSpace) => _buildSpaceTile(childSpace, community))
.toList()
: [], // Recursively render child spaces if available : [], // Recursively render child spaces if available
); );
} }
+30 -15
View File
@@ -8,22 +8,31 @@ import 'package:syncrow_web/utils/constants/api_const.dart';
class CommunitySpaceManagementApi { class CommunitySpaceManagementApi {
// Community Management APIs // Community Management APIs
Future<List<CommunityModel>> fetchCommunities() async { Future<List<CommunityModel>> fetchCommunities({int page = 1}) async {
try { try {
final response = await HTTPService().get( List<CommunityModel> allCommunities = [];
path: ApiEndpoints.getCommunityList, bool hasNext = true;
expectedResponseModel: (json) {
// Access the 'data' key from the response
List<dynamic> jsonData = json['data'];
// Check if jsonData is actually a List while (hasNext) {
await HTTPService().get(
path: ApiEndpoints.getCommunityList,
queryParameters: {'page': page},
expectedResponseModel: (json) {
List<dynamic> jsonData = json['data'];
hasNext = json['hasNext'] ?? false;
int currentPage = json['page'] ?? 1;
List<CommunityModel> communityList = jsonData.map((jsonItem) { List<CommunityModel> communityList = jsonData.map((jsonItem) {
return CommunityModel.fromJson(jsonItem); return CommunityModel.fromJson(jsonItem);
}).toList(); }).toList();
allCommunities.addAll(communityList);
page = currentPage + 1;
return communityList; return communityList;
}, },
); );
return response; }
return allCommunities;
} catch (e) { } catch (e) {
debugPrint('Error fetching communities: $e'); debugPrint('Error fetching communities: $e');
return []; return [];
@@ -33,7 +42,8 @@ class CommunitySpaceManagementApi {
Future<CommunityModel?> getCommunityById(String communityId) async { Future<CommunityModel?> getCommunityById(String communityId) async {
try { try {
final response = await HTTPService().get( final response = await HTTPService().get(
path: ApiEndpoints.getCommunityById.replaceAll('{communityId}', communityId), path: ApiEndpoints.getCommunityById
.replaceAll('{communityId}', communityId),
expectedResponseModel: (json) { expectedResponseModel: (json) {
return CommunityModel.fromJson(json['data']); return CommunityModel.fromJson(json['data']);
}, },
@@ -45,7 +55,8 @@ class CommunitySpaceManagementApi {
} }
} }
Future<CommunityModel?> createCommunity(String name, String description) async { Future<CommunityModel?> createCommunity(
String name, String description) async {
try { try {
final response = await HTTPService().post( final response = await HTTPService().post(
path: ApiEndpoints.createCommunity, path: ApiEndpoints.createCommunity,
@@ -67,7 +78,8 @@ class CommunitySpaceManagementApi {
Future<bool> updateCommunity(String communityId, String name) async { Future<bool> updateCommunity(String communityId, String name) async {
try { try {
final response = await HTTPService().put( final response = await HTTPService().put(
path: ApiEndpoints.updateCommunity.replaceAll('{communityId}', communityId), path: ApiEndpoints.updateCommunity
.replaceAll('{communityId}', communityId),
body: { body: {
'name': name, 'name': name,
}, },
@@ -85,7 +97,8 @@ class CommunitySpaceManagementApi {
Future<bool> deleteCommunity(String communityId) async { Future<bool> deleteCommunity(String communityId) async {
try { try {
final response = await HTTPService().delete( final response = await HTTPService().delete(
path: ApiEndpoints.deleteCommunity.replaceAll('{communityId}', communityId), path: ApiEndpoints.deleteCommunity
.replaceAll('{communityId}', communityId),
expectedResponseModel: (json) { expectedResponseModel: (json) {
return json['success'] ?? false; return json['success'] ?? false;
}, },
@@ -236,10 +249,12 @@ class CommunitySpaceManagementApi {
Future<List<SpaceModel>> getSpaceHierarchy(String communityId) async { Future<List<SpaceModel>> getSpaceHierarchy(String communityId) async {
try { try {
final response = await HTTPService().get( final response = await HTTPService().get(
path: ApiEndpoints.getSpaceHierarchy.replaceAll('{communityId}', communityId), path: ApiEndpoints.getSpaceHierarchy
.replaceAll('{communityId}', communityId),
expectedResponseModel: (json) { expectedResponseModel: (json) {
final spaceModels = final spaceModels = (json['data'] as List)
(json['data'] as List).map((spaceJson) => SpaceModel.fromJson(spaceJson)).toList(); .map((spaceJson) => SpaceModel.fromJson(spaceJson))
.toList();
return spaceModels; return spaceModels;
}, },