From 10f684aa478f6bcf6b10aa3d8d15c6409002556d Mon Sep 17 00:00:00 2001 From: Rhon Date: Sun, 3 Mar 2024 22:43:49 +0800 Subject: [PATCH 1/6] Better navigation, Timer functioning, on progress latex --- d | 79 +++++++++ lib/core/models/firestore_model.dart | 1 + lib/core/models/question_model.dart | 29 +++- .../pages/finished_quiz_page.dart | 31 ++-- .../presentation/pages/quiz_page.dart | 92 ++++++----- .../presentation/pages/ready_quiz_page.dart | 15 +- .../state_management/quiz/quiz_bloc.dart | 23 +-- .../state_management/quiz/quiz_event.dart | 10 -- .../state_management/quiz/quiz_state.dart | 6 - .../ready_quiz/ready_quiz_cubit.dart | 17 -- .../ready_quiz/ready_quiz_state.dart | 22 --- .../confirm_cancel_quiz_dialogue.dart | 29 ++++ .../confirm_cancel_quiz_display.dart | 44 ----- .../confirm_submit_quiz_dialogue.dart | 52 ++++++ .../confirm_submit_quiz_display.dart | 66 -------- .../confirm_timeout_quiz_dialogue.dart | 47 ++++++ .../confirm_timeout_quiz_display.dart | 41 ----- .../quiz_page_widgets/timer_display.dart | 19 ++- .../question_card_choices_display.dart | 1 + .../choose_subject_confirm_display.dart | 48 ------ .../choose_subject_display.dart | 28 +--- .../subject_chosen_dialogue.dart | 33 ++++ .../quiz_upload_repository_impl.dart | 1 + .../pages/upload_questions_page.dart | 5 +- .../pages/upload_success_page.dart | 14 +- .../quiz_input/quiz_input_cubit.dart | 9 +- .../quiz_input/quiz_input_state.dart | 8 + .../quiz_input_page/quiz_input_page_bloc.dart | 15 +- .../quiz_input_page_event.dart | 2 + .../quiz_input_page_state.dart | 2 - .../add_or_submit_display.dart | 9 +- ...> confirm_cancel_quiz_input_dialogue.dart} | 0 .../confirm_submit_quiz_input_dialogue.dart | 45 ++++++ .../confirm_submit_quiz_input_display.dart | 53 ------ .../question_input_card_display.dart | 54 ++++++- .../question_input_tab_display.dart | 151 ++++++++++++++++++ .../upload_complete_display.dart | 23 ++- lib/router/app_router.dart | 29 +++- 38 files changed, 671 insertions(+), 482 deletions(-) create mode 100644 d delete mode 100644 lib/features/quiz_mode/presentation/state_management/ready_quiz/ready_quiz_cubit.dart delete mode 100644 lib/features/quiz_mode/presentation/state_management/ready_quiz/ready_quiz_state.dart create mode 100644 lib/features/quiz_mode/presentation/widgets/quiz_page_widgets/confirm_cancel_quiz_dialogue.dart delete mode 100644 lib/features/quiz_mode/presentation/widgets/quiz_page_widgets/confirm_cancel_quiz_display.dart create mode 100644 lib/features/quiz_mode/presentation/widgets/quiz_page_widgets/confirm_submit_quiz_dialogue.dart delete mode 100644 lib/features/quiz_mode/presentation/widgets/quiz_page_widgets/confirm_submit_quiz_display.dart create mode 100644 lib/features/quiz_mode/presentation/widgets/quiz_page_widgets/confirm_timeout_quiz_dialogue.dart delete mode 100644 lib/features/quiz_mode/presentation/widgets/quiz_page_widgets/confirm_timeout_quiz_display.dart delete mode 100644 lib/features/quiz_mode/presentation/widgets/ready_quiz_page_widgets/choose_subject_confirm_display.dart create mode 100644 lib/features/quiz_mode/presentation/widgets/ready_quiz_page_widgets/subject_chosen_dialogue.dart rename lib/features/quiz_upload/presentation/widgets/quiz_input_page_widgets/{confirm_cancel_quiz_input_display.dart => confirm_cancel_quiz_input_dialogue.dart} (100%) create mode 100644 lib/features/quiz_upload/presentation/widgets/quiz_input_page_widgets/confirm_submit_quiz_input_dialogue.dart delete mode 100644 lib/features/quiz_upload/presentation/widgets/quiz_input_page_widgets/confirm_submit_quiz_input_display.dart create mode 100644 lib/features/quiz_upload/presentation/widgets/quiz_input_widgets/question_input_tab_display.dart diff --git a/d b/d new file mode 100644 index 00000000..cfb13278 --- /dev/null +++ b/d @@ -0,0 +1,79 @@ +86b3463 (HEAD -> dev-led, origin/main, origin/dev-led, origin/HEAD, main) style: improve user profile UI/UX +3b59a19 feat: reset password via email +0eb834e dev: very rough release candidate v0.0.1 +a8ca87d chore: sync git +ea5178b Quiz mode and upload feature (#28) +de56798 dev: basic profile screen +59b56f3 dev: auth sign-out option +d3a2d13 dev: backend code for email & password authentication +cc03ebc dev: other experimental rich text editor using Markdown (w/ latex support) +930ece0 style: improve layout and visual elements in email verification screen +d5c7456 dev: backend code for email verification +ed8ace7 dev: add working BE code for creating a user +3cb413d dev: add UserEntity +3b2e6d8 (origin/dev) chore: daily git sync +40533dd dev: explore the viability of flutter_quill text editor (experimental) +9558e2f deps: add flutter_quill and set proper configurations +cd06c58 style: add signup screen and validators +edb7f15 dev: git daily sync +e97d12f style: add reset password screen, password reset email confirmation, and validators +7dfe0ec style: add login screen +cbb8640 refactor: convert Entity and Params interfaces to base classes +cd01dd1 dev: add custom Failure classes +dbe2873 refactor: improve custom Exception classes +f68d955 dev: add utility exception classes (auth and db) +c05e662 dev: setup the folder structure for the core features: auth, app_settings, and system +76928c8 style: setup general screens +664bd8f dev: add no_params class and use case interface +545489d dev: add failure and params interfaces +6c219de dev: add entity and exception interfaces +686e5c8 style: add icon launcher for android and web +272adc3 style: implement a native splash screen (supports light and dark mode) +bcedf26 dev: clean assets folder and add sg-scholar's guide logos +3b35948 dev: refactor app_router and upgrade go_router dependency +5dd9766 dev: implement a nested and stateful router (using go_router and NavigationBar) +d59ab29 style: add different app themes +779c18b dev: add a ServiceLocator (using get_it package) +3212041 dev: add UpCampuses enum +7d3bad7 dev: add StorageTypes enum +67c4a8b dev: add Genders and Sex enums +3070e29 dev: add OnlineTimeSource enum +eb79b9c dev: add AccountTypes enum +c7e8640 dev: add AppColors utility class +7a0aed5 fix: add flutter cli in github ci +b46554b fix: enable firebase experimental web framework support (2) +45a3b2a fix: enable firebase experimental web framework support +fdbe1b0 fix: remove npm run build error +1815a27 fix: remove npm ci error (2) via generation of package-lock.json +4dc3927 fix: remove npm ci error +af535cf feat: enable firebase web app hosting +fc57e15 fix: add multidex support +f0c1528 deps: add go_router, bloc, and tes oriented dev dependencies +774b890 deps: add relevant firebase features +a726300 deps: initialize firebase core +6fec13f chore: remove remnant node_modules dir +a4011e5 chore: add foundational assets +a2c1c49 chore: initialize flutter project +caf3db3 chore: Add LICENSE +2d9e7c0 Update README.md +c910fd2 Update README.md +96d01e6 chore: wipe repo (change from nuxt to flutter) +cd12869 Update README.md +5f9cc43 Initialize firebase hosting +6221d0e Add firebase +b83e093 Add basic email verification logic +285bc5d Add basic forgot password logic +dc03fcb Add basic login auth logic (using Appwrite) +f761b88 Add email verification page view +83ca2c1 Add sign-up page view +293f5cf Add forgot password page view +bd76adb Add login page view +0ea4124 Update README.md +3bf6648 feat: Initialize project - Led (#20) +42d4622 Initialize WebStorm IDE dev environment +cdd7465 Create README.md +1bd46ad Create SECURITY.md (#2) +44a9f4b Update FUNDING.yml +03ca1d5 Create FUNDING.yml +60caecf Update issue templates +072f3a6 Initial commit diff --git a/lib/core/models/firestore_model.dart b/lib/core/models/firestore_model.dart index 3e389584..eb5fab9c 100644 --- a/lib/core/models/firestore_model.dart +++ b/lib/core/models/firestore_model.dart @@ -15,6 +15,7 @@ class FireStore { //* Field names of each document in the Firestore // Question related fields static const String question = 'question'; + static const String solution = 'solutionData'; static const String options = 'choices'; static const String correctIndex = 'correctChoiceKey'; // static const String questionReference = 'additionalQuestionDataRef'; diff --git a/lib/core/models/question_model.dart b/lib/core/models/question_model.dart index 23aa2c5c..8d806864 100644 --- a/lib/core/models/question_model.dart +++ b/lib/core/models/question_model.dart @@ -2,25 +2,39 @@ import 'dart:math'; +import 'package:cloud_firestore/cloud_firestore.dart'; import 'package:scholars_guide/core/models/firestore_model.dart'; enum SUBJ { MATH, SCIENCE, READING, LANGUAGE, ALL } class Question { - final String question; - final List options; - final int correctIndex; - final SUBJ subject; - const Question({ required this.question, + required this.solution, required this.options, required this.correctIndex, required this.subject, }); + + final String question; + final String solution; + final List options; + final int correctIndex; + final SUBJ subject; + + static var createdAt = FieldValue.serverTimestamp(); + static const String createdBy = ''; // ! This is a placeholder + + static var updatedAt = FieldValue.serverTimestamp(); + static const String updatedBy = ''; // ! This is a placeholder + + static bool isVerified = false; + static const String verifiedAt = ''; + static const String verifiedBy = ''; void printQuestion() { print('Question: $question'); + print('Solution: $solution'); print('Options: $options'); print('Correct: $correctIndex'); } @@ -31,13 +45,14 @@ class Question { for (var val in data[FireStore.options].values) { temp.add(val); } - - String temp2 = temp[int.parse(data[FireStore.correctIndex])]; + + String temp2 = temp[int.parse(data[FireStore.correctIndex])]; temp.shuffle(Random()); return Question( subject: subject, question: data[FireStore.question], + solution: '', // ! This is a placeholder options: temp, correctIndex: temp.indexWhere((element) => element == temp2)); } diff --git a/lib/features/quiz_mode/presentation/pages/finished_quiz_page.dart b/lib/features/quiz_mode/presentation/pages/finished_quiz_page.dart index 74e915ba..e35bd9b0 100644 --- a/lib/features/quiz_mode/presentation/pages/finished_quiz_page.dart +++ b/lib/features/quiz_mode/presentation/pages/finished_quiz_page.dart @@ -1,22 +1,24 @@ // ignore_for_file: prefer_const_constructors import 'package:flutter/material.dart'; +import 'package:go_router/go_router.dart'; import 'package:scholars_guide/core/models/question_model.dart'; import 'package:scholars_guide/features/quiz_mode/presentation/state_management/quiz_card/quiz_card_cubit.dart'; import 'package:scholars_guide/features/quiz_mode/presentation/widgets/quiz_widgets/question_display.dart'; -import 'ready_quiz_page.dart'; - class FinishedQuizPage extends StatelessWidget { - const FinishedQuizPage({super.key, required this.subjectQuestionsMap, required this.subject}); - - final Map> subjectQuestionsMap; - final SUBJ subject; + const FinishedQuizPage({super.key}); @override Widget build(BuildContext context) { + + final extraMap = GoRouterState.of(context).extra as Map; + final SUBJ subject = extraMap['subject'] as SUBJ; + final Map> subjectQuestionsMap = extraMap['subjectQuestionsMap'] as Map>; + return Scaffold( appBar: AppBar( + automaticallyImplyLeading: false, title: const Text('Quiz Results'), ), body: Center( @@ -30,19 +32,16 @@ class FinishedQuizPage extends StatelessWidget { ElevatedButton( onPressed: () { - Navigator.push( - context, - MaterialPageRoute(builder: (context) => (ReadyQuizPage())), - ); + GoRouter.of(context).go('/quiz-mode'); }, child: Text('Take Another Quiz'), ), - ElevatedButton( - onPressed: () { - // TODO: Push to the home page - }, - child: Text("Back to the home page"), - ), + + // ElevatedButton( + // onPressed: () { + // }, + // child: Text("Back to the home page"), + // ), ], ), )); diff --git a/lib/features/quiz_mode/presentation/pages/quiz_page.dart b/lib/features/quiz_mode/presentation/pages/quiz_page.dart index a251ed2b..f2c6efc0 100644 --- a/lib/features/quiz_mode/presentation/pages/quiz_page.dart +++ b/lib/features/quiz_mode/presentation/pages/quiz_page.dart @@ -2,20 +2,18 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:go_router/go_router.dart'; import 'package:scholars_guide/core/models/question_model.dart'; -import 'package:scholars_guide/features/quiz_mode/presentation/pages/ready_quiz_page.dart'; import 'package:scholars_guide/features/quiz_mode/presentation/state_management/quiz/quiz_bloc.dart'; -import 'package:scholars_guide/features/quiz_mode/presentation/widgets/quiz_page_widgets/confirm_cancel_quiz_display.dart'; -import 'package:scholars_guide/features/quiz_mode/presentation/widgets/quiz_page_widgets/confirm_submit_quiz_display.dart'; -import 'package:scholars_guide/features/quiz_mode/presentation/widgets/quiz_page_widgets/confirm_timeout_quiz_display.dart'; +import 'package:scholars_guide/features/quiz_mode/presentation/widgets/quiz_page_widgets/confirm_cancel_quiz_dialogue.dart'; +import 'package:scholars_guide/features/quiz_mode/presentation/widgets/quiz_page_widgets/confirm_submit_quiz_dialogue.dart'; import 'package:scholars_guide/features/quiz_mode/presentation/widgets/quiz_page_widgets/question_loading_display.dart'; import 'package:scholars_guide/features/quiz_mode/presentation/widgets/quiz_page_widgets/timer_display.dart'; import 'package:scholars_guide/features/quiz_mode/presentation/widgets/quiz_widgets/question_display.dart'; class QuizPage extends StatefulWidget { - const QuizPage({super.key, required this.subject}); + const QuizPage({super.key}); - final SUBJ subject; final int numQuestions = 10; // ! For now, only 10 question @override @@ -25,61 +23,61 @@ class QuizPage extends StatefulWidget { class _QuizPageState extends State { @override Widget build(BuildContext context) { + final subject = GoRouterState.of(context).extra as SUBJ; + return BlocProvider( create: (context) => QuizBloc() ..add(QuizLoadQuestions( - subject: widget.subject, numQuestions: widget.numQuestions)), + subject: subject, numQuestions: widget.numQuestions)), child: Builder(builder: (builderContext) { return Scaffold( appBar: AppBar( - title: Text('${Question.SUBJ2string(widget.subject)} Quiz'), + title: Text('${Question.SUBJ2string(subject)} Quiz'), leading: IconButton( icon: Icon(Icons.arrow_back), onPressed: () { - BlocProvider.of(builderContext) - .add(QuizCancelBtnPressed()); + showDialog( + context: context, + builder: (BuildContext buildContext) { + return ConfirmCancelQuizDialogue(); + }); }, )), body: Center(child: BlocBuilder( - builder: (quizBlocContext, state) { - if (state is QuizLoading) { - return QuestionLoadingDisplay(); - } else if (state is QuizOngoing) { - return Column( - children: [ - // * Display the questions - QuestionDisplay( - subjectQuestionsMap: quizBlocContext - .read() - .subjectQuestionsMap, - subject: widget.subject), + builder: (quizBlocContext, state) { + if (state is QuizLoading) { + return QuestionLoadingDisplay(); + } else if (state is QuizOngoing) { + return Column( + children: [ + // * Display the questions + QuestionDisplay( + subjectQuestionsMap: quizBlocContext + .read() + .subjectQuestionsMap, + subject: subject), + + // * Display the submit button + ElevatedButton( + onPressed: () { + showDialog( + context: context, + builder: (BuildContext buildContext) { + return ConfirmSubmitQuizDialogue(quizBloc: quizBlocContext.read()); + }); + }, + child: Text("Submit")), - // * Display the submit button - ElevatedButton( - onPressed: () { - quizBlocContext - .read() - .add(QuizFinishBtnPressed()); - }, - child: Text("Submit")), + // * Display the timer + TimerDisplay() + ], + ); + } - // * Display the timer - TimerDisplay(), - ], - ); - } else if (state is QuizFinishConfirmation) { - return ConfirmSubmitQuizDisplay(); - } else if (state is QuizCancelConfirmation) { - return ConfirmCancelQuizDisplay(); - } else if (state is QuizOutOfTime) { - return (ConfirmTimeOutQuizDisplay()); - } else if (state is QuizError) { - return ReadyQuizPage(); - } - return Text( - "SOMETHING WENT WRONG! (No Quiz Bloc State Matched)"); - }, - ))); + GoRouter.of(context).go('/quiz-mode'); + return Text( + "SOMETHING WENT WRONG! (No Quiz Bloc State Matched)"); + }))); })); } } diff --git a/lib/features/quiz_mode/presentation/pages/ready_quiz_page.dart b/lib/features/quiz_mode/presentation/pages/ready_quiz_page.dart index 9b1abfb0..b50438ce 100644 --- a/lib/features/quiz_mode/presentation/pages/ready_quiz_page.dart +++ b/lib/features/quiz_mode/presentation/pages/ready_quiz_page.dart @@ -1,10 +1,6 @@ // ignore_for_file: prefer_const_constructors import 'package:flutter/material.dart'; -import 'package:flutter_bloc/flutter_bloc.dart'; -import 'package:scholars_guide/features/quiz_mode/presentation/widgets/ready_quiz_page_widgets/choose_subject_confirm_display.dart'; - -import '../state_management/ready_quiz/ready_quiz_cubit.dart'; import '../widgets/ready_quiz_page_widgets/choose_subject_display.dart'; class ReadyQuizPage extends StatelessWidget { @@ -14,18 +10,11 @@ class ReadyQuizPage extends StatelessWidget { Widget build(BuildContext context) { return Scaffold( appBar: AppBar( + automaticallyImplyLeading: false, title: const Text('Submit Questions'), ), body: Center( - child: BlocProvider( - create: (context) => ReadyQuizCubit(), - child: BlocBuilder( - builder: (readyQuizCubitContext, state) { - if (state is ReadyQuizSubjectChosen) { - return ChooseSubjectConfirmDisplay(subject: state.subject); - } - return ChooseSubjectDisplay(); - })), + child: ChooseSubjectDisplay(), )); } } diff --git a/lib/features/quiz_mode/presentation/state_management/quiz/quiz_bloc.dart b/lib/features/quiz_mode/presentation/state_management/quiz/quiz_bloc.dart index 20b863bb..e13696e7 100644 --- a/lib/features/quiz_mode/presentation/state_management/quiz/quiz_bloc.dart +++ b/lib/features/quiz_mode/presentation/state_management/quiz/quiz_bloc.dart @@ -47,29 +47,10 @@ class QuizBloc extends Bloc { emit(QuizOngoing()); }); - // * For submitting the quiz - on((event, emit) { - emit(QuizFinishConfirmation()); - }); - on((event, emit) { - emit(QuizOngoing()); - }); - on((event, emit) { - emit(QuizOutOfTime()); - }); - - // * For cancelling the quiz - on((event, emit) { - emit(QuizCancelConfirmation()); - }); - on((event, emit) { - emit(QuizOngoing()); - }); - // * Only for quiz taking all subjects. // TODO: Implement the logic for the next and previous page. - on((event, emit) {}); - on((event, emit) {}); + // on((event, emit) {}); + // on((event, emit) {}); } late SUBJ subject; diff --git a/lib/features/quiz_mode/presentation/state_management/quiz/quiz_event.dart b/lib/features/quiz_mode/presentation/state_management/quiz/quiz_event.dart index b3739676..e336f702 100644 --- a/lib/features/quiz_mode/presentation/state_management/quiz/quiz_event.dart +++ b/lib/features/quiz_mode/presentation/state_management/quiz/quiz_event.dart @@ -17,16 +17,6 @@ class QuizLoadQuestions extends QuizEvent { List get props => [subject, numQuestions]; } -class QuizFinishBtnPressed extends QuizEvent {} - -class QuizConfirmFinishBtnPressed extends QuizEvent {} - -class QuizCancelBtnPressed extends QuizEvent {} - -class QuizConfirmCancelBtnPressed extends QuizEvent {} - -class QuizTimeFinished extends QuizEvent {} - class QuizNextPageBtnPressed extends QuizEvent {} class QuizPreviousPageBtnPressed extends QuizEvent {} diff --git a/lib/features/quiz_mode/presentation/state_management/quiz/quiz_state.dart b/lib/features/quiz_mode/presentation/state_management/quiz/quiz_state.dart index 888b8f87..3a85b578 100644 --- a/lib/features/quiz_mode/presentation/state_management/quiz/quiz_state.dart +++ b/lib/features/quiz_mode/presentation/state_management/quiz/quiz_state.dart @@ -11,10 +11,4 @@ final class QuizLoading extends QuizState {} final class QuizOngoing extends QuizState {} -final class QuizFinishConfirmation extends QuizState {} - -final class QuizCancelConfirmation extends QuizState {} - -final class QuizOutOfTime extends QuizState {} - final class QuizError extends QuizState {} diff --git a/lib/features/quiz_mode/presentation/state_management/ready_quiz/ready_quiz_cubit.dart b/lib/features/quiz_mode/presentation/state_management/ready_quiz/ready_quiz_cubit.dart deleted file mode 100644 index 7ce8333e..00000000 --- a/lib/features/quiz_mode/presentation/state_management/ready_quiz/ready_quiz_cubit.dart +++ /dev/null @@ -1,17 +0,0 @@ -import 'package:bloc/bloc.dart'; -import 'package:equatable/equatable.dart'; - -import '../../../../../core/models/question_model.dart'; - -part 'ready_quiz_state.dart'; - -class ReadyQuizCubit extends Cubit { - ReadyQuizCubit() : super(ReadyQuizInitial()); - - void chooseMath() => emit(const ReadyQuizSubjectChosen(subject: SUBJ.MATH)); - void chooseScience() => emit(const ReadyQuizSubjectChosen(subject: SUBJ.SCIENCE)); - void chooseReading() => emit(const ReadyQuizSubjectChosen(subject: SUBJ.READING)); - void chooseLanguage() => emit(const ReadyQuizSubjectChosen(subject: SUBJ.LANGUAGE)); - void chooseAll() => emit(const ReadyQuizSubjectChosen(subject: SUBJ.ALL)); - void chooseAgain() => emit(ReadyQuizInitial()); -} diff --git a/lib/features/quiz_mode/presentation/state_management/ready_quiz/ready_quiz_state.dart b/lib/features/quiz_mode/presentation/state_management/ready_quiz/ready_quiz_state.dart deleted file mode 100644 index c75bb39c..00000000 --- a/lib/features/quiz_mode/presentation/state_management/ready_quiz/ready_quiz_state.dart +++ /dev/null @@ -1,22 +0,0 @@ -part of 'ready_quiz_cubit.dart'; - -sealed class ReadyQuizState extends Equatable { - const ReadyQuizState(); - - @override - List get props => []; -} - -// Initial state shows 5 options in the ready quiz page. -class ReadyQuizInitial extends ReadyQuizState {} - -// Once the user chooses a subject, the state changes to ReadyQuizSubjectChosen. -// User will choose to confirm or not. -class ReadyQuizSubjectChosen extends ReadyQuizState { - const ReadyQuizSubjectChosen({required this.subject}); - - final SUBJ subject; - - @override - List get props => [subject]; -} diff --git a/lib/features/quiz_mode/presentation/widgets/quiz_page_widgets/confirm_cancel_quiz_dialogue.dart b/lib/features/quiz_mode/presentation/widgets/quiz_page_widgets/confirm_cancel_quiz_dialogue.dart new file mode 100644 index 00000000..842595db --- /dev/null +++ b/lib/features/quiz_mode/presentation/widgets/quiz_page_widgets/confirm_cancel_quiz_dialogue.dart @@ -0,0 +1,29 @@ +import 'package:flutter/material.dart'; +import 'package:go_router/go_router.dart'; + +class ConfirmCancelQuizDialogue extends StatelessWidget { + const ConfirmCancelQuizDialogue({super.key}); + + @override + Widget build(BuildContext context) { + return AlertDialog( + title: const Text('Cancel Confirmation'), + content: const Text("You can't go back after this! Are you sure?"), + actions: [ + ElevatedButton( + child: const Text('Continue Quiz'), + onPressed: () { + Navigator.of(context).pop(); + }, + ), + ElevatedButton( + child: const Text('Go Back'), + onPressed: () { + Navigator.of(context).pop(); + GoRouter.of(context).go('/quiz-mode'); + }, + ), + ], + ); + } +} diff --git a/lib/features/quiz_mode/presentation/widgets/quiz_page_widgets/confirm_cancel_quiz_display.dart b/lib/features/quiz_mode/presentation/widgets/quiz_page_widgets/confirm_cancel_quiz_display.dart deleted file mode 100644 index e808a349..00000000 --- a/lib/features/quiz_mode/presentation/widgets/quiz_page_widgets/confirm_cancel_quiz_display.dart +++ /dev/null @@ -1,44 +0,0 @@ -// ignore_for_file: prefer_const_constructors - -import 'package:flutter/material.dart'; -import 'package:flutter_bloc/flutter_bloc.dart'; -import 'package:scholars_guide/features/quiz_mode/presentation/pages/ready_quiz_page.dart'; -import 'package:scholars_guide/features/quiz_mode/presentation/state_management/quiz/quiz_bloc.dart'; - -class ConfirmCancelQuizDisplay extends StatelessWidget { - const ConfirmCancelQuizDisplay({super.key}); - - @override - Widget build(BuildContext context) { - return Scaffold( - body: Container( - width: double.infinity, - height: double.infinity, - decoration: BoxDecoration( - color: Colors.black.withOpacity(0.6), - ), - child: AlertDialog( - title: const Text('Cancel Confirmation'), - content: const Text("You can't go back after this! Are you sure?"), - actions: [ - ElevatedButton( - child: const Text('Continue Quiz'), - onPressed: () { - context.read().add(QuizConfirmCancelBtnPressed()); - }, - ), - ElevatedButton( - child: const Text('Go Back'), - onPressed: () { - Navigator.push( - context, - MaterialPageRoute( - builder: (context) => (ReadyQuizPage())), - ); - }, - ), - ], - )), - ); - } -} \ No newline at end of file diff --git a/lib/features/quiz_mode/presentation/widgets/quiz_page_widgets/confirm_submit_quiz_dialogue.dart b/lib/features/quiz_mode/presentation/widgets/quiz_page_widgets/confirm_submit_quiz_dialogue.dart new file mode 100644 index 00000000..5302f5d9 --- /dev/null +++ b/lib/features/quiz_mode/presentation/widgets/quiz_page_widgets/confirm_submit_quiz_dialogue.dart @@ -0,0 +1,52 @@ +import 'package:flutter/material.dart'; +import 'package:go_router/go_router.dart'; +import 'package:scholars_guide/core/models/question_model.dart'; +import 'package:scholars_guide/features/quiz_mode/presentation/state_management/quiz/quiz_bloc.dart'; +import 'package:scholars_guide/features/quiz_mode/presentation/state_management/quiz_card/quiz_card_cubit.dart'; + +class ConfirmSubmitQuizDialogue extends StatelessWidget { + const ConfirmSubmitQuizDialogue({super.key, required this.quizBloc}); + + final QuizBloc quizBloc; + + @override + Widget build(BuildContext context) { + return AlertDialog( + title: const Text('Submit Confirmation'), + content: const Text("You can't go back after this! Are you sure?"), + actions: [ + ElevatedButton( + child: const Text('Submit Quiz'), + onPressed: () { + final SUBJ subject = quizBloc.subject; + Map> subjectQuestionsMap = + quizBloc.subjectQuestionsMap; + + for (List subject in subjectQuestionsMap.values) { + for (QuizCardCubit questionCubit in subject) { + if (questionCubit.state is QuizCardUnanswered) { + questionCubit.revealAnswer(index: -1); + } + if (questionCubit.state is QuizCardAnswered) { + questionCubit.revealAnswer( + index: questionCubit.state.props[0] as int); + } + } + } + Navigator.of(context).pop(); + GoRouter.of(context).go('/quiz-mode/finished-quiz', extra: { + 'subjectQuestionsMap': subjectQuestionsMap, + 'subject': subject + }); + }, + ), + ElevatedButton( + child: const Text('Go Back'), + onPressed: () { + Navigator.of(context).pop(); + }, + ), + ], + ); + } +} diff --git a/lib/features/quiz_mode/presentation/widgets/quiz_page_widgets/confirm_submit_quiz_display.dart b/lib/features/quiz_mode/presentation/widgets/quiz_page_widgets/confirm_submit_quiz_display.dart deleted file mode 100644 index aaba9e2d..00000000 --- a/lib/features/quiz_mode/presentation/widgets/quiz_page_widgets/confirm_submit_quiz_display.dart +++ /dev/null @@ -1,66 +0,0 @@ -// ignore_for_file: prefer_const_constructors - -import 'package:flutter/material.dart'; -import 'package:flutter_bloc/flutter_bloc.dart'; -import 'package:scholars_guide/core/models/question_model.dart'; -import 'package:scholars_guide/features/quiz_mode/presentation/pages/finished_quiz_page.dart'; -import 'package:scholars_guide/features/quiz_mode/presentation/state_management/quiz/quiz_bloc.dart'; -import 'package:scholars_guide/features/quiz_mode/presentation/state_management/quiz_card/quiz_card_cubit.dart'; - -class ConfirmSubmitQuizDisplay extends StatelessWidget { - const ConfirmSubmitQuizDisplay({super.key}); - - @override - Widget build(BuildContext context) { - return Scaffold( - body: Container( - width: double.infinity, - height: double.infinity, - decoration: BoxDecoration( - color: Colors.black.withOpacity(0.6), - ), - child: AlertDialog( - title: const Text('Submit Confirmation'), - content: const Text("You can't go back after this! Are you sure?"), - actions: [ - ElevatedButton( - child: const Text('Submit Quiz'), - onPressed: () { - Map> subjectQuestionsMap = - context.read().subjectQuestionsMap; - - for (List subject - in subjectQuestionsMap.values) { - - for (QuizCardCubit questionCubit in subject) { - if (questionCubit.state is QuizCardUnanswered) { - questionCubit.revealAnswer( - index: -1); - } - if (questionCubit.state is QuizCardAnswered) { - questionCubit.revealAnswer( - index: questionCubit.state.props[0] as int); - } - } - } - Navigator.push( - context, - MaterialPageRoute( - builder: (newContext) => (FinishedQuizPage( - subjectQuestionsMap: - context.read().subjectQuestionsMap, - subject: context.read().subject)), - )); - }, - ), - ElevatedButton( - child: const Text('Go Back'), - onPressed: () { - context.read().add(QuizConfirmFinishBtnPressed()); - }, - ), - ], - )), - ); - } -} diff --git a/lib/features/quiz_mode/presentation/widgets/quiz_page_widgets/confirm_timeout_quiz_dialogue.dart b/lib/features/quiz_mode/presentation/widgets/quiz_page_widgets/confirm_timeout_quiz_dialogue.dart new file mode 100644 index 00000000..a16093d9 --- /dev/null +++ b/lib/features/quiz_mode/presentation/widgets/quiz_page_widgets/confirm_timeout_quiz_dialogue.dart @@ -0,0 +1,47 @@ +import 'package:flutter/material.dart'; +import 'package:go_router/go_router.dart'; +import 'package:scholars_guide/core/models/question_model.dart'; +import 'package:scholars_guide/features/quiz_mode/presentation/state_management/quiz/quiz_bloc.dart'; +import 'package:scholars_guide/features/quiz_mode/presentation/state_management/quiz_card/quiz_card_cubit.dart'; + +class ConfirmTimeoutQuizDialogue extends StatelessWidget { + const ConfirmTimeoutQuizDialogue({super.key, required this.quizBloc}); + + final QuizBloc quizBloc; + + @override + Widget build(BuildContext context) { + return AlertDialog( + title: const Text('Out of Time'), + content: const Text("Time's up! You did great"), + actions: [ + ElevatedButton( + child: const Text('Submit Quiz'), + onPressed: () { + final SUBJ subject = quizBloc.subject; + Map> subjectQuestionsMap = + quizBloc.subjectQuestionsMap; + + for (List subject in subjectQuestionsMap.values) { + for (QuizCardCubit questionCubit in subject) { + if (questionCubit.state is QuizCardUnanswered) { + questionCubit.revealAnswer(index: -1); + } + if (questionCubit.state is QuizCardAnswered) { + questionCubit.revealAnswer( + index: questionCubit.state.props[0] as int); + } + } + } + + Navigator.of(context).pop(); + GoRouter.of(context).go('/quiz-mode/finished-quiz', extra: { + 'subjectQuestionsMap': subjectQuestionsMap, + 'subject': subject + }); + }, + ), + ], + ); + } +} diff --git a/lib/features/quiz_mode/presentation/widgets/quiz_page_widgets/confirm_timeout_quiz_display.dart b/lib/features/quiz_mode/presentation/widgets/quiz_page_widgets/confirm_timeout_quiz_display.dart deleted file mode 100644 index 5065cc6b..00000000 --- a/lib/features/quiz_mode/presentation/widgets/quiz_page_widgets/confirm_timeout_quiz_display.dart +++ /dev/null @@ -1,41 +0,0 @@ -// ignore_for_file: prefer_const_constructors - -import 'package:flutter/material.dart'; -import 'package:flutter_bloc/flutter_bloc.dart'; -import 'package:scholars_guide/features/quiz_mode/presentation/pages/finished_quiz_page.dart'; -import 'package:scholars_guide/features/quiz_mode/presentation/state_management/quiz/quiz_bloc.dart'; - -class ConfirmTimeOutQuizDisplay extends StatelessWidget { - const ConfirmTimeOutQuizDisplay({super.key}); - - @override - Widget build(BuildContext context) { - return Scaffold( - body: Container( - width: double.infinity, - height: double.infinity, - decoration: BoxDecoration( - color: Colors.black.withOpacity(0.6), - ), - child: AlertDialog( - title: const Text('Out of Time'), - content: const Text("You did great! Let's see you did"), - actions: [ - ElevatedButton( - child: const Text('Submit Quiz'), - onPressed: () { - Navigator.push( - context, - MaterialPageRoute( - builder: (context) => (FinishedQuizPage( - subjectQuestionsMap: - context.read().subjectQuestionsMap, - subject: context.read().subject))), - ); - }, - ), - ], - )), - ); - } -} diff --git a/lib/features/quiz_mode/presentation/widgets/quiz_page_widgets/timer_display.dart b/lib/features/quiz_mode/presentation/widgets/quiz_page_widgets/timer_display.dart index bb36efb8..449e55e3 100644 --- a/lib/features/quiz_mode/presentation/widgets/quiz_page_widgets/timer_display.dart +++ b/lib/features/quiz_mode/presentation/widgets/quiz_page_widgets/timer_display.dart @@ -2,7 +2,10 @@ import 'package:circular_countdown_timer/circular_countdown_timer.dart'; import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_portal/flutter_portal.dart'; +import 'package:scholars_guide/features/quiz_mode/presentation/state_management/quiz/quiz_bloc.dart'; +import 'package:scholars_guide/features/quiz_mode/presentation/widgets/quiz_page_widgets/confirm_timeout_quiz_dialogue.dart'; class TimerDisplay extends StatefulWidget { const TimerDisplay({super.key}); @@ -12,8 +15,6 @@ class TimerDisplay extends StatefulWidget { } class _TimerDisplayState extends State { - // final CountDownController _controller = CountDownController(); - @override Widget build(BuildContext context) { return Expanded( @@ -59,7 +60,7 @@ class _CountdownTimerState extends State { @override Widget build(BuildContext context) { return CircularCountDownTimer( - duration: 60, + duration: 60 * 10, // ! 10 minutes muna initialDuration: 0, controller: CountDownController(), width: MediaQuery.of(context).size.width / 6, @@ -80,13 +81,19 @@ class _CountdownTimerState extends State { isTimerTextShown: true, autoStart: true, onStart: () { - debugPrint('Countdown Started'); + // debugPrint('Countdown Started'); }, onComplete: () { - debugPrint('Countdown Ended'); + // debugPrint('Countdown Ended'); + showDialog( + barrierDismissible: false, + context: context, + builder: (BuildContext buildContext) { + return ConfirmTimeoutQuizDialogue(quizBloc: context.read()); + }); }, onChange: (String timeStamp) { - debugPrint('Countdown Changed $timeStamp'); + // debugPrint('Countdown Changed $timeStamp'); }, timeFormatterFunction: (defaultFormatterFunction, duration) { if (duration.inSeconds == 0) { diff --git a/lib/features/quiz_mode/presentation/widgets/quiz_widgets/question_card_choices_display.dart b/lib/features/quiz_mode/presentation/widgets/quiz_widgets/question_card_choices_display.dart index 05c8e0c7..aff95fd4 100644 --- a/lib/features/quiz_mode/presentation/widgets/quiz_widgets/question_card_choices_display.dart +++ b/lib/features/quiz_mode/presentation/widgets/quiz_widgets/question_card_choices_display.dart @@ -43,6 +43,7 @@ class _QuestionCardChoicesDisplayState widget.bloc.chooseOption(index: i); }, isChosen: state.chosenIndex == i, + isCorrect: widget.bloc.correctIndex == state.chosenIndex, context: context); // ! This is the line that needs to be changed } else if (state is QuizCardRevealed) { diff --git a/lib/features/quiz_mode/presentation/widgets/ready_quiz_page_widgets/choose_subject_confirm_display.dart b/lib/features/quiz_mode/presentation/widgets/ready_quiz_page_widgets/choose_subject_confirm_display.dart deleted file mode 100644 index 0accdf9f..00000000 --- a/lib/features/quiz_mode/presentation/widgets/ready_quiz_page_widgets/choose_subject_confirm_display.dart +++ /dev/null @@ -1,48 +0,0 @@ -// ignore_for_file: prefer_const_constructors - -import 'package:flutter/material.dart'; -import 'package:flutter_bloc/flutter_bloc.dart'; -import 'package:scholars_guide/core/models/question_model.dart'; -import 'package:scholars_guide/features/quiz_mode/presentation/pages/quiz_page.dart'; -import 'package:scholars_guide/features/quiz_mode/presentation/state_management/ready_quiz/ready_quiz_cubit.dart'; - -class ChooseSubjectConfirmDisplay extends StatelessWidget { - const ChooseSubjectConfirmDisplay({super.key, required this.subject}); - - final SUBJ subject; - - @override - Widget build(BuildContext context) { - return Scaffold( - body: Container( - width: double.infinity, - height: double.infinity, - decoration: BoxDecoration( - color: Colors.black.withOpacity(0.6), - ), - child: AlertDialog( - title: Text('Confirmation'), - content: Text( - "Let's start answering ${Question.SUBJ2string(subject)}! Are you ready?"), - actions: [ - ElevatedButton( - child: Text('Start'), - onPressed: () { - Navigator.push( - context, - MaterialPageRoute( - builder: (context) => (QuizPage(subject: subject))), - ); - }, - ), - ElevatedButton( - child: Text('Go Back'), - onPressed: () { - BlocProvider.of(context).chooseAgain(); - }, - ), - ], - )), - ); - } -} diff --git a/lib/features/quiz_mode/presentation/widgets/ready_quiz_page_widgets/choose_subject_display.dart b/lib/features/quiz_mode/presentation/widgets/ready_quiz_page_widgets/choose_subject_display.dart index c2976893..af57280b 100644 --- a/lib/features/quiz_mode/presentation/widgets/ready_quiz_page_widgets/choose_subject_display.dart +++ b/lib/features/quiz_mode/presentation/widgets/ready_quiz_page_widgets/choose_subject_display.dart @@ -1,8 +1,6 @@ import 'package:flutter/material.dart'; -import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_portal/flutter_portal.dart'; - -import '../../state_management/ready_quiz/ready_quiz_cubit.dart'; +import 'package:scholars_guide/features/quiz_mode/presentation/widgets/ready_quiz_page_widgets/subject_chosen_dialogue.dart'; class ChooseSubjectDisplay extends StatefulWidget { const ChooseSubjectDisplay({super.key}); @@ -36,25 +34,11 @@ Container buildElevatedButton(String text, BuildContext context) { width: 220, child: ElevatedButton( onPressed: () { - switch (text) { - case "Math": - context.read().chooseMath(); - break; - case "Science": - context.read().chooseScience(); - break; - case "Language Proficiency": - context.read().chooseLanguage(); - break; - case "Reading Comprehension": - context.read().chooseReading(); - break; - case "UPCAT Challenge": - // context.read().chooseAll(); - break; - default: - context.read().chooseAll(); - } + showDialog( + context: context, + builder: (BuildContext buildContext) { + return SubjectChosenDialogue(subjectTest: text,); + }); }, child: Text(text))); } diff --git a/lib/features/quiz_mode/presentation/widgets/ready_quiz_page_widgets/subject_chosen_dialogue.dart b/lib/features/quiz_mode/presentation/widgets/ready_quiz_page_widgets/subject_chosen_dialogue.dart new file mode 100644 index 00000000..b9c19bee --- /dev/null +++ b/lib/features/quiz_mode/presentation/widgets/ready_quiz_page_widgets/subject_chosen_dialogue.dart @@ -0,0 +1,33 @@ +import 'package:flutter/material.dart'; +import 'package:go_router/go_router.dart'; +import 'package:scholars_guide/core/models/question_model.dart'; + +class SubjectChosenDialogue extends StatelessWidget { + const SubjectChosenDialogue({super.key, required this.subjectTest}); + + final String subjectTest; + + @override + Widget build(BuildContext context) { + return AlertDialog( + title: const Text('Confirmation'), + content: Text("Let's start answering $subjectTest! Are you ready?"), + actions: [ + ElevatedButton( + child: const Text('Start'), + onPressed: () { + Navigator.of(context).pop(); + GoRouter.of(context).go('/quiz-mode/start-quiz', + extra: Question.string2SUBJ(subjectTest)); + }, + ), + ElevatedButton( + child: const Text('Go Back'), + onPressed: () { + Navigator.of(context).pop(); + }, + ), + ], + ); + } +} diff --git a/lib/features/quiz_upload/data/repositories/quiz_upload_repository_impl.dart b/lib/features/quiz_upload/data/repositories/quiz_upload_repository_impl.dart index 8d626543..f578b149 100644 --- a/lib/features/quiz_upload/data/repositories/quiz_upload_repository_impl.dart +++ b/lib/features/quiz_upload/data/repositories/quiz_upload_repository_impl.dart @@ -25,6 +25,7 @@ class QuizUploadRepositoryImpl implements QuizUploadRepositoryContract { for (QuizInputCubit questionCubit in questionsToUpload) { Question question = Question( question: questionCubit.state.question, + solution: questionCubit.state.solution, options: questionCubit.state.options, correctIndex: int.parse(questionCubit.state.answerIndex), subject: subjToUpload); diff --git a/lib/features/quiz_upload/presentation/pages/upload_questions_page.dart b/lib/features/quiz_upload/presentation/pages/upload_questions_page.dart index b457e886..2da9be56 100644 --- a/lib/features/quiz_upload/presentation/pages/upload_questions_page.dart +++ b/lib/features/quiz_upload/presentation/pages/upload_questions_page.dart @@ -4,8 +4,7 @@ import 'package:flutter/material.dart'; import 'package:scholars_guide/features/quiz_upload/presentation/state_management/quiz_input_page/quiz_input_page_bloc.dart'; import 'package:scholars_guide/features/quiz_upload/presentation/widgets/quiz_input_page_widgets/add_or_submit_display.dart'; import 'package:scholars_guide/features/quiz_upload/presentation/widgets/quiz_input_page_widgets/change_subject_display.dart'; -import 'package:scholars_guide/features/quiz_upload/presentation/widgets/quiz_input_page_widgets/confirm_cancel_quiz_input_display.dart'; -import 'package:scholars_guide/features/quiz_upload/presentation/widgets/quiz_input_page_widgets/confirm_submit_quiz_input_display.dart'; +import 'package:scholars_guide/features/quiz_upload/presentation/widgets/quiz_input_page_widgets/confirm_cancel_quiz_input_dialogue.dart'; import 'package:scholars_guide/features/quiz_upload/presentation/widgets/quiz_input_widgets/question_input_display.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; @@ -47,8 +46,6 @@ class _UploadQuestionPageState extends State { ), ], )); - } else if (state is QuizInputPageConfirmSubmit) { - return ConfirmSubmitQuizInputDisplay(); } else if (state is QuizInputPageConfirmCancel) { return ConfirmCancelQuizInputDisplay(); } diff --git a/lib/features/quiz_upload/presentation/pages/upload_success_page.dart b/lib/features/quiz_upload/presentation/pages/upload_success_page.dart index 860f678c..180110f3 100644 --- a/lib/features/quiz_upload/presentation/pages/upload_success_page.dart +++ b/lib/features/quiz_upload/presentation/pages/upload_success_page.dart @@ -1,6 +1,7 @@ // ignore_for_file: prefer_const_constructors import 'package:flutter/material.dart'; +import 'package:go_router/go_router.dart'; import 'package:scholars_guide/core/models/question_model.dart'; import 'package:scholars_guide/features/quiz_upload/presentation/state_management/quiz_input/quiz_input_cubit.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; @@ -9,11 +10,7 @@ import 'package:scholars_guide/features/quiz_upload/presentation/widgets/upload_ import 'package:scholars_guide/features/quiz_upload/presentation/widgets/upload_quiz_widgets/upload_loading_display.dart'; class UploadSuccessPage extends StatefulWidget { - const UploadSuccessPage( - {super.key, required this.questionsToUpload, required this.subjToUpload}); - - final List questionsToUpload; - final SUBJ subjToUpload; + const UploadSuccessPage({super.key}); @override State createState() => _UploadSuccessPageState(); @@ -22,12 +19,17 @@ class UploadSuccessPage extends StatefulWidget { class _UploadSuccessPageState extends State { @override Widget build(BuildContext buildContext) { + final extraMap = GoRouterState.of(context).extra as Map; + final List questionsToUpload = extraMap['questionsToUpload'] as List; + final SUBJ subjToUpload = extraMap['subjToUpload'] as SUBJ; + return BlocProvider( create: (providerContext) => UploadQuizCubit( - questions: widget.questionsToUpload, subj: widget.subjToUpload) + questions: questionsToUpload, subj: subjToUpload) ..uploadQuiz(), child: Scaffold( appBar: AppBar( + automaticallyImplyLeading: false, title: const Text('Upload Questions'), ), body: BlocBuilder( diff --git a/lib/features/quiz_upload/presentation/state_management/quiz_input/quiz_input_cubit.dart b/lib/features/quiz_upload/presentation/state_management/quiz_input/quiz_input_cubit.dart index 66c20845..4272ae21 100644 --- a/lib/features/quiz_upload/presentation/state_management/quiz_input/quiz_input_cubit.dart +++ b/lib/features/quiz_upload/presentation/state_management/quiz_input/quiz_input_cubit.dart @@ -8,24 +8,27 @@ class QuizInputCubit extends Cubit { QuizInputCubit() : super(const QuizInputState( question: '', + solution: '', options: ['', '', '', ''], answerIndex: '0', questionNonEmpty: false, + solutionNonEmpty: false, optionsNonEmpty: [false, false, false, false], )); void questionChanged({required String question}) { - // print("Question changed to $question"); emit(state.copyWith(question: question)); } + void solutionChanged({required String solution}) { + emit(state.copyWith(solution: solution)); + } + void optionChanged({required List options, required List optionsNonEmpty}) { - // print("Options changed to $options"); emit(state.copyWith(options: options, optionsNonEmpty: optionsNonEmpty)); } void answerChanged({required String answerIndex}) { - // print("Answer changed to $answerIndex"); emit(state.copyWith(answerIndex: answerIndex)); } } diff --git a/lib/features/quiz_upload/presentation/state_management/quiz_input/quiz_input_state.dart b/lib/features/quiz_upload/presentation/state_management/quiz_input/quiz_input_state.dart index 75b72202..67f30ac7 100644 --- a/lib/features/quiz_upload/presentation/state_management/quiz_input/quiz_input_state.dart +++ b/lib/features/quiz_upload/presentation/state_management/quiz_input/quiz_input_state.dart @@ -3,18 +3,22 @@ part of 'quiz_input_cubit.dart'; class QuizInputState extends Equatable { const QuizInputState({ required this.question, + required this.solution, required this.options, required this.answerIndex, required this.questionNonEmpty, + required this.solutionNonEmpty, required this.optionsNonEmpty, }); final String question; + final String solution; final List options; final String answerIndex; // Empty checker final bool questionNonEmpty; + final bool solutionNonEmpty; final List optionsNonEmpty; @override @@ -22,16 +26,20 @@ class QuizInputState extends Equatable { QuizInputState copyWith({ String? question, + String? solution, List? options, String? answerIndex, bool? questionNonEmpty, + bool? solutionNonEmpty, List? optionsNonEmpty, }) { return QuizInputState( question: question ?? this.question, + solution: solution ?? this.solution, options: options ?? this.options, answerIndex: answerIndex ?? this.answerIndex, questionNonEmpty: question?.isNotEmpty ?? this.questionNonEmpty, + solutionNonEmpty: solution?.isNotEmpty ?? this.solutionNonEmpty, optionsNonEmpty: options?.map((e) => e.isNotEmpty).toList() ?? this.optionsNonEmpty, ); diff --git a/lib/features/quiz_upload/presentation/state_management/quiz_input_page/quiz_input_page_bloc.dart b/lib/features/quiz_upload/presentation/state_management/quiz_input_page/quiz_input_page_bloc.dart index e7369e5b..0f6f44d4 100644 --- a/lib/features/quiz_upload/presentation/state_management/quiz_input_page/quiz_input_page_bloc.dart +++ b/lib/features/quiz_upload/presentation/state_management/quiz_input_page/quiz_input_page_bloc.dart @@ -24,15 +24,22 @@ class QuizInputPageBloc extends Bloc { if (_questionsNotEmpty() && questions.isNotEmpty) { revealBlanks = false; - emit(QuizInputPageConfirmSubmit()); } else { revealBlanks = true; emit(QuizInputPageRefresh()); emit(QuizInputPageQuestionsAdd()); } }); - on((event, emit) { + + // * For resetting the quiz upload after submitting + on((event, emit) { + + questions = [QuizInputCubit()]; + subject = SUBJ.MATH; + revealBlanks = false; + emit(QuizInputPageRefresh()); emit(QuizInputPageQuestionsAdd()); + }); // * For cancelling the quiz upload @@ -60,6 +67,10 @@ class QuizInputPageBloc extends Bloc { SUBJ subject = SUBJ.MATH; bool revealBlanks = false; + bool isSubmittable() { + return _questionsNotEmpty() && questions.isNotEmpty; + } + bool _questionsNotEmpty() { for (var question in questions) { if (!question.state.questionNonEmpty) return false; diff --git a/lib/features/quiz_upload/presentation/state_management/quiz_input_page/quiz_input_page_event.dart b/lib/features/quiz_upload/presentation/state_management/quiz_input_page/quiz_input_page_event.dart index ac7ec335..9519bbfa 100644 --- a/lib/features/quiz_upload/presentation/state_management/quiz_input_page/quiz_input_page_event.dart +++ b/lib/features/quiz_upload/presentation/state_management/quiz_input_page/quiz_input_page_event.dart @@ -18,6 +18,8 @@ class QuizInputPageChangedSubject extends QuizInputPageEvent { class QuizInputPageSubmitBtnPressed extends QuizInputPageEvent {} class QuizInputPageCancelSubmitBtnPressed extends QuizInputPageEvent {} +class QuizInputPageReset extends QuizInputPageEvent {} + class QuizInputPageBackBtnPressed extends QuizInputPageEvent {} class QuizInputPageCancelBackBtnPressed extends QuizInputPageEvent {} diff --git a/lib/features/quiz_upload/presentation/state_management/quiz_input_page/quiz_input_page_state.dart b/lib/features/quiz_upload/presentation/state_management/quiz_input_page/quiz_input_page_state.dart index 84331ce8..d306d47c 100644 --- a/lib/features/quiz_upload/presentation/state_management/quiz_input_page/quiz_input_page_state.dart +++ b/lib/features/quiz_upload/presentation/state_management/quiz_input_page/quiz_input_page_state.dart @@ -11,6 +11,4 @@ class QuizInputPageRefresh extends QuizInputPageState {} class QuizInputPageQuestionsAdd extends QuizInputPageState {} -class QuizInputPageConfirmSubmit extends QuizInputPageState {} - class QuizInputPageConfirmCancel extends QuizInputPageState {} diff --git a/lib/features/quiz_upload/presentation/widgets/quiz_input_page_widgets/add_or_submit_display.dart b/lib/features/quiz_upload/presentation/widgets/quiz_input_page_widgets/add_or_submit_display.dart index 40d8bf52..bbe6f7d9 100644 --- a/lib/features/quiz_upload/presentation/widgets/quiz_input_page_widgets/add_or_submit_display.dart +++ b/lib/features/quiz_upload/presentation/widgets/quiz_input_page_widgets/add_or_submit_display.dart @@ -1,5 +1,6 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:scholars_guide/features/quiz_upload/presentation/widgets/quiz_input_page_widgets/confirm_submit_quiz_input_dialogue.dart'; import '../../state_management/quiz_input_page/quiz_input_page_bloc.dart'; @@ -19,9 +20,11 @@ class AddOrSubmitDisplay extends StatelessWidget { ), ElevatedButton( onPressed: () { - context - .read() - .add(QuizInputPageSubmitBtnPressed()); + showDialog( + context: context, + builder: (BuildContext buildContext) { + return ConfirmSubmitQuizInputDialogue(quizInputPageBloc: context.read()); + }); }, child: const Text('Submit Questions'), ) diff --git a/lib/features/quiz_upload/presentation/widgets/quiz_input_page_widgets/confirm_cancel_quiz_input_display.dart b/lib/features/quiz_upload/presentation/widgets/quiz_input_page_widgets/confirm_cancel_quiz_input_dialogue.dart similarity index 100% rename from lib/features/quiz_upload/presentation/widgets/quiz_input_page_widgets/confirm_cancel_quiz_input_display.dart rename to lib/features/quiz_upload/presentation/widgets/quiz_input_page_widgets/confirm_cancel_quiz_input_dialogue.dart diff --git a/lib/features/quiz_upload/presentation/widgets/quiz_input_page_widgets/confirm_submit_quiz_input_dialogue.dart b/lib/features/quiz_upload/presentation/widgets/quiz_input_page_widgets/confirm_submit_quiz_input_dialogue.dart new file mode 100644 index 00000000..88b7abb9 --- /dev/null +++ b/lib/features/quiz_upload/presentation/widgets/quiz_input_page_widgets/confirm_submit_quiz_input_dialogue.dart @@ -0,0 +1,45 @@ +// ignore_for_file: prefer_const_constructors + +import 'package:flutter/material.dart'; +import 'package:go_router/go_router.dart'; +import 'package:scholars_guide/features/quiz_upload/presentation/state_management/quiz_input/quiz_input_cubit.dart'; +import 'package:scholars_guide/features/quiz_upload/presentation/state_management/quiz_input_page/quiz_input_page_bloc.dart'; + +class ConfirmSubmitQuizInputDialogue extends StatelessWidget { + const ConfirmSubmitQuizInputDialogue({super.key, required this.quizInputPageBloc}); + + final QuizInputPageBloc quizInputPageBloc; + + @override + Widget build(BuildContext context) { + return AlertDialog( + title: const Text('Submit Confirmation'), + content: const Text("Your questions will be submitted! Are you sure?"), + actions: [ + ElevatedButton( + child: const Text('Submit Questions'), + onPressed: () { + Navigator.of(context).pop(); + + quizInputPageBloc.add(QuizInputPageSubmitBtnPressed()); + + if (quizInputPageBloc.isSubmittable()){ + GoRouter.of(context).go('/quiz-upload/finished-quiz-upload', extra: { + 'questionsToUpload': quizInputPageBloc.questions, + 'subjToUpload': quizInputPageBloc.subject, + }); + quizInputPageBloc.add(QuizInputPageReset()); + } + + }, + ), + ElevatedButton( + child: const Text('Go Back'), + onPressed: () { + Navigator.of(context).pop(); + }, + ), + ], + ); + } +} diff --git a/lib/features/quiz_upload/presentation/widgets/quiz_input_page_widgets/confirm_submit_quiz_input_display.dart b/lib/features/quiz_upload/presentation/widgets/quiz_input_page_widgets/confirm_submit_quiz_input_display.dart deleted file mode 100644 index 2fff7eb2..00000000 --- a/lib/features/quiz_upload/presentation/widgets/quiz_input_page_widgets/confirm_submit_quiz_input_display.dart +++ /dev/null @@ -1,53 +0,0 @@ -// ignore_for_file: prefer_const_constructors - -import 'package:flutter/material.dart'; -import 'package:flutter_bloc/flutter_bloc.dart'; -import 'package:scholars_guide/features/quiz_upload/presentation/pages/upload_success_page.dart'; -import 'package:scholars_guide/features/quiz_upload/presentation/state_management/quiz_input_page/quiz_input_page_bloc.dart'; - -class ConfirmSubmitQuizInputDisplay extends StatelessWidget { - const ConfirmSubmitQuizInputDisplay({super.key}); - - @override - Widget build(BuildContext context) { - return Scaffold( - body: Container( - width: double.infinity, - height: double.infinity, - decoration: BoxDecoration( - color: Colors.black.withOpacity(0.6), - ), - child: AlertDialog( - title: const Text('Submit Confirmation'), - content: - const Text("Your questions will be submitted! Are you sure?"), - actions: [ - ElevatedButton( - child: const Text('Submit Questions'), - onPressed: () { - Navigator.push( - context, - MaterialPageRoute( - builder: (newContext) => (UploadSuccessPage( - questionsToUpload: - context.read().questions, - subjToUpload: context - .read() - .subject, - )), - )); - }, - ), - ElevatedButton( - child: const Text('Go Back'), - onPressed: () { - context - .read() - .add(QuizInputPageCancelSubmitBtnPressed()); - }, - ), - ], - )), - ); - } -} diff --git a/lib/features/quiz_upload/presentation/widgets/quiz_input_widgets/question_input_card_display.dart b/lib/features/quiz_upload/presentation/widgets/quiz_input_widgets/question_input_card_display.dart index 241d7971..94ed15ee 100644 --- a/lib/features/quiz_upload/presentation/widgets/quiz_input_widgets/question_input_card_display.dart +++ b/lib/features/quiz_upload/presentation/widgets/quiz_input_widgets/question_input_card_display.dart @@ -2,6 +2,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:flutter_form_builder/flutter_form_builder.dart'; import 'package:scholars_guide/features/quiz_upload/presentation/state_management/quiz_input/quiz_input_cubit.dart'; import 'package:scholars_guide/features/quiz_upload/presentation/state_management/quiz_input_page/quiz_input_page_bloc.dart'; @@ -23,21 +24,30 @@ class _QuestionInputCardState extends State { TextEditingController questionController = TextEditingController(text: widget.questionCubit.state.question); + TextEditingController solutionController = + TextEditingController(text: widget.questionCubit.state.solution); + questionController.selection = TextSelection.fromPosition( TextPosition( offset: questionController.text.length, ), ); + solutionController.selection = TextSelection.fromPosition( + TextPosition( + offset: solutionController.text.length, + ), + ); + return Container( width: MediaQuery.of(context).size.width * 0.90, - height: 430, + height: 630, padding: EdgeInsets.only(top: 10.0, bottom: 10.0), - child: Container( - decoration: BoxDecoration( - color: Colors.deepOrange[100], // ! Panget UI - borderRadius: BorderRadius.circular(10.0), - ), + child: FormBuilder( + // decoration: BoxDecoration( + // color: Colors.deepPurple[100], // ! Panget UI + // borderRadius: BorderRadius.circular(10.0), + // ), child: Column( children: [ Container( @@ -53,7 +63,7 @@ class _QuestionInputCardState extends State { ? Colors.black : Colors.red : Colors.black, - width: 2), + width: 1), ), hintText: "Enter the question here", filled: true, @@ -89,6 +99,34 @@ class _QuestionInputCardState extends State { optionIndex: 3, revealBlank: quizInputPageBloc.revealBlanks && !widget.questionCubit.state.optionsNonEmpty[3]), + Container( + padding: EdgeInsets.all(8.0), + height: 200, + child: TextField( + decoration: InputDecoration( + enabledBorder: OutlineInputBorder( + borderRadius: BorderRadius.circular(7.0), + borderSide: BorderSide( + color: quizInputPageBloc.revealBlanks + ? widget.questionCubit.state.solutionNonEmpty + ? Colors.black + : Colors.red + : Colors.black, + width: 1), + ), + hintText: "Enter the solution here", + filled: true, + fillColor: Colors.white, + ), + maxLines: null, + expands: true, + controller: solutionController, + onChanged: (solutionInput) { + widget.questionCubit + .solutionChanged(solution: solutionInput); + }, + ), + ), ], )), ); @@ -138,7 +176,7 @@ class _QuestionInputOptionsState extends State { borderRadius: BorderRadius.circular(7.0), borderSide: BorderSide( color: widget.revealBlank ? Colors.red : Colors.black, - width: 2), + width: 1), ), hintText: "Enter one of the options here", filled: true, diff --git a/lib/features/quiz_upload/presentation/widgets/quiz_input_widgets/question_input_tab_display.dart b/lib/features/quiz_upload/presentation/widgets/quiz_input_widgets/question_input_tab_display.dart new file mode 100644 index 00000000..271fc6fd --- /dev/null +++ b/lib/features/quiz_upload/presentation/widgets/quiz_input_widgets/question_input_tab_display.dart @@ -0,0 +1,151 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_form_builder/flutter_form_builder.dart'; +import 'package:flutter_markdown/flutter_markdown.dart'; +import 'package:flutter_markdown_latex/flutter_markdown_latex.dart'; +import 'package:form_builder_validators/form_builder_validators.dart'; +import 'package:logger/logger.dart'; +import 'package:markdown/markdown.dart' as md; +import 'package:scholars_guide/service_locator/service_locator.dart'; + +class QuestionInputTabDisplay extends StatefulWidget { + const QuestionInputTabDisplay({super.key}); + + @override + State createState() => + _QuestionInputTabDisplayState(); +} + +class _QuestionInputTabDisplayState extends State { + @override + Widget build(BuildContext context) { + final editFormKey = GlobalKey(); + late String markdownData = ''' + This is inline latex: \$f(x) = \\sum_{i=0}^{n} \\frac{a_i}{1+x}\$ + + This is block level latex: + + \$ + c = \\pm\\sqrt{a^2 + b^2} + \$ + + This is inline latex with displayMode: \$\$f(x) = \\sum_{i=0}^{n} \\frac{a_i}{1+x}\$\$ + + To calculate the area of an equilateral triangle using trigonometric functions, one can consider using the length of the side and the height. The relationship between the height and the side length of an equilateral triangle is: + + \\[ \\text{Height} = \\frac{\\sqrt{3}}{2} \\times \\text{Side Length} \\] + + 因此,边长为 9 的正三角形的面积为: + + \\[ \\text{面积} = \\frac{1}{2} \\times \\text{底} \\times \\text{高} = \\frac{1}{2} \\times 9 \\times \\frac{\\sqrt{3}}{2} \\times 9 = \\frac{81\\sqrt{3}}{4} \\] + + 所以正三角形的面积为 \\( \\frac{81\\sqrt{3}}{4} \\)。 + + '''; + + return DefaultTabController( + initialIndex: 0, + length: 2, + child: Scaffold( + appBar: AppBar( + title: const Text('Question Input'), + bottom: TabBar( + onTap: (value) { + services().i('Tab index: $value'); + + if (value == 0) { + setState(() { + editFormKey.currentState?.fields['question'] + ?.didChange(markdownData); + }); + } + }, + tabs: const [ + Tab( + icon: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Icon(Icons.edit_outlined), + SizedBox(width: 8.0), + Text('Edit'), + ], + ), + ), + Tab( + icon: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Icon(Icons.preview_outlined), + SizedBox(width: 8.0), + Text('Preview'), + ], + ), + // text: 'Preview', + ), + ], + ), + ), + body: SafeArea( + child: TabBarView( + children: [ + // Edit. + SingleChildScrollView( + child: Padding( + padding: const EdgeInsets.all(32.0), + child: FormBuilder( + key: editFormKey, + child: Column( + children: [ + // Question. + FormBuilderTextField( + name: 'question', + // TODO: [P2]: Make maxLines a user option. + maxLines: null, + minLines: 6, + initialValue: markdownData, + decoration: const InputDecoration( + border: OutlineInputBorder(), + ), + validator: FormBuilderValidators.compose([ + FormBuilderValidators.required(), + FormBuilderValidators.minLength(5), + // TODO [P3]: Needs more user research. Subject to change. + FormBuilderValidators.maxLength(1500), + ]), + onChanged: (String? value) { + setState(() { + markdownData = value ?? ''; + }); + }, + ), + + // Choices + const SizedBox(height: 400.0) + ], + ), + ), + ), + ), + + // Preview. + Padding( + padding: const EdgeInsets.all(24.0), + child: Card( + child: Markdown( + data: markdownData, + builders: { + 'latex': LatexElementBuilder(), + }, + extensionSet: md.ExtensionSet( + [LatexBlockSyntax()], + [LatexInlineSyntax()], + ), + ), + ), + ), + ], + ), + ), + ), + ); + } +} diff --git a/lib/features/quiz_upload/presentation/widgets/upload_quiz_widgets/upload_complete_display.dart b/lib/features/quiz_upload/presentation/widgets/upload_quiz_widgets/upload_complete_display.dart index 47ff2e3e..58bef0ab 100644 --- a/lib/features/quiz_upload/presentation/widgets/upload_quiz_widgets/upload_complete_display.dart +++ b/lib/features/quiz_upload/presentation/widgets/upload_quiz_widgets/upload_complete_display.dart @@ -1,8 +1,7 @@ // ignore_for_file: prefer_const_constructors, prefer_const_literals_to_create_immutables, prefer_const_constructors_in_immutables import 'package:flutter/material.dart'; -import 'package:scholars_guide/features/quiz_mode/presentation/pages/ready_quiz_page.dart'; -import 'package:scholars_guide/features/quiz_upload/presentation/pages/upload_questions_page.dart'; +import 'package:go_router/go_router.dart'; class UploadCompleteDisplay extends StatelessWidget { UploadCompleteDisplay({super.key}); @@ -14,23 +13,19 @@ class UploadCompleteDisplay extends StatelessWidget { mainAxisAlignment: MainAxisAlignment.center, children: [ Text("Upload Success!"), - // TODO: Show results / Show all questions uploaded by user + + // Todo: Show results / Show all questions uploaded by user + ElevatedButton( onPressed: () { - Navigator.push( - context, - MaterialPageRoute(builder: (context) => (UploadQuestionPage())), - ); + GoRouter.of(context).go('/quiz-upload'); }, child: Text("Add more questions")), - ElevatedButton( - onPressed: () {}, - child: Text("Go Back to Home Page")), // ! Add a route to home page + // ElevatedButton( + // onPressed: () {}, + // child: Text("Go Back to Home Page")), // ! Add a route to home page ElevatedButton(onPressed: () { - Navigator.push( - context, - MaterialPageRoute(builder: (context) => (ReadyQuizPage())), - ); + GoRouter.of(context).go('/quiz-mode'); }, child: Text("Take a quiz now!")) ], )); diff --git a/lib/router/app_router.dart b/lib/router/app_router.dart index c5e94740..10896d20 100644 --- a/lib/router/app_router.dart +++ b/lib/router/app_router.dart @@ -1,7 +1,11 @@ import 'package:flutter/material.dart'; import 'package:go_router/go_router.dart'; +import 'package:scholars_guide/features/quiz_mode/presentation/pages/finished_quiz_page.dart'; +import 'package:scholars_guide/features/quiz_mode/presentation/pages/quiz_page.dart'; import 'package:scholars_guide/features/quiz_mode/presentation/pages/ready_quiz_page.dart'; import 'package:scholars_guide/features/quiz_upload/presentation/pages/upload_questions_page.dart'; +import 'package:scholars_guide/features/quiz_upload/presentation/pages/upload_success_page.dart'; +import 'package:scholars_guide/features/quiz_upload/presentation/widgets/quiz_input_widgets/question_input_tab_display.dart'; import '../core/features/auth/presentation/screens/email_verification_screen.dart'; import '../core/features/auth/presentation/screens/forgot_password_screen.dart'; @@ -122,6 +126,20 @@ class AppRouter { child: ReadyQuizPage(), ); }, + routes: [ + GoRoute( + path: 'start-quiz', + pageBuilder: (context, state) => const NoTransitionPage( + child: QuizPage(), + ), + ), + GoRoute( + path: 'finished-quiz', + pageBuilder: (context, state) => const NoTransitionPage( + child: FinishedQuizPage(), + ), + ) + ] ), ], ), @@ -135,9 +153,18 @@ class AppRouter { path: '/quiz-upload', pageBuilder: (_, __) { return const NoTransitionPage( - child: UploadQuestionPage(), + // child: UploadQuestionPage(), + child: QuestionInputTabDisplay() ); }, + routes: [ + GoRoute( + path: 'finished-quiz-upload', + pageBuilder: (context, state) => const NoTransitionPage( + child: UploadSuccessPage(), + ), + ), + ] ), ], ), From 63e4c59384534a785bcbddf419fc7d37e1815133 Mon Sep 17 00:00:00 2001 From: Rhon Date: Mon, 4 Mar 2024 04:35:08 +0800 Subject: [PATCH 2/6] Fixed latex, better ui --- .../pages/finished_quiz_page.dart | 24 ++- .../presentation/pages/quiz_page.dart | 115 +++++++------ .../quiz_page_widgets/timer_display.dart | 54 ++----- .../question_card_choices_display.dart | 68 +++++--- .../quiz_widgets/question_card_display.dart | 23 ++- .../question_card_question_display.dart | 43 ++++- .../quiz_widgets/question_display.dart | 25 ++- .../choose_subject_display.dart | 31 ++-- .../pages/upload_questions_page.dart | 92 +++++++---- .../quiz_input/quiz_input_state.dart | 2 +- .../quiz_input_page/quiz_input_page_bloc.dart | 2 + .../add_or_submit_display.dart | 43 ++--- .../question_input_card_display.dart | 24 ++- .../question_input_display.dart | 67 ++++---- .../question_input_tab_display.dart | 151 ------------------ .../question_preview_card_display.dart | 119 ++++++++++++++ .../question_preview_display.dart | 22 +++ lib/router/app_router.dart | 4 +- pubspec.lock | 8 - pubspec.yaml | 4 - 20 files changed, 492 insertions(+), 429 deletions(-) delete mode 100644 lib/features/quiz_upload/presentation/widgets/quiz_input_widgets/question_input_tab_display.dart create mode 100644 lib/features/quiz_upload/presentation/widgets/quiz_input_widgets/question_preview_card_display.dart create mode 100644 lib/features/quiz_upload/presentation/widgets/quiz_input_widgets/question_preview_display.dart diff --git a/lib/features/quiz_mode/presentation/pages/finished_quiz_page.dart b/lib/features/quiz_mode/presentation/pages/finished_quiz_page.dart index e35bd9b0..620031a4 100644 --- a/lib/features/quiz_mode/presentation/pages/finished_quiz_page.dart +++ b/lib/features/quiz_mode/presentation/pages/finished_quiz_page.dart @@ -11,24 +11,30 @@ class FinishedQuizPage extends StatelessWidget { @override Widget build(BuildContext context) { - final extraMap = GoRouterState.of(context).extra as Map; final SUBJ subject = extraMap['subject'] as SUBJ; - final Map> subjectQuestionsMap = extraMap['subjectQuestionsMap'] as Map>; + final Map> subjectQuestionsMap = + extraMap['subjectQuestionsMap'] as Map>; return Scaffold( appBar: AppBar( automaticallyImplyLeading: false, title: const Text('Quiz Results'), ), - body: Center( + body: SingleChildScrollView( child: Column( children: [ - Text("Congrats on finishing! Let's see how you did"), + Container( + padding: const EdgeInsets.all(10.0), + margin: EdgeInsets.only(top: 20, bottom: 20), + child: Text( + "Congrats on finishing! Let's see how you did", + style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold), + ), + ), QuestionDisplay( - subjectQuestionsMap: subjectQuestionsMap, - subject: subject), + subjectQuestionsMap: subjectQuestionsMap, subject: subject), ElevatedButton( onPressed: () { @@ -36,7 +42,11 @@ class FinishedQuizPage extends StatelessWidget { }, child: Text('Take Another Quiz'), ), - + + SizedBox( + height: 20, + ), + // ElevatedButton( // onPressed: () { // }, diff --git a/lib/features/quiz_mode/presentation/pages/quiz_page.dart b/lib/features/quiz_mode/presentation/pages/quiz_page.dart index f2c6efc0..76932b6a 100644 --- a/lib/features/quiz_mode/presentation/pages/quiz_page.dart +++ b/lib/features/quiz_mode/presentation/pages/quiz_page.dart @@ -26,58 +26,77 @@ class _QuizPageState extends State { final subject = GoRouterState.of(context).extra as SUBJ; return BlocProvider( - create: (context) => QuizBloc() - ..add(QuizLoadQuestions( - subject: subject, numQuestions: widget.numQuestions)), - child: Builder(builder: (builderContext) { + create: (context) => QuizBloc() + ..add(QuizLoadQuestions( + subject: subject, numQuestions: widget.numQuestions)), + child: Builder( + builder: (builderContext) { return Scaffold( - appBar: AppBar( - title: Text('${Question.SUBJ2string(subject)} Quiz'), - leading: IconButton( - icon: Icon(Icons.arrow_back), - onPressed: () { - showDialog( - context: context, - builder: (BuildContext buildContext) { - return ConfirmCancelQuizDialogue(); - }); + appBar: AppBar( + title: Text('${Question.SUBJ2string(subject)} Quiz'), + leading: IconButton( + icon: Icon(Icons.arrow_back), + onPressed: () { + showDialog( + context: context, + builder: (BuildContext buildContext) { + return ConfirmCancelQuizDialogue(); }, - )), - body: Center(child: BlocBuilder( - builder: (quizBlocContext, state) { - if (state is QuizLoading) { - return QuestionLoadingDisplay(); - } else if (state is QuizOngoing) { - return Column( - children: [ - // * Display the questions - QuestionDisplay( - subjectQuestionsMap: quizBlocContext - .read() - .subjectQuestionsMap, - subject: subject), + ); + }, + ), + ), + body: Stack( + children: [ + SingleChildScrollView( + child: BlocBuilder( + builder: (quizBlocContext, state) { + if (state is QuizLoading) { + return QuestionLoadingDisplay(); + } else if (state is QuizOngoing) { + return Column( + children: [ + // * Display the questions + QuestionDisplay( + subjectQuestionsMap: quizBlocContext + .read() + .subjectQuestionsMap, + subject: subject), - // * Display the submit button - ElevatedButton( - onPressed: () { - showDialog( - context: context, - builder: (BuildContext buildContext) { - return ConfirmSubmitQuizDialogue(quizBloc: quizBlocContext.read()); - }); - }, - child: Text("Submit")), + // * Display the submit button + ElevatedButton( + onPressed: () { + showDialog( + context: context, + builder: (BuildContext buildContext) { + return ConfirmSubmitQuizDialogue( + quizBloc: + quizBlocContext.read(), + ); + }, + ); + }, + child: Text("Submit")), - // * Display the timer - TimerDisplay() - ], - ); - } + + ], + ); + } - GoRouter.of(context).go('/quiz-mode'); - return Text( - "SOMETHING WENT WRONG! (No Quiz Bloc State Matched)"); - }))); - })); + GoRouter.of(context).go('/quiz-mode'); + return Text( + "SOMETHING WENT WRONG! (No Quiz Bloc State Matched)"); + }, + ), + ), + + // * Display the timer + TimerDisplay(), + ], + ), + ); + }, + ), + ); } } diff --git a/lib/features/quiz_mode/presentation/widgets/quiz_page_widgets/timer_display.dart b/lib/features/quiz_mode/presentation/widgets/quiz_page_widgets/timer_display.dart index 449e55e3..6c492c93 100644 --- a/lib/features/quiz_mode/presentation/widgets/quiz_page_widgets/timer_display.dart +++ b/lib/features/quiz_mode/presentation/widgets/quiz_page_widgets/timer_display.dart @@ -3,7 +3,6 @@ import 'package:circular_countdown_timer/circular_countdown_timer.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; -import 'package:flutter_portal/flutter_portal.dart'; import 'package:scholars_guide/features/quiz_mode/presentation/state_management/quiz/quiz_bloc.dart'; import 'package:scholars_guide/features/quiz_mode/presentation/widgets/quiz_page_widgets/confirm_timeout_quiz_dialogue.dart'; @@ -17,35 +16,11 @@ class TimerDisplay extends StatefulWidget { class _TimerDisplayState extends State { @override Widget build(BuildContext context) { - return Expanded( - flex: 0, - child: Align( - alignment: Alignment.bottomRight, - child: TimerOverlay(), - )); - } -} - -class TimerOverlay extends StatefulWidget { - const TimerOverlay({super.key}); - - @override - State createState() => _TimerOverlayState(); -} - -class _TimerOverlayState extends State { - @override - Widget build(BuildContext context) { - return Portal( - child: PortalTarget( - visible: true, - anchor: const Aligned( - follower: Alignment.bottomRight, - target: Alignment.bottomCenter, - ), - portalFollower: Container( - margin: EdgeInsets.only(right: 20), child: CountdownTimer()), - child: SizedBox())); + return Positioned( + right: MediaQuery.of(context).size.width * 0.05, + bottom: MediaQuery.of(context).size.width * 0.05, + child: CountdownTimer(), + ); } } @@ -63,18 +38,20 @@ class _CountdownTimerState extends State { duration: 60 * 10, // ! 10 minutes muna initialDuration: 0, controller: CountDownController(), - width: MediaQuery.of(context).size.width / 6, - height: MediaQuery.of(context).size.height / 6, - ringColor: Colors.grey[300]!, + width: MediaQuery.of(context).size.width / 7, + height: MediaQuery.of(context).size.height / 7, + ringColor: Colors.indigo.withOpacity(0.0), ringGradient: null, - fillColor: Colors.grey[600]!, + fillColor: Colors.indigo.withOpacity(0.5), fillGradient: null, - backgroundColor: Colors.grey[500], + backgroundColor: Colors.indigo.withOpacity(0.3), backgroundGradient: null, - strokeWidth: 10.0, + strokeWidth: 5.0, strokeCap: StrokeCap.round, textStyle: TextStyle( - fontSize: 20.0, color: Colors.white, fontWeight: FontWeight.bold), + fontSize: 20.0, + color: Colors.indigo.withOpacity(0.8), + fontWeight: FontWeight.bold), textFormat: CountdownTextFormat.S, isReverse: true, isReverseAnimation: true, @@ -89,7 +66,8 @@ class _CountdownTimerState extends State { barrierDismissible: false, context: context, builder: (BuildContext buildContext) { - return ConfirmTimeoutQuizDialogue(quizBloc: context.read()); + return ConfirmTimeoutQuizDialogue( + quizBloc: context.read()); }); }, onChange: (String timeStamp) { diff --git a/lib/features/quiz_mode/presentation/widgets/quiz_widgets/question_card_choices_display.dart b/lib/features/quiz_mode/presentation/widgets/quiz_widgets/question_card_choices_display.dart index aff95fd4..204bdae2 100644 --- a/lib/features/quiz_mode/presentation/widgets/quiz_widgets/question_card_choices_display.dart +++ b/lib/features/quiz_mode/presentation/widgets/quiz_widgets/question_card_choices_display.dart @@ -2,11 +2,13 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:flutter_markdown/flutter_markdown.dart'; +import 'package:flutter_markdown_latex/flutter_markdown_latex.dart'; import 'package:scholars_guide/features/quiz_mode/presentation/state_management/quiz_card/quiz_card_cubit.dart'; +import 'package:markdown/markdown.dart' as md; class QuestionCardChoicesDisplay extends StatefulWidget { - const QuestionCardChoicesDisplay( - {super.key, required this.bloc}); + const QuestionCardChoicesDisplay({super.key, required this.bloc}); final QuizCardCubit bloc; @@ -61,16 +63,7 @@ class _QuestionCardChoicesDisplayState buttons.add(button); } - return Expanded( - flex: 2, - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceEvenly, - children: [ - Column(children: buttons.sublist(0, 2)), - Column(children: buttons.sublist(2)) - ], - ), - ); + return Column(children: buttons); }); } } @@ -84,20 +77,55 @@ ElevatedButton _createChoiceButton( required context}) { Color color = Colors.black; if (isRevealed) { - color = isCorrect ? isChosen? Colors.green: Colors.black : isChosen ? Colors.red : Colors.black; + color = isCorrect + ? isChosen + ? Colors.green + : Colors.black + : isChosen + ? Colors.red + : Colors.black; } else if (isChosen) { - color = Colors.blue; + color = Colors.indigoAccent; } return ElevatedButton( style: ButtonStyle( shape: MaterialStateProperty.all( - RoundedRectangleBorder( - borderRadius: BorderRadius.circular(7.0), - side: BorderSide(color: color))), - minimumSize: MaterialStateProperty.all( - Size(MediaQuery.of(context).size.width * 0.4, 40.0)), + RoundedRectangleBorder( + borderRadius: BorderRadius.circular(10.0), + side: BorderSide(color: color), + ), + ), ), onPressed: isRevealed ? null : func, - child: Text(choice, style: TextStyle(fontSize: 15.0)), + child: TextMarkdown(text: choice), ); } + +class TextMarkdown extends StatelessWidget { + const TextMarkdown({ + super.key, + required this.text, + }); + + final String text; + + @override + Widget build(BuildContext context) { + return Markdown( + padding: EdgeInsets.all(0.0), + shrinkWrap: true, + data: text, + builders: { + 'latex': LatexElementBuilder(), + }, + styleSheet: MarkdownStyleSheet( + textAlign: WrapAlignment.center, + p: const TextStyle(fontSize: 15.0), + ), + extensionSet: md.ExtensionSet( + [LatexBlockSyntax()], + [LatexInlineSyntax()], + ), + ); + } +} diff --git a/lib/features/quiz_mode/presentation/widgets/quiz_widgets/question_card_display.dart b/lib/features/quiz_mode/presentation/widgets/quiz_widgets/question_card_display.dart index 2b116dc7..654de70c 100644 --- a/lib/features/quiz_mode/presentation/widgets/quiz_widgets/question_card_display.dart +++ b/lib/features/quiz_mode/presentation/widgets/quiz_widgets/question_card_display.dart @@ -25,20 +25,17 @@ class QuestionCardDisplay extends StatefulWidget { class _QuestionCardDisplayState extends State { @override Widget build(BuildContext context) { - return Container( - height: 300, - decoration: BoxDecoration( - color: Colors.grey[400], - borderRadius: BorderRadius.circular(7.0), - ), - // alignment: Alignment.center, + return Card( margin: EdgeInsets.all(10.0), - child: Column( - children: [ - QuestionCardQuestionDisplay(question: widget.question), - QuestionCardChoicesDisplay( - bloc: widget.bloc) - ], + child: Padding( + padding: const EdgeInsets.all(8.0), + child: Column( + children: [ + QuestionCardQuestionDisplay(question: widget.question), + QuestionCardChoicesDisplay(bloc: widget.bloc), + SizedBox(height: 15.0) + ], + ), )); } } diff --git a/lib/features/quiz_mode/presentation/widgets/quiz_widgets/question_card_question_display.dart b/lib/features/quiz_mode/presentation/widgets/quiz_widgets/question_card_question_display.dart index f6aa5be3..df4cb426 100644 --- a/lib/features/quiz_mode/presentation/widgets/quiz_widgets/question_card_question_display.dart +++ b/lib/features/quiz_mode/presentation/widgets/quiz_widgets/question_card_question_display.dart @@ -1,4 +1,9 @@ +// ignore_for_file: prefer_const_constructors + import 'package:flutter/material.dart'; +import 'package:flutter_markdown/flutter_markdown.dart'; +import 'package:flutter_markdown_latex/flutter_markdown_latex.dart'; +import 'package:markdown/markdown.dart' as md; class QuestionCardQuestionDisplay extends StatelessWidget { const QuestionCardQuestionDisplay({super.key, required this.question}); @@ -7,13 +12,35 @@ class QuestionCardQuestionDisplay extends StatelessWidget { @override Widget build(BuildContext context) { - return Expanded( - flex: 3, - child: Align( - alignment: Alignment.center, - child: Container( - margin: const EdgeInsets.only(left: 25.0, right: 25.0), - child: - Text(question, style: const TextStyle(fontSize: 17.0))))); + return TextMarkdown(text: question); + } +} + +class TextMarkdown extends StatelessWidget { + const TextMarkdown({ + super.key, + required this.text, + }); + + final String text; + + @override + Widget build(BuildContext context) { + return Markdown( + physics: const NeverScrollableScrollPhysics(), + shrinkWrap: true, + data: text, + styleSheet: MarkdownStyleSheet( + textAlign: WrapAlignment.center, + p: const TextStyle(fontSize: 17.0), + ), + builders: { + 'latex': LatexElementBuilder(), + }, + extensionSet: md.ExtensionSet( + [LatexBlockSyntax()], + [LatexInlineSyntax()], + ), + ); } } diff --git a/lib/features/quiz_mode/presentation/widgets/quiz_widgets/question_display.dart b/lib/features/quiz_mode/presentation/widgets/quiz_widgets/question_display.dart index fe9e0133..7c769b4f 100644 --- a/lib/features/quiz_mode/presentation/widgets/quiz_widgets/question_display.dart +++ b/lib/features/quiz_mode/presentation/widgets/quiz_widgets/question_display.dart @@ -9,9 +9,7 @@ import 'question_card_display.dart'; // Holds multiple question answer cards class QuestionDisplay extends StatefulWidget { const QuestionDisplay( - {super.key, - required this.subjectQuestionsMap, - required this.subject}); + {super.key, required this.subjectQuestionsMap, required this.subject}); final SUBJ subject; final Map> subjectQuestionsMap; @@ -24,16 +22,17 @@ class _QuestionDisplayState extends State { @override Widget build(BuildContext context) { List questions = widget.subjectQuestionsMap[widget.subject]!; - return Expanded( - child: ListView.builder( - itemCount: widget.subjectQuestionsMap[widget.subject]?.length, - itemBuilder: (context, index) { - return QuestionCardDisplay( - bloc: questions[index], - choices: questions[index].optionsArray, - correctIndex: questions[index].correctIndex, - question: questions[index].question); - }), + return ListView.builder( + shrinkWrap: true, + physics: const NeverScrollableScrollPhysics(), + itemCount: widget.subjectQuestionsMap[widget.subject]?.length, + itemBuilder: (context, index) { + return QuestionCardDisplay( + bloc: questions[index], + choices: questions[index].optionsArray, + correctIndex: questions[index].correctIndex, + question: questions[index].question); + }, ); } } diff --git a/lib/features/quiz_mode/presentation/widgets/ready_quiz_page_widgets/choose_subject_display.dart b/lib/features/quiz_mode/presentation/widgets/ready_quiz_page_widgets/choose_subject_display.dart index af57280b..a954a7d2 100644 --- a/lib/features/quiz_mode/presentation/widgets/ready_quiz_page_widgets/choose_subject_display.dart +++ b/lib/features/quiz_mode/presentation/widgets/ready_quiz_page_widgets/choose_subject_display.dart @@ -1,5 +1,4 @@ import 'package:flutter/material.dart'; -import 'package:flutter_portal/flutter_portal.dart'; import 'package:scholars_guide/features/quiz_mode/presentation/widgets/ready_quiz_page_widgets/subject_chosen_dialogue.dart'; class ChooseSubjectDisplay extends StatefulWidget { @@ -12,18 +11,20 @@ class ChooseSubjectDisplay extends StatefulWidget { class _ChooseSubjectDisplayState extends State { @override Widget build(BuildContext context) { - return Portal( - child: Column(mainAxisAlignment: MainAxisAlignment.center, children: [ - const Text( - "Choose a subject to start the quiz", - style: TextStyle(fontSize: 20), - ), - // buildElevatedButton("UPCAT Challenge", context), // TODO: Implement UPCAT Challenge - buildElevatedButton("Math", context), - buildElevatedButton("Science", context), - buildElevatedButton("Language Proficiency", context), - buildElevatedButton("Reading Comprehension", context), - ])); + return Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + const Text( + "Choose a subject to start the quiz", + style: TextStyle(fontSize: 20), + ), + // buildElevatedButton("UPCAT Challenge", context), // TODO: Implement UPCAT Challenge + buildElevatedButton("Math", context), + buildElevatedButton("Science", context), + buildElevatedButton("Language Proficiency", context), + buildElevatedButton("Reading Comprehension", context), + ], + ); } } @@ -37,7 +38,9 @@ Container buildElevatedButton(String text, BuildContext context) { showDialog( context: context, builder: (BuildContext buildContext) { - return SubjectChosenDialogue(subjectTest: text,); + return SubjectChosenDialogue( + subjectTest: text, + ); }); }, child: Text(text))); diff --git a/lib/features/quiz_upload/presentation/pages/upload_questions_page.dart b/lib/features/quiz_upload/presentation/pages/upload_questions_page.dart index 2da9be56..7a4070c5 100644 --- a/lib/features/quiz_upload/presentation/pages/upload_questions_page.dart +++ b/lib/features/quiz_upload/presentation/pages/upload_questions_page.dart @@ -1,12 +1,13 @@ // ignore_for_file: prefer_const_constructors, prefer_const_literals_to_create_immutables import 'package:flutter/material.dart'; +import 'package:flutter/widgets.dart'; import 'package:scholars_guide/features/quiz_upload/presentation/state_management/quiz_input_page/quiz_input_page_bloc.dart'; import 'package:scholars_guide/features/quiz_upload/presentation/widgets/quiz_input_page_widgets/add_or_submit_display.dart'; import 'package:scholars_guide/features/quiz_upload/presentation/widgets/quiz_input_page_widgets/change_subject_display.dart'; -import 'package:scholars_guide/features/quiz_upload/presentation/widgets/quiz_input_page_widgets/confirm_cancel_quiz_input_dialogue.dart'; import 'package:scholars_guide/features/quiz_upload/presentation/widgets/quiz_input_widgets/question_input_display.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:scholars_guide/features/quiz_upload/presentation/widgets/quiz_input_widgets/question_preview_display.dart'; class UploadQuestionPage extends StatefulWidget { const UploadQuestionPage({super.key}); @@ -18,41 +19,68 @@ class UploadQuestionPage extends StatefulWidget { class _UploadQuestionPageState extends State { @override Widget build(BuildContext context) { - return BlocProvider( - create: (blocContext) => QuizInputPageBloc(), - child: GestureDetector( - onTap: () { - FocusScope.of(context).unfocus(); - }, - child: Scaffold( - appBar: AppBar( - title: const Text('Upload Questions'), - ), - body: BlocBuilder( - builder: (blocBuilderContext, state) { - if (state is QuizInputPageQuestionsAdd) { - return Center( - // Possible use ListView to have a scrollable navigation - child: Column( + return DefaultTabController( + initialIndex: 0, + length: 2, + child: BlocProvider( + create: (blocContext) => QuizInputPageBloc(), + child: GestureDetector( + onTap: () { + FocusScope.of(context).unfocus(); + }, + child: BlocBuilder( + builder: (blocBuilderContext, state) { + if (state is QuizInputPageQuestionsAdd) { + return Scaffold( + appBar: AppBar( + title: const Text('Upload Questions'), + bottom: TabBar( + tabs: const [ + Tab( + icon: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Icon(Icons.edit_outlined), + SizedBox(width: 8.0), + Text('Edit'), + ], + ), + ), + Tab( + icon: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Icon(Icons.preview_outlined), + SizedBox(width: 8.0), + Text('Preview'), + ], + ), + ), + ], + ), + ), + body: TabBarView( children: [ - Container( - margin: EdgeInsets.only(top: 10.0), + SingleChildScrollView( + child: Column( + children: [ + ChangeSubjectDisplay(), + QuestionInputDisplay(), + AddOrSubmitDisplay(), + ], + ), ), - ChangeSubjectDisplay(), - QuestionInputDisplay(), - AddOrSubmitDisplay(), - Container( - margin: EdgeInsets.only(bottom: 20.0), + SingleChildScrollView( + child: QuestionPreviewDisplay(), ), ], - )); - } else if (state is QuizInputPageConfirmCancel) { - return ConfirmCancelQuizInputDisplay(); - } - - return Text("Something went wrong! (state not found)"); - }, - )), + ), + ); + } + return Text("Something went wrong! (state not found)"); + }, + ), + ), ), ); } diff --git a/lib/features/quiz_upload/presentation/state_management/quiz_input/quiz_input_state.dart b/lib/features/quiz_upload/presentation/state_management/quiz_input/quiz_input_state.dart index 67f30ac7..2aa67b7a 100644 --- a/lib/features/quiz_upload/presentation/state_management/quiz_input/quiz_input_state.dart +++ b/lib/features/quiz_upload/presentation/state_management/quiz_input/quiz_input_state.dart @@ -22,7 +22,7 @@ class QuizInputState extends Equatable { final List optionsNonEmpty; @override - List get props => [question, options, answerIndex]; + List get props => [question, solution, options, answerIndex, questionNonEmpty, solutionNonEmpty, optionsNonEmpty]; QuizInputState copyWith({ String? question, diff --git a/lib/features/quiz_upload/presentation/state_management/quiz_input_page/quiz_input_page_bloc.dart b/lib/features/quiz_upload/presentation/state_management/quiz_input_page/quiz_input_page_bloc.dart index 0f6f44d4..dec49c76 100644 --- a/lib/features/quiz_upload/presentation/state_management/quiz_input_page/quiz_input_page_bloc.dart +++ b/lib/features/quiz_upload/presentation/state_management/quiz_input_page/quiz_input_page_bloc.dart @@ -87,9 +87,11 @@ class QuizInputPageBloc extends Bloc { print( " ====================================== Printing question ======================================"); print("Question: ${question.state.question}"); + print("Solution: ${question.state.solution}"); print("Options: ${question.state.options}"); print("Answer: ${question.state.answerIndex}"); print("Question non empty: ${question.state.questionNonEmpty}"); + print("Solution non empty: ${question.state.solutionNonEmpty}"); print("Options non empty: ${question.state.optionsNonEmpty}"); } } diff --git a/lib/features/quiz_upload/presentation/widgets/quiz_input_page_widgets/add_or_submit_display.dart b/lib/features/quiz_upload/presentation/widgets/quiz_input_page_widgets/add_or_submit_display.dart index bbe6f7d9..ceb33fa1 100644 --- a/lib/features/quiz_upload/presentation/widgets/quiz_input_page_widgets/add_or_submit_display.dart +++ b/lib/features/quiz_upload/presentation/widgets/quiz_input_page_widgets/add_or_submit_display.dart @@ -9,26 +9,29 @@ class AddOrSubmitDisplay extends StatelessWidget { @override Widget build(BuildContext context) { - return Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - ElevatedButton( - onPressed: () { - context.read().add(QuizInputPageAddBtnPressed()); - }, - child: const Text('Add Question'), - ), - ElevatedButton( - onPressed: () { - showDialog( - context: context, - builder: (BuildContext buildContext) { - return ConfirmSubmitQuizInputDialogue(quizInputPageBloc: context.read()); - }); - }, - child: const Text('Submit Questions'), - ) - ], + return Padding( + padding: const EdgeInsets.all(8.0), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + ElevatedButton( + onPressed: () { + context.read().add(QuizInputPageAddBtnPressed()); + }, + child: const Text('Add Question'), + ), + ElevatedButton( + onPressed: () { + showDialog( + context: context, + builder: (BuildContext buildContext) { + return ConfirmSubmitQuizInputDialogue(quizInputPageBloc: context.read()); + }); + }, + child: const Text('Submit Questions'), + ) + ], + ), ); } } diff --git a/lib/features/quiz_upload/presentation/widgets/quiz_input_widgets/question_input_card_display.dart b/lib/features/quiz_upload/presentation/widgets/quiz_input_widgets/question_input_card_display.dart index 94ed15ee..451fe964 100644 --- a/lib/features/quiz_upload/presentation/widgets/quiz_input_widgets/question_input_card_display.dart +++ b/lib/features/quiz_upload/presentation/widgets/quiz_input_widgets/question_input_card_display.dart @@ -39,15 +39,9 @@ class _QuestionInputCardState extends State { ), ); - return Container( - width: MediaQuery.of(context).size.width * 0.90, - height: 630, - padding: EdgeInsets.only(top: 10.0, bottom: 10.0), + return Card( + child: FormBuilder( - // decoration: BoxDecoration( - // color: Colors.deepPurple[100], // ! Panget UI - // borderRadius: BorderRadius.circular(10.0), - // ), child: Column( children: [ Container( @@ -60,9 +54,9 @@ class _QuestionInputCardState extends State { borderSide: BorderSide( color: quizInputPageBloc.revealBlanks ? widget.questionCubit.state.questionNonEmpty - ? Colors.black + ? Color.fromARGB(255, 178, 178, 178) : Colors.red - : Colors.black, + : Color.fromARGB(255, 178, 178, 178), width: 1), ), hintText: "Enter the question here", @@ -109,9 +103,9 @@ class _QuestionInputCardState extends State { borderSide: BorderSide( color: quizInputPageBloc.revealBlanks ? widget.questionCubit.state.solutionNonEmpty - ? Colors.black + ? Color.fromARGB(255, 178, 178, 178) : Colors.red - : Colors.black, + : Color.fromARGB(255, 178, 178, 178), width: 1), ), hintText: "Enter the solution here", @@ -164,7 +158,7 @@ class _QuestionInputOptionsState extends State { return Container( padding: EdgeInsets.all(2.0), margin: EdgeInsets.only(left: 30.0), - width: MediaQuery.of(context).size.width * 0.70, + width: MediaQuery.of(context).size.width * 0.90, height: 50, child: Row( mainAxisAlignment: MainAxisAlignment.center, @@ -175,7 +169,7 @@ class _QuestionInputOptionsState extends State { enabledBorder: OutlineInputBorder( borderRadius: BorderRadius.circular(7.0), borderSide: BorderSide( - color: widget.revealBlank ? Colors.red : Colors.black, + color: widget.revealBlank ? Colors.red : Color.fromARGB(255, 178, 178, 178), width: 1), ), hintText: "Enter one of the options here", @@ -205,7 +199,7 @@ class _QuestionInputOptionsState extends State { color: widget.optionIndex.toString() == widget.questionCubit.state.answerIndex ? Colors.green - : Colors.black, + : Color.fromARGB(255, 108, 108, 108), onPressed: () { if (widget.optionIndex.toString() != widget.questionCubit.state.answerIndex) { diff --git a/lib/features/quiz_upload/presentation/widgets/quiz_input_widgets/question_input_display.dart b/lib/features/quiz_upload/presentation/widgets/quiz_input_widgets/question_input_display.dart index d4f21635..b8124538 100644 --- a/lib/features/quiz_upload/presentation/widgets/quiz_input_widgets/question_input_display.dart +++ b/lib/features/quiz_upload/presentation/widgets/quiz_input_widgets/question_input_display.dart @@ -17,43 +17,42 @@ class QuestionInputDisplay extends StatefulWidget { class _QuestionInputDisplayState extends State { @override Widget build(BuildContext buildContext) { - return Expanded( - child: ListView.builder( + return ListView.builder( + physics: NeverScrollableScrollPhysics(), + padding: EdgeInsets.all(5.0), + shrinkWrap: true, itemCount: buildContext.read().questions.length, - itemBuilder: (listViewBuilderContext, index) { - return Center( - child: Dismissible( - background: Container( - width: 50.0, - height: 50.0, - color: Colors.red, - alignment: Alignment.centerRight, - padding: EdgeInsets.only(right: 20.0), - margin: EdgeInsets.only(bottom: 10.0), - child: Icon(Icons.delete), - ), - direction: DismissDirection.endToStart, - key: ValueKey(index), - child: BlocBuilder( - bloc: buildContext.read().questions[index], - builder: (context, state) { - return QuestionInputCard( - questionCubit: buildContext - .read() - .questions[index]); - }, - ), - onDismissed: (_) { - buildContext.read().add( - QuizInputPageDeleteBtnPressed( - questionCubit: buildContext - .read() - .questions[index])); + itemBuilder: (_, index) { + return Dismissible( + background: Container( + color: Colors.white, + alignment: Alignment.centerRight, + padding: EdgeInsets.only(right: 20.0), + margin: EdgeInsets.only(bottom: 10.0), + child: Icon(Icons.delete), + ), + direction: DismissDirection.endToStart, + key: ValueKey(index), + child: BlocBuilder( + bloc: buildContext.read().questions[index], + builder: (context, state) { + return QuestionInputCard( + questionCubit: buildContext + .read() + .questions[index]); }, ), + onDismissed: (_) { + buildContext.read().add( + QuizInputPageDeleteBtnPressed( + questionCubit: buildContext + .read() + .questions[index], + ), + ); + }, ); - }, - ), - ); + } + ); } } diff --git a/lib/features/quiz_upload/presentation/widgets/quiz_input_widgets/question_input_tab_display.dart b/lib/features/quiz_upload/presentation/widgets/quiz_input_widgets/question_input_tab_display.dart deleted file mode 100644 index 271fc6fd..00000000 --- a/lib/features/quiz_upload/presentation/widgets/quiz_input_widgets/question_input_tab_display.dart +++ /dev/null @@ -1,151 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:flutter_form_builder/flutter_form_builder.dart'; -import 'package:flutter_markdown/flutter_markdown.dart'; -import 'package:flutter_markdown_latex/flutter_markdown_latex.dart'; -import 'package:form_builder_validators/form_builder_validators.dart'; -import 'package:logger/logger.dart'; -import 'package:markdown/markdown.dart' as md; -import 'package:scholars_guide/service_locator/service_locator.dart'; - -class QuestionInputTabDisplay extends StatefulWidget { - const QuestionInputTabDisplay({super.key}); - - @override - State createState() => - _QuestionInputTabDisplayState(); -} - -class _QuestionInputTabDisplayState extends State { - @override - Widget build(BuildContext context) { - final editFormKey = GlobalKey(); - late String markdownData = ''' - This is inline latex: \$f(x) = \\sum_{i=0}^{n} \\frac{a_i}{1+x}\$ - - This is block level latex: - - \$ - c = \\pm\\sqrt{a^2 + b^2} - \$ - - This is inline latex with displayMode: \$\$f(x) = \\sum_{i=0}^{n} \\frac{a_i}{1+x}\$\$ - - To calculate the area of an equilateral triangle using trigonometric functions, one can consider using the length of the side and the height. The relationship between the height and the side length of an equilateral triangle is: - - \\[ \\text{Height} = \\frac{\\sqrt{3}}{2} \\times \\text{Side Length} \\] - - 因此,边长为 9 的正三角形的面积为: - - \\[ \\text{面积} = \\frac{1}{2} \\times \\text{底} \\times \\text{高} = \\frac{1}{2} \\times 9 \\times \\frac{\\sqrt{3}}{2} \\times 9 = \\frac{81\\sqrt{3}}{4} \\] - - 所以正三角形的面积为 \\( \\frac{81\\sqrt{3}}{4} \\)。 - - '''; - - return DefaultTabController( - initialIndex: 0, - length: 2, - child: Scaffold( - appBar: AppBar( - title: const Text('Question Input'), - bottom: TabBar( - onTap: (value) { - services().i('Tab index: $value'); - - if (value == 0) { - setState(() { - editFormKey.currentState?.fields['question'] - ?.didChange(markdownData); - }); - } - }, - tabs: const [ - Tab( - icon: Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Icon(Icons.edit_outlined), - SizedBox(width: 8.0), - Text('Edit'), - ], - ), - ), - Tab( - icon: Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Icon(Icons.preview_outlined), - SizedBox(width: 8.0), - Text('Preview'), - ], - ), - // text: 'Preview', - ), - ], - ), - ), - body: SafeArea( - child: TabBarView( - children: [ - // Edit. - SingleChildScrollView( - child: Padding( - padding: const EdgeInsets.all(32.0), - child: FormBuilder( - key: editFormKey, - child: Column( - children: [ - // Question. - FormBuilderTextField( - name: 'question', - // TODO: [P2]: Make maxLines a user option. - maxLines: null, - minLines: 6, - initialValue: markdownData, - decoration: const InputDecoration( - border: OutlineInputBorder(), - ), - validator: FormBuilderValidators.compose([ - FormBuilderValidators.required(), - FormBuilderValidators.minLength(5), - // TODO [P3]: Needs more user research. Subject to change. - FormBuilderValidators.maxLength(1500), - ]), - onChanged: (String? value) { - setState(() { - markdownData = value ?? ''; - }); - }, - ), - - // Choices - const SizedBox(height: 400.0) - ], - ), - ), - ), - ), - - // Preview. - Padding( - padding: const EdgeInsets.all(24.0), - child: Card( - child: Markdown( - data: markdownData, - builders: { - 'latex': LatexElementBuilder(), - }, - extensionSet: md.ExtensionSet( - [LatexBlockSyntax()], - [LatexInlineSyntax()], - ), - ), - ), - ), - ], - ), - ), - ), - ); - } -} diff --git a/lib/features/quiz_upload/presentation/widgets/quiz_input_widgets/question_preview_card_display.dart b/lib/features/quiz_upload/presentation/widgets/quiz_input_widgets/question_preview_card_display.dart new file mode 100644 index 00000000..4500042b --- /dev/null +++ b/lib/features/quiz_upload/presentation/widgets/quiz_input_widgets/question_preview_card_display.dart @@ -0,0 +1,119 @@ +// ignore_for_file: prefer_const_constructors + +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/widgets.dart'; +import 'package:flutter_markdown/flutter_markdown.dart'; +import 'package:flutter_markdown_latex/flutter_markdown_latex.dart'; +import 'package:scholars_guide/features/quiz_upload/presentation/state_management/quiz_input/quiz_input_cubit.dart'; +import 'package:markdown/markdown.dart' as md; + +class QuestionPreviewCardDisplay extends StatelessWidget { + const QuestionPreviewCardDisplay({super.key, required this.questionCubit}); + + final QuizInputCubit questionCubit; + + @override + Widget build(BuildContext context) { + int correctIndex = int.parse(questionCubit.state.answerIndex); + + return Card( + margin: EdgeInsets.all(10.0), + child: Column( + children: [ + // Question + TextContainer(text: questionCubit.state.question), + + // Choices + OptionContainer(text: "A: ${questionCubit.state.options[0]}", isCorrect: 0 == correctIndex), + OptionContainer(text: "B: ${questionCubit.state.options[1]}", isCorrect: 1 == correctIndex), + OptionContainer(text: "C: ${questionCubit.state.options[2]}", isCorrect: 2 == correctIndex), + OptionContainer(text: "D: ${questionCubit.state.options[3]}", isCorrect: 3 == correctIndex), + + // Solution + TextContainer(text: questionCubit.state.solution), + ], + ), + ); + } +} + +class TextContainer extends StatelessWidget { + const TextContainer({ + super.key, + required this.text, + }); + + final String text; + + @override + Widget build(BuildContext context) { + return Container( + decoration: BoxDecoration( + border: Border.all( + color: Color.fromARGB(255, 178, 178, 178), + ), + borderRadius: BorderRadius.circular(10.0) + ), + constraints: BoxConstraints( + minHeight: MediaQuery.of(context).size.height * 0.1, + ), + margin: EdgeInsets.all(8.0), + child: TextMarkdown(text: text), + ); + } +} + +class OptionContainer extends StatelessWidget { + const OptionContainer({ + super.key, + required this.text, + required this.isCorrect, + }); + + final String text; + final bool isCorrect; + + @override + Widget build(BuildContext context) { + return Container( + decoration: BoxDecoration( + border: Border.all( + color: isCorrect ?Colors.green: Color.fromARGB(255, 178, 178, 178), + ), + borderRadius: BorderRadius.circular(10.0) + ), + margin: EdgeInsets.all(2.0), + constraints: BoxConstraints( + minHeight: MediaQuery.of(context).size.height * 0.02, + ), + width: MediaQuery.of(context).size.width * 0.8, + child: TextMarkdown(text: text), + ); + } +} + +class TextMarkdown extends StatelessWidget { + const TextMarkdown({ + super.key, + required this.text, + }); + + final String text; + + @override + Widget build(BuildContext context) { + return Markdown( + physics: NeverScrollableScrollPhysics(), + shrinkWrap: true, + data: text, + builders: { + 'latex': LatexElementBuilder(), + }, + extensionSet: md.ExtensionSet( + [LatexBlockSyntax()], + [LatexInlineSyntax()], + ), + ); + } +} diff --git a/lib/features/quiz_upload/presentation/widgets/quiz_input_widgets/question_preview_display.dart b/lib/features/quiz_upload/presentation/widgets/quiz_input_widgets/question_preview_display.dart new file mode 100644 index 00000000..157f0355 --- /dev/null +++ b/lib/features/quiz_upload/presentation/widgets/quiz_input_widgets/question_preview_display.dart @@ -0,0 +1,22 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:scholars_guide/features/quiz_upload/presentation/state_management/quiz_input_page/quiz_input_page_bloc.dart'; +import 'package:scholars_guide/features/quiz_upload/presentation/widgets/quiz_input_widgets/question_preview_card_display.dart'; + +class QuestionPreviewDisplay extends StatelessWidget { + const QuestionPreviewDisplay({super.key}); + + @override + Widget build(BuildContext context) { + return Column( + children: List.generate( + context.read().questions.length, + (index) { + return QuestionPreviewCardDisplay( + questionCubit: context.read().questions[index], + ); + }, + ), + ); + } +} \ No newline at end of file diff --git a/lib/router/app_router.dart b/lib/router/app_router.dart index 10896d20..9005467f 100644 --- a/lib/router/app_router.dart +++ b/lib/router/app_router.dart @@ -5,7 +5,6 @@ import 'package:scholars_guide/features/quiz_mode/presentation/pages/quiz_page.d import 'package:scholars_guide/features/quiz_mode/presentation/pages/ready_quiz_page.dart'; import 'package:scholars_guide/features/quiz_upload/presentation/pages/upload_questions_page.dart'; import 'package:scholars_guide/features/quiz_upload/presentation/pages/upload_success_page.dart'; -import 'package:scholars_guide/features/quiz_upload/presentation/widgets/quiz_input_widgets/question_input_tab_display.dart'; import '../core/features/auth/presentation/screens/email_verification_screen.dart'; import '../core/features/auth/presentation/screens/forgot_password_screen.dart'; @@ -153,8 +152,7 @@ class AppRouter { path: '/quiz-upload', pageBuilder: (_, __) { return const NoTransitionPage( - // child: UploadQuestionPage(), - child: QuestionInputTabDisplay() + child: UploadQuestionPage(), ); }, routes: [ diff --git a/pubspec.lock b/pubspec.lock index 812d5f12..20ab4648 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -499,14 +499,6 @@ packages: url: "https://pub.dev" source: hosted version: "2.3.10" - flutter_portal: - dependency: "direct main" - description: - name: flutter_portal - sha256: "4601b3dc24f385b3761721bd852a3f6c09cddd4e943dd184ed58ee1f43006257" - url: "https://pub.dev" - source: hosted - version: "1.1.4" flutter_quill: dependency: "direct main" description: diff --git a/pubspec.yaml b/pubspec.yaml index 6ecd1342..77272c05 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -98,10 +98,6 @@ dependencies: # An animated circular countdown using Circular Countdown Timer. # Used for quiz mode's countdown timer. circular_countdown_timer: ^0.2.3 - - # This library is an enhancement and replacement to Flutter's - # built-in Overlay/OverlayEntry. Intuitive and declarative. - flutter_portal: ^1.1.4 # This package helps in creation of forms in Flutter by removing the boilerplate # code, reusing validation, react to changes, and collect final user input. From 25200e69844d60f19bcc22a6d29d562b7bcd2882 Mon Sep 17 00:00:00 2001 From: Rhon Date: Mon, 11 Mar 2024 14:42:38 +0800 Subject: [PATCH 3/6] Fixed quiz upload input bug, few changes in its UI, and changed cubit deets --- .../pages/solutions_quiz_page.dart | 96 ++++++ .../quiz_upload_repository_impl.dart | 14 +- .../quiz_input/quiz_input_cubit.dart | 35 +-- .../quiz_input/quiz_input_state.dart | 46 +-- .../quiz_input_page/quiz_input_page_bloc.dart | 37 ++- .../change_subject_display.dart | 5 +- .../confirm_submit_quiz_input_dialogue.dart | 10 +- .../question_input_card_display.dart | 281 +++++++++--------- .../question_input_display.dart | 51 ++-- .../question_preview_card_display.dart | 41 +-- lib/router/app_router.dart | 12 +- 11 files changed, 339 insertions(+), 289 deletions(-) create mode 100644 lib/features/quiz_mode/presentation/pages/solutions_quiz_page.dart diff --git a/lib/features/quiz_mode/presentation/pages/solutions_quiz_page.dart b/lib/features/quiz_mode/presentation/pages/solutions_quiz_page.dart new file mode 100644 index 00000000..3422e4b4 --- /dev/null +++ b/lib/features/quiz_mode/presentation/pages/solutions_quiz_page.dart @@ -0,0 +1,96 @@ +import 'package:flutter/material.dart'; + +class SolutionsQuizPage extends StatefulWidget { + const SolutionsQuizPage({super.key}); + + @override + State createState() => _SolutionsQuizPageState(); +} + +class _SolutionsQuizPageState extends State { + @override + Widget build(BuildContext context) { + return Container(); + +// StreamBuilder( +// stream: _dbService +// .collection('users') +// .doc(_authService.currentUser?.uid) +// .snapshots(), +// builder: (BuildContext context, +// AsyncSnapshot snapshot) { +// if (snapshot.hasError) { +// return const Text('Something went wrong'); +// } + +// if (snapshot.connectionState == ConnectionState.waiting) { +// // Show a loading spinner. +// return const CircularProgressIndicator(); +// } + +// // Convert timestamp to DateTime. +// var createdAt = +// (snapshot.data?.get('createdAt') as Timestamp).toDate(); +// var updatedAt = snapshot.data?.get('updatedAt').toDate(); + +// // createdAt = createdAt is Timestamp +// // ? createdAt.toDate() +// // : DateTime.now(); +// // updatedAt = updatedAt is Timestamp +// // ? updatedAt.toDate() +// // : DateTime.now(); + +// String username = +// snapshot.data?.get('username') ?? 'Loading...'; + +// return Column( +// crossAxisAlignment: CrossAxisAlignment.center, +// children: [ +// CircleAvatar( +// backgroundColor: Colors.blue, +// radius: 50, +// child: Text( +// username[0].toUpperCase(), +// style: TextStyle( +// fontSize: 40, +// color: Colors.white, +// ), +// ), +// ), +// SizedBox(height: 16), +// Text( +// username, +// style: TextStyle( +// fontSize: 24, +// fontWeight: FontWeight.bold, +// ), +// ), +// SizedBox(height: 4), +// Text( +// _authService.currentUser?.email ?? 'Loading...', +// style: TextStyle( +// fontSize: 18, +// ), +// ), +// SizedBox(height: 32), +// ProfileDetailRow( +// title: 'UID:', +// detail: +// '***${_authService.currentUser?.uid.substring(12, _authService.currentUser?.uid.length)}' ?? +// 'Loading...', +// ), +// SizedBox(height: 10), +// ProfileDetailRow( +// title: 'Joined in:', +// detail: 'January 1, 2022', +// ), +// SizedBox(height: 10), +// ProfileDetailRow( +// title: 'Updated in:', +// detail: 'February 20, 2024', +// ), +// ], +// ); +// }), + } +} diff --git a/lib/features/quiz_upload/data/repositories/quiz_upload_repository_impl.dart b/lib/features/quiz_upload/data/repositories/quiz_upload_repository_impl.dart index f578b149..0341db6b 100644 --- a/lib/features/quiz_upload/data/repositories/quiz_upload_repository_impl.dart +++ b/lib/features/quiz_upload/data/repositories/quiz_upload_repository_impl.dart @@ -24,10 +24,10 @@ class QuizUploadRepositoryImpl implements QuizUploadRepositoryContract { .whenComplete(() { for (QuizInputCubit questionCubit in questionsToUpload) { Question question = Question( - question: questionCubit.state.question, - solution: questionCubit.state.solution, - options: questionCubit.state.options, - correctIndex: int.parse(questionCubit.state.answerIndex), + question: questionCubit.question, + solution: questionCubit.solution, + options: questionCubit.options, + correctIndex: int.parse(questionCubit.answerIndex), subject: subjToUpload); FirebaseFirestore.instance @@ -35,9 +35,9 @@ class QuizUploadRepositoryImpl implements QuizUploadRepositoryContract { .add(question.toMap()) .then((value) => print('Question added to the database')) .catchError((error) { - print('Failed to add question: $error'); - uploadSucess = false; - }); + print('Failed to add question: $error'); + uploadSucess = false; + }); } }); diff --git a/lib/features/quiz_upload/presentation/state_management/quiz_input/quiz_input_cubit.dart b/lib/features/quiz_upload/presentation/state_management/quiz_input/quiz_input_cubit.dart index 4272ae21..6bc37851 100644 --- a/lib/features/quiz_upload/presentation/state_management/quiz_input/quiz_input_cubit.dart +++ b/lib/features/quiz_upload/presentation/state_management/quiz_input/quiz_input_cubit.dart @@ -1,34 +1,21 @@ - import 'package:bloc/bloc.dart'; import 'package:equatable/equatable.dart'; part 'quiz_input_state.dart'; class QuizInputCubit extends Cubit { - QuizInputCubit() - : super(const QuizInputState( - question: '', - solution: '', - options: ['', '', '', ''], - answerIndex: '0', - questionNonEmpty: false, - solutionNonEmpty: false, - optionsNonEmpty: [false, false, false, false], - )); - - void questionChanged({required String question}) { - emit(state.copyWith(question: question)); - } + QuizInputCubit() : super(const QuizInputState()); - void solutionChanged({required String solution}) { - emit(state.copyWith(solution: solution)); + void refresh() { + emit(QuizInputRefresh()); + emit(QuizInputState()); } - void optionChanged({required List options, required List optionsNonEmpty}) { - emit(state.copyWith(options: options, optionsNonEmpty: optionsNonEmpty)); - } - - void answerChanged({required String answerIndex}) { - emit(state.copyWith(answerIndex: answerIndex)); - } + String question = ''; + String solution = ''; + List options = ['', '', '', '']; + String answerIndex = '0'; + bool questionNonEmpty = false; + bool solutionNonEmpty = false; + List optionsNonEmpty = [false, false, false, false]; } diff --git a/lib/features/quiz_upload/presentation/state_management/quiz_input/quiz_input_state.dart b/lib/features/quiz_upload/presentation/state_management/quiz_input/quiz_input_state.dart index 2aa67b7a..0b9d99d5 100644 --- a/lib/features/quiz_upload/presentation/state_management/quiz_input/quiz_input_state.dart +++ b/lib/features/quiz_upload/presentation/state_management/quiz_input/quiz_input_state.dart @@ -1,47 +1,9 @@ part of 'quiz_input_cubit.dart'; class QuizInputState extends Equatable { - const QuizInputState({ - required this.question, - required this.solution, - required this.options, - required this.answerIndex, - required this.questionNonEmpty, - required this.solutionNonEmpty, - required this.optionsNonEmpty, - }); - - final String question; - final String solution; - final List options; - final String answerIndex; - - // Empty checker - final bool questionNonEmpty; - final bool solutionNonEmpty; - final List optionsNonEmpty; - + const QuizInputState(); @override - List get props => [question, solution, options, answerIndex, questionNonEmpty, solutionNonEmpty, optionsNonEmpty]; - - QuizInputState copyWith({ - String? question, - String? solution, - List? options, - String? answerIndex, - bool? questionNonEmpty, - bool? solutionNonEmpty, - List? optionsNonEmpty, - }) { - return QuizInputState( - question: question ?? this.question, - solution: solution ?? this.solution, - options: options ?? this.options, - answerIndex: answerIndex ?? this.answerIndex, - questionNonEmpty: question?.isNotEmpty ?? this.questionNonEmpty, - solutionNonEmpty: solution?.isNotEmpty ?? this.solutionNonEmpty, - optionsNonEmpty: - options?.map((e) => e.isNotEmpty).toList() ?? this.optionsNonEmpty, - ); - } + List get props => []; } + +class QuizInputRefresh extends QuizInputState {} diff --git a/lib/features/quiz_upload/presentation/state_management/quiz_input_page/quiz_input_page_bloc.dart b/lib/features/quiz_upload/presentation/state_management/quiz_input_page/quiz_input_page_bloc.dart index dec49c76..61ffb7dc 100644 --- a/lib/features/quiz_upload/presentation/state_management/quiz_input_page/quiz_input_page_bloc.dart +++ b/lib/features/quiz_upload/presentation/state_management/quiz_input_page/quiz_input_page_bloc.dart @@ -20,8 +20,6 @@ class QuizInputPageBloc extends Bloc { // * For submitting the quiz upload on((event, emit) { - _printQuestions(); - if (_questionsNotEmpty() && questions.isNotEmpty) { revealBlanks = false; } else { @@ -29,17 +27,16 @@ class QuizInputPageBloc extends Bloc { emit(QuizInputPageRefresh()); emit(QuizInputPageQuestionsAdd()); } + _printQuestions(); }); // * For resetting the quiz upload after submitting on((event, emit) { - questions = [QuizInputCubit()]; subject = SUBJ.MATH; revealBlanks = false; emit(QuizInputPageRefresh()); emit(QuizInputPageQuestionsAdd()); - }); // * For cancelling the quiz upload @@ -73,8 +70,8 @@ class QuizInputPageBloc extends Bloc { bool _questionsNotEmpty() { for (var question in questions) { - if (!question.state.questionNonEmpty) return false; - for (var option in question.state.optionsNonEmpty) { + if (!question.questionNonEmpty) return false; + for (var option in question.optionsNonEmpty) { if (!option) return false; } } @@ -86,13 +83,27 @@ class QuizInputPageBloc extends Bloc { for (var question in questions) { print( " ====================================== Printing question ======================================"); - print("Question: ${question.state.question}"); - print("Solution: ${question.state.solution}"); - print("Options: ${question.state.options}"); - print("Answer: ${question.state.answerIndex}"); - print("Question non empty: ${question.state.questionNonEmpty}"); - print("Solution non empty: ${question.state.solutionNonEmpty}"); - print("Options non empty: ${question.state.optionsNonEmpty}"); + print("Question: ${question.question}"); + print("Solution: ${question.solution}"); + print("Options: ${question.options}"); + print("Answer: ${question.answerIndex}"); + print("Question non empty: ${question.questionNonEmpty}"); + print("Solution non empty: ${question.solutionNonEmpty}"); + print("Options non empty: ${question.optionsNonEmpty}"); } } + // void _printQuestions() { + // print("REVEAL THE BLANKS ? $revealBlanks"); + // for (var question in questions) { + // print( + // " ====================================== Printing question ======================================"); + // print("Question: ${question.state.question}"); + // print("Solution: ${question.state.solution}"); + // print("Options: ${question.state.options}"); + // print("Answer: ${question.state.answerIndex}"); + // print("Question non empty: ${question.state.questionNonEmpty}"); + // print("Solution non empty: ${question.state.solutionNonEmpty}"); + // print("Options non empty: ${question.state.optionsNonEmpty}"); + // } + // } } diff --git a/lib/features/quiz_upload/presentation/widgets/quiz_input_page_widgets/change_subject_display.dart b/lib/features/quiz_upload/presentation/widgets/quiz_input_page_widgets/change_subject_display.dart index 8ab6a357..06754e56 100644 --- a/lib/features/quiz_upload/presentation/widgets/quiz_input_page_widgets/change_subject_display.dart +++ b/lib/features/quiz_upload/presentation/widgets/quiz_input_page_widgets/change_subject_display.dart @@ -32,10 +32,11 @@ class _ChangeSubjectDisplayState extends State { icon: Icon(Icons.arrow_drop_down), iconSize: 24, elevation: 16, - style: TextStyle(color: Colors.deepPurple), // ! Panget UI + style: TextStyle( + color: Color.fromARGB(255, 63, 86, 169)), // ! Panget UI underline: Container( height: 2, - color: Colors.deepPurpleAccent, + color: Color.fromARGB(255, 63, 86, 169), ), value: Question.SUBJ2string( context.read().subject), diff --git a/lib/features/quiz_upload/presentation/widgets/quiz_input_page_widgets/confirm_submit_quiz_input_dialogue.dart b/lib/features/quiz_upload/presentation/widgets/quiz_input_page_widgets/confirm_submit_quiz_input_dialogue.dart index 88b7abb9..d79eba72 100644 --- a/lib/features/quiz_upload/presentation/widgets/quiz_input_page_widgets/confirm_submit_quiz_input_dialogue.dart +++ b/lib/features/quiz_upload/presentation/widgets/quiz_input_page_widgets/confirm_submit_quiz_input_dialogue.dart @@ -6,7 +6,8 @@ import 'package:scholars_guide/features/quiz_upload/presentation/state_managemen import 'package:scholars_guide/features/quiz_upload/presentation/state_management/quiz_input_page/quiz_input_page_bloc.dart'; class ConfirmSubmitQuizInputDialogue extends StatelessWidget { - const ConfirmSubmitQuizInputDialogue({super.key, required this.quizInputPageBloc}); + const ConfirmSubmitQuizInputDialogue( + {super.key, required this.quizInputPageBloc}); final QuizInputPageBloc quizInputPageBloc; @@ -20,17 +21,16 @@ class ConfirmSubmitQuizInputDialogue extends StatelessWidget { child: const Text('Submit Questions'), onPressed: () { Navigator.of(context).pop(); - quizInputPageBloc.add(QuizInputPageSubmitBtnPressed()); - if (quizInputPageBloc.isSubmittable()){ - GoRouter.of(context).go('/quiz-upload/finished-quiz-upload', extra: { + if (quizInputPageBloc.isSubmittable()) { + GoRouter.of(context) + .go('/quiz-upload/finished-quiz-upload', extra: { 'questionsToUpload': quizInputPageBloc.questions, 'subjToUpload': quizInputPageBloc.subject, }); quizInputPageBloc.add(QuizInputPageReset()); } - }, ), ElevatedButton( diff --git a/lib/features/quiz_upload/presentation/widgets/quiz_input_widgets/question_input_card_display.dart b/lib/features/quiz_upload/presentation/widgets/quiz_input_widgets/question_input_card_display.dart index 451fe964..79d82170 100644 --- a/lib/features/quiz_upload/presentation/widgets/quiz_input_widgets/question_input_card_display.dart +++ b/lib/features/quiz_upload/presentation/widgets/quiz_input_widgets/question_input_card_display.dart @@ -21,108 +21,98 @@ class _QuestionInputCardState extends State { Widget build(BuildContext context) { QuizInputPageBloc quizInputPageBloc = context.read(); + QuizInputCubit questionCubit = widget.questionCubit; + TextEditingController questionController = - TextEditingController(text: widget.questionCubit.state.question); + TextEditingController(text: questionCubit.question); TextEditingController solutionController = - TextEditingController(text: widget.questionCubit.state.solution); - - questionController.selection = TextSelection.fromPosition( - TextPosition( - offset: questionController.text.length, - ), - ); - - solutionController.selection = TextSelection.fromPosition( - TextPosition( - offset: solutionController.text.length, - ), - ); + TextEditingController(text: questionCubit.solution); return Card( - child: FormBuilder( - child: Column( - children: [ - Container( - padding: EdgeInsets.all(8.0), - height: 200, - child: TextField( - decoration: InputDecoration( - enabledBorder: OutlineInputBorder( - borderRadius: BorderRadius.circular(7.0), - borderSide: BorderSide( - color: quizInputPageBloc.revealBlanks - ? widget.questionCubit.state.questionNonEmpty - ? Color.fromARGB(255, 178, 178, 178) - : Colors.red - : Color.fromARGB(255, 178, 178, 178), - width: 1), - ), - hintText: "Enter the question here", - filled: true, - fillColor: Colors.white, + child: Column( + children: [ + Container( + padding: EdgeInsets.all(8.0), + height: 200, + child: TextField( + decoration: InputDecoration( + enabledBorder: OutlineInputBorder( + borderRadius: BorderRadius.circular(7.0), + borderSide: BorderSide( + color: quizInputPageBloc.revealBlanks + ? questionCubit.questionNonEmpty + ? Color.fromARGB(255, 178, 178, 178) + : Colors.red + : Color.fromARGB(255, 178, 178, 178), + width: 1), ), - maxLines: null, - expands: true, - controller: questionController, - onChanged: (questionInput) { - widget.questionCubit - .questionChanged(question: questionInput); - }, + hintText: "Enter the question here", + filled: true, + fillColor: Colors.white, ), + maxLines: null, + expands: true, + controller: questionController, + onChanged: (questionInput) { + questionCubit.question = questionInput; + questionCubit.questionNonEmpty = questionInput.isNotEmpty; + }, ), - QuestionInputOptions( - questionCubit: widget.questionCubit, - optionIndex: 0, + ), + QuestionInputOptions( + questionCubit: questionCubit, + optionIndex: 0, + revealBlank: quizInputPageBloc.revealBlanks && + !questionCubit.optionsNonEmpty[0], + ), + QuestionInputOptions( + questionCubit: questionCubit, + optionIndex: 1, revealBlank: quizInputPageBloc.revealBlanks && - !widget.questionCubit.state.optionsNonEmpty[0], - ), - QuestionInputOptions( - questionCubit: widget.questionCubit, - optionIndex: 1, - revealBlank: quizInputPageBloc.revealBlanks && - !widget.questionCubit.state.optionsNonEmpty[1]), - QuestionInputOptions( - questionCubit: widget.questionCubit, - optionIndex: 2, - revealBlank: quizInputPageBloc.revealBlanks && - !widget.questionCubit.state.optionsNonEmpty[2]), - QuestionInputOptions( - questionCubit: widget.questionCubit, - optionIndex: 3, - revealBlank: quizInputPageBloc.revealBlanks && - !widget.questionCubit.state.optionsNonEmpty[3]), - Container( - padding: EdgeInsets.all(8.0), - height: 200, - child: TextField( - decoration: InputDecoration( - enabledBorder: OutlineInputBorder( - borderRadius: BorderRadius.circular(7.0), - borderSide: BorderSide( - color: quizInputPageBloc.revealBlanks - ? widget.questionCubit.state.solutionNonEmpty - ? Color.fromARGB(255, 178, 178, 178) - : Colors.red - : Color.fromARGB(255, 178, 178, 178), - width: 1), - ), - hintText: "Enter the solution here", - filled: true, - fillColor: Colors.white, + !questionCubit.optionsNonEmpty[1]), + QuestionInputOptions( + questionCubit: questionCubit, + optionIndex: 2, + revealBlank: quizInputPageBloc.revealBlanks && + !questionCubit.optionsNonEmpty[2]), + QuestionInputOptions( + questionCubit: questionCubit, + optionIndex: 3, + revealBlank: quizInputPageBloc.revealBlanks && + !questionCubit.optionsNonEmpty[3]), + Container( + padding: EdgeInsets.all(8.0), + height: 200, + child: TextField( + decoration: InputDecoration( + enabledBorder: OutlineInputBorder( + borderRadius: BorderRadius.circular(7.0), + borderSide: BorderSide( + color: quizInputPageBloc.revealBlanks + ? questionCubit.solutionNonEmpty + ? Color.fromARGB(255, 178, 178, 178) + : Colors.red + : Color.fromARGB(255, 178, 178, 178), + width: 1), ), - maxLines: null, - expands: true, - controller: solutionController, - onChanged: (solutionInput) { - widget.questionCubit - .solutionChanged(solution: solutionInput); - }, + hintText: "Enter the solution here", + filled: true, + fillColor: Colors.white, ), + maxLines: null, + expands: true, + controller: solutionController, + onChanged: (solutionInput) { + questionCubit.solution = solutionInput; + questionCubit.solutionNonEmpty = solutionInput.isNotEmpty; + }, ), - ], - )), + ), + ], + ), + ), ); } } @@ -143,72 +133,69 @@ class QuestionInputOptions extends StatefulWidget { } class _QuestionInputOptionsState extends State { - bool correctAnswer = true; @override Widget build(BuildContext context) { - TextEditingController optionController = TextEditingController( - text: widget.questionCubit.state.options[widget.optionIndex]); + QuizInputCubit questionCubit = widget.questionCubit; + int optionIndex = widget.optionIndex; + bool revealBlank = widget.revealBlank; - optionController.selection = TextSelection.fromPosition( - TextPosition( - offset: optionController.text.length, - ), - ); + TextEditingController optionController = + TextEditingController(text: questionCubit.options[optionIndex]); return Container( - padding: EdgeInsets.all(2.0), - margin: EdgeInsets.only(left: 30.0), - width: MediaQuery.of(context).size.width * 0.90, - height: 50, - child: Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Flexible( - child: TextField( - decoration: InputDecoration( - enabledBorder: OutlineInputBorder( - borderRadius: BorderRadius.circular(7.0), - borderSide: BorderSide( - color: widget.revealBlank ? Colors.red : Color.fromARGB(255, 178, 178, 178), - width: 1), - ), - hintText: "Enter one of the options here", - filled: true, - fillColor: Colors.white, + padding: EdgeInsets.all(2.0), + margin: EdgeInsets.only(left: 30.0), + width: MediaQuery.of(context).size.width * 0.90, + height: 50, + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Flexible( + child: TextField( + decoration: InputDecoration( + enabledBorder: OutlineInputBorder( + borderRadius: BorderRadius.circular(7.0), + borderSide: BorderSide( + color: revealBlank + ? Colors.red + : Color.fromARGB(255, 178, 178, 178), + width: 1), ), - controller: optionController, - onChanged: (optionInput) { - List options = - List.from(widget.questionCubit.state.options); - options[widget.optionIndex] = optionInput; - - List optionsNonEmpty = - List.from(widget.questionCubit.state.optionsNonEmpty); - optionsNonEmpty[widget.optionIndex] = - options[widget.optionIndex].isNotEmpty; - - widget.questionCubit.optionChanged( - options: options, optionsNonEmpty: optionsNonEmpty); - }, + hintText: "Enter one of the options here", + filled: true, + fillColor: Colors.white, ), - ), - IconButton( - icon: Icon(correctAnswer - ? Icons.check_circle - : Icons.check_circle_outline_rounded), - color: widget.optionIndex.toString() == - widget.questionCubit.state.answerIndex - ? Colors.green - : Color.fromARGB(255, 108, 108, 108), - onPressed: () { - if (widget.optionIndex.toString() != - widget.questionCubit.state.answerIndex) { - widget.questionCubit.answerChanged( - answerIndex: widget.optionIndex.toString()); - } + controller: optionController, + onChanged: (optionInput) { + List options = List.from(questionCubit.options); + options[optionIndex] = optionInput; + + List optionsNonEmpty = + List.from(questionCubit.optionsNonEmpty); + optionsNonEmpty[optionIndex] = optionInput.isNotEmpty; + + questionCubit.options = options; + questionCubit.optionsNonEmpty = optionsNonEmpty; }, ), - ], - )); + ), + BlocBuilder( + bloc: questionCubit, + builder: (context, state) { + return IconButton( + icon: Icon(Icons.check_circle_outline_rounded), + color: optionIndex.toString() == questionCubit.answerIndex + ? Colors.green + : Color.fromARGB(255, 108, 108, 108), + onPressed: () { + questionCubit.refresh(); + questionCubit.answerIndex = optionIndex.toString(); + }, + ); + }, + ), + ], + ), + ); } } diff --git a/lib/features/quiz_upload/presentation/widgets/quiz_input_widgets/question_input_display.dart b/lib/features/quiz_upload/presentation/widgets/quiz_input_widgets/question_input_display.dart index b8124538..7cec9c86 100644 --- a/lib/features/quiz_upload/presentation/widgets/quiz_input_widgets/question_input_display.dart +++ b/lib/features/quiz_upload/presentation/widgets/quiz_input_widgets/question_input_display.dart @@ -21,38 +21,31 @@ class _QuestionInputDisplayState extends State { physics: NeverScrollableScrollPhysics(), padding: EdgeInsets.all(5.0), shrinkWrap: true, - itemCount: buildContext.read().questions.length, - itemBuilder: (_, index) { - return Dismissible( - background: Container( - color: Colors.white, - alignment: Alignment.centerRight, - padding: EdgeInsets.only(right: 20.0), - margin: EdgeInsets.only(bottom: 10.0), - child: Icon(Icons.delete), - ), - direction: DismissDirection.endToStart, - key: ValueKey(index), - child: BlocBuilder( - bloc: buildContext.read().questions[index], - builder: (context, state) { - return QuestionInputCard( - questionCubit: buildContext - .read() - .questions[index]); - }, - ), - onDismissed: (_) { - buildContext.read().add( + itemCount: buildContext.read().questions.length, + itemBuilder: (_, index) { + return Dismissible( + background: Container( + color: Colors.white, + alignment: Alignment.centerRight, + padding: EdgeInsets.only(right: 20.0), + margin: EdgeInsets.only(bottom: 10.0), + child: Icon(Icons.delete), + ), + direction: DismissDirection.endToStart, + key: ValueKey(index), + child: QuestionInputCard( + questionCubit: + buildContext.read().questions[index]), + onDismissed: (_) { + buildContext.read().add( QuizInputPageDeleteBtnPressed( - questionCubit: buildContext - .read() - .questions[index], + questionCubit: + buildContext.read().questions[index], ), ); - }, - ); - } + }, ); + }, + ); } } diff --git a/lib/features/quiz_upload/presentation/widgets/quiz_input_widgets/question_preview_card_display.dart b/lib/features/quiz_upload/presentation/widgets/quiz_input_widgets/question_preview_card_display.dart index 4500042b..2a2555d2 100644 --- a/lib/features/quiz_upload/presentation/widgets/quiz_input_widgets/question_preview_card_display.dart +++ b/lib/features/quiz_upload/presentation/widgets/quiz_input_widgets/question_preview_card_display.dart @@ -15,23 +15,31 @@ class QuestionPreviewCardDisplay extends StatelessWidget { @override Widget build(BuildContext context) { - int correctIndex = int.parse(questionCubit.state.answerIndex); + int correctIndex = int.parse(questionCubit.answerIndex); return Card( margin: EdgeInsets.all(10.0), child: Column( children: [ // Question - TextContainer(text: questionCubit.state.question), + TextContainer(text: questionCubit.question), // Choices - OptionContainer(text: "A: ${questionCubit.state.options[0]}", isCorrect: 0 == correctIndex), - OptionContainer(text: "B: ${questionCubit.state.options[1]}", isCorrect: 1 == correctIndex), - OptionContainer(text: "C: ${questionCubit.state.options[2]}", isCorrect: 2 == correctIndex), - OptionContainer(text: "D: ${questionCubit.state.options[3]}", isCorrect: 3 == correctIndex), + OptionContainer( + text: "A: ${questionCubit.options[0]}", + isCorrect: 0 == correctIndex), + OptionContainer( + text: "B: ${questionCubit.options[1]}", + isCorrect: 1 == correctIndex), + OptionContainer( + text: "C: ${questionCubit.options[2]}", + isCorrect: 2 == correctIndex), + OptionContainer( + text: "D: ${questionCubit.options[3]}", + isCorrect: 3 == correctIndex), // Solution - TextContainer(text: questionCubit.state.solution), + TextContainer(text: questionCubit.solution), ], ), ); @@ -50,11 +58,10 @@ class TextContainer extends StatelessWidget { Widget build(BuildContext context) { return Container( decoration: BoxDecoration( - border: Border.all( - color: Color.fromARGB(255, 178, 178, 178), - ), - borderRadius: BorderRadius.circular(10.0) - ), + border: Border.all( + color: Color.fromARGB(255, 178, 178, 178), + ), + borderRadius: BorderRadius.circular(10.0)), constraints: BoxConstraints( minHeight: MediaQuery.of(context).size.height * 0.1, ), @@ -78,11 +85,11 @@ class OptionContainer extends StatelessWidget { Widget build(BuildContext context) { return Container( decoration: BoxDecoration( - border: Border.all( - color: isCorrect ?Colors.green: Color.fromARGB(255, 178, 178, 178), - ), - borderRadius: BorderRadius.circular(10.0) - ), + border: Border.all( + color: + isCorrect ? Colors.green : Color.fromARGB(255, 178, 178, 178), + ), + borderRadius: BorderRadius.circular(10.0)), margin: EdgeInsets.all(2.0), constraints: BoxConstraints( minHeight: MediaQuery.of(context).size.height * 0.02, diff --git a/lib/router/app_router.dart b/lib/router/app_router.dart index 9005467f..6038cc46 100644 --- a/lib/router/app_router.dart +++ b/lib/router/app_router.dart @@ -137,8 +137,14 @@ class AppRouter { pageBuilder: (context, state) => const NoTransitionPage( child: FinishedQuizPage(), ), - ) - ] + ), + GoRoute( + path: 'solutions-quiz', + pageBuilder: (context, state) => const NoTransitionPage( + child: FinishedQuizPage(), // ! Change + ), + ), + ], ), ], ), @@ -162,7 +168,7 @@ class AppRouter { child: UploadSuccessPage(), ), ), - ] + ], ), ], ), From a8644fecd5e76e7ada7d5d4b9b97edf4df331f3f Mon Sep 17 00:00:00 2001 From: Rhon Date: Mon, 11 Mar 2024 22:43:32 +0800 Subject: [PATCH 4/6] Better upload details. Quizzes now have UserRef and SolutionsRef --- lib/core/models/firestore_model.dart | 3 +- lib/core/models/question_model.dart | 28 ++++--- .../remote_data_source/fetch_questions.dart | 22 ------ .../quiz_mode_repository_impl.dart | 32 ++++---- .../pages/finished_quiz_page.dart | 75 ++++++++++--------- .../pages/solutions_quiz_page.dart | 18 ++++- .../quiz_upload_repository_impl.dart | 62 +++++++++------ .../confirm_submit_quiz_input_dialogue.dart | 1 - .../question_input_display.dart | 1 - 9 files changed, 131 insertions(+), 111 deletions(-) delete mode 100644 lib/features/quiz_mode/data/data_sources/remote_data_source/fetch_questions.dart diff --git a/lib/core/models/firestore_model.dart b/lib/core/models/firestore_model.dart index eb5fab9c..83f1d17a 100644 --- a/lib/core/models/firestore_model.dart +++ b/lib/core/models/firestore_model.dart @@ -15,7 +15,8 @@ class FireStore { //* Field names of each document in the Firestore // Question related fields static const String question = 'question'; - static const String solution = 'solutionData'; + static const String solutionRef = 'solutionsRef'; + static const String solutionData = 'solutionData'; static const String options = 'choices'; static const String correctIndex = 'correctChoiceKey'; // static const String questionReference = 'additionalQuestionDataRef'; diff --git a/lib/core/models/question_model.dart b/lib/core/models/question_model.dart index 8d806864..d221c33c 100644 --- a/lib/core/models/question_model.dart +++ b/lib/core/models/question_model.dart @@ -8,24 +8,26 @@ import 'package:scholars_guide/core/models/firestore_model.dart'; enum SUBJ { MATH, SCIENCE, READING, LANGUAGE, ALL } class Question { - const Question({ + Question({ + this.id = '', required this.question, - required this.solution, + required this.solutionRef, required this.options, required this.correctIndex, required this.subject, + required this.createdBy, }); - + String id; final String question; - final String solution; final List options; final int correctIndex; final SUBJ subject; + final DocumentReference solutionRef; - static var createdAt = FieldValue.serverTimestamp(); - static const String createdBy = ''; // ! This is a placeholder + final DocumentReference createdBy; + final FieldValue createdAt = FieldValue.serverTimestamp(); - static var updatedAt = FieldValue.serverTimestamp(); + static var updatedAt = FieldValue.serverTimestamp(); static const String updatedBy = ''; // ! This is a placeholder static bool isVerified = false; @@ -34,13 +36,12 @@ class Question { void printQuestion() { print('Question: $question'); - print('Solution: $solution'); print('Options: $options'); print('Correct: $correctIndex'); } // Returns a Question Class using data fetched from the Firestore - factory Question.fromMap(Map data, SUBJ subject) { + factory Question.fromMap(String id, Map data, SUBJ subject) { List temp = []; for (var val in data[FireStore.options].values) { temp.add(val); @@ -50,11 +51,13 @@ class Question { temp.shuffle(Random()); return Question( + id: id, subject: subject, question: data[FireStore.question], - solution: '', // ! This is a placeholder + solutionRef: data[FireStore.solutionRef], options: temp, - correctIndex: temp.indexWhere((element) => element == temp2)); + correctIndex: temp.indexWhere((element) => element == temp2), + createdBy: data[FireStore.createdBy]); } // Converts the Question Class to a json like file to be stored in the Firestore @@ -68,6 +71,9 @@ class Question { '3': options[3], }, FireStore.correctIndex: correctIndex.toString(), + FireStore.solutionRef: solutionRef, + FireStore.createdBy: createdBy, + FireStore.createdAt: createdAt, }; } diff --git a/lib/features/quiz_mode/data/data_sources/remote_data_source/fetch_questions.dart b/lib/features/quiz_mode/data/data_sources/remote_data_source/fetch_questions.dart deleted file mode 100644 index 220aad2f..00000000 --- a/lib/features/quiz_mode/data/data_sources/remote_data_source/fetch_questions.dart +++ /dev/null @@ -1,22 +0,0 @@ - -// import 'package:scholars_guide/service_locator/service_locator.dart'; -import 'package:cloud_firestore/cloud_firestore.dart'; - -Future fetchQuestions(String subject) { - return FirebaseFirestore.instance.collection(subject).get(); -} - - - -// FirebaseFirestore.instance.collection('math-questions') -// .doc(blank means random generate) -// .get() -// .set( some json data) -// .snapshots() returns - - -// json example = { -// "question": "What is 2 + 2?", -// "options": ["1", "2", "3", "4"], -// "answer": "4" -// } \ No newline at end of file diff --git a/lib/features/quiz_mode/data/repositories/quiz_mode_repository_impl.dart b/lib/features/quiz_mode/data/repositories/quiz_mode_repository_impl.dart index db778355..4e69cbf6 100644 --- a/lib/features/quiz_mode/data/repositories/quiz_mode_repository_impl.dart +++ b/lib/features/quiz_mode/data/repositories/quiz_mode_repository_impl.dart @@ -3,29 +3,33 @@ // import 'package:scholars_guide/service_locator/service_locator.dart'; import 'package:cloud_firestore/cloud_firestore.dart'; -import 'package:firebase_core/firebase_core.dart'; import 'package:scholars_guide/core/models/firestore_model.dart'; import 'package:scholars_guide/core/models/question_model.dart'; import 'package:scholars_guide/features/quiz_mode/domain/repositories_contract/quiz_mode_repository_contract.dart'; -import 'package:scholars_guide/firebase_options.dart'; -class QuizModeRepositoryImpl implements QuizModeRepositoryContract{ +import '../../../../service_locator/service_locator.dart'; + +class QuizModeRepositoryImpl implements QuizModeRepositoryContract { const QuizModeRepositoryImpl(); - + @override Future> collectQuestions({required SUBJ subj}) async { + final dbService = services(); List questions = []; - await Firebase.initializeApp(options: DefaultFirebaseOptions.currentPlatform) - .whenComplete(() => FirebaseFirestore.instance - .collection(FireStore.SUBJ2subject(subj)) - .get() - .then((snapshot) => snapshot.docs - .map((e) => questions.add(Question.fromMap(e.data(), subj))).toList())); + await dbService + .collection(FireStore.SUBJ2subject(subj)) + .get() + .then((snapshot) { + snapshot.docs + .map((e) => questions.add(Question.fromMap(e.id, e.data(), subj))) + .toList(); + for (var d in snapshot.docs) { + print(d.id); + print(d.data()); + } + }); - // TODO: Update imports and change actual code below once get_it is done - // await fetchQuestions(subject).then((snapshot) => snapshot.docs.map((e) => questions.add(Question.fromMap(e.data(), subj)))); - print("DONE COLLECTING QUESTIONS ========================================"); // ! Debugging return questions; - } } +} diff --git a/lib/features/quiz_mode/presentation/pages/finished_quiz_page.dart b/lib/features/quiz_mode/presentation/pages/finished_quiz_page.dart index 620031a4..8553e2d9 100644 --- a/lib/features/quiz_mode/presentation/pages/finished_quiz_page.dart +++ b/lib/features/quiz_mode/presentation/pages/finished_quiz_page.dart @@ -17,43 +17,44 @@ class FinishedQuizPage extends StatelessWidget { extraMap['subjectQuestionsMap'] as Map>; return Scaffold( - appBar: AppBar( - automaticallyImplyLeading: false, - title: const Text('Quiz Results'), - ), - body: SingleChildScrollView( - child: Column( - children: [ - Container( - padding: const EdgeInsets.all(10.0), - margin: EdgeInsets.only(top: 20, bottom: 20), - child: Text( - "Congrats on finishing! Let's see how you did", - style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold), - ), - ), - - QuestionDisplay( - subjectQuestionsMap: subjectQuestionsMap, subject: subject), - - ElevatedButton( - onPressed: () { - GoRouter.of(context).go('/quiz-mode'); - }, - child: Text('Take Another Quiz'), + appBar: AppBar( + automaticallyImplyLeading: false, + title: const Text('Quiz Results'), + ), + body: SingleChildScrollView( + child: Column( + children: [ + Container( + padding: const EdgeInsets.all(10.0), + margin: EdgeInsets.only(top: 20, bottom: 20), + child: Text( + "Congrats on finishing! Let's see how you did", + style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold), ), - - SizedBox( - height: 20, - ), - - // ElevatedButton( - // onPressed: () { - // }, - // child: Text("Back to the home page"), - // ), - ], - ), - )); + ), + + QuestionDisplay( + subjectQuestionsMap: subjectQuestionsMap, subject: subject), + + ElevatedButton( + onPressed: () { + GoRouter.of(context).go('/quiz-mode'); + }, + child: Text('Take Another Quiz'), + ), + + SizedBox( + height: 20, + ), + + // ElevatedButton( + // onPressed: () { + // }, + // child: Text("Back to the home page"), + // ), + ], + ), + ), + ); } } diff --git a/lib/features/quiz_mode/presentation/pages/solutions_quiz_page.dart b/lib/features/quiz_mode/presentation/pages/solutions_quiz_page.dart index 3422e4b4..1ea97ff8 100644 --- a/lib/features/quiz_mode/presentation/pages/solutions_quiz_page.dart +++ b/lib/features/quiz_mode/presentation/pages/solutions_quiz_page.dart @@ -10,7 +10,23 @@ class SolutionsQuizPage extends StatefulWidget { class _SolutionsQuizPageState extends State { @override Widget build(BuildContext context) { - return Container(); + return Scaffold( + appBar: AppBar( + title: Text('Card Page'), + ), + body: ListView.builder( + itemCount: [].length, + itemBuilder: (BuildContext context, int index) { + return Card( + child: ListTile( + leading: Image.network([][index]['imageUrl']), + title: Text([][index]['title']), + subtitle: Text([][index]['description']), + ), + ); + }, + ), + ); // StreamBuilder( // stream: _dbService diff --git a/lib/features/quiz_upload/data/repositories/quiz_upload_repository_impl.dart b/lib/features/quiz_upload/data/repositories/quiz_upload_repository_impl.dart index 0341db6b..009c046f 100644 --- a/lib/features/quiz_upload/data/repositories/quiz_upload_repository_impl.dart +++ b/lib/features/quiz_upload/data/repositories/quiz_upload_repository_impl.dart @@ -3,12 +3,14 @@ // import 'package:scholars_guide/service_locator/service_locator.dart'; import 'package:cloud_firestore/cloud_firestore.dart'; -import 'package:firebase_core/firebase_core.dart'; +import 'package:firebase_auth/firebase_auth.dart'; import 'package:scholars_guide/core/models/firestore_model.dart'; import 'package:scholars_guide/core/models/question_model.dart'; + import 'package:scholars_guide/features/quiz_upload/domain/repositories_contract/quiz_upload_repository_contract.dart'; import 'package:scholars_guide/features/quiz_upload/presentation/state_management/quiz_input/quiz_input_cubit.dart'; -import 'package:scholars_guide/firebase_options.dart'; + +import '../../../../service_locator/service_locator.dart'; class QuizUploadRepositoryImpl implements QuizUploadRepositoryContract { const QuizUploadRepositoryImpl(); @@ -17,29 +19,43 @@ class QuizUploadRepositoryImpl implements QuizUploadRepositoryContract { Future uploadQuestions( {required List questionsToUpload, required SUBJ subjToUpload}) async { + final authService = services(); + final dbService = services(); + + final DocumentReference userRef = + dbService.collection('users').doc(authService.currentUser?.uid); + late bool uploadSucess = true; - await Firebase.initializeApp( - options: DefaultFirebaseOptions.currentPlatform) - .whenComplete(() { - for (QuizInputCubit questionCubit in questionsToUpload) { - Question question = Question( - question: questionCubit.question, - solution: questionCubit.solution, - options: questionCubit.options, - correctIndex: int.parse(questionCubit.answerIndex), - subject: subjToUpload); - - FirebaseFirestore.instance - .collection(FireStore.SUBJ2subject(subjToUpload)) - .add(question.toMap()) - .then((value) => print('Question added to the database')) - .catchError((error) { - print('Failed to add question: $error'); - uploadSucess = false; - }); - } - }); + print('Username uid is: ${authService.currentUser?.uid}'); + + for (QuizInputCubit questionCubit in questionsToUpload) { + // * Uploading the solutions first to the database + DocumentReference solRef = await dbService.collection('solutions').add({ + FireStore.solutionData: questionCubit.solution, + FireStore.createdBy: userRef, + FireStore.createdAt: FieldValue.serverTimestamp(), + }); + + // * Uploading the questions to the database w/ solutions reference + Question question = Question( + question: questionCubit.question, + solutionRef: solRef, + options: questionCubit.options, + correctIndex: int.parse(questionCubit.answerIndex), + subject: subjToUpload, + createdBy: userRef); + + dbService + .collection(FireStore.SUBJ2subject(subjToUpload)) + .add(question.toMap()) + .then((value) => + print('[SUCCESS] | Question added to the database: $value')) + .catchError((error) { + print('[ERROR] | Failed to add question: $error'); + uploadSucess = false; + }); + } return uploadSucess; } diff --git a/lib/features/quiz_upload/presentation/widgets/quiz_input_page_widgets/confirm_submit_quiz_input_dialogue.dart b/lib/features/quiz_upload/presentation/widgets/quiz_input_page_widgets/confirm_submit_quiz_input_dialogue.dart index d79eba72..ffd13c30 100644 --- a/lib/features/quiz_upload/presentation/widgets/quiz_input_page_widgets/confirm_submit_quiz_input_dialogue.dart +++ b/lib/features/quiz_upload/presentation/widgets/quiz_input_page_widgets/confirm_submit_quiz_input_dialogue.dart @@ -2,7 +2,6 @@ import 'package:flutter/material.dart'; import 'package:go_router/go_router.dart'; -import 'package:scholars_guide/features/quiz_upload/presentation/state_management/quiz_input/quiz_input_cubit.dart'; import 'package:scholars_guide/features/quiz_upload/presentation/state_management/quiz_input_page/quiz_input_page_bloc.dart'; class ConfirmSubmitQuizInputDialogue extends StatelessWidget { diff --git a/lib/features/quiz_upload/presentation/widgets/quiz_input_widgets/question_input_display.dart b/lib/features/quiz_upload/presentation/widgets/quiz_input_widgets/question_input_display.dart index 7cec9c86..cf266656 100644 --- a/lib/features/quiz_upload/presentation/widgets/quiz_input_widgets/question_input_display.dart +++ b/lib/features/quiz_upload/presentation/widgets/quiz_input_widgets/question_input_display.dart @@ -2,7 +2,6 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; -import 'package:scholars_guide/features/quiz_upload/presentation/state_management/quiz_input/quiz_input_cubit.dart'; import 'package:scholars_guide/features/quiz_upload/presentation/state_management/quiz_input_page/quiz_input_page_bloc.dart'; import 'package:scholars_guide/features/quiz_upload/presentation/widgets/quiz_input_widgets/question_input_card_display.dart'; From ad87a437d30f5ff888aa8ce1c378237c3df61d9c Mon Sep 17 00:00:00 2001 From: Rhon Date: Tue, 12 Mar 2024 02:26:58 +0800 Subject: [PATCH 5/6] Working solutions page, with working scores --- lib/core/models/firestore_model.dart | 3 + lib/core/models/question_model.dart | 16 +- .../quiz_mode_repository_impl.dart | 12 +- .../domain/usecases/select_questions.dart | 1 - .../pages/finished_quiz_page.dart | 67 +++++-- .../presentation/pages/quiz_page.dart | 87 +++++----- .../pages/solutions_quiz_page.dart | 133 +++++--------- .../state_management/quiz/quiz_bloc.dart | 6 +- .../quiz_card/quiz_card_cubit.dart | 6 +- .../quiz_card/quiz_card_state.dart | 6 +- .../question_loading_display.dart | 7 +- .../question_card_choices_display.dart | 163 ++++++++++++------ .../quiz_widgets/question_display.dart | 2 +- .../quiz_input_page/quiz_input_page_bloc.dart | 14 -- .../change_subject_display.dart | 3 +- .../confirm_cancel_quiz_input_dialogue.dart | 61 ++++--- .../question_input_display.dart | 2 +- .../upload_complete_display.dart | 14 +- lib/router/app_router.dart | 3 +- 19 files changed, 335 insertions(+), 271 deletions(-) diff --git a/lib/core/models/firestore_model.dart b/lib/core/models/firestore_model.dart index 83f1d17a..fa508e7c 100644 --- a/lib/core/models/firestore_model.dart +++ b/lib/core/models/firestore_model.dart @@ -4,6 +4,9 @@ import 'package:scholars_guide/core/models/question_model.dart'; class FireStore { //* Collection names in the Firestore + static const solutionsCollection = 'solutions'; + static const commentsCollection = 'comments'; + static const additionalQuestionDataRef = 'additionalQuestionsData'; static const subjects = { SUBJ.MATH: 'mathematicsQuestions', SUBJ.SCIENCE: 'scienceQuestions', diff --git a/lib/core/models/question_model.dart b/lib/core/models/question_model.dart index d221c33c..0ecbdde7 100644 --- a/lib/core/models/question_model.dart +++ b/lib/core/models/question_model.dart @@ -9,22 +9,25 @@ enum SUBJ { MATH, SCIENCE, READING, LANGUAGE, ALL } class Question { Question({ - this.id = '', required this.question, - required this.solutionRef, required this.options, required this.correctIndex, required this.subject, - required this.createdBy, + this.id = '', + this.solution = '', + this.solutionRef, + this.createdBy, }); - String id; + final String question; final List options; final int correctIndex; final SUBJ subject; - final DocumentReference solutionRef; + String id; + String solution; + DocumentReference? solutionRef; - final DocumentReference createdBy; + DocumentReference? createdBy; final FieldValue createdAt = FieldValue.serverTimestamp(); static var updatedAt = FieldValue.serverTimestamp(); @@ -46,7 +49,6 @@ class Question { for (var val in data[FireStore.options].values) { temp.add(val); } - String temp2 = temp[int.parse(data[FireStore.correctIndex])]; temp.shuffle(Random()); diff --git a/lib/features/quiz_mode/data/repositories/quiz_mode_repository_impl.dart b/lib/features/quiz_mode/data/repositories/quiz_mode_repository_impl.dart index 4e69cbf6..4c21222e 100644 --- a/lib/features/quiz_mode/data/repositories/quiz_mode_repository_impl.dart +++ b/lib/features/quiz_mode/data/repositories/quiz_mode_repository_impl.dart @@ -17,19 +17,23 @@ class QuizModeRepositoryImpl implements QuizModeRepositoryContract { final dbService = services(); List questions = []; + // * Fetching the questions from the Firestore await dbService .collection(FireStore.SUBJ2subject(subj)) .get() .then((snapshot) { + print("GETTING QUESTIONS"); snapshot.docs .map((e) => questions.add(Question.fromMap(e.id, e.data(), subj))) .toList(); - for (var d in snapshot.docs) { - print(d.id); - print(d.data()); - } }); + // * Fetching the solutions from the Firestore + for (Question q in questions) { + await q.solutionRef?.get().then((value) { + q.solution = value[FireStore.solutionData]; + }); + } return questions; } } diff --git a/lib/features/quiz_mode/domain/usecases/select_questions.dart b/lib/features/quiz_mode/domain/usecases/select_questions.dart index 20df0cad..c85c2e24 100644 --- a/lib/features/quiz_mode/domain/usecases/select_questions.dart +++ b/lib/features/quiz_mode/domain/usecases/select_questions.dart @@ -13,7 +13,6 @@ class ChooseQuestions { List totalQuestions = await const QuizModeRepositoryImpl().collectQuestions(subj: subj); totalQuestions.shuffle(Random()); - // TODO: randomize the choices as well for each question return totalQuestions.sublist( 0, diff --git a/lib/features/quiz_mode/presentation/pages/finished_quiz_page.dart b/lib/features/quiz_mode/presentation/pages/finished_quiz_page.dart index 8553e2d9..ac677d71 100644 --- a/lib/features/quiz_mode/presentation/pages/finished_quiz_page.dart +++ b/lib/features/quiz_mode/presentation/pages/finished_quiz_page.dart @@ -16,6 +16,11 @@ class FinishedQuizPage extends StatelessWidget { final Map> subjectQuestionsMap = extraMap['subjectQuestionsMap'] as Map>; + final int score = + countCorrect(quizCardCubitList: subjectQuestionsMap[subject]!); + final int scorePercentage = + ((score / subjectQuestionsMap[subject]!.length) * 100).round(); + return Scaffold( appBar: AppBar( automaticallyImplyLeading: false, @@ -26,35 +31,77 @@ class FinishedQuizPage extends StatelessWidget { children: [ Container( padding: const EdgeInsets.all(10.0), - margin: EdgeInsets.only(top: 20, bottom: 20), + margin: EdgeInsets.only(top: 20, bottom: 10), child: Text( "Congrats on finishing! Let's see how you did", style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold), ), ), - + Card( + child: Container( + width: MediaQuery.of(context).size.width * 0.5, + margin: EdgeInsets.only(top: 20, bottom: 10), + child: Column( + children: [ + Text( + "Score", + style: + TextStyle(fontSize: 16, fontWeight: FontWeight.bold), + ), + Container( + padding: EdgeInsets.all(10), + child: Text( + "$score/${subjectQuestionsMap[subject]?.length}", + style: TextStyle( + color: scorePercentage <= 60 + ? Colors.red + : scorePercentage <= 80 + ? Colors.orange + : Colors.green, + fontSize: 20, + fontWeight: FontWeight.bold), + ), + ), + ElevatedButton( + onPressed: () { + GoRouter.of(context).go( + '/quiz-mode/solutions-quiz', + extra: { + 'subjectQuestionsMap': subjectQuestionsMap, + 'subject': subject + }, + ); + }, + child: Text("Review"), + ), + ], + ), + ), + ), QuestionDisplay( subjectQuestionsMap: subjectQuestionsMap, subject: subject), - ElevatedButton( onPressed: () { GoRouter.of(context).go('/quiz-mode'); }, child: Text('Take Another Quiz'), ), - SizedBox( height: 20, ), - - // ElevatedButton( - // onPressed: () { - // }, - // child: Text("Back to the home page"), - // ), ], ), ), ); } } + +int countCorrect({required List quizCardCubitList}) { + int count = 0; + for (QuizCardCubit cubit in quizCardCubitList) { + if (cubit.state.chosenIndex == cubit.correctIndex) { + count++; + } + } + return count; +} diff --git a/lib/features/quiz_mode/presentation/pages/quiz_page.dart b/lib/features/quiz_mode/presentation/pages/quiz_page.dart index 76932b6a..fa997f1d 100644 --- a/lib/features/quiz_mode/presentation/pages/quiz_page.dart +++ b/lib/features/quiz_mode/presentation/pages/quiz_page.dart @@ -1,6 +1,7 @@ // ignore_for_file: prefer_const_literals_to_create_immutables, prefer_const_constructors, curly_braces_in_flow_control_structures import 'package:flutter/material.dart'; +import 'package:flutter/widgets.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:go_router/go_router.dart'; import 'package:scholars_guide/core/models/question_model.dart'; @@ -27,8 +28,12 @@ class _QuizPageState extends State { return BlocProvider( create: (context) => QuizBloc() - ..add(QuizLoadQuestions( - subject: subject, numQuestions: widget.numQuestions)), + ..add( + QuizLoadQuestions( + subject: subject, + numQuestions: widget.numQuestions, + ), + ), child: Builder( builder: (builderContext) { return Scaffold( @@ -46,53 +51,53 @@ class _QuizPageState extends State { }, ), ), - body: Stack( - children: [ - SingleChildScrollView( - child: BlocBuilder( - builder: (quizBlocContext, state) { - if (state is QuizLoading) { - return QuestionLoadingDisplay(); - } else if (state is QuizOngoing) { - return Column( + body: BlocBuilder( + builder: (quizBlocContext, state) { + if (state is QuizLoading) { + return QuestionLoadingDisplay(); + } else if (state is QuizOngoing) { + return Stack( + children: [ + SingleChildScrollView( + child: Column( children: [ // * Display the questions QuestionDisplay( - subjectQuestionsMap: quizBlocContext - .read() - .subjectQuestionsMap, - subject: subject), + subjectQuestionsMap: quizBlocContext + .read() + .subjectQuestionsMap, + subject: subject, + ), // * Display the submit button ElevatedButton( - onPressed: () { - showDialog( - context: context, - builder: (BuildContext buildContext) { - return ConfirmSubmitQuizDialogue( - quizBloc: - quizBlocContext.read(), - ); - }, - ); - }, - child: Text("Submit")), - - + onPressed: () { + showDialog( + context: context, + builder: (BuildContext buildContext) { + return ConfirmSubmitQuizDialogue( + quizBloc: + quizBlocContext.read(), + ); + }, + ); + }, + child: Text("Submit"), + ), ], - ); - } + ), + ), - GoRouter.of(context).go('/quiz-mode'); - return Text( - "SOMETHING WENT WRONG! (No Quiz Bloc State Matched)"); - }, - ), - ), - - // * Display the timer - TimerDisplay(), - ], + // * Display the timer + TimerDisplay(), + ], + ); + } + + GoRouter.of(context).go('/quiz-mode'); + return Text( + "SOMETHING WENT WRONG! (No Quiz Bloc State Matched)"); + }, ), ); }, diff --git a/lib/features/quiz_mode/presentation/pages/solutions_quiz_page.dart b/lib/features/quiz_mode/presentation/pages/solutions_quiz_page.dart index 1ea97ff8..f1749ecd 100644 --- a/lib/features/quiz_mode/presentation/pages/solutions_quiz_page.dart +++ b/lib/features/quiz_mode/presentation/pages/solutions_quiz_page.dart @@ -1,4 +1,13 @@ +// ignore_for_file: prefer_const_constructors, prefer_const_literals_to_create_immutables + import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:go_router/go_router.dart'; +import 'package:scholars_guide/core/models/question_model.dart'; +import 'package:scholars_guide/features/quiz_mode/presentation/state_management/quiz_card/quiz_card_cubit.dart'; +import 'package:scholars_guide/features/quiz_mode/presentation/state_management/solution_quiz/solution_quiz_cubit.dart'; +import 'package:scholars_guide/features/quiz_mode/presentation/widgets/quiz_page_widgets/question_loading_display.dart'; +import 'package:scholars_guide/features/quiz_mode/presentation/widgets/solution_quiz_page_widgets/solution_card_display.dart'; class SolutionsQuizPage extends StatefulWidget { const SolutionsQuizPage({super.key}); @@ -10,103 +19,41 @@ class SolutionsQuizPage extends StatefulWidget { class _SolutionsQuizPageState extends State { @override Widget build(BuildContext context) { + final extraMap = GoRouterState.of(context).extra as Map; + final SUBJ subject = extraMap['subject'] as SUBJ; + final Map> subjectQuestionsMap = + extraMap['subjectQuestionsMap'] as Map>; + return Scaffold( appBar: AppBar( - title: Text('Card Page'), + automaticallyImplyLeading: false, + title: Text('Solutions Page'), ), - body: ListView.builder( - itemCount: [].length, - itemBuilder: (BuildContext context, int index) { - return Card( - child: ListTile( - leading: Image.network([][index]['imageUrl']), - title: Text([][index]['title']), - subtitle: Text([][index]['description']), - ), - ); - }, + body: BlocProvider( + create: (providerContext) => SolutionQuizCubit()..loadSolutions(), + child: BlocBuilder( + builder: (solutionQuizCubitContext, state) { + List questions = subjectQuestionsMap[subject]!; + + if (state is SolutionQuizLoad) { + return QuestionLoadingDisplay(); + } else if (state is SolutionQuizShown) { + return PageView.builder( + itemCount: questions.length, + itemBuilder: (context, index) => SolutionCardDisplay( + question: questions[index].question, + answer: questions[index] + .optionsArray[questions[index].correctIndex], + solution: questions[index].solution, + ), + ); + } + return Center( + child: Text('Something went wrong'), + ); + }, + ), ), ); - -// StreamBuilder( -// stream: _dbService -// .collection('users') -// .doc(_authService.currentUser?.uid) -// .snapshots(), -// builder: (BuildContext context, -// AsyncSnapshot snapshot) { -// if (snapshot.hasError) { -// return const Text('Something went wrong'); -// } - -// if (snapshot.connectionState == ConnectionState.waiting) { -// // Show a loading spinner. -// return const CircularProgressIndicator(); -// } - -// // Convert timestamp to DateTime. -// var createdAt = -// (snapshot.data?.get('createdAt') as Timestamp).toDate(); -// var updatedAt = snapshot.data?.get('updatedAt').toDate(); - -// // createdAt = createdAt is Timestamp -// // ? createdAt.toDate() -// // : DateTime.now(); -// // updatedAt = updatedAt is Timestamp -// // ? updatedAt.toDate() -// // : DateTime.now(); - -// String username = -// snapshot.data?.get('username') ?? 'Loading...'; - -// return Column( -// crossAxisAlignment: CrossAxisAlignment.center, -// children: [ -// CircleAvatar( -// backgroundColor: Colors.blue, -// radius: 50, -// child: Text( -// username[0].toUpperCase(), -// style: TextStyle( -// fontSize: 40, -// color: Colors.white, -// ), -// ), -// ), -// SizedBox(height: 16), -// Text( -// username, -// style: TextStyle( -// fontSize: 24, -// fontWeight: FontWeight.bold, -// ), -// ), -// SizedBox(height: 4), -// Text( -// _authService.currentUser?.email ?? 'Loading...', -// style: TextStyle( -// fontSize: 18, -// ), -// ), -// SizedBox(height: 32), -// ProfileDetailRow( -// title: 'UID:', -// detail: -// '***${_authService.currentUser?.uid.substring(12, _authService.currentUser?.uid.length)}' ?? -// 'Loading...', -// ), -// SizedBox(height: 10), -// ProfileDetailRow( -// title: 'Joined in:', -// detail: 'January 1, 2022', -// ), -// SizedBox(height: 10), -// ProfileDetailRow( -// title: 'Updated in:', -// detail: 'February 20, 2024', -// ), -// ], -// ); -// }), } } diff --git a/lib/features/quiz_mode/presentation/state_management/quiz/quiz_bloc.dart b/lib/features/quiz_mode/presentation/state_management/quiz/quiz_bloc.dart index e13696e7..57316565 100644 --- a/lib/features/quiz_mode/presentation/state_management/quiz/quiz_bloc.dart +++ b/lib/features/quiz_mode/presentation/state_management/quiz/quiz_bloc.dart @@ -26,7 +26,8 @@ class QuizBloc extends Bloc { .map((e) => QuizCardCubit( correctIndex: e.correctIndex, optionsArray: e.options, - question: e.question)) + question: e.question, + solution: e.solution)) .toList()); }); } else { @@ -39,7 +40,8 @@ class QuizBloc extends Bloc { .map((e) => QuizCardCubit( correctIndex: e.correctIndex, optionsArray: e.options, - question: e.question)) + question: e.question, + solution: e.solution)) .toList() }; } diff --git a/lib/features/quiz_mode/presentation/state_management/quiz_card/quiz_card_cubit.dart b/lib/features/quiz_mode/presentation/state_management/quiz_card/quiz_card_cubit.dart index 034feea4..b8807f80 100644 --- a/lib/features/quiz_mode/presentation/state_management/quiz_card/quiz_card_cubit.dart +++ b/lib/features/quiz_mode/presentation/state_management/quiz_card/quiz_card_cubit.dart @@ -8,15 +8,19 @@ class QuizCardCubit extends Cubit { required this.correctIndex, required this.optionsArray, required this.question, + required this.solution, }) : super(QuizCardUnanswered()); final int correctIndex; final List optionsArray; final String question; + final String solution; void chooseOption({required int index}) { emit(QuizCardUnanswered()); emit(QuizCardAnswered(chosenIndex: index)); } - void revealAnswer({required int index}) => emit(QuizCardRevealed(chosenIndex: index)); + + void revealAnswer({required int index}) => + emit(QuizCardRevealed(chosenIndex: index)); } diff --git a/lib/features/quiz_mode/presentation/state_management/quiz_card/quiz_card_state.dart b/lib/features/quiz_mode/presentation/state_management/quiz_card/quiz_card_state.dart index c7c91215..d3af299b 100644 --- a/lib/features/quiz_mode/presentation/state_management/quiz_card/quiz_card_state.dart +++ b/lib/features/quiz_mode/presentation/state_management/quiz_card/quiz_card_state.dart @@ -1,10 +1,14 @@ +// ignore_for_file: overridden_fields, annotate_overrides + part of 'quiz_card_cubit.dart'; sealed class QuizCardState extends Equatable { const QuizCardState(); + final int chosenIndex = -1; + @override - List get props => []; + List get props => [chosenIndex]; } final class QuizCardUnanswered extends QuizCardState {} diff --git a/lib/features/quiz_mode/presentation/widgets/quiz_page_widgets/question_loading_display.dart b/lib/features/quiz_mode/presentation/widgets/quiz_page_widgets/question_loading_display.dart index fe953c40..026cb21a 100644 --- a/lib/features/quiz_mode/presentation/widgets/quiz_page_widgets/question_loading_display.dart +++ b/lib/features/quiz_mode/presentation/widgets/quiz_page_widgets/question_loading_display.dart @@ -5,6 +5,11 @@ class QuestionLoadingDisplay extends StatelessWidget { @override Widget build(BuildContext context) { - return const Center(child: CircularProgressIndicator(color: Colors.white, backgroundColor: Colors.yellow,)); + return const Center( + child: CircularProgressIndicator( + color: Colors.white, + backgroundColor: Color.fromARGB(255, 145, 133, 165), + ), + ); } } diff --git a/lib/features/quiz_mode/presentation/widgets/quiz_widgets/question_card_choices_display.dart b/lib/features/quiz_mode/presentation/widgets/quiz_widgets/question_card_choices_display.dart index 204bdae2..892b8e6e 100644 --- a/lib/features/quiz_mode/presentation/widgets/quiz_widgets/question_card_choices_display.dart +++ b/lib/features/quiz_mode/presentation/widgets/quiz_widgets/question_card_choices_display.dart @@ -1,4 +1,4 @@ -// ignore_for_file: prefer_const_constructors +// ignore_for_file: prefer_const_constructors, prefer_typing_uninitialized_variables, must_be_immutable import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; @@ -25,40 +25,41 @@ class _QuestionCardChoicesDisplayState bloc: widget.bloc, builder: (choicesContext, state) { List choices = widget.bloc.optionsArray; - List buttons = []; + List buttons = []; for (int i = 0; i < choices.length; i++) { String c = choices[i]; - ElevatedButton button; + ChoiceButton button; if (state is QuizCardUnanswered) { - button = _createChoiceButton( - choice: c, - func: () { - widget.bloc.chooseOption(index: i); - }, - context: context); + button = ChoiceButton( + choice: c, + func: () { + widget.bloc.chooseOption(index: i); + }, + ); } else if (state is QuizCardAnswered) { - button = _createChoiceButton( - choice: c, - func: () { - widget.bloc.chooseOption(index: i); - }, - isChosen: state.chosenIndex == i, - isCorrect: widget.bloc.correctIndex == state.chosenIndex, - context: - context); // ! This is the line that needs to be changed + button = ChoiceButton( + choice: c, + func: () { + widget.bloc.chooseOption(index: i); + }, + isChosen: state.chosenIndex == i, + isCorrect: widget.bloc.correctIndex == state.chosenIndex, + ); } else if (state is QuizCardRevealed) { - button = _createChoiceButton( - choice: c, - func: () {}, - isChosen: state.chosenIndex == i, - isCorrect: widget.bloc.correctIndex == state.chosenIndex, - isRevealed: true, - context: context); + button = ChoiceButton( + choice: c, + func: () {}, + isChosen: state.chosenIndex == i, + isCorrect: widget.bloc.correctIndex == state.chosenIndex, + isRevealed: true, + ); } else { - button = - _createChoiceButton(choice: c, func: () {}, context: context); + button = ChoiceButton( + choice: c, + func: () {}, + ); } buttons.add(button); } @@ -68,39 +69,89 @@ class _QuestionCardChoicesDisplayState } } -ElevatedButton _createChoiceButton( - {required String choice, - bool isChosen = false, - bool isCorrect = false, - bool isRevealed = false, - required func, - required context}) { - Color color = Colors.black; - if (isRevealed) { - color = isCorrect - ? isChosen - ? Colors.green - : Colors.black - : isChosen - ? Colors.red - : Colors.black; - } else if (isChosen) { - color = Colors.indigoAccent; - } - return ElevatedButton( - style: ButtonStyle( - shape: MaterialStateProperty.all( - RoundedRectangleBorder( - borderRadius: BorderRadius.circular(10.0), - side: BorderSide(color: color), +class ChoiceButton extends StatelessWidget { + ChoiceButton({ + super.key, + required this.choice, + required this.func, + this.isChosen = false, + this.isCorrect = false, + this.isRevealed = false, + }); + + bool isChosen; + bool isCorrect; + bool isRevealed; + final String choice; + final func; + + @override + Widget build(BuildContext context) { + Color color = Colors.black; + if (isRevealed) { + color = isCorrect + ? isChosen + ? Colors.green + : Colors.black + : isChosen + ? Colors.red + : Colors.black; + } else if (isChosen) { + color = Colors.indigoAccent; + } + + return Container( + width: MediaQuery.of(context).size.width * 0.7, + margin: EdgeInsets.only(top: 2.0, bottom: 2.0), + child: ElevatedButton( + style: ButtonStyle( + shape: MaterialStateProperty.all( + RoundedRectangleBorder( + borderRadius: BorderRadius.circular(10.0), + side: BorderSide(color: color), + ), + ), ), + onPressed: isRevealed ? null : func, + child: TextMarkdown(text: choice), ), - ), - onPressed: isRevealed ? null : func, - child: TextMarkdown(text: choice), - ); + ); + } } +// ElevatedButton _createChoiceButton( +// {required String choice, +// bool isChosen = false, +// bool isCorrect = false, +// bool isRevealed = false, +// required func, +// required context}) { +// Color color = Colors.black; +// if (isRevealed) { +// color = isCorrect +// ? isChosen +// ? Colors.green +// : Colors.black +// : isChosen +// ? Colors.red +// : Colors.black; +// } else if (isChosen) { +// color = Colors.indigoAccent; +// } +// return ElevatedButton( +// style: ButtonStyle( +// shape: MaterialStateProperty.all( +// RoundedRectangleBorder( +// borderRadius: BorderRadius.circular(10.0), +// side: BorderSide(color: color), +// ), +// ), +// ), +// onPressed: isRevealed ? null : func, +// child: TextMarkdown(text: choice), +// ); +// } + class TextMarkdown extends StatelessWidget { const TextMarkdown({ super.key, diff --git a/lib/features/quiz_mode/presentation/widgets/quiz_widgets/question_display.dart b/lib/features/quiz_mode/presentation/widgets/quiz_widgets/question_display.dart index 7c769b4f..6615ae09 100644 --- a/lib/features/quiz_mode/presentation/widgets/quiz_widgets/question_display.dart +++ b/lib/features/quiz_mode/presentation/widgets/quiz_widgets/question_display.dart @@ -25,7 +25,7 @@ class _QuestionDisplayState extends State { return ListView.builder( shrinkWrap: true, physics: const NeverScrollableScrollPhysics(), - itemCount: widget.subjectQuestionsMap[widget.subject]?.length, + itemCount: questions.length, itemBuilder: (context, index) { return QuestionCardDisplay( bloc: questions[index], diff --git a/lib/features/quiz_upload/presentation/state_management/quiz_input_page/quiz_input_page_bloc.dart b/lib/features/quiz_upload/presentation/state_management/quiz_input_page/quiz_input_page_bloc.dart index 61ffb7dc..3c944b05 100644 --- a/lib/features/quiz_upload/presentation/state_management/quiz_input_page/quiz_input_page_bloc.dart +++ b/lib/features/quiz_upload/presentation/state_management/quiz_input_page/quiz_input_page_bloc.dart @@ -92,18 +92,4 @@ class QuizInputPageBloc extends Bloc { print("Options non empty: ${question.optionsNonEmpty}"); } } - // void _printQuestions() { - // print("REVEAL THE BLANKS ? $revealBlanks"); - // for (var question in questions) { - // print( - // " ====================================== Printing question ======================================"); - // print("Question: ${question.state.question}"); - // print("Solution: ${question.state.solution}"); - // print("Options: ${question.state.options}"); - // print("Answer: ${question.state.answerIndex}"); - // print("Question non empty: ${question.state.questionNonEmpty}"); - // print("Solution non empty: ${question.state.solutionNonEmpty}"); - // print("Options non empty: ${question.state.optionsNonEmpty}"); - // } - // } } diff --git a/lib/features/quiz_upload/presentation/widgets/quiz_input_page_widgets/change_subject_display.dart b/lib/features/quiz_upload/presentation/widgets/quiz_input_page_widgets/change_subject_display.dart index 06754e56..87fc5b7a 100644 --- a/lib/features/quiz_upload/presentation/widgets/quiz_input_page_widgets/change_subject_display.dart +++ b/lib/features/quiz_upload/presentation/widgets/quiz_input_page_widgets/change_subject_display.dart @@ -32,8 +32,7 @@ class _ChangeSubjectDisplayState extends State { icon: Icon(Icons.arrow_drop_down), iconSize: 24, elevation: 16, - style: TextStyle( - color: Color.fromARGB(255, 63, 86, 169)), // ! Panget UI + style: TextStyle(color: Color.fromARGB(255, 63, 86, 169)), underline: Container( height: 2, color: Color.fromARGB(255, 63, 86, 169), diff --git a/lib/features/quiz_upload/presentation/widgets/quiz_input_page_widgets/confirm_cancel_quiz_input_dialogue.dart b/lib/features/quiz_upload/presentation/widgets/quiz_input_page_widgets/confirm_cancel_quiz_input_dialogue.dart index f045a13d..afda315b 100644 --- a/lib/features/quiz_upload/presentation/widgets/quiz_input_page_widgets/confirm_cancel_quiz_input_dialogue.dart +++ b/lib/features/quiz_upload/presentation/widgets/quiz_input_page_widgets/confirm_cancel_quiz_input_dialogue.dart @@ -5,6 +5,7 @@ import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:scholars_guide/features/quiz_mode/presentation/pages/ready_quiz_page.dart'; import 'package:scholars_guide/features/quiz_upload/presentation/state_management/quiz_input_page/quiz_input_page_bloc.dart'; +// ! Currently not used class ConfirmCancelQuizInputDisplay extends StatelessWidget { const ConfirmCancelQuizInputDisplay({super.key}); @@ -12,33 +13,37 @@ class ConfirmCancelQuizInputDisplay extends StatelessWidget { Widget build(BuildContext context) { return Scaffold( body: Container( - width: double.infinity, - height: double.infinity, - decoration: BoxDecoration( - color: Colors.black.withOpacity(0.6), - ), - child: AlertDialog( - title: const Text('Cancel Quiz Input'), - content: const Text("Your inputs will be discared! Are you sure?"), - actions: [ - ElevatedButton( - child: const Text('Continue Quiz Input'), - onPressed: () { - context.read().add(QuizInputPageCancelBackBtnPressed()); - }, - ), - ElevatedButton( - child: const Text('Go Back'), - onPressed: () { - Navigator.push( - context, - MaterialPageRoute( - builder: (context) => (ReadyQuizPage())), // ! Change to home page - ); - }, - ), - ], - )), + width: double.infinity, + height: double.infinity, + decoration: BoxDecoration( + color: Colors.black.withOpacity(0.6), + ), + child: AlertDialog( + title: const Text('Cancel Quiz Input'), + content: const Text("Your inputs will be discared! Are you sure?"), + actions: [ + ElevatedButton( + child: const Text('Continue Quiz Input'), + onPressed: () { + context + .read() + .add(QuizInputPageCancelBackBtnPressed()); + }, + ), + ElevatedButton( + child: const Text('Go Back'), + onPressed: () { + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => (ReadyQuizPage()), + ), // ! Change to home page + ); + }, + ), + ], + ), + ), ); } -} \ No newline at end of file +} diff --git a/lib/features/quiz_upload/presentation/widgets/quiz_input_widgets/question_input_display.dart b/lib/features/quiz_upload/presentation/widgets/quiz_input_widgets/question_input_display.dart index cf266656..0f331244 100644 --- a/lib/features/quiz_upload/presentation/widgets/quiz_input_widgets/question_input_display.dart +++ b/lib/features/quiz_upload/presentation/widgets/quiz_input_widgets/question_input_display.dart @@ -31,7 +31,7 @@ class _QuestionInputDisplayState extends State { child: Icon(Icons.delete), ), direction: DismissDirection.endToStart, - key: ValueKey(index), + key: UniqueKey(), child: QuestionInputCard( questionCubit: buildContext.read().questions[index]), diff --git a/lib/features/quiz_upload/presentation/widgets/upload_quiz_widgets/upload_complete_display.dart b/lib/features/quiz_upload/presentation/widgets/upload_quiz_widgets/upload_complete_display.dart index 58bef0ab..da5e9e41 100644 --- a/lib/features/quiz_upload/presentation/widgets/upload_quiz_widgets/upload_complete_display.dart +++ b/lib/features/quiz_upload/presentation/widgets/upload_quiz_widgets/upload_complete_display.dart @@ -13,7 +13,7 @@ class UploadCompleteDisplay extends StatelessWidget { mainAxisAlignment: MainAxisAlignment.center, children: [ Text("Upload Success!"), - + // Todo: Show results / Show all questions uploaded by user ElevatedButton( @@ -21,12 +21,12 @@ class UploadCompleteDisplay extends StatelessWidget { GoRouter.of(context).go('/quiz-upload'); }, child: Text("Add more questions")), - // ElevatedButton( - // onPressed: () {}, - // child: Text("Go Back to Home Page")), // ! Add a route to home page - ElevatedButton(onPressed: () { - GoRouter.of(context).go('/quiz-mode'); - }, child: Text("Take a quiz now!")) + + ElevatedButton( + onPressed: () { + GoRouter.of(context).go('/quiz-mode'); + }, + child: Text("Take a quiz now!")) ], )); } diff --git a/lib/router/app_router.dart b/lib/router/app_router.dart index 6038cc46..f2891569 100644 --- a/lib/router/app_router.dart +++ b/lib/router/app_router.dart @@ -3,6 +3,7 @@ import 'package:go_router/go_router.dart'; import 'package:scholars_guide/features/quiz_mode/presentation/pages/finished_quiz_page.dart'; import 'package:scholars_guide/features/quiz_mode/presentation/pages/quiz_page.dart'; import 'package:scholars_guide/features/quiz_mode/presentation/pages/ready_quiz_page.dart'; +import 'package:scholars_guide/features/quiz_mode/presentation/pages/solutions_quiz_page.dart'; import 'package:scholars_guide/features/quiz_upload/presentation/pages/upload_questions_page.dart'; import 'package:scholars_guide/features/quiz_upload/presentation/pages/upload_success_page.dart'; @@ -141,7 +142,7 @@ class AppRouter { GoRoute( path: 'solutions-quiz', pageBuilder: (context, state) => const NoTransitionPage( - child: FinishedQuizPage(), // ! Change + child: SolutionsQuizPage(), ), ), ], From b58716eddadd34847edf0cf7ff2681ad725be315 Mon Sep 17 00:00:00 2001 From: Rhon Date: Tue, 12 Mar 2024 02:27:18 +0800 Subject: [PATCH 6/6] Working solutions page, with working scores p2 --- .../solution_quiz/solution_quiz_cubit.dart | 17 +++ .../solution_quiz/solution_quiz_state.dart | 12 ++ .../solution_card_display.dart | 118 ++++++++++++++++++ 3 files changed, 147 insertions(+) create mode 100644 lib/features/quiz_mode/presentation/state_management/solution_quiz/solution_quiz_cubit.dart create mode 100644 lib/features/quiz_mode/presentation/state_management/solution_quiz/solution_quiz_state.dart create mode 100644 lib/features/quiz_mode/presentation/widgets/solution_quiz_page_widgets/solution_card_display.dart diff --git a/lib/features/quiz_mode/presentation/state_management/solution_quiz/solution_quiz_cubit.dart b/lib/features/quiz_mode/presentation/state_management/solution_quiz/solution_quiz_cubit.dart new file mode 100644 index 00000000..6412229a --- /dev/null +++ b/lib/features/quiz_mode/presentation/state_management/solution_quiz/solution_quiz_cubit.dart @@ -0,0 +1,17 @@ +import 'package:bloc/bloc.dart'; +import 'package:equatable/equatable.dart'; + +part 'solution_quiz_state.dart'; + +class SolutionQuizCubit extends Cubit { + SolutionQuizCubit() : super(SolutionQuizLoad()); + + void loadSolutions() async { + await placeholder(); + emit(SolutionQuizShown()); + } +} + +Future placeholder() async { + await Future.delayed(Duration(seconds: 3)); +} diff --git a/lib/features/quiz_mode/presentation/state_management/solution_quiz/solution_quiz_state.dart b/lib/features/quiz_mode/presentation/state_management/solution_quiz/solution_quiz_state.dart new file mode 100644 index 00000000..848ab063 --- /dev/null +++ b/lib/features/quiz_mode/presentation/state_management/solution_quiz/solution_quiz_state.dart @@ -0,0 +1,12 @@ +part of 'solution_quiz_cubit.dart'; + +sealed class SolutionQuizState extends Equatable { + const SolutionQuizState(); + + @override + List get props => []; +} + +final class SolutionQuizLoad extends SolutionQuizState {} + +final class SolutionQuizShown extends SolutionQuizState {} diff --git a/lib/features/quiz_mode/presentation/widgets/solution_quiz_page_widgets/solution_card_display.dart b/lib/features/quiz_mode/presentation/widgets/solution_quiz_page_widgets/solution_card_display.dart new file mode 100644 index 00000000..4c172c7b --- /dev/null +++ b/lib/features/quiz_mode/presentation/widgets/solution_quiz_page_widgets/solution_card_display.dart @@ -0,0 +1,118 @@ +// ignore_for_file: prefer_const_constructors + +import 'package:flutter/material.dart'; +import 'package:flutter_markdown/flutter_markdown.dart'; +import 'package:flutter_markdown_latex/flutter_markdown_latex.dart'; +import 'package:markdown/markdown.dart' as md; + +class SolutionCardDisplay extends StatefulWidget { + const SolutionCardDisplay( + {super.key, + required this.question, + required this.answer, + required this.solution}); + + final String question; + final String answer; + final String solution; + + @override + State createState() => _SolutionCardDisplayState(); +} + +class _SolutionCardDisplayState extends State { + @override + Widget build(BuildContext context) { + return Card( + margin: EdgeInsets.all(10.0), + child: Padding( + padding: const EdgeInsets.all(8.0), + child: Container( + padding: EdgeInsets.all(20.0), + child: Column( + children: [ + TextTitle(title: "Question"), + TextMarkdown( + text: widget.question, + styleSheet: MarkdownStyleSheet( + textAlign: WrapAlignment.center, + p: const TextStyle( + fontSize: 20.0, fontWeight: FontWeight.bold), + ), + ), + TextTitle(title: "Answer"), + TextMarkdown( + text: widget.answer, + styleSheet: MarkdownStyleSheet( + textAlign: WrapAlignment.center, + p: const TextStyle( + fontSize: 20.0, fontWeight: FontWeight.bold), + ), + ), + TextTitle(title: "Solution"), + TextMarkdown( + text: widget.solution == "" + ? "No solution yet provided" + : widget.solution, + styleSheet: MarkdownStyleSheet( + textAlign: WrapAlignment.center, + p: const TextStyle(fontSize: 18), + ), + ) + ], + ), + ), + ), + ); + } +} + +class TextTitle extends StatelessWidget { + const TextTitle({super.key, required this.title}); + + final String title; + + @override + Widget build(BuildContext context) { + return Container( + margin: EdgeInsets.only(bottom: 10.0), + child: Text(title, + style: TextStyle( + fontSize: 20.0, + fontWeight: FontWeight.bold, + color: Color.fromARGB(255, 95, 55, 213), + )), + ); + } +} + +class TextMarkdown extends StatelessWidget { + const TextMarkdown({ + super.key, + required this.text, + required this.styleSheet, + }); + + final String text; + final MarkdownStyleSheet styleSheet; + + @override + Widget build(BuildContext context) { + return Container( + margin: EdgeInsets.only(bottom: 20.0), + child: Markdown( + padding: EdgeInsets.all(0.0), + shrinkWrap: true, + data: text, + builders: { + 'latex': LatexElementBuilder(), + }, + styleSheet: styleSheet, + extensionSet: md.ExtensionSet( + [LatexBlockSyntax()], + [LatexInlineSyntax()], + ), + ), + ); + } +}