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;
});
Comment on lines 30 to 32
Copy link
Contributor

Choose a reason for hiding this comment

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

medium

Calling setState within a ref.listen callback inside the build method is discouraged as it can lead to unexpected rebuilds. Since _initialRequestSent is a flag to prevent re-execution and doesn't directly affect the UI, you can update it without setState to avoid an unnecessary widget rebuild. You can replace these lines with a simple assignment:

_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, '
'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);
}
_isProcessingValue = isProcessing.value;
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,8 @@ class _AppNavigatorState extends ConsumerState<AppNavigator> {
// 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