Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion examples/verdure/client/lib/features/ai/ai_provider.dart
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ class AiClientState {
required this.surfaceUpdateController,
});

/// The A2uiMessageProcessor.
/// The A2UI message processor.
final A2uiMessageProcessor a2uiMessageProcessor;

/// The content generator.
Expand Down
21 changes: 10 additions & 11 deletions examples/verdure/client/lib/features/ai/ai_provider.g.dart

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ class OrderConfirmationScreen extends ConsumerWidget {
error: (error, stackTrace) => Center(child: Text('Error: $error')),
),
bottomNavigationBar: Padding(
padding: const EdgeInsets.all(16.0),
padding: const EdgeInsets.all(16),
child: ElevatedButton(
onPressed: () => context.go('/'),
child: const Text('Back to Start'),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,12 +25,12 @@ class _QuestionnaireScreenState extends ConsumerState<QuestionnaireScreen> {
@override
Widget build(BuildContext context) {
ref.listen<AsyncValue<AiClientState>>(aiProvider, (previous, next) {
if (next is AsyncData && !_initialRequestSent) {
if (_initialRequestSent) return;
if (next case AsyncData(value: final aiState)) {
setState(() {
_initialRequestSent = true;
});
final AiClientState? aiState = next.value;
aiState?.conversation.sendRequest(
aiState.conversation.sendRequest(
UserMessage.text('USER_SUBMITTED_DETAILS'),
);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ class UploadPhotoScreen extends ConsumerWidget {
),
body: SingleChildScrollView(
child: Padding(
padding: const EdgeInsets.all(16.0),
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
Expand All @@ -52,7 +52,9 @@ class UploadPhotoScreen extends ConsumerWidget {
),
const SizedBox(height: 16),
Text(
'''Upload a photo of your front or back yard, and our designers will use it to create a custom vision. Get ready to see the potential.''',
'Upload a photo of your front or back yard, '
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was using triple quotes here because agents seem to have a hard time wrapping these multi-line strings, and triple quotes avoid the lint warning. I'm fine with converting them back, but it does mean more manual work if the LLMs modify the strings, since they will inevitably fail to wrap them properly.

Copy link
Member Author

@parlough parlough Jan 6, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, thanks for the context here and thanks for raising this.

I think since this is an example, we should still try to be idiomatic even if it involves a little extra work.

I do wonder what we can do to make agents better at breaking strings in general. I find while agents don't handle wrapping/splitting strings well, LLM-based tab/auto-complete handles it quite well across different editors. Perhaps similar examples in the edited file are ending up closer in the context window?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Works for me. It think it's just that they are bad at counting characters. I think they need an edit tool that takes a line range and wraps it, contextually, based on language and context in the code.

'and our designers will use it to create a custom vision. '
'Get ready to see the potential.',
textAlign: TextAlign.center,
style: Theme.of(context).textTheme.bodyMedium!.copyWith(
color: Theme.of(context).colorScheme.onSurface,
Expand Down Expand Up @@ -146,34 +148,34 @@ class InfoCard extends StatelessWidget {
child: InkWell(
onTap: onTap,
child: Padding(
padding: const EdgeInsets.all(16.0),
padding: const EdgeInsets.all(16),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
spacing: 16,
children: [
CircleAvatar(
maxRadius: 25,
child: Icon(icon, size: 25, color: const Color(0xff15a34a)),
),
if (title != null || subtitle != null) const SizedBox(width: 16),
if (title != null || subtitle != null)
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
spacing: 4,
children: [
if (title != null)
if (title case final title?)
Text(
title!,
title,
style: Theme.of(context).textTheme.titleMedium!
.copyWith(
fontWeight: FontWeight.bold,
color: Theme.of(context).colorScheme.onSurface,
),
),
if (subtitle != null) const SizedBox(height: 4),
if (subtitle != null)
if (subtitle case final subtitle?)
Text(
subtitle!,
subtitle,
style: Theme.of(context).textTheme.bodyMedium!
.copyWith(
color: Theme.of(context).colorScheme.onSurface,
Expand Down
14 changes: 7 additions & 7 deletions examples/verdure/client/lib/features/screens/welcome_screen.dart
Original file line number Diff line number Diff line change
Expand Up @@ -37,12 +37,12 @@ class WelcomeScreen extends StatelessWidget {
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Padding(
padding: const EdgeInsets.only(top: 64.0),
padding: const EdgeInsets.only(top: 64),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
spacing: 8,
children: [
const Icon(Icons.eco, color: Colors.white, size: 32),
const SizedBox(width: 8),
Text(
'Verdure',
style: Theme.of(context).textTheme.headlineSmall
Expand All @@ -55,8 +55,9 @@ class WelcomeScreen extends StatelessWidget {
),
),
const Padding(
padding: EdgeInsets.all(16.0),
padding: EdgeInsets.all(16),
child: Column(
spacing: 16,
children: [
Text(
'Envision Your Dream Landscape',
Expand All @@ -68,9 +69,9 @@ class WelcomeScreen extends StatelessWidget {
fontWeight: FontWeight.bold,
),
),
SizedBox(height: 16),
Text(
'''Bring your perfect outdoor space to life with our suite of AI design agents.''',
'Bring your perfect outdoor space to life with '
'our suite of AI design agents.',
textAlign: TextAlign.center,
style: TextStyle(
fontFamily: 'SpaceGrotesk',
Expand All @@ -89,6 +90,7 @@ class WelcomeScreen extends StatelessWidget {
Container(
padding: const EdgeInsets.fromLTRB(16, 16, 16, 24),
child: Column(
spacing: 16,
children: [
ElevatedButton(
onPressed: () => context.push('/upload_photo'),
Expand All @@ -99,12 +101,10 @@ class WelcomeScreen extends StatelessWidget {
),
child: const Text('Start New Project'),
),
const SizedBox(height: 16),
TextButton(
onPressed: () {},
child: const Text('Explore Ideas'),
),
const SizedBox(height: 16),
TextButton(
onPressed: () {},
child: const Text('I\'m a returning user'),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ class LoadingState {
} else if (_isProcessingValue && !isProcessing.value) {
// Went from true to false, reset messages after a short delay
// to allow the fade-out animation to complete.
Future.delayed(const Duration(milliseconds: 500), clearMessages);
Future<void>.delayed(const Duration(milliseconds: 500), clearMessages);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Perhaps we should enable strict-raw-types (https://dart.dev/tools/analysis#enabling-additional-type-checks)? Or is that too much?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm all for it, but since we use a workspace-wide analysis config, I can do that in a follow-up PR for the repository.

I enabled it temporarily for verdure and fixed the one other instance of it.

}
_isProcessingValue = isProcessing.value;
});
Expand Down
10 changes: 5 additions & 5 deletions examples/verdure/client/lib/features/widgets/app_navigator.dart
Original file line number Diff line number Diff line change
Expand Up @@ -23,16 +23,16 @@ class AppNavigator extends ConsumerStatefulWidget {
}

class _AppNavigatorState extends ConsumerState<AppNavigator> {
StreamSubscription? _subscription;
StreamSubscription<void>? _subscription;

@override
void initState() {
super.initState();
// It's safe to use ref.read here because we are not rebuilding the widget
// when the provider changes, but instead subscribing to a stream.
final AsyncValue<AiClientState> aiState = ref.read(aiProvider);
if (aiState is AsyncData) {
_subscription = aiState.value!.surfaceUpdateController.stream.listen(
if (aiState case AsyncData(:final value)) {
_subscription = value.surfaceUpdateController.stream.listen(
_onSurfaceUpdate,
);
}
Expand Down Expand Up @@ -62,9 +62,9 @@ class _AppNavigatorState extends ConsumerState<AppNavigator> {
@override
Widget build(BuildContext context) {
ref.listen<AsyncValue<AiClientState?>>(aiProvider, (previous, next) {
if (next is AsyncData) {
if (next case AsyncData(:final value?)) {
_subscription?.cancel();
_subscription = next.value!.surfaceUpdateController.stream.listen(
_subscription = value.surfaceUpdateController.stream.listen(
_onSurfaceUpdate,
);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -111,27 +111,27 @@ class _LoadingMessagesState extends State<_LoadingMessages> {
color: Colors.transparent,
child: Center(
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 24.0),
padding: const EdgeInsets.symmetric(horizontal: 24),
child: Container(
height: 80.0,
height: 80,
padding: const EdgeInsets.symmetric(horizontal: 24),
decoration: BoxDecoration(
color: colorScheme.primary,
borderRadius: BorderRadius.circular(8.0),
borderRadius: BorderRadius.circular(8),
),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
spacing: 16,
children: [
const SizedBox(
width: 20,
height: 20,
child: CircularProgressIndicator(
strokeWidth: 2.0,
strokeWidth: 2,
valueColor: AlwaysStoppedAnimation<Color>(Colors.white),
),
),
const SizedBox(width: 16),
Expanded(
child: AnimatedSwitcher(
duration: const Duration(milliseconds: 500),
Expand Down
14 changes: 8 additions & 6 deletions examples/verdure/client/pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,10 @@
# found in the LICENSE file.

name: verdure
description: "A new Flutter project."
publish_to: "none"
description: >-
A sample of a Flutter client interacting with a Python-based A2A
(Agent-to-Agent) server for landscape design.
publish_to: none
version: 0.1.0

environment:
Expand All @@ -16,23 +18,23 @@ dependencies:
device_info_plus: ^12.2.0
flutter:
sdk: flutter
flutter_riverpod: ^3.0.3
flutter_riverpod: ^3.1.0
flutter_svg: ^2.2.2
genui: ^0.6.0
genui_a2ui: ^0.6.0
go_router: ^17.0.0
image_picker: ^1.2.0
logging: ^1.3.0
mime: ^2.0.0
riverpod_annotation: ^3.0.3
riverpod_annotation: ^4.0.0

dev_dependencies:
build_runner: ^2.7.1
build_runner: ^2.10.3
dart_flutter_team_lints: ^3.5.2
flutter_lints: ^6.0.0
flutter_test:
sdk: flutter
riverpod_generator: ^3.0.3
riverpod_generator: ^4.0.0

flutter:
uses-material-design: true
Expand Down
Loading
Loading