Skip to content

Commit 9881d56

Browse files
Gemini Chatbot
1 parent 3d58d29 commit 9881d56

File tree

5 files changed

+342
-115
lines changed

5 files changed

+342
-115
lines changed

android/app/build.gradle

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ android {
4545
applicationId "com.example.flutter_ai"
4646
// You can update the following values to match your application needs.
4747
// For more information, see: https://docs.flutter.dev/deployment/android#reviewing-the-gradle-build-configuration.
48-
minSdkVersion flutter.minSdkVersion
48+
minSdkVersion 21
4949
targetSdkVersion flutter.targetSdkVersion
5050
versionCode flutterVersionCode.toInteger()
5151
versionName flutterVersionName

android/app/src/main/AndroidManifest.xml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
2+
<uses-permission android:name="android.permission.INTERNET"/>
23
<application
3-
android:label="flutter_ai"
4+
android:label="AI with Flutter"
45
android:name="${applicationName}"
56
android:icon="@mipmap/ic_launcher">
67
<activity

lib/main.dart

Lines changed: 4 additions & 113 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import 'package:flutter/material.dart';
2-
import 'package:flutter_ai/services/gemini_services.dart';
3-
import 'package:flutter_ai/widgets/content_widget.dart';
2+
3+
import 'screens/home_screen.dart';
44

55
void main() {
66
runApp(const MyApp());
@@ -12,121 +12,12 @@ class MyApp extends StatelessWidget {
1212
Widget build(BuildContext context) {
1313
return MaterialApp(
1414
title: 'Flutter with AI',
15+
debugShowCheckedModeBanner: false,
16+
home: const MyHomePage(title: 'Flutter with AI'),
1517
theme: ThemeData(
1618
colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
1719
useMaterial3: true,
1820
),
19-
home: const MyHomePage(title: 'Flutter with AI'),
20-
debugShowCheckedModeBanner: false,
21-
);
22-
}
23-
}
24-
25-
class MyHomePage extends StatefulWidget {
26-
const MyHomePage({super.key, required this.title});
27-
final String title;
28-
29-
@override
30-
State<MyHomePage> createState() => _MyHomePageState();
31-
}
32-
33-
class _MyHomePageState extends State<MyHomePage> {
34-
List<Map<String, dynamic>> prompts = <Map<String, dynamic>>[];
35-
TextEditingController query = TextEditingController();
36-
bool addPrompt = false, loading = false;
37-
38-
@override
39-
Widget build(BuildContext context) {
40-
return Scaffold(
41-
appBar: AppBar(
42-
backgroundColor: Theme.of(context).colorScheme.inversePrimary,
43-
title: Text(widget.title),
44-
),
45-
body: prompts.isEmpty
46-
? const Center(
47-
child: Text('No content has been generated yet'),
48-
)
49-
: ListView.builder(
50-
itemCount: prompts.length,
51-
padding: const EdgeInsets.symmetric(vertical: 20, horizontal: 10),
52-
itemBuilder: (context, index) {
53-
return ContentWidget(prompts: prompts[index]);
54-
},
55-
),
56-
floatingActionButton: addPrompt
57-
? null
58-
: FloatingActionButton.extended(
59-
onPressed: () {
60-
setState(() {
61-
addPrompt = !addPrompt;
62-
});
63-
},
64-
tooltip: 'Increment',
65-
icon: const Icon(Icons.add),
66-
label: const Text('Add Prompt'),
67-
),
68-
bottomNavigationBar: addPrompt
69-
? Padding(
70-
padding: EdgeInsets.only(
71-
bottom: MediaQuery.of(context).viewInsets.bottom,
72-
left: 15,
73-
right: 15,
74-
),
75-
child: Padding(
76-
padding: const EdgeInsets.only(bottom: 20),
77-
child: Row(
78-
children: [
79-
Expanded(
80-
child: TextFormField(
81-
controller: query,
82-
maxLines: null,
83-
keyboardType: TextInputType.multiline,
84-
textInputAction: TextInputAction.send,
85-
onChanged: (value) => setState(() {}),
86-
decoration: InputDecoration(
87-
isDense: true,
88-
hintText: 'Write something here...',
89-
enabledBorder: OutlineInputBorder(
90-
borderRadius: BorderRadius.circular(30),
91-
borderSide: const BorderSide(color: Colors.grey),
92-
),
93-
focusedBorder: OutlineInputBorder(
94-
borderRadius: BorderRadius.circular(30),
95-
borderSide: const BorderSide(color: Colors.grey),
96-
),
97-
),
98-
),
99-
),
100-
if (query.text.isNotEmpty)
101-
Padding(
102-
padding: const EdgeInsets.only(left: 10),
103-
child: InkWell(
104-
onTap: () async {
105-
setState(() {
106-
loading = true;
107-
});
108-
String text = await GeminiServices.generateText(
109-
query.text,
110-
);
111-
prompts.add({'query': query.text, 'content': text});
112-
query.clear();
113-
loading = false;
114-
addPrompt = false;
115-
setState(() {});
116-
},
117-
child: CircleAvatar(
118-
radius: 30,
119-
child: loading
120-
? const CircularProgressIndicator()
121-
: const Icon(Icons.send),
122-
),
123-
),
124-
),
125-
],
126-
),
127-
),
128-
)
129-
: null,
13021
);
13122
}
13223
}

lib/screens/gemini_chat.dart

Lines changed: 209 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,209 @@
1+
import 'dart:convert';
2+
3+
import 'package:flutter/rendering.dart';
4+
import 'package:flutter_ai/services/gemini_services.dart';
5+
import 'package:http/http.dart' as http;
6+
import 'package:flutter/material.dart';
7+
8+
class GeminiMsgScreen extends StatefulWidget {
9+
const GeminiMsgScreen({super.key});
10+
11+
@override
12+
State<GeminiMsgScreen> createState() => _GeminiMsgScreenState();
13+
}
14+
15+
class _GeminiMsgScreenState extends State<GeminiMsgScreen> {
16+
final TextEditingController chatController = TextEditingController();
17+
final ScrollController scrollController = ScrollController();
18+
List<Map<String, dynamic>> chatHistory = [];
19+
20+
sendMessage() {
21+
FocusManager.instance.primaryFocus?.unfocus();
22+
if (chatController.text.isNotEmpty) {
23+
chatHistory.add({
24+
"time": DateTime.now(),
25+
"message": chatController.text,
26+
"isSender": true,
27+
});
28+
chatController.clear();
29+
}
30+
if (mounted) setState(() {});
31+
if (scrollController.hasClients) {
32+
scrollController.jumpTo(scrollController.position.maxScrollExtent);
33+
}
34+
if (mounted) setState(() {});
35+
getResponses();
36+
}
37+
38+
void getResponses() async {
39+
final url =
40+
"https://generativelanguage.googleapis.com/v1beta2/models/chat-bison-001:generateMessage?key=${GeminiServices.apikey}";
41+
final uri = Uri.parse(url);
42+
List<Map<String, String>> msg = [];
43+
for (var i = 0; i < chatHistory.length; i++) {
44+
msg.add({"content": chatHistory[i]["message"]});
45+
}
46+
Map<String, dynamic> request = {
47+
"prompt": {
48+
"messages": [msg]
49+
},
50+
"temperature": 0.25,
51+
"candidateCount": 1,
52+
"topP": 1,
53+
"topK": 1
54+
};
55+
chatHistory.add({
56+
"time": DateTime.now(),
57+
"message": 'typing',
58+
"isSender": false,
59+
});
60+
if (scrollController.hasClients) {
61+
scrollController.jumpTo(scrollController.position.maxScrollExtent);
62+
}
63+
final response = await http.post(uri, body: jsonEncode(request));
64+
chatHistory.removeLast();
65+
chatHistory.add({
66+
"time": DateTime.now(),
67+
"message": json.decode(response.body)["candidates"][0]["content"],
68+
"isSender": false,
69+
});
70+
if (mounted) setState(() {});
71+
if (scrollController.hasClients) {
72+
scrollController.animateTo(
73+
scrollController.position.maxScrollExtent,
74+
duration: const Duration(microseconds: 1),
75+
curve: Curves.linear,
76+
);
77+
}
78+
if (mounted) setState(() {});
79+
}
80+
81+
double previousScrollPosition = 0.0;
82+
ScrollDirection scrollDirection = ScrollDirection.idle;
83+
void onScroll() {
84+
double currentScrollPosition = scrollController.position.pixels;
85+
if (currentScrollPosition > previousScrollPosition) {
86+
scrollDirection = ScrollDirection.forward;
87+
if (mounted) setState(() {});
88+
} else if (currentScrollPosition < previousScrollPosition) {
89+
scrollDirection = ScrollDirection.reverse;
90+
if (mounted) setState(() {});
91+
}
92+
93+
previousScrollPosition = currentScrollPosition;
94+
}
95+
96+
@override
97+
void initState() {
98+
scrollController.addListener(onScroll);
99+
if (mounted) setState(() {});
100+
super.initState();
101+
}
102+
103+
@override
104+
Widget build(BuildContext context) {
105+
return Scaffold(
106+
appBar: AppBar(title: const Text('Chat with Gemini AI')),
107+
bottomNavigationBar: Padding(
108+
padding: EdgeInsets.only(
109+
left: 15,
110+
right: 15,
111+
bottom: MediaQuery.of(context).viewInsets.bottom,
112+
),
113+
child: Padding(
114+
padding: const EdgeInsets.only(bottom: 20),
115+
child: Row(
116+
mainAxisSize: MainAxisSize.min,
117+
children: [
118+
Expanded(
119+
child: TextFormField(
120+
maxLines: null,
121+
controller: chatController,
122+
keyboardType: TextInputType.multiline,
123+
onChanged: (value) => setState(() {}),
124+
textInputAction: TextInputAction.send,
125+
textCapitalization: TextCapitalization.sentences,
126+
decoration: InputDecoration(
127+
isDense: true,
128+
hintText: "Type a message",
129+
contentPadding: const EdgeInsets.all(12.0),
130+
enabledBorder: OutlineInputBorder(
131+
borderRadius: BorderRadius.circular(30),
132+
borderSide: const BorderSide(color: Colors.grey),
133+
),
134+
focusedBorder: OutlineInputBorder(
135+
borderRadius: BorderRadius.circular(30),
136+
borderSide: const BorderSide(color: Colors.grey),
137+
),
138+
),
139+
),
140+
),
141+
if (chatController.text.isNotEmpty)
142+
Padding(
143+
padding: const EdgeInsets.only(left: 10),
144+
child: InkWell(
145+
onTap: () => sendMessage(),
146+
child: const CircleAvatar(child: Icon(Icons.send)),
147+
),
148+
),
149+
],
150+
),
151+
),
152+
),
153+
body: Padding(
154+
padding: const EdgeInsets.symmetric(horizontal: 15, vertical: 20),
155+
child: chatHistory.isEmpty
156+
? const Center(
157+
child: Text('Start conservating with Gemini...'),
158+
)
159+
: ListView.builder(
160+
itemCount: chatHistory.length,
161+
controller: scrollController,
162+
padding: const EdgeInsets.only(top: 10, bottom: 10),
163+
physics: const BouncingScrollPhysics(),
164+
itemBuilder: (context, index) {
165+
return Container(
166+
padding: const EdgeInsets.only(
167+
left: 14,
168+
right: 14,
169+
top: 10,
170+
bottom: 10,
171+
),
172+
child: Align(
173+
alignment: (chatHistory[index]["isSender"]
174+
? Alignment.topRight
175+
: Alignment.topLeft),
176+
child: Container(
177+
padding: const EdgeInsets.all(16),
178+
decoration: BoxDecoration(
179+
borderRadius: BorderRadius.circular(20),
180+
boxShadow: [
181+
BoxShadow(
182+
color: Colors.grey.withOpacity(0.5),
183+
spreadRadius: 2,
184+
blurRadius: 5,
185+
offset: const Offset(0, 3),
186+
),
187+
],
188+
color: chatHistory[index]["isSender"]
189+
? Colors.pink
190+
: Colors.white,
191+
),
192+
child: Text(
193+
chatHistory[index]["message"],
194+
style: TextStyle(
195+
fontSize: 15,
196+
color: chatHistory[index]["isSender"]
197+
? Colors.white
198+
: Colors.black,
199+
),
200+
),
201+
),
202+
),
203+
);
204+
},
205+
),
206+
),
207+
);
208+
}
209+
}

0 commit comments

Comments
 (0)