├── .gitignore ├── .metadata ├── .vscode └── settings.json ├── LICENSE ├── README.md ├── analysis_options.yaml ├── android ├── .gitignore ├── app │ ├── build.gradle │ └── src │ │ ├── debug │ │ └── AndroidManifest.xml │ │ ├── main │ │ ├── AndroidManifest.xml │ │ ├── kotlin │ │ │ └── com │ │ │ │ └── example │ │ │ │ └── quizwiz │ │ │ │ └── MainActivity.kt │ │ └── res │ │ │ ├── drawable-v21 │ │ │ └── launch_background.xml │ │ │ ├── drawable │ │ │ └── launch_background.xml │ │ │ ├── mipmap-hdpi │ │ │ └── ic_launcher.png │ │ │ ├── mipmap-mdpi │ │ │ └── ic_launcher.png │ │ │ ├── mipmap-xhdpi │ │ │ └── ic_launcher.png │ │ │ ├── mipmap-xxhdpi │ │ │ └── ic_launcher.png │ │ │ ├── mipmap-xxxhdpi │ │ │ └── ic_launcher.png │ │ │ ├── values-night │ │ │ └── styles.xml │ │ │ └── values │ │ │ └── styles.xml │ │ └── profile │ │ └── AndroidManifest.xml ├── build.gradle ├── build │ └── ios │ │ └── Pods.build │ │ └── Release-iphonesimulator │ │ ├── DKImagePickerController-DKImagePickerController.build │ │ └── dgph │ │ ├── DKImagePickerController.build │ │ └── dgph │ │ ├── DKPhotoGallery-DKPhotoGallery.build │ │ └── dgph │ │ ├── DKPhotoGallery.build │ │ └── dgph │ │ ├── Flutter.build │ │ └── dgph │ │ ├── Pods-Runner.build │ │ └── dgph │ │ ├── Pods-RunnerTests.build │ │ └── dgph │ │ ├── ReachabilitySwift.build │ │ └── dgph │ │ ├── SDWebImage.build │ │ └── dgph │ │ ├── SwiftyGif.build │ │ └── dgph │ │ ├── connectivity_plus.build │ │ └── dgph │ │ ├── file_picker.build │ │ └── dgph │ │ ├── isar_flutter_libs.build │ │ └── dgph │ │ ├── path_provider_foundation.build │ │ └── dgph │ │ └── pdf_text.build │ │ └── dgph ├── gradle.properties ├── gradle │ └── wrapper │ │ └── gradle-wrapper.properties └── settings.gradle ├── assets ├── 1697293737915 -completed.json ├── 93134-not-found.json └── icon.jpeg ├── ios ├── .gitignore ├── Flutter │ ├── AppFrameworkInfo.plist │ ├── Debug.xcconfig │ └── Release.xcconfig ├── Podfile ├── Podfile.lock ├── Runner.xcodeproj │ ├── project.pbxproj │ ├── project.xcworkspace │ │ ├── contents.xcworkspacedata │ │ └── xcshareddata │ │ │ ├── IDEWorkspaceChecks.plist │ │ │ └── WorkspaceSettings.xcsettings │ └── xcshareddata │ │ └── xcschemes │ │ └── Runner.xcscheme ├── Runner.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ ├── IDEWorkspaceChecks.plist │ │ └── WorkspaceSettings.xcsettings ├── Runner │ ├── AppDelegate.swift │ ├── Assets.xcassets │ │ ├── AppIcon.appiconset │ │ │ ├── Contents.json │ │ │ ├── Icon-App-1024x1024@1x.png │ │ │ ├── Icon-App-20x20@1x.png │ │ │ ├── Icon-App-20x20@2x.png │ │ │ ├── Icon-App-20x20@3x.png │ │ │ ├── Icon-App-29x29@1x.png │ │ │ ├── Icon-App-29x29@2x.png │ │ │ ├── Icon-App-29x29@3x.png │ │ │ ├── Icon-App-40x40@1x.png │ │ │ ├── Icon-App-40x40@2x.png │ │ │ ├── Icon-App-40x40@3x.png │ │ │ ├── Icon-App-50x50@1x.png │ │ │ ├── Icon-App-50x50@2x.png │ │ │ ├── Icon-App-57x57@1x.png │ │ │ ├── Icon-App-57x57@2x.png │ │ │ ├── Icon-App-60x60@2x.png │ │ │ ├── Icon-App-60x60@3x.png │ │ │ ├── Icon-App-72x72@1x.png │ │ │ ├── Icon-App-72x72@2x.png │ │ │ ├── Icon-App-76x76@1x.png │ │ │ ├── Icon-App-76x76@2x.png │ │ │ └── Icon-App-83.5x83.5@2x.png │ │ └── LaunchImage.imageset │ │ │ ├── Contents.json │ │ │ ├── LaunchImage.png │ │ │ ├── LaunchImage@2x.png │ │ │ ├── LaunchImage@3x.png │ │ │ └── README.md │ ├── Base.lproj │ │ ├── LaunchScreen.storyboard │ │ └── Main.storyboard │ ├── Info.plist │ └── Runner-Bridging-Header.h └── RunnerTests │ └── RunnerTests.swift ├── lib ├── main.dart └── src │ ├── app.dart │ ├── core │ ├── core.dart │ ├── errors │ │ ├── exceptions.dart │ │ └── failure.dart │ ├── router │ │ ├── route_generator.dart │ │ └── routes.dart │ ├── services │ │ ├── internet_connection.dart │ │ └── service_locator.dart │ ├── theme │ │ ├── app_theme.dart │ │ └── cubit │ │ │ └── theme_cubit.dart │ ├── utils │ │ ├── enums.dart │ │ ├── network_constants.dart │ │ ├── strings.dart │ │ └── typdef.dart │ └── widgets │ │ ├── custom_snackbar.dart │ │ ├── error_widget.dart │ │ ├── loading_widget.dart │ │ └── no_result_screen.dart │ └── features │ └── cards │ ├── controller │ ├── bloc │ │ ├── cards_bloc.dart │ │ ├── cards_events.dart │ │ └── cards_state.dart │ └── controller.dart │ ├── data │ ├── data.dart │ ├── data_source │ │ ├── local_data_source │ │ │ ├── collection_local_data_source.dart │ │ │ └── flashcard_local_data_source.dart │ │ └── remote_data_source │ │ │ ├── api_client.dart │ │ │ └── remote_data_source.dart │ ├── models │ │ ├── card_calculation.dart │ │ ├── edit_flashcard_parameters.dart │ │ ├── flashcard_collection.dart │ │ ├── flashcard_collection.g.dart │ │ └── multiple_choice_quiz.dart │ └── repository │ │ ├── base_cards_repository.dart │ │ └── cards_repository.dart │ └── presentation │ ├── presentation.dart │ ├── screens │ ├── create_flashcards_screen.dart │ ├── edit_flashcard_screen.dart │ ├── flashcards_list_screen.dart │ ├── generated_flashcards_screen.dart │ ├── home_screen.dart │ ├── multiple_choice_quiz_screen.dart │ ├── practice_cards_screen.dart │ ├── review_result_screen.dart │ └── writing_quiz_screen.dart │ └── widgets │ ├── create_flashcards_widgets │ └── forms.dart │ ├── flashcards_list_widgets │ ├── custom_focused_menu_holder.dart │ └── flashcard_widget.dart │ ├── generated_flashcards │ └── generated_flashcard_widget.dart │ ├── home_screen_widgets │ ├── collection_card_widget.dart │ ├── collections_list_screen.dart │ ├── combine_collection_dialog.dart │ ├── create_collection_dialog.dart │ ├── create_or_edit_collection_dialog.dart │ └── edit_collection_dialog.dart │ ├── multiple_choice_quiz_widgets │ └── multiple_choice_body.dart │ ├── practice_cards_widgets │ ├── choose_quiz_dialog.dart │ └── no_flashcards_to_review.dart │ ├── review_result_widgets │ └── review_bar.dart │ └── writing_quiz_widgets │ ├── answer_form_field.dart │ ├── display_answer_widget.dart │ └── display_question_widget.dart ├── pubspec.lock ├── pubspec.yaml └── test └── src ├── app_test.dart ├── features └── cards │ └── presentation │ ├── screens │ ├── create_flashcards_screen_test.dart │ ├── edit_flashcards_screen_test.dart │ ├── flashcards_list_screen_test.dart │ ├── generated_flashcards_screen_test.dart │ ├── home_screen_test.dart │ ├── multiple_choice_quiz_screen_test.dart │ ├── practice_cards_screen_test.dart │ ├── review_result_screen_test.dart │ └── writing_quiz_screen_test.dart │ └── widgets │ ├── create_flashcards_widgets │ └── forms_test.dart │ ├── flashcards_list_widgets │ ├── custom_focus_menu_holder_test.dart │ └── flashcard_widget_test.dart │ ├── generated_flashcard_widget │ └── generated_fashcard_widget_test.dart │ └── home_screen_widgets │ ├── collection_card_widget_test.dart │ └── collection_list_screen_test.dart └── tests_imports.dart /.gitignore: -------------------------------------------------------------------------------- 1 | # Miscellaneous 2 | *.class 3 | *.log 4 | *.pyc 5 | *.swp 6 | .DS_Store 7 | .atom/ 8 | .buildlog/ 9 | .history 10 | .svn/ 11 | migrate_working_dir/ 12 | 13 | # IntelliJ related 14 | *.iml 15 | *.ipr 16 | *.iws 17 | .idea/ 18 | 19 | # The .vscode folder contains launch configuration and tasks you configure in 20 | # VS Code which you may wish to be included in version control, so this line 21 | # is commented out by default. 22 | #.vscode/ 23 | 24 | # Flutter/Dart/Pub related 25 | **/doc/api/ 26 | **/ios/Flutter/.last_build_id 27 | .dart_tool/ 28 | .flutter-plugins 29 | .flutter-plugins-dependencies 30 | .packages 31 | .pub-cache/ 32 | .pub/ 33 | /build/ 34 | 35 | # Symbolication related 36 | app.*.symbols 37 | 38 | # Obfuscation related 39 | app.*.map.json 40 | 41 | # Android Studio will place build artifacts here 42 | /android/app/debug 43 | /android/app/profile 44 | /android/app/release 45 | 46 | private_key.dart 47 | *.env -------------------------------------------------------------------------------- /.metadata: -------------------------------------------------------------------------------- 1 | # This file tracks properties of this Flutter project. 2 | # Used by Flutter tool to assess capabilities and perform upgrades etc. 3 | # 4 | # This file should be version controlled. 5 | 6 | version: 7 | revision: f92f44110e87bad5ff168335c36da6f6053036e6 8 | channel: stable 9 | 10 | project_type: app 11 | 12 | # Tracks metadata for the flutter migrate command 13 | migration: 14 | platforms: 15 | - platform: root 16 | create_revision: f92f44110e87bad5ff168335c36da6f6053036e6 17 | base_revision: f92f44110e87bad5ff168335c36da6f6053036e6 18 | - platform: android 19 | create_revision: f92f44110e87bad5ff168335c36da6f6053036e6 20 | base_revision: f92f44110e87bad5ff168335c36da6f6053036e6 21 | - platform: ios 22 | create_revision: f92f44110e87bad5ff168335c36da6f6053036e6 23 | base_revision: f92f44110e87bad5ff168335c36da6f6053036e6 24 | - platform: linux 25 | create_revision: f92f44110e87bad5ff168335c36da6f6053036e6 26 | base_revision: f92f44110e87bad5ff168335c36da6f6053036e6 27 | - platform: macos 28 | create_revision: f92f44110e87bad5ff168335c36da6f6053036e6 29 | base_revision: f92f44110e87bad5ff168335c36da6f6053036e6 30 | - platform: web 31 | create_revision: f92f44110e87bad5ff168335c36da6f6053036e6 32 | base_revision: f92f44110e87bad5ff168335c36da6f6053036e6 33 | - platform: windows 34 | create_revision: f92f44110e87bad5ff168335c36da6f6053036e6 35 | base_revision: f92f44110e87bad5ff168335c36da6f6053036e6 36 | 37 | # User provided section 38 | 39 | # List of Local paths (relative to this file) that should be 40 | # ignored by the migrate tool. 41 | # 42 | # Files that are not part of the templates will be ignored by default. 43 | unmanaged_files: 44 | - 'lib/main.dart' 45 | - 'ios/Runner.xcodeproj/project.pbxproj' 46 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "cSpell.words": [ 3 | "quizwiz", 4 | "typdef" 5 | ] 6 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Dev-Salem 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## Description 2 | QuizWiz is a flashcard app that utilizes the power of AI to make flashcards from documents. 3 | 4 | ## 📌 Features 5 | - Create, edit, and combine collections (deck). 6 | - Create cards manually or by uploading pdf files/pasting materials. 7 | - Review cards based on SM-2 algorithm 8 | - Practice reviewed cards using multiple choice or by writing them manually. 9 | - Light and dark themes. 10 | 11 | ## 📸 Screenshots 12 | 13 | | ![Create Collection](https://s12.gifyu.com/images/SWyEz.gif)| ![Edit, delete, combine collections and cards](https://s11.gifyu.com/images/SWyEb.gif) | 14 | |-------|--------| 15 | |![Review cards](https://s11.gifyu.com/images/SWyEL.gif) | ![Practice using multiple choice quiz](https://s12.gifyu.com/images/SWyEs.gif) | 16 | | ![Practice by writing definitions manually](https://s12.gifyu.com/images/SWyES.gif) | ![Upload pdf then save all generated cards](https://s12.gifyu.com/images/SWyE2.gif) | 17 | |![Paste text then save individual generated cards](https://s11.gifyu.com/images/SWyEM.gif)| - | 18 | 19 | 20 | 21 | 22 | 23 | ## 📦 Packages & Technologies 24 | 25 | | Description | Package | 26 | | ---------| -------| 27 | | Architecture | Bloc Pattern| 28 | | State Management | [flutter_bloc](https://pub.dev/packages/flutter_bloc) 29 | | Dependency Injection | [get_it](https://pub.dev/packages/get_it) 30 | | Theming | [flex_color_scheme](https://pub.dev/packages/flex_color_scheme) | 31 | | Internet Connection | [connectivity_plus](https://pub.dev/packages/connectivity_plus) | 32 | | Functional Programming | [dartz](https://pub.dev/packages/dartz) | 33 | | database | [Isar](https://pub.dev/packages/isar) | 34 | 35 | 36 | ## 🩻 Project Structure 37 | 38 | ``` 39 | lib 40 | | 41 | |_ 📁src 42 | | 43 | |__ 📁core 44 | | |__ 📁errors <- define errors and exceptions 45 | | |__ 📁router <- generated router & route names 46 | | |__ 📁services <- dependency injection & internet connection 47 | | |__ 📁theme <- define themes & dynamic theming 48 | | |__ 📁utils <- constants (enums, strings, etc..) 49 | | |__ 📁widgets <- widgets that are used in multiple screens 50 | | 51 | |__ 📁features 52 | | 53 | |__ 📁cards 54 | |__ 📁controller <- Bloc 55 | |__ 📁data <- data retrieval and caching 56 | | |__ 📁models <- business logic 57 | | |__ 📁data_source <- works with db and api 58 | | |__ 📁repository <- combine and map data 59 | |__ 📁presentation <- screens and widgets 60 | ``` 61 | 62 | ## 🏃‍♂️ Install & Run The App 63 | 1. clone the project by running `git clone https://github.com/Dev-Salem/quizwiz.git` in your preferred directory 64 | 2. Run `flutter pub get` 65 | 3. Get an api key from [ChatPDF](https://www.chatpdf.com/docs/api/backend) 66 | 4. create .env file in the root directory and assign the key to `chatPdfApi` 67 | 5. Run `flutter_run` 68 | 69 | 70 | ## 💡 Contribution 71 | Feel free to add/request features by making a pull request, or by reporting bugs. 72 | 73 | ## 🗺️ Roadmap 74 | [✅] add widget tests 75 | 76 | [⏳] migrate to Go_Router 77 | 78 | [✅] use a new API (e.g ChatPDF) 79 | 80 | ## 🗞️ License 81 | MIT License 82 | 83 | -------------------------------------------------------------------------------- /analysis_options.yaml: -------------------------------------------------------------------------------- 1 | # This file configures the analyzer, which statically analyzes Dart code to 2 | # check for errors, warnings, and lints. 3 | # 4 | # The issues identified by the analyzer are surfaced in the UI of Dart-enabled 5 | # IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be 6 | # invoked from the command line by running `flutter analyze`. 7 | 8 | # The following line activates a set of recommended lints for Flutter apps, 9 | # packages, and plugins designed to encourage good coding practices. 10 | include: package:flutter_lints/flutter.yaml 11 | 12 | linter: 13 | # The lint rules applied to this project can be customized in the 14 | # section below to disable rules from the `package:flutter_lints/flutter.yaml` 15 | # included above or to enable additional rules. A list of all available lints 16 | # and their documentation is published at 17 | # https://dart-lang.github.io/linter/lints/index.html. 18 | # 19 | # Instead of disabling a lint rule for the entire project in the 20 | # section below, it can also be suppressed for a single line of code 21 | # or a specific dart file by using the `// ignore: name_of_lint` and 22 | # `// ignore_for_file: name_of_lint` syntax on the line or in the file 23 | # producing the lint. 24 | rules: 25 | # avoid_print: false # Uncomment to disable the `avoid_print` rule 26 | # prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule 27 | 28 | # Additional information about this file can be found at 29 | # https://dart.dev/guides/language/analysis-options 30 | -------------------------------------------------------------------------------- /android/.gitignore: -------------------------------------------------------------------------------- 1 | gradle-wrapper.jar 2 | /.gradle 3 | /captures/ 4 | /gradlew 5 | /gradlew.bat 6 | /local.properties 7 | GeneratedPluginRegistrant.java 8 | 9 | # Remember to never publicly share your keystore. 10 | # See https://flutter.dev/docs/deployment/android#reference-the-keystore-from-the-app 11 | key.properties 12 | **/*.keystore 13 | **/*.jks 14 | -------------------------------------------------------------------------------- /android/app/build.gradle: -------------------------------------------------------------------------------- 1 | def localProperties = new Properties() 2 | def localPropertiesFile = rootProject.file('local.properties') 3 | if (localPropertiesFile.exists()) { 4 | localPropertiesFile.withReader('UTF-8') { reader -> 5 | localProperties.load(reader) 6 | } 7 | } 8 | 9 | def flutterRoot = localProperties.getProperty('flutter.sdk') 10 | if (flutterRoot == null) { 11 | throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.") 12 | } 13 | 14 | def flutterVersionCode = localProperties.getProperty('flutter.versionCode') 15 | if (flutterVersionCode == null) { 16 | flutterVersionCode = '1' 17 | } 18 | 19 | def flutterVersionName = localProperties.getProperty('flutter.versionName') 20 | if (flutterVersionName == null) { 21 | flutterVersionName = '1.0' 22 | } 23 | 24 | apply plugin: 'com.android.application' 25 | apply plugin: 'kotlin-android' 26 | apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" 27 | 28 | android { 29 | namespace "com.example.quizwiz" 30 | compileSdkVersion flutter.compileSdkVersion 31 | ndkVersion flutter.ndkVersion 32 | 33 | compileOptions { 34 | sourceCompatibility JavaVersion.VERSION_1_8 35 | targetCompatibility JavaVersion.VERSION_1_8 36 | } 37 | 38 | kotlinOptions { 39 | jvmTarget = '1.8' 40 | } 41 | 42 | sourceSets { 43 | main.java.srcDirs += 'src/main/kotlin' 44 | } 45 | 46 | defaultConfig { 47 | // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). 48 | applicationId "com.example.quizwiz" 49 | // You can update the following values to match your application needs. 50 | // For more information, see: https://docs.flutter.dev/deployment/android#reviewing-the-gradle-build-configuration. 51 | minSdkVersion 19 52 | targetSdkVersion 33 53 | versionCode flutterVersionCode.toInteger() 54 | versionName flutterVersionName 55 | } 56 | 57 | buildTypes { 58 | release { 59 | // TODO: Add your own signing config for the release build. 60 | // Signing with the debug keys for now, so `flutter run --release` works. 61 | signingConfig signingConfigs.debug 62 | } 63 | } 64 | } 65 | 66 | flutter { 67 | source '../..' 68 | } 69 | 70 | dependencies { 71 | implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" 72 | } 73 | -------------------------------------------------------------------------------- /android/app/src/debug/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /android/app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 14 | 18 | 22 | 23 | 24 | 25 | 26 | 27 | 29 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /android/app/src/main/kotlin/com/example/quizwiz/MainActivity.kt: -------------------------------------------------------------------------------- 1 | package com.example.quizwiz 2 | 3 | import io.flutter.embedding.android.FlutterActivity 4 | 5 | class MainActivity: FlutterActivity() { 6 | } 7 | -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-v21/launch_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 12 | 13 | -------------------------------------------------------------------------------- /android/app/src/main/res/drawable/launch_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 12 | 13 | -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dev-Salem/quizwiz/f2ea0575758acdf1a092c2b20f294bb850e3e503/android/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dev-Salem/quizwiz/f2ea0575758acdf1a092c2b20f294bb850e3e503/android/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dev-Salem/quizwiz/f2ea0575758acdf1a092c2b20f294bb850e3e503/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dev-Salem/quizwiz/f2ea0575758acdf1a092c2b20f294bb850e3e503/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dev-Salem/quizwiz/f2ea0575758acdf1a092c2b20f294bb850e3e503/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/values-night/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 15 | 18 | 19 | -------------------------------------------------------------------------------- /android/app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 15 | 18 | 19 | -------------------------------------------------------------------------------- /android/app/src/profile/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /android/build.gradle: -------------------------------------------------------------------------------- 1 | buildscript { 2 | ext.kotlin_version = '1.7.10' 3 | repositories { 4 | google() 5 | mavenCentral() 6 | } 7 | 8 | dependencies { 9 | classpath 'com.android.tools.build:gradle:7.3.0' 10 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" 11 | } 12 | } 13 | 14 | allprojects { 15 | repositories { 16 | google() 17 | mavenCentral() 18 | } 19 | } 20 | 21 | rootProject.buildDir = '../build' 22 | subprojects { 23 | project.buildDir = "${rootProject.buildDir}/${project.name}" 24 | } 25 | subprojects { 26 | project.evaluationDependsOn(':app') 27 | } 28 | 29 | tasks.register("clean", Delete) { 30 | delete rootProject.buildDir 31 | } 32 | -------------------------------------------------------------------------------- /android/build/ios/Pods.build/Release-iphonesimulator/DKImagePickerController-DKImagePickerController.build/dgph: -------------------------------------------------------------------------------- 1 | DGPH1.04 Mar 24 202300:10:16/UsersdevsalemDesktopflutter_projectsquizwiziosPods -------------------------------------------------------------------------------- /android/build/ios/Pods.build/Release-iphonesimulator/DKImagePickerController.build/dgph: -------------------------------------------------------------------------------- 1 | DGPH1.04 Mar 24 202300:10:16/UsersdevsalemDesktopflutter_projectsquizwiziosPods -------------------------------------------------------------------------------- /android/build/ios/Pods.build/Release-iphonesimulator/DKPhotoGallery-DKPhotoGallery.build/dgph: -------------------------------------------------------------------------------- 1 | DGPH1.04 Mar 24 202300:10:16/UsersdevsalemDesktopflutter_projectsquizwiziosPods -------------------------------------------------------------------------------- /android/build/ios/Pods.build/Release-iphonesimulator/DKPhotoGallery.build/dgph: -------------------------------------------------------------------------------- 1 | DGPH1.04 Mar 24 202300:10:16/UsersdevsalemDesktopflutter_projectsquizwiziosPods -------------------------------------------------------------------------------- /android/build/ios/Pods.build/Release-iphonesimulator/Flutter.build/dgph: -------------------------------------------------------------------------------- 1 | DGPH1.04 Mar 24 202300:10:16/UsersdevsalemDesktopflutter_projectsquizwiziosPods -------------------------------------------------------------------------------- /android/build/ios/Pods.build/Release-iphonesimulator/Pods-Runner.build/dgph: -------------------------------------------------------------------------------- 1 | DGPH1.04 Mar 24 202300:10:16/UsersdevsalemDesktopflutter_projectsquizwiziosPods -------------------------------------------------------------------------------- /android/build/ios/Pods.build/Release-iphonesimulator/Pods-RunnerTests.build/dgph: -------------------------------------------------------------------------------- 1 | DGPH1.04 Mar 24 202300:10:16/UsersdevsalemDesktopflutter_projectsquizwiziosPods -------------------------------------------------------------------------------- /android/build/ios/Pods.build/Release-iphonesimulator/ReachabilitySwift.build/dgph: -------------------------------------------------------------------------------- 1 | DGPH1.04 Mar 24 202300:10:16/UsersdevsalemDesktopflutter_projectsquizwiziosPods -------------------------------------------------------------------------------- /android/build/ios/Pods.build/Release-iphonesimulator/SDWebImage.build/dgph: -------------------------------------------------------------------------------- 1 | DGPH1.04 Mar 24 202300:10:16/UsersdevsalemDesktopflutter_projectsquizwiziosPods -------------------------------------------------------------------------------- /android/build/ios/Pods.build/Release-iphonesimulator/SwiftyGif.build/dgph: -------------------------------------------------------------------------------- 1 | DGPH1.04 Mar 24 202300:10:16/UsersdevsalemDesktopflutter_projectsquizwiziosPods -------------------------------------------------------------------------------- /android/build/ios/Pods.build/Release-iphonesimulator/connectivity_plus.build/dgph: -------------------------------------------------------------------------------- 1 | DGPH1.04 Mar 24 202300:10:16/UsersdevsalemDesktopflutter_projectsquizwiziosPods -------------------------------------------------------------------------------- /android/build/ios/Pods.build/Release-iphonesimulator/file_picker.build/dgph: -------------------------------------------------------------------------------- 1 | DGPH1.04 Mar 24 202300:10:16/UsersdevsalemDesktopflutter_projectsquizwiziosPods -------------------------------------------------------------------------------- /android/build/ios/Pods.build/Release-iphonesimulator/isar_flutter_libs.build/dgph: -------------------------------------------------------------------------------- 1 | DGPH1.04 Mar 24 202300:10:16/UsersdevsalemDesktopflutter_projectsquizwiziosPods -------------------------------------------------------------------------------- /android/build/ios/Pods.build/Release-iphonesimulator/path_provider_foundation.build/dgph: -------------------------------------------------------------------------------- 1 | DGPH1.04 Mar 24 202300:10:16/UsersdevsalemDesktopflutter_projectsquizwiziosPods -------------------------------------------------------------------------------- /android/build/ios/Pods.build/Release-iphonesimulator/pdf_text.build/dgph: -------------------------------------------------------------------------------- 1 | DGPH1.04 Mar 24 202300:10:16/UsersdevsalemDesktopflutter_projectsquizwiziosPods -------------------------------------------------------------------------------- /android/gradle.properties: -------------------------------------------------------------------------------- 1 | org.gradle.jvmargs=-Xmx1536M 2 | android.useAndroidX=true 3 | android.enableJetifier=true 4 | -------------------------------------------------------------------------------- /android/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | zipStoreBase=GRADLE_USER_HOME 4 | zipStorePath=wrapper/dists 5 | distributionUrl=https\://services.gradle.org/distributions/gradle-7.5-all.zip 6 | -------------------------------------------------------------------------------- /android/settings.gradle: -------------------------------------------------------------------------------- 1 | include ':app' 2 | 3 | def localPropertiesFile = new File(rootProject.projectDir, "local.properties") 4 | def properties = new Properties() 5 | 6 | assert localPropertiesFile.exists() 7 | localPropertiesFile.withReader("UTF-8") { reader -> properties.load(reader) } 8 | 9 | def flutterSdkPath = properties.getProperty("flutter.sdk") 10 | assert flutterSdkPath != null, "flutter.sdk not set in local.properties" 11 | apply from: "$flutterSdkPath/packages/flutter_tools/gradle/app_plugin_loader.gradle" 12 | -------------------------------------------------------------------------------- /assets/icon.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dev-Salem/quizwiz/f2ea0575758acdf1a092c2b20f294bb850e3e503/assets/icon.jpeg -------------------------------------------------------------------------------- /ios/.gitignore: -------------------------------------------------------------------------------- 1 | **/dgph 2 | *.mode1v3 3 | *.mode2v3 4 | *.moved-aside 5 | *.pbxuser 6 | *.perspectivev3 7 | **/*sync/ 8 | .sconsign.dblite 9 | .tags* 10 | **/.vagrant/ 11 | **/DerivedData/ 12 | Icon? 13 | **/Pods/ 14 | **/.symlinks/ 15 | profile 16 | xcuserdata 17 | **/.generated/ 18 | Flutter/App.framework 19 | Flutter/Flutter.framework 20 | Flutter/Flutter.podspec 21 | Flutter/Generated.xcconfig 22 | Flutter/ephemeral/ 23 | Flutter/app.flx 24 | Flutter/app.zip 25 | Flutter/flutter_assets/ 26 | Flutter/flutter_export_environment.sh 27 | ServiceDefinitions.json 28 | Runner/GeneratedPluginRegistrant.* 29 | 30 | # Exceptions to above rules. 31 | !default.mode1v3 32 | !default.mode2v3 33 | !default.pbxuser 34 | !default.perspectivev3 35 | -------------------------------------------------------------------------------- /ios/Flutter/AppFrameworkInfo.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | App 9 | CFBundleIdentifier 10 | io.flutter.flutter.app 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | App 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1.0 23 | MinimumOSVersion 24 | 11.0 25 | 26 | 27 | -------------------------------------------------------------------------------- /ios/Flutter/Debug.xcconfig: -------------------------------------------------------------------------------- 1 | #include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" 2 | #include "Generated.xcconfig" 3 | -------------------------------------------------------------------------------- /ios/Flutter/Release.xcconfig: -------------------------------------------------------------------------------- 1 | #include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" 2 | #include "Generated.xcconfig" 3 | -------------------------------------------------------------------------------- /ios/Podfile: -------------------------------------------------------------------------------- 1 | # Uncomment this line to define a global platform for your project 2 | # platform :ios, '11.0' 3 | 4 | # CocoaPods analytics sends network stats synchronously affecting flutter build latency. 5 | ENV['COCOAPODS_DISABLE_STATS'] = 'true' 6 | 7 | project 'Runner', { 8 | 'Debug' => :debug, 9 | 'Profile' => :release, 10 | 'Release' => :release, 11 | } 12 | 13 | def flutter_root 14 | generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'Generated.xcconfig'), __FILE__) 15 | unless File.exist?(generated_xcode_build_settings_path) 16 | raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure flutter pub get is executed first" 17 | end 18 | 19 | File.foreach(generated_xcode_build_settings_path) do |line| 20 | matches = line.match(/FLUTTER_ROOT\=(.*)/) 21 | return matches[1].strip if matches 22 | end 23 | raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Generated.xcconfig, then run flutter pub get" 24 | end 25 | 26 | require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root) 27 | 28 | flutter_ios_podfile_setup 29 | 30 | target 'Runner' do 31 | use_frameworks! 32 | use_modular_headers! 33 | 34 | flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__)) 35 | target 'RunnerTests' do 36 | inherit! :search_paths 37 | end 38 | end 39 | 40 | post_install do |installer| 41 | installer.pods_project.targets.each do |target| 42 | flutter_additional_ios_build_settings(target) 43 | end 44 | end 45 | -------------------------------------------------------------------------------- /ios/Podfile.lock: -------------------------------------------------------------------------------- 1 | PODS: 2 | - connectivity_plus (0.0.1): 3 | - Flutter 4 | - ReachabilitySwift 5 | - DKImagePickerController/Core (4.3.4): 6 | - DKImagePickerController/ImageDataManager 7 | - DKImagePickerController/Resource 8 | - DKImagePickerController/ImageDataManager (4.3.4) 9 | - DKImagePickerController/PhotoGallery (4.3.4): 10 | - DKImagePickerController/Core 11 | - DKPhotoGallery 12 | - DKImagePickerController/Resource (4.3.4) 13 | - DKPhotoGallery (0.0.17): 14 | - DKPhotoGallery/Core (= 0.0.17) 15 | - DKPhotoGallery/Model (= 0.0.17) 16 | - DKPhotoGallery/Preview (= 0.0.17) 17 | - DKPhotoGallery/Resource (= 0.0.17) 18 | - SDWebImage 19 | - SwiftyGif 20 | - DKPhotoGallery/Core (0.0.17): 21 | - DKPhotoGallery/Model 22 | - DKPhotoGallery/Preview 23 | - SDWebImage 24 | - SwiftyGif 25 | - DKPhotoGallery/Model (0.0.17): 26 | - SDWebImage 27 | - SwiftyGif 28 | - DKPhotoGallery/Preview (0.0.17): 29 | - DKPhotoGallery/Model 30 | - DKPhotoGallery/Resource 31 | - SDWebImage 32 | - SwiftyGif 33 | - DKPhotoGallery/Resource (0.0.17): 34 | - SDWebImage 35 | - SwiftyGif 36 | - file_picker (0.0.1): 37 | - DKImagePickerController/PhotoGallery 38 | - Flutter 39 | - Flutter (1.0.0) 40 | - isar_flutter_libs (1.0.0): 41 | - Flutter 42 | - path_provider_foundation (0.0.1): 43 | - Flutter 44 | - FlutterMacOS 45 | - ReachabilitySwift (5.0.0) 46 | - read_pdf_text (0.0.1): 47 | - Flutter 48 | - SDWebImage (5.16.0): 49 | - SDWebImage/Core (= 5.16.0) 50 | - SDWebImage/Core (5.16.0) 51 | - SwiftyGif (5.4.4) 52 | 53 | DEPENDENCIES: 54 | - connectivity_plus (from `.symlinks/plugins/connectivity_plus/ios`) 55 | - file_picker (from `.symlinks/plugins/file_picker/ios`) 56 | - Flutter (from `Flutter`) 57 | - isar_flutter_libs (from `.symlinks/plugins/isar_flutter_libs/ios`) 58 | - path_provider_foundation (from `.symlinks/plugins/path_provider_foundation/darwin`) 59 | - read_pdf_text (from `.symlinks/plugins/read_pdf_text/ios`) 60 | 61 | SPEC REPOS: 62 | trunk: 63 | - DKImagePickerController 64 | - DKPhotoGallery 65 | - ReachabilitySwift 66 | - SDWebImage 67 | - SwiftyGif 68 | 69 | EXTERNAL SOURCES: 70 | connectivity_plus: 71 | :path: ".symlinks/plugins/connectivity_plus/ios" 72 | file_picker: 73 | :path: ".symlinks/plugins/file_picker/ios" 74 | Flutter: 75 | :path: Flutter 76 | isar_flutter_libs: 77 | :path: ".symlinks/plugins/isar_flutter_libs/ios" 78 | path_provider_foundation: 79 | :path: ".symlinks/plugins/path_provider_foundation/darwin" 80 | read_pdf_text: 81 | :path: ".symlinks/plugins/read_pdf_text/ios" 82 | 83 | SPEC CHECKSUMS: 84 | connectivity_plus: 07c49e96d7fc92bc9920617b83238c4d178b446a 85 | DKImagePickerController: b512c28220a2b8ac7419f21c491fc8534b7601ac 86 | DKPhotoGallery: fdfad5125a9fdda9cc57df834d49df790dbb4179 87 | file_picker: ce3938a0df3cc1ef404671531facef740d03f920 88 | Flutter: f04841e97a9d0b0a8025694d0796dd46242b2854 89 | isar_flutter_libs: b69f437aeab9c521821c3f376198c4371fa21073 90 | path_provider_foundation: eaf5b3e458fc0e5fbb9940fb09980e853fe058b8 91 | ReachabilitySwift: 985039c6f7b23a1da463388634119492ff86c825 92 | read_pdf_text: e29ee5e80e1dad2f855cdbfc0cbb2097a1cae569 93 | SDWebImage: 2aea163b50bfcb569a2726b6a754c54a4506fcf6 94 | SwiftyGif: 93a1cc87bf3a51916001cf8f3d63835fb64c819f 95 | 96 | PODFILE CHECKSUM: 70d9d25280d0dd177a5f637cdb0f0b0b12c6a189 97 | 98 | COCOAPODS: 1.12.1 99 | -------------------------------------------------------------------------------- /ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PreviewsEnabled 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 37 | 38 | 39 | 40 | 43 | 49 | 50 | 51 | 52 | 53 | 63 | 65 | 71 | 72 | 73 | 74 | 80 | 82 | 88 | 89 | 90 | 91 | 93 | 94 | 97 | 98 | 99 | -------------------------------------------------------------------------------- /ios/Runner.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PreviewsEnabled 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /ios/Runner/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | import Flutter 3 | 4 | @UIApplicationMain 5 | @objc class AppDelegate: FlutterAppDelegate { 6 | override func application( 7 | _ application: UIApplication, 8 | didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? 9 | ) -> Bool { 10 | GeneratedPluginRegistrant.register(with: self) 11 | return super.application(application, didFinishLaunchingWithOptions: launchOptions) 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "size" : "20x20", 5 | "idiom" : "iphone", 6 | "filename" : "Icon-App-20x20@2x.png", 7 | "scale" : "2x" 8 | }, 9 | { 10 | "size" : "20x20", 11 | "idiom" : "iphone", 12 | "filename" : "Icon-App-20x20@3x.png", 13 | "scale" : "3x" 14 | }, 15 | { 16 | "size" : "29x29", 17 | "idiom" : "iphone", 18 | "filename" : "Icon-App-29x29@1x.png", 19 | "scale" : "1x" 20 | }, 21 | { 22 | "size" : "29x29", 23 | "idiom" : "iphone", 24 | "filename" : "Icon-App-29x29@2x.png", 25 | "scale" : "2x" 26 | }, 27 | { 28 | "size" : "29x29", 29 | "idiom" : "iphone", 30 | "filename" : "Icon-App-29x29@3x.png", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "size" : "40x40", 35 | "idiom" : "iphone", 36 | "filename" : "Icon-App-40x40@2x.png", 37 | "scale" : "2x" 38 | }, 39 | { 40 | "size" : "40x40", 41 | "idiom" : "iphone", 42 | "filename" : "Icon-App-40x40@3x.png", 43 | "scale" : "3x" 44 | }, 45 | { 46 | "size" : "60x60", 47 | "idiom" : "iphone", 48 | "filename" : "Icon-App-60x60@2x.png", 49 | "scale" : "2x" 50 | }, 51 | { 52 | "size" : "60x60", 53 | "idiom" : "iphone", 54 | "filename" : "Icon-App-60x60@3x.png", 55 | "scale" : "3x" 56 | }, 57 | { 58 | "size" : "20x20", 59 | "idiom" : "ipad", 60 | "filename" : "Icon-App-20x20@1x.png", 61 | "scale" : "1x" 62 | }, 63 | { 64 | "size" : "20x20", 65 | "idiom" : "ipad", 66 | "filename" : "Icon-App-20x20@2x.png", 67 | "scale" : "2x" 68 | }, 69 | { 70 | "size" : "29x29", 71 | "idiom" : "ipad", 72 | "filename" : "Icon-App-29x29@1x.png", 73 | "scale" : "1x" 74 | }, 75 | { 76 | "size" : "29x29", 77 | "idiom" : "ipad", 78 | "filename" : "Icon-App-29x29@2x.png", 79 | "scale" : "2x" 80 | }, 81 | { 82 | "size" : "40x40", 83 | "idiom" : "ipad", 84 | "filename" : "Icon-App-40x40@1x.png", 85 | "scale" : "1x" 86 | }, 87 | { 88 | "size" : "40x40", 89 | "idiom" : "ipad", 90 | "filename" : "Icon-App-40x40@2x.png", 91 | "scale" : "2x" 92 | }, 93 | { 94 | "size" : "76x76", 95 | "idiom" : "ipad", 96 | "filename" : "Icon-App-76x76@1x.png", 97 | "scale" : "1x" 98 | }, 99 | { 100 | "size" : "76x76", 101 | "idiom" : "ipad", 102 | "filename" : "Icon-App-76x76@2x.png", 103 | "scale" : "2x" 104 | }, 105 | { 106 | "size" : "83.5x83.5", 107 | "idiom" : "ipad", 108 | "filename" : "Icon-App-83.5x83.5@2x.png", 109 | "scale" : "2x" 110 | }, 111 | { 112 | "size" : "1024x1024", 113 | "idiom" : "ios-marketing", 114 | "filename" : "Icon-App-1024x1024@1x.png", 115 | "scale" : "1x" 116 | } 117 | ], 118 | "info" : { 119 | "version" : 1, 120 | "author" : "xcode" 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dev-Salem/quizwiz/f2ea0575758acdf1a092c2b20f294bb850e3e503/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dev-Salem/quizwiz/f2ea0575758acdf1a092c2b20f294bb850e3e503/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dev-Salem/quizwiz/f2ea0575758acdf1a092c2b20f294bb850e3e503/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dev-Salem/quizwiz/f2ea0575758acdf1a092c2b20f294bb850e3e503/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dev-Salem/quizwiz/f2ea0575758acdf1a092c2b20f294bb850e3e503/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dev-Salem/quizwiz/f2ea0575758acdf1a092c2b20f294bb850e3e503/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dev-Salem/quizwiz/f2ea0575758acdf1a092c2b20f294bb850e3e503/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dev-Salem/quizwiz/f2ea0575758acdf1a092c2b20f294bb850e3e503/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dev-Salem/quizwiz/f2ea0575758acdf1a092c2b20f294bb850e3e503/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dev-Salem/quizwiz/f2ea0575758acdf1a092c2b20f294bb850e3e503/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-50x50@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dev-Salem/quizwiz/f2ea0575758acdf1a092c2b20f294bb850e3e503/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-50x50@1x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-50x50@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dev-Salem/quizwiz/f2ea0575758acdf1a092c2b20f294bb850e3e503/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-50x50@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dev-Salem/quizwiz/f2ea0575758acdf1a092c2b20f294bb850e3e503/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@1x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dev-Salem/quizwiz/f2ea0575758acdf1a092c2b20f294bb850e3e503/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dev-Salem/quizwiz/f2ea0575758acdf1a092c2b20f294bb850e3e503/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dev-Salem/quizwiz/f2ea0575758acdf1a092c2b20f294bb850e3e503/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-72x72@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dev-Salem/quizwiz/f2ea0575758acdf1a092c2b20f294bb850e3e503/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-72x72@1x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-72x72@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dev-Salem/quizwiz/f2ea0575758acdf1a092c2b20f294bb850e3e503/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-72x72@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dev-Salem/quizwiz/f2ea0575758acdf1a092c2b20f294bb850e3e503/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dev-Salem/quizwiz/f2ea0575758acdf1a092c2b20f294bb850e3e503/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dev-Salem/quizwiz/f2ea0575758acdf1a092c2b20f294bb850e3e503/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "LaunchImage.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "LaunchImage@2x.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "filename" : "LaunchImage@3x.png", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dev-Salem/quizwiz/f2ea0575758acdf1a092c2b20f294bb850e3e503/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dev-Salem/quizwiz/f2ea0575758acdf1a092c2b20f294bb850e3e503/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dev-Salem/quizwiz/f2ea0575758acdf1a092c2b20f294bb850e3e503/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md: -------------------------------------------------------------------------------- 1 | # Launch Screen Assets 2 | 3 | You can customize the launch screen with your own desired assets by replacing the image files in this directory. 4 | 5 | You can also do it by opening your Flutter project's Xcode project with `open ios/Runner.xcworkspace`, selecting `Runner/Assets.xcassets` in the Project Navigator and dropping in the desired images. -------------------------------------------------------------------------------- /ios/Runner/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /ios/Runner/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /ios/Runner/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleDisplayName 8 | Quizwiz 9 | CFBundleExecutable 10 | $(EXECUTABLE_NAME) 11 | CFBundleIdentifier 12 | $(PRODUCT_BUNDLE_IDENTIFIER) 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | quizwiz 17 | CFBundlePackageType 18 | APPL 19 | CFBundleShortVersionString 20 | $(FLUTTER_BUILD_NAME) 21 | CFBundleSignature 22 | ???? 23 | CFBundleVersion 24 | $(FLUTTER_BUILD_NUMBER) 25 | LSRequiresIPhoneOS 26 | 27 | UILaunchStoryboardName 28 | LaunchScreen 29 | UIMainStoryboardFile 30 | Main 31 | UISupportedInterfaceOrientations 32 | 33 | UIInterfaceOrientationPortrait 34 | UIInterfaceOrientationLandscapeLeft 35 | UIInterfaceOrientationLandscapeRight 36 | 37 | UISupportedInterfaceOrientations~ipad 38 | 39 | UIInterfaceOrientationPortrait 40 | UIInterfaceOrientationPortraitUpsideDown 41 | UIInterfaceOrientationLandscapeLeft 42 | UIInterfaceOrientationLandscapeRight 43 | 44 | UIViewControllerBasedStatusBarAppearance 45 | 46 | CADisableMinimumFrameDurationOnPhone 47 | 48 | UIApplicationSupportsIndirectInputEvents 49 | 50 | 51 | 52 | -------------------------------------------------------------------------------- /ios/Runner/Runner-Bridging-Header.h: -------------------------------------------------------------------------------- 1 | #import "GeneratedPluginRegistrant.h" 2 | -------------------------------------------------------------------------------- /ios/RunnerTests/RunnerTests.swift: -------------------------------------------------------------------------------- 1 | import Flutter 2 | import UIKit 3 | import XCTest 4 | 5 | class RunnerTests: XCTestCase { 6 | 7 | func testExample() { 8 | // If you add code to the Runner application, consider adding tests here. 9 | // See https://developer.apple.com/documentation/xctest for more information about using XCTest. 10 | } 11 | 12 | } 13 | -------------------------------------------------------------------------------- /lib/main.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:quizwiz/src/app.dart'; 3 | import 'package:quizwiz/src/core/core.dart'; 4 | import 'package:path_provider/path_provider.dart'; 5 | import 'package:quizwiz/src/features/cards/controller/controller.dart'; 6 | import 'src/features/cards/data/data.dart'; 7 | import 'package:flutter_dotenv/flutter_dotenv.dart'; 8 | 9 | Future main() async { 10 | await _initialize(); 11 | runApp(QuizWizApp( 12 | cardsBloc: sl()..add(const GetCollectionsEvent()), 13 | themeCubit: sl(), 14 | )); 15 | } 16 | 17 | Future _openIsarBox() async { 18 | final dir = await getApplicationDocumentsDirectory(); 19 | await Isar.open([FlashcardCollectionSchema], directory: dir.path); 20 | } 21 | 22 | Future _initialize() async { 23 | await dotenv.load(fileName: ".env"); 24 | ServiceLocator().init(); 25 | WidgetsFlutterBinding.ensureInitialized(); 26 | await _openIsarBox(); 27 | } 28 | -------------------------------------------------------------------------------- /lib/src/app.dart: -------------------------------------------------------------------------------- 1 | // ignore_for_file: public_member_api_docs, sort_constructors_first 2 | import 'package:quizwiz/src/core/core.dart'; 3 | import 'package:quizwiz/src/features/cards/presentation/presentation.dart'; 4 | 5 | import 'features/cards/controller/controller.dart'; 6 | 7 | class QuizWizApp extends StatelessWidget { 8 | final CardsBloc cardsBloc; 9 | final ThemeCubit themeCubit; 10 | const QuizWizApp({ 11 | Key? key, 12 | required this.cardsBloc, 13 | required this.themeCubit, 14 | }) : super(key: key); 15 | 16 | @override 17 | Widget build(BuildContext context) { 18 | return MultiBlocProvider( 19 | providers: [ 20 | BlocProvider( 21 | create: (_) => cardsBloc, 22 | ), 23 | BlocProvider(create: (_) => themeCubit) 24 | ], 25 | child: Builder(builder: (context) { 26 | return MaterialApp( 27 | debugShowCheckedModeBanner: false, 28 | themeMode: context.watch().state, 29 | onGenerateRoute: (settings) => 30 | RouteGenerator.generateRoute(settings), 31 | theme: AppTheme.lightTheme(), 32 | darkTheme: AppTheme.darkTheme(), 33 | home: const HomeScreen()); 34 | }), 35 | ); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /lib/src/core/core.dart: -------------------------------------------------------------------------------- 1 | export 'package:quizwiz/src/core/theme/cubit/theme_cubit.dart'; 2 | export 'package:quizwiz/src/core/services/service_locator.dart'; 3 | export 'package:quizwiz/src/core/router/routes.dart'; 4 | export 'package:quizwiz/src/core/router/route_generator.dart'; 5 | export 'package:quizwiz/src/core/errors/failure.dart'; 6 | export 'package:quizwiz/src/core/theme/app_theme.dart'; 7 | export 'package:quizwiz/src/core/utils/enums.dart'; 8 | export 'package:quizwiz/src/core/utils/strings.dart'; 9 | export 'package:quizwiz/src/core/utils/typdef.dart'; 10 | export 'package:quizwiz/src/core/widgets/custom_snackbar.dart'; 11 | export 'package:quizwiz/src/core/widgets/error_widget.dart'; 12 | export 'package:quizwiz/src/core/widgets/loading_widget.dart'; 13 | export 'package:quizwiz/src/core/widgets/no_result_screen.dart'; 14 | export 'package:quizwiz/src/core/errors/exceptions.dart'; 15 | export 'package:quizwiz/src/core/services/internet_connection.dart'; 16 | export 'package:quizwiz/src/core/utils/network_constants.dart'; 17 | -------------------------------------------------------------------------------- /lib/src/core/errors/exceptions.dart: -------------------------------------------------------------------------------- 1 | // ignore_for_file: public_member_api_docs, sort_constructors_first 2 | class LocalStorageException implements Exception { 3 | final String message; 4 | 5 | const LocalStorageException({ 6 | required this.message, 7 | }); 8 | 9 | @override 10 | String toString() => message; 11 | } 12 | 13 | class NetworkException implements Exception { 14 | final String message; 15 | const NetworkException(this.message); 16 | 17 | @override 18 | String toString() => message; 19 | } 20 | 21 | class JsonDeserializationException implements Exception { 22 | final String message; 23 | const JsonDeserializationException(this.message); 24 | @override 25 | String toString() => message; 26 | } 27 | 28 | class UnexpectedNetworkException implements Exception { 29 | final String message; 30 | const UnexpectedNetworkException(this.message); 31 | @override 32 | String toString() => message; 33 | } 34 | -------------------------------------------------------------------------------- /lib/src/core/errors/failure.dart: -------------------------------------------------------------------------------- 1 | // ignore_for_file: public_member_api_docs, sort_constructors_first 2 | import 'package:equatable/equatable.dart'; 3 | 4 | class Failure extends Equatable { 5 | final String message; 6 | const Failure({ 7 | required this.message, 8 | }); 9 | @override 10 | List get props => [message]; 11 | } 12 | 13 | class LocalStorageFailure extends Failure { 14 | const LocalStorageFailure({required super.message}); 15 | } 16 | 17 | class NetworkFailure extends Failure { 18 | const NetworkFailure({required super.message}); 19 | } 20 | -------------------------------------------------------------------------------- /lib/src/core/router/route_generator.dart: -------------------------------------------------------------------------------- 1 | import 'package:quizwiz/src/core/router/routes.dart'; 2 | import 'package:quizwiz/src/core/widgets/error_widget.dart'; 3 | import 'package:quizwiz/src/features/cards/data/data.dart'; 4 | import 'package:quizwiz/src/features/cards/presentation/presentation.dart'; 5 | import 'package:quizwiz/src/features/cards/presentation/screens/writing_quiz_screen.dart'; 6 | 7 | class RouteGenerator { 8 | static Route generateRoute(RouteSettings settings) { 9 | switch (settings.name) { 10 | case '/': 11 | return MaterialPageRoute(builder: (context) => const HomeScreen()); 12 | case Routes.createFlashcards: 13 | var uuid = settings.arguments as String; 14 | return MaterialPageRoute( 15 | builder: (context) => CreateFlashcardsScreen( 16 | collectionUuid: uuid, 17 | )); 18 | case Routes.flashcardsList: 19 | var collectionUuid = settings.arguments as String; 20 | return MaterialPageRoute( 21 | builder: (context) => 22 | FlashcardsListScreen(collectionUuid: collectionUuid), 23 | ); 24 | case Routes.practiceCards: 25 | var collection = settings.arguments as FlashcardCollection; 26 | return MaterialPageRoute( 27 | builder: (context) => PracticeCardsScreen( 28 | collection: collection, 29 | ), 30 | ); 31 | case Routes.reviewResult: 32 | var cardAndCollection = settings.arguments as ( 33 | Flashcard card, 34 | FlashcardCollection collection 35 | ); 36 | return MaterialPageRoute( 37 | builder: (context) => ReviewResultScreen( 38 | cardAndCollection: cardAndCollection, 39 | ), 40 | ); 41 | case Routes.editFlashcard: 42 | var parameters = settings.arguments as EditFlashcardParameters; 43 | return MaterialPageRoute( 44 | builder: (context) => EditFlashcardScreen( 45 | parameters: parameters, 46 | )); 47 | case Routes.quiz: 48 | return MaterialPageRoute( 49 | builder: (context) => const MultipleChoiceQuizScreen()); 50 | case Routes.generatedFlashcards: 51 | var collectionUuid = settings.arguments as String; 52 | return MaterialPageRoute( 53 | builder: (context) => 54 | GeneratedFlashcardsScreen(collectionUuid: collectionUuid)); 55 | case Routes.writingQuiz: 56 | var flashcards = settings.arguments as List; 57 | return MaterialPageRoute( 58 | builder: (context) => WritingQuizScreen(flashcards: flashcards), 59 | ); 60 | default: 61 | return MaterialPageRoute( 62 | builder: (context) => const CustomErrorWidget(), 63 | ); 64 | } 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /lib/src/core/router/routes.dart: -------------------------------------------------------------------------------- 1 | class Routes { 2 | static const createFlashcards = '/create_flashcards'; 3 | static const flashcardsList = '/flashcards_list'; 4 | static const practiceCards = '/practice_flashcards'; 5 | static const reviewResult = '/review_result'; 6 | static const editFlashcard = '/edit_flashcard'; 7 | static const generateFlashcards = '/generate_flashcards'; 8 | static const quiz = '/multiple_choice_quiz'; 9 | static const generatedFlashcards = '/generated_flashcards'; 10 | static const writingQuiz = '/writing_quiz'; 11 | } 12 | -------------------------------------------------------------------------------- /lib/src/core/services/internet_connection.dart: -------------------------------------------------------------------------------- 1 | import 'package:connectivity_plus/connectivity_plus.dart'; 2 | 3 | class InternetConnectivity { 4 | /// Check internet connection before making an api call to avoid 5 | /// throwing [SocketException] (can't catch the exception) 6 | static Future isConnected() async { 7 | final connectivityResult = await Connectivity().checkConnectivity(); 8 | return connectivityResult == ConnectivityResult.mobile || 9 | connectivityResult == ConnectivityResult.wifi || 10 | connectivityResult == ConnectivityResult.vpn; 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /lib/src/core/services/service_locator.dart: -------------------------------------------------------------------------------- 1 | import 'package:get_it/get_it.dart'; 2 | import 'package:quizwiz/src/core/core.dart'; 3 | import 'package:quizwiz/src/features/cards/controller/controller.dart'; 4 | import 'package:quizwiz/src/features/cards/data/data.dart'; 5 | 6 | final sl = GetIt.instance; 7 | 8 | class ServiceLocator { 9 | void init() { 10 | //data sources 11 | sl.registerLazySingleton(() => CollectionLocalDataSource()); 12 | sl.registerLazySingleton(() => FlashcardLocalDataSource()); 13 | sl.registerLazySingleton(() => BaseRemoteDataSource()); 14 | sl.registerLazySingleton(() => Isar.getInstance()!); 15 | //repository 16 | sl.registerLazySingleton(() => BaseCardsRepository(sl(), sl(), sl())); 17 | //bloc 18 | sl.registerFactory(() => CardsBloc(sl(), sl())); 19 | sl.registerFactory(() => ThemeCubit()); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /lib/src/core/theme/app_theme.dart: -------------------------------------------------------------------------------- 1 | import 'package:flex_color_scheme/flex_color_scheme.dart'; 2 | import 'package:flutter/material.dart'; 3 | 4 | class AppTheme { 5 | static ThemeData lightTheme() => FlexThemeData.light( 6 | scheme: FlexScheme.materialBaseline, 7 | surfaceMode: FlexSurfaceMode.levelSurfacesLowScaffold, 8 | blendLevel: 7, 9 | subThemesData: const FlexSubThemesData( 10 | blendOnLevel: 10, 11 | blendOnColors: false, 12 | useM2StyleDividerInM3: true, 13 | ), 14 | visualDensity: FlexColorScheme.comfortablePlatformDensity, 15 | useMaterial3: true, 16 | swapLegacyOnMaterial3: true, 17 | ).copyWith(appBarTheme: const AppBarTheme(centerTitle: true)); 18 | 19 | static ThemeData darkTheme() => FlexThemeData.dark( 20 | scheme: FlexScheme.materialBaseline, 21 | surfaceMode: FlexSurfaceMode.levelSurfacesLowScaffold, 22 | blendLevel: 13, 23 | subThemesData: const FlexSubThemesData( 24 | blendOnLevel: 20, 25 | useM2StyleDividerInM3: true, 26 | ), 27 | visualDensity: FlexColorScheme.comfortablePlatformDensity, 28 | useMaterial3: true, 29 | swapLegacyOnMaterial3: true, 30 | ).copyWith(appBarTheme: const AppBarTheme(centerTitle: true)); 31 | } 32 | -------------------------------------------------------------------------------- /lib/src/core/theme/cubit/theme_cubit.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter_bloc/flutter_bloc.dart'; 2 | import 'package:quizwiz/src/features/cards/presentation/presentation.dart'; 3 | 4 | class ThemeCubit extends Cubit { 5 | ThemeCubit() : super(ThemeMode.dark); 6 | void toggleTheme() { 7 | return switch (state) { 8 | ThemeMode.dark => emit(ThemeMode.light), 9 | ThemeMode.light => emit(ThemeMode.dark), 10 | _ => emit(ThemeMode.dark) 11 | }; 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /lib/src/core/utils/enums.dart: -------------------------------------------------------------------------------- 1 | enum ReviewResult { again, hard, good, easy } 2 | 3 | enum RequestState { loading, error, success } 4 | -------------------------------------------------------------------------------- /lib/src/core/utils/network_constants.dart: -------------------------------------------------------------------------------- 1 | class NetworkConstants { 2 | static const apiKeyName = "chatPdfApi"; 3 | static const addFileEndPoint = "https://api.chatpdf.com/v1/sources/add-file"; 4 | static const chatEndPoint = "https://api.chatpdf.com/v1/chats/message"; 5 | static const deleteFileEndPoint = 'https://api.chatpdf.com/v1/sources/delete'; 6 | static const String noConnectionErrorMessage = "No Internet connection"; 7 | static const String invalidNetworkErrorMessage = 8 | "Invalid API response, try again"; 9 | static const String generateFlashcardsPrompt = """ 10 | pretend to be an expert in summarizing studying material. 11 | create a valid JSON array of objects for material provided , use only the material provided, provide as much flashcards as possible 12 | following this format [no prose, only the result in json format]: 13 | 14 | [ 15 | { 16 | "term": "clean code", 17 | "definition": "code that meets the standards" 18 | }, 19 | { 20 | "term": "science", 21 | "definitions": "the pursuit and application of knowledge" 22 | } 23 | 24 | ] 25 | """; 26 | } 27 | -------------------------------------------------------------------------------- /lib/src/core/utils/strings.dart: -------------------------------------------------------------------------------- 1 | class AppStrings { 2 | static const notFoundAsset = "assets/93134-not-found.json"; 3 | static const completedTaskAsset = 'assets/1697293737915 -completed.json'; 4 | static const String errorMessage = 'Something went wrong'; 5 | static const String review = 'Review'; 6 | static const String homeScreen = "Home Screen"; 7 | static const String delete = "Delete"; 8 | static const String createCollection = "Create Collection"; 9 | static const String cancel = "Cancel"; 10 | static const String defaultCollectionName = 'untitled'; 11 | static const String create = "Create"; 12 | static const String nameTextFieldLabel = "Name"; 13 | static const String descriptionTextFieldLabel = "Description (optional)"; 14 | static const String generateWithAI = "Generate Cards With AI"; 15 | static const String createFlashcards = "Create Flashcards"; 16 | static const String addCard = "Add Card"; 17 | static const String addAnotherCard = "Add Another Card"; 18 | static const String editFlashcard = "Edit Flashcard"; 19 | static const String noCollection = 20 | "Nothing's Here, Start Making Collections!"; 21 | static const String noFlashcardsToReview = "No Flashcards To Review Today"; 22 | static const String goBack = "Go Back"; 23 | static const String practice = "Practice"; 24 | static const String noCards = "No Cards Here, Try Adding New Cards"; 25 | static const String done = "Done"; 26 | static const generatedFlashcard = "Generated Flashcard"; 27 | static const String addAll = "Add All"; 28 | static const String pasteMaterial = "Paste Your Material Here"; 29 | static const String generate = "Generate"; 30 | static const String combineWith = "Combine With"; 31 | static const String combineCollectionMessage = 32 | "Collections Combined Successfully"; 33 | static const String edit = "Edit"; 34 | static const String combineCollection = "Combine Collection"; 35 | static const String reviewFlashcard = "Review Flashcard"; 36 | static const String multipleChoice = "Multiple Choice"; 37 | static const String writingQuiz = "Writing Quiz"; 38 | static const String quizType = "Quiz Type"; 39 | } 40 | -------------------------------------------------------------------------------- /lib/src/core/utils/typdef.dart: -------------------------------------------------------------------------------- 1 | import 'package:dartz/dartz.dart'; 2 | import 'package:quizwiz/src/core/errors/failure.dart'; 3 | import 'package:quizwiz/src/features/cards/data/data.dart'; 4 | 5 | typedef EitherFlashcards = Future>>; 6 | typedef EitherCollections = Future>>; 7 | typedef EitherCollection = Future>; 8 | typedef EitherUnit = Future>; 9 | typedef EitherMultiple = Future>>; 10 | -------------------------------------------------------------------------------- /lib/src/core/widgets/custom_snackbar.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | void customSnackBar(String text, BuildContext context) => 4 | ScaffoldMessenger.of(context).showSnackBar(SnackBar( 5 | content: Text(text), 6 | duration: const Duration(seconds: 1), 7 | )); 8 | -------------------------------------------------------------------------------- /lib/src/core/widgets/error_widget.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:quizwiz/src/core/core.dart'; 3 | import 'package:quizwiz/src/features/cards/controller/controller.dart'; 4 | 5 | class CustomErrorWidget extends StatelessWidget { 6 | final String errorMessage; 7 | const CustomErrorWidget( 8 | {super.key, this.errorMessage = AppStrings.errorMessage}); 9 | 10 | @override 11 | Widget build(BuildContext context) { 12 | return Scaffold( 13 | appBar: AppBar( 14 | title: const Text("Error"), 15 | ), 16 | body: Center( 17 | child: Column( 18 | mainAxisAlignment: MainAxisAlignment.center, 19 | children: [ 20 | Text("Error:$errorMessage"), 21 | TextButton( 22 | onPressed: () { 23 | context.read().add(const GetCollectionsEvent()); 24 | Navigator.of(context).pushReplacementNamed('/'); 25 | }, 26 | child: const Text(AppStrings.goBack)) 27 | ], 28 | ), 29 | ), 30 | ); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /lib/src/core/widgets/loading_widget.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class LoadingWidget extends StatelessWidget { 4 | final String? message; 5 | const LoadingWidget({super.key, this.message}); 6 | 7 | @override 8 | Widget build(BuildContext context) { 9 | return Scaffold( 10 | appBar: AppBar(), 11 | body: Center( 12 | child: Column( 13 | mainAxisAlignment: MainAxisAlignment.center, 14 | children: [ 15 | const CircularProgressIndicator.adaptive(), 16 | const SizedBox( 17 | height: 10, 18 | ), 19 | message != null ? Text(message!) : const SizedBox() 20 | ], 21 | ), 22 | ), 23 | ); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /lib/src/core/widgets/no_result_screen.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:lottie/lottie.dart'; 3 | import 'package:quizwiz/src/core/utils/strings.dart'; 4 | 5 | class NoResultScreen extends StatelessWidget { 6 | final String description; 7 | const NoResultScreen({super.key, required this.description}); 8 | 9 | @override 10 | Widget build(BuildContext context) { 11 | return SafeArea( 12 | child: Scaffold( 13 | body: Center( 14 | child: Column( 15 | mainAxisAlignment: MainAxisAlignment.center, 16 | children: [ 17 | LottieBuilder.asset( 18 | AppStrings.notFoundAsset, 19 | height: 250, 20 | ), 21 | Text( 22 | description, 23 | textAlign: TextAlign.center, 24 | style: Theme.of(context).textTheme.headlineSmall, 25 | ), 26 | ], 27 | ), 28 | ), 29 | ), 30 | ); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /lib/src/features/cards/controller/bloc/cards_events.dart: -------------------------------------------------------------------------------- 1 | // ignore_for_file: public_member_api_docs, sort_constructors_first 2 | import 'dart:io'; 3 | 4 | import 'package:equatable/equatable.dart'; 5 | import 'package:quizwiz/src/core/core.dart'; 6 | import 'package:quizwiz/src/features/cards/data/data.dart'; 7 | 8 | class CardsEvents extends Equatable { 9 | const CardsEvents(); 10 | @override 11 | List get props => []; 12 | } 13 | 14 | class GetCollectionsEvent extends CardsEvents { 15 | const GetCollectionsEvent(); 16 | } 17 | 18 | class RemoveCollectionEvent extends CardsEvents { 19 | final String uuid; 20 | const RemoveCollectionEvent({ 21 | required this.uuid, 22 | }); 23 | } 24 | 25 | class CreateCollectionsEvent extends CardsEvents { 26 | final String name; 27 | final String description; 28 | const CreateCollectionsEvent({ 29 | required this.name, 30 | required this.description, 31 | }); 32 | } 33 | 34 | class AddFlashcardsEvent extends CardsEvents { 35 | final String collectionUuid; 36 | final String question; 37 | final String answer; 38 | const AddFlashcardsEvent({ 39 | required this.collectionUuid, 40 | required this.question, 41 | required this.answer, 42 | }); 43 | } 44 | 45 | class UpdateDueTimeEvent extends CardsEvents { 46 | final Flashcard card; 47 | final String collectionUuid; 48 | final ReviewResult reviewResult; 49 | const UpdateDueTimeEvent({ 50 | required this.card, 51 | required this.collectionUuid, 52 | required this.reviewResult, 53 | }); 54 | } 55 | 56 | class RemoveFlashcardsEvent extends CardsEvents { 57 | final FlashcardCollection collection; 58 | final String flashcardUuid; 59 | const RemoveFlashcardsEvent({ 60 | required this.collection, 61 | required this.flashcardUuid, 62 | }); 63 | } 64 | 65 | class EditFlashcardsEvent extends CardsEvents { 66 | final EditFlashcardParameters parameters; 67 | const EditFlashcardsEvent({required this.parameters}); 68 | } 69 | 70 | class GetDueReviewsEvent extends CardsEvents { 71 | final FlashcardCollection collection; 72 | const GetDueReviewsEvent({required this.collection}); 73 | } 74 | 75 | class EditCollectionEvent extends CardsEvents { 76 | final String name; 77 | final String description; 78 | final FlashcardCollection collection; 79 | const EditCollectionEvent({ 80 | required this.name, 81 | required this.description, 82 | required this.collection, 83 | }); 84 | } 85 | 86 | class GetMultipleQuizOptionsEvent extends CardsEvents { 87 | final FlashcardCollection collection; 88 | 89 | const GetMultipleQuizOptionsEvent({required this.collection}); 90 | } 91 | 92 | class GenerateFlashcardsEvent extends CardsEvents { 93 | final File file; 94 | const GenerateFlashcardsEvent({ 95 | required this.file, 96 | }); 97 | } 98 | 99 | class SaveAllGenerateFlashcardsEvent extends CardsEvents { 100 | final List flashcards; 101 | final String collectionUuid; 102 | 103 | const SaveAllGenerateFlashcardsEvent( 104 | {required this.flashcards, required this.collectionUuid}); 105 | } 106 | 107 | class CombineCollectionsEvent extends CardsEvents { 108 | final FlashcardCollection mainCollection; 109 | final FlashcardCollection secondaryCollection; 110 | const CombineCollectionsEvent({ 111 | required this.mainCollection, 112 | required this.secondaryCollection, 113 | }); 114 | } 115 | -------------------------------------------------------------------------------- /lib/src/features/cards/controller/bloc/cards_state.dart: -------------------------------------------------------------------------------- 1 | // ignore_for_file: public_member_api_docs, sort_constructors_first 2 | 3 | part of 'cards_bloc.dart'; 4 | 5 | class CardsState extends Equatable { 6 | //state of all the collections 7 | final RequestState collectionsRequestState; 8 | final String collectionsErrorMessage; 9 | final List collections; 10 | 11 | //state of flashcards of a specific collection 12 | final RequestState flashcardRequestState; 13 | final String flashcardErrorMessage; 14 | final List flashcards; 15 | 16 | //state of [MultipleChoiceQuiz] 17 | final RequestState quizRequestState; 18 | final String quizErrorMessage; 19 | final List multipleChoices; 20 | 21 | const CardsState({ 22 | this.collectionsRequestState = RequestState.loading, 23 | this.collectionsErrorMessage = '', 24 | this.collections = const [], 25 | this.flashcardRequestState = RequestState.loading, 26 | this.flashcardErrorMessage = '', 27 | this.flashcards = const [], 28 | this.quizRequestState = RequestState.loading, 29 | this.quizErrorMessage = '', 30 | this.multipleChoices = const [], 31 | }); 32 | 33 | @override 34 | List get props { 35 | return [ 36 | collectionsRequestState, 37 | collectionsErrorMessage, 38 | collections, 39 | flashcardRequestState, 40 | flashcardErrorMessage, 41 | flashcards, 42 | quizRequestState, 43 | quizErrorMessage, 44 | multipleChoices, 45 | ]; 46 | } 47 | 48 | CardsState copyWith({ 49 | RequestState? collectionsRequestState, 50 | String? collectionsErrorMessage, 51 | List? collections, 52 | RequestState? flashcardRequestState, 53 | String? flashcardErrorMessage, 54 | List? flashcards, 55 | RequestState? quizRequestState, 56 | String? quizErrorMessage, 57 | List? multipleChoices, 58 | }) { 59 | return CardsState( 60 | collectionsRequestState: 61 | collectionsRequestState ?? this.collectionsRequestState, 62 | collectionsErrorMessage: 63 | collectionsErrorMessage ?? this.collectionsErrorMessage, 64 | collections: collections ?? this.collections, 65 | flashcardRequestState: 66 | flashcardRequestState ?? this.flashcardRequestState, 67 | flashcardErrorMessage: 68 | flashcardErrorMessage ?? this.flashcardErrorMessage, 69 | flashcards: flashcards ?? this.flashcards, 70 | quizRequestState: quizRequestState ?? this.quizRequestState, 71 | quizErrorMessage: quizErrorMessage ?? this.quizErrorMessage, 72 | multipleChoices: multipleChoices ?? this.multipleChoices, 73 | ); 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /lib/src/features/cards/controller/controller.dart: -------------------------------------------------------------------------------- 1 | export 'package:quizwiz/src/features/cards/controller/bloc/cards_bloc.dart'; 2 | export 'package:quizwiz/src/features/cards/controller/bloc/cards_events.dart'; 3 | export 'package:flutter_bloc/flutter_bloc.dart'; 4 | -------------------------------------------------------------------------------- /lib/src/features/cards/data/data.dart: -------------------------------------------------------------------------------- 1 | export 'package:quizwiz/src/features/cards/data/repository/base_cards_repository.dart'; 2 | export 'package:quizwiz/src/features/cards/data/data_source/remote_data_source/remote_data_source.dart'; 3 | export 'package:quizwiz/src/features/cards/data/models/multiple_choice_quiz.dart'; 4 | export 'package:quizwiz/src/features/cards/data/data_source/local_data_source/collection_local_data_source.dart'; 5 | export 'package:quizwiz/src/features/cards/data/data_source/local_data_source/flashcard_local_data_source.dart'; 6 | export 'package:quizwiz/src/features/cards/data/models/flashcard_collection.dart'; 7 | export 'package:quizwiz/src/features/cards/data/repository/cards_repository.dart'; 8 | export 'package:isar/isar.dart'; 9 | export 'package:quizwiz/src/features/cards/data/models/edit_flashcard_parameters.dart'; 10 | export 'package:quizwiz/src/features/cards/data/models/card_calculation.dart'; 11 | export 'package:quizwiz/src/features/cards/data/data_source/remote_data_source/api_client.dart'; 12 | -------------------------------------------------------------------------------- /lib/src/features/cards/data/data_source/local_data_source/collection_local_data_source.dart: -------------------------------------------------------------------------------- 1 | import 'package:dartz/dartz.dart'; 2 | import 'package:quizwiz/src/core/errors/exceptions.dart'; 3 | import 'package:quizwiz/src/features/cards/data/data.dart'; 4 | import 'package:uuid/uuid.dart'; 5 | 6 | abstract class CollectionLocalDataSource { 7 | factory CollectionLocalDataSource() => IsarCollectionDataSource(); 8 | Future> getCollections(); 9 | Future createCollection(String name, {description = ''}); 10 | Future removeCollection(String uuid); 11 | Future editCollection( 12 | ({ 13 | FlashcardCollection collection, 14 | String name, 15 | String description 16 | }) collection); 17 | Future combineCollections(FlashcardCollection mainCollection, 18 | FlashcardCollection secondaryCollection); 19 | } 20 | 21 | class IsarCollectionDataSource implements CollectionLocalDataSource { 22 | final _instance = Isar.getInstance()!; 23 | final uuid = const Uuid(); 24 | @override 25 | Future createCollection(String name, {description = ''}) async { 26 | final collection = FlashcardCollection( 27 | name: name, description: description, uuid: uuid.v4()); 28 | _instance.writeTxn(() async { 29 | await _instance.flashcardCollections.put(collection).onError( 30 | (error, stackTrace) => 31 | throw LocalStorageException(message: error.toString())); 32 | }); 33 | return unit; 34 | } 35 | 36 | @override 37 | Future removeCollection(String uuid) async { 38 | _instance.writeTxn(() async { 39 | await _instance.flashcardCollections.deleteByUuid(uuid).onError( 40 | (error, stackTrace) => 41 | throw LocalStorageException(message: error.toString())); 42 | }); 43 | return unit; 44 | } 45 | 46 | @override 47 | Future> getCollections() async { 48 | final collections = await _instance.flashcardCollections 49 | .where() 50 | .findAll() 51 | .onError((error, stackTrace) => 52 | throw LocalStorageException(message: error.toString())); 53 | return collections.reversed.toList(); 54 | } 55 | 56 | @override 57 | Future editCollection( 58 | ({ 59 | FlashcardCollection collection, 60 | String description, 61 | String name 62 | }) collection) async { 63 | await _instance.writeTxn(() async { 64 | final newCollection = collection.collection 65 | .copyWith(name: collection.name, description: collection.description); 66 | await _instance.flashcardCollections.put(newCollection); 67 | }).onError((error, stackTrace) { 68 | throw LocalStorageException(message: error.toString()); 69 | }); 70 | return unit; 71 | } 72 | 73 | @override 74 | Future combineCollections(FlashcardCollection mainCollection, 75 | FlashcardCollection secondaryCollection) async { 76 | List newCardList = [ 77 | ...mainCollection.cards, 78 | ...secondaryCollection.cards 79 | ]; 80 | await _instance.writeTxn(() async { 81 | await _instance.flashcardCollections 82 | .put(mainCollection.copyWith(cards: newCardList)); 83 | }); 84 | return unit; 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /lib/src/features/cards/data/data_source/remote_data_source/api_client.dart: -------------------------------------------------------------------------------- 1 | // ignore_for_file: public_member_api_docs, sort_constructors_first 2 | import 'dart:convert'; 3 | import 'dart:io'; 4 | import 'package:dio/dio.dart'; 5 | import 'package:flutter_dotenv/flutter_dotenv.dart'; 6 | import 'package:quizwiz/src/core/core.dart'; 7 | 8 | class ApiProvider { 9 | final Dio _dio; 10 | ApiProvider(this._dio); 11 | 12 | Future getResult( 13 | File file, 14 | ) async { 15 | try { 16 | final apiKey = dotenv.get(NetworkConstants.apiKeyName); 17 | _dio.options.headers = { 18 | 'x-api-key': apiKey, 19 | 'Content-Type': 'application/json' 20 | }; 21 | final sourceId = await _uploadFile(file, apiKey); 22 | final result = 23 | await _generateFlashcards(apiKey: apiKey, fileSourceId: sourceId); 24 | await _deleteFile(sourceId, apiKey); 25 | return result; 26 | } on Exception { 27 | rethrow; 28 | } finally { 29 | // _dio.close(); 30 | } 31 | } 32 | 33 | Future _uploadFile(File file, apiKey) async { 34 | try { 35 | final fileName = file.path.split('/').last; 36 | FormData formData = FormData.fromMap({ 37 | 'file': await MultipartFile.fromFile( 38 | file.path, 39 | filename: fileName, 40 | ), 41 | }); 42 | Response response = await _dio.post( 43 | NetworkConstants.addFileEndPoint, 44 | data: formData, 45 | ); 46 | return response.data['sourceId']; 47 | } on Exception catch (e) { 48 | throw NetworkException(e.toString()); 49 | } 50 | } 51 | 52 | Future _generateFlashcards( 53 | {required String apiKey, required String fileSourceId}) async { 54 | try { 55 | final data = { 56 | "sourceId": fileSourceId, 57 | "messages": [ 58 | {"role": 'user', "content": NetworkConstants.generateFlashcardsPrompt} 59 | ] 60 | }; 61 | Response response = await _dio.post( 62 | NetworkConstants.chatEndPoint, 63 | data: data, 64 | ); 65 | return jsonDecode(response.data['content']); 66 | } on Exception { 67 | rethrow; 68 | } 69 | } 70 | 71 | Future _deleteFile(String fileSourceId, apiKey) async { 72 | final data = { 73 | 'sources': [fileSourceId] 74 | }; 75 | try { 76 | _dio.post( 77 | NetworkConstants.deleteFileEndPoint, 78 | data: data, 79 | ); 80 | } on Exception { 81 | rethrow; 82 | } 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /lib/src/features/cards/data/data_source/remote_data_source/remote_data_source.dart: -------------------------------------------------------------------------------- 1 | import 'package:dio/dio.dart'; 2 | import 'package:quizwiz/src/core/core.dart'; 3 | import 'package:quizwiz/src/features/cards/data/data.dart'; 4 | import 'dart:io'; 5 | 6 | abstract class BaseRemoteDataSource { 7 | factory BaseRemoteDataSource() => DioRemoteDataSource(); 8 | Future> generateFlashcards(File file); 9 | } 10 | 11 | class DioRemoteDataSource implements BaseRemoteDataSource { 12 | @override 13 | Future> generateFlashcards(File file) async { 14 | try { 15 | final api = ApiProvider(Dio()); 16 | final List result = await api.getResult(file); 17 | final flashcards = result.map((e) => Flashcard.fromMap(e)).toList(); 18 | return flashcards; 19 | } on NetworkException catch (e) { 20 | throw NetworkException("Failed To Connect: ${e.message}"); 21 | } on Exception catch (e) { 22 | throw JsonDeserializationException( 23 | "Invalid API response: ${e.toString()}"); 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /lib/src/features/cards/data/models/card_calculation.dart: -------------------------------------------------------------------------------- 1 | import 'dart:math'; 2 | import 'package:quizwiz/src/core/utils/enums.dart'; 3 | import 'package:quizwiz/src/features/cards/data/models/flashcard_collection.dart'; 4 | 5 | class CardCalculation { 6 | Flashcard card; 7 | CardCalculation(this.card); 8 | 9 | Flashcard update( 10 | ReviewResult result, 11 | ) { 12 | final now = DateTime.now().millisecondsSinceEpoch; 13 | switch (result) { 14 | case ReviewResult.again: 15 | card.repetitions = 0; 16 | card.interval = 0; 17 | card.dueTime = now + _toMilliseconds(card.interval); 18 | break; 19 | case ReviewResult.hard: 20 | card.repetitions = max(1, card.repetitions); 21 | card.interval = _calculateInterval(card.repetitions, card.factor); 22 | card.factor = max(1.3, card.factor - 0.15); 23 | card.dueTime = now + _toMilliseconds(card.interval); 24 | card.repetitions++; 25 | break; 26 | case ReviewResult.good: 27 | card.repetitions++; 28 | card.interval = _calculateInterval(card.repetitions, card.factor); 29 | card.dueTime = now + _toMilliseconds(card.interval); 30 | break; 31 | case ReviewResult.easy: 32 | card.repetitions++; 33 | card.interval = _calculateInterval(card.repetitions, card.factor); 34 | card.factor = min(2.5, card.factor + 0.15); 35 | card.dueTime = now + _toMilliseconds(card.interval); 36 | break; 37 | } 38 | return card; 39 | } 40 | 41 | double _calculateInterval(int repetitions, double factor) { 42 | if (repetitions == 1) { 43 | return 1; 44 | } else if (repetitions == 2) { 45 | return 4; 46 | } else { 47 | return card.interval * factor; 48 | } 49 | } 50 | 51 | int _toMilliseconds(double days) { 52 | return (days * 24 * 60 * 60 * 1000).toInt(); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /lib/src/features/cards/data/models/edit_flashcard_parameters.dart: -------------------------------------------------------------------------------- 1 | // ignore_for_file: public_member_api_docs, sort_constructors_first 2 | import 'package:quizwiz/src/features/cards/data/data.dart'; 3 | 4 | class EditFlashcardParameters { 5 | final String question; 6 | final String answer; 7 | final FlashcardCollection collection; 8 | final Flashcard flashcard; 9 | const EditFlashcardParameters({ 10 | required this.question, 11 | required this.answer, 12 | required this.collection, 13 | required this.flashcard, 14 | }); 15 | 16 | EditFlashcardParameters copyWith({ 17 | String? question, 18 | String? answer, 19 | FlashcardCollection? collection, 20 | Flashcard? flashcard, 21 | }) { 22 | return EditFlashcardParameters( 23 | question: question ?? this.question, 24 | answer: answer ?? this.answer, 25 | collection: collection ?? this.collection, 26 | flashcard: flashcard ?? this.flashcard, 27 | ); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /lib/src/features/cards/data/models/flashcard_collection.dart: -------------------------------------------------------------------------------- 1 | // ignore_for_file: public_member_api_docs, sort_constructors_first 2 | 3 | import 'package:isar/isar.dart'; 4 | import 'package:uuid/uuid.dart'; 5 | 6 | part 'flashcard_collection.g.dart'; 7 | 8 | @collection 9 | class FlashcardCollection { 10 | String name; 11 | String description; 12 | List cards; 13 | Id id = Isar.autoIncrement; 14 | @Index(unique: true, replace: true, type: IndexType.hash) 15 | final String uuid; 16 | FlashcardCollection({ 17 | required this.name, 18 | required this.uuid, 19 | this.description = '', 20 | this.cards = const [], 21 | }); 22 | 23 | FlashcardCollection copyWith({ 24 | String? name, 25 | String? description, 26 | List? cards, 27 | Id? id, 28 | String? uuid, 29 | }) { 30 | return FlashcardCollection( 31 | name: name ?? this.name, 32 | description: description ?? this.description, 33 | cards: cards ?? this.cards, 34 | uuid: uuid ?? this.uuid, 35 | ); 36 | } 37 | } 38 | 39 | @embedded 40 | class Flashcard { 41 | String question; 42 | String answer; 43 | int dueTime; 44 | double interval; 45 | double factor; 46 | int repetitions; 47 | final String uuid; 48 | Flashcard({ 49 | this.uuid = '', 50 | this.question = '', 51 | this.answer = '', 52 | this.dueTime = 0, 53 | this.interval = 1.0, 54 | this.factor = 2.5, 55 | this.repetitions = 0, 56 | }); 57 | 58 | Flashcard copyWith({ 59 | String? question, 60 | String? answer, 61 | int? dueTime, 62 | double? interval, 63 | double? factor, 64 | int? repetitions, 65 | String? uuid, 66 | }) { 67 | return Flashcard( 68 | question: question ?? this.question, 69 | answer: answer ?? this.answer, 70 | dueTime: dueTime ?? this.dueTime, 71 | interval: interval ?? this.interval, 72 | factor: factor ?? this.factor, 73 | repetitions: repetitions ?? this.repetitions, 74 | uuid: uuid ?? this.uuid, 75 | ); 76 | } 77 | 78 | factory Flashcard.fromMap(Map map) { 79 | return Flashcard( 80 | question: map['term'], 81 | answer: map['definition'], 82 | uuid: const Uuid().v4()); 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /lib/src/features/cards/data/models/multiple_choice_quiz.dart: -------------------------------------------------------------------------------- 1 | // ignore_for_file: public_member_api_docs, sort_constructors_first 2 | import 'dart:math'; 3 | 4 | import 'package:quizwiz/src/features/cards/data/data.dart'; 5 | 6 | class MultipleChoiceQuiz { 7 | final String question; 8 | final List options; 9 | final String rightAnswer; 10 | const MultipleChoiceQuiz({ 11 | required this.question, 12 | required this.options, 13 | required this.rightAnswer, 14 | }); 15 | //takes a collection and a card and generates multiple choice quiz question 16 | factory MultipleChoiceQuiz.fromCollection( 17 | {required card, required FlashcardCollection collection}) { 18 | //insert the right answer into `options` 19 | List options = [card.answer]; 20 | var random = Random(); 21 | //pick 3 unique and random answers from the collection 22 | while (options.length < 4) { 23 | int randomElement = random.nextInt(collection.cards.length); 24 | if (!options.contains(collection.cards[randomElement].answer)) { 25 | options.add(collection.cards[randomElement].answer); 26 | } 27 | } 28 | //shuffle the options 29 | options.shuffle(); 30 | return MultipleChoiceQuiz( 31 | question: card.question, options: options, rightAnswer: card.answer); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /lib/src/features/cards/data/repository/base_cards_repository.dart: -------------------------------------------------------------------------------- 1 | import 'package:quizwiz/src/core/core.dart'; 2 | import 'package:quizwiz/src/features/cards/data/data.dart'; 3 | import 'dart:io'; 4 | 5 | abstract class BaseCardsRepository { 6 | factory BaseCardsRepository( 7 | FlashcardLocalDataSource flashcardLocalDataSource, 8 | CollectionLocalDataSource collectionLocalDataSource, 9 | BaseRemoteDataSource baseRemoteDataSource) => 10 | CardsRepository(flashcardLocalDataSource, collectionLocalDataSource, 11 | baseRemoteDataSource); 12 | 13 | EitherFlashcards getDueReviewCards( 14 | FlashcardCollection collection, 15 | ); 16 | EitherCollections getCollections(); 17 | EitherUnit createCollection(String name, {description = ''}); 18 | EitherUnit addFlashcard( 19 | String question, String answer, String collectionUuid); 20 | EitherUnit removeCollection(String uuid); 21 | EitherUnit updateDueTime( 22 | Flashcard card, 23 | String collectionUuid, 24 | ReviewResult reviewResult, 25 | ); 26 | EitherUnit removeFlashcard( 27 | FlashcardCollection collection, String flashcardUuid); 28 | EitherUnit editFlashcard(EditFlashcardParameters parameters); 29 | EitherUnit editCollection( 30 | ({ 31 | FlashcardCollection collection, 32 | String name, 33 | String description 34 | }) collection); 35 | EitherMultiple getMultipleChoiceOptions(FlashcardCollection collection); 36 | EitherFlashcards generateFlashcards(File file); 37 | EitherUnit saveAllGeneratedFlashcard( 38 | String collectionUuid, List flashcards); 39 | EitherUnit combineCollections(FlashcardCollection mainCollection, 40 | FlashcardCollection secondaryCollection); 41 | } 42 | -------------------------------------------------------------------------------- /lib/src/features/cards/data/repository/cards_repository.dart: -------------------------------------------------------------------------------- 1 | // ignore_for_file: public_member_api_docs, sort_constructors_first 2 | import 'dart:io'; 3 | 4 | import 'package:dartz/dartz.dart'; 5 | import 'package:quizwiz/src/core/core.dart'; 6 | import 'package:quizwiz/src/features/cards/data/data.dart'; 7 | 8 | class CardsRepository implements BaseCardsRepository { 9 | final FlashcardLocalDataSource _flashcardDataSource; 10 | final CollectionLocalDataSource _collectionDataSource; 11 | final BaseRemoteDataSource _remoteDataSource; 12 | const CardsRepository( 13 | this._flashcardDataSource, 14 | this._collectionDataSource, 15 | this._remoteDataSource, 16 | ); 17 | 18 | @override 19 | EitherUnit addFlashcard( 20 | String question, String answer, String collectionUuid) async { 21 | try { 22 | final result = await _flashcardDataSource.addFlashcard( 23 | question, answer, collectionUuid); 24 | return Right(result); 25 | } on Exception catch (e) { 26 | return Left(LocalStorageFailure(message: e.toString())); 27 | } 28 | } 29 | 30 | @override 31 | EitherUnit createCollection(String name, {description = ''}) async { 32 | try { 33 | final result = await _collectionDataSource.createCollection(name, 34 | description: description); 35 | return Right(result); 36 | } on Exception catch (e) { 37 | return Left(LocalStorageFailure(message: e.toString())); 38 | } 39 | } 40 | 41 | @override 42 | EitherCollections getCollections() async { 43 | try { 44 | final result = await _collectionDataSource.getCollections(); 45 | return Right(result); 46 | } on Exception catch (e) { 47 | return Left(LocalStorageFailure(message: e.toString())); 48 | } 49 | } 50 | 51 | @override 52 | EitherFlashcards getDueReviewCards( 53 | FlashcardCollection collection, 54 | ) async { 55 | try { 56 | final result = await _flashcardDataSource.getDueReviewCards(collection); 57 | return Right(result); 58 | } on Exception catch (e) { 59 | return Left(LocalStorageFailure(message: e.toString())); 60 | } 61 | } 62 | 63 | @override 64 | EitherUnit removeCollection(String uuid) async { 65 | try { 66 | final result = await _collectionDataSource.removeCollection(uuid); 67 | return Right(result); 68 | } on Exception catch (e) { 69 | return Left(LocalStorageFailure(message: e.toString())); 70 | } 71 | } 72 | 73 | @override 74 | EitherUnit updateDueTime( 75 | Flashcard card, String collectionUuid, ReviewResult reviewResult) async { 76 | try { 77 | final result = await _flashcardDataSource.updateDueTime( 78 | card, collectionUuid, reviewResult); 79 | return Right(result); 80 | } on Exception catch (e) { 81 | return Left(LocalStorageFailure(message: e.toString())); 82 | } 83 | } 84 | 85 | @override 86 | EitherUnit removeFlashcard( 87 | FlashcardCollection collection, String flashcardUuid) async { 88 | try { 89 | final result = 90 | await _flashcardDataSource.removeFlashcard(collection, flashcardUuid); 91 | return Right(result); 92 | } on Exception catch (e) { 93 | return Left(LocalStorageFailure(message: e.toString())); 94 | } 95 | } 96 | 97 | @override 98 | EitherUnit editFlashcard(EditFlashcardParameters parameters) async { 99 | try { 100 | final result = await _flashcardDataSource.editFlashcard(parameters); 101 | return Right(result); 102 | } on Exception catch (e) { 103 | return Left(LocalStorageFailure(message: e.toString())); 104 | } 105 | } 106 | 107 | @override 108 | EitherUnit editCollection( 109 | ({ 110 | FlashcardCollection collection, 111 | String description, 112 | String name 113 | }) collection) async { 114 | try { 115 | final result = await _collectionDataSource.editCollection(collection); 116 | return Right(result); 117 | } on Exception catch (e) { 118 | return Left(LocalStorageFailure(message: e.toString())); 119 | } 120 | } 121 | 122 | @override 123 | EitherMultiple getMultipleChoiceOptions( 124 | FlashcardCollection collection) async { 125 | try { 126 | final result = 127 | await _flashcardDataSource.getMultipleChoiceOptions(collection); 128 | return Right(result); 129 | } on Exception catch (e) { 130 | return Left(LocalStorageFailure(message: e.toString())); 131 | } 132 | } 133 | 134 | @override 135 | EitherFlashcards generateFlashcards(File file) async { 136 | try { 137 | final result = await _remoteDataSource.generateFlashcards(file); 138 | return Right(result); 139 | } catch (e) { 140 | return Left(NetworkFailure(message: e.toString())); 141 | } 142 | } 143 | 144 | @override 145 | EitherUnit saveAllGeneratedFlashcard( 146 | String collectionUuid, List flashcards) async { 147 | try { 148 | final result = await _flashcardDataSource.saveAllGeneratedFlashcard( 149 | collectionUuid, flashcards); 150 | return Right(result); 151 | } on Exception catch (e) { 152 | return Left(LocalStorageFailure(message: e.toString())); 153 | } 154 | } 155 | 156 | @override 157 | EitherUnit combineCollections(FlashcardCollection mainCollection, 158 | FlashcardCollection secondaryCollection) async { 159 | try { 160 | final result = await _collectionDataSource.combineCollections( 161 | mainCollection, secondaryCollection); 162 | return Right(result); 163 | } on Exception catch (e) { 164 | return Left(LocalStorageFailure(message: e.toString())); 165 | } 166 | } 167 | } 168 | -------------------------------------------------------------------------------- /lib/src/features/cards/presentation/presentation.dart: -------------------------------------------------------------------------------- 1 | export 'package:quizwiz/src/features/cards/presentation/screens/create_flashcards_screen.dart'; 2 | export 'package:quizwiz/src/features/cards/presentation/screens/home_screen.dart'; 3 | export 'package:quizwiz/src/features/cards/presentation/widgets/home_screen_widgets/collections_list_screen.dart'; 4 | export 'package:quizwiz/src/features/cards/presentation/widgets/home_screen_widgets/collection_card_widget.dart'; 5 | export 'package:quizwiz/src/features/cards/presentation/widgets/home_screen_widgets/create_or_edit_collection_dialog.dart'; 6 | export 'package:quizwiz/src/features/cards/presentation/screens/edit_flashcard_screen.dart'; 7 | export 'package:quizwiz/src/features/cards/presentation/screens/flashcards_list_screen.dart'; 8 | export 'package:quizwiz/src/features/cards/presentation/screens/generated_flashcards_screen.dart'; 9 | export 'package:quizwiz/src/features/cards/presentation/screens/multiple_choice_quiz_screen.dart'; 10 | export 'package:quizwiz/src/features/cards/presentation/screens/practice_cards_screen.dart'; 11 | export 'package:quizwiz/src/features/cards/presentation/screens/review_result_screen.dart'; 12 | export 'package:quizwiz/src/features/cards/presentation/widgets/home_screen_widgets/create_collection_dialog.dart'; 13 | export 'package:quizwiz/src/features/cards/presentation/widgets/generated_flashcards/generated_flashcard_widget.dart'; 14 | export 'package:flutter/material.dart'; 15 | export 'package:quizwiz/src/features/cards/presentation/widgets/flashcards_list_widgets/flashcard_widget.dart'; 16 | export 'package:quizwiz/src/features/cards/presentation/widgets/create_flashcards_widgets/forms.dart'; 17 | export 'package:quizwiz/src/features/cards/presentation/widgets/flashcards_list_widgets/custom_focused_menu_holder.dart'; 18 | export 'package:quizwiz/src/features/cards/presentation/widgets/multiple_choice_quiz_widgets/multiple_choice_body.dart'; 19 | export 'package:quizwiz/src/features/cards/presentation/widgets/practice_cards_widgets/no_flashcards_to_review.dart'; 20 | export 'package:quizwiz/src/features/cards/presentation/widgets/review_result_widgets/review_bar.dart'; 21 | export 'package:quizwiz/src/features/cards/presentation/widgets/writing_quiz_widgets/answer_form_field.dart'; 22 | export 'package:quizwiz/src/features/cards/presentation/widgets/writing_quiz_widgets/display_answer_widget.dart'; 23 | export 'package:quizwiz/src/features/cards/presentation/widgets/writing_quiz_widgets/display_question_widget.dart'; 24 | -------------------------------------------------------------------------------- /lib/src/features/cards/presentation/screens/create_flashcards_screen.dart: -------------------------------------------------------------------------------- 1 | import 'dart:io'; 2 | import 'package:file_picker/file_picker.dart'; 3 | import 'package:quizwiz/src/core/core.dart'; 4 | import 'package:quizwiz/src/features/cards/controller/controller.dart'; 5 | import 'package:quizwiz/src/features/cards/presentation/presentation.dart'; 6 | 7 | class CreateFlashcardsScreen extends StatefulWidget { 8 | final String collectionUuid; 9 | const CreateFlashcardsScreen({ 10 | super.key, 11 | required this.collectionUuid, 12 | }); 13 | 14 | @override 15 | State createState() => _CreateFlashcardsScreenState(); 16 | } 17 | 18 | class _CreateFlashcardsScreenState extends State { 19 | late final TextEditingController questionController; 20 | late final TextEditingController answerController; 21 | final key = GlobalKey(); 22 | @override 23 | void initState() { 24 | super.initState(); 25 | questionController = TextEditingController(); 26 | answerController = TextEditingController(); 27 | } 28 | 29 | @override 30 | void dispose() { 31 | questionController.dispose(); 32 | answerController.dispose(); 33 | super.dispose(); 34 | } 35 | 36 | bool _addFlashcard() { 37 | if (key.currentState!.validate()) { 38 | context.read().add(AddFlashcardsEvent( 39 | collectionUuid: widget.collectionUuid, 40 | question: questionController.text, 41 | answer: answerController.text)); 42 | return true; //if card was added successfully, return true to navigate 43 | } 44 | return false; 45 | } 46 | 47 | void generateFlashcards(File? file) { 48 | if (file != null) { 49 | context.read().add(GenerateFlashcardsEvent(file: file)); 50 | Navigator.of(context).pushReplacementNamed(Routes.generatedFlashcards, 51 | arguments: widget.collectionUuid); 52 | } else { 53 | customSnackBar("No File was selected", context); 54 | } 55 | } 56 | 57 | @override 58 | Widget build(BuildContext context) { 59 | return Scaffold( 60 | appBar: AppBar( 61 | title: const Text(AppStrings.createFlashcards), 62 | ), 63 | body: LayoutBuilder( 64 | builder: (context, size) => ListView( 65 | padding: const EdgeInsets.symmetric(horizontal: 20), 66 | children: [ 67 | SizedBox( 68 | height: size.maxHeight * 0.2, 69 | ), 70 | CustomForms( 71 | formKey: key, 72 | questionController: questionController, 73 | answerController: answerController), 74 | TextButton.icon( 75 | key: const Key(AppStrings.generateWithAI), 76 | onPressed: () async { 77 | final file = await _pickFile(); 78 | generateFlashcards(file); 79 | }, 80 | icon: const Icon(Icons.rocket), 81 | label: const Text(AppStrings.generateWithAI)), 82 | SizedBox(height: size.maxHeight * 0.2), 83 | FilledButton( 84 | key: const Key(AppStrings.addCard), 85 | onPressed: () { 86 | if (_addFlashcard()) { 87 | Navigator.of(context).pushReplacementNamed( 88 | Routes.flashcardsList, 89 | arguments: widget.collectionUuid); 90 | } 91 | }, 92 | child: const Text(AppStrings.addCard)), 93 | const SizedBox( 94 | height: 20, 95 | ), 96 | FilledButton( 97 | key: const Key(AppStrings.addAnotherCard), 98 | onPressed: () { 99 | if (_addFlashcard()) { 100 | Navigator.of(context).pushReplacementNamed( 101 | Routes.createFlashcards, 102 | arguments: widget.collectionUuid); 103 | } 104 | }, 105 | child: const Text(AppStrings.addAnotherCard)), 106 | ], 107 | ))); 108 | } 109 | } 110 | 111 | Future _pickFile() async { 112 | FilePickerResult? result = await FilePicker.platform 113 | .pickFiles(type: FileType.custom, allowedExtensions: ['pdf']); 114 | if (result != null) { 115 | return File(result.files.single.path!); 116 | } 117 | return null; 118 | } 119 | -------------------------------------------------------------------------------- /lib/src/features/cards/presentation/screens/edit_flashcard_screen.dart: -------------------------------------------------------------------------------- 1 | import 'package:quizwiz/src/core/core.dart'; 2 | import 'package:quizwiz/src/features/cards/controller/controller.dart'; 3 | import 'package:quizwiz/src/features/cards/data/data.dart'; 4 | import 'package:quizwiz/src/features/cards/presentation/presentation.dart'; 5 | 6 | class EditFlashcardScreen extends StatefulWidget { 7 | final EditFlashcardParameters parameters; 8 | const EditFlashcardScreen({super.key, required this.parameters}); 9 | 10 | @override 11 | State createState() => _EditFlashcardScreenState(); 12 | } 13 | 14 | class _EditFlashcardScreenState extends State { 15 | late final TextEditingController questionController; 16 | late final TextEditingController answerController; 17 | final key = GlobalKey(); 18 | @override 19 | void initState() { 20 | super.initState(); 21 | questionController = 22 | TextEditingController(text: widget.parameters.question); 23 | answerController = TextEditingController(text: widget.parameters.answer); 24 | } 25 | 26 | @override 27 | void dispose() { 28 | questionController.dispose(); 29 | answerController.dispose(); 30 | super.dispose(); 31 | } 32 | 33 | bool _editFlashcard() { 34 | if (key.currentState!.validate()) { 35 | context.read().add(EditFlashcardsEvent( 36 | parameters: widget.parameters.copyWith( 37 | question: questionController.text, 38 | answer: answerController.text))); 39 | return true; //if card was added successfully, return true to navigate 40 | } 41 | return false; 42 | } 43 | 44 | @override 45 | Widget build(BuildContext context) { 46 | return Scaffold( 47 | appBar: AppBar( 48 | title: const Text(AppStrings.editFlashcard), 49 | ), 50 | body: LayoutBuilder( 51 | builder: (context, size) => ListView( 52 | padding: const EdgeInsets.symmetric(horizontal: 20), 53 | children: [ 54 | SizedBox( 55 | height: size.maxHeight * 0.2, 56 | ), 57 | CustomForms( 58 | questionController: questionController, 59 | answerController: answerController, 60 | formKey: key), 61 | SizedBox(height: size.maxHeight * 0.2), 62 | FilledButton( 63 | key: const Key(AppStrings.editFlashcard), 64 | onPressed: () { 65 | if (_editFlashcard()) { 66 | Navigator.of(context).pushReplacementNamed( 67 | Routes.flashcardsList, 68 | arguments: widget.parameters.collection.uuid); 69 | } 70 | }, 71 | child: const Text(AppStrings.editFlashcard)), 72 | const SizedBox( 73 | height: 20, 74 | ), 75 | ], 76 | ))); 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /lib/src/features/cards/presentation/screens/flashcards_list_screen.dart: -------------------------------------------------------------------------------- 1 | // ignore_for_file: public_member_api_docs, sort_constructors_first 2 | import 'package:flutter_staggered_grid_view/flutter_staggered_grid_view.dart'; 3 | import 'package:quizwiz/src/core/core.dart'; 4 | import 'package:quizwiz/src/features/cards/controller/controller.dart'; 5 | import 'package:quizwiz/src/features/cards/data/data.dart'; 6 | import 'package:quizwiz/src/features/cards/presentation/presentation.dart'; 7 | 8 | class FlashcardsListScreen extends StatelessWidget { 9 | final String collectionUuid; 10 | const FlashcardsListScreen({super.key, required this.collectionUuid}); 11 | 12 | @override 13 | Widget build(BuildContext context) { 14 | return BlocBuilder( 15 | buildWhen: (previous, current) => 16 | current.collections != previous.collections, 17 | builder: (context, state) { 18 | switch (state.collectionsRequestState) { 19 | case RequestState.loading: 20 | return const LoadingWidget(); 21 | case RequestState.error: 22 | return CustomErrorWidget( 23 | errorMessage: state.flashcardErrorMessage, 24 | ); 25 | case RequestState.success: 26 | final collection = state.collections 27 | .where( 28 | (element) => element.uuid == collectionUuid, 29 | ) 30 | .single; 31 | //place recent cards at the top 32 | List flashcards = collection.cards.reversed.toList(); 33 | return WillPopScope( 34 | onWillPop: () async { 35 | Navigator.of(context).pushReplacementNamed('/'); 36 | return true; 37 | }, 38 | child: Scaffold( 39 | appBar: AppBar( 40 | title: Text(collection.name), 41 | ), 42 | floatingActionButton: FloatingActionButton( 43 | onPressed: () => Navigator.of(context).pushNamed( 44 | Routes.createFlashcards, 45 | arguments: collectionUuid), 46 | child: const Icon(Icons.add), 47 | ), 48 | body: flashcards.isEmpty 49 | ? const NoResultScreen(description: AppStrings.noCards) 50 | : MasonryGridView.builder( 51 | padding: const EdgeInsets.all(15), 52 | gridDelegate: 53 | const SliverSimpleGridDelegateWithFixedCrossAxisCount( 54 | crossAxisCount: 2, 55 | ), 56 | itemCount: flashcards.length, 57 | itemBuilder: (context, index) { 58 | return CustomFocusedMenuHolder( 59 | collection: collection, 60 | index: index, 61 | child: 62 | FlashcardWidget(card: flashcards[index])); 63 | })), 64 | ); 65 | } 66 | }, 67 | ); 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /lib/src/features/cards/presentation/screens/generated_flashcards_screen.dart: -------------------------------------------------------------------------------- 1 | import 'package:quizwiz/src/core/core.dart'; 2 | import 'package:quizwiz/src/features/cards/controller/controller.dart'; 3 | import 'package:quizwiz/src/features/cards/presentation/presentation.dart'; 4 | 5 | class GeneratedFlashcardsScreen extends StatelessWidget { 6 | final String collectionUuid; 7 | const GeneratedFlashcardsScreen({super.key, required this.collectionUuid}); 8 | 9 | @override 10 | Widget build(BuildContext context) { 11 | return BlocBuilder( 12 | builder: (context, state) { 13 | switch (state.flashcardRequestState) { 14 | case RequestState.loading: 15 | return const LoadingWidget( 16 | message: "Making Flashcards...", 17 | ); 18 | case RequestState.error: 19 | return CustomErrorWidget( 20 | errorMessage: state.flashcardErrorMessage, 21 | ); 22 | case RequestState.success: 23 | return GeneratedFlashcardWidget( 24 | flashcards: state.flashcards, 25 | collectionUuid: collectionUuid, 26 | ); 27 | } 28 | }, 29 | ); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /lib/src/features/cards/presentation/screens/home_screen.dart: -------------------------------------------------------------------------------- 1 | import 'package:quizwiz/src/core/core.dart'; 2 | import 'package:quizwiz/src/features/cards/controller/controller.dart'; 3 | import 'package:quizwiz/src/features/cards/presentation/presentation.dart'; 4 | 5 | class HomeScreen extends StatelessWidget { 6 | const HomeScreen({super.key}); 7 | @override 8 | Widget build(BuildContext context) { 9 | return WillPopScope( 10 | onWillPop: () async => false, 11 | child: Scaffold( 12 | appBar: AppBar( 13 | title: const Text(AppStrings.homeScreen), 14 | actions: [ 15 | IconButton( 16 | onPressed: () => context.read().toggleTheme(), 17 | icon: const Icon(Icons.dark_mode)) 18 | ], 19 | ), 20 | floatingActionButton: FloatingActionButton( 21 | onPressed: () => showDialog( 22 | context: context, 23 | builder: (context) => const CreateCollectionDialog()), 24 | child: const Icon(Icons.add), 25 | ), 26 | body: BlocBuilder( 27 | builder: (context, state) { 28 | switch (state.collectionsRequestState) { 29 | case RequestState.loading: 30 | return const LoadingWidget(); 31 | case RequestState.error: 32 | return CustomErrorWidget( 33 | errorMessage: state.flashcardErrorMessage, 34 | ); 35 | case RequestState.success: 36 | return state.collections.isEmpty 37 | ? const NoResultScreen( 38 | description: AppStrings.noCollection) 39 | : CollectionsListScreen(collections: state.collections); 40 | } 41 | }, 42 | ))); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /lib/src/features/cards/presentation/screens/multiple_choice_quiz_screen.dart: -------------------------------------------------------------------------------- 1 | import 'package:quizwiz/src/core/core.dart'; 2 | import 'package:quizwiz/src/features/cards/controller/controller.dart'; 3 | import 'package:quizwiz/src/features/cards/data/data.dart'; 4 | import 'package:quizwiz/src/features/cards/presentation/presentation.dart'; 5 | 6 | class MultipleChoiceQuizScreen extends StatefulWidget { 7 | const MultipleChoiceQuizScreen({super.key}); 8 | 9 | @override 10 | State createState() => 11 | _MultipleChoiceQuizScreenState(); 12 | } 13 | 14 | class _MultipleChoiceQuizScreenState extends State { 15 | bool showCorrectAnswer = false; 16 | late final PageController _controller; 17 | final int _slideDuration = 400; //time in milliseconds 18 | @override 19 | void initState() { 20 | super.initState(); 21 | _controller = PageController(); 22 | } 23 | 24 | @override 25 | void dispose() { 26 | _controller.dispose(); 27 | super.dispose(); 28 | } 29 | 30 | void _changeAnswerColor() { 31 | setState(() { 32 | showCorrectAnswer = !showCorrectAnswer; 33 | }); 34 | } 35 | 36 | @override 37 | Widget build(BuildContext context) { 38 | return Scaffold( 39 | appBar: AppBar( 40 | title: const Text(AppStrings.multipleChoice), 41 | ), 42 | body: BlocBuilder( 43 | builder: (context, state) { 44 | switch (state.quizRequestState) { 45 | case RequestState.loading: 46 | return const LoadingWidget(); 47 | case RequestState.error: 48 | return CustomErrorWidget( 49 | errorMessage: state.quizErrorMessage, 50 | ); 51 | case RequestState.success: 52 | final quiz = state.multipleChoices; 53 | return MultipleChoiceQuizBody( 54 | quiz: quiz, 55 | controller: _controller, 56 | showCorrectAnswer: showCorrectAnswer, 57 | onTap: _onTap); 58 | } 59 | }, 60 | ), 61 | ); 62 | } 63 | 64 | void _onTap(int index, List quiz) { 65 | //change the color to green 66 | _changeAnswerColor(); 67 | //after [_slideDuration + 100ms] change the color answer 68 | Future.delayed(Duration(milliseconds: _slideDuration + 100), 69 | () => _changeAnswerColor()); 70 | //if this's the last question, navigate to the home page, else slide to the 71 | //next question 72 | if (index == quiz.length - 1) { 73 | Future.delayed(Duration(milliseconds: _slideDuration), 74 | () => Navigator.of(context).pushReplacementNamed('/')); 75 | } else { 76 | Future.delayed( 77 | Duration(milliseconds: _slideDuration + 500), 78 | () => _controller.nextPage( 79 | duration: Duration(milliseconds: _slideDuration), 80 | curve: Curves.easeInOut)); 81 | } 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /lib/src/features/cards/presentation/screens/practice_cards_screen.dart: -------------------------------------------------------------------------------- 1 | import 'package:quizwiz/src/core/core.dart'; 2 | import 'package:quizwiz/src/features/cards/controller/controller.dart'; 3 | import 'package:quizwiz/src/features/cards/data/data.dart'; 4 | import 'package:quizwiz/src/features/cards/presentation/presentation.dart'; 5 | 6 | class PracticeCardsScreen extends StatelessWidget { 7 | final FlashcardCollection collection; 8 | const PracticeCardsScreen({super.key, required this.collection}); 9 | @override 10 | Widget build(BuildContext context) { 11 | return WillPopScope( 12 | onWillPop: () async { 13 | Navigator.of(context).pushNamed('/'); 14 | return true; 15 | }, 16 | child: Scaffold( 17 | appBar: AppBar( 18 | title: const Text(AppStrings.reviewFlashcard), 19 | ), 20 | body: BlocBuilder( 21 | builder: (context, state) { 22 | switch (state.flashcardRequestState) { 23 | case RequestState.loading: 24 | return const LoadingWidget(); 25 | case RequestState.error: 26 | return CustomErrorWidget( 27 | errorMessage: state.flashcardErrorMessage, 28 | ); 29 | case RequestState.success: 30 | return state.flashcards.isEmpty 31 | ? NoFlashcardsToReview( 32 | collection: collection, 33 | ) 34 | : PageView.builder( 35 | itemCount: state.flashcards.length, 36 | itemBuilder: (context, index) => InkWell( 37 | onTap: () => Navigator.of(context) 38 | .pushNamed(Routes.reviewResult, arguments: ( 39 | state.flashcards[index], 40 | collection 41 | )), 42 | child: Center( 43 | child: Text( 44 | state.flashcards[index].question, 45 | textAlign: TextAlign.center, 46 | style: Theme.of(context) 47 | .textTheme 48 | .displayMedium, 49 | ), 50 | ), 51 | )); 52 | } 53 | }, 54 | )), 55 | ); 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /lib/src/features/cards/presentation/screens/review_result_screen.dart: -------------------------------------------------------------------------------- 1 | import 'package:auto_size_text/auto_size_text.dart'; 2 | import 'package:quizwiz/src/core/core.dart'; 3 | import 'package:quizwiz/src/features/cards/controller/controller.dart'; 4 | import 'package:quizwiz/src/features/cards/data/data.dart'; 5 | import 'package:quizwiz/src/features/cards/presentation/presentation.dart'; 6 | 7 | class ReviewResultScreen extends StatelessWidget { 8 | final (Flashcard card, FlashcardCollection collection) cardAndCollection; 9 | const ReviewResultScreen({super.key, required this.cardAndCollection}); 10 | 11 | @override 12 | Widget build(BuildContext context) { 13 | return Scaffold( 14 | appBar: AppBar(), 15 | body: LayoutBuilder(builder: (context, size) { 16 | return Column( 17 | mainAxisAlignment: MainAxisAlignment.center, 18 | children: [ 19 | const Expanded(child: SizedBox()), 20 | Padding( 21 | padding: const EdgeInsets.symmetric(horizontal: 8), 22 | child: ConstrainedBox( 23 | constraints: BoxConstraints(maxHeight: size.maxHeight / 4), 24 | child: AutoSizeText( 25 | cardAndCollection.$1.question, 26 | textAlign: TextAlign.center, 27 | style: Theme.of(context).textTheme.displaySmall, 28 | ), 29 | ), 30 | ), 31 | const SizedBox( 32 | height: 20, 33 | ), 34 | Padding( 35 | padding: const EdgeInsets.symmetric(horizontal: 8), 36 | child: ConstrainedBox( 37 | constraints: BoxConstraints(maxHeight: size.maxHeight / 3.5), 38 | child: AutoSizeText( 39 | cardAndCollection.$1.answer, 40 | textAlign: TextAlign.center, 41 | style: Theme.of(context).textTheme.headlineMedium, 42 | ), 43 | ), 44 | ), 45 | const Expanded(child: SizedBox()), 46 | Row( 47 | children: [ 48 | ReviewBar( 49 | color: Colors.red, 50 | onTap: () => _addReview(context, ReviewResult.again), 51 | text: "Again"), 52 | ReviewBar( 53 | color: Colors.redAccent, 54 | onTap: () => _addReview(context, ReviewResult.hard), 55 | text: "Hard"), 56 | ReviewBar( 57 | color: Colors.green, 58 | onTap: () => _addReview(context, ReviewResult.good), 59 | text: "Good"), 60 | ReviewBar( 61 | color: Colors.blue, 62 | onTap: () => _addReview(context, ReviewResult.easy), 63 | text: "Easy"), 64 | ], 65 | ), 66 | const SizedBox( 67 | height: 25, 68 | ) 69 | ], 70 | ); 71 | }), 72 | ); 73 | } 74 | 75 | void _addReview(BuildContext buildContext, ReviewResult result) { 76 | buildContext.read() 77 | ..add(UpdateDueTimeEvent( 78 | card: cardAndCollection.$1, 79 | collectionUuid: cardAndCollection.$2.uuid, 80 | reviewResult: result)) 81 | ..add(GetDueReviewsEvent(collection: cardAndCollection.$2)); 82 | 83 | Navigator.of(buildContext).pushReplacementNamed(Routes.practiceCards, 84 | arguments: cardAndCollection.$2); 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /lib/src/features/cards/presentation/screens/writing_quiz_screen.dart: -------------------------------------------------------------------------------- 1 | import 'package:quizwiz/src/core/core.dart'; 2 | import 'package:quizwiz/src/features/cards/data/data.dart'; 3 | import 'package:quizwiz/src/features/cards/presentation/presentation.dart'; 4 | 5 | class WritingQuizScreen extends StatefulWidget { 6 | final List flashcards; 7 | const WritingQuizScreen({super.key, required this.flashcards}); 8 | 9 | @override 10 | State createState() => _WritingQuizScreenState(); 11 | } 12 | 13 | class _WritingQuizScreenState extends State { 14 | late final PageController _pageController; 15 | late final TextEditingController _textController; 16 | bool _showAnswer = false; 17 | @override 18 | void initState() { 19 | super.initState(); 20 | _pageController = PageController(); 21 | _textController = TextEditingController(); 22 | } 23 | 24 | @override 25 | void dispose() { 26 | _pageController.dispose(); 27 | _textController.dispose(); 28 | super.dispose(); 29 | } 30 | 31 | @override 32 | Widget build(BuildContext context) { 33 | return Scaffold( 34 | appBar: AppBar( 35 | title: const Text(AppStrings.writingQuiz), 36 | ), 37 | body: LayoutBuilder(builder: (context, size) { 38 | return PageView.builder( 39 | physics: const NeverScrollableScrollPhysics(), 40 | controller: _pageController, 41 | itemCount: widget.flashcards.length, 42 | itemBuilder: (context, index) { 43 | String question = widget.flashcards[index].question; 44 | String answer = widget.flashcards[index].answer; 45 | return SingleChildScrollView( 46 | child: IntrinsicHeight( 47 | child: Column( 48 | children: [ 49 | const SizedBox( 50 | height: 20, 51 | ), 52 | DisplayQuestionWidget(question: question, size: size), 53 | const SizedBox( 54 | height: 30, 55 | ), 56 | _showAnswer 57 | ? DisplayAnswerWidget( 58 | answer: answer, 59 | size: size, 60 | textbackgroundColor: _changeAnswerColor( 61 | answer, _textController.text)) 62 | : SizedBox( 63 | height: size.maxHeight * 0.2, 64 | ), 65 | const SizedBox( 66 | height: 50, 67 | ), 68 | AnswerFormField( 69 | controller: _textController, 70 | enabled: !_showAnswer, 71 | onSubmit: (value) => _showAnswer = true), 72 | SizedBox( 73 | height: size.maxHeight * 0.2, 74 | ), 75 | _showAnswer 76 | ? Padding( 77 | padding: 78 | const EdgeInsets.symmetric(horizontal: 10), 79 | child: ElevatedButton.icon( 80 | onPressed: () => _onPressed(index), 81 | icon: const Icon(Icons.arrow_forward), 82 | label: const Text("Next")), 83 | ) 84 | : const SizedBox(), 85 | const SizedBox( 86 | height: 20, 87 | ) 88 | ], 89 | ), 90 | ), 91 | ); 92 | }); 93 | }), 94 | ); 95 | } 96 | 97 | void _onPressed(int index) { 98 | ///Navigate to home screen if the user reached the las question, 99 | /// otherwise go to the next question 100 | if (_pageController.page == widget.flashcards.length - 1) { 101 | Navigator.of(context).pushReplacementNamed('/'); 102 | } else { 103 | _textController.clear(); 104 | _showAnswer = false; 105 | _pageController.nextPage( 106 | duration: const Duration(milliseconds: 200), curve: Curves.easeIn); 107 | } 108 | } 109 | 110 | Color _changeAnswerColor(String answer, String userInput) { 111 | if (answer.trim().toLowerCase() == userInput.trim().toLowerCase()) { 112 | return Colors.green; 113 | } else { 114 | return Colors.red; 115 | } 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /lib/src/features/cards/presentation/widgets/create_flashcards_widgets/forms.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class CustomForms extends StatelessWidget { 4 | final TextEditingController questionController; 5 | final TextEditingController answerController; 6 | final GlobalKey formKey; 7 | const CustomForms( 8 | {super.key, 9 | required this.questionController, 10 | required this.answerController, 11 | required this.formKey}); 12 | 13 | @override 14 | Widget build(BuildContext context) { 15 | return Form( 16 | key: formKey, 17 | child: Column( 18 | children: [ 19 | TextFormField( 20 | key: const Key("question"), 21 | controller: questionController, 22 | maxLines: 2, 23 | minLines: 1, 24 | validator: (value) => 25 | value!.isEmpty ? "Invalid Input: Empty Field" : null, 26 | decoration: const InputDecoration( 27 | label: Text("Term (question)"), 28 | ), 29 | ), 30 | const SizedBox( 31 | height: 30, 32 | ), 33 | TextFormField( 34 | key: const Key("answer"), 35 | maxLines: 4, 36 | minLines: 1, 37 | controller: answerController, 38 | validator: (value) => 39 | value!.isEmpty ? "Invalid Input: Empty Field" : null, 40 | decoration: const InputDecoration( 41 | label: Text("Definition (answer)"), 42 | ), 43 | ), 44 | ], 45 | ), 46 | ); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /lib/src/features/cards/presentation/widgets/flashcards_list_widgets/custom_focused_menu_holder.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:focused_menu/focused_menu.dart'; 3 | import 'package:focused_menu/modals.dart'; 4 | import 'package:quizwiz/src/core/core.dart'; 5 | import 'package:quizwiz/src/features/cards/controller/controller.dart'; 6 | import 'package:quizwiz/src/features/cards/data/data.dart'; 7 | 8 | class CustomFocusedMenuHolder extends StatelessWidget { 9 | final Widget child; 10 | final FlashcardCollection collection; 11 | final int index; 12 | const CustomFocusedMenuHolder({ 13 | Key? key, 14 | required this.collection, 15 | required this.index, 16 | required this.child, 17 | }) : super(key: key); 18 | 19 | @override 20 | Widget build(BuildContext context) { 21 | return FocusedMenuHolder( 22 | menuBoxDecoration: const BoxDecoration(color: Colors.black), 23 | onPressed: () {}, 24 | menuItems: [ 25 | FocusedMenuItem( 26 | backgroundColor: Theme.of(context).cardColor, 27 | title: const Text( 28 | AppStrings.delete, 29 | style: TextStyle(color: Colors.red), 30 | ), 31 | onPressed: () { 32 | context.read().add(RemoveFlashcardsEvent( 33 | collection: collection, 34 | flashcardUuid: collection.cards[index].uuid)); 35 | }), 36 | FocusedMenuItem( 37 | backgroundColor: Theme.of(context).cardColor, 38 | title: const Text(AppStrings.edit), 39 | onPressed: () { 40 | Navigator.of(context).pushReplacementNamed(Routes.editFlashcard, 41 | arguments: EditFlashcardParameters( 42 | question: collection.cards[index].question, 43 | answer: collection.cards[index].answer, 44 | collection: collection, 45 | flashcard: collection.cards[index])); 46 | }) 47 | ], 48 | child: child); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /lib/src/features/cards/presentation/widgets/flashcards_list_widgets/flashcard_widget.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:jiffy/jiffy.dart'; 3 | import 'package:quizwiz/src/features/cards/data/data.dart'; 4 | 5 | class FlashcardWidget extends StatelessWidget { 6 | final Flashcard card; 7 | const FlashcardWidget({super.key, required this.card}); 8 | 9 | @override 10 | Widget build(BuildContext context) { 11 | return Card( 12 | child: Padding( 13 | padding: const EdgeInsets.all(15.0), 14 | child: Column( 15 | crossAxisAlignment: CrossAxisAlignment.start, 16 | children: [ 17 | Text( 18 | card.question, 19 | style: Theme.of(context).textTheme.headlineSmall, 20 | ), 21 | Text(card.answer), 22 | Padding( 23 | padding: const EdgeInsets.only(top: 10), 24 | child: Text( 25 | 'review due: ${Jiffy.parseFromDateTime(DateTime.fromMillisecondsSinceEpoch(card.dueTime)).MEd}', 26 | style: Theme.of(context).textTheme.bodySmall, 27 | ), 28 | ) 29 | ], 30 | ), 31 | ), 32 | ); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /lib/src/features/cards/presentation/widgets/generated_flashcards/generated_flashcard_widget.dart: -------------------------------------------------------------------------------- 1 | // ignore_for_file: public_member_api_docs, sort_constructors_first 2 | import 'package:flutter/material.dart'; 3 | 4 | import 'package:quizwiz/src/core/core.dart'; 5 | import 'package:quizwiz/src/features/cards/controller/controller.dart'; 6 | import 'package:quizwiz/src/features/cards/data/data.dart'; 7 | 8 | class GeneratedFlashcardWidget extends StatelessWidget { 9 | final List flashcards; 10 | final String collectionUuid; 11 | const GeneratedFlashcardWidget( 12 | {super.key, required this.flashcards, required this.collectionUuid}); 13 | 14 | @override 15 | Widget build(BuildContext context) { 16 | return Scaffold( 17 | appBar: AppBar( 18 | title: const Text(AppStrings.generatedFlashcard), 19 | actions: [ 20 | TextButton( 21 | key: key, 22 | onPressed: () { 23 | Navigator.of(context).pushReplacementNamed( 24 | Routes.flashcardsList, 25 | arguments: collectionUuid); 26 | }, 27 | child: const Text(AppStrings.done)) 28 | ], 29 | ), 30 | floatingActionButton: FloatingActionButton.extended( 31 | onPressed: () { 32 | context.read().add(SaveAllGenerateFlashcardsEvent( 33 | flashcards: flashcards, collectionUuid: collectionUuid)); 34 | Navigator.of(context).pushReplacementNamed(Routes.flashcardsList, 35 | arguments: collectionUuid); 36 | }, 37 | icon: const Icon(Icons.add), 38 | label: const Text(AppStrings.addAll)), 39 | floatingActionButtonLocation: FloatingActionButtonLocation.centerFloat, 40 | body: ListView.builder( 41 | itemCount: flashcards.length, 42 | padding: const EdgeInsets.symmetric( 43 | horizontal: 20, 44 | ), 45 | itemBuilder: (context, index) => GeneratedFlashcardCardWidget( 46 | flashcards: flashcards, 47 | index: index, 48 | collectionUuid: collectionUuid)), 49 | ); 50 | } 51 | } 52 | 53 | class GeneratedFlashcardCardWidget extends StatelessWidget { 54 | final List flashcards; 55 | final int index; 56 | final String collectionUuid; 57 | const GeneratedFlashcardCardWidget({ 58 | Key? key, 59 | required this.flashcards, 60 | required this.index, 61 | required this.collectionUuid, 62 | }) : super(key: key); 63 | 64 | @override 65 | Widget build(BuildContext context) { 66 | return Card( 67 | margin: const EdgeInsets.all(10), 68 | child: Padding( 69 | padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 10), 70 | child: Column( 71 | crossAxisAlignment: CrossAxisAlignment.start, 72 | children: [ 73 | Row( 74 | mainAxisAlignment: MainAxisAlignment.spaceBetween, 75 | children: [ 76 | Expanded( 77 | child: Text(flashcards[index].question, 78 | style: Theme.of(context).textTheme.titleLarge), 79 | ), 80 | IconButton( 81 | key: Key("$index"), 82 | onPressed: () { 83 | context.read().add(AddFlashcardsEvent( 84 | collectionUuid: collectionUuid, 85 | question: flashcards[index].question, 86 | answer: flashcards[index].answer)); 87 | customSnackBar("Flashcard Was Added", context); 88 | }, 89 | icon: const Icon(Icons.add)) 90 | ], 91 | ), 92 | Text(flashcards[index].answer) 93 | ], 94 | ), 95 | ), 96 | ); 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /lib/src/features/cards/presentation/widgets/home_screen_widgets/collection_card_widget.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:quizwiz/src/core/core.dart'; 3 | import 'package:quizwiz/src/features/cards/controller/controller.dart'; 4 | import 'package:quizwiz/src/features/cards/data/models/flashcard_collection.dart'; 5 | 6 | class CollectionCardWidget extends StatelessWidget { 7 | final FlashcardCollection collection; 8 | const CollectionCardWidget({super.key, required this.collection}); 9 | 10 | @override 11 | Widget build(BuildContext context) { 12 | return Card( 13 | child: Container( 14 | //height: 150, 15 | margin: const EdgeInsets.only(left: 10, top: 14), 16 | child: Column( 17 | crossAxisAlignment: CrossAxisAlignment.start, 18 | children: [ 19 | Text( 20 | collection.name, 21 | style: Theme.of(context).textTheme.headlineLarge, 22 | maxLines: 2, 23 | overflow: TextOverflow.ellipsis, 24 | ), 25 | Text( 26 | collection.description, 27 | maxLines: 2, 28 | overflow: TextOverflow.ellipsis, 29 | ), 30 | Text("${collection.cards.length} cards"), 31 | const SizedBox( 32 | height: 15, 33 | ), 34 | Padding( 35 | padding: const EdgeInsets.all(8.0), 36 | child: Row( 37 | mainAxisAlignment: MainAxisAlignment.spaceAround, 38 | children: [ 39 | OutlinedButton( 40 | onPressed: () { 41 | context 42 | .read() 43 | .add(GetDueReviewsEvent(collection: collection)); 44 | Navigator.of(context).pushReplacementNamed( 45 | Routes.practiceCards, 46 | arguments: collection); 47 | }, 48 | child: const Text(AppStrings.review)), 49 | FilledButton( 50 | onPressed: () => Navigator.of(context).pushNamed( 51 | Routes.createFlashcards, 52 | arguments: collection.uuid), 53 | child: const Text(AppStrings.addCard)) 54 | ], 55 | ), 56 | ) 57 | ], 58 | ), 59 | ), 60 | ); 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /lib/src/features/cards/presentation/widgets/home_screen_widgets/collections_list_screen.dart: -------------------------------------------------------------------------------- 1 | // ignore_for_file: public_member_api_docs, sort_constructors_first 2 | import 'package:focused_menu/modals.dart'; 3 | import 'package:focused_menu/focused_menu.dart'; 4 | import 'package:quizwiz/src/core/core.dart'; 5 | import 'package:quizwiz/src/features/cards/controller/controller.dart'; 6 | import 'package:quizwiz/src/features/cards/data/data.dart'; 7 | import 'package:quizwiz/src/features/cards/presentation/presentation.dart'; 8 | import 'package:quizwiz/src/features/cards/presentation/widgets/home_screen_widgets/combine_collection_dialog.dart'; 9 | import 'package:quizwiz/src/features/cards/presentation/widgets/home_screen_widgets/edit_collection_dialog.dart'; 10 | 11 | class CollectionsListScreen extends StatelessWidget { 12 | final List collections; 13 | const CollectionsListScreen({super.key, required this.collections}); 14 | 15 | @override 16 | Widget build(BuildContext context) { 17 | return ListView.builder( 18 | padding: const EdgeInsets.symmetric(horizontal: 15, vertical: 20), 19 | itemCount: collections.length, 20 | itemBuilder: (listViewContext, index) => Padding( 21 | padding: const EdgeInsets.symmetric(vertical: 10 // 10, 22 | ), 23 | child: FocusedMenuHolder( 24 | onPressed: () { 25 | Navigator.of(context).pushNamed(Routes.flashcardsList, 26 | arguments: collections[index].uuid); 27 | }, 28 | menuItems: [ 29 | FocusedMenuItem( 30 | backgroundColor: Theme.of(context).cardColor, 31 | title: const Text( 32 | AppStrings.delete, 33 | style: TextStyle(color: Colors.red), 34 | ), 35 | onPressed: () { 36 | context.read().add( 37 | RemoveCollectionEvent(uuid: collections[index].uuid)); 38 | }), 39 | FocusedMenuItem( 40 | backgroundColor: Theme.of(context).cardColor, 41 | title: const Text( 42 | AppStrings.edit, 43 | ), 44 | onPressed: () => showDialog( 45 | context: context, 46 | builder: (context) => EditCollectionDialog( 47 | collection: collections[index]))), 48 | FocusedMenuItem( 49 | backgroundColor: Theme.of(context).cardColor, 50 | title: const Text( 51 | AppStrings.combineCollection, 52 | ), 53 | onPressed: () => showDialog( 54 | context: context, 55 | useRootNavigator: false, 56 | builder: (_) { 57 | return CombineCollectionDialog( 58 | //the selected collection 59 | mainCollection: collections[index], 60 | //the available collections to combine with 61 | availableCollection: collections 62 | .where((element) => element != collections[index]) 63 | .toList(), 64 | ); 65 | })), 66 | ], 67 | child: CollectionCardWidget( 68 | collection: collections[index], 69 | ), 70 | )), 71 | ); 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /lib/src/features/cards/presentation/widgets/home_screen_widgets/combine_collection_dialog.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:quizwiz/src/core/core.dart'; 3 | import 'package:quizwiz/src/features/cards/controller/controller.dart'; 4 | import 'package:quizwiz/src/features/cards/data/data.dart'; 5 | 6 | class CombineCollectionDialog extends StatelessWidget { 7 | final FlashcardCollection mainCollection; 8 | final List availableCollection; 9 | const CombineCollectionDialog( 10 | {super.key, 11 | required this.mainCollection, 12 | required this.availableCollection}); 13 | 14 | @override 15 | Widget build(BuildContext context) { 16 | return SimpleDialog( 17 | title: const Text(AppStrings.combineWith), 18 | contentPadding: const EdgeInsets.all(30), 19 | children: List.generate(availableCollection.length, (index) { 20 | return InkWell( 21 | onTap: () { 22 | context.read().add(CombineCollectionsEvent( 23 | mainCollection: mainCollection, 24 | secondaryCollection: availableCollection[index])); 25 | customSnackBar(AppStrings.combineCollectionMessage, context); 26 | Navigator.of(context).pop(); 27 | }, 28 | child: Row( 29 | children: [ 30 | const Icon(Icons.folder), 31 | const SizedBox( 32 | width: 10, 33 | ), 34 | Text(availableCollection[index].name) 35 | ], 36 | ), 37 | ); 38 | }), 39 | ); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /lib/src/features/cards/presentation/widgets/home_screen_widgets/create_collection_dialog.dart: -------------------------------------------------------------------------------- 1 | import 'package:quizwiz/src/core/core.dart'; 2 | import 'package:quizwiz/src/features/cards/controller/controller.dart'; 3 | import 'package:quizwiz/src/features/cards/presentation/presentation.dart'; 4 | 5 | class CreateCollectionDialog extends StatefulWidget { 6 | const CreateCollectionDialog({super.key}); 7 | 8 | @override 9 | State createState() => _CreateCollectionDialogState(); 10 | } 11 | 12 | class _CreateCollectionDialogState extends State { 13 | late final TextEditingController nameController; 14 | late final TextEditingController descriptionController; 15 | @override 16 | void initState() { 17 | super.initState(); 18 | nameController = TextEditingController(); 19 | descriptionController = TextEditingController(); 20 | } 21 | 22 | @override 23 | void dispose() { 24 | super.dispose(); 25 | nameController.dispose(); 26 | descriptionController.dispose(); 27 | } 28 | 29 | @override 30 | Widget build(BuildContext context) { 31 | return CreateOrEditCollectionDialog( 32 | nameController: nameController, 33 | descriptionController: descriptionController, 34 | button: TextButton( 35 | key: const Key(AppStrings.create), 36 | onPressed: () { 37 | context.read().add(CreateCollectionsEvent( 38 | //if the user didn't enter a collection name, assign 'untitled' 39 | //to the collection name 40 | name: nameController.text == '' 41 | ? AppStrings.defaultCollectionName 42 | : nameController.text, 43 | description: descriptionController.text)); 44 | Navigator.of(context).pop(); 45 | }, 46 | child: const Text(AppStrings.create)), 47 | ); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /lib/src/features/cards/presentation/widgets/home_screen_widgets/create_or_edit_collection_dialog.dart: -------------------------------------------------------------------------------- 1 | // ignore_for_file: public_member_api_docs, sort_constructors_first 2 | import 'package:flutter/material.dart'; 3 | 4 | import 'package:quizwiz/src/core/core.dart'; 5 | 6 | class CreateOrEditCollectionDialog extends StatelessWidget { 7 | /// A template for using [AlertDialog] to create or edit a collection 8 | final TextEditingController nameController; 9 | final TextEditingController descriptionController; 10 | final TextButton button; 11 | const CreateOrEditCollectionDialog({ 12 | Key? key, 13 | required this.nameController, 14 | required this.descriptionController, 15 | required this.button, 16 | }) : super(key: key); 17 | 18 | @override 19 | Widget build(BuildContext context) { 20 | return AlertDialog( 21 | contentPadding: const EdgeInsets.all(20), 22 | title: const Text(AppStrings.createCollection), 23 | actionsAlignment: MainAxisAlignment.spaceBetween, 24 | actions: [ 25 | TextButton( 26 | key: const Key(AppStrings.cancel), 27 | onPressed: () => Navigator.of(context).pop(), 28 | child: const Text( 29 | AppStrings.cancel, 30 | style: TextStyle(color: Colors.red), 31 | )), 32 | button 33 | ], 34 | content: SizedBox( 35 | height: 200, 36 | child: SingleChildScrollView( 37 | child: Column( 38 | children: [ 39 | TextFormField( 40 | key: const Key(AppStrings.nameTextFieldLabel), 41 | controller: nameController, 42 | maxLength: 30, 43 | decoration: const InputDecoration( 44 | label: Text(AppStrings.nameTextFieldLabel), 45 | ), 46 | ), 47 | const SizedBox( 48 | height: 20, 49 | ), 50 | TextFormField( 51 | key: const Key(AppStrings.descriptionTextFieldLabel), 52 | controller: descriptionController, 53 | maxLength: 70, 54 | decoration: const InputDecoration( 55 | label: Text(AppStrings.descriptionTextFieldLabel), 56 | ), 57 | ), 58 | ], 59 | ), 60 | ), 61 | ), 62 | ); 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /lib/src/features/cards/presentation/widgets/home_screen_widgets/edit_collection_dialog.dart: -------------------------------------------------------------------------------- 1 | import 'package:quizwiz/src/core/core.dart'; 2 | import 'package:quizwiz/src/features/cards/controller/controller.dart'; 3 | import 'package:quizwiz/src/features/cards/data/data.dart'; 4 | import 'package:quizwiz/src/features/cards/presentation/presentation.dart'; 5 | 6 | class EditCollectionDialog extends StatefulWidget { 7 | final FlashcardCollection collection; 8 | const EditCollectionDialog({super.key, required this.collection}); 9 | 10 | @override 11 | State createState() => _EditCollectionDialogState(); 12 | } 13 | 14 | class _EditCollectionDialogState extends State { 15 | late final TextEditingController nameController; 16 | late final TextEditingController descriptionController; 17 | @override 18 | void initState() { 19 | super.initState(); 20 | nameController = TextEditingController(text: widget.collection.name); 21 | descriptionController = 22 | TextEditingController(text: widget.collection.description); 23 | } 24 | 25 | @override 26 | void dispose() { 27 | super.dispose(); 28 | nameController.dispose(); 29 | descriptionController.dispose(); 30 | } 31 | 32 | @override 33 | Widget build(BuildContext context) { 34 | return CreateOrEditCollectionDialog( 35 | nameController: nameController, 36 | descriptionController: descriptionController, 37 | button: TextButton( 38 | onPressed: () { 39 | context.read().add(EditCollectionEvent( 40 | collection: widget.collection, 41 | name: nameController.text.isEmpty 42 | ? AppStrings.defaultCollectionName 43 | : nameController.text, 44 | description: descriptionController.text)); 45 | Navigator.of(context).pop(); 46 | }, 47 | child: const Text(AppStrings.create)), 48 | ); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /lib/src/features/cards/presentation/widgets/multiple_choice_quiz_widgets/multiple_choice_body.dart: -------------------------------------------------------------------------------- 1 | // ignore_for_file: public_member_api_docs, sort_constructors_first 2 | import 'package:quizwiz/src/features/cards/data/data.dart'; 3 | import 'package:quizwiz/src/features/cards/presentation/presentation.dart'; 4 | 5 | class MultipleChoiceQuizBody extends StatelessWidget { 6 | final List quiz; 7 | final PageController controller; 8 | final bool showCorrectAnswer; 9 | final Function onTap; 10 | const MultipleChoiceQuizBody( 11 | {Key? key, 12 | required this.quiz, 13 | required this.controller, 14 | required this.showCorrectAnswer, 15 | required this.onTap}) 16 | : super(key: key); 17 | 18 | @override 19 | Widget build(BuildContext context) { 20 | return PageView.builder( 21 | controller: controller, 22 | physics: const NeverScrollableScrollPhysics(), 23 | itemCount: quiz.length, 24 | itemBuilder: (context, index) => LayoutBuilder( 25 | builder: (context, constraints) => ListView( 26 | children: [ 27 | Container( 28 | height: constraints.maxHeight / 3, 29 | alignment: Alignment.center, 30 | child: Text( 31 | quiz[index].question, 32 | textAlign: TextAlign.center, 33 | style: Theme.of(context).textTheme.displaySmall, 34 | )), 35 | ...List.generate(quiz[index].options.length, (i) { 36 | final option = quiz[index].options[i]; 37 | return InkWell( 38 | onTap: () => onTap(index, quiz), 39 | child: Container( 40 | width: constraints.maxWidth, 41 | padding: 42 | const EdgeInsets.symmetric(vertical: 20, horizontal: 10), 43 | margin: const EdgeInsets.symmetric(vertical: 5), 44 | alignment: Alignment.center, 45 | color: option == quiz[index].rightAnswer && showCorrectAnswer 46 | ? Colors.green 47 | : Theme.of(context).cardColor, 48 | child: Text( 49 | option, 50 | textAlign: TextAlign.center, 51 | ), 52 | ), 53 | ); 54 | }) 55 | ], 56 | ), 57 | ), 58 | ); 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /lib/src/features/cards/presentation/widgets/practice_cards_widgets/choose_quiz_dialog.dart: -------------------------------------------------------------------------------- 1 | // ignore_for_file: public_member_api_docs, sort_constructors_first 2 | import 'package:flutter/material.dart'; 3 | import 'package:quizwiz/src/core/core.dart'; 4 | import 'package:quizwiz/src/features/cards/controller/controller.dart'; 5 | import 'package:quizwiz/src/features/cards/data/data.dart'; 6 | 7 | class ChooseQuizDialog extends StatelessWidget { 8 | final FlashcardCollection collection; 9 | const ChooseQuizDialog({ 10 | Key? key, 11 | required this.collection, 12 | }) : super(key: key); 13 | 14 | @override 15 | Widget build(BuildContext context) { 16 | return SimpleDialog( 17 | title: const Text(AppStrings.quizType), 18 | alignment: Alignment.center, 19 | contentPadding: const EdgeInsets.all(25), 20 | children: [ 21 | Column( 22 | children: [ 23 | CustomListTile( 24 | text: AppStrings.multipleChoice, 25 | icon: Icons.quiz, 26 | onPressed: () { 27 | if (collection.cards.length < 4) { 28 | customSnackBar("Add At Least 4 Flashcards", context); 29 | Navigator.of(context).pushReplacementNamed('/'); 30 | } else { 31 | context.read().add( 32 | GetMultipleQuizOptionsEvent(collection: collection)); 33 | Navigator.of(context).pushNamed( 34 | Routes.quiz, 35 | ); 36 | } 37 | }), 38 | CustomListTile( 39 | text: AppStrings.writingQuiz, 40 | icon: Icons.edit, 41 | onPressed: () { 42 | var shuffledCards = [...collection.cards]; 43 | shuffledCards.shuffle(); 44 | Navigator.of(context) 45 | .pushNamed(Routes.writingQuiz, arguments: shuffledCards); 46 | }), 47 | ], 48 | ) 49 | ], 50 | ); 51 | } 52 | } 53 | 54 | class CustomListTile extends StatelessWidget { 55 | final String text; 56 | final IconData icon; 57 | final Function() onPressed; 58 | const CustomListTile({ 59 | Key? key, 60 | required this.text, 61 | required this.icon, 62 | required this.onPressed, 63 | }) : super(key: key); 64 | 65 | @override 66 | Widget build(BuildContext context) { 67 | return InkWell( 68 | onTap: onPressed, 69 | child: ListTile( 70 | leading: Icon(icon), 71 | title: Text(text), 72 | ), 73 | ); 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /lib/src/features/cards/presentation/widgets/practice_cards_widgets/no_flashcards_to_review.dart: -------------------------------------------------------------------------------- 1 | // ignore_for_file: public_member_api_docs, sort_constructors_first 2 | import 'package:flutter/material.dart'; 3 | import 'package:lottie/lottie.dart'; 4 | import 'package:quizwiz/src/core/core.dart'; 5 | import 'package:quizwiz/src/features/cards/data/data.dart'; 6 | import 'package:quizwiz/src/features/cards/presentation/widgets/practice_cards_widgets/choose_quiz_dialog.dart'; 7 | 8 | class NoFlashcardsToReview extends StatelessWidget { 9 | final FlashcardCollection collection; 10 | const NoFlashcardsToReview({ 11 | Key? key, 12 | required this.collection, 13 | }) : super(key: key); 14 | 15 | @override 16 | Widget build(BuildContext context) { 17 | return WillPopScope( 18 | onWillPop: () async { 19 | Navigator.of(context).pushReplacementNamed('/'); 20 | return true; 21 | }, 22 | child: Column( 23 | mainAxisAlignment: MainAxisAlignment.center, 24 | children: [ 25 | const Expanded(child: SizedBox()), 26 | LottieBuilder.asset(AppStrings.completedTaskAsset), 27 | Text( 28 | AppStrings.noFlashcardsToReview, 29 | style: Theme.of(context).textTheme.displaySmall, 30 | textAlign: TextAlign.center, 31 | ), 32 | const Expanded(child: SizedBox()), 33 | Padding( 34 | padding: const EdgeInsets.symmetric(horizontal: 30), 35 | child: Row( 36 | children: [ 37 | OutlinedButton( 38 | onPressed: () { 39 | Navigator.of(context).pushReplacementNamed('/'); 40 | }, 41 | child: const Text(AppStrings.goBack)), 42 | const Expanded(child: SizedBox()), 43 | FilledButton.icon( 44 | onPressed: () => showDialog( 45 | context: context, 46 | builder: (context) => ChooseQuizDialog( 47 | collection: collection, 48 | )), 49 | icon: const Icon(Icons.quiz), 50 | label: const Text(AppStrings.practice)), 51 | ], 52 | ), 53 | ), 54 | const SizedBox( 55 | height: 50, 56 | ) 57 | ], 58 | ), 59 | ); 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /lib/src/features/cards/presentation/widgets/review_result_widgets/review_bar.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class ReviewBar extends StatelessWidget { 4 | final Color color; 5 | final String text; 6 | final Function() onTap; 7 | const ReviewBar( 8 | {super.key, 9 | required this.color, 10 | required this.onTap, 11 | required this.text}); 12 | 13 | @override 14 | Widget build(BuildContext context) { 15 | return Expanded( 16 | child: InkWell( 17 | onTap: onTap, 18 | child: Container( 19 | height: 50, 20 | color: color, 21 | alignment: Alignment.center, 22 | child: Text( 23 | text, 24 | style: const TextStyle(fontWeight: FontWeight.bold), 25 | ), 26 | ), 27 | ), 28 | ); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /lib/src/features/cards/presentation/widgets/writing_quiz_widgets/answer_form_field.dart: -------------------------------------------------------------------------------- 1 | // ignore_for_file: public_member_api_docs, sort_constructors_first 2 | import 'package:flutter/material.dart'; 3 | 4 | class AnswerFormField extends StatelessWidget { 5 | final TextEditingController controller; 6 | final bool enabled; 7 | final Function(String) onSubmit; 8 | const AnswerFormField({ 9 | Key? key, 10 | required this.controller, 11 | required this.enabled, 12 | required this.onSubmit, 13 | }) : super(key: key); 14 | 15 | @override 16 | Widget build(BuildContext context) { 17 | return Padding( 18 | padding: const EdgeInsets.symmetric(horizontal: 25), 19 | child: TextFormField( 20 | controller: controller, 21 | minLines: 1, 22 | maxLines: 3, 23 | enabled: enabled, 24 | textInputAction: TextInputAction.done, 25 | onFieldSubmitted: onSubmit, 26 | autofocus: true, 27 | decoration: const InputDecoration(labelText: "Definition / Answer"), 28 | ), 29 | ); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /lib/src/features/cards/presentation/widgets/writing_quiz_widgets/display_answer_widget.dart: -------------------------------------------------------------------------------- 1 | // ignore_for_file: public_member_api_docs, sort_constructors_first 2 | import 'package:auto_size_text/auto_size_text.dart'; 3 | import 'package:flutter/material.dart'; 4 | 5 | class DisplayAnswerWidget extends StatelessWidget { 6 | final String answer; 7 | final BoxConstraints size; 8 | final Color textbackgroundColor; 9 | const DisplayAnswerWidget({ 10 | Key? key, 11 | required this.answer, 12 | required this.size, 13 | required this.textbackgroundColor, 14 | }) : super(key: key); 15 | 16 | @override 17 | Widget build(BuildContext context) { 18 | return SizedBox( 19 | height: size.maxHeight * 0.25, 20 | child: AutoSizeText( 21 | answer, 22 | textAlign: TextAlign.center, 23 | style: Theme.of(context) 24 | .textTheme 25 | .displaySmall! 26 | .copyWith(backgroundColor: textbackgroundColor), 27 | ), 28 | ); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /lib/src/features/cards/presentation/widgets/writing_quiz_widgets/display_question_widget.dart: -------------------------------------------------------------------------------- 1 | // ignore_for_file: public_member_api_docs, sort_constructors_first 2 | import 'package:auto_size_text/auto_size_text.dart'; 3 | import 'package:flutter/material.dart'; 4 | 5 | class DisplayQuestionWidget extends StatelessWidget { 6 | final String question; 7 | final BoxConstraints size; 8 | const DisplayQuestionWidget({ 9 | Key? key, 10 | required this.question, 11 | required this.size, 12 | }) : super(key: key); 13 | 14 | @override 15 | Widget build(BuildContext context) { 16 | return SizedBox( 17 | height: size.maxHeight * 0.15, 18 | child: AutoSizeText( 19 | question, 20 | textAlign: TextAlign.center, 21 | style: Theme.of(context).textTheme.displaySmall, 22 | )); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: quizwiz 2 | description: QuizWiz is a flashcard app that utilizes the power of AI to make 3 | flashcards from documents. 4 | publish_to: 'none' # Remove this line if you wish to publish to pub.dev 5 | 6 | version: 1.0.0+1 7 | 8 | environment: 9 | sdk: '>=3.0.3 <4.0.0' 10 | 11 | dependencies: 12 | auto_size_text: ^3.0.0 13 | bloc_test: ^9.1.3 14 | connectivity_plus: ^4.0.1 15 | cupertino_icons: ^1.0.2 16 | dartz: ^0.10.1 17 | dio: ^5.2.1+1 18 | equatable: ^2.0.5 19 | file_picker: ^5.3.2 20 | flex_color_scheme: ^7.1.2 21 | flutter: 22 | sdk: flutter 23 | flutter_bloc: ^8.1.3 24 | flutter_dotenv: ^5.1.0 25 | flutter_staggered_grid_view: ^0.6.2 26 | focused_menu: ^1.0.5 27 | get_it: ^7.6.0 28 | isar: ^3.1.0+1 29 | isar_flutter_libs: ^3.1.0+1 30 | jiffy: ^6.2.1 31 | lottie: ^2.4.0 32 | mockito: ^5.4.2 33 | path_provider: ^2.0.15 34 | read_pdf_text: ^0.2.1 35 | uuid: ^3.0.7 36 | 37 | dev_dependencies: 38 | build_runner: ^2.4.5 39 | flutter_launcher_icons: ^0.13.1 40 | flutter_lints: ^2.0.0 41 | flutter_test: 42 | sdk: flutter 43 | isar_generator: ^3.1.0+1 44 | 45 | flutter_launcher_icons: 46 | android: true 47 | ios: true 48 | image_path: "assets/icon.jpeg" 49 | 50 | flutter: 51 | 52 | uses-material-design: true 53 | # To add assets to your application, add an assets section, like this: 54 | assets: 55 | - assets/ 56 | - .env 57 | # - images/a_dot_ham.jpeg 58 | # An image asset can refer to one or more resolution-specific "variants", see 59 | -------------------------------------------------------------------------------- /test/src/app_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:bloc_test/bloc_test.dart'; 2 | import 'package:flutter_test/flutter_test.dart'; 3 | import 'package:quizwiz/src/app.dart'; 4 | import 'package:quizwiz/src/core/core.dart'; 5 | import 'package:quizwiz/src/features/cards/controller/controller.dart'; 6 | 7 | class CardsBlocMock extends MockBloc 8 | implements CardsBloc {} 9 | 10 | void main() { 11 | late CardsBlocMock blocMock; 12 | setUp(() { 13 | ServiceLocator().init(); 14 | blocMock = CardsBlocMock(); 15 | sl.unregister(); 16 | sl.registerFactory(() => blocMock); 17 | }); 18 | tearDownAll(() { 19 | sl.reset(dispose: true); 20 | }); 21 | testWidgets("render QizWiz app, expect to find one widget", (tester) async { 22 | whenListen(blocMock, const Stream.empty(), 23 | initialState: const CardsState()); 24 | await tester.pumpWidget(QuizWizApp( 25 | cardsBloc: sl()..add(const GetCollectionsEvent()), 26 | themeCubit: sl())); 27 | expect(find.byType(QuizWizApp), findsOneWidget); 28 | }); 29 | } 30 | -------------------------------------------------------------------------------- /test/src/features/cards/presentation/screens/create_flashcards_screen_test.dart: -------------------------------------------------------------------------------- 1 | import '../../../../tests_imports.dart'; 2 | 3 | class CardsBlocMock extends MockBloc 4 | implements CardsBloc {} 5 | 6 | void main() { 7 | late Widget createFlashcardsScreen; 8 | late CardsBloc blocMock; 9 | setUp(() { 10 | blocMock = CardsBlocMock(); 11 | createFlashcardsScreen = BlocProvider.value( 12 | value: blocMock, 13 | child: MaterialApp( 14 | onGenerateRoute: ((settings) => RouteGenerator.generateRoute(settings)), 15 | home: const CreateFlashcardsScreen(collectionUuid: ""), 16 | ), 17 | ); 18 | }); 19 | tearDown(() => blocMock.close()); 20 | group("Test create_flashcards_screen - ", () { 21 | testWidgets("Render create_flashcards_screen", (tester) async { 22 | await tester.pumpWidget(createFlashcardsScreen); 23 | expect(find.byType(CustomForms), findsOneWidget); 24 | expect(find.byKey(const Key(AppStrings.generateWithAI)), findsOneWidget); 25 | expect(find.byType(FilledButton), findsNWidgets(2)); 26 | expect(find.byType(LayoutBuilder), findsOneWidget); 27 | expect(find.byType(ListView), findsWidgets); 28 | }); 29 | testWidgets( 30 | "When GenerateWithAI button is clicked, expect to navigate to open file picker ", 31 | (tester) async { 32 | await tester.pumpWidget(createFlashcardsScreen); 33 | final generateButton = find.byKey(const Key(AppStrings.generateWithAI)); 34 | await tester.tap(generateButton); 35 | await tester.pumpAndSettle(); 36 | // expect(find.byType(GenerateCardsScreen), findsOneWidget); 37 | }); 38 | 39 | testWidgets('''When entering texts into [TextFormField], 40 | expect non-null values in [TextEditingController] and true value from the validator''', 41 | (tester) async { 42 | await tester.pumpWidget(createFlashcardsScreen); 43 | var questionTextFormField = find.byKey(const Key("question")); 44 | var answerTextFormField = find.byKey(const Key("answer")); 45 | await tester.enterText(questionTextFormField, "Question"); 46 | await tester.enterText(answerTextFormField, "Answer"); 47 | var customFormsFinder = 48 | tester.widget(find.byType(CustomForms)); 49 | expect(customFormsFinder.questionController.text, "Question"); 50 | expect(customFormsFinder.answerController.text, "Answer"); 51 | expect(customFormsFinder.formKey.currentState?.validate(), true); 52 | }); 53 | 54 | testWidgets('''When leaving [TextFormField] empty, 55 | expect false from the validator''', (tester) async { 56 | await tester.pumpWidget(createFlashcardsScreen); 57 | var customFormsFinder = 58 | tester.widget(find.byType(CustomForms)); 59 | expect(customFormsFinder.formKey.currentState?.validate(), false); 60 | }); 61 | 62 | testWidgets( 63 | "When AddCard button is clicked and the validation is true, expect to navigate to [FlashcardListScreen] ", 64 | (tester) async { 65 | await tester.pumpWidget(createFlashcardsScreen); 66 | var questionTextFormField = find.byKey(const Key("question")); 67 | var answerTextFormField = find.byKey(const Key("answer")); 68 | await tester.enterText(questionTextFormField, "Question"); 69 | await tester.enterText(answerTextFormField, "Answer"); 70 | await tester.tap(find.byKey(const Key(AppStrings.addCard))); 71 | whenListen( 72 | blocMock, 73 | Stream.value(CardsState( 74 | collectionsRequestState: RequestState.success, 75 | collections: [FlashcardCollection(name: "", uuid: "")])), 76 | initialState: const CardsState()); 77 | await tester.pumpAndSettle(); 78 | expect(find.byType(FlashcardsListScreen), findsOneWidget); 79 | }); 80 | 81 | testWidgets( 82 | "When AddAnotherCard button is clicked and the validation is true, expect to push [CreateFlashcardScreen] ", 83 | (tester) async { 84 | await tester.pumpWidget(createFlashcardsScreen); 85 | var questionTextFormField = find.byKey(const Key("question")); 86 | var answerTextFormField = find.byKey(const Key("answer")); 87 | await tester.enterText(questionTextFormField, "Question"); 88 | await tester.enterText(answerTextFormField, "Answer"); 89 | await tester.tap(find.byKey(const Key(AppStrings.addAnotherCard))); 90 | whenListen( 91 | blocMock, 92 | Stream.value(CardsState( 93 | collectionsRequestState: RequestState.success, 94 | collections: [FlashcardCollection(name: "", uuid: "")])), 95 | initialState: const CardsState()); 96 | await tester.pumpAndSettle(); 97 | expect(find.byType(CreateFlashcardsScreen), findsOneWidget); 98 | }); 99 | }); 100 | } 101 | -------------------------------------------------------------------------------- /test/src/features/cards/presentation/screens/edit_flashcards_screen_test.dart: -------------------------------------------------------------------------------- 1 | import '../../../../tests_imports.dart'; 2 | 3 | class CardsBlocMock extends MockBloc 4 | implements CardsBloc {} 5 | 6 | void main() { 7 | late Widget createFlashcardsScreen; 8 | late CardsBloc blocMock; 9 | 10 | final initialFlashcard = 11 | Flashcard(question: "question", answer: "answer", uuid: "123"); 12 | final editedFlashcard = 13 | initialFlashcard.copyWith(question: "Edited", answer: "Edited"); 14 | final collection = FlashcardCollection( 15 | name: "name", uuid: "uuid", cards: [initialFlashcard]); 16 | setUp(() { 17 | blocMock = CardsBlocMock(); 18 | createFlashcardsScreen = BlocProvider.value( 19 | value: blocMock, 20 | child: MaterialApp( 21 | onGenerateRoute: ((settings) => RouteGenerator.generateRoute(settings)), 22 | home: EditFlashcardScreen( 23 | parameters: EditFlashcardParameters( 24 | question: "question", 25 | answer: "answer", 26 | collection: collection, 27 | flashcard: initialFlashcard)), 28 | ), 29 | ); 30 | }); 31 | tearDown(() => blocMock.close()); 32 | group("Test edit_flashcards_screen - ", () { 33 | testWidgets("Render edit_flashcards_screen", (tester) async { 34 | await tester.pumpWidget(createFlashcardsScreen); 35 | expect(find.byType(CustomForms), findsOneWidget); 36 | expect(find.byType(FilledButton), findsNWidgets(1)); 37 | expect(find.byType(LayoutBuilder), findsOneWidget); 38 | expect(find.byType(ListView), findsWidgets); 39 | }); 40 | 41 | testWidgets('''When entering texts to [TextFormField], 42 | expect non-null values in [TextEditingController] and true value from the validator''', 43 | (tester) async { 44 | await tester.pumpWidget(createFlashcardsScreen); 45 | var questionTextFormField = find.byKey(const Key("question")); 46 | var answerTextFormField = find.byKey(const Key("answer")); 47 | await tester.enterText(questionTextFormField, "Question"); 48 | await tester.enterText(answerTextFormField, "Answer"); 49 | var customFormsFinder = 50 | tester.widget(find.byType(CustomForms)); 51 | expect(customFormsFinder.questionController.text, "Question"); 52 | expect(customFormsFinder.answerController.text, "Answer"); 53 | expect(customFormsFinder.formKey.currentState?.validate(), true); 54 | }); 55 | 56 | testWidgets('''By Default, expect non-null values in [TextFormField]''', 57 | (tester) async { 58 | await tester.pumpWidget(createFlashcardsScreen); 59 | var customFormsFinder = 60 | tester.widget(find.byType(CustomForms)); 61 | expect(customFormsFinder.formKey.currentState?.validate(), true); 62 | }); 63 | 64 | testWidgets( 65 | "When Edit button is clicked and the validation is true, expect to navigate to [FlashcardListScreen] ", 66 | (tester) async { 67 | await tester.pumpWidget(createFlashcardsScreen); 68 | var questionTextFormField = find.byKey(const Key("question")); 69 | var answerTextFormField = find.byKey(const Key("answer")); 70 | await tester.enterText(questionTextFormField, "Edited"); 71 | await tester.enterText(answerTextFormField, "Edited"); 72 | whenListen( 73 | blocMock, 74 | Stream.value(CardsState( 75 | collectionsRequestState: RequestState.success, 76 | collections: [ 77 | collection.copyWith(cards: [editedFlashcard]) 78 | ])), 79 | initialState: const CardsState()); 80 | var customFormsFinder = 81 | tester.widget(find.byType(CustomForms)); 82 | expect(customFormsFinder.formKey.currentState?.validate(), true); 83 | await tester.tap(find.byKey(const Key(AppStrings.editFlashcard))); 84 | await tester.pumpAndSettle(); 85 | expect(find.byType(FlashcardsListScreen), findsOneWidget); 86 | expect(find.byType(FlashcardWidget), findsOneWidget); 87 | }); 88 | }); 89 | } 90 | -------------------------------------------------------------------------------- /test/src/features/cards/presentation/screens/flashcards_list_screen_test.dart: -------------------------------------------------------------------------------- 1 | import '../../../../tests_imports.dart'; 2 | import 'package:flutter_staggered_grid_view/flutter_staggered_grid_view.dart'; 3 | 4 | class CardsBlocMock extends MockBloc 5 | implements CardsBloc {} 6 | 7 | void main() { 8 | late CardsBloc mockBloc = CardsBlocMock(); 9 | late Widget flashcardWidget; 10 | setUp(() { 11 | mockBloc = CardsBlocMock(); 12 | flashcardWidget = BlocProvider.value( 13 | value: mockBloc, 14 | child: const MaterialApp( 15 | home: FlashcardsListScreen( 16 | collectionUuid: "", 17 | )), 18 | ); 19 | }); 20 | tearDown(() { 21 | mockBloc.close(); 22 | }); 23 | group("Test flash_cards_screen when it's loading, loaded and has error - ", 24 | () { 25 | testWidgets( 26 | "when [collectionsRequestState] is loading, expect [LoadingWidget]", 27 | (tester) async { 28 | whenListen(mockBloc, const Stream.empty(), 29 | initialState: const CardsState()); 30 | await tester.pumpWidget(flashcardWidget); 31 | expect(find.byType(LoadingWidget), findsOneWidget); 32 | }); 33 | testWidgets( 34 | "when [collectionsRequestState] is success (loaded), expect loading then a list of flashcards", 35 | (tester) async { 36 | whenListen( 37 | mockBloc, 38 | Stream.value(CardsState( 39 | collectionsRequestState: RequestState.success, 40 | collections: [ 41 | FlashcardCollection(name: "", uuid: "", cards: [ 42 | Flashcard(), 43 | Flashcard(), 44 | Flashcard(), 45 | ]) 46 | ])), 47 | initialState: const CardsState()); 48 | await tester.pumpWidget(flashcardWidget); 49 | expect(find.byType(LoadingWidget), findsOneWidget); 50 | await tester.pump(); 51 | expect(find.byType(MasonryGridView), findsOneWidget); 52 | expect(find.byType(CustomFocusedMenuHolder), findsNWidgets(3)); 53 | expect(find.byType(FlashcardWidget), findsNWidgets(3)); 54 | expect(find.byType(FloatingActionButton), findsOneWidget); 55 | }); 56 | 57 | testWidgets( 58 | "when [collectionsRequestState] is success (loaded), expect loading then [NoResultScreen] (an empty flashcards list)", 59 | (tester) async { 60 | whenListen( 61 | mockBloc, 62 | Stream.value(CardsState( 63 | collectionsRequestState: RequestState.success, 64 | collections: [FlashcardCollection(name: "", uuid: "")])), 65 | initialState: const CardsState()); 66 | await tester.pumpWidget(flashcardWidget); 67 | expect(find.byType(LoadingWidget), findsOneWidget); 68 | await tester.pump(); 69 | expect(find.byType(NoResultScreen), findsOneWidget); 70 | expect(find.byType(FloatingActionButton), findsOneWidget); 71 | }); 72 | testWidgets( 73 | "when [collectionsRequestState] has error, expect [CustomErrorWidget]", 74 | (tester) async { 75 | whenListen(mockBloc, const Stream.empty(), 76 | initialState: const CardsState( 77 | collectionsRequestState: RequestState.error, 78 | collectionsErrorMessage: "Some Error")); 79 | await tester.pumpWidget(flashcardWidget); 80 | expect(find.byType(CustomErrorWidget), findsOneWidget); 81 | }); 82 | }); 83 | } 84 | -------------------------------------------------------------------------------- /test/src/features/cards/presentation/screens/home_screen_test.dart: -------------------------------------------------------------------------------- 1 | import '../../../../tests_imports.dart'; 2 | 3 | class CardsBlocMock extends MockBloc 4 | implements CardsBloc {} 5 | 6 | void main() { 7 | late CardsBloc mockBloc = CardsBlocMock(); 8 | late Widget homeScreen; 9 | setUp(() { 10 | mockBloc = CardsBlocMock(); 11 | homeScreen = BlocProvider.value( 12 | value: mockBloc, 13 | child: const MaterialApp(home: HomeScreen()), 14 | ); 15 | }); 16 | group("Test home_screen page when it's loading, loaded and has an error - ", 17 | () { 18 | testWidgets( 19 | "when [collectionsRequestState] is loading, expect LoadingWidget", 20 | (tester) async { 21 | whenListen(mockBloc, const Stream.empty(), 22 | initialState: const CardsState()); 23 | await tester.pumpWidget(homeScreen); 24 | expect(find.byType(LoadingWidget), findsOneWidget); 25 | expect(find.byType(FloatingActionButton), findsOneWidget); 26 | }); 27 | testWidgets( 28 | "when [collectionsRequestState] is success (loaded), expect loading then a list of collections (One Collection)", 29 | (tester) async { 30 | whenListen( 31 | mockBloc, 32 | Stream.value(CardsState( 33 | collectionsRequestState: RequestState.success, 34 | collections: [FlashcardCollection(name: "name", uuid: "uuid")])), 35 | initialState: const CardsState()); 36 | await tester.pumpWidget(homeScreen); 37 | expect(find.byType(LoadingWidget), findsOneWidget); 38 | await tester.pump(); 39 | expect(find.byType(CollectionsListScreen), findsOneWidget); 40 | expect(find.byType(CollectionCardWidget), findsOneWidget); 41 | expect(find.byType(FloatingActionButton), findsOneWidget); 42 | }); 43 | 44 | testWidgets( 45 | "when [collectionsRequestState] is success (loaded), expect loading then [NoResultScreen] (an empty collection)", 46 | (tester) async { 47 | whenListen( 48 | mockBloc, 49 | Stream.value(const CardsState( 50 | collectionsRequestState: RequestState.success, collections: [])), 51 | initialState: const CardsState()); 52 | await tester.pumpWidget(homeScreen); 53 | expect(find.byType(LoadingWidget), findsOneWidget); 54 | await tester.pump(); 55 | expect(find.byType(NoResultScreen), findsOneWidget); 56 | expect(find.byType(FloatingActionButton), findsOneWidget); 57 | }); 58 | 59 | testWidgets( 60 | "when [collectionsRequestState] has error, expect [CustomErrorWidget]", 61 | (tester) async { 62 | whenListen( 63 | mockBloc, 64 | Stream.value(const CardsState( 65 | collectionsRequestState: RequestState.error, 66 | collectionsErrorMessage: "Some Error")), 67 | initialState: const CardsState()); 68 | await tester.pumpWidget(homeScreen); 69 | expect(find.byType(LoadingWidget), findsOneWidget); 70 | await tester.pump(); 71 | expect(find.byType(CustomErrorWidget), findsOneWidget); 72 | expect(find.byType(FloatingActionButton), findsOneWidget); 73 | }); 74 | 75 | testWidgets( 76 | "When tapping a [FloatingActionButton], expect [CreateCollectionDialogue] to appear", 77 | (tester) async { 78 | whenListen( 79 | mockBloc, 80 | Stream.value(const CardsState( 81 | collectionsRequestState: RequestState.success, collections: [])), 82 | initialState: const CardsState()); 83 | await tester.pumpWidget(homeScreen); 84 | await tester.tap(find.byType(FloatingActionButton)); 85 | await tester.pump(); 86 | expect(find.byType(CreateCollectionDialog), findsOneWidget); 87 | expect(find.byType(TextFormField), findsNWidgets(2)); 88 | expect(find.byType(TextButton), findsNWidgets(2)); 89 | }); 90 | 91 | testWidgets( 92 | "When tapping a [FloatingActionButton] and filling out [TextFormField] then tapping create, expect a new collection to appear", 93 | (tester) async { 94 | //first: render the page with [NoResultScreen] (No collections at all) 95 | whenListen( 96 | mockBloc, 97 | Stream.fromIterable([ 98 | CardsState( 99 | collectionsRequestState: RequestState.success, 100 | collections: [FlashcardCollection(name: "name", uuid: "uuid")]), 101 | ]), 102 | initialState: const CardsState( 103 | collectionsRequestState: RequestState.success, collections: [])); 104 | await tester.pumpWidget(homeScreen); 105 | expect(find.byType(NoResultScreen), findsOneWidget); 106 | //second: tap on the [FloatingActionButton], expect a dialog to appear 107 | await tester.tap(find.byType(FloatingActionButton)); 108 | await tester.pump(); 109 | expect(find.byType(CreateCollectionDialog), findsOneWidget); 110 | //third: fill out the [TextFormField], and then click create. expect 1) 111 | //the dialog to be dismissed and a new collection to appear. 112 | await tester.enterText( 113 | find.byKey(const Key(AppStrings.nameTextFieldLabel)), "name"); 114 | await tester.enterText( 115 | find.byKey(const Key(AppStrings.descriptionTextFieldLabel)), ""); 116 | await tester.tap(find.byKey(const Key(AppStrings.create))); 117 | await tester.pump(); 118 | expect(find.byType(CreateCollectionDialog), findsNothing); 119 | expect(find.byType(NoResultScreen), findsNothing); 120 | expect(find.byType(CollectionCardWidget), findsOneWidget); 121 | }); 122 | }); 123 | } 124 | -------------------------------------------------------------------------------- /test/src/features/cards/presentation/screens/practice_cards_screen_test.dart: -------------------------------------------------------------------------------- 1 | import '../../../../tests_imports.dart'; 2 | 3 | class CardsBlocMock extends MockBloc 4 | implements CardsBloc {} 5 | 6 | void main() { 7 | late Widget practiceCardsScreen; 8 | late CardsBloc mockBloc; 9 | setUp(() { 10 | mockBloc = CardsBlocMock(); 11 | practiceCardsScreen = BlocProvider.value( 12 | value: mockBloc, 13 | child: MaterialApp( 14 | onGenerateRoute: (settings) => RouteGenerator.generateRoute(settings), 15 | home: PracticeCardsScreen( 16 | collection: FlashcardCollection( 17 | name: "name", 18 | uuid: "", 19 | )), 20 | ), 21 | ); 22 | }); 23 | group( 24 | "Test practice_cards_screen when it's loading,loaded, and has an error -", 25 | () { 26 | testWidgets( 27 | "When [flashcardsRequestState] is loading, expect [LoadingWidget]", 28 | (tester) async { 29 | whenListen(mockBloc, const Stream.empty(), 30 | initialState: const CardsState()); 31 | await tester.pumpWidget(practiceCardsScreen); 32 | expect(find.byType(LoadingWidget), findsOneWidget); 33 | }); 34 | testWidgets( 35 | "When [flashcardsRequestState] has an error, expect [LoadingWidget] then [CustomErrorWidget]", 36 | (tester) async { 37 | whenListen( 38 | mockBloc, 39 | Stream.value(const CardsState( 40 | flashcardRequestState: RequestState.error, 41 | flashcardErrorMessage: "error message")), 42 | initialState: const CardsState()); 43 | await tester.pumpWidget(practiceCardsScreen); 44 | expect(find.byType(LoadingWidget), findsOneWidget); 45 | await tester.pump(); 46 | expect(find.byType(CustomErrorWidget), findsOneWidget); 47 | expect(find.byType(PageView), findsNothing); 48 | }); 49 | testWidgets( 50 | '''When [flashcardsRequestState] is success with empty flashcards list, 51 | expect [LoadingWidget] then [NoFlashcardsToReview] widget''', 52 | (tester) async { 53 | whenListen( 54 | mockBloc, 55 | Stream.value(const CardsState( 56 | flashcardRequestState: RequestState.success, flashcards: [])), 57 | initialState: const CardsState()); 58 | await tester.pumpWidget(practiceCardsScreen); 59 | expect(find.byType(LoadingWidget), findsOneWidget); 60 | await tester.pump(); 61 | expect(find.byType(NoFlashcardsToReview), findsOneWidget); 62 | }); 63 | testWidgets( 64 | '''When [flashcardsRequestState] is success with a flashcards list, 65 | expect [LoadingWidget] then [PageView] and [Text] widgets''', 66 | (tester) async { 67 | whenListen( 68 | mockBloc, 69 | Stream.value(CardsState( 70 | flashcardRequestState: RequestState.success, 71 | collectionsRequestState: RequestState.success, 72 | flashcards: [ 73 | Flashcard( 74 | question: "question", 75 | answer: "answer", 76 | ) 77 | ])), 78 | initialState: const CardsState()); 79 | await tester.pumpWidget(practiceCardsScreen); 80 | expect(find.byType(LoadingWidget), findsOneWidget); 81 | await tester.pump(); 82 | expect(find.byType(PageView), findsOneWidget); 83 | expect(find.text('question'), findsOneWidget); 84 | }); 85 | testWidgets( 86 | '''When tapping [Text], expect to navigate to [ReviewResultScree]''', 87 | (tester) async { 88 | whenListen( 89 | mockBloc, 90 | Stream.value(CardsState( 91 | flashcardRequestState: RequestState.success, 92 | collectionsRequestState: RequestState.success, 93 | flashcards: [ 94 | Flashcard( 95 | question: "question", 96 | answer: "answer", 97 | ) 98 | ])), 99 | initialState: const CardsState()); 100 | await tester.pumpWidget(practiceCardsScreen); 101 | await tester.pump(); 102 | await tester.tap(find.byType(InkWell)); 103 | await tester.pumpAndSettle(); 104 | expect(find.byType(ReviewResultScreen), findsOneWidget); 105 | }); 106 | }); 107 | } 108 | -------------------------------------------------------------------------------- /test/src/features/cards/presentation/screens/review_result_screen_test.dart: -------------------------------------------------------------------------------- 1 | import '../../../../tests_imports.dart'; 2 | 3 | class CardsBlocMock extends MockBloc 4 | implements CardsBloc {} 5 | 6 | void main() { 7 | late Widget reviewScreen; 8 | late CardsBloc mockBloc; 9 | final flashcard = 10 | Flashcard(question: "question", answer: "answer", uuid: '1'); 11 | final flashcard2 = 12 | Flashcard(question: "question 2", answer: "answer 2", uuid: '2'); 13 | final collection = FlashcardCollection( 14 | name: "Collection", uuid: "uuid", cards: [flashcard, flashcard2]); 15 | setUp(() { 16 | mockBloc = CardsBlocMock(); 17 | reviewScreen = BlocProvider.value( 18 | value: mockBloc, 19 | child: MaterialApp( 20 | onGenerateRoute: (settings) => RouteGenerator.generateRoute(settings), 21 | home: ReviewResultScreen(cardAndCollection: (flashcard, collection))), 22 | ); 23 | }); 24 | group("Test review_result_screen -", () { 25 | testWidgets( 26 | "Render review_result_screen, expect two text widgets and 4 review bar", 27 | (tester) async { 28 | await tester.pumpWidget(reviewScreen); 29 | expect(find.text('question'), findsOneWidget); 30 | expect(find.text("answer"), findsOneWidget); 31 | expect(find.byType(ReviewBar), findsNWidgets(4)); 32 | }); 33 | testWidgets( 34 | "when tapping Again option, expect to navigate to [PracticeFlashcardsScreen]", 35 | (tester) async { 36 | await tester.pumpWidget(reviewScreen); 37 | whenListen( 38 | mockBloc, 39 | Stream.value(CardsState( 40 | flashcardRequestState: RequestState.success, 41 | collectionsRequestState: RequestState.success, 42 | flashcards: [ 43 | Flashcard( 44 | question: "question", 45 | answer: "answer", 46 | ) 47 | ])), 48 | initialState: const CardsState()); 49 | await tester.tap(find.text('Again')); 50 | await tester.pumpAndSettle(); 51 | expect(find.byType(PracticeCardsScreen), findsOneWidget); 52 | }); 53 | 54 | testWidgets( 55 | "when tapping Hard option, expect to navigate to [PracticeFlashcardsScreen]", 56 | (tester) async { 57 | await tester.pumpWidget(reviewScreen); 58 | whenListen( 59 | mockBloc, 60 | Stream.value(CardsState( 61 | flashcardRequestState: RequestState.success, 62 | collectionsRequestState: RequestState.success, 63 | flashcards: [ 64 | Flashcard( 65 | question: "question", 66 | answer: "answer", 67 | ) 68 | ])), 69 | initialState: const CardsState()); 70 | await tester.tap(find.text('Hard')); 71 | await tester.pumpAndSettle(); 72 | expect(find.byType(PracticeCardsScreen), findsOneWidget); 73 | }); 74 | 75 | testWidgets( 76 | "when tapping Good option, expect to navigate to [PracticeFlashcardsScreen]", 77 | (tester) async { 78 | await tester.pumpWidget(reviewScreen); 79 | whenListen( 80 | mockBloc, 81 | Stream.value(CardsState( 82 | flashcardRequestState: RequestState.success, 83 | collectionsRequestState: RequestState.success, 84 | flashcards: [ 85 | Flashcard( 86 | question: "question", 87 | answer: "answer", 88 | ) 89 | ])), 90 | initialState: const CardsState()); 91 | await tester.tap(find.text('Good')); 92 | await tester.pumpAndSettle(); 93 | expect(find.byType(PracticeCardsScreen), findsOneWidget); 94 | }); 95 | 96 | testWidgets( 97 | "when tapping Easy option, expect to navigate to [PracticeFlashcardsScreen]", 98 | (tester) async { 99 | await tester.pumpWidget(reviewScreen); 100 | whenListen( 101 | mockBloc, 102 | Stream.value(CardsState( 103 | flashcardRequestState: RequestState.success, 104 | collectionsRequestState: RequestState.success, 105 | flashcards: [ 106 | Flashcard( 107 | question: "question", 108 | answer: "answer", 109 | ) 110 | ])), 111 | initialState: const CardsState()); 112 | await tester.tap(find.text('Easy')); 113 | await tester.pumpAndSettle(); 114 | expect(find.byType(PracticeCardsScreen), findsOneWidget); 115 | }); 116 | }); 117 | } 118 | -------------------------------------------------------------------------------- /test/src/features/cards/presentation/screens/writing_quiz_screen_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:quizwiz/src/features/cards/presentation/screens/writing_quiz_screen.dart'; 2 | import '../../../../tests_imports.dart'; 3 | 4 | void main() { 5 | late Widget quizScreen; 6 | final flashcards = [ 7 | Flashcard(question: "question 1", answer: "answer 1", uuid: "1"), 8 | Flashcard(question: "question 2", answer: "answer 2", uuid: "2"), 9 | ]; 10 | setUp(() { 11 | quizScreen = MaterialApp( 12 | home: WritingQuizScreen(flashcards: flashcards), 13 | ); 14 | }); 15 | group('Test writing_quiz_screen -', () { 16 | testWidgets( 17 | 'Render [WritingQuizScreen], expect [Text] with "question" and an empty [TextFormField]', 18 | (tester) async { 19 | await tester.pumpWidget(quizScreen); 20 | expect(find.text(flashcards[0].question), findsOneWidget); 21 | final textFormFieldFinder = find.byType(TextFormField); 22 | expect(textFormFieldFinder, findsOneWidget); 23 | expect(tester.widget(textFormFieldFinder).controller?.text, 24 | ""); 25 | expect(find.byType(ElevatedButton), findsNothing); 26 | expect(find.text(flashcards[0].answer), findsNothing); 27 | }); 28 | testWidgets('''When filling out a [TextFormField] and tapping done, 29 | expect two [Text] and an [ElevatedButton]''', (tester) async { 30 | await tester.pumpWidget(quizScreen); 31 | await tester.enterText(find.byType(TextFormField), "text"); 32 | await tester.testTextInput.receiveAction(TextInputAction.done); 33 | await tester.pumpAndSettle(); 34 | 35 | expect(find.text(flashcards[0].question), findsOneWidget); 36 | // expect(find.text(flashcards[0].answer), findsOneWidget); 37 | // expect(find.byType(ElevatedButton), findsOneWidget); 38 | }); 39 | testWidgets('''When tapping next, 40 | expect to navigate to a new screen''', (tester) async { 41 | await tester.pumpWidget(quizScreen); 42 | await tester.testTextInput.receiveAction(TextInputAction.done); 43 | await tester.pump(); 44 | // await tester.tap(find.byType(ElevatedButton)); 45 | await tester.pump(); 46 | // expect(find.text(flashcards[1].question), findsOneWidget); 47 | }); 48 | }); 49 | } 50 | -------------------------------------------------------------------------------- /test/src/features/cards/presentation/widgets/create_flashcards_widgets/forms_test.dart: -------------------------------------------------------------------------------- 1 | import '../../../../../tests_imports.dart'; 2 | 3 | void main() { 4 | late Widget customForms; 5 | setUp(() => customForms = MaterialApp( 6 | home: Scaffold( 7 | body: CustomForms( 8 | questionController: TextEditingController(), 9 | answerController: TextEditingController(), 10 | formKey: GlobalKey()), 11 | ), 12 | )); 13 | group("Test [CustomForms] widget -", () { 14 | testWidgets( 15 | "Render [CustomForms], expect [Form], [Column] and two [TextFormField]", 16 | (tester) async { 17 | await tester.pumpWidget(customForms); 18 | expect(find.byType(Column), findsOneWidget); 19 | expect(find.byType(Form), findsOneWidget); 20 | expect(find.byType(TextFormField), findsAtLeastNWidgets(2)); 21 | }); 22 | testWidgets( 23 | "when filling out [TextFormField], expect the same values in the controller", 24 | (tester) async { 25 | await tester.pumpWidget(customForms); 26 | await tester.enterText( 27 | find.byKey(const Key('question')), "question text"); 28 | await tester.enterText(find.byKey(const Key("answer")), "answer text"); 29 | final questionController = 30 | tester.widget(find.byKey(const Key("question"))); 31 | final answerController = 32 | tester.widget(find.byKey(const Key("answer"))); 33 | expect(questionController.controller?.text, "question text"); 34 | expect(answerController.controller?.text, "answer text"); 35 | }); 36 | }); 37 | } 38 | -------------------------------------------------------------------------------- /test/src/features/cards/presentation/widgets/flashcards_list_widgets/custom_focus_menu_holder_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:focused_menu/focused_menu.dart'; 2 | import '../../../../../tests_imports.dart'; 3 | 4 | void main() { 5 | late Widget customFocusMenu; 6 | setUp(() => customFocusMenu = MaterialApp( 7 | onGenerateRoute: (settings) => RouteGenerator.generateRoute(settings), 8 | home: Scaffold( 9 | body: CustomFocusedMenuHolder( 10 | collection: FlashcardCollection(name: "name", uuid: ""), 11 | index: 0, 12 | child: const Text("child")), 13 | ), 14 | )); 15 | group("Test [CustomFocusedMenuHolder] -", () { 16 | testWidgets( 17 | "Render [CustomFocusedMenuHolder], expect [FocusedMenuHolder], and two [FocusedMenu]", 18 | (tester) async { 19 | await tester.pumpWidget(customFocusMenu); 20 | final menuHolder = 21 | tester.widget(find.byType(FocusedMenuHolder)); 22 | expect(find.byType(FocusedMenuHolder), findsOneWidget); 23 | expect(menuHolder.menuItems.length, 2); 24 | }); 25 | testWidgets( 26 | "when tapping edit button, expect to navigate to [EditFlashcardsScreen]", 27 | (tester) async { 28 | await tester.pumpWidget(customFocusMenu); 29 | await tester.longPress(find.text("child")); 30 | await tester.pump(); 31 | await tester.tap(find.text(AppStrings.edit)); 32 | await tester.pumpAndSettle(); 33 | //expect(find.text(AppStrings.edit), findsOneWidget); 34 | }); 35 | }); 36 | } 37 | -------------------------------------------------------------------------------- /test/src/features/cards/presentation/widgets/flashcards_list_widgets/flashcard_widget_test.dart: -------------------------------------------------------------------------------- 1 | import '../../../../../tests_imports.dart'; 2 | 3 | void main() { 4 | late Widget flashcardWidget; 5 | setUp(() => flashcardWidget = MaterialApp( 6 | home: Scaffold( 7 | body: FlashcardWidget( 8 | card: Flashcard( 9 | question: "question", 10 | answer: "answer", 11 | dueTime: DateTime.now().millisecondsSinceEpoch)), 12 | ), 13 | )); 14 | group("Test [FlashcardWidget] -", () { 15 | testWidgets( 16 | "Render [FlashcardWidget], expect [Card], two [Padding], [Column], three [Text]", 17 | (tester) async { 18 | await tester.pumpWidget(flashcardWidget); 19 | expect(find.byType(Card), findsOneWidget); 20 | expect(find.byType(Padding), findsAtLeastNWidgets(2)); 21 | expect(find.byType(Column), findsOneWidget); 22 | expect(find.byType(Text), findsNWidgets(3)); 23 | }); 24 | }); 25 | } 26 | -------------------------------------------------------------------------------- /test/src/features/cards/presentation/widgets/generated_flashcard_widget/generated_fashcard_widget_test.dart: -------------------------------------------------------------------------------- 1 | import '../../../../../tests_imports.dart'; 2 | 3 | class CardsBlocMock extends MockBloc 4 | implements CardsBloc {} 5 | 6 | void main() { 7 | late Widget generatedFlashcard; 8 | late CardsBloc blocMock; 9 | final flashcards = [ 10 | Flashcard(question: "question", answer: "answer", uuid: "1"), 11 | Flashcard(question: "question 2", answer: "answer 2", uuid: "2"), 12 | Flashcard(question: "question 3", answer: "answer 3", uuid: "3"), 13 | ]; 14 | setUp(() { 15 | blocMock = CardsBlocMock(); 16 | generatedFlashcard = BlocProvider.value( 17 | value: blocMock, 18 | child: MaterialApp( 19 | onGenerateRoute: (settings) => RouteGenerator.generateRoute(settings), 20 | home: GeneratedFlashcardWidget( 21 | flashcards: flashcards, collectionUuid: ""), 22 | ), 23 | ); 24 | whenListen( 25 | blocMock, 26 | Stream.value(CardsState( 27 | collectionsRequestState: RequestState.success, 28 | collections: [ 29 | FlashcardCollection(name: "", uuid: "", cards: [ 30 | Flashcard(), 31 | Flashcard(), 32 | Flashcard(), 33 | ]) 34 | ])), 35 | initialState: const CardsState()); 36 | }); 37 | 38 | group("Test [GeneratedFlashcardWidget] -", () { 39 | testWidgets('''Render [GeneratedFlashcardWidget], 40 | expect [AppBar], [FloatingActionButton], [TextButton], [ListView], [GeneratedFlashcardCardWidget]''', 41 | (tester) async { 42 | await tester.pumpWidget(generatedFlashcard); 43 | expect(find.byType(AppBar), findsOneWidget); 44 | expect(find.byType(FloatingActionButton), findsOneWidget); 45 | expect(find.byType(TextButton), findsOneWidget); 46 | expect(find.byType(ListView), findsOneWidget); 47 | expect(find.byType(GeneratedFlashcardCardWidget), findsNWidgets(3)); 48 | }); 49 | testWidgets('''Render [GeneratedFlashcardCardWidget], expect three widgets, 50 | with three different questions and answers''', (tester) async { 51 | await tester.pumpWidget(generatedFlashcard); 52 | expect(find.byType(GeneratedFlashcardCardWidget), findsNWidgets(3)); 53 | expect(find.text(flashcards[0].question), findsOneWidget); 54 | expect(find.text(flashcards[1].question), findsOneWidget); 55 | expect(find.text(flashcards[2].question), findsOneWidget); 56 | expect(find.text(flashcards[0].answer), findsOneWidget); 57 | expect(find.text(flashcards[1].answer), findsOneWidget); 58 | expect(find.text(flashcards[2].answer), findsOneWidget); 59 | }); 60 | testWidgets( 61 | '''When tapping done button, expect to navigate to [FlashcardListScree]''', 62 | (tester) async { 63 | await tester.pumpWidget(generatedFlashcard); 64 | await tester.tap(find.text(AppStrings.done)); 65 | await tester.pump(); 66 | await tester.pumpAndSettle(); 67 | expect(find.byType(FlashcardsListScreen), findsOneWidget); 68 | expect(find.byType(FlashcardWidget), findsNWidgets(3)); 69 | }); 70 | testWidgets( 71 | '''When tapping [FloatingActionButton], expect to navigate to [FlashcardListScree]''', 72 | (tester) async { 73 | await tester.pumpWidget(generatedFlashcard); 74 | await tester.tap(find.byType(FloatingActionButton)); 75 | await tester.pump(); 76 | await tester.pumpAndSettle(); 77 | expect(find.byType(FlashcardsListScreen), findsOneWidget); 78 | expect(find.byType(FlashcardWidget), findsNWidgets(3)); 79 | }); 80 | testWidgets( 81 | '''When tapping an add button for [GeneratedFlashcardCardWidget], 82 | expect a [Snackbar] with a text''', (tester) async { 83 | await tester.pumpWidget(generatedFlashcard); 84 | await tester.tap(find.byKey(const Key('0'))); 85 | await tester.pump(); 86 | expect( 87 | find.widgetWithText(SnackBar, "Flashcard Was Added"), findsOneWidget); 88 | }); 89 | }); 90 | } 91 | -------------------------------------------------------------------------------- /test/src/features/cards/presentation/widgets/home_screen_widgets/collection_card_widget_test.dart: -------------------------------------------------------------------------------- 1 | import '../../../../../tests_imports.dart'; 2 | 3 | class CardsBlocMock extends MockBloc 4 | implements CardsBloc {} 5 | 6 | void main() { 7 | late Widget collectionCardWidget; 8 | late CardsBloc mockBloc; 9 | 10 | setUp(() { 11 | mockBloc = CardsBlocMock(); 12 | return collectionCardWidget = BlocProvider.value( 13 | value: mockBloc, 14 | child: MaterialApp( 15 | onGenerateRoute: (settings) => RouteGenerator.generateRoute(settings), 16 | home: CollectionCardWidget( 17 | collection: FlashcardCollection(name: "name", uuid: "uuid")), 18 | ), 19 | ); 20 | }); 21 | group('Test [CollectionCardWidget] -', () { 22 | testWidgets('''Render [CollectionCardWidget], expect at least three [Text], 23 | one [OutlinedButton], one [FilledButton, one [Column]]''', 24 | (tester) async { 25 | await tester.pumpWidget(collectionCardWidget); 26 | expect(find.byType(Column), findsOneWidget); 27 | expect(find.byType(Text), findsAtLeastNWidgets(3)); 28 | expect(find.byType(FilledButton), findsOneWidget); 29 | expect(find.byType(OutlinedButton), findsOneWidget); 30 | }); 31 | testWidgets( 32 | '''When tapping [OutlinedButton], expect to navigate to [PracticeCardsScreen]''', 33 | (tester) async { 34 | await tester.pumpWidget(collectionCardWidget); 35 | whenListen( 36 | mockBloc, 37 | Stream.value(CardsState( 38 | flashcardRequestState: RequestState.success, 39 | collectionsRequestState: RequestState.success, 40 | flashcards: [ 41 | Flashcard( 42 | question: "question", 43 | answer: "answer", 44 | ) 45 | ])), 46 | initialState: const CardsState()); 47 | await tester.tap(find.byType(OutlinedButton)); 48 | await tester.pump(); 49 | await tester.pumpAndSettle(); 50 | expect(find.byType(PracticeCardsScreen), findsOneWidget); 51 | }); 52 | testWidgets( 53 | '''When tapping [FilledButton], expect to navigate to [CreateFlashcardsScreen]''', 54 | (tester) async { 55 | await tester.pumpWidget(collectionCardWidget); 56 | whenListen( 57 | mockBloc, 58 | Stream.value(CardsState( 59 | flashcardRequestState: RequestState.success, 60 | collectionsRequestState: RequestState.success, 61 | flashcards: [ 62 | Flashcard( 63 | question: "question", 64 | answer: "answer", 65 | ) 66 | ])), 67 | initialState: const CardsState()); 68 | await tester.tap(find.byType(FilledButton)); 69 | await tester.pump(); 70 | await tester.pumpAndSettle(); 71 | expect(find.byType(CreateFlashcardsScreen), findsOneWidget); 72 | }); 73 | }); 74 | } 75 | -------------------------------------------------------------------------------- /test/src/features/cards/presentation/widgets/home_screen_widgets/collection_list_screen_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:focused_menu/focused_menu.dart'; 2 | import '../../../../../tests_imports.dart'; 3 | 4 | void main() { 5 | late Widget collectionListScreen; 6 | setUp(() { 7 | collectionListScreen = MaterialApp( 8 | onGenerateRoute: (settings) => RouteGenerator.generateRoute(settings), 9 | home: CollectionsListScreen( 10 | collections: [FlashcardCollection(name: "name", uuid: "uuid")]), 11 | ); 12 | }); 13 | 14 | group('Test [CollectionListScreen] -', () { 15 | testWidgets('''Render [CollectionListScreen], expect [ListView], 16 | [FocusedMenuHolder], and one [CollectionCardWidget] ''', (tester) async { 17 | await tester.pumpWidget(collectionListScreen); 18 | expect(find.byType(ListView), findsOneWidget); 19 | expect(find.byType(FocusedMenuHolder), findsOneWidget); 20 | expect(find.byType(CollectionCardWidget), findsOneWidget); 21 | }); 22 | }); 23 | } 24 | -------------------------------------------------------------------------------- /test/src/tests_imports.dart: -------------------------------------------------------------------------------- 1 | export 'package:bloc_test/bloc_test.dart'; 2 | export 'package:flutter_test/flutter_test.dart'; 3 | export 'package:quizwiz/src/core/core.dart'; 4 | export 'package:quizwiz/src/features/cards/controller/controller.dart'; 5 | export 'package:quizwiz/src/features/cards/data/data.dart'; 6 | export 'package:quizwiz/src/features/cards/presentation/presentation.dart'; 7 | --------------------------------------------------------------------------------