Skip to content

Commit 191ae1c

Browse files
authored
Merge pull request #1 from vineyrawat/develop
Develop
2 parents 3247c61 + 23f7e29 commit 191ae1c

File tree

8 files changed

+264
-62
lines changed

8 files changed

+264
-62
lines changed

android/app/build.gradle

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,8 @@ android {
4545
applicationId "com.vinaybyte.convogen"
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 flutter.minSdkVersion
49+
minSdkVersion 21
4950
targetSdkVersion flutter.targetSdkVersion
5051
versionCode flutterVersionCode.toInteger()
5152
versionName flutterVersionName

android/app/src/main/AndroidManifest.xml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,11 @@
11
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
22
<uses-permission android:name="android.permission.INTERNET" />
33
<uses-permission android:name="android.permission.CAMERA" />
4+
<uses-permission android:name="android.permission.RECORD_AUDIO" />
5+
<uses-permission android:name="android.permission.INTERNET" />
6+
<uses-permission android:name="android.permission.BLUETOOTH" />
7+
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
8+
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
49
<application
510
android:label="convogen"
611
android:name="${applicationName}"

ios/Runner/Info.plist

Lines changed: 50 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -1,49 +1,53 @@
11
<?xml version="1.0" encoding="UTF-8"?>
22
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
33
<plist version="1.0">
4-
<dict>
5-
<key>CFBundleDevelopmentRegion</key>
6-
<string>$(DEVELOPMENT_LANGUAGE)</string>
7-
<key>CFBundleDisplayName</key>
8-
<string>Convogen</string>
9-
<key>CFBundleExecutable</key>
10-
<string>$(EXECUTABLE_NAME)</string>
11-
<key>CFBundleIdentifier</key>
12-
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
13-
<key>CFBundleInfoDictionaryVersion</key>
14-
<string>6.0</string>
15-
<key>CFBundleName</key>
16-
<string>convogen</string>
17-
<key>CFBundlePackageType</key>
18-
<string>APPL</string>
19-
<key>CFBundleShortVersionString</key>
20-
<string>$(FLUTTER_BUILD_NAME)</string>
21-
<key>CFBundleSignature</key>
22-
<string>????</string>
23-
<key>CFBundleVersion</key>
24-
<string>$(FLUTTER_BUILD_NUMBER)</string>
25-
<key>LSRequiresIPhoneOS</key>
26-
<true/>
27-
<key>UILaunchStoryboardName</key>
28-
<string>LaunchScreen</string>
29-
<key>UIMainStoryboardFile</key>
30-
<string>Main</string>
31-
<key>UISupportedInterfaceOrientations</key>
32-
<array>
33-
<string>UIInterfaceOrientationPortrait</string>
34-
<string>UIInterfaceOrientationLandscapeLeft</string>
35-
<string>UIInterfaceOrientationLandscapeRight</string>
36-
</array>
37-
<key>UISupportedInterfaceOrientations~ipad</key>
38-
<array>
39-
<string>UIInterfaceOrientationPortrait</string>
40-
<string>UIInterfaceOrientationPortraitUpsideDown</string>
41-
<string>UIInterfaceOrientationLandscapeLeft</string>
42-
<string>UIInterfaceOrientationLandscapeRight</string>
43-
</array>
44-
<key>CADisableMinimumFrameDurationOnPhone</key>
45-
<true/>
46-
<key>UIApplicationSupportsIndirectInputEvents</key>
47-
<true/>
48-
</dict>
49-
</plist>
4+
<dict>
5+
<key>CFBundleDevelopmentRegion</key>
6+
<string>$(DEVELOPMENT_LANGUAGE)</string>
7+
<key>CFBundleDisplayName</key>
8+
<string>Convogen</string>
9+
<key>CFBundleExecutable</key>
10+
<string>$(EXECUTABLE_NAME)</string>
11+
<key>CFBundleIdentifier</key>
12+
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
13+
<key>CFBundleInfoDictionaryVersion</key>
14+
<string>6.0</string>
15+
<key>CFBundleName</key>
16+
<string>convogen</string>
17+
<key>CFBundlePackageType</key>
18+
<string>APPL</string>
19+
<key>CFBundleShortVersionString</key>
20+
<string>$(FLUTTER_BUILD_NAME)</string>
21+
<key>CFBundleSignature</key>
22+
<string>????</string>
23+
<key>CFBundleVersion</key>
24+
<string>$(FLUTTER_BUILD_NUMBER)</string>
25+
<key>LSRequiresIPhoneOS</key>
26+
<true />
27+
<key>UILaunchStoryboardName</key>
28+
<string>LaunchScreen</string>
29+
<key>UIMainStoryboardFile</key>
30+
<string>Main</string>
31+
<key>UISupportedInterfaceOrientations</key>
32+
<array>
33+
<string>UIInterfaceOrientationPortrait</string>
34+
<string>UIInterfaceOrientationLandscapeLeft</string>
35+
<string>UIInterfaceOrientationLandscapeRight</string>
36+
</array>
37+
<key>UISupportedInterfaceOrientations~ipad</key>
38+
<array>
39+
<string>UIInterfaceOrientationPortrait</string>
40+
<string>UIInterfaceOrientationPortraitUpsideDown</string>
41+
<string>UIInterfaceOrientationLandscapeLeft</string>
42+
<string>UIInterfaceOrientationLandscapeRight</string>
43+
</array>
44+
<key>CADisableMinimumFrameDurationOnPhone</key>
45+
<true />
46+
<key>UIApplicationSupportsIndirectInputEvents</key>
47+
<true />
48+
<key>NSSpeechRecognitionUsageDescription</key>
49+
<string>Speech recognition is used to search text over google's gemini api</string>
50+
<key>NSMicrophoneUsageDescription</key>
51+
<string>Speech recognition is used to search text over google's gemini api</string>
52+
</dict>
53+
</plist>

lib/providers/gemini_chat_provider.dart

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,10 @@ class GeminiChatProvider extends StateNotifier<GeminiChatState> {
5252
state = state.copyWith(isTyping: isTyping);
5353
}
5454

55+
reset() {
56+
state = state.copyWith(isLoading: false, messages: [], isTyping: false);
57+
}
58+
5559
init() async {
5660
// create a timeout
5761
await Future.delayed(const Duration(seconds: 2));

lib/screens/chat.dart

Lines changed: 150 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,20 @@
11
import 'dart:developer';
22
import 'dart:io';
33

4+
import 'package:flutter/cupertino.dart';
45
import "package:flutter/material.dart";
6+
import 'package:flutter/services.dart';
7+
import 'package:flutter_markdown/flutter_markdown.dart';
58
import 'package:image_picker/image_picker.dart';
69
import 'package:loading_animation_widget/loading_animation_widget.dart';
710
import 'package:flutter_chat_ui/flutter_chat_ui.dart';
811
import 'package:flutter_riverpod/flutter_riverpod.dart';
912
import 'package:convogen/providers/gemini_chat_provider.dart';
1013
import 'package:simple_gradient_text/simple_gradient_text.dart';
1114
import 'package:shimmer/shimmer.dart';
15+
import 'package:speech_to_text/speech_recognition_result.dart';
16+
import 'package:speech_to_text/speech_to_text.dart' as stt;
17+
import 'package:toastification/toastification.dart';
1218

1319
class ChatPage extends ConsumerStatefulWidget {
1420
const ChatPage({super.key});
@@ -35,6 +41,64 @@ class _ChatPageState extends ConsumerState<ChatPage> {
3541
customStatusBuilder: (message, {required context}) {
3642
return const SizedBox();
3743
},
44+
textMessageBuilder: (p0, {required messageWidth, required showName}) {
45+
if (p0.author.id == '1') {
46+
return Padding(
47+
padding: const EdgeInsets.all(15.0),
48+
child: Text(p0.text, style: const TextStyle(color: Colors.white)),
49+
);
50+
}
51+
return Markdown(
52+
data: p0.text,
53+
shrinkWrap: true,
54+
physics: const NeverScrollableScrollPhysics(),
55+
);
56+
},
57+
listBottomWidget: AnimatedContainer(
58+
height: geminiChat.isTyping ? 0 : 80,
59+
duration: const Duration(milliseconds: 300),
60+
child: Padding(
61+
padding: const EdgeInsets.symmetric(horizontal: 20.0)
62+
.copyWith(bottom: 20),
63+
child: Row(
64+
children: [
65+
TextButton(
66+
onPressed: () {
67+
ref.read(geminiChatProvider.notifier).reset();
68+
},
69+
child: Row(
70+
children: [
71+
Icon(CupertinoIcons.bolt_circle,
72+
color: Theme.of(context).colorScheme.primary),
73+
const SizedBox(width: 5),
74+
const Text("Start New Chat"),
75+
],
76+
)),
77+
IconButton(
78+
onPressed: () {
79+
var text =
80+
(geminiChat.messages.first.toJson()["type"] == 'text')
81+
? geminiChat.messages.first.toJson()["text"]
82+
: '';
83+
log(text);
84+
Clipboard.setData(ClipboardData(text: text));
85+
toastification.show(
86+
context: context,
87+
type: ToastificationType.success,
88+
style: ToastificationStyle.flat,
89+
title: const Text('Copied'),
90+
description: const Text('Copied to clipboard'),
91+
alignment: Alignment.bottomCenter,
92+
autoCloseDuration: const Duration(seconds: 4),
93+
boxShadow: lowModeShadow,
94+
);
95+
},
96+
icon: Icon(Icons.copy_rounded,
97+
color: Theme.of(context).colorScheme.primary))
98+
],
99+
),
100+
),
101+
),
38102
emptyState: EmptyStateWidget(onSendPressed: (p0) async {
39103
FocusManager.instance.primaryFocus?.unfocus();
40104
await ref
@@ -79,6 +143,7 @@ class _ChatPageState extends ConsumerState<ChatPage> {
79143
.read(geminiChatProvider.notifier)
80144
.getPrompt(p0, selectedImage);
81145
}
146+
log("SEND PRESSED: $p0");
82147
}),
83148
typingIndicatorOptions: TypingIndicatorOptions(
84149
customTypingIndicator: Padding(
@@ -230,7 +295,7 @@ class EmptyStateWidget extends StatelessWidget {
230295
}
231296
}
232297

233-
class CustomBottomInputBar extends StatelessWidget {
298+
class CustomBottomInputBar extends StatefulWidget {
234299
final bool collapsed;
235300
final Function onSendPressed;
236301
final Function setImage;
@@ -242,16 +307,72 @@ class CustomBottomInputBar extends StatelessWidget {
242307
required this.onSendPressed,
243308
required this.setImage});
244309

310+
@override
311+
State<CustomBottomInputBar> createState() => _CustomBottomInputBarState();
312+
}
313+
314+
class _CustomBottomInputBarState extends State<CustomBottomInputBar> {
315+
var inputController = TextEditingController();
316+
final stt.SpeechToText _speechToText = stt.SpeechToText();
317+
bool _speechEnabled = false;
318+
String _lastWords = '';
319+
320+
@override
321+
void initState() {
322+
super.initState();
323+
_initSpeech();
324+
}
325+
326+
/// This has to happen only once per app
327+
void _initSpeech() async {
328+
_speechEnabled = await _speechToText.initialize();
329+
setState(() {});
330+
}
331+
332+
/// Each time to start a speech recognition session
333+
void _startListening() async {
334+
await _speechToText.listen(onResult: _onSpeechResult);
335+
setState(() {});
336+
}
337+
338+
/// Manually stop the active speech recognition session
339+
/// Note that there are also timeouts that each platform enforces
340+
/// and the SpeechToText plugin supports setting timeouts on the
341+
/// listen method.
342+
void _stopListening() async {
343+
await _speechToText.stop();
344+
setState(() {});
345+
}
346+
347+
/// This is the callback that the SpeechToText plugin calls when
348+
/// the platform returns recognized words.
349+
void _onSpeechResult(SpeechRecognitionResult result) {
350+
setState(() {
351+
_lastWords = result.recognizedWords;
352+
if (result.finalResult) {
353+
inputController.text = _lastWords;
354+
}
355+
});
356+
}
357+
245358
@override
246359
Widget build(BuildContext context) {
247-
var inputController = TextEditingController();
360+
handleMicPress() async {
361+
log("Mic Pressed");
362+
if (_speechEnabled) {
363+
_startListening();
364+
} else {
365+
// show snackbar
366+
log("Speech not enabled");
367+
}
368+
}
248369

249370
handleCameraPressed() {
250371
log("Camera pressed");
251372
ImagePicker().pickImage(source: ImageSource.gallery).then((image) {
252373
if (image != null) {
253374
log("IMAGE SELECTED: ${image.path}");
254-
setImage(image);
375+
widget.setImage(image);
255376
}
256377
});
257378
}
@@ -274,18 +395,25 @@ class CustomBottomInputBar extends StatelessWidget {
274395
children: [
275396
TextField(
276397
onSubmitted: (value) {
277-
onSendPressed(inputController.text);
398+
widget.onSendPressed(inputController.text);
399+
inputController.clear();
278400
},
401+
minLines: 1,
402+
maxLines: 5,
279403
controller: inputController,
404+
keyboardType: TextInputType.text,
280405
decoration: InputDecoration(
281406
hintStyle: const TextStyle(
282407
fontSize: 18,
283408
),
284-
hintText: 'Type, talk, or share \na photo',
409+
hintText: _speechToText.isListening
410+
? 'Listening...'
411+
: 'Type, talk, or share \na photo',
285412
hintMaxLines: 2,
286413
suffix: IconButton(
287414
onPressed: () {
288-
onSendPressed(inputController.text);
415+
widget.onSendPressed(inputController.text);
416+
inputController.clear();
289417
},
290418
icon: const Icon(Icons.send_rounded)),
291419
border: InputBorder.none)),
@@ -295,28 +423,30 @@ class CustomBottomInputBar extends StatelessWidget {
295423
Row(
296424
mainAxisAlignment: MainAxisAlignment.center,
297425
children: [
298-
selectedImage != null
426+
widget.selectedImage != null
299427
? Container(
300428
margin: const EdgeInsets.only(right: 10),
301429
child: ClipRRect(
302430
borderRadius: BorderRadius.circular(10),
303431
child: Image.file(
304-
File(selectedImage!.path),
432+
File(widget.selectedImage!.path),
305433
width: 50,
306434
height: 50,
307435
),
308436
),
309437
)
310438
: const SizedBox(),
311-
selectedImage != null
439+
widget.selectedImage != null
312440
? IconButton(
313-
onPressed: () => setImage(null),
441+
onPressed: () => widget.setImage(null),
314442
icon: Icon(
315443
Icons.delete_outline,
316444
color: Theme.of(context).colorScheme.error,
317445
))
318446
: const SizedBox(),
319-
selectedImage != null ? const Spacer() : const SizedBox(),
447+
widget.selectedImage != null
448+
? const Spacer()
449+
: const SizedBox(),
320450
FilledButton(
321451
// color: Colors.red,
322452
style: ButtonStyle(
@@ -327,9 +457,15 @@ class CustomBottomInputBar extends StatelessWidget {
327457
crossAxisAlignment: CrossAxisAlignment.center,
328458
mainAxisAlignment: MainAxisAlignment.center,
329459
children: [
330-
IconButton(
331-
onPressed: () {},
332-
icon: const Icon(Icons.mic_none_outlined)),
460+
_speechToText.isListening
461+
? IconButton(
462+
onPressed: () async {
463+
_stopListening();
464+
},
465+
icon: const Icon(Icons.stop_circle_outlined))
466+
: IconButton(
467+
onPressed: handleMicPress,
468+
icon: const Icon(Icons.mic_none_outlined)),
333469
const SizedBox(
334470
width: 10,
335471
),

0 commit comments

Comments
 (0)