gemini-app/lib/screens/gemini_chat_screen.dart

356 lines
11 KiB
Dart
Raw Normal View History

2024-07-16 19:02:02 +02:00
import 'package:flutter/material.dart';
import 'package:gemini_app/bloc/gemini_state.dart';
import 'package:flutter_markdown/flutter_markdown.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:gemini_app/config.dart';
import 'package:gemini_app/eeg/eeg_service.dart';
import 'package:get_it/get_it.dart';
2024-07-16 19:02:02 +02:00
class GeminiScreen extends StatelessWidget {
const GeminiScreen({super.key, required this.lessonId});
final String lessonId;
2024-07-16 19:02:02 +02:00
@override
Widget build(BuildContext context) {
return MultiBlocProvider(
providers: [
BlocProvider(create: (context) => GeminiCubit()),
],
child: GeminiChat(
lessonId: lessonId,
),
2024-07-16 19:02:02 +02:00
);
}
}
class GeminiChat extends StatefulWidget {
const GeminiChat({super.key, required this.lessonId});
final String lessonId;
2024-07-16 19:02:02 +02:00
@override
GeminiChatState createState() => GeminiChatState();
}
class GeminiChatState extends State<GeminiChat> {
final _textController = TextEditingController();
2024-08-03 22:51:03 +02:00
bool _quizMode = false; // Add this line
final EegService _eegService = GetIt.instance<EegService>();
2024-07-16 19:02:02 +02:00
2024-08-03 22:51:03 +02:00
@override
void initState() {
super.initState();
_startConversation();
}
2024-07-16 19:02:02 +02:00
2024-08-03 22:51:03 +02:00
void _toggleQuizMode() {
setState(() {
_quizMode = !_quizMode;
});
}
void _checkAnswer(int answer) {
context.read<GeminiCubit>().checkAnswer(answer);
}
@override
void dispose() {
_eegService.stopPolling();
2024-08-03 22:51:03 +02:00
super.dispose();
}
2024-07-16 19:02:02 +02:00
void _startConversation() async {
context.read<GeminiCubit>().startLesson(widget.lessonId);
2024-07-16 19:02:02 +02:00
}
void _sendMessage() async {
2024-08-08 18:18:01 +02:00
context.read<GeminiCubit>().sendMessage(_textController.text);
2024-07-16 19:02:02 +02:00
_textController.clear();
}
2024-08-03 22:51:03 +02:00
2024-07-29 22:33:48 +02:00
void _toggleEegState() {
2024-08-08 18:18:01 +02:00
setState(() {
_eegService.toggleState();
});
2024-07-29 22:33:48 +02:00
}
void _resetConversation() {
context.read<GeminiCubit>().resetConversation();
_startConversation();
}
2024-07-16 19:02:02 +02:00
2024-08-03 22:51:03 +02:00
void _enterQuizMode() {
context.read<GeminiCubit>().enterQuizMode();
}
2024-07-16 19:02:02 +02:00
@override
Widget build(BuildContext context) {
return Scaffold(
2024-07-29 22:33:48 +02:00
resizeToAvoidBottomInset: true,
2024-07-16 19:02:02 +02:00
appBar: AppBar(
2024-08-03 22:51:03 +02:00
title: BlocBuilder<GeminiCubit, GeminiState>(
builder: (context, state) {
return Text(state.isQuizMode ? 'Quiz Mode' : 'Gemini Pro Chat');
},
),
2024-07-16 19:02:02 +02:00
),
body: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
2024-08-08 18:18:01 +02:00
crossAxisAlignment: CrossAxisAlignment.start,
2024-07-16 19:02:02 +02:00
children: [
isDebug
? Card(
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'Mind Wandering: ${_eegService.state.mindWandering.toStringAsFixed(2)}'),
Text(
'Focus: ${_eegService.state.focus.toStringAsFixed(2)}'),
],
),
),
)
: Container(),
2024-08-08 18:18:01 +02:00
const SizedBox(height: 16),
2024-07-16 19:02:02 +02:00
Expanded(
child: BlocBuilder<GeminiCubit, GeminiState>(
builder: (context, state) {
if (state.status == GeminiStatus.loading) {
return buildChatList(state, loading: true);
} else if (state.status == GeminiStatus.error) {
return Text('Error: ${state.error}');
} else {
return buildChatList(state);
}
},
),
),
2024-08-08 18:18:01 +02:00
const SizedBox(height: 16),
2024-08-03 22:51:03 +02:00
BlocBuilder<GeminiCubit, GeminiState>(
builder: (context, state) {
return state.isQuizMode
2024-08-08 18:18:01 +02:00
? Container()
2024-08-03 22:51:03 +02:00
: TextField(
controller: _textController,
2024-08-08 18:18:01 +02:00
decoration: InputDecoration(
2024-08-03 22:51:03 +02:00
hintText: 'Enter your message',
2024-08-08 18:18:01 +02:00
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(10.0),
),
filled: true,
fillColor: Colors.grey[200],
contentPadding: const EdgeInsets.symmetric(
horizontal: 16.0, vertical: 12.0),
2024-08-03 22:51:03 +02:00
),
onSubmitted: (_) => _sendMessage(),
);
},
2024-07-16 19:02:02 +02:00
),
2024-08-08 18:18:01 +02:00
const SizedBox(height: 16),
2024-08-03 22:51:03 +02:00
Row(
children: [
ElevatedButton(
onPressed: _resetConversation,
2024-08-08 18:18:01 +02:00
child: const Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(Icons.refresh),
SizedBox(width: 3),
Text('Reset'),
],
),
2024-08-03 22:51:03 +02:00
),
ElevatedButton(
onPressed: _sendMessage,
2024-08-08 18:18:01 +02:00
child: const Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(Icons.send),
2024-08-08 18:18:01 +02:00
SizedBox(width: 3),
Text('Send'),
2024-08-08 18:18:01 +02:00
],
),
2024-08-03 22:51:03 +02:00
),
isDebug
? ElevatedButton(
onPressed: _toggleEegState,
child: const Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(Icons.toggle_on),
SizedBox(width: 3),
Text('Toggle State'),
],
),
)
: Container(),
2024-08-03 22:51:03 +02:00
],
),
2024-07-16 19:02:02 +02:00
],
),
),
);
}
2024-08-03 22:51:03 +02:00
ListView buildChatList(GeminiState state, {bool loading = false}) {
return ListView.builder(
itemCount: state.messages.length + (loading ? 1 : 0),
itemBuilder: (context, index) {
if (index == state.messages.length && loading) {
return const Card(
child: Padding(
padding: EdgeInsets.all(16.0),
child: Align(
alignment: Alignment.centerLeft,
child: BouncingDots(),
),
2024-07-16 19:02:02 +02:00
),
2024-08-03 22:51:03 +02:00
);
}
if (state.messages[index].type == MessageType.lessonScript ||
state.messages[index].source == MessageSource.app) {
2024-08-03 22:51:03 +02:00
// skip
return Container();
}
2024-07-16 19:02:02 +02:00
2024-08-03 22:51:03 +02:00
final message = state.messages[index];
// String text = message.parts.whereType<TextPart>().map((part) => part.text).join();
String text = message.text;
2024-07-16 19:02:02 +02:00
2024-08-03 22:51:03 +02:00
if (message.type == MessageType.quizQuestion) {
return Card(
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
MarkdownBody(data: text),
...message.quizOptions!.asMap().entries.map((entry) {
return ElevatedButton(
onPressed: () => {_checkAnswer(entry.key)},
child: Text(entry.value),
);
}),
],
),
),
);
} else if (message.type == MessageType.quizAnswer) {
bool correct =
message.text == message.quizOptions![message.correctAnswer!];
2024-08-03 22:51:03 +02:00
var text = Text(
correct
? "Correct!"
: "Incorrect. The correct answer was: ${message.quizOptions![message.correctAnswer!]}",
style: TextStyle(
color: correct ? Colors.green : Colors.red,
fontWeight: FontWeight.bold,
),
);
return Card(
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [text],
),
),
);
} else {
return Card(
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Column(
crossAxisAlignment: message.source == MessageSource.agent
? CrossAxisAlignment.start
: CrossAxisAlignment.end,
children: [MarkdownBody(data: text)],
),
),
);
2024-07-16 19:02:02 +02:00
}
2024-08-03 22:51:03 +02:00
},
);
}
2024-07-16 19:02:02 +02:00
}
class BouncingDots extends StatefulWidget {
const BouncingDots({super.key});
@override
BouncingDotsState createState() => BouncingDotsState();
}
2024-08-03 22:51:03 +02:00
class BouncingDotsState extends State<BouncingDots>
with TickerProviderStateMixin {
2024-07-16 19:02:02 +02:00
late List<AnimationController> _controllers;
late List<Animation<double>> _animations;
@override
void initState() {
super.initState();
_controllers = List.generate(
3,
2024-08-03 22:51:03 +02:00
(index) => AnimationController(
2024-07-16 19:02:02 +02:00
duration: const Duration(milliseconds: 400),
vsync: this,
),
);
2024-08-03 22:51:03 +02:00
_animations = _controllers
.map((controller) => Tween<double>(begin: 0, end: -10).animate(
CurvedAnimation(parent: controller, curve: Curves.easeInOut),
))
.toList();
2024-07-16 19:02:02 +02:00
for (var i = 0; i < 3; i++) {
Future.delayed(Duration(milliseconds: i * 180), () {
_controllers[i].repeat(reverse: true);
});
}
}
@override
void dispose() {
for (var controller in _controllers) {
controller.dispose();
}
super.dispose();
}
@override
Widget build(BuildContext context) {
return Row(
2024-08-08 18:18:01 +02:00
mainAxisAlignment: MainAxisAlignment.center,
2024-07-16 19:02:02 +02:00
children: List.generate(3, (index) {
return AnimatedBuilder(
animation: _controllers[index],
builder: (context, child) {
return Container(
2024-08-08 18:18:01 +02:00
margin: const EdgeInsets.symmetric(horizontal: 4.0),
2024-07-16 19:02:02 +02:00
padding: const EdgeInsets.all(2.5),
child: Transform.translate(
offset: Offset(0, _animations[index].value),
child: Container(
width: 10,
height: 10,
decoration: BoxDecoration(
color: Theme.of(context).primaryColor,
shape: BoxShape.circle,
),
),
),
);
},
);
}),
);
}
}