Fixed transitions, added baseline screen, added list screen

This commit is contained in:
Dawid Pietrykowski 2024-08-09 00:18:15 +02:00
parent b04585e2e2
commit 55325b260d
7 changed files with 384 additions and 224 deletions

View File

@ -0,0 +1,63 @@
[
{
"id": "rjp",
"title": "Uniformly accelerated motion",
"content": "This lesson covers the basics of uniformly accelerated motion, including the equations for displacement, velocity, and acceleration."
},
{
"id": "cells",
"title": "Cells",
"content": "This lesson covers the structure and function of cells, including the cell membrane, cytoplasm, and organelles."
},
{
"id": "pw",
"title": "Photosynthesis and Respiration",
"content": "This lesson explores the processes of photosynthesis and respiration, detailing how plants convert light energy into chemical energy and how both plants and animals use respiration for energy."
},
{
"id": "thermodynamics",
"title": "Thermodynamics",
"content": "This lesson introduces the laws of thermodynamics and their applications in energy transfer, heat engines, and entropy."
},
{
"id": "newton",
"title": "Newton's Laws of Motion",
"content": "This lesson discusses Newton's three laws of motion and their significance in understanding the behavior of objects in motion."
},
{
"id": "genetics",
"title": "Genetics",
"content": "This lesson covers the basics of genetics, including DNA structure, gene expression, and inheritance patterns."
},
{
"id": "waves",
"title": "Waves and Sound",
"content": "This lesson explores the properties of waves, including frequency, wavelength, and amplitude, as well as the principles of sound and hearing."
},
{
"id": "electricity",
"title": "Electricity and Magnetism",
"content": "This lesson covers the fundamentals of electricity and magnetism, including electric circuits, Ohm's law, and electromagnetic waves."
},
{
"id": "chemistry",
"title": "Introduction to Chemistry",
"content": "This lesson provides an overview of basic chemistry concepts, such as atoms, molecules, chemical bonds, and reactions."
},
{
"id": "ecology",
"title": "Ecology and Ecosystems",
"content": "This lesson discusses the interactions between living organisms and their environments, including food webs, energy flow, and ecosystem dynamics."
},
{
"id": "astronomy",
"title": "Introduction to Astronomy",
"content": "This lesson introduces the study of astronomy, including the solar system, stars, galaxies, and the universe."
},
{
"id": "geology",
"title": "Geology and Earth Science",
"content": "This lesson provides an overview of geology, including the structure of the Earth, rock formation, plate tectonics, and natural hazards."
}
]

View File

@ -10,8 +10,8 @@ import 'package:google_generative_ai/google_generative_ai.dart';
const String systemPrmpt = const String systemPrmpt =
"""You are an AI tutor helping students understand topics with help of biometric data. You will be supplied with a json containing data extracted from an EEG device, use that data to modify your approach and help the student learn more effectively. """You are an AI tutor helping students understand topics with help of biometric data. You will be supplied with a json containing data extracted from an EEG device, use that data to modify your approach and help the student learn more effectively.
At the start you will be provided a script with a lesson to cover. At the start you will be provided a script with a lesson to cover.
Keep the analysis and responses short. Keep the analysis short but the lesson can be as long as needed.
Student is 15 years old. Student is 15 years old. You can only interact using text, no videos, images, or audio.
After completing the theoretical part there's a quiz, you can start it yourself at the appropriate time or react to users' request by including <QUIZ_START_TOKEN> at the start of your response After completing the theoretical part there's a quiz, you can start it yourself at the appropriate time or react to users' request by including <QUIZ_START_TOKEN> at the start of your response
@ -31,7 +31,7 @@ enum GeminiStatus { initial, loading, success, error }
// enum MessageType { text, image, audio, video } // enum MessageType { text, image, audio, video }
enum MessageSource { user, agent, app} enum MessageSource { user, agent, app }
class QuizMessage { class QuizMessage {
final String content; final String content;
@ -97,14 +97,18 @@ class Message {
return Content.text(text); return Content.text(text);
case MessageType.quizQuestion: case MessageType.quizQuestion:
String question = text; String question = text;
List<String> options = quizOptions!.map((option) => option.trim()).toList(); List<String> options =
quizOptions!.map((option) => option.trim()).toList();
String answer = options[correctAnswer!]; String answer = options[correctAnswer!];
String formattedQuestion = "$question\n\nOptions:\n${options.map((option) => "- $option").join('\n')}\n\nCorrect Answer: $answer"; String formattedQuestion =
"$question\n\nOptions:\n${options.map((option) => "- $option").join('\n')}\n\nCorrect Answer: $answer";
return Content.model([TextPart(formattedQuestion)]); return Content.model([TextPart(formattedQuestion)]);
case MessageType.quizAnswer: case MessageType.quizAnswer:
String expectedAnswer = quizOptions![correctAnswer!]; String expectedAnswer = quizOptions![correctAnswer!];
bool userCorrect = expectedAnswer == text; bool userCorrect = expectedAnswer == text;
String result = userCorrect ? "User answered correctly with: $text" : "User answered incorrectly with: $text instead of $expectedAnswer"; String result = userCorrect
? "User answered correctly with: $text"
: "User answered incorrectly with: $text instead of $expectedAnswer";
return Content.text(result); return Content.text(result);
default: default:
throw UnsupportedError('Unsupported message type'); throw UnsupportedError('Unsupported message type');
@ -132,6 +136,7 @@ class GeminiState {
bool isQuizMode; bool isQuizMode;
int currentQuizIndex; int currentQuizIndex;
GenerativeModel? model; GenerativeModel? model;
String? lessonId;
GeminiState( GeminiState(
{required this.status, {required this.status,
@ -140,7 +145,8 @@ class GeminiState {
this.quizQuestions, this.quizQuestions,
this.isQuizMode = false, this.isQuizMode = false,
this.currentQuizIndex = -1, this.currentQuizIndex = -1,
this.model}); this.model,
this.lessonId});
GeminiState copyWith({ GeminiState copyWith({
GeminiStatus? status, GeminiStatus? status,
@ -150,6 +156,7 @@ class GeminiState {
bool? isQuizMode, bool? isQuizMode,
int? currentQuizIndex, int? currentQuizIndex,
GenerativeModel? model, GenerativeModel? model,
String? lessonId,
}) { }) {
return GeminiState( return GeminiState(
status: status ?? this.status, status: status ?? this.status,
@ -159,6 +166,7 @@ class GeminiState {
isQuizMode: isQuizMode ?? this.isQuizMode, isQuizMode: isQuizMode ?? this.isQuizMode,
currentQuizIndex: currentQuizIndex ?? this.currentQuizIndex, currentQuizIndex: currentQuizIndex ?? this.currentQuizIndex,
model: model ?? this.model, model: model ?? this.model,
lessonId: lessonId ?? this.lessonId,
); );
} }
@ -172,10 +180,10 @@ class GeminiState {
class GeminiCubit extends Cubit<GeminiState> { class GeminiCubit extends Cubit<GeminiState> {
GeminiCubit() : super(GeminiState.initialState); GeminiCubit() : super(GeminiState.initialState);
void startLesson() async { void startLesson(String lessonId) async {
final quizQuestions = await loadQuizQuestions(); final quizQuestions = await loadQuizQuestions(lessonId);
final String lessonScript = final String lessonScript =
await rootBundle.loadString('assets/lessons/cells.md'); await rootBundle.loadString('assets/lessons/$lessonId.md');
// final String prompt = // final String prompt =
// "Jesteś nauczycielem/chatbotem prowadzącym zajęcia z jednym uczniem. Uczeń ma możliwość zadawania pytań w trakcie, natomiast jesteś odpowiedzialny za prowadzenie lekcji i przedstawienie tematu. Zacznij prowadzić lekcje dla jednego ucznia na podstawie poniszego skryptu:\n$rjp"; // "Jesteś nauczycielem/chatbotem prowadzącym zajęcia z jednym uczniem. Uczeń ma możliwość zadawania pytań w trakcie, natomiast jesteś odpowiedzialny za prowadzenie lekcji i przedstawienie tematu. Zacznij prowadzić lekcje dla jednego ucznia na podstawie poniszego skryptu:\n$rjp";
final String prompt = final String prompt =
@ -206,38 +214,11 @@ class GeminiCubit extends Cubit<GeminiState> {
messages: [lessonScriptMessage], messages: [lessonScriptMessage],
quizQuestions: quizQuestions, quizQuestions: quizQuestions,
isQuizMode: false, isQuizMode: false,
model: model); model: model,
lessonId: lessonId);
emit(initialState); emit(initialState);
sendMessage(""); sendMessage("");
// try {
// final chat = state.model!.startChat(history: [Content.text(prompt)]);
// final stream = chat.sendMessageStream(Content.text(
// "EEG DATA:\n${GetIt.instance<EegService>().state.getJsonString()}\nMessage:\n$prompt"));
// String responseText = '';
// await for (final chunk in stream) {
// responseText += chunk.text ?? '';
// emit(initialState.copyWith(
// status: GeminiStatus.success,
// messages: [
// lessonScriptMessage,
// Message(
// source: MessageSource.agent,
// text: responseText,
// type: MessageType.text)
// ],
// model: model));
// }
// } catch (e) {
// emit(GeminiState(
// status: GeminiStatus.error,
// messages: state.messages,
// error: e.toString(),
// ));
// }
} }
void sendMessage(String prompt) async { void sendMessage(String prompt) async {
@ -261,11 +242,9 @@ class GeminiCubit extends Cubit<GeminiState> {
)); ));
try { try {
final chatHistory = messagesWithoutPrompt final chatHistory =
.map((mess) => mess.toGeminiContent()) messagesWithoutPrompt.map((mess) => mess.toGeminiContent()).toList();
.toList(); final chat = state.model!.startChat(history: chatHistory);
final chat = state.model!.startChat(
history: chatHistory);
final stream = chat.sendMessageStream(Content.text( final stream = chat.sendMessageStream(Content.text(
"EEG DATA:\n${GetIt.instance<EegService>().state.getJsonString()}\nUser message:\n$prompt")); "EEG DATA:\n${GetIt.instance<EegService>().state.getJsonString()}\nUser message:\n$prompt"));
@ -298,10 +277,10 @@ class GeminiCubit extends Cubit<GeminiState> {
} }
} }
if (responseText.contains(QUIZ_START_TOKEN) || analysisData.contains(QUIZ_START_TOKEN)) { if (responseText.contains(QUIZ_START_TOKEN) ||
emit(state.copyWith( analysisData.contains(QUIZ_START_TOKEN)) {
status: GeminiStatus.success, emit(state.copyWith(
messages: messagesWithPrompt)); status: GeminiStatus.success, messages: messagesWithPrompt));
enterQuizMode(); enterQuizMode();
} }
} catch (e) { } catch (e) {
@ -334,9 +313,9 @@ class GeminiCubit extends Cubit<GeminiState> {
askNextQuizQuestion(); askNextQuizQuestion();
} }
Future<List<QuizQuestion>> loadQuizQuestions() async { Future<List<QuizQuestion>> loadQuizQuestions(String lessonId) async {
final String quizJson = final String quizJson =
await rootBundle.loadString('assets/lessons/cells.json'); await rootBundle.loadString('assets/lessons/$lessonId.json');
final List<dynamic> quizData = json.decode(quizJson); final List<dynamic> quizData = json.decode(quizJson);
return quizData return quizData
@ -356,19 +335,22 @@ class GeminiCubit extends Cubit<GeminiState> {
void askNextQuizQuestion() { void askNextQuizQuestion() {
var currentQuizIndex = state.currentQuizIndex + 1; var currentQuizIndex = state.currentQuizIndex + 1;
if (currentQuizIndex >= state.quizQuestions!.length) { if (currentQuizIndex >= state.quizQuestions!.length ||
// if (currentQuizIndex >= 2) { currentQuizIndex >= 2) {
// if (currentQuizIndex >= 2) {
List<Message> messagesWithPrompt = state.messages + List<Message> messagesWithPrompt = state.messages +
[ [
Message( Message(
text: "Quiz is over. Write a summary of user's performance.", text: "Quiz is over. Write a summary of user's performance.",
type: MessageType.text, type: MessageType.text,
source: MessageSource.app) source: MessageSource.app)
]; ];
// Quiz is over // Quiz is over
emit(state.copyWith(isQuizMode: false, currentQuizIndex: 0, emit(state.copyWith(
messages: messagesWithPrompt)); isQuizMode: false,
currentQuizIndex: 0,
messages: messagesWithPrompt));
// Send a message to Gemini to end the quiz // Send a message to Gemini to end the quiz
sendMessage(""); sendMessage("");
@ -398,7 +380,6 @@ class GeminiCubit extends Cubit<GeminiState> {
} }
void checkAnswer(int answerIndex) { void checkAnswer(int answerIndex) {
print("checkAnswer $answerIndex");
passAnswerToGemini(answerIndex); passAnswerToGemini(answerIndex);
} }

15
lib/lesson.dart Normal file
View File

@ -0,0 +1,15 @@
class Lesson {
final String id;
final String title;
final String content;
Lesson({required this.id, required this.title, required this.content});
factory Lesson.fromJson(Map<String, dynamic> json) {
return Lesson(
id: json['id'],
title: json['title'],
content: json['content'],
);
}
}

View File

@ -1,6 +1,6 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:gemini_app/eeg/eeg_service.dart'; import 'package:gemini_app/eeg/eeg_service.dart';
import 'package:gemini_app/screens/gemini_chat_screen.dart'; import 'package:gemini_app/screens/eeg_calibration_screen.dart';
import 'package:get_it/get_it.dart'; import 'package:get_it/get_it.dart';
void main() { void main() {
@ -11,119 +11,36 @@ void main() {
class MyApp extends StatelessWidget { class MyApp extends StatelessWidget {
const MyApp({super.key}); const MyApp({super.key});
// This widget is the root of your application.
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return MaterialApp( return MaterialApp(
title: 'Flutter Demo', title: 'MindEasy',
theme: ThemeData( theme: ThemeData(
// This is the theme of your application.
//
// TRY THIS: Try running your application with "flutter run". You'll see
// the application has a purple toolbar. Then, without quitting the app,
// try changing the seedColor in the colorScheme below to Colors.green
// and then invoke "hot reload" (save your changes or press the "hot
// reload" button in a Flutter-supported IDE, or press "r" if you used
// the command line to start the app).
//
// Notice that the counter didn't reset back to zero; the application
// state is not lost during the reload. To reset the state, use hot
// restart instead.
//
// This works for code too, not just values: Most code changes can be
// tested with just a hot reload.
colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple), colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
useMaterial3: true, useMaterial3: true,
), ),
home: const GeminiScreen(), home: const MindWanderScreen(),
); );
} }
} }
class MyHomePage extends StatefulWidget { Route createSmoothRoute(Widget page) {
const MyHomePage({super.key, required this.title}); return PageRouteBuilder(
pageBuilder: (context, animation, secondaryAnimation) => page,
transitionsBuilder: (context, animation, secondaryAnimation, child) {
const begin = Offset(1.0, 0.0);
const end = Offset.zero;
final tween = Tween(begin: begin, end: end)
.chain(CurveTween(curve: Curves.easeInOut));
// This widget is the home page of your application. It is stateful, meaning return SlideTransition(
// that it has a State object (defined below) that contains fields that affect position: animation.drive(tween),
// how it looks. child: child,
);
// This class is the configuration for the state. It holds the values (in this },
// case the title) provided by the parent (in this case the App widget) and transitionDuration:
// used by the build method of the State. Fields in a Widget subclass are Duration(milliseconds: 300), // Adjust the duration to your preference
// always marked "final". reverseTransitionDuration:
Duration(milliseconds: 300), // Adjust for reverse transition too
final String title; );
@override
State<MyHomePage> createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
int _counter = 0;
void _incrementCounter() {
setState(() {
// This call to setState tells the Flutter framework that something has
// changed in this State, which causes it to rerun the build method below
// so that the display can reflect the updated values. If we changed
// _counter without calling setState(), then the build method would not be
// called again, and so nothing would appear to happen.
_counter++;
});
}
@override
Widget build(BuildContext context) {
// This method is rerun every time setState is called, for instance as done
// by the _incrementCounter method above.
//
// The Flutter framework has been optimized to make rerunning build methods
// fast, so that you can just rebuild anything that needs updating rather
// than having to individually change instances of widgets.
return Scaffold(
appBar: AppBar(
// TRY THIS: Try changing the color here to a specific color (to
// Colors.amber, perhaps?) and trigger a hot reload to see the AppBar
// change color while the other colors stay the same.
backgroundColor: Theme.of(context).colorScheme.inversePrimary,
// Here we take the value from the MyHomePage object that was created by
// the App.build method, and use it to set our appbar title.
title: Text(widget.title),
),
body: Center(
// Center is a layout widget. It takes a single child and positions it
// in the middle of the parent.
child: Column(
// Column is also a layout widget. It takes a list of children and
// arranges them vertically. By default, it sizes itself to fit its
// children horizontally, and tries to be as tall as its parent.
//
// Column has various properties to control how it sizes itself and
// how it positions its children. Here we use mainAxisAlignment to
// center the children vertically; the main axis here is the vertical
// axis because Columns are vertical (the cross axis would be
// horizontal).
//
// TRY THIS: Invoke "debug painting" (choose the "Toggle Debug Paint"
// action in the IDE, or press "p" in the console), to see the
// wireframe for each widget.
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
const Text(
'You have pushed the button this many times:',
),
Text(
'$_counter',
style: Theme.of(context).textTheme.headlineMedium,
),
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: _incrementCounter,
tooltip: 'Increment',
child: const Icon(Icons.add),
), // This trailing comma makes auto-formatting nicer for build methods.
);
}
} }

View File

@ -0,0 +1,136 @@
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:gemini_app/main.dart';
import 'package:gemini_app/screens/lesson_list_screen.dart';
class MindWanderScreen extends StatefulWidget {
const MindWanderScreen({super.key});
@override
MindWanderScreenState createState() => MindWanderScreenState();
}
class MindWanderScreenState extends State<MindWanderScreen>
with TickerProviderStateMixin {
late AnimationController _controller;
late Animation<double> _animation;
int _secondsRemaining = 60;
Timer? _timer;
@override
void initState() {
super.initState();
_controller = AnimationController(
duration: const Duration(seconds: 2),
vsync: this,
)..repeat(reverse: true);
_animation = Tween<double>(begin: 0.0, end: 1.0).animate(_controller);
// _startTimer();
}
void _startTimer() {
setState(() {
_timer = Timer.periodic(const Duration(seconds: 1), (timer) {
if (_secondsRemaining > 0) {
setState(() {
_secondsRemaining--;
});
} else {
_timer!.cancel();
// Perform any action when the timer reaches zero
_skipCalibration();
}
});
});
}
@override
void dispose() {
_controller.dispose();
_timer?.cancel();
super.dispose();
}
void _skipCalibration() {
Navigator.push(context, createSmoothRoute(LessonListScreen()));
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('EEG Calibration'),
),
body: Stack(
children: [
// Background Animation
AnimatedBuilder(
animation: _animation,
builder: (context, child) {
return Opacity(
opacity: _animation.value,
child: Container(
decoration: BoxDecoration(
gradient: LinearGradient(
begin: Alignment.topLeft,
end: Alignment.bottomRight,
colors: [
Colors.blue.withOpacity(0.5),
Colors.green.withOpacity(0.5),
],
transform:
GradientRotation(_animation.value * 2 * 3.1415926535),
),
),
),
);
},
),
// Main Content
Padding(
padding: const EdgeInsets.all(8.0),
child: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Text(
"""When you're ready, press the start button and start mind wandering by letting your thoughts drift to your favorite places or stories while you sit quietly""",
style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold),
textAlign: TextAlign.center,
),
const SizedBox(height: 20),
_timer == null
? ElevatedButton(
onPressed: _startTimer,
child: const Text('Start'),
)
: Text(
'Time remaining: $_secondsRemaining seconds',
style: const TextStyle(fontSize: 18),
),
],
),
),
),
Positioned(
bottom: 16.0,
right: 16.0,
child: ElevatedButton(
onPressed: _skipCalibration,
child: const Text('Skip'),
),
),
],
),
);
}
}
void main() {
runApp(MaterialApp(
home: MindWanderScreen(),
));
}

View File

@ -2,11 +2,14 @@ import 'package:flutter/material.dart';
import 'package:gemini_app/bloc/gemini_state.dart'; import 'package:gemini_app/bloc/gemini_state.dart';
import 'package:flutter_markdown/flutter_markdown.dart'; import 'package:flutter_markdown/flutter_markdown.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:gemini_app/config.dart';
import 'package:gemini_app/eeg/eeg_service.dart'; import 'package:gemini_app/eeg/eeg_service.dart';
import 'package:get_it/get_it.dart'; import 'package:get_it/get_it.dart';
class GeminiScreen extends StatelessWidget { class GeminiScreen extends StatelessWidget {
const GeminiScreen({super.key}); const GeminiScreen({super.key, required this.lessonId});
final String lessonId;
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
@ -14,13 +17,17 @@ class GeminiScreen extends StatelessWidget {
providers: [ providers: [
BlocProvider(create: (context) => GeminiCubit()), BlocProvider(create: (context) => GeminiCubit()),
], ],
child: const GeminiChat(), child: GeminiChat(
lessonId: lessonId,
),
); );
} }
} }
class GeminiChat extends StatefulWidget { class GeminiChat extends StatefulWidget {
const GeminiChat({super.key}); const GeminiChat({super.key, required this.lessonId});
final String lessonId;
@override @override
GeminiChatState createState() => GeminiChatState(); GeminiChatState createState() => GeminiChatState();
@ -54,7 +61,7 @@ class GeminiChatState extends State<GeminiChat> {
} }
void _startConversation() async { void _startConversation() async {
context.read<GeminiCubit>().startLesson(); context.read<GeminiCubit>().startLesson(widget.lessonId);
} }
void _sendMessage() async { void _sendMessage() async {
@ -93,20 +100,22 @@ class GeminiChatState extends State<GeminiChat> {
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
Card( isDebug
child: Padding( ? Card(
padding: const EdgeInsets.all(16.0), child: Padding(
child: Column( padding: const EdgeInsets.all(16.0),
crossAxisAlignment: CrossAxisAlignment.start, child: Column(
children: [ crossAxisAlignment: CrossAxisAlignment.start,
Text( children: [
'Mind Wandering: ${_eegService.state.mindWandering.toStringAsFixed(2)}'), Text(
Text( 'Mind Wandering: ${_eegService.state.mindWandering.toStringAsFixed(2)}'),
'Focus: ${_eegService.state.focus.toStringAsFixed(2)}'), Text(
], 'Focus: ${_eegService.state.focus.toStringAsFixed(2)}'),
), ],
), ),
), ),
)
: Container(),
const SizedBox(height: 16), const SizedBox(height: 16),
Expanded( Expanded(
child: BlocBuilder<GeminiCubit, GeminiState>( child: BlocBuilder<GeminiCubit, GeminiState>(
@ -145,30 +154,6 @@ class GeminiChatState extends State<GeminiChat> {
const SizedBox(height: 16), const SizedBox(height: 16),
Row( Row(
children: [ children: [
// BlocBuilder<GeminiCubit, GeminiState>(
// builder: (context, state) {
// return state.isQuizMode
// ? Container()
// : Expanded(
// child: ElevatedButton(
// onPressed: _sendMessage,
// child: const Text('Send'),
// ),
// );
// },
// ),
ElevatedButton(
onPressed: _sendMessage,
child: const Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(Icons.send),
SizedBox(width: 3),
Text('Send'),
],
),
),
ElevatedButton( ElevatedButton(
onPressed: _resetConversation, onPressed: _resetConversation,
child: const Row( child: const Row(
@ -181,27 +166,29 @@ class GeminiChatState extends State<GeminiChat> {
), ),
), ),
ElevatedButton( ElevatedButton(
onPressed: _toggleEegState, onPressed: _sendMessage,
child: const Row( child: const Row(
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
children: [ children: [
Icon(Icons.toggle_on), Icon(Icons.send),
SizedBox(width: 3), SizedBox(width: 3),
Text('Toggle State'), Text('Send'),
], ],
), ),
), ),
// ElevatedButton( isDebug
// onPressed: _enterQuizMode, ? ElevatedButton(
// child: const Row( onPressed: _toggleEegState,
// mainAxisAlignment: MainAxisAlignment.center, child: const Row(
// children: [ mainAxisAlignment: MainAxisAlignment.center,
// Icon(Icons.quiz), children: [
// SizedBox(width: 8), Icon(Icons.toggle_on),
// Text('Start Quiz'), SizedBox(width: 3),
// ], Text('Toggle State'),
// ), ],
// ), ),
)
: Container(),
], ],
), ),
], ],
@ -226,8 +213,8 @@ class GeminiChatState extends State<GeminiChat> {
); );
} }
if (state.messages[index].type == MessageType.lessonScript || if (state.messages[index].type == MessageType.lessonScript ||
state.messages[index].source == MessageSource.app) { state.messages[index].source == MessageSource.app) {
// skip // skip
return Container(); return Container();
} }
@ -255,7 +242,8 @@ class GeminiChatState extends State<GeminiChat> {
), ),
); );
} else if (message.type == MessageType.quizAnswer) { } else if (message.type == MessageType.quizAnswer) {
bool correct = message.text == message.quizOptions![message.correctAnswer!]; bool correct =
message.text == message.quizOptions![message.correctAnswer!];
var text = Text( var text = Text(
correct correct
? "Correct!" ? "Correct!"

View File

@ -0,0 +1,60 @@
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:gemini_app/lesson.dart';
import 'package:gemini_app/main.dart';
import 'package:gemini_app/screens/gemini_chat_screen.dart';
class LessonListScreen extends StatefulWidget {
@override
_LessonListScreenState createState() => _LessonListScreenState();
}
class _LessonListScreenState extends State<LessonListScreen> {
List<Lesson> lessons = [];
@override
void initState() {
super.initState();
loadLessons();
}
Future<void> loadLessons() async {
final String response =
await rootBundle.loadString('assets/lessons/lessons.json');
final List<dynamic> data = json.decode(response);
setState(() {
lessons = data.map((json) => Lesson.fromJson(json)).toList();
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Available Lessons'),
),
body: ListView.separated(
itemCount: lessons.length,
itemBuilder: (context, index) {
final lesson = lessons[index];
return ListTile(
title: Text(lesson.title),
subtitle: Text(lesson.content),
onTap: () {
Navigator.push(
context,
createSmoothRoute(
GeminiScreen(lessonId: lesson.id.toString()),
),
);
},
);
},
separatorBuilder: (context, index) {
return Divider(); // or any other widget you want to use as a separator
},
),
);
}
}