Skip to content

Commit 94a501d

Browse files
author
Babak
committed
first publish
1 parent 4cc4e58 commit 94a501d

File tree

14 files changed

+436
-27
lines changed

14 files changed

+436
-27
lines changed

CHANGELOG.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
1-
## 0.0.1-dev-4
1+
## 1.0.0
22

33
* first publish

README.md

Lines changed: 17 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -23,9 +23,14 @@ This package provides a powerful bridge between your Flutter application and Goo
2323

2424
To use the Gemini API, you'll need an API key. If you don't already have one, create a key in Google AI Studio. [Get an API key](https://ai.google.dev/).
2525

26+
### online demo
27+
28+
[https://babakcode.github.io/flutter_gemini](https://babakcode.github.io/flutter_gemini)
29+
2630
## Initialize Gemini
2731

28-
To initialize Gemini you must add an init factory in the main function.
32+
For initialization, you must call the init constructor for Flutter Gemini in the main function.
33+
2934
```dart
3035
void main() {
3136
@@ -38,24 +43,22 @@ void main() {
3843

3944
Now you can create an instance
4045

41-
```dart
42-
final gemini = Gemini.instance;
43-
```
44-
4546
## Content-based APIs
4647

4748
### Text-only input
4849

4950
This feature lets you perform natural language processing (NLP) tasks such as text completion and summarization.
5051

5152
```dart
52-
final gemini = Gemini.instance;
53+
final gemini = Gemini.instance;
5354
54-
gemini.text("Write a story about a magic backpack.")
55-
.then((value) => print( value?.output )) /// or value?.content?.parts?.last.text
56-
.catchError((e) => print(e));
55+
gemini.text("Write a story about a magic backpack.")
56+
.then((value) => print( value?.output )) /// or value?.content?.parts?.last.text
57+
.catchError((e) => print(e));
5758
```
5859

60+
![Flutter gemini Text only example gif](https://miro.medium.com/v2/resize:fit:828/format:webp/1*41dnttHItU2v4hobJ_DGSA.gif "Flutter_Gemini example")
61+
5962
### Text-and-image input
6063

6164
If the input contains both text and image, You can send a text prompt with an image to the gemini-pro-vision model to perform a vision-related task. For example, captioning an image or identifying what's in an image.
@@ -72,6 +75,8 @@ If the input contains both text and image, You can send a text prompt with an im
7275
.catchError((e) => log('textAndImageInput', error: e));
7376
```
7477

78+
![Flutter gemini Text and Image example gif](https://miro.medium.com/v2/resize:fit:828/format:webp/1*3JEeJaBRSpif6hOl2pt3RA.gif "Flutter_Gemini example")
79+
7580

7681
### Multi-turn conversations (chat)
7782

@@ -96,6 +101,9 @@ Using Gemini, you can build freeform conversations across multiple turns.
96101
```
97102

98103

104+
![Flutter gemini Text and Image example gif](https://miro.medium.com/v2/resize:fit:828/format:webp/1*MoVz4Z5KpxVUocEHLmzDew.gif "Flutter_Gemini example")
105+
106+
99107
### Count tokens
100108

101109
When using long prompts, it might be useful to count tokens before sending any content to the model.

example/lib/main.dart

Lines changed: 242 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
1+
import 'dart:typed_data';
12
import 'package:flutter/material.dart';
23
import 'package:flutter_gemini/flutter_gemini.dart';
34
import 'package:flutter_markdown/flutter_markdown.dart';
5+
import 'package:image_picker/image_picker.dart';
46
import 'package:lottie/lottie.dart';
57

68
void main() {
@@ -27,12 +29,12 @@ class MyApp extends StatelessWidget {
2729
}
2830
}
2931

30-
class PageItem {
32+
class SectionItem {
3133
final int index;
3234
final String title;
3335
final Widget widget;
3436

35-
PageItem(this.index, this.title, this.widget);
37+
SectionItem(this.index, this.title, this.widget);
3638
}
3739

3840
class MyHomePage extends StatefulWidget {
@@ -47,9 +49,10 @@ class MyHomePage extends StatefulWidget {
4749
class _MyHomePageState extends State<MyHomePage> {
4850
int _selectedItem = 0;
4951

50-
final _pages = <PageItem>[
51-
PageItem(0, 'text', const SectionTextInput()),
52-
PageItem(1, 'textAndImage', const SectionTextAndImageInput()),
52+
final _sections = <SectionItem>[
53+
SectionItem(0, 'text', const SectionTextInput()),
54+
SectionItem(1, 'textAndImage', const SectionTextAndImageInput()),
55+
SectionItem(2, 'chat', const SectionChat()),
5356
];
5457

5558
@override
@@ -62,7 +65,7 @@ class _MyHomePageState extends State<MyHomePage> {
6265
PopupMenuButton<int>(
6366
initialValue: _selectedItem,
6467
onSelected: (value) => setState(() => _selectedItem = value),
65-
itemBuilder: (context) => _pages.map((e) {
68+
itemBuilder: (context) => _sections.map((e) {
6669
return PopupMenuItem<int>(value: e.index, child: Text(e.title));
6770
}).toList(),
6871
child: const Icon(Icons.more_vert_rounded),
@@ -71,12 +74,13 @@ class _MyHomePageState extends State<MyHomePage> {
7174
),
7275
body: IndexedStack(
7376
index: _selectedItem,
74-
children: _pages.map((e) => e.widget).toList(),
77+
children: _sections.map((e) => e.widget).toList(),
7578
),
7679
);
7780
}
7881
}
7982

83+
/// -------------------------------------------------------------
8084
class SectionTextInput extends StatefulWidget {
8185
const SectionTextInput({super.key});
8286

@@ -157,11 +161,240 @@ class _SectionTextInputState extends State<SectionTextInput> {
157161
}
158162
}
159163

160-
class SectionTextAndImageInput extends StatelessWidget {
164+
/// -----------------------------------------------------
165+
class SectionTextAndImageInput extends StatefulWidget {
161166
const SectionTextAndImageInput({super.key});
162167

168+
@override
169+
State<SectionTextAndImageInput> createState() =>
170+
_SectionTextAndImageInputState();
171+
}
172+
173+
class _SectionTextAndImageInputState extends State<SectionTextAndImageInput> {
174+
final ImagePicker picker = ImagePicker();
175+
final controller = TextEditingController();
176+
final gemini = Gemini.instance;
177+
String? searchedText, result;
178+
bool _loading = false;
179+
180+
Uint8List? selectedImage;
181+
182+
bool get loading => _loading;
183+
184+
set loading(bool set) => setState(() => _loading = set);
185+
186+
@override
187+
Widget build(BuildContext context) {
188+
return Column(
189+
children: [
190+
if (searchedText != null)
191+
MaterialButton(
192+
color: Colors.blue.shade700,
193+
onPressed: () {
194+
setState(() {
195+
searchedText = null;
196+
result = null;
197+
});
198+
},
199+
child: Text('search: $searchedText')),
200+
Expanded(
201+
child: Padding(
202+
padding: const EdgeInsets.all(8.0),
203+
child: Row(
204+
mainAxisAlignment: MainAxisAlignment.center,
205+
children: [
206+
Expanded(
207+
flex: 2,
208+
child: loading
209+
? Lottie.asset('assets/lottie/ai.json')
210+
: result != null
211+
? Markdown(
212+
data: result!,
213+
padding:
214+
const EdgeInsets.symmetric(horizontal: 12),
215+
)
216+
: const Center(
217+
child: Text('Search something!'),
218+
),
219+
),
220+
if (selectedImage != null)
221+
Expanded(
222+
flex: 1,
223+
child: ClipRRect(
224+
borderRadius: BorderRadius.circular(32),
225+
child: Image.memory(
226+
selectedImage!,
227+
fit: BoxFit.cover,
228+
),
229+
),
230+
)
231+
],
232+
),
233+
),
234+
),
235+
Card(
236+
margin: const EdgeInsets.all(12),
237+
child: Row(
238+
children: [
239+
Expanded(
240+
child: TextField(
241+
controller: controller,
242+
decoration: const InputDecoration(
243+
contentPadding: EdgeInsets.symmetric(horizontal: 12),
244+
hintText: 'write something ...',
245+
border: InputBorder.none,
246+
),
247+
onTapOutside: (event) =>
248+
FocusManager.instance.primaryFocus?.unfocus(),
249+
)),
250+
Padding(
251+
padding: const EdgeInsets.only(right: 8.0),
252+
child: IconButton.filledTonal(
253+
color: Colors.blue,
254+
onPressed: () async {
255+
// Capture a photo.
256+
final XFile? photo =
257+
await picker.pickImage(source: ImageSource.camera);
258+
259+
if (photo != null) {
260+
photo.readAsBytes().then((value) => setState(() {
261+
selectedImage = value;
262+
}));
263+
}
264+
},
265+
icon: const Icon(Icons.file_copy_outlined)),
266+
),
267+
Padding(
268+
padding: const EdgeInsets.only(right: 8.0),
269+
child: IconButton(
270+
onPressed: () {
271+
if (controller.text.isNotEmpty && selectedImage != null) {
272+
searchedText = controller.text;
273+
controller.clear();
274+
loading = true;
275+
276+
gemini
277+
.textAndImage(
278+
text: searchedText!, image: selectedImage!)
279+
.then((value) {
280+
result = value?.content?.parts?.last.text;
281+
loading = false;
282+
});
283+
}
284+
},
285+
icon: const Icon(Icons.send_rounded)),
286+
),
287+
],
288+
),
289+
)
290+
],
291+
);
292+
}
293+
}
294+
295+
/// ---------------------------------------------------
296+
class SectionChat extends StatefulWidget {
297+
const SectionChat({super.key});
298+
299+
@override
300+
State<SectionChat> createState() => _SectionChatState();
301+
}
302+
303+
class _SectionChatState extends State<SectionChat> {
304+
final controller = TextEditingController();
305+
final gemini = Gemini.instance;
306+
bool _loading = false;
307+
308+
bool get loading => _loading;
309+
310+
set loading(bool set) => setState(() => _loading = set);
311+
final List<Content> chats = [];
312+
163313
@override
164314
Widget build(BuildContext context) {
165-
return const Placeholder();
315+
return Column(
316+
children: [
317+
Expanded(
318+
child: chats.isNotEmpty
319+
? Align(
320+
alignment: Alignment.bottomCenter,
321+
child: SingleChildScrollView(
322+
reverse: true,
323+
child: ListView.builder(
324+
itemBuilder: chatItem,
325+
shrinkWrap: true,
326+
physics: const NeverScrollableScrollPhysics(),
327+
itemCount: chats.length,
328+
reverse: false,
329+
),
330+
),
331+
)
332+
: const Center(child: Text('Search something!'))),
333+
if (loading) const CircularProgressIndicator(),
334+
Card(
335+
margin: const EdgeInsets.all(12),
336+
child: Row(
337+
children: [
338+
Expanded(
339+
child: TextField(
340+
controller: controller,
341+
decoration: const InputDecoration(
342+
contentPadding: EdgeInsets.symmetric(horizontal: 12),
343+
hintText: 'write something ...',
344+
border: InputBorder.none,
345+
),
346+
onTapOutside: (event) =>
347+
FocusManager.instance.primaryFocus?.unfocus(),
348+
)),
349+
Padding(
350+
padding: const EdgeInsets.only(right: 8.0),
351+
child: IconButton(
352+
onPressed: () {
353+
if (controller.text.isNotEmpty) {
354+
final searchedText = controller.text;
355+
chats.add(Content(
356+
role: 'user', parts: [Parts(text: searchedText)]));
357+
controller.clear();
358+
loading = true;
359+
360+
gemini.chat(chats).then((value) {
361+
chats.add(Content(
362+
role: 'model',
363+
parts: [Parts(text: value?.output)]));
364+
loading = false;
365+
});
366+
}
367+
},
368+
icon: const Icon(Icons.send_rounded)),
369+
)
370+
],
371+
),
372+
)
373+
],
374+
);
375+
}
376+
377+
Widget chatItem(BuildContext context, int index) {
378+
final Content content = chats[index];
379+
380+
return Card(
381+
elevation: 0,
382+
color:
383+
content.role == 'model' ? Colors.blue.shade800 : Colors.transparent,
384+
child: Padding(
385+
padding: const EdgeInsets.all(8.0),
386+
child: Column(
387+
crossAxisAlignment: CrossAxisAlignment.start,
388+
children: [
389+
Text(content.role ?? 'role'),
390+
Markdown(
391+
shrinkWrap: true,
392+
physics: const NeverScrollableScrollPhysics(),
393+
data:
394+
content.parts?.lastOrNull?.text ?? 'cannot generate data!'),
395+
],
396+
),
397+
),
398+
);
166399
}
167400
}

example/linux/flutter/generated_plugin_registrant.cc

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,10 @@
66

77
#include "generated_plugin_registrant.h"
88

9+
#include <file_selector_linux/file_selector_plugin.h>
910

1011
void fl_register_plugins(FlPluginRegistry* registry) {
12+
g_autoptr(FlPluginRegistrar) file_selector_linux_registrar =
13+
fl_plugin_registry_get_registrar_for_plugin(registry, "FileSelectorPlugin");
14+
file_selector_plugin_register_with_registrar(file_selector_linux_registrar);
1115
}

example/linux/flutter/generated_plugins.cmake

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
#
44

55
list(APPEND FLUTTER_PLUGIN_LIST
6+
file_selector_linux
67
)
78

89
list(APPEND FLUTTER_FFI_PLUGIN_LIST

0 commit comments

Comments
 (0)