space canvas initial commit

This commit is contained in:
hannathkadher
2024-09-05 12:11:35 +04:00
parent 46689bde5f
commit 0c3b820be0
13 changed files with 649 additions and 20 deletions
@@ -0,0 +1,44 @@
import 'package:flutter/material.dart';
class AddSpaceButton extends StatelessWidget {
final VoidCallback onTap;
const AddSpaceButton({super.key, required this.onTap});
@override
Widget build(BuildContext context) {
return GestureDetector(
onTap: onTap, // Handle tap event
child: Container(
width: 120, // Width of the button
height: 60, // Height of the button
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(20), // Rounded corners
boxShadow: [
BoxShadow(
color: Colors.grey.withOpacity(0.5), // Shadow color
spreadRadius: 5, // Spread radius of the shadow
blurRadius: 7, // Blur effect
offset: const Offset(0, 3), // Shadow position
),
],
),
child: Center(
child: Container(
width: 40, // Size of the inner circle
height: 40,
decoration: BoxDecoration(
color: const Color(0xFFF5F6F7), // Light gray background
shape: BoxShape.circle, // Circular shape for the icon container
),
child: const Icon(
Icons.add, // Add icon
color: Colors.blue, // Icon color
),
),
),
),
);
}
}
@@ -0,0 +1,38 @@
import 'package:flutter/material.dart';
import 'package:syncrow_web/utils/color_manager.dart';
class CancelButton extends StatelessWidget {
final String label;
final VoidCallback? onPressed;
final double? height; // Optional height parameter for customization
final double? borderRadius; // Optional border radius customization
final double? width;
const CancelButton({
super.key,
required this.label, // Button label
required this.onPressed, // Button action
this.height = 40, // Default height
this.width = 140,
this.borderRadius = 10, // Default border radius
});
@override
Widget build(BuildContext context) {
return ElevatedButton(
onPressed: onPressed,
style: ButtonStyle(
backgroundColor: WidgetStateProperty.all(ColorsManager.boxColor), // White background
foregroundColor: WidgetStateProperty.all(Colors.black), // Black text color
shape: WidgetStateProperty.all<RoundedRectangleBorder>(
RoundedRectangleBorder(
borderRadius: BorderRadius.circular(borderRadius ?? 10),
side: const BorderSide(color: ColorsManager.boxColor), // Black border
),
),
fixedSize: WidgetStateProperty.all(Size(width ?? 50, height ?? 40)), // Set button height
),
child: Text(label), // Dynamic label
);
}
}
+7 -6
View File
@@ -15,7 +15,8 @@ class DefaultButton extends StatelessWidget {
this.backgroundColor,
this.foregroundColor,
this.borderRadius,
this.height,
this.height = 40,
this.width = 140,
this.padding,
});
final void Function()? onPressed;
@@ -31,6 +32,8 @@ class DefaultButton extends StatelessWidget {
final ButtonStyle? customButtonStyle;
final Color? backgroundColor;
final Color? foregroundColor;
final double? width;
@override
Widget build(BuildContext context) {
return ElevatedButton(
@@ -39,6 +42,7 @@ class DefaultButton extends StatelessWidget {
? null
: customButtonStyle ??
ButtonStyle(
fixedSize: WidgetStateProperty.all(Size(width ?? 50, height ?? 40)), // Set button height
textStyle: MaterialStateProperty.all(
customTextStyle ??
Theme.of(context).textTheme.bodySmall!.copyWith(
@@ -59,14 +63,11 @@ class DefaultButton extends StatelessWidget {
? backgroundColor ?? ColorsManager.primaryColor
: Colors.black.withOpacity(0.2);
}),
shape: MaterialStateProperty.all(
shape: WidgetStateProperty.all<RoundedRectangleBorder>(
RoundedRectangleBorder(
borderRadius: BorderRadius.circular(borderRadius ?? 20),
borderRadius: BorderRadius.circular(borderRadius ?? 10),
),
),
fixedSize: MaterialStateProperty.all(
const Size.fromHeight(50),
),
padding: MaterialStateProperty.all(
EdgeInsets.all(padding ?? 10),
),
@@ -0,0 +1,60 @@
import 'package:equatable/equatable.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:syncrow_web/pages/spaces_management/model/space_model.dart';
// Events
abstract class SpaceEvent extends Equatable {
@override
List<Object> get props => [];
}
class LoadSpaces extends SpaceEvent {}
class SelectSpace extends SpaceEvent {
final String selectedSpace;
SelectSpace(this.selectedSpace);
@override
List<Object> get props => [selectedSpace];
}
// States
abstract class SpaceState extends Equatable {
@override
List<Object> get props => [];
}
class SpaceInitial extends SpaceState {}
class SpaceLoaded extends SpaceState {
final List<SpaceModel> spaces;
final String selectedSpace;
SpaceLoaded({required this.spaces, required this.selectedSpace});
@override
List<Object> get props => [spaces, selectedSpace];
}
// Bloc
class SpacesManagementBloc extends Bloc<SpaceEvent, SpaceState> {
SpacesManagementBloc() : super(SpaceInitial()) {
on<LoadSpaces>((event, emit) {
final List<SpaceModel> spaces = [
SpaceModel(communityName: 'Downtown Dubai', subSpaces: ['Sub Space 1', 'Sub Space 2']),
SpaceModel(communityName: 'Dubai Creek Harbour', subSpaces: ['Sub Space 1', 'Sub Space 2']),
SpaceModel(communityName: 'Dubai Hills Estate', subSpaces: ['Sub Space 1', 'Sub Space 2']),
];
emit(SpaceLoaded(spaces: spaces, selectedSpace: spaces[0].communityName));
});
on<SelectSpace>((event, emit) {
if (state is SpaceLoaded) {
final loadedState = state as SpaceLoaded;
emit(SpaceLoaded(spaces: loadedState.spaces, selectedSpace: event.selectedSpace));
}
});
}
}
@@ -0,0 +1,6 @@
class SpaceModel {
final String communityName;
final List<String> subSpaces;
SpaceModel({required this.communityName, required this.subSpaces});
}
@@ -0,0 +1,198 @@
import 'package:flutter/material.dart';
import 'package:flutter_svg/flutter_svg.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/utils/color_manager.dart';
import 'package:syncrow_web/utils/constants/assets.dart';
class CreateSpaceDialog extends StatefulWidget {
// Add the onCreateSpace parameter as a required field
final Function(String, String) onCreateSpace;
const CreateSpaceDialog({super.key, required this.onCreateSpace});
@override
CreateSpaceDialogState createState() => CreateSpaceDialogState();
}
class CreateSpaceDialogState extends State<CreateSpaceDialog> {
String selectedIcon = Assets.location; // Initially selected icon
String enteredName = ''; // Store entered space name
@override
Widget build(BuildContext context) {
return AlertDialog(
title: const Text('Create New Space'),
backgroundColor: ColorsManager.whiteColors,
content: SizedBox(
width: 600, // Set width for the dialog
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Stack(
alignment: Alignment.center,
children: [
Container(
width: 100,
height: 100,
decoration: const BoxDecoration(
color: Color(0xFFF5F6F7),
shape: BoxShape.circle,
),
),
SvgPicture.asset(
selectedIcon, // Display the selected icon here
width: 60,
height: 60,
),
Positioned(
top: 2,
left: 2,
child: InkWell(
onTap: () =>
_showIconSelectionDialog(), // Open the icon selection dialog
child: Container(
width: 20,
height: 20,
decoration: const BoxDecoration(
color: Colors.white,
shape: BoxShape.circle,
),
child: SvgPicture.asset(Assets.iconEdit,
width: 10, height: 10),
),
),
),
],
),
const SizedBox(width: 16),
Expanded(
child: TextField(
onChanged: (value) {
enteredName = value; // Capture entered name
},
decoration: InputDecoration(
hintText: 'Please enter the name',
filled: true,
fillColor: const Color(0xFFF5F6F7),
border: OutlineInputBorder(
borderSide: const BorderSide(color: Color(0xFFF5F6F7)),
borderRadius: BorderRadius.circular(10),
),
enabledBorder: OutlineInputBorder(
borderSide: BorderSide(
color: Color(0xFFF5F6F7),
width: 1), // Default border
borderRadius: BorderRadius.circular(10),
),
focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(10),
borderSide:
BorderSide(color: Color(0xFFF5F6F7), width: 1),
),
),
),
),
],
),
],
),
),
actions: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Expanded(
child: CancelButton(
label: 'Cancel',
onPressed: () => Navigator.of(context).pop(),
),
),
const SizedBox(width: 10),
Expanded(
child: DefaultButton(
onPressed: () {
if (enteredName.isNotEmpty) {
widget.onCreateSpace(enteredName, selectedIcon); // Pass name and icon back
Navigator.of(context).pop(); // Close dialog
}
},
child: const Text('OK'),
backgroundColor: const Color(0xFF023DFE),
foregroundColor: Colors.white,
),
),
],
),
],
);
}
// Icon selection dialog
void _showIconSelectionDialog() {
showDialog(
context: context,
builder: (BuildContext context) {
return AlertDialog(
title: const Text('Select Icon'),
backgroundColor: Colors.white,
content: Container(
width: 500, // Width of the icon selection dialog
height: 200, // Height of the dialog
padding: const EdgeInsets.all(18),
decoration: BoxDecoration(
color: const Color(0xFFF5F6F7),
borderRadius: BorderRadius.circular(12),
),
child: GridView.builder(
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 7, // Number of icons per row
crossAxisSpacing: 10, // Space between icons horizontally
mainAxisSpacing: 22, // Space between icons vertically
),
itemCount: _iconList.length,
itemBuilder: (BuildContext context, int index) {
return GestureDetector(
onTap: () {
setState(() {
selectedIcon =
_iconList[index]; // Update the selected icon
});
Navigator.of(context)
.pop(); // Close the icon selection dialog
},
child: SvgPicture.asset(
_iconList[index],
width: 50, // Adjust size as needed
height: 50,
),
);
},
),
),
);
},
);
}
// Icon list containing SVG asset paths
final List<String> _iconList = [
Assets.location,
Assets.villa,
Assets.gym,
Assets.sauna,
Assets.bbq,
Assets.building,
Assets.desk,
Assets.door,
Assets.parking,
Assets.pool,
Assets.stair,
Assets.steamRoom,
Assets.street,
Assets.unit,
];
}
@@ -0,0 +1,47 @@
import 'package:flutter/material.dart';
class SpaceWidget extends StatelessWidget {
final String name;
final Offset position;
final VoidCallback onTap;
const SpaceWidget({
super.key,
required this.name,
required this.position,
required this.onTap,
});
@override
Widget build(BuildContext context) {
return Positioned(
left: position.dx,
top: position.dy,
child: GestureDetector(
onTap: onTap,
child: Container(
padding: const EdgeInsets.all(8.0),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(8),
boxShadow: [
BoxShadow(
color: Colors.grey.withOpacity(0.5),
spreadRadius: 5,
blurRadius: 7,
offset: const Offset(0, 3),
),
],
),
child: Row(
children: [
const Icon(Icons.location_on, color: Colors.blue),
const SizedBox(width: 8),
Text(name, style: const TextStyle(fontSize: 16)),
],
),
),
),
);
}
}
@@ -0,0 +1,203 @@
import 'package:flutter/material.dart';
import 'package:flutter_svg/flutter_svg.dart';
import 'package:syncrow_web/pages/common/buttons/add_space_button.dart';
import 'package:syncrow_web/pages/spaces_management/view/dialogs/create_space_dialog.dart';
import 'package:syncrow_web/utils/color_manager.dart';
class SpaceManagementPage extends StatefulWidget {
@override
SpaceManagementPageState createState() => SpaceManagementPageState();
}
class SpaceManagementPageState extends State<SpaceManagementPage> {
// Store created spaces
List<SpaceData> spaces = [];
@override
Widget build(BuildContext context) {
Size screenSize = MediaQuery.of(context).size;
return Scaffold(
backgroundColor: ColorsManager.whiteColors,
appBar: AppBar(
title: const Text('Space Management'),
),
body: Stack(
children: [
Center(
child: spaces.isEmpty
? AddSpaceButton(
onTap: () {
_showCreateSpaceDialog(screenSize);
},
)
: Stack(
children: spaces
.asMap()
.entries
.map((entry) => _buildSpaceCard(entry.key, screenSize))
.toList(),
),
),
],
),
);
}
// Function to open the Create Space dialog
void _showCreateSpaceDialog(Size screenSize, {Offset? position}) {
showDialog(
context: context,
builder: (BuildContext context) {
return CreateSpaceDialog(
onCreateSpace: (String name, String icon) {
setState(() {
// Set the first space in the center or use passed position
Offset centerPosition = position ??
Offset(
screenSize.width / 2 - 75, // Center horizontally
screenSize.height / 2 - 100, // Slightly above the center vertically
);
spaces.add(SpaceData(name: name, icon: icon, position: centerPosition));
});
},
);
},
);
}
// Function to build a draggable space card
Widget _buildSpaceCard(int index, Size screenSize) {
return Positioned(
left: spaces[index].position.dx,
top: spaces[index].position.dy,
child: GestureDetector(
onPanUpdate: (details) {
// Update the position of the space card while dragging
setState(() {
spaces[index].position += details.delta;
});
},
child: MouseRegion(
onEnter: (_) {
// Show plus buttons on hover
setState(() {
spaces[index].isHovered = true;
});
},
onExit: (_) {
// Hide plus buttons when not hovered
setState(() {
spaces[index].isHovered = false;
});
},
child: Stack(
clipBehavior: Clip.none,
children: [
_buildSpaceContainer(index),
if (spaces[index].isHovered) ...[
_buildPlusButton(index, 'left', const Offset(-21, 20), screenSize),
_buildPlusButton(index, 'right', const Offset(140, 20), screenSize),
_buildPlusButton(index, 'down', const Offset(63, 55), screenSize),
],
],
),
),
),
);
}
// Function to build the space container with the styled format
Widget _buildSpaceContainer(int index) {
return Container(
width: 150,
height: 60,
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(15),
boxShadow: [
BoxShadow(
color: Colors.grey.withOpacity(0.5),
spreadRadius: 2,
blurRadius: 5,
offset: const Offset(0, 3),
),
],
),
child: Row(
children: [
Container(
width: 40,
height: 60,
decoration: const BoxDecoration(
color: Color(0xFF023DFE),
borderRadius: BorderRadius.only(
topLeft: Radius.circular(15),
bottomLeft: Radius.circular(15),
),
),
child: Center(
child: SvgPicture.asset(spaces[index].icon, width: 24, height: 24, color: Colors.white),
),
),
const SizedBox(width: 10),
Text(
spaces[index].name,
style: const TextStyle(
fontWeight: FontWeight.bold,
fontSize: 16,
),
),
],
),
);
}
// Function to build plus buttons for new space creation
Widget _buildPlusButton(int index, String direction, Offset offset, Size screenSize) {
return Positioned(
left: offset.dx,
top: offset.dy,
child: GestureDetector(
onTap: () {
Offset newPosition;
switch (direction) {
case 'left':
newPosition = spaces[index].position + const Offset(-200, 0);
break;
case 'right':
newPosition = spaces[index].position + const Offset(200, 0);
break;
case 'down':
newPosition = spaces[index].position + const Offset(0, 150);
break;
default:
newPosition = spaces[index].position;
}
_showCreateSpaceDialog(screenSize, position: newPosition);
},
child: Container(
width: 30,
height: 30,
decoration: const BoxDecoration(
color: Color(0xFF023DFE),
shape: BoxShape.circle,
),
child: const Icon(Icons.add, color: Colors.white, size: 20),
),
),
);
}
}
// Model for storing space information
class SpaceData {
final String name;
final String icon;
Offset position;
bool isHovered;
SpaceData({required this.name, required this.icon, required this.position, this.isHovered = false});
}
+5 -1
View File
@@ -3,6 +3,7 @@ import 'package:syncrow_web/pages/access_management/view/access_management.dart'
import 'package:syncrow_web/pages/auth/view/login_page.dart';
import 'package:syncrow_web/pages/device_managment/all_devices/view/device_managment_page.dart';
import 'package:syncrow_web/pages/home/view/home_page.dart';
import 'package:syncrow_web/pages/spaces_management/view/spaces_management_page.dart';
import 'package:syncrow_web/pages/visitor_password/view/visitor_password_dialog.dart';
import 'package:syncrow_web/utils/constants/routes_const.dart';
@@ -11,7 +12,7 @@ class AppRoutes {
return [
GoRoute(
path: RoutesConst.auth,
builder: (context, state) => const LoginPage(),
builder: (context, state) => SpaceManagementPage(),
),
GoRoute(
path: RoutesConst.home,
@@ -29,6 +30,9 @@ class AppRoutes {
path: RoutesConst.deviceManagementPage,
builder: (context, state) => const DeviceManagementPage(),
),
GoRoute(
path: RoutesConst.spacesManagementPage,
builder: (context, state) => SpaceManagementPage()),
];
}
}