From 34672afd9375e0a2dd7be205fd2fa733da4a0af1 Mon Sep 17 00:00:00 2001 From: Cynthia J Date: Tue, 7 Apr 2026 11:37:45 -0700 Subject: [PATCH 1/4] update example to merge multimodel and add nano banana page --- .../firebase_ai/example/lib/main.dart | 43 +-- .../example/lib/pages/audio_page.dart | 185 ---------- .../example/lib/pages/document.dart | 117 ------- .../lib/pages/image_generation_page.dart | 198 +++++++++++ .../example/lib/pages/multimodal_page.dart | 319 ++++++++++++++++++ .../example/lib/pages/video_page.dart | 114 ------- 6 files changed, 535 insertions(+), 441 deletions(-) delete mode 100644 packages/firebase_ai/firebase_ai/example/lib/pages/audio_page.dart delete mode 100644 packages/firebase_ai/firebase_ai/example/lib/pages/document.dart create mode 100644 packages/firebase_ai/firebase_ai/example/lib/pages/image_generation_page.dart create mode 100644 packages/firebase_ai/firebase_ai/example/lib/pages/multimodal_page.dart delete mode 100644 packages/firebase_ai/firebase_ai/example/lib/pages/video_page.dart diff --git a/packages/firebase_ai/firebase_ai/example/lib/main.dart b/packages/firebase_ai/firebase_ai/example/lib/main.dart index 69f37490f6ba..77afe3faa236 100644 --- a/packages/firebase_ai/firebase_ai/example/lib/main.dart +++ b/packages/firebase_ai/firebase_ai/example/lib/main.dart @@ -18,19 +18,18 @@ import 'package:firebase_core/firebase_core.dart'; import 'package:flutter/material.dart'; // Import after file is generated through flutterfire_cli. -//import 'package:firebase_ai_example/firebase_options.dart'; +// import 'package:firebase_ai_example/firebase_options.dart'; -import 'pages/audio_page.dart'; import 'pages/bidi_page.dart'; import 'pages/chat_page.dart'; -import 'pages/document.dart'; import 'pages/function_calling_page.dart'; +import 'pages/image_generation_page.dart'; import 'pages/image_prompt_page.dart'; import 'pages/json_schema_page.dart'; +import 'pages/multimodal_page.dart'; import 'pages/schema_page.dart'; -import 'pages/token_count_page.dart'; -import 'pages/video_page.dart'; import 'pages/server_template_page.dart'; +import 'pages/token_count_page.dart'; void main() async { WidgetsFlutterBinding.ensureInitialized(); @@ -142,7 +141,7 @@ class _HomeScreenState extends State { useVertexBackend: useVertexBackend, ); case 1: - return AudioPage(title: 'Audio', model: currentModel); + return MultimodalPage(title: 'Multimodal', model: currentModel); case 2: return TokenCountPage(title: 'Token Count', model: currentModel); case 3: @@ -154,20 +153,19 @@ class _HomeScreenState extends State { case 4: return ImagePromptPage(title: 'Image Prompt', model: currentModel); case 5: - return SchemaPromptPage(title: 'Schema Prompt', model: currentModel); + return ImageGenerationPage( + title: 'Image Gen', useVertexBackend: useVertexBackend); case 6: - return JsonSchemaPage(title: 'JSON Schema', model: currentModel); + return SchemaPromptPage(title: 'Schema Prompt', model: currentModel); case 7: - return DocumentPage(title: 'Document Prompt', model: currentModel); + return JsonSchemaPage(title: 'JSON Schema', model: currentModel); case 8: - return VideoPage(title: 'Video Prompt', model: currentModel); - case 9: return BidiPage( title: 'Live Stream', model: currentModel, useVertexBackend: useVertexBackend, ); - case 10: + case 9: return ServerTemplatePage( title: 'Server Template', useVertexBackend: useVertexBackend, @@ -251,9 +249,9 @@ class _HomeScreenState extends State { tooltip: 'Chat', ), BottomNavigationBarItem( - icon: Icon(Icons.mic), - label: 'Audio', - tooltip: 'Audio Prompt', + icon: Icon(Icons.perm_media), + label: 'Multimodal', + tooltip: 'Multimodal Prompt', ), BottomNavigationBarItem( icon: Icon(Icons.numbers), @@ -270,6 +268,11 @@ class _HomeScreenState extends State { label: 'Image', tooltip: 'Image Prompt', ), + BottomNavigationBarItem( + icon: Icon(Icons.brush), + label: 'NanoBanana', + tooltip: 'Image Generation', + ), BottomNavigationBarItem( icon: Icon(Icons.schema), label: 'Schema', @@ -280,16 +283,6 @@ class _HomeScreenState extends State { label: 'JSON', tooltip: 'JSON Schema', ), - BottomNavigationBarItem( - icon: Icon(Icons.edit_document), - label: 'Document', - tooltip: 'Document Prompt', - ), - BottomNavigationBarItem( - icon: Icon(Icons.video_collection), - label: 'Video', - tooltip: 'Video Prompt', - ), BottomNavigationBarItem( icon: Icon( Icons.stream, diff --git a/packages/firebase_ai/firebase_ai/example/lib/pages/audio_page.dart b/packages/firebase_ai/firebase_ai/example/lib/pages/audio_page.dart deleted file mode 100644 index 4af259693bac..000000000000 --- a/packages/firebase_ai/firebase_ai/example/lib/pages/audio_page.dart +++ /dev/null @@ -1,185 +0,0 @@ -// Copyright 2025 Google LLC -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -import 'dart:io'; - -import 'package:flutter/material.dart'; -import 'package:firebase_ai/firebase_ai.dart'; -import '../widgets/message_widget.dart'; -import 'package:record/record.dart'; -import 'package:path_provider/path_provider.dart'; - -final record = AudioRecorder(); - -class AudioPage extends StatefulWidget { - const AudioPage({super.key, required this.title, required this.model}); - - final String title; - final GenerativeModel model; - - @override - State createState() => _AudioPageState(); -} - -class _AudioPageState extends State { - ChatSession? chat; - final ScrollController _scrollController = ScrollController(); - final List _messages = []; - bool _recording = false; - - @override - void initState() { - super.initState(); - chat = widget.model.startChat(); - } - - Future recordAudio() async { - if (!await record.hasPermission()) { - print('Audio recording permission denied'); - return; - } - - final dir = Directory( - '${(await getApplicationDocumentsDirectory()).path}/libs/recordings', - ); - - // ignore: avoid_slow_async_io - if (!await dir.exists()) { - await dir.create(recursive: true); - } - - String filePath = - '${dir.path}/recording_${DateTime.now().millisecondsSinceEpoch}.wav'; - - await record.start( - const RecordConfig( - encoder: AudioEncoder.wav, - ), - path: filePath, - ); - } - - Future stopRecord() async { - var path = await record.stop(); - - if (path == null) { - print('Failed to stop recording'); - return; - } - - debugPrint('Recording saved to: $path'); - - try { - File file = File(path); - final audio = await file.readAsBytes(); - debugPrint('Audio file size: ${audio.length} bytes'); - - final audioPart = InlineDataPart('audio/wav', audio); - - await _submitAudioToModel(audioPart); - - await file.delete(); - debugPrint('Recording deleted successfully.'); - } catch (e) { - debugPrint('Error processing recording: $e'); - } - } - - Future _submitAudioToModel(audioPart) async { - try { - String textPrompt = 'What is in the audio recording?'; - const prompt = TextPart('What is in the audio recording?'); - - setState(() { - _messages.add(MessageData(text: textPrompt, fromUser: true)); - }); - - final response = await widget.model.generateContent([ - Content.multi([prompt, audioPart]), - ]); - - setState(() { - _messages.add(MessageData(text: response.text, fromUser: false)); - }); - - debugPrint(response.text); - } catch (e) { - debugPrint('Error sending audio to model: $e'); - } - } - - @override - Widget build(BuildContext context) { - return Scaffold( - appBar: AppBar( - title: Text(widget.title), - ), - body: Padding( - padding: const EdgeInsets.all(8), - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Expanded( - child: ListView.builder( - controller: _scrollController, - itemBuilder: (context, idx) { - return MessageWidget( - text: _messages[idx].text, - isFromUser: _messages[idx].fromUser ?? false, - ); - }, - itemCount: _messages.length, - ), - ), - Padding( - padding: const EdgeInsets.symmetric( - vertical: 25, - horizontal: 15, - ), - child: Row( - children: [ - IconButton( - onPressed: () async { - setState(() { - _recording = !_recording; - }); - if (_recording) { - await recordAudio(); - } else { - await stopRecord(); - } - }, - icon: Icon( - Icons.mic, - color: _recording - ? Colors.blueGrey - : Theme.of(context).colorScheme.primary, - ), - ), - const SizedBox.square( - dimension: 15, - ), - const Text( - 'Tap the mic to record, tap again to submit', - ), - ], - ), - ), - ], - ), - ), - ); - } -} diff --git a/packages/firebase_ai/firebase_ai/example/lib/pages/document.dart b/packages/firebase_ai/firebase_ai/example/lib/pages/document.dart deleted file mode 100644 index db2715c402e0..000000000000 --- a/packages/firebase_ai/firebase_ai/example/lib/pages/document.dart +++ /dev/null @@ -1,117 +0,0 @@ -// Copyright 2025 Google LLC -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -import 'package:flutter/material.dart'; -import 'package:firebase_ai/firebase_ai.dart'; -import 'package:flutter/services.dart'; -import '../widgets/message_widget.dart'; - -class DocumentPage extends StatefulWidget { - const DocumentPage({super.key, required this.title, required this.model}); - - final String title; - final GenerativeModel model; - - @override - State createState() => _DocumentPageState(); -} - -class _DocumentPageState extends State { - ChatSession? chat; - late final GenerativeModel model; - final List _messages = []; - bool _loading = false; - - @override - void initState() { - super.initState(); - chat = widget.model.startChat(); - } - - Future _testDocumentReading(model) async { - try { - ByteData docBytes = - await rootBundle.load('assets/documents/gemini_summary.pdf'); - - const _prompt = - 'Write me a summary in one sentence what this document is about.'; - - const prompt = TextPart(_prompt); - - setState(() { - _messages.add(MessageData(text: _prompt, fromUser: true)); - }); - - final pdfPart = - InlineDataPart('application/pdf', docBytes.buffer.asUint8List()); - - final response = await widget.model.generateContent([ - Content.multi([prompt, pdfPart]), - ]); - - setState(() { - _messages.add(MessageData(text: response.text, fromUser: false)); - }); - } catch (e) { - print('Error sending document to model: $e'); - } - } - - @override - Widget build(BuildContext context) { - return Scaffold( - appBar: AppBar( - title: Text(widget.title), - ), - body: Padding( - padding: const EdgeInsets.all(8), - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Expanded( - child: ListView.builder( - itemBuilder: (context, idx) { - return MessageWidget( - text: _messages[idx].text, - isFromUser: _messages[idx].fromUser ?? false, - ); - }, - itemCount: _messages.length, - ), - ), - Padding( - padding: const EdgeInsets.symmetric( - vertical: 25, - horizontal: 15, - ), - child: Center( - child: SizedBox( - child: ElevatedButton( - onPressed: !_loading - ? () async { - await _testDocumentReading(widget.model); - } - : null, - child: const Text('Test Document Reading'), - ), - ), - ), - ), - ], - ), - ), - ); - } -} diff --git a/packages/firebase_ai/firebase_ai/example/lib/pages/image_generation_page.dart b/packages/firebase_ai/firebase_ai/example/lib/pages/image_generation_page.dart new file mode 100644 index 000000000000..29c6e897eb5a --- /dev/null +++ b/packages/firebase_ai/firebase_ai/example/lib/pages/image_generation_page.dart @@ -0,0 +1,198 @@ +// Copyright 2025 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import 'dart:typed_data'; +import 'package:flutter/material.dart'; +import 'package:firebase_ai/firebase_ai.dart'; +import 'package:firebase_auth/firebase_auth.dart'; +import '../widgets/message_widget.dart'; + +class ImageGenerationPage extends StatefulWidget { + const ImageGenerationPage({ + super.key, + required this.title, + required this.useVertexBackend, + }); + + final String title; + final bool useVertexBackend; + + @override + State createState() => _ImageGenerationPageState(); +} + +class _ImageGenerationPageState extends State { + late GenerativeModel _model; + final ScrollController _scrollController = ScrollController(); + final TextEditingController _textController = TextEditingController(); + final FocusNode _textFieldFocus = FocusNode(); + final List _messages = []; + bool _loading = false; + + @override + void initState() { + super.initState(); + _initializeModel(); + } + + void _initializeModel() { + final aiClient = widget.useVertexBackend + ? FirebaseAI.vertexAI(auth: FirebaseAuth.instance) + : FirebaseAI.googleAI(auth: FirebaseAuth.instance); + + _model = aiClient.generativeModel( + model: 'gemini-2.5-flash-image', + generationConfig: GenerationConfig( + responseModalities: [ResponseModalities.text, ResponseModalities.image], + ), + ); + } + + void _scrollDown() { + WidgetsBinding.instance.addPostFrameCallback( + (_) => _scrollController.animateTo( + _scrollController.position.maxScrollExtent, + duration: const Duration(milliseconds: 750), + curve: Curves.easeOutCirc, + ), + ); + } + + Future _generateImage(String prompt) async { + if (prompt.trim().isEmpty) return; + + setState(() { + _loading = true; + _messages.add(MessageData(text: prompt, fromUser: true)); + }); + _textController.clear(); + _scrollDown(); + + try { + final response = await _model.generateContent([Content.text(prompt)]); + + String? textResponse = response.text; + Uint8List? imageBytes; + + if (response.inlineDataParts.isNotEmpty) { + imageBytes = response.inlineDataParts.first.bytes; + } + + setState(() { + _messages.add( + MessageData( + text: (textResponse ?? '') + + (imageBytes != null + ? '\nGenerated Image:' + : 'No picture generated'), + imageBytes: imageBytes, + fromUser: false, + ), + ); + }); + } catch (e) { + _showError(e.toString()); + } finally { + setState(() { + _loading = false; + }); + _scrollDown(); + _textFieldFocus.requestFocus(); + } + } + + void _showError(String message) { + showDialog( + context: context, + builder: (context) { + return AlertDialog( + title: const Text('Something went wrong'), + content: SingleChildScrollView( + child: SelectableText(message), + ), + actions: [ + TextButton( + onPressed: () => Navigator.of(context).pop(), + child: const Text('OK'), + ), + ], + ); + }, + ); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: Text(widget.title), + ), + body: Padding( + padding: const EdgeInsets.all(8.0), + child: Column( + children: [ + Expanded( + child: ListView.builder( + controller: _scrollController, + itemCount: _messages.length, + itemBuilder: (context, index) { + final message = _messages[index]; + return MessageWidget( + text: message.text, + image: message.imageBytes == null + ? null + : Image.memory( + message.imageBytes!, + fit: BoxFit.contain, + ), + isFromUser: message.fromUser ?? false, + ); + }, + ), + ), + Padding( + padding: const EdgeInsets.symmetric(vertical: 25, horizontal: 15), + child: Row( + children: [ + Expanded( + child: TextField( + autofocus: true, + focusNode: _textFieldFocus, + controller: _textController, + decoration: const InputDecoration( + hintText: 'Enter image prompt...', + ), + onSubmitted: _generateImage, + ), + ), + const SizedBox(width: 15), + if (!_loading) + IconButton( + onPressed: () => _generateImage(_textController.text), + icon: Icon( + Icons.send, + color: Theme.of(context).colorScheme.primary, + ), + ) + else + const CircularProgressIndicator(), + ], + ), + ), + ], + ), + ), + ); + } +} diff --git a/packages/firebase_ai/firebase_ai/example/lib/pages/multimodal_page.dart b/packages/firebase_ai/firebase_ai/example/lib/pages/multimodal_page.dart new file mode 100644 index 000000000000..b9333090ac56 --- /dev/null +++ b/packages/firebase_ai/firebase_ai/example/lib/pages/multimodal_page.dart @@ -0,0 +1,319 @@ +// Copyright 2025 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import 'dart:io'; + +import 'package:flutter/material.dart'; +import 'package:firebase_ai/firebase_ai.dart'; +import 'package:flutter/services.dart'; +import '../widgets/message_widget.dart'; +import 'package:record/record.dart'; +import 'package:path_provider/path_provider.dart'; + +final record = AudioRecorder(); + +class MultimodalPage extends StatefulWidget { + const MultimodalPage({super.key, required this.title, required this.model}); + + final String title; + final GenerativeModel model; + + @override + State createState() => _MultimodalPageState(); +} + +class _MultimodalPageState extends State { + final ScrollController _scrollController = ScrollController(); + final List _messages = []; + bool _recording = false; + bool _loading = false; + + @override + void dispose() { + _scrollController.dispose(); + super.dispose(); + } + + Future recordAudio() async { + if (!await record.hasPermission()) { + debugPrint('Audio recording permission denied'); + return; + } + + final dir = Directory( + '${(await getApplicationDocumentsDirectory()).path}/libs/recordings', + ); + + if (!await dir.exists()) { + await dir.create(recursive: true); + } + + String filePath = + '${dir.path}/recording_${DateTime.now().millisecondsSinceEpoch}.wav'; + + await record.start( + const RecordConfig( + encoder: AudioEncoder.wav, + ), + path: filePath, + ); + } + + Future stopRecord() async { + var path = await record.stop(); + + if (path == null) { + debugPrint('Failed to stop recording'); + return; + } + + debugPrint('Recording saved to: $path'); + + try { + File file = File(path); + final audio = await file.readAsBytes(); + debugPrint('Audio file size: ${audio.length} bytes'); + + final audioPart = InlineDataPart('audio/wav', audio); + + await _submitAudioToModel(audioPart); + + await file.delete(); + debugPrint('Recording deleted successfully.'); + } catch (e) { + debugPrint('Error processing recording: $e'); + } + } + + Future _submitAudioToModel(InlineDataPart audioPart) async { + try { + String textPrompt = 'What is in the audio recording?'; + const prompt = TextPart('What is in the audio recording?'); + + setState(() { + _messages.add(MessageData(text: textPrompt, fromUser: true)); + _loading = true; + }); + + final response = await widget.model.generateContent([ + Content.multi([prompt, audioPart]), + ]); + + setState(() { + _messages.add(MessageData(text: response.text, fromUser: false)); + _loading = false; + }); + + _scrollToBottom(); + } catch (e) { + debugPrint('Error sending audio to model: $e'); + setState(() { + _loading = false; + }); + } + } + + Future _testVideo() async { + try { + setState(() { + _loading = true; + }); + + ByteData videoBytes = + await rootBundle.load('assets/videos/landscape.mp4'); + + const promptText = 'Can you tell me what is in the video?'; + + setState(() { + _messages.add(MessageData(text: promptText, fromUser: true)); + }); + + final videoPart = + InlineDataPart('video/mp4', videoBytes.buffer.asUint8List()); + + final response = await widget.model.generateContent([ + Content.multi([const TextPart(promptText), videoPart]), + ]); + + setState(() { + _messages.add(MessageData(text: response.text, fromUser: false)); + _loading = false; + }); + + _scrollToBottom(); + } catch (e) { + debugPrint('Error sending video to model: $e'); + setState(() { + _loading = false; + }); + } + } + + Future _testDocumentReading() async { + try { + setState(() { + _loading = true; + }); + + ByteData docBytes = + await rootBundle.load('assets/documents/gemini_summary.pdf'); + + const promptText = + 'Write me a summary in one sentence what this document is about.'; + + setState(() { + _messages.add(MessageData(text: promptText, fromUser: true)); + }); + + final pdfPart = + InlineDataPart('application/pdf', docBytes.buffer.asUint8List()); + + final response = await widget.model.generateContent([ + Content.multi([const TextPart(promptText), pdfPart]), + ]); + + setState(() { + _messages.add(MessageData(text: response.text, fromUser: false)); + _loading = false; + }); + + _scrollToBottom(); + } catch (e) { + debugPrint('Error sending document to model: $e'); + setState(() { + _loading = false; + }); + } + } + + void _scrollToBottom() { + WidgetsBinding.instance.addPostFrameCallback((_) { + if (_scrollController.hasClients) { + _scrollController.animateTo( + _scrollController.position.maxScrollExtent, + duration: const Duration(milliseconds: 300), + curve: Curves.easeOut, + ); + } + }); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: Text(widget.title), + ), + body: Padding( + padding: const EdgeInsets.all(8), + child: Column( + children: [ + Expanded( + child: ListView.builder( + controller: _scrollController, + itemBuilder: (context, idx) { + return MessageWidget( + text: _messages[idx].text, + isFromUser: _messages[idx].fromUser ?? false, + ); + }, + itemCount: _messages.length, + ), + ), + if (_loading) + const Padding( + padding: EdgeInsets.all(8.0), + child: CircularProgressIndicator(), + ), + Padding( + padding: const EdgeInsets.symmetric( + vertical: 15, + horizontal: 10, + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + Column( + mainAxisSize: MainAxisSize.min, + children: [ + IconButton( + onPressed: _loading + ? null + : () async { + setState(() { + _recording = !_recording; + }); + if (_recording) { + await recordAudio(); + } else { + await stopRecord(); + } + }, + icon: Icon( + Icons.mic, + color: _recording + ? Colors.red + : Theme.of(context).colorScheme.primary, + ), + iconSize: 32, + ), + Text( + _recording ? 'Stop' : 'Record', + style: const TextStyle(fontSize: 12), + ), + ], + ), + Column( + mainAxisSize: MainAxisSize.min, + children: [ + IconButton( + onPressed: _loading ? null : _testVideo, + icon: Icon( + Icons.video_collection, + color: Theme.of(context).colorScheme.primary, + ), + iconSize: 32, + ), + const Text( + 'Test Video', + style: TextStyle(fontSize: 12), + ), + ], + ), + Column( + mainAxisSize: MainAxisSize.min, + children: [ + IconButton( + onPressed: _loading ? null : _testDocumentReading, + icon: Icon( + Icons.edit_document, + color: Theme.of(context).colorScheme.primary, + ), + iconSize: 32, + ), + const Text( + 'Test Doc', + style: TextStyle(fontSize: 12), + ), + ], + ), + ], + ), + ), + ], + ), + ), + ); + } +} diff --git a/packages/firebase_ai/firebase_ai/example/lib/pages/video_page.dart b/packages/firebase_ai/firebase_ai/example/lib/pages/video_page.dart deleted file mode 100644 index 565555e19cd6..000000000000 --- a/packages/firebase_ai/firebase_ai/example/lib/pages/video_page.dart +++ /dev/null @@ -1,114 +0,0 @@ -// Copyright 2025 Google LLC -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -import 'package:flutter/material.dart'; -import 'package:firebase_ai/firebase_ai.dart'; -import 'package:flutter/services.dart'; -import '../widgets/message_widget.dart'; - -class VideoPage extends StatefulWidget { - const VideoPage({super.key, required this.title, required this.model}); - - final String title; - final GenerativeModel model; - - @override - State createState() => _VideoPageState(); -} - -class _VideoPageState extends State { - ChatSession? chat; - late final GenerativeModel model; - final List _messages = []; - bool _loading = false; - - @override - void initState() { - super.initState(); - chat = widget.model.startChat(); - } - - Future _testVideo(model) async { - try { - ByteData videoBytes = - await rootBundle.load('assets/videos/landscape.mp4'); - - const _prompt = 'Can you tell me what is in the video?'; - - setState(() { - _messages.add(MessageData(text: _prompt, fromUser: true)); - }); - - final videoPart = - InlineDataPart('video/mp4', videoBytes.buffer.asUint8List()); - - final response = await widget.model.generateContent([ - Content.multi([const TextPart(_prompt), videoPart]), - ]); - - setState(() { - _messages.add(MessageData(text: response.text, fromUser: false)); - }); - } catch (e) { - print('Error sending video to model: $e'); - } - } - - @override - Widget build(BuildContext context) { - return Scaffold( - appBar: AppBar( - title: Text(widget.title), - ), - body: Padding( - padding: const EdgeInsets.all(8), - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Expanded( - child: ListView.builder( - itemBuilder: (context, idx) { - return MessageWidget( - text: _messages[idx].text, - isFromUser: _messages[idx].fromUser ?? false, - ); - }, - itemCount: _messages.length, - ), - ), - Padding( - padding: const EdgeInsets.symmetric( - vertical: 25, - horizontal: 15, - ), - child: Center( - child: SizedBox( - child: ElevatedButton( - onPressed: !_loading - ? () async { - await _testVideo(widget.model); - } - : null, - child: const Text('Test Video Prompt'), - ), - ), - ), - ), - ], - ), - ), - ); - } -} From ad06fab59e2e4f25eaaf5ca578e2c01e26f0ac4f Mon Sep 17 00:00:00 2001 From: Cynthia J Date: Tue, 7 Apr 2026 11:39:37 -0700 Subject: [PATCH 2/4] fix the new file year header --- .../firebase_ai/example/lib/pages/image_generation_page.dart | 2 +- .../firebase_ai/example/lib/pages/multimodal_page.dart | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/firebase_ai/firebase_ai/example/lib/pages/image_generation_page.dart b/packages/firebase_ai/firebase_ai/example/lib/pages/image_generation_page.dart index 29c6e897eb5a..e6830292bde3 100644 --- a/packages/firebase_ai/firebase_ai/example/lib/pages/image_generation_page.dart +++ b/packages/firebase_ai/firebase_ai/example/lib/pages/image_generation_page.dart @@ -1,4 +1,4 @@ -// Copyright 2025 Google LLC +// Copyright 2026 Google LLC // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/packages/firebase_ai/firebase_ai/example/lib/pages/multimodal_page.dart b/packages/firebase_ai/firebase_ai/example/lib/pages/multimodal_page.dart index b9333090ac56..f9fd48aedcf2 100644 --- a/packages/firebase_ai/firebase_ai/example/lib/pages/multimodal_page.dart +++ b/packages/firebase_ai/firebase_ai/example/lib/pages/multimodal_page.dart @@ -1,4 +1,4 @@ -// Copyright 2025 Google LLC +// Copyright 2026 Google LLC // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. From f3b22520348369be57c4d59c9b3fd668ad6279bf Mon Sep 17 00:00:00 2001 From: Cynthia J Date: Wed, 8 Apr 2026 11:12:54 -0700 Subject: [PATCH 3/4] fix analyzer --- packages/firebase_ai/firebase_ai/example/lib/main.dart | 2 +- .../example/lib/pages/image_generation_page.dart | 2 +- .../firebase_ai/example/lib/pages/multimodal_page.dart | 6 ++---- 3 files changed, 4 insertions(+), 6 deletions(-) diff --git a/packages/firebase_ai/firebase_ai/example/lib/main.dart b/packages/firebase_ai/firebase_ai/example/lib/main.dart index 77afe3faa236..b46aa9b99e49 100644 --- a/packages/firebase_ai/firebase_ai/example/lib/main.dart +++ b/packages/firebase_ai/firebase_ai/example/lib/main.dart @@ -154,7 +154,7 @@ class _HomeScreenState extends State { return ImagePromptPage(title: 'Image Prompt', model: currentModel); case 5: return ImageGenerationPage( - title: 'Image Gen', useVertexBackend: useVertexBackend); + title: 'Image Gen', useVertexBackend: useVertexBackend,); case 6: return SchemaPromptPage(title: 'Schema Prompt', model: currentModel); case 7: diff --git a/packages/firebase_ai/firebase_ai/example/lib/pages/image_generation_page.dart b/packages/firebase_ai/firebase_ai/example/lib/pages/image_generation_page.dart index e6830292bde3..f4e2f527d3c4 100644 --- a/packages/firebase_ai/firebase_ai/example/lib/pages/image_generation_page.dart +++ b/packages/firebase_ai/firebase_ai/example/lib/pages/image_generation_page.dart @@ -139,7 +139,7 @@ class _ImageGenerationPageState extends State { title: Text(widget.title), ), body: Padding( - padding: const EdgeInsets.all(8.0), + padding: const EdgeInsets.all(8), child: Column( children: [ Expanded( diff --git a/packages/firebase_ai/firebase_ai/example/lib/pages/multimodal_page.dart b/packages/firebase_ai/firebase_ai/example/lib/pages/multimodal_page.dart index f9fd48aedcf2..9c559abdaada 100644 --- a/packages/firebase_ai/firebase_ai/example/lib/pages/multimodal_page.dart +++ b/packages/firebase_ai/firebase_ai/example/lib/pages/multimodal_page.dart @@ -55,9 +55,7 @@ class _MultimodalPageState extends State { '${(await getApplicationDocumentsDirectory()).path}/libs/recordings', ); - if (!await dir.exists()) { - await dir.create(recursive: true); - } + await dir.create(recursive: true); String filePath = '${dir.path}/recording_${DateTime.now().millisecondsSinceEpoch}.wav'; @@ -233,7 +231,7 @@ class _MultimodalPageState extends State { ), if (_loading) const Padding( - padding: EdgeInsets.all(8.0), + padding: EdgeInsets.all(8), child: CircularProgressIndicator(), ), Padding( From 211ec5e8c7943085ac5cdeb579c0f926ea447838 Mon Sep 17 00:00:00 2001 From: Cynthia J Date: Wed, 8 Apr 2026 11:17:34 -0700 Subject: [PATCH 4/4] fix the format --- packages/firebase_ai/firebase_ai/example/lib/main.dart | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/firebase_ai/firebase_ai/example/lib/main.dart b/packages/firebase_ai/firebase_ai/example/lib/main.dart index b46aa9b99e49..c978b210a066 100644 --- a/packages/firebase_ai/firebase_ai/example/lib/main.dart +++ b/packages/firebase_ai/firebase_ai/example/lib/main.dart @@ -154,7 +154,9 @@ class _HomeScreenState extends State { return ImagePromptPage(title: 'Image Prompt', model: currentModel); case 5: return ImageGenerationPage( - title: 'Image Gen', useVertexBackend: useVertexBackend,); + title: 'Image Gen', + useVertexBackend: useVertexBackend, + ); case 6: return SchemaPromptPage(title: 'Schema Prompt', model: currentModel); case 7: