Skip to content

Commit e9e43ec

Browse files
committed
feat: node pikcer search and export dialog
1 parent edf7795 commit e9e43ec

File tree

3 files changed

+213
-60
lines changed

3 files changed

+213
-60
lines changed
Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
import 'package:flutter/material.dart';
2+
import 'package:flutter_screenutil/flutter_screenutil.dart';
3+
import 'package:phosphor_flutter/phosphor_flutter.dart';
4+
5+
class ExportDialog extends StatefulWidget {
6+
const ExportDialog({super.key});
7+
8+
@override
9+
State<ExportDialog> createState() => _ExportDialogState();
10+
}
11+
12+
class _ExportDialogState extends State<ExportDialog> {
13+
String _selectedFormat = 'PNG';
14+
final List<String> _formats = ['PNG', 'JSON', 'SVG'];
15+
16+
@override
17+
Widget build(BuildContext context) {
18+
return Dialog(
19+
backgroundColor: Colors.transparent,
20+
elevation: 0,
21+
child: Container(
22+
width: 320.w,
23+
padding: EdgeInsets.all(24.r),
24+
decoration: BoxDecoration(
25+
color: Colors.white,
26+
borderRadius: BorderRadius.circular(16.r),
27+
boxShadow: [
28+
BoxShadow(
29+
color: Colors.black.withOpacity(0.1),
30+
blurRadius: 12,
31+
offset: const Offset(0, 4),
32+
),
33+
],
34+
),
35+
child: Column(
36+
mainAxisSize: MainAxisSize.min,
37+
crossAxisAlignment: CrossAxisAlignment.start,
38+
children: [
39+
// Header
40+
Row(
41+
mainAxisAlignment: MainAxisAlignment.spaceBetween,
42+
children: [
43+
Text(
44+
'Export',
45+
style: TextStyle(
46+
fontSize: 22.sp,
47+
fontWeight: FontWeight.w600,
48+
color: const Color(0xFF111827),
49+
),
50+
),
51+
IconButton(
52+
icon: Icon(PhosphorIconsRegular.x, size: 24.sp),
53+
onPressed: () => Navigator.of(context).pop(),
54+
padding: EdgeInsets.zero,
55+
constraints: const BoxConstraints(),
56+
),
57+
],
58+
),
59+
SizedBox(height: 24.h),
60+
61+
// Export As section
62+
Row(
63+
mainAxisAlignment: MainAxisAlignment.spaceBetween,
64+
children: [
65+
Text(
66+
'Export as',
67+
style: TextStyle(
68+
fontSize: 16.sp,
69+
color: Colors.grey[700],
70+
),
71+
),
72+
Container(
73+
padding: EdgeInsets.symmetric(horizontal: 12.w),
74+
decoration: BoxDecoration(
75+
borderRadius: BorderRadius.circular(8.r),
76+
border: Border.all(color: Colors.grey.shade300, width: 1.5),
77+
),
78+
child: DropdownButton<String>(
79+
value: _selectedFormat,
80+
underline: const SizedBox.shrink(),
81+
icon: Icon(PhosphorIconsRegular.caretDown, size: 16.sp),
82+
onChanged: (String? newValue) {
83+
if (newValue != null) {
84+
setState(() {
85+
_selectedFormat = newValue;
86+
});
87+
}
88+
},
89+
items: _formats.map<DropdownMenuItem<String>>((String value) {
90+
return DropdownMenuItem<String>(
91+
value: value,
92+
child: Text(value, style: TextStyle(fontSize: 16.sp)),
93+
);
94+
}).toList(),
95+
),
96+
),
97+
],
98+
),
99+
SizedBox(height: 32.h),
100+
101+
// Export Button
102+
SizedBox(
103+
width: double.infinity,
104+
child: ElevatedButton(
105+
onPressed: () {
106+
// TODO: Implement export logic for _selectedFormat
107+
print('Exporting as $_selectedFormat...');
108+
Navigator.of(context).pop();
109+
},
110+
style: ElevatedButton.styleFrom(
111+
backgroundColor: const Color(0xFFEA580C), // Orange color
112+
foregroundColor: Colors.white,
113+
padding: EdgeInsets.symmetric(vertical: 16.h),
114+
shape: RoundedRectangleBorder(
115+
borderRadius: BorderRadius.circular(12.r),
116+
),
117+
elevation: 2,
118+
),
119+
child: Text(
120+
'Export Flowchart',
121+
style: TextStyle(
122+
fontSize: 16.sp,
123+
fontWeight: FontWeight.w600,
124+
),
125+
),
126+
),
127+
),
128+
],
129+
),
130+
),
131+
);
132+
}
133+
}

lib/features/workspace/widgets/export_project_button.dart

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import 'package:cookethflow/core/theme/colors.dart';
2+
import 'package:cookethflow/features/workspace/widgets/export_dialog.dart';
23
import 'package:flutter/material.dart';
34
import 'package:flutter_screenutil/flutter_screenutil.dart';
45
import 'package:phosphor_flutter/phosphor_flutter.dart';
@@ -9,7 +10,9 @@ class ExportProjectButton extends StatelessWidget {
910
@override
1011
Widget build(BuildContext context) {
1112
return ElevatedButton(
12-
onPressed: () {},
13+
onPressed: () {
14+
showDialog(context: context, builder: (context) => ExportDialog(),);
15+
},
1316
style: ElevatedButton.styleFrom(
1417
elevation: 0,
1518
backgroundColor: primaryColor,

lib/features/workspace/widgets/node_picker.dart

Lines changed: 76 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -5,17 +5,64 @@ import 'dart:math' as math;
55

66
import 'package:provider/provider.dart';
77

8-
// The main NodePicker widget as per the design
9-
class NodePicker extends StatelessWidget {
8+
// The main NodePicker widget, now stateful
9+
class NodePicker extends StatefulWidget {
1010
const NodePicker({super.key});
1111

12+
@override
13+
State<NodePicker> createState() => _NodePickerState();
14+
}
15+
16+
class _NodePickerState extends State<NodePicker> {
17+
late final TextEditingController _searchController;
18+
late final List<Map<String, dynamic>> _allShapes;
19+
List<Map<String, dynamic>> _filteredShapes = [];
20+
21+
@override
22+
void initState() {
23+
super.initState();
24+
_searchController = TextEditingController();
25+
26+
// Define all available shapes
27+
_allShapes = [
28+
{'name': 'Square', 'drawMode': DrawMode.square, 'shapeType': ShapeType.square},
29+
{'name': 'Diamond', 'drawMode': DrawMode.diamond, 'shapeType': ShapeType.diamond},
30+
{'name': 'Rounded Square', 'drawMode': DrawMode.roundedSquare, 'shapeType': ShapeType.roundedSquare},
31+
{'name': 'Parallelogram', 'drawMode': DrawMode.parallelogram, 'shapeType': ShapeType.parallelogram},
32+
{'name': 'Cylinder', 'drawMode': DrawMode.cylinder, 'shapeType': ShapeType.cylinder},
33+
{'name': 'Circle', 'drawMode': DrawMode.circle, 'shapeType': ShapeType.circle},
34+
{'name': 'Triangle', 'drawMode': DrawMode.triangle, 'shapeType': ShapeType.triangle},
35+
{'name': 'Inverted Triangle', 'drawMode': DrawMode.invertedTriangle, 'shapeType': ShapeType.invertedTriangle},
36+
];
37+
38+
_filteredShapes = _allShapes;
39+
40+
_searchController.addListener(_filterShapes);
41+
}
42+
43+
void _filterShapes() {
44+
final query = _searchController.text.toLowerCase();
45+
setState(() {
46+
_filteredShapes = _allShapes.where((shape) {
47+
final shapeName = shape['name'].toString().toLowerCase();
48+
return shapeName.contains(query);
49+
}).toList();
50+
});
51+
}
52+
53+
@override
54+
void dispose() {
55+
_searchController.removeListener(_filterShapes);
56+
_searchController.dispose();
57+
super.dispose();
58+
}
59+
1260
@override
1361
Widget build(BuildContext context) {
14-
// Using a container to create the card-like appearance
1562
return Consumer<WorkspaceProvider>(
1663
builder: (context, provider, child) {
1764
return Container(
18-
width: 340, // Fixed width as it appears in the image
65+
width: 340,
1966
padding: const EdgeInsets.all(24.0),
2067
decoration: BoxDecoration(
2168
color: Colors.white,
@@ -29,11 +76,9 @@ class NodePicker extends StatelessWidget {
2976
],
3077
),
3178
child: Column(
32-
mainAxisSize:
33-
MainAxisSize.min, // To make the column wrap its content
79+
mainAxisSize: MainAxisSize.min,
3480
crossAxisAlignment: CrossAxisAlignment.start,
3581
children: [
36-
// Header section with Title and Close button
3782
Row(
3883
mainAxisAlignment: MainAxisAlignment.spaceBetween,
3984
children: [
@@ -45,15 +90,13 @@ class NodePicker extends StatelessWidget {
4590
color: Color(0xFF111827),
4691
),
4792
),
48-
// The close button inside the picker itself
4993
IconButton(
5094
icon: const Icon(
5195
Icons.close,
5296
color: Color(0xFF111827),
5397
size: 28,
5498
),
5599
onPressed: () {
56-
// Closes the dialog
57100
Navigator.of(context).pop();
58101
},
59102
),
@@ -62,6 +105,7 @@ class NodePicker extends StatelessWidget {
62105
const SizedBox(height: 20),
63106
// Search bar
64107
TextField(
108+
controller: _searchController,
65109
decoration: InputDecoration(
66110
hintText: 'Search for a shape',
67111
hintStyle: const TextStyle(color: Color(0xFF9CA3AF)),
@@ -91,52 +135,25 @@ class NodePicker extends StatelessWidget {
91135
),
92136
const SizedBox(height: 24),
93137
// Grid of shapes
94-
GridView.count(
95-
crossAxisCount: 4,
96-
shrinkWrap:
97-
true, // Important to make GridView work inside a Column
98-
physics:
99-
const NeverScrollableScrollPhysics(), // Disable scrolling in the grid
100-
mainAxisSpacing: 16,
101-
crossAxisSpacing: 16,
102-
children: [
103-
GestureDetector(
104-
onTap: () => provider.changeDrawMode(DrawMode.square),
105-
child: ShapeWidget(shapeType: ShapeType.square),
106-
),
107-
GestureDetector(
108-
onTap: () => provider.changeDrawMode(DrawMode.diamond),
109-
child: ShapeWidget(shapeType: ShapeType.diamond),
110-
),
111-
GestureDetector(
112-
onTap:
113-
() => provider.changeDrawMode(DrawMode.roundedSquare),
114-
child: ShapeWidget(shapeType: ShapeType.roundedSquare),
115-
),
116-
GestureDetector(
117-
onTap:
118-
() => provider.changeDrawMode(DrawMode.parallelogram),
119-
child: ShapeWidget(shapeType: ShapeType.parallelogram),
120-
),
121-
GestureDetector(
122-
onTap: () => provider.changeDrawMode(DrawMode.cylinder),
123-
child: ShapeWidget(shapeType: ShapeType.cylinder),
124-
),
125-
GestureDetector(
126-
onTap: () => provider.changeDrawMode(DrawMode.circle),
127-
child: ShapeWidget(shapeType: ShapeType.circle),
128-
),
129-
GestureDetector(
130-
onTap: () => provider.changeDrawMode(DrawMode.triangle),
131-
child: ShapeWidget(shapeType: ShapeType.triangle),
132-
),
133-
GestureDetector(
134-
onTap:
135-
() =>
136-
provider.changeDrawMode(DrawMode.invertedTriangle),
137-
child: ShapeWidget(shapeType: ShapeType.invertedTriangle),
138-
),
139-
],
138+
GridView.builder(
139+
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
140+
crossAxisCount: 4,
141+
mainAxisSpacing: 16,
142+
crossAxisSpacing: 16,
143+
),
144+
itemCount: _filteredShapes.length,
145+
shrinkWrap: true,
146+
physics: const NeverScrollableScrollPhysics(),
147+
itemBuilder: (context, index) {
148+
final shape = _filteredShapes[index];
149+
return GestureDetector(
150+
onTap: () {
151+
provider.changeDrawMode(shape['drawMode']);
152+
Navigator.of(context).pop(); // Close picker on selection
153+
},
154+
child: ShapeWidget(shapeType: shape['shapeType']),
155+
);
156+
},
140157
),
141158
],
142159
),
@@ -158,7 +175,7 @@ class ShapeWidget extends StatelessWidget {
158175
width: 60,
159176
height: 60,
160177
decoration: BoxDecoration(
161-
color: Colors.transparent, // transparent background
178+
color: Colors.transparent,
162179
borderRadius: BorderRadius.circular(8.0),
163180
),
164181
child: CustomPaint(painter: ShapePainter(shapeType: shapeType)),
@@ -178,7 +195,7 @@ class ShapePainter extends CustomPainter {
178195
Paint()
179196
..color =
180197
Colors
181-
.black // A nice purple-blue color
198+
.black
182199
..style = PaintingStyle.stroke
183200
..strokeWidth = 1.0;
184201

@@ -239,7 +256,7 @@ class ShapePainter extends CustomPainter {
239256
);
240257
canvas.drawLine(rect.topLeft, rect.bottomLeft, paint);
241258
canvas.drawLine(rect.topRight, rect.bottomRight, paint);
242-
return; // Return early as we are drawing multiple parts
259+
return;
243260
case ShapeType.circle:
244261
path.addOval(Rect.fromLTWH(w * 0.1, h * 0.1, w * 0.8, h * 0.8));
245262
break;
@@ -263,4 +280,4 @@ class ShapePainter extends CustomPainter {
263280
bool shouldRepaint(covariant CustomPainter oldDelegate) {
264281
return false;
265282
}
266-
}
283+
}

0 commit comments

Comments
 (0)