Skip to content

Commit 76bcdb0

Browse files
committed
chore: better demo for mobile
1 parent dace9dd commit 76bcdb0

File tree

13 files changed

+359
-199
lines changed

13 files changed

+359
-199
lines changed

packages/demo/lib/example_browser.dart

Lines changed: 37 additions & 99 deletions
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,43 @@ class _ExampleBrowserState extends State<ExampleBrowser> {
9797
final theme = Theme.of(context);
9898

9999
return AppBar(
100-
title: _buildExampleDropdown(),
100+
title: _selectedExample != null
101+
? Row(
102+
children: [
103+
if (_selectedExample!.icon != null) ...[
104+
Icon(_selectedExample!.icon, size: 20),
105+
const SizedBox(width: 8),
106+
],
107+
Expanded(
108+
child: Column(
109+
crossAxisAlignment: CrossAxisAlignment.start,
110+
mainAxisSize: MainAxisSize.min,
111+
children: [
112+
Text(
113+
_selectedExample!.title,
114+
style: theme.textTheme.titleSmall?.copyWith(
115+
fontWeight: FontWeight.w600,
116+
),
117+
maxLines: 1,
118+
overflow: TextOverflow.ellipsis,
119+
),
120+
if (_selectedExample!.description.isNotEmpty)
121+
Text(
122+
_selectedExample!.description,
123+
style: theme.textTheme.labelSmall?.copyWith(
124+
color: theme.colorScheme.onSurface.withValues(
125+
alpha: 0.7,
126+
),
127+
),
128+
maxLines: 1,
129+
overflow: TextOverflow.ellipsis,
130+
),
131+
],
132+
),
133+
),
134+
],
135+
)
136+
: const Text('Examples'),
101137
leading: IconButton(
102138
icon: const Icon(Icons.menu),
103139
onPressed: () {
@@ -113,104 +149,6 @@ class _ExampleBrowserState extends State<ExampleBrowser> {
113149
);
114150
}
115151

116-
Widget _buildExampleDropdown() {
117-
final theme = Theme.of(context);
118-
119-
// Build a list that includes both category headers and examples
120-
final List<Map<String, dynamic>> allItems = [];
121-
122-
for (final category in widget.categories) {
123-
// Add category header
124-
allItems.add({'type': 'category', 'category': category});
125-
126-
// Add examples under this category
127-
for (final example in category.examples) {
128-
allItems.add({
129-
'type': 'example',
130-
'category': category,
131-
'example': example,
132-
});
133-
}
134-
}
135-
136-
// Find current index based on selected example
137-
final currentIndex = allItems.indexWhere(
138-
(item) =>
139-
item['type'] == 'example' &&
140-
item['example'] == _selectedExample &&
141-
(item['category'] as ExampleCategory).id == _selectedCategoryId,
142-
);
143-
144-
return DropdownButton<int>(
145-
value: currentIndex >= 0 ? currentIndex : null,
146-
isExpanded: true,
147-
underline: const SizedBox(),
148-
dropdownColor: theme.colorScheme.surfaceContainerHigh,
149-
style: theme.textTheme.titleSmall?.copyWith(
150-
color: theme.colorScheme.onSurface,
151-
fontWeight: FontWeight.w600,
152-
),
153-
items: allItems.asMap().entries.map((entry) {
154-
final item = entry.value;
155-
156-
if (item['type'] == 'category') {
157-
// Category header - disabled
158-
final category = item['category'] as ExampleCategory;
159-
return DropdownMenuItem<int>(
160-
value: entry.key,
161-
enabled: false,
162-
child: Padding(
163-
padding: const EdgeInsets.only(top: 8, bottom: 4),
164-
child: Text(
165-
category.title.toUpperCase(),
166-
style: theme.textTheme.labelSmall?.copyWith(
167-
fontWeight: FontWeight.bold,
168-
color: theme.colorScheme.primary,
169-
letterSpacing: 0.5,
170-
),
171-
),
172-
),
173-
);
174-
} else {
175-
// Regular example item
176-
final example = item['example'] as Example;
177-
return DropdownMenuItem<int>(
178-
value: entry.key,
179-
child: Padding(
180-
padding: const EdgeInsets.only(left: 16),
181-
child: Row(
182-
children: [
183-
if (example.icon != null) ...[
184-
Icon(example.icon, size: 16),
185-
const SizedBox(width: 8),
186-
],
187-
Expanded(
188-
child: Text(
189-
example.title,
190-
style: theme.textTheme.bodyMedium,
191-
overflow: TextOverflow.ellipsis,
192-
),
193-
),
194-
],
195-
),
196-
),
197-
);
198-
}
199-
}).toList(),
200-
onChanged: (index) {
201-
if (index != null) {
202-
final item = allItems[index];
203-
// Only handle example selections (categories are disabled)
204-
if (item['type'] == 'example') {
205-
final category = item['category'] as ExampleCategory;
206-
final example = item['example'] as Example;
207-
_onExampleSelected(example, category.id);
208-
}
209-
}
210-
},
211-
);
212-
}
213-
214152
Widget _buildDrawer() {
215153
return Drawer(
216154
child: ExampleNavigation(

packages/demo/lib/example_detail_view.dart

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import 'package:flutter/material.dart';
22

33
import 'example_model.dart';
4+
import 'shared/responsive.dart';
45

56
class ExampleDetailView extends StatelessWidget {
67
final Example? example;
@@ -13,10 +14,13 @@ class ExampleDetailView extends StatelessWidget {
1314
return _buildEmptyState(context);
1415
}
1516

17+
final isMobile = Responsive.isMobile(context);
18+
1619
return Column(
1720
crossAxisAlignment: CrossAxisAlignment.stretch,
1821
children: [
19-
_buildHeader(context, example!),
22+
// Skip header in mobile mode as title is shown in AppBar
23+
if (!isMobile) _buildHeader(context, example!),
2024
Expanded(child: example!.builder(context)),
2125
],
2226
);

packages/demo/lib/example_registry.dart

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,10 @@ import 'package:flutter/material.dart';
33
import 'example_model.dart';
44
// Layout examples
55
import 'examples/advanced/alignment.dart';
6+
// Annotations examples
7+
import 'examples/advanced/annotations.dart';
68
// Advanced examples
79
import 'examples/advanced/shortcuts.dart';
8-
// Annotations examples
9-
import 'examples/advanced/system.dart';
1010
import 'examples/advanced/theming.dart';
1111
import 'examples/advanced/validation.dart';
1212
import 'examples/advanced/viewer.dart';

packages/demo/lib/examples/advanced/system.dart renamed to packages/demo/lib/examples/advanced/annotations.dart

Lines changed: 8 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,14 @@ class _AnnotationExampleState extends State<AnnotationExample> {
2929
);
3030
_setupExampleGraph();
3131

32-
controller.resetViewport();
32+
// Fit view to show all content after first frame
33+
WidgetsBinding.instance.addPostFrameCallback((_) {
34+
Future.delayed(const Duration(milliseconds: 100), () {
35+
if (mounted) {
36+
controller.fitToView();
37+
}
38+
});
39+
});
3340
}
3441

3542
void _setupExampleGraph() {
@@ -250,9 +257,6 @@ class _AnnotationExampleState extends State<AnnotationExample> {
250257
);
251258
},
252259
),
253-
254-
// Instructions overlay
255-
_buildInstructions(),
256260
],
257261
),
258262
children: [
@@ -307,42 +311,6 @@ class _AnnotationExampleState extends State<AnnotationExample> {
307311
);
308312
}
309313

310-
Widget _buildInstructions() {
311-
return Positioned(
312-
top: 16,
313-
left: 16,
314-
child: Container(
315-
padding: const EdgeInsets.all(12),
316-
decoration: BoxDecoration(
317-
color: Colors.black87,
318-
borderRadius: BorderRadius.circular(8),
319-
),
320-
child: const Column(
321-
crossAxisAlignment: CrossAxisAlignment.start,
322-
mainAxisSize: MainAxisSize.min,
323-
children: [
324-
Text(
325-
'Annotation System Demo',
326-
style: TextStyle(
327-
color: Colors.white,
328-
fontSize: 16,
329-
fontWeight: FontWeight.bold,
330-
),
331-
),
332-
SizedBox(height: 8),
333-
Text(
334-
'• Drag sticky notes and markers around\n'
335-
'• Group annotations follow their nodes\n'
336-
'• Green sticky follows Process C\n'
337-
'• Use toolbar buttons to add more',
338-
style: TextStyle(color: Colors.white, fontSize: 12),
339-
),
340-
],
341-
),
342-
),
343-
);
344-
}
345-
346314
void _addRandomStickyNote() {
347315
final random = DateTime.now().millisecondsSinceEpoch;
348316
controller.createStickyNote(

packages/demo/lib/examples/advanced/theming.dart

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,10 @@ class _ThemingExampleState extends State<ThemingExample> {
126126
targetPortId: 'in1',
127127
),
128128
);
129+
130+
WidgetsBinding.instance.addPostFrameCallback((_) {
131+
_controller.fitToView();
132+
});
129133
}
130134

131135
void _updateTheme(NodeFlowTheme newTheme) {

packages/demo/lib/examples/advanced/validation.dart

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,10 @@ class _ConnectionValidationExampleState
139139
targetPortId: 'in1',
140140
),
141141
);
142+
143+
WidgetsBinding.instance.addPostFrameCallback((_) {
144+
_controller.fitToView();
145+
});
142146
}
143147

144148
void _showMessage(String message) {

packages/demo/lib/examples/advanced/viewer.dart

Lines changed: 31 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2,33 +2,55 @@ import 'package:flutter/material.dart';
22
import 'package:vyuh_node_flow/vyuh_node_flow.dart';
33

44
/// Example demonstrating the NodeFlowViewer widget
5-
class ViewerExample extends StatelessWidget {
5+
class ViewerExample extends StatefulWidget {
66
const ViewerExample({super.key});
77

88
@override
9-
Widget build(BuildContext context) {
10-
return _buildViewer();
11-
}
9+
State<ViewerExample> createState() => _ViewerExampleState();
10+
}
1211

13-
Widget _buildViewer() {
14-
final controller = NodeFlowController<String>();
12+
class _ViewerExampleState extends State<ViewerExample> {
13+
late final NodeFlowController<String> _controller;
14+
15+
@override
16+
void initState() {
17+
super.initState();
18+
_controller = NodeFlowController<String>();
1519

1620
// Load sample data
1721
final nodes = _createSampleNodes();
1822
final connections = _createSampleConnections();
1923

2024
// Add nodes to controller
2125
for (final node in nodes.values) {
22-
controller.addNode(node);
26+
_controller.addNode(node);
2327
}
2428

2529
// Add connections to controller
2630
for (final connection in connections) {
27-
controller.addConnection(connection);
31+
_controller.addConnection(connection);
2832
}
2933

34+
// Fit view to show all nodes centered after first frame
35+
WidgetsBinding.instance.addPostFrameCallback((_) {
36+
Future.delayed(const Duration(milliseconds: 100), () {
37+
if (mounted) {
38+
_controller.fitToView();
39+
}
40+
});
41+
});
42+
}
43+
44+
@override
45+
void dispose() {
46+
_controller.dispose();
47+
super.dispose();
48+
}
49+
50+
@override
51+
Widget build(BuildContext context) {
3052
return NodeFlowViewer<String>(
31-
controller: controller,
53+
controller: _controller,
3254
nodeBuilder: _buildNode,
3355
theme: NodeFlowTheme.light,
3456
enablePanning: true,

0 commit comments

Comments
 (0)