Skip to content

Commit 2d28ae9

Browse files
authored
Add sample browser to catalog_gallery app (#567)
1 parent c4242e1 commit 2d28ae9

31 files changed

+687
-60
lines changed

examples/catalog_gallery/.metadata

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,20 +4,20 @@
44
# This file should be version controlled and should not be manually edited.
55

66
version:
7-
revision: "e11e2c11288b6a3f9f9bb3dcfb9bb459a75e048c"
8-
channel: "main"
7+
revision: "b45fa18946ecc2d9b4009952c636ba7e2ffbb787"
8+
channel: "stable"
99

1010
project_type: app
1111

1212
# Tracks metadata for the flutter migrate command
1313
migration:
1414
platforms:
1515
- platform: root
16-
create_revision: e11e2c11288b6a3f9f9bb3dcfb9bb459a75e048c
17-
base_revision: e11e2c11288b6a3f9f9bb3dcfb9bb459a75e048c
16+
create_revision: b45fa18946ecc2d9b4009952c636ba7e2ffbb787
17+
base_revision: b45fa18946ecc2d9b4009952c636ba7e2ffbb787
1818
- platform: macos
19-
create_revision: e11e2c11288b6a3f9f9bb3dcfb9bb459a75e048c
20-
base_revision: e11e2c11288b6a3f9f9bb3dcfb9bb459a75e048c
19+
create_revision: b45fa18946ecc2d9b4009952c636ba7e2ffbb787
20+
base_revision: b45fa18946ecc2d9b4009952c636ba7e2ffbb787
2121

2222
# User provided section
2323

examples/catalog_gallery/lib/main.dart

Lines changed: 73 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -3,16 +3,45 @@
33
// found in the LICENSE file.
44

55
import 'dart:convert';
6-
6+
import 'package:args/args.dart';
7+
import 'package:file/file.dart';
8+
import 'package:file/local.dart';
79
import 'package:flutter/material.dart';
810
import 'package:genui/genui.dart';
911

10-
void main() {
11-
runApp(const CatalogGalleryApp());
12+
import 'samples_view.dart';
13+
14+
void main(List<String> args) {
15+
final parser = ArgParser()
16+
..addOption('samples', abbr: 's', help: 'Path to the samples directory');
17+
final ArgResults results = parser.parse(args);
18+
19+
const FileSystem fs = LocalFileSystem();
20+
Directory? samplesDir;
21+
if (results.wasParsed('samples')) {
22+
samplesDir = fs.directory(results['samples'] as String);
23+
} else {
24+
final Directory current = fs.currentDirectory;
25+
final Directory defaultSamples = fs
26+
.directory(current.path)
27+
.childDirectory('samples');
28+
if (defaultSamples.existsSync()) {
29+
samplesDir = defaultSamples;
30+
}
31+
}
32+
33+
runApp(CatalogGalleryApp(samplesDir: samplesDir, fs: fs));
1234
}
1335

1436
class CatalogGalleryApp extends StatefulWidget {
15-
const CatalogGalleryApp({super.key});
37+
final Directory? samplesDir;
38+
final FileSystem fs;
39+
40+
const CatalogGalleryApp({
41+
super.key,
42+
this.samplesDir,
43+
this.fs = const LocalFileSystem(),
44+
});
1645

1746
@override
1847
State<CatalogGalleryApp> createState() => _CatalogGalleryAppState();
@@ -23,27 +52,51 @@ class _CatalogGalleryAppState extends State<CatalogGalleryApp> {
2352

2453
@override
2554
Widget build(BuildContext context) {
55+
final bool showSamples =
56+
widget.samplesDir != null && widget.samplesDir!.existsSync();
57+
2658
return MaterialApp(
2759
theme: ThemeData(
2860
colorScheme: ColorScheme.fromSeed(seedColor: Colors.blue),
2961
),
30-
home: Scaffold(
31-
appBar: AppBar(
32-
backgroundColor: Theme.of(context).colorScheme.inversePrimary,
33-
title: const Text('Catalog items that has "exampleData" field set'),
34-
),
35-
body: DebugCatalogView(
36-
catalog: catalog,
37-
onSubmit: (message) {
38-
ScaffoldMessenger.of(context).showSnackBar(
39-
SnackBar(
40-
content: Text(
41-
'User action: '
42-
'${jsonEncode(message.parts.last)}',
43-
),
62+
home: DefaultTabController(
63+
length: showSamples ? 2 : 1,
64+
child: Scaffold(
65+
appBar: AppBar(
66+
backgroundColor: Theme.of(context).colorScheme.inversePrimary,
67+
title: const Text('Catalog Gallery'),
68+
bottom: showSamples
69+
? const TabBar(
70+
tabs: [
71+
Tab(text: 'Catalog'),
72+
Tab(text: 'Samples'),
73+
],
74+
)
75+
: null,
76+
),
77+
body: TabBarView(
78+
children: [
79+
DebugCatalogView(
80+
catalog: catalog,
81+
onSubmit: (message) {
82+
ScaffoldMessenger.of(context).showSnackBar(
83+
SnackBar(
84+
content: Text(
85+
'User action: '
86+
'${jsonEncode(message.parts.last)}',
87+
),
88+
),
89+
);
90+
},
4491
),
45-
);
46-
},
92+
if (showSamples)
93+
SamplesView(
94+
samplesDir: widget.samplesDir!,
95+
catalog: catalog,
96+
fs: widget.fs,
97+
),
98+
],
99+
),
47100
),
48101
),
49102
);
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
// Copyright 2025 The Flutter Authors.
2+
// Use of this source code is governed by a BSD-style license that can be
3+
// found in the LICENSE file.
4+
5+
import 'dart:convert';
6+
7+
import 'package:file/file.dart';
8+
import 'package:genui/genui.dart';
9+
import 'package:yaml/yaml.dart';
10+
11+
class Sample {
12+
final String name;
13+
final String description;
14+
final Stream<A2uiMessage> messages;
15+
16+
Sample({
17+
required this.name,
18+
required this.description,
19+
required this.messages,
20+
});
21+
}
22+
23+
class SampleParser {
24+
static Future<Sample> parseFile(File file) async {
25+
final String content = await file.readAsString();
26+
return parseString(content);
27+
}
28+
29+
static Sample parseString(String content) {
30+
final List<String> lines = const LineSplitter().convert(content);
31+
final int separatorIndex = lines.indexOf('---');
32+
33+
if (separatorIndex == -1) {
34+
throw const FormatException(
35+
'Sample file must contain a YAML header and a JSONL body separated '
36+
'by "---"',
37+
);
38+
}
39+
40+
final String yamlHeader = lines.sublist(0, separatorIndex).join('\n');
41+
final String jsonlBody = lines.sublist(separatorIndex + 1).join('\n');
42+
43+
final header = loadYaml(yamlHeader) as YamlMap;
44+
final String name = header['name'] as String? ?? 'Untitled Sample';
45+
final String description = header['description'] as String? ?? '';
46+
47+
final Stream<A2uiMessage> messages = Stream.fromIterable(
48+
const LineSplitter()
49+
.convert(jsonlBody)
50+
.where((line) => line.trim().isNotEmpty)
51+
.map((line) {
52+
final dynamic json = jsonDecode(line);
53+
if (json is Map<String, dynamic>) {
54+
return A2uiMessage.fromJson(json);
55+
}
56+
throw FormatException('Invalid JSON line: $line');
57+
}),
58+
);
59+
60+
return Sample(name: name, description: description, messages: messages);
61+
}
62+
}

0 commit comments

Comments
 (0)