Merge pull request #55 from SyncrowIOT/bugfix/community-flow

Bugfix/community flow
This commit is contained in:
Abdullah
2024-11-29 18:36:03 +03:00
committed by GitHub
10 changed files with 206 additions and 94 deletions

View File

@ -25,6 +25,7 @@ class SpaceManagementBloc
on<SaveSpacesEvent>(_onSaveSpaces);
on<FetchProductsEvent>(_onFetchProducts);
on<SelectSpaceEvent>(_onSelectSpace);
on<NewCommunityEvent>(_onNewCommunity);
}
void _onUpdateCommunity(
@ -83,6 +84,25 @@ class SpaceManagementBloc
return await _api.getSpaceHierarchy(communityUuid);
}
void _onNewCommunity(
NewCommunityEvent event,
Emitter<SpaceManagementState> emit,
) {
try {
if (event.communities.isEmpty) {
emit(const SpaceManagementError('No communities provided.'));
return;
}
emit(BlankState(
communities: event.communities,
products: _cachedProducts ?? [],
));
} catch (error) {
emit(SpaceManagementError('Error loading communities: $error'));
}
}
void _onLoadCommunityAndSpaces(
LoadCommunityAndSpacesEvent event,
Emitter<SpaceManagementState> emit,
@ -151,14 +171,16 @@ class SpaceManagementBloc
await _api.createCommunity(event.name, event.description);
if (newCommunity != null) {
if (previousState is SpaceManagementLoaded) {
final updatedCommunities =
List<CommunityModel>.from(previousState.communities)
..add(newCommunity);
if (previousState is SpaceManagementLoaded ||
previousState is BlankState) {
final prevCommunities = List<CommunityModel>.from(
(previousState as dynamic).communities,
);
final updatedCommunities = prevCommunities..add(newCommunity);
emit(SpaceManagementLoaded(
communities: updatedCommunities,
products: _cachedProducts ?? [],
selectedCommunity: null,
selectedCommunity: newCommunity,
selectedSpace: null));
}
} else {
@ -200,7 +222,8 @@ class SpaceManagementBloc
emit(SpaceManagementLoading());
try {
if (previousState is SpaceManagementLoaded) {
if (previousState is SpaceManagementLoaded ||
previousState is BlankState) {
final communities = List<CommunityModel>.from(
(previousState as dynamic).communities,
);

View File

@ -108,6 +108,15 @@ class SelectCommunityEvent extends SpaceManagementEvent {
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;

View File

@ -27,6 +27,16 @@ class SpaceManagementLoaded extends SpaceManagementState {
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 {
final List<SpaceModel> spaces;

View File

@ -45,6 +45,13 @@ class SpaceManagementPageState extends State<SpaceManagementPage> {
builder: (context, state) {
if (state is SpaceManagementLoading) {
return const Center(child: CircularProgressIndicator());
} else if (state is BlankState) {
return LoadedSpaceView(
communities: state.communities,
selectedCommunity: null,
selectedSpace: null,
products: state.products,
);
} else if (state is SpaceManagementLoaded) {
return LoadedSpaceView(
communities: state.communities,

View File

@ -2,12 +2,20 @@ import 'package:flutter/material.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_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/utils/color_manager.dart';
class BlankCommunityWidget extends StatelessWidget {
const BlankCommunityWidget({Key? key}) : super(key: key);
class BlankCommunityWidget extends StatefulWidget {
final List<CommunityModel> communities;
BlankCommunityWidget({required this.communities});
@override
_BlankCommunityWidgetState createState() => _BlankCommunityWidgetState();
}
class _BlankCommunityWidgetState extends State<BlankCommunityWidget> {
@override
Widget build(BuildContext context) {
return Expanded(
@ -17,11 +25,10 @@ class BlankCommunityWidget extends StatelessWidget {
child: GridView.builder(
padding: const EdgeInsets.only(left: 40.0, top: 20.0),
gridDelegate: const SliverGridDelegateWithMaxCrossAxisExtent(
maxCrossAxisExtent: 400, // Each item's width will be 400 or less
mainAxisSpacing: 10, // Spacing between items
crossAxisSpacing: 10, // Spacing between items
childAspectRatio:
2.0, // Aspect ratio for width:height (e.g., 300:150 = 2.0)
maxCrossAxisExtent: 400,
mainAxisSpacing: 10,
crossAxisSpacing: 10,
childAspectRatio: 2.0,
),
itemCount: 1, // Only one item
itemBuilder: (context, index) {
@ -48,9 +55,8 @@ class BlankCommunityWidget extends StatelessWidget {
),
),
),
const SizedBox(height: 9), // Space between item and text
Text('Blank', // Text below the item
const SizedBox(height: 9),
Text('Blank',
style: Theme.of(context).textTheme.bodyLarge?.copyWith(
color: ColorsManager.blackColor,
)),
@ -66,6 +72,7 @@ class BlankCommunityWidget extends StatelessWidget {
showDialog(
context: parentContext,
builder: (context) => CreateCommunityDialog(
communities: widget.communities,
onCreateCommunity: (String communityName, String description) {
parentContext.read<SpaceManagementBloc>().add(
CreateCommunityEvent(

View File

@ -24,12 +24,13 @@ class CommunityStructureArea extends StatefulWidget {
SpaceModel? selectedSpace;
final List<ProductModel>? products;
final ValueChanged<SpaceModel?>? onSpaceSelected;
final List<CommunityModel> communities;
final List<SpaceModel> spaces;
CommunityStructureArea({
this.selectedCommunity,
this.selectedSpace,
required this.communities,
this.products,
required this.spaces,
this.onSpaceSelected,
@ -97,7 +98,9 @@ class _CommunityStructureAreaState extends State<CommunityStructureArea> {
@override
Widget build(BuildContext context) {
if (widget.selectedCommunity == null) {
return BlankCommunityWidget();
return BlankCommunityWidget(
communities: widget.communities,
);
}
Size screenSize = MediaQuery.of(context).size;
@ -328,6 +331,7 @@ class _CommunityStructureAreaState extends State<CommunityStructureArea> {
products: widget.products,
name: space.name,
icon: space.icon,
editSpace: space,
isEdit: true,
selectedProducts: space.selectedProducts,
onCreateSpace: (String name, String icon,

View File

@ -1,37 +1,47 @@
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';
class CreateCommunityDialog extends StatefulWidget {
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
CreateCommunityDialogState createState() => CreateCommunityDialogState();
}
class CreateCommunityDialogState extends State<CreateCommunityDialog> {
String enteredName = ''; // Store the entered community name
String enteredName = '';
bool isNameFieldExist = false;
bool isNameEmpty = false;
@override
Widget build(BuildContext context) {
final screenWidth = MediaQuery.of(context).size.width;
return Dialog(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(20),
),
backgroundColor: Colors.transparent, // Transparent for shadow effect
backgroundColor:
ColorsManager.transparentColor, // Transparent for shadow effect
child: Stack(
children: [
// Background container with shadow and rounded corners
Container(
width: 490,
width: screenWidth * 0.3,
padding: const EdgeInsets.all(20),
decoration: BoxDecoration(
color: Colors.white,
color: ColorsManager.whiteColors,
borderRadius: BorderRadius.circular(20),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.25),
color: ColorsManager.blackColor.withOpacity(0.25),
blurRadius: 20,
spreadRadius: 5,
offset: const Offset(0, 5),
@ -53,10 +63,20 @@ class CreateCommunityDialogState extends State<CreateCommunityDialog> {
// Input field for the community name
TextField(
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(
color: Colors.black,
color: ColorsManager.blackColor,
),
decoration: InputDecoration(
hintText: 'Please enter the community name',
@ -68,45 +88,68 @@ class CreateCommunityDialogState extends State<CreateCommunityDialog> {
fontWeight: FontWeight.w400,
),
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),
),
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),
),
focusedBorder: OutlineInputBorder(
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),
// Action buttons
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Expanded(
child: TextButton(
child: CancelButton(
label: 'Cancel',
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),
Expanded(
child: ElevatedButton(
child: DefaultButton(
onPressed: () {
if (enteredName.isNotEmpty) {
if (enteredName.isNotEmpty && !isNameFieldExist) {
widget.onCreateCommunity(
enteredName,
"",
@ -114,14 +157,11 @@ class CreateCommunityDialogState extends State<CreateCommunityDialog> {
Navigator.of(context).pop();
}
},
style: ElevatedButton.styleFrom(
padding: const EdgeInsets.symmetric(vertical: 16),
backgroundColor: ColorsManager.secondaryColor,
foregroundColor: Colors.white,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(10),
),
),
backgroundColor: isNameFieldExist || isNameEmpty
? ColorsManager.lightGrayColor
: ColorsManager.secondaryColor,
borderRadius: 10,
foregroundColor: ColorsManager.whiteColors,
child: const Text('OK'),
),
),

View File

@ -13,13 +13,15 @@ import 'package:syncrow_web/utils/constants/assets.dart';
import 'package:syncrow_web/utils/constants/space_icon_const.dart';
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 String? name;
final String? icon;
final bool isEdit;
final List<SelectedProduct> selectedProducts;
final SpaceModel? parentSpace;
final SpaceModel? editSpace;
const CreateSpaceDialog(
{super.key,
@ -29,6 +31,7 @@ class CreateSpaceDialog extends StatefulWidget {
this.name,
this.icon,
this.isEdit = false,
this.editSpace,
this.selectedProducts = const []});
@override
@ -49,8 +52,10 @@ class CreateSpaceDialogState extends State<CreateSpaceDialog> {
super.initState();
selectedIcon = widget.icon ?? Assets.location;
nameController = TextEditingController(text: widget.name ?? '');
selectedProducts = widget.selectedProducts.isNotEmpty ? widget.selectedProducts : [];
isOkButtonEnabled = enteredName.isNotEmpty || nameController.text.isNotEmpty;
selectedProducts =
widget.selectedProducts.isNotEmpty ? widget.selectedProducts : [];
isOkButtonEnabled =
enteredName.isNotEmpty || nameController.text.isNotEmpty;
}
@override
@ -59,7 +64,9 @@ class CreateSpaceDialogState extends State<CreateSpaceDialog> {
final screenWidth = MediaQuery.of(context).size.width;
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,
content: SizedBox(
width: screenWidth * 0.5, // Limit dialog width
@ -126,11 +133,17 @@ class CreateSpaceDialogState extends State<CreateSpaceDialog> {
isNameFieldInvalid = value.isEmpty;
if (!isNameFieldInvalid) {
if (widget.parentSpace?.children
.any((child) => child.name == value) ??
false) {
if ((widget.parentSpace?.children.any(
(child) => child.name == value) ??
false) ||
(widget.parentSpace?.name == value) ||
(widget.editSpace?.children.any(
(child) => child.name == value) ??
false)) {
isNameFieldExist = true;
isOkButtonEnabled = false;
} else {
isNameFieldExist = false;
isOkButtonEnabled = true;
}
}
@ -218,7 +231,8 @@ class CreateSpaceDialogState extends State<CreateSpaceDialog> {
padding: const EdgeInsets.only(left: 6.0),
child: SvgPicture.asset(
Assets.addIcon,
width: screenWidth * 0.015, // Adjust icon size
width: screenWidth *
0.015, // Adjust icon size
height: screenWidth * 0.015,
),
),
@ -226,8 +240,11 @@ class CreateSpaceDialogState extends State<CreateSpaceDialog> {
Flexible(
child: Text(
'Add devices / Assign a space model',
overflow: TextOverflow.ellipsis, // Prevent overflow
style: Theme.of(context).textTheme.bodyMedium,
overflow: TextOverflow
.ellipsis, // Prevent overflow
style: Theme.of(context)
.textTheme
.bodyMedium,
),
),
],
@ -262,16 +279,20 @@ class CreateSpaceDialogState extends State<CreateSpaceDialog> {
});
return;
} else {
String newName = enteredName.isNotEmpty ? enteredName : (widget.name ?? '');
String newName = enteredName.isNotEmpty
? enteredName
: (widget.name ?? '');
if (newName.isNotEmpty) {
widget.onCreateSpace(newName, selectedIcon, selectedProducts);
widget.onCreateSpace(
newName, selectedIcon, selectedProducts);
Navigator.of(context).pop();
}
}
},
borderRadius: 10,
backgroundColor:
isOkButtonEnabled ? ColorsManager.secondaryColor : ColorsManager.grayColor,
backgroundColor: isOkButtonEnabled
? ColorsManager.secondaryColor
: ColorsManager.grayColor,
foregroundColor: ColorsManager.whiteColors,
child: const Text('OK'),
),
@ -318,7 +339,8 @@ class CreateSpaceDialogState extends State<CreateSpaceDialog> {
children: [
for (var i = 0; i < selectedProducts.length; i++) ...[
HoverableButton(
iconPath: _mapIconToProduct(selectedProducts[i].productId, products),
iconPath:
_mapIconToProduct(selectedProducts[i].productId, products),
text: 'x${selectedProducts[i].count}',
onTap: () {
setState(() {
@ -328,7 +350,8 @@ class CreateSpaceDialogState extends State<CreateSpaceDialog> {
},
),
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),
GestureDetector(
@ -364,6 +387,8 @@ class CreateSpaceDialogState extends State<CreateSpaceDialog> {
);
}
String _mapIconToProduct(String uuid, List<ProductModel> products) {
// Find the product with the matching UUID
final product = products.firstWhere(

View File

@ -34,13 +34,16 @@ class _LoadedStateViewState extends State<LoadedSpaceView> {
children: [
SidebarWidget(
communities: widget.communities,
selectedSpaceUuid: widget.selectedSpace?.uuid,
selectedSpaceUuid: widget.selectedSpace?.uuid ??
widget.selectedCommunity?.uuid ??
'',
),
CommunityStructureArea(
selectedCommunity: widget.selectedCommunity,
selectedSpace: widget.selectedSpace,
spaces: widget.selectedCommunity?.spaces ?? [],
products: widget.products,
communities: widget.communities,
),
],
),

View File

@ -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/space_model.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/utils/color_manager.dart';
import 'package:syncrow_web/utils/constants/assets.dart';
import 'package:syncrow_web/utils/style.dart';
class SidebarWidget extends StatefulWidget {
final Function(CommunityModel)? onCommunitySelected;
final Function(SpaceModel?)? onSpaceSelected;
final List<CommunityModel> communities;
final Function(String?)? onSelectedSpaceChanged; // New callback
final String? selectedSpaceUuid;
const SidebarWidget({
super.key,
this.onCommunitySelected,
this.onSpaceSelected,
this.onSelectedSpaceChanged,
required this.communities,
this.selectedSpaceUuid,
});
@ -56,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
List<CommunityModel> _filterCommunities() {
if (_searchQuery.isEmpty) {
@ -145,7 +121,7 @@ class _SidebarWidgetState extends State<SidebarWidget> {
color: Colors.black,
)),
GestureDetector(
onTap: () => _showCreateCommunityDialog(context),
onTap: () => _navigateToBlank(context),
child: Container(
width: 30,
height: 30,
@ -187,6 +163,15 @@ class _SidebarWidgetState extends State<SidebarWidget> {
);
}
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;
@ -218,7 +203,6 @@ class _SidebarWidgetState extends State<SidebarWidget> {
Widget _buildSpaceTile(SpaceModel space, CommunityModel community) {
bool isExpandedSpace = _isSpaceOrChildSelected(space);
// Check if space should be expanded
return SpaceTile(
title: space.name,
key: ValueKey(space.uuid),