From fc9ab973d2bf93c6d18cc9a717a1cfcc0553b9f6 Mon Sep 17 00:00:00 2001 From: Dawid Pietrykowski Date: Fri, 9 Aug 2024 01:03:08 +0200 Subject: [PATCH] Fixed texts, added auto scroll --- lib/bloc/gemini_state.dart | 8 +- lib/config.dart | 2 +- lib/main.dart | 8 +- lib/screens/eeg_calibration_screen.dart | 29 ++--- lib/screens/gemini_chat_screen.dart | 98 ++++++++++++---- lib/screens/lesson_list_screen.dart | 63 +++++++---- macos/Flutter/GeneratedPluginRegistrant.swift | 2 + pubspec.lock | 106 +++++++++++++++++- pubspec.yaml | 2 + 9 files changed, 255 insertions(+), 63 deletions(-) diff --git a/lib/bloc/gemini_state.dart b/lib/bloc/gemini_state.dart index 63eacdd..b110b09 100644 --- a/lib/bloc/gemini_state.dart +++ b/lib/bloc/gemini_state.dart @@ -12,6 +12,7 @@ const String systemPrmpt = At the start you will be provided a script with a lesson to cover. Keep the analysis short but the lesson can be as long as needed. Student is 15 years old. You can only interact using text, no videos, images, or audio. +Make the lesson more in the style of a lecture, with you explaining the topic and the student asking questions. 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 at the start of your response @@ -81,11 +82,6 @@ class Message { } Content toGeminiContent() { - // if (source == MessageSource.user || type == MessageType.lessonScript) { - // return Content.text(text); - // } else { - // return Content.model([TextPart(text)]); - // } switch (type) { case MessageType.text: if (source == MessageSource.user) { @@ -187,7 +183,7 @@ class GeminiCubit extends Cubit { // 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"; final String prompt = - "You are a teacher/chatbot conducting a class with one student. The student has the ability to ask questions during the lesson, while you are responsible for leading the class and presenting the topic. Start conducting the lesson for one student based on the script below:\n$lessonScript"; + "You are a teacher/chatbot conducting a class with one student. The student has the ability to ask questions during the lesson, while you are responsible for leading the class and presenting the topic. Start conducting the lecture for one student based on the script below:\n$lessonScript"; final safetySettings = [ SafetySetting(HarmCategory.harassment, HarmBlockThreshold.none), diff --git a/lib/config.dart b/lib/config.dart index 08606f4..fa541a8 100644 --- a/lib/config.dart +++ b/lib/config.dart @@ -1 +1 @@ -const bool isDebug = true; +const bool isDebug = false; diff --git a/lib/main.dart b/lib/main.dart index bf42256..3d8345b 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -2,6 +2,7 @@ import 'package:flutter/material.dart'; import 'package:gemini_app/eeg/eeg_service.dart'; import 'package:gemini_app/screens/eeg_calibration_screen.dart'; import 'package:get_it/get_it.dart'; +import 'package:google_fonts/google_fonts.dart'; void main() { GetIt.I.registerSingleton(EegService()); @@ -16,8 +17,11 @@ class MyApp extends StatelessWidget { return MaterialApp( title: 'MindEasy', theme: ThemeData( - colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple), - useMaterial3: true, + brightness: Brightness.dark, + primarySwatch: Colors.teal, + textTheme: GoogleFonts.openSansTextTheme( + Theme.of(context).textTheme, + ), ), home: const MindWanderScreen(), ); diff --git a/lib/screens/eeg_calibration_screen.dart b/lib/screens/eeg_calibration_screen.dart index e3f1de1..1f6c0b0 100644 --- a/lib/screens/eeg_calibration_screen.dart +++ b/lib/screens/eeg_calibration_screen.dart @@ -70,19 +70,22 @@ class MindWanderScreenState extends State 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), + return Container( + color: Colors.white, + child: 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), + ), ), ), ), diff --git a/lib/screens/gemini_chat_screen.dart b/lib/screens/gemini_chat_screen.dart index 596414e..388d371 100644 --- a/lib/screens/gemini_chat_screen.dart +++ b/lib/screens/gemini_chat_screen.dart @@ -5,6 +5,7 @@ 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'; +import 'package:google_fonts/google_fonts.dart'; class GeminiScreen extends StatelessWidget { const GeminiScreen({super.key, required this.lessonId}); @@ -35,8 +36,9 @@ class GeminiChat extends StatefulWidget { class GeminiChatState extends State { final _textController = TextEditingController(); - bool _quizMode = false; // Add this line + bool _quizMode = false; final EegService _eegService = GetIt.instance(); + final ScrollController _scrollController = ScrollController(); @override void initState() { @@ -57,6 +59,7 @@ class GeminiChatState extends State { @override void dispose() { _eegService.stopPolling(); + _scrollController.dispose(); // Add this line super.dispose(); } @@ -67,6 +70,20 @@ class GeminiChatState extends State { void _sendMessage() async { context.read().sendMessage(_textController.text); _textController.clear(); + + WidgetsBinding.instance.addPostFrameCallback((_) { + _scrollToBottom(); + }); + } + + void _scrollToBottom() { + if (_scrollController.hasClients) { + _scrollController.animateTo( + _scrollController.position.maxScrollExtent, + duration: const Duration(milliseconds: 300), + curve: Curves.easeOut, + ); + } } void _toggleEegState() { @@ -108,9 +125,21 @@ class GeminiChatState extends State { crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( - 'Mind Wandering: ${_eegService.state.mindWandering.toStringAsFixed(2)}'), + 'Mind Wandering: ${_eegService.state.mindWandering.toStringAsFixed(2)}', + style: GoogleFonts.roboto( + textStyle: const TextStyle( + fontSize: 18, + fontWeight: FontWeight.bold, + color: Colors.white), + )), Text( - 'Focus: ${_eegService.state.focus.toStringAsFixed(2)}'), + 'Focus: ${_eegService.state.focus.toStringAsFixed(2)}', + style: GoogleFonts.roboto( + textStyle: const TextStyle( + fontSize: 18, + fontWeight: FontWeight.bold, + color: Colors.white), + )), ], ), ), @@ -120,6 +149,10 @@ class GeminiChatState extends State { Expanded( child: BlocBuilder( builder: (context, state) { + WidgetsBinding.instance.addPostFrameCallback((_) { + _scrollToBottom(); + }); + if (state.status == GeminiStatus.loading) { return buildChatList(state, loading: true); } else if (state.status == GeminiStatus.error) { @@ -137,15 +170,18 @@ class GeminiChatState extends State { ? Container() : TextField( controller: _textController, + style: TextStyle(color: Colors.white), decoration: InputDecoration( hintText: 'Enter your message', + hintStyle: TextStyle(color: Colors.grey), border: OutlineInputBorder( - borderRadius: BorderRadius.circular(10.0), + borderRadius: BorderRadius.circular(20.0), + borderSide: BorderSide.none, ), filled: true, - fillColor: Colors.grey[200], + // fillColor: Colors.grey[200], contentPadding: const EdgeInsets.symmetric( - horizontal: 16.0, vertical: 12.0), + horizontal: 20.0, vertical: 14.0), ), onSubmitted: (_) => _sendMessage(), ); @@ -153,6 +189,7 @@ class GeminiChatState extends State { ), const SizedBox(height: 16), Row( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: [ ElevatedButton( onPressed: _resetConversation, @@ -176,19 +213,18 @@ class GeminiChatState extends State { ], ), ), - isDebug - ? ElevatedButton( - onPressed: _toggleEegState, - child: const Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Icon(Icons.toggle_on), - SizedBox(width: 3), - Text('Toggle State'), - ], - ), - ) - : Container(), + if (isDebug) + ElevatedButton( + onPressed: _toggleEegState, + child: const Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Icon(Icons.toggle_on), + SizedBox(width: 3), + Text('Toggle State'), + ], + ), + ) ], ), ], @@ -200,6 +236,7 @@ class GeminiChatState extends State { ListView buildChatList(GeminiState state, {bool loading = false}) { return ListView.builder( itemCount: state.messages.length + (loading ? 1 : 0), + controller: _scrollController, itemBuilder: (context, index) { if (index == state.messages.length && loading) { return const Card( @@ -230,7 +267,16 @@ class GeminiChatState extends State { child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - MarkdownBody(data: text), + MarkdownBody( + data: text, + styleSheet: MarkdownStyleSheet( + p: GoogleFonts.roboto( + textStyle: const TextStyle( + fontSize: 18, + color: Colors.white, + fontWeight: FontWeight.w300)), + )), + const SizedBox(height: 16), ...message.quizOptions!.asMap().entries.map((entry) { return ElevatedButton( onPressed: () => {_checkAnswer(entry.key)}, @@ -271,7 +317,17 @@ class GeminiChatState extends State { crossAxisAlignment: message.source == MessageSource.agent ? CrossAxisAlignment.start : CrossAxisAlignment.end, - children: [MarkdownBody(data: text)], + children: [ + MarkdownBody( + data: text, + styleSheet: MarkdownStyleSheet( + p: GoogleFonts.roboto( + textStyle: const TextStyle( + fontSize: 18, + color: Colors.white, + fontWeight: FontWeight.w300)), + )) + ], ), ), ); diff --git a/lib/screens/lesson_list_screen.dart b/lib/screens/lesson_list_screen.dart index ad882b2..941b5e1 100644 --- a/lib/screens/lesson_list_screen.dart +++ b/lib/screens/lesson_list_screen.dart @@ -1,9 +1,11 @@ import 'dart:convert'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; +import 'package:flutter_staggered_animations/flutter_staggered_animations.dart'; import 'package:gemini_app/lesson.dart'; import 'package:gemini_app/main.dart'; import 'package:gemini_app/screens/gemini_chat_screen.dart'; +import 'package:google_fonts/google_fonts.dart'; class LessonListScreen extends StatefulWidget { @override @@ -34,26 +36,49 @@ class _LessonListScreenState extends State { 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()), + body: AnimationLimiter( + child: ListView.separated( + itemCount: lessons.length, + separatorBuilder: (context, index) => const Divider(), + itemBuilder: (context, index) { + final lesson = lessons[index]; + return AnimationConfiguration.staggeredList( + position: index, + duration: const Duration(milliseconds: 375), + child: SlideAnimation( + verticalOffset: 50.0, + child: FadeInAnimation( + child: Card( + elevation: 4, + margin: + const EdgeInsets.symmetric(vertical: 8, horizontal: 16), + child: ListTile( + title: Text( + lesson.title, + style: GoogleFonts.roboto( + textStyle: const TextStyle( + fontSize: 18, fontWeight: FontWeight.bold), + ), + ), + subtitle: Text( + lesson.content, + style: GoogleFonts.roboto(fontSize: 14), + ), + 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 - }, + ), + ); + }, + ), ), ); } diff --git a/macos/Flutter/GeneratedPluginRegistrant.swift b/macos/Flutter/GeneratedPluginRegistrant.swift index cccf817..e777c67 100644 --- a/macos/Flutter/GeneratedPluginRegistrant.swift +++ b/macos/Flutter/GeneratedPluginRegistrant.swift @@ -5,6 +5,8 @@ import FlutterMacOS import Foundation +import path_provider_foundation func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { + PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin")) } diff --git a/pubspec.lock b/pubspec.lock index 3da9757..3ba9216 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -57,6 +57,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.18.0" + crypto: + dependency: transitive + description: + name: crypto + sha256: ff625774173754681d66daaf4a448684fb04b78f902da9cb3d308c19cc5e8bab + url: "https://pub.dev" + source: hosted + version: "3.0.3" cupertino_icons: dependency: "direct main" description: @@ -73,6 +81,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.3.1" + ffi: + dependency: transitive + description: + name: ffi + sha256: "493f37e7df1804778ff3a53bd691d8692ddf69702cf4c1c1096a2e41b4779e21" + url: "https://pub.dev" + source: hosted + version: "2.1.2" flutter: dependency: "direct main" description: flutter @@ -102,6 +118,14 @@ packages: url: "https://pub.dev" source: hosted version: "0.7.3" + flutter_staggered_animations: + dependency: "direct main" + description: + name: flutter_staggered_animations + sha256: "81d3c816c9bb0dca9e8a5d5454610e21ffb068aedb2bde49d2f8d04f75538351" + url: "https://pub.dev" + source: hosted + version: "1.1.1" flutter_test: dependency: "direct dev" description: flutter @@ -115,6 +139,14 @@ packages: url: "https://pub.dev" source: hosted version: "7.7.0" + google_fonts: + dependency: "direct main" + description: + name: google_fonts + sha256: b1ac0fe2832c9cc95e5e88b57d627c5e68c223b9657f4b96e1487aa9098c7b82 + url: "https://pub.dev" + source: hosted + version: "6.2.1" google_generative_ai: dependency: "direct main" description: @@ -219,6 +251,70 @@ packages: url: "https://pub.dev" source: hosted version: "1.9.0" + path_provider: + dependency: transitive + description: + name: path_provider + sha256: fec0d61223fba3154d87759e3cc27fe2c8dc498f6386c6d6fc80d1afdd1bf378 + url: "https://pub.dev" + source: hosted + version: "2.1.4" + path_provider_android: + dependency: transitive + description: + name: path_provider_android + sha256: "490539678396d4c3c0b06efdaab75ae60675c3e0c66f72bc04c2e2c1e0e2abeb" + url: "https://pub.dev" + source: hosted + version: "2.2.9" + path_provider_foundation: + dependency: transitive + description: + name: path_provider_foundation + sha256: f234384a3fdd67f989b4d54a5d73ca2a6c422fa55ae694381ae0f4375cd1ea16 + url: "https://pub.dev" + source: hosted + version: "2.4.0" + path_provider_linux: + dependency: transitive + description: + name: path_provider_linux + sha256: f7a1fe3a634fe7734c8d3f2766ad746ae2a2884abe22e241a8b301bf5cac3279 + url: "https://pub.dev" + source: hosted + version: "2.2.1" + path_provider_platform_interface: + dependency: transitive + description: + name: path_provider_platform_interface + sha256: "88f5779f72ba699763fa3a3b06aa4bf6de76c8e5de842cf6f29e2e06476c2334" + url: "https://pub.dev" + source: hosted + version: "2.1.2" + path_provider_windows: + dependency: transitive + description: + name: path_provider_windows + sha256: bd6f00dbd873bfb70d0761682da2b3a2c2fccc2b9e84c495821639601d81afe7 + url: "https://pub.dev" + source: hosted + version: "2.3.0" + platform: + dependency: transitive + description: + name: platform + sha256: "9b71283fc13df574056616011fb138fd3b793ea47cc509c189a6c3fa5f8a1a65" + url: "https://pub.dev" + source: hosted + version: "3.1.5" + plugin_platform_interface: + dependency: transitive + description: + name: plugin_platform_interface + sha256: "4820fbfdb9478b1ebae27888254d445073732dae3d6ea81f0b7e06d5dedc3f02" + url: "https://pub.dev" + source: hosted + version: "2.1.8" provider: dependency: transitive description: @@ -312,6 +408,14 @@ packages: url: "https://pub.dev" source: hosted version: "0.5.1" + xdg_directories: + dependency: transitive + description: + name: xdg_directories + sha256: faea9dee56b520b55a566385b84f2e8de55e7496104adada9962e0bd11bcff1d + url: "https://pub.dev" + source: hosted + version: "1.0.4" sdks: dart: ">=3.4.1 <4.0.0" - flutter: ">=3.19.0" + flutter: ">=3.22.0" diff --git a/pubspec.yaml b/pubspec.yaml index d08c6c8..52fceca 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -44,6 +44,8 @@ dependencies: get_it: ^7.7.0 http: ^1.2.2 bloc: ^8.1.4 + flutter_staggered_animations: ^1.1.1 + google_fonts: ^6.2.1 dev_dependencies: flutter_test: