Implemented the selection behavior of the side tree

This commit is contained in:
Abdullah Alassaf
2025-01-30 04:03:54 +03:00
parent 2221d9ae7b
commit 43c17d1c18
14 changed files with 446 additions and 501 deletions

View File

@ -46,14 +46,13 @@ class CustomSearchBar extends StatelessWidget {
filled: true, filled: true,
fillColor: ColorsManager.textFieldGreyColor, fillColor: ColorsManager.textFieldGreyColor,
hintText: hintText, hintText: hintText,
hintStyle: TextStyle( hintStyle: Theme.of(context).textTheme.bodyLarge!.copyWith(
color: Color(0xB2999999), color: ColorsManager.lightGrayColor,
fontSize: 12, fontSize: 12,
fontFamily: 'Aftika', fontWeight: FontWeight.w400,
fontWeight: FontWeight.w400, height: 0,
height: 0, letterSpacing: -0.24,
letterSpacing: -0.24, ),
),
suffixIcon: Padding( suffixIcon: Padding(
padding: const EdgeInsets.only(right: 16), padding: const EdgeInsets.only(right: 16),
child: SvgPicture.asset( child: SvgPicture.asset(

View File

@ -8,7 +8,7 @@ import 'package:syncrow_web/pages/device_managment/all_devices/models/devices_mo
import 'package:syncrow_web/pages/device_managment/all_devices/widgets/device_search_filters.dart'; import 'package:syncrow_web/pages/device_managment/all_devices/widgets/device_search_filters.dart';
import 'package:syncrow_web/pages/device_managment/shared/device_batch_control_dialog.dart'; import 'package:syncrow_web/pages/device_managment/shared/device_batch_control_dialog.dart';
import 'package:syncrow_web/pages/device_managment/shared/device_control_dialog.dart'; import 'package:syncrow_web/pages/device_managment/shared/device_control_dialog.dart';
import 'package:syncrow_web/pages/space_tree/view/side_spaces_view.dart'; import 'package:syncrow_web/pages/space_tree/view/side_tree_view.dart';
import 'package:syncrow_web/utils/format_date_time.dart'; import 'package:syncrow_web/utils/format_date_time.dart';
import 'package:syncrow_web/utils/helpers/responsice_layout_helper/responsive_layout_helper.dart'; import 'package:syncrow_web/utils/helpers/responsice_layout_helper/responsive_layout_helper.dart';
import 'package:syncrow_web/utils/style.dart'; import 'package:syncrow_web/utils/style.dart';
@ -62,12 +62,13 @@ class DeviceManagementBody extends StatelessWidget with HelperResponsiveLayout {
return Row( return Row(
children: [ children: [
Flexible(child: SideSpacesView( const Expanded(
onSelectAction: (String communityId, String spaceId) { child: SideTreeView(
context.read<DeviceManagementBloc>().add(FetchDevices(communityId, spaceId)); // onSelectAction: (String communityId, String spaceId) {
}, // context.read<DeviceManagementBloc>().add(FetchDevices(communityId, spaceId));
)), // },
Flexible( )),
Expanded(
flex: 3, flex: 3,
child: state is DeviceManagementLoading child: state is DeviceManagementLoading
? const Center(child: CircularProgressIndicator()) ? const Center(child: CircularProgressIndicator())

View File

@ -82,7 +82,7 @@ class HomeWebPage extends StatelessWidget {
child: GridView.builder( child: GridView.builder(
itemCount: 3, //8 itemCount: 3, //8
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount( gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 4, crossAxisCount: 3, //4
crossAxisSpacing: 20.0, crossAxisSpacing: 20.0,
mainAxisSpacing: 20.0, mainAxisSpacing: 20.0,
childAspectRatio: 1.5, childAspectRatio: 1.5,

View File

@ -5,7 +5,7 @@ import 'package:syncrow_web/pages/routines/view/create_new_routine_view.dart';
import 'package:syncrow_web/pages/routines/widgets/main_routine_view/fetch_routine_scenes_automation.dart'; import 'package:syncrow_web/pages/routines/widgets/main_routine_view/fetch_routine_scenes_automation.dart';
import 'package:syncrow_web/pages/routines/widgets/main_routine_view/routine_view_card.dart'; import 'package:syncrow_web/pages/routines/widgets/main_routine_view/routine_view_card.dart';
import 'package:syncrow_web/pages/space_tree/bloc/space_tree_bloc.dart'; import 'package:syncrow_web/pages/space_tree/bloc/space_tree_bloc.dart';
import 'package:syncrow_web/pages/space_tree/view/side_spaces_view.dart'; import 'package:syncrow_web/pages/space_tree/view/side_tree_view.dart';
import 'package:syncrow_web/utils/color_manager.dart'; import 'package:syncrow_web/utils/color_manager.dart';
import 'package:syncrow_web/utils/snack_bar.dart'; import 'package:syncrow_web/utils/snack_bar.dart';
@ -32,13 +32,16 @@ class _RoutinesViewState extends State<RoutinesView> {
} }
return Row( return Row(
children: [ children: [
Expanded(child: SideSpacesView( const Expanded(
onSelectAction: (String communityId, String spaceId) { child:
context.read<RoutineBloc>() // SideSpacesView(
..add(LoadScenes(spaceId, communityId)) // onSelectAction: (String communityId, String spaceId) {
..add(LoadAutomation(spaceId)); // // context.read<RoutineBloc>()
}, // // ..add(LoadScenes(spaceId, communityId))
)), // // ..add(LoadAutomation(spaceId));
// },
// )
SideTreeView()),
Expanded( Expanded(
flex: 3, flex: 3,
child: Padding( child: Padding(

View File

@ -8,21 +8,26 @@ import 'package:syncrow_web/services/space_mana_api.dart';
class SpaceTreeBloc extends Bloc<SpaceTreeEvent, SpaceTreeState> { class SpaceTreeBloc extends Bloc<SpaceTreeEvent, SpaceTreeState> {
String selectedCommunityId = ''; String selectedCommunityId = '';
String selectedSpaceId = ''; String selectedSpaceId = '';
SpaceTreeBloc() : super(const SpaceTreeState()) { SpaceTreeBloc() : super(const SpaceTreeState()) {
on<InitialEvent>(_fetchSpaces); on<InitialEvent>(_fetchSpaces);
on<OnSelectSpaceEvent>(_onSelectSpace); on<OnCommunityExpanded>(_onCommunityExpanded);
on<OnSpaceExpanded>(_onSpaceExpanded);
on<OnCommunitySelected>(_onCommunitySelected);
on<OnSpaceSelected>(_onSpaceSelected);
on<SearchQueryEvent>(_onSearch);
} }
_fetchSpaces(InitialEvent event, Emitter<SpaceTreeState> emit) async { _fetchSpaces(InitialEvent event, Emitter<SpaceTreeState> emit) async {
emit(SpaceTreeLoadingState()); emit(SpaceTreeLoadingState());
try { try {
// _onloadProducts();
List<CommunityModel> communities = await CommunitySpaceManagementApi().fetchCommunities(); List<CommunityModel> communities = await CommunitySpaceManagementApi().fetchCommunities();
List<CommunityModel> updatedCommunities = await Future.wait( List<CommunityModel> updatedCommunities = await Future.wait(
communities.map((community) async { communities.map((community) async {
List<SpaceModel> spaces = List<SpaceModel> spaces =
await CommunitySpaceManagementApi().getSpaceHierarchy(community.uuid); await CommunitySpaceManagementApi().getSpaceHierarchy(community.uuid);
return CommunityModel( return CommunityModel(
uuid: community.uuid, uuid: community.uuid,
createdAt: community.createdAt, createdAt: community.createdAt,
@ -35,35 +40,170 @@ class SpaceTreeBloc extends Bloc<SpaceTreeEvent, SpaceTreeState> {
}).toList(), }).toList(),
); );
if (updatedCommunities.isNotEmpty &&
state.selectedSpace.isEmpty &&
state.selectedCommunity.isEmpty) {
selectedCommunityId = updatedCommunities[0].uuid;
} else {
selectedCommunityId = state.selectedCommunity;
selectedSpaceId = state.selectedSpace;
}
emit(state.copyWith( emit(state.copyWith(
communitiesList: updatedCommunities, communitiesList: updatedCommunities, expandedCommunity: [], expandedSpaces: []));
selectedCommunity: selectedCommunityId,
selectedSpace: selectedSpaceId));
} catch (e) { } catch (e) {
emit(SpaceTreeErrorState('Error loading communities and spaces: $e')); emit(SpaceTreeErrorState('Error loading communities and spaces: $e'));
} }
} }
_onSelectSpace(OnSelectSpaceEvent event, Emitter<SpaceTreeState> emit) async { _onCommunityExpanded(OnCommunityExpanded event, Emitter<SpaceTreeState> emit) async {
try { try {
selectedCommunityId = event.communityId; List<String> updatedExpandedCommunityList = List.from(state.expandedCommunities);
selectedSpaceId = event.spaceId;
if (updatedExpandedCommunityList.contains(event.communityId)) {
updatedExpandedCommunityList.remove(event.communityId);
} else {
updatedExpandedCommunityList.add(event.communityId);
}
emit(state.copyWith( emit(state.copyWith(
communitiesList: state.spacesList, expandedCommunity: updatedExpandedCommunityList,
selectedCommunity: event.communityId, ));
selectedSpace: event.spaceId));
} catch (e) { } catch (e) {
emit(const SpaceTreeErrorState('Something went wrong')); emit(const SpaceTreeErrorState('Something went wrong'));
} }
} }
_onSpaceExpanded(OnSpaceExpanded event, Emitter<SpaceTreeState> emit) async {
try {
List<String> updatedExpandedSpacesList = List.from(state.expandedSpaces);
if (updatedExpandedSpacesList.contains(event.spaceId)) {
updatedExpandedSpacesList.remove(event.spaceId);
} else {
updatedExpandedSpacesList.add(event.spaceId);
}
emit(state.copyWith(expandedSpaces: updatedExpandedSpacesList));
} catch (e) {
emit(const SpaceTreeErrorState('Something went wrong'));
}
}
_onCommunitySelected(OnCommunitySelected event, Emitter<SpaceTreeState> emit) async {
try {
List<String> updatedSelectedCommunities = List.from(state.selectedCommunities);
List<String> updatedSelectedSpaces = List.from(state.selectedSpaces);
List<String> updatedSoldChecks = List.from(state.soldCheck);
List<String> childrenIds = _getAllChildIds(event.children);
if (!updatedSelectedCommunities.contains(event.communityId)) {
// Select the community and all its children
updatedSelectedCommunities.add(event.communityId);
updatedSelectedSpaces.addAll(childrenIds);
} else {
// Unselect the community and all its children
updatedSelectedCommunities.remove(event.communityId);
updatedSelectedSpaces.removeWhere(childrenIds.contains);
updatedSoldChecks.removeWhere(childrenIds.contains);
}
emit(state.copyWith(
selectedCommunities: updatedSelectedCommunities,
selectedSpaces: updatedSelectedSpaces,
soldCheck: updatedSoldChecks));
} catch (e) {
emit(const SpaceTreeErrorState('Something went wrong'));
}
}
_onSpaceSelected(OnSpaceSelected event, Emitter<SpaceTreeState> emit) async {
try {
List<String> updatedSelectedCommunities = List.from(state.selectedCommunities);
List<String> updatedSelectedSpaces = List.from(state.selectedSpaces);
List<String> updatedSoldChecks = List.from(state.soldCheck);
List<String> childrenIds = _getAllChildIds(event.children);
// List<String> childrenIds = _getAllChildSpaceIds(event.communityId);
if (!updatedSelectedSpaces.contains(event.spaceId) &&
!updatedSoldChecks.contains(event.spaceId)) {
// First click: Select the space and all its children
updatedSelectedSpaces.add(event.spaceId);
updatedSelectedCommunities.add(event.communityId);
if (childrenIds.isNotEmpty) {
updatedSelectedSpaces.addAll(childrenIds);
}
} else if (!updatedSoldChecks.contains(event.spaceId) && childrenIds.isNotEmpty) {
// Second click: Unselect space but keep children
updatedSelectedSpaces.remove(event.spaceId);
updatedSoldChecks.add(event.spaceId);
} else {
// Third click: Unselect space and all its children
if (!_anySpacesSelectedInCommunity(event.communityId)) {
updatedSelectedCommunities.remove(event.communityId);
}
updatedSelectedSpaces.remove(event.spaceId);
if (childrenIds.isNotEmpty) {
updatedSelectedSpaces.removeWhere(childrenIds.contains);
}
updatedSoldChecks.remove(event.spaceId);
}
emit(state.copyWith(
selectedCommunities: updatedSelectedCommunities,
selectedSpaces: updatedSelectedSpaces,
soldCheck: updatedSoldChecks));
emit(state.copyWith(selectedSpaces: updatedSelectedSpaces));
} catch (e) {
emit(const SpaceTreeErrorState('Something went wrong'));
}
}
_onSearch(SearchQueryEvent event, Emitter<SpaceTreeState> emit) async {
try {
List<CommunityModel> communities = List.from(state.communityList);
List<CommunityModel> filteredCommunity = [];
// Filter communities and expand only those that match the query
filteredCommunity = communities.where((community) {
final containsQueryInCommunity =
community.name.toLowerCase().contains(event.searchQuery.toLowerCase());
final containsQueryInSpaces =
community.spaces.any((space) => _containsQuery(space, event.searchQuery.toLowerCase()));
return containsQueryInCommunity || containsQueryInSpaces;
}).toList();
emit(state.copyWith(
filteredCommunity: filteredCommunity, isSearching: event.searchQuery.isNotEmpty));
} catch (e) {
emit(const SpaceTreeErrorState('Something went wrong'));
}
}
// Helper function to determine if any space or its children match the search query
bool _containsQuery(SpaceModel space, String query) {
final matchesSpace = space.name.toLowerCase().contains(query);
final matchesChildren =
space.children.any((child) => _containsQuery(child, query)); // Recursive check for children
return matchesSpace || matchesChildren;
}
List<String> _getAllChildIds(List<SpaceModel> spaces) {
List<String> ids = [];
for (var child in spaces) {
ids.add(child.uuid!);
ids.addAll(_getAllChildIds(child.children));
}
return ids;
}
bool _anySpacesSelectedInCommunity(String communityId) {
bool result = false;
for (var community in state.communityList) {
if (community.uuid == communityId) {
List<String> ids = _getAllChildIds(community.spaces);
for (var id in ids) {
result = state.selectedSpaces.contains(id);
break;
}
}
}
return result;
}
} }

View File

@ -1,4 +1,5 @@
import 'package:equatable/equatable.dart'; import 'package:equatable/equatable.dart';
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/space_model.dart';
class SpaceTreeEvent extends Equatable { class SpaceTreeEvent extends Equatable {
const SpaceTreeEvent(); const SpaceTreeEvent();
@ -9,16 +10,6 @@ class SpaceTreeEvent extends Equatable {
class InitialEvent extends SpaceTreeEvent {} class InitialEvent extends SpaceTreeEvent {}
class OnSelectSpaceEvent extends SpaceTreeEvent {
final String communityId;
final String spaceId;
const OnSelectSpaceEvent(this.communityId, this.spaceId);
@override
List<Object> get props => [communityId, spaceId];
}
class SearchForSpace extends SpaceTreeEvent { class SearchForSpace extends SpaceTreeEvent {
final String searchQuery; final String searchQuery;
@ -27,3 +18,52 @@ class SearchForSpace extends SpaceTreeEvent {
@override @override
List<Object> get props => [searchQuery]; List<Object> get props => [searchQuery];
} }
class OnCommunityExpanded extends SpaceTreeEvent {
final String communityId;
const OnCommunityExpanded(this.communityId);
@override
List<Object> get props => [communityId];
}
class OnCommunitySelected extends SpaceTreeEvent {
final String communityId;
final List<SpaceModel> children;
const OnCommunitySelected(this.communityId, this.children);
@override
List<Object> get props => [communityId, children];
}
class OnSpaceExpanded extends SpaceTreeEvent {
final String communityId;
final String spaceId;
const OnSpaceExpanded(this.communityId, this.spaceId);
@override
List<Object> get props => [communityId, spaceId];
}
class OnSpaceSelected extends SpaceTreeEvent {
final String communityId;
final String spaceId;
final List<SpaceModel> children;
const OnSpaceSelected(this.communityId, this.spaceId, this.children);
@override
List<Object> get props => [communityId, spaceId, children];
}
class SearchQueryEvent extends SpaceTreeEvent {
final String searchQuery;
const SearchQueryEvent(this.searchQuery);
@override
List<Object> get props => [searchQuery];
}

View File

@ -2,23 +2,56 @@ import 'package:equatable/equatable.dart';
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/community_model.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/model/community_model.dart';
class SpaceTreeState extends Equatable { class SpaceTreeState extends Equatable {
final List<CommunityModel> spacesList; final List<CommunityModel> communityList;
final String selectedSpace; final List<CommunityModel> filteredCommunity;
final String selectedCommunity; final List<String> expandedCommunities;
final List<String> expandedSpaces;
final List<String> selectedCommunities;
final List<String> selectedSpaces;
final List<String> soldCheck;
final bool isSearching;
const SpaceTreeState( const SpaceTreeState(
{this.spacesList = const [], this.selectedSpace = '', this.selectedCommunity = ''}); {this.communityList = const [],
this.filteredCommunity = const [],
this.expandedCommunities = const [],
this.expandedSpaces = const [],
this.selectedCommunities = const [],
this.selectedSpaces = const [],
this.soldCheck = const [],
this.isSearching = false});
SpaceTreeState copyWith( SpaceTreeState copyWith(
{List<CommunityModel>? communitiesList, String? selectedSpace, String? selectedCommunity}) { {List<CommunityModel>? communitiesList,
List<CommunityModel>? filteredCommunity,
List<String>? expandedSpaces,
List<String>? expandedCommunity,
List<String>? selectedCommunities,
List<String>? selectedSpaces,
List<String>? soldCheck,
bool? isSearching}) {
return SpaceTreeState( return SpaceTreeState(
spacesList: communitiesList ?? this.spacesList, communityList: communitiesList ?? this.communityList,
selectedSpace: selectedSpace ?? this.selectedSpace, filteredCommunity: filteredCommunity ?? this.filteredCommunity,
selectedCommunity: selectedCommunity ?? this.selectedCommunity); expandedSpaces: expandedSpaces ?? this.expandedSpaces,
expandedCommunities: expandedCommunity ?? this.expandedCommunities,
selectedCommunities: selectedCommunities ?? this.selectedCommunities,
selectedSpaces: selectedSpaces ?? this.selectedSpaces,
soldCheck: soldCheck ?? this.soldCheck,
isSearching: isSearching ?? this.isSearching);
} }
@override @override
List<Object?> get props => [spacesList, selectedSpace, selectedCommunity]; List<Object?> get props => [
communityList,
filteredCommunity,
expandedSpaces,
expandedCommunities,
selectedCommunities,
selectedSpaces,
soldCheck,
isSearching
];
} }
class SpaceTreeLoadingState extends SpaceTreeState {} class SpaceTreeLoadingState extends SpaceTreeState {}
@ -26,6 +59,7 @@ class SpaceTreeLoadingState extends SpaceTreeState {}
class SpaceTreeErrorState extends SpaceTreeState { class SpaceTreeErrorState extends SpaceTreeState {
final String message; final String message;
const SpaceTreeErrorState(this.message); const SpaceTreeErrorState(this.message);
@override @override
List<Object?> get props => [message]; List<Object?> get props => [message];
} }

View File

@ -1,35 +0,0 @@
import 'package:flutter/material.dart';
import 'package:syncrow_web/pages/space_tree/view/custom_expansion.dart';
class CommunityTileSpaceTree extends StatelessWidget {
final String title;
final List<Widget>? children;
final bool isExpanded;
final bool isSelected;
final Function(String, bool) onExpansionChanged;
final Function() onItemSelected;
const CommunityTileSpaceTree({
super.key,
required this.title,
required this.isExpanded,
required this.onExpansionChanged,
required this.onItemSelected,
required this.isSelected,
this.children,
});
@override
Widget build(BuildContext context) {
return CustomExpansionTileSpaceTree(
title: title,
initiallyExpanded: isExpanded,
isSelected: isSelected,
onExpansionChanged: (bool expanded) {
onExpansionChanged(title, expanded);
},
onItemSelected: onItemSelected,
children: children ?? [],
);
}
}

View File

@ -1,54 +1,26 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:syncrow_web/utils/color_manager.dart'; import 'package:syncrow_web/utils/color_manager.dart';
class CustomExpansionTileSpaceTree extends StatefulWidget { class CustomExpansionTileSpaceTree extends StatelessWidget {
final String? spaceId;
final String title; final String title;
final List<Widget>? children; final List<Widget>? children;
final bool initiallyExpanded; final bool isSelected;
final bool isSelected; // Add this to track selection final bool isSoldCheck;
final bool? isExpanded; // External control over expansion final bool isExpanded;
final ValueChanged<bool>? onExpansionChanged; // Notify when expansion changes final Function? onExpansionChanged;
final VoidCallback? onItemSelected; // Callback for selecting the item final Function? onItemSelected;
CustomExpansionTileSpaceTree({ const CustomExpansionTileSpaceTree(
required this.title, {super.key,
this.children, this.spaceId,
this.initiallyExpanded = false, required this.title,
this.isExpanded, // Allow external control over expansion this.children,
this.onExpansionChanged, // Notify when expansion changes this.isExpanded = false,
this.onItemSelected, // Trigger item selection when name is tapped this.onExpansionChanged,
required this.isSelected, // Add this to initialize selection state this.onItemSelected,
}); required this.isSelected,
this.isSoldCheck = false});
@override
CustomExpansionTileState createState() => CustomExpansionTileState();
}
class CustomExpansionTileState extends State<CustomExpansionTileSpaceTree> {
bool _isExpanded = false; // Local expansion state
@override
void initState() {
super.initState();
_isExpanded = widget.initiallyExpanded;
}
@override
void didUpdateWidget(CustomExpansionTileSpaceTree oldWidget) {
super.didUpdateWidget(oldWidget);
// Sync local state with external control of expansion state
if (widget.isExpanded != null && widget.isExpanded != _isExpanded) {
setState(() {
_isExpanded = widget.isExpanded!;
});
}
}
// Utility function to capitalize the first letter of the title
String _capitalizeFirstLetter(String text) {
if (text.isEmpty) return text;
return text[0].toUpperCase() + text.substring(1);
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
@ -56,14 +28,14 @@ class CustomExpansionTileState extends State<CustomExpansionTileSpaceTree> {
children: [ children: [
Row( Row(
children: [ children: [
// Checkbox with independent state management
Checkbox( Checkbox(
value: widget.isSelected, value: isSoldCheck ? null : isSelected,
onChanged: (bool? value) { onChanged: (bool? value) {
if (widget.onItemSelected != null) { if (onItemSelected != null) {
widget.onItemSelected!(); onItemSelected!();
} }
}, },
tristate: true,
side: WidgetStateBorderSide.resolveWith((states) { side: WidgetStateBorderSide.resolveWith((states) {
return const BorderSide(color: ColorsManager.grayBorder); return const BorderSide(color: ColorsManager.grayBorder);
}), }),
@ -76,52 +48,52 @@ class CustomExpansionTileState extends State<CustomExpansionTileSpaceTree> {
}), }),
checkColor: ColorsManager.whiteColors, checkColor: ColorsManager.whiteColors,
), ),
// Expand/collapse icon, now wrapped in a GestureDetector for specific onTap if (children != null && children!.isNotEmpty)
if (widget.children != null && widget.children!.isNotEmpty) InkWell(
GestureDetector(
onTap: () { onTap: () {
setState(() { if (onExpansionChanged != null) {
_isExpanded = !_isExpanded; onExpansionChanged!();
widget.onExpansionChanged?.call(_isExpanded); }
});
}, },
child: Icon( child: Icon(
_isExpanded ? Icons.keyboard_arrow_down : Icons.keyboard_arrow_right, isExpanded ? Icons.keyboard_arrow_down : Icons.keyboard_arrow_right,
color: ColorsManager.lightGrayColor, color: ColorsManager.lightGrayColor,
size: 16.0, // Adjusted size for better alignment size: 16.0,
), ),
), ),
// The title text, wrapped in GestureDetector to handle selection
Expanded( Expanded(
child: GestureDetector( child: GestureDetector(
onTap: () { onTap: () {
if (widget.onItemSelected != null) { if (onItemSelected != null) {
widget.onItemSelected!(); onItemSelected!();
} }
}, },
child: Text( child: Text(
_capitalizeFirstLetter(widget.title), _capitalizeFirstLetter(title),
style: TextStyle( style: Theme.of(context).textTheme.bodySmall!.copyWith(
color: widget.isSelected color: isSelected
? ColorsManager.blackColor // Change color to black when selected ? ColorsManager.blackColor // Change color to black when selected
: ColorsManager.lightGrayColor, // Gray when not selected : ColorsManager.lightGrayColor, // Gray when not selected
fontWeight: FontWeight.w400, fontWeight: FontWeight.w400,
), ),
), ),
), ),
), ),
], ],
), ),
// The expanded section (children) that shows when the tile is expanded if (isExpanded && children != null && children!.isNotEmpty)
if (_isExpanded && widget.children != null && widget.children!.isNotEmpty)
Padding( Padding(
padding: const EdgeInsets.only(left: 48.0), // Indented children padding: const EdgeInsets.only(left: 48.0),
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, children: children ?? [],
children: widget.children!,
), ),
), ),
], ],
); );
} }
String _capitalizeFirstLetter(String text) {
if (text.isEmpty) return text;
return text[0].toUpperCase() + text.substring(1);
}
} }

View File

@ -1,26 +0,0 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:syncrow_web/pages/space_tree/bloc/space_tree_bloc.dart';
import 'package:syncrow_web/pages/space_tree/bloc/space_tree_state.dart';
import 'package:syncrow_web/pages/space_tree/view/space_tree_view.dart';
class SideSpacesView extends StatelessWidget {
final Function onSelectAction;
const SideSpacesView({required this.onSelectAction, super.key});
@override
Widget build(BuildContext context) {
return BlocConsumer<SpaceTreeBloc, SpaceTreeState>(
listener: (context, state) {},
builder: (context, state) {
return SpaceTreeView(
communities: state.spacesList,
selectedSpaceUuid: state.selectedSpace,
selectedCommunityId: state.selectedCommunity,
buildContext: context,
action: onSelectAction,
isLoading: state is SpaceTreeLoadingState,
);
});
}
}

View File

@ -0,0 +1,120 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:syncrow_web/common/widgets/search_bar.dart';
import 'package:syncrow_web/pages/space_tree/bloc/space_tree_bloc.dart';
import 'package:syncrow_web/pages/space_tree/bloc/space_tree_event.dart';
import 'package:syncrow_web/pages/space_tree/bloc/space_tree_state.dart';
import 'package:syncrow_web/pages/space_tree/view/custom_expansion.dart';
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/community_model.dart';
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/space_model.dart';
import 'package:syncrow_web/utils/color_manager.dart';
import 'package:syncrow_web/utils/style.dart';
class SideTreeView extends StatelessWidget {
const SideTreeView({super.key});
@override
Widget build(BuildContext context) {
return BlocBuilder<SpaceTreeBloc, SpaceTreeState>(builder: (context, state) {
List<CommunityModel> list = state.isSearching ? state.filteredCommunity : state.communityList;
return Container(
height: MediaQuery.sizeOf(context).height,
decoration: subSectionContainerDecoration,
// padding: const EdgeInsets.all(16.0),
child: state is SpaceTreeLoadingState
? const Center(child: CircularProgressIndicator())
: Column(
children: [
CustomSearchBar(
onSearchChanged: (query) {
context.read<SpaceTreeBloc>().add(SearchQueryEvent(query));
},
),
const SizedBox(height: 16),
Expanded(
child: Padding(
padding: const EdgeInsets.all(8.0),
child: list.isEmpty
? Center(
child: Text(
'No results found',
style: Theme.of(context).textTheme.bodySmall!.copyWith(
color: ColorsManager.lightGrayColor, // Gray when not selected
fontWeight: FontWeight.w400,
),
),
)
: ListView(
shrinkWrap: true,
children: list
.map(
(community) => CustomExpansionTileSpaceTree(
title: community.name,
isSelected:
state.selectedCommunities.contains(community.uuid),
isSoldCheck:
state.selectedCommunities.contains(community.uuid),
onExpansionChanged: () {
context
.read<SpaceTreeBloc>()
.add(OnCommunityExpanded(community.uuid));
},
isExpanded:
state.expandedCommunities.contains(community.uuid),
onItemSelected: () {
context.read<SpaceTreeBloc>().add(
OnCommunitySelected(community.uuid, community.spaces));
},
children: community.spaces.map((space) {
return CustomExpansionTileSpaceTree(
title: space.name,
isExpanded: state.expandedSpaces.contains(space.uuid),
onItemSelected: () {
context.read<SpaceTreeBloc>().add(OnSpaceSelected(
community.uuid, space.uuid ?? '', space.children));
},
onExpansionChanged: () {
context.read<SpaceTreeBloc>().add(
OnSpaceExpanded(community.uuid, space.uuid ?? ''));
},
isSelected: state.selectedSpaces.contains(space.uuid) ||
state.soldCheck.contains(space.uuid),
isSoldCheck: state.soldCheck.contains(space.uuid),
children: _buildNestedSpaces(
context, state, space, community.uuid),
);
}).toList(),
),
)
.toList(),
),
),
),
],
),
);
});
}
List<Widget> _buildNestedSpaces(
BuildContext context, SpaceTreeState state, SpaceModel space, String communityId) {
return space.children.map((child) {
return CustomExpansionTileSpaceTree(
isSelected:
state.selectedSpaces.contains(child.uuid) || state.soldCheck.contains(child.uuid),
isSoldCheck: state.soldCheck.contains(child.uuid),
title: child.name,
isExpanded: state.expandedSpaces.contains(child.uuid),
onItemSelected: () {
context
.read<SpaceTreeBloc>()
.add(OnSpaceSelected(communityId, child.uuid ?? '', child.children));
},
onExpansionChanged: () {
context.read<SpaceTreeBloc>().add(OnSpaceExpanded(communityId, child.uuid ?? ''));
},
children: _buildNestedSpaces(context, state, child, communityId),
);
}).toList();
}
}

View File

@ -1,52 +0,0 @@
import 'package:flutter/material.dart';
import 'package:syncrow_web/pages/space_tree/view/custom_expansion.dart';
class SpaceTileSpaceTree extends StatefulWidget {
final String title;
final bool isSelected;
final bool initiallyExpanded;
final ValueChanged<bool> onExpansionChanged;
final List<Widget>? children;
final Function() onItemSelected;
const SpaceTileSpaceTree({
super.key,
required this.title,
required this.initiallyExpanded,
required this.onExpansionChanged,
required this.onItemSelected,
required this.isSelected,
this.children,
});
@override
_SpaceTileState createState() => _SpaceTileState();
}
class _SpaceTileState extends State<SpaceTileSpaceTree> {
late bool _isExpanded;
@override
void initState() {
super.initState();
_isExpanded = widget.initiallyExpanded;
}
@override
Widget build(BuildContext context) {
return CustomExpansionTileSpaceTree(
isSelected: widget.isSelected,
title: widget.title,
initiallyExpanded: _isExpanded,
onItemSelected: widget.onItemSelected,
onExpansionChanged: (bool expanded) {
setState(() {
_isExpanded = expanded;
});
widget.onExpansionChanged(expanded);
},
children: widget.children ?? [],
);
}
}

View File

@ -1,251 +0,0 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_svg/svg.dart';
import 'package:syncrow_web/common/widgets/search_bar.dart';
import 'package:syncrow_web/pages/space_tree/bloc/space_tree_bloc.dart';
import 'package:syncrow_web/pages/space_tree/bloc/space_tree_event.dart';
import 'package:syncrow_web/pages/space_tree/view/community_tile.dart';
import 'package:syncrow_web/pages/space_tree/view/space_tile.dart';
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/community_model.dart';
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/space_model.dart';
import 'package:syncrow_web/utils/color_manager.dart';
import 'package:syncrow_web/utils/constants/assets.dart';
import 'package:syncrow_web/utils/style.dart';
class SpaceTreeView extends StatefulWidget {
final List<CommunityModel> communities;
final String? selectedCommunityId;
final String? selectedSpaceUuid;
final BuildContext buildContext;
final Function action;
final bool isLoading;
const SpaceTreeView(
{super.key,
this.selectedCommunityId,
required this.communities,
this.selectedSpaceUuid,
required this.buildContext,
required this.action,
required this.isLoading});
@override
State<SpaceTreeView> createState() => _SpaceTreeViewState();
}
class _SpaceTreeViewState extends State<SpaceTreeView> {
String _searchQuery = ''; // Track search query
String? _selectedSpaceUuid;
String? _selectedCommunityId;
String? _expandedId;
@override
void initState() {
super.initState();
// _selectedId = widget.selectedSpaceUuid; // Initialize with the passed selected space UUID
_selectedCommunityId = widget.selectedCommunityId;
_selectedSpaceUuid = widget.selectedSpaceUuid;
}
@override
void didUpdateWidget(covariant SpaceTreeView oldWidget) {
super.didUpdateWidget(oldWidget);
if (widget.selectedSpaceUuid != oldWidget.selectedSpaceUuid ||
widget.selectedCommunityId != oldWidget.selectedCommunityId) {
setState(() {
// _selectedId = widget.selectedSpaceUuid;
_selectedCommunityId = widget.selectedCommunityId;
_selectedSpaceUuid = widget.selectedSpaceUuid;
});
}
}
// Function to filter communities based on the search query
List<CommunityModel> _filterCommunities() {
_expandedId = null;
if (_searchQuery.isEmpty) {
// Reset the selected community and space UUIDs if there's no query
// _selectedSpaceUuid = null;
// _selectedCommunityId = null;
return widget.communities;
}
// Filter communities and expand only those that match the query
return widget.communities.where((community) {
final containsQueryInCommunity =
community.name.toLowerCase().contains(_searchQuery.toLowerCase());
final containsQueryInSpaces =
community.spaces.any((space) => _containsQuery(space, _searchQuery.toLowerCase()));
return containsQueryInCommunity || containsQueryInSpaces;
}).toList();
}
// Helper function to determine if any space or its children match the search query
bool _containsQuery(SpaceModel space, String query) {
final matchesSpace = space.name.toLowerCase().contains(query);
final matchesChildren =
space.children.any((child) => _containsQuery(child, query)); // Recursive check for children
// If the space or any of its children match the query, expand this space
if (matchesSpace || matchesChildren) {
_expandedId = space.uuid;
}
return matchesSpace || matchesChildren;
}
bool _isSpaceOrChildSelected(SpaceModel space) {
// Return true if the current space or any of its child spaces is selected
if (_expandedId == space.uuid) {
return true;
}
// Recursively check if any child spaces match the query
for (var child in space.children) {
if (_isSpaceOrChildSelected(child)) {
return true;
}
}
return false;
}
@override
Widget build(BuildContext context) {
final filteredCommunities = _filterCommunities();
return Container(
// width: MediaQuery.sizeOf(context).width * 0.25,
height: MediaQuery.sizeOf(context).height,
margin: const EdgeInsets.only(right: 24),
decoration: subSectionContainerDecoration,
child: widget.isLoading
? const Center(child: CircularProgressIndicator())
: Column(
mainAxisSize: MainAxisSize.min, // Ensures the Column only takes necessary height
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// Communities title with the add button
Container(
decoration: subSectionContainerDecoration,
padding: const EdgeInsets.all(16.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text('Communities',
style: Theme.of(context).textTheme.titleMedium?.copyWith(
color: Colors.black,
)),
GestureDetector(
onTap: () => _navigateToBlank(context),
child: Container(
width: 30,
height: 30,
decoration: const BoxDecoration(
color: ColorsManager.whiteColors,
shape: BoxShape.circle,
),
child: Center(
child: SvgPicture.asset(
Assets.roundedAddIcon,
width: 24,
height: 24,
),
),
),
),
],
),
),
// Search bar
CustomSearchBar(
onSearchChanged: (query) {
setState(() {
_selectedSpaceUuid = null;
_searchQuery = query;
});
},
),
const SizedBox(height: 16),
// Community list
Expanded(
child: ListView(
children: filteredCommunities.map((community) {
return _buildCommunityTile(context, community);
}).toList(),
),
),
],
),
);
}
void _navigateToBlank(BuildContext context) {
setState(() {
// _selectedId = '';
_selectedSpaceUuid = '';
});
// context.read<SpaceManagementBloc>().add(
// NewCommunityEvent(communities: widget.communities),
// );
}
Widget _buildCommunityTile(BuildContext context, CommunityModel community) {
bool hasChildren = community.spaces.isNotEmpty;
return CommunityTileSpaceTree(
title: community.name,
key: ValueKey(community.uuid),
isSelected: _selectedCommunityId == community.uuid,
isExpanded: false,
onItemSelected: () {
setState(() {
_selectedCommunityId = community.uuid;
_selectedSpaceUuid = null;
});
widget.buildContext.read<SpaceTreeBloc>().add(
OnSelectSpaceEvent(community.uuid, ''),
);
widget.action(community.uuid, '');
},
onExpansionChanged: (String title, bool expanded) {
_handleExpansionChange(community.uuid, expanded);
},
children: hasChildren
? community.spaces.map((space) => _buildSpaceTile(space, community)).toList()
: null,
);
}
Widget _buildSpaceTile(SpaceModel space, CommunityModel community) {
return SpaceTileSpaceTree(
title: space.name,
key: ValueKey(space.uuid),
isSelected: _selectedSpaceUuid == space.uuid,
initiallyExpanded: _isSpaceOrChildSelected(space),
onExpansionChanged: (bool expanded) {
_handleExpansionChange(space.uuid ?? '', expanded);
},
onItemSelected: () {
setState(() {
_selectedSpaceUuid = space.uuid;
_selectedCommunityId = community.uuid;
});
widget.buildContext.read<SpaceTreeBloc>().add(
OnSelectSpaceEvent(community.uuid, space.uuid ?? ''),
);
widget.action(community.uuid, space.uuid);
},
children: space.children.isNotEmpty
? space.children.map((childSpace) => _buildSpaceTile(childSpace, community)).toList()
: [],
);
}
void _handleExpansionChange(String uuid, bool expanded) {}
}

View File

@ -73,7 +73,7 @@ class SceneApi {
{showInDevice = false}) async { {showInDevice = false}) async {
try { try {
final response = await _httpService.get( final response = await _httpService.get(
path: ApiEndpoints.getScenes path: ApiEndpoints.getUnitScenes
.replaceAll('{spaceUuid}', spaceId) .replaceAll('{spaceUuid}', spaceId)
.replaceAll('{communityUuid}', communityId) .replaceAll('{communityUuid}', communityId)
.replaceAll('{projectId}', TempConst.projectId), .replaceAll('{projectId}', TempConst.projectId),