Sp 1601 fe community and space dialog redesign in the routine tab (#236)

<!--
  Thanks for contributing!

Provide a description of your changes below and a general summary in the
title

Please look at the following checklist to ensure that your PR can be
accepted quickly:
-->

## Jira Ticket
[SP-1601](https://syncrow.atlassian.net/browse/SP-1601)

## Description

<!--- Describe your changes in detail -->
redesign community and space dialog and On Access management and Devices
Management Pages Clicking the Reset button should reset the table and
buttons not refresh the Page

## Type of Change

<!--- Put an `x` in all the boxes that apply: -->

- [ ]  New feature (non-breaking change which adds functionality)
- [x] 🛠️ Bug fix (non-breaking change which fixes an issue)
- [x]  Breaking change (fix or feature that would cause existing
functionality to change)
- [ ] 🧹 Code refactor
- [ ]  Build configuration change
- [ ] 📝 Documentation
- [ ] 🗑️ Chore 


[SP-1601]:
https://syncrow.atlassian.net/browse/SP-1601?atlOrigin=eyJpIjoiNWRkNTljNzYxNjVmNDY3MDlhMDU5Y2ZhYzA5YTRkZjUiLCJwIjoiZ2l0aHViLWNvbS1KU1cifQ
This commit is contained in:
mohammadnemer1
2025-06-11 14:46:35 +03:00
committed by GitHub
13 changed files with 416 additions and 191 deletions

View File

Before

Width:  |  Height:  |  Size: 5.0 KiB

After

Width:  |  Height:  |  Size: 5.0 KiB

View File

@ -267,7 +267,8 @@ class AccessBloc extends Bloc<AccessEvent, AccessState> {
selectedIndex = 0; selectedIndex = 0;
effectiveTimeTimeStamp = null; effectiveTimeTimeStamp = null;
expirationTimeTimeStamp = null; expirationTimeTimeStamp = null;
add(FetchTableData()); filteredData = List.from(data);
emit(TableLoaded(filteredData));
} }
String timestampToDate(dynamic timestamp) { String timestampToDate(dynamic timestamp) {

View File

@ -34,7 +34,8 @@ class _DeviceSearchFiltersState extends State<DeviceSearchFilters>
runSpacing: 10, runSpacing: 10,
children: [ children: [
_buildSearchField("Space Name", _unitNameController, 200), _buildSearchField("Space Name", _unitNameController, 200),
_buildSearchField("Device Name / Product Name", _productNameController, 300), _buildSearchField(
"Device Name / Product Name", _productNameController, 300),
_buildSearchResetButtons(), _buildSearchResetButtons(),
], ],
); );
@ -74,9 +75,7 @@ class _DeviceSearchFiltersState extends State<DeviceSearchFilters>
onReset: () { onReset: () {
_unitNameController.clear(); _unitNameController.clear();
_productNameController.clear(); _productNameController.clear();
context.read<DeviceManagementBloc>() context.read<DeviceManagementBloc>().add(ResetFilters());
..add(ResetFilters())
..add(FetchDevices(context));
}, },
); );
} }

View File

@ -379,7 +379,7 @@ class GarageDoorBloc extends Bloc<GarageDoorEvent, GarageDoorState> {
} }
emit(GarageDoorLoadedState(status: deviceStatus)); emit(GarageDoorLoadedState(status: deviceStatus));
add(GarageDoorControlEvent( add(GarageDoorControlEvent(
deviceId: event.deviceId, deviceId: deviceId,
value: deviceStatus.delay.inSeconds, value: deviceStatus.delay.inSeconds,
code: 'countdown_1')); code: 'countdown_1'));
} catch (e) { } catch (e) {
@ -396,7 +396,7 @@ class GarageDoorBloc extends Bloc<GarageDoorEvent, GarageDoorState> {
_updateLocalValue(event.code, event.value); _updateLocalValue(event.code, event.value);
emit(GarageDoorLoadedState(status: deviceStatus)); emit(GarageDoorLoadedState(status: deviceStatus));
final success = await _runDeBouncer( final success = await _runDeBouncer(
deviceId: event.deviceId, deviceId: deviceId,
code: event.code, code: event.code,
value: event.value, value: event.value,
oldValue: oldValue, oldValue: oldValue,

View File

@ -36,7 +36,7 @@ class HomeBloc extends Bloc<HomeEvent, HomeState> {
if (user != null && user!.project != null) { if (user != null && user!.project != null) {
await ProjectManager.setProjectUUID(user!.project!.uuid); await ProjectManager.setProjectUUID(user!.project!.uuid);
// NavigationService.navigatorKey.currentContext!.read<SpaceTreeBloc>().add(InitialEvent());
} }
add(FetchTermEvent()); add(FetchTermEvent());
add(FetchPolicyEvent()); add(FetchPolicyEvent());

View File

@ -20,12 +20,7 @@ class _HomeWebPageState extends State<HomeWebPage> {
// Flag to track whether the dialog is already shown. // Flag to track whether the dialog is already shown.
bool _dialogShown = false; bool _dialogShown = false;
@override
void initState() {
super.initState();
final homeBloc = BlocProvider.of<HomeBloc>(context);
// homeBloc.add(const FetchUserInfo());
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {

View File

@ -1,156 +1,181 @@
import 'package:dropdown_button2/dropdown_button2.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:syncrow_web/pages/routines/create_new_routines/dropdown_menu_content.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/spaces_management/all_spaces/model/community_model.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/model/community_model.dart';
import 'package:syncrow_web/utils/color_manager.dart'; import 'package:syncrow_web/utils/color_manager.dart';
import 'space_tree_dropdown_bloc.dart';
class CommunityDropdown extends StatelessWidget { class SpaceTreeDropdown extends StatefulWidget {
final String? selectedValue; final String? selectedSpaceId;
final List<CommunityModel> communities; final Function(String?)? onChanged;
final Function(String?) onChanged;
final TextEditingController _searchController = TextEditingController();
CommunityDropdown({ const SpaceTreeDropdown({
Key? key, super.key,
required this.selectedValue, this.selectedSpaceId,
required this.onChanged, this.onChanged,
required this.communities, });
}) : super(key: key);
@override
State<SpaceTreeDropdown> createState() => _SpaceTreeDropdownState();
}
class _SpaceTreeDropdownState extends State<SpaceTreeDropdown> {
late SpaceTreeDropdownBloc _dropdownBloc;
final LayerLink _layerLink = LayerLink();
OverlayEntry? _overlayEntry;
@override
void initState() {
super.initState();
_dropdownBloc = SpaceTreeDropdownBloc(widget.selectedSpaceId);
}
@override
void dispose() {
_dropdownBloc.close();
_removeOverlay();
super.dispose();
}
void _removeOverlay() {
_overlayEntry?.remove();
_overlayEntry = null;
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Padding( return BlocProvider.value(
padding: const EdgeInsets.all(10.0), value: _dropdownBloc,
child: Column( child: BlocBuilder<SpaceTreeBloc, SpaceTreeState>(
crossAxisAlignment: CrossAxisAlignment.start, builder: (context, spaceTreeState) {
children: [ final communities = spaceTreeState.searchQuery.isNotEmpty
Text( ? spaceTreeState.filteredCommunity
"Community", : spaceTreeState.communityList;
style: Theme.of(context).textTheme.bodyMedium!.copyWith(
fontWeight: FontWeight.w400, return BlocBuilder<SpaceTreeDropdownBloc, SpaceTreeDropdownState>(
fontSize: 13, builder: (context, dropdownState) {
color: ColorsManager.blackColor, final selectedCommunity = _findCommunity(
), communities,
), dropdownState.selectedSpaceId,
const SizedBox(height: 8), );
SizedBox(
child: Container( return Column(
decoration: BoxDecoration( crossAxisAlignment: CrossAxisAlignment.start,
borderRadius: BorderRadius.circular(10), mainAxisAlignment: MainAxisAlignment.start,
), children: [
child: DropdownButton2<String>( Padding(
underline: const SizedBox(), padding: const EdgeInsets.symmetric(horizontal: 10),
value: selectedValue, child: Text(
items: communities.map((community) { "Community",
return DropdownMenuItem<String>( style: Theme.of(context).textTheme.bodyMedium!.copyWith(
value: community.uuid, fontWeight: FontWeight.w400,
child: Text( fontSize: 13,
' ${community.name}', color: ColorsManager.blackColor,
overflow: TextOverflow.ellipsis,
maxLines: 1,
),
);
}).toList(),
onChanged: onChanged,
style: const TextStyle(color: Colors.black),
hint: Padding(
padding: const EdgeInsets.only(left: 10),
child: Text(
" Please Select",
style: Theme.of(context).textTheme.bodySmall!.copyWith(
color: ColorsManager.textGray,
),
),
),
customButton: Container(
height: 45,
decoration: BoxDecoration(
border: Border.all(color: ColorsManager.textGray, width: 1.0),
borderRadius: BorderRadius.circular(10),
),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Expanded(
flex: 5,
child: Text(
selectedValue != null
? " ${communities.firstWhere((element) => element.uuid == selectedValue).name}"
: ' Please Select',
style: Theme.of(context).textTheme.bodySmall!.copyWith(
color: selectedValue != null
? Colors.black
: ColorsManager.textGray,
),
overflow: TextOverflow.ellipsis,
),
),
Expanded(
child: Container(
decoration: BoxDecoration(
color: Colors.grey[100],
borderRadius: const BorderRadius.only(
topRight: Radius.circular(10),
bottomRight: Radius.circular(10),
), ),
),
height: 45,
child: const Icon(
Icons.keyboard_arrow_down,
color: ColorsManager.textGray,
),
),
), ),
], ),
), CompositedTransformTarget(
), link: _layerLink,
dropdownStyleData: DropdownStyleData( child: GestureDetector(
maxHeight: MediaQuery.of(context).size.height * 0.4, onTap: () => _toggleDropdown(context, communities),
decoration: BoxDecoration( child: Container(
borderRadius: BorderRadius.circular(10), height: 46,
), decoration: BoxDecoration(
), border: Border.all(color: Colors.grey.shade300),
dropdownSearchData: DropdownSearchData( borderRadius: BorderRadius.circular(12),
searchController: _searchController, ),
searchInnerWidgetHeight: 50, margin: const EdgeInsets.symmetric(horizontal: 10),
searchInnerWidget: Container( child: Row(
height: 50, mainAxisAlignment: MainAxisAlignment.spaceBetween,
padding: children: [
const EdgeInsets.symmetric(horizontal: 8, vertical: 4), Padding(
child: TextFormField( padding:
style: const TextStyle(color: Colors.black), const EdgeInsets.symmetric(horizontal: 10),
controller: _searchController, child: Text(
decoration: InputDecoration( selectedCommunity?.name ?? 'Please Select',
isDense: true, style: TextStyle(
contentPadding: const EdgeInsets.symmetric( color: selectedCommunity != null
horizontal: 10, ? ColorsManager.blackColor
vertical: 12, : ColorsManager.textGray,
), overflow: TextOverflow.ellipsis,
hintText: 'Search for community...', fontWeight: FontWeight.w400,
border: OutlineInputBorder( fontSize: 13,
borderRadius: BorderRadius.circular(8), ),
),
),
Container(
decoration: BoxDecoration(
color: Colors.grey[200],
borderRadius: const BorderRadius.only(
topRight: Radius.circular(10),
bottomRight: Radius.circular(10),
),
),
height: 45,
width: 33,
child: const Icon(
Icons.keyboard_arrow_down,
color: ColorsManager.textGray,
),
),
],
),
), ),
), ),
), ),
), ],
searchMatchFn: (item, searchValue) { );
final communityName = },
(item.child as Text).data?.toLowerCase() ?? ''; );
return communityName },
.contains(searchValue.toLowerCase().trim());
},
),
onMenuStateChange: (isOpen) {
if (!isOpen) {
_searchController.clear();
}
},
menuItemStyleData: const MenuItemStyleData(
height: 40,
),
),
))
],
), ),
); );
} }
void _toggleDropdown(BuildContext context, List<CommunityModel> communities) {
if (_overlayEntry != null) {
_removeOverlay();
return;
}
_overlayEntry = OverlayEntry(
builder: (context) => Positioned(
width: 300,
child: CompositedTransformFollower(
link: _layerLink,
showWhenUnlinked: false,
offset: const Offset(0, 48),
child: Material(
elevation: 8,
borderRadius: BorderRadius.circular(12),
child: DropdownMenuContent(
selectedSpaceId: _dropdownBloc.state.selectedSpaceId,
onChanged: (id) {
if (id != null && mounted) {
_dropdownBloc.add(SpaceTreeDropdownSelectEvent(id));
widget.onChanged?.call(id);
_removeOverlay();
}
},
onClose: _removeOverlay,
),
),
),
),
);
Overlay.of(context).insert(_overlayEntry!);
}
CommunityModel? _findCommunity(
List<CommunityModel> communities, String? communityId) {
if (communityId == null) return null;
try {
return communities.firstWhere((c) => c.uuid == communityId);
} catch (e) {
return null;
}
}
} }

View File

@ -18,6 +18,7 @@ class CreateNewRoutinesDialog extends StatefulWidget {
class _CreateNewRoutinesDialogState extends State<CreateNewRoutinesDialog> { class _CreateNewRoutinesDialogState extends State<CreateNewRoutinesDialog> {
String? _selectedCommunity; String? _selectedCommunity;
String? _selectedSpace; String? _selectedSpace;
String? _selectedId;
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
@ -30,7 +31,7 @@ class _CreateNewRoutinesDialogState extends State<CreateNewRoutinesDialog> {
final spaces = _bloc.spacesOnlyWithDevices; final spaces = _bloc.spacesOnlyWithDevices;
final isLoadingCommunities = state is CommunitiesLoadingState; final isLoadingCommunities = state is CommunitiesLoadingState;
final isLoadingSpaces = state is SpaceWithDeviceLoadingState; final isLoadingSpaces = state is SpaceWithDeviceLoadingState;
String spaceHint = 'Select a community first'; String spaceHint = 'Please Select';
if (_selectedCommunity != null) { if (_selectedCommunity != null) {
if (isLoadingSpaces) { if (isLoadingSpaces) {
spaceHint = 'Loading spaces...'; spaceHint = 'Loading spaces...';
@ -40,7 +41,10 @@ class _CreateNewRoutinesDialogState extends State<CreateNewRoutinesDialog> {
spaceHint = 'Select Space'; spaceHint = 'Select Space';
} }
} }
if (_selectedId != null && _selectedCommunity != _selectedId) {
_selectedSpace = null;
_selectedCommunity = _selectedId;
}
return AlertDialog( return AlertDialog(
backgroundColor: Colors.white, backgroundColor: Colors.white,
insetPadding: EdgeInsets.zero, insetPadding: EdgeInsets.zero,
@ -51,7 +55,9 @@ class _CreateNewRoutinesDialogState extends State<CreateNewRoutinesDialog> {
'Create New Routines', 'Create New Routines',
textAlign: TextAlign.center, textAlign: TextAlign.center,
style: Theme.of(context).textTheme.bodyMedium!.copyWith( style: Theme.of(context).textTheme.bodyMedium!.copyWith(
color: ColorsManager.primaryColor, color: ColorsManager.spaceColor,
fontSize: 20,
fontWeight: FontWeight.w700,
), ),
), ),
content: Stack( content: Stack(
@ -60,40 +66,44 @@ class _CreateNewRoutinesDialogState extends State<CreateNewRoutinesDialog> {
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: [ children: [
const Divider(), const Divider(),
Padding( const SizedBox(height: 20),
padding: const EdgeInsets.only(left: 15, right: 15), Column(
child: CommunityDropdown( children: [
communities: _bloc.communities..sort( Padding(
(a, b) => a.name.toLowerCase().compareTo( padding:
b.name.toLowerCase(), const EdgeInsets.only(left: 13, right: 8),
child: Column(
children: [
SpaceTreeDropdown(
selectedSpaceId: _selectedId,
onChanged: (String? newValue) {
setState(() => _selectedId = newValue!);
if (_selectedId != null) {
_bloc.add(SpaceOnlyWithDevicesEvent(
_selectedId!));
}
},
), ),
],
)),
const SizedBox(height: 5),
const SizedBox(height: 8),
Padding(
padding: const EdgeInsets.only(left: 15, right: 15),
child: SpaceDropdown(
hintMessage: spaceHint,
spaces: spaces,
selectedValue: _selectedSpace,
onChanged: (String? newValue) {
setState(() {
_selectedSpace = newValue;
});
},
), ),
selectedValue: _selectedCommunity, ),
onChanged: (String? newValue) { ],
setState(() {
_selectedCommunity = newValue;
_selectedSpace = null;
});
if (newValue != null) {
_bloc.add(SpaceOnlyWithDevicesEvent(newValue));
}
},
),
),
const SizedBox(height: 5),
Padding(
padding: const EdgeInsets.only(left: 15, right: 15),
child: SpaceDropdown(
hintMessage: spaceHint,
spaces: spaces,
selectedValue: _selectedSpace,
onChanged: (String? newValue) {
setState(() {
_selectedSpace = newValue;
});
},
),
), ),
const SizedBox(height: 20),
const Divider(), const Divider(),
Row( Row(
mainAxisAlignment: MainAxisAlignment.spaceAround, mainAxisAlignment: MainAxisAlignment.spaceAround,

View File

@ -0,0 +1,148 @@
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_event.dart';
import 'package:syncrow_web/pages/space_tree/bloc/space_tree_state.dart';
class DropdownMenuContent extends StatefulWidget {
final String? selectedSpaceId;
final ValueChanged<String?> onChanged;
final VoidCallback onClose;
const DropdownMenuContent({
required this.selectedSpaceId,
required this.onChanged,
required this.onClose,
});
@override
State<DropdownMenuContent> createState() => _DropdownMenuContentState();
}
class _DropdownMenuContentState extends State<DropdownMenuContent> {
final ScrollController _scrollController = ScrollController();
final TextEditingController _searchController = TextEditingController();
@override
void initState() {
super.initState();
_scrollController.addListener(_onScroll);
}
@override
void dispose() {
_scrollController.dispose();
_searchController.dispose();
super.dispose();
}
void _onScroll() {
final bloc = context.read<SpaceTreeBloc>();
final state = bloc.state;
if (_scrollController.position.pixels >=
_scrollController.position.maxScrollExtent - 30) {
if (state is SpaceTreeState && !state.paginationIsLoading) {
bloc.add(PaginationEvent(state.paginationModel, state.communityList));
}
}
}
@override
Widget build(BuildContext context) {
return ConstrainedBox(
constraints: const BoxConstraints(maxHeight: 300),
child: BlocBuilder<SpaceTreeBloc, SpaceTreeState>(
builder: (context, state) {
final communities = state.searchQuery.isNotEmpty
? state.filteredCommunity
: state.communityList;
return Column(
mainAxisSize: MainAxisSize.min,
children: [
// Search bar
Padding(
padding: const EdgeInsets.all(8.0),
child: TextFormField(
controller: _searchController,
onChanged: (query) {
context.read<SpaceTreeBloc>().add(SearchQueryEvent(query));
},
style: const TextStyle(fontSize: 14, color: Colors.black),
decoration: InputDecoration(
hintText: 'Search for space...',
prefixIcon: const Icon(Icons.search, size: 20),
contentPadding:
const EdgeInsets.symmetric(vertical: 8, horizontal: 12),
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(8),
),
isDense: true,
),
),
),
// Community list
Expanded(
child: ListView.builder(
controller: _scrollController,
itemCount:
communities.length + (state.paginationIsLoading ? 1 : 0),
itemBuilder: (context, index) {
if (index >= communities.length) {
return state.paginationIsLoading
? const Padding(
padding: EdgeInsets.all(8.0),
child: Center(
child: SizedBox(
width: 20,
height: 20,
child:
CircularProgressIndicator(strokeWidth: 2),
),
),
)
: const SizedBox.shrink();
}
final community = communities[index];
final isSelected = community.uuid == widget.selectedSpaceId;
return ListTile(
title: Text(
community.name,
style: TextStyle(
color: isSelected ? Colors.blue : Colors.black,
fontWeight:
isSelected ? FontWeight.bold : FontWeight.normal,
),
),
onTap: () {
setState(() {
_searchController.clear();
_searchController.text.isEmpty
? context
.read<SpaceTreeBloc>()
.add(SearchQueryEvent(''))
: context.read<SpaceTreeBloc>().add(
SearchQueryEvent(_searchController.text));
});
// Future.delayed(const Duration(seconds: 1), () {
widget.onChanged(community.uuid);
widget.onClose();
// });
},
);
},
),
),
],
);
},
),
);
}
}

View File

@ -3,7 +3,6 @@ import 'package:flutter/material.dart';
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/space_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/color_manager.dart';
class SpaceDropdown extends StatelessWidget { class SpaceDropdown extends StatelessWidget {
final List<SpaceModel> spaces; final List<SpaceModel> spaces;
final String? selectedValue; final String? selectedValue;
@ -21,7 +20,7 @@ class SpaceDropdown extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Padding( return Padding(
padding: const EdgeInsets.all(10.0), padding: const EdgeInsets.only(left: 10),
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
@ -33,7 +32,6 @@ class SpaceDropdown extends StatelessWidget {
color: ColorsManager.blackColor, color: ColorsManager.blackColor,
), ),
), ),
const SizedBox(height: 8),
SizedBox( SizedBox(
child: Container( child: Container(
decoration: BoxDecoration( decoration: BoxDecoration(
@ -90,7 +88,7 @@ class SpaceDropdown extends StatelessWidget {
mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [ children: [
Expanded( Expanded(
flex: 5, flex: 6,
child: Padding( child: Padding(
padding: const EdgeInsets.only(left: 10), padding: const EdgeInsets.only(left: 10),
child: Text( child: Text(

View File

@ -0,0 +1,27 @@
import 'package:flutter_bloc/flutter_bloc.dart';
part 'space_tree_dropdown_event.dart';
part 'space_tree_dropdown_state.dart';
class SpaceTreeDropdownBloc
extends Bloc<SpaceTreeDropdownEvent, SpaceTreeDropdownState> {
SpaceTreeDropdownBloc(String? initialId)
: super(SpaceTreeDropdownState(selectedSpaceId: initialId)) {
on<SpaceTreeDropdownSelectEvent>(_onSelect);
on<SpaceTreeDropdownResetEvent>(_onReset);
}
void _onSelect(
SpaceTreeDropdownSelectEvent event,
Emitter<SpaceTreeDropdownState> emit,
) {
emit(SpaceTreeDropdownState(selectedSpaceId: event.spaceId));
}
void _onReset(
SpaceTreeDropdownResetEvent event,
Emitter<SpaceTreeDropdownState> emit,
) {
emit(SpaceTreeDropdownState(selectedSpaceId: event.initialId));
}
}

View File

@ -0,0 +1,15 @@
part of 'space_tree_dropdown_bloc.dart';
abstract class SpaceTreeDropdownEvent {}
class SpaceTreeDropdownSelectEvent extends SpaceTreeDropdownEvent {
final String? spaceId;
SpaceTreeDropdownSelectEvent(this.spaceId);
}
class SpaceTreeDropdownResetEvent extends SpaceTreeDropdownEvent {
final String? initialId;
SpaceTreeDropdownResetEvent(this.initialId);
}

View File

@ -0,0 +1,7 @@
part of 'space_tree_dropdown_bloc.dart';
class SpaceTreeDropdownState {
final String? selectedSpaceId;
SpaceTreeDropdownState({this.selectedSpaceId});
}